diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..4ff7f5b1 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,22 @@ +[run] +branch = True + +source = + hid_parser + hidapi + keysyms + logitech_receiver + solaar + +omit = + */tests/* + */setup.py + */__main__.py + +[report] +exclude_lines = + pragma: no cover + if __name__ == '__main__': + if typing.TYPE_CHECKING + +fail_under = 40 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 32385469..7853e9a1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,13 +8,23 @@ assignees: '' --- **Information** - - - + + + + + + - Solaar version (`solaar --version` or `git describe --tags` if cloned from this repository): - Distribution: -- Kernel version (ex. `uname -srmo`): `KERNEL VERSION HERE` +- Kernel version (ex. `uname -srmo`): - Output of `solaar show`: +
@@ -34,11 +44,11 @@ CONTENTS HERE - Errors or warrnings from Solaar: - +run Solaar as `solaar -ddd`, after killing any running Solaar processes to +have Solaar log debug, informational, warning, and error messages to stdout. --> **Describe the bug** diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 960ef7ab..6dfc1c93 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -7,10 +7,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v4.3.0 + - name: Set up Python + uses: actions/setup-python@v5 - name: Run pre-commit uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 00000000..f52e5d76 --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,52 @@ +name: Deploy to GitHub Pages +on: + push: + branches: + - master + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: 'pages' + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + pip install mkdocs mkdocs-rtd-dropdown mkdocs-mermaid2-plugin mkdocstrings[python] + + - name: Build and deploy + run: | + mkdocs build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: 'site' + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..0354cc68 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,90 @@ +name: tests + +on: [push, pull_request] + +jobs: + ubuntu-tests: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: [3.13] + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Ubuntu dependencies for python 3.8 + if: matrix.python-version == '3.8' + run: | + make install_apt + + - name: Install Ubuntu dependencies for python 3.13 + if: matrix.python-version == '3.13' + run: | + make install_apt_python3.13 + + - name: Install Python dependencies + run: | + make install_pip PIP_ARGS='.["test"]' + + - name: Run tests on Ubuntu + run: | + make test + + - name: Upload coverage to Codecov + if: github.ref == 'refs/heads/master' + uses: codecov/codecov-action@v4.5.0 + with: + directory: ./coverage/reports/ + env_vars: OS, PYTHON + files: ./coverage.xml + flags: unittests + name: codecov-umbrella + token: ${{ secrets.CODECOV_TOKEN }} + + macos-tests: + runs-on: macos-latest + + strategy: + matrix: + python-version: [3.13] + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Set up macOS dependencies + run: | + make install_brew + - name: Add Homebrew's library directory to dyld search path + run: | + echo "DYLD_FALLBACK_LIBRARY_PATH=$(brew --prefix)/lib:$DYLD_FALLBACK_LIBRARY_PATH" >> $GITHUB_ENV + - name: Install Python dependencies + run: | + make install_pip PIP_ARGS='.["test"]' + - name: Run tests on macOS + run: | + pytest --cov --cov-report=xml + - name: Upload coverage to Codecov + if: github.ref == 'refs/heads/master' + uses: codecov/codecov-action@v4.5.0 + with: + directory: ./coverage/reports/ + env_vars: OS, PYTHON + files: ./coverage.xml + flags: unittests + name: codecov-umbrella + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index b764c736..44a2dd8c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,19 @@ __pycache__/ /deb_dist/ /MANIFEST +.coverage +/htmlcov/ + /docs/captures/ /share/logitech_icons/ /share/locale/ /po/*.po~ + +/.idea/ + +.DS_Store +._* + +Pipfile +Pipfile.lock diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c195bc21..204fd1fe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,19 +8,13 @@ repos: - id: check-yaml - id: check-toml - id: debug-statements - - id: double-quote-string-fixer - id: end-of-file-fixer - id: trailing-whitespace -- repo: https://github.com/pre-commit/mirrors-yapf - rev: v0.32.0 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.2.2 hooks: - - id: yapf -- repo: https://github.com/pre-commit/mirrors-isort - rev: v5.10.1 - hooks: - - id: isort -- repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - additional_dependencies: ['flake8-bugbear'] + - id: ruff + name: ruff lint + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format + name: ruff format diff --git a/ChangeLog.md b/CHANGELOG.md similarity index 67% rename from ChangeLog.md rename to CHANGELOG.md index 0ff1651a..5427b22c 100644 --- a/ChangeLog.md +++ b/CHANGELOG.md @@ -1,3 +1,346 @@ +# 1.1.19 + +* New Georgian translation +* Remove test that doesn't work in older Pythons +* Update help messages for CLI commands +* Allow solaar config to change LED settings +* Add python3-devel to install-dnf in Makefile +* hidconsole can send an HID command non-interactively +* Add info about new lightspeed receiver +* Update Swedish and zh_TW translation +* Fix bug when showing details about direct-connected device +* Drop testing of Python before 3.13 +* Fix crash in solaar show when showing notification flags. (#3070) + +# 1.1.18 + +* Fix crash when showing notification flags + +# 1.1.17 + +* Add dark icons +* Permit onboard profiles version 5 +* Update Russian and Polish translations +* Add onboard profiles warning to sensitivity tooltip +* Better error messages for solaar profile +* Remove Solaar name for mice with WPID 4008 +* Prevent lock failure when showing debug messages +* Replace color picker (#3028) +* Add setting for HAPTIC feature +* Add setting to adjust force needed for force-sensing buttons +* Expand new settings type +* Add new settings type for structure-backed setting +* Use PATH instead of hardcoded absolute paths (#3014) +* Add scroll ratchet force setting +* Fix debug messages for MouseClick rule +* Improve debug message for rule evaluation +* App wrapper and launch agent scripts for MacOS +* Ignore hidden features +* Don't pop up window in response to ADC changes +* Update bug report template +* Fixed malformed doc file by adding closing tag +* Fix error in low-level request for device with no recevier +* Update documentation files + +# 1.1.16 + +* Add new flags for reprogrammable keys feature +* Correctly handle missing battery feature + +# 1.1.15 + +* Correctly re-raise permissions exception +* Add several new special keys and tasks +* Update several translations +* Center labels and remove buggy entry resizing logic +* Add shape keys from Key POP Icon +* Device and Action rule conditions match on codename and name +* Fix listing hidpp10 devices - bytes vs string concatenation (#2856) +* Add present flag, unset when internal error occurs, set when notification appears +* Pause setting up features when error occurs; use ADC message to signal connection and disconnection +* Fix listing of hidpp10 peripherals +* Complete DEVICE_FEATURES to DeviceFeature transition for hidpp10 devices +* Fix NOTIFICATION_FLAG to NotificationFlag transition leftovers +* Fix github workflow stopping all matrix jobs when one of them fails +* Fix ubuntu github CI +* Update index.md +* Python documentation appears to be broken so don't set it up +* Improve documentation on onboard profiles +* Use correct LOD values for extended adjustable dpi +* Better support RGB Effects - not readable +* Fix crash when asking for help about config +* Fix error when updating ChoiceControlBig box +* Add uninstallation docs +* Handle unknown power switch locations again +* Correctly handle selection of [empty] in rule editor +* Handle `HIDError` in `hidapi.hidapi_impl._match()` (#2804) +* Give ghost devices a path +* Guard against typeerror when setting the value of a control box +* Recover from errors in ping +* Replace spaces by underscores when looking up features +* Rewrote string concatenation/format with f strings +* Fix logo not showing in about dialog box +* Make typing-extensions dependency mandatory +* Properly ignore unsupported locale +* hidapi: skip unsupported devices and handle exception on open +* Ignore macOS junk files and pipenv config +* Fix ui desktop notifications test +* hidpp20: Remove dependency to NamedInts +* Estimate accurate battery level for some rechargable devices (#2745) +* Upgrade desktop notifications tests to take notifications availability into account +* Update tests to run on Python 3.13 +* Remove outdated logger enabled checks +* Introduce GTK signal types +* Introduce error types +* Remove alias for SupportedFeature +* Refactor process_device_notification +* Refactor process_receiver_notification +* Refactor receiver event handling +* Introduce custom logger +* Refactor notifications +* Rename variable to full name notification +* Test notifications +* Test extraction of serial and max. devices +* Refactor extraction of serial and max. devices +* macOS: Fix int.from_bytes, int.to_bytes for show.py +* macOS: Remove udev rule warning +* macOS: Add support for Bluetooth devices +* Add back and forward mouseclick actions +* Speedup lookup of known receivers +* Refactor device filtering +* Reorder private functions and variable definitions +* Turn filter_products_of_interest into a public function +* Improve tests of known receivers +* Refactor: Remove NamedInts and move enums where used +* Add docstrings and type hints +* Enforce rules on RuleComponentUI subclasses +* Simplify settings UI class +* Remove diversion alias +* Refactor: Convert Kind to IntEnum +* Split up huge settings module +* Remove Python 2 specific path handling +* Delete logging temp file on exit +* Update Swedish translation + +# 1.1.14 + +* Handle fake feature enums in show +* Fix battery entries in config.yaml +* Add ratchet setting for smart shift enhanced devices +* Refactor Gesture into enum +* Replace ERROR NamedInts by IntEnum (#2645) +* Refactor hidpp20 to use enum +* Update Polish, Swedish, Norwegian Nynorsk (nn), and Norwegian Bokmål (nb) translations +* Use IntEnum for firmware and cidgroup constances +* Change pairing error values to intenums +* Fix initialization bug for PackedRangeControl +* Add tests for feature class, process_notification, and key_is_down +* Check all bits for extended report rate +* Add type hints +* Improve about dialog +* Reduce dependencies +* Refactor code +* Improve testing +* Allow unknown keys in Key rule conditions +* Improve documentation for cli actions +* Cycle sw_id to better guard against duplication of messages +* Handle error return on root feature +* Clean up documentation +* Improve github interactions +* Add information about Onboard Profiles overriding some settings +* Add wording to README.md that Solaar is not a device driver +* Clean up imports +* Handle unknown device kinds +* Fix broken links to Solaar logo +* Use mkdocs for public documentation +* Clean up setup.py +* Remove Dead links in the AppStream file +* Update about.py +* Remove check on driver +* Improve base module +* Remove unnecessary receiver info 'hid_driver' +* Convert HIDPPNotification to dataclass +* Be defensive when converting battery status to string +* Automatically detect packages in /lib +* Clean up locale code +* Improve rules documentation +* Refactor creation of devices +* Add headings to structure rules.md +* Unify imports in logitech package +* Don't ping device when getting name or codename +* Use dataclasses and enums where useful +* Introduce Device protocol and type hints +* Add typing_extensions dependency +* Move hidpp10 independent functions to module level +* Fix macOS compatibility and reenable CI tests +* Unify imports in hidapi package +* Move screenshots into dedicated folder and add high-level graph of components +* Update French and Chinese translations +* Drop support for end-of-life Python 3.7 + +# 1.1.13 + +* Update Polish and Russian translations. +* Fix bug in suspend and resume callback +* Add choices universe for backlight setting +* Add simplify diversion.py and add unit tests +* Get and use current host number for K375sFnSwap because of bug in firmware of MX Keys S +* Fix bug with logo in about window +* Don't ping device just to get logging information +* Optimize write for per-key lighting +* Add and initialize per-key lighting to a special no-change value +* Remove some Python 2 compatibility code +* Update French translation +* Refactor rule loading for testability + +# 1.1.12 + +* Check for existence of keys file before opening +* Perform translation for all translatable strings. +* Add included hid_parser to packages installed +* Improve label and description for LED zone settings +* Add message about Onboard Profiles to LED Zone settings +* Initialize device registers to empty list +* Use bluez dbus signals to disconnect and connect bluetooth devices +* Handle a different signal for onboard profiles directory in ROM +* Introduce small delay in getting pairing information to let receiver settle after pairing +* Improve testing for settings_templates, settings, hidpp20, and device and fix small bugs found +* Add extended adjustable DPI setting +* Improve and extend infrastructure for testing setting_templates +* Update Greek, Polish, Russian, and Traditional Chinese translations +* Implement and test per-key lighting +* Refactor and test pair_window in GUI +* Handle situation when read of a setting fails in GUI +* Permit continuing when a read during pushing fails +* Fix bug in LEDZoneSetting when effect is not implemented +* Add tests for LEDEffect structures in hidpp20 +* Handle BRIGHTNESS_CONTROL notifications +* Add settings for BRIGHTNESS_CONTROL and RGB_EFFECTS features +* Fix small bugs found from testing in settings +* Use f-strings for more exceptions and log message +* Tests for setting_templates +* Simple change in settings to improve testability +* Use feature_request from the device everywhere in hidpp20 +* Fix bug in backlight 2 durations +* Replace deprecated code constructs +* Set up test data and classes to help test HID++ interactions +* Use pytest to test code for logitech_receiver modules +* Align init methods for all receiver classes +* Start refactoring of code base +* Allow sub-second delays in Later +* Fix bug in setting configuration cookie due to bad documentation +* Use ruff for code styling and linting +* Upgrade string formating to f-string +* Document battery-icons=solaar option +* Tell devices to delay device sending first messages until configuration is done +* Optimize some functions in FeaturesArray +* Fix bug in creating features array +* Fix bug in building battery line in show +* Refactor diversion_rules +* Fix bug in determining tray icon +* Fix bug in getting friendly name +* Move status information to Device and Receiver objects +* Add tests for get_kind_from_index and base product information +* Update EX100 documentation +* Use object attributes instead of dictionary in status objects +* Create subclasses of receiver for different variants +* Add requirement for CONFIG_HIDRAW to documentation +* Add some low-level tests for some hidpp20 functions, profiles, and lighting and some hidpp10 tests +* Fix app name casing in UI +* Add missing receiver type for Lightspeed receivers +* Add new device types +* Refactor device and receiver instantiation +* Simplify naming of distribution files +* Clean up some logging code +* Remove duplicated code to read register +* Introduce Hidpp20 and Hidpp10 class +* Remove unnecessary calls of del +* Fix bug when reading BACKLIGHT setting from device +* Replace invalid hidpp10 and hidpp20 usages +* Use only timer thread to save config.yaml +* Improve README +* Copy newer version of hid_parser +* Reorder code in settings +* Update installation documentation +* Add missing license blocks +* Clean up listener and notifications code +* Add locks to prevent multiple persisters for device +* Clean up configuration, device, and receiver code +* Move battery constants common to HID++ 1.0 and 2.0 to common +* Move mapping of device kind into hidpp20 +* Move pairing information gathering to receiver +* update contributors +* Expand allowable profile numbers +* Clean up __init__ in logitech_receiver +* Modify pre-commit args to make ruff change files +* Fix bug in hidpp20 get host names +* Use ruff for formatting and linting +* Fix bug in rule Set action +* Add notify module to logitech_receiver +* Implement setting_changed callback and pass in to new devices and receivers +* Add callback to call when changing a setting +* Move exceptions, hidpp20 and hidpp10 constants into new modules +* Streamline status code +* Upgrade debugging in udev +* Fix deprecated GitHub actions +* Extend makefile and tests +* Improve features array +* Move ui_async to common.py +* Improve module imports +* Add tests of common module + +# 1.1.11 + +* Rename light icons and install them in correct place +* Setup macOS tests using GitHub action (#2284) +* Better checking for setting in record_setting +* Fix invalid func name set logo name +* Simplify installation of udev rules +* Add document on implementation +* Tidy up scrolling appearance in configuration panel +* Correctly handle profile button with no action +* Don't unlock setting when changed by external means +* Refactor code to record change to setting +* Add GitHub action for tests +* Introduce tests with pytest +* Simplify logger instantiation +* Update label and tooltip for divert-gkeys setting +* Don't lock setting when an error occurs +* Catch assertion errors when reading setting values from devices +* Support LED Zone control feature +* Dump and load device profiles +* Select among profiles. +* Support backlight levels and duration +* Use "Report Rate" instead of "Polling" for movement report rate +* Support extended report rate setting +* Add stable branch to release.sh (#2236) +* Fix changelog parsing in release.sh +* Update installation.md with new udev rules location +* Downgrade assertion on missing notification flag to warning +* Write empty file if there are no rules to save +* Be defensive in device error messages +* Add descriptions of M650, PRO X 2, G915, MX Anywhere 2S, G305, and MX Keys S +* Report hidraw node in debugging messages +* Add names for new Logitech features +* Update Spanish, French, and Polish translations +* Defend against lightspeed receivers that contact devices for basic information +* Remove incorrect feature for M325 mice +* Add K845 keyboard +* Add partial support for macOS and minimal support for Windows +* Correctly enumerate devices on receiver +* Add wording in documentation about Logitech reusing model numbers +* Better handling and installation of icons +* Catch errors when pinging to try to put device online +* Be more cautious when creating log messages to avoid exceptions +* Correctly handle NoSuchDevice exception when pinging device +* Fix test in rules for device equality +* Add installation instructions for pipx and add not about other GTK system packages +* Fix bug in NamedInt +* Add support for MK550 +* Install udev rule files to correct placces +* Expand expected ping responses +* Update codename when device status changes + # 1.1.10 * Add information about NixOS flake package diff --git a/COPYING b/LICENSE.txt similarity index 100% rename from COPYING rename to LICENSE.txt diff --git a/MANIFEST.in b/MANIFEST.in index ca3fa2bf..05fa9c35 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ -include COPYRIGHT COPYING README.md ChangeLog.md lib/solaar/version lib/solaar/commit +include COPYRIGHT LICENSE.txt README.md CHANGELOG.md lib/solaar/version lib/solaar/commit recursive-include rules.d * recursive-include share/locale * diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..6ec4ea17 --- /dev/null +++ b/Makefile @@ -0,0 +1,74 @@ +UDEV_RULE_FILE = 42-logitech-unify-permissions.rules +UDEV_RULES_SOURCE := rules.d/$(UDEV_RULE_FILE) +UDEV_RULES_SOURCE_UINPUT := rules.d-uinput/$(UDEV_RULE_FILE) +UDEV_RULES_DEST := /etc/udev/rules.d/ + +PIP_ARGS ?= . + +.PHONY: install_ubuntu install_macos +.PHONY: install_apt install_brew install_pip +.PHONY: install_udev install_udev_uinput reload_udev uninstall_udev +.PHONY: format lint test + +install_ubuntu: install_apt install_udev_uinput install_pip + +install_macos: install_brew install_pip + +install_apt: + @echo "Installing Solaar dependencies via apt" + sudo apt update + sudo apt install libdbus-1-dev libglib2.0-dev libgtk-3-dev libgirepository1.0-dev + +install_apt_python3.13: + @echo "Installing Solaar dependencies via apt" + sudo apt update + sudo apt install libdbus-1-dev libglib2.0-dev libgtk-3-dev libgirepository-2.0-dev gobject-introspection + +install_dnf: + @echo "Installing Solaar dependencies via dnf" + sudo dnf install gtk3 python3-devel python3-gobject python3-dbus python3-pyudev python3-psutil python3-xlib python3-yaml + +install_brew: + @echo "Installing Solaar dependencies via brew" + brew update + brew install hidapi gtk+3 pygobject3 gobject-introspection + +install_pip: + @echo "Installing Solaar via pip" + python -m pip install --upgrade pip + pip install $(PIP_ARGS) + +install_pipx: + @echo "Installing Solaar via pipx" + pipx install --system-site-packages $(PIP_ARGS) + +install_udev: + @echo "Copying Solaar udev rule to $(UDEV_RULES_DEST)" + sudo cp $(UDEV_RULES_SOURCE) $(UDEV_RULES_DEST) + make reload_udev + +install_udev_uinput: + @echo "Copying Solaar udev rule (uinput) to $(UDEV_RULES_DEST)" + sudo cp $(UDEV_RULES_SOURCE_UINPUT) $(UDEV_RULES_DEST) + make reload_udev + +reload_udev: + @echo "Reloading udev rules" + sudo udevadm control --reload-rules + +uninstall_udev: + @echo "Removing Solaar udev rules from $(UDEV_RULES_DEST)" + sudo rm -f $(UDEV_RULES_DEST)/$(UDEV_RULE_FILE) + make reload_udev + +format: + @echo "Formatting Solaar code" + ruff format . + +lint: + @echo "Linting Solaar code" + ruff check . --fix + +test: + @echo "Running Solaar tests" + pytest --cov --cov-report=xml diff --git a/README.md b/README.md deleted file mode 120000 index e8923303..00000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -docs/index.md \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..c3444c0f --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# Solaar + +Solaar is a Linux manager for many Logitech keyboards, mice, and other devices +that connect wirelessly to a Unifying, Bolt, Lightspeed or Nano receiver +as well as many Logitech devices that connect via a USB cable or Bluetooth. +Solaar is not a device driver and responds only to special messages from devices +that are otherwise ignored by the Linux input system. + +More Information - +Usage - +Capabilities - +Rules - +Manual Installation - +Known Issues + + +[![codecov](https://codecov.io/gh/pwr-Solaar/Solaar/graph/badge.svg?token=D7YWFEWID6)](https://codecov.io/gh/pwr-Solaar/Solaar) +[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2+-blue.svg)](../LICENSE.txt) + +

+ +   + +

+ +

+ +   + +

+ +Solaar supports: +- pairing/unpairing of devices with receivers +- configuring device settings +- custom button configuration +- running rules in response to special messages from devices + +For more information see + the main Solaar documentation page. - + + +## Installation Packages + +Up-to-date prebuilt packages are available for some Linux distros +(e.g., Fedora) in their standard repositories. +If a recent version of Solaar is not +available from the standard repositories for your distribution, you can try +one of these packages: + +- Arch solaar package in the [extra repository][arch] +- Ubuntu/Kubuntu package in [Solaar stable ppa][ppa stable] +- NixOS Flake package in [Svenum/Solaar-Flake][nix flake] + +Solaar is available from some other repositories +but may be several versions behind the current version: + +- a [Debian package][debian], courtesy of Stephen Kitt +- a Ubuntu package is available from [universe repository][ubuntu universe repository] +- a [Gentoo package][gentoo], courtesy of Carlos Silva and Tim Harder +- a [Mageia package][mageia], courtesy of David Geiger + +[ppa stable]: https://launchpad.net/~solaar-unifying/+archive/ubuntu/stable +[arch]: https://www.archlinux.org/packages/extra/any/solaar/ +[gentoo]: https://packages.gentoo.org/packages/app-misc/solaar +[mageia]: http://mageia.madb.org/package/show/release/cauldron/application/0/name/solaar +[ubuntu universe repository]: http://packages.ubuntu.com/search?keywords=solaar&searchon=names&suite=all§ion=all +[nix flake]: https://github.com/Svenum/Solaar-Flake +[debian]: https://packages.debian.org/search?keywords=solaar&searchon=names&suite=all§ion=all diff --git a/RELEASE.md b/RELEASE.md index 130b70d3..59b61bf0 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -8,7 +8,7 @@ candidates (ex. `1.0.0rc1`). Release candidates must have a `rcX` suffix. Release routine: - Update version in `lib/solaar/version` -- Add release changes to `ChangeLog.md` +- Add release changes to `CHANGELOG.md` - Add release information to `share/solaar/io.github.pwr_solaar.solaar.metainfo.xml` - Create a commit that starts with `release VERSION` - Push commit to Solaar repository diff --git a/RHEL.md b/RHEL.md new file mode 100644 index 00000000..10687b33 --- /dev/null +++ b/RHEL.md @@ -0,0 +1,175 @@ +# Solaar installation guide for RHEL, Rocky, AlmaLinux, and CentOS Stream + +This guide covers manual installation and an automated install example for +RHEL-family systems using `dnf`. + +## Supported distributions + +- Red Hat Enterprise Linux (RHEL) +- Rocky Linux +- AlmaLinux +- Oracle Linux +- CentOS Stream + +The commands assume a minimal CLI system with `sudo` access. + +## 1) Install dependencies + +```bash +sudo dnf makecache --refresh +sudo dnf install -y \ + git \ + gtk3 \ + python3 \ + python3-devel \ + python3-dbus \ + python3-gobject \ + python3-pip \ + python3-psutil \ + python3-pyudev \ + python3-setuptools \ + python3-xlib \ + python3-yaml +``` + +Optional troubleshooting helpers: + +```bash +sudo dnf install -y \ + evemu \ + libinput \ + usbutils +``` + +## 2) Clone Solaar + +```bash +git clone https://github.com/pwr-Solaar/Solaar.git +cd Solaar +``` + +## 3) Install Solaar + +Install for the current user: + +```bash +python3 -m pip install --user . +``` + +Or install system-wide: + +```bash +sudo python3 -m pip install . +``` + +## 4) Install udev rules + +Install the recommended `uinput` rule: + +```bash +sudo make install_udev_uinput +``` + +Verify rule installation: + +```bash +ls -l /etc/udev/rules.d/42-logitech-unify-permissions.rules +``` + +Rollback udev rule installation: + +```bash +sudo make uninstall_udev +``` + +## 5) Run Solaar + +```bash +solaar +``` + +or: + +```bash +python3 -m solaar +``` + +## 6) Automated install options + +Use the guided installer in this repository: + +```bash +./tools/install-rhel.sh +``` + +Minimal non-interactive example script: + +```bash +cat > install-rhel-solaar.sh <<'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail + +if [[ "${EUID}" -eq 0 ]]; then + echo "Run as a regular user with sudo access, not as root." + exit 1 +fi + +sudo dnf makecache --refresh +sudo dnf install -y \ + git \ + gtk3 \ + python3 \ + python3-devel \ + python3-dbus \ + python3-gobject \ + python3-pip \ + python3-psutil \ + python3-pyudev \ + python3-setuptools \ + python3-xlib \ + python3-yaml + +if [[ ! -d Solaar/.git ]]; then + git clone https://github.com/pwr-Solaar/Solaar.git +fi + +cd Solaar +python3 -m pip install --user . +sudo make install_udev_uinput +~/.local/bin/solaar --version +SCRIPT + +chmod +x install-rhel-solaar.sh +./install-rhel-solaar.sh +``` + +## 7) Verification + +```bash +command -v solaar +solaar --version +python3 -m pip show solaar +``` + +If installed with `--user`, ensure `~/.local/bin` is on your `PATH`: + +```bash +echo "$PATH" | tr ':' '\n' | grep -Fx "$HOME/.local/bin" >/dev/null || \ + echo 'Add ~/.local/bin to PATH' +``` + +## 8) Troubleshooting + +Receiver not detected: + +```bash +lsusb | grep -Ei 'logitech|046d' +sudo udevadm trigger +``` + +Check access to hidraw devices: + +```bash +ls -l /dev/hidraw* +getfacl /dev/hidraw* 2>/dev/null | sed -n '1,80p' +``` diff --git a/Release_Notes.md b/Release_Notes.md index 0acabcb0..d27ac40f 100644 --- a/Release_Notes.md +++ b/Release_Notes.md @@ -1,5 +1,61 @@ # Notes on Major Changes in Releases +## Version 1.1.18 + +* Solaar is only guaranteed to work in Python 3.13 or later. + +## Version 1.1.17 + +* Several new features have been added related to the MX Master 4 + * The scroll ratchet force can be adjusted + * The force required to click the button under your thumb can be adjusted + * The haptic force can be adjusted + * Haptic feeback can be triggered by commands like `solaar config 'mx master 4' haptic-play 'HAPPY ALER'` + +## Version 1.1.16 + +* Two bugs that were affecting users in 1.1.15 are fixed. + +## Version 1.1.15 + +* Some key names have been changed to match Logitech names. Rules that use removed names will no longer work and will end up with a key of 0. +* Device and Action rule conditions match on device codename and name +* Solaar supports configuration of Bluetooth devices on macOS. + +## Version 1.1.13 + +* Solaar will drop support for Python 3.7 immediately after version 1.1.13. + +* Version 1.1.12 does not push settings to many devices after a resume resulting in the device reverting to its default behaviour. This is fixed in 1.1.13. + +## Version 1.1.12 + +* Solaar now processes DBus disconnection and connection messages from Bluez and re-initializes devices when they reconnect, to handle to a change introduced in Bluez 5.73. The HID++ driver does not re-initialize devices, which causes problems with smooth scrolling. Until the issue is resolved having Scroll Wheel Resolution set to true (and not ignored) may be helpful. + +* The credits for translations have not been kept up to date. Translators who are not listed can update docs/i18n.ml and lib/solaar/ui/about.py. + +* Solaar now has settings for features BRIGHTNESS_CONTROL, RGB_EFFECTS, and PER_KEY_LIGHTING features. The names of keys in the Per-key Lighting setting are for the standard US keyboard. Users who wish to modify these names should look at the section Keyboard Key Names and Locations in `https://pwr-solaar.github.io/Solaar/capabilities` + +* A unit test test suite is being constructed using pytest. + +* The Solaar code for communicating with receivers and devices has been significantly modified to improve testability and organization. Errors may have been introduced for uncommon hardware. + +* The Later rule action uses precision timing for delays of less than one second. + +* Solaar now indentifies supported devices by whether they support the HID protocols that Solaar needs. If a device does not show up at all when running Solaar, it almost certainly cannot be supported by Solaar. + +## Version 1.1.11 + +* Solaar can dump device profiles in YAMLfor devices that support profiles and load profiles back from an edited file. See [the capabilities page](https://pwr-solaar.github.io/Solaar/capabilities) for more information. + +* Solaar has settings for each LED Zone that a device supports under feature Color LED Effects. + +* Solaar has settings for extended report rate, backlight levels, durations, and profile selection. + +* Solaar now partly works in MacOS. Please open new issues for problems. Solaar may work in Windows. Please open new issues for problems. See https://github.com/pwr-Solaar/Solaar/pull/1971 for more information. Fixing problems in MacOS or Windows may take considerable time. + +* Solaar works better when the Python package hid-parser is available. Distriubtions should try have this package installed. + ## Version 1.1.10 * The mouse click rule action can now just simulate depressing or releasing the button. diff --git a/bin/solaar b/bin/solaar index 1ed01d81..07ceeee9 100755 --- a/bin/solaar +++ b/bin/solaar @@ -21,35 +21,22 @@ def init_paths(): """Make the app work in the source tree.""" - import os.path as _path + import os.path import sys - # Python 2 need conversion from utf-8 filenames - # Python 3 might have problems converting back to UTF-8 in case of Unicode surrogates - try: - decoded_path = sys.path[0] - sys.path[0].encode(sys.getfilesystemencoding()) - - except UnicodeError: - sys.stderr.write( - 'ERROR: Solaar cannot recognize encoding of filesystem path, ' - 'this may happen because non UTF-8 characters in the pathname.\n' - ) - sys.exit(1) - - prefix = _path.normpath(_path.join(_path.realpath(decoded_path), '..')) - src_lib = _path.join(prefix, 'lib') - share_lib = _path.join(prefix, 'share', 'solaar', 'lib') + root = os.path.join(os.path.realpath(sys.path[0]), "..") + prefix = os.path.normpath(root) + src_lib = os.path.join(prefix, "lib") + share_lib = os.path.join(prefix, "share", "solaar", "lib") for location in src_lib, share_lib: - init_py = _path.join(location, 'solaar', '__init__.py') - # print ("sys.path[0]: checking", init_py) - if _path.exists(init_py): - # print ("sys.path[0]: found", location, "replacing", sys.path[0]) + init_py = os.path.join(location, "solaar", "__init__.py") + if os.path.exists(init_py): sys.path[0] = location break -if __name__ == '__main__': +if __name__ == "__main__": init_paths() import solaar.gtk + solaar.gtk.main() diff --git a/docs/LICENSE.txt b/docs/LICENSE.txt new file mode 100644 index 00000000..d014535c --- /dev/null +++ b/docs/LICENSE.txt @@ -0,0 +1,132 @@ +GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. + b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. + c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + +one line to give the program's name and an idea of what it does. +Copyright (C) yyyy name of author + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, see +. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it starts in an interactive mode: + +Gnomovision version 69, Copyright (C) year name of author +Gnomovision comes with ABSOLUTELY NO WARRANTY; for details +type `show w'. This is free software, and you are welcome +to redistribute it under certain conditions; type `show c' +for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright +interest in the program `Gnomovision' +(which makes passes at compilers) written +by James Hacker. + +signature of Moe Ghoul, 1 April 1989 +Moe Ghoul, President of Vice + +This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. diff --git a/docs/PRO_X2_SUPERSTRIKE_CLI.md b/docs/PRO_X2_SUPERSTRIKE_CLI.md new file mode 100644 index 00000000..ed767e1b --- /dev/null +++ b/docs/PRO_X2_SUPERSTRIKE_CLI.md @@ -0,0 +1,374 @@ +# Logitech PRO X 2 Superstrike - Solaar CLI Reference + +This document describes all available settings for the Logitech PRO X 2 Superstrike mouse via the Solaar CLI. + +## Device Identification + +| Property | Value | +|----------|-------| +| Name | PRO X 2 Superstrike | +| WPID | 40BD | +| Protocol | HID++ 4.2 | +| Kind | mouse | + +## General CLI Syntax + +```bash +# List all settings for device +solaar config + +# Read a specific setting +solaar config + +# Write a specific setting +solaar config +``` + +The `` can be: +- Device number (e.g., `1`) +- Device name (e.g., `"PRO X 2 Superstrike"`) +- Serial number (e.g., `A1C55DB2`) + +--- + +## Available Settings + +### 1. Onboard Profiles + +Controls whether the device uses its onboard profile or host-controlled settings. + +| Property | Value | +|----------|-------| +| Setting Name | `onboard_profiles` | +| Type | Choice | +| Possible Values | `Disabled`, `Profile 1` | + +**Commands:** + +```bash +# Read current value +solaar config 1 onboard_profiles + +# Set to disabled (allows host control of DPI, report rate, etc.) +solaar config 1 onboard_profiles Disabled + +# Set to Profile 1 (use onboard profile) +solaar config 1 onboard_profiles "Profile 1" +``` + +**Note:** Many settings require `onboard_profiles` to be set to `Disabled` to be effective. + +--- + +### 2. Report Rate + +Controls the frequency of device movement reports. + +| Property | Value | +|----------|-------| +| Setting Name | `report_rate_extended` | +| Type | Choice | +| Possible Values | `8ms`, `4ms`, `2ms`, `1ms`, `500us`, `250us`, `125us` | + +**Commands:** + +```bash +# Read current value +solaar config 1 report_rate_extended + +# Set to 1ms (1000Hz) +solaar config 1 report_rate_extended 1ms + +# Set to 500us (2000Hz) +solaar config 1 report_rate_extended 500us + +# Set to 125us (8000Hz) +solaar config 1 report_rate_extended 125us +``` + +**Polling Rate Reference:** + +| Value | Polling Rate | +|-------|--------------| +| `8ms` | 125 Hz | +| `4ms` | 250 Hz | +| `2ms` | 500 Hz | +| `1ms` | 1000 Hz | +| `500us` | 2000 Hz | +| `250us` | 4000 Hz | +| `125us` | 8000 Hz | + +--- + +### 3. Sensitivity (DPI) + +Controls mouse movement sensitivity. + +| Property | Value | +|----------|-------| +| Setting Name | `dpi_extended` | +| Type | Complex (X, Y, LOD) | +| DPI Range | 100 - 32000 | +| LOD Values | `LOW`, `HIGH` | + +**Commands:** + +```bash +# Read current value +solaar config 1 dpi_extended + +# Set DPI (format: {X:, Y:, LOD:}) +solaar config 1 dpi_extended "{X:800, Y:800, LOD:HIGH}" + +# Set to 1600 DPI +solaar config 1 dpi_extended "{X:1600, Y:1600, LOD:HIGH}" + +# Set different X and Y sensitivity +solaar config 1 dpi_extended "{X:800, Y:1600, LOD:LOW}" +``` + +--- + +## HITS Tuning Settings (Hall-Effect Inductive Trigger Switch) + +These settings control the advanced click behavior of the PRO X 2 Superstrike's hall-effect switches. + +### 4. Actuation Point + +Controls how deep the button must be pressed to register a click. + +| Property | Value | +|----------|-------| +| Setting Name (Left) | `superstrike-tuning_actuation-0` | +| Setting Name (Right) | `superstrike-tuning_actuation-1` | +| Type | Range | +| Range | 1 - 10 | +| Default | 5 | + +**Value Interpretation:** +- `1` = Shallowest (hair trigger, minimal press) +- `10` = Deepest (full press required) + +**Commands:** + +```bash +# Read left button actuation +solaar config 1 superstrike-tuning_actuation-0 + +# Read right button actuation +solaar config 1 superstrike-tuning_actuation-1 + +# Set left button to shallow actuation (hair trigger) +solaar config 1 superstrike-tuning_actuation-0 1 + +# Set left button to deep actuation +solaar config 1 superstrike-tuning_actuation-0 10 + +# Set right button to medium actuation +solaar config 1 superstrike-tuning_actuation-1 5 +``` + +--- + +### 5. Rapid Trigger Level + +Controls the rapid trigger sensitivity, which allows the button to re-actuate quickly after partial release. + +| Property | Value | +|----------|-------| +| Setting Name (Left) | `superstrike-tuning_rapid-trigger-level-0` | +| Setting Name (Right) | `superstrike-tuning_rapid-trigger-level-1` | +| Type | Range | +| Range | 1 - 5 | +| Default | 3 | + +**Value Interpretation:** +- `1` = Fastest (most sensitive, smallest movement to re-trigger) +- `5` = Slowest (least sensitive, larger movement needed) + +**Note:** Rapid trigger cannot be disabled on this device. The minimum level is 1. + +**Commands:** + +```bash +# Read left button rapid trigger level +solaar config 1 superstrike-tuning_rapid-trigger-level-0 + +# Read right button rapid trigger level +solaar config 1 superstrike-tuning_rapid-trigger-level-1 + +# Set left button to fastest rapid trigger +solaar config 1 superstrike-tuning_rapid-trigger-level-0 1 + +# Set left button to slowest rapid trigger +solaar config 1 superstrike-tuning_rapid-trigger-level-0 5 + +# Set right button to medium rapid trigger +solaar config 1 superstrike-tuning_rapid-trigger-level-1 3 +``` + +--- + +### 6. Click Haptics + +Controls the intensity of the haptic feedback when clicking. + +| Property | Value | +|----------|-------| +| Setting Name (Left) | `superstrike-tuning_haptics-0` | +| Setting Name (Right) | `superstrike-tuning_haptics-1` | +| Type | Range | +| Range | 0 - 5 | +| Default | 3 | + +**Value Interpretation:** +- `0` = Off (no haptic feedback) +- `1` = Minimal +- `2` = Light +- `3` = Medium +- `4` = Strong +- `5` = Strongest (maximum haptic feedback) + +**Commands:** + +```bash +# Read left button haptics level +solaar config 1 superstrike-tuning_haptics-0 + +# Read right button haptics level +solaar config 1 superstrike-tuning_haptics-1 + +# Disable haptics on left button +solaar config 1 superstrike-tuning_haptics-0 0 + +# Set left button to maximum haptics +solaar config 1 superstrike-tuning_haptics-0 5 + +# Set right button to medium haptics +solaar config 1 superstrike-tuning_haptics-1 3 +``` + +--- + +## Complete Settings Summary + +| Setting | CLI Name | Type | Range/Values | Button-Specific | +|---------|----------|------|--------------|-----------------| +| Onboard Profiles | `onboard_profiles` | Choice | `Disabled`, `Profile 1` | No | +| Report Rate | `report_rate_extended` | Choice | `8ms` to `125us` | No | +| Sensitivity | `dpi_extended` | Complex | 100-32000 DPI | No | +| Actuation Point | `superstrike-tuning_actuation-{0,1}` | Range | 1-10 | Yes | +| Rapid Trigger | `superstrike-tuning_rapid-trigger-level-{0,1}` | Range | 1-5 | Yes | +| Click Haptics | `superstrike-tuning_haptics-{0,1}` | Range | 0-5 | Yes | + +--- + +## Batch Configuration Examples + +### Gaming Profile (Fast Response) + +```bash +#!/bin/bash +# Gaming profile: fast actuation, sensitive rapid trigger, medium haptics + +solaar config 1 onboard_profiles Disabled +solaar config 1 report_rate_extended 125us +solaar config 1 dpi_extended "{X:800, Y:800, LOD:HIGH}" + +# Left button - hair trigger +solaar config 1 superstrike-tuning_actuation-0 1 +solaar config 1 superstrike-tuning_rapid-trigger-level-0 1 +solaar config 1 superstrike-tuning_haptics-0 3 + +# Right button - hair trigger +solaar config 1 superstrike-tuning_actuation-1 1 +solaar config 1 superstrike-tuning_rapid-trigger-level-1 1 +solaar config 1 superstrike-tuning_haptics-1 3 +``` + +### Productivity Profile (Comfortable) + +```bash +#!/bin/bash +# Productivity profile: deeper actuation, slower rapid trigger, strong haptics + +solaar config 1 onboard_profiles Disabled +solaar config 1 report_rate_extended 1ms +solaar config 1 dpi_extended "{X:1600, Y:1600, LOD:HIGH}" + +# Left button - comfortable click +solaar config 1 superstrike-tuning_actuation-0 7 +solaar config 1 superstrike-tuning_rapid-trigger-level-0 4 +solaar config 1 superstrike-tuning_haptics-0 5 + +# Right button - comfortable click +solaar config 1 superstrike-tuning_actuation-1 7 +solaar config 1 superstrike-tuning_rapid-trigger-level-1 4 +solaar config 1 superstrike-tuning_haptics-1 5 +``` + +### Silent Profile (No Haptics) + +```bash +#!/bin/bash +# Silent profile: no haptic feedback + +solaar config 1 superstrike-tuning_haptics-0 0 +solaar config 1 superstrike-tuning_haptics-1 0 +``` + +--- + +## Programmatic Usage + +### Reading All Settings (JSON-like parsing) + +```bash +# Get all settings as output +solaar config 1 2>/dev/null | grep "^[a-z]" | while read line; do + setting=$(echo "$line" | cut -d'=' -f1 | tr -d ' ') + value=$(echo "$line" | cut -d'=' -f2 | tr -d ' ') + echo "{\"setting\": \"$setting\", \"value\": \"$value\"}" +done +``` + +### Reading a Single Setting Value + +```bash +# Extract just the value +solaar config 1 superstrike-tuning_actuation-0 2>/dev/null | grep "^superstrike" | cut -d'=' -f2 | tr -d ' ' +``` + +### Error Handling + +```bash +# Check if command succeeded +if solaar config 1 superstrike-tuning_actuation-0 5 2>/dev/null; then + echo "Setting applied successfully" +else + echo "Failed to apply setting" +fi +``` + +--- + +## Exit Codes + +| Code | Meaning | +|------|---------| +| 0 | Success | +| 1 | Error (device not found, invalid setting, invalid value) | + +--- + +## Notes + +1. **Device Discovery**: Use `solaar show` to list all connected devices and their indices. + +2. **Persistence**: Settings are saved to `~/.config/solaar/config.yaml` and automatically reapplied when the device reconnects. + +3. **Onboard Profiles**: When `onboard_profiles` is set to `Profile 1`, some settings (DPI, report rate) are controlled by the device's onboard memory and cannot be changed via Solaar. + +4. **HITS Settings**: The actuation, rapid trigger, and haptics settings are stored in the device and persist across reconnections, regardless of the onboard profile setting. + +5. **Button Index**: `0` = Left button, `1` = Right button. diff --git a/docs/README.md b/docs/README.text similarity index 100% rename from docs/README.md rename to docs/README.text diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index d3ad18e3..00000000 --- a/docs/_config.yml +++ /dev/null @@ -1,9 +0,0 @@ -title: Solaar -description: Linux Device Manager for Logitech Unifying Receivers and Devices. -tagline: Linux Device Manager for Logitech Unifying Receivers and Devices. -owner: pwr-Solaar -owner_url: https://github.com/pwr-Solaar -repository: pwr-Solaar/Solaar -show_downloads: false -encoding: utf-8 -theme: jekyll-theme-slate diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html deleted file mode 100644 index d9f79750..00000000 --- a/docs/_layouts/default.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - -{% seo %} - - - - - -
-
- {% if site.github.is_project_page %} - View on GitHub - {% endif %} -

- - {{ site.title | default: site.github.repository_name }}

-

{{ site.description | default: site.github.project_tagline }}

- - {% if site.show_downloads %} -
- Download this project as a .zip file - Download this project as a tar.gz file -
- {% endif %} -
-
- - -
-
- {{ content }} -
-
- - - - - diff --git a/docs/_layouts/page.html b/docs/_layouts/page.html deleted file mode 100644 index 52ab0801..00000000 --- a/docs/_layouts/page.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - -{% seo %} - - - - - -
-
- {% if site.github.is_project_page %} - View on GitHub - {% endif %} - -

- - - {{ site.title | default: site.github.repository_name }}

- -
-
- - -
-
- {{ content }} -
-
- - - - - diff --git a/docs/capabilities.md b/docs/capabilities.md index 8c0436d4..f61d42a9 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -21,9 +21,9 @@ Not all such devices supported in Solaar as information needs to be added to Sol for each device type that directly connects. -## HID++ +## HID++ and Centurion -The devices that Solaar handles use Logitech's HID++ protocol. +The devices that Solaar handles use Logitech's HID++ and Centurion protocols. HID++ is a Logitech-proprietary protocol that extends the standard HID protocol for interfacing with receivers, keyboards, mice, and so on. It allows @@ -43,6 +43,8 @@ Contrariwise, two different devices may appear different physically but actually look the same to software. (For example, some M185 mice look the same to software as some M310 mice.) +Centurion is closely related to HID++ and is used by some Logitech headsets. + The software identity of a receiver can be determined by its USB product ID (reported by Solaar and also viewable in Linux using `lsusb`). The software identity of a device that connects to a receiver can be determined by @@ -51,17 +53,17 @@ connect via a USB cable or via bluetooth can be determined by their USB or Bluetooth product ID. -# Pairing and Unpairing +## Pairing and Unpairing Solaar is able to pair and unpair devices with receivers as supported by the device and receiver. -For Unifying receivers, pairing adds a new paired device, but +For Unifying and Bolt receivers, pairing adds a new paired device, but only if there is an open slot on the receiver. So these receivers need to be able to unpair devices that they have been paired with or else they will -not have any open slots for pairing. Some other receivers, like the -Nano receiver with USB ID `046d:c534`, can only pair with particular kinds of -devices and pairing a new device replaces whatever device of that kind was +not have any open slots for pairing. Some Nano and Lightspeed receivers, like the +Nano receiver with USB ID `046d:c534`, can only pair with one keyboard and one mouse +and pairing a new device replaces whatever device of that kind was previously paired to the receiver. These receivers cannot unpair. Further, some receivers can pair an unlimited number of times but others can only pair a limited number of times. @@ -69,20 +71,22 @@ pair a limited number of times. Bolt receivers add an authentication phase to pairing, where the user has type a passcode or click some buttons to authenticate the device. -Only some connections between receivers and devices are possible. In should +Only some connections between receivers and devices are possible. It should be possible to connect any device with a Unifying logo on it to any receiver -with a Unifying logo on it. Receivers without the Unifying logo probably -can connect only to the kind of devices they were bought with and devices -without the Unifying logo can probably only connect to the kind of receiver -that they were bought with. +with a Unifying logo on it and any device with a Bolt logo on it to any receiver +with a Bolt logo on it. +Many receivers without the Unifying or Bolt logo +can connect only to the model of devices they were bought with and many devices +without the Unifying or Bolt logo can only connect to a receiver +that matches the one they were bought with. ## Device Settings Solaar can display quite a few changeable settings of receivers and devices. -For a list of HID++ features and their support see [the features page](features). +For a list of features and their support see [the features page](features.md). -Solaar does not do much beyond using the HID++ protocol to change the +Solaar does not do much beyond using the protocols to change the behavior of receivers and devices via changing their settings. In particular, Solaar cannot change how the operating system turns the keycodes that a keyboard produces into @@ -112,7 +116,7 @@ Solaar keeps track of settings independently on each computer. As a result if a device is switched between different computers Solaar may apply different settings for it on the different computers. -Querying a device for its current state can require quite a few HID++ +Querying a device for its current state can require quite a few interactions. These interactions can temporarily slow down the device, so Solaar tries to internally cache information about devices while it is running. If the device @@ -174,6 +178,154 @@ is sent to the Solaar rule system so that rules can detect these notifications. For more information on Mouse Gestures rule conditions see [the rules page](https://pwr-solaar.github.io/Solaar/rules). +### Keyboard Key Names and Locations + +Solaar uses the standard Logitech names for keyboard keys. Some Logitech keyboards have different icons on some of their keys and have different functionality than suggested by these names. + +Solaar uses the standard US keyboard layout. This currently only matters for the `Per-key Lighting` setting. Users who want to have the key names for this setting reflect the keyboard layout that they use can create and edit `~/.config/solaar/keys.yaml` which contains a YAML dictionary of key names and locations. For example, switching the `Y` and `Z` keys can be done as: + + Z: 25 + Y: 26 + +This is an experimental feature and may be modified or even eliminated. + + +### HITS Tuning (Hall-Effect Inductive Trigger Switch) + +Some gaming mice (such as the PRO X 2 Superstrike) feature hall-effect magnetic switches on their primary buttons instead of traditional mechanical switches. These switches expose tunable parameters via the `SUPERSTRIKE_TUNING` HID++ feature (`0x1B0C`). + +Solaar supports three per-button settings for each primary button (left = 0, right = 1): + +- **Actuation Point** (`superstrike-tuning_actuation-{0,1}`): How deep the button must be pressed to register a click. Range 1–10, where 1 is the shallowest (hair trigger) and 10 is the deepest (full press). Default is 5. +- **Rapid Trigger Level** (`superstrike-tuning_rapid-trigger-level-{0,1}`): Sensitivity of rapid re-actuation after partial release. Range 1–5, where 1 is the most sensitive and 5 is the least. This cannot be fully disabled. +- **Click Haptics** (`superstrike-tuning_haptics-{0,1}`): Intensity of haptic feedback on click. Range 0–5, where 0 disables haptics and 5 is maximum intensity. + +These settings are written directly to the device and persist across reconnections regardless of the onboard profile state. + +### Extended DPI + +Some gaming mice (such as the PRO X 2 Superstrike) support the `EXTENDED_ADJUSTABLE_DPI` feature (`0x2202`) which allows independent X and Y axis DPI configuration as well as lift-off distance (LOD) control. This is exposed via the `dpi_extended` setting: + +```bash +solaar config dpi_extended "{X:1600, Y:1600, LOD:HIGH}" +``` + +LOD values are `LOW` and `HIGH`. DPI range depends on the device sensor (up to 32000 DPI on the PRO X 2 Superstrike). + +### Haptic Feedback + +Some devices, such as the MX Master 4 have haptic feeback. +The Solaar CLI can be used to 'play' wave forms, for example +``` +solaar config 'mx master 4' haptic-play 'HAPPY ALERT' +``` +Solaar rules can also do this using their `Set` action. + + +### Extended Report Rate + +Some gaming mice (such as the PRO X 2 Superstrike) support the `EXTENDED_ADJUSTABLE_REPORT_RATE` feature (`0x8061`) which enables sub-millisecond polling rates beyond the standard 1 ms (1000 Hz). This is exposed via the `report_rate_extended` setting: + +| Value | Polling Rate | +|---------|-------------| +| `8ms` | 125 Hz | +| `4ms` | 250 Hz | +| `2ms` | 500 Hz | +| `1ms` | 1000 Hz | +| `500us` | 2000 Hz | +| `250us` | 4000 Hz | +| `125us` | 8000 Hz | + +### Onboard Profiles + +Some mice store one or more profiles onboard. An onboard profile controls certain aspects of the behavior of the mouse, including the rate at which the mouse reports movement, the resolution of the the movement reports, what the mouse buttons do, LED effects, and maybe more. Solaar has a setting that switches between profiles or disables all profiles. + +When an onboard profile is active it may not be possible to change the aspects that the profile controls. This is often seen for the Report Rate setting. For some devices it is possible to make changes to the Sensitivity setting and to LED settings. These changes are likely to only be temporary and may be overridden when the device reconnects or when Solaar is restarted. This is in keeping with the intent of Onboard Profiles as controlling the device behavior. To make the changes to these settings permanent it is necessary to disable onboard profiles. Alternatively, multiple profiles can be set up as described below and these settings controlled by switching between the profiles. + +Solaar can dump the entire set of profiles into a YAML file and can load the entire set of profiles from a file. Users can edit the file to effect changes to the profiles. + +A profile file has some bookkeeping information, including profile version and the name of the device, and a sequence of profiles. + +Each profile has the following fields: +- enabled: Whether the profile is enabled. +- sector: Where the profile is stored in device memory. Sectors greater than 0xFF are in ROM and cannot be written (use the low byte as the sector to write to Flash). +- name: A memonic name for the profile. +- report_rate: A report rate in milliseconds from 1 to 8. +- resolutions: A sequence of five sensor resolutions in DPI. +- resolution_default_index: The index of the default sensor resolution (0 to 4). +- resolution_shift_index: The index of the sensor resolution used when the DPI Shift button is pressed (0 to 4). +- buttons: The action for each button on the mouse in normal mode. +- gbuttons: The action for each button on the mouse in G-Shift mode. +- angle_snap: Enable angle snapping for devices. +- red, blue, green: Color indicator for the profile. +- lighting: Lighting information for logo and side LEDs in normal mode, then for power saving mode. +- ps_timeout: Delay in ms to go into power saving mode. +- po_timeout: Delay in ms to go from power saving mode to fully off. +- power_mode: Unknown purpose. +- write count: Unknown purpose. +Missing or unused parts of a profile are often a sequence of 0xFF bytes. + +Button actions can either perform a function (behavior: 9) or send a button click or key press (behaviour: 8). +Functions are: +- 0: No Action - do nothing +- 1: Tilt Left +- 2: Tilt Right +- 3: Next DPI - change device resolution to the next DPI +- 4: Previous DPI - change device resolution to the previous DPI +- 5: Cycle DPI - change device resolution to the next DPI considered as a cycle +- 6: Default_DPI - change device resolution to the default resolution +- 7: Shift_DPI - change device resolution to the shift resolution +- 8: Next Profile - change to the next enabled profile +- 9: Previous Profile - change to the previous enabled profile +- 10: Cycle Profile - change to the next enabled profile considered as a cycle +- 11: G-Shift - change all buttons to their G-Shift state +- 12: Battery Status - show battery status on the device LEDs +- 13: Profile Select - select the n'th enabled profile +- 14: Mode Switch +- 15: Host Button - switch between hosts (unverified) +- 16: Scroll Down +- 17: Scroll Up +Some devices might not be able to perform all functions. + +Buttons can send (type): +- 0: Don't send anything. +- 1: A button click (value) as a 16-bit bitmap, i.e., 1 is left click, 2 is right, 4 is middle, etc. +- 2: An 8-bit USB HID keycode (value) plus an 8-bit modifier bitmap (modifiers), i.e., 0 for no modifiers, 1 for control, 2 for shift, etc. +- 3: A 16-bit HID Consumer keycode (value). + +See USB_HID_KEYCODES and HID_CONSUMERCODES in lib/logitech_receiver/special_keys.py for values to use for keycodes. + +Buttons can also execute macros but Solaar does not provide any support for macros. + +Lighting information is a sequence of lighting effects, with the first usually for the logo LEDs and the second usually for the side LEDs. + +The fields possible in an effect are: +- ID: The kind of effect: +- color: A color parameter for the effect as a 24-bit RGB value. +- intensity: How intense to make the color (1%-100%), 0 for the default (usually 100%). +- speed: How fast to pulse in ms (one byte). +- ramp: How to change to the color (0=default, 1=ramp up/down, 2=no ramping). +- period: How fast to perform the effect in ms (two bytes). +- form: The form of the breathe effect. +- bytes: The raw bytes of other effects. + +The possible effects and the fields the use are: +- 0x0: Disable - No fields. +- 0x1: Fixed color - color, whether to ramp. +- 0x2: Pulse a color - color, speed. +- 0x3 Cycle through the spectrum - period, intensity, form. +- 0x8; A boot effect - No fields. +- 0x9: A demo effect - NO fields. +- 0xa: breathe a color (like pulse) - color, period. +- 0xb: Ripple - color, period. +- null: An unknown effect. +Only effects supported by the device can be used. + +To set up profiles, first run `solaar profiles `, which will output a YAML dump of the profiles on the device. Then store the YAML dump into a file and edit the file to make changes. Finally run `solaar profiles ` to load the profiles back onto the device. Profiles are stored in flash memory and persist when the device is inactive or turned off. When loading profiles Solaar is careful to only write the flash memory sectors that need to be changed. Solaar also checks for correct profile version and device name before loading a profile into a device. + +Keep a copy of the initial dump of profiles so that it can be loaded back to the device if problems are encountered with the edited profiles. The safest changes are to take an unused or unenabled profile sector and create a new profile in it, likely mostly copying parts of another profile. + + ## System Tray Solaar's GUI normally uses an icon in the system tray. diff --git a/docs/devices.md b/docs/devices.md index a10c81a7..58972047 100644 --- a/docs/devices.md +++ b/docs/devices.md @@ -5,78 +5,32 @@ layout: page # Supported receivers and devices -Solaar only supports Logitech receivers and devices that use the Logitech proprietary HID++ protocol. +Solaar only supports Logitech receivers and devices that use the Logitech proprietary HID++ and Centurion protocols. Solaar supports most Logitech Nano, Unifying, and Bolt receivers. Solaar supports some Lightspeed receivers. See the receiver table below for the list of currently supported receivers. -Solaar supports most recent and many older Logitech devices -(keyboards, mice, trackballs, touchpads, and headsets) +Solaar supports all Logitech devices (keyboards, mice, trackballs, touchpads, and headsets) that can connect to supported receivers. -Solaar supports many recent Logitech devices that can connect via a USB cable, -but some such Logitech devices are not suited for use in Solaar because they do not use the HID++ protocol. -One example is the MX518 Gaming Mouse. -Solaar supports most recent Logitech devices that can connect via Bluetooth. +Solaar supports all Logitech devices that can connect via a USB cable or via Bluetooth, +as long as the device uses the HID++ or Centurion protocol. The best way to determine whether Solaar supports a device is to run Solaar while the device is connected. If the device is supported, it will show up in the Solaar main window. -If it is not, and there is no issue about the device in the Solaar GitHub repository, -open an enhancement issue requesting that it be supported. The directory contains edited output of `solaar show` on many devices and can be used to see what Solaar can do with the device. -## Adding new devices +## Supporting old devices -Most new HID++ devices do not need to be known to Solaar to work. -You should be able to just run Solaar and the device will show up. +Some old Logitech devices use an old version of HID++. +For Solaar to support these devices well, Solaar needs some information about them. -If your device does not show up, -either it doesn't use HID++ or the interface it uses isn't the one Solaar normally uses. -To start the process of support for a Logitech device open an enhancement issue for Solaar and -follow these steps: - -1. Make sure the receiver or device is connected and active. - -2. Look at the output of `grep -H . /sys/class/hidraw/hidraw*/device/uevent` to find -where information about the device is kept. -You are looking for a line like `/sys/class/hidraw/hidrawN/device/uevent:HID_NAME=` -where \ is the name of your receiver or device. -N is the current HID raw number of your receiver or device. - -3. Provide the contents of the file `/sys/class/hidraw/hidrawN/device/uevent` where N was found -above. - -4. Also attach the contents of the file `/sys/class/hidraw/hidrawN/device/report_descriptor` -to the enhancement request. -You will have to copy the contents to a file with txt extension before attaching it. -Or, better, install hidrd-convert and attach the output of -`hidrd-convert -o spec /sys/class/hidraw/hidrawN/device/report_descriptor` -(To install hidrd on Fedora use `sudo dnf install hidrd`.) - -5. If your device or receiver connects via USB, look at the output of `lsusb` -to find the ID of the device or receiver and also provide the output of -`lsusb -vv -d xxxx:yyyy` where xxxx:yyyy is the ID of the device or receiver. - -If your device can connect in multiple ways - via a receiver, via USB (not just charging via a USB cable), -via Bluetooth - do this for each way it can connect. - -### Adding information about a new device to the Solaar code - -The _D function in `../lib/logitech_receiver/descriptors.py` makes a device known to Solaar. -The usual arguments to the _D function are the device's long name, its short name -(codename), and its HID++ protocol version. -Devices that use HID++ 1.0 need a tuple of known registers (registers) and settings (settings). -Settings can be provided for Devices that use HID++ 2.0 or later, -but Solaar can determine these from the device. -If the device can connect to a receiver, provide its wireless product ID (wpid), -If the device can connect via Bluetooth, provide its Bluetooth product ID (btid). -If the device can connect via a USB cable, provide its USB product ID (usbid), -and the interface it uses to send and receiver HID++ messages (interface - default 2). -The use of a non-default USB interface is the main reason for requiring information about -modern devices to be added to Solaar. +If you have an old Logitech device that shows up in Solaar but has no settings +and you feel that Solaar should be able to do more with the device you can +open an enhancement request for Solaar to better support the device. ## Adding new receivers @@ -129,7 +83,7 @@ a subset of the HID++ 1.0 protocol. Only hardware pairing is supported. -## Supported Devices +## Supported Devices (Historical Interest Only) The device tables below provide a list of some of the devices that Solaar supports, giving their product name, WPID product number, and HID++ protocol information. @@ -255,7 +209,9 @@ so what is important for support is the USB WPID or Bluetooth model ID. | Device | WPID | HID++ | |------------------------------|------|-------| +| G604 Wireless Gaming Mouse | 4085 | 4.2 | | PRO X Superlight Wireless | 4093 | 4.2 | +| PRO X 2 Superstrike | 40BD | 4.2 | ### Trackballs (Unifying) diff --git a/docs/devices/EX100 Receiver 27 Mhz C517.text b/docs/devices/EX100 Receiver 27 Mhz C517.text index 8fdbbe97..8b050c42 100644 --- a/docs/devices/EX100 Receiver 27 Mhz C517.text +++ b/docs/devices/EX100 Receiver 27 Mhz C517.text @@ -1,11 +1,35 @@ -./bin/solaar probe +solaar version 1.1.11-80-gdea496f + EX100 Receiver 27 Mhz - Device path : /dev/hidraw3 + Device path : /dev/hidraw2 USB id : 046d:C517 Serial : None - Has 1 paired device(s) out of a maximum of 4. + Has 2 paired device(s) out of a maximum of 4. Notifications: wireless (0x000100) + 1: Wireless Mouse EX100 + Device path : /dev/hidraw3 + WPID : 003F + Codename : EX100m + Kind : mouse + Protocol : HID++ 1.0 + Serial number: + The power switch is located on the (unknown). + Notifications: roller V, mouse extra buttons, battery status, roller H (0x3C0000). + Battery: good, discharging. + + 3: Wireless Keyboard EX100 + Device path : /dev/hidraw6 + WPID : 0065 + Codename : EX100 + Kind : keyboard + Protocol : HID++ 1.0 + Serial number: + The power switch is located on the (unknown). + Notifications: keyboard multimedia raw, battery status (0x110000). + Battery: good, discharging. + + Register Dump Notifications 0x00: 0x000100 Connection State 0x02: 0x000100 diff --git a/docs/devices/G305 Lightspeed Wireless Gaming Mouse 4074.text b/docs/devices/G305 Lightspeed Wireless Gaming Mouse 4074.text new file mode 100644 index 00000000..0fe7c9a5 --- /dev/null +++ b/docs/devices/G305 Lightspeed Wireless Gaming Mouse 4074.text @@ -0,0 +1,58 @@ +solaar version 1.1.10 + + 1: G305 Lightspeed Wireless Gaming Mouse + Device path : /dev/hidraw7 + WPID : 4074 + Codename : G305 + Kind : mouse + Protocol : HID++ 4.2 + Polling rate : 1 ms (1000Hz) + Serial number: ED5E9515 + Model ID: 407400000000 + Unit ID: F074D567 + Bootloader: BOT 69.02.B0021 + Firmware: RQM 68.02.B0021 + The power switch is located on the base. + Supports 27 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V2 + Firmware: Bootloader BOT 69.02.B0021 4074452F3940 + Firmware: Firmware RQM 68.02.B0021 4074452F3940 + Unit ID: F074D567 Model ID: 407400000000 Transport IDs: {'wpid': '4074'} + 3: DEVICE NAME {0005} V0 + Name: G305 Lightspeed Wireless Gaming Mouse + Kind: mouse + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: BATTERY STATUS {1000} V0 + Battery: 50%, discharging, next level 30%. + 6: COLOR LED EFFECTS {8070} V6 + 7: ONBOARD PROFILES {8100} V0 + Device Mode: On-Board + Onboard Profiles (saved): Enable + Onboard Profiles : Enable + 8: MOUSE BUTTON SPY {8110} V0 + 9: REPORT RATE {8060} V0 + Polling Rate (ms): 1 + Polling Rate (ms) (saved): 1 + Polling Rate (ms) : 1 + 10: MODE STATUS {8090} V1 + 11: DFUCONTROL SIGNED {00C2} V0 + 12: DEVICE RESET {1802} V0 internal, hidden + 13: unknown:1803 {1803} V0 internal, hidden + 14: CONFIG DEVICE PROPS {1806} V4 internal, hidden + 15: unknown:1811 {1811} V0 internal, hidden + 16: OOBSTATE {1805} V0 internal, hidden + 17: unknown:1830 {1830} V0 internal, hidden + 18: unknown:1890 {1890} V0 internal, hidden + 19: unknown:1DF3 {1DF3} V0 internal, hidden + 20: unknown:1E00 {1E00} V0 hidden + 21: unknown:1EB0 {1EB0} V0 internal, hidden + 22: unknown:1861 {1861} V0 internal, hidden + 23: unknown:18B1 {18B1} V0 internal, hidden + 24: unknown:1E22 {1E22} V0 internal, hidden + 25: unknown:1801 {1801} V0 internal, hidden + 26: ADJUSTABLE DPI {2201} V1 + Sensitivity (DPI) (saved): 1600 + Sensitivity (DPI) : 1600 + Battery: 50%, discharging, next level 30%. diff --git a/docs/devices/G502 Lightspeed Wireless Gaming Mouse 407F.txt b/docs/devices/G502 Lightspeed Wireless Gaming Mouse 407F.txt index f69ae099..fefbfae9 100644 --- a/docs/devices/G502 Lightspeed Wireless Gaming Mouse 407F.txt +++ b/docs/devices/G502 Lightspeed Wireless Gaming Mouse 407F.txt @@ -1,18 +1,18 @@ -Solaar version 1.1.7 +solaar version 1.1.12rc1 1: G502 Gaming Mouse - Device path : /dev/hidraw4 + Device path : /dev/hidraw20 WPID : 407F Codename : G502 Kind : mouse Protocol : HID++ 4.2 - Polling rate : 1 ms (1000Hz) - Serial number: 1F2DBC7E + Report Rate : 1ms + Serial number: DDDAADBC Model ID: 407FC08D0000 - Unit ID: 1F2DBC7E - Bootloader: BOT 92.00.B0008 - Firmware: MPM 17.00.B0008 - Other: + Unit ID: DDDAADBC + 1: BOT 92.00.B0008 + 0: MPM 17.00.B0008 + 3: The power switch is located on the base. Supports 30 HID++ 2.0 features: 0: ROOT {0000} V0 @@ -21,28 +21,34 @@ Solaar version 1.1.7 Firmware: Bootloader BOT 92.00.B0008 AAEF21F1FA5F Firmware: Firmware MPM 17.00.B0008 407F21F1FA5F Firmware: Other - Unit ID: 1F2DBC7E Model ID: 407FC08D0000 Transport IDs: {'wpid': '407F', 'usbid': 'C08D'} + Unit ID: DDDAADBC Model ID: 407FC08D0000 Transport IDs: {'wpid': '407F', 'usbid': 'C08D'} 3: DEVICE NAME {0005} V0 Name: G502 LIGHTSPEED Wireless Gaming Mouse Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} V0 - 5: RESET {0020} V0 + 5: CONFIG CHANGE {0020} V0 + Configuration: 11000000000000000000000000000000 6: BATTERY VOLTAGE {1001} V2 - Battery: 70% 3978mV , discharging. + Battery: 90% 4166mV , discharging. 7: COLOR LED EFFECTS {8070} V4 + LED Control (saved): Device + LED Control : Device + LEDs Primary (saved): !LEDEffectSetting {ID: 1, color: 16711680, intensity: 0, period: 100, ramp: 0, speed: 0} + LEDs Primary : None + LEDs Logo : None 8: LED CONTROL {1300} V0 9: ONBOARD PROFILES {8100} V0 Device Mode: On-Board - Onboard Profiles (saved): Enable - Onboard Profiles : Enable + Onboard Profiles (saved): Profile 1 + Onboard Profiles : Profile 1 10: MOUSE BUTTON SPY {8110} V0 11: REPORT RATE {8060} V0 - Polling Rate (ms): 1 - Polling Rate (ms) (saved): 1 - Polling Rate (ms) : 1 + Report Rate: 1ms + Report Rate (saved): 1ms + Report Rate : 1ms 12: ADJUSTABLE DPI {2201} V1 - Sensitivity (DPI) (saved): 800 - Sensitivity (DPI) : 800 + Sensitivity (DPI) (saved): 900 + Sensitivity (DPI) : 900 13: DEVICE RESET {1802} V0 internal, hidden 14: unknown:1803 {1803} V0 internal, hidden 15: OOBSTATE {1805} V0 internal, hidden @@ -63,12 +69,12 @@ Solaar version 1.1.7 Multiplier: 8 Has invert: Normal wheel motion Has ratchet switch: Normal wheel mode - Low resolution mode + High resolution mode HID notification Scroll Wheel Direction (saved): False Scroll Wheel Direction : False - Scroll Wheel Resolution (saved): False - Scroll Wheel Resolution : False + Scroll Wheel Resolution (saved): True + Scroll Wheel Resolution : True Scroll Wheel Diversion (saved): False Scroll Wheel Diversion : False - Battery: 70% 3978mV , discharging. + Battery: 90% 4166mV , discharging. diff --git a/docs/devices/G502 X PLUS 4099.txt b/docs/devices/G502 X PLUS 4099.txt new file mode 100644 index 00000000..25482b4c --- /dev/null +++ b/docs/devices/G502 X PLUS 4099.txt @@ -0,0 +1,101 @@ +solaar version 1.1.19-25-g7520c9cc + + 1: G502 X PLUS + Device path : /dev/hidraw8 + WPID : 4099 + Codename : G502 X PLUS + Kind : mouse + Protocol : HID++ 4.2 + Report Rate : 1ms + Serial number: C6884511 + Model ID: 4099C0950000 + Unit ID: C6884511 + 1: BL1 42.00.B0016 + 0: MPM 27.00.B0016 + 3: + 3: + 3: + 3: + 3: + 3: + 3: + 3: + 3: + 3: + The power switch is located on the unknown. + Supports 32 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V4 + Firmware: 1 BL1 42.00.B0016 AB0BFBB13A33 + Firmware: 0 MPM 27.00.B0016 4099FBB13A33 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Firmware: 3 + Unit ID: C6884511 Model ID: 4099C0950000 Transport IDs: {'wpid': '4099', 'usbid': 'C095'} + 3: DEVICE NAME {0005} V0 + Name: G502 X PLUS + Kind: mouse + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + Configuration: 11000000000000000000000000000000 + 6: UNIFIED BATTERY {1004} V3 + Battery: 77%, BatteryStatus.DISCHARGING. + 7: ADJUSTABLE DPI {2201} V2 + Sensitivity (DPI) (saved): 800 + Sensitivity (DPI) : 800 + 8: HIRES WHEEL {2121} V0 + Multiplier: 8 + Has invert: Normal wheel motion + Has ratchet switch: Normal wheel mode + Low resolution mode + HID notification + Scroll Wheel Direction (saved): False + Scroll Wheel Direction : False + Scroll Wheel Resolution (saved): False + Scroll Wheel Resolution : False + Scroll Wheel Diversion (saved): False + Scroll Wheel Diversion : False + 9: RGB EFFECTS {8071} V2 + LED Control (saved): Solaar + LED Control : Solaar + LEDs Primary (saved): !LEDEffectSetting {ID: 0, color: 10820909, intensity: 0, period: 100, ramp: 0, speed: 0} + LEDs Primary : !LEDEffectSetting {ID: 0, color: 10820909, intensity: 0, period: 100, ramp: 0, speed: 0} + 10: PER KEY LIGHTING V2 {8081} V2 + Per-key Lighting (saved): {A:No change, B:No change, C:No change, D:No change, E:No change, F:No change, G:No change, H:No change} + Per-key Lighting : {A:No change, B:No change, C:No change, D:No change, E:No change, F:No change, G:No change, H:No change} + 11: ONBOARD PROFILES {8100} V0 + Device Mode: On-Board + Onboard Profiles (saved): Profile 1 + Onboard Profiles : Profile 1 + 12: MOUSE BUTTON SPY {8110} V0 + 13: REPORT RATE {8060} V0 + Report Rate: 1ms + Report Rate (saved): 1ms + Report Rate : 1ms + 14: FORCE PAIRING {1500} V0 + 15: DFU {00D0} V3 + 16: DEVICE RESET {1802} V0 + 17: unknown:1803 {0318} V0 internal, hidden, unknown:000010 + 18: CONFIG DEVICE PROPS {1806} V8 + 19: unknown:1811 {1118} V0 internal, hidden, unknown:000010 + 20: OOBSTATE {1805} V0 + 21: unknown:1830 {3018} V0 internal, hidden, unknown:000010 + 22: unknown:1875 {7518} V0 internal, hidden, unknown:000010 + 23: unknown:1861 {6118} V0 internal, hidden, unknown:000010 + 24: unknown:1890 {9018} V0 internal, hidden, unknown:000008 + 25: unknown:18A1 {A118} V0 internal, hidden, unknown:000010 + 26: unknown:1801 {0118} V0 internal, hidden, unknown:000010 + 27: unknown:1E00 {001E} V0 hidden + 28: unknown:1E22 {221E} V0 internal, hidden, unknown:000010 + 29: unknown:1EB0 {B01E} V0 internal, hidden, unknown:000010 + 30: unknown:18B1 {B118} V0 internal, hidden, unknown:000010 + 31: unknown:18C0 {C018} V0 internal, hidden, unknown:000010 + Battery: 77%, BatteryStatus.DISCHARGING. diff --git a/docs/devices/G515 LS TKL 40B4.text b/docs/devices/G515 LS TKL 40B4.text new file mode 100644 index 00000000..7351bfba --- /dev/null +++ b/docs/devices/G515 LS TKL 40B4.text @@ -0,0 +1,71 @@ +solaar version 1.1.13+dfsg-1 + + 1: G515 LS TKL + Device path : None + WPID : 40B4 + Codename : G515 LS TKL + Kind : keyboard + Protocol : HID++ 4.2 + Report Rate : 8ms + Serial number: 54FEF928 + Model ID: B38940B4C355 + Unit ID: 54FEF928 + 1: BL2 19.01.B0011 + 3: + 0: MPK 25.01.B0011 + 3: + The power switch is located on the top right corner. + Supports 34 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V6 + Firmware: Bootloader BL2 19.01.B0011 ABD580558692 + Firmware: Other + Firmware: Firmware MPK 25.01.B0011 40B480558692 + Firmware: Other + Unit ID: 54FEF928 Model ID: B38940B4C355 Transport IDs: {'btleid': 'B389', 'wpid': '40B4', 'usbid': 'C355'} + 3: DEVICE NAME {0005} V3 + Name: G515 LS TKL + Kind: keyboard + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + Configuration: 11000000000000000000000000000000 + 6: DEVICE FRIENDLY NAME {0007} V0 + Friendly Name: G515 LS TKL + 7: unknown:0011 {0011} V0 + 8: UNIFIED BATTERY {1004} V5 + Battery: 82%, discharging. + 9: RGB EFFECTS {8071} V4 + LED Control (saved): Solaar + LED Control : Solaar + LEDs Primary (saved): !LEDEffectSetting {ID: 1, color: 16776960, intensity: 26, period: 2167, ramp: 1, speed: 0} + LEDs Primary : HID++ error {'number': 1, 'request': 2537, 'error': 7, 'params': b'\x00'} + 10: PER KEY LIGHTING V2 {8081} V0 + Per-key Lighting (saved): {A:indian red, B:indian red, C:indian red, D:indian red, E:indian red, F:indian red, G:indian red, H:indian red, I:indian red, J:indian red, K:indian red, L:indian red, M:indian red, N:indian red, O:indian red, P:indian red, Q:indian red, R:indian red, S:indian red, T:indian red, U:indian red, V:indian red, W:indian red, X:indian red, Y:indian red, Z:indian red, 1:orange, 2:orange, 3:orange, 4:orange, 5:orange, 6:orange, 7:orange, 8:orange, 9:orange, 0:yellow, ENTER:green, ESC:green, BACKSPACE:red, TAB:yellow, SPACE:yellow, -:indian red, =:indian red, [:indian red, \:indian red, KEY 46:white, ~:indian red, ;:indian red, ':indian red, `:indian red, ,:indian red, .:indian red, /:indian red, CAPS LOCK:red, F1:indian red, F2:indian red, F3:indian red, F4:indian red, F5:indian red, F6:indian red, F7:indian red, F8:indian red, F9:indian red, F10:indian red, F11:indian red, F12:indian red, PRINT:red, SCROLL LOCK:orange, PASTE:indian red, INSERT:green, HOME:indian red, PAGE UP:yellow, DELETE:red, END:indian red, PAGE DOWN:yellow, RIGHT:indian red, LEFT:indian red, DOWN:indian red, UP:indian red, KEY 97:indian red, COMPOSE:white, POWER:white, KEY 100:indian red, KEY 101:red, KEY 102:red, KEY 103:red, LEFT CTRL:indian red, LEFT SHIFT:yellow, LEFT ALT:indian red, LEFT WINDOWS:blue, RIGHT CTRL:indian red, RIGHT SHIFT:yellow, RIGHT ALTGR:blue, RIGHT WINDOWS:indian red, KEY 254:white} + Per-key Lighting : {A:No change, B:No change, C:No change, D:No change, E:No change, F:No change, G:No change, H:No change, I:No change, J:No change, K:No change, L:No change, M:No change, N:No change, O:No change, P:No change, Q:No change, R:No change, S:No change, T:No change, U:No change, V:No change, W:No change, X:No change, Y:No change, Z:No change, 1:No change, 2:No change, 3:No change, 4:No change, 5:No change, 6:No change, 7:No change, 8:No change, 9:No change, 0:No change, ENTER:No change, ESC:No change, BACKSPACE:No change, TAB:No change, SPACE:No change, -:No change, =:No change, [:No change, \:No change, KEY 46:No change, ~:No change, ;:No change, ':No change, `:No change, ,:No change, .:No change, /:No change, CAPS LOCK:No change, F1:No change, F2:No change, F3:No change, F4:No change, F5:No change, F6:No change, F7:No change, F8:No change, F9:No change, F10:No change, F11:No change, F12:No change, PRINT:No change, SCROLL LOCK:No change, PASTE:No change, INSERT:No change, HOME:No change, PAGE UP:No change, DELETE:No change, END:No change, PAGE DOWN:No change, RIGHT:No change, LEFT:No change, DOWN:No change, UP:No change, KEY 97:No change, COMPOSE:No change, POWER:No change, KEY 100:No change, KEY 101:No change, KEY 102:No change, KEY 103:No change, LEFT CTRL:No change, LEFT SHIFT:No change, LEFT ALT:No change, LEFT WINDOWS:No change, RIGHT CTRL:No change, RIGHT SHIFT:No change, RIGHT ALTGR:No change, RIGHT WINDOWS:No change, KEY 254:No change} + 11: unknown:1B10 {1B10} V0 + 12: unknown:4523 {4523} V1 + 13: KEYBOARD LAYOUT 2 {4540} V1 + 14: BRIGHTNESS CONTROL {8040} V0 + Brightness Control (saved): 40 + Brightness Control : 40 + 15: unknown:8101 {8101} V0 + 16: unknown:1B05 {1B05} V0 + 17: unknown:8051 {8051} V0 + 18: DFU {00D0} V3 + 19: DEVICE RESET {1802} V0 internal, hidden, unknown:000010 + 20: unknown:1803 {1803} V1 internal, hidden, unknown:000010 + 21: unknown:1807 {1807} V3 internal, hidden, unknown:000010 + 22: unknown:1817 {1817} V0 internal, hidden, unknown:000010 + 23: OOBSTATE {1805} V0 internal, hidden + 24: unknown:1830 {1830} V0 internal, hidden, unknown:000010 + 25: unknown:1890 {1890} V9 internal, hidden, unknown:000008 + 26: unknown:1891 {1891} V9 internal, hidden, unknown:000008 + 27: unknown:1E00 {1E00} V0 hidden + 28: unknown:1E02 {1E02} V0 internal, hidden + 29: unknown:1602 {1602} V0 + 30: unknown:1EB0 {1EB0} V0 internal, hidden, unknown:000010 + 31: unknown:1861 {1861} V1 internal, hidden, unknown:000010 + 32: unknown:18B0 {18B0} V1 internal, hidden, unknown:000010 + 33: unknown:1801 {1801} V0 internal, hidden, unknown:000010 + Battery: 82%, discharging. diff --git a/docs/devices/G604 Wireless Gaming Mouse 4085.txt b/docs/devices/G604 Wireless Gaming Mouse 4085.txt new file mode 100644 index 00000000..39e9e3cf --- /dev/null +++ b/docs/devices/G604 Wireless Gaming Mouse 4085.txt @@ -0,0 +1,84 @@ +solaar version 03cfa128 + + 1: G604 Wireless Gaming Mouse + Device path : /dev/hidraw6 + WPID : 4085 + Codename : G604 + Kind : mouse + Protocol : HID++ 4.2 + Report Rate : 1ms + Serial number: XXXXXXXX + Model ID: B02440850000 + Unit ID: XXXXXXXX + 1: BL1 04.01.B0014 + 0: MPM 21.01.B0014 + 3: + The power switch is located on the base. + Supports 33 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V2 + Firmware: 1 BL1 04.01.B0014 0000B01B3067 + Firmware: 0 MPM 21.01.B0014 4085B01B3067 + Firmware: 3 + Unit ID: XXXXXXXX Model ID: B02440850000 Transport IDs: {'btleid': 'B024', 'wpid': '4085'} + 3: DEVICE NAME {0005} V0 + Name: G604 Wireless Gaming Mouse + Kind: mouse + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + Configuration: 00000000000000000000000000000000 + 6: BATTERY STATUS {1000} V0 + Battery: 30%, BatteryStatus.DISCHARGING, next level 15%. + 7: COLOR LED EFFECTS {8070} V4 + LED Control (saved): Device + LED Control : Device + LEDs Primary : None + 8: LED CONTROL {1300} V0 + 9: ONBOARD PROFILES {8100} V0 + Device Mode: On-Board + Onboard Profiles (saved): Profile 1 + Onboard Profiles : Profile 1 + 10: MOUSE BUTTON SPY {8110} V0 + 11: REPORT RATE {8060} V0 + Report Rate: 1ms + Report Rate (saved): 1ms + Report Rate : 1ms + 12: ADJUSTABLE DPI {2201} V1 + Sensitivity (DPI) (saved): 800 + Sensitivity (DPI) : 800 + 13: DFUCONTROL SIGNED {00C2} V0 + 14: DEVICE RESET {1802} V0 + 15: unknown:1803 {0318} V0 internal, hidden + 16: OOBSTATE {1805} V0 + 17: CONFIG DEVICE PROPS {1806} V4 + 18: unknown:1813 {1318} V0 internal, hidden + 19: unknown:1830 {3018} V0 internal, hidden + 20: unknown:1890 {9018} V0 internal, hidden + 21: unknown:1891 {9118} V0 internal, hidden + 22: unknown:1861 {6118} V0 internal, hidden + 23: unknown:1801 {0118} V0 internal, hidden + 24: unknown:18B1 {B118} V0 internal, hidden + 25: unknown:1DF3 {F31D} V0 internal, hidden + 26: unknown:1E00 {001E} V0 hidden + 27: unknown:1EB0 {B01E} V0 internal, hidden + 28: unknown:1E22 {221E} V0 internal, hidden + 29: HIRES WHEEL {2121} V0 + Multiplier: 8 + Has invert: Normal wheel motion + Has ratchet switch: Normal wheel mode + High resolution mode + HID notification + Scroll Wheel Direction (saved): False + Scroll Wheel Direction : False + Scroll Wheel Resolution (saved): True + Scroll Wheel Resolution : True + Scroll Wheel Diversion (saved): False + Scroll Wheel Diversion : False + 30: unknown:18C0 {C018} V0 internal, hidden + 31: CHANGE HOST {1814} V1 + Change Host : 1:host1 + 32: HOSTS INFO {1815} V1 + Host 0 (unpaired): host1 + Host 1 (paired): + Battery: 30%, BatteryStatus.DISCHARGING, next level 15%. diff --git a/docs/devices/G613 Wireless Mechanical Gaming Keyboard 4065.txt b/docs/devices/G613 Wireless Mechanical Gaming Keyboard 4065.txt index 3b1642bb..fcab437d 100644 --- a/docs/devices/G613 Wireless Mechanical Gaming Keyboard 4065.txt +++ b/docs/devices/G613 Wireless Mechanical Gaming Keyboard 4065.txt @@ -1,4 +1,4 @@ -Solaar version 1.1.3 +solaar version 1.1.19-25-g7520c9cc 1: G613 Wireless Mechanical Gaming Keyboard Device path : None @@ -6,69 +6,71 @@ Solaar version 1.1.3 Codename : G613 Kind : keyboard Protocol : HID++ 4.2 - Polling rate : 1 ms (1000Hz) - Serial number: 0DBC5FF6 + Report Rate : 1ms + Serial number: 710EC3A3 Model ID: B34F40650000 - Unit ID: 9AAB3225 - Bootloader: BOT 46.00.B0006 - Firmware: MPK 05.02.B0021 - Other: + Unit ID: 2A923B25 + 1: BOT 46.00.B0006 + 0: MPK 05.02.B0021 + 3: + The power switch is located on the unknown. Supports 32 HID++ 2.0 features: - 0: ROOT {0000} - 1: FEATURE SET {0001} - 2: DEVICE FW VERSION {0003} - Firmware: Bootloader BOT 46.00.B0006 00006E86A7BD - Firmware: Firmware MPK 05.02.B0021 40656E86A7BD - Firmware: Other - Unit ID: 9AAB3225 Model ID: B34F40650000 Transport IDs: {'btleid': 'B34F', 'wpid': '4065'} - 3: DEVICE NAME {0005} + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V2 + Firmware: 1 BOT 46.00.B0006 00006E86A7BD + Firmware: 0 MPK 05.02.B0021 40656E86A7BD + Firmware: 3 + Unit ID: 2A923B25 Model ID: B34F40650000 Transport IDs: {'btleid': 'B34F', 'wpid': '4065'} + 3: DEVICE NAME {0005} V0 Name: G613 Wireless Mechanical Gaming Keyboard Kind: keyboard - 4: WIRELESS DEVICE STATUS {1D4B} - 5: RESET {0020} - 6: DEVICE FRIENDLY NAME {0007} + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + Configuration: 11000000000000000000000000000000 + 6: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: G613 - 7: BATTERY STATUS {1000} - Battery: 50%, discharging, next level 20%. - 8: CHANGE HOST {1814} - Change Host : 1:video2 - 9: HOSTS INFO {1815} - Host 0 (paired): video2 - Host 1 (paired): CB73CG2 - 10: REPROG CONTROLS V4 {1B04} + 7: BATTERY STATUS {1000} V0 + Battery: 80%, BatteryStatus.DISCHARGING, next level 50%. + 8: CHANGE HOST {1814} V1 + Change Host : 1:cosmo + 9: HOSTS INFO {1815} V1 + Host 0 (paired): cosmo + Host 1 (paired): Mi 11 + 10: REPROG CONTROLS V4 {1B04} V3 Key/Button Diversion (saved): {Host Switch Channel 1:Regular, Host Switch Channel 2:Regular} Key/Button Diversion : {Host Switch Channel 1:Regular, Host Switch Channel 2:Regular} - 11: REPORT HID USAGE {1BC0} - 12: ENCRYPTION {4100} - 13: KEYBOARD DISABLE BY USAGE {4522} - 14: KEYBOARD LAYOUT 2 {4540} - 15: GKEY {8010} - Divert G Keys (saved): True - Divert G Keys : False - 16: REPORT RATE {8060} - Polling Rate (ms): 1 - Polling Rate (ms) (saved): 1 - Polling Rate (ms) : 1 - 17: DFUCONTROL SIGNED {00C2} - 18: DEVICE RESET {1802} internal, hidden - 19: unknown:1803 {1803} internal, hidden - 20: CONFIG DEVICE PROPS {1806} internal, hidden - 21: unknown:1813 {1813} internal, hidden - 22: OOBSTATE {1805} internal, hidden - 23: unknown:1830 {1830} internal, hidden - 24: unknown:1890 {1890} internal, hidden - 25: unknown:1891 {1891} internal, hidden - 26: unknown:18A1 {18A1} internal, hidden - 27: unknown:1DF3 {1DF3} internal, hidden - 28: unknown:1E00 {1E00} hidden - 29: unknown:1EB0 {1EB0} internal, hidden - 30: unknown:1861 {1861} internal, hidden - 31: unknown:18B1 {18B1} internal, hidden + 11: REPORT HID USAGE {1BC0} V1 + 12: ENCRYPTION {4100} V0 + 13: KEYBOARD DISABLE BY USAGE {4522} V0 + 14: KEYBOARD LAYOUT 2 {4540} V0 + 15: GKEY {8010} V0 + Divert G and M Keys (saved): False + Divert G and M Keys : False + 16: REPORT RATE {8060} V0 + Report Rate: 1ms + Report Rate (saved): 1ms + Report Rate : 1ms + 17: DFUCONTROL SIGNED {00C2} V0 + 18: DEVICE RESET {1802} V0 + 19: unknown:1803 {0318} V0 internal, hidden + 20: CONFIG DEVICE PROPS {1806} V3 + 21: unknown:1813 {1318} V0 internal, hidden + 22: OOBSTATE {1805} V0 + 23: unknown:1830 {3018} V0 internal, hidden + 24: unknown:1890 {9018} V0 internal, hidden + 25: unknown:1891 {9118} V0 internal, hidden + 26: unknown:18A1 {A118} V0 internal, hidden + 27: unknown:1DF3 {F31D} V0 internal, hidden + 28: unknown:1E00 {001E} V0 hidden + 29: unknown:1EB0 {B01E} V0 internal, hidden + 30: unknown:1861 {6118} V0 internal, hidden + 31: unknown:18B1 {B118} V0 internal, hidden Has 2 reprogrammable keys: - 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 - divertable, persistently divertable, pos:1, group:0, group mask:empty + 0: Host Switch Channel 1 , default: Hostswitch Channel 1 => Hostswitch Channel 1 + persistently_divertable, divertable, pos:1, group:0, group mask:empty reporting: default - 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 - divertable, persistently divertable, pos:2, group:0, group mask:empty + 1: Host Switch Channel 2 , default: Hostswitch Channel 2 => Hostswitch Channel 2 + persistently_divertable, divertable, pos:2, group:0, group mask:empty reporting: default - Battery: 50%, discharging, next level 20%. + Battery: 80%, BatteryStatus.DISCHARGING, next level 50%. diff --git a/docs/devices/G733 Gaming Headset 0AB5.text b/docs/devices/G733 Gaming Headset 0AB5.text index d91245e5..2e1125db 100644 --- a/docs/devices/G733 Gaming Headset 0AB5.text +++ b/docs/devices/G733 Gaming Headset 0AB5.text @@ -1,7 +1,7 @@ -Solaar version 1.1.5 +Solaar version 1.1.19 - 2: G733 Gaming Headset - Device path : /dev/hidraw2 +G733 Gaming Headset + Device path : /dev/hidraw0 USB id : 046d:0AB5 Codename : G733 Headset Kind : headset @@ -9,24 +9,34 @@ Solaar version 1.1.5 Serial number: Model ID: 0AB500000000 Unit ID: FFFFFFFF - Firmware: U1 37.00.B0131 + 0: U1 37.00.B0131 Supports 9 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V2 - Firmware: Firmware U1 37.00.B0131 0AB5 + Firmware: 0 U1 37.00.B0131 0AB5 Unit ID: FFFFFFFF Model ID: 0AB500000000 Transport IDs: {'usbid': '0AB5'} 3: DEVICE NAME {0005} V0 Name: G733 Gaming Headset Kind: None - 4: COLOR LED EFFECTS {8070} V0 + 4: COLOR LED EFFECTS {8070} V3 + Sterowanie diodami LED (saved): Device + Sterowanie diodami LED : Device + Diody LED None (saved): !LEDEffectSetting {ID: 0} + Diody LED None : !LEDEffectSetting {ID: 0} + Diody LED None (saved): !LEDEffectSetting {ID: 1, color: 131072, ramp: 0} + Diody LED None : !LEDEffectSetting {ID: 1, color: 66048, ramp: 0} 5: GKEY {8010} V0 - Divert G Keys (saved): True - Divert G Keys : False - 6: EQUALIZER {8310} V0 + Przekieruj klawisze G i M (saved): False + Przekieruj klawisze G i M : False + 6: EQUALIZER {8310} V1 + Korektor (saved): {0: 5, 1: 4, 2: 3, 3: 5, 4: 5, 5: 5, 6: 4, 7: 3, 8: 4, 9: 5} + Korektor : {0: 5, 1: 4, 2: 3, 3: 5, 4: 5, 5: 5, 6: 4, 7: 3, 8: 4, 9: 5} 7: SIDETONE {8300} V0 - Sidetone (saved): 65 - Sidetone : 65 - 8: ADC MEASUREMENT {1F20} V0 - Battery status unavailable. - Battery status unavailable. + Efekt lokalny (saved): 0 + Efekt lokalny : 0 + 8: ADC MEASUREMENT {1F20} V4 + Battery: 89% 4058mV , BatteryStatus.DISCHARGING. + Zarządzanie energią (saved): 30 + Zarządzanie energią : 30 + Battery: 89% 4058mV , BatteryStatus.DISCHARGING. diff --git a/docs/devices/G733 Gaming Headset 0AFE.text b/docs/devices/G733 Gaming Headset 0AFE.text new file mode 100644 index 00000000..9f4d9383 --- /dev/null +++ b/docs/devices/G733 Gaming Headset 0AFE.text @@ -0,0 +1,42 @@ +solaar version 1.1.11 + +G733 Gaming Headset + Device path : /dev/hidraw3 + USB id : 046d:0AFE + Codename : G733 Headset New + Kind : headset + Protocol : HID++ 4.2 + Serial number: + Model ID: 0AFE00000000 + Unit ID: FFFFFFFF + Firmware: U2 00.06 + Supports 9 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V2 + Firmware: Firmware U2 00.06 0AFE + Unit ID: FFFFFFFF Model ID: 0AFE00000000 Transport IDs: {'usbid': '0AFE'} + 3: DEVICE NAME {0005} V0 + Name: G733 Gaming Headset + Kind: None + 4: COLOR LED EFFECTS {8070} V3 + LED Control (saved): Device + LED Control : Device + LEDs Logo (saved): !LEDEffectSetting {ID: 0x0} + LEDs Logo : !LEDEffectSetting {ID: 0} + LEDs Primary (saved): !LEDEffectSetting {ID: 0x1, color: 0x0, ramp: 0x0} + LEDs Primary : !LEDEffectSetting {ID: 1, color: 0x10000, ramp: 0x0} + 5: GKEY {8010} V0 + Divert G and M Keys (saved): False + Divert G and M Keys : False + 6: EQUALIZER {8310} V1 + Equalizer (saved): {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0} + Equalizer : {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0} + 7: SIDETONE {8300} V0 + Sidetone (saved): 0 + Sidetone : 0 + 8: ADC MEASUREMENT {1F20} V4 + Battery: 60% 3867mV , discharging. + Power Management (saved): 0 + Power Management : 0 + Battery: 60% 3867mV , discharging. diff --git a/docs/devices/G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD 407C.text b/docs/devices/G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD 407C.text new file mode 100644 index 00000000..34a573d2 --- /dev/null +++ b/docs/devices/G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD 407C.text @@ -0,0 +1,103 @@ +solaar version 1.1.12rc1 + + 1: G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD + Device path : None + WPID : 407C + Codename : G915 KEYBOARD + Kind : keyboard + Protocol : HID++ 4.2 + Report Rate : 1ms + Serial number: A502B0E1 + Model ID: B354407CC33E + Unit ID: A502B0E1 + 1: BOT 77.02.B0039 + 3: + 0: MPK 09.03.B0041 + 3: + 3: + The power switch is located on the top left corner. + Supports 38 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V4 + Firmware: Bootloader BOT 77.02.B0039 0000EC44D534 + Firmware: Other + Firmware: Firmware MPK 09.03.B0041 407C3791543D + Firmware: Other + Firmware: Other + Unit ID: A502B0E1 Model ID: B354407CC33E Transport IDs: {'btleid': 'B354', 'wpid': '407C', 'usbid': 'C33E'} + 3: DEVICE NAME {0005} V0 + Name: G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD + Kind: keyboard + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + Configuration: 11000000000000000000000000000000 + 6: DEVICE FRIENDLY NAME {0007} V0 + Friendly Name: G915 KEYBOARD� + 7: BATTERY VOLTAGE {1001} V3 + Battery: 80% 3998mV , discharging. + 8: CHANGE HOST {1814} V1 + Change Host : 1:Yon + 9: HOSTS INFO {1815} V1 + Host 0 (paired): Yon + Host 1 (paired): + 10: RGB EFFECTS {8071} V0 + RGB Control (saved): Device + RGB Control : Device + LEDs Logo (saved): !LEDEffectSetting {ID: 1, color: 11546720, intensity: 0, period: 100, ramp: 0, speed: 0} + LEDs Logo : HID++ error {'number': 1, 'request': 2799, 'error': 7, 'params': b'\x00'} + LEDs Primary (saved): !LEDEffectSetting {ID: 1, color: 16776960, intensity: 0, period: 100, ramp: 0, speed: 0} + LEDs Primary : HID++ error {'number': 1, 'request': 2796, 'error': 7, 'params': b'\x01'} + 11: PER KEY LIGHTING V2 {8081} V2 + Per-key Lighting (saved): {A:white, B:red, C:white, D:white, E:white, F:white, G:white, H:white, I:white, J:white, K:white, L:white, M:white, N:white, O:white, P:white, Q:white, R:white, S:white, T:white, U:white, V:white, W:white, X:white, Y:white, Z:white, 1:white, 2:white, 3:white, 4:white, 5:white, 6:white, 7:white, 8:white, 9:white, 0:white, ENTER:white, ESC:white, BACKSPACE:white, TAB:white, SPACE:white, -:white, =:white, [:white, \:white, KEY 46:white, ~:white, ;:white, ':white, `:white, ,:white, .:white, /:white, CAPS LOCK:white, F1:white, F2:white, F3:white, F4:white, F5:white, F6:white, F7:white, F8:white, F9:white, F10:white, F11:white, F12:white, PRINT:white, SCROLL LOCK:white, PASTE:white, INSERT:white, HOME:white, PAGE UP:white, DELETE:white, END:white, PAGE DOWN:white, RIGHT:white, LEFT:white, DOWN:white, UP:white, NUMLOCK:white, KEYPAD /:white, KEYPAD *:white, KEYPAD -:white, KEYPAD +:white, KEYPAD ENTER:white, KEYPAD 1:white, KEYPAD 2:white, KEYPAD 3:white, KEYPAD 4:white, KEYPAD 5:white, KEYPAD 6:white, KEYPAD 7:white, KEYPAD 8:white, KEYPAD 9:white, KEYPAD 0:white, KEYPAD .:white, KEY 97:white, COMPOSE:white, POWER:white, KEY 100:white, KEY 101:white, KEY 102:white, KEY 103:white, LEFT CTRL:white, LEFT SHIFT:white, LEFT ALT:white, LEFT WINDOWS:white, RIGHT CTRL:white, RIGHT SHIFT:white, RIGHT ALTGR:white, RIGHT WINDOWS:white, BRIGHTNESS:white, PAUSE:white, MUTE:white, NEXT:white, PREVIOUS:white, G1:white, G2:white, G3:white, G4:white, G5:white, LOGO:white} + Per-key Lighting : {A:white, B:white, C:white, D:white, E:white, F:white, G:white, H:white, I:white, J:white, K:white, L:white, M:white, N:white, O:white, P:white, Q:white, R:white, S:white, T:white, U:white, V:white, W:white, X:white, Y:white, Z:white, 1:white, 2:white, 3:white, 4:white, 5:white, 6:white, 7:white, 8:white, 9:white, 0:white, ENTER:white, ESC:white, BACKSPACE:white, TAB:white, SPACE:white, -:white, =:white, [:white, \:white, KEY 46:white, ~:white, ;:white, ':white, `:white, ,:white, .:white, /:white, CAPS LOCK:white, F1:white, F2:white, F3:white, F4:white, F5:white, F6:white, F7:white, F8:white, F9:white, F10:white, F11:white, F12:white, PRINT:white, SCROLL LOCK:white, PASTE:white, INSERT:white, HOME:white, PAGE UP:white, DELETE:white, END:white, PAGE DOWN:white, RIGHT:white, LEFT:white, DOWN:white, UP:white, NUMLOCK:white, KEYPAD /:white, KEYPAD *:white, KEYPAD -:white, KEYPAD +:white, KEYPAD ENTER:white, KEYPAD 1:white, KEYPAD 2:white, KEYPAD 3:white, KEYPAD 4:white, KEYPAD 5:white, KEYPAD 6:white, KEYPAD 7:white, KEYPAD 8:white, KEYPAD 9:white, KEYPAD 0:white, KEYPAD .:white, KEY 97:white, COMPOSE:white, POWER:white, KEY 100:white, KEY 101:white, KEY 102:white, KEY 103:white, LEFT CTRL:white, LEFT SHIFT:white, LEFT ALT:white, LEFT WINDOWS:white, RIGHT CTRL:white, RIGHT SHIFT:white, RIGHT ALTGR:white, RIGHT WINDOWS:white, BRIGHTNESS:white, PAUSE:white, MUTE:white, NEXT:white, PREVIOUS:white, G1:white, G2:white, G3:white, G4:white, G5:white, LOGO:white} + 12: REPROG CONTROLS V4 {1B04} V4 + Key/Button Diversion (saved): {Host Switch Channel 1:Regular, Host Switch Channel 2:Regular} + Key/Button Diversion : {Host Switch Channel 1:Regular, Host Switch Channel 2:Regular} + 13: REPORT HID USAGE {1BC0} V1 + 14: ENCRYPTION {4100} V0 + 15: KEYBOARD DISABLE BY USAGE {4522} V0 + 16: KEYBOARD LAYOUT 2 {4540} V0 + 17: GKEY {8010} V0 + Divert G and M Keys (saved): False + Divert G and M Keys : False + 18: MKEYS {8020} V0 + M-Key LEDs (saved): {M1:False, M2:False, M3:False} + M-Key LEDs : {M1:False, M2:False, M3:False} + 19: MR {8030} V0 + MR-Key LED (saved): False + MR-Key LED : False + 20: BRIGHTNESS CONTROL {8040} V0 + Brightness Control (saved): 12 + Brightness Control : 12 + 21: ONBOARD PROFILES {8100} V0 + Device Mode: Host + Onboard Profiles (saved): Disabled + Onboard Profiles : Disabled + 22: REPORT RATE {8060} V0 + Report Rate: 1ms + Report Rate (saved): 1ms + Report Rate : 1ms + 23: DFUCONTROL SIGNED {00C2} V0 + 24: DFU {00D0} V3 + 25: DEVICE RESET {1802} V0 internal, hidden + 26: unknown:1803 {1803} V0 internal, hidden + 27: CONFIG DEVICE PROPS {1806} V8 internal, hidden + 28: unknown:1813 {1813} V0 internal, hidden + 29: OOBSTATE {1805} V0 internal, hidden + 30: unknown:1830 {1830} V0 internal, hidden + 31: unknown:1890 {1890} V5 internal, hidden + 32: unknown:1891 {1891} V5 internal, hidden + 33: unknown:18A1 {18A1} V0 internal, hidden + 34: unknown:1E00 {1E00} V0 hidden + 35: unknown:1EB0 {1EB0} V0 internal, hidden + 36: unknown:1861 {1861} V0 internal, hidden + 37: unknown:18B0 {18B0} V0 internal, hidden + Has 2 reprogrammable keys: + 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 + divertable, persistently divertable, pos:1, group:0, group mask:empty + reporting: default + 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 + divertable, persistently divertable, pos:2, group:0, group mask:empty + reporting: default + Battery: 80% 3998mV , discharging. diff --git a/docs/devices/G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD C33E.text b/docs/devices/G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD C33E.text new file mode 100644 index 00000000..0dda3f5b --- /dev/null +++ b/docs/devices/G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD C33E.text @@ -0,0 +1,91 @@ +solaar version 1.1.10 + +USB and Bluetooth Devices + + 1: G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD + Device path : /dev/hidraw13 + USB id : 046d:C33E + Codename : G915 + Kind : ? + Protocol : HID++ 4.2 + Polling rate : 1 ms (1000Hz) + Serial number: + Model ID: B354407CC33E + Unit ID: 8816D0DF + Bootloader: BOT 77.03.B0041 + Other: + Firmware: MPK 09.04.B0042 + Other: + Other: + Supports 37 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V4 + Firmware: Bootloader BOT 77.03.B0041 00003791543D + Firmware: Other + Firmware: Firmware MPK 09.04.B0042 C33E8A23A76B + Firmware: Other + Firmware: Other + Unit ID: 8816D0DF Model ID: B354407CC33E Transport IDs: {'btleid': 'B354', 'wpid': '407C', 'usbid': 'C33E'} + 3: DEVICE NAME {0005} V0 + Name: G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD + Kind: keyboard + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + 6: BATTERY VOLTAGE {1001} V3 + Battery: 70% 3965mV , recharging. + 7: CHANGE HOST {1814} V1 + Changer d'hôte : 1:stagcrown + 8: HOSTS INFO {1815} V1 + Host 0 (paired): stagcrown + Host 1 (paired): + 9: RGB EFFECTS {8071} V0 + 10: PER KEY LIGHTING V2 {8081} V2 + 11: REPROG CONTROLS V4 {1B04} V4 + Interception des boutons/touches (saved): {Host Switch Channel 1:Interception, Host Switch Channel 2:Interception} + Interception des boutons/touches : {Host Switch Channel 1:Interception, Host Switch Channel 2:Interception} + 12: REPORT HID USAGE {1BC0} V1 + 13: ENCRYPTION {4100} V0 + 14: KEYBOARD DISABLE BY USAGE {4522} V0 + 15: KEYBOARD LAYOUT 2 {4540} V0 + 16: GKEY {8010} V0 + Définir les touches G (saved): True + Définir les touches G : False + 17: MKEYS {8020} V0 + LEDs de touche M (saved): {M1:False, M2:False, M3:False} + LEDs de touche M : {M1:False, M2:False, M3:False} + 18: MR {8030} V0 + LED de touche MR (saved): False + LED de touche MR : False + 19: BRIGHTNESS CONTROL {8040} V0 + 20: ONBOARD PROFILES {8100} V0 + Device Mode: On-Board + Profils embarqués (saved): Enable + Profils embarqués : Enable + 21: REPORT RATE {8060} V0 + Polling Rate (ms): 1 + Taux de scrutation (ms) (saved): 1 + Taux de scrutation (ms) : 1 + 22: DFUCONTROL SIGNED {00C2} V0 + 23: DFU {00D0} V3 + 24: DEVICE RESET {1802} V0 internal, hidden + 25: unknown:1803 {1803} V0 internal, hidden + 26: CONFIG DEVICE PROPS {1806} V8 internal, hidden + 27: unknown:1813 {1813} V0 internal, hidden + 28: OOBSTATE {1805} V0 internal, hidden + 29: unknown:1830 {1830} V0 internal, hidden + 30: unknown:1890 {1890} V9 internal, hidden + 31: unknown:1891 {1891} V9 internal, hidden + 32: unknown:18A1 {18A1} V0 internal, hidden + 33: unknown:1E00 {1E00} V0 hidden + 34: unknown:1EB0 {1EB0} V0 internal, hidden + 35: unknown:1861 {1861} V0 internal, hidden + 36: unknown:18B0 {18B0} V0 internal, hidden + Has 2 reprogrammable keys: + 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 + divertable, persistently divertable, pos:1, group:0, group mask:empty + reporting: diverted + 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 + divertable, persistently divertable, pos:2, group:0, group mask:empty + reporting: diverted + Battery: 70% 3965mV , recharging. diff --git a/docs/devices/Logitech G933 Gaming Wireless Headset 0A5B.txt b/docs/devices/Logitech G933 Gaming Wireless Headset 0A5B.txt new file mode 100644 index 00000000..d48fb58d --- /dev/null +++ b/docs/devices/Logitech G933 Gaming Wireless Headset 0A5B.txt @@ -0,0 +1,41 @@ +solaar version 1.1.19-25-g7520c9cc + +Logitech G933 Gaming Wireless Headset + Device path : /dev/hidraw4 + USB id : 046d:0A5B + Codename : Logitech + Kind : ? + Protocol : HID++ 4.2 + Serial number: + Model ID: 000000000A5B + Unit ID: FFFFFFFF + 0: U 98.03.B0027 + Supports 9 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V2 + Firmware: 0 U 98.03.B0027 0A5B + Unit ID: FFFFFFFF Model ID: 000000000A5B Transport IDs: {'btid': '0000', 'btleid': '0000'} + 3: DEVICE NAME {0005} V0 + Name: Logitech G933 Gaming Wireless Headset + Kind: None + 4: COLOR LED EFFECTS {8070} V3 + LED Control : HID++ error {'number': 255, 'request': 1147, 'error': 7, 'params': b''} + LEDs None (saved): !LEDEffectSetting {ID: 0} + LEDs None : !LEDEffectSetting {ID: 0} + LEDs None (saved): !LEDEffectSetting {ID: 0, color: 9519532, intensity: 0, period: 100, ramp: 0, speed: 0} + LEDs None : !LEDEffectSetting {ID: 1, color: 0, ramp: 0} + 5: GKEY {8010} V0 + Divert G and M Keys (saved): False + Divert G and M Keys : False + 6: EQUALIZER {8310} V1 + Equalizer (saved): {0: 8, 1: 8, 2: 4, 3: 2, 4: 1, 5: 4, 6: 7, 7: 10, 8: 5, 9: 11} + Equalizer : {0: 8, 1: 8, 2: 4, 3: 2, 4: 1, 5: 4, 6: 7, 7: 10, 8: 5, 9: 11} + 7: SIDETONE {8300} V0 + Sidetone (saved): 30 + Sidetone : 30 + 8: ADC MEASUREMENT {1F20} V3 + Battery: 100% 4183mV , BatteryStatus.RECHARGING. + Power Management (saved): 30 + Power Management : 30 + Battery: 100% 4183mV , BatteryStatus.RECHARGING. diff --git a/docs/devices/MX Anywhere 3 for Business B02D.txt b/docs/devices/MX Anywhere 3 for Business B02D.txt new file mode 100644 index 00000000..9fbc1982 --- /dev/null +++ b/docs/devices/MX Anywhere 3 for Business B02D.txt @@ -0,0 +1,100 @@ +solaar version 1.1.14 + + 1: MX Anywhere 3 for Business + Device path : None + WPID : B02D + Codename : MX Anywhere 3 + Kind : mouse + Protocol : HID++ 4.5 + Serial number: 00000000 + Model ID: B02D00000000 + Unit ID: 00000000 + 1: BL1 36.01.B0011 + 0: RBM 15.01.B0011 + 3: + The power switch is located on the (unknown). + Supports 35 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V4 + Firmware: 1 BL1 36.01.B0011 B02D1EEFD8F8 + Firmware: 0 RBM 15.01.B0011 B02D1EEFD8F8 + Firmware: 3 + Unit ID: 00000000 Model ID: B02D00000000 Transport IDs: {'btleid': 'B02D'} + 3: DEVICE NAME {0005} V0 + Name: MX Anywhere 3 for Business + Kind: mouse + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + Configuration: 11000000000000000000000000000000 + 6: CRYPTO ID {0021} V1 + 7: DEVICE FRIENDLY NAME {0007} V0 + Friendly Name: MX Anywhere 3B + 8: UNIFIED BATTERY {1004} V3 + Battery: 75%, 0. + 9: REPROG CONTROLS V4 {1B04} V5 + Key/Button Actions : {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Smart Shift:Smart Shift} + Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Smart Shift:Diverted} + 10: CHANGE HOST {1814} V1 + Change Host : 2:archlinux + 11: HOSTS INFO {1815} V2 + Host 0 (paired): archlinux + Host 1 (paired): archlinux + Host 2 (unpaired): + 12: XY STATS {2250} V1 + 13: ADJUSTABLE DPI {2201} V2 + Sensitivity (DPI) : 1000 + 14: SMART SHIFT ENHANCED {2111} V0 + Scroll Wheel Ratcheted : Ratcheted + Scroll Wheel Ratchet Speed : 15 + 15: HIRES WHEEL {2121} V1 + Multiplier: 15 + Has invert: Normal wheel motion + Has ratchet switch: Normal wheel mode + Low resolution mode + HID notification + Scroll Wheel Direction : False + Scroll Wheel Resolution : False + Scroll Wheel Diversion : False + 16: WHEEL STATS {2251} V0 + 17: DFUCONTROL {00C3} V0 + 18: DEVICE RESET {1802} V0 internal, hidden, unknown:000010 + 19: unknown:1803 {1803} V0 internal, hidden, unknown:000010 + 20: CONFIG DEVICE PROPS {1806} V8 internal, hidden, unknown:000010 + 21: unknown:1816 {1816} V0 internal, hidden, unknown:000010 + 22: OOBSTATE {1805} V0 internal, hidden + 23: unknown:1830 {1830} V0 internal, hidden, unknown:000010 + 24: unknown:1891 {1891} V7 internal, hidden, unknown:000008 + 25: unknown:18A1 {18A1} V0 internal, hidden, unknown:000010 + 26: unknown:1E00 {1E00} V0 hidden + 27: unknown:1E02 {1E02} V0 internal, hidden + 28: unknown:1602 {1602} V0 + 29: unknown:1EB0 {1EB0} V0 internal, hidden, unknown:000010 + 30: unknown:1861 {1861} V1 internal, hidden, unknown:000010 + 31: unknown:9300 {9300} V1 internal, hidden, unknown:000010 + 32: unknown:9001 {9001} V0 internal, hidden, unknown:000010 + 33: unknown:1E22 {1E22} V0 internal, hidden, unknown:000010 + 34: unknown:9205 {9205} V0 internal, hidden, unknown:000010 + Has 7 reprogrammable keys: + 0: Left Button , default: Left Click => Left Click + mse, analytics key events, pos:0, group:1, group mask:g1 + reporting: default + 1: Right Button , default: Right Click => Right Click + mse, analytics key events, pos:0, group:1, group mask:g1 + reporting: default + 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button + mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 + reporting: default + 3: Back Button , default: Mouse Back Button => Mouse Back Button + mse, reprogrammable, divertable, raw XY, analytics key events, unknown:000800, pos:0, group:2, group mask:g1,g2 + reporting: default + 4: Forward Button , default: Mouse Forward Button => Mouse Forward Button + mse, reprogrammable, divertable, raw XY, analytics key events, unknown:000800, pos:0, group:2, group mask:g1,g2 + reporting: default + 5: Smart Shift , default: Smart Shift => Smart Shift + mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 + reporting: diverted, raw XY diverted + 6: Virtual Gesture Button , default: Virtual Gesture Button => Virtual Gesture Button + divertable, virtual, raw XY, force raw XY, pos:0, group:3, group mask:empty + reporting: default + Battery: 75%, 0. diff --git a/docs/devices/MX Keys S B378.text b/docs/devices/MX Keys S B378.text new file mode 100644 index 00000000..48670d52 --- /dev/null +++ b/docs/devices/MX Keys S B378.text @@ -0,0 +1,137 @@ +solaar version 1.1.10 + + 1: MX Keys S + Device path : None + WPID : B378 + Codename : MX KEYS S + Kind : keyboard + Protocol : HID++ 4.5 + Serial number: 48548420 + Model ID: B37800000000 + Unit ID: 48548420 + Bootloader: BL1 88.00.B0013 + Firmware: RBK 81.00.B0013 + Other: + The power switch is located on the (unknown). + Supports 34 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V4 + Firmware: Bootloader BL1 88.00.B0013 B37851DB9520 + Firmware: Firmware RBK 81.00.B0013 B37851DB9520 + Firmware: Other + Unit ID: 48548420 Model ID: B37800000000 Transport IDs: {'btleid': 'B378'} + 3: DEVICE NAME {0005} V0 + Name: MX Keys S + Kind: keyboard + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + 6: DEVICE FRIENDLY NAME {0007} V0 + Friendly Name: MX KEYS S + 7: unknown:0011 {0011} V0 + 8: UNIFIED BATTERY {1004} V3 + Battery: 75%, discharging. + 9: REPROG CONTROLS V4 {1B04} V5 + Key/Button Diversion (saved): {Calculator:Regular, Lock PC:Regular, Brightness Down:Regular, Brightness Up:Regular, Backlight Down:Regular, Backlight Up:Regular, Previous Fn:Regular, Play/Pause Fn:Regular, Next Fn:Regular, Mute Fn:Regular, Volume Down Fn:Regular, Volume Up Fn:Regular, App Contextual Menu/Right Click:Regular, Voice Dictation:Regular, Open Emoji Panel:Regular, Snipping Tool:Diverted, Mute Microphone:Regular} + Key/Button Diversion : {Calculator:Regular, Lock PC:Regular, Brightness Down:Regular, Brightness Up:Regular, Backlight Down:Regular, Backlight Up:Regular, Previous Fn:Regular, Play/Pause Fn:Regular, Next Fn:Regular, Mute Fn:Regular, Volume Down Fn:Regular, Volume Up Fn:Regular, App Contextual Menu/Right Click:Regular, Voice Dictation:Regular, Open Emoji Panel:Regular, Snipping Tool:Diverted, Mute Microphone:Regular} + 10: CHANGE HOST {1814} V1 + Change Host : 1:vs + 11: HOSTS INFO {1815} V2 + Host 0 (paired): vs + Host 1 (paired): DEV + Host 2 (unpaired): + 12: BACKLIGHT2 {1982} V3 + Backlight (saved): False + Backlight : True + 13: K375S FN INVERSION {40A3} V0 + Swap Fx function (saved): False + Swap Fx function : False + 14: LOCK KEY STATE {4220} V0 + 15: KEYBOARD DISABLE KEYS {4521} V0 + Disable keys (saved): {Caps Lock:False, Num Lock:False, Scroll Lock:False, Insert:False, Win:False} + Disable keys : {Caps Lock:False, Num Lock:False, Scroll Lock:False, Insert:False, Win:False} + 16: MULTIPLATFORM {4531} V1 + Set OS (saved): Linux + Set OS : Linux + 17: KEYBOARD LAYOUT 2 {4540} V0 + 18: DFUCONTROL {00C3} V0 + 19: DEVICE RESET {1802} V0 internal, hidden, unknown:000010 + 20: unknown:1803 {1803} V0 internal, hidden, unknown:000010 + 21: unknown:1807 {1807} V0 internal, hidden, unknown:000010 + 22: unknown:1816 {1816} V0 internal, hidden, unknown:000010 + 23: OOBSTATE {1805} V0 internal, hidden + 24: unknown:1830 {1830} V0 internal, hidden, unknown:000010 + 25: unknown:1891 {1891} V7 internal, hidden, unknown:000008 + 26: unknown:18A1 {18A1} V0 internal, hidden, unknown:000010 + 27: unknown:1E00 {1E00} V0 hidden + 28: unknown:1E02 {1E02} V0 internal, hidden + 29: unknown:1602 {1602} V0 + 30: unknown:1EB0 {1EB0} V0 internal, hidden, unknown:000010 + 31: unknown:1861 {1861} V1 internal, hidden, unknown:000010 + 32: unknown:1A20 {1A20} V1 internal, hidden, unknown:000010 + 33: unknown:18B0 {18B0} V1 internal, hidden, unknown:000010 + Has 21 reprogrammable keys: + 0: Brightness Down , default: Brightness Down => Brightness Down + is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:1, group:0, group mask:empty + reporting: default + 1: Brightness Up , default: Brightness Up => Brightness Up + is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:2, group:0, group mask:empty + reporting: default + 2: Backlight Down , default: Backlight Down => Backlight Down + is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:3, group:0, group mask:empty + reporting: default + 3: Backlight Up , default: Backlight Up => Backlight Up + is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:4, group:0, group mask:empty + reporting: default + 4: Voice Dictation , default: Voice Dictation => Voice Dictation + is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:5, group:0, group mask:empty + reporting: default + 5: Open Emoji Panel , default: Open Emoji Panel => Open Emoji Panel + is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:6, group:0, group mask:empty + reporting: default + 6: Mute Microphone , default: Mute Microphone => Mute Microphone + is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:7, group:0, group mask:empty + reporting: default + 7: Previous Fn , default: Previous => Previous + is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:8, group:0, group mask:empty + reporting: default + 8: Play/Pause Fn , default: Play/Pause => Play/Pause + is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:9, group:0, group mask:empty + reporting: default + 9: Next Fn , default: Next => Next + is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:10, group:0, group mask:empty + reporting: default + 10: Mute Fn , default: Mute => Mute + is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:11, group:0, group mask:empty + reporting: default + 11: Volume Down Fn , default: Volume Down => Volume Down + is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:12, group:0, group mask:empty + reporting: default + 12: Volume Up Fn , default: Volume Up => Volume Up + nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty + reporting: default + 13: Calculator , default: Calculator => Calculator + nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty + reporting: default + 14: Snipping Tool , default: Snipping Tool => Snipping Tool + nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty + reporting: diverted + 15: App Contextual Menu/Right Click, default: Right Click/App Contextual Menu => Right Click/App Contextual Menu + nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty + reporting: default + 16: Lock PC , default: WindowsLock => WindowsLock + nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty + reporting: default + 17: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 + nonstandard, analytics key events, pos:0, group:0, group mask:empty + reporting: default + 18: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 + nonstandard, analytics key events, pos:0, group:0, group mask:empty + reporting: default + 19: Host Switch Channel 3 , default: HostSwitch Channel 3 => HostSwitch Channel 3 + nonstandard, analytics key events, pos:0, group:0, group mask:empty + reporting: default + 20: F Lock , default: Do Nothing One => Do Nothing One + analytics key events, pos:0, group:0, group mask:empty + reporting: default + Battery: 75%, discharging. diff --git a/docs/devices/MX Master 4.text b/docs/devices/MX Master 4.text new file mode 100644 index 00000000..aedfc34a Binary files /dev/null and b/docs/devices/MX Master 4.text differ diff --git a/docs/devices/PRO X 2 40A9.text b/docs/devices/PRO X 2 40A9.text new file mode 100644 index 00000000..2a39077d --- /dev/null +++ b/docs/devices/PRO X 2 40A9.text @@ -0,0 +1,68 @@ +solaar version 1.1.10 + +Receiver + Device path : /dev/hidraw3 + USB id : 046d:C54D + Serial : 8FF3BF7B + Firmware : 07.00.B0008 + Bootloader : 00.08 + Other : C1.53 + Has 1 paired device(s) out of a maximum of 2. + Notifications: (none) + Device activity counters: 1=51 + + 1: PRO X 2 + Device path : None + WPID : 40A9 + Codename : PRO X 2 + Kind : mouse + Protocol : HID++ 4.2 + Polling rate : 8 ms (125Hz) + Serial number: + Model ID: 40A9C09B0000 + Unit ID: + Bootloader: BL1 71.00.B0012 + Firmware: MPM 32.00.B0012 + Supports 32 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V4 + Firmware: Bootloader BL1 71.00.B0012 AB1CDBC0A7D9 + Firmware: Firmware MPM 32.00.B0012 40A9DBC0A7D9 + Unit ID: Model ID: 40A9C09B0000 Transport IDs: {'wpid': '40A9', 'usbid': 'C09B'} + 3: DEVICE NAME {0005} V2 + Name: PRO X 2 + Kind: mouse + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + 6: UNIFIED BATTERY {1004} V3 + Battery: 96%, discharging. + 7: XY STATS {2250} V1 + 8: WHEEL STATS {2251} V0 + 9: unknown:2202 {2202} V0 EXTENDED_ADJUSTABLE_DPI + 10: MODE STATUS {8090} V2 + 11: unknown:8061 {8061} V0 EXTENDED_ADJUSTABLE_REPORT_RATE + 12: ONBOARD PROFILES {8100} V0 + Device Mode: On-Board + Onboard Profiles (saved): Enable + Onboard Profiles : Enable + 13: MOUSE BUTTON SPY {8110} V0 + 14: unknown:1500 {1500} V0 FORCE_PAIRING + 15: unknown:1801 {1801} V0 internal, hidden, unknown:000010 + 16: DEVICE RESET {1802} V0 internal, hidden, unknown:000010 + 17: unknown:1803 {1803} V0 internal, hidden, unknown:000010 + 18: CONFIG DEVICE PROPS {1806} V8 internal, hidden, unknown:000010 + 19: unknown:1817 {1817} V0 internal, hidden, unknown:000010 + 20: OOBSTATE {1805} V0 internal, hidden + 21: unknown:1830 {1830} V0 internal, hidden, unknown:000010 + 22: unknown:1875 {1875} V0 internal, hidden, unknown:000010 + 23: unknown:1861 {1861} V1 internal, hidden, unknown:000010 + 24: unknown:1890 {1890} V9 internal, hidden, unknown:000008 + 25: unknown:18A1 {18A1} V0 internal, hidden, unknown:000010 + 26: unknown:1E00 {1E00} V0 hidden + 27: unknown:1E02 {1E02} V0 internal, hidden + 28: unknown:1E22 {1E22} V1 internal, hidden, unknown:000010 + 29: unknown:1602 {1602} V0 + 30: unknown:1EB0 {1EB0} V0 internal, hidden, unknown:000010 + 31: unknown:18B1 {18B1} V0 internal, hidden, unknown:000010 + Battery: 96%, discharging. diff --git a/docs/devices/PRO X 2 LIGHTSPEED 0AF7.text b/docs/devices/PRO X 2 LIGHTSPEED 0AF7.text new file mode 100644 index 00000000..fac5a2a0 --- /dev/null +++ b/docs/devices/PRO X 2 LIGHTSPEED 0AF7.text @@ -0,0 +1,47 @@ +solaar version 1.1.19-24-g693159ee + +Centurion Receiver + Device path : /dev/hidraw4 + USB id : 046d:0AF7 + Protocol : Centurion + 1 : 0.02 + Has 1 device(s) out of a maximum of 1. + Supports 5 dongle features: + 0: ROOT {0000} + 1: FEATURE SET {0001} + 2: CENTURION DEVICE INFO {0100} + Firmware: 1 0.02 + Hardware: model 26 rev 255 product 0508 + 3: CENTPP BRIDGE {0003} + 4: CENTURION GENERIC DFU {010A} + + 1: PRO X 2 LIGHTSPEED + Device path : /dev/hidraw4 + USB id : 046d:0AF7 + Codename : PRO X 2 LIGHTSPEED + Kind : headset + Protocol : Centurion 2.6 + Serial number: + Model ID: 0508 + Unit ID: + 1: 0.02 + Supports 10 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: CENTURION DEVICE NAME {0101} V0 + 3: CENTURION DEVICE INFO {0100} V0 + Firmware: 1 0.02 + Serial: + Hardware: model 26 rev 255 product 0508 + 4: CENTURION BATTERY SOC {0104} V0 + Battery: 97%, BatteryStatus.DISCHARGING. + 5: CENTURION GENERIC DFU {010A} V0 + 6: CENTURION AUTO SLEEP {0108} V0 + Auto Sleep Timeout: 10 + 7: HEADSET AUDIO SIDETONE {0604} V0 + Headset Sidetone: 55 + 8: HEADSET MIC SNR {0602} V0 + Mic SNR: True + 9: HEADSET ONBOARD EQ {0636} V0 + EQ: 80Hz:+0dB, 240Hz:+0dB, 750Hz:+0dB, 2200Hz:+0dB, 6600Hz:+0dB + Battery: 97%, BatteryStatus.DISCHARGING. diff --git a/docs/devices/PRO X 2 Superstrike 40BD.txt b/docs/devices/PRO X 2 Superstrike 40BD.txt new file mode 100644 index 00000000..dc16dd8b --- /dev/null +++ b/docs/devices/PRO X 2 Superstrike 40BD.txt @@ -0,0 +1,100 @@ +Solaar version 1.1.19 + + 1: PRO X 2 Superstrike + Device path : None + WPID : 40BD + Codename : PRO X 2 Superstrike + Kind : mouse + Protocol : HID++ 4.2 + Report Rate : 1ms + Serial number: + Model ID: 40BDC0A80000 + Unit ID: + Bootloader: BL2 73.00.B0011 + Firmware: MPM 42.00.B0011 + The power switch is located on the base. + Supports 36 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V7 + Firmware: Bootloader BL2 73.00.B0011 + Firmware: Firmware MPM 42.00.B0011 + Unit ID: Model ID: 40BDC0A80000 Transport IDs: {'wpid': '40BD', 'usbid': 'C0A8'} + 3: DEVICE NAME {0005} V5 + Name: PRO X2 SUPERSTRIKE + Kind: mouse + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + 6: UNIFIED BATTERY {1004} V5 + 7: XY STATS {2250} V1 + 8: WHEEL STATS {2251} V0 + 9: EXTENDED ADJUSTABLE DPI {2202} V0 + Sensitivity (DPI): {X:800, Y:800, LOD:HIGH} + 10: MODE STATUS {8090} V3 + 11: unknown:80E0 {80E0} V0 + 12: SUPERSTRIKE TUNING {1B0C} V0 + Left Button Actuation Point: 5 + Left Button Rapid Trigger Level: 3 + Left Button Click Haptics: 3 + Right Button Actuation Point: 5 + Right Button Rapid Trigger Level: 3 + Right Button Click Haptics: 3 + 13: EXTENDED ADJUSTABLE REPORT RATE {8061} V0 + Report Rate: 1ms + 14: ONBOARD PROFILES {8100} V0 + Device Mode: On-Board + Onboard Profiles: Profile 1 + 15: MOUSE BUTTON SPY {8110} V0 + 16: FORCE PAIRING {1500} V0 + 17: unknown:1801 {1801} V0 internal, hidden + 18: DEVICE RESET {1802} V0 + 19: unknown:1803 {1803} V0 internal, hidden + 20: CONFIG DEVICE PROPS {1806} V8 + 21: unknown:1817 {1817} V0 internal, hidden + 22: OOBSTATE {1805} V0 + 23: unknown:1830 {1830} V0 internal, hidden + 24: unknown:1877 {1877} V0 internal, hidden + 25: unknown:9403 {9403} V0 internal, hidden + 26: unknown:1861 {1861} V0 internal, hidden + 27: unknown:1890 {1890} V0 internal, hidden + 28: unknown:18A1 {18A1} V0 internal, hidden + 29: unknown:1E00 {1E00} V0 hidden + 30: unknown:1E02 {1E02} V0 internal, hidden + 31: unknown:1E22 {1E22} V0 internal, hidden + 32: unknown:1E30 {1E30} V0 internal, hidden + 33: unknown:1602 {1602} V0 + 34: unknown:1EB0 {1EB0} V0 internal, hidden + 35: unknown:18B1 {18B1} V0 internal, hidden + Battery: discharging. + +SUPERSTRIKE TUNING Feature (0x1B0C) +----------------------------------- +This feature controls the HITS (Hall-Effect Inductive Trigger Switch) settings +for the left and right mouse buttons. + +Capabilities (function 0x00): + Byte 0: Flags + Byte 1: Button count (3, but only 0 and 1 are accessible) + Byte 2: Max actuation value (40) + Byte 3: Max rapid trigger value (20) + Byte 4: Max haptics value (20) + +Read button settings (function 0x20, param: button_index): + Response: [button_index, actuation, rapid_trigger, haptics, ...] + - actuation: 4-40 (quantized to multiples of 4) + - rapid_trigger: 1-20 (cannot be set to 0) + - haptics: 0-20 (quantized to multiples of 4: 0, 4, 8, 12, 16, 20) + +Write button settings (function 0x10): + Params: [button_index, actuation, rapid_trigger, haptics] + +Solaar Settings: + - superstrike-tuning_actuation-{0,1}: Range 1-10 (maps to device 4-40) + - superstrike-tuning_rapid-trigger-level-{0,1}: Range 1-5 (maps to device 1-20) + - superstrike-tuning_haptics-{0,1}: Range 0-5 (maps to device 0-20) + +Note: Feature 0x80E0 (unknown:80E0) appears to be a non-functional stub for haptics. +Haptics are actually controlled via byte 3 of the SUPERSTRIKE TUNING feature. + +Note: Feature 0x9403 (unknown:9403) appears to be a hidden BHOP (bunny hop) feature +that is not accessible via HID++. diff --git a/docs/devices/PRO X Wireless 4093.txt b/docs/devices/PRO X Wireless 4093.txt index a86d7b1c..5ec9a104 100644 --- a/docs/devices/PRO X Wireless 4093.txt +++ b/docs/devices/PRO X Wireless 4093.txt @@ -1,59 +1,62 @@ -Solaar version 1.1.3 +Solaar version 1.1.14 - 1: PRO X Wireless + 2: PRO X Wireless Device path : None WPID : 4093 Codename : PRO X Kind : mouse Protocol : HID++ 4.2 - Polling rate : 8 ms (125Hz) - Serial number: 42F42E12 + Report Rate : 1ms + Serial number: 8B24D1D1 Model ID: 4093C0940000 - Unit ID: 42F42E12 - Bootloader: BL1 25.00.B0013 - Other: - Firmware: MPM 25.01.B0018 + Unit ID: 8B24D1D1 + 1: BL1 25.01.B0018 + 3: + 0: MPM 25.01.B0018 Supports 28 HID++ 2.0 features: - 0: ROOT {0000} - 1: FEATURE SET {0001} - 2: DEVICE FW VERSION {0003} - Firmware: Bootloader BL1 25.00.B0013 AB00BE657A82 - Firmware: Other - Firmware: Firmware MPM 25.01.B0018 4093FE92436C - Unit ID: 42F42E12 Model ID: 4093C0940000 Transport IDs: {'wpid': '4093', 'usbid': 'C094'} - 3: DEVICE NAME {0005} + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V3 + Firmware: 1 BL1 25.01.B0018 AB00FE92436C + Firmware: 3 + Firmware: 0 MPM 25.01.B0018 4093FE92436C + Unit ID: 8B24D1D1 Model ID: 4093C0940000 Transport IDs: {'wpid': '4093', 'usbid': 'C094'} + 3: DEVICE NAME {0005} V0 Name: PRO X Wireless Kind: mouse - 4: WIRELESS DEVICE STATUS {1D4B} - 5: RESET {0020} - 6: UNIFIED BATTERY {1004} - 7: COLOR LED EFFECTS {8070} internal, hidden - 8: ONBOARD PROFILES {8100} - Device Mode: Host - Onboard Profiles (saved): Disable - Onboard Profiles : Disable - 9: MOUSE BUTTON SPY {8110} - 10: REPORT RATE {8060} - Polling Rate (ms): 1 - Polling Rate (ms) (saved): 1 - Polling Rate (ms) : 1 - 11: ADJUSTABLE DPI {2201} - Sensitivity (DPI) (saved): 1000 - Sensitivity (DPI) : 1000 - 12: unknown:1500 {1500} - 13: DEVICE RESET {1802} internal, hidden - 14: unknown:1803 {1803} internal, hidden - 15: CONFIG DEVICE PROPS {1806} internal, hidden - 16: unknown:1811 {1811} internal, hidden - 17: OOBSTATE {1805} internal, hidden - 18: unknown:1830 {1830} internal, hidden - 19: unknown:1890 {1890} internal, hidden - 20: unknown:1891 {1891} internal, hidden - 21: unknown:18A1 {18A1} internal, hidden - 22: unknown:1801 {1801} internal, hidden - 23: unknown:18B1 {18B1} internal, hidden - 24: unknown:1E00 {1E00} hidden - 25: unknown:1EB0 {1EB0} internal, hidden - 26: unknown:1863 {1863} internal, hidden - 27: unknown:1E22 {1E22} internal, hidden - Battery: 76%, discharging. + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + Configuration: 11000000000000000000000000000000 + 6: UNIFIED BATTERY {1004} V1 + Battery: 71%, 0. + 7: COLOR LED EFFECTS {8070} V4 internal, hidden + LED Control : HID++ error {'number': 2, 'request': 1908, 'error': 5, 'params': b''} + 8: ONBOARD PROFILES {8100} V0 + Device Mode: On-Board + Onboard Profiles (saved): Profile 1 + Onboard Profiles : Profile 1 + 9: MOUSE BUTTON SPY {8110} V0 + 10: REPORT RATE {8060} V0 + Report Rate: 1ms + Report Rate (saved): 1ms + Report Rate : 1ms + 11: ADJUSTABLE DPI {2201} V2 + Sensitivity (DPI) (saved): 800 + Sensitivity (DPI) : 800 + 12: FORCE PAIRING {1500} V0 + 13: DEVICE RESET {1802} V0 internal, hidden + 14: unknown:1803 {1803} V0 internal, hidden + 15: CONFIG DEVICE PROPS {1806} V4 internal, hidden + 16: unknown:1811 {1811} V0 internal, hidden + 17: OOBSTATE {1805} V0 internal, hidden + 18: unknown:1830 {1830} V0 internal, hidden + 19: unknown:1890 {1890} V5 internal, hidden + 20: unknown:1891 {1891} V5 internal, hidden + 21: unknown:18A1 {18A1} V0 internal, hidden + 22: unknown:1801 {1801} V0 internal, hidden + 23: unknown:18B1 {18B1} V0 internal, hidden + 24: unknown:1E00 {1E00} V0 hidden + 25: unknown:1EB0 {1EB0} V0 internal, hidden + 26: unknown:1863 {1863} V0 internal, hidden + 27: unknown:1E22 {1E22} V0 internal, hidden + Battery: 71%, 0. diff --git a/docs/devices/Signature M550.text b/docs/devices/Signature M550.text new file mode 100644 index 00000000..6822b2a1 --- /dev/null +++ b/docs/devices/Signature M550.text @@ -0,0 +1,79 @@ +solaar version 1.1.19 + + 1: Signature M550 + Device path : None + WPID : B02B + Codename : Logi M550 + Kind : mouse + Protocol : HID++ 4.5 + Serial number: D074434E + Model ID: B02B00000000 + Unit ID: D074434E + 1: BL1 39.01.B0013 + 0: RBM 17.01.B0013 + 3: + The power switch is located on the (unknown). + Supports 30 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V4 + Firmware: 1 BL1 39.01.B0013 B02BB0706FCD + Firmware: 0 RBM 17.01.B0013 B02BB0706FCD + Firmware: 3 + Unit ID: D074434E Model ID: B02B00000000 Transport IDs: {'btleid': 'B02B'} + 3: DEVICE NAME {0005} V0 + Name: Signature M550 + Kind: mouse + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + Configuration: 11000000000000000000000000000000 + 6: DEVICE FRIENDLY NAME {0007} V0 + Friendly Name: Logi M550 + 7: UNIFIED BATTERY {1004} V3 + Battery: 35%, BatteryStatus.DISCHARGING. + 8: REPROG CONTROLS V4 {1B04} V5 + Key/Button Actions (saved): {Middle Button:Mouse Middle Button} + Key/Button Actions : {Middle Button:Mouse Middle Button} + Key/Button Diversion (saved): {Middle Button:Regular} + Key/Button Diversion : {Middle Button:Regular} + 9: HOSTS INFO {1815} V2 + Host 0 (paired): mst + 10: XY STATS {2250} V1 + 11: LOWRES WHEEL {2130} V0 + Wheel Reports: HID + Scroll Wheel Diversion (saved): False + Scroll Wheel Diversion : False + 12: ADJUSTABLE DPI {2201} V2 + Sensitivity (DPI) (saved): 1000 + Sensitivity (DPI) : 1000 + 13: DFUCONTROL {00C3} V0 + 14: DEVICE RESET {1802} V0 + 15: unknown:1803 {0318} V0 internal, hidden, unknown:000010 + 16: CONFIG DEVICE PROPS {1806} V8 + 17: unknown:1816 {1618} V0 internal, hidden, unknown:000010 + 18: OOBSTATE {1805} V0 + 19: unknown:1830 {3018} V0 internal, hidden, unknown:000010 + 20: unknown:1891 {9118} V0 internal, hidden, unknown:000008 + 21: unknown:18A1 {A118} V0 internal, hidden, unknown:000010 + 22: unknown:1E00 {001E} V0 hidden + 23: unknown:1E02 {021E} V0 internal, hidden + 24: unknown:1E22 {221E} V0 internal, hidden, unknown:000010 + 25: unknown:1602 {0216} V0 + 26: unknown:1EB0 {B01E} V0 internal, hidden, unknown:000010 + 27: unknown:1861 {6118} V0 internal, hidden, unknown:000010 + 28: unknown:18B1 {B118} V0 internal, hidden, unknown:000010 + 29: unknown:920A {0A92} V0 internal, hidden, unknown:000010 + Has 4 reprogrammable keys: + 0: Left Button , default: Left Click => Left Click + analytics_key_events, mse, pos:0, group:1, group mask:empty + reporting: default + 1: Right Button , default: Right Click => Right Click + analytics_key_events, mse, pos:0, group:1, group mask:empty + reporting: default + 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button + analytics_key_events, raw_xy, divertable, reprogrammable, mse, pos:0, group:2, group mask:g1,g2 + reporting: default + 3: Virtual Gesture Button , default: Virtual Gesture Button => Virtual Gesture Button + force_raw_xy, raw_xy, virtual, divertable, pos:0, group:3, group mask:empty + reporting: default + Battery: 35%, BatteryStatus.DISCHARGING. diff --git a/docs/devices/Signature M650 L Mouse B02A.txt b/docs/devices/Signature M650 L Mouse B02A.txt index 4a0e76f4..51c2c2d7 100644 --- a/docs/devices/Signature M650 L Mouse B02A.txt +++ b/docs/devices/Signature M650 L Mouse B02A.txt @@ -40,8 +40,7 @@ Solaar version 1.1.3 Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular} 9: HOSTS INFO {1815} Host 0 (paired): legion15 - 10: XY STATS {2250} - 11: LOWRES WHEEL {2130} + 10: XY STATS 11: LOWRES WHEEL {2130} Wheel Reports: HID Scroll Wheel Diversion (saved): False Scroll Wheel Diversion : False diff --git a/docs/devices/Wireless Mobile Mouse MX Anywhere 2S B01A.txt b/docs/devices/Wireless Mobile Mouse MX Anywhere 2S B01A.txt new file mode 100644 index 00000000..864ae389 --- /dev/null +++ b/docs/devices/Wireless Mobile Mouse MX Anywhere 2S B01A.txt @@ -0,0 +1,95 @@ +solaar version 1.1.9 + + 1: Wireless Mobile Mouse MX Anywhere 2S + Device path : /dev/hidraw1 + USB id : 046d:B01A + Codename : Wireless + Kind : mouse + Protocol : HID++ 4.5 + Serial number: + Model ID: B01A406A0000 + Unit ID: 3F714CA3 + Bootloader: BOT 57.00.B0003 + Firmware: MPM 13.00.B0003 + Firmware: MPM 13.00.B0003 + Other: + Supports 24 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V2 + Firmware: Bootloader BOT 57.00.B0003 406AD22DCF4D01 + Firmware: Firmware MPM 13.00.B0003 B01AD22DCF4D01 + Firmware: Firmware MPM 13.00.B0003 406AD22DCF4D01 + Firmware: Other + Unit ID: 3F714CA3 Model ID: B01A406A0000 Transport IDs: {'btleid': 'B01A', 'wpid': '406A'} + 3: DEVICE NAME {0005} V0 + Name: Wireless Mobile Mouse MX Anywhere 2S + Kind: mouse + 4: WIRELESS DEVICE STATUS {1D4B} V0 + 5: CONFIG CHANGE {0020} V0 + 6: BATTERY STATUS {1000} V0 + Battery: 90%, discharging, next level 50%. + 7: CONFIG DEVICE PROPS {1806} V0 internal, hidden + 8: CHANGE HOST {1814} V1 + Change Host : 2:mburcheri2 + 9: REPROG CONTROLS V4 {1B04} V3 + Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Gesture Button Navigation, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Left Tilt:Mouse Scroll Left Button , Right Tilt:Mouse Scroll Right Button} + Key/Button Actions : {Left Button:Left Click, Right Button:Right Click, Middle Button:Gesture Button Navigation, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Left Tilt:Mouse Scroll Left Button , Right Tilt:Mouse Scroll Right Button} + Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Sliding DPI, Forward Button:Regular, Left Tilt:Regular, Right Tilt:Regular} + Key/Button Diversion : {Middle Button:Regular, Back Button:Diverted, Forward Button:Regular, Left Tilt:Regular, Right Tilt:Regular} + 10: ADJUSTABLE DPI {2201} V1 + Sensitivity (DPI) (saved): 3400 + Sensitivity (DPI) : 3400 + 11: VERTICAL SCROLLING {2100} V0 + Roller type: 3G + Ratchet per turn: 24 + Scroll lines: 0 + 12: HIRES WHEEL {2121} V0 + Multiplier: 8 + Has invert: Normal wheel motion + Has ratchet switch: Free wheel mode + Low resolution mode + HID notification + Scroll Wheel Direction (saved): False + Scroll Wheel Direction : False + Scroll Wheel Resolution (saved): False + Scroll Wheel Resolution : False + Scroll Wheel Diversion (saved): False + Scroll Wheel Diversion : False + 13: unknown:1813 {1813} V0 internal, hidden + 14: unknown:1830 {1830} V0 internal, hidden + 15: unknown:18A1 {18A1} V0 internal, hidden + 16: unknown:18C0 {18C0} V0 internal, hidden + 17: unknown:1DF3 {1DF3} V0 internal, hidden + 18: unknown:1E00 {1E00} V0 hidden + 19: unknown:1EB0 {1EB0} V0 internal, hidden + 20: unknown:1803 {1803} V0 internal, hidden + 21: unknown:1861 {1861} V0 internal, hidden + 22: unknown:9001 {9001} V0 internal, hidden + 23: OOBSTATE {1805} V0 internal, hidden + Has 8 reprogrammable keys: + 0: Left Button , default: Left Click => Left Click + mse, pos:0, group:1, group mask:g1 + reporting: default + 1: Right Button , default: Right Click => Right Click + mse, pos:0, group:1, group mask:g1 + reporting: default + 2: Middle Button , default: Gesture Button Navigation => Gesture Button Navigation + mse, reprogrammable, divertable, raw XY, pos:0, group:2, group mask:g1,g2,g4 + reporting: default + 3: Back Button , default: Mouse Back Button => Mouse Back Button + mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3,g4 + reporting: diverted, raw XY diverted + 4: Forward Button , default: Mouse Forward Button => Mouse Forward Button + mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3,g4 + reporting: default + 5: Left Tilt , default: Mouse Scroll Left Button => Mouse Scroll Left Button + mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3,g4 + reporting: default + 6: Right Tilt , default: Mouse Scroll Right Button => Mouse Scroll Right Button + mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3,g4 + reporting: default + 7: Virtual Gesture Button , default: Virtual Gesture Button => Virtual Gesture Button + divertable, virtual, raw XY, force raw XY, pos:0, group:4, group mask:empty + reporting: default + Battery: 90%, discharging, next level 50%. diff --git a/docs/devices/Wireless Mouse M185.text b/docs/devices/Wireless Mouse M185.text new file mode 100644 index 00000000..26240e87 --- /dev/null +++ b/docs/devices/Wireless Mouse M185.text @@ -0,0 +1,64 @@ +solaar show +rules cannot access modifier keys in Wayland, accessing process only works on GNOME with Solaar Gnome extension installed +solaar version 1.1.14-2 + +Unifying Receiver + Device path : /dev/hidraw1 + USB id : 046d:C52B + Serial : EC219AC2 + C Pending : ff + 0 : 12.11.B0032 + 1 : 04.16 + 3 : AA.AA + Has 2 paired device(s) out of a maximum of 6. + Notifications: wireless (0x000100) + Device activity counters: 1=195, 2=74 + + 1: Wireless Mouse M175 + Device path : /dev/hidraw2 + WPID : 4008 + Codename : M175 + Kind : mouse + Protocol : HID++ 2.0 + Report Rate : 8ms + Serial number: 16E46E8C + Model ID: 000000000000 + Unit ID: 00000000 + 0: RQM 40.00.B0016 + The power switch is located on the base. + Supports 21 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V0 + Firmware: 0 RQM 40.00.B0016 4008 + Unit ID: 00000000 Model ID: 000000000000 Transport IDs: {} + 3: DEVICE NAME {0005} V0 + Name: Wireless Mouse M185 + Kind: mouse + 4: BATTERY STATUS {1000} V0 + Battery: 70%, 0, next level 5%. + 5: unknown:1830 {1830} V0 internal, hidden + 6: unknown:1850 {1850} V0 internal, hidden + 7: unknown:1860 {1860} V0 internal, hidden + 8: unknown:1890 {1890} V0 internal, hidden + 9: unknown:18A0 {18A0} V0 internal, hidden + 10: unknown:18C0 {18C0} V0 internal, hidden + 11: WIRELESS DEVICE STATUS {1D4B} V0 + 12: unknown:1DF3 {1DF3} V0 internal, hidden + 13: REPROG CONTROLS {1B00} V0 + 14: REMAINING PAIRING {1DF0} V0 hidden + Remaining Pairings: 117 + 15: unknown:1E00 {1E00} V0 hidden + 16: unknown:1E80 {1E80} V0 internal, hidden + 17: unknown:1E90 {1E90} V0 internal, hidden + 18: unknown:1F03 {1F03} V0 internal, hidden + 19: VERTICAL SCROLLING {2100} V0 + Roller type: standard + Ratchet per turn: 24 + Scroll lines: 0 + 20: MOUSE POINTER {2200} V0 + DPI: 1000 + Acceleration: low + Override OS ballistics + No vertical tuning, standard mice + Battery: 70%, 0, next level 5%. diff --git a/docs/devices/Wireless Mouse M325 400A.txt b/docs/devices/Wireless Mouse M325 400A.txt index ca4b1cad..bcc3b990 100644 --- a/docs/devices/Wireless Mouse M325 400A.txt +++ b/docs/devices/Wireless Mouse M325 400A.txt @@ -1,3 +1,55 @@ + + 1: Wireless Mouse M325 + Device path : /dev/hidraw4 + WPID : 400A + Codename : M325 + Kind : mouse + Protocol : HID++ 2.0 + Polling rate : 8 ms (125Hz) + Serial number: D72D97E9 + Model ID: 000000000000 + Unit ID: 00000000 + Firmware: RQM 40.01.B0018 + The power switch is located on the base. + Supports 22 HID++ 2.0 features: + 0: ROOT {0000} V0 + 1: FEATURE SET {0001} V0 + 2: DEVICE FW VERSION {0003} V0 + Firmware: Firmware RQM 40.01.B0018 400A + Unit ID: 00000000 Model ID: 000000000000 Transport IDs: {} + 3: DEVICE NAME {0005} V0 + Name: Wireless Mouse M325 + Kind: mouse + 4: BATTERY STATUS {1000} V0 + Battery: 70%, discharging, next level 5%. + 5: unknown:1830 {1830} V0 internal, hidden + 6: unknown:1850 {1850} V0 internal, hidden + 7: unknown:1860 {1860} V0 internal, hidden + 8: unknown:1890 {1890} V0 internal, hidden + 9: unknown:18A0 {18A0} V0 internal, hidden + 10: unknown:18C0 {18C0} V0 internal, hidden + 11: WIRELESS DEVICE STATUS {1D4B} V0 + 12: unknown:1DF3 {1DF3} V0 internal, hidden + 13: REPROG CONTROLS {1B00} V0 + 14: REMAINING PAIRING {1DF0} V0 hidden + Remaining Pairings: 117 + 15: unknown:1E00 {1E00} V0 hidden + 16: unknown:1E80 {1E80} V0 internal, hidden + 17: unknown:1E90 {1E90} V0 internal, hidden + 18: unknown:1F03 {1F03} V0 internal, hidden + 19: VERTICAL SCROLLING {2100} V0 + Roller type: micro + Ratchet per turn: 36 + Scroll lines: 0 + 20: MOUSE POINTER {2200} V0 + DPI: 800 + Acceleration: low + Override OS ballistics + No vertical tuning, standard mice + 21: unknown:18B0 {18B0} V0 internal, hidden + Battery: 70%, discharging, next level 5%. + + Wireless Mouse M325 Codename : M325 Kind : mouse diff --git a/docs/features.md b/docs/features.md index f3fe1fcb..b7319566 100644 --- a/docs/features.md +++ b/docs/features.md @@ -33,14 +33,15 @@ Feature | ID | Status | Notes `UNIFIED_BATTERY` | `0x1004` | Supported | `get_battery`, read only `CHARGING_CONTROL` | `0x1010` | Unsupported | `LED_CONTROL` | `0x1300` | Unsupported | +`FORCE_PAIRING` | `0x1500` | Unsupported | `GENERIC_TEST` | `0x1800` | Unsupported | `DEVICE_RESET` | `0x1802` | Unsupported | `OOBSTATE` | `0x1805` | Unsupported | `CONFIG_DEVICE_PROPS` | `0x1806` | Unsupported | `CHANGE_HOST` | `0x1814` | Supported | `ChangeHost` `HOSTS_INFO` | `0x1815` | Partial Support | `get_host_names`, partial listing only -`BACKLIGHT` | `0x1981` | Unsupported | -`BACKLIGHT2` | `0x1982` | Supported | `Backlight2` +`BACKLIGHT` | `0x1981` | Supported | `Backlight` +`BACKLIGHT2` | `0x1982` | Supported | `Backlight2`, ... `BACKLIGHT3` | `0x1983` | Unsupported | `PRESENTER_CONTROL` | `0x1A00` | Unsupported | `SENSOR_3D` | `0x1A01` | Unsupported | @@ -49,12 +50,13 @@ Feature | ID | Status | Notes `REPROG_CONTROLS_V2_2` | `0x1B02` | Unsupported | `REPROG_CONTROLS_V3` | `0x1B03` | Unsupported | `REPROG_CONTROLS_V4` | `0x1B04` | Partial Support | `ReprogrammableKeys`, `DivertKeys`, `MouseGesture`, `get_keys` +`SUPERSTRIKE_TUNING` | `0x1B0C` | Supported | `SuperstrikeTuning` (actuation point, rapid trigger, click haptics) `REPORT_HID_USAGE` | `0x1BC0` | Unsupported | `PERSISTENT_REMAPPABLE_ACTION` | `0x1C00` | Supported | `PersistentRemappableAction` `WIRELESS_DEVICE_STATUS` | `0x1D4B` | Read only | status reporting from device `REMAINING_PAIRING` | `0x1DF0` | Unsupported | `FIRMWARE_PROPERTIES` | `0x1F1F` | Unsupported | -`ADC_MEASUREMENT` | `0x1F20` | Unsupported | +`ADC_MEASUREMENT` | `0x1F20` | Supported | `ADCPower` `LEFT_RIGHT_SWAP` | `0x2001` | Unsupported | `SWAP_BUTTON_CANCEL` | `0x2005` | Unsupported | `POINTER_AXIS_ORIENTATION` | `0x2006` | Unsupported | @@ -67,9 +69,12 @@ Feature | ID | Status | Notes `THUMB_WHEEL` | `0x2150` | Supported | `ThumbMode`, `ThumbInvert` `MOUSE_POINTER` | `0x2200` | Supported | `get_mouse_pointer_info`, read only `ADJUSTABLE_DPI` | `0x2201` | Supported | `AdjustableDpi`, `DpiSliding` +`EXTENDED_ADJUSTABLE_DPI` | `0x2202` | Supported | `ExtendedAdjustableDpi` (X/Y DPI + lift-off distance) `POINTER_SPEED` | `0x2205` | Supported | `PointerSpeed`, `SpeedChange`, `get_pointer_speed_info` `ANGLE_SNAPPING` | `0x2230` | Unsupported | `SURFACE_TUNING` | `0x2240` | Unsupported | +`XY_STATS` | `0x2250` | Unsupported | +`WHEEL_STATS` | `0x2251` | Unsupported | `HYBRID_TRACKING` | `0x2400` | Unsupported | `FN_INVERSION` | `0x40A0` | Supported | `FnSwap` `NEW_FN_INVERSION` | `0x40A2` | Supported | `NewFnSwap`, `get_new_fn_inversion @@ -97,22 +102,23 @@ Feature | ID | Status | Notes `GESTURE` | `0x6500` | Unsupported | `GESTURE_2` | `0x6501` | Partial Support | `Gesture2Gestures`, `Gesture2Params` `GKEY` | `0x8010` | Partial Support | `DivertGkeys` -`MKEYS` | `0x8020` | Unsupported | -`MR` | `0x8030` | Unsupported | -`BRIGHTNESS_CONTROL` | `0x8040` | Unsupported | -`REPORT_RATE` | `0x8060` | Supported | `ReportRate` -`COLOR_LED_EFFECTS` | `0x8070` | Unsupported | -`RGB_EFFECTS` | `0X8071` | Unsupported | +`MKEYS` | `0x8020` | Supported | `MkeyLEDs` +`MR` | `0x8030` | Supported | `MRKeyLED` +`BRIGHTNESS_CONTROL` | `0x8040` | Supported | `BrightnessControl` +`REPORT_RATE` | `0x8060` | Supported | `ReportRate` +`EXTENDED_ADJUSTABLE_REPORT_RATE` | `0x8061` | Supported | `report_rate_extended` (sub-millisecond polling up to 8000 Hz) +`COLOR_LED_EFFECTS` | `0x8070` | Supported | `LEDControl`, `LEDZoneSetting` +`RGB_EFFECTS` | `0X8071` | Supported | `RGBControl`, `RGBEffectSetting` `PER_KEY_LIGHTING` | `0x8080` | Unsupported | -`PER_KEY_LIGHTING_V2` | `0x8081` | Unsupported | +`PER_KEY_LIGHTING_V2` | `0x8081` | Supported | `PerKeyLighting` `MODE_STATUS` | `0x8090` | Unsupported | -`ONBOARD_PROFILES` | `0x8100` | Unsupported | +`ONBOARD_PROFILES` | `0x8100` | Supported | `MOUSE_BUTTON_SPY` | `0x8110` | Unsupported | `LATENCY_MONITORING` | `0x8111` | Unsupported | `GAMING_ATTACHMENTS` | `0x8120` | Unsupported | `FORCE_FEEDBACK` | `0x8123` | Unsupported | -`SIDETONE` | `0x8300` | Unsupported | -`EQUALIZER` | `0x8310` | Unsupported | +`SIDETONE` | `0x8300` | Supported | `Sidetone` +`EQUALIZER` | `0x8310` | Supported | `Equalizer` `HEADSET_OUT` | `0x8320` | Unsupported | A “read only” note means the feature is a read-only feature. diff --git a/docs/i18n.md b/docs/i18n.md index 7b7f71fe..2826808c 100644 --- a/docs/i18n.md +++ b/docs/i18n.md @@ -5,7 +5,7 @@ layout: page # Translating Solaar -First, make sure you have installed the `gettext` package. +First, make sure you have installed the `gettext` package. Also, you would need to install language pack for Gnome for your language, e.g. `language-pack-gnome-XX-base` for Debian/Ubuntu. Here are the steps to add/update a translation (you should run all scripts from the source root): @@ -22,7 +22,7 @@ the source root): the translation (msgstr); if you leave msgstr empty, the string will remain untranslated. - Alternatively, you can use the excellent `poedit`. + Alternatively, you can use the excellent [Poedit](https://poedit.net/) or [Lokalize](https://apps.kde.org/lokalize/). 4. Run `./tools/po-compile.sh`. It will bring up-to-date all the compiled language files, necessary at runtime. @@ -43,16 +43,32 @@ a translation. Some of the languages Solaar has been translated to are listed below. A full list of available translations can be obtained by checking the `/po` folder for translation files. - Chinese (Simplified): [Rongrong][Rongronggg9] -- Français: [Papoteur][papoteur], [David Geiger][david-geiger], - [Damien Lallement][damsweb] -- Italiano: [Michele Olivo][micheleolivo] +- Chinese (Taiwan): Peter Dave Hello +- Czech: Marián Kyral +- Croatian: gogo +- Danish: John Erling Blad +- Dutch: Heimen Stoffels +- Français: [Papoteur][papoteur], [David Geiger][david-geiger], [Damien Lallement][damsweb] +- Finnish: Tomi Leppänen, [Niko Savola][nikosavola] +- German: Daniel Frost +- Greek: Vangelis Skarmoutsos +- Indonesia: [Ferdina Kusumah][feku] +- Italiano: [Michele Olivo][micheleolivo], Lorenzo +- Japanese: Ryunosuke Toda - Norsk (Bokmål): [John Erling Blad][jeblad] -- Polski: [Adrian Piotrowicz][nexces] -- Portuguese-BR: [Drovetto][drovetto], [Josenivaldo Benito Jr.][jrbenito] +- Norsk (Nynorsk): [John Erling Blad][jeblad] +- Polski: [Adrian Piotrowicz][nexces], Matthaiks +- Portuguese: Américo Monteiro +- Portuguese-BR: [Drovetto][drovetto], [Josenivaldo Benito Jr.][jrbenito], Vinícius - Română: Daniel Pavel -- Russian: [Dimitriy Ryazantcev][DJm00n] +- Russian: [Dimitriy Ryazantcev][DJm00n], Anton Soroko +- Serbian: [Renato Kaurić][renatoka] - Slovak: [Jose Riha][jose1711] -- Svensk: [Daniel Zippert][zipperten], Emelie Snecker +- Spanish, Castilian: Jose Luis Tirado +- Swedish: John Erling Blad, [Daniel Zippert][zipperten], Emelie Snecker, Jonatan Nyberg +- Turkish: Osman Karagöz +- Ukrainian: Олександр Афанасьєв +- Bulgarian Николай Йорданов [Rongronggg9]: https://github.com/Rongronggg9 [papoteur]: https://github.com/papoteur @@ -66,3 +82,6 @@ Some of the languages Solaar has been translated to are listed below. A full lis [drovetto]: https://github.com/drovetto [jrbenito]: https://github.com/jrbenito [jeblad]: https://github.com/jeblad +[feku]: https://github.com/FerdinaKusumah +[renatoka]: https://github.com/renatoka +[nikosavola]: https://github.com/nikosavola diff --git a/docs/assets/favicon.png b/docs/img/favicon.ico similarity index 100% rename from docs/assets/favicon.png rename to docs/img/favicon.ico diff --git a/docs/img/favicon.png b/docs/img/favicon.png new file mode 100644 index 00000000..1f290489 Binary files /dev/null and b/docs/img/favicon.png differ diff --git a/docs/assets/solaar.svg b/docs/img/solaar.svg similarity index 100% rename from docs/assets/solaar.svg rename to docs/img/solaar.svg diff --git a/docs/implementation.md b/docs/implementation.md new file mode 100644 index 00000000..e3fca943 --- /dev/null +++ b/docs/implementation.md @@ -0,0 +1,204 @@ +--- +title: Solaar Implementation +layout: page +--- + +# Solaar Implementation + +Solaar has three main components: code mostly about receivers and devices, code for the command line interface, and code for the graphical user interface. + +The following graph shows the main components of Solaar and how they interact. +```mermaid +graph TD + subgraph User interface + U[UI] + C[CLI] + end + + subgraph Core + U --> S{Solaar} + C --> S + S --> L[Logitech receiver] + L --> R[Receiver] + L --> D[Device] + S --> B[dbus] + end + + subgraph Hardware interface + R --> A + D --> A + A[hidapi]--> P[hid parser] + end + + subgraph Peripherals + P <-.-> M[Logitech mouse] + P <-.-> K[Logitech keyboard] + end +``` + + +## Receivers and Devices + +The code in `logitech_receiver` is responsible for creating and maintaining receiver (`receiver/Receiver`) and device (`device/Device`) objects for each device on the computer that uses the Logitech HID++ protocol. These objects are discovered in Linux by interacting with the Linux `udev` system using code in `hidapi`. + +The code in `logitech_receiver/receiver' is responsible for receiver objects. +... + +The code in `logitech_receiver/device' is responsible for device objects. + +... the complex device setup process + +A device object stores the currrent value of many aspects of the device. It provides methods for retrieving and setting these aspects. The setters generally store the new value and call an hidpp10 or hidpp20 function to modify the device accordingly. The retrievers generally check whether the value is cached on the device if so just returning the cached value and if not calling an hidpp10 or hidpp20 function to retrieve the value and returning the value after caching it. +... + +Not all communication with a device is done through the `Device` class. Some is done directly from settings. +.... + +### HID++ + +#### HID++ 2.0 + +The code in `logitech_receiver/hidpp20' interacts with devices using the HID++ 2.0 (and greater) protocol. Many of the functions in this module send messages to devices to modify their internal state, for example setting a host name stored in the device. Many other functions send messages to devices that query their internal state and interpret the response, for example returning how often a mouse sends movement reports. The result of these latter functions are generally cached in device objects. + +A few of these functions create and return a large structure or a class object. + +The HID++ 2.0 protocol is built around a number of features, each with its own functionality. One of the features, that is required to be implemented by all devices supporting the protocol, provide information on which features the device provides. The `hidpp20` module provides a class (`FeaturesArray`) to store information on what features are provided by a device and how to access them. Each device that implements the HID++ 2.0 protocol has an instance of this class. The heavily used function `feature_request` creates an HID++ 2.0 message using this information to help determine what data to put into the message. + +Many devices allow reprogramming some keys or buttons. One the main reasons for reprogramming a key or device is to cause it to produce an HID++ message instead of its normal HID message, this is referred to as diverting the key (to HID++). The `ReprogrammableKey` class stores information about the reprogramming of one key for one version of this capability, with methods to access and update this information. The `PersistentRemappableAction` class does the same for another version. The `KeysArray` class stores information about the reprogramming of a collection of keys, with methods to access this information. Functions in the Device class request `KeysArray` information for a device when appropriate and store it on the device. + +Many pointing devices provide a facility for recognizing gestures and sending an HID message for the gesture. The `Gesture` class stores inforation for one gesture and the `Gestures` class stores information for all the gestures on a device. Functions in the Device class request `KeysArray` information and store it on devices. Functions in the Device class request `Gestures` information for a device when appropriate and store it on the device. + +Many gaming devices provide an interface to controlling their LEDs by zone. The `LEDEffectSetting` class stores the current state of one zone of LEDs. This information can come directly from an LED feature but is also part of Onboard Profiles so this class provides a byte string interface. Solaar stores this information in YAML so this class provides a YAML interface. The `LEDEffectsInfo` class stores information about what LED zones are on a device and what effects they can perform and provides a method that builds an object by querying a device. + +Many gaming devices can be controlled by selecting one of their Onboard Profiles. An Onboard Profile sets up the rate at which the device reports movement, a set of sensitivites of its movement detector, a set of actions to be performed by mouse buttons or G and M keys, and effects for up to two LED zones. The `Button` class stores information about a button or key action. The `OnboardProfile` class stores a single profile, using the `LEDEffectSetting` and `Button` classes. Because retrieving and changing a profile is complex, this class provides a byte string interface. Because Solaar dumps profiles from devices as YAML documents and loads them into devices from YAML documents, this class provides a YAML interface. The `OnboardProfiles` class class stores the entire profiles information for a device. It provides an interface to construct an `OnboardProfiles` object by querying a device. +Because Solaar dumps profiles from devices as YAML documents and loads them into devices from YAML documents, these classes also provide a YAML interface. + +#### HID++ 1.0 + +The code in `logitech_receiver/hidpp10' interacts with devices using the HID++ 1.0 protocol. + +... + +### Low Level Information and Access + +The module `descriptors` sets up information on device models for which Solaar needs information to support. Solaar can determine all this information for most modern devices so it is only needed for older devices or devices that are unusual in some way. The information may include the name of the device model, short name of the device model, the HID++ protocol used by the device model, HID++ registers supported by the device model, various identifiers for the device model, and the USB interface that the device model uses for HID++ messages. It used to include the HID++-based settings for the device model but this information is now added in `setting_templates`. The information about a device model can be retrieved in several ways. + + +The module `base_usb` sets up information for most of the receiver models that Solaar supports, including USB id, USB interface used for HID++ messages, what kind of receiver model it is, and some capabilities of the receiver model. Solaar can now support other receivers as long as they are not too unusual. The module lso sets up lists of device models by USB ID and Bluetooth ID and provides a function to determine whether a USB ID or Bluetooth ID is an HID++ device model + +The module `base` provides functions that call discovery to enumerate all current receivers and devices and to set up a callback for when new receivers or devices are discovered. It provides functions to open and close I/O channels to receivers and devices, write HID++ messages to receivers and devices, and read HID++ messages from receivers and devices. It provides a function to turn an HID++ message into a notification. + +The module provides a function to send an HID++ message to a receiver or device, constructing the message from parameters to the function, and optionally waiting for and returning a response. The function checks messages from the receiver or device, only terminating at timeout or when a message that appears to be the response is seen. Other messages are turned into notifications if appropriate and ignoreed otherwise. A separate function sends a ping message and waits for a reply to the ping. + + +### Notifications and Status + +HID++ devices not only respond to commands but can spontaneously emit HID++ messages, such as when their movement sensitivity changes or when a diverted key is pressed. These spontaneous messages are called notifications and if software is well behaved can be distinguished from messages that are responses to commands. (The Linux HIDPP driver was not well behaved at some time and still may not be well behaved, resulting in it causing devices to send responses that cannot be distinguished from notifications.) + +The `listener` module provides a class to set up a thread that listens to all the HID++ messages that come from a given device or receiver, convert the message that are notifications to a Solaar notification structure, and invoke a callback on the notification. + +The 'notifications` module provides a function to take a notification from a receiver or device and initiate processing required for that notification. For receivers notifications are used to signal the progress of pairing attempts. For devices some notifications are for pairing, some signal device connection and diconnection from a receiver, some are other parts of the HID++ 1.0 protocol, and some are for the HID++ 2.0 protocol. Devices can provide a callback for special handling of notifications. This facility is used for two special kinds of Solaar settings. + +The module contains code that determines the meaning of a notification based on fields in the notification and the status and HID++ 2.0 features of the device if appropriate and updates the device and its status accordingly. Updates to device status can trigger updates to the Solaar user interface. The processing of some notifications also directly runs a function to update the Solaar user interface. + +After this processing HID++ 2.0 notifications are sent to the `diversion` module where they initiate Solaar rule processing. + +The `status` module provides the `DeviceStatus` class to record the battery status of a device. It also provides an interface to signal changes to the connection status of the device that can invoke a callback. This callback is used to update the Solaar user interface when the status changes. + + +### Settings + +The Solaar GUI is based around settings. +A setting contains all the information needed to store the value of some aspect of a device, read it from the device, write it to the device, and record its state in a dictionary. A setting also contains information to display and manipulate a setting, namely what kind of user interface element to use, what values are permissable, a label to use for the setting, and a tooltip to provide additional information for the setting. Settings can be either based on HID++ 1.0, using an HID++ 1.0 register that the device provides, or based on HID++ 2.0, using an HID++ 2.0 feature that the device provides. The module `settings` provides classes and methods to create and support a setting. The module `setting_templates` contains all the settings that Solaar supports as well as functions to determine what feature-based settings a device can support. + +A simple boolean setting can be set up as follows: +``` +class HiresSmoothInvert(_Setting): + name = 'hires-smooth-invert' + label = _('Scroll Wheel Direction') + description = _('Invert direction for vertical scroll with wheel.') + feature = _F.HIRES_WHEEL + rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} + validator_options = {'true_value': 0x04, 'mask': 0x04} +``` +The setting is a boolean setting, the default for settings. +`name` is the dictionary key for recording the state of the setting. +`label` is the label to be shown for the setting in a user interface and `description` is the tooltip. +`feature` is the HID++ 2.0 feature that is used to read the current state of the setting from a device and write it back to a device. +`rw_options` contains options used when reading or writing the state of the setting, here to use feature command 0x10 to read the value and feature command 0x20 to write the value. +`validator_options` contains options to turn setting values into bytes and bytes into setting values. The options here to take a single byte (the default) and mask it with 0x04 to get a value with a result of 0x04 being true and anything else being false. They also say to use 0x04 when writing a true value and 0x00 (the default) when writing a false value. Because this is a boolean setting and the mask masks off part of a byte the value to be written is or'ed with the byte read for the setting before writing to the device. + +A simple choice setting can be set up as follows: +``` +class Backlight(_Setting): + name = 'backlight-qualitative' + label = _('Backlight') + description = _('Set illumination time for keyboard.') + feature = _F.BACKLIGHT + choices_universe = _NamedInts(Off=0, Varying=2, VeryShort=5, Short=10, Medium=20, Long=60, VeryLong=180) + validator_class = _ChoicesV + validator_options = {'choices': choices_universe} +``` +This is a choice setting because of the value for `validator_class`. +`choices_universe` is all the possible stored values for the setting along with how they are to be displayed in a user interface. +`validator_options` provides the current permissable choices, here always are the same as all the choices. + +The Solaar GUI takes these settings and constructs an interface for displaying and changing the setting. + +This setup allows for very quick implementation of simple settings but it bypasses the data stored in a device object. + + +### Solaar Rules + +The `diversion` module (so-named because it initially mostly handled diverted key notifications) implements Solaar rules. + +... + + +### Utility Functions, Structures, and Classes + +The module `common.py` provides utility functions, structures, and classes. +`crc16` is a function to compute checksums used in profiles. +`NamedInt`, `NamedInts`, and `UnsortedNamedInts` provide integers and sets of integers with attached names. +`FirmwareInfo` provides information about device firmware. +`BATTERY_APPROX` provides named integers used for approximate battery levels of devices. + +`i18n.py` provides a few strings that need translations and might not otherwise be visible to translation software. + +`special_keys.py` provides named integers for various collections of key codes and colors. + + +## Discovery of HID++ Receivers and Devices and I/O + +The code in `hidapi` is responsible for discovery of receivers and devices that use the HID++ protocol. The module used in Linux is `hidapi/udev` which is a modification of some old Python code that provides an interface to the Linux `udev` system. + +The code originally was only for receivers that used USB and devices paired with them. It identifies HID++ receivers by their USB ids, based on a list of Logitech HID++ receivers with their USB ids. It then added all devices that were paired with them and that were in a list of HID++ devices with their WPID. A WPID is used to identify the device type for devices paired with HID++ receivers. This code now also adds all devices paired with HID++ receivers whether they are in this list or not. + +The code now also identifies HID ++ devices that are directly connected via either USB or Bluetooth. These devices are recognized by several means: the internal list of HID++ devices for elements of the list that have either a USB IS or a Bluetooth ID, any device with a USB ID or Bluetooth ID that falls in one of several ranges of IDs that are known to support HID++, or any device that has an HID protocol descriptor that claims support for HID++. This last method requires an external Pyshon module to decipher HID protocol descriptors that is not always present. + +Device and receiver discovery is performed when Solaar starts. While the Solaar GUI is running the `udev` code also listens for connections of new hardware using facilities from `GLib`. + +This code is also responsible for actual writing data to devices and receivers and reading data from them. + + +## Solaar + +### Startup and Commonalities + +__init__.py +configuration.py +gtk.py* +i18n.py +listener.py +tasks.py +upower.py + +The files `version` and `commit` contain data about the current version and git commit of Solaar. + +### Solaar Command Line Interface + +solaar/cli + +### Solaar (Graphical) User Interface + +solaar/ui diff --git a/docs/index.md b/docs/index.md index 999d0e12..7ec881ca 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,9 +15,11 @@ using one of the methods described below. Solaar runs as a regular user process, albeit with direct access to the Linux interface that lets it directly communicate with the Logitech devices it manages using special -Logitech-proprietary (HID++) commands. +Logitech-proprietary (HID++ and Centurion) commands. Each Logitech device implements a different subset of these commands. -Solaar is thus only able to make the changes to devices that devices implement. +Solaar is thus only able to make the changes that a particular device supports. + +Note: Support for Centurion devices is new and should be considered experimental. Solaar is not a device driver and does not process normal input from devices. It is thus unable to fix problems that arise from incorrect handling of @@ -46,8 +48,8 @@ and for more information on its capabilities see Solaar's GUI normally uses an icon in the system tray and starts with its main window visible. This aspect of Solaar depends on having an active system tray, which is not the default -situation for recent versions of Gnome. For information on to set up a system tray under Gnome see -[the capabilities page](https://pwr-solaar.github.io/Solaar/capabilities). +situation for recent versions of Gnome. For information on how to set up a system tray under +Gnome see [the capabilities page](https://pwr-solaar.github.io/Solaar/capabilities). Solaar's GUI can be started in several ways @@ -75,7 +77,7 @@ Please report such experiences by creating an issue in Solaar will detect all devices paired with supported Unifying, Bolt, Lightspeed, or Nano receivers, and at the very least display some basic information about them. -Solaar will detect some Logitech devices that connect via a USB cable or Bluetooth. +Solaar will detect many Logitech devices that connect via a USB cable or Bluetooth. Solaar can pair and unpair a Logitech device showing the Unifying logo (Solaar's version of the [logo][logo]) @@ -95,7 +97,7 @@ which is done using the usual Bluetooth mechanisms. For a partial list of supported devices and their features, see [the devices page](https://pwr-solaar.github.io/Solaar/devices). -[logo]: https://pwr-solaar.github.io/Solaar/assets/solaar.svg +[logo]: https://pwr-solaar.github.io/Solaar/img/solaar.svg ## Prebuilt packages @@ -106,12 +108,11 @@ available from the standard repositories for your distribution, you can try one of these packages. - Arch solaar package in the [extra repository][arch] -- Ubuntu/Kubuntu stable packages: use the [Solaar stable ppa][ppa2], courtesy of [gogo][ppa4] -- Ubuntu/Kubuntu git build packages: use the [Solaar git ppa][ppa1], courtesy of [gogo][ppa4] -- NixOS Flake: see [Svenum/Solaar-Flake][nix flake] +- Ubuntu/Kubuntu package in [Solaar stable ppa][ppa2] +- NixOS Flake package in [Svenum/Solaar-Flake][nix flake] Solaar is available from some other repositories -but they are several versions behind the current version. +but they may be several versions behind the current version. - for Ubuntu/Kubuntu 16.04+: the solaar package from [universe repository][universe repository] - a [Gentoo package][gentoo], courtesy of Carlos Silva and Tim Harder @@ -121,8 +122,6 @@ Solaar uses a standard system tray implementation; solaar-gnome3 is no longer re [ppa4]: https://launchpad.net/~trebelnik-stefina [ppa2]: https://launchpad.net/~solaar-unifying/+archive/ubuntu/stable -[ppa1]: https://launchpad.net/~solaar-unifying/+archive/ubuntu/ppa -[ppa]: http://launchpad.net/~daniel.pavel/+archive/solaar [arch]: https://www.archlinux.org/packages/extra/any/solaar/ [gentoo]: https://packages.gentoo.org/packages/app-misc/solaar [mageia]: http://mageia.madb.org/package/show/release/cauldron/application/0/name/solaar @@ -134,60 +133,10 @@ Solaar uses a standard system tray implementation; solaar-gnome3 is no longer re See [the installation page](https://pwr-solaar.github.io/Solaar/installation) for the step-by-step procedure for manual installation. -## Known Issues +## License -- Solaar expects that it has exclusive control over settings that are not ignored. - Running other programs that modify these settings, such as logiops, - will likely result in unexpected device behavior. - -- The Linux HID++ driver modifies the Scroll Wheel Resolution setting to - implement smooth scrolling. If Solaar later changes this setting, scrolling - can be either very fast or very slow. To fix this problem - click on the icon at the right edge of the setting to set it to - "Ignore this setting", which is the default for new devices. - The mouse has to be reset (e.g., by turning it off and on again) before this fix will take effect. - -- The driver also sets the scrolling direction to its normal setting when implementing smooth scrolling. - This can interfere with the Scroll Wheel Direction setting, requiring flipping this setting back and forth - to restore reversed scrolling. - -- The driver sends messages to devices that do not conform with the Logitech HID++ specification - resulting in responses being sent back that look like other messages. For some devices this causes - Solaar to report incorrect battery levels. - -- If the Python hid-parser package is not available, Solaar will not recognize some devices. - Use pip to install hid-parser. - -- Solaar normally uses icon names for its icons, which in some system tray implementations - results in missing or wrong-sized icons. - The `--tray-icon-size` option forces Solaar to use icon files of appropriate size - for tray icons instead, which produces better results in some system tray implementations. - To use icon files close to 32 pixels in size use `--tray-icon-size=32`. - -- The icon in the system tray can show up as 'black on black' in dark - themes or as non-symbolic when the theme uses symbolic icons. This is due to problems - in some system tray implementations. Changing to a different theme may help. - The `--battery-icons=symbolic` option can be used to force symbolic icons. - -- Many gaming mice and keyboards have the ONBOARD PROFILES feature. - This feature can override other features, including polling rate and key lighting. - To make the Polling Rate and M-Key LEDs settings effective, the Onboard Profiles setting has to be disabled. - This may have other effects, such as turning off backlighting. - -- Solaar will try to use uinput to simulate input from rules under Wayland or if Xtest is not available - but this needs write permission on /dev/uinput. - For more information see [the rules page](https://pwr-solaar.github.io/Solaar/rules). - -- Diverted keys remain diverted and so do not have their normal behavior when Solaar terminates - or a device disconnects from a host that is running Solaar. If necessary, their normal behavior - can be reestablished by turning the device off and on again. This is most important to restore - the host switching behavior of a host switch key that was diverted, for example to switch away - from a host that crashed or was turned off. - -- When a receiver-connected device changes hosts Solaar remembers which diverted keys were down on it. - When the device changes back the first time any of these diverted keys is depressed Solaar will not - realize that the key was newly depressed. For this reason Solaar rules that can change hosts should - trigger on key releasing. +This software is distributed under the terms of the +[GNU Public License, v2](LICENSE.txt), or later. ## Contributing to Solaar @@ -201,28 +150,21 @@ If you find a bug, please check first if it has already been reported. If yes, p If you want to add a new feature to Solaar, feel free to open a feature request issue to discuss your proposal. There are also usually several open issues for enhancements that have already been requested. - -## License - -This software is distributed under the terms of the -[GNU Public License, v2](COPYING). - -## Thanks +## Contributors This project began as a third-hand clone of [Noah K. Tilton](https://github.com/noah)'s logitech-solar-k750 project on GitHub (no longer available). It was developed -further thanks to the diggings in Logitech's HID++ protocol done by many other -people: +further thanks to the contributions of many other people, including: -- [Julien Danjou](http://julien.danjou.info/blog/2012/logitech-k750-linux-support), -who also provided some internal -[Logitech documentation](http://julien.danjou.info/blog/2012/logitech-unifying-upower) +- [Daniel Pavel](https://github.com/pwr) +- [Filipe Lains](https://github.com/FFY00) +- [Peter Wu](https://github.com/Lekensteyn), who also did some [reverse engineering on pairing](https://lekensteyn.nl/logitech-unifying.html) +- Julien Danjou - [Lars-Dominik Braun](http://6xq.net/git/lars/lshidpp.git) - [Alexander Hofbauer](http://derhofbauer.at/blog/blog/2012/08/28/logitech-performance-mx) -- [Clach04](http://bitbucket.org/clach04/logitech-unifying-receiver-tools) -- [Peter Wu](https://lekensteyn.nl/logitech-unifying.html) -- [Nestor Lopez Casado](http://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28) -provided some more Logitech specifications for the HID++ protocol +- [Clach04](https://github.com/clach04) +- [Peter F. Patel-Schneider](https://github.com/pfps) -Also, thanks to Douglas Wagner, Julien Gascard, and Peter Wu for helping with -application testing and supporting new devices. +Thanks go to Nestor Lopez Casado, who +provided [public Logitech specifications for the HID++ protocol](http://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28). +Also, thanks to Douglas Wagner, Julien Gascard, and others for helping with application testing and supporting new devices. diff --git a/docs/installation.md b/docs/installation.md index 569822d6..c9b58919 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -5,60 +5,82 @@ layout: page # Installing from PyPI -An easy way to install the most recent release version of Solaar is from the PyPI repository. +An easy way to install the most recent release version of Solaar is from the PyPI repository. First install pip, and then run -`pip install --user solaar`. -If you are using pipx add the `--system-site-packages` flag. +`pip install --user solaar` or `pipx install --system-site-packages solaar`. This will not install the Solaar udev rule, which you will need to install manually by copying -`~/.local/share/solaar/udev-rules.d/42-logitech-unify-permissions.rules` +`~/.local/lib/udev/rules.d/42-logitech-unify-permissions.rules` to `/etc/udev/rules.d` as root. -## macOS support +## Installing in macOS Solaar has limited support for macOS. You can use it to pair devices and configure settings but the rule system and diversion will not work. -After installing Solaar via pip use homebrew to install the hidapi library: +After installing Solaar via pip use homebrew to install the needed libraries: ``` -brew install hidapi -``` -If you only want to use the CLI that's all that is needed. To use the GUI you need to also -install GTK and its python bindings: -``` -brew install gtk+3 pygobject3 +brew update +brew install hidapi gtk+3 pygobject3 ``` -# Manual installation from GitHub +### Optional: Set up macOS launcher +* Option A (recommended): Configure a LaunchAgent to automatically start Solaar and keep it running in the background. +It will also automatically restart Solaar if it crashed or closed. +``` +bash <(curl -fsSL https://raw.githubusercontent.com/pwr-Solaar/Solaar/refs/heads/master/tools/create-macos-launchagent.sh) +``` +* Option B: Create Solaar.app launcher in /Applications. +It can be added to Login Items to start on login, but it will not automatically recover on crashes. +``` +bash <(curl -fsSL https://raw.githubusercontent.com/pwr-Solaar/Solaar/refs/heads/master/tools/create-macos-app.sh) +``` + +# Installating from GitHub ## Downloading Clone Solaar from GitHub by `git clone https://github.com/pwr-Solaar/Solaar.git`. +## Installing using the Makefile + +Solaar has a makefile that can be used to easily install Solaar after cloning the repository. + +First, install the needed system packages by `make install_apt` +or `make install_dnf` or `make install_brew`. +These might not install all needed packages in older versions of your distribution. +Next, install the Solaar rule via `make install_udev`. +Finally, install Solaar via `make install_pip` or `make install_pipx`. + +Parts of the installation process require sudo privileges so you may be asked for your password. + +## Running from the download directory + +To run Solaar from the download directory, just cd to there and run `bin/solaar` for the GUI +or `bin/solaar ` for the CLI. + ## Requirements for Solaar -If you have previously successfully installed a recent version of Solaar from a repository -you should be able to skip this section. +This is only relevant if you have problems with the easier methods above. -Solaar needs a reasonably new kernel with kernel modules `hid-logitech-dj` -and `hid-logitech-hidpp` loaded. +Solaar needs a reasonably new kernel with kernel modules `hid-logitech-dj` and `hid-logitech-hidpp` loaded. +The kernel option CONFIG_HIDRAW also needs to be enabled. Most of Solaar should work fine with any kernel more recent than 5.2, but newer kernels might be needed for some devices to be correctly recognized and handled. The `udev` package must be installed and its daemon running. Solaar requires Python 3.7+ and requires several packages to be installed. If you are running the system version of Python you should have the -`python3-pyudev`, `python3-psutil`, `python3-xlib`, `python3-evdev`, `python3-typing-extensions`, `dbus-python`, -and `python3-yaml` or `python3-pyyaml` packages installed. +`python3-pyudev`, `python3-psutil`, `python3-xlib`, `python3-evdev`, `python3-typing-extensions`, `dbus-python` +or `python3-dbus`, and `python3-yaml` or `python3-pyyaml` packages installed. To run the GUI Solaar also requires Gtk3 and its GObject introspection bindings. -If you are running the system version of Python -in Debian/Ubuntu you should have the +If you are running the system version of Python in Debian/Ubuntu you should have the `python3-gi` and `gir1.2-gtk-3.0` packages installed. In Fedora you need `gtk3` and `python3-gobject`. You may have to install `gcc` and the Python development package (`python3-dev` or `python3-devel`, depending on your distribution). -Other system packages may be required depending on your distribution, such as `python-gobject-common-devel`. +Other system packages may be required depending on your distribution, such as `python-gobject-common-devel` and `python-typing-extensions'. Although the Solaar CLI does not require Gtk3, `solaar config` does use Gtk3 capabilities to determine whether the Solaar GUI is running and thus should tell the Solaar GUI to update its information about settings @@ -77,10 +99,11 @@ If desktop notifications bindings are also installed (`gir1.2-notify-0.7` for Debian/Ubuntu), you will also see desktop notifications when devices come online and go offline. -If the `hid_parser` Python package is available, Solaar parses HID report descriptors -and can control more HID++ devices that do not use a receiver. -This package may not be available in some distributions but can be installed using pip -via `pip install --user hid-parser`. +Solaar includes its own version of `hid_parser` because the version that is in PyPi +(at https://pypi.org/project/hid-parser/) does not have some changes that are in +https://github.com/usb-tools/python-hid-parser and are needed for some devices. +Do not use pip to install hid_parser! +Some distributions (e.g., Fedora) may separately package this code. If the `gitinfo` Python package is available, Solaar shows better information about which version of Solaar is running. @@ -97,76 +120,23 @@ which requires installation of the X11 development package. Solaar will run under Wayland but some parts of Solaar rules will not work. For more information see [the rules page](https://pwr-solaar.github.io/Solaar/rules). -### Installing Solaar's udev rule +## Installing Solaar's udev rule manually -Solaar needs to write to HID devices for receivers and devices. -To be able to do this without running as root requires a udev rule -that gives seated users write access to the HID devices for Logitech receivers and devices. +You can install Solaar's udev rule manually by copying the file +`rules.d/42-logitech-unify-permissions.rules` +as root from the Solaar repository to `/etc/udev/rules.d`. +Let udev reload its rules by running `sudo udevadm control --reload-rules`. -You can install this rule by copying, as root, -`rules.d/42-logitech-unify-permissions.rules` from Solaar to -`/etc/udev/rules.d`. -You will probably also have to tell udev to reload its rule via -`sudo udevadm control --reload-rules`. - -For this rule to set up the correct permissions for your receivers and devices -you will then need to either disconnect your receivers and -any USB-connected or Bluetooth-connected devices and -re-connect them or reboot your computer. - -## Running from the download directory - -To run Solaar from the download directory, first install the Solaar udev rule if necessary. -Then cd to the solaar directory and run `bin/solaar` for the GUI -or `bin/solaar ` for the CLI. - -Do not run Solaar as root, as you may encounter problems with X11 integration and with the system tray. - -## Installing Solaar from the download directory using Pip - -Python programs are usually installed using [pip][pip]. -The pip instructions for Solaar are in `setup.py`, the standard place to put such instructions. - -To install Solaar for yourself only run -`pip install --user '.[report-descriptor,git-commit]'` -from the download directory. -This tells pip to install Solaar into your `~/.local` directory, but does not install Solaar's udev rule. -(See above for installing the udev rule.) -Once the udev rule has been installed you can then run Solaar as `~/.local/bin/solaar`. - -Installing Python programs to system directories using pip is generally frowned on both -because this runs arbitrary code as root and because this can override existing python libraries -that other users or even the system depend on. If you want to install Solaar to /usr/local run -`sudo bash -c 'umask 022 ; pip install .'` in the solaar directory. -(The umask is needed so that the created files and directories can be read and executed by everyone.) -Then Solaar can be run as /usr/local/bin/solaar. -You will also have to install the udev rule. - -[pip]: https://en.wikipedia.org/wiki/Pip_(package_manager) - -## Solaar in other languages +# Solaar in other languages If you want to have Solaar's user messages in some other language you need to run `tools/po-compile.sh` to create the translation files before running or installing Solaar and set the LANGUAGE environment variable appropriately when running Solaar. -# Setting up Solaar's icons - -Solaar uses a number of custom icons, which have to be installed in a place where GTK can access them. - -If Solaar has never been installed, and only run from the download directory then Solaar will not be able to find the icons. -If Solaar has only been installed for a user (e.g., via pip) then Solaar will be able to find the icons, -but they may not show up in the system tray. - -One solution is to install a version of Solaar on a system-wide basis. -A more-recent version of Solaar can then be installed for a user or Solaar can be run out of the download directory. -Another solution is to copy the Solaar custom icons from share/solaar/icons to a place they can be found by GTK, -likely /usr/share/icons/hicolor/scalable/apps. - # Running Solaar at Startup Distributions can cause Solaar can be run automatically at user login by installing a desktop file at `/etc/xdg/autostart/solaar.desktop`. An example of this file content can be seen in the repository at -[share/autostart/solaar.desktop](https://github.com/pwr-Solaar/Solaar/blob/master/share/autostart/solaar.desktop). +[`share/autostart/solaar.desktop`](https://github.com/pwr-Solaar/Solaar/blob/master/share/autostart/solaar.desktop). If you install Solaar yourself you may need to create or modify this file or install a startup file under your home directory. diff --git a/docs/issues.md b/docs/issues.md new file mode 100644 index 00000000..46cc1102 --- /dev/null +++ b/docs/issues.md @@ -0,0 +1,61 @@ +--- +title: Known Issues +layout: page +--- + +# Known Issues + +- Some internal structures in Solaar have been updated to use more standard Python language features. + This has caused some problems and introduced bugs are still being found. + +- Onboard Profiles, when active, can prevent changes to other settings, such as Polling Rate, DPI, and various LED settings. Which settings are affected depends on the device. To make changes to affected settings, disable Onboard Profiles. If Onboard Profiles are later enabled the affected settings may change to the value in the profile. + +- Bluez 5.73 does not remove Bluetooth devices when they disconnect. + Solaar 1.1.12 processes the DBus disconnection and connection messages from Bluez and does re-initialize devices when they reconnect. + The HID++ driver does not re-initialize devices, which causes problems with smooth scrolling. + Until the problem is resolved having Scroll Wheel Resolution set to true (and not ignored) may be helpful. + +- The Linux HID++ driver modifies the Scroll Wheel Resolution setting to + implement smooth scrolling. If Solaar changes this setting, scrolling + can be either very fast or very slow. To fix this problem + click on the icon at the right edge of the setting to set it to + "Ignore this setting", which is the default for new devices. + The mouse has to be reset (e.g., by turning it off and on again) before this fix will take effect. + +- Solaar expects that it has exclusive control over settings that are not ignored. + Running other programs that modify these settings, such as logiops, + will likely result in unexpected device behavior. + +- The driver also sets the scrolling direction to its normal setting when implementing smooth scrolling. + This can interfere with the Scroll Wheel Direction setting, requiring flipping this setting back and forth + to restore reversed scrolling. + +- The driver sends messages to devices that do not conform with the Logitech HID++ specification + resulting in responses being sent back that look like other messages. For some devices this causes + Solaar to report incorrect battery levels. + +- Solaar normally uses icon names for its icons, which in some system tray implementations + results in missing or wrong-sized icons. + The `--tray-icon-size` option forces Solaar to use icon files of appropriate size + for tray icons instead, which produces better results in some system tray implementations. + To use icon files close to 32 pixels in size use `--tray-icon-size=32`. + +- The icon in the system tray can show up as 'black on black' in dark + themes or as non-symbolic when the theme uses symbolic icons. This is due to problems + in some system tray implementations. Changing to a different theme may help. + The `--battery-icons=symbolic` option can be used to force symbolic icons. + +- Solaar uses uinput to simulate input + but this needs write permission on /dev/uinput. + For more information see [the rules page](https://pwr-solaar.github.io/Solaar/rules). + +- Diverted keys remain diverted and so do not have their normal behavior when Solaar terminates + or a device disconnects from a host that is running Solaar. If necessary, their normal behavior + can be reestablished by turning the device off and on again. This is most important to restore + the host switching behavior of a host switch key that was diverted, for example to switch away + from a host that crashed or was turned off. + +- When a receiver-connected device changes hosts Solaar remembers which diverted keys were down on it. + When the device changes back the first time any of these diverted keys is depressed Solaar will not + realize that the key was newly depressed. For this reason Solaar rules that can change hosts should + trigger on key releasing. diff --git a/docs/rules.md b/docs/rules.md index 01439920..da19895d 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -3,11 +3,10 @@ title: Rule Processing of HID++ Notifications layout: page --- +# Rule Processing of HID++ Notifications Creating and editing most rules can be done in the Solaar GUI, by pressing the 'Rule Editor' button in the Solaar main window. -Rule processing is an experimental feature. Significant changes might be made in response to problems. - Note that rule processing only fully works under X11. When running under Wayland with X11 libraries loaded some features will not be available. When running under Wayland without X11 libraries loaded even more features will not be available. @@ -16,16 +15,17 @@ although on GNOME desktop under Wayland, you can use those with the Solaar Gnome You can install it from `https://extensions.gnome.org/extension/6162/solaar-extension`. Under Wayland using keyboard groups may result in incorrect symbols being input for simulated input. Under Wayland simulating inputs when modifier keys are pressed may result in incorrect symbols being sent. -Simulated input uses Xtest if available under X11 or uinput if the user has write access to /dev/uinput. -The easiest way to maintain write access to /dev/uinput is to use Solaar's alternative udev rule by downloading -`https://raw.githubusercontent.com/pwr-Solaar/Solaar/master/rules.d-uinput/42-logitech-unify-permissions.rules` +Simulated input uses uinput to simulate input so the user has to have write access to /dev/uinput. +The easiest way to maintain write access to /dev/uinput is to use Solaar's udev rule by downloading +`https://raw.githubusercontent.com/pwr-Solaar/Solaar/master/rules.d/42-logitech-unify-permissions.rules` and copying it as root into the `/etc/udev/rules.d` directory. You may have to reboot your system for the write permission to be set up. Another way to get write access to /dev/uinput is to run `sudo setfacl -m u:${USER}:rw /dev/uinput` but this needs to be done every time the system is rebooted. -Logitech devices that use HID++ version 2.0 or greater produce feature-based -notifications that Solaar can process using a simple rule language. For +## HID++ notifications and diversion +Logitech devices that use HID++ version 2.0 or greater, produce feature-based +notifications that Solaar can process using a simple rule language. For example, using rules Solaar can emulate an `XF86_MonBrightnessDown` key tap in response to the pressing of the `Brightness Down` key on Craft keyboards, which normally does not produce any input at all when the keyboard is in @@ -34,7 +34,7 @@ Windows mode. Solaar's rules only trigger on HID++ notifications so device actions that normally produce HID output have to be first be set (diverted) to produce HID++ notifications instead of their normal behavior. -Currently Solaar can divert some mouse scroll wheels, some +Currently, Solaar can divert some mouse scroll wheels, some mouse thumb wheels, the crown of Craft keyboards, and some keys and buttons. If the scroll wheel, thumb wheel, crown, key, or button is not diverted by setting the appropriate setting then no HID++ notification is @@ -42,50 +42,60 @@ generated and rules will not be triggered by manipulating the wheel, crown, key, Look for `HID++` or `Diversion` settings to see what diversion can be done with your devices. +### Show notifications Running Solaar with the `-ddd` option will show information about notifications, including their feature name, report number, and data. In response to a feature-based HID++ notification Solaar runs a sequence of -rules. A `Rule` is a sequence of components, which are either sub-rules, -conditions, or actions. Conditions and actions are dictionaries with one +rules. A `Rule` is a sequence of components, which are either sub-rules, +conditions, or actions. Conditions and actions are dictionaries with one entry whose key is the name of the condition or action and whose value is the argument of the action. If the last thing that a rule does is execute an action, no more rules are processed for the notification. -Rules are evaluated by evaluating each of their components in order. The +Rules are evaluated by evaluating each of their components in order. The evaluation of a rule is terminated early if a condition component evaluates -to false or the last evaluated sub-component of a component is an action. A -rule is false if its last evaluated component evaluates to a false value. +to false or the last evaluated subcomponent of a component is an action. A +rule is false if its last evaluated component evaluates to false. +## Conditions + +### Not `Not` conditions take a single component and are true if their component evaluates to a false value. + +### Or `Or` conditions take a sequence of components and are evaluated by evaluating each of their components in order. An Or condition is terminated early if a component evaluates to true or the -last evaluated sub-component of a component is an action. +last evaluated subcomponent of a component is an action. A Or condition is true if its last evaluated component evaluates to a true -value. `And` conditions take a sequence of components which are evaluated the same +value. `And` conditions take a sequence of components which are evaluated the same as rules. +### Feature `Feature` conditions are true if the name of the feature of the current notification is their string argument. `Report` conditions are true if the report number in the current notification is their integer argument. +### Key `Key` conditions are true if the Logitech name of the current **diverted** key or button being pressed is their -string argument. Alternatively, if the argument is a list `[name, action]` where `action` +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. These names are also shown in the output of `solaar show` in the 'reprogrammable keys' -section. Only keys or buttons that have 'divertable' in their report can be diverted. -Some keyboards have Gn, Mn, or MR keys, which are diverted using the 'Divert G Keys' setting. +matched, respectively. Logitech key and button names are shown in the `Key/Button Diversion` +setting. These names are also shown in the output of `solaar show` in the 'Reprogrammable keys' +section. Only keys or buttons that have 'Divertable' in their report can be diverted. +Some keyboards have 'Gn', 'Mn', or 'MR' keys, which are diverted using the 'Divert G Keys' setting. +### Key is down `KeyIsDown` conditions are true if the **diverted** key or button that is their string argument is currently down. Note that this only works for **diverted** keys or buttons, including diverted Gn, Mn, and MR keys. +### Key and button diversion Solaar can also create special notifications in response to mouse movements on some mice. Setting `Key/Button Diversion` for a key or button to Mouse Gestures causes the key or button to create a `Mouse Gesture` notification for the period that the key or button is depressed. @@ -95,6 +105,7 @@ Pressing a diverted key creates a key event. When the key is released the sequence of events is sent as a synthetic notification that can be matched with `Mouse Gesture` conditions. +### Mouse gestures `Mouse Gesture` conditions are true if the actions (mouse movements and diverted key presses) taken while a mouse gestures button is held down match the arguments of the condition. Mouse gestures buttons can be set using the 'Key/Button Diversion' setting, by changing the value to `Mouse Gestures`. The arguments of a Mouse Gesture condition can be a direction, i.e., `Mouse Up`, `Mouse Down`, `Mouse Left`, `Mouse Right`, `Mouse Up-Left`, `Mouse Up-Right`, `Mouse Down-Left`, or `Mouse Down-Right`, or the Logitech name of a key. @@ -104,24 +115,32 @@ The condition `Smart Shift` -> `Mouse Down` -> `Back Button` would match pressin Directions and buttons can be mixed and chained together however you like. It's possible to create a `No-op` gesture by clicking 'Delete' on the initial Action when you first create the rule. This gesture will trigger when you simply click a Mouse Gestures button. +### Key modifiers `Modifiers` conditions take either a string or a sequence of strings, which can only be `Shift`, `Control`, `Alt`, and `Super`. Modifiers conditions are true if their argument is the current keyboard modifiers. +### Process focused `Process` conditions are true if the process for the focused input window or the window's Window manager class or instance name starts with their string argument. + +### Window under cursor `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. +### Device notification and device active `Device` conditions are true if a particular device originated the notification. `Active` conditions are true if a particular device is active. `Device` and `Active` conditions take one argument, which is the serial number or unit ID of a device, -as shown in Solaar's detail pane. -Some older devices do not have a useful serial number or unit ID and so cannot be tested for by these conditions. +as shown in Solaar's detail pane, or either of its names, as shown by Solaar. +Some older devices do not have a useful serial number or unit ID and so cannot +distinguished from other devices with the same names. -`Host' conditions are true if the computers hostname starts with the condition's argument. +### Host +`Host` conditions are true if the computers hostname starts with the condition's argument. +### Solaar device setting `Setting` conditions check the value of a Solaar setting on a device. `Setting` conditions take three or four arguments, depending on the setting: the Serial number or Unit ID of a device, as shown in Solaar's detail pane, @@ -147,16 +166,17 @@ For settings that use gestures as an argument the internal name of the gesture i which can be found in the GESTURE2_GESTURES_LABELS structure in lib/logitech_receiver/settings_templates. For boolean settings '~' can be used to toggle the setting. +### Test and TestBytes `Test` and `TestBytes` conditions are true if their test evaluates to true on the feature, -report, and data of the current notification. -`TestBytes` conditions can return a number instead of a boolean. +report and data of the current notification. +`TestBytes` conditions can return a number instead of a boolean. `TestBytes` conditions consist of a sequence of three or four integers and use the first two to select bytes of the notification data. Writing this kind of test condition is not trivial. -Three-element `TestBytes` conditions are true if the selected bytes bit-wise anded +Three-element `TestBytes` conditions are true if the selected bytes bit-wise AND with its third element is non-zero. -The value of these test conditions is the result of the and. +The value of these test conditions is the result of the AND. Four-element `TestBytes` conditions are true if the selected bytes form a signed integer between the third and fourth elements. The value of these conditions is the signed value of the selected bytes @@ -184,12 +204,15 @@ This displacement is reset when the thumb wheel is inactive. With a parameter the test is only true if the current thumb wheel displacement is greater than the parameter. The displacement is then lessened by the amount of the parameter. +## Actions + +### Key press A `KeyPress` action takes either the name of an X11 key symbol, such as "a", -a list of X11 key symbols, such as "a" or "Control+a", +a list of X11 key symbols, such as "a" or "CTRL + A", or a two-element list with the first element as above -and the second element one of 'click', 'depress', or 'release' +and the second element one of `'click'`, `'depress'`, or `'release'` and executes key actions on a simulated keyboard to produce these symbols. -Use separate `KeyPress` actions for multiple characters, +Use separate `KeyPress` actions for multiple characters, i.e., don't use a single `KeyPress` like 'a+b'. The `KeyPress` action normally both depresses and releases (clicks) the keys, but can also just depress the keys or just release the keys. @@ -215,38 +238,43 @@ simulate inputting a key symbol. Unfortunately, this determination can go wrong in several ways and is more likely to go wrong under Wayland than under X11. +### Mouse scroll A `MouseScroll` action takes a sequence of two numbers and simulates a horizontal and vertical mouse scroll of these amounts. If the previous condition in the parent rule returns a number the scroll amounts are multiplied by this number. + +### Mouse click A `MouseClick` action takes a mouse button name (`left`, `middle` or `right`) and a positive number or 'click', 'depress', or 'release'. The action simulates that number of clicks of the specified button or just one click, depress, or release of the button. -A `MouseClick` action takes a mouse button name (`left`, `middle` or `right`) and a positive number, and simulates that number of clicks of the specified button. + +### Execute An `Execute` action takes a program and arguments and executes it asynchronously. +### Set setting A `Set` action changes a Solaar setting for a device, provided that the device is on-line. `Set` actions take three or four arguments, depending on the setting. The first two are the Serial number or Unit ID of a device, as shown in Solaar's detail pane, or null for the device that initiated rule processing; and -the internal name of a setting (which can be found from solaar config \). +the internal name of a setting (which can be found from `solaar config `). Simple settings take one extra argument, the value to set the setting to. -For boolean settings '~' can be used to toggle the setting. +For boolean settings `~` can be used to toggle the setting. Other simple settings take two extra arguments, a key indicating which sub-setting to set and the value to set it to. For settings that use gestures as an argument the internal name of the gesture is used, -which can be found in the GESTURE2_GESTURES_LABELS structure in lib/logitech_receiver/settings_templates. +which can be found in the GESTURE2_GESTURES_LABELS structure in `lib/logitech_receiver/settings_templates`. All settings are supported. +### Later A `Later` action executes rule components later. `Later` actions take an integer delay in seconds between 1 and 100 followed by zero or more rule components that will be executed later. Processing of the rest of the rule continues immediately. -Solaar has several built-in rules, which are run after user-created rules and so can be overridden by user-created rules. -One rule turns +## Built-in Rules + +Solaar has a built-in rule, which is run after user-created rules and so can be overridden by user-created rules. +This rule turns `Brightness Down` key press notifications into `XF86_MonBrightnessDown` key taps and `Brightness Up` key press notifications into `XF86_MonBrightnessUp` key taps. -Another rule makes Craft crown ratchet movements move between tabs when the crown is pressed -and up and down otherwise. -A third rule turns Craft crown ratchet movements into `XF86_AudioNext` or `XF86_AudioPrev` key taps when the crown is pressed and `XF86_AudioRaiseVolume` or `XF86_AudioLowerVolume` otherwise. -A fourth rule doubles the speed of `THUMB WHEEL` movements unless the `Control` modifier is on. -All of these rules are only active if the key or feature is diverted, of course. + +## Example Solaar Rule File Solaar reads rules from a YAML configuration file (normally `~/.config/solaar/rules.yaml`). This file contains zero or more documents, each a rule. @@ -294,10 +322,11 @@ Here is a file with six rules: ... ``` +## Button diversion example Here is an example showing how to divert the Back Button on an MX Master 3 so that pressing the button will initiate rule processing and a rule that triggers on this notification and switches the mouse to host 3 after popping up a simple notification. -![Solaar-divert-back](Solaar-main-window-back-divert.png) +![Solaar-divert-back](screenshots/Solaar-main-window-back-divert.png) -![Solaar-rule-back-host](Solaar-rule-editor.png) +![Solaar-rule-back-host](screenshots/Solaar-rule-editor.png) diff --git a/docs/Solaar-main-window-back-divert.png b/docs/screenshots/Solaar-main-window-back-divert.png similarity index 100% rename from docs/Solaar-main-window-back-divert.png rename to docs/screenshots/Solaar-main-window-back-divert.png diff --git a/docs/Solaar-main-window-button-actions.png b/docs/screenshots/Solaar-main-window-button-actions.png similarity index 100% rename from docs/Solaar-main-window-button-actions.png rename to docs/screenshots/Solaar-main-window-button-actions.png diff --git a/docs/Solaar-main-window-keyboard.png b/docs/screenshots/Solaar-main-window-keyboard.png similarity index 100% rename from docs/Solaar-main-window-keyboard.png rename to docs/screenshots/Solaar-main-window-keyboard.png diff --git a/docs/Solaar-main-window-mouse.png b/docs/screenshots/Solaar-main-window-mouse.png similarity index 100% rename from docs/Solaar-main-window-mouse.png rename to docs/screenshots/Solaar-main-window-mouse.png diff --git a/docs/Solaar-main-window-multiple.png b/docs/screenshots/Solaar-main-window-multiple.png similarity index 100% rename from docs/Solaar-main-window-multiple.png rename to docs/screenshots/Solaar-main-window-multiple.png diff --git a/docs/Solaar-main-window-offline.png b/docs/screenshots/Solaar-main-window-offline.png similarity index 100% rename from docs/Solaar-main-window-offline.png rename to docs/screenshots/Solaar-main-window-offline.png diff --git a/docs/Solaar-main-window-receiver.png b/docs/screenshots/Solaar-main-window-receiver-old.png similarity index 100% rename from docs/Solaar-main-window-receiver.png rename to docs/screenshots/Solaar-main-window-receiver-old.png diff --git a/docs/screenshots/Solaar-main-window-receiver.png b/docs/screenshots/Solaar-main-window-receiver.png new file mode 100644 index 00000000..8cf10dba Binary files /dev/null and b/docs/screenshots/Solaar-main-window-receiver.png differ diff --git a/docs/Solaar-menu.png b/docs/screenshots/Solaar-menu.png similarity index 100% rename from docs/Solaar-menu.png rename to docs/screenshots/Solaar-menu.png diff --git a/docs/Solaar-rule-editor.png b/docs/screenshots/Solaar-rule-editor.png similarity index 100% rename from docs/Solaar-rule-editor.png rename to docs/screenshots/Solaar-rule-editor.png diff --git a/docs/uninstallation.md b/docs/uninstallation.md new file mode 100644 index 00000000..4051b7d1 --- /dev/null +++ b/docs/uninstallation.md @@ -0,0 +1,40 @@ +--- +title: Uninstalling Solaar +layout: page +--- + +# Uninstalling Solaar + +## Uninstalling from Debian systems + +If you installed Solaar using `apt`, you can remove it by running: + +```bash +sudo apt remove --purge solaar +``` + +## Uninstalling from GitHub + +If you cloned and installed Solaar from GitHub manually, navigate to the cloned directory and run: + +```bash +sudo make uninstall +``` + +## Removing Configuration Files + +Solaar may leave behind configuration files in your home directory. To delete them, run: + +```bash +rm -rf ~/.config/solaar +``` + +## Verifying Uninstallation + +To confirm that Solaar is fully removed, try running: + +```bash +which solaar +``` + +If no output is returned, Solaar has been successfully uninstalled. diff --git a/docs/usage.md b/docs/usage.md index 83f36707..11c3d3d3 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -22,7 +22,7 @@ The following is an image of the Solaar menu and the icon (the battery symbol is in the system tray at the left of the image). The icon can also be other battery icons or versions of the Logitech Unifying icon. -![Solaar-menu](Solaar-menu.png) +![Solaar-menu](screenshots/Solaar-menu.png) Clicking on “Quit” in the Solaar menu terminates the program. Clicking on “About Solaar” pops up a window with further information about Solaar. @@ -32,10 +32,13 @@ Clicking on “About Solaar” pops up a window with further information about S There are several options that affect how the Solaar GUI behaves: * `--help` shows a help message and then quits +* `--version` shows the version of Solaar and then quits * `--window=show` starts Solaar with the main window showing * `--window=hide` starts Solaar with the main window not showing * `--window=only` starts Solaar with no system tray icon and the main window showing +* `--battery-icons=regular` uses regular icons for battery levels * `--battery-icons=symbolic` uses symbolic icons for battery levels +* `--battery-icons=solaar` uses only the Solaar icon in the system tray ## Solaar main window @@ -61,7 +64,7 @@ To pair with a Bolt receiver you have to type a passcode followed by enter or click the left and right buttons in the correct sequence followed by clicking both buttons simultaneously. -![Solaar-main-window-receiver](Solaar-main-window-receiver.png) +![Solaar-main-window-receiver](screenshots/Solaar-main-window-receiver.png) When a device is selected you can unpair the device if your receiver supports unpairing. To unpair the device, just click on the “Unpair” button and @@ -90,26 +93,26 @@ You can also see and change the settings of devices. Changing settings is performed by clicking on buttons, moving sliders, or selecting from alternatives. -![Solaar-main-window-keyboard](Solaar-main-window-keyboard.png) +![Solaar-main-window-keyboard](screenshots/Solaar-main-window-keyboard.png) -![Solaar-main-window-mouse](Solaar-main-window-mouse.png) +![Solaar-main-window-mouse](screenshots/Solaar-main-window-mouse.png) Device settings now have a clickable icon that determines whether the setting can be changed and whether the setting is ignored. -![Solaar-divert-back](Solaar-main-window-back-divert.png) +![Solaar-divert-back](screenshots/Solaar-main-window-back-divert.png) If the selected device that is paired with a receiver is powered down or otherwise disconnected its settings cannot be changed but it still can be unpaired if its receiver allows unpairing. -![Solaar-main-window-offline](Solaar-main-window-offline.png) +![Solaar-main-window-offline](screenshots/Solaar-main-window-offline.png) If a device is paired with a receiver but directly connected via USB or Bluetooth the receiver pairing will show up as well as the direct connection. The device can only be manipulated using the direct connection. -![Solaar-main-window-multiple](Solaar-main-window-multiple.png) +![Solaar-main-window-multiple](screenshots/Solaar-main-window-multiple.png) #### Remapping key and button actions @@ -124,7 +127,7 @@ action is always the one shown first in the list. As with all settings, Solaar will remember past action settings and restore them on the device from then on. -![Solaar-main-window-actions](Solaar-main-window-button-actions.png) +![Solaar-main-window-actions](screenshots/Solaar-main-window-button-actions.png) The names of the keys, buttons, and actions are mostly taken from Logitech documentation and may not be completely obvious. @@ -133,9 +136,9 @@ It is possible to end up with an unusable system, for example by having no way to do a mouse left click, so exercise caution when remapping keys or buttons that are needed to operate your system. -## Solaar command line interface +## Solaar command-line interface -Solaar also has a command line interface that can do most of what can be +Solaar also has a command-line interface that can do most of what can be done using the main window. For more information on the command line interface, run `solaar --help` to see the commands and then `solaar --help` to see the arguments to any of the commands. diff --git a/jekyll/images/bg_hr.png b/jekyll/images/bg_hr.png deleted file mode 100644 index 92f519c8..00000000 Binary files a/jekyll/images/bg_hr.png and /dev/null differ diff --git a/jekyll/images/blacktocat.png b/jekyll/images/blacktocat.png deleted file mode 100644 index 59248ee9..00000000 Binary files a/jekyll/images/blacktocat.png and /dev/null differ diff --git a/jekyll/images/icon_download.png b/jekyll/images/icon_download.png deleted file mode 100644 index 72003409..00000000 Binary files a/jekyll/images/icon_download.png and /dev/null differ diff --git a/jekyll/images/sprite_download.png b/jekyll/images/sprite_download.png deleted file mode 100644 index ebf5fe20..00000000 Binary files a/jekyll/images/sprite_download.png and /dev/null differ diff --git a/lib/hid_parser/__init__.py b/lib/hid_parser/__init__.py new file mode 100644 index 00000000..459b809e --- /dev/null +++ b/lib/hid_parser/__init__.py @@ -0,0 +1,1052 @@ +# SPDX-License-Identifier: MIT + +from __future__ import annotations # noqa:F407 + +import functools +import struct +import sys +import textwrap +import typing +import warnings + +from typing import Any +from typing import Dict +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Literal +from typing import Optional +from typing import Sequence +from typing import TextIO +from typing import Tuple +from typing import Union + +import hid_parser.data + +__version__ = "0.0.3" + + +class HIDWarning(Warning): + pass + + +class HIDComplianceWarning(HIDWarning): + pass + + +class HIDReportWarning(HIDWarning): + pass + + +class HIDUnsupportedWarning(HIDWarning): + pass + + +class Type: + MAIN = 0 + GLOBAL = 1 + LOCAL = 2 + + +class TagMain: + INPUT = 0b1000 + OUTPUT = 0b1001 + FEATURE = 0b1011 + COLLECTION = 0b1010 + END_COLLECTION = 0b1100 + + +class TagGlobal: + USAGE_PAGE = 0b0000 + LOGICAL_MINIMUM = 0b0001 + LOGICAL_MAXIMUM = 0b0010 + PHYSICAL_MINIMUM = 0b0011 + PHYSICAL_MAXIMUM = 0b0100 + UNIT_EXPONENT = 0b0101 + UNIT = 0b0110 + REPORT_SIZE = 0b0111 + REPORT_ID = 0b1000 + REPORT_COUNT = 0b1001 + PUSH = 0b1010 + POP = 0b1011 + + +class TagLocal: + USAGE = 0b0000 + USAGE_MINIMUM = 0b0001 + USAGE_MAXIMUM = 0b0010 + DESIGNATOR_INDEX = 0b0011 + DESIGNATOR_MINIMUM = 0b0100 + DESIGNATOR_MAXIMUM = 0b0101 + STRING_INDEX = 0b0111 + STRING_MINIMUM = 0b1000 + STRING_MAXIMUM = 0b1001 + DELIMITER = 0b1010 + + +def _data_bit_shift(data: Sequence[int], offset: int, length: int) -> Sequence[int]: + if not length > 0: + raise ValueError(f"Invalid specified length: {length}") + + left_extra = offset % 8 + right_extra = 8 - (offset + length) % 8 + start_offset = offset // 8 + end_offset = (offset + length - 1) // 8 + byte_length = (length - 1) // 8 + 1 + + if not end_offset < len(data): + raise ValueError(f"Invalid data length: {len(data)} (expecting {end_offset + 1})") + + shifted = [0] * byte_length + + if right_extra == 8: + right_extra = 0 + + i = end_offset + shifted_offset = byte_length - 1 + while shifted_offset >= 0: + shifted[shifted_offset] = data[i] >> right_extra + + if i - start_offset >= 0: + shifted[shifted_offset] |= (data[i - 1] & (0xFF >> (8 - right_extra))) << (8 - right_extra) + + shifted_offset -= 1 + i -= 1 + + shifted[0] &= 0xFF >> ((left_extra + right_extra) % 8) + + if not len(shifted) == byte_length: + raise ValueError("Invalid data") + + return shifted + + +class BitNumber(int): + def __init__(self, value: int): + self._value = value + + def __int__(self) -> int: + return self._value + + def __eq__(self, other: Any) -> bool: + try: + return self._value == int(other) + except: # noqa: E722 + return False + + @property + def byte(self) -> int: + """ + Number of bytes + """ + return self._value // 8 + + @property + def bit(self) -> int: + """ + Number of unaligned bits + + n.byte * 8 + n.bits = n + """ + if self.byte == 0: + return self._value + + return self._value % (self.byte * 8) + + @staticmethod + def _param_repr(value: int, unit: str) -> str: + if value != 1: + unit += "s" + return f"{value}{unit}" + + def __repr__(self) -> str: + byte_str = self._param_repr(self.byte, "byte") + bit_str = self._param_repr(self.bit, "bit") + + if self.byte == 0 and self.bit == 0: + return bit_str + + parts = [] + if self.byte != 0: + parts.append(byte_str) + if self.bit != 0: + parts.append(bit_str) + + return " ".join(parts) + + +class Usage: + def __init__( + self, page: Optional[int] = None, usage: Optional[int] = None, *, extended_usage: Optional[int] = None + ) -> None: + if extended_usage and page and usage: + raise ValueError("You need to specify either the usage page and usage or the extended usage") + if extended_usage is not None: + self.page = extended_usage >> (2 * 8) + self.usage = extended_usage & 0xFFFF + elif page is not None and usage is not None: + self.page = page + self.usage = usage + else: + raise ValueError("No usage specified") + + def __int__(self) -> int: + return self.page << (2 * 8) | self.usage + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, self.__class__): + return False + return self.page == other.page and self.usage == other.usage + + def __hash__(self) -> int: + return self.usage << (2 * 8) + self.page + + def __repr__(self) -> str: + try: + page_str = hid_parser.data.UsagePages.get_description(self.page) + except KeyError: + page_str = f"0x{self.page:04x}" + usage_str = f"0x{self.usage:04x}" + else: + try: + page = hid_parser.data.UsagePages.get_subdata(self.page) + usage_str = page.get_description(self.usage) + except (KeyError, ValueError): + usage_str = f"0x{self.usage:04x}" + return f"Usage(page={page_str}, usage={usage_str})" + + @property + def usage_types(self) -> Tuple[hid_parser.data.UsageTypes]: + subdata = hid_parser.data.UsagePages.get_subdata(self.page).get_subdata(self.usage) + + if isinstance(subdata, tuple): + types = subdata + else: + types = (subdata,) + + for typ in types: + if not isinstance(typ, hid_parser.data.UsageTypes): + raise ValueError(f"Expecting usage type but got '{type(typ)}'") + + return typing.cast(Tuple[hid_parser.data.UsageTypes], types) + + +class UsageValue: + def __init__(self, item: MainItem, value: int): + self._item = item + self._value = value + + def __int__(self) -> int: + return self.value + + def __repr__(self) -> str: + return repr(self.value) + + @property + def value(self) -> Union[int, bool]: + return self._value + + @property + def constant(self) -> bool: + return self._item.constant + + @property + def data(self) -> bool: + return self._item.data + + @property + def relative(self) -> bool: + return self._item.relative + + @property + def absolute(self) -> bool: + return self._item.absolute + + +class VendorUsageValue(UsageValue): + def __init__( + self, + item: MainItem, + *, + value: Optional[int] = None, + value_list: Optional[List[int]] = None, + ): + self._item = item + if value: + self._list = [value] + elif value_list: + self._list = value_list + else: + self._list = [] + + def __int__(self) -> int: + return self.value + + def __iter__(self) -> Iterator[int]: + return iter(self.list) + + @property + def value(self) -> Union[int, bool]: + return int.from_bytes(self._list, byteorder="little") + + @property + def list(self) -> List[int]: + return self._list + + +class BaseItem: + def __init__(self, offset: int, size: int): + self._offset = BitNumber(offset) + self._size = BitNumber(size) + + @property + def offset(self) -> BitNumber: + return self._offset + + @property + def size(self) -> BitNumber: + return self._size + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(offset={self.offset}, size={self.size})" + + +class PaddingItem(BaseItem): + pass + + +class MainItem(BaseItem): + def __init__( + self, + offset: int, + size: int, + flags: int, + logical_min: int, + logical_max: int, + physical_min: Optional[int] = None, + physical_max: Optional[int] = None, + ): + super().__init__(offset, size) + self._flags = flags + self._logical_min = logical_min + self._logical_max = logical_max + self._physical_min = physical_min + self._physical_max = physical_max + # TODO: unit + + @property + def offset(self) -> BitNumber: + return self._offset + + @property + def size(self) -> BitNumber: + return self._size + + @property + def logical_min(self) -> int: + return self._logical_min + + @property + def logical_max(self) -> int: + return self._logical_max + + @property + def physical_min(self) -> Optional[int]: + return self._physical_min + + @property + def physical_max(self) -> Optional[int]: + return self._physical_max + + # flags + + @property + def constant(self) -> bool: + return self._flags & (1 << 0) != 0 + + @property + def data(self) -> bool: + return self._flags & (1 << 0) == 0 + + @property + def relative(self) -> bool: + return self._flags & (1 << 2) != 0 + + @property + def absolute(self) -> bool: + return self._flags & (1 << 2) == 0 + + +class VariableItem(MainItem): + _INCOMPATIBLE_TYPES = ( + # array types + hid_parser.data.UsageTypes.SELECTOR, + # collection types + hid_parser.data.UsageTypes.NAMED_ARRAY, + hid_parser.data.UsageTypes.COLLECTION_APPLICATION, + hid_parser.data.UsageTypes.COLLECTION_LOGICAL, + hid_parser.data.UsageTypes.COLLECTION_PHYSICAL, + hid_parser.data.UsageTypes.USAGE_SWITCH, + hid_parser.data.UsageTypes.USAGE_MODIFIER, + ) + + def __init__( + self, + offset: int, + size: int, + flags: int, + usage: Usage, + logical_min: int, + logical_max: int, + physical_min: Optional[int] = None, + physical_max: Optional[int] = None, + ): + super().__init__(offset, size, flags, logical_min, logical_max, physical_min, physical_max) + self._usage = usage + + try: + if all(usage_type in self._INCOMPATIBLE_TYPES for usage_type in usage.usage_types): + warnings.warn(HIDComplianceWarning(f"{usage} has no compatible usage types with a variable item")) # noqa + except (KeyError, ValueError): + pass + + def __repr__(self) -> str: + return f"VariableItem(offset={self.offset}, size={self.size}, usage={self.usage})" + + def parse(self, data: Sequence[int]) -> UsageValue: + data = _data_bit_shift(data, self.offset, self.size) + + if hid_parser.data.UsageTypes.LINEAR_CONTROL in self.usage.usage_types or any( + usage_type in hid_parser.data.UsageTypesData and usage_type != hid_parser.data.UsageTypes.SELECTOR + for usage_type in self.usage.usage_types + ): # int + value = int.from_bytes(data, byteorder="little") + elif ( + hid_parser.data.UsageTypes.ON_OFF_CONTROL in self.usage.usage_types + and not self.preferred_state + and self.logical_min == -1 + and self.logical_max == 1 + ): # bool - -1 is false + value = int.from_bytes(data, byteorder="little") == 1 + else: # bool + value = bool.from_bytes(data, byteorder="little") + + return UsageValue(self, value) + + @property + def usage(self) -> Usage: + return self._usage + + # flags (variable only, see HID spec 1.11 page 32) + + @property + def wrap(self) -> bool: + return self._flags & (1 << 3) != 0 + + @property + def linear(self) -> bool: + return self._flags & (1 << 4) != 0 + + @property + def preferred_state(self) -> bool: + return self._flags & (1 << 5) != 0 + + @property + def null_state(self) -> bool: + return self._flags & (1 << 6) != 0 + + @property + def buffered_bytes(self) -> bool: + return self._flags & (1 << 7) != 0 + + @property + def bitfield(self) -> bool: + return self._flags & (1 << 7) == 0 + + +class ArrayItem(MainItem): + _INCOMPATIBLE_TYPES = ( + # variable types + hid_parser.data.UsageTypes.LINEAR_CONTROL, + hid_parser.data.UsageTypes.ON_OFF_CONTROL, + hid_parser.data.UsageTypes.MOMENTARY_CONTROL, + hid_parser.data.UsageTypes.ONE_SHOT_CONTROL, + hid_parser.data.UsageTypes.RE_TRIGGER_CONTROL, + hid_parser.data.UsageTypes.STATIC_VALUE, + hid_parser.data.UsageTypes.STATIC_FLAG, + hid_parser.data.UsageTypes.DYNAMIC_VALUE, + hid_parser.data.UsageTypes.DYNAMIC_FLAG, + # collection types + hid_parser.data.UsageTypes.NAMED_ARRAY, + hid_parser.data.UsageTypes.COLLECTION_APPLICATION, + hid_parser.data.UsageTypes.COLLECTION_LOGICAL, + hid_parser.data.UsageTypes.COLLECTION_PHYSICAL, + hid_parser.data.UsageTypes.USAGE_SWITCH, + hid_parser.data.UsageTypes.USAGE_MODIFIER, + ) + _IGNORE_USAGE_VALUES = ((hid_parser.data.UsagePages.KEYBOARD_KEYPAD_PAGE, hid_parser.data.KeyboardKeypad.NO_EVENT),) + + def __init__( + self, + offset: int, + size: int, + count: int, + flags: int, + usages: List[Usage], + logical_min: int, + logical_max: int, + physical_min: Optional[int] = None, + physical_max: Optional[int] = None, + ): + super().__init__(offset, size, flags, logical_min, logical_max, physical_min, physical_max) + self._count = count + self._usages = usages + self._page = self._usages[0].page if usages else None + + for usage in self._usages: + if usage.page != self._page: + raise ValueError(f"Mismatching usage page in usage: {usage} (expecting {self._usages[0]})") + try: + if all(usage_type in self._INCOMPATIBLE_TYPES for usage_type in usage.usage_types): + warnings.warn(HIDComplianceWarning(f"{usage} has no compatible usage types with an array item")) # noqa + except (KeyError, ValueError): + pass + + self._ignore_usages: List[Usage] = [] + for page, usage_id in self._IGNORE_USAGE_VALUES: + assert isinstance(page, int) and isinstance(usage_id, int) + self._ignore_usages.append(Usage(page, usage_id)) + + def __repr__(self) -> str: + return ( + textwrap.dedent( + """ + ArrayItem( + offset={}, size={}, count={}, + usages=[ + {}, + ], + ) + """ + ) + .strip() + .format( + self.offset, + self.size, + self.count, + ",\n ".join(repr(usage) for usage in self.usages), + ) + ) + + def parse(self, data: Sequence[int]) -> Dict[Usage, UsageValue]: + usage_values: Dict[Usage, UsageValue] = {} + + for i in range(self.count): + aligned_data = _data_bit_shift(data, self.offset + i * 8, self.size) + usage = Usage(self._page, int.from_bytes(aligned_data, byteorder="little")) + + if usage in self._ignore_usages: + continue + + # vendor usages don't have usage any standard type - just save the raw data + if usage.page in hid_parser.data.UsagePages.VENDOR_PAGE: + if usage not in usage_values: + usage_values[usage] = VendorUsageValue( + self, + value=int.from_bytes(aligned_data, byteorder="little"), + ) + typing.cast(VendorUsageValue, usage_values[usage]).list.append( + int.from_bytes(aligned_data, byteorder="little") + ) + continue + + not_incompatible_type = all(usage_type not in self._INCOMPATIBLE_TYPES for usage_type in usage.usage_types) + if usage in self._usages and not_incompatible_type: + usage_values[usage] = UsageValue(self, True) + + return usage_values + + @property + def count(self) -> int: + return self._count + + @property + def usages(self) -> List[Usage]: + return self._usages + + +class InvalidReportDescriptor(Exception): + pass + + +# report ID (None for no report ID), item list +_ITEM_POOL = Dict[Optional[int], List[BaseItem]] + + +class ReportDescriptor: + def __init__(self, data: Sequence[int]) -> None: + self._data = data + + for byte in data: + if byte < 0 or byte > 255: + raise InvalidReportDescriptor( + f"A report descriptor should be represented by a list of bytes: found value {byte}" + ) + + self._input: _ITEM_POOL = {} + self._output: _ITEM_POOL = {} + self._feature: _ITEM_POOL = {} + + self._parse() + + @property + def data(self) -> Sequence[int]: + return self._data + + @property + def input_report_ids(self) -> List[Optional[int]]: + return list(self._input.keys()) + + @property + def output_report_ids(self) -> List[Optional[int]]: + return list(self._output.keys()) + + @property + def feature_report_ids(self) -> List[Optional[int]]: + return list(self._feature.keys()) + + def _get_report_size(self, items: List[BaseItem]) -> BitNumber: + size = 0 + for item in items: + if isinstance(item, ArrayItem): + size += item.size * item.count + else: + size += item.size + return BitNumber(size) + + def get_input_items(self, report_id: Optional[int] = None) -> List[BaseItem]: + return self._input[report_id] + + @functools.lru_cache(maxsize=16) # noqa + def get_input_report_size(self, report_id: Optional[int] = None) -> BitNumber: + return self._get_report_size(self.get_input_items(report_id)) + + def get_output_items(self, report_id: Optional[int] = None) -> List[BaseItem]: + return self._output[report_id] + + @functools.lru_cache(maxsize=16) # noqa + def get_output_report_size(self, report_id: Optional[int] = None) -> BitNumber: + return self._get_report_size(self.get_output_items(report_id)) + + def get_feature_items(self, report_id: Optional[int] = None) -> List[BaseItem]: + return self._feature[report_id] + + @functools.lru_cache(maxsize=16) # noqa + def get_feature_report_size(self, report_id: Optional[int] = None) -> BitNumber: + return self._get_report_size(self.get_feature_items(report_id)) + + def _parse_report_items(self, items: List[BaseItem], data: Sequence[int]) -> Dict[Usage, UsageValue]: + parsed: Dict[Usage, UsageValue] = {} + for item in items: + if isinstance(item, VariableItem): + parsed[item.usage] = item.parse(data) + elif isinstance(item, ArrayItem): + usage_values = item.parse(data) + for usage in usage_values: + if usage in parsed: + warnings.warn(HIDReportWarning(f"Overriding usage: {usage}")) # noqa + parsed.update(usage_values) + elif isinstance(item, PaddingItem): + pass + else: + raise TypeError(f"Unknown item: {item}") + return parsed + + def _parse_report(self, item_poll: _ITEM_POOL, data: Sequence[int]) -> Dict[Usage, UsageValue]: + if None in item_poll: # unnumbered reports + return self._parse_report_items(item_poll[None], data) + else: # numbered reports + return self._parse_report_items(item_poll[data[0]], data[1:]) + + def parse_input_report(self, data: Sequence[int]) -> Dict[Usage, UsageValue]: + return self._parse_report(self._input, data) + + def parse_output_report(self, data: Sequence[int]) -> Dict[Usage, UsageValue]: + return self._parse_report(self._output, data) + + def parse_feature_report(self, data: Sequence[int]) -> Dict[Usage, UsageValue]: + return self._parse_report(self._feature, data) + + def _iterate_raw(self) -> Iterable[Tuple[int, int, Optional[int]]]: + i = 0 + while i < len(self.data): + prefix = self.data[i] + tag = (prefix & 0b11110000) >> 4 + typ = (prefix & 0b00001100) >> 2 + size = prefix & 0b00000011 + + if size == 3: # 6.2.2.2 + size = 4 + + if size == 0: + data = None + elif size == 1: + if i + 1 >= len(self.data): + raise InvalidReportDescriptor(f"Invalid size: expecting >={i + 1}, got {len(self.data)}") + data = self.data[i + 1] + else: + if i + 1 + size >= len(self.data): + raise InvalidReportDescriptor(f"Invalid size: expecting >={i + 1 + size}, got {len(self.data)}") + if size == 2: + pack_type = "H" + elif size == 4: + pack_type = "L" + else: + raise ValueError(f"Invalid item size: {size}") + data = struct.unpack(f"<{pack_type}", bytes(self.data[i + 1 : i + 1 + size]))[0] + + yield typ, tag, data + + i += size + 1 + + def _append_item( + self, + offset_list: Dict[Optional[int], int], + pool: _ITEM_POOL, + report_id: Optional[int], + item: BaseItem, + ) -> None: + offset_list[report_id] += item.size + if report_id in pool: + pool[report_id].append(item) + else: + pool[report_id] = [item] + + def _append_items( + self, + offset_list: Dict[Optional[int], int], + pool: _ITEM_POOL, + report_id: Optional[int], + report_count: int, + report_size: int, + usages: List[Usage], + flags: int, + data: Dict[str, Any], + ) -> None: + item: BaseItem + is_array = flags & (1 << 1) == 0 # otherwise variable + + """ + HID 1.11, 6.2.2.9 says reports can be byte aligned by declaring a + main item without usage. A main item can have multiple usages, as I + interpret it, items are only considered padding when they have NO + usages. + """ + if len(usages) == 0 or not usages: + for _ in range(report_count): + item = PaddingItem(offset_list[report_id], report_size) + self._append_item(offset_list, pool, report_id, item) + return + + if is_array: + item = ArrayItem( + offset=offset_list[report_id], + size=report_size, + usages=usages, + count=report_count, + flags=flags, + **data, + ) + self._append_item(offset_list, pool, report_id, item) + else: + if len(usages) != report_count: + error_str = f"Expecting {report_count} usages but got {len(usages)}" + if len(usages) == 1: + warnings.warn(HIDComplianceWarning(error_str)) # noqa + usages *= report_count + else: + raise InvalidReportDescriptor(error_str) + + for usage in usages: + item = VariableItem( + offset=offset_list[report_id], + size=report_size, + usage=usage, + flags=flags, + **data, + ) + self._append_item(offset_list, pool, report_id, item) + + def _parse(self, level: int = 0, file: TextIO = sys.stdout) -> None: # noqa: C901 + offset_input: Dict[Optional[int], int] = { + None: 0, + } + offset_output: Dict[Optional[int], int] = { + None: 0, + } + offset_feature: Dict[Optional[int], int] = { + None: 0, + } + report_id: Optional[int] = None + report_count: Optional[int] = None + report_size: Optional[int] = None + usage_page: Optional[int] = None + usages: List[Usage] = [] + usage_min: Optional[int] = None + glob: Dict[str, Any] = {} + local: Dict[str, Any] = {} + + for typ, tag, data in self._iterate_raw(): + if typ == Type.MAIN: + if tag in (TagMain.COLLECTION, TagMain.END_COLLECTION): + usages = [] + + # we only care about input, output and features for now + if tag not in (TagMain.INPUT, TagMain.OUTPUT, TagMain.FEATURE): + continue + + if report_count is None: + raise InvalidReportDescriptor("Trying to append an item but no report count given") + if report_size is None: + raise InvalidReportDescriptor("Trying to append an item but no report size given") + + if tag == TagMain.INPUT: + if data is None: + raise InvalidReportDescriptor("Invalid input item") + self._append_items( + offset_input, self._input, report_id, report_count, report_size, usages, data, {**glob, **local} + ) + + elif tag == TagMain.OUTPUT: + if data is None: + raise InvalidReportDescriptor("Invalid output item") + self._append_items( + offset_output, + self._output, + report_id, + report_count, + report_size, + usages, + data, + {**glob, **local}, + ) + + elif tag == TagMain.FEATURE: + if data is None: + raise InvalidReportDescriptor("Invalid feature item") + self._append_items( + offset_feature, + self._feature, + report_id, + report_count, + report_size, + usages, + data, + {**glob, **local}, + ) + + # clear local + usages = [] + usage_min = None + local = {} + + # we don't care about collections for now, maybe in the future... + + elif typ == Type.GLOBAL: + if tag == TagGlobal.USAGE_PAGE: + usage_page = data + + elif tag == TagGlobal.LOGICAL_MINIMUM: + glob["logical_min"] = data + + elif tag == TagGlobal.LOGICAL_MAXIMUM: + glob["logical_max"] = data + + elif tag == TagGlobal.PHYSICAL_MINIMUM: + glob["physical_min"] = data + + elif tag == TagGlobal.PHYSICAL_MAXIMUM: + glob["physical_max"] = data + + elif tag == TagGlobal.REPORT_SIZE: + report_size = data + + elif tag == TagGlobal.REPORT_ID: + if not report_id and (self._input or self._output or self._feature): + raise InvalidReportDescriptor("Tried to set a report ID in a report that does not use them") + report_id = data + # initialize the item offset for this report ID + for offset_list in (offset_input, offset_output, offset_feature): + if report_id not in offset_list: + offset_list[report_id] = 0 + + elif tag in (TagGlobal.UNIT, TagGlobal.UNIT_EXPONENT): + warnings.warn( # noqa + HIDUnsupportedWarning("Data specifies a unit or unit exponent, but we don't support those yet") + ) + + elif tag in (TagGlobal.PUSH, TagGlobal.POP): + warnings.warn(HIDUnsupportedWarning("Push and pop are not supported yet")) # noqa + + elif tag == TagGlobal.REPORT_COUNT: + report_count = data + + else: + raise NotImplementedError(f"Unsupported global tag: {bin(tag)}") + + elif typ == Type.LOCAL: + if tag == TagLocal.USAGE: + if usage_page is None: + raise InvalidReportDescriptor("Usage field found but no usage page") + usages.append(Usage(usage_page, data)) + + elif tag == TagLocal.USAGE_MINIMUM: + usage_min = data + + elif tag == TagLocal.USAGE_MAXIMUM: + if usage_min is None: + raise InvalidReportDescriptor("Usage maximum set but no usage minimum") + if data is None: + raise InvalidReportDescriptor("Invalid usage maximum value") + for i in range(usage_min, data + 1): + usages.append(Usage(usage_page, i)) + usage_min = None + + elif tag in (TagLocal.STRING_INDEX, TagLocal.STRING_MINIMUM, TagLocal.STRING_MAXIMUM): + pass # we don't care about this information to parse the reports + + else: + raise NotImplementedError(f"Unsupported local tag: {bin(tag)}") + + @staticmethod + def _get_main_item_desc(value: int) -> str: + fields = [ + "Constant" if value & (1 << 0) else "Data", + "Variable" if value & (1 << 1) else "Array", + "Relative" if value & (1 << 2) else "Absolute", + ] + if value & (1 << 1): + # variable only + fields += [ + "Wrap" if value & (1 << 3) else "No Wrap", + "Non Linear" if value & (1 << 4) else "Linear", + "No Preferred State" if value & (1 << 5) else "Preferred State", + "Null State" if value & (1 << 6) else "No Null position", + "Buffered Bytes" if value & (1 << 8) else "Bit Field", + ] + return ", ".join(fields) + + def print(self, level: int = 0, file: TextIO = sys.stdout) -> None: # noqa: C901 + def printl(string: str) -> None: + print(" " * level + string, file=file) + + usage_data: Union[Literal[False], Optional[hid_parser.data._Data]] = False + + for typ, tag, data in self._iterate_raw(): + if typ == Type.MAIN: + if tag == TagMain.INPUT: + if data is None: + raise InvalidReportDescriptor("Invalid input item") + printl(f"Input ({self._get_main_item_desc(data)})") + + elif tag == TagMain.OUTPUT: + if data is None: + raise InvalidReportDescriptor("Invalid output item") + printl(f"Output ({self._get_main_item_desc(data)})") + + elif tag == TagMain.FEATURE: + if data is None: + raise InvalidReportDescriptor("Invalid feature item") + printl(f"Feature ({self._get_main_item_desc(data)})") + + elif tag == TagMain.COLLECTION: + printl(f"Collection ({hid_parser.data.Collections.get_description(data)})") + level += 1 + + elif tag == TagMain.END_COLLECTION: + level -= 1 + printl("End Collection") + + elif typ == Type.GLOBAL: + if tag == TagGlobal.USAGE_PAGE: + try: + printl(f"Usage Page ({hid_parser.data.UsagePages.get_description(data)})") + try: + usage_data = hid_parser.data.UsagePages.get_subdata(data) + except ValueError: + usage_data = None + except KeyError: + printl(f"Usage Page (Unknown 0x{data:04x})") + + elif tag == TagGlobal.LOGICAL_MINIMUM: + printl(f"Logical Minimum ({data})") + + elif tag == TagGlobal.LOGICAL_MAXIMUM: + printl(f"Logical Maximum ({data})") + + elif tag == TagGlobal.PHYSICAL_MINIMUM: + printl(f"Physical Minimum ({data})") + + elif tag == TagGlobal.PHYSICAL_MAXIMUM: + printl(f"Physical Maximum ({data})") + + elif tag == TagGlobal.UNIT_EXPONENT: + printl(f"Unit Exponent (0x{data:04x})") + + elif tag == TagGlobal.UNIT: + printl(f"Unit (0x{data:04x})") + + elif tag == TagGlobal.REPORT_SIZE: + printl(f"Report Size ({data})") + + elif tag == TagGlobal.REPORT_ID: + printl(f"Report ID (0x{data:02x})") + + elif tag == TagGlobal.REPORT_COUNT: + printl(f"Report Count ({data})") + + elif tag == TagGlobal.PUSH: + printl(f"Push ({data})") + + elif tag == TagGlobal.POP: + printl(f"Pop ({data})") + + elif typ == Type.LOCAL: + if tag == TagLocal.USAGE: + if usage_data is False: + raise InvalidReportDescriptor("Usage field found but no usage page") + + if usage_data: + try: + printl(f"Usage ({usage_data.get_description(data)})") + except KeyError: + printl(f"Usage (Unknown, 0x{data:04x})") + else: + printl(f"Usage (0x{data:04x})") + + elif tag == TagLocal.USAGE_MINIMUM: + printl(f"Usage Minimum ({data})") + + elif tag == TagLocal.USAGE_MAXIMUM: + printl(f"Usage Maximum ({data})") + + elif tag == TagLocal.DESIGNATOR_INDEX: + printl(f"Designator Index ({data})") + + elif tag == TagLocal.DESIGNATOR_MINIMUM: + printl(f"Designator Minimum ({data})") + + elif tag == TagLocal.DESIGNATOR_MAXIMUM: + printl(f"Designator Maximum ({data})") + + elif tag == TagLocal.STRING_INDEX: + printl(f"String Index ({data})") + + elif tag == TagLocal.STRING_MINIMUM: + printl(f"String Minimum ({data})") + + elif tag == TagLocal.STRING_MAXIMUM: + printl(f"String Maximum ({data})") + + elif tag == TagLocal.DELIMITER: + printl(f"Delemiter ({data})") diff --git a/lib/hid_parser/data.py b/lib/hid_parser/data.py new file mode 100644 index 00000000..ce81ae1f --- /dev/null +++ b/lib/hid_parser/data.py @@ -0,0 +1,1086 @@ +# SPDX-License-Identifier: MIT + +import enum + +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple + + +class _DataMeta(type): + """ + This metaclass populates _single and _range, following the structure described bellow + + The class should declare data as follows + + MY_DATA_VALUE = 0x01, 'Data description' + + or, for ranges, + + MY_DATA_RANGE = 0x02, ..., 0x06, 'Data range description' + + _single and _range will then be populated with + + MY_DATA_VALUE = 0x01 + _single[0x01] = ('Data description', None) + MY_DATA_RANGE = range(0x02, 0x06+1) + _range.append(tuple(0x02, 0x06, ('Data range description', None))) + + As you can see, for single data insertions, the variable will be kept with + the first value of the tuple. Both single and range data insertions will + register the data into the correspondent data holders. + + You can also define subdata, + + MY_DATA_VALUE = 0x01, 'Data description', OTHER_DATA_TYPE + MY_DATA_RANGE = 0x02, ..., 0x06, 'Data range description', YET_OTHER_DATA_TYPE + + Which will result in + + MY_DATA_VALUE = 0x01 + _single[0x01] = ('Data description', OTHER_DATA_TYPE) + _range.append(tuple(0x02, 0x06, ('Data range description', YET_OTHER_DATA_TYPE))) + + This metaclass also does some verification to prevent duplicated data. + """ + + def __new__(mcs, name: str, bases: Tuple[Any], dic: Dict[str, Any]): # type: ignore + dic["_single"] = {} + dic["_range"] = [] + + # allow constructing data via a data dictionary as opposed to directly in the object body + if "data" in dic: + data = dic.pop("data") + else: + data = dic + + for attr in data: + if not attr.startswith("_") and isinstance(data[attr], tuple): + if len(data[attr]) == 2 or len(data[attr]) == 4: # missing sub data + data[attr] = data[attr] + (None,) + + if len(data[attr]) == 3: # single + num, desc, sub = data[attr] + + if not isinstance(num, int): + raise TypeError(f"First element of '{attr}' should be an int") + if not isinstance(desc, str): + raise TypeError(f"Second element of '{attr}' should be a string") + + if num in dic["_single"]: + raise ValueError(f"Duplicated value in '{attr}' ({num})") + + for nmin, nmax, _ in dic["_range"]: + if nmin <= num <= nmax: + raise ValueError(f"Duplicated value in '{attr}' ({num})") + + dic[attr] = num + dic["_single"][num] = desc, sub + elif len(data[attr]) == 5: # range + nmin, el, nmax, desc, sub = data[attr] + + if not el == Ellipsis: + raise TypeError(f"Second element of '{attr}' should be an ellipsis (...)") + if not isinstance(nmin, int): + raise TypeError(f"First element of '{attr}' should be an int") + if not isinstance(nmax, int): + raise TypeError(f"Third element of '{attr}' should be an int") + if not isinstance(desc, str): + raise TypeError(f"Fourth element of '{attr}' should be a string") + + for num in dic["_single"]: + if nmin <= num <= nmax: + raise ValueError(f"Duplicated value in '{attr}' ({num})") + + dic[attr] = range(nmin, nmax + 1) + dic["_range"].append((nmin, nmax, (desc, sub))) + + else: + raise ValueError(f"Invalid field: {attr}") + + return super().__new__(mcs, name, bases, dic) + + +class _Data(metaclass=_DataMeta): + """ + This class provides a get_description method to get data out of _single and _range. + See the _DataMeta documentation for more information. + """ + + _DATA = Tuple[str, Optional[Any]] + _single: Dict[int, _DATA] + _range: List[Tuple[int, int, _DATA]] + + @classmethod + def _get_data(cls, num: Optional[int]) -> _DATA: + if num is None: + raise KeyError("Data index is not an int") + + if num in cls._single: + return cls._single[num] + + for nmin, nmax, data in cls._range: + if nmin <= num <= nmax: + return data + + raise KeyError(f"Data not found for index 0x{num:02x} in {cls.__name__}") + + @classmethod + def get_description(cls, num: Optional[int]) -> str: + return cls._get_data(num)[0] + + @classmethod + def get_subdata(cls, num: Optional[int]) -> Any: + subdata = cls._get_data(num)[1] + + if not subdata: + raise ValueError("Sub-data not available") + + return subdata + + +class UsageTypes(enum.Enum): + # controls + LINEAR_CONTROL = LC = 0 + ON_OFF_CONTROL = OOC = 1 + MOMENTARY_CONTROL = MC = 2 + ONE_SHOT_CONTROL = OSC = 3 + RE_TRIGGER_CONTROL = RTC = 4 + # data + SELECTOR = SEL = 5 + STATIC_VALUE = SV = 6 + STATIC_FLAG = SF = 7 + DYNAMIC_FLAG = DF = 8 + DYNAMIC_VALUE = DV = 9 + # collection + NAMED_ARRAY = NARY = 10 + COLLECTION_APPLICATION = CA = 11 + COLLECTION_LOGICAL = CL = 12 + COLLECTION_PHYSICAL = CP = 13 + USAGE_SWITCH = US = 14 + USAGE_MODIFIER = UM = 15 + + +UsageTypesControls = ( + UsageTypes.LINEAR_CONTROL, + UsageTypes.ON_OFF_CONTROL, + UsageTypes.ONE_SHOT_CONTROL, + UsageTypes.RE_TRIGGER_CONTROL, +) + + +UsageTypesData = ( + UsageTypes.SELECTOR, + UsageTypes.STATIC_VALUE, + UsageTypes.STATIC_FLAG, + UsageTypes.DYNAMIC_VALUE, + UsageTypes.DYNAMIC_FLAG, +) + + +UsageTypesCollection = ( + UsageTypes.NAMED_ARRAY, + UsageTypes.COLLECTION_APPLICATION, + UsageTypes.COLLECTION_LOGICAL, + UsageTypes.COLLECTION_PHYSICAL, + UsageTypes.USAGE_SWITCH, + UsageTypes.USAGE_MODIFIER, +) + + +class Collections(_Data): + PHYSICAL = 0x00, "Physical" + APPLICATION = 0x01, "Application" + LOGICAL = 0x02, "Logical" + REPORT = 0x03, "Report" + NAMED_ARRAY = 0x04, "Named Array" + USAGE_SWITCH = 0x05, "Usage Switch" + USAGE_MODIFIER = 0x06, "Usage Modifier" + VENDOR = 0x80, ..., 0xFF, "Vendor" + + +class GenericDesktopControls(_Data): + POINTER = 0x01, "Pointer", UsageTypes.CP + MOUSE = 0x02, "Mouse", UsageTypes.CA + JOYSTICK = 0x04, "Joystick", UsageTypes.CA + GAMEPAD = 0x05, "Game Pad", UsageTypes.CA + KEYBOARD = 0x06, "Keyboard", UsageTypes.CA + KEYPAD = 0x07, "Keypad", UsageTypes.CA + MULTI_AXIS_CONTROLLER = 0x08, "Multi-axis Controller", UsageTypes.CA + TABLET_PC_SYSTEM_CONTROLS = 0x09, "Tablet PC System Controls", UsageTypes.CA + X = 0x30, "X", UsageTypes.DV + Y = 0x31, "Y", UsageTypes.DV + Z = 0x32, "Z", UsageTypes.DV + RX = 0x33, "Rx", UsageTypes.DV + RY = 0x34, "Ry", UsageTypes.DV + RZ = 0x35, "Rz", UsageTypes.DV + SLIDER = 0x36, "Slider", UsageTypes.DV + DIAL = 0x37, "Dial", UsageTypes.DV + WHEEL = 0x38, "Wheel", UsageTypes.DV + HAT_SWITCH = 0x39, "Hat switch", UsageTypes.DV + COUNTED_BUFFER = 0x3A, "Counted Buffer", UsageTypes.CL + BYTE_COUNT = 0x3B, "Byte Count", UsageTypes.DV + MOTION_WAKEUP = 0x3C, "Motion Wakeup", UsageTypes.OSC + START = 0x3D, "Start", UsageTypes.OOC + SELECT = 0x3E, "Select", UsageTypes.OOC + VX = 0x40, "Vx", UsageTypes.DV + VY = 0x41, "Vy", UsageTypes.DV + VZ = 0x42, "Vz", UsageTypes.DV + VBRX = 0x43, "Vbrx", UsageTypes.DV + VBRY = 0x44, "Vbry", UsageTypes.DV + VBRZ = 0x45, "Vbrz", UsageTypes.DV + VNO = 0x46, "Vno", UsageTypes.DV + FEATURE_NOTIFICATION = 0x47, "Feature Notification", (UsageTypes.DV, UsageTypes.DF) + RESOLUTION_MULTIPLIER = 0x48, "Resolution Multiplier", UsageTypes.DV + SYSTEM_CONTROL = 0x80, "System Control", UsageTypes.CA + SYSTEM_POWER_CONTROL = 0x81, "System Power Down", UsageTypes.OSC + SYSTEM_SLEEP = 0x82, "System Sleep", UsageTypes.OSC + SYSTEM_WAKE_UP = 0x83, "System Wake Up", UsageTypes.OSC + SYSTEM_CONTEXT_MENU = 0x84, "System Context Menu", UsageTypes.OSC + SYSTEM_MAIN_MENU = 0x85, "System Main Menu", UsageTypes.OSC + SYSTEM_APP_MENU = 0x86, "System App Menu", UsageTypes.OSC + SYSTEM_MENU_HELP = 0x87, "System Menu Help", UsageTypes.OSC + SYSTEM_MENU_EXIT = 0x88, "System Menu Exit", UsageTypes.OSC + SYSTEM_MENU_SELECT = 0x89, "System Menu Select", UsageTypes.OSC + SYSTEM_MENU_RIGHT = 0x8A, "System Menu Right", UsageTypes.RTC + SYSTEM_MENU_LEFT = 0x8B, "System Menu Left", UsageTypes.RTC + SYSTEM_MENU_UP = 0x8C, "System Menu Up", UsageTypes.RTC + SYSTEM_MENU_DOWN = 0x8D, "System Menu Down", UsageTypes.RTC + SYSTEM_COLD_RESTART = 0x8E, "System Cold Restart", UsageTypes.OSC + SYSTEM_WARM_RESTART = 0x8F, "System Warm Restart", UsageTypes.OSC + DPAD_UP = 0x90, "D-pad Up", UsageTypes.OOC + DPAD_DOWN = 0x91, "D-pad Down", UsageTypes.OOC + DPAD_RIGHT = 0x92, "D-pad Right", UsageTypes.OOC + DPAD_LEFT = 0x93, "D-pad Left", UsageTypes.OOC + SYSTEM_DOCK = 0xA0, "System Dock", UsageTypes.OSC + SYSTEM_UNDOCK = 0xA1, "System Undock", UsageTypes.OSC + SYSTEM_SETUP = 0xA2, "System Setup", UsageTypes.OSC + SYSTEM_BREAK = 0xA3, "System Break", UsageTypes.OSC + SYSTEM_DEBBUGER_BREAK = 0xA4, "System Debugger Break", UsageTypes.OSC + APPLICATION_BREAK = 0xA5, "Application Break", UsageTypes.OSC + APPLICATION_DEBBUGER_BREAK = 0xA6, "Application Debugger Break", UsageTypes.OSC + SYSTEM_SPEAKER_MUTE = 0xA7, "System Speaker Mute", UsageTypes.OSC + SYSTEM_HIBERNATE = 0xA8, "System Hibernate", UsageTypes.OSC + SYSTEM_DISPLAY_INVERT = 0xB0, "System Display Invert", UsageTypes.OSC + SYSTEM_DISPLAY_INTERNAL = 0xB1, "System Display Internal", UsageTypes.OSC + SYSTEM_DISPLAY_EXTERNAL = 0xB2, "System Display External", UsageTypes.OSC + SYSTEM_DISPLAY_BOTH = 0xB3, "System Display Both", UsageTypes.OSC + SYSTEM_DISPLAY_DUAL = 0xB4, "System Display Dual", UsageTypes.OSC + SYSTEM_DISPLAY_TOGGLE = 0xB5, "System Display Toggle Int/Ext", UsageTypes.OSC + SYSTEM_DISPLAY_SWAP = 0xB6, "System Display Swap Primary/Secondary", UsageTypes.OSC + SYSTEM_DISPLAY_LCD_AUTOSCALE = 0xB7, "System Display LCD Autoscale", UsageTypes.OSC + + +class KeyboardKeypad(_Data): + NO_EVENT = 0x00, "No event indicated", UsageTypes.SEL + KEYBOARD_ERROR_ROLL_OVER = 0x01, "Keyboard ErrorRollOver", UsageTypes.SEL + KEYBOARD_POST = 0x02, "Keyboard POSTFail", UsageTypes.SEL + KEYBOARD_ERROR_UNDEFINED = 0x03, "Keyboard ErrorUndefined", UsageTypes.SEL + KEYBOARD_A = 0x04, "Keyboard a and A", UsageTypes.SEL + KEYBOARD_B = 0x05, "Keyboard b and B", UsageTypes.SEL + KEYBOARD_C = 0x06, "Keyboard c and C", UsageTypes.SEL + KEYBOARD_D = 0x07, "Keyboard d and D", UsageTypes.SEL + KEYBOARD_E = 0x08, "Keyboard e and E", UsageTypes.SEL + KEYBOARD_F = 0x09, "Keyboard f and F", UsageTypes.SEL + KEYBOARD_G = 0x0A, "Keyboard g and G", UsageTypes.SEL + KEYBOARD_H = 0x0B, "Keyboard h and H", UsageTypes.SEL + KEYBOARD_I = 0x0C, "Keyboard i and I", UsageTypes.SEL + KEYBOARD_J = 0x0D, "Keyboard j and J", UsageTypes.SEL + KEYBOARD_K = 0x0E, "Keyboard k and K", UsageTypes.SEL + KEYBOARD_L = 0x0F, "Keyboard l and L", UsageTypes.SEL + KEYBOARD_M = 0x10, "Keyboard m and M", UsageTypes.SEL + KEYBOARD_N = 0x11, "Keyboard n and N", UsageTypes.SEL + KEYBOARD_O = 0x12, "Keyboard o and O", UsageTypes.SEL + KEYBOARD_P = 0x13, "Keyboard p and P", UsageTypes.SEL + KEYBOARD_Q = 0x14, "Keyboard q and Q", UsageTypes.SEL + KEYBOARD_R = 0x15, "Keyboard r and R", UsageTypes.SEL + KEYBOARD_S = 0x16, "Keyboard s and S", UsageTypes.SEL + KEYBOARD_T = 0x17, "Keyboard t and T", UsageTypes.SEL + KEYBOARD_U = 0x18, "Keyboard u and U", UsageTypes.SEL + KEYBOARD_V = 0x19, "Keyboard v and V", UsageTypes.SEL + KEYBOARD_W = 0x1A, "Keyboard w and W", UsageTypes.SEL + KEYBOARD_X = 0x1B, "Keyboard x and X", UsageTypes.SEL + KEYBOARD_Y = 0x1C, "Keyboard y and Y", UsageTypes.SEL + KEYBOARD_Z = 0x1D, "Keyboard z and Z", UsageTypes.SEL + KEYBOARD_1 = 0x1E, "Keyboard 1 and !", UsageTypes.SEL + KEYBOARD_2 = 0x1F, "Keyboard 2 and @", UsageTypes.SEL + KEYBOARD_3 = 0x20, "Keyboard 3 and #", UsageTypes.SEL + KEYBOARD_4 = 0x21, "Keyboard 4 and $", UsageTypes.SEL + KEYBOARD_5 = 0x22, "Keyboard 5 and %", UsageTypes.SEL + KEYBOARD_6 = 0x23, "Keyboard 6 and ^", UsageTypes.SEL + KEYBOARD_7 = 0x24, "Keyboard 7 and &", UsageTypes.SEL + KEYBOARD_8 = 0x25, "Keyboard 8 and *", UsageTypes.SEL + KEYBOARD_9 = 0x26, "Keyboard 9 and (", UsageTypes.SEL + KEYBOARD_0 = 0x27, "Keyboard 0 and )", UsageTypes.SEL + KEYBOARD_ENTER = 0x28, "Keyboard Return (ENTER)", UsageTypes.SEL + KEYBOARD_ESCAPE = 0x29, "Keyboard ESCAPE", UsageTypes.SEL + KEYBOARD_DELETE = 0x2A, "Keyboard DELETE (Backspace)", UsageTypes.SEL + KEYBOARD_TAB = 0x2B, "Keyboard Tab", UsageTypes.SEL + KEYBOARD_SPACEBAR = 0x2C, "Keyboard Spacebar", UsageTypes.SEL + KEYBOARD_MINUS = 0x2D, "Keyboard - and (underscore)", UsageTypes.SEL + KEYBOARD_PLUS = 0x2E, "Keyboard = and +", UsageTypes.SEL + KEYBOARD_LEFT_BRACKET = 0x2F, "Keyboard [ and {", UsageTypes.SEL + KEYBOARD_RIGHT_BRACKET = 0x30, "Keyboard ] and }", UsageTypes.SEL + KEYBOARD_BACKSLASH = 0x31, "Keyboard \\ and |", UsageTypes.SEL + KEYBOARD_CARDINAL = 0x32, "Keyboard Non-US # and ~", UsageTypes.SEL + KEYBOARD_SEMICOLON = 0x33, "Keyboard ; and :", UsageTypes.SEL + KEYBOARD_QUOTE = 0x34, "Keyboard ' and \"", UsageTypes.SEL + KEYBOARD_GRAVE = 0x35, "Keyboard Grave Accent and Tilde", UsageTypes.SEL + KEYBOARD_COMMA = 0x36, "Keyboard , and <", UsageTypes.SEL + KEYBOARD_DOT = 0x37, "Keyboard . and >", UsageTypes.SEL + KEYBOARD_SLASH = 0x38, "Keyboard / and ?", UsageTypes.SEL + KEYBOARD_CAPS_LOCK = 0x39, "Keyboard Caps Lock", UsageTypes.SEL + KEYBOARD_F1 = 0x3A, "Keyboard F1", UsageTypes.SEL + KEYBOARD_F2 = 0x3B, "Keyboard F2", UsageTypes.SEL + KEYBOARD_F3 = 0x3C, "Keyboard F3", UsageTypes.SEL + KEYBOARD_F4 = 0x3D, "Keyboard F4", UsageTypes.SEL + KEYBOARD_F5 = 0x3E, "Keyboard F5", UsageTypes.SEL + KEYBOARD_F6 = 0x3F, "Keyboard F6", UsageTypes.SEL + KEYBOARD_F7 = 0x40, "Keyboard F7", UsageTypes.SEL + KEYBOARD_F8 = 0x41, "Keyboard F8", UsageTypes.SEL + KEYBOARD_F9 = 0x42, "Keyboard F9", UsageTypes.SEL + KEYBOARD_F10 = 0x43, "Keyboard F10", UsageTypes.SEL + KEYBOARD_F11 = 0x44, "Keyboard F11", UsageTypes.SEL + KEYBOARD_F12 = 0x45, "Keyboard F12", UsageTypes.SEL + KEYBOARD_PRINTSCREEN = 0x46, "Keyboard PrintScreen", UsageTypes.SEL + KEYBOARD_SCROLL_LOCK = 0x47, "Keyboard Scroll Lock", UsageTypes.SEL + KEYBOARD_PAUSE = 0x48, "Keyboard Pause", UsageTypes.SEL + KEYBOARD_INSERT = 0x49, "Keyboard Insert", UsageTypes.SEL + KEYBOARD_HOME = 0x4A, "Keyboard Home", UsageTypes.SEL + KEYBOARD_PAGE_UP = 0x4B, "Keyboard PageUp", UsageTypes.SEL + KEYBOARD_DELETE_FORWARD = 0x4C, "Keyboard Delete Forward", UsageTypes.SEL + KEYBOARD_END = 0x4D, "Keyboard End", UsageTypes.SEL + KEYBOARD_PAGE_DOWN = 0x4E, "Keyboard PageDown", UsageTypes.SEL + KEYBOARD_RIGHT_ARROW = 0x4F, "Keyboard RightArrow", UsageTypes.SEL + KEYBOARD_LEFT_ARROW = 0x50, "Keyboard LeftArrow", UsageTypes.SEL + KEYBOARD_UP_ARROW = 0x51, "Keyboard DownArrow", UsageTypes.SEL + KEYBOARD_DOWN_ARROW = 0x52, "Keyboard UpArrow", UsageTypes.SEL + KEYBOARD_NUM_LOCK = 0x53, "Keypad Num Lock and Clear", UsageTypes.SEL + KEYPAD_SLASH = 0x54, "Keypad /", UsageTypes.SEL + KEYPAD_ASTERISK = 0x55, "Keypad *", UsageTypes.SEL + KEYPAD_MINUS = 0x56, "Keypad -", UsageTypes.SEL + KEYPAD_PLUS = 0x57, "Keypad +", UsageTypes.SEL + KEYPAD_ENTER = 0x58, "Keypad ENTER", UsageTypes.SEL + KEYPAD_1 = 0x59, "Keypad 1 and End", UsageTypes.SEL + KEYPAD_2 = 0x5A, "Keypad 2 and Down Arrow", UsageTypes.SEL + KEYPAD_3 = 0x5B, "Keypad 3 and PageDn", UsageTypes.SEL + KEYPAD_4 = 0x5C, "Keypad 4 and Left Arrow", UsageTypes.SEL + KEYPAD_5 = 0x5D, "Keypad 5", UsageTypes.SEL + KEYPAD_6 = 0x5E, "Keypad 6 and Right Arrow", UsageTypes.SEL + KEYPAD_7 = 0x5F, "Keypad 7 and Home", UsageTypes.SEL + KEYPAD_8 = 0x60, "Keypad 8 and Up Arrow", UsageTypes.SEL + KEYPAD_9 = 0x61, "Keypad 9 and PageUp", UsageTypes.SEL + KEYPAD_0 = 0x62, "Keypad 0 and Insert", UsageTypes.SEL + KEYPAD_DOT = 0x63, "Keypad . and Delete", UsageTypes.SEL + KEYPAD_BACKSLASH = 0x64, "Keyboard Non-US \\ and |", UsageTypes.SEL + KEYPAD_APPLICATION = 0x65, "Keyboard Application", UsageTypes.SEL + KEYPAD_POWER = 0x66, "Keyboard Power", UsageTypes.SEL + KEYPAD_EQUALS = 0x67, "Keypad =", UsageTypes.SEL + KEYBOARD_F13 = 0x68, "Keyboard F13", UsageTypes.SEL + KEYBOARD_F14 = 0x69, "Keyboard F14", UsageTypes.SEL + KEYBOARD_F15 = 0x6A, "Keyboard F15", UsageTypes.SEL + KEYBOARD_F16 = 0x6B, "Keyboard F16", UsageTypes.SEL + KEYBOARD_F17 = 0x6C, "Keyboard F17", UsageTypes.SEL + KEYBOARD_F18 = 0x6D, "Keyboard F18", UsageTypes.SEL + KEYBOARD_F19 = 0x6E, "Keyboard F19", UsageTypes.SEL + KEYBOARD_F20 = 0x6F, "Keyboard F20", UsageTypes.SEL + KEYBOARD_F21 = 0x70, "Keyboard F21", UsageTypes.SEL + KEYBOARD_F22 = 0x71, "Keyboard F22", UsageTypes.SEL + KEYBOARD_F23 = 0x72, "Keyboard F23", UsageTypes.SEL + KEYBOARD_F24 = 0x73, "Keyboard F24", UsageTypes.SEL + KEYBOARD_EXECUTE = 0x74, "Keyboard Execute", UsageTypes.SEL + KEYBOARD_HELP = 0x75, "Keyboard Help", UsageTypes.SEL + KEYBOARD_MENU = 0x76, "Keyboard Menu", UsageTypes.SEL + KEYBOARD_SELECT = 0x77, "Keyboard Select", UsageTypes.SEL + KEYBOARD_STOP = 0x78, "Keyboard Stop", UsageTypes.SEL + KEYBOARD_AGAIN = 0x79, "Keyboard Again", UsageTypes.SEL + KEYBOARD_UNDO = 0x7A, "Keyboard Undo", UsageTypes.SEL + KEYBOARD_CUT = 0x7B, "Keyboard Cut", UsageTypes.SEL + KEYBOARD_COPY = 0x7C, "Keyboard Copy", UsageTypes.SEL + KEYBOARD_PASTE = 0x7D, "Keyboard Paste", UsageTypes.SEL + KEYBOARD_FIND = 0x7E, "Keyboard Find", UsageTypes.SEL + KEYBOARD_MUTE = 0x7F, "Keyboard Mute", UsageTypes.SEL + KEYBOARD_VOLUME_UP = 0x80, "Keyboard Volume Up", UsageTypes.SEL + KEYBOARD_VOLUME_DOWN = 0x81, "Keyboard Volume Down", UsageTypes.SEL + KEYBOARD_LOCKING_CAPS_LOCK = 0x82, "Keyboard Locking Caps Lock", UsageTypes.SEL + KEYBOARD_LOCKING_NUM_LOCK = 0x83, "Keyboard Locking Num Lock", UsageTypes.SEL + KEYBOARD_LOCKING_SCROLL_LOCK = 0x84, "Keyboard Locking Scroll Lock", UsageTypes.SEL + KEYPAD_COMMA = 0x85, "Keypad Comma", UsageTypes.SEL + KEYPAD_EQUALS_SIGN = 0x86, "Keypad Equal Sign", UsageTypes.SEL + KEYBOARD_INTERNATIONAL1 = 0x87, "Keyboard International1", UsageTypes.SEL + KEYBOARD_INTERNATIONAL2 = 0x88, "Keyboard International2", UsageTypes.SEL + KEYBOARD_INTERNATIONAL3 = 0x89, "Keyboard International3", UsageTypes.SEL + KEYBOARD_INTERNATIONAL4 = 0x8A, "Keyboard International4", UsageTypes.SEL + KEYBOARD_INTERNATIONAL5 = 0x8B, "Keyboard International5", UsageTypes.SEL + KEYBOARD_INTERNATIONAL6 = 0x8C, "Keyboard International6", UsageTypes.SEL + KEYBOARD_INTERNATIONAL7 = 0x8D, "Keyboard International7", UsageTypes.SEL + KEYBOARD_INTERNATIONAL8 = 0x8E, "Keyboard International8", UsageTypes.SEL + KEYBOARD_INTERNATIONAL9 = 0x8F, "Keyboard International9", UsageTypes.SEL + KEYBOARD_LANG1 = 0x90, "Keyboard LANG1", UsageTypes.SEL + KEYBOARD_LANG2 = 0x91, "Keyboard LANG2", UsageTypes.SEL + KEYBOARD_LANG3 = 0x92, "Keyboard LANG3", UsageTypes.SEL + KEYBOARD_LANG4 = 0x93, "Keyboard LANG4", UsageTypes.SEL + KEYBOARD_LANG5 = 0x94, "Keyboard LANG5", UsageTypes.SEL + KEYBOARD_LANG6 = 0x95, "Keyboard LANG6", UsageTypes.SEL + KEYBOARD_LANG7 = 0x96, "Keyboard LANG7", UsageTypes.SEL + KEYBOARD_LANG8 = 0x97, "Keyboard LANG8", UsageTypes.SEL + KEYBOARD_LANG9 = 0x98, "Keyboard LANG9", UsageTypes.SEL + KEYBOARD_ALTERNATE_ERASE = 0x99, "Keyboard Alternate Erase", UsageTypes.SEL + KEYBOARD_SYSREQ_ATTENTION = 0x9A, "Keyboard SysReq/Attention", UsageTypes.SEL + KEYBOARD_CANCEL = 0x9B, "Keyboard Cancel", UsageTypes.SEL + KEYBOARD_CLEAR = 0x9C, "Keyboard Clear", UsageTypes.SEL + KEYBOARD_PRIOR = 0x9D, "Keyboard Prior", UsageTypes.SEL + KEYBOARD_RETURN = 0x9E, "Keyboard Return", UsageTypes.SEL + KEYBOARD_SEPARATOE = 0x9F, "Keyboard Separator", UsageTypes.SEL + KEYBOARD_OUT = 0xA0, "Keyboard Out", UsageTypes.SEL + KEYBOARD_OPER = 0xA1, "Keyboard Oper", UsageTypes.SEL + KEYBOARD_CLEAR_AGAIN = 0xA2, "Keyboard Clear/Again", UsageTypes.SEL + KEYBOARD_CRSEL_PROPS = 0xA3, "Keyboard CrSel/Props", UsageTypes.SEL + KEYBOARD_EXSELL = 0xA4, "Keyboard ExSel", UsageTypes.SEL + KEYPAD_ZERO_ZERO = 0xB0, "Keypad 00", UsageTypes.SEL + KEYPAD_ZERO_ZERO_ZERO = 0xB1, "Keypad 000", UsageTypes.SEL + THOUSANDS_SEPARATOR = 0xB2, "Thousands Separator", UsageTypes.SEL + DECIMAL_SEPARATOR = 0xB3, "Decimal Separator", UsageTypes.SEL + CURRENCY_UNIT = 0xB4, "Currency Unit", UsageTypes.SEL + CURRENCY_SUBUNIT = 0xB5, "Currency Sub-unit", UsageTypes.SEL + KEYPAD_LEFT_PARENTHESIS = 0xB6, "Keypad (", UsageTypes.SEL + KEYPAD_RIGHT_PARENTHESIS = 0xB7, "Keypad )", UsageTypes.SEL + KEYPAD_LEFT_CURLY_BRACKET = 0xB8, "Keypad {", UsageTypes.SEL + KEYPAD_RIGHT_CURLY_BRACKET = 0xB9, "Keypad }", UsageTypes.SEL + KEYPAD_TAB = 0xBA, "Keypad Tab", UsageTypes.SEL + KEYPAD_BACKSPACE = 0xBB, "Keypad Backspace", UsageTypes.SEL + KEYPAD_A = 0xBC, "Keypad A", UsageTypes.SEL + KEYPAD_B = 0xBD, "Keypad B", UsageTypes.SEL + KEYPAD_C = 0xBE, "Keypad C", UsageTypes.SEL + KEYPAD_D = 0xBF, "Keypad D", UsageTypes.SEL + KEYPAD_E = 0xC0, "Keypad E", UsageTypes.SEL + KEYPAD_F = 0xC1, "Keypad F", UsageTypes.SEL + KEYPAD_XOR = 0xC2, "Keypad XOR", UsageTypes.SEL + KEYPAD_AND = 0xC3, "Keypad ^", UsageTypes.SEL + KEYPAD_PERCENTAGE = 0xC4, "Keypad %", UsageTypes.SEL + KEYPAD_LESS_THAN = 0xC5, "Keypad <", UsageTypes.SEL + KEYPAD_MORE_THAN = 0xC6, "Keypad >", UsageTypes.SEL + KEYPAD_AMPERSAND = 0xC7, "Keypad &", UsageTypes.SEL + KEYPAD_2_AMPERSAND = 0xC8, "Keypad &&", UsageTypes.SEL + KEYPAD_VERTICAL_BAR = 0xC9, "Keypad |", UsageTypes.SEL + KEYPAD_2_VERTICAL_BAR = 0xCA, "Keypad ||", UsageTypes.SEL + KEYPAD_COLON = 0xCB, "Keypad :", UsageTypes.SEL + KEYPAD_CARDINAL = 0xCC, "Keypad #", UsageTypes.SEL + KEYPAD_SPACE = 0xCD, "Keypad Space", UsageTypes.SEL + KEYPAD_AT = 0xCE, "Keypad @", UsageTypes.SEL + KEYPAD_EXCLAMATION = 0xCF, "Keypad !", UsageTypes.SEL + KEYPAD_MEMORY_STORE = 0xD0, "Keypad Memory Store", UsageTypes.SEL + KEYPAD_MEMORY_RECALL = 0xD1, "Keypad Memory Recall", UsageTypes.SEL + KEYPAD_MEMORY_CLEAR = 0xD2, "Keypad Memory Clear", UsageTypes.SEL + KEYPAD_MEMORY_ADD = 0xD3, "Keypad Memory Add", UsageTypes.SEL + KEYPAD_MEMORY_SUBTRACT = 0xD4, "Keypad Memory Subtract", UsageTypes.SEL + KEYPAD_MEMORY_MULTIPLY = 0xD5, "Keypad Memory Multiply", UsageTypes.SEL + KEYPAD_MEMORY_DIVIDE = 0xD6, "Keypad Memory Divide", UsageTypes.SEL + KEYPAD_PLUS_MINUS = 0xD7, "Keypad +/-", UsageTypes.SEL + KEYPAD_CLEAR = 0xD8, "Keypad Clear", UsageTypes.SEL + KEYPAD_CLEAR_ENTRY = 0xD9, "Keypad Clear Entry", UsageTypes.SEL + KEYPAD_BINARY = 0xDA, "Keypad Binary", UsageTypes.SEL + KEYPAD_OCTAL = 0xDB, "Keypad Octal", UsageTypes.SEL + KEYPAD_DECIMAL = 0xDC, "Keypad Decimal", UsageTypes.SEL + KEYPAD_HEXADECIMAL = 0xDD, "Keypad Hexadecimal", UsageTypes.DV + KEYBOARD_LEFT_CONTROL = 0xE0, "Keyboard LeftControl", UsageTypes.DV + KEYBOARD_LEFT_SHIFT = 0xE1, "Keyboard LeftShift", UsageTypes.DV + KEYBOARD_LEFT_ALT = 0xE2, "Keyboard LeftAlt", UsageTypes.DV + KEYBOARD_LEFT_GUI = 0xE3, "Keyboard Left GUI", UsageTypes.DV + KEYBOARD_RIGHT_CONTROL = 0xE4, "Keyboard RightControl", UsageTypes.DV + KEYBOARD_RIGHT_SHIFT = 0xE5, "Keyboard RightShift", UsageTypes.DV + KEYBOARD_RIGHT_ALT = 0xE6, "Keyboard RightAlt", UsageTypes.DV + KEYBOARD_RIGHT_GUI = 0xE7, "Keyboard Right GUI", UsageTypes.DV + + +class Led(_Data): + NUM_LOCK = 0x01, "Num Lock", UsageTypes.OOC + CAPS_LOCK = 0x02, "Caps Lock", UsageTypes.OOC + SCROLL_LOCK = 0x03, "Scroll Lock", UsageTypes.OOC + COMPOSE = 0x04, "Compose", UsageTypes.OOC + KANA = 0x05, "Kana", UsageTypes.OOC + POWER = 0x06, "Power", UsageTypes.OOC + SHIFT = 0x07, "Shift", UsageTypes.OOC + DO_NOT_DISTURB = 0x08, "Do Not Disturb", UsageTypes.OOC + MUTE = 0x09, "Mute", UsageTypes.OOC + TONE_ENABLE = 0x0A, "Tone Enable", UsageTypes.OOC + HIGH_CUT_FILTER = 0x0B, "High Cut Filter", UsageTypes.OOC + LOW_CUT_FILTER = 0x0C, "Low Cut Filter", UsageTypes.OOC + EQUALIZER_ENABLE = 0x0D, "Equalizer Enable", UsageTypes.OOC + SOUND_FIELD_ON = 0x0E, "Sound Field On", UsageTypes.OOC + SURROUND_ON = 0x0F, "Surround On", UsageTypes.OOC + REPEAR = 0x10, "Repeat", UsageTypes.OOC + STEREO = 0x11, "Stereo", UsageTypes.OOC + SAMPLING_RATE_DETECT = 0x12, "Sampling Rate Detect", UsageTypes.OOC + SPINNING = 0x13, "Spinning", UsageTypes.OOC + CAV = 0x14, "CAV", UsageTypes.OOC + CLV = 0x15, "CLV", UsageTypes.OOC + RECORDING_FORMAT_DETECT = 0x16, "Recording Format Detect", UsageTypes.OOC + OFF_HOOK = 0x17, "Off-Hook", UsageTypes.OOC + RING = 0x18, "Ring", UsageTypes.OOC + MESSAGE_WAITING = 0x19, "Message Waiting", UsageTypes.OOC + DATA_MODE = 0x1A, "Data Mode", UsageTypes.OOC + BATTERY_OPERATION = 0x1B, "Battery Operation", UsageTypes.OOC + BATTERY_OK = 0x1C, "Battery OK", UsageTypes.OOC + BATTERY_LOW = 0x1D, "Battery Low", UsageTypes.OOC + SPEAKER = 0x1E, "Speaker", UsageTypes.OOC + HEAD_SET = 0x1F, "Head Set", UsageTypes.OOC + HOLD = 0x20, "Hold", UsageTypes.OOC + MICROPHONE = 0x21, "Microphone", UsageTypes.OOC + COVERAGE = 0x22, "Coverage", UsageTypes.OOC + NIGHT_MODE = 0x23, "Night Mode", UsageTypes.OOC + SEND_CALLS = 0x24, "Send Calls", UsageTypes.OOC + CALL_PICKUP = 0x25, "Call Pickup", UsageTypes.OOC + CONFERENCE = 0x26, "Conference", UsageTypes.OOC + STAND_BY = 0x27, "Stand-by", UsageTypes.OOC + CAMERA_ON = 0x28, "Camera On", UsageTypes.OOC + CAMERA_OFF = 0x29, "Camera Off", UsageTypes.OOC + ON_LINE = 0x2A, "On-Line", UsageTypes.OOC + OFF_LINE = 0x2B, "Off-Line", UsageTypes.OOC + BUSY = 0x2C, "Busy", UsageTypes.OOC + READY = 0x2D, "Ready", UsageTypes.OOC + PAPER_OUT = 0x2E, "Paper-Out", UsageTypes.OOC + PAPER_JAM = 0x2F, "Paper-Jam", UsageTypes.OOC + REMOTE = 0x30, "Remote", UsageTypes.OOC + FORWARD = 0x31, "Forward", UsageTypes.OOC + REVERSE = 0x32, "Reverse", UsageTypes.OOC + STOP = 0x33, "Stop", UsageTypes.OOC + REWIND = 0x34, "Rewind", UsageTypes.OOC + FAST_FORWARD = 0x35, "Fast Forward", UsageTypes.OOC + PLAY = 0x36, "Play", UsageTypes.OOC + PAUSE = 0x37, "Pause", UsageTypes.OOC + RECORD = 0x38, "Record", UsageTypes.OOC + ERROR = 0x39, "Error", UsageTypes.OOC + USAGE_SELECTED_INDICATOR = 0x3A, "Usage Selected Indicator", UsageTypes.US + USAGE_IN_USE_INDICATOR = 0x3B, "Usage In Use Indicator", UsageTypes.US + USAGE_MULTI_MODE_INDICATOR = 0x3C, "Usage Multi Mode Indicator", UsageTypes.UM + INDICATOR_ON = 0x3D, "Indicator On", UsageTypes.SEL + INDICATOR_FLASH = 0x3E, "Indicator Flash", UsageTypes.SEL + INDICATOR_SLOW_BLINK = 0x3F, "Indicator Slow Blink", UsageTypes.SEL + INDICATOR_FAST_BLINK = 0x40, "Indicator Fast Blink", UsageTypes.SEL + INDICATOR_OFF = 0x41, "Indicator Off", UsageTypes.SEL + FLASH_ON_TIME = 0x42, "Flash On Time", UsageTypes.DV + SLOW_BLINK_ON_TIME = 0x43, "Slow Blink On Time", UsageTypes.DV + SLOW_BLINK_OFF_TIME = 0x44, "Slow Blink Off Time", UsageTypes.DV + FAST_BLINK_ON_TIME = 0x45, "Fast Blink On Time", UsageTypes.DV + FAST_BLINK_OFF_TIME = 0x46, "Fast Blink Off Time", UsageTypes.DV + USAGE_INDICATOR_COLOR = 0x47, "Usage Indicator Color", UsageTypes.UM + INDICATOR_RED = 0x48, "Indicator Red", UsageTypes.SEL + INDICATOR_GREEN = 0x49, "Indicator Green", UsageTypes.SEL + INDICATOR_AMBER = 0x4A, "Indicator Amber", UsageTypes.SEL + GENERIC_INDICATOR = 0x4B, "Generic Indicator", UsageTypes.OOC + SYSTEM_SUSPEND = 0x4C, "System Suspend", UsageTypes.OOC + EXTERNAL_POWER_CONNECTED = 0x4D, "External Power Connected", UsageTypes.OOC + + +class Button(_Data): + _USAGE_TYPES = ( + UsageTypes.SEL, + UsageTypes.OOC, + UsageTypes.MC, + UsageTypes.OSC, + ) + + data = { + "NO_BUTTON": (0x0000, "Button 1 (primary/trigger)", _USAGE_TYPES), + "BUTTON_1": (0x0001, "Button 1 (primary/trigger)", _USAGE_TYPES), + "BUTTON_2": (0x0002, "Button 2 (secondary)", _USAGE_TYPES), + "BUTTON_3": (0x0003, "Button 3 (tertiary)", _USAGE_TYPES), + } + + for _i in range(0x0004, 0xFFFF): + data[f"BUTTON_{_i}"] = _i, f"Button {_i}", _USAGE_TYPES + + +class Consumer(_Data): + CONSUMER_CONTROL = 0x0001, "Consumer Control", UsageTypes.CA + NUMERIC_KEY_PAD = 0x0002, "Numeric Key Pad", UsageTypes.NARY + PROGRAMMABLE_BUTTONS = 0x0003, "Programmable Buttons", UsageTypes.NARY + MICROPHONE = 0x0004, "Microphone", UsageTypes.CA + HEADPHONE = 0x0005, "Headphone", UsageTypes.CA + GRAPHIC_EQUALIZER = 0x0006, "Graphic Equalizer", UsageTypes.CA + PLUS10 = 0x0020, "+10", UsageTypes.OSC + PULS100 = 0x0021, "+100", UsageTypes.OSC + AM_PM = 0x0022, "AM/PM", UsageTypes.OSC + POWER = 0x0030, "Power", UsageTypes.OOC + REST = 0x0031, "Reset", UsageTypes.OSC + SLEEP = 0x0032, "Sleep", UsageTypes.OSC + SLEEP_AFTER = 0x0033, "Sleep After", UsageTypes.OSC + SLEEP_MODE = 0x0034, "Sleep Mode", UsageTypes.RTC + ILLUMINATION = 0x0035, "Illumination", UsageTypes.OOC + FUNCTION_BUTTONS = 0x0036, "Function Buttons", UsageTypes.NARY + MENU = 0x0040, "Menu", UsageTypes.OOC + MENU_PICK = 0x0041, "Menu Pick", UsageTypes.OSC + MENU_UP = 0x0042, "Menu Up", UsageTypes.OSC + MENU_DOWN = 0x0043, "Menu Down", UsageTypes.OSC + MENU_LEFT = 0x0044, "Menu Left", UsageTypes.OSC + MENU_RIGHT = 0x0045, "Menu Right", UsageTypes.OSC + MENU_ESCAPE = 0x0046, "Menu Escape", UsageTypes.OSC + MENU_VALUE_INCREASE = 0x0047, "Menu Value Increase", UsageTypes.OSC + MENU_VALUE_DECREASE = 0x0048, "Menu Value Decrease", UsageTypes.OSC + DATA_ON_SCREEN = 0x0060, "Data On Screen", UsageTypes.OOC + CLOSED_CAPTION = 0x0061, "Closed Caption", UsageTypes.OOC + CLOSED_CAPTION_SELECT = 0x0062, "Closed Caption Select", UsageTypes.OOC + VCR_TV = 0x0063, "VCR/TV", UsageTypes.OSC + BROADCAST_MODE = 0x0064, "Broadcast Mode", UsageTypes.OOC + SNAPSHOT = 0x0065, "Snapshot", UsageTypes.OOC + STILL = 0x0066, "Still", UsageTypes.OOC + SELECTION = 0x0080, "Selection", UsageTypes.NARY + ASSIGN_SELECTION = 0x0081, "Assign Selection", UsageTypes.OSC + MODE_STEP = 0x0082, "Mode Step", UsageTypes.OSC + RECALL_LAST = 0x0083, "Recall Last", UsageTypes.OSC + ENTER_CHANNEL = 0x0084, "Enter Channel", UsageTypes.OSC + ORDER_MOVIE = 0x0085, "Order Movie", UsageTypes.OSC + CHANNEL = 0x0086, "Channel", UsageTypes.LC + MEDIA_SELECTION = 0x0087, "Media Selection", UsageTypes.NARY + MEDIA_SELECT_COMPUTER = 0x0088, "Media Select Computer", UsageTypes.SEL + MEDIA_SELECT_TV = 0x0089, "Media Select TV", UsageTypes.SEL + MEDIA_SELECT_WWW = 0x008A, "Media Select WWW", UsageTypes.SEL + MEDIA_SELECT_DVD = 0x008B, "Media Select DVD", UsageTypes.SEL + MEDIA_SELECT_TELEPHONE = 0x008C, "Media Select Telephone", UsageTypes.SEL + MEDIA_SELECT_PROGRAM_GUIDE = 0x008D, "Media Select Program Guide", UsageTypes.SEL + MEDIA_SELECT_VIDEO_PHONE = 0x008E, "Media Select Video Phone", UsageTypes.SEL + MEDIA_SELECT_GAMES = 0x008F, "Media Select Games", UsageTypes.SEL + MEDIA_SELECT_MESSAGES = 0x0090, "Media Select Messages", UsageTypes.SEL + MEDIA_SELECT_CD = 0x0091, "Media Select CD ", UsageTypes.SEL + MEDIA_SELECT_VCR = 0x0092, "Media Select VCR", UsageTypes.SEL + MEDIA_SELECT_TUNER = 0x0093, "Media Select Tuner", UsageTypes.SEL + QUIT = 0x0094, "Quit", UsageTypes.OSC + HELP = 0x0095, "Help", UsageTypes.OOC + MEDIA_SELECT_TAPE = 0x0096, "Media Select Tape", UsageTypes.SEL + MEDIA_SELECT_CABLE = 0x0097, "Media Select Cable", UsageTypes.SEL + MEDIA_SELECT_SATELLITE = 0x0098, "Media Select Satellite", UsageTypes.SEL + MEDIA_SELECT_SECURITY = 0x0099, "Media Select Security", UsageTypes.SEL + MEDIA_SELECT_HOME = 0x009A, "Media Select Home", UsageTypes.SEL + MEDIA_SELECT_CALL = 0x009B, "Media Select Call", UsageTypes.SEL + CHANNEL_INCREMENT = 0x009C, "Channel Increment", UsageTypes.OSC + CHANNEL_DECREMENT = 0x009D, "Channel Decrement", UsageTypes.OSC + MEDIA_SELECT_SAP = 0x009E, "Media Select SAP", UsageTypes.SEL + VCR_PLUS = 0x00A0, "VCR Plus", UsageTypes.OSC + ONCE = 0x00A1, "Once", UsageTypes.OSC + DAILY = 0x00A2, "Daily", UsageTypes.OSC + WEEKLY = 0x00A3, "Weekly", UsageTypes.OSC + MONTHLY = 0x00A4, "Monthly", UsageTypes.OSC + PLAY = 0x00B0, "Play", UsageTypes.OOC + PAUSE = 0x00B1, "Pause", UsageTypes.OOC + RECORD = 0x00B2, "Record", UsageTypes.OOC + FAST_FORWARD = 0x00B3, "Fast Forward", UsageTypes.OOC + REWIND = 0x00B4, "Rewind", UsageTypes.OOC + SCAN_NEXT_TRACK = 0x00B5, "Scan Next Track", UsageTypes.OSC + SCAN_PREVIOUS_TRACK = 0x00B6, "Scan Previous Track", UsageTypes.OSC + STOP = 0x00B7, "Stop", UsageTypes.OSC + EJECT = 0x00B8, "Eject", UsageTypes.OSC + RANDOM_PLAY = 0x00B9, "Random Play", UsageTypes.OOC + SELECT_DISC = 0x00BA, "Select Disc", UsageTypes.NARY + ENTER_DISC = 0x00BB, "Enter Disc", UsageTypes.MC + REPEAT = 0x00BC, "Repeat", UsageTypes.OSC + TRACKING = 0x00BD, "Tracking", UsageTypes.LC + TRACK_NORMAL = 0x00BE, "Track Normal", UsageTypes.OSC + SLOW_TRACKING = 0x00BF, "Slow Tracking", UsageTypes.LC + FRAME_FORWARD = 0x00C0, "Frame Forward", UsageTypes.RTC + FRAME_BACK = 0x00C1, "Frame Back", UsageTypes.RTC + MARK = 0x00C2, "Mark", UsageTypes.OSC + CLEAR_MARK = 0x00C3, "Clear Mark", UsageTypes.OSC + REPEAT_FROM_MARK = 0x00C4, "Repeat From Mark", UsageTypes.OOC + RETURN_TO_MARK = 0x00C5, "Return To Mark", UsageTypes.OSC + SEARCH_MARK_FORWARD = 0x00C6, "Search Mark Forward", UsageTypes.OSC + SEARCH_MARK_BACKWARDS = 0x00C7, "Search Mark Backwards", UsageTypes.OSC + COUNTER_RESET = 0x00C8, "Counter Reset", UsageTypes.OSC + SHOW_COUNTER = 0x00C9, "Show Counter", UsageTypes.OSC + TRACKING_INCREMENT = 0x00CA, "Tracking Increment", UsageTypes.RTC + TRACKING_DECREMENT = 0x00CB, "Tracking Decrement", UsageTypes.RTC + STOP_EJECT = 0x00CC, "Stop/Eject", UsageTypes.OSC + PLAY_PAUSE = 0x00CD, "Play/Pause", UsageTypes.OSC + PLAY_SKIP = 0x00CE, "Play/Skip", UsageTypes.OSC + VOLUME = 0x00E0, "Volume", UsageTypes.LC + BALANCE = 0x00E1, "Balance", UsageTypes.LC + MUTE = 0x00E2, "Mute", UsageTypes.OOC + BASS = 0x00E3, "Bass", UsageTypes.LC + TREBLE = 0x00E4, "Treble", UsageTypes.LC + BASS_BOOST = 0x00E5, "Bass Boost", UsageTypes.OOC + SURROUND_MODE = 0x00E6, "Surround Mode", UsageTypes.OSC + LOUDNESS = 0x00E7, "Loudness", UsageTypes.OOC + MPX = 0x00E8, "MPX", UsageTypes.OOC + VOLUME_INCREMENT = 0x00E9, "Volume Increment", UsageTypes.RTC + VOLUME_DECREMENT = 0x00EA, "Volume Decrement", UsageTypes.RTC + SPEED_SELECT = 0x00F0, "Speed Select", UsageTypes.OSC + PLAYBACK_SPEED = 0x00F1, "Playback Speed", UsageTypes.NARY + STANDARD_PLAY = 0x00F2, "Standard Play", UsageTypes.SEL + LONG_PLAY = 0x00F3, "Long Play", UsageTypes.SEL + EXTENDED_PLAY = 0x00F4, "Extended Play", UsageTypes.SEL + SLOW = 0x00F5, "Slow", UsageTypes.OSC + FAN_ENABLE = 0x0100, "Fan Enable", UsageTypes.OOC + FAN_SPEED = 0x0101, "Fan Speed", UsageTypes.LC + LIGHT_ENABLE = 0x0102, "Light Enable", UsageTypes.OOC + LIGHT_ILLUMINATION_LEVEL = 0x0103, "Light Illumination Level", UsageTypes.LC + CLIMATE_CONTROL_ENABLE = 0x0104, "Climate Control Enable", UsageTypes.OOC + ROOM_TEMPERATURE = 0x0105, "Room Temperature", UsageTypes.LC + SECURITY_ENABLE = 0x0106, "Security Enable", UsageTypes.OOC + FIRE_ALARM = 0x0107, "Fire Alarm", UsageTypes.OSC + POLICE_ALARM = 0x0108, "Police Alarm", UsageTypes.OSC + PROXIMITY = 0x0109, "Proximity", UsageTypes.LC + MOTION = 0x010A, "Motion", UsageTypes.OSC + DURESS_ALARM = 0x010B, "Duress Alarm", UsageTypes.OSC + HOLDUP_ALARM = 0x010C, "Holdup Alarm", UsageTypes.OSC + MEDICAL_ALARM = 0x010D, "Medical Alarm", UsageTypes.OSC + BALANCE_RIGHT = 0x0150, "Balance Right", UsageTypes.RTC + BALANCE_LEFT = 0x0151, "Balance Left", UsageTypes.RTC + BASS_INCREMENT = 0x0152, "Bass Increment", UsageTypes.RTC + BASS_DECREMENT = 0x0153, "Bass Decrement", UsageTypes.RTC + TREBLE_INCREMENT = 0x0154, "Treble Increment", UsageTypes.RTC + TREBLE_DECREMENT = 0x0155, "Treble Decrement", UsageTypes.RTC + SPEAKER_SYSTEM = 0x0160, "Speaker System", UsageTypes.CL + CHANNEL_LEFT = 0x0161, "Channel Left", UsageTypes.CL + CHANNEL_RIGHT = 0x0162, "Channel Right", UsageTypes.CL + CHANNEL_CENTER = 0x0163, "Channel Center", UsageTypes.CL + CHANNEL_FRONT = 0x0164, "Channel Front", UsageTypes.CL + CHANNEL_CENTER_FRONT = 0x0165, "Channel Center Front", UsageTypes.CL + CHANNEL_SIDE = 0x0166, "Channel Side", UsageTypes.CL + CHANNEL_SURROUND = 0x0167, "Channel Surround", UsageTypes.CL + CHANNEL_LOW_FREQUENCY_ENHANCEMENT = 0x0168, "Channel Low Frequency Enhancement", UsageTypes.CL + CHANNEL_TOP = 0x0169, "Channel Top", UsageTypes.CL + CHANNEL_UNKNOWN = 0x016A, "Channel Unknown", UsageTypes.CL + SUBCHANNEL = 0x0170, "Sub-channel", UsageTypes.LC + SUBCHANNEL_INCREMENT = 0x0171, "Sub-channel Increment", UsageTypes.OSC + SUBCHANNEL_DECREMENT = 0x0172, "Sub-channel Decrement", UsageTypes.OSC + ALTERNATE_AUDIO_INCREMENT = 0x0173, "Alternate Audio Increment", UsageTypes.OSC + ALTERNATE_AUDIO_DECREMENT = 0x0174, "Alternate Audio Decrement", UsageTypes.OSC + APPLICATION_LAUNCH_BUTTONS = 0x0180, "Application Launch Buttons", UsageTypes.NARY + AL_LAUCH_BUTTON_CONFIGURATION_TOOL = 0x0181, "AL Launch Button Configuration Tool", UsageTypes.SEL + AL_PROGRAMMABLE_BUTTON_CONFIGURATION = 0x0182, "AL Programmable Button Configuration", UsageTypes.SEL + AL_CONSUMER_CONTROL_CONFIGURATION = 0x0183, "AL Consumer Control Configuration", UsageTypes.SEL + AL_WORD_PROCESSOR = 0x0184, "AL Word Processor", UsageTypes.SEL + AL_TEXT_EDITOR = 0x0185, "AL Text Editor", UsageTypes.SEL + AL_SPREADSHEET = 0x0186, "AL Spreadsheet", UsageTypes.SEL + AL_GRAPHICS_EDITOR = 0x0187, "AL Graphics Editor", UsageTypes.SEL + AL_PRESENTATION_APP = 0x0188, "AL Presentation App", UsageTypes.SEL + AL_DATABASE_APP = 0x0189, "AL Database App", UsageTypes.SEL + AL_EMAIL_READER = 0x018A, "AL Email Reader", UsageTypes.SEL + AL_NEWSREADER = 0x018B, "AL Newsreader", UsageTypes.SEL + AL_VOICEMAIL = 0x018C, "AL Voicemail", UsageTypes.SEL + AL_CONTACTS_ADDRESS_BOOK = 0x018D, "AL Contacts/Address Book", UsageTypes.SEL + AL_CALENDAR_SCHEDULE = 0x018E, "AL Calendar/Schedule", UsageTypes.SEL + AL_TASK_PROJECT_MANAGER = 0x018F, "AL Task/Project Manager", UsageTypes.SEL + AL_LOG_JOURNAL_TIMECARD = 0x0190, "AL Log/Journal/Timecard", UsageTypes.SEL + AL_CHECKBOOK_FINANCE = 0x0191, "AL Checkbook/Finance", UsageTypes.SEL + AL_CALCULATOR = 0x0192, "AL Calculator", UsageTypes.SEL + AL_AV_CAPTURE_PLAYBACK = 0x0193, "AL A/V Capture/Playback", UsageTypes.SEL + AL_LOCAL_MACHINE_BROWSER = 0x0194, "AL Local Machine Browser", UsageTypes.SEL + AL_LAN_WAN_BROWSER = 0x0195, "AL LAN/WAN Browser", UsageTypes.SEL + AL_INTERNET_BROWSER = 0x0196, "AL Internet Browser", UsageTypes.SEL + AL_REMOTE_NETWORKING_ISP_CONNECT = 0x0197, "AL Remote Networking/ISP Connect", UsageTypes.SEL + AL_NETWORK_CONFERENCE = 0x0198, "AL Network Conference", UsageTypes.SEL + AL_NETWORK_CHAT = 0x0199, "AL Network Chat", UsageTypes.SEL + AL_TELEPHONY_DIALER = 0x019A, "AL Telephony/Dialer", UsageTypes.SEL + AL_LOGON = 0x019B, "AL Logon", UsageTypes.SEL + AL_LOGOFF = 0x019C, "AL Logoff", UsageTypes.SEL + AL_LOGON_LOGOFF = 0x019D, "AL Logon/Logoff", UsageTypes.SEL + AL_LOCK_SCREEN_SAVER = 0x019E, "AL Terminal Lock/Screensaver", UsageTypes.SEL + AL_CONTROL_PANEL = 0x019F, "AL Control Panel", UsageTypes.SEL + AL_COMMAND_LINE_PROCESSOR_RUN = 0x01A0, "AL Command Line Processor/Run", UsageTypes.SEL + AL_PROCESS_TASK_MANAGER = 0x01A1, "AL Process/Task Manager", UsageTypes.SEL + AL_SELECT_TASK_APPLICATION = 0x01A2, "AL Select Task/Application", UsageTypes.SEL + AL_NEXT_TASK_APPLICATION = 0x01A3, "AL Next Task/Application", UsageTypes.SEL + AL_PREVIOUS_TASK_APPLICATION = 0x01A4, "AL Previous Task/Application", UsageTypes.SEL + AL_HALT_TASK_APPLICATION = 0x01A5, "AL Preemptive Halt Task/Application", UsageTypes.SEL + AL_INTEGRATED_HELP_CENTER = 0x01A6, "AL Integrated Help Center", UsageTypes.SEL + AL_DOCUMENTS = 0x01A7, "AL Documents", UsageTypes.SEL + AL_THESAURUS = 0x01A8, "AL Thesaurus", UsageTypes.SEL + AL_DICTIONARY = 0x01A9, "AL Dictionary", UsageTypes.SEL + AL_DESKTOP = 0x01AA, "AL Desktop", UsageTypes.SEL + AL_SPELL_CHECK = 0x01AB, "AL Spell Check", UsageTypes.SEL + AL_GRAMMAR_CHECK = 0x01AC, "AL Grammar Check", UsageTypes.SEL + AL_WIRELESS_STATUS = 0x01AD, "AL Wireless Status", UsageTypes.SEL + AL_KEYBOARD_LAYOUT = 0x01AE, "AL Keyboard Layout", UsageTypes.SEL + AL_VIRUS_PROTECTION = 0x01AF, "AL Virus Protection", UsageTypes.SEL + AL_ENCRYPTION = 0x01B0, "AL Encryption", UsageTypes.SEL + AL_SCREEN_SAVER = 0x01B1, "AL Screen Saver", UsageTypes.SEL + AL_ALARMS = 0x01B2, "AL Alarms", UsageTypes.SEL + AL_CLOCK = 0x01B3, "AL Clock", UsageTypes.SEL + AL_FILE_BROWSER = 0x01B4, "AL File Browser", UsageTypes.SEL + AL_POWER_STATUS = 0x01B5, "AL Power Status", UsageTypes.SEL + AL_IMAGE_BROWSER = 0x01B6, "AL Image Browser", UsageTypes.SEL + AL_AUDIO_BROWSER = 0x01B7, "AL Audio Browser", UsageTypes.SEL + AL_VIDEO_BROWSER = 0x01B8, "AL Movie Browser", UsageTypes.SEL + AL_DIGITAL_RIGHTS_MANAGER = 0x01B9, "AL Digital Rights Manager", UsageTypes.SEL + AL_DIGITAL_WALLET = 0x01BA, "AL Digital Wallet", UsageTypes.SEL + AL_INSTANT_MESSAGING = 0x01BC, "AL Instant Messaging", UsageTypes.SEL + AL_OEM_FEATURES_TIPS_TUTORIAL_BROWSER = 0x01BD, "AL OEM Features/ Tips/Tutorial Browser", UsageTypes.SEL + AL_OEM_HELP = 0x01BE, "AL OEM Help", UsageTypes.SEL + AL_ONLINE_COMMUNITY = 0x01BF, "AL Online Community", UsageTypes.SEL + AL_ENTERTAINMENT_CONTENT_BROWSER = 0x01C0, "AL Entertainment Content Browser", UsageTypes.SEL + AL_ONLINE_SHOPPING_BROWSER = 0x01C1, "AL Online Shopping Browser", UsageTypes.SEL + AL_SMARTCARD_INFORMATION_HELP = 0x01C2, "AL SmartCard Information/Help", UsageTypes.SEL + AL_MARKET_MONITOR_FINANCE_BROWSER = 0x01C3, "AL Market Monitor/Finance Browser", UsageTypes.SEL + AL_CUSTOMIZED_CORPORATE_NEWS_BROWSER = 0x01C4, "AL Customized Corporate News Browser", UsageTypes.SEL + AL_ONLINE_ACTIVITY_BROWSER = 0x01C5, "AL Online Activity Browser", UsageTypes.SEL + AL_RESEARCH_SEARCH_BROWSER = 0x01C6, "AL Research/Search Browser", UsageTypes.SEL + AL_AUDIO_PLAYER = 0x01C7, "AL Audio Player", UsageTypes.SEL + GENERIC_GUI_APPLICATION_CONTROLS = 0x0200, "Generic GUI Application Controls", UsageTypes.NARY + AC_NEW = 0x0201, "AC New", UsageTypes.SEL + AC_OPEN = 0x0202, "AC Open", UsageTypes.SEL + AC_CLOSE = 0x0203, "AC Close", UsageTypes.SEL + AC_EXIT = 0x0204, "AC Exit", UsageTypes.SEL + AC_MAXIMIZE = 0x0205, "AC Maximize", UsageTypes.SEL + AC_MINIMIZE = 0x0206, "AC Minimize", UsageTypes.SEL + AC_SAVE = 0x0207, "AC Save", UsageTypes.SEL + AC_PRINT = 0x0208, "AC Print", UsageTypes.SEL + AC_PROPERTIES = 0x0209, "AC Properties", UsageTypes.SEL + AC_UNDO = 0x021A, "AC Undo", UsageTypes.SEL + AC_COPY = 0x021B, "AC Copy", UsageTypes.SEL + AC_CUT = 0x021C, "AC Cut", UsageTypes.SEL + AC_PASTE = 0x021D, "AC Paste", UsageTypes.SEL + AC_SELECT_ALL = 0x021E, "AC Select All", UsageTypes.SEL + AC_FIND = 0x021F, "AC Find", UsageTypes.SEL + AC_FIND_AND_REPLACE = 0x0220, "AC Find and Replace", UsageTypes.SEL + AC_SEARCH = 0x0221, "AC Search", UsageTypes.SEL + AC_GO_TO = 0x0222, "AC Go To", UsageTypes.SEL + AC_HOME = 0x0223, "AC Home", UsageTypes.SEL + AC_BACK = 0x0224, "AC Back", UsageTypes.SEL + AC_FORWARD = 0x0225, "AC Forward", UsageTypes.SEL + AC_STOP = 0x0226, "AC Stop", UsageTypes.SEL + AC_REFRESH = 0x0227, "AC Refresh", UsageTypes.SEL + AC_PREVIOUS_LINK = 0x0228, "AC Previous Link", UsageTypes.SEL + AC_NEXT_LINK = 0x0229, "AC Next Link", UsageTypes.SEL + AC_BOOKMARKS = 0x022A, "AC Bookmarks", UsageTypes.SEL + AC_HISTORY = 0x022B, "AC History", UsageTypes.SEL + AC_SUBSCRIPTIONS = 0x022C, "AC Subscriptions", UsageTypes.SEL + AC_ZOOM_IN = 0x022D, "AC Zoom In", UsageTypes.SEL + AC_ZOOM_OUT = 0x022E, "AC Zoom Out", UsageTypes.SEL + AC_ZOOM = 0x022F, "AC Zoom", UsageTypes.LC + AC_FULL_SCREEN_VIEW = 0x0230, "AC Full Screen View", UsageTypes.SEL + AC_NORMAL_VIEW = 0x0231, "AC Normal View", UsageTypes.SEL + AC_VIEW_TOGGLE = 0x0232, "AC View Toggle", UsageTypes.SEL + AC_SCROLL_UP = 0x0233, "AC Scroll Up", UsageTypes.SEL + AC_SCROLL_DOWN = 0x0234, "AC Scroll Down", UsageTypes.SEL + AC_SCROLL = 0x0235, "AC Scroll", UsageTypes.LC + AC_PAN_LEFT = 0x0236, "AC Pan Left", UsageTypes.SEL + AC_PAN_RIGHT = 0x0237, "AC Pan Right", UsageTypes.SEL + AC_PAN = 0x0238, "AC Pan", UsageTypes.LC + AC_NEW_WINDOWS = 0x0239, "AC New Window", UsageTypes.SEL + AC_TILE_HORIZONTALLY = 0x023A, "AC Tile Horizontally", UsageTypes.SEL + AC_TILE_VERTICALLY = 0x023B, "AC Tile Vertically", UsageTypes.SEL + AC_FORMAT = 0x023C, "AC Format", UsageTypes.SEL + AC_EDIT = 0x023D, "AC Edit", UsageTypes.SEL + AC_BOLD = 0x023E, "AC Bold", UsageTypes.SEL + AC_ITALICS = 0x023F, "AC Italics", UsageTypes.SEL + AC_UNDERLINE = 0x0240, "AC Underline", UsageTypes.SEL + AC_STRIKETHROUGH = 0x0241, "AC Strikethrough", UsageTypes.SEL + AC_SUBSCRIPT = 0x0242, "AC Subscript", UsageTypes.SEL + AC_SUPERSCRIPT = 0x0243, "AC Superscript", UsageTypes.SEL + AC_ALL_CAPS = 0x0244, "AC All Caps", UsageTypes.SEL + AC_ROTATE = 0x0245, "AC Rotate", UsageTypes.SEL + AC_RESIZE = 0x0246, "AC Resize", UsageTypes.SEL + AC_FLIP_HORIZONTAL = 0x0247, "AC Flip horizontal", UsageTypes.SEL + AC_FLIP_VERTICAL = 0x0248, "AC Flip Vertical", UsageTypes.SEL + AC_MIRROR_HORIZONTAL = 0x0249, "AC Mirror Horizontal", UsageTypes.SEL + AC_MIRROR_VERTICAL = 0x024A, "AC Mirror Vertical", UsageTypes.SEL + AC_FONT_SELECT = 0x024B, "AC Font Select", UsageTypes.SEL + AC_FONT_COLOR = 0x024C, "AC Font Color", UsageTypes.SEL + AC_FONT_SIZE = 0x024D, "AC Font Size", UsageTypes.SEL + AC_JUSTIFY_LEFT = 0x024E, "AC Justify Left", UsageTypes.SEL + AC_JUSTIFY_CENTER_H = 0x024F, "AC Justify Center H", UsageTypes.SEL + AC_JUSTIFY_RIGHT = 0x0250, "AC Justify Right", UsageTypes.SEL + AC_JUSTIFY_BLOCK_H = 0x0251, "AC Justify Block H", UsageTypes.SEL + AC_JUSTIFY_TOP = 0x0252, "AC Justify Top", UsageTypes.SEL + AC_JUSTIFY_CENTER_V = 0x0253, "AC Justify Center V", UsageTypes.SEL + AC_JUSTIFY_BOTTOM = 0x0254, "AC Justify Bottom", UsageTypes.SEL + AC_JUSTIFY_BLOCK_V = 0x0255, "AC Justify Block V", UsageTypes.SEL + AC_INDENT_INCREASE = 0x0256, "AC Indent Decrease", UsageTypes.SEL + AC_INDENT_DECREASE = 0x0257, "AC Indent Increase", UsageTypes.SEL + AC_NUMBERED_LIST = 0x0258, "AC Numbered List", UsageTypes.SEL + AC_RESTART_NUMBERING = 0x0259, "AC Restart Numbering", UsageTypes.SEL + AC_BULLETED_LIST = 0x025A, "AC Bulleted List", UsageTypes.SEL + AC_PROMOTE = 0x025B, "AC Promote", UsageTypes.SEL + AC_DEMOTE = 0x025C, "AC Demote", UsageTypes.SEL + AC_YES = 0x025D, "AC Yes", UsageTypes.SEL + AC_NO = 0x025E, "AC No", UsageTypes.SEL + AC_CANCEL = 0x025F, "AC Cancel", UsageTypes.SEL + AC_CATALOG = 0x0260, "AC Catalog", UsageTypes.SEL + AC_BUY_CHECKOUT = 0x0261, "AC Buy/Checkout", UsageTypes.SEL + AC_ADD_TO_CART = 0x0262, "AC Add to Cart", UsageTypes.SEL + AC_EXPAND = 0x0263, "AC Expand", UsageTypes.SEL + AC_EXPAND_ALL = 0x0264, "AC Expand All", UsageTypes.SEL + AC_COLLAPSE = 0x0265, "AC Collapse", UsageTypes.SEL + AC_COLLAPSE_ALL = 0x0266, "AC Collapse All", UsageTypes.SEL + AC_PRINT_PREVIEW = 0x0267, "AC Print Preview", UsageTypes.SEL + AC_PASTE_SPECIAL = 0x0268, "AC Paste Special", UsageTypes.SEL + AC_INSER_MODE = 0x0269, "AC Insert Mode", UsageTypes.SEL + AC_DELETE = 0x026A, "AC Delete", UsageTypes.SEL + AC_LOCK = 0x026B, "AC Lock", UsageTypes.SEL + AC_UNLOCK = 0x026C, "AC Unlock", UsageTypes.SEL + AC_PROTECT = 0x026D, "AC Protect", UsageTypes.SEL + AC_UNPROTECT = 0x026E, "AC Unprotect", UsageTypes.SEL + AC_ATTACH_COMMENT = 0x026F, "AC Attach Comment", UsageTypes.SEL + AC_DELETE_COMMENT = 0x0270, "AC Delete Comment", UsageTypes.SEL + AC_VIEW_COMMENT = 0x0271, "AC View Comment", UsageTypes.SEL + AC_SELECT_WORD = 0x0272, "AC Select Word", UsageTypes.SEL + AC_SELECT_SENTENCE = 0x0273, "AC Select Sentence", UsageTypes.SEL + AC_SELECT_PARAGRAPH = 0x0274, "AC Select Paragraph", UsageTypes.SEL + AC_SELECT_COLUMN = 0x0275, "AC Select Column", UsageTypes.SEL + AC_SELECT_ROW = 0x0276, "AC Select Row", UsageTypes.SEL + AC_SELECT_TABLE = 0x0277, "AC Select Table", UsageTypes.SEL + AC_SELECT_OBJECT = 0x0278, "AC Select Object", UsageTypes.SEL + AC_REDO_REPEAT = 0x0279, "AC Redo/Repeat", UsageTypes.SEL + AC_SORT = 0x027A, "AC Sort", UsageTypes.SEL + AC_SORT_ASCENDING = 0x027B, "AC Sort Ascending", UsageTypes.SEL + AC_SORT_DESCENDING = 0x027C, "AC Sort Descending", UsageTypes.SEL + AC_FILTER = 0x027D, "AC Filter", UsageTypes.SEL + AC_SET_CLOCK = 0x027E, "AC Set Clock", UsageTypes.SEL + AC_VIEW_CLOCK = 0x027F, "AC View Clock", UsageTypes.SEL + AC_SELECT_TIME_ZONE = 0x0280, "AC Select Time Zone", UsageTypes.SEL + AC_EDIT_TIME_ZONES = 0x0281, "AC Edit Time Zones", UsageTypes.SEL + AC_SET_ALARM = 0x0282, "AC Set Alarm", UsageTypes.SEL + AC_CLEAR_ALARM = 0x0283, "AC Clear Alarm", UsageTypes.SEL + AC_SNOOZE_ALARM = 0x0284, "AC Snooze Alarm", UsageTypes.SEL + AC_RESET_ALARM = 0x0285, "AC Reset Alarm", UsageTypes.SEL + AC_SYNCHRONIZE = 0x0286, "AC Synchronize", UsageTypes.SEL + AC_SEND_RECEIVE = 0x0287, "AC Send/Receive", UsageTypes.SEL + AC_SEND_TO = 0x0288, "AC Send To", UsageTypes.SEL + AC_REPLY = 0x0289, "AC Reply", UsageTypes.SEL + AC_REPLY_ALL = 0x028A, "AC Reply All", UsageTypes.SEL + AC_FORWARD_MSG = 0x028B, "AC Forward Msg", UsageTypes.SEL + AC_SEND = 0x028C, "AC Send", UsageTypes.SEL + AC_ATTACH_FILE = 0x028D, "AC Attach File", UsageTypes.SEL + AC_UPLOAD = 0x028E, "AC Upload", UsageTypes.SEL + AC_DOWNLOAD = 0x028F, "AC Download (Save Target As)", UsageTypes.SEL + AC_SET_BORDERS = 0x0290, "AC Set Borders", UsageTypes.SEL + AC_INSERT_ROW = 0x0291, "AC Insert Row", UsageTypes.SEL + AC_INSERT_COLUMN = 0x0292, "AC Insert Column", UsageTypes.SEL + AC_INSERT_FILE = 0x0293, "AC Insert File", UsageTypes.SEL + AC_INSERT_PICTURE = 0x0294, "AC Insert Picture", UsageTypes.SEL + AC_INSERT_OBJECT = 0x0295, "AC Insert Object", UsageTypes.SEL + AC_INSERT_SYMBOL = 0x0296, "AC Insert Symbol", UsageTypes.SEL + AC_SAVE_AND_CLOSE = 0x0297, "AC Save and Close", UsageTypes.SEL + AC_RENAME = 0x0298, "AC Rename", UsageTypes.SEL + AC_MERGE = 0x0299, "AC Merge", UsageTypes.SEL + AC_SPLIT = 0x029A, "AC Split", UsageTypes.SEL + AC_DISTRIBUTE_HORIZONTICALLY = 0x029B, "AC Disribute Horizontally", UsageTypes.SEL + AC_DISTRIBUTE_VERTICALLY = 0x029C, "AC Distribute Vertically", UsageTypes.SEL + + +class PowerDevice(_Data): + INAME = 0x01, "iName", UsageTypes.SV + PRESENT_STATUS = 0x02, "PresentStatus", UsageTypes.CL + CHARGED_STATUS = 0x03, "ChangedStatus", UsageTypes.CL + UPS = 0x04, "UPS", UsageTypes.CA + POWER_SUPPLY = 0x05, "PowerSupply", UsageTypes.CA + BATTERY_SYSTEM = 0x10, "BatterySystem", UsageTypes.CP + BATTERY_SYSTEM_ID = 0x11, "BatterySystemID", UsageTypes.SV + BATTERY = 0x12, "Battery", UsageTypes.CP + BATTERY_ID = 0x13, "BatteryID", UsageTypes.SV + CHARGER = 0x14, "Charger", UsageTypes.CP + CHARGER_ID = 0x15, "ChargerID", UsageTypes.SV + POWER_CONVERTER = 0x16, "PowerConverter", UsageTypes.CP + POWER_CONVERTER_ID = 0x17, "PowerConverterID", UsageTypes.SV + OUTLET_SYSTEM = 0x18, "OutletSystem", UsageTypes.CP + OUTLET_SYSTEM_ID = 0x19, "OutletSystemID", UsageTypes.SV + INPUT = 0x1A, "Input", UsageTypes.CP + INPUT_ID = 0x1B, "InputID", UsageTypes.SV + OUTPUT = 0x1C, "Output", UsageTypes.CP + OUTPUT_ID = 0x1D, "OutputID", UsageTypes.SV + FLOW = 0x1E, "Flow", UsageTypes.CP + FLOW_ID = 0x1F, "FlowID", UsageTypes.SV + OUTLET = 0x20, "Outlet", UsageTypes.CP + OUTLET_ID = 0x21, "OutletID", UsageTypes.SV + GANG = 0x22, "Gang", UsageTypes.CP + GANG_ID = 0x23, "GangID", UsageTypes.SV + POWER_SUMMARY = 0x24, "PowerSummary", UsageTypes.CP + POWER_SUMMARY_ID = 0x25, "PowerSummaryID", UsageTypes.SV + VOLTAGE = 0x30, "Voltage", UsageTypes.DV + CURRENT = 0x31, "Current", UsageTypes.DV + FREQUENCY = 0x32, "Frequency", UsageTypes.DV + APPARENT_POWER = 0x33, "ApparentPower", UsageTypes.DV + ACTIVE_POWER = 0x34, "ActivePower", UsageTypes.DV + PERCENT_LOAD = 0x35, "PercentLoad", UsageTypes.DV + TEMPERATURE = 0x36, "Temperature", UsageTypes.DV + HUMIFITY = 0x37, "Humidity", UsageTypes.DV + BAD_COUNT = 0x38, "BadCount", UsageTypes.DV + CONFIG_VOLTAGE = 0x40, "ConfigVoltage", (UsageTypes.SV, UsageTypes.DV) + CONFIG_CURRENT = 0x41, "ConfigCurrent", (UsageTypes.SV, UsageTypes.DV) + CONFIG_FREQUENCY = 0x42, "ConfigFrequency", (UsageTypes.SV, UsageTypes.DV) + CONFIG_APPARENT_POWER = 0x43, "ConfigApparentPower", (UsageTypes.SV, UsageTypes.DV) + CONFIG_ACTIVE_POWER = 0x44, "ConfigActivePower", (UsageTypes.SV, UsageTypes.DV) + CONFIG_PERCENT_LOAD = 0x45, "ConfigPercentLoad", (UsageTypes.SV, UsageTypes.DV) + CONFIG_TEMPERATURE = 0x46, "ConfigTemperature", (UsageTypes.SV, UsageTypes.DV) + CONFIG_HUMIFITY = 0x47, "ConfigHumidity", (UsageTypes.SV, UsageTypes.DV) + SWITCH_ON_CONTROL = 0x50, "SwitchOnControl", UsageTypes.DV + SWITCH_OFF_CONTROL = 0x51, "SwitchOffControl", UsageTypes.DV + TOGGLE_CONTROL = 0x52, "ToggleControl", UsageTypes.DV + LOW_VOLTAGE_TRANSFER = 0x53, "LowVoltageTransfer", UsageTypes.DV + HIGH_VOLTAGE_TRANSFER = 0x54, "HighVoltageTransfer", UsageTypes.DV + DELAY_BEFORE_REBOOT = 0x55, "DelayBeforeReboot", UsageTypes.DV + DELAY_BEFORE_STARTUP = 0x56, "DelayBeforeStartup", UsageTypes.DV + DELAY_BEFORE_SHUTDOWN = 0x57, "DelayBeforeShutdown", UsageTypes.DV + TEST = 0x58, "Test", UsageTypes.DV + MODULE_RESET = 0x59, "ModuleReset", UsageTypes.DV + AUDIBLE_ALARM_CONTROL = 0x5A, "AudibleAlarmControl", UsageTypes.DV + PRESENT = 0x60, "Present", UsageTypes.DF + GOOD = 0x61, "Good", UsageTypes.DF + INTERNAL_FAILURE = 0x62, "InternalFailure", UsageTypes.DF + VOLTAGE_OUT_OF_RANGE = 0x63, "VoltageOutOfRange", UsageTypes.DF + FREQUENCY_OUT_OF_RANGE = 0x64, "FrequencyOutOfRange", UsageTypes.DF + OVERLOAD = 0x65, "Overload", UsageTypes.DF + OVERCHARGED = 0x66, "OverCharged", UsageTypes.DF + OVER_TEMPERATURE = 0x67, "OverTemperature", UsageTypes.DF + SHUTDOWN_REQUESTED = 0x68, "ShutdownRequested", UsageTypes.DF + SHUTDOWN_IMMINEBT = 0x69, "ShutdownImminent", UsageTypes.DF + SWITCH_ON_OFF = 0x6B, "SwitchOn/Off", UsageTypes.DF + SWITCHABLE = 0x6C, "Switchable", UsageTypes.DF + USED = 0x6D, "Used", UsageTypes.DF + BOOST = 0x6E, "Boost", UsageTypes.DF + BUCK = 0x6F, "Buck", UsageTypes.DF + INITIALIZED = 0x70, "Initialized", UsageTypes.DF + TESTED = 0x71, "Tested", UsageTypes.DF + AWAITING_POWER = 0x72, "AwaitingPower", UsageTypes.DF + COMMUNICATION_LOST = 0x73, "CommunicationLost", UsageTypes.DF + IMANUFACTURER = 0xFD, "iManufacturer", UsageTypes.SV + IPRODUCT = 0xFE, "iProduct", UsageTypes.SV + ISERIALNUMBER = 0xFF, "iSerialNumber", UsageTypes.SV + + +class FIDO(_Data): + U2F_AUTHENTICATOR_DEVICEM = 0x01, "U2F Authenticator Device" + INPUT_REPORT_DATA = 0x20, "Input Report Data" + OUTPUT_REPORT_DATA = 0x21, "Output Report Data" + + +class UsagePages(_Data): + GENERIC_DESKTOP_CONTROLS_PAGE = 0x01, "Generic Desktop Controls", GenericDesktopControls + SIMULATION_CONTROLS_PAGE = 0x02, "Simulation Controls" + VR_CONTROLS_PAGE = 0x03, "VR Controls" + SPORT_CONTROLS_PAGE = 0x04, "Sport Controls" + GAME_CONTROLS_PAGE = 0x05, "Game Controls" + GENERIC_DEVICE_CONTROLS_PAGE = 0x06, "Generic Device Controls" + KEYBOARD_KEYPAD_PAGE = 0x07, "Keyboard/Keypad", KeyboardKeypad + LED_PAGE = 0x08, "LED", Led + BUTTON_PAGE = 0x09, "Button", Button + ORDINAL_PAGE = 0x0A, "Ordinal" + TELEPHONY_PAGE = 0x0B, "Telephony" + CONSUMER_PAGE = 0x0C, "Consumer", Consumer + DIGITIZER_PAGE = 0x0D, "Digitizer" + HAPTICS_PAGE = 0x0E, "Haptics" + PID_PAGE = 0x0F, "PID" + UNICODE_PAGE = 0x10, "Unicode" + EYE_AND_HEAD_TRACKER_PAGE = 0x12, "Eye and Head Tracker" + ALPHANUMERIC_DISPLAY_PAGE = 0x14, "Alphanumeric Display" + SENSOR_PAGE = 0x20, "Sensor" + MEDICAL_INSTRUMENTS_PAGE = 0x40, "Medical Instruments" + BRAILLE_DISPLAY_PAGE = 0x41, "Braillie" + LIGHTING_AND_ILLUMINATION_PAGE = 0x59, "Lighting and Illumination" + USB_MONITOR_PAGE = 0x80, "USB Monitor" + USB_ENUMERATED_VALUES_PAGE = 0x81, "USB Enumerated Values" + VESA_VIRTUAL_CONTROLS_PAGE = 0x82, "VESA Virtual Controls" + POWER_DEVICE_PAGE = 0x84, "Power Device", PowerDevice + BATTERY_SYSTEM_PAGE = 0x85, "Battery System" + BARCODE_SCANNER_PAGE = 0x8C, "Barcode Scanner" + WEIGHING_PAGE = 0x8D, "Weighing" + MSR_PAGE = 0x8E, "MSR" + RESERVED_POS_PAGE = 0x8F, "Reserved POS" + CAMERA_CONTROL_PAGE = 0x90, "Camera Control" + ARCADE_PAGE = 0x91, "Arcade" + GAMING_DEVICE_PAGE = 0x92, "Gaming Device" + FIDO_ALLIANCE_PAGE = 0xF1D0, "FIDO Alliance", FIDO + VENDOR_PAGE = 0xFF00, ..., 0xFFFF, "Vendor Page" diff --git a/lib/hidapi/__init__.py b/lib/hidapi/__init__.py index b29b8c2c..e69de29b 100644 --- a/lib/hidapi/__init__.py +++ b/lib/hidapi/__init__.py @@ -1,49 +0,0 @@ -# -*- python-mode -*- - -## Copyright (C) 2012-2013 Daniel Pavel -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License along -## with this program; if not, write to the Free Software Foundation, Inc., -## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -"""Generic Human Interface Device API.""" - -import platform as _platform - -if _platform.system() in ('Darwin', 'Windows'): - from hidapi.hidapi import close # noqa: F401 - from hidapi.hidapi import enumerate # noqa: F401 - from hidapi.hidapi import find_paired_node # noqa: F401 - from hidapi.hidapi import find_paired_node_wpid # noqa: F401 - from hidapi.hidapi import get_manufacturer # noqa: F401 - from hidapi.hidapi import get_product # noqa: F401 - from hidapi.hidapi import get_serial # noqa: F401 - from hidapi.hidapi import monitor_glib # noqa: F401 - from hidapi.hidapi import open # noqa: F401 - from hidapi.hidapi import open_path # noqa: F401 - from hidapi.hidapi import read # noqa: F401 - from hidapi.hidapi import write # noqa: F401 -else: - from hidapi.udev import close # noqa: F401 - from hidapi.udev import enumerate # noqa: F401 - from hidapi.udev import find_paired_node # noqa: F401 - from hidapi.udev import find_paired_node_wpid # noqa: F401 - from hidapi.udev import get_manufacturer # noqa: F401 - from hidapi.udev import get_product # noqa: F401 - from hidapi.udev import get_serial # noqa: F401 - from hidapi.udev import monitor_glib # noqa: F401 - from hidapi.udev import open # noqa: F401 - from hidapi.udev import open_path # noqa: F401 - from hidapi.udev import read # noqa: F401 - from hidapi.udev import write # noqa: F401 - -__version__ = '0.9' diff --git a/lib/hidapi/common.py b/lib/hidapi/common.py new file mode 100644 index 00000000..6817511a --- /dev/null +++ b/lib/hidapi/common.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +import dataclasses + + +@dataclasses.dataclass +class DeviceInfo: + path: str + bus_id: str | None + vendor_id: str + product_id: str + interface: str | None + driver: str | None + manufacturer: str | None + product: str | None + serial: str | None + release: str | None + isDevice: bool + hidpp_short: str | None + hidpp_long: str | None + centurion: bool = False diff --git a/lib/hidapi/hidapi.py b/lib/hidapi/hidapi_impl.py similarity index 67% rename from lib/hidapi/hidapi.py rename to lib/hidapi/hidapi_impl.py index 324cc329..d9fc3047 100644 --- a/lib/hidapi/hidapi.py +++ b/lib/hidapi/hidapi_impl.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -24,47 +22,49 @@ See https://github.com/libusb/hidapi for how to obtain binaries. Parts of this code are adapted from https://github.com/apmorton/pyhidapi which is MIT licensed. """ + +from __future__ import annotations + import atexit import ctypes -import platform as _platform +import logging +import platform +import typing -from collections import namedtuple -from logging import INFO as _INFO -from logging import getLogger from threading import Thread from time import sleep +from typing import Any +from typing import Callable -_log = getLogger(__name__) -del getLogger -native_implementation = 'hidapi' +from hidapi.common import DeviceInfo -# Device info as expected by Solaar -DeviceInfo = namedtuple( - 'DeviceInfo', [ - 'path', - 'bus_id', - 'vendor_id', - 'product_id', - 'interface', - 'driver', - 'manufacturer', - 'product', - 'serial', - 'release', - 'isDevice', - 'hidpp_short', - 'hidpp_long', - ] -) -del namedtuple +LOGITECH_VENDOR_ID = 0x046D + +if typing.TYPE_CHECKING: + import gi + + gi.require_version("Gdk", "3.0") + from gi.repository import GLib # NOQA: E402 + +logger = logging.getLogger(__name__) + +ACTION_ADD = "add" +ACTION_REMOVE = "remove" # Global handle to hidapi _hidapi = None # hidapi binary names for various platforms _library_paths = ( - 'libhidapi-hidraw.so', 'libhidapi-hidraw.so.0', 'libhidapi-libusb.so', 'libhidapi-libusb.so.0', - 'libhidapi-iohidmanager.so', 'libhidapi-iohidmanager.so.0', 'libhidapi.dylib', 'hidapi.dll', 'libhidapi-0.dll' + "libhidapi-hidraw.so", + "libhidapi-hidraw.so.0", + "libhidapi-libusb.so", + "libhidapi-libusb.so.0", + "libhidapi-iohidmanager.so", + "libhidapi-iohidmanager.so.0", + "libhidapi.dylib", + "hidapi.dll", + "libhidapi-0.dll", ) for lib in _library_paths: @@ -74,15 +74,15 @@ for lib in _library_paths: except OSError: pass else: - raise ImportError(f"Unable to load hdiapi library, tried: {' '.join(_library_paths)}") + raise ImportError(f"Unable to load hidapi library, tried: {' '.join(_library_paths)}") # Retrieve version of hdiapi library class _cHidApiVersion(ctypes.Structure): _fields_ = [ - ('major', ctypes.c_int), - ('minor', ctypes.c_int), - ('patch', ctypes.c_int), + ("major", ctypes.c_int), + ("minor", ctypes.c_int), + ("patch", ctypes.c_int), ] @@ -93,28 +93,27 @@ _hid_version = _hidapi.hid_version() # Construct device info struct based on API version class _cDeviceInfo(ctypes.Structure): - def as_dict(self): - return {name: getattr(self, name) for name, _t in self._fields_ if name != 'next'} + return {name: getattr(self, name) for name, _t in self._fields_ if name != "next"} # Low level hdiapi device info struct # See https://github.com/libusb/hidapi/blob/master/hidapi/hidapi.h#L143 _cDeviceInfo_fields = [ - ('path', ctypes.c_char_p), - ('vendor_id', ctypes.c_ushort), - ('product_id', ctypes.c_ushort), - ('serial_number', ctypes.c_wchar_p), - ('release_number', ctypes.c_ushort), - ('manufacturer_string', ctypes.c_wchar_p), - ('product_string', ctypes.c_wchar_p), - ('usage_page', ctypes.c_ushort), - ('usage', ctypes.c_ushort), - ('interface_number', ctypes.c_int), - ('next', ctypes.POINTER(_cDeviceInfo)), + ("path", ctypes.c_char_p), + ("vendor_id", ctypes.c_ushort), + ("product_id", ctypes.c_ushort), + ("serial_number", ctypes.c_wchar_p), + ("release_number", ctypes.c_ushort), + ("manufacturer_string", ctypes.c_wchar_p), + ("product_string", ctypes.c_wchar_p), + ("usage_page", ctypes.c_ushort), + ("usage", ctypes.c_ushort), + ("interface_number", ctypes.c_int), + ("next", ctypes.POINTER(_cDeviceInfo)), ] if _hid_version.contents.major >= 0 and _hid_version.contents.minor >= 13: - _cDeviceInfo_fields.append(('bus_type', ctypes.c_int)) + _cDeviceInfo_fields.append(("bus_type", ctypes.c_int)) _cDeviceInfo._fields_ = _cDeviceInfo_fields # Set up hidapi functions @@ -164,7 +163,7 @@ atexit.register(_hidapi.hid_exit) # Solaar opens the same device more than once which will fail unless we # allow non-exclusive opening. On windows opening with shared access is # the default, for macOS we need to set it explicitly. -if _platform.system() == 'Darwin': +if platform.system() == "Darwin": _hidapi.hid_darwin_set_open_exclusive.argtypes = [ctypes.c_int] _hidapi.hid_darwin_set_open_exclusive.restype = None _hidapi.hid_darwin_set_open_exclusive(0) @@ -175,7 +174,7 @@ class HIDError(Exception): def _enumerate_devices(): - """ Returns all HID devices which are potentially useful to us """ + """Returns all HID devices which are potentially useful to us""" devices = [] c_devices = _hidapi.hid_enumerate(0, 0) p = c_devices @@ -184,19 +183,12 @@ def _enumerate_devices(): p = p.contents.next _hidapi.hid_free_enumeration(c_devices) - keyboard_or_mouse = {d['path'] for d in devices if d['usage_page'] == 1 and d['usage'] in (6, 2)} unique_devices = {} for device in devices: - # On macOS we cannot access keyboard or mouse devices without special permissions. Since - # we don't need them anyway we remove them so opening them doesn't cause errors later. - if device['path'] in keyboard_or_mouse: - # print(f"Ignoring keyboard or mouse device: {device}") - continue - # hidapi returns separate entries for each usage page of a device. # Deduplicate by path to only keep one device entry. - if device['path'] not in unique_devices: - unique_devices[device['path']] = device + if device["path"] not in unique_devices: + unique_devices[device["path"]] = device unique_devices = unique_devices.values() # print("Unique devices:\n" + '\n'.join([f"{dev}" for dev in unique_devices])) @@ -205,10 +197,10 @@ def _enumerate_devices(): # Use a separate thread to check if devices have been removed or connected class _DeviceMonitor(Thread): - def __init__(self, device_callback, polling_delay=5.0): self.device_callback = device_callback self.polling_delay = polling_delay + self.prev_devices = None # daemon threads are automatically killed when main thread exits super().__init__(daemon=True) @@ -221,118 +213,173 @@ class _DeviceMonitor(Thread): current_devices = {tuple(dev.items()): dev for dev in _enumerate_devices()} for key, device in self.prev_devices.items(): if key not in current_devices: - self.device_callback('remove', device) + self.device_callback(ACTION_REMOVE, device) for key, device in current_devices.items(): if key not in self.prev_devices: - self.device_callback('add', device) + self.device_callback(ACTION_ADD, device) self.prev_devices = current_devices sleep(self.polling_delay) -# The filterfn is used to determine whether this is a device of interest to Solaar. -# It is given the bus id, vendor id, and product id and returns a dictionary -# with the required hid_driver and usb_interface and whether this is a receiver or device. -def _match(action, device, filterfn): - vid = device['vendor_id'] - pid = device['product_id'] +def _match( + action: str, + device: dict[str, Any], + filter_func: Callable[[int, int, int, bool, bool], dict[str, Any]], +): + """ + The filter_func is used to determine whether this is a device of + interest to Solaar. It is given the bus id, vendor id, and product + id and returns a dictionary with the required hid_driver and + usb_interface and whether this is a receiver or device. + """ + + vid = device["vendor_id"] + pid = device["product_id"] + hid_bus_type = device["bus_type"] # Translate hidapi bus_type to the bus_id values Solaar expects - if device.get('bus_type') == 0x01: + if device.get("bus_type") == 0x01: bus_id = 0x03 # USB - elif device.get('bus_type') == 0x02: + elif device.get("bus_type") == 0x02: bus_id = 0x05 # Bluetooth else: bus_id = None + logger.info(f"Device {device['path']} has an unsupported bus type {hid_bus_type:02X}") + return None + + # Skip unlikely devices with all-zero VID PID or unsupported bus IDs + if vid == 0 and pid == 0: + logger.info(f"Device {device['path']} has all-zero VID and PID") + logger.info(f"Skipping unlikely device {device['path']} ({bus_id}/{vid:04X}/{pid:04X})") + return None + + # Skip Logitech webcams to prevent them from locking up during hidpp checks + # (product IDs range for webcams from docs/usb.ids.txt) + if vid == LOGITECH_VENDOR_ID and 0x0800 <= pid <= 0x09FF: + logger.info(f"Skipping Logitech webcam {device['path']} ({bus_id}/{vid:04X}/{pid:04X})") + return None # Check for hidpp support - device['hidpp_short'] = False - device['hidpp_long'] = False + device["hidpp_short"] = False + device["hidpp_long"] = False device_handle = None - try: - device_handle = open_path(device['path']) - report = get_input_report(device_handle, 0x10, 32) + + def check_hidpp_short(): + report = _get_input_report(device_handle, 0x10, 32) if len(report) == 1 + 6 and report[0] == 0x10: - device['hidpp_short'] = True - report = get_input_report(device_handle, 0x11, 32) + device["hidpp_short"] = True + + def check_hidpp_long(): + report = _get_input_report(device_handle, 0x11, 32) if len(report) == 1 + 19 and report[0] == 0x11: - device['hidpp_long'] = True - except HIDError as e: # noqa: F841 - if _log.isEnabledFor(_INFO): - _log.info(f"Error opening device {device['path']} ({bus_id}/{vid:04X}/{pid:04X}) for hidpp check: {e}") + device["hidpp_long"] = True + + try: + device_handle = open_path(device["path"]) + + for check_func in (check_hidpp_short, check_hidpp_long): + try: + check_func() + except HIDError as e: + logger.info( + f"Error while {check_func.__name__}" + f"on device {device['path']} ({bus_id}/{vid:04X}/{pid:04X}) for hidpp check: {e}" + ) + except HIDError as e: + logger.info(f"Error opening device {device['path']} ({bus_id}/{vid:04X}/{pid:04X}) for hidpp check: {e}") finally: if device_handle: close(device_handle) - if _log.isEnabledFor(_INFO): - _log.info( - 'Found device BID %s VID %04X PID %04X HID++ %s %s', bus_id, vid, pid, device['hidpp_short'], device['hidpp_long'] - ) + logger.info( + "Found device BID %s VID %04X PID %04X HID++ SHORT %s LONG %s", + bus_id, + vid, + pid, + device["hidpp_short"], + device["hidpp_long"], + ) - if not device['hidpp_short'] and not device['hidpp_long']: + if not device["hidpp_short"] and not device["hidpp_long"]: return None - filter = filterfn(bus_id, vid, pid, device['hidpp_short'], device['hidpp_long']) - if not filter: + filtered_result = filter_func(bus_id, vid, pid, device["hidpp_short"], device["hidpp_long"]) + if not filtered_result: return - isDevice = filter.get('isDevice') + is_device = filtered_result.get("isDevice") - if action == 'add': + if action == ACTION_ADD: d_info = DeviceInfo( - path=device['path'].decode(), + path=device["path"].decode(), bus_id=bus_id, - vendor_id=f'{vid:04X}', - product_id=f'{pid:04X}', + vendor_id=f"{vid:04X}", # noqa + product_id=f"{pid:04X}", # noqa interface=None, driver=None, - manufacturer=device['manufacturer_string'], - product=device['product_string'], - serial=device['serial_number'], - release=device['release_number'], - isDevice=isDevice, - hidpp_short=device['hidpp_short'], - hidpp_long=device['hidpp_long'], + manufacturer=device["manufacturer_string"], + product=device["product_string"], + serial=device["serial_number"], + release=device["release_number"], + isDevice=is_device, + hidpp_short=device["hidpp_short"], + hidpp_long=device["hidpp_long"], ) return d_info - elif action == 'remove': + elif action == ACTION_REMOVE: d_info = DeviceInfo( - path=device['path'].decode(), + path=device["path"].decode(), bus_id=None, - vendor_id=f'{vid:04X}', - product_id=f'{pid:04X}', + vendor_id=f"{vid:04X}", # noqa + product_id=f"{pid:04X}", # noqa interface=None, driver=None, manufacturer=None, product=None, serial=None, release=None, - isDevice=isDevice, + isDevice=is_device, hidpp_short=None, hidpp_long=None, ) return d_info + logger.info(f"Finished checking HIDPP support for device {device['path']} ({bus_id}/{vid:04X}/{pid:04X})") -def find_paired_node(receiver_path, index, timeout): + +def find_paired_node(receiver_path: str, index: int, timeout: int): """Find the node of a device paired with a receiver""" return None -def find_paired_node_wpid(receiver_path, index): +def find_paired_node_wpid(receiver_path: str, index: int): """Find the node of a device paired with a receiver, get wpid from udev""" return None -def monitor_glib(callback, filterfn): - from gi.repository import GLib +def monitor_glib( + glib: GLib, + callback: Callable, + filter_func: Callable[[int, int, int, bool, bool], dict[str, Any]], +) -> None: + """Monitor GLib. - def device_callback(action, device): - # print(f"device_callback({action}): {device}") - if action == 'add': - d_info = _match(action, device, filterfn) + Parameters + ---------- + glib + GLib instance. + callback + Called when device found. + filter_func + Filter devices callback. + """ + + def device_callback(action: str, device): + if action == ACTION_ADD: + d_info = _match(action, device, filter_func) if d_info: - GLib.idle_add(callback, action, d_info) - elif action == 'remove': + glib.idle_add(callback, action, d_info) + elif action == ACTION_REMOVE: # Removed devices will be detected by Solaar directly pass @@ -340,7 +387,7 @@ def monitor_glib(callback, filterfn): monitor.start() -def enumerate(filterfn): +def enumerate(filter_func) -> DeviceInfo: """Enumerate the HID Devices. List all the HID devices attached to the system, optionally filtering by @@ -349,7 +396,7 @@ def enumerate(filterfn): :returns: a list of matching ``DeviceInfo`` tuples. """ for device in _enumerate_devices(): - d_info = _match('add', device, filterfn) + d_info = _match(ACTION_ADD, device, filter_func) if d_info: yield d_info @@ -370,7 +417,7 @@ def open(vendor_id, product_id, serial=None): return device_handle -def open_path(device_path): +def open_path(device_path: str) -> int: """Open a HID device by its path name. :param device_path: the path of a ``DeviceInfo`` tuple returned by enumerate(). @@ -386,7 +433,7 @@ def open_path(device_path): return device_handle -def close(device_handle): +def close(device_handle) -> None: """Close a HID device. :param device_handle: a device handle returned by open() or open_path(). @@ -395,7 +442,7 @@ def close(device_handle): _hidapi.hid_close(device_handle) -def write(device_handle, data): +def write(device_handle: int, data: bytes) -> int: """Write an Output report to a HID device. :param device_handle: a device handle returned by open() or open_path(). @@ -452,15 +499,14 @@ def read(device_handle, bytes_count, timeout_ms=None): if bytes_read < 0: raise HIDError(_hidapi.hid_error(device_handle)) - return None return data.raw[:bytes_read] -def get_input_report(device_handle, report_id, size): +def _get_input_report(device_handle, report_id, size): assert device_handle data = ctypes.create_string_buffer(size) - data[0] = bytearray((report_id, )) + data[0] = bytearray((report_id,)) size = _hidapi.hid_get_input_report(device_handle, data, size) if size < 0: raise HIDError(_hidapi.hid_error(device_handle)) @@ -472,7 +518,7 @@ def _readstring(device_handle, func, max_length=255): buf = ctypes.create_unicode_buffer(max_length) ret = func(device_handle, buf, max_length) if ret < 0: - raise HIDError('Error reading device property') + raise HIDError("Error reading device property") return buf.value diff --git a/lib/hidapi/hidconsole.py b/lib/hidapi/hidconsole.py index bc7bf4c0..c39f760a 100644 --- a/lib/hidapi/hidconsole.py +++ b/lib/hidapi/hidconsole.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -16,47 +14,46 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import argparse import os +import os.path +import platform +import readline import sys import time -from binascii import hexlify, unhexlify -from select import select as _select +from binascii import hexlify +from binascii import unhexlify +from select import select from threading import Lock +from threading import Thread -import hidapi as _hid +if platform.system() == "Linux": + import hidapi.udev_impl as hidapi +else: + import hidapi.hidapi_impl as hidapi -# -# -# - -try: - read_packet = raw_input -except NameError: - # Python 3 equivalent of raw_input - read_packet = input +LOGITECH_VENDOR_ID = 0x046D interactive = os.isatty(0) -prompt = '?? Input: ' if interactive else '' +prompt = "?? Input: " if interactive else "" start_time = time.time() -strhex = lambda d: hexlify(d).decode('ascii').upper() -# -# -# +def strhex(d): + return hexlify(d).decode("ascii").upper() + print_lock = Lock() -del Lock def _print(marker, data, scroll=False): t = time.time() - start_time if isinstance(data, str): - s = marker + ' ' + data + s = f"{marker} {data}" else: hexs = strhex(data) - s = '%s (% 8.3f) [%s %s %s %s] %s' % (marker, t, hexs[0:2], hexs[2:4], hexs[4:8], hexs[8:], repr(data)) + s = "%s (% 8.3f) [%s %s %s %s] %s" % (marker, t, hexs[0:2], hexs[2:4], hexs[4:8], hexs[8:], repr(data)) with print_lock: # allow only one thread at a time to write to the console, otherwise @@ -65,18 +62,18 @@ def _print(marker, data, scroll=False): if interactive and scroll: # scroll the entire screen above the current line up by 1 line sys.stdout.write( - '\033[s' # save cursor position - '\033[S' # scroll up - '\033[A' # cursor up - '\033[L' # insert 1 line - '\033[G' + "\033[s" # save cursor position + "\033[S" # scroll up + "\033[A" # cursor up + "\033[L" # insert 1 line + "\033[G" ) # move cursor to column 1 sys.stdout.write(s) if interactive and scroll: # restore cursor position - sys.stdout.write('\033[u') + sys.stdout.write("\033[u") else: - sys.stdout.write('\n') + sys.stdout.write("\n") # flush stdout manually... # because trying to open stdin/out unbuffered programmatically @@ -85,107 +82,103 @@ def _print(marker, data, scroll=False): def _error(text, scroll=False): - _print('!!', text, scroll) + _print("!!", text, scroll) def _continuous_read(handle, timeout=2000): while True: try: - reply = _hid.read(handle, 128, timeout) + reply = hidapi.read(handle, 128, timeout) except OSError as e: - _error('Read failed, aborting: ' + str(e), True) + _error(f"Read failed, aborting: {str(e)}", True) break assert reply is not None if reply: - _print('>>', reply, True) + _print(">>", reply, True) def _validate_input(line, hidpp=False): try: - data = unhexlify(line.encode('ascii')) + data = unhexlify(line.encode("ascii")) except Exception as e: - _error('Invalid input: ' + str(e)) + _error(f"Invalid input: {str(e)}") return None if hidpp: if len(data) < 4: - _error('Invalid HID++ request: need at least 4 bytes') + _error("Invalid HID++ request: need at least 4 bytes") return None - if data[:1] not in b'\x10\x11': - _error('Invalid HID++ request: first byte must be 0x10 or 0x11') + if data[:1] not in b"\x10\x11": + _error("Invalid HID++ request: first byte must be 0x10 or 0x11") return None - if data[1:2] not in b'\xFF\x00\x01\x02\x03\x04\x05\x06\x07': - _error('Invalid HID++ request: second byte must be 0xFF or one of 0x00..0x07') + if data[1:2] not in b"\xff\x00\x01\x02\x03\x04\x05\x06\x07": + _error("Invalid HID++ request: second byte must be 0xFF or one of 0x00..0x07") return None - if data[:1] == b'\x10': + if data[:1] == b"\x10": if len(data) > 7: - _error('Invalid HID++ request: maximum length of a 0x10 request is 7 bytes') + _error("Invalid HID++ request: maximum length of a 0x10 request is 7 bytes") return None while len(data) < 7: - data = (data + b'\x00' * 7)[:7] - elif data[:1] == b'\x11': + data = (data + b"\x00" * 7)[:7] + elif data[:1] == b"\x11": if len(data) > 20: - _error('Invalid HID++ request: maximum length of a 0x11 request is 20 bytes') + _error("Invalid HID++ request: maximum length of a 0x11 request is 20 bytes") return None while len(data) < 20: - data = (data + b'\x00' * 20)[:20] + data = (data + b"\x00" * 20)[:20] return data def _open(args): - def matchfn(bid, vid, pid, _a, _b): - if vid == 0x046d: - return {'vid': 0x046d} + if vid == LOGITECH_VENDOR_ID: + return {"vid": vid} - device = args.device - if args.hidpp and not device: - for d in _hid.enumerate(matchfn): - if d.driver == 'logitech-djreceiver': + device = args.path + d = None + if not device: + for d in hidapi.enumerate(matchfn): + if (d.hidpp_short or d.hidpp_long) and (args.id is None or args.id.lower() == d.product_id.lower()): device = d.path break if not device: - sys.exit('!! No HID++ receiver found.') + sys.exit("!! No HID++ receiver found.") if not device: - sys.exit('!! Device path required.') + sys.exit("!! Device path required.") - print('.. Opening device', device) - handle = _hid.open_path(device) + handle = hidapi.open_path(device) if not handle: - sys.exit('!! Failed to open %s, aborting.' % device) + sys.exit(f"!! Failed to open {device}, aborting.") print( - '.. Opened handle %r, vendor %r product %r serial %r.' % - (handle, _hid.get_manufacturer(handle), _hid.get_product(handle), _hid.get_serial(handle)) + ".. Opened device %r, vendor %r product %r serial %r." + % ( + device, + hidapi.get_manufacturer(handle) or d.vendor_id if d else None, + hidapi.get_product(handle) or d.product_id if d else None, + hidapi.get_serial(handle), + ) ) if args.hidpp: - if _hid.get_manufacturer(handle) is not None and _hid.get_manufacturer(handle) != b'Logitech': - sys.exit('!! Only Logitech devices support the HID++ protocol.') - print('.. HID++ validation enabled.') + if hidapi.get_manufacturer(handle) is not None and hidapi.get_manufacturer(handle) != b"Logitech": + sys.exit("!! Only Logitech devices support the HID++ protocol.") + print(".. HID++ validation enabled.") else: - if (_hid.get_manufacturer(handle) == b'Logitech' and b'Receiver' in _hid.get_product(handle)): + if hidapi.get_manufacturer(handle) == b"Logitech" and b"Receiver" in hidapi.get_product(handle): args.hidpp = True - print('.. Logitech receiver detected, HID++ validation enabled.') + print(".. Logitech receiver detected, HID++ validation enabled.") return handle -# -# -# - - def _parse_arguments(): - import argparse arg_parser = argparse.ArgumentParser() - arg_parser.add_argument('--history', help='history file (default ~/.hidconsole-history)') - arg_parser.add_argument('--hidpp', action='store_true', help='ensure input data is a valid HID++ request') - arg_parser.add_argument( - 'device', - nargs='?', - help='linux device to connect to (/dev/hidrawX); ' - 'may be omitted if --hidpp is given, in which case it looks for the first Logitech receiver' - ) + arg_parser.add_argument("--history", help="history file (default ~/.hidconsole-history)") + arg_parser.add_argument("--hidpp", action="store_true", help="ensure input data is a valid HID++ request") + arg_parser.add_argument("command", nargs="?", help="command to send (otherwise get commands from input)") + group = arg_parser.add_mutually_exclusive_group() + group.add_argument("-p", "--path", help="HID raw device to connect to (/dev/hidrawX); ") + group.add_argument("-i", "--id", help="product ID of device to connect to (XXXX)") return arg_parser.parse_args() @@ -193,13 +186,22 @@ def main(): args = _parse_arguments() handle = _open(args) - if interactive: - print('.. Press ^C/^D to exit, or type hex bytes to write to the device.') + if args.command: # send a command + data = _validate_input(args.command, args.hidpp) + if data: + hidapi.write(handle, data) + reply = hidapi.read(handle, 128, 1) + if reply: + hexs = strhex(reply) + s = "[%s %s %s %s] %s" % (hexs[0:2], hexs[2:4], hexs[4:8], hexs[8:], repr(data)) + print(s) + exit() + + if interactive: + print(".. Press ^C/^D to exit, or type hex bytes to write to the device.") - import readline if args.history is None: - import os.path - args.history = os.path.join(os.path.expanduser('~'), '.hidconsole-history') + args.history = os.path.join(os.path.expanduser("~"), ".hidconsole-history") try: readline.read_history_file(args.history) except Exception: @@ -207,18 +209,17 @@ def main(): pass try: - from threading import Thread - t = Thread(target=_continuous_read, args=(handle, )) + t = Thread(target=_continuous_read, args=(handle,)) t.daemon = True t.start() if interactive: # move the cursor at the bottom of the screen - sys.stdout.write('\033[300B') # move cusor at most 300 lines down, don't scroll + sys.stdout.write("\033[300B") # move cusor at most 300 lines down, don't scroll while t.is_alive(): - line = read_packet(prompt) - line = line.strip().replace(' ', '') + line = input(prompt) + line = line.strip().replace(" ", "") # print ("line", line) if not line: continue @@ -227,12 +228,12 @@ def main(): if data is None: continue - _print('<<', data) - _hid.write(handle, data) + _print("<<", data) + hidapi.write(handle, data) # wait for some kind of reply if args.hidpp and not interactive: - rlist, wlist, xlist = _select([handle], [], [], 1) - if data[1:2] == b'\xFF': + rlist, wlist, xlist = select([handle], [], [], 1) + if data[1:2] == b"\xff": # the receiver will reply very fast, in a few milliseconds time.sleep(0.010) else: @@ -240,16 +241,15 @@ def main(): time.sleep(0.700) except EOFError: if interactive: - print('') + print("") else: time.sleep(1) finally: - print('.. Closing handle %r' % handle) - _hid.close(handle) + hidapi.close(handle) if interactive: readline.write_history_file(args.history) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/lib/hidapi/udev.py b/lib/hidapi/udev_impl.py similarity index 58% rename from lib/hidapi/udev.py rename to lib/hidapi/udev_impl.py index d94de66c..4b5a6168 100644 --- a/lib/hidapi/udev.py +++ b/lib/hidapi/udev_impl.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -15,6 +13,7 @@ ## You should have received a copy of the GNU General Public License along ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + """Generic Human Interface Device API. It is currently a partial pure-Python implementation of the native HID API @@ -24,47 +23,37 @@ The docstrings are mostly copied from the hidapi API header, with changes where necessary. """ -import errno as _errno -import os as _os -import warnings as _warnings +from __future__ import annotations + +import errno +import logging +import os +import typing +import warnings + # the tuple object we'll expose when enumerating devices -from collections import namedtuple -from logging import INFO as _INFO -from logging import getLogger -from select import select as _select +from select import select from time import sleep -from time import time as _timestamp +from time import time +from typing import Callable -from pyudev import Context as _Context -from pyudev import Device as _Device -from pyudev import DeviceNotFoundError -from pyudev import Devices as _Devices -from pyudev import Monitor as _Monitor +import pyudev + +from hidapi.common import DeviceInfo + +if typing.TYPE_CHECKING: + import gi + + gi.require_version("Gdk", "3.0") + from gi.repository import GLib # NOQA: E402 + +logger = logging.getLogger(__name__) -_log = getLogger(__name__) -del getLogger -native_implementation = 'udev' fileopen = open -DeviceInfo = namedtuple( - 'DeviceInfo', [ - 'path', - 'bus_id', - 'vendor_id', - 'product_id', - 'interface', - 'driver', - 'manufacturer', - 'product', - 'serial', - 'release', - 'isDevice', - 'hidpp_short', - 'hidpp_long', - ] -) -del namedtuple +ACTION_ADD = "add" +ACTION_REMOVE = "remove" # # exposed API @@ -90,69 +79,82 @@ def exit(): return True -# The filterfn is used to determine whether this is a device of interest to Solaar. -# It is given the bus id, vendor id, and product id and returns a dictionary -# with the required hid_driver and usb_interface and whether this is a receiver or device. -def _match(action, device, filterfn): - hid_device = device.find_parent('hid') - if not hid_device: # only HID devices are of interest to Solaar +def _match(action: str, device, filter_func: typing.Callable[[int, int, int, bool, bool], dict[str, typing.Any]]): + """ + + The filter_func is used to determine whether this is a device of + interest to Solaar. It is given the bus id, vendor id, and product + id and returns a dictionary with the required hid_driver and + usb_interface and whether this is a receiver or device.""" + logger.debug(f"Dbus event {action} {device}") + hid_device = device.find_parent("hid") + if hid_device is None: # only HID devices are of interest to Solaar return - hid_id = hid_device.get('HID_ID') + hid_id = hid_device.properties.get("HID_ID") if not hid_id: return # there are reports that sometimes the id isn't set up right so be defensive - bid, vid, pid = hid_id.split(':') - hid_hid_device = hid_device.find_parent('hid') - if hid_hid_device: + bid, vid, pid = hid_id.split(":") + hid_hid_device = hid_device.find_parent("hid") + if hid_hid_device is not None: return # these are devices connected through a receiver so don't pick them up here try: # if report descriptor does not indicate HID++ capabilities then this device is not of interest to Solaar - from hid_parser import ReportDescriptor as _ReportDescriptor + from hid_parser import ReportDescriptor - # from hid_parser import Usage as _Usage - hidpp_short = hidpp_long = False - devfile = '/sys' + hid_device.get('DEVPATH') + '/report_descriptor' - with fileopen(devfile, 'rb') as fd: - with _warnings.catch_warnings(): - _warnings.simplefilter('ignore') - rd = _ReportDescriptor(fd.read()) + hidpp_short = hidpp_long = centurion = False + devfile = "/sys" + hid_device.properties.get("DEVPATH") + "/report_descriptor" + with fileopen(devfile, "rb") as fd: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + rd = ReportDescriptor(fd.read()) hidpp_short = 0x10 in rd.input_report_ids and 6 * 8 == int(rd.get_input_report_size(0x10)) # and _Usage(0xFF00, 0x0001) in rd.get_input_items(0x10)[0].usages # be more permissive hidpp_long = 0x11 in rd.input_report_ids and 19 * 8 == int(rd.get_input_report_size(0x11)) # and _Usage(0xFF00, 0x0002) in rd.get_input_items(0x11)[0].usages # be more permissive - if not hidpp_short and not hidpp_long: + # Centurion transport: report ID 0x51, 63-byte reports (usage page 0xFFA0) + centurion = ( + 0x51 in rd.input_report_ids and 63 * 8 == int(rd.get_input_report_size(0x51)) and 0x51 in rd.output_report_ids + ) + if not hidpp_short and not hidpp_long and not centurion: return except Exception as e: # if can't process report descriptor fall back to old scheme - hidpp_short = hidpp_long = None - _log.warn('Report Descriptor not processed for BID %s VID %s PID %s: %s', bid, vid, pid, e) + hidpp_short = None + hidpp_long = None + centurion = False + logger.info( + "Report Descriptor not processed for DEVICE %s BID %s VID %s PID %s: %s", + device.device_node, + bid, + vid, + pid, + e, + ) - filter = filterfn(int(bid, 16), int(vid, 16), int(pid, 16), hidpp_short, hidpp_long) - if not filter: + filtered_result = filter_func(int(bid, 16), int(vid, 16), int(pid, 16), hidpp_short or centurion, hidpp_long or centurion) + if not filtered_result: return - hid_driver = filter.get('hid_driver') - interface_number = filter.get('usb_interface') - isDevice = filter.get('isDevice') + interface_number = filtered_result.get("usb_interface") + isDevice = filtered_result.get("isDevice") - if action == 'add': - hid_driver_name = hid_device.get('DRIVER') - # print ("** found hid", action, device, "hid:", hid_device, hid_driver_name) - if hid_driver: - if isinstance(hid_driver, tuple): - if hid_driver_name not in hid_driver: - return - elif hid_driver_name != hid_driver: - return - - intf_device = device.find_parent('usb', 'usb_interface') - usb_interface = None if intf_device is None else intf_device.attributes.asint('bInterfaceNumber') + if action == ACTION_ADD: + hid_driver_name = hid_device.properties.get("DRIVER") + intf_device = device.find_parent("usb", "usb_interface") + usb_interface = None if intf_device is None else intf_device.attributes.asint("bInterfaceNumber") # print('*** usb interface', action, device, 'usb_interface:', intf_device, usb_interface, interface_number) - if _log.isEnabledFor(_INFO): - _log.info( - 'Found device BID %s VID %s PID %s HID++ %s %s USB %s %s', bid, vid, pid, hidpp_short, hidpp_long, - usb_interface, interface_number - ) + logger.info( + "Found device %s BID %s VID %s PID %s HID++ %s %s USB %s %s", + device.device_node, + bid, + vid, + pid, + hidpp_short, + hidpp_long, + usb_interface, + interface_number, + ) if not (hidpp_short or hidpp_long or interface_number is None or interface_number == usb_interface): return - attrs = intf_device.attributes if intf_device else None + attrs = intf_device.attributes if intf_device is not None else None d_info = DeviceInfo( path=device.device_node, @@ -161,19 +163,18 @@ def _match(action, device, filterfn): product_id=pid[-4:], interface=usb_interface, driver=hid_driver_name, - manufacturer=attrs.get('manufacturer') if attrs else None, - product=attrs.get('product') if attrs else None, - serial=hid_device.get('HID_UNIQ'), - release=attrs.get('bcdDevice') if attrs else None, + manufacturer=attrs.get("manufacturer") if attrs else None, + product=attrs.get("product") if attrs else None, + serial=hid_device.properties.get("HID_UNIQ"), + release=attrs.get("bcdDevice") if attrs else None, isDevice=isDevice, hidpp_short=hidpp_short, hidpp_long=hidpp_long, + centurion=centurion if centurion else False, ) return d_info - elif action == 'remove': - # print (dict(device), dict(usb_device)) - + elif action == ACTION_REMOVE: d_info = DeviceInfo( path=device.device_node, bus_id=None, @@ -192,41 +193,41 @@ def _match(action, device, filterfn): return d_info -def find_paired_node(receiver_path, index, timeout): +def find_paired_node(receiver_path: str, index: int, timeout: int): """Find the node of a device paired with a receiver""" - context = _Context() - receiver_phys = _Devices.from_device_file(context, receiver_path).find_parent('hid').get('HID_PHYS') + context = pyudev.Context() + receiver_phys = pyudev.Devices.from_device_file(context, receiver_path).find_parent("hid").get("HID_PHYS") if not receiver_phys: return None - phys = f'{receiver_phys}:{index}' - timeout += _timestamp() - delta = _timestamp() + phys = f"{receiver_phys}:{index}" # noqa: E231 + timeout += time() + delta = time() while delta < timeout: - for dev in context.list_devices(subsystem='hidraw'): - dev_phys = dev.find_parent('hid').get('HID_PHYS') + for dev in context.list_devices(subsystem="hidraw"): + dev_phys = dev.find_parent("hid").get("HID_PHYS") if dev_phys and dev_phys == phys: return dev.device_node - delta = _timestamp() + delta = time() return None -def find_paired_node_wpid(receiver_path, index): +def find_paired_node_wpid(receiver_path: str, index: int): """Find the node of a device paired with a receiver, get wpid from udev""" - context = _Context() - receiver_phys = _Devices.from_device_file(context, receiver_path).find_parent('hid').get('HID_PHYS') + context = pyudev.Context() + receiver_phys = pyudev.Devices.from_device_file(context, receiver_path).find_parent("hid").get("HID_PHYS") if not receiver_phys: return None - phys = f'{receiver_phys}:{index}' - for dev in context.list_devices(subsystem='hidraw'): - dev_phys = dev.find_parent('hid').get('HID_PHYS') + phys = f"{receiver_phys}:{index}" # noqa: E231 + for dev in context.list_devices(subsystem="hidraw"): + dev_phys = dev.find_parent("hid").get("HID_PHYS") if dev_phys and dev_phys == phys: # get hid id like 0003:0000046D:00000065 - hid_id = dev.find_parent('hid').get('HID_ID') + hid_id = dev.find_parent("hid").get("HID_ID") # get wpid - last 4 symbols udev_wpid = hid_id[-4:] return udev_wpid @@ -234,55 +235,48 @@ def find_paired_node_wpid(receiver_path, index): return None -def monitor_glib(callback, filterfn): - from gi.repository import GLib +def monitor_glib(glib: GLib, callback: Callable, filter_func: Callable): + """Monitor GLib. - c = _Context() + Parameters + ---------- + glib + GLib instance. + """ + c = pyudev.Context() + m = pyudev.Monitor.from_netlink(c) + m.filter_by(subsystem="hidraw") - # already existing devices - # for device in c.list_devices(subsystem='hidraw'): - # # print (device, dict(device), dict(device.attributes)) - # for filter in device_filters: - # d_info = _match('add', device, *filter) - # if d_info: - # GLib.idle_add(callback, 'add', d_info) - # break - - m = _Monitor.from_netlink(c) - m.filter_by(subsystem='hidraw') - - def _process_udev_event(monitor, condition, cb, filterfn): - if condition == GLib.IO_IN: + def _process_udev_event(monitor, condition, cb, filter_func): + if condition == glib.IO_IN: event = monitor.receive_device() if event: action, device = event # print ("***", action, device) - if action == 'add': - d_info = _match(action, device, filterfn) + if action == ACTION_ADD: + d_info = _match(action, device, filter_func) if d_info: - GLib.idle_add(cb, action, d_info) - elif action == 'remove': + glib.idle_add(cb, action, d_info) + elif action == ACTION_REMOVE: # the GLib notification does _not_ match! pass return True try: # io_add_watch_full may not be available... - GLib.io_add_watch_full(m, GLib.PRIORITY_LOW, GLib.IO_IN, _process_udev_event, callback, filterfn) - # print ("did io_add_watch_full") + glib.io_add_watch_full(m, glib.PRIORITY_LOW, glib.IO_IN, _process_udev_event, callback, filter_func) except AttributeError: try: # and the priority parameter appeared later in the API - GLib.io_add_watch(m, GLib.PRIORITY_LOW, GLib.IO_IN, _process_udev_event, callback, filterfn) - # print ("did io_add_watch with priority") + glib.io_add_watch(m, glib.PRIORITY_LOW, glib.IO_IN, _process_udev_event, callback, filter_func) except Exception: - GLib.io_add_watch(m, GLib.IO_IN, _process_udev_event, callback, filterfn) - # print ("did io_add_watch") + glib.io_add_watch(m, glib.IO_IN, _process_udev_event, callback, filter_func) + logger.debug("Starting dbus monitoring") m.start() -def enumerate(filterfn): +def enumerate(filter_func: typing.Callable[[int, int, int, bool, bool], dict[str, typing.Any]]): """Enumerate the HID Devices. List all the HID devices attached to the system, optionally filtering by @@ -291,8 +285,9 @@ def enumerate(filterfn): :returns: a list of matching ``DeviceInfo`` tuples. """ - for dev in _Context().list_devices(subsystem='hidraw'): - dev_info = _match('add', dev, filterfn) + logger.debug("Starting dbus enumeration") + for dev in pyudev.Context().list_devices(subsystem="hidraw"): + dev_info = _match(ACTION_ADD, dev, filter_func) if dev_info: yield dev_info @@ -321,29 +316,29 @@ def open_path(device_path): :returns: an opaque device handle, or ``None``. """ assert device_path - assert device_path.startswith('/dev/hidraw') + assert device_path.startswith("/dev/hidraw") - _log.info('OPEN PATH %s', device_path) + logger.info("OPEN PATH %s", device_path) retrycount = 0 - while (retrycount < 3): + while retrycount < 3: retrycount += 1 try: - return _os.open(device_path, _os.O_RDWR | _os.O_SYNC) + return os.open(device_path, os.O_RDWR | os.O_SYNC) except OSError as e: - _log.info('OPEN PATH FAILED %s ERROR %s %s', device_path, e.errno, e) - if e.errno == _errno.EACCES: + logger.info("OPEN PATH FAILED %s ERROR %s %s", device_path, e.errno, e) + if e.errno == errno.EACCES: sleep(0.1) else: - raise + raise e -def close(device_handle): +def close(device_handle) -> None: """Close a HID device. :param device_handle: a device handle returned by open() or open_path(). """ assert device_handle - _os.close(device_handle) + os.close(device_handle) def write(device_handle, data): @@ -372,17 +367,17 @@ def write(device_handle, data): assert isinstance(data, bytes), (repr(data), type(data)) retrycount = 0 bytes_written = 0 - while (retrycount < 3): + while retrycount < 3: try: retrycount += 1 - bytes_written = _os.write(device_handle, data) + bytes_written = os.write(device_handle, data) except OSError as e: - if e.errno == _errno.EPIPE: + if e.errno == errno.EPIPE: sleep(0.1) else: break if bytes_written != len(data): - raise OSError(_errno.EIO, 'written %d bytes out of expected %d' % (bytes_written, len(data))) + raise OSError(errno.EIO, f"written {int(bytes_written)} bytes out of expected {len(data)}") def read(device_handle, bytes_count, timeout_ms=-1): @@ -403,26 +398,28 @@ def read(device_handle, bytes_count, timeout_ms=-1): """ assert device_handle timeout = None if timeout_ms < 0 else timeout_ms / 1000.0 - rlist, wlist, xlist = _select([device_handle], [], [device_handle], timeout) + rlist, wlist, xlist = select([device_handle], [], [device_handle], timeout) if xlist: assert xlist == [device_handle] - raise OSError(_errno.EIO, 'exception on file descriptor %d' % device_handle) + raise OSError(errno.EIO, f"exception on file descriptor {int(device_handle)}") if rlist: assert rlist == [device_handle] - data = _os.read(device_handle, bytes_count) + data = os.read(device_handle, bytes_count) assert data is not None assert isinstance(data, bytes), (repr(data), type(data)) + if not data: # empty read when select() said readable means EOF (device removed) + raise OSError(errno.EIO, f"device disconnected on file descriptor {int(device_handle)}") return data else: - return b'' + return b"" _DEVICE_STRINGS = { - 0: 'manufacturer', - 1: 'product', - 2: 'serial', + 0: "manufacturer", + 1: "product", + 2: "serial", } @@ -467,22 +464,22 @@ def get_indexed_string(device_handle, index): return None assert device_handle - stat = _os.fstat(device_handle) + stat = os.fstat(device_handle) try: - dev = _Device.from_device_number(_Context(), 'char', stat.st_rdev) - except (DeviceNotFoundError, ValueError): + dev = pyudev.Devices.from_device_number(pyudev.Context(), "char", stat.st_rdev) + except (pyudev.DeviceNotFoundError, ValueError): return None - hid_dev = dev.find_parent('hid') + hid_dev = dev.find_parent("hid") if hid_dev: - assert 'HID_ID' in hid_dev - bus, _ignore, _ignore = hid_dev['HID_ID'].split(':') + assert "HID_ID" in hid_dev + bus, _ignore, _ignore = hid_dev["HID_ID"].split(":") - if bus == '0003': # USB - usb_dev = dev.find_parent('usb', 'usb_device') + if bus == "0003": # USB + usb_dev = dev.find_parent("usb", "usb_device") assert usb_dev return usb_dev.attributes.get(key) - elif bus == '0005': # BLUETOOTH + elif bus == "0005": # BLUETOOTH # TODO pass diff --git a/lib/keysyms/generate.py b/lib/keysyms/generate.py index 5ad68298..f913036a 100755 --- a/lib/keysyms/generate.py +++ b/lib/keysyms/generate.py @@ -1,43 +1,39 @@ #!/usr/bin/env python3 +"""Extract key symbol encodings from X11 header files.""" + from pathlib import Path from pprint import pprint from re import findall from subprocess import run from tempfile import TemporaryDirectory -repo = 'https://github.com/freedesktop/xorg-proto-x11proto.git' -xx = 'https://gitlab.freedesktop.org/xorg/proto/xorgproto/-/tree/master/include/X11/' -repo = 'https://gitlab.freedesktop.org/xorg/proto/xorgproto.git' -pattern = r'#define XK_(\w+)\s+0x(\w+)(?:\s+/\*\s+U\+(\w+))?' -xf86pattern = r'#define XF86XK_(\w+)\s+0x(\w+)(?:\s+/\*\s+U\+(\w+))?' +repo = "https://gitlab.freedesktop.org/xorg/proto/xorgproto.git" +pattern = r"#define XK_(\w+)\s+0x(\w+)(?:\s+/\*\s+U\+(\w+))?" +xf86pattern = r"#define XF86XK_(\w+)\s+0x(\w+)(?:\s+/\*\s+U\+(\w+))?" def main(): keysymdef = {} + keysym_files = [ + ("include/X11/keysymdef.h", pattern, ""), + ("include/X11/XF86keysym.h", xf86pattern, "XF86_"), + ] with TemporaryDirectory() as temp: - run(['git', 'clone', repo, '.'], cwd=temp) - # text = Path(temp, 'keysymdef.h').read_text() - text = Path(temp, 'include/X11/keysymdef.h').read_text() - for name, sym, uni in findall(pattern, text): - sym = int(sym, 16) - uni = int(uni, 16) if uni else None - if keysymdef.get(name, None): - print('KEY DUP', name) - keysymdef[name] = sym - # text = Path(temp, 'keysymdef.h').read_text() - text = Path(temp, 'include/X11/XF86keysym.h').read_text() - for name, sym, uni in findall(xf86pattern, text): - sym = int(sym, 16) - uni = int(uni, 16) if uni else None - if keysymdef.get('XF86_' + name, None): - print('KEY DUP', 'XF86_' + name) - keysymdef['XF86_' + name] = sym + run(["git", "clone", repo, "."], cwd=temp) - with open('keysymdef.py', 'w') as f: - f.write('# flake8: noqa\nkeysymdef = \\\n') + for filename, extraction_pattern, prefix in keysym_files: + text = Path(temp, filename).read_text() + for name, sym, _ in findall(extraction_pattern, text): + sym = int(sym, 16) + if keysymdef.get(f"{prefix}{name}", None): + print(f"KEY DUP {prefix}{name}") + keysymdef[f"{prefix}{name}"] = sym + + with open("keysymdef.py", "w") as f: + f.write("# flake8: noqa\nkey_symbols = \\\n") pprint(keysymdef, f) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/lib/keysyms/keysymdef.py b/lib/keysyms/keysymdef.py index c3ff1771..1d377d60 100644 --- a/lib/keysyms/keysymdef.py +++ b/lib/keysyms/keysymdef.py @@ -1,2293 +1,2294 @@ # flake8: noqa -keysymdef = \ -{'0': 48, - '1': 49, - '2': 50, - '3': 51, - '3270_AltCursor': 64784, - '3270_Attn': 64782, - '3270_BackTab': 64773, - '3270_ChangeScreen': 64793, - '3270_Copy': 64789, - '3270_CursorBlink': 64783, - '3270_CursorSelect': 64796, - '3270_DeleteWord': 64794, - '3270_Duplicate': 64769, - '3270_Enter': 64798, - '3270_EraseEOF': 64774, - '3270_EraseInput': 64775, - '3270_ExSelect': 64795, - '3270_FieldMark': 64770, - '3270_Ident': 64787, - '3270_Jump': 64786, - '3270_KeyClick': 64785, - '3270_Left2': 64772, - '3270_PA1': 64778, - '3270_PA2': 64779, - '3270_PA3': 64780, - '3270_Play': 64790, - '3270_PrintScreen': 64797, - '3270_Quit': 64777, - '3270_Record': 64792, - '3270_Reset': 64776, - '3270_Right2': 64771, - '3270_Rule': 64788, - '3270_Setup': 64791, - '3270_Test': 64781, - '4': 52, - '5': 53, - '6': 54, - '7': 55, - '8': 56, - '9': 57, - 'A': 65, - 'AE': 198, - 'Aacute': 193, - 'Abelowdot': 16785056, - 'Abreve': 451, - 'Abreveacute': 16785070, - 'Abrevebelowdot': 16785078, - 'Abrevegrave': 16785072, - 'Abrevehook': 16785074, - 'Abrevetilde': 16785076, - 'AccessX_Enable': 65136, - 'AccessX_Feedback_Enable': 65137, - 'Acircumflex': 194, - 'Acircumflexacute': 16785060, - 'Acircumflexbelowdot': 16785068, - 'Acircumflexgrave': 16785062, - 'Acircumflexhook': 16785064, - 'Acircumflextilde': 16785066, - 'Adiaeresis': 196, - 'Agrave': 192, - 'Ahook': 16785058, - 'Alt_L': 65513, - 'Alt_R': 65514, - 'Amacron': 960, - 'Aogonek': 417, - 'Arabic_0': 16778848, - 'Arabic_1': 16778849, - 'Arabic_2': 16778850, - 'Arabic_3': 16778851, - 'Arabic_4': 16778852, - 'Arabic_5': 16778853, - 'Arabic_6': 16778854, - 'Arabic_7': 16778855, - 'Arabic_8': 16778856, - 'Arabic_9': 16778857, - 'Arabic_ain': 1497, - 'Arabic_alef': 1479, - 'Arabic_alefmaksura': 1513, - 'Arabic_beh': 1480, - 'Arabic_comma': 1452, - 'Arabic_dad': 1494, - 'Arabic_dal': 1487, - 'Arabic_damma': 1519, - 'Arabic_dammatan': 1516, - 'Arabic_ddal': 16778888, - 'Arabic_farsi_yeh': 16778956, - 'Arabic_fatha': 1518, - 'Arabic_fathatan': 1515, - 'Arabic_feh': 1505, - 'Arabic_fullstop': 16778964, - 'Arabic_gaf': 16778927, - 'Arabic_ghain': 1498, - 'Arabic_ha': 1511, - 'Arabic_hah': 1485, - 'Arabic_hamza': 1473, - 'Arabic_hamza_above': 16778836, - 'Arabic_hamza_below': 16778837, - 'Arabic_hamzaonalef': 1475, - 'Arabic_hamzaonwaw': 1476, - 'Arabic_hamzaonyeh': 1478, - 'Arabic_hamzaunderalef': 1477, - 'Arabic_heh': 1511, - 'Arabic_heh_doachashmee': 16778942, - 'Arabic_heh_goal': 16778945, - 'Arabic_jeem': 1484, - 'Arabic_jeh': 16778904, - 'Arabic_kaf': 1507, - 'Arabic_kasra': 1520, - 'Arabic_kasratan': 1517, - 'Arabic_keheh': 16778921, - 'Arabic_khah': 1486, - 'Arabic_lam': 1508, - 'Arabic_madda_above': 16778835, - 'Arabic_maddaonalef': 1474, - 'Arabic_meem': 1509, - 'Arabic_noon': 1510, - 'Arabic_noon_ghunna': 16778938, - 'Arabic_peh': 16778878, - 'Arabic_percent': 16778858, - 'Arabic_qaf': 1506, - 'Arabic_question_mark': 1471, - 'Arabic_ra': 1489, - 'Arabic_rreh': 16778897, - 'Arabic_sad': 1493, - 'Arabic_seen': 1491, - 'Arabic_semicolon': 1467, - 'Arabic_shadda': 1521, - 'Arabic_sheen': 1492, - 'Arabic_sukun': 1522, - 'Arabic_superscript_alef': 16778864, - 'Arabic_switch': 65406, - 'Arabic_tah': 1495, - 'Arabic_tatweel': 1504, - 'Arabic_tcheh': 16778886, - 'Arabic_teh': 1482, - 'Arabic_tehmarbuta': 1481, - 'Arabic_thal': 1488, - 'Arabic_theh': 1483, - 'Arabic_tteh': 16778873, - 'Arabic_veh': 16778916, - 'Arabic_waw': 1512, - 'Arabic_yeh': 1514, - 'Arabic_yeh_baree': 16778962, - 'Arabic_zah': 1496, - 'Arabic_zain': 1490, - 'Aring': 197, - 'Armenian_AT': 16778552, - 'Armenian_AYB': 16778545, - 'Armenian_BEN': 16778546, - 'Armenian_CHA': 16778569, - 'Armenian_DA': 16778548, - 'Armenian_DZA': 16778561, - 'Armenian_E': 16778551, - 'Armenian_FE': 16778582, - 'Armenian_GHAT': 16778562, - 'Armenian_GIM': 16778547, - 'Armenian_HI': 16778565, - 'Armenian_HO': 16778560, - 'Armenian_INI': 16778555, - 'Armenian_JE': 16778571, - 'Armenian_KE': 16778580, - 'Armenian_KEN': 16778559, - 'Armenian_KHE': 16778557, - 'Armenian_LYUN': 16778556, - 'Armenian_MEN': 16778564, - 'Armenian_NU': 16778566, - 'Armenian_O': 16778581, - 'Armenian_PE': 16778570, - 'Armenian_PYUR': 16778579, - 'Armenian_RA': 16778572, - 'Armenian_RE': 16778576, - 'Armenian_SE': 16778573, - 'Armenian_SHA': 16778567, - 'Armenian_TCHE': 16778563, - 'Armenian_TO': 16778553, - 'Armenian_TSA': 16778558, - 'Armenian_TSO': 16778577, - 'Armenian_TYUN': 16778575, - 'Armenian_VEV': 16778574, - 'Armenian_VO': 16778568, - 'Armenian_VYUN': 16778578, - 'Armenian_YECH': 16778549, - 'Armenian_ZA': 16778550, - 'Armenian_ZHE': 16778554, - 'Armenian_accent': 16778587, - 'Armenian_amanak': 16778588, - 'Armenian_apostrophe': 16778586, - 'Armenian_at': 16778600, - 'Armenian_ayb': 16778593, - 'Armenian_ben': 16778594, - 'Armenian_but': 16778589, - 'Armenian_cha': 16778617, - 'Armenian_da': 16778596, - 'Armenian_dza': 16778609, - 'Armenian_e': 16778599, - 'Armenian_exclam': 16778588, - 'Armenian_fe': 16778630, - 'Armenian_full_stop': 16778633, - 'Armenian_ghat': 16778610, - 'Armenian_gim': 16778595, - 'Armenian_hi': 16778613, - 'Armenian_ho': 16778608, - 'Armenian_hyphen': 16778634, - 'Armenian_ini': 16778603, - 'Armenian_je': 16778619, - 'Armenian_ke': 16778628, - 'Armenian_ken': 16778607, - 'Armenian_khe': 16778605, - 'Armenian_ligature_ew': 16778631, - 'Armenian_lyun': 16778604, - 'Armenian_men': 16778612, - 'Armenian_nu': 16778614, - 'Armenian_o': 16778629, - 'Armenian_paruyk': 16778590, - 'Armenian_pe': 16778618, - 'Armenian_pyur': 16778627, - 'Armenian_question': 16778590, - 'Armenian_ra': 16778620, - 'Armenian_re': 16778624, - 'Armenian_se': 16778621, - 'Armenian_separation_mark': 16778589, - 'Armenian_sha': 16778615, - 'Armenian_shesht': 16778587, - 'Armenian_tche': 16778611, - 'Armenian_to': 16778601, - 'Armenian_tsa': 16778606, - 'Armenian_tso': 16778625, - 'Armenian_tyun': 16778623, - 'Armenian_verjaket': 16778633, - 'Armenian_vev': 16778622, - 'Armenian_vo': 16778616, - 'Armenian_vyun': 16778626, - 'Armenian_yech': 16778597, - 'Armenian_yentamna': 16778634, - 'Armenian_za': 16778598, - 'Armenian_zhe': 16778602, - 'Atilde': 195, - 'AudibleBell_Enable': 65146, - 'B': 66, - 'Babovedot': 16784898, - 'BackSpace': 65288, - 'Begin': 65368, - 'BounceKeys_Enable': 65140, - 'Break': 65387, - 'Byelorussian_SHORTU': 1726, - 'Byelorussian_shortu': 1710, - 'C': 67, - 'CH': 65186, - 'C_H': 65189, - 'C_h': 65188, - 'Cabovedot': 709, - 'Cacute': 454, - 'Cancel': 65385, - 'Caps_Lock': 65509, - 'Ccaron': 456, - 'Ccedilla': 199, - 'Ccircumflex': 710, - 'Ch': 65185, - 'Clear': 65291, - 'Codeinput': 65335, - 'ColonSign': 16785569, - 'Control_L': 65507, - 'Control_R': 65508, - 'CruzeiroSign': 16785570, - 'Cyrillic_A': 1761, - 'Cyrillic_BE': 1762, - 'Cyrillic_CHE': 1790, - 'Cyrillic_CHE_descender': 16778422, - 'Cyrillic_CHE_vertstroke': 16778424, - 'Cyrillic_DE': 1764, - 'Cyrillic_DZHE': 1727, - 'Cyrillic_E': 1788, - 'Cyrillic_EF': 1766, - 'Cyrillic_EL': 1772, - 'Cyrillic_EM': 1773, - 'Cyrillic_EN': 1774, - 'Cyrillic_EN_descender': 16778402, - 'Cyrillic_ER': 1778, - 'Cyrillic_ES': 1779, - 'Cyrillic_GHE': 1767, - 'Cyrillic_GHE_bar': 16778386, - 'Cyrillic_HA': 1768, - 'Cyrillic_HARDSIGN': 1791, - 'Cyrillic_HA_descender': 16778418, - 'Cyrillic_I': 1769, - 'Cyrillic_IE': 1765, - 'Cyrillic_IO': 1715, - 'Cyrillic_I_macron': 16778466, - 'Cyrillic_JE': 1720, - 'Cyrillic_KA': 1771, - 'Cyrillic_KA_descender': 16778394, - 'Cyrillic_KA_vertstroke': 16778396, - 'Cyrillic_LJE': 1721, - 'Cyrillic_NJE': 1722, - 'Cyrillic_O': 1775, - 'Cyrillic_O_bar': 16778472, - 'Cyrillic_PE': 1776, - 'Cyrillic_SCHWA': 16778456, - 'Cyrillic_SHA': 1787, - 'Cyrillic_SHCHA': 1789, - 'Cyrillic_SHHA': 16778426, - 'Cyrillic_SHORTI': 1770, - 'Cyrillic_SOFTSIGN': 1784, - 'Cyrillic_TE': 1780, - 'Cyrillic_TSE': 1763, - 'Cyrillic_U': 1781, - 'Cyrillic_U_macron': 16778478, - 'Cyrillic_U_straight': 16778414, - 'Cyrillic_U_straight_bar': 16778416, - 'Cyrillic_VE': 1783, - 'Cyrillic_YA': 1777, - 'Cyrillic_YERU': 1785, - 'Cyrillic_YU': 1760, - 'Cyrillic_ZE': 1786, - 'Cyrillic_ZHE': 1782, - 'Cyrillic_ZHE_descender': 16778390, - 'Cyrillic_a': 1729, - 'Cyrillic_be': 1730, - 'Cyrillic_che': 1758, - 'Cyrillic_che_descender': 16778423, - 'Cyrillic_che_vertstroke': 16778425, - 'Cyrillic_de': 1732, - 'Cyrillic_dzhe': 1711, - 'Cyrillic_e': 1756, - 'Cyrillic_ef': 1734, - 'Cyrillic_el': 1740, - 'Cyrillic_em': 1741, - 'Cyrillic_en': 1742, - 'Cyrillic_en_descender': 16778403, - 'Cyrillic_er': 1746, - 'Cyrillic_es': 1747, - 'Cyrillic_ghe': 1735, - 'Cyrillic_ghe_bar': 16778387, - 'Cyrillic_ha': 1736, - 'Cyrillic_ha_descender': 16778419, - 'Cyrillic_hardsign': 1759, - 'Cyrillic_i': 1737, - 'Cyrillic_i_macron': 16778467, - 'Cyrillic_ie': 1733, - 'Cyrillic_io': 1699, - 'Cyrillic_je': 1704, - 'Cyrillic_ka': 1739, - 'Cyrillic_ka_descender': 16778395, - 'Cyrillic_ka_vertstroke': 16778397, - 'Cyrillic_lje': 1705, - 'Cyrillic_nje': 1706, - 'Cyrillic_o': 1743, - 'Cyrillic_o_bar': 16778473, - 'Cyrillic_pe': 1744, - 'Cyrillic_schwa': 16778457, - 'Cyrillic_sha': 1755, - 'Cyrillic_shcha': 1757, - 'Cyrillic_shha': 16778427, - 'Cyrillic_shorti': 1738, - 'Cyrillic_softsign': 1752, - 'Cyrillic_te': 1748, - 'Cyrillic_tse': 1731, - 'Cyrillic_u': 1749, - 'Cyrillic_u_macron': 16778479, - 'Cyrillic_u_straight': 16778415, - 'Cyrillic_u_straight_bar': 16778417, - 'Cyrillic_ve': 1751, - 'Cyrillic_ya': 1745, - 'Cyrillic_yeru': 1753, - 'Cyrillic_yu': 1728, - 'Cyrillic_ze': 1754, - 'Cyrillic_zhe': 1750, - 'Cyrillic_zhe_descender': 16778391, - 'D': 68, - 'Dabovedot': 16784906, - 'Dcaron': 463, - 'Delete': 65535, - 'DongSign': 16785579, - 'Down': 65364, - 'Dstroke': 464, - 'E': 69, - 'ENG': 957, - 'ETH': 208, - 'EZH': 16777655, - 'Eabovedot': 972, - 'Eacute': 201, - 'Ebelowdot': 16785080, - 'Ecaron': 460, - 'Ecircumflex': 202, - 'Ecircumflexacute': 16785086, - 'Ecircumflexbelowdot': 16785094, - 'Ecircumflexgrave': 16785088, - 'Ecircumflexhook': 16785090, - 'Ecircumflextilde': 16785092, - 'EcuSign': 16785568, - 'Ediaeresis': 203, - 'Egrave': 200, - 'Ehook': 16785082, - 'Eisu_Shift': 65327, - 'Eisu_toggle': 65328, - 'Emacron': 938, - 'End': 65367, - 'Eogonek': 458, - 'Escape': 65307, - 'Eth': 208, - 'Etilde': 16785084, - 'EuroSign': 8364, - 'Execute': 65378, - 'F': 70, - 'F1': 65470, - 'F10': 65479, - 'F11': 65480, - 'F12': 65481, - 'F13': 65482, - 'F14': 65483, - 'F15': 65484, - 'F16': 65485, - 'F17': 65486, - 'F18': 65487, - 'F19': 65488, - 'F2': 65471, - 'F20': 65489, - 'F21': 65490, - 'F22': 65491, - 'F23': 65492, - 'F24': 65493, - 'F25': 65494, - 'F26': 65495, - 'F27': 65496, - 'F28': 65497, - 'F29': 65498, - 'F3': 65472, - 'F30': 65499, - 'F31': 65500, - 'F32': 65501, - 'F33': 65502, - 'F34': 65503, - 'F35': 65504, - 'F4': 65473, - 'F5': 65474, - 'F6': 65475, - 'F7': 65476, - 'F8': 65477, - 'F9': 65478, - 'FFrancSign': 16785571, - 'Fabovedot': 16784926, - 'Farsi_0': 16778992, - 'Farsi_1': 16778993, - 'Farsi_2': 16778994, - 'Farsi_3': 16778995, - 'Farsi_4': 16778996, - 'Farsi_5': 16778997, - 'Farsi_6': 16778998, - 'Farsi_7': 16778999, - 'Farsi_8': 16779000, - 'Farsi_9': 16779001, - 'Farsi_yeh': 16778956, - 'Find': 65384, - 'First_Virtual_Screen': 65232, - 'G': 71, - 'Gabovedot': 725, - 'Gbreve': 683, - 'Gcaron': 16777702, - 'Gcedilla': 939, - 'Gcircumflex': 728, - 'Georgian_an': 16781520, - 'Georgian_ban': 16781521, - 'Georgian_can': 16781546, - 'Georgian_char': 16781549, - 'Georgian_chin': 16781545, - 'Georgian_cil': 16781548, - 'Georgian_don': 16781523, - 'Georgian_en': 16781524, - 'Georgian_fi': 16781558, - 'Georgian_gan': 16781522, - 'Georgian_ghan': 16781542, - 'Georgian_hae': 16781552, - 'Georgian_har': 16781556, - 'Georgian_he': 16781553, - 'Georgian_hie': 16781554, - 'Georgian_hoe': 16781557, - 'Georgian_in': 16781528, - 'Georgian_jhan': 16781551, - 'Georgian_jil': 16781547, - 'Georgian_kan': 16781529, - 'Georgian_khar': 16781541, - 'Georgian_las': 16781530, - 'Georgian_man': 16781531, - 'Georgian_nar': 16781532, - 'Georgian_on': 16781533, - 'Georgian_par': 16781534, - 'Georgian_phar': 16781540, - 'Georgian_qar': 16781543, - 'Georgian_rae': 16781536, - 'Georgian_san': 16781537, - 'Georgian_shin': 16781544, - 'Georgian_tan': 16781527, - 'Georgian_tar': 16781538, - 'Georgian_un': 16781539, - 'Georgian_vin': 16781525, - 'Georgian_we': 16781555, - 'Georgian_xan': 16781550, - 'Georgian_zen': 16781526, - 'Georgian_zhar': 16781535, - 'Greek_ALPHA': 1985, - 'Greek_ALPHAaccent': 1953, - 'Greek_BETA': 1986, - 'Greek_CHI': 2007, - 'Greek_DELTA': 1988, - 'Greek_EPSILON': 1989, - 'Greek_EPSILONaccent': 1954, - 'Greek_ETA': 1991, - 'Greek_ETAaccent': 1955, - 'Greek_GAMMA': 1987, - 'Greek_IOTA': 1993, - 'Greek_IOTAaccent': 1956, - 'Greek_IOTAdiaeresis': 1957, - 'Greek_IOTAdieresis': 1957, - 'Greek_KAPPA': 1994, - 'Greek_LAMBDA': 1995, - 'Greek_LAMDA': 1995, - 'Greek_MU': 1996, - 'Greek_NU': 1997, - 'Greek_OMEGA': 2009, - 'Greek_OMEGAaccent': 1963, - 'Greek_OMICRON': 1999, - 'Greek_OMICRONaccent': 1959, - 'Greek_PHI': 2006, - 'Greek_PI': 2000, - 'Greek_PSI': 2008, - 'Greek_RHO': 2001, - 'Greek_SIGMA': 2002, - 'Greek_TAU': 2004, - 'Greek_THETA': 1992, - 'Greek_UPSILON': 2005, - 'Greek_UPSILONaccent': 1960, - 'Greek_UPSILONdieresis': 1961, - 'Greek_XI': 1998, - 'Greek_ZETA': 1990, - 'Greek_accentdieresis': 1966, - 'Greek_alpha': 2017, - 'Greek_alphaaccent': 1969, - 'Greek_beta': 2018, - 'Greek_chi': 2039, - 'Greek_delta': 2020, - 'Greek_epsilon': 2021, - 'Greek_epsilonaccent': 1970, - 'Greek_eta': 2023, - 'Greek_etaaccent': 1971, - 'Greek_finalsmallsigma': 2035, - 'Greek_gamma': 2019, - 'Greek_horizbar': 1967, - 'Greek_iota': 2025, - 'Greek_iotaaccent': 1972, - 'Greek_iotaaccentdieresis': 1974, - 'Greek_iotadieresis': 1973, - 'Greek_kappa': 2026, - 'Greek_lambda': 2027, - 'Greek_lamda': 2027, - 'Greek_mu': 2028, - 'Greek_nu': 2029, - 'Greek_omega': 2041, - 'Greek_omegaaccent': 1979, - 'Greek_omicron': 2031, - 'Greek_omicronaccent': 1975, - 'Greek_phi': 2038, - 'Greek_pi': 2032, - 'Greek_psi': 2040, - 'Greek_rho': 2033, - 'Greek_sigma': 2034, - 'Greek_switch': 65406, - 'Greek_tau': 2036, - 'Greek_theta': 2024, - 'Greek_upsilon': 2037, - 'Greek_upsilonaccent': 1976, - 'Greek_upsilonaccentdieresis': 1978, - 'Greek_upsilondieresis': 1977, - 'Greek_xi': 2030, - 'Greek_zeta': 2022, - 'H': 72, - 'Hangul': 65329, - 'Hangul_A': 3775, - 'Hangul_AE': 3776, - 'Hangul_AraeA': 3830, - 'Hangul_AraeAE': 3831, - 'Hangul_Banja': 65337, - 'Hangul_Cieuc': 3770, - 'Hangul_Codeinput': 65335, - 'Hangul_Dikeud': 3751, - 'Hangul_E': 3780, - 'Hangul_EO': 3779, - 'Hangul_EU': 3793, - 'Hangul_End': 65331, - 'Hangul_Hanja': 65332, - 'Hangul_Hieuh': 3774, - 'Hangul_I': 3795, - 'Hangul_Ieung': 3767, - 'Hangul_J_Cieuc': 3818, - 'Hangul_J_Dikeud': 3802, - 'Hangul_J_Hieuh': 3822, - 'Hangul_J_Ieung': 3816, - 'Hangul_J_Jieuj': 3817, - 'Hangul_J_Khieuq': 3819, - 'Hangul_J_Kiyeog': 3796, - 'Hangul_J_KiyeogSios': 3798, - 'Hangul_J_KkogjiDalrinIeung': 3833, - 'Hangul_J_Mieum': 3811, - 'Hangul_J_Nieun': 3799, - 'Hangul_J_NieunHieuh': 3801, - 'Hangul_J_NieunJieuj': 3800, - 'Hangul_J_PanSios': 3832, - 'Hangul_J_Phieuf': 3821, - 'Hangul_J_Pieub': 3812, - 'Hangul_J_PieubSios': 3813, - 'Hangul_J_Rieul': 3803, - 'Hangul_J_RieulHieuh': 3810, - 'Hangul_J_RieulKiyeog': 3804, - 'Hangul_J_RieulMieum': 3805, - 'Hangul_J_RieulPhieuf': 3809, - 'Hangul_J_RieulPieub': 3806, - 'Hangul_J_RieulSios': 3807, - 'Hangul_J_RieulTieut': 3808, - 'Hangul_J_Sios': 3814, - 'Hangul_J_SsangKiyeog': 3797, - 'Hangul_J_SsangSios': 3815, - 'Hangul_J_Tieut': 3820, - 'Hangul_J_YeorinHieuh': 3834, - 'Hangul_Jamo': 65333, - 'Hangul_Jeonja': 65336, - 'Hangul_Jieuj': 3768, - 'Hangul_Khieuq': 3771, - 'Hangul_Kiyeog': 3745, - 'Hangul_KiyeogSios': 3747, - 'Hangul_KkogjiDalrinIeung': 3827, - 'Hangul_Mieum': 3761, - 'Hangul_MultipleCandidate': 65341, - 'Hangul_Nieun': 3748, - 'Hangul_NieunHieuh': 3750, - 'Hangul_NieunJieuj': 3749, - 'Hangul_O': 3783, - 'Hangul_OE': 3786, - 'Hangul_PanSios': 3826, - 'Hangul_Phieuf': 3773, - 'Hangul_Pieub': 3762, - 'Hangul_PieubSios': 3764, - 'Hangul_PostHanja': 65339, - 'Hangul_PreHanja': 65338, - 'Hangul_PreviousCandidate': 65342, - 'Hangul_Rieul': 3753, - 'Hangul_RieulHieuh': 3760, - 'Hangul_RieulKiyeog': 3754, - 'Hangul_RieulMieum': 3755, - 'Hangul_RieulPhieuf': 3759, - 'Hangul_RieulPieub': 3756, - 'Hangul_RieulSios': 3757, - 'Hangul_RieulTieut': 3758, - 'Hangul_RieulYeorinHieuh': 3823, - 'Hangul_Romaja': 65334, - 'Hangul_SingleCandidate': 65340, - 'Hangul_Sios': 3765, - 'Hangul_Special': 65343, - 'Hangul_SsangDikeud': 3752, - 'Hangul_SsangJieuj': 3769, - 'Hangul_SsangKiyeog': 3746, - 'Hangul_SsangPieub': 3763, - 'Hangul_SsangSios': 3766, - 'Hangul_Start': 65330, - 'Hangul_SunkyeongeumMieum': 3824, - 'Hangul_SunkyeongeumPhieuf': 3828, - 'Hangul_SunkyeongeumPieub': 3825, - 'Hangul_Tieut': 3772, - 'Hangul_U': 3788, - 'Hangul_WA': 3784, - 'Hangul_WAE': 3785, - 'Hangul_WE': 3790, - 'Hangul_WEO': 3789, - 'Hangul_WI': 3791, - 'Hangul_YA': 3777, - 'Hangul_YAE': 3778, - 'Hangul_YE': 3782, - 'Hangul_YEO': 3781, - 'Hangul_YI': 3794, - 'Hangul_YO': 3787, - 'Hangul_YU': 3792, - 'Hangul_YeorinHieuh': 3829, - 'Hangul_switch': 65406, - 'Hankaku': 65321, - 'Hcircumflex': 678, - 'Hebrew_switch': 65406, - 'Help': 65386, - 'Henkan': 65315, - 'Henkan_Mode': 65315, - 'Hiragana': 65317, - 'Hiragana_Katakana': 65319, - 'Home': 65360, - 'Hstroke': 673, - 'Hyper_L': 65517, - 'Hyper_R': 65518, - 'I': 73, - 'ISO_Center_Object': 65075, - 'ISO_Continuous_Underline': 65072, - 'ISO_Discontinuous_Underline': 65073, - 'ISO_Emphasize': 65074, - 'ISO_Enter': 65076, - 'ISO_Fast_Cursor_Down': 65071, - 'ISO_Fast_Cursor_Left': 65068, - 'ISO_Fast_Cursor_Right': 65069, - 'ISO_Fast_Cursor_Up': 65070, - 'ISO_First_Group': 65036, - 'ISO_First_Group_Lock': 65037, - 'ISO_Group_Latch': 65030, - 'ISO_Group_Lock': 65031, - 'ISO_Group_Shift': 65406, - 'ISO_Last_Group': 65038, - 'ISO_Last_Group_Lock': 65039, - 'ISO_Left_Tab': 65056, - 'ISO_Level2_Latch': 65026, - 'ISO_Level3_Latch': 65028, - 'ISO_Level3_Lock': 65029, - 'ISO_Level3_Shift': 65027, - 'ISO_Level5_Latch': 65042, - 'ISO_Level5_Lock': 65043, - 'ISO_Level5_Shift': 65041, - 'ISO_Lock': 65025, - 'ISO_Move_Line_Down': 65058, - 'ISO_Move_Line_Up': 65057, - 'ISO_Next_Group': 65032, - 'ISO_Next_Group_Lock': 65033, - 'ISO_Partial_Line_Down': 65060, - 'ISO_Partial_Line_Up': 65059, - 'ISO_Partial_Space_Left': 65061, - 'ISO_Partial_Space_Right': 65062, - 'ISO_Prev_Group': 65034, - 'ISO_Prev_Group_Lock': 65035, - 'ISO_Release_Both_Margins': 65067, - 'ISO_Release_Margin_Left': 65065, - 'ISO_Release_Margin_Right': 65066, - 'ISO_Set_Margin_Left': 65063, - 'ISO_Set_Margin_Right': 65064, - 'Iabovedot': 681, - 'Iacute': 205, - 'Ibelowdot': 16785098, - 'Ibreve': 16777516, - 'Icircumflex': 206, - 'Idiaeresis': 207, - 'Igrave': 204, - 'Ihook': 16785096, - 'Imacron': 975, - 'Insert': 65379, - 'Iogonek': 967, - 'Itilde': 933, - 'J': 74, - 'Jcircumflex': 684, - 'K': 75, - 'KP_0': 65456, - 'KP_1': 65457, - 'KP_2': 65458, - 'KP_3': 65459, - 'KP_4': 65460, - 'KP_5': 65461, - 'KP_6': 65462, - 'KP_7': 65463, - 'KP_8': 65464, - 'KP_9': 65465, - 'KP_Add': 65451, - 'KP_Begin': 65437, - 'KP_Decimal': 65454, - 'KP_Delete': 65439, - 'KP_Divide': 65455, - 'KP_Down': 65433, - 'KP_End': 65436, - 'KP_Enter': 65421, - 'KP_Equal': 65469, - 'KP_F1': 65425, - 'KP_F2': 65426, - 'KP_F3': 65427, - 'KP_F4': 65428, - 'KP_Home': 65429, - 'KP_Insert': 65438, - 'KP_Left': 65430, - 'KP_Multiply': 65450, - 'KP_Next': 65435, - 'KP_Page_Down': 65435, - 'KP_Page_Up': 65434, - 'KP_Prior': 65434, - 'KP_Right': 65432, - 'KP_Separator': 65452, - 'KP_Space': 65408, - 'KP_Subtract': 65453, - 'KP_Tab': 65417, - 'KP_Up': 65431, - 'Kana_Lock': 65325, - 'Kana_Shift': 65326, - 'Kanji': 65313, - 'Kanji_Bangou': 65335, - 'Katakana': 65318, - 'Kcedilla': 979, - 'Korean_Won': 3839, - 'L': 76, - 'L1': 65480, - 'L10': 65489, - 'L2': 65481, - 'L3': 65482, - 'L4': 65483, - 'L5': 65484, - 'L6': 65485, - 'L7': 65486, - 'L8': 65487, - 'L9': 65488, - 'Lacute': 453, - 'Last_Virtual_Screen': 65236, - 'Lbelowdot': 16784950, - 'Lcaron': 421, - 'Lcedilla': 934, - 'Left': 65361, - 'Linefeed': 65290, - 'LiraSign': 16785572, - 'Lstroke': 419, - 'M': 77, - 'Mabovedot': 16784960, - 'Macedonia_DSE': 1717, - 'Macedonia_GJE': 1714, - 'Macedonia_KJE': 1724, - 'Macedonia_dse': 1701, - 'Macedonia_gje': 1698, - 'Macedonia_kje': 1708, - 'Mae_Koho': 65342, - 'Massyo': 65324, - 'Menu': 65383, - 'Meta_L': 65511, - 'Meta_R': 65512, - 'MillSign': 16785573, - 'Mode_switch': 65406, - 'MouseKeys_Accel_Enable': 65143, - 'MouseKeys_Enable': 65142, - 'Muhenkan': 65314, - 'Multi_key': 65312, - 'MultipleCandidate': 65341, - 'N': 78, - 'Nacute': 465, - 'NairaSign': 16785574, - 'Ncaron': 466, - 'Ncedilla': 977, - 'NewSheqelSign': 16785578, - 'Next': 65366, - 'Next_Virtual_Screen': 65234, - 'Ntilde': 209, - 'Num_Lock': 65407, - 'O': 79, - 'OE': 5052, - 'Oacute': 211, - 'Obarred': 16777631, - 'Obelowdot': 16785100, - 'Ocaron': 16777681, - 'Ocircumflex': 212, - 'Ocircumflexacute': 16785104, - 'Ocircumflexbelowdot': 16785112, - 'Ocircumflexgrave': 16785106, - 'Ocircumflexhook': 16785108, - 'Ocircumflextilde': 16785110, - 'Odiaeresis': 214, - 'Odoubleacute': 469, - 'Ograve': 210, - 'Ohook': 16785102, - 'Ohorn': 16777632, - 'Ohornacute': 16785114, - 'Ohornbelowdot': 16785122, - 'Ohorngrave': 16785116, - 'Ohornhook': 16785118, - 'Ohorntilde': 16785120, - 'Omacron': 978, - 'Ooblique': 216, - 'Oslash': 216, - 'Otilde': 213, - 'Overlay1_Enable': 65144, - 'Overlay2_Enable': 65145, - 'P': 80, - 'Pabovedot': 16784982, - 'Page_Down': 65366, - 'Page_Up': 65365, - 'Pause': 65299, - 'PesetaSign': 16785575, - 'Pointer_Accelerate': 65274, - 'Pointer_Button1': 65257, - 'Pointer_Button2': 65258, - 'Pointer_Button3': 65259, - 'Pointer_Button4': 65260, - 'Pointer_Button5': 65261, - 'Pointer_Button_Dflt': 65256, - 'Pointer_DblClick1': 65263, - 'Pointer_DblClick2': 65264, - 'Pointer_DblClick3': 65265, - 'Pointer_DblClick4': 65266, - 'Pointer_DblClick5': 65267, - 'Pointer_DblClick_Dflt': 65262, - 'Pointer_DfltBtnNext': 65275, - 'Pointer_DfltBtnPrev': 65276, - 'Pointer_Down': 65251, - 'Pointer_DownLeft': 65254, - 'Pointer_DownRight': 65255, - 'Pointer_Drag1': 65269, - 'Pointer_Drag2': 65270, - 'Pointer_Drag3': 65271, - 'Pointer_Drag4': 65272, - 'Pointer_Drag5': 65277, - 'Pointer_Drag_Dflt': 65268, - 'Pointer_EnableKeys': 65273, - 'Pointer_Left': 65248, - 'Pointer_Right': 65249, - 'Pointer_Up': 65250, - 'Pointer_UpLeft': 65252, - 'Pointer_UpRight': 65253, - 'Prev_Virtual_Screen': 65233, - 'PreviousCandidate': 65342, - 'Print': 65377, - 'Prior': 65365, - 'Q': 81, - 'R': 82, - 'R1': 65490, - 'R10': 65499, - 'R11': 65500, - 'R12': 65501, - 'R13': 65502, - 'R14': 65503, - 'R15': 65504, - 'R2': 65491, - 'R3': 65492, - 'R4': 65493, - 'R5': 65494, - 'R6': 65495, - 'R7': 65496, - 'R8': 65497, - 'R9': 65498, - 'Racute': 448, - 'Rcaron': 472, - 'Rcedilla': 931, - 'Redo': 65382, - 'RepeatKeys_Enable': 65138, - 'Return': 65293, - 'Right': 65363, - 'Romaji': 65316, - 'RupeeSign': 16785576, - 'S': 83, - 'SCHWA': 16777615, - 'Sabovedot': 16784992, - 'Sacute': 422, - 'Scaron': 425, - 'Scedilla': 426, - 'Scircumflex': 734, - 'Scroll_Lock': 65300, - 'Select': 65376, - 'Serbian_DJE': 1713, - 'Serbian_DZE': 1727, - 'Serbian_JE': 1720, - 'Serbian_LJE': 1721, - 'Serbian_NJE': 1722, - 'Serbian_TSHE': 1723, - 'Serbian_dje': 1697, - 'Serbian_dze': 1711, - 'Serbian_je': 1704, - 'Serbian_lje': 1705, - 'Serbian_nje': 1706, - 'Serbian_tshe': 1707, - 'Shift_L': 65505, - 'Shift_Lock': 65510, - 'Shift_R': 65506, - 'SingleCandidate': 65340, - 'Sinh_a': 16780677, - 'Sinh_aa': 16780678, - 'Sinh_aa2': 16780751, - 'Sinh_ae': 16780679, - 'Sinh_ae2': 16780752, - 'Sinh_aee': 16780680, - 'Sinh_aee2': 16780753, - 'Sinh_ai': 16780691, - 'Sinh_ai2': 16780763, - 'Sinh_al': 16780746, - 'Sinh_au': 16780694, - 'Sinh_au2': 16780766, - 'Sinh_ba': 16780726, - 'Sinh_bha': 16780727, - 'Sinh_ca': 16780704, - 'Sinh_cha': 16780705, - 'Sinh_dda': 16780713, - 'Sinh_ddha': 16780714, - 'Sinh_dha': 16780719, - 'Sinh_dhha': 16780720, - 'Sinh_e': 16780689, - 'Sinh_e2': 16780761, - 'Sinh_ee': 16780690, - 'Sinh_ee2': 16780762, - 'Sinh_fa': 16780742, - 'Sinh_ga': 16780700, - 'Sinh_gha': 16780701, - 'Sinh_h2': 16780675, - 'Sinh_ha': 16780740, - 'Sinh_i': 16780681, - 'Sinh_i2': 16780754, - 'Sinh_ii': 16780682, - 'Sinh_ii2': 16780755, - 'Sinh_ja': 16780706, - 'Sinh_jha': 16780707, - 'Sinh_jnya': 16780709, - 'Sinh_ka': 16780698, - 'Sinh_kha': 16780699, - 'Sinh_kunddaliya': 16780788, - 'Sinh_la': 16780733, - 'Sinh_lla': 16780741, - 'Sinh_lu': 16780687, - 'Sinh_lu2': 16780767, - 'Sinh_luu': 16780688, - 'Sinh_luu2': 16780787, - 'Sinh_ma': 16780728, - 'Sinh_mba': 16780729, - 'Sinh_na': 16780721, - 'Sinh_ndda': 16780716, - 'Sinh_ndha': 16780723, - 'Sinh_ng': 16780674, - 'Sinh_ng2': 16780702, - 'Sinh_nga': 16780703, - 'Sinh_nja': 16780710, - 'Sinh_nna': 16780715, - 'Sinh_nya': 16780708, - 'Sinh_o': 16780692, - 'Sinh_o2': 16780764, - 'Sinh_oo': 16780693, - 'Sinh_oo2': 16780765, - 'Sinh_pa': 16780724, - 'Sinh_pha': 16780725, - 'Sinh_ra': 16780731, - 'Sinh_ri': 16780685, - 'Sinh_rii': 16780686, - 'Sinh_ru2': 16780760, - 'Sinh_ruu2': 16780786, - 'Sinh_sa': 16780739, - 'Sinh_sha': 16780737, - 'Sinh_ssha': 16780738, - 'Sinh_tha': 16780717, - 'Sinh_thha': 16780718, - 'Sinh_tta': 16780711, - 'Sinh_ttha': 16780712, - 'Sinh_u': 16780683, - 'Sinh_u2': 16780756, - 'Sinh_uu': 16780684, - 'Sinh_uu2': 16780758, - 'Sinh_va': 16780736, - 'Sinh_ya': 16780730, - 'SlowKeys_Enable': 65139, - 'StickyKeys_Enable': 65141, - 'Super_L': 65515, - 'Super_R': 65516, - 'Sys_Req': 65301, - 'T': 84, - 'THORN': 222, - 'Tab': 65289, - 'Tabovedot': 16785002, - 'Tcaron': 427, - 'Tcedilla': 478, - 'Terminate_Server': 65237, - 'Thai_baht': 3551, - 'Thai_bobaimai': 3514, - 'Thai_chochan': 3496, - 'Thai_chochang': 3498, - 'Thai_choching': 3497, - 'Thai_chochoe': 3500, - 'Thai_dochada': 3502, - 'Thai_dodek': 3508, - 'Thai_fofa': 3517, - 'Thai_fofan': 3519, - 'Thai_hohip': 3531, - 'Thai_honokhuk': 3534, - 'Thai_khokhai': 3490, - 'Thai_khokhon': 3493, - 'Thai_khokhuat': 3491, - 'Thai_khokhwai': 3492, - 'Thai_khorakhang': 3494, - 'Thai_kokai': 3489, - 'Thai_lakkhangyao': 3557, - 'Thai_lekchet': 3575, - 'Thai_lekha': 3573, - 'Thai_lekhok': 3574, - 'Thai_lekkao': 3577, - 'Thai_leknung': 3569, - 'Thai_lekpaet': 3576, - 'Thai_leksam': 3571, - 'Thai_leksi': 3572, - 'Thai_leksong': 3570, - 'Thai_leksun': 3568, - 'Thai_lochula': 3532, - 'Thai_loling': 3525, - 'Thai_lu': 3526, - 'Thai_maichattawa': 3563, - 'Thai_maiek': 3560, - 'Thai_maihanakat': 3537, - 'Thai_maihanakat_maitho': 3550, - 'Thai_maitaikhu': 3559, - 'Thai_maitho': 3561, - 'Thai_maitri': 3562, - 'Thai_maiyamok': 3558, - 'Thai_moma': 3521, - 'Thai_ngongu': 3495, - 'Thai_nikhahit': 3565, - 'Thai_nonen': 3507, - 'Thai_nonu': 3513, - 'Thai_oang': 3533, - 'Thai_paiyannoi': 3535, - 'Thai_phinthu': 3546, - 'Thai_phophan': 3518, - 'Thai_phophung': 3516, - 'Thai_phosamphao': 3520, - 'Thai_popla': 3515, - 'Thai_rorua': 3523, - 'Thai_ru': 3524, - 'Thai_saraa': 3536, - 'Thai_saraaa': 3538, - 'Thai_saraae': 3553, - 'Thai_saraaimaimalai': 3556, - 'Thai_saraaimaimuan': 3555, - 'Thai_saraam': 3539, - 'Thai_sarae': 3552, - 'Thai_sarai': 3540, - 'Thai_saraii': 3541, - 'Thai_sarao': 3554, - 'Thai_sarau': 3544, - 'Thai_saraue': 3542, - 'Thai_sarauee': 3543, - 'Thai_sarauu': 3545, - 'Thai_sorusi': 3529, - 'Thai_sosala': 3528, - 'Thai_soso': 3499, - 'Thai_sosua': 3530, - 'Thai_thanthakhat': 3564, - 'Thai_thonangmontho': 3505, - 'Thai_thophuthao': 3506, - 'Thai_thothahan': 3511, - 'Thai_thothan': 3504, - 'Thai_thothong': 3512, - 'Thai_thothung': 3510, - 'Thai_topatak': 3503, - 'Thai_totao': 3509, - 'Thai_wowaen': 3527, - 'Thai_yoyak': 3522, - 'Thai_yoying': 3501, - 'Thorn': 222, - 'Touroku': 65323, - 'Tslash': 940, - 'U': 85, - 'Uacute': 218, - 'Ubelowdot': 16785124, - 'Ubreve': 733, - 'Ucircumflex': 219, - 'Udiaeresis': 220, - 'Udoubleacute': 475, - 'Ugrave': 217, - 'Uhook': 16785126, - 'Uhorn': 16777647, - 'Uhornacute': 16785128, - 'Uhornbelowdot': 16785136, - 'Uhorngrave': 16785130, - 'Uhornhook': 16785132, - 'Uhorntilde': 16785134, - 'Ukrainian_GHE_WITH_UPTURN': 1725, - 'Ukrainian_I': 1718, - 'Ukrainian_IE': 1716, - 'Ukrainian_YI': 1719, - 'Ukrainian_ghe_with_upturn': 1709, - 'Ukrainian_i': 1702, - 'Ukrainian_ie': 1700, - 'Ukrainian_yi': 1703, - 'Ukranian_I': 1718, - 'Ukranian_JE': 1716, - 'Ukranian_YI': 1719, - 'Ukranian_i': 1702, - 'Ukranian_je': 1700, - 'Ukranian_yi': 1703, - 'Umacron': 990, - 'Undo': 65381, - 'Uogonek': 985, - 'Up': 65362, - 'Uring': 473, - 'Utilde': 989, - 'V': 86, - 'VoidSymbol': 16777215, - 'W': 87, - 'Wacute': 16785026, - 'Wcircumflex': 16777588, - 'Wdiaeresis': 16785028, - 'Wgrave': 16785024, - 'WonSign': 16785577, - 'X': 88, - 'XF86_AddFavorite': 269025081, - 'XF86_ApplicationLeft': 269025104, - 'XF86_ApplicationRight': 269025105, - 'XF86_AudioCycleTrack': 269025179, - 'XF86_AudioForward': 269025175, - 'XF86_AudioLowerVolume': 269025041, - 'XF86_AudioMedia': 269025074, - 'XF86_AudioMicMute': 269025202, - 'XF86_AudioMute': 269025042, - 'XF86_AudioNext': 269025047, - 'XF86_AudioPause': 269025073, - 'XF86_AudioPlay': 269025044, - 'XF86_AudioPreset': 269025206, - 'XF86_AudioPrev': 269025046, - 'XF86_AudioRaiseVolume': 269025043, - 'XF86_AudioRandomPlay': 269025177, - 'XF86_AudioRecord': 269025052, - 'XF86_AudioRepeat': 269025176, - 'XF86_AudioRewind': 269025086, - 'XF86_AudioStop': 269025045, - 'XF86_Away': 269025165, - 'XF86_Back': 269025062, - 'XF86_BackForward': 269025087, - 'XF86_Battery': 269025171, - 'XF86_Blue': 269025190, - 'XF86_Bluetooth': 269025172, - 'XF86_Book': 269025106, - 'XF86_BrightnessAdjust': 269025083, - 'XF86_CD': 269025107, - 'XF86_Calculater': 269025108, - 'XF86_Calculator': 269025053, - 'XF86_Calendar': 269025056, - 'XF86_Clear': 269025109, - 'XF86_ClearGrab': 269024801, - 'XF86_Close': 269025110, - 'XF86_Community': 269025085, - 'XF86_ContrastAdjust': 269025058, - 'XF86_Copy': 269025111, - 'XF86_Cut': 269025112, - 'XF86_CycleAngle': 269025180, - 'XF86_DOS': 269025114, - 'XF86_Display': 269025113, - 'XF86_Documents': 269025115, - 'XF86_Eject': 269025068, - 'XF86_Excel': 269025116, - 'XF86_Explorer': 269025117, - 'XF86_Favorites': 269025072, - 'XF86_Finance': 269025084, - 'XF86_Forward': 269025063, - 'XF86_FrameBack': 269025181, - 'XF86_FrameForward': 269025182, - 'XF86_FullScreen': 269025208, - 'XF86_Game': 269025118, - 'XF86_Go': 269025119, - 'XF86_Green': 269025188, - 'XF86_Hibernate': 269025192, - 'XF86_History': 269025079, - 'XF86_HomePage': 269025048, - 'XF86_HotLinks': 269025082, - 'XF86_KbdBrightnessDown': 269025030, - 'XF86_KbdBrightnessUp': 269025029, - 'XF86_KbdLightOnOff': 269025028, - 'XF86_Keyboard': 269025203, - 'XF86_Launch0': 269025088, - 'XF86_Launch1': 269025089, - 'XF86_Launch2': 269025090, - 'XF86_Launch3': 269025091, - 'XF86_Launch4': 269025092, - 'XF86_Launch5': 269025093, - 'XF86_Launch6': 269025094, - 'XF86_Launch7': 269025095, - 'XF86_Launch8': 269025096, - 'XF86_Launch9': 269025097, - 'XF86_LaunchA': 269025098, - 'XF86_LaunchB': 269025099, - 'XF86_LaunchC': 269025100, - 'XF86_LaunchD': 269025101, - 'XF86_LaunchE': 269025102, - 'XF86_LaunchF': 269025103, - 'XF86_LightBulb': 269025077, - 'XF86_LogGrabInfo': 269024805, - 'XF86_LogOff': 269025121, - 'XF86_LogWindowTree': 269024804, - 'XF86_MacroRecordStart': 268964528, - 'XF86_Mail': 269025049, - 'XF86_MailForward': 269025168, - 'XF86_Market': 269025122, - 'XF86_Meeting': 269025123, - 'XF86_Memo': 269025054, - 'XF86_MenuKB': 269025125, - 'XF86_MenuPB': 269025126, - 'XF86_Messenger': 269025166, - 'XF86_ModeLock': 269025025, - 'XF86_MonBrightnessCycle': 269025031, - 'XF86_MonBrightnessDown': 269025027, - 'XF86_MonBrightnessUp': 269025026, - 'XF86_Music': 269025170, - 'XF86_MyComputer': 269025075, - 'XF86_MySites': 269025127, - 'XF86_New': 269025128, - 'XF86_News': 269025129, - 'XF86_Next_VMode': 269024802, - 'XF86_OfficeHome': 269025130, - 'XF86_Open': 269025131, - 'XF86_OpenURL': 269025080, - 'XF86_Option': 269025132, - 'XF86_Paste': 269025133, - 'XF86_Phone': 269025134, - 'XF86_Pictures': 269025169, - 'XF86_PowerDown': 269025057, - 'XF86_PowerOff': 269025066, - 'XF86_Prev_VMode': 269024803, - 'XF86_Q': 269025136, - 'XF86_RFKill': 269025205, - 'XF86_Red': 269025187, - 'XF86_Refresh': 269025065, - 'XF86_Reload': 269025139, - 'XF86_Reply': 269025138, - 'XF86_RockerDown': 269025060, - 'XF86_RockerEnter': 269025061, - 'XF86_RockerUp': 269025059, - 'XF86_RotateWindows': 269025140, - 'XF86_RotationKB': 269025142, - 'XF86_RotationLockToggle': 269025207, - 'XF86_RotationPB': 269025141, - 'XF86_Save': 269025143, - 'XF86_ScreenSaver': 269025069, - 'XF86_ScrollClick': 269025146, - 'XF86_ScrollDown': 269025145, - 'XF86_ScrollUp': 269025144, - 'XF86_Search': 269025051, - 'XF86_Select': 269025184, - 'XF86_Send': 269025147, - 'XF86_Shop': 269025078, - 'XF86_Sleep': 269025071, - 'XF86_Spell': 269025148, - 'XF86_SplitScreen': 269025149, - 'XF86_Standby': 269025040, - 'XF86_Start': 269025050, - 'XF86_Stop': 269025064, - 'XF86_Subtitle': 269025178, - 'XF86_Support': 269025150, - 'XF86_Suspend': 269025191, - 'XF86_Switch_VT_1': 269024769, - 'XF86_Switch_VT_10': 269024778, - 'XF86_Switch_VT_11': 269024779, - 'XF86_Switch_VT_12': 269024780, - 'XF86_Switch_VT_2': 269024770, - 'XF86_Switch_VT_3': 269024771, - 'XF86_Switch_VT_4': 269024772, - 'XF86_Switch_VT_5': 269024773, - 'XF86_Switch_VT_6': 269024774, - 'XF86_Switch_VT_7': 269024775, - 'XF86_Switch_VT_8': 269024776, - 'XF86_Switch_VT_9': 269024777, - 'XF86_TaskPane': 269025151, - 'XF86_Terminal': 269025152, - 'XF86_Time': 269025183, - 'XF86_ToDoList': 269025055, - 'XF86_Tools': 269025153, - 'XF86_TopMenu': 269025186, - 'XF86_TouchpadOff': 269025201, - 'XF86_TouchpadOn': 269025200, - 'XF86_TouchpadToggle': 269025193, - 'XF86_Travel': 269025154, - 'XF86_UWB': 269025174, - 'XF86_Ungrab': 269024800, - 'XF86_User1KB': 269025157, - 'XF86_User2KB': 269025158, - 'XF86_UserPB': 269025156, - 'XF86_VendorHome': 269025076, - 'XF86_Video': 269025159, - 'XF86_View': 269025185, - 'XF86_WLAN': 269025173, - 'XF86_WWAN': 269025204, - 'XF86_WWW': 269025070, - 'XF86_WakeUp': 269025067, - 'XF86_WebCam': 269025167, - 'XF86_WheelButton': 269025160, - 'XF86_Word': 269025161, - 'XF86_Xfer': 269025162, - 'XF86_Yellow': 269025189, - 'XF86_ZoomIn': 269025163, - 'XF86_ZoomOut': 269025164, - 'XF86_iTouch': 269025120, - 'Xabovedot': 16785034, - 'Y': 89, - 'Yacute': 221, - 'Ybelowdot': 16785140, - 'Ycircumflex': 16777590, - 'Ydiaeresis': 5054, - 'Ygrave': 16785138, - 'Yhook': 16785142, - 'Ytilde': 16785144, - 'Z': 90, - 'Zabovedot': 431, - 'Zacute': 428, - 'Zcaron': 430, - 'Zen_Koho': 65341, - 'Zenkaku': 65320, - 'Zenkaku_Hankaku': 65322, - 'Zstroke': 16777653, - 'a': 97, - 'aacute': 225, - 'abelowdot': 16785057, - 'abovedot': 511, - 'abreve': 483, - 'abreveacute': 16785071, - 'abrevebelowdot': 16785079, - 'abrevegrave': 16785073, - 'abrevehook': 16785075, - 'abrevetilde': 16785077, - 'acircumflex': 226, - 'acircumflexacute': 16785061, - 'acircumflexbelowdot': 16785069, - 'acircumflexgrave': 16785063, - 'acircumflexhook': 16785065, - 'acircumflextilde': 16785067, - 'acute': 180, - 'adiaeresis': 228, - 'ae': 230, - 'agrave': 224, - 'ahook': 16785059, - 'amacron': 992, - 'ampersand': 38, - 'aogonek': 433, - 'apostrophe': 39, - 'approxeq': 16785992, - 'approximate': 2248, - 'aring': 229, - 'asciicircum': 94, - 'asciitilde': 126, - 'asterisk': 42, - 'at': 64, - 'atilde': 227, - 'b': 98, - 'babovedot': 16784899, - 'backslash': 92, - 'ballotcross': 2804, - 'bar': 124, - 'because': 16785973, - 'blank': 2527, - 'botintegral': 2213, - 'botleftparens': 2220, - 'botleftsqbracket': 2216, - 'botleftsummation': 2226, - 'botrightparens': 2222, - 'botrightsqbracket': 2218, - 'botrightsummation': 2230, - 'bott': 2550, - 'botvertsummationconnector': 2228, - 'braceleft': 123, - 'braceright': 125, - 'bracketleft': 91, - 'bracketright': 93, - 'braille_blank': 16787456, - 'braille_dot_1': 65521, - 'braille_dot_10': 65530, - 'braille_dot_2': 65522, - 'braille_dot_3': 65523, - 'braille_dot_4': 65524, - 'braille_dot_5': 65525, - 'braille_dot_6': 65526, - 'braille_dot_7': 65527, - 'braille_dot_8': 65528, - 'braille_dot_9': 65529, - 'braille_dots_1': 16787457, - 'braille_dots_12': 16787459, - 'braille_dots_123': 16787463, - 'braille_dots_1234': 16787471, - 'braille_dots_12345': 16787487, - 'braille_dots_123456': 16787519, - 'braille_dots_1234567': 16787583, - 'braille_dots_12345678': 16787711, - 'braille_dots_1234568': 16787647, - 'braille_dots_123457': 16787551, - 'braille_dots_1234578': 16787679, - 'braille_dots_123458': 16787615, - 'braille_dots_12346': 16787503, - 'braille_dots_123467': 16787567, - 'braille_dots_1234678': 16787695, - 'braille_dots_123468': 16787631, - 'braille_dots_12347': 16787535, - 'braille_dots_123478': 16787663, - 'braille_dots_12348': 16787599, - 'braille_dots_1235': 16787479, - 'braille_dots_12356': 16787511, - 'braille_dots_123567': 16787575, - 'braille_dots_1235678': 16787703, - 'braille_dots_123568': 16787639, - 'braille_dots_12357': 16787543, - 'braille_dots_123578': 16787671, - 'braille_dots_12358': 16787607, - 'braille_dots_1236': 16787495, - 'braille_dots_12367': 16787559, - 'braille_dots_123678': 16787687, - 'braille_dots_12368': 16787623, - 'braille_dots_1237': 16787527, - 'braille_dots_12378': 16787655, - 'braille_dots_1238': 16787591, - 'braille_dots_124': 16787467, - 'braille_dots_1245': 16787483, - 'braille_dots_12456': 16787515, - 'braille_dots_124567': 16787579, - 'braille_dots_1245678': 16787707, - 'braille_dots_124568': 16787643, - 'braille_dots_12457': 16787547, - 'braille_dots_124578': 16787675, - 'braille_dots_12458': 16787611, - 'braille_dots_1246': 16787499, - 'braille_dots_12467': 16787563, - 'braille_dots_124678': 16787691, - 'braille_dots_12468': 16787627, - 'braille_dots_1247': 16787531, - 'braille_dots_12478': 16787659, - 'braille_dots_1248': 16787595, - 'braille_dots_125': 16787475, - 'braille_dots_1256': 16787507, - 'braille_dots_12567': 16787571, - 'braille_dots_125678': 16787699, - 'braille_dots_12568': 16787635, - 'braille_dots_1257': 16787539, - 'braille_dots_12578': 16787667, - 'braille_dots_1258': 16787603, - 'braille_dots_126': 16787491, - 'braille_dots_1267': 16787555, - 'braille_dots_12678': 16787683, - 'braille_dots_1268': 16787619, - 'braille_dots_127': 16787523, - 'braille_dots_1278': 16787651, - 'braille_dots_128': 16787587, - 'braille_dots_13': 16787461, - 'braille_dots_134': 16787469, - 'braille_dots_1345': 16787485, - 'braille_dots_13456': 16787517, - 'braille_dots_134567': 16787581, - 'braille_dots_1345678': 16787709, - 'braille_dots_134568': 16787645, - 'braille_dots_13457': 16787549, - 'braille_dots_134578': 16787677, - 'braille_dots_13458': 16787613, - 'braille_dots_1346': 16787501, - 'braille_dots_13467': 16787565, - 'braille_dots_134678': 16787693, - 'braille_dots_13468': 16787629, - 'braille_dots_1347': 16787533, - 'braille_dots_13478': 16787661, - 'braille_dots_1348': 16787597, - 'braille_dots_135': 16787477, - 'braille_dots_1356': 16787509, - 'braille_dots_13567': 16787573, - 'braille_dots_135678': 16787701, - 'braille_dots_13568': 16787637, - 'braille_dots_1357': 16787541, - 'braille_dots_13578': 16787669, - 'braille_dots_1358': 16787605, - 'braille_dots_136': 16787493, - 'braille_dots_1367': 16787557, - 'braille_dots_13678': 16787685, - 'braille_dots_1368': 16787621, - 'braille_dots_137': 16787525, - 'braille_dots_1378': 16787653, - 'braille_dots_138': 16787589, - 'braille_dots_14': 16787465, - 'braille_dots_145': 16787481, - 'braille_dots_1456': 16787513, - 'braille_dots_14567': 16787577, - 'braille_dots_145678': 16787705, - 'braille_dots_14568': 16787641, - 'braille_dots_1457': 16787545, - 'braille_dots_14578': 16787673, - 'braille_dots_1458': 16787609, - 'braille_dots_146': 16787497, - 'braille_dots_1467': 16787561, - 'braille_dots_14678': 16787689, - 'braille_dots_1468': 16787625, - 'braille_dots_147': 16787529, - 'braille_dots_1478': 16787657, - 'braille_dots_148': 16787593, - 'braille_dots_15': 16787473, - 'braille_dots_156': 16787505, - 'braille_dots_1567': 16787569, - 'braille_dots_15678': 16787697, - 'braille_dots_1568': 16787633, - 'braille_dots_157': 16787537, - 'braille_dots_1578': 16787665, - 'braille_dots_158': 16787601, - 'braille_dots_16': 16787489, - 'braille_dots_167': 16787553, - 'braille_dots_1678': 16787681, - 'braille_dots_168': 16787617, - 'braille_dots_17': 16787521, - 'braille_dots_178': 16787649, - 'braille_dots_18': 16787585, - 'braille_dots_2': 16787458, - 'braille_dots_23': 16787462, - 'braille_dots_234': 16787470, - 'braille_dots_2345': 16787486, - 'braille_dots_23456': 16787518, - 'braille_dots_234567': 16787582, - 'braille_dots_2345678': 16787710, - 'braille_dots_234568': 16787646, - 'braille_dots_23457': 16787550, - 'braille_dots_234578': 16787678, - 'braille_dots_23458': 16787614, - 'braille_dots_2346': 16787502, - 'braille_dots_23467': 16787566, - 'braille_dots_234678': 16787694, - 'braille_dots_23468': 16787630, - 'braille_dots_2347': 16787534, - 'braille_dots_23478': 16787662, - 'braille_dots_2348': 16787598, - 'braille_dots_235': 16787478, - 'braille_dots_2356': 16787510, - 'braille_dots_23567': 16787574, - 'braille_dots_235678': 16787702, - 'braille_dots_23568': 16787638, - 'braille_dots_2357': 16787542, - 'braille_dots_23578': 16787670, - 'braille_dots_2358': 16787606, - 'braille_dots_236': 16787494, - 'braille_dots_2367': 16787558, - 'braille_dots_23678': 16787686, - 'braille_dots_2368': 16787622, - 'braille_dots_237': 16787526, - 'braille_dots_2378': 16787654, - 'braille_dots_238': 16787590, - 'braille_dots_24': 16787466, - 'braille_dots_245': 16787482, - 'braille_dots_2456': 16787514, - 'braille_dots_24567': 16787578, - 'braille_dots_245678': 16787706, - 'braille_dots_24568': 16787642, - 'braille_dots_2457': 16787546, - 'braille_dots_24578': 16787674, - 'braille_dots_2458': 16787610, - 'braille_dots_246': 16787498, - 'braille_dots_2467': 16787562, - 'braille_dots_24678': 16787690, - 'braille_dots_2468': 16787626, - 'braille_dots_247': 16787530, - 'braille_dots_2478': 16787658, - 'braille_dots_248': 16787594, - 'braille_dots_25': 16787474, - 'braille_dots_256': 16787506, - 'braille_dots_2567': 16787570, - 'braille_dots_25678': 16787698, - 'braille_dots_2568': 16787634, - 'braille_dots_257': 16787538, - 'braille_dots_2578': 16787666, - 'braille_dots_258': 16787602, - 'braille_dots_26': 16787490, - 'braille_dots_267': 16787554, - 'braille_dots_2678': 16787682, - 'braille_dots_268': 16787618, - 'braille_dots_27': 16787522, - 'braille_dots_278': 16787650, - 'braille_dots_28': 16787586, - 'braille_dots_3': 16787460, - 'braille_dots_34': 16787468, - 'braille_dots_345': 16787484, - 'braille_dots_3456': 16787516, - 'braille_dots_34567': 16787580, - 'braille_dots_345678': 16787708, - 'braille_dots_34568': 16787644, - 'braille_dots_3457': 16787548, - 'braille_dots_34578': 16787676, - 'braille_dots_3458': 16787612, - 'braille_dots_346': 16787500, - 'braille_dots_3467': 16787564, - 'braille_dots_34678': 16787692, - 'braille_dots_3468': 16787628, - 'braille_dots_347': 16787532, - 'braille_dots_3478': 16787660, - 'braille_dots_348': 16787596, - 'braille_dots_35': 16787476, - 'braille_dots_356': 16787508, - 'braille_dots_3567': 16787572, - 'braille_dots_35678': 16787700, - 'braille_dots_3568': 16787636, - 'braille_dots_357': 16787540, - 'braille_dots_3578': 16787668, - 'braille_dots_358': 16787604, - 'braille_dots_36': 16787492, - 'braille_dots_367': 16787556, - 'braille_dots_3678': 16787684, - 'braille_dots_368': 16787620, - 'braille_dots_37': 16787524, - 'braille_dots_378': 16787652, - 'braille_dots_38': 16787588, - 'braille_dots_4': 16787464, - 'braille_dots_45': 16787480, - 'braille_dots_456': 16787512, - 'braille_dots_4567': 16787576, - 'braille_dots_45678': 16787704, - 'braille_dots_4568': 16787640, - 'braille_dots_457': 16787544, - 'braille_dots_4578': 16787672, - 'braille_dots_458': 16787608, - 'braille_dots_46': 16787496, - 'braille_dots_467': 16787560, - 'braille_dots_4678': 16787688, - 'braille_dots_468': 16787624, - 'braille_dots_47': 16787528, - 'braille_dots_478': 16787656, - 'braille_dots_48': 16787592, - 'braille_dots_5': 16787472, - 'braille_dots_56': 16787504, - 'braille_dots_567': 16787568, - 'braille_dots_5678': 16787696, - 'braille_dots_568': 16787632, - 'braille_dots_57': 16787536, - 'braille_dots_578': 16787664, - 'braille_dots_58': 16787600, - 'braille_dots_6': 16787488, - 'braille_dots_67': 16787552, - 'braille_dots_678': 16787680, - 'braille_dots_68': 16787616, - 'braille_dots_7': 16787520, - 'braille_dots_78': 16787648, - 'braille_dots_8': 16787584, - 'breve': 418, - 'brokenbar': 166, - 'c': 99, - 'c_h': 65187, - 'cabovedot': 741, - 'cacute': 486, - 'careof': 2744, - 'caret': 2812, - 'caron': 439, - 'ccaron': 488, - 'ccedilla': 231, - 'ccircumflex': 742, - 'cedilla': 184, - 'cent': 162, - 'ch': 65184, - 'checkerboard': 2529, - 'checkmark': 2803, - 'circle': 3023, - 'club': 2796, - 'colon': 58, - 'combining_acute': 16777985, - 'combining_belowdot': 16778019, - 'combining_grave': 16777984, - 'combining_hook': 16777993, - 'combining_tilde': 16777987, - 'comma': 44, - 'containsas': 16785931, - 'copyright': 169, - 'cr': 2532, - 'crossinglines': 2542, - 'cuberoot': 16785947, - 'currency': 164, - 'cursor': 2815, - 'd': 100, - 'dabovedot': 16784907, - 'dagger': 2801, - 'dcaron': 495, - 'dead_A': 65153, - 'dead_E': 65155, - 'dead_I': 65157, - 'dead_O': 65159, - 'dead_SCHWA': 65163, - 'dead_U': 65161, - 'dead_a': 65152, - 'dead_abovecomma': 65124, - 'dead_abovedot': 65110, - 'dead_abovereversedcomma': 65125, - 'dead_abovering': 65112, - 'dead_acute': 65105, - 'dead_belowbreve': 65131, - 'dead_belowcircumflex': 65129, - 'dead_belowcomma': 65134, - 'dead_belowdiaeresis': 65132, - 'dead_belowdot': 65120, - 'dead_belowmacron': 65128, - 'dead_belowring': 65127, - 'dead_belowtilde': 65130, - 'dead_breve': 65109, - 'dead_capital_schwa': 65163, - 'dead_caron': 65114, - 'dead_cedilla': 65115, - 'dead_circumflex': 65106, - 'dead_currency': 65135, - 'dead_dasia': 65125, - 'dead_diaeresis': 65111, - 'dead_doubleacute': 65113, - 'dead_doublegrave': 65126, - 'dead_e': 65154, - 'dead_grave': 65104, - 'dead_greek': 65164, - 'dead_hamza': 65165, - 'dead_hook': 65121, - 'dead_horn': 65122, - 'dead_i': 65156, - 'dead_invertedbreve': 65133, - 'dead_iota': 65117, - 'dead_macron': 65108, - 'dead_o': 65158, - 'dead_ogonek': 65116, - 'dead_perispomeni': 65107, - 'dead_psili': 65124, - 'dead_schwa': 65162, - 'dead_semivoiced_sound': 65119, - 'dead_small_schwa': 65162, - 'dead_stroke': 65123, - 'dead_tilde': 65107, - 'dead_u': 65160, - 'dead_voiced_sound': 65118, - 'decimalpoint': 2749, - 'degree': 176, - 'diaeresis': 168, - 'diamond': 2797, - 'digitspace': 2725, - 'dintegral': 16785964, - 'division': 247, - 'dollar': 36, - 'doubbaselinedot': 2735, - 'doubleacute': 445, - 'doubledagger': 2802, - 'doublelowquotemark': 2814, - 'downarrow': 2302, - 'downcaret': 2984, - 'downshoe': 3030, - 'downstile': 3012, - 'downtack': 3010, - 'dstroke': 496, - 'e': 101, - 'eabovedot': 1004, - 'eacute': 233, - 'ebelowdot': 16785081, - 'ecaron': 492, - 'ecircumflex': 234, - 'ecircumflexacute': 16785087, - 'ecircumflexbelowdot': 16785095, - 'ecircumflexgrave': 16785089, - 'ecircumflexhook': 16785091, - 'ecircumflextilde': 16785093, - 'ediaeresis': 235, - 'egrave': 232, - 'ehook': 16785083, - 'eightsubscript': 16785544, - 'eightsuperior': 16785528, - 'elementof': 16785928, - 'ellipsis': 2734, - 'em3space': 2723, - 'em4space': 2724, - 'emacron': 954, - 'emdash': 2729, - 'emfilledcircle': 2782, - 'emfilledrect': 2783, - 'emopencircle': 2766, - 'emopenrectangle': 2767, - 'emptyset': 16785925, - 'emspace': 2721, - 'endash': 2730, - 'enfilledcircbullet': 2790, - 'enfilledsqbullet': 2791, - 'eng': 959, - 'enopencircbullet': 2784, - 'enopensquarebullet': 2785, - 'enspace': 2722, - 'eogonek': 490, - 'equal': 61, - 'eth': 240, - 'etilde': 16785085, - 'exclam': 33, - 'exclamdown': 161, - 'ezh': 16777874, - 'f': 102, - 'fabovedot': 16784927, - 'femalesymbol': 2808, - 'ff': 2531, - 'figdash': 2747, - 'filledlefttribullet': 2780, - 'filledrectbullet': 2779, - 'filledrighttribullet': 2781, - 'filledtribulletdown': 2793, - 'filledtribulletup': 2792, - 'fiveeighths': 2757, - 'fivesixths': 2743, - 'fivesubscript': 16785541, - 'fivesuperior': 16785525, - 'fourfifths': 2741, - 'foursubscript': 16785540, - 'foursuperior': 16785524, - 'fourthroot': 16785948, - 'function': 2294, - 'g': 103, - 'gabovedot': 757, - 'gbreve': 699, - 'gcaron': 16777703, - 'gcedilla': 955, - 'gcircumflex': 760, - 'grave': 96, - 'greater': 62, - 'greaterthanequal': 2238, - 'guillemetleft': 171, - 'guillemetright': 187, - 'guillemotleft': 171, - 'guillemotright': 187, - 'h': 104, - 'hairspace': 2728, - 'hcircumflex': 694, - 'heart': 2798, - 'hebrew_aleph': 3296, - 'hebrew_ayin': 3314, - 'hebrew_bet': 3297, - 'hebrew_beth': 3297, - 'hebrew_chet': 3303, - 'hebrew_dalet': 3299, - 'hebrew_daleth': 3299, - 'hebrew_doublelowline': 3295, - 'hebrew_finalkaph': 3306, - 'hebrew_finalmem': 3309, - 'hebrew_finalnun': 3311, - 'hebrew_finalpe': 3315, - 'hebrew_finalzade': 3317, - 'hebrew_finalzadi': 3317, - 'hebrew_gimel': 3298, - 'hebrew_gimmel': 3298, - 'hebrew_he': 3300, - 'hebrew_het': 3303, - 'hebrew_kaph': 3307, - 'hebrew_kuf': 3319, - 'hebrew_lamed': 3308, - 'hebrew_mem': 3310, - 'hebrew_nun': 3312, - 'hebrew_pe': 3316, - 'hebrew_qoph': 3319, - 'hebrew_resh': 3320, - 'hebrew_samech': 3313, - 'hebrew_samekh': 3313, - 'hebrew_shin': 3321, - 'hebrew_taf': 3322, - 'hebrew_taw': 3322, - 'hebrew_tet': 3304, - 'hebrew_teth': 3304, - 'hebrew_waw': 3301, - 'hebrew_yod': 3305, - 'hebrew_zade': 3318, - 'hebrew_zadi': 3318, - 'hebrew_zain': 3302, - 'hebrew_zayin': 3302, - 'hexagram': 2778, - 'horizconnector': 2211, - 'horizlinescan1': 2543, - 'horizlinescan3': 2544, - 'horizlinescan5': 2545, - 'horizlinescan7': 2546, - 'horizlinescan9': 2547, - 'hstroke': 689, - 'ht': 2530, - 'hyphen': 173, - 'i': 105, - 'iacute': 237, - 'ibelowdot': 16785099, - 'ibreve': 16777517, - 'icircumflex': 238, - 'identical': 2255, - 'idiaeresis': 239, - 'idotless': 697, - 'ifonlyif': 2253, - 'igrave': 236, - 'ihook': 16785097, - 'imacron': 1007, - 'implies': 2254, - 'includedin': 2266, - 'includes': 2267, - 'infinity': 2242, - 'integral': 2239, - 'intersection': 2268, - 'iogonek': 999, - 'itilde': 949, - 'j': 106, - 'jcircumflex': 700, - 'jot': 3018, - 'k': 107, - 'kana_A': 1201, - 'kana_CHI': 1217, - 'kana_E': 1204, - 'kana_FU': 1228, - 'kana_HA': 1226, - 'kana_HE': 1229, - 'kana_HI': 1227, - 'kana_HO': 1230, - 'kana_HU': 1228, - 'kana_I': 1202, - 'kana_KA': 1206, - 'kana_KE': 1209, - 'kana_KI': 1207, - 'kana_KO': 1210, - 'kana_KU': 1208, - 'kana_MA': 1231, - 'kana_ME': 1234, - 'kana_MI': 1232, - 'kana_MO': 1235, - 'kana_MU': 1233, - 'kana_N': 1245, - 'kana_NA': 1221, - 'kana_NE': 1224, - 'kana_NI': 1222, - 'kana_NO': 1225, - 'kana_NU': 1223, - 'kana_O': 1205, - 'kana_RA': 1239, - 'kana_RE': 1242, - 'kana_RI': 1240, - 'kana_RO': 1243, - 'kana_RU': 1241, - 'kana_SA': 1211, - 'kana_SE': 1214, - 'kana_SHI': 1212, - 'kana_SO': 1215, - 'kana_SU': 1213, - 'kana_TA': 1216, - 'kana_TE': 1219, - 'kana_TI': 1217, - 'kana_TO': 1220, - 'kana_TSU': 1218, - 'kana_TU': 1218, - 'kana_U': 1203, - 'kana_WA': 1244, - 'kana_WO': 1190, - 'kana_YA': 1236, - 'kana_YO': 1238, - 'kana_YU': 1237, - 'kana_a': 1191, - 'kana_closingbracket': 1187, - 'kana_comma': 1188, - 'kana_conjunctive': 1189, - 'kana_e': 1194, - 'kana_fullstop': 1185, - 'kana_i': 1192, - 'kana_middledot': 1189, - 'kana_o': 1195, - 'kana_openingbracket': 1186, - 'kana_switch': 65406, - 'kana_tsu': 1199, - 'kana_tu': 1199, - 'kana_u': 1193, - 'kana_ya': 1196, - 'kana_yo': 1198, - 'kana_yu': 1197, - 'kappa': 930, - 'kcedilla': 1011, - 'kra': 930, - 'l': 108, - 'lacute': 485, - 'latincross': 2777, - 'lbelowdot': 16784951, - 'lcaron': 437, - 'lcedilla': 950, - 'leftanglebracket': 2748, - 'leftarrow': 2299, - 'leftcaret': 2979, - 'leftdoublequotemark': 2770, - 'leftmiddlecurlybrace': 2223, - 'leftopentriangle': 2764, - 'leftpointer': 2794, - 'leftradical': 2209, - 'leftshoe': 3034, - 'leftsinglequotemark': 2768, - 'leftt': 2548, - 'lefttack': 3036, - 'less': 60, - 'lessthanequal': 2236, - 'lf': 2533, - 'logicaland': 2270, - 'logicalor': 2271, - 'lowleftcorner': 2541, - 'lowrightcorner': 2538, - 'lstroke': 435, - 'm': 109, - 'mabovedot': 16784961, - 'macron': 175, - 'malesymbol': 2807, - 'maltesecross': 2800, - 'marker': 2751, - 'masculine': 186, - 'minus': 45, - 'minutes': 2774, - 'mu': 181, - 'multiply': 215, - 'musicalflat': 2806, - 'musicalsharp': 2805, - 'n': 110, - 'nabla': 2245, - 'nacute': 497, - 'ncaron': 498, - 'ncedilla': 1009, - 'ninesubscript': 16785545, - 'ninesuperior': 16785529, - 'nl': 2536, - 'nobreakspace': 160, - 'notapproxeq': 16785991, - 'notelementof': 16785929, - 'notequal': 2237, - 'notidentical': 16786018, - 'notsign': 172, - 'ntilde': 241, - 'numbersign': 35, - 'numerosign': 1712, - 'o': 111, - 'oacute': 243, - 'obarred': 16777845, - 'obelowdot': 16785101, - 'ocaron': 16777682, - 'ocircumflex': 244, - 'ocircumflexacute': 16785105, - 'ocircumflexbelowdot': 16785113, - 'ocircumflexgrave': 16785107, - 'ocircumflexhook': 16785109, - 'ocircumflextilde': 16785111, - 'odiaeresis': 246, - 'odoubleacute': 501, - 'oe': 5053, - 'ogonek': 434, - 'ograve': 242, - 'ohook': 16785103, - 'ohorn': 16777633, - 'ohornacute': 16785115, - 'ohornbelowdot': 16785123, - 'ohorngrave': 16785117, - 'ohornhook': 16785119, - 'ohorntilde': 16785121, - 'omacron': 1010, - 'oneeighth': 2755, - 'onefifth': 2738, - 'onehalf': 189, - 'onequarter': 188, - 'onesixth': 2742, - 'onesubscript': 16785537, - 'onesuperior': 185, - 'onethird': 2736, - 'ooblique': 248, - 'openrectbullet': 2786, - 'openstar': 2789, - 'opentribulletdown': 2788, - 'opentribulletup': 2787, - 'ordfeminine': 170, - 'ordmasculine': 186, - 'oslash': 248, - 'otilde': 245, - 'overbar': 3008, - 'overline': 1150, - 'p': 112, - 'pabovedot': 16784983, - 'paragraph': 182, - 'parenleft': 40, - 'parenright': 41, - 'partdifferential': 16785922, - 'partialderivative': 2287, - 'percent': 37, - 'period': 46, - 'periodcentered': 183, - 'permille': 2773, - 'phonographcopyright': 2811, - 'plus': 43, - 'plusminus': 177, - 'prescription': 2772, - 'prolongedsound': 1200, - 'punctspace': 2726, - 'q': 113, - 'quad': 3020, - 'question': 63, - 'questiondown': 191, - 'quotedbl': 34, - 'quoteleft': 96, - 'quoteright': 39, - 'r': 114, - 'racute': 480, - 'radical': 2262, - 'rcaron': 504, - 'rcedilla': 947, - 'registered': 174, - 'rightanglebracket': 2750, - 'rightarrow': 2301, - 'rightcaret': 2982, - 'rightdoublequotemark': 2771, - 'rightmiddlecurlybrace': 2224, - 'rightmiddlesummation': 2231, - 'rightopentriangle': 2765, - 'rightpointer': 2795, - 'rightshoe': 3032, - 'rightsinglequotemark': 2769, - 'rightt': 2549, - 'righttack': 3068, - 's': 115, - 'sabovedot': 16784993, - 'sacute': 438, - 'scaron': 441, - 'scedilla': 442, - 'schwa': 16777817, - 'scircumflex': 766, - 'script_switch': 65406, - 'seconds': 2775, - 'section': 167, - 'semicolon': 59, - 'semivoicedsound': 1247, - 'seveneighths': 2758, - 'sevensubscript': 16785543, - 'sevensuperior': 16785527, - 'signaturemark': 2762, - 'signifblank': 2732, - 'similarequal': 2249, - 'singlelowquotemark': 2813, - 'sixsubscript': 16785542, - 'sixsuperior': 16785526, - 'slash': 47, - 'soliddiamond': 2528, - 'space': 32, - 'squareroot': 16785946, - 'ssharp': 223, - 'sterling': 163, - 'stricteq': 16786019, - 't': 116, - 'tabovedot': 16785003, - 'tcaron': 443, - 'tcedilla': 510, - 'telephone': 2809, - 'telephonerecorder': 2810, - 'therefore': 2240, - 'thinspace': 2727, - 'thorn': 254, - 'threeeighths': 2756, - 'threefifths': 2740, - 'threequarters': 190, - 'threesubscript': 16785539, - 'threesuperior': 179, - 'tintegral': 16785965, - 'topintegral': 2212, - 'topleftparens': 2219, - 'topleftradical': 2210, - 'topleftsqbracket': 2215, - 'topleftsummation': 2225, - 'toprightparens': 2221, - 'toprightsqbracket': 2217, - 'toprightsummation': 2229, - 'topt': 2551, - 'topvertsummationconnector': 2227, - 'trademark': 2761, - 'trademarkincircle': 2763, - 'tslash': 956, - 'twofifths': 2739, - 'twosubscript': 16785538, - 'twosuperior': 178, - 'twothirds': 2737, - 'u': 117, - 'uacute': 250, - 'ubelowdot': 16785125, - 'ubreve': 765, - 'ucircumflex': 251, - 'udiaeresis': 252, - 'udoubleacute': 507, - 'ugrave': 249, - 'uhook': 16785127, - 'uhorn': 16777648, - 'uhornacute': 16785129, - 'uhornbelowdot': 16785137, - 'uhorngrave': 16785131, - 'uhornhook': 16785133, - 'uhorntilde': 16785135, - 'umacron': 1022, - 'underbar': 3014, - 'underscore': 95, - 'union': 2269, - 'uogonek': 1017, - 'uparrow': 2300, - 'upcaret': 2985, - 'upleftcorner': 2540, - 'uprightcorner': 2539, - 'upshoe': 3011, - 'upstile': 3027, - 'uptack': 3022, - 'uring': 505, - 'utilde': 1021, - 'v': 118, - 'variation': 2241, - 'vertbar': 2552, - 'vertconnector': 2214, - 'voicedsound': 1246, - 'vt': 2537, - 'w': 119, - 'wacute': 16785027, - 'wcircumflex': 16777589, - 'wdiaeresis': 16785029, - 'wgrave': 16785025, - 'x': 120, - 'xabovedot': 16785035, - 'y': 121, - 'yacute': 253, - 'ybelowdot': 16785141, - 'ycircumflex': 16777591, - 'ydiaeresis': 255, - 'yen': 165, - 'ygrave': 16785139, - 'yhook': 16785143, - 'ytilde': 16785145, - 'z': 122, - 'zabovedot': 447, - 'zacute': 444, - 'zcaron': 446, - 'zerosubscript': 16785536, - 'zerosuperior': 16785520, - 'zstroke': 16777654} +key_symbols = { + "0": 48, + "1": 49, + "2": 50, + "3": 51, + "3270_AltCursor": 64784, + "3270_Attn": 64782, + "3270_BackTab": 64773, + "3270_ChangeScreen": 64793, + "3270_Copy": 64789, + "3270_CursorBlink": 64783, + "3270_CursorSelect": 64796, + "3270_DeleteWord": 64794, + "3270_Duplicate": 64769, + "3270_Enter": 64798, + "3270_EraseEOF": 64774, + "3270_EraseInput": 64775, + "3270_ExSelect": 64795, + "3270_FieldMark": 64770, + "3270_Ident": 64787, + "3270_Jump": 64786, + "3270_KeyClick": 64785, + "3270_Left2": 64772, + "3270_PA1": 64778, + "3270_PA2": 64779, + "3270_PA3": 64780, + "3270_Play": 64790, + "3270_PrintScreen": 64797, + "3270_Quit": 64777, + "3270_Record": 64792, + "3270_Reset": 64776, + "3270_Right2": 64771, + "3270_Rule": 64788, + "3270_Setup": 64791, + "3270_Test": 64781, + "4": 52, + "5": 53, + "6": 54, + "7": 55, + "8": 56, + "9": 57, + "A": 65, + "AE": 198, + "Aacute": 193, + "Abelowdot": 16785056, + "Abreve": 451, + "Abreveacute": 16785070, + "Abrevebelowdot": 16785078, + "Abrevegrave": 16785072, + "Abrevehook": 16785074, + "Abrevetilde": 16785076, + "AccessX_Enable": 65136, + "AccessX_Feedback_Enable": 65137, + "Acircumflex": 194, + "Acircumflexacute": 16785060, + "Acircumflexbelowdot": 16785068, + "Acircumflexgrave": 16785062, + "Acircumflexhook": 16785064, + "Acircumflextilde": 16785066, + "Adiaeresis": 196, + "Agrave": 192, + "Ahook": 16785058, + "Alt_L": 65513, + "Alt_R": 65514, + "Amacron": 960, + "Aogonek": 417, + "Arabic_0": 16778848, + "Arabic_1": 16778849, + "Arabic_2": 16778850, + "Arabic_3": 16778851, + "Arabic_4": 16778852, + "Arabic_5": 16778853, + "Arabic_6": 16778854, + "Arabic_7": 16778855, + "Arabic_8": 16778856, + "Arabic_9": 16778857, + "Arabic_ain": 1497, + "Arabic_alef": 1479, + "Arabic_alefmaksura": 1513, + "Arabic_beh": 1480, + "Arabic_comma": 1452, + "Arabic_dad": 1494, + "Arabic_dal": 1487, + "Arabic_damma": 1519, + "Arabic_dammatan": 1516, + "Arabic_ddal": 16778888, + "Arabic_farsi_yeh": 16778956, + "Arabic_fatha": 1518, + "Arabic_fathatan": 1515, + "Arabic_feh": 1505, + "Arabic_fullstop": 16778964, + "Arabic_gaf": 16778927, + "Arabic_ghain": 1498, + "Arabic_ha": 1511, + "Arabic_hah": 1485, + "Arabic_hamza": 1473, + "Arabic_hamza_above": 16778836, + "Arabic_hamza_below": 16778837, + "Arabic_hamzaonalef": 1475, + "Arabic_hamzaonwaw": 1476, + "Arabic_hamzaonyeh": 1478, + "Arabic_hamzaunderalef": 1477, + "Arabic_heh": 1511, + "Arabic_heh_doachashmee": 16778942, + "Arabic_heh_goal": 16778945, + "Arabic_jeem": 1484, + "Arabic_jeh": 16778904, + "Arabic_kaf": 1507, + "Arabic_kasra": 1520, + "Arabic_kasratan": 1517, + "Arabic_keheh": 16778921, + "Arabic_khah": 1486, + "Arabic_lam": 1508, + "Arabic_madda_above": 16778835, + "Arabic_maddaonalef": 1474, + "Arabic_meem": 1509, + "Arabic_noon": 1510, + "Arabic_noon_ghunna": 16778938, + "Arabic_peh": 16778878, + "Arabic_percent": 16778858, + "Arabic_qaf": 1506, + "Arabic_question_mark": 1471, + "Arabic_ra": 1489, + "Arabic_rreh": 16778897, + "Arabic_sad": 1493, + "Arabic_seen": 1491, + "Arabic_semicolon": 1467, + "Arabic_shadda": 1521, + "Arabic_sheen": 1492, + "Arabic_sukun": 1522, + "Arabic_superscript_alef": 16778864, + "Arabic_switch": 65406, + "Arabic_tah": 1495, + "Arabic_tatweel": 1504, + "Arabic_tcheh": 16778886, + "Arabic_teh": 1482, + "Arabic_tehmarbuta": 1481, + "Arabic_thal": 1488, + "Arabic_theh": 1483, + "Arabic_tteh": 16778873, + "Arabic_veh": 16778916, + "Arabic_waw": 1512, + "Arabic_yeh": 1514, + "Arabic_yeh_baree": 16778962, + "Arabic_zah": 1496, + "Arabic_zain": 1490, + "Aring": 197, + "Armenian_AT": 16778552, + "Armenian_AYB": 16778545, + "Armenian_BEN": 16778546, + "Armenian_CHA": 16778569, + "Armenian_DA": 16778548, + "Armenian_DZA": 16778561, + "Armenian_E": 16778551, + "Armenian_FE": 16778582, + "Armenian_GHAT": 16778562, + "Armenian_GIM": 16778547, + "Armenian_HI": 16778565, + "Armenian_HO": 16778560, + "Armenian_INI": 16778555, + "Armenian_JE": 16778571, + "Armenian_KE": 16778580, + "Armenian_KEN": 16778559, + "Armenian_KHE": 16778557, + "Armenian_LYUN": 16778556, + "Armenian_MEN": 16778564, + "Armenian_NU": 16778566, + "Armenian_O": 16778581, + "Armenian_PE": 16778570, + "Armenian_PYUR": 16778579, + "Armenian_RA": 16778572, + "Armenian_RE": 16778576, + "Armenian_SE": 16778573, + "Armenian_SHA": 16778567, + "Armenian_TCHE": 16778563, + "Armenian_TO": 16778553, + "Armenian_TSA": 16778558, + "Armenian_TSO": 16778577, + "Armenian_TYUN": 16778575, + "Armenian_VEV": 16778574, + "Armenian_VO": 16778568, + "Armenian_VYUN": 16778578, + "Armenian_YECH": 16778549, + "Armenian_ZA": 16778550, + "Armenian_ZHE": 16778554, + "Armenian_accent": 16778587, + "Armenian_amanak": 16778588, + "Armenian_apostrophe": 16778586, + "Armenian_at": 16778600, + "Armenian_ayb": 16778593, + "Armenian_ben": 16778594, + "Armenian_but": 16778589, + "Armenian_cha": 16778617, + "Armenian_da": 16778596, + "Armenian_dza": 16778609, + "Armenian_e": 16778599, + "Armenian_exclam": 16778588, + "Armenian_fe": 16778630, + "Armenian_full_stop": 16778633, + "Armenian_ghat": 16778610, + "Armenian_gim": 16778595, + "Armenian_hi": 16778613, + "Armenian_ho": 16778608, + "Armenian_hyphen": 16778634, + "Armenian_ini": 16778603, + "Armenian_je": 16778619, + "Armenian_ke": 16778628, + "Armenian_ken": 16778607, + "Armenian_khe": 16778605, + "Armenian_ligature_ew": 16778631, + "Armenian_lyun": 16778604, + "Armenian_men": 16778612, + "Armenian_nu": 16778614, + "Armenian_o": 16778629, + "Armenian_paruyk": 16778590, + "Armenian_pe": 16778618, + "Armenian_pyur": 16778627, + "Armenian_question": 16778590, + "Armenian_ra": 16778620, + "Armenian_re": 16778624, + "Armenian_se": 16778621, + "Armenian_separation_mark": 16778589, + "Armenian_sha": 16778615, + "Armenian_shesht": 16778587, + "Armenian_tche": 16778611, + "Armenian_to": 16778601, + "Armenian_tsa": 16778606, + "Armenian_tso": 16778625, + "Armenian_tyun": 16778623, + "Armenian_verjaket": 16778633, + "Armenian_vev": 16778622, + "Armenian_vo": 16778616, + "Armenian_vyun": 16778626, + "Armenian_yech": 16778597, + "Armenian_yentamna": 16778634, + "Armenian_za": 16778598, + "Armenian_zhe": 16778602, + "Atilde": 195, + "AudibleBell_Enable": 65146, + "B": 66, + "Babovedot": 16784898, + "BackSpace": 65288, + "Begin": 65368, + "BounceKeys_Enable": 65140, + "Break": 65387, + "Byelorussian_SHORTU": 1726, + "Byelorussian_shortu": 1710, + "C": 67, + "CH": 65186, + "C_H": 65189, + "C_h": 65188, + "Cabovedot": 709, + "Cacute": 454, + "Cancel": 65385, + "Caps_Lock": 65509, + "Ccaron": 456, + "Ccedilla": 199, + "Ccircumflex": 710, + "Ch": 65185, + "Clear": 65291, + "Codeinput": 65335, + "ColonSign": 16785569, + "Control_L": 65507, + "Control_R": 65508, + "CruzeiroSign": 16785570, + "Cyrillic_A": 1761, + "Cyrillic_BE": 1762, + "Cyrillic_CHE": 1790, + "Cyrillic_CHE_descender": 16778422, + "Cyrillic_CHE_vertstroke": 16778424, + "Cyrillic_DE": 1764, + "Cyrillic_DZHE": 1727, + "Cyrillic_E": 1788, + "Cyrillic_EF": 1766, + "Cyrillic_EL": 1772, + "Cyrillic_EM": 1773, + "Cyrillic_EN": 1774, + "Cyrillic_EN_descender": 16778402, + "Cyrillic_ER": 1778, + "Cyrillic_ES": 1779, + "Cyrillic_GHE": 1767, + "Cyrillic_GHE_bar": 16778386, + "Cyrillic_HA": 1768, + "Cyrillic_HARDSIGN": 1791, + "Cyrillic_HA_descender": 16778418, + "Cyrillic_I": 1769, + "Cyrillic_IE": 1765, + "Cyrillic_IO": 1715, + "Cyrillic_I_macron": 16778466, + "Cyrillic_JE": 1720, + "Cyrillic_KA": 1771, + "Cyrillic_KA_descender": 16778394, + "Cyrillic_KA_vertstroke": 16778396, + "Cyrillic_LJE": 1721, + "Cyrillic_NJE": 1722, + "Cyrillic_O": 1775, + "Cyrillic_O_bar": 16778472, + "Cyrillic_PE": 1776, + "Cyrillic_SCHWA": 16778456, + "Cyrillic_SHA": 1787, + "Cyrillic_SHCHA": 1789, + "Cyrillic_SHHA": 16778426, + "Cyrillic_SHORTI": 1770, + "Cyrillic_SOFTSIGN": 1784, + "Cyrillic_TE": 1780, + "Cyrillic_TSE": 1763, + "Cyrillic_U": 1781, + "Cyrillic_U_macron": 16778478, + "Cyrillic_U_straight": 16778414, + "Cyrillic_U_straight_bar": 16778416, + "Cyrillic_VE": 1783, + "Cyrillic_YA": 1777, + "Cyrillic_YERU": 1785, + "Cyrillic_YU": 1760, + "Cyrillic_ZE": 1786, + "Cyrillic_ZHE": 1782, + "Cyrillic_ZHE_descender": 16778390, + "Cyrillic_a": 1729, + "Cyrillic_be": 1730, + "Cyrillic_che": 1758, + "Cyrillic_che_descender": 16778423, + "Cyrillic_che_vertstroke": 16778425, + "Cyrillic_de": 1732, + "Cyrillic_dzhe": 1711, + "Cyrillic_e": 1756, + "Cyrillic_ef": 1734, + "Cyrillic_el": 1740, + "Cyrillic_em": 1741, + "Cyrillic_en": 1742, + "Cyrillic_en_descender": 16778403, + "Cyrillic_er": 1746, + "Cyrillic_es": 1747, + "Cyrillic_ghe": 1735, + "Cyrillic_ghe_bar": 16778387, + "Cyrillic_ha": 1736, + "Cyrillic_ha_descender": 16778419, + "Cyrillic_hardsign": 1759, + "Cyrillic_i": 1737, + "Cyrillic_i_macron": 16778467, + "Cyrillic_ie": 1733, + "Cyrillic_io": 1699, + "Cyrillic_je": 1704, + "Cyrillic_ka": 1739, + "Cyrillic_ka_descender": 16778395, + "Cyrillic_ka_vertstroke": 16778397, + "Cyrillic_lje": 1705, + "Cyrillic_nje": 1706, + "Cyrillic_o": 1743, + "Cyrillic_o_bar": 16778473, + "Cyrillic_pe": 1744, + "Cyrillic_schwa": 16778457, + "Cyrillic_sha": 1755, + "Cyrillic_shcha": 1757, + "Cyrillic_shha": 16778427, + "Cyrillic_shorti": 1738, + "Cyrillic_softsign": 1752, + "Cyrillic_te": 1748, + "Cyrillic_tse": 1731, + "Cyrillic_u": 1749, + "Cyrillic_u_macron": 16778479, + "Cyrillic_u_straight": 16778415, + "Cyrillic_u_straight_bar": 16778417, + "Cyrillic_ve": 1751, + "Cyrillic_ya": 1745, + "Cyrillic_yeru": 1753, + "Cyrillic_yu": 1728, + "Cyrillic_ze": 1754, + "Cyrillic_zhe": 1750, + "Cyrillic_zhe_descender": 16778391, + "D": 68, + "Dabovedot": 16784906, + "Dcaron": 463, + "Delete": 65535, + "DongSign": 16785579, + "Down": 65364, + "Dstroke": 464, + "E": 69, + "ENG": 957, + "ETH": 208, + "EZH": 16777655, + "Eabovedot": 972, + "Eacute": 201, + "Ebelowdot": 16785080, + "Ecaron": 460, + "Ecircumflex": 202, + "Ecircumflexacute": 16785086, + "Ecircumflexbelowdot": 16785094, + "Ecircumflexgrave": 16785088, + "Ecircumflexhook": 16785090, + "Ecircumflextilde": 16785092, + "EcuSign": 16785568, + "Ediaeresis": 203, + "Egrave": 200, + "Ehook": 16785082, + "Eisu_Shift": 65327, + "Eisu_toggle": 65328, + "Emacron": 938, + "End": 65367, + "Eogonek": 458, + "Escape": 65307, + "Eth": 208, + "Etilde": 16785084, + "EuroSign": 8364, + "Execute": 65378, + "F": 70, + "F1": 65470, + "F10": 65479, + "F11": 65480, + "F12": 65481, + "F13": 65482, + "F14": 65483, + "F15": 65484, + "F16": 65485, + "F17": 65486, + "F18": 65487, + "F19": 65488, + "F2": 65471, + "F20": 65489, + "F21": 65490, + "F22": 65491, + "F23": 65492, + "F24": 65493, + "F25": 65494, + "F26": 65495, + "F27": 65496, + "F28": 65497, + "F29": 65498, + "F3": 65472, + "F30": 65499, + "F31": 65500, + "F32": 65501, + "F33": 65502, + "F34": 65503, + "F35": 65504, + "F4": 65473, + "F5": 65474, + "F6": 65475, + "F7": 65476, + "F8": 65477, + "F9": 65478, + "FFrancSign": 16785571, + "Fabovedot": 16784926, + "Farsi_0": 16778992, + "Farsi_1": 16778993, + "Farsi_2": 16778994, + "Farsi_3": 16778995, + "Farsi_4": 16778996, + "Farsi_5": 16778997, + "Farsi_6": 16778998, + "Farsi_7": 16778999, + "Farsi_8": 16779000, + "Farsi_9": 16779001, + "Farsi_yeh": 16778956, + "Find": 65384, + "First_Virtual_Screen": 65232, + "G": 71, + "Gabovedot": 725, + "Gbreve": 683, + "Gcaron": 16777702, + "Gcedilla": 939, + "Gcircumflex": 728, + "Georgian_an": 16781520, + "Georgian_ban": 16781521, + "Georgian_can": 16781546, + "Georgian_char": 16781549, + "Georgian_chin": 16781545, + "Georgian_cil": 16781548, + "Georgian_don": 16781523, + "Georgian_en": 16781524, + "Georgian_fi": 16781558, + "Georgian_gan": 16781522, + "Georgian_ghan": 16781542, + "Georgian_hae": 16781552, + "Georgian_har": 16781556, + "Georgian_he": 16781553, + "Georgian_hie": 16781554, + "Georgian_hoe": 16781557, + "Georgian_in": 16781528, + "Georgian_jhan": 16781551, + "Georgian_jil": 16781547, + "Georgian_kan": 16781529, + "Georgian_khar": 16781541, + "Georgian_las": 16781530, + "Georgian_man": 16781531, + "Georgian_nar": 16781532, + "Georgian_on": 16781533, + "Georgian_par": 16781534, + "Georgian_phar": 16781540, + "Georgian_qar": 16781543, + "Georgian_rae": 16781536, + "Georgian_san": 16781537, + "Georgian_shin": 16781544, + "Georgian_tan": 16781527, + "Georgian_tar": 16781538, + "Georgian_un": 16781539, + "Georgian_vin": 16781525, + "Georgian_we": 16781555, + "Georgian_xan": 16781550, + "Georgian_zen": 16781526, + "Georgian_zhar": 16781535, + "Greek_ALPHA": 1985, + "Greek_ALPHAaccent": 1953, + "Greek_BETA": 1986, + "Greek_CHI": 2007, + "Greek_DELTA": 1988, + "Greek_EPSILON": 1989, + "Greek_EPSILONaccent": 1954, + "Greek_ETA": 1991, + "Greek_ETAaccent": 1955, + "Greek_GAMMA": 1987, + "Greek_IOTA": 1993, + "Greek_IOTAaccent": 1956, + "Greek_IOTAdiaeresis": 1957, + "Greek_IOTAdieresis": 1957, + "Greek_KAPPA": 1994, + "Greek_LAMBDA": 1995, + "Greek_LAMDA": 1995, + "Greek_MU": 1996, + "Greek_NU": 1997, + "Greek_OMEGA": 2009, + "Greek_OMEGAaccent": 1963, + "Greek_OMICRON": 1999, + "Greek_OMICRONaccent": 1959, + "Greek_PHI": 2006, + "Greek_PI": 2000, + "Greek_PSI": 2008, + "Greek_RHO": 2001, + "Greek_SIGMA": 2002, + "Greek_TAU": 2004, + "Greek_THETA": 1992, + "Greek_UPSILON": 2005, + "Greek_UPSILONaccent": 1960, + "Greek_UPSILONdieresis": 1961, + "Greek_XI": 1998, + "Greek_ZETA": 1990, + "Greek_accentdieresis": 1966, + "Greek_alpha": 2017, + "Greek_alphaaccent": 1969, + "Greek_beta": 2018, + "Greek_chi": 2039, + "Greek_delta": 2020, + "Greek_epsilon": 2021, + "Greek_epsilonaccent": 1970, + "Greek_eta": 2023, + "Greek_etaaccent": 1971, + "Greek_finalsmallsigma": 2035, + "Greek_gamma": 2019, + "Greek_horizbar": 1967, + "Greek_iota": 2025, + "Greek_iotaaccent": 1972, + "Greek_iotaaccentdieresis": 1974, + "Greek_iotadieresis": 1973, + "Greek_kappa": 2026, + "Greek_lambda": 2027, + "Greek_lamda": 2027, + "Greek_mu": 2028, + "Greek_nu": 2029, + "Greek_omega": 2041, + "Greek_omegaaccent": 1979, + "Greek_omicron": 2031, + "Greek_omicronaccent": 1975, + "Greek_phi": 2038, + "Greek_pi": 2032, + "Greek_psi": 2040, + "Greek_rho": 2033, + "Greek_sigma": 2034, + "Greek_switch": 65406, + "Greek_tau": 2036, + "Greek_theta": 2024, + "Greek_upsilon": 2037, + "Greek_upsilonaccent": 1976, + "Greek_upsilonaccentdieresis": 1978, + "Greek_upsilondieresis": 1977, + "Greek_xi": 2030, + "Greek_zeta": 2022, + "H": 72, + "Hangul": 65329, + "Hangul_A": 3775, + "Hangul_AE": 3776, + "Hangul_AraeA": 3830, + "Hangul_AraeAE": 3831, + "Hangul_Banja": 65337, + "Hangul_Cieuc": 3770, + "Hangul_Codeinput": 65335, + "Hangul_Dikeud": 3751, + "Hangul_E": 3780, + "Hangul_EO": 3779, + "Hangul_EU": 3793, + "Hangul_End": 65331, + "Hangul_Hanja": 65332, + "Hangul_Hieuh": 3774, + "Hangul_I": 3795, + "Hangul_Ieung": 3767, + "Hangul_J_Cieuc": 3818, + "Hangul_J_Dikeud": 3802, + "Hangul_J_Hieuh": 3822, + "Hangul_J_Ieung": 3816, + "Hangul_J_Jieuj": 3817, + "Hangul_J_Khieuq": 3819, + "Hangul_J_Kiyeog": 3796, + "Hangul_J_KiyeogSios": 3798, + "Hangul_J_KkogjiDalrinIeung": 3833, + "Hangul_J_Mieum": 3811, + "Hangul_J_Nieun": 3799, + "Hangul_J_NieunHieuh": 3801, + "Hangul_J_NieunJieuj": 3800, + "Hangul_J_PanSios": 3832, + "Hangul_J_Phieuf": 3821, + "Hangul_J_Pieub": 3812, + "Hangul_J_PieubSios": 3813, + "Hangul_J_Rieul": 3803, + "Hangul_J_RieulHieuh": 3810, + "Hangul_J_RieulKiyeog": 3804, + "Hangul_J_RieulMieum": 3805, + "Hangul_J_RieulPhieuf": 3809, + "Hangul_J_RieulPieub": 3806, + "Hangul_J_RieulSios": 3807, + "Hangul_J_RieulTieut": 3808, + "Hangul_J_Sios": 3814, + "Hangul_J_SsangKiyeog": 3797, + "Hangul_J_SsangSios": 3815, + "Hangul_J_Tieut": 3820, + "Hangul_J_YeorinHieuh": 3834, + "Hangul_Jamo": 65333, + "Hangul_Jeonja": 65336, + "Hangul_Jieuj": 3768, + "Hangul_Khieuq": 3771, + "Hangul_Kiyeog": 3745, + "Hangul_KiyeogSios": 3747, + "Hangul_KkogjiDalrinIeung": 3827, + "Hangul_Mieum": 3761, + "Hangul_MultipleCandidate": 65341, + "Hangul_Nieun": 3748, + "Hangul_NieunHieuh": 3750, + "Hangul_NieunJieuj": 3749, + "Hangul_O": 3783, + "Hangul_OE": 3786, + "Hangul_PanSios": 3826, + "Hangul_Phieuf": 3773, + "Hangul_Pieub": 3762, + "Hangul_PieubSios": 3764, + "Hangul_PostHanja": 65339, + "Hangul_PreHanja": 65338, + "Hangul_PreviousCandidate": 65342, + "Hangul_Rieul": 3753, + "Hangul_RieulHieuh": 3760, + "Hangul_RieulKiyeog": 3754, + "Hangul_RieulMieum": 3755, + "Hangul_RieulPhieuf": 3759, + "Hangul_RieulPieub": 3756, + "Hangul_RieulSios": 3757, + "Hangul_RieulTieut": 3758, + "Hangul_RieulYeorinHieuh": 3823, + "Hangul_Romaja": 65334, + "Hangul_SingleCandidate": 65340, + "Hangul_Sios": 3765, + "Hangul_Special": 65343, + "Hangul_SsangDikeud": 3752, + "Hangul_SsangJieuj": 3769, + "Hangul_SsangKiyeog": 3746, + "Hangul_SsangPieub": 3763, + "Hangul_SsangSios": 3766, + "Hangul_Start": 65330, + "Hangul_SunkyeongeumMieum": 3824, + "Hangul_SunkyeongeumPhieuf": 3828, + "Hangul_SunkyeongeumPieub": 3825, + "Hangul_Tieut": 3772, + "Hangul_U": 3788, + "Hangul_WA": 3784, + "Hangul_WAE": 3785, + "Hangul_WE": 3790, + "Hangul_WEO": 3789, + "Hangul_WI": 3791, + "Hangul_YA": 3777, + "Hangul_YAE": 3778, + "Hangul_YE": 3782, + "Hangul_YEO": 3781, + "Hangul_YI": 3794, + "Hangul_YO": 3787, + "Hangul_YU": 3792, + "Hangul_YeorinHieuh": 3829, + "Hangul_switch": 65406, + "Hankaku": 65321, + "Hcircumflex": 678, + "Hebrew_switch": 65406, + "Help": 65386, + "Henkan": 65315, + "Henkan_Mode": 65315, + "Hiragana": 65317, + "Hiragana_Katakana": 65319, + "Home": 65360, + "Hstroke": 673, + "Hyper_L": 65517, + "Hyper_R": 65518, + "I": 73, + "ISO_Center_Object": 65075, + "ISO_Continuous_Underline": 65072, + "ISO_Discontinuous_Underline": 65073, + "ISO_Emphasize": 65074, + "ISO_Enter": 65076, + "ISO_Fast_Cursor_Down": 65071, + "ISO_Fast_Cursor_Left": 65068, + "ISO_Fast_Cursor_Right": 65069, + "ISO_Fast_Cursor_Up": 65070, + "ISO_First_Group": 65036, + "ISO_First_Group_Lock": 65037, + "ISO_Group_Latch": 65030, + "ISO_Group_Lock": 65031, + "ISO_Group_Shift": 65406, + "ISO_Last_Group": 65038, + "ISO_Last_Group_Lock": 65039, + "ISO_Left_Tab": 65056, + "ISO_Level2_Latch": 65026, + "ISO_Level3_Latch": 65028, + "ISO_Level3_Lock": 65029, + "ISO_Level3_Shift": 65027, + "ISO_Level5_Latch": 65042, + "ISO_Level5_Lock": 65043, + "ISO_Level5_Shift": 65041, + "ISO_Lock": 65025, + "ISO_Move_Line_Down": 65058, + "ISO_Move_Line_Up": 65057, + "ISO_Next_Group": 65032, + "ISO_Next_Group_Lock": 65033, + "ISO_Partial_Line_Down": 65060, + "ISO_Partial_Line_Up": 65059, + "ISO_Partial_Space_Left": 65061, + "ISO_Partial_Space_Right": 65062, + "ISO_Prev_Group": 65034, + "ISO_Prev_Group_Lock": 65035, + "ISO_Release_Both_Margins": 65067, + "ISO_Release_Margin_Left": 65065, + "ISO_Release_Margin_Right": 65066, + "ISO_Set_Margin_Left": 65063, + "ISO_Set_Margin_Right": 65064, + "Iabovedot": 681, + "Iacute": 205, + "Ibelowdot": 16785098, + "Ibreve": 16777516, + "Icircumflex": 206, + "Idiaeresis": 207, + "Igrave": 204, + "Ihook": 16785096, + "Imacron": 975, + "Insert": 65379, + "Iogonek": 967, + "Itilde": 933, + "J": 74, + "Jcircumflex": 684, + "K": 75, + "KP_0": 65456, + "KP_1": 65457, + "KP_2": 65458, + "KP_3": 65459, + "KP_4": 65460, + "KP_5": 65461, + "KP_6": 65462, + "KP_7": 65463, + "KP_8": 65464, + "KP_9": 65465, + "KP_Add": 65451, + "KP_Begin": 65437, + "KP_Decimal": 65454, + "KP_Delete": 65439, + "KP_Divide": 65455, + "KP_Down": 65433, + "KP_End": 65436, + "KP_Enter": 65421, + "KP_Equal": 65469, + "KP_F1": 65425, + "KP_F2": 65426, + "KP_F3": 65427, + "KP_F4": 65428, + "KP_Home": 65429, + "KP_Insert": 65438, + "KP_Left": 65430, + "KP_Multiply": 65450, + "KP_Next": 65435, + "KP_Page_Down": 65435, + "KP_Page_Up": 65434, + "KP_Prior": 65434, + "KP_Right": 65432, + "KP_Separator": 65452, + "KP_Space": 65408, + "KP_Subtract": 65453, + "KP_Tab": 65417, + "KP_Up": 65431, + "Kana_Lock": 65325, + "Kana_Shift": 65326, + "Kanji": 65313, + "Kanji_Bangou": 65335, + "Katakana": 65318, + "Kcedilla": 979, + "Korean_Won": 3839, + "L": 76, + "L1": 65480, + "L10": 65489, + "L2": 65481, + "L3": 65482, + "L4": 65483, + "L5": 65484, + "L6": 65485, + "L7": 65486, + "L8": 65487, + "L9": 65488, + "Lacute": 453, + "Last_Virtual_Screen": 65236, + "Lbelowdot": 16784950, + "Lcaron": 421, + "Lcedilla": 934, + "Left": 65361, + "Linefeed": 65290, + "LiraSign": 16785572, + "Lstroke": 419, + "M": 77, + "Mabovedot": 16784960, + "Macedonia_DSE": 1717, + "Macedonia_GJE": 1714, + "Macedonia_KJE": 1724, + "Macedonia_dse": 1701, + "Macedonia_gje": 1698, + "Macedonia_kje": 1708, + "Mae_Koho": 65342, + "Massyo": 65324, + "Menu": 65383, + "Meta_L": 65511, + "Meta_R": 65512, + "MillSign": 16785573, + "Mode_switch": 65406, + "MouseKeys_Accel_Enable": 65143, + "MouseKeys_Enable": 65142, + "Muhenkan": 65314, + "Multi_key": 65312, + "MultipleCandidate": 65341, + "N": 78, + "Nacute": 465, + "NairaSign": 16785574, + "Ncaron": 466, + "Ncedilla": 977, + "NewSheqelSign": 16785578, + "Next": 65366, + "Next_Virtual_Screen": 65234, + "Ntilde": 209, + "Num_Lock": 65407, + "O": 79, + "OE": 5052, + "Oacute": 211, + "Obarred": 16777631, + "Obelowdot": 16785100, + "Ocaron": 16777681, + "Ocircumflex": 212, + "Ocircumflexacute": 16785104, + "Ocircumflexbelowdot": 16785112, + "Ocircumflexgrave": 16785106, + "Ocircumflexhook": 16785108, + "Ocircumflextilde": 16785110, + "Odiaeresis": 214, + "Odoubleacute": 469, + "Ograve": 210, + "Ohook": 16785102, + "Ohorn": 16777632, + "Ohornacute": 16785114, + "Ohornbelowdot": 16785122, + "Ohorngrave": 16785116, + "Ohornhook": 16785118, + "Ohorntilde": 16785120, + "Omacron": 978, + "Ooblique": 216, + "Oslash": 216, + "Otilde": 213, + "Overlay1_Enable": 65144, + "Overlay2_Enable": 65145, + "P": 80, + "Pabovedot": 16784982, + "Page_Down": 65366, + "Page_Up": 65365, + "Pause": 65299, + "PesetaSign": 16785575, + "Pointer_Accelerate": 65274, + "Pointer_Button1": 65257, + "Pointer_Button2": 65258, + "Pointer_Button3": 65259, + "Pointer_Button4": 65260, + "Pointer_Button5": 65261, + "Pointer_Button_Dflt": 65256, + "Pointer_DblClick1": 65263, + "Pointer_DblClick2": 65264, + "Pointer_DblClick3": 65265, + "Pointer_DblClick4": 65266, + "Pointer_DblClick5": 65267, + "Pointer_DblClick_Dflt": 65262, + "Pointer_DfltBtnNext": 65275, + "Pointer_DfltBtnPrev": 65276, + "Pointer_Down": 65251, + "Pointer_DownLeft": 65254, + "Pointer_DownRight": 65255, + "Pointer_Drag1": 65269, + "Pointer_Drag2": 65270, + "Pointer_Drag3": 65271, + "Pointer_Drag4": 65272, + "Pointer_Drag5": 65277, + "Pointer_Drag_Dflt": 65268, + "Pointer_EnableKeys": 65273, + "Pointer_Left": 65248, + "Pointer_Right": 65249, + "Pointer_Up": 65250, + "Pointer_UpLeft": 65252, + "Pointer_UpRight": 65253, + "Prev_Virtual_Screen": 65233, + "PreviousCandidate": 65342, + "Print": 65377, + "Prior": 65365, + "Q": 81, + "R": 82, + "R1": 65490, + "R10": 65499, + "R11": 65500, + "R12": 65501, + "R13": 65502, + "R14": 65503, + "R15": 65504, + "R2": 65491, + "R3": 65492, + "R4": 65493, + "R5": 65494, + "R6": 65495, + "R7": 65496, + "R8": 65497, + "R9": 65498, + "Racute": 448, + "Rcaron": 472, + "Rcedilla": 931, + "Redo": 65382, + "RepeatKeys_Enable": 65138, + "Return": 65293, + "Right": 65363, + "Romaji": 65316, + "RupeeSign": 16785576, + "S": 83, + "SCHWA": 16777615, + "Sabovedot": 16784992, + "Sacute": 422, + "Scaron": 425, + "Scedilla": 426, + "Scircumflex": 734, + "Scroll_Lock": 65300, + "Select": 65376, + "Serbian_DJE": 1713, + "Serbian_DZE": 1727, + "Serbian_JE": 1720, + "Serbian_LJE": 1721, + "Serbian_NJE": 1722, + "Serbian_TSHE": 1723, + "Serbian_dje": 1697, + "Serbian_dze": 1711, + "Serbian_je": 1704, + "Serbian_lje": 1705, + "Serbian_nje": 1706, + "Serbian_tshe": 1707, + "Shift_L": 65505, + "Shift_Lock": 65510, + "Shift_R": 65506, + "SingleCandidate": 65340, + "Sinh_a": 16780677, + "Sinh_aa": 16780678, + "Sinh_aa2": 16780751, + "Sinh_ae": 16780679, + "Sinh_ae2": 16780752, + "Sinh_aee": 16780680, + "Sinh_aee2": 16780753, + "Sinh_ai": 16780691, + "Sinh_ai2": 16780763, + "Sinh_al": 16780746, + "Sinh_au": 16780694, + "Sinh_au2": 16780766, + "Sinh_ba": 16780726, + "Sinh_bha": 16780727, + "Sinh_ca": 16780704, + "Sinh_cha": 16780705, + "Sinh_dda": 16780713, + "Sinh_ddha": 16780714, + "Sinh_dha": 16780719, + "Sinh_dhha": 16780720, + "Sinh_e": 16780689, + "Sinh_e2": 16780761, + "Sinh_ee": 16780690, + "Sinh_ee2": 16780762, + "Sinh_fa": 16780742, + "Sinh_ga": 16780700, + "Sinh_gha": 16780701, + "Sinh_h2": 16780675, + "Sinh_ha": 16780740, + "Sinh_i": 16780681, + "Sinh_i2": 16780754, + "Sinh_ii": 16780682, + "Sinh_ii2": 16780755, + "Sinh_ja": 16780706, + "Sinh_jha": 16780707, + "Sinh_jnya": 16780709, + "Sinh_ka": 16780698, + "Sinh_kha": 16780699, + "Sinh_kunddaliya": 16780788, + "Sinh_la": 16780733, + "Sinh_lla": 16780741, + "Sinh_lu": 16780687, + "Sinh_lu2": 16780767, + "Sinh_luu": 16780688, + "Sinh_luu2": 16780787, + "Sinh_ma": 16780728, + "Sinh_mba": 16780729, + "Sinh_na": 16780721, + "Sinh_ndda": 16780716, + "Sinh_ndha": 16780723, + "Sinh_ng": 16780674, + "Sinh_ng2": 16780702, + "Sinh_nga": 16780703, + "Sinh_nja": 16780710, + "Sinh_nna": 16780715, + "Sinh_nya": 16780708, + "Sinh_o": 16780692, + "Sinh_o2": 16780764, + "Sinh_oo": 16780693, + "Sinh_oo2": 16780765, + "Sinh_pa": 16780724, + "Sinh_pha": 16780725, + "Sinh_ra": 16780731, + "Sinh_ri": 16780685, + "Sinh_rii": 16780686, + "Sinh_ru2": 16780760, + "Sinh_ruu2": 16780786, + "Sinh_sa": 16780739, + "Sinh_sha": 16780737, + "Sinh_ssha": 16780738, + "Sinh_tha": 16780717, + "Sinh_thha": 16780718, + "Sinh_tta": 16780711, + "Sinh_ttha": 16780712, + "Sinh_u": 16780683, + "Sinh_u2": 16780756, + "Sinh_uu": 16780684, + "Sinh_uu2": 16780758, + "Sinh_va": 16780736, + "Sinh_ya": 16780730, + "SlowKeys_Enable": 65139, + "StickyKeys_Enable": 65141, + "Super_L": 65515, + "Super_R": 65516, + "Sys_Req": 65301, + "T": 84, + "THORN": 222, + "Tab": 65289, + "Tabovedot": 16785002, + "Tcaron": 427, + "Tcedilla": 478, + "Terminate_Server": 65237, + "Thai_baht": 3551, + "Thai_bobaimai": 3514, + "Thai_chochan": 3496, + "Thai_chochang": 3498, + "Thai_choching": 3497, + "Thai_chochoe": 3500, + "Thai_dochada": 3502, + "Thai_dodek": 3508, + "Thai_fofa": 3517, + "Thai_fofan": 3519, + "Thai_hohip": 3531, + "Thai_honokhuk": 3534, + "Thai_khokhai": 3490, + "Thai_khokhon": 3493, + "Thai_khokhuat": 3491, + "Thai_khokhwai": 3492, + "Thai_khorakhang": 3494, + "Thai_kokai": 3489, + "Thai_lakkhangyao": 3557, + "Thai_lekchet": 3575, + "Thai_lekha": 3573, + "Thai_lekhok": 3574, + "Thai_lekkao": 3577, + "Thai_leknung": 3569, + "Thai_lekpaet": 3576, + "Thai_leksam": 3571, + "Thai_leksi": 3572, + "Thai_leksong": 3570, + "Thai_leksun": 3568, + "Thai_lochula": 3532, + "Thai_loling": 3525, + "Thai_lu": 3526, + "Thai_maichattawa": 3563, + "Thai_maiek": 3560, + "Thai_maihanakat": 3537, + "Thai_maihanakat_maitho": 3550, + "Thai_maitaikhu": 3559, + "Thai_maitho": 3561, + "Thai_maitri": 3562, + "Thai_maiyamok": 3558, + "Thai_moma": 3521, + "Thai_ngongu": 3495, + "Thai_nikhahit": 3565, + "Thai_nonen": 3507, + "Thai_nonu": 3513, + "Thai_oang": 3533, + "Thai_paiyannoi": 3535, + "Thai_phinthu": 3546, + "Thai_phophan": 3518, + "Thai_phophung": 3516, + "Thai_phosamphao": 3520, + "Thai_popla": 3515, + "Thai_rorua": 3523, + "Thai_ru": 3524, + "Thai_saraa": 3536, + "Thai_saraaa": 3538, + "Thai_saraae": 3553, + "Thai_saraaimaimalai": 3556, + "Thai_saraaimaimuan": 3555, + "Thai_saraam": 3539, + "Thai_sarae": 3552, + "Thai_sarai": 3540, + "Thai_saraii": 3541, + "Thai_sarao": 3554, + "Thai_sarau": 3544, + "Thai_saraue": 3542, + "Thai_sarauee": 3543, + "Thai_sarauu": 3545, + "Thai_sorusi": 3529, + "Thai_sosala": 3528, + "Thai_soso": 3499, + "Thai_sosua": 3530, + "Thai_thanthakhat": 3564, + "Thai_thonangmontho": 3505, + "Thai_thophuthao": 3506, + "Thai_thothahan": 3511, + "Thai_thothan": 3504, + "Thai_thothong": 3512, + "Thai_thothung": 3510, + "Thai_topatak": 3503, + "Thai_totao": 3509, + "Thai_wowaen": 3527, + "Thai_yoyak": 3522, + "Thai_yoying": 3501, + "Thorn": 222, + "Touroku": 65323, + "Tslash": 940, + "U": 85, + "Uacute": 218, + "Ubelowdot": 16785124, + "Ubreve": 733, + "Ucircumflex": 219, + "Udiaeresis": 220, + "Udoubleacute": 475, + "Ugrave": 217, + "Uhook": 16785126, + "Uhorn": 16777647, + "Uhornacute": 16785128, + "Uhornbelowdot": 16785136, + "Uhorngrave": 16785130, + "Uhornhook": 16785132, + "Uhorntilde": 16785134, + "Ukrainian_GHE_WITH_UPTURN": 1725, + "Ukrainian_I": 1718, + "Ukrainian_IE": 1716, + "Ukrainian_YI": 1719, + "Ukrainian_ghe_with_upturn": 1709, + "Ukrainian_i": 1702, + "Ukrainian_ie": 1700, + "Ukrainian_yi": 1703, + "Ukranian_I": 1718, + "Ukranian_JE": 1716, + "Ukranian_YI": 1719, + "Ukranian_i": 1702, + "Ukranian_je": 1700, + "Ukranian_yi": 1703, + "Umacron": 990, + "Undo": 65381, + "Uogonek": 985, + "Up": 65362, + "Uring": 473, + "Utilde": 989, + "V": 86, + "VoidSymbol": 16777215, + "W": 87, + "Wacute": 16785026, + "Wcircumflex": 16777588, + "Wdiaeresis": 16785028, + "Wgrave": 16785024, + "WonSign": 16785577, + "X": 88, + "XF86_AddFavorite": 269025081, + "XF86_ApplicationLeft": 269025104, + "XF86_ApplicationRight": 269025105, + "XF86_AudioCycleTrack": 269025179, + "XF86_AudioForward": 269025175, + "XF86_AudioLowerVolume": 269025041, + "XF86_AudioMedia": 269025074, + "XF86_AudioMicMute": 269025202, + "XF86_AudioMute": 269025042, + "XF86_AudioNext": 269025047, + "XF86_AudioPause": 269025073, + "XF86_AudioPlay": 269025044, + "XF86_AudioPreset": 269025206, + "XF86_AudioPrev": 269025046, + "XF86_AudioRaiseVolume": 269025043, + "XF86_AudioRandomPlay": 269025177, + "XF86_AudioRecord": 269025052, + "XF86_AudioRepeat": 269025176, + "XF86_AudioRewind": 269025086, + "XF86_AudioStop": 269025045, + "XF86_Away": 269025165, + "XF86_Back": 269025062, + "XF86_BackForward": 269025087, + "XF86_Battery": 269025171, + "XF86_Blue": 269025190, + "XF86_Bluetooth": 269025172, + "XF86_Book": 269025106, + "XF86_BrightnessAdjust": 269025083, + "XF86_CD": 269025107, + "XF86_Calculater": 269025108, + "XF86_Calculator": 269025053, + "XF86_Calendar": 269025056, + "XF86_Clear": 269025109, + "XF86_ClearGrab": 269024801, + "XF86_Close": 269025110, + "XF86_Community": 269025085, + "XF86_ContrastAdjust": 269025058, + "XF86_Copy": 269025111, + "XF86_Cut": 269025112, + "XF86_CycleAngle": 269025180, + "XF86_DOS": 269025114, + "XF86_Display": 269025113, + "XF86_Documents": 269025115, + "XF86_Eject": 269025068, + "XF86_Excel": 269025116, + "XF86_Explorer": 269025117, + "XF86_Favorites": 269025072, + "XF86_Finance": 269025084, + "XF86_Forward": 269025063, + "XF86_FrameBack": 269025181, + "XF86_FrameForward": 269025182, + "XF86_FullScreen": 269025208, + "XF86_Game": 269025118, + "XF86_Go": 269025119, + "XF86_Green": 269025188, + "XF86_Hibernate": 269025192, + "XF86_History": 269025079, + "XF86_HomePage": 269025048, + "XF86_HotLinks": 269025082, + "XF86_KbdBrightnessDown": 269025030, + "XF86_KbdBrightnessUp": 269025029, + "XF86_KbdLightOnOff": 269025028, + "XF86_Keyboard": 269025203, + "XF86_Launch0": 269025088, + "XF86_Launch1": 269025089, + "XF86_Launch2": 269025090, + "XF86_Launch3": 269025091, + "XF86_Launch4": 269025092, + "XF86_Launch5": 269025093, + "XF86_Launch6": 269025094, + "XF86_Launch7": 269025095, + "XF86_Launch8": 269025096, + "XF86_Launch9": 269025097, + "XF86_LaunchA": 269025098, + "XF86_LaunchB": 269025099, + "XF86_LaunchC": 269025100, + "XF86_LaunchD": 269025101, + "XF86_LaunchE": 269025102, + "XF86_LaunchF": 269025103, + "XF86_LightBulb": 269025077, + "XF86_LogGrabInfo": 269024805, + "XF86_LogOff": 269025121, + "XF86_LogWindowTree": 269024804, + "XF86_MacroRecordStart": 268964528, + "XF86_Mail": 269025049, + "XF86_MailForward": 269025168, + "XF86_Market": 269025122, + "XF86_Meeting": 269025123, + "XF86_Memo": 269025054, + "XF86_MenuKB": 269025125, + "XF86_MenuPB": 269025126, + "XF86_Messenger": 269025166, + "XF86_ModeLock": 269025025, + "XF86_MonBrightnessCycle": 269025031, + "XF86_MonBrightnessDown": 269025027, + "XF86_MonBrightnessUp": 269025026, + "XF86_Music": 269025170, + "XF86_MyComputer": 269025075, + "XF86_MySites": 269025127, + "XF86_New": 269025128, + "XF86_News": 269025129, + "XF86_Next_VMode": 269024802, + "XF86_OfficeHome": 269025130, + "XF86_Open": 269025131, + "XF86_OpenURL": 269025080, + "XF86_Option": 269025132, + "XF86_Paste": 269025133, + "XF86_Phone": 269025134, + "XF86_Pictures": 269025169, + "XF86_PowerDown": 269025057, + "XF86_PowerOff": 269025066, + "XF86_Prev_VMode": 269024803, + "XF86_Q": 269025136, + "XF86_RFKill": 269025205, + "XF86_Red": 269025187, + "XF86_Refresh": 269025065, + "XF86_Reload": 269025139, + "XF86_Reply": 269025138, + "XF86_RockerDown": 269025060, + "XF86_RockerEnter": 269025061, + "XF86_RockerUp": 269025059, + "XF86_RotateWindows": 269025140, + "XF86_RotationKB": 269025142, + "XF86_RotationLockToggle": 269025207, + "XF86_RotationPB": 269025141, + "XF86_Save": 269025143, + "XF86_ScreenSaver": 269025069, + "XF86_ScrollClick": 269025146, + "XF86_ScrollDown": 269025145, + "XF86_ScrollUp": 269025144, + "XF86_Search": 269025051, + "XF86_Select": 269025184, + "XF86_Send": 269025147, + "XF86_Shop": 269025078, + "XF86_Sleep": 269025071, + "XF86_Spell": 269025148, + "XF86_SplitScreen": 269025149, + "XF86_Standby": 269025040, + "XF86_Start": 269025050, + "XF86_Stop": 269025064, + "XF86_Subtitle": 269025178, + "XF86_Support": 269025150, + "XF86_Suspend": 269025191, + "XF86_Switch_VT_1": 269024769, + "XF86_Switch_VT_10": 269024778, + "XF86_Switch_VT_11": 269024779, + "XF86_Switch_VT_12": 269024780, + "XF86_Switch_VT_2": 269024770, + "XF86_Switch_VT_3": 269024771, + "XF86_Switch_VT_4": 269024772, + "XF86_Switch_VT_5": 269024773, + "XF86_Switch_VT_6": 269024774, + "XF86_Switch_VT_7": 269024775, + "XF86_Switch_VT_8": 269024776, + "XF86_Switch_VT_9": 269024777, + "XF86_TaskPane": 269025151, + "XF86_Terminal": 269025152, + "XF86_Time": 269025183, + "XF86_ToDoList": 269025055, + "XF86_Tools": 269025153, + "XF86_TopMenu": 269025186, + "XF86_TouchpadOff": 269025201, + "XF86_TouchpadOn": 269025200, + "XF86_TouchpadToggle": 269025193, + "XF86_Travel": 269025154, + "XF86_UWB": 269025174, + "XF86_Ungrab": 269024800, + "XF86_User1KB": 269025157, + "XF86_User2KB": 269025158, + "XF86_UserPB": 269025156, + "XF86_VendorHome": 269025076, + "XF86_Video": 269025159, + "XF86_View": 269025185, + "XF86_WLAN": 269025173, + "XF86_WWAN": 269025204, + "XF86_WWW": 269025070, + "XF86_WakeUp": 269025067, + "XF86_WebCam": 269025167, + "XF86_WheelButton": 269025160, + "XF86_Word": 269025161, + "XF86_Xfer": 269025162, + "XF86_Yellow": 269025189, + "XF86_ZoomIn": 269025163, + "XF86_ZoomOut": 269025164, + "XF86_iTouch": 269025120, + "Xabovedot": 16785034, + "Y": 89, + "Yacute": 221, + "Ybelowdot": 16785140, + "Ycircumflex": 16777590, + "Ydiaeresis": 5054, + "Ygrave": 16785138, + "Yhook": 16785142, + "Ytilde": 16785144, + "Z": 90, + "Zabovedot": 431, + "Zacute": 428, + "Zcaron": 430, + "Zen_Koho": 65341, + "Zenkaku": 65320, + "Zenkaku_Hankaku": 65322, + "Zstroke": 16777653, + "a": 97, + "aacute": 225, + "abelowdot": 16785057, + "abovedot": 511, + "abreve": 483, + "abreveacute": 16785071, + "abrevebelowdot": 16785079, + "abrevegrave": 16785073, + "abrevehook": 16785075, + "abrevetilde": 16785077, + "acircumflex": 226, + "acircumflexacute": 16785061, + "acircumflexbelowdot": 16785069, + "acircumflexgrave": 16785063, + "acircumflexhook": 16785065, + "acircumflextilde": 16785067, + "acute": 180, + "adiaeresis": 228, + "ae": 230, + "agrave": 224, + "ahook": 16785059, + "amacron": 992, + "ampersand": 38, + "aogonek": 433, + "apostrophe": 39, + "approxeq": 16785992, + "approximate": 2248, + "aring": 229, + "asciicircum": 94, + "asciitilde": 126, + "asterisk": 42, + "at": 64, + "atilde": 227, + "b": 98, + "babovedot": 16784899, + "backslash": 92, + "ballotcross": 2804, + "bar": 124, + "because": 16785973, + "blank": 2527, + "botintegral": 2213, + "botleftparens": 2220, + "botleftsqbracket": 2216, + "botleftsummation": 2226, + "botrightparens": 2222, + "botrightsqbracket": 2218, + "botrightsummation": 2230, + "bott": 2550, + "botvertsummationconnector": 2228, + "braceleft": 123, + "braceright": 125, + "bracketleft": 91, + "bracketright": 93, + "braille_blank": 16787456, + "braille_dot_1": 65521, + "braille_dot_10": 65530, + "braille_dot_2": 65522, + "braille_dot_3": 65523, + "braille_dot_4": 65524, + "braille_dot_5": 65525, + "braille_dot_6": 65526, + "braille_dot_7": 65527, + "braille_dot_8": 65528, + "braille_dot_9": 65529, + "braille_dots_1": 16787457, + "braille_dots_12": 16787459, + "braille_dots_123": 16787463, + "braille_dots_1234": 16787471, + "braille_dots_12345": 16787487, + "braille_dots_123456": 16787519, + "braille_dots_1234567": 16787583, + "braille_dots_12345678": 16787711, + "braille_dots_1234568": 16787647, + "braille_dots_123457": 16787551, + "braille_dots_1234578": 16787679, + "braille_dots_123458": 16787615, + "braille_dots_12346": 16787503, + "braille_dots_123467": 16787567, + "braille_dots_1234678": 16787695, + "braille_dots_123468": 16787631, + "braille_dots_12347": 16787535, + "braille_dots_123478": 16787663, + "braille_dots_12348": 16787599, + "braille_dots_1235": 16787479, + "braille_dots_12356": 16787511, + "braille_dots_123567": 16787575, + "braille_dots_1235678": 16787703, + "braille_dots_123568": 16787639, + "braille_dots_12357": 16787543, + "braille_dots_123578": 16787671, + "braille_dots_12358": 16787607, + "braille_dots_1236": 16787495, + "braille_dots_12367": 16787559, + "braille_dots_123678": 16787687, + "braille_dots_12368": 16787623, + "braille_dots_1237": 16787527, + "braille_dots_12378": 16787655, + "braille_dots_1238": 16787591, + "braille_dots_124": 16787467, + "braille_dots_1245": 16787483, + "braille_dots_12456": 16787515, + "braille_dots_124567": 16787579, + "braille_dots_1245678": 16787707, + "braille_dots_124568": 16787643, + "braille_dots_12457": 16787547, + "braille_dots_124578": 16787675, + "braille_dots_12458": 16787611, + "braille_dots_1246": 16787499, + "braille_dots_12467": 16787563, + "braille_dots_124678": 16787691, + "braille_dots_12468": 16787627, + "braille_dots_1247": 16787531, + "braille_dots_12478": 16787659, + "braille_dots_1248": 16787595, + "braille_dots_125": 16787475, + "braille_dots_1256": 16787507, + "braille_dots_12567": 16787571, + "braille_dots_125678": 16787699, + "braille_dots_12568": 16787635, + "braille_dots_1257": 16787539, + "braille_dots_12578": 16787667, + "braille_dots_1258": 16787603, + "braille_dots_126": 16787491, + "braille_dots_1267": 16787555, + "braille_dots_12678": 16787683, + "braille_dots_1268": 16787619, + "braille_dots_127": 16787523, + "braille_dots_1278": 16787651, + "braille_dots_128": 16787587, + "braille_dots_13": 16787461, + "braille_dots_134": 16787469, + "braille_dots_1345": 16787485, + "braille_dots_13456": 16787517, + "braille_dots_134567": 16787581, + "braille_dots_1345678": 16787709, + "braille_dots_134568": 16787645, + "braille_dots_13457": 16787549, + "braille_dots_134578": 16787677, + "braille_dots_13458": 16787613, + "braille_dots_1346": 16787501, + "braille_dots_13467": 16787565, + "braille_dots_134678": 16787693, + "braille_dots_13468": 16787629, + "braille_dots_1347": 16787533, + "braille_dots_13478": 16787661, + "braille_dots_1348": 16787597, + "braille_dots_135": 16787477, + "braille_dots_1356": 16787509, + "braille_dots_13567": 16787573, + "braille_dots_135678": 16787701, + "braille_dots_13568": 16787637, + "braille_dots_1357": 16787541, + "braille_dots_13578": 16787669, + "braille_dots_1358": 16787605, + "braille_dots_136": 16787493, + "braille_dots_1367": 16787557, + "braille_dots_13678": 16787685, + "braille_dots_1368": 16787621, + "braille_dots_137": 16787525, + "braille_dots_1378": 16787653, + "braille_dots_138": 16787589, + "braille_dots_14": 16787465, + "braille_dots_145": 16787481, + "braille_dots_1456": 16787513, + "braille_dots_14567": 16787577, + "braille_dots_145678": 16787705, + "braille_dots_14568": 16787641, + "braille_dots_1457": 16787545, + "braille_dots_14578": 16787673, + "braille_dots_1458": 16787609, + "braille_dots_146": 16787497, + "braille_dots_1467": 16787561, + "braille_dots_14678": 16787689, + "braille_dots_1468": 16787625, + "braille_dots_147": 16787529, + "braille_dots_1478": 16787657, + "braille_dots_148": 16787593, + "braille_dots_15": 16787473, + "braille_dots_156": 16787505, + "braille_dots_1567": 16787569, + "braille_dots_15678": 16787697, + "braille_dots_1568": 16787633, + "braille_dots_157": 16787537, + "braille_dots_1578": 16787665, + "braille_dots_158": 16787601, + "braille_dots_16": 16787489, + "braille_dots_167": 16787553, + "braille_dots_1678": 16787681, + "braille_dots_168": 16787617, + "braille_dots_17": 16787521, + "braille_dots_178": 16787649, + "braille_dots_18": 16787585, + "braille_dots_2": 16787458, + "braille_dots_23": 16787462, + "braille_dots_234": 16787470, + "braille_dots_2345": 16787486, + "braille_dots_23456": 16787518, + "braille_dots_234567": 16787582, + "braille_dots_2345678": 16787710, + "braille_dots_234568": 16787646, + "braille_dots_23457": 16787550, + "braille_dots_234578": 16787678, + "braille_dots_23458": 16787614, + "braille_dots_2346": 16787502, + "braille_dots_23467": 16787566, + "braille_dots_234678": 16787694, + "braille_dots_23468": 16787630, + "braille_dots_2347": 16787534, + "braille_dots_23478": 16787662, + "braille_dots_2348": 16787598, + "braille_dots_235": 16787478, + "braille_dots_2356": 16787510, + "braille_dots_23567": 16787574, + "braille_dots_235678": 16787702, + "braille_dots_23568": 16787638, + "braille_dots_2357": 16787542, + "braille_dots_23578": 16787670, + "braille_dots_2358": 16787606, + "braille_dots_236": 16787494, + "braille_dots_2367": 16787558, + "braille_dots_23678": 16787686, + "braille_dots_2368": 16787622, + "braille_dots_237": 16787526, + "braille_dots_2378": 16787654, + "braille_dots_238": 16787590, + "braille_dots_24": 16787466, + "braille_dots_245": 16787482, + "braille_dots_2456": 16787514, + "braille_dots_24567": 16787578, + "braille_dots_245678": 16787706, + "braille_dots_24568": 16787642, + "braille_dots_2457": 16787546, + "braille_dots_24578": 16787674, + "braille_dots_2458": 16787610, + "braille_dots_246": 16787498, + "braille_dots_2467": 16787562, + "braille_dots_24678": 16787690, + "braille_dots_2468": 16787626, + "braille_dots_247": 16787530, + "braille_dots_2478": 16787658, + "braille_dots_248": 16787594, + "braille_dots_25": 16787474, + "braille_dots_256": 16787506, + "braille_dots_2567": 16787570, + "braille_dots_25678": 16787698, + "braille_dots_2568": 16787634, + "braille_dots_257": 16787538, + "braille_dots_2578": 16787666, + "braille_dots_258": 16787602, + "braille_dots_26": 16787490, + "braille_dots_267": 16787554, + "braille_dots_2678": 16787682, + "braille_dots_268": 16787618, + "braille_dots_27": 16787522, + "braille_dots_278": 16787650, + "braille_dots_28": 16787586, + "braille_dots_3": 16787460, + "braille_dots_34": 16787468, + "braille_dots_345": 16787484, + "braille_dots_3456": 16787516, + "braille_dots_34567": 16787580, + "braille_dots_345678": 16787708, + "braille_dots_34568": 16787644, + "braille_dots_3457": 16787548, + "braille_dots_34578": 16787676, + "braille_dots_3458": 16787612, + "braille_dots_346": 16787500, + "braille_dots_3467": 16787564, + "braille_dots_34678": 16787692, + "braille_dots_3468": 16787628, + "braille_dots_347": 16787532, + "braille_dots_3478": 16787660, + "braille_dots_348": 16787596, + "braille_dots_35": 16787476, + "braille_dots_356": 16787508, + "braille_dots_3567": 16787572, + "braille_dots_35678": 16787700, + "braille_dots_3568": 16787636, + "braille_dots_357": 16787540, + "braille_dots_3578": 16787668, + "braille_dots_358": 16787604, + "braille_dots_36": 16787492, + "braille_dots_367": 16787556, + "braille_dots_3678": 16787684, + "braille_dots_368": 16787620, + "braille_dots_37": 16787524, + "braille_dots_378": 16787652, + "braille_dots_38": 16787588, + "braille_dots_4": 16787464, + "braille_dots_45": 16787480, + "braille_dots_456": 16787512, + "braille_dots_4567": 16787576, + "braille_dots_45678": 16787704, + "braille_dots_4568": 16787640, + "braille_dots_457": 16787544, + "braille_dots_4578": 16787672, + "braille_dots_458": 16787608, + "braille_dots_46": 16787496, + "braille_dots_467": 16787560, + "braille_dots_4678": 16787688, + "braille_dots_468": 16787624, + "braille_dots_47": 16787528, + "braille_dots_478": 16787656, + "braille_dots_48": 16787592, + "braille_dots_5": 16787472, + "braille_dots_56": 16787504, + "braille_dots_567": 16787568, + "braille_dots_5678": 16787696, + "braille_dots_568": 16787632, + "braille_dots_57": 16787536, + "braille_dots_578": 16787664, + "braille_dots_58": 16787600, + "braille_dots_6": 16787488, + "braille_dots_67": 16787552, + "braille_dots_678": 16787680, + "braille_dots_68": 16787616, + "braille_dots_7": 16787520, + "braille_dots_78": 16787648, + "braille_dots_8": 16787584, + "breve": 418, + "brokenbar": 166, + "c": 99, + "c_h": 65187, + "cabovedot": 741, + "cacute": 486, + "careof": 2744, + "caret": 2812, + "caron": 439, + "ccaron": 488, + "ccedilla": 231, + "ccircumflex": 742, + "cedilla": 184, + "cent": 162, + "ch": 65184, + "checkerboard": 2529, + "checkmark": 2803, + "circle": 3023, + "club": 2796, + "colon": 58, + "combining_acute": 16777985, + "combining_belowdot": 16778019, + "combining_grave": 16777984, + "combining_hook": 16777993, + "combining_tilde": 16777987, + "comma": 44, + "containsas": 16785931, + "copyright": 169, + "cr": 2532, + "crossinglines": 2542, + "cuberoot": 16785947, + "currency": 164, + "cursor": 2815, + "d": 100, + "dabovedot": 16784907, + "dagger": 2801, + "dcaron": 495, + "dead_A": 65153, + "dead_E": 65155, + "dead_I": 65157, + "dead_O": 65159, + "dead_SCHWA": 65163, + "dead_U": 65161, + "dead_a": 65152, + "dead_abovecomma": 65124, + "dead_abovedot": 65110, + "dead_abovereversedcomma": 65125, + "dead_abovering": 65112, + "dead_acute": 65105, + "dead_belowbreve": 65131, + "dead_belowcircumflex": 65129, + "dead_belowcomma": 65134, + "dead_belowdiaeresis": 65132, + "dead_belowdot": 65120, + "dead_belowmacron": 65128, + "dead_belowring": 65127, + "dead_belowtilde": 65130, + "dead_breve": 65109, + "dead_capital_schwa": 65163, + "dead_caron": 65114, + "dead_cedilla": 65115, + "dead_circumflex": 65106, + "dead_currency": 65135, + "dead_dasia": 65125, + "dead_diaeresis": 65111, + "dead_doubleacute": 65113, + "dead_doublegrave": 65126, + "dead_e": 65154, + "dead_grave": 65104, + "dead_greek": 65164, + "dead_hamza": 65165, + "dead_hook": 65121, + "dead_horn": 65122, + "dead_i": 65156, + "dead_invertedbreve": 65133, + "dead_iota": 65117, + "dead_macron": 65108, + "dead_o": 65158, + "dead_ogonek": 65116, + "dead_perispomeni": 65107, + "dead_psili": 65124, + "dead_schwa": 65162, + "dead_semivoiced_sound": 65119, + "dead_small_schwa": 65162, + "dead_stroke": 65123, + "dead_tilde": 65107, + "dead_u": 65160, + "dead_voiced_sound": 65118, + "decimalpoint": 2749, + "degree": 176, + "diaeresis": 168, + "diamond": 2797, + "digitspace": 2725, + "dintegral": 16785964, + "division": 247, + "dollar": 36, + "doubbaselinedot": 2735, + "doubleacute": 445, + "doubledagger": 2802, + "doublelowquotemark": 2814, + "downarrow": 2302, + "downcaret": 2984, + "downshoe": 3030, + "downstile": 3012, + "downtack": 3010, + "dstroke": 496, + "e": 101, + "eabovedot": 1004, + "eacute": 233, + "ebelowdot": 16785081, + "ecaron": 492, + "ecircumflex": 234, + "ecircumflexacute": 16785087, + "ecircumflexbelowdot": 16785095, + "ecircumflexgrave": 16785089, + "ecircumflexhook": 16785091, + "ecircumflextilde": 16785093, + "ediaeresis": 235, + "egrave": 232, + "ehook": 16785083, + "eightsubscript": 16785544, + "eightsuperior": 16785528, + "elementof": 16785928, + "ellipsis": 2734, + "em3space": 2723, + "em4space": 2724, + "emacron": 954, + "emdash": 2729, + "emfilledcircle": 2782, + "emfilledrect": 2783, + "emopencircle": 2766, + "emopenrectangle": 2767, + "emptyset": 16785925, + "emspace": 2721, + "endash": 2730, + "enfilledcircbullet": 2790, + "enfilledsqbullet": 2791, + "eng": 959, + "enopencircbullet": 2784, + "enopensquarebullet": 2785, + "enspace": 2722, + "eogonek": 490, + "equal": 61, + "eth": 240, + "etilde": 16785085, + "exclam": 33, + "exclamdown": 161, + "ezh": 16777874, + "f": 102, + "fabovedot": 16784927, + "femalesymbol": 2808, + "ff": 2531, + "figdash": 2747, + "filledlefttribullet": 2780, + "filledrectbullet": 2779, + "filledrighttribullet": 2781, + "filledtribulletdown": 2793, + "filledtribulletup": 2792, + "fiveeighths": 2757, + "fivesixths": 2743, + "fivesubscript": 16785541, + "fivesuperior": 16785525, + "fourfifths": 2741, + "foursubscript": 16785540, + "foursuperior": 16785524, + "fourthroot": 16785948, + "function": 2294, + "g": 103, + "gabovedot": 757, + "gbreve": 699, + "gcaron": 16777703, + "gcedilla": 955, + "gcircumflex": 760, + "grave": 96, + "greater": 62, + "greaterthanequal": 2238, + "guillemetleft": 171, + "guillemetright": 187, + "guillemotleft": 171, + "guillemotright": 187, + "h": 104, + "hairspace": 2728, + "hcircumflex": 694, + "heart": 2798, + "hebrew_aleph": 3296, + "hebrew_ayin": 3314, + "hebrew_bet": 3297, + "hebrew_beth": 3297, + "hebrew_chet": 3303, + "hebrew_dalet": 3299, + "hebrew_daleth": 3299, + "hebrew_doublelowline": 3295, + "hebrew_finalkaph": 3306, + "hebrew_finalmem": 3309, + "hebrew_finalnun": 3311, + "hebrew_finalpe": 3315, + "hebrew_finalzade": 3317, + "hebrew_finalzadi": 3317, + "hebrew_gimel": 3298, + "hebrew_gimmel": 3298, + "hebrew_he": 3300, + "hebrew_het": 3303, + "hebrew_kaph": 3307, + "hebrew_kuf": 3319, + "hebrew_lamed": 3308, + "hebrew_mem": 3310, + "hebrew_nun": 3312, + "hebrew_pe": 3316, + "hebrew_qoph": 3319, + "hebrew_resh": 3320, + "hebrew_samech": 3313, + "hebrew_samekh": 3313, + "hebrew_shin": 3321, + "hebrew_taf": 3322, + "hebrew_taw": 3322, + "hebrew_tet": 3304, + "hebrew_teth": 3304, + "hebrew_waw": 3301, + "hebrew_yod": 3305, + "hebrew_zade": 3318, + "hebrew_zadi": 3318, + "hebrew_zain": 3302, + "hebrew_zayin": 3302, + "hexagram": 2778, + "horizconnector": 2211, + "horizlinescan1": 2543, + "horizlinescan3": 2544, + "horizlinescan5": 2545, + "horizlinescan7": 2546, + "horizlinescan9": 2547, + "hstroke": 689, + "ht": 2530, + "hyphen": 173, + "i": 105, + "iacute": 237, + "ibelowdot": 16785099, + "ibreve": 16777517, + "icircumflex": 238, + "identical": 2255, + "idiaeresis": 239, + "idotless": 697, + "ifonlyif": 2253, + "igrave": 236, + "ihook": 16785097, + "imacron": 1007, + "implies": 2254, + "includedin": 2266, + "includes": 2267, + "infinity": 2242, + "integral": 2239, + "intersection": 2268, + "iogonek": 999, + "itilde": 949, + "j": 106, + "jcircumflex": 700, + "jot": 3018, + "k": 107, + "kana_A": 1201, + "kana_CHI": 1217, + "kana_E": 1204, + "kana_FU": 1228, + "kana_HA": 1226, + "kana_HE": 1229, + "kana_HI": 1227, + "kana_HO": 1230, + "kana_HU": 1228, + "kana_I": 1202, + "kana_KA": 1206, + "kana_KE": 1209, + "kana_KI": 1207, + "kana_KO": 1210, + "kana_KU": 1208, + "kana_MA": 1231, + "kana_ME": 1234, + "kana_MI": 1232, + "kana_MO": 1235, + "kana_MU": 1233, + "kana_N": 1245, + "kana_NA": 1221, + "kana_NE": 1224, + "kana_NI": 1222, + "kana_NO": 1225, + "kana_NU": 1223, + "kana_O": 1205, + "kana_RA": 1239, + "kana_RE": 1242, + "kana_RI": 1240, + "kana_RO": 1243, + "kana_RU": 1241, + "kana_SA": 1211, + "kana_SE": 1214, + "kana_SHI": 1212, + "kana_SO": 1215, + "kana_SU": 1213, + "kana_TA": 1216, + "kana_TE": 1219, + "kana_TI": 1217, + "kana_TO": 1220, + "kana_TSU": 1218, + "kana_TU": 1218, + "kana_U": 1203, + "kana_WA": 1244, + "kana_WO": 1190, + "kana_YA": 1236, + "kana_YO": 1238, + "kana_YU": 1237, + "kana_a": 1191, + "kana_closingbracket": 1187, + "kana_comma": 1188, + "kana_conjunctive": 1189, + "kana_e": 1194, + "kana_fullstop": 1185, + "kana_i": 1192, + "kana_middledot": 1189, + "kana_o": 1195, + "kana_openingbracket": 1186, + "kana_switch": 65406, + "kana_tsu": 1199, + "kana_tu": 1199, + "kana_u": 1193, + "kana_ya": 1196, + "kana_yo": 1198, + "kana_yu": 1197, + "kappa": 930, + "kcedilla": 1011, + "kra": 930, + "l": 108, + "lacute": 485, + "latincross": 2777, + "lbelowdot": 16784951, + "lcaron": 437, + "lcedilla": 950, + "leftanglebracket": 2748, + "leftarrow": 2299, + "leftcaret": 2979, + "leftdoublequotemark": 2770, + "leftmiddlecurlybrace": 2223, + "leftopentriangle": 2764, + "leftpointer": 2794, + "leftradical": 2209, + "leftshoe": 3034, + "leftsinglequotemark": 2768, + "leftt": 2548, + "lefttack": 3036, + "less": 60, + "lessthanequal": 2236, + "lf": 2533, + "logicaland": 2270, + "logicalor": 2271, + "lowleftcorner": 2541, + "lowrightcorner": 2538, + "lstroke": 435, + "m": 109, + "mabovedot": 16784961, + "macron": 175, + "malesymbol": 2807, + "maltesecross": 2800, + "marker": 2751, + "masculine": 186, + "minus": 45, + "minutes": 2774, + "mu": 181, + "multiply": 215, + "musicalflat": 2806, + "musicalsharp": 2805, + "n": 110, + "nabla": 2245, + "nacute": 497, + "ncaron": 498, + "ncedilla": 1009, + "ninesubscript": 16785545, + "ninesuperior": 16785529, + "nl": 2536, + "nobreakspace": 160, + "notapproxeq": 16785991, + "notelementof": 16785929, + "notequal": 2237, + "notidentical": 16786018, + "notsign": 172, + "ntilde": 241, + "numbersign": 35, + "numerosign": 1712, + "o": 111, + "oacute": 243, + "obarred": 16777845, + "obelowdot": 16785101, + "ocaron": 16777682, + "ocircumflex": 244, + "ocircumflexacute": 16785105, + "ocircumflexbelowdot": 16785113, + "ocircumflexgrave": 16785107, + "ocircumflexhook": 16785109, + "ocircumflextilde": 16785111, + "odiaeresis": 246, + "odoubleacute": 501, + "oe": 5053, + "ogonek": 434, + "ograve": 242, + "ohook": 16785103, + "ohorn": 16777633, + "ohornacute": 16785115, + "ohornbelowdot": 16785123, + "ohorngrave": 16785117, + "ohornhook": 16785119, + "ohorntilde": 16785121, + "omacron": 1010, + "oneeighth": 2755, + "onefifth": 2738, + "onehalf": 189, + "onequarter": 188, + "onesixth": 2742, + "onesubscript": 16785537, + "onesuperior": 185, + "onethird": 2736, + "ooblique": 248, + "openrectbullet": 2786, + "openstar": 2789, + "opentribulletdown": 2788, + "opentribulletup": 2787, + "ordfeminine": 170, + "ordmasculine": 186, + "oslash": 248, + "otilde": 245, + "overbar": 3008, + "overline": 1150, + "p": 112, + "pabovedot": 16784983, + "paragraph": 182, + "parenleft": 40, + "parenright": 41, + "partdifferential": 16785922, + "partialderivative": 2287, + "percent": 37, + "period": 46, + "periodcentered": 183, + "permille": 2773, + "phonographcopyright": 2811, + "plus": 43, + "plusminus": 177, + "prescription": 2772, + "prolongedsound": 1200, + "punctspace": 2726, + "q": 113, + "quad": 3020, + "question": 63, + "questiondown": 191, + "quotedbl": 34, + "quoteleft": 96, + "quoteright": 39, + "r": 114, + "racute": 480, + "radical": 2262, + "rcaron": 504, + "rcedilla": 947, + "registered": 174, + "rightanglebracket": 2750, + "rightarrow": 2301, + "rightcaret": 2982, + "rightdoublequotemark": 2771, + "rightmiddlecurlybrace": 2224, + "rightmiddlesummation": 2231, + "rightopentriangle": 2765, + "rightpointer": 2795, + "rightshoe": 3032, + "rightsinglequotemark": 2769, + "rightt": 2549, + "righttack": 3068, + "s": 115, + "sabovedot": 16784993, + "sacute": 438, + "scaron": 441, + "scedilla": 442, + "schwa": 16777817, + "scircumflex": 766, + "script_switch": 65406, + "seconds": 2775, + "section": 167, + "semicolon": 59, + "semivoicedsound": 1247, + "seveneighths": 2758, + "sevensubscript": 16785543, + "sevensuperior": 16785527, + "signaturemark": 2762, + "signifblank": 2732, + "similarequal": 2249, + "singlelowquotemark": 2813, + "sixsubscript": 16785542, + "sixsuperior": 16785526, + "slash": 47, + "soliddiamond": 2528, + "space": 32, + "squareroot": 16785946, + "ssharp": 223, + "sterling": 163, + "stricteq": 16786019, + "t": 116, + "tabovedot": 16785003, + "tcaron": 443, + "tcedilla": 510, + "telephone": 2809, + "telephonerecorder": 2810, + "therefore": 2240, + "thinspace": 2727, + "thorn": 254, + "threeeighths": 2756, + "threefifths": 2740, + "threequarters": 190, + "threesubscript": 16785539, + "threesuperior": 179, + "tintegral": 16785965, + "topintegral": 2212, + "topleftparens": 2219, + "topleftradical": 2210, + "topleftsqbracket": 2215, + "topleftsummation": 2225, + "toprightparens": 2221, + "toprightsqbracket": 2217, + "toprightsummation": 2229, + "topt": 2551, + "topvertsummationconnector": 2227, + "trademark": 2761, + "trademarkincircle": 2763, + "tslash": 956, + "twofifths": 2739, + "twosubscript": 16785538, + "twosuperior": 178, + "twothirds": 2737, + "u": 117, + "uacute": 250, + "ubelowdot": 16785125, + "ubreve": 765, + "ucircumflex": 251, + "udiaeresis": 252, + "udoubleacute": 507, + "ugrave": 249, + "uhook": 16785127, + "uhorn": 16777648, + "uhornacute": 16785129, + "uhornbelowdot": 16785137, + "uhorngrave": 16785131, + "uhornhook": 16785133, + "uhorntilde": 16785135, + "umacron": 1022, + "underbar": 3014, + "underscore": 95, + "union": 2269, + "uogonek": 1017, + "uparrow": 2300, + "upcaret": 2985, + "upleftcorner": 2540, + "uprightcorner": 2539, + "upshoe": 3011, + "upstile": 3027, + "uptack": 3022, + "uring": 505, + "utilde": 1021, + "v": 118, + "variation": 2241, + "vertbar": 2552, + "vertconnector": 2214, + "voicedsound": 1246, + "vt": 2537, + "w": 119, + "wacute": 16785027, + "wcircumflex": 16777589, + "wdiaeresis": 16785029, + "wgrave": 16785025, + "x": 120, + "xabovedot": 16785035, + "y": 121, + "yacute": 253, + "ybelowdot": 16785141, + "ycircumflex": 16777591, + "ydiaeresis": 255, + "yen": 165, + "ygrave": 16785139, + "yhook": 16785143, + "ytilde": 16785145, + "z": 122, + "zabovedot": 447, + "zacute": 444, + "zcaron": 446, + "zerosubscript": 16785536, + "zerosuperior": 16785520, + "zstroke": 16777654, +} diff --git a/lib/logitech_receiver/__init__.py b/lib/logitech_receiver/__init__.py index 9625ddb2..9ae782db 100644 --- a/lib/logitech_receiver/__init__.py +++ b/lib/logitech_receiver/__init__.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -15,35 +13,12 @@ ## You should have received a copy of the GNU General Public License along ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -"""Low-level interface for devices connected through a Logitech Universal -Receiver (UR). +"""Low-level interface for devices using Logitech HID++ protocol. -Uses the HID api exposed through hidapi.py, a Python thin layer over a native +Uses the HID api exposed through hidapi_impl.py, a Python thin layer over a native implementation. - -Incomplete. Based on a bit of documentation, trial-and-error, and guesswork. - -References: -http://julien.danjou.info/blog/2012/logitech-k750-linux-support -http://6xq.net/git/lars/lshidpp.git/plain/doc/ """ import logging -from . import listener, status # noqa: F401 -from .base import DeviceUnreachable, NoReceiver, NoSuchDevice # noqa: F401 -from .common import strhex # noqa: F401 -from .device import Device # noqa: F401 -from .hidpp20 import FeatureCallError, FeatureNotSupported # noqa: F401 -from .receiver import Receiver # noqa: F401 - -_DEBUG = logging.DEBUG -_log = logging.getLogger(__name__) -_log.setLevel(logging.root.level) -# if logging.root.level > logging.DEBUG: -# _log.addHandler(logging.NullHandler()) -# _log.propagate = 0 - -del logging - -__version__ = '0.9' +logger = logging.getLogger(__name__) diff --git a/lib/logitech_receiver/base.py b/lib/logitech_receiver/base.py index c9dd1c63..b66e2a1e 100644 --- a/lib/logitech_receiver/base.py +++ b/lib/logitech_receiver/base.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -16,38 +14,80 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# Base low-level functions used by the API proper. -# Unlikely to be used directly unless you're expanding the API. +"""Base low-level functions as API for upper layers.""" -import threading as _threading +from __future__ import annotations + +import dataclasses +import logging +import platform +import struct +import threading +import typing -from collections import namedtuple from contextlib import contextmanager -from logging import DEBUG as _DEBUG -from logging import INFO as _INFO -from logging import getLogger -from random import getrandbits as _random_bits -from struct import pack as _pack -from time import time as _timestamp +from random import getrandbits +from time import time +from typing import Any +from typing import Callable -import hidapi as _hid +from . import base_usb +from . import common +from . import descriptors +from . import exceptions +from .common import LOGITECH_VENDOR_ID +from .common import BusID +from .hidpp10_constants import ErrorCode as Hidpp10ErrorCode +from .hidpp20_constants import ErrorCode as Hidpp20ErrorCode -from . import hidpp10 as _hidpp10 -from . import hidpp20 as _hidpp20 -from .base_usb import ALL as _RECEIVER_USB_IDS -from .base_usb import DEVICES as _DEVICE_IDS -from .base_usb import other_device_check as _other_device_check -from .common import KwException as _KwException -from .common import strhex as _strhex +if typing.TYPE_CHECKING: + import gi -_log = getLogger(__name__) -del getLogger + from hidapi.common import DeviceInfo -# -# -# + gi.require_version("Gdk", "3.0") + from gi.repository import GLib # NOQA: E402 -_SHORT_MESSAGE_SIZE = 7 +if platform.system() == "Linux": + import hidapi.udev_impl as hidapi +else: + import hidapi.hidapi_impl as hidapi + +logger = logging.getLogger(__name__) + + +class HIDProtocol(typing.Protocol): + def find_paired_node_wpid(self, receiver_path: str, index: int): + ... + + def find_paired_node(self, receiver_path: str, index: int, timeout: int): + ... + + def open(self, vendor_id, product_id, serial=None): + ... + + def open_path(self, path) -> int: + ... + + def enumerate(self, filter_func: Callable[[int, int, int, bool, bool], dict[str, typing.Any]]) -> DeviceInfo: + ... + + def monitor_glib( + self, glib: GLib, callback: Callable, filter_func: Callable[[int, int, int, bool, bool], dict[str, typing.Any]] + ) -> None: + ... + + def read(self, device_handle, bytes_count, timeout_ms): + ... + + def write(self, device_handle: int, data: bytes) -> int: + ... + + def close(self, device_handle) -> None: + ... + + +SHORT_MESSAGE_SIZE = 7 _LONG_MESSAGE_SIZE = 20 _MEDIUM_MESSAGE_SIZE = 15 _MAX_READ_SIZE = 32 @@ -56,13 +96,21 @@ HIDPP_SHORT_MESSAGE_ID = 0x10 HIDPP_LONG_MESSAGE_ID = 0x11 DJ_MESSAGE_ID = 0x20 -# mapping from report_id to message length -report_lengths = { - HIDPP_SHORT_MESSAGE_ID: _SHORT_MESSAGE_SIZE, - HIDPP_LONG_MESSAGE_ID: _LONG_MESSAGE_SIZE, - DJ_MESSAGE_ID: _MEDIUM_MESSAGE_SIZE, - 0x21: _MAX_READ_SIZE -} +# Centurion transport (used by PRO X 2 LIGHTSPEED headset and similar) +# Uses report ID 0x51 on usage page 0xFFA0, 64-byte frames. +# Wire format (CPL): [0x51, cpl_length, flags=0x00, feat_idx, func_sw, params..., pad] +# cpl_length = number of bytes from flags to end of meaningful data (includes flags byte). +# The device_index byte from standard HID++ is NOT present in Centurion framing. +CENTURION_REPORT_ID = 0x51 +CENTURION_FRAME_SIZE = 64 # 1 byte report ID + 63 bytes payload +_CENTURION_MSG_SIZE = 63 # max reconstructed message size after unwrapping (2 + 61 payload bytes) + +# Set of handles that use Centurion framing +_centurion_handles: set[int] = set() +# Raw Centurion protocol version (major, minor) by handle, from ping response +_centurion_protocol_versions: dict[int, tuple[int, int]] = {} + + """Default timeout on read (in seconds).""" DEFAULT_TIMEOUT = 4 # the receiver itself should reply very fast, within 500ms @@ -72,84 +120,156 @@ _DEVICE_REQUEST_TIMEOUT = DEFAULT_TIMEOUT # when pinging, be extra patient (no longer) _PING_TIMEOUT = DEFAULT_TIMEOUT -# -# Exceptions that may be raised by this API. -# +hidapi = typing.cast(HIDProtocol, hidapi) + +request_lock = threading.Lock() # serialize all requests +handles_lock = {} -class NoReceiver(_KwException): - """Raised when trying to talk through a previously open handle, when the - receiver is no longer available. Should only happen if the receiver is - physically disconnected from the machine, or its kernel driver module is - unloaded.""" - pass +@dataclasses.dataclass +class HIDPPNotification: + report_id: int + devnumber: int + sub_id: int + address: int + data: bytes + + def __str__(self): + text_as_hex = common.strhex(self.data) + return f"Notification({self.report_id:02x},{self.devnumber},{self.sub_id:02X},{self.address:02X},{text_as_hex})" -class NoSuchDevice(_KwException): - """Raised when trying to reach a device number not paired to the receiver.""" - pass +def _usb_device(product_id: int, usb_interface: int) -> dict[str, Any]: + return { + "vendor_id": LOGITECH_VENDOR_ID, + "product_id": product_id, + "bus_id": BusID.USB, + "usb_interface": usb_interface, + "isDevice": True, + } -class DeviceUnreachable(_KwException): - """Raised when a request is made to an unreachable (turned off) device.""" - pass +def _bluetooth_device(product_id: int) -> dict[str, Any]: + return {"vendor_id": LOGITECH_VENDOR_ID, "product_id": product_id, "bus_id": BusID.BLUETOOTH, "isDevice": True} -# -# -# +KNOWN_DEVICE_IDS = [] + +for _ignore, d in descriptors.DEVICES.items(): + if d.usbid: + usb_interface = d.interface if d.interface else 2 + KNOWN_DEVICE_IDS.append(_usb_device(d.usbid, usb_interface)) + if d.btid: + KNOWN_DEVICE_IDS.append(_bluetooth_device(d.btid)) -def match(record, bus_id, vendor_id, product_id): - return ((record.get('bus_id') is None or record.get('bus_id') == bus_id) - and (record.get('vendor_id') is None or record.get('vendor_id') == vendor_id) - and (record.get('product_id') is None or record.get('product_id') == product_id)) - - -def filter_receivers(bus_id, vendor_id, product_id, hidpp_short=False, hidpp_long=False): - """Check that this product is a Logitech receiver and if so return the receiver record for further checking""" - for record in _RECEIVER_USB_IDS: # known receivers - if match(record, bus_id, vendor_id, product_id): - return record - if vendor_id == 0x046D and 0xC500 <= product_id <= 0xC5FF: # unknown receiver - return {'vendor_id': vendor_id, 'product_id': product_id, 'bus_id': bus_id, 'isDevice': False} +def product_information(usb_id: int) -> dict[str, Any]: + """Returns hardcoded information from USB receiver.""" + return base_usb.get_receiver_info(usb_id) def receivers(): """Enumerate all the receivers attached to the machine.""" - yield from _hid.enumerate(filter_receivers) + yield from hidapi.enumerate(get_known_receiver_info) -def filter(bus_id, vendor_id, product_id, hidpp_short=False, hidpp_long=False): +def filter_products_of_interest( + bus_id: int, vendor_id: int, product_id: int, hidpp_short: bool = False, hidpp_long: bool = False +) -> dict[str, Any] | None: """Check that this product is of interest and if so return the device record for further checking""" - record = filter_receivers(bus_id, vendor_id, product_id, hidpp_short, hidpp_long) - if record: # known or unknown receiver - return record - for record in _DEVICE_IDS: # known devices - if match(record, bus_id, vendor_id, product_id): + + recv = get_known_receiver_info(bus_id, vendor_id, product_id, hidpp_short, hidpp_long) + if recv: # known or unknown receiver + return recv + + device = get_known_device_info(bus_id, vendor_id, product_id) + if device: + return device + + if hidpp_short or hidpp_long: + return get_unknown_hid_device_info(bus_id, vendor_id, product_id) + + if hidpp_short is None and hidpp_long is None: + return get_unknown_logitech_device_info(bus_id, vendor_id, product_id) + return None + + +def get_known_device_info(bus_id: int, vendor_id: int, product_id: int) -> dict[str, Any]: + for recv in KNOWN_DEVICE_IDS: + if _match_device(recv, bus_id, vendor_id, product_id): + return recv + + +def get_unknown_hid_device_info(bus_id: int, vendor_id: int, product_id: int) -> dict[str, Any]: + return {"vendor_id": vendor_id, "product_id": product_id, "bus_id": bus_id, "isDevice": True} + + +def get_unknown_logitech_device_info(bus_id: int, vendor_id: int, product_id: int) -> dict[str, Any] | None: + """Get info from unknown device in Logitech product range. + + Check whether product is a Logitech USB-connected or Bluetooth + device based on bus, vendor, and product ID. This allows Solaar to + support receiverless HID++ 2.0 devices that it knows nothing about. + """ + if vendor_id != LOGITECH_VENDOR_ID: + return None + + if bus_id == BusID.USB.value and (0xC07D <= product_id <= 0xC094 or 0xC32B <= product_id <= 0xC344): + device_info = _usb_device(product_id, 2) + return device_info + elif bus_id == BusID.BLUETOOTH.value and (0xB012 <= product_id <= 0xB0FF or 0xB317 <= product_id <= 0xB3FF): + device_info = _bluetooth_device(product_id) + return device_info + + return None + + +def _match_device(record: dict[str, Any], bus_id: int, vendor_id: int, product_id: int): + return ( + (record.get("bus_id") is None or record.get("bus_id") == bus_id) + and (record.get("vendor_id") is None or record.get("vendor_id") == vendor_id) + and (record.get("product_id") is None or record.get("product_id") == product_id) + ) + + +def get_known_receiver_info( + bus_id: int, vendor_id: int, product_id: int, _hidpp_short: bool = False, _hidpp_long: bool = False +) -> dict[str, Any]: + """Check that this product is a Logitech receiver and return it. + + Filters based on bus_id, vendor_id and product_id. + + If so return the receiver record for further checking. + """ + try: + record = base_usb.get_receiver_info(product_id) + if _match_device(record, bus_id, vendor_id, product_id): return record - if hidpp_short or hidpp_long: # unknown devices that use HID++ - return {'vendor_id': vendor_id, 'product_id': product_id, 'bus_id': bus_id, 'isDevice': True} - elif hidpp_short is None and hidpp_long is None: # unknown devices in correct range of IDs - return _other_device_check(bus_id, vendor_id, product_id) + except ValueError: + pass + + if vendor_id == LOGITECH_VENDOR_ID and 0xC500 <= product_id <= 0xC5FF: # unknown receiver + return {"vendor_id": vendor_id, "product_id": product_id, "bus_id": bus_id, "isDevice": False} + return None def receivers_and_devices(): """Enumerate all the receivers and devices directly attached to the machine.""" - yield from _hid.enumerate(filter) + yield from hidapi.enumerate(filter_products_of_interest) -def notify_on_receivers_glib(callback): - """Watch for matching devices and notifies the callback on the GLib thread.""" - return _hid.monitor_glib(callback, filter) +def notify_on_receivers_glib(glib: GLib, callback: Callable): + """Watch for matching devices and notifies the callback on the GLib thread. + + Parameters + ---------- + glib + GLib instance. + """ + return hidapi.monitor_glib(glib, callback, filter_products_of_interest) -# -# -# - - -def open_path(path): +def open_path(path) -> int: """Checks if the given Linux device path points to the right UR device. :param path: the Linux device path. @@ -162,7 +282,7 @@ def open_path(path): :returns: an open receiver handle if this is the right Linux device, or ``None``. """ - return _hid.open_path(path) + return hidapi.open_path(path) def open(): @@ -181,13 +301,13 @@ def close(handle): if handle: try: if isinstance(handle, int): - _hid.close(handle) + _centurion_handles.discard(handle) + _centurion_protocol_versions.pop(handle, None) + hidapi.close(handle) else: handle.close() - # _log.info("closed receiver handle %r", handle) return True except Exception: - # _log.exception("closing receiver handle %r", handle) pass return False @@ -210,19 +330,63 @@ def write(handle, devnumber, data, long_message=False): assert data is not None assert isinstance(data, bytes), (repr(data), type(data)) - if long_message or len(data) > _SHORT_MESSAGE_SIZE - 2 or data[:1] == b'\x82': - wdata = _pack('!BB18s', HIDPP_LONG_MESSAGE_ID, devnumber, data) + if long_message or len(data) > SHORT_MESSAGE_SIZE - 2 or data[:1] == b"\x82": + wdata = struct.pack("!BB18s", HIDPP_LONG_MESSAGE_ID, devnumber, data) else: - wdata = _pack('!BB5s', HIDPP_SHORT_MESSAGE_ID, devnumber, data) - if _log.isEnabledFor(_DEBUG): - _log.debug('(%s) <= w[%02X %02X %s %s]', handle, ord(wdata[:1]), devnumber, _strhex(wdata[2:4]), _strhex(wdata[4:])) + wdata = struct.pack("!BB5s", HIDPP_SHORT_MESSAGE_ID, devnumber, data) + + ihandle = int(handle) + if ihandle in _centurion_handles: + # Centurion CPL framing: [0x51, cpl_length, flags=0x00, feat_idx, func_sw, params...] + # cpl_length = len(meaningful_payload) + 1 (the +1 counts the flags byte) + # The device_index is stripped — only the HID++ payload (feat_idx + func_sw + params) remains. + payload = wdata[2:] # skip report_id and devnumber from standard frame + cpl_length = len(data) + 1 # data is the unpadded payload; +1 for flags byte + wdata = struct.pack("!BBB", CENTURION_REPORT_ID, cpl_length, 0x00) + payload + wdata = wdata + b"\x00" * (CENTURION_FRAME_SIZE - len(wdata)) + + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + "(%s) <= w[%02X %02X %s %s]", + handle, + ord(wdata[:1]), + devnumber, + common.strhex(wdata[2:4]), + common.strhex(wdata[4:]), + ) try: - _hid.write(int(handle), wdata) + hidapi.write(ihandle, wdata) except Exception as reason: - _log.error('write failed, assuming handle %r no longer available', handle) + logger.error("write failed, assuming handle %r no longer available", handle) close(handle) - raise NoReceiver(reason=reason) + raise exceptions.NoReceiver(reason=reason) from reason + + +def write_centurion_cpl(handle, layer3_payload, flags=0x00): + """Send a Centurion CPL frame with the given Layer 3+ payload. + + Builds: [0x51, cpl_length, flags, layer3_payload..., zero-pad to 64 bytes] + where cpl_length = len(layer3_payload) + 1 (the +1 counts the flags byte). + + For multi-fragment sends, flags encodes fragment index and continuation: + flags = (fragment_index << 1) | (1 if more_fragments else 0) + Single-frame messages use flags=0x00 (default). + """ + ihandle = int(handle) + if ihandle not in _centurion_handles: + raise ValueError("write_centurion_cpl called on non-Centurion handle") + cpl_length = len(layer3_payload) + 1 # +1 for flags byte + wdata = struct.pack("!BBB", CENTURION_REPORT_ID, cpl_length, flags) + layer3_payload + wdata = wdata + b"\x00" * (CENTURION_FRAME_SIZE - len(wdata)) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("(%s) <= centurion_cpl[%s]", handle, common.strhex(wdata[: cpl_length + 2])) + try: + hidapi.write(ihandle, wdata) + except Exception as reason: + logger.error("write failed, assuming handle %r no longer available", handle) + close(handle) + raise exceptions.NoReceiver(reason=reason) from reason def read(handle, timeout=DEFAULT_TIMEOUT): @@ -243,19 +407,31 @@ def read(handle, timeout=DEFAULT_TIMEOUT): return reply -# sanity checks on message report id and size -def check_message(data): +def _is_relevant_message(data: bytes) -> bool: + """Checks if given id is a HID++ or DJ message. + + Applies sanity checks on message report ID and message size. + """ assert isinstance(data, bytes), (repr(data), type(data)) + + # mapping from report_id to accepted message lengths + report_lengths = { + HIDPP_SHORT_MESSAGE_ID: (SHORT_MESSAGE_SIZE,), + HIDPP_LONG_MESSAGE_ID: (_LONG_MESSAGE_SIZE, _CENTURION_MSG_SIZE), + DJ_MESSAGE_ID: (_MEDIUM_MESSAGE_SIZE,), + 0x21: (_MAX_READ_SIZE,), + } + report_id = ord(data[:1]) - if report_id in report_lengths: # is this an HID++ or DJ message? - if report_lengths.get(report_id) == len(data): + if report_id in report_lengths: + if len(data) in report_lengths[report_id]: return True else: - _log.warn('unexpected message size: report_id %02X message %s' % (report_id, _strhex(data))) + logger.warning(f"unexpected message size: report_id {report_id:02X} message {common.strhex(data)}") return False -def _read(handle, timeout): +def _read(handle, timeout) -> tuple[int, int, bytes]: """Read an incoming packet from the receiver. :returns: a tuple of (report_id, devnumber, data), or `None`. @@ -264,108 +440,95 @@ def _read(handle, timeout): been physically removed from the machine, or the kernel driver has been unloaded. The handle will be closed automatically. """ + ihandle = int(handle) + is_centurion = ihandle in _centurion_handles + read_size = CENTURION_FRAME_SIZE if is_centurion else _MAX_READ_SIZE try: # convert timeout to milliseconds, the hidapi expects it timeout = int(timeout * 1000) - data = _hid.read(int(handle), _MAX_READ_SIZE, timeout) + data = hidapi.read(ihandle, read_size, timeout) except Exception as reason: - _log.warn('read failed, assuming handle %r no longer available', handle) + logger.warning("read failed, assuming handle %r no longer available", handle) close(handle) - raise NoReceiver(reason=reason) + raise exceptions.NoReceiver(reason=reason) from reason - if data and check_message(data): # ignore messages that fail check + if data and is_centurion and ord(data[:1]) == CENTURION_REPORT_ID: + # Unwrap Centurion CPL framing: + # RX: [0x51, cpl_length, flags=0x00, feat_idx, func_sw, data...] + # cpl_length includes the flags byte, so meaningful payload starts at byte 3 + # and has (cpl_length - 1) bytes. + # Reconstruct as HID++ long message: [0x11, devnumber=0xFF, feat_idx, func_sw, data...] + cpl_length = ord(data[1:2]) + inner_payload = data[3 : 2 + cpl_length] # bytes 3..2+cpl_length-1 = cpl_length-1 bytes + data = bytes([HIDPP_LONG_MESSAGE_ID, 0xFF]) + inner_payload + # Pad to a valid message size: standard long (20) or Centurion extended (63) + if len(data) <= _LONG_MESSAGE_SIZE: + data = data + b"\x00" * (_LONG_MESSAGE_SIZE - len(data)) + elif len(data) <= _CENTURION_MSG_SIZE: + data = data + b"\x00" * (_CENTURION_MSG_SIZE - len(data)) + else: + data = data[:_CENTURION_MSG_SIZE] + + if data and _is_relevant_message(data): # ignore messages that fail check report_id = ord(data[:1]) devnumber = ord(data[1:2]) - if _log.isEnabledFor(_DEBUG) and (report_id != DJ_MESSAGE_ID or ord(data[2:3]) > 0x10): # ignore DJ input messages - _log.debug('(%s) => r[%02X %02X %s %s]', handle, report_id, devnumber, _strhex(data[2:4]), _strhex(data[4:])) + if logger.isEnabledFor(logging.DEBUG) and ( + report_id != DJ_MESSAGE_ID or ord(data[2:3]) > 0x10 + ): # ignore DJ input messages + logger.debug( + "(%s) => r[%02X %02X %s %s]", + handle, + report_id, + devnumber, + common.strhex(data[2:4]), + common.strhex(data[4:]), + ) return report_id, devnumber, data[2:] -# -# -# - - -def _skip_incoming(handle, ihandle, notifications_hook): - """Read anything already in the input buffer. - - Used by request() and ping() before their write. - """ - - while True: - try: - # read whatever is already in the buffer, if any - data = _hid.read(ihandle, _MAX_READ_SIZE, 0) - except Exception as reason: - _log.error('read failed, assuming receiver %s no longer available', handle) - close(handle) - raise NoReceiver(reason=reason) - - if data: - if check_message(data): # only process messages that pass check - # report_id = ord(data[:1]) - if notifications_hook: - n = make_notification(ord(data[:1]), ord(data[1:2]), data[2:]) - if n: - notifications_hook(n) - else: - # nothing in the input buffer, we're done - return - - -def make_notification(report_id, devnumber, data): +def make_notification(report_id: int, devnumber: int, data: bytes) -> HIDPPNotification | None: """Guess if this is a notification (and not just a request reply), and - return a Notification tuple if it is.""" + return a Notification if it is.""" sub_id = ord(data[:1]) if sub_id & 0x80 == 0x80: # this is either a HID++1.0 register r/w, or an error reply - return + return None # DJ input records are not notifications if report_id == DJ_MESSAGE_ID and (sub_id < 0x10): - return + return None address = ord(data[1:2]) if sub_id == 0x00 and (address & 0x0F == 0x00): # this is a no-op notification - don't do anything with it - return + return None if ( # standard HID++ 1.0 notification, SubId may be 0x40 - 0x7F - (sub_id >= 0x40) or # noqa: E131 + (sub_id >= 0x40) # noqa: E131 + or # custom HID++1.0 battery events, where SubId is 0x07/0x0D - (sub_id in (0x07, 0x0D) and len(data) == 5 and data[4:5] == b'\x00') or + (sub_id in (0x07, 0x0D) and len(data) == 5 and data[4:5] == b"\x00") + or # custom HID++1.0 illumination event, where SubId is 0x17 - (sub_id == 0x17 and len(data) == 5) or + (sub_id == 0x17 and len(data) == 5) + or # HID++ 2.0 feature notifications have the SoftwareID 0 (address & 0x0F == 0x00) ): # noqa: E129 - return _HIDPP_Notification(report_id, devnumber, sub_id, address, data[2:]) - - -_HIDPP_Notification = namedtuple('_HIDPP_Notification', ('report_id', 'devnumber', 'sub_id', 'address', 'data')) -_HIDPP_Notification.__str__ = lambda self: 'Notification(%02x,%d,%02X,%02X,%s)' % ( - self.report_id, self.devnumber, self.sub_id, self.address, _strhex(self.data) -) -del namedtuple - -# -# -# - -request_lock = _threading.Lock() # serialize all requests -handles_lock = {} + return HIDPPNotification(report_id, devnumber, sub_id, address, data[2:]) + return None def handle_lock(handle): with request_lock: if handles_lock.get(handle) is None: - if _log.isEnabledFor(_INFO): - _log.info('New lock %s', repr(handle)) - handles_lock[handle] = _threading.Lock() # Serialize requests on the handle + if logger.isEnabledFor(logging.INFO): + logger.info("New lock %s", repr(handle)) + handles_lock[handle] = threading.Lock() # Serialize requests on the handle return handles_lock[handle] @@ -375,15 +538,37 @@ def acquire_timeout(lock, handle, timeout): result = lock.acquire(timeout=timeout) try: if not result: - _log.error('lock on handle %d not acquired, probably due to timeout', int(handle)) + logger.error("lock on handle %d not acquired, probably due to timeout", int(handle)) yield result finally: if result: lock.release() +def find_paired_node(receiver_path: str, index: int, timeout: int): + """Find the node of a device paired with a receiver.""" + return hidapi.find_paired_node(receiver_path, index, timeout) + + +def find_paired_node_wpid(receiver_path: str, index: int): + """Find the node of a device paired with a receiver. + + Get wpid from udev. + """ + return hidapi.find_paired_node_wpid(receiver_path, index) + + # a very few requests (e.g., host switching) do not expect a reply, but use no_reply=True with extreme caution -def request(handle, devnumber, request_id, *params, no_reply=False, return_error=False, long_message=False, protocol=1.0): +def request( + handle, + devnumber, + request_id: int, + *params, + no_reply: bool = False, + return_error: bool = False, + long_message: bool = False, + protocol: float = 1.0, +): """Makes a feature call to a device and waits for a matching reply. :param handle: an open UR handle. :param devnumber: attached device number. @@ -391,19 +576,14 @@ def request(handle, devnumber, request_id, *params, no_reply=False, return_error :param params: parameters for the feature call, 3 to 16 bytes. :returns: the reply data, or ``None`` if some error occurred. or no reply expected """ - - # import inspect as _inspect - # print ('\n '.join(str(s) for s in _inspect.stack())) - - with acquire_timeout(handle_lock(handle), handle, 10.): + with acquire_timeout(handle_lock(handle), handle, 10.0): assert isinstance(request_id, int) if (devnumber != 0xFF or protocol >= 2.0) and request_id < 0x8000: - # For HID++ 2.0 feature requests, randomize the SoftwareId to make it - # easier to recognize the reply for this request. also, always set the - # most significant bit (8) in SoftwareId, to make notifications easier - # to distinguish from request replies. + # Always set the most significant bit (8) in SoftwareId, + # to make notifications easier to distinguish from request replies. # This only applies to peripheral requests, ofc. - request_id = (request_id & 0xFFF0) | 0x08 | _random_bits(3) + sw_id = _get_next_sw_id() + request_id = (request_id & 0xFFF0) | sw_id # was 0x08 | getrandbits(3) timeout = _RECEIVER_REQUEST_TIMEOUT if devnumber == 0xFF else _DEVICE_REQUEST_TIMEOUT # be extra patient on long register read @@ -411,19 +591,17 @@ def request(handle, devnumber, request_id, *params, no_reply=False, return_error timeout *= 2 if params: - params = b''.join(_pack('B', p) if isinstance(p, int) else p for p in params) + params = b"".join(struct.pack("B", p) if isinstance(p, int) else p for p in params) else: - params = b'' - # if _log.isEnabledFor(_DEBUG): - # _log.debug("(%s) device %d request_id {%04X} params [%s]", handle, devnumber, request_id, _strhex(params)) - request_data = _pack('!H', request_id) + params + params = b"" + request_data = struct.pack("!H", request_id) + params ihandle = int(handle) - notifications_hook = getattr(handle, 'notifications_hook', None) + notifications_hook = getattr(handle, "notifications_hook", None) try: - _skip_incoming(handle, ihandle, notifications_hook) - except NoReceiver: - _log.warn('device or receiver disconnected') + _read_input_buffer(handle, ihandle, notifications_hook) + except exceptions.NoReceiver: + logger.warning("device or receiver disconnected") return None write(ihandle, devnumber, request_data, long_message) @@ -431,33 +609,52 @@ def request(handle, devnumber, request_id, *params, no_reply=False, return_error return None # we consider timeout from this point - request_started = _timestamp() + request_started = time() delta = 0 while delta < timeout: reply = _read(handle, timeout) - if reply: report_id, reply_devnumber, reply_data = reply - if reply_devnumber == devnumber or reply_devnumber == devnumber ^ 0xff: # BT device returning 0x00 - if report_id == HIDPP_SHORT_MESSAGE_ID and reply_data[:1] == b'\x8F' and reply_data[1:3] == request_data[:2 - ]: + if reply_devnumber == devnumber or reply_devnumber == devnumber ^ 0xFF: # BT device returning 0x00 + if ( + report_id == HIDPP_SHORT_MESSAGE_ID + and reply_data[:1] == b"\x8f" + and reply_data[1:3] == request_data[:2] + ): error = ord(reply_data[3:4]) - if _log.isEnabledFor(_DEBUG): - _log.debug( - '(%s) device 0x%02X error on request {%04X}: %d = %s', handle, devnumber, request_id, error, - _hidpp10.ERROR[error] + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + "(%s) device 0x%02X error on request {%04X}: %d = %s", + handle, + devnumber, + request_id, + error, + Hidpp10ErrorCode(error), ) - return _hidpp10.ERROR[error] if return_error else None - if reply_data[:1] == b'\xFF' and reply_data[1:3] == request_data[:2]: + return Hidpp10ErrorCode(error) if return_error else None + if reply_data[:1] == b"\xff" and reply_data[1:3] == request_data[:2]: # a HID++ 2.0 feature call returned with an error error = ord(reply_data[3:4]) - _log.error( - '(%s) device %d error on feature request {%04X}: %d = %s', handle, devnumber, request_id, error, - _hidpp20.ERROR[error] + try: + error_name = Hidpp20ErrorCode(error) + except ValueError: + error_name = f"unknown:{error:02X}" + logger.error( + "(%s) device %d error on feature request {%04X}: %d = %s", + handle, + devnumber, + request_id, + error, + error_name, + ) + raise exceptions.FeatureCallError( + number=devnumber, + request=request_id, + error=error, + params=params, ) - raise _hidpp20.FeatureCallError(number=devnumber, request=request_id, error=error, params=params) if reply_data[:2] == request_data[:2]: if devnumber == 0xFF: @@ -475,78 +672,138 @@ def request(handle, devnumber, request_id, *params, no_reply=False, return_error else: # a reply was received, but did not match our request in any way # reset the timeout starting point - request_started = _timestamp() + request_started = time() if notifications_hook: n = make_notification(report_id, reply_devnumber, reply_data) if n: notifications_hook(n) - # elif _log.isEnabledFor(_DEBUG): - # _log.debug("(%s) ignoring reply %02X [%s]", handle, reply_devnumber, _strhex(reply_data)) - # elif _log.isEnabledFor(_DEBUG): - # _log.debug("(%s) ignoring reply %02X [%s]", handle, reply_devnumber, _strhex(reply_data)) + delta = time() - request_started - delta = _timestamp() - request_started - # if _log.isEnabledFor(_DEBUG): - # _log.debug("(%s) still waiting for reply, delta %f", handle, delta) - - _log.warn( - 'timeout (%0.2f/%0.2f) on device %d request {%04X} params [%s]', delta, timeout, devnumber, request_id, - _strhex(params) + logger.warning( + "timeout (%0.2f/%0.2f) on device %d request {%04X} params [%s]", + delta, + timeout, + devnumber, + request_id, + common.strhex(params), ) # raise DeviceUnreachable(number=devnumber, request=request_id) -def ping(handle, devnumber, long_message=False): +def ping(handle, devnumber, long_message: bool = False): """Check if a device is connected to the receiver. :returns: The HID protocol supported by the device, as a floating point number, if the device is active. """ - if _log.isEnabledFor(_DEBUG): - _log.debug('(%s) pinging device %d', handle, devnumber) - with acquire_timeout(handle_lock(handle), handle, 10.): - notifications_hook = getattr(handle, 'notifications_hook', None) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("(%s) pinging device %d", handle, devnumber) + with acquire_timeout(handle_lock(handle), handle, 10.0): + notifications_hook = getattr(handle, "notifications_hook", None) try: - _skip_incoming(handle, int(handle), notifications_hook) - except NoReceiver: - _log.warn('device or receiver disconnected') + _read_input_buffer(handle, int(handle), notifications_hook) + except exceptions.NoReceiver: + logger.warning("device or receiver disconnected") return - # randomize the SoftwareId and mark byte to be able to identify the ping - # reply, and set most significant (0x8) bit in SoftwareId so that the reply - # is always distinguishable from notifications - request_id = 0x0018 | _random_bits(3) - request_data = _pack('!HBBB', request_id, 0, 0, _random_bits(8)) + # randomize the mark byte to be able to identify the ping reply + sw_id = _get_next_sw_id() + request_id = 0x0010 | sw_id # was 0x0018 | getrandbits(3) + request_data = struct.pack("!HBBB", request_id, 0, 0, getrandbits(8)) write(int(handle), devnumber, request_data, long_message) - request_started = _timestamp() # we consider timeout from this point + request_started = time() # we consider timeout from this point delta = 0 while delta < _PING_TIMEOUT: reply = _read(handle, _PING_TIMEOUT) if reply: report_id, reply_devnumber, reply_data = reply - if reply_devnumber == devnumber or reply_devnumber == devnumber ^ 0xff: # BT device returning 0x00 - if reply_data[:2] == request_data[:2] and reply_data[4:5] == request_data[-1:]: + if reply_devnumber == devnumber or reply_devnumber == devnumber ^ 0xFF: # BT device returning 0x00 + is_centurion = int(handle) in _centurion_handles + mark_ok = is_centurion or reply_data[4:5] == request_data[-1:] + if reply_data[:2] == request_data[:2] and mark_ok: # HID++ 2.0+ device, currently connected - return ord(reply_data[2:3]) + ord(reply_data[3:4]) / 10.0 + major = ord(reply_data[2:3]) + minor = ord(reply_data[3:4]) + if is_centurion: + _centurion_protocol_versions[int(handle)] = (major, minor) + return major + minor / 10.0 - if report_id == HIDPP_SHORT_MESSAGE_ID and reply_data[:1] == b'\x8F' and \ - reply_data[1:3] == request_data[:2]: # error response + if ( + report_id == HIDPP_SHORT_MESSAGE_ID + and reply_data[:1] == b"\x8f" + and reply_data[1:3] == request_data[:2] + ): # error response error = ord(reply_data[3:4]) - if error == _hidpp10.ERROR.invalid_SubID__command: # a valid reply from a HID++ 1.0 device + if error == Hidpp10ErrorCode.INVALID_SUB_ID_COMMAND: + # a valid reply from a HID++ 1.0 device return 1.0 - if error == _hidpp10.ERROR.resource_error or error == _hidpp10.ERROR.connection_request_failed: + if error in [Hidpp10ErrorCode.RESOURCE_ERROR, Hidpp10ErrorCode.CONNECTION_REQUEST_FAILED]: return # device unreachable - if error == _hidpp10.ERROR.unknown_device: # no paired device with that number - _log.error('(%s) device %d error on ping request: unknown device', handle, devnumber) - raise NoSuchDevice(number=devnumber, request=request_id) + if error == Hidpp10ErrorCode.UNKNOWN_DEVICE: # no device with that number currently accessible + logger.info("(%s) device %d error on ping request: unknown device", handle, devnumber) + raise exceptions.NoSuchDevice(number=devnumber, request=request_id) if notifications_hook: n = make_notification(report_id, reply_devnumber, reply_data) if n: notifications_hook(n) - # elif _log.isEnabledFor(_DEBUG): - # _log.debug("(%s) ignoring reply %02X [%s]", handle, reply_devnumber, _strhex(reply_data)) - delta = _timestamp() - request_started + delta = time() - request_started - _log.warn('(%s) timeout (%0.2f/%0.2f) on device %d ping', handle, delta, _PING_TIMEOUT, devnumber) + logger.warning("(%s) timeout (%0.2f/%0.2f) on device %d ping", handle, delta, _PING_TIMEOUT, devnumber) + + +def _read_input_buffer(handle, ihandle, notifications_hook): + """Consume anything already in the input buffer. + + Used by request() and ping() before their write. + """ + is_centurion = ihandle in _centurion_handles + read_size = CENTURION_FRAME_SIZE if is_centurion else _MAX_READ_SIZE + + while True: + try: + # read whatever is already in the buffer, if any + data = hidapi.read(ihandle, read_size, 0) + except Exception as reason: + logger.error("read failed, assuming receiver %s no longer available", handle) + close(handle) + raise exceptions.NoReceiver(reason=reason) from reason + + if data: + if is_centurion and ord(data[:1]) == CENTURION_REPORT_ID: + # Unwrap Centurion CPL framing same as in _read() + cpl_length = ord(data[1:2]) + inner_payload = data[3 : 2 + cpl_length] + data = bytes([HIDPP_LONG_MESSAGE_ID, 0xFF]) + inner_payload + if len(data) <= _LONG_MESSAGE_SIZE: + data = data + b"\x00" * (_LONG_MESSAGE_SIZE - len(data)) + elif len(data) <= _CENTURION_MSG_SIZE: + data = data + b"\x00" * (_CENTURION_MSG_SIZE - len(data)) + else: + data = data[:_CENTURION_MSG_SIZE] + if _is_relevant_message(data): # only process messages that pass check + # report_id = ord(data[:1]) + if notifications_hook: + n = make_notification(ord(data[:1]), ord(data[1:2]), data[2:]) + if n: + notifications_hook(n) + else: + # nothing in the input buffer, we're done + return + + +def _get_next_sw_id() -> int: + """Returns 'random' software ID to separate replies from different devices. + + Cycle the HID++ 2.0 software ID from 0x2 to 0xF to separate + results and notifications. + """ + if not hasattr(_get_next_sw_id, "software_id"): + _get_next_sw_id.software_id = 0xF + + if _get_next_sw_id.software_id < 0xF: + _get_next_sw_id.software_id += 1 + else: + _get_next_sw_id.software_id = 2 + return _get_next_sw_id.software_id diff --git a/lib/logitech_receiver/base_usb.py b/lib/logitech_receiver/base_usb.py index d3fa3589..f4edbb6d 100644 --- a/lib/logitech_receiver/base_usb.py +++ b/lib/logitech_receiver/base_usb.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -16,235 +14,225 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -## According to Logitech, they use the following product IDs (as of September 2020) -## USB product IDs for receivers: 0xC526 - 0xC5xx -## Wireless PIDs for hidpp10 devices: 0x2006 - 0x2019 -## Wireless PIDs for hidpp20 devices: 0x4002 - 0x4097, 0x4101 - 0x4102 -## USB product IDs for hidpp20 devices: 0xC07D - 0xC094, 0xC32B - 0xC344 -## Bluetooth product IDs (for hidpp20 devices): 0xB012 - 0xB0xx, 0xB32A - 0xB3xx +"""Collection of known Logitech product IDs. -# USB ids of Logitech wireless receivers. -# Only receivers supporting the HID++ protocol can go in here. +According to Logitech, they use the following product IDs (as of September 2020) +USB product IDs for receivers: 0xC526 - 0xC5xx +Wireless PIDs for hidpp10 devices: 0x2006 - 0x2019 +Wireless PIDs for hidpp20 devices: 0x4002 - 0x4097, 0x4101 - 0x4102 +USB product IDs for hidpp20 devices: 0xC07D - 0xC094, 0xC32B - 0xC344 +Bluetooth product IDs (for hidpp20 devices): 0xB012 - 0xB0xx, 0xB32A - 0xB3xx -from .descriptors import DEVICES as _DEVICES -from .i18n import _ +USB ids of Logitech wireless receivers. +Only receivers supporting the HID++ protocol can go in here. +""" -# max_devices is only used for receivers that do not support reading from _R.receiver_info offset 0x03, default to 1 -# may_unpair is only used for receivers that do not support reading from _R.receiver_info offset 0x03, default to False -# unpair is for receivers that do support reading from _R.receiver_info offset 0x03, no default -## should this last be changed so that may_unpair is used for all receivers? writing to _R.receiver_pairing doesn't seem right -# re_pairs determines whether a receiver pairs by replacing existing pairings, default to False -## currently only one receiver is so marked - should there be more? +from __future__ import annotations -_DRIVER = ('hid-generic', 'generic-usb', 'logitech-djreceiver') +from typing import Any -_bolt_receiver = lambda product_id: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 2, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Bolt Receiver'), - 'receiver_kind': 'bolt', - 'max_devices': 6, - 'may_unpair': True -} +from solaar.i18n import _ -_unifying_receiver = lambda product_id: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 2, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Unifying Receiver'), - 'receiver_kind': 'unifying', - 'may_unpair': True -} +# max_devices is only used for receivers that do not support reading from Registers.RECEIVER_INFO offset 0x03, default +# to 1. +# may_unpair is only used for receivers that do not support reading from Registers.RECEIVER_INFO offset 0x03, +# default to False. +# unpair is for receivers that do support reading from Registers.RECEIVER_INFO offset 0x03, no default. +## should this last be changed so that may_unpair is used for all receivers? writing to Registers.RECEIVER_PAIRING +## doesn't seem right -_nano_receiver = lambda product_id: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 1, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Nano Receiver'), - 'receiver_kind': 'nano', - 'may_unpair': False, - 're_pairs': True -} +LOGITECH_VENDOR_ID = 0x046D -_nano_receiver_no_unpair = lambda product_id: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 1, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Nano Receiver'), - 'receiver_kind': 'nano', - 'may_unpair': False, - 'unpair': False, - 're_pairs': True -} -_nano_receiver_max2 = lambda product_id: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 1, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Nano Receiver'), - 'receiver_kind': 'nano', - 'max_devices': 2, - 'may_unpair': False, - 're_pairs': True -} +def _bolt_receiver(product_id: int) -> dict: + return { + "vendor_id": LOGITECH_VENDOR_ID, + "product_id": product_id, + "usb_interface": 2, + "name": _("Bolt Receiver"), + "receiver_kind": "bolt", + "max_devices": 6, + "may_unpair": True, + } -_nano_receiver_maxn = lambda product_id, max: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 1, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Nano Receiver'), - 'receiver_kind': 'nano', - 'max_devices': max, - 'may_unpair': False, - 're_pairs': True -} -_lenovo_receiver = lambda product_id: { - 'vendor_id': 0x17ef, - 'product_id': product_id, - 'usb_interface': 1, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Nano Receiver'), - 'receiver_kind': 'nano', - 'may_unpair': False -} +def _unifying_receiver(product_id: int) -> dict: + return { + "vendor_id": LOGITECH_VENDOR_ID, + "product_id": product_id, + "usb_interface": 2, + "name": _("Unifying Receiver"), + "receiver_kind": "unifying", + "may_unpair": True, + } -_lightspeed_receiver = lambda product_id: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 2, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('Lightspeed Receiver'), - 'may_unpair': False -} -_ex100_receiver = lambda product_id: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'usb_interface': 1, - 'hid_driver': _DRIVER, # noqa: F821 - 'name': _('EX100 Receiver 27 Mhz'), - 'receiver_kind': '27Mhz', - 'max_devices': 4, - 'may_unpair': False, - 're_pairs': True -} +def _nano_receiver(product_id: int) -> dict: + return { + "vendor_id": LOGITECH_VENDOR_ID, + "product_id": product_id, + "usb_interface": 1, + "name": _("Nano Receiver"), + "receiver_kind": "nano", + "may_unpair": False, + "re_pairs": True, + } + + +def _nano_receiver_no_unpair(product_id: int) -> dict: + return { + "vendor_id": LOGITECH_VENDOR_ID, + "product_id": product_id, + "usb_interface": 1, + "name": _("Nano Receiver"), + "receiver_kind": "nano", + "may_unpair": False, + "unpair": False, + "re_pairs": True, + } + + +def _nano_receiver_max2(product_id: int) -> dict: + return { + "vendor_id": LOGITECH_VENDOR_ID, + "product_id": product_id, + "usb_interface": 1, + "name": _("Nano Receiver"), + "receiver_kind": "nano", + "max_devices": 2, + "may_unpair": False, + "re_pairs": True, + } + + +def _lenovo_receiver(product_id: int) -> dict: + return { + "vendor_id": 6127, + "product_id": product_id, + "usb_interface": 1, + "name": _("Nano Receiver"), + "receiver_kind": "nano", + "may_unpair": False, + } + + +def _lightspeed_receiver(product_id: int) -> dict: + return { + "vendor_id": LOGITECH_VENDOR_ID, + "product_id": product_id, + "usb_interface": 2, + "receiver_kind": "lightspeed", + "name": _("Lightspeed Receiver"), + "may_unpair": True, + "re_pairs": False, + } + + +def _ex100_receiver(product_id: int) -> dict: + return { + "vendor_id": LOGITECH_VENDOR_ID, + "product_id": product_id, + "usb_interface": 1, + "name": _("EX100 Receiver 27 Mhz"), + "receiver_kind": "27Mhz", + "max_devices": 4, + "may_unpair": False, + "re_pairs": True, + } + # Receivers added here should also be listed in -# share/solaar/io.github.pwr_solaar.solaar.metainfo.xml +# share/solaar/io.github.pwr_solaar.solaar.meta-info.xml # Look in https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h # Bolt receivers (marked with the yellow lightning bolt logo) -BOLT_RECEIVER_C548 = _bolt_receiver(0xc548) +BOLT_RECEIVER_C548 = _bolt_receiver(0xC548) # standard Unifying receivers (marked with the orange Unifying logo) -UNIFYING_RECEIVER_C52B = _unifying_receiver(0xc52b) -UNIFYING_RECEIVER_C532 = _unifying_receiver(0xc532) +UNIFYING_RECEIVER_C52B = _unifying_receiver(0xC52B) +UNIFYING_RECEIVER_C532 = _unifying_receiver(0xC532) # Nano receivers (usually sold with low-end devices) -NANO_RECEIVER_ADVANCED = _nano_receiver_no_unpair(0xc52f) -NANO_RECEIVER_C518 = _nano_receiver(0xc518) -NANO_RECEIVER_C51A = _nano_receiver(0xc51a) -NANO_RECEIVER_C51B = _nano_receiver(0xc51b) -NANO_RECEIVER_C521 = _nano_receiver(0xc521) -NANO_RECEIVER_C525 = _nano_receiver(0xc525) -NANO_RECEIVER_C526 = _nano_receiver(0xc526) -NANO_RECEIVER_C52E = _nano_receiver_no_unpair(0xc52e) -NANO_RECEIVER_C531 = _nano_receiver(0xc531) -NANO_RECEIVER_C534 = _nano_receiver_max2(0xc534) -NANO_RECEIVER_C535 = _nano_receiver(0xc535) # branded as Dell -NANO_RECEIVER_C537 = _nano_receiver(0xc537) -# NANO_RECEIVER_C542 = _nano_receiver(0xc542) # does not use HID++ +NANO_RECEIVER_ADVANCED = _nano_receiver_no_unpair(0xC52F) +NANO_RECEIVER_C518 = _nano_receiver(0xC518) +NANO_RECEIVER_C51A = _nano_receiver(0xC51A) +NANO_RECEIVER_C51B = _nano_receiver(0xC51B) +NANO_RECEIVER_C521 = _nano_receiver(0xC521) +NANO_RECEIVER_C525 = _nano_receiver(0xC525) +NANO_RECEIVER_C526 = _nano_receiver(0xC526) +NANO_RECEIVER_C52E = _nano_receiver_no_unpair(0xC52E) +NANO_RECEIVER_C531 = _nano_receiver(0xC531) +NANO_RECEIVER_C534 = _nano_receiver_max2(0xC534) +NANO_RECEIVER_C535 = _nano_receiver(0xC535) # branded as Dell +NANO_RECEIVER_C537 = _nano_receiver(0xC537) NANO_RECEIVER_6042 = _lenovo_receiver(0x6042) # Lightspeed receivers (usually sold with gaming devices) -LIGHTSPEED_RECEIVER_C539 = _lightspeed_receiver(0xc539) -LIGHTSPEED_RECEIVER_C53A = _lightspeed_receiver(0xc53a) -LIGHTSPEED_RECEIVER_C53D = _lightspeed_receiver(0xc53d) -LIGHTSPEED_RECEIVER_C53F = _lightspeed_receiver(0xc53f) -LIGHTSPEED_RECEIVER_C541 = _lightspeed_receiver(0xc541) -LIGHTSPEED_RECEIVER_C545 = _lightspeed_receiver(0xc545) -LIGHTSPEED_RECEIVER_C547 = _lightspeed_receiver(0xc547) +LIGHTSPEED_RECEIVER_C539 = _lightspeed_receiver(0xC539) +LIGHTSPEED_RECEIVER_C53A = _lightspeed_receiver(0xC53A) +LIGHTSPEED_RECEIVER_C53D = _lightspeed_receiver(0xC53D) +LIGHTSPEED_RECEIVER_C53F = _lightspeed_receiver(0xC53F) +LIGHTSPEED_RECEIVER_C541 = _lightspeed_receiver(0xC541) +LIGHTSPEED_RECEIVER_C545 = _lightspeed_receiver(0xC545) +LIGHTSPEED_RECEIVER_C547 = _lightspeed_receiver(0xC547) +LIGHTSPEED_RECEIVER_C54D = _lightspeed_receiver(0xC54D) # EX100 old style receiver pre-unifying protocol -# EX100_27MHZ_RECEIVER_C50C = _ex100_receiver(0xc50C) # in hid/hid-ids.h -EX100_27MHZ_RECEIVER_C517 = _ex100_receiver(0xc517) -# EX100_27MHZ_RECEIVER_C51B = _ex100_receiver(0xc51B) # in hid/hid-ids.h +EX100_27MHZ_RECEIVER_C517 = _ex100_receiver(0xC517) -ALL = ( - BOLT_RECEIVER_C548, - UNIFYING_RECEIVER_C52B, - UNIFYING_RECEIVER_C532, - NANO_RECEIVER_ADVANCED, - NANO_RECEIVER_C518, - NANO_RECEIVER_C51A, - NANO_RECEIVER_C51B, - NANO_RECEIVER_C521, - NANO_RECEIVER_C525, - NANO_RECEIVER_C526, - NANO_RECEIVER_C52E, - NANO_RECEIVER_C531, - NANO_RECEIVER_C534, - NANO_RECEIVER_C535, - NANO_RECEIVER_C537, - # NANO_RECEIVER_C542, # does not use HID++ - NANO_RECEIVER_6042, - LIGHTSPEED_RECEIVER_C539, - LIGHTSPEED_RECEIVER_C53A, - LIGHTSPEED_RECEIVER_C53D, - LIGHTSPEED_RECEIVER_C53F, - LIGHTSPEED_RECEIVER_C541, - LIGHTSPEED_RECEIVER_C545, - LIGHTSPEED_RECEIVER_C547, - EX100_27MHZ_RECEIVER_C517, -) - -_wired_device = lambda product_id, interface: { - 'vendor_id': 0x046d, - 'product_id': product_id, - 'bus_id': 0x3, - 'usb_interface': interface, - 'isDevice': True +KNOWN_RECEIVERS = { + 0xC548: BOLT_RECEIVER_C548, + 0xC52B: UNIFYING_RECEIVER_C52B, + 0xC532: UNIFYING_RECEIVER_C532, + 0xC52F: NANO_RECEIVER_ADVANCED, + 0xC518: NANO_RECEIVER_C518, + 0xC51A: NANO_RECEIVER_C51A, + 0xC51B: NANO_RECEIVER_C51B, + 0xC521: NANO_RECEIVER_C521, + 0xC525: NANO_RECEIVER_C525, + 0xC526: NANO_RECEIVER_C526, + 0xC52E: NANO_RECEIVER_C52E, + 0xC531: NANO_RECEIVER_C531, + 0xC534: NANO_RECEIVER_C534, + 0xC535: NANO_RECEIVER_C535, + 0xC537: NANO_RECEIVER_C537, + 0x6042: NANO_RECEIVER_6042, + 0xC539: LIGHTSPEED_RECEIVER_C539, + 0xC53A: LIGHTSPEED_RECEIVER_C53A, + 0xC53D: LIGHTSPEED_RECEIVER_C53D, + 0xC53F: LIGHTSPEED_RECEIVER_C53F, + 0xC541: LIGHTSPEED_RECEIVER_C541, + 0xC545: LIGHTSPEED_RECEIVER_C545, + 0xC547: LIGHTSPEED_RECEIVER_C547, + 0xC54D: LIGHTSPEED_RECEIVER_C54D, + 0xC517: EX100_27MHZ_RECEIVER_C517, } -_bt_device = lambda product_id: {'vendor_id': 0x046d, 'product_id': product_id, 'bus_id': 0x5, 'isDevice': True} -DEVICES = [] +def get_receiver_info(product_id: int) -> dict[str, Any]: + """Returns hardcoded information about a Logitech receiver. -for _ignore, d in _DEVICES.items(): - if d.usbid: - DEVICES.append(_wired_device(d.usbid, d.interface if d.interface else 2)) - if d.btid: - DEVICES.append(_bt_device(d.btid)) + Parameters + ---------- + product_id + Product ID (pid) of the receiver, e.g. 0xC548 for a Logitech + Bolt receiver. + Returns + ------- + dict[str, Any] + Receiver info with mandatory fields: + - vendor_id + - product_id -def other_device_check(bus_id, vendor_id, product_id): - """Check whether product is a Logitech USB-connected or Bluetooth device based on bus, vendor, and product IDs - This allows Solaar to support receiverless HID++ 2.0 devices that it knows nothing about""" - if vendor_id != 0x46d: # Logitech - return - if bus_id == 0x3: # USB - if (product_id >= 0xC07D and product_id <= 0xC094 or product_id >= 0xC32B and product_id <= 0xC344): - return _wired_device(product_id, 2) - elif bus_id == 0x5: # Bluetooth - if (product_id >= 0xB012 and product_id <= 0xB0FF or product_id >= 0xB317 and product_id <= 0xB3FF): - return _bt_device(product_id) + Raises + ------ + ValueError + If the product ID is unknown. + """ + try: + return KNOWN_RECEIVERS[product_id] + except KeyError: + pass - -def product_information(usb_id): - if isinstance(usb_id, str): - usb_id = int(usb_id, 16) - for r in ALL: - if usb_id == r.get('product_id'): - return r - return {} - - -del _DRIVER, _unifying_receiver, _nano_receiver, _lenovo_receiver, _lightspeed_receiver + raise ValueError(f"Unknown product ID '0x{product_id:02X}'") diff --git a/lib/logitech_receiver/centurion.py b/lib/logitech_receiver/centurion.py new file mode 100644 index 00000000..f1222869 --- /dev/null +++ b/lib/logitech_receiver/centurion.py @@ -0,0 +1,511 @@ +## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Centurion device protocol — receiver class, factory, device info/firmware/battery queries. + +CenturionReceiver is a lightweight receiver-like container for Centurion +(PRO X 2 LIGHTSPEED and similar) dongles. Protocol functions query device +info, firmware, serial, name, and battery via Centurion-specific HID++ features. +""" + +from __future__ import annotations + +import errno +import logging +import struct + +from typing import Callable + +from solaar import configuration + +from . import base +from . import exceptions +from . import hidpp10 +from .centurion_constants import CenturionCoreFeature +from .common import Alert +from .common import Battery +from .common import BatteryStatus +from .common import FirmwareKind +from .common import _read_usb_product_string +from .hidpp20_constants import SupportedFeature + +logger = logging.getLogger(__name__) + + +# --- Centurion protocol functions (standalone, operate on any device-like object) --- + + +def get_firmware_centurion(device): + """Reads firmware info from a Centurion device via DeviceInfo (0x0100) function 1.""" + from . import common + + fw = [] + seen = set() # track response signatures to detect duplicates + for index in range(0, 8): # try up to 8 entities + try: + report = device.feature_request(SupportedFeature.CENTURION_DEVICE_INFO, 0x10, index) + except exceptions.FeatureCallError: + break + if not report or len(report) < 5: + break + # Dedup: parent device returns the same response for every entity index + sig = bytes(report[: 5 + report[4]]) + if sig in seen: + break + seen.add(sig) + fw_type = report[0] + version = struct.unpack("!H", report[2:4])[0] + name_len = report[4] + name = report[5 : 5 + name_len].decode("ascii", errors="replace").rstrip("\x00") if name_len else "" + version_str = f"{version >> 8}.{version & 0xFF:02d}" + kind = FirmwareKind(fw_type) if fw_type <= 3 else FirmwareKind.Other + fw.append(common.FirmwareInfo(kind, name, version_str, None)) + return tuple(fw) if fw else None + + +def get_serial_centurion(device): + """Reads the serial number from a Centurion device via DeviceInfo (0x0100) function 2.""" + try: + report = device.feature_request(SupportedFeature.CENTURION_DEVICE_INFO, 0x20) + except exceptions.FeatureCallError: + return None + if not report or len(report) < 2: + return None + str_len = report[0] + return report[1 : 1 + str_len].decode("ascii", errors="replace").rstrip("\x00") + + +def get_hardware_info_centurion(device): + """Reads hardware info from a Centurion device via DeviceInfo (0x0100) function 0. + + Returns (modelId, hardwareRevision, productId) or None. + """ + try: + report = device.feature_request(SupportedFeature.CENTURION_DEVICE_INFO) + except exceptions.FeatureCallError: + return None + if not report or len(report) < 4: + return None + model_id = report[0] + hw_revision = report[1] + product_id = struct.unpack("!H", report[2:4])[0] + return model_id, hw_revision, product_id + + +def _centurion_sub_device_info_request(device, function=0x00, *params): + """Send a DeviceInfo (0x0100) request to the sub-device via bridge.""" + sub_indices = getattr(device, "_centurion_sub_indices", {}) + sub_idx = sub_indices.get(SupportedFeature.CENTURION_DEVICE_INFO) + if sub_idx is None: + return None + return device.centurion_bridge_request(sub_idx, function, *params) + + +def get_firmware_centurion_sub(device): + """Reads firmware info from the Centurion sub-device (headset) via bridge.""" + from . import common + + fw = [] + seen = set() + for index in range(0, 8): + report = _centurion_sub_device_info_request(device, 0x10, index) + if not report or len(report) < 5: + break + sig = bytes(report[: 5 + report[4]]) + if sig in seen: + break + seen.add(sig) + fw_type = report[0] + version = struct.unpack("!H", report[2:4])[0] + name_len = report[4] + name = report[5 : 5 + name_len].decode("ascii", errors="replace").rstrip("\x00") if name_len else "" + version_str = f"{version >> 8}.{version & 0xFF:02d}" + kind = FirmwareKind(fw_type) if fw_type <= 3 else FirmwareKind.Other + fw.append(common.FirmwareInfo(kind, name, version_str, None)) + return tuple(fw) if fw else None + + +def get_serial_centurion_sub(device): + """Reads the serial number from the Centurion sub-device (headset) via bridge.""" + report = _centurion_sub_device_info_request(device, 0x20) + if not report or len(report) < 2: + return None + str_len = report[0] + return report[1 : 1 + str_len].decode("ascii", errors="replace").rstrip("\x00") + + +def get_hardware_info_centurion_sub(device): + """Reads hardware info from the Centurion sub-device (headset) via bridge. + + Returns (modelId, hardwareRevision, productId) or None. + """ + report = _centurion_sub_device_info_request(device) + if not report or len(report) < 4: + return None + model_id = report[0] + hw_revision = report[1] + product_id = struct.unpack("!H", report[2:4])[0] + return model_id, hw_revision, product_id + + +def get_name_centurion(device): + """Reads a Centurion device's name via DeviceName (0x0101). + + Tries two response formats: + 1. Inline: function 0 returns [name_len, name_bytes...] (like serial) + 2. Chunked: function 0 returns [name_len], function 1 returns [name_bytes...] (like standard DeviceName) + """ + try: + reply = device.feature_request(SupportedFeature.CENTURION_DEVICE_NAME) + except exceptions.FeatureCallError: + return None + if not reply: + return None + name_length = reply[0] + if name_length == 0: + return None + # If the full name is inline (length + name bytes in one response) + if len(reply) >= 1 + name_length: + return reply[1 : 1 + name_length].decode("utf-8", errors="replace").rstrip("\x00") + # Otherwise, fetch name in chunks via function 1 (like standard DEVICE_NAME) + name = b"" + while len(name) < name_length: + try: + fragment = device.feature_request(SupportedFeature.CENTURION_DEVICE_NAME, 0x10, len(name)) + except exceptions.FeatureCallError: + break + if fragment: + name += fragment[: name_length - len(name)] + else: + break + return name.decode("utf-8", errors="replace").rstrip("\x00") if name else None + + +def get_battery_centurion(device): + """Query battery via CENTURION_BATTERY_SOC.""" + try: + report = device.feature_request(SupportedFeature.CENTURION_BATTERY_SOC) + if report is not None: + return decipher_battery_centurion(report) + except exceptions.FeatureCallError: + if SupportedFeature.CENTURION_BATTERY_SOC in device.features: + return SupportedFeature.CENTURION_BATTERY_SOC + return None + + +def decipher_battery_centurion(report) -> tuple[SupportedFeature, Battery]: + """Decipher CENTURION_BATTERY_SOC (0x0104) response. + + Response format (3 bytes): + Byte 0: Battery Percentage (0-100) + Byte 1: Battery Percentage (duplicate) + Byte 2: Charging Status (0=discharging, 1=charging, 2=charging via USB, 3=charge complete) + """ + if len(report) < 1: + return SupportedFeature.CENTURION_BATTERY_SOC, Battery(None, None, BatteryStatus.DISCHARGING, None) + soc = report[0] + logger.debug("centurion battery SOC raw: %s", report[:8].hex()) + charging_status = report[2] if len(report) >= 3 else 0 + if charging_status in (1, 2): + status = BatteryStatus.RECHARGING + elif charging_status == 3: + status = BatteryStatus.FULL + else: + status = BatteryStatus.DISCHARGING + return SupportedFeature.CENTURION_BATTERY_SOC, Battery(soc, None, status, None) + + +# --- CenturionReceiver class --- + + +class CenturionReceiver: + """A lightweight receiver-like container for Centurion (PRO X 2 LIGHTSPEED) dongles. + + Provides the Receiver interface to the UI so the dongle appears as a parent + with the headset as an indented child device. NOT a subclass of Receiver — + Receiver's __init__ does HID++ 1.0 register reads and pairing setup that + don't apply to Centurion. + + All centurion communication (bridge, features, settings, battery) lives in + the child Device; this class is just a UI container + handle owner. + """ + + read_register: Callable = hidpp10.read_register + write_register: Callable = hidpp10.write_register + number = 0xFF + kind = None + isDevice = False + may_unpair = False + re_pairs = False + max_devices = 1 + + def __init__(self, low_level, handle, device_info, setting_callback=None): + assert handle + self.low_level = low_level + self.handle = handle + self.path = device_info.path + self.product_id = device_info.product_id + self.setting_callback = setting_callback + self.status_callback = None + self.notification_flags = None + self._devices = {} + self._firmware = None + self._dongle_features = None # independently probed dongle features + self.cleanups = [] + + # Receiver identity + self.serial = None + self._usb_name = getattr(device_info, "product", None) + if not self._usb_name and self.path: + self._usb_name = _read_usb_product_string(self.path) + self.name = "Centurion Receiver" + + # Dummy pairing object — lock_open stays False + from .receiver import Pairing + + self.pairing = Pairing() + + # Discover dongle features independently + self._discover_dongle_features() + + # Read serial from dongle's CENTURION_DEVICE_INFO if available + if self.serial is None: + try: + s = get_serial_centurion(self) + if s and s.strip() and s.strip().isprintable(): + self.serial = s.strip() + except Exception: + pass + + def enable_connection_notifications(self, enable=True): + return False + + def remaining_pairings(self, cache=True): + return None + + def device_codename(self, n): + return self._usb_name + + def request(self, request_id, *params, no_reply=False): + """Send an HID++ request directly to the dongle (not through bridge).""" + if self.handle: + return self.low_level.request( + self.handle, 0xFF, request_id, *params, no_reply=no_reply, long_message=True, protocol=2.0 + ) + + def feature_request(self, feature, function=0x00, *params, no_reply=False): + """Send a feature request to the dongle using discovered feature indices.""" + if self._dongle_features is None: + self._discover_dongle_features() + feature_int = int(feature) + for _feat, feat_id, index in self._dongle_features or []: + if feat_id == feature_int: + request_id = (index << 8) | (function & 0xFF) + return self.request(request_id, *params, no_reply=no_reply) + raise exceptions.FeatureNotSupported(feature) + + def _discover_dongle_features(self): + """Independently discover features on the dongle hardware.""" + self._dongle_features = [] + try: + # Query ROOT for FEATURE_SET index + response = self.request(0x0000, 0x00, 0x01) + if response is None or response[0] == 0: + return + fs_index = response[0] + # Get feature count + count_resp = self.request(fs_index << 8) + if count_resp is None: + return + feature_count = count_resp[0] + # Enumerate features via CenturionFeatureSet (func 1 = 0x10, per-index query) + for idx in range(feature_count): + resp = self.request((fs_index << 8) | 0x10, idx) + if resp is None or len(resp) < 3: + continue + feat_id = struct.unpack("!H", resp[1:3])[0] + try: + feature = SupportedFeature(feat_id) + except ValueError: + feature = f"unknown:{feat_id:04X}" + self._dongle_features.append((feature, feat_id, idx)) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("Centurion dongle features: %s", self._dongle_features) + except Exception: + logger.debug("Centurion dongle feature discovery failed", exc_info=True) + + @property + def dongle_features(self): + """Return list of (feature, feat_id, index) tuples for dongle features.""" + if self._dongle_features is None: + self._discover_dongle_features() + return self._dongle_features + + def count(self): + return len([d for d in self._devices.values() if d is not None]) + + @property + def firmware(self): + if self._firmware is None and self.handle: + self._firmware = get_firmware_centurion(self) + return self._firmware or () + + def notify_devices(self): + """Create child Device for the headset and trigger its initialization.""" + # Import Device locally to avoid circular import (centurion.py ↔ device.py) + from .device import Device + + # Signal receiver to UI first — tray/window need the receiver entry + # before a child device can be added under it. + self.changed(alert=Alert.NONE) + + # Create child Device with receiver=self, number=1 + pairing_info = { + "wpid": self.product_id, + "kind": None, + "serial": None, + "polling": None, + "power_switch": None, + } + dev = Device( + self.low_level, + self, + 1, + None, + pairing_info=pairing_info, + setting_callback=self.setting_callback, + ) + # Set centurion attributes on the child + dev.centurion = True + dev.product_id = self.product_id + dev.hidpp_long = True + dev._centurion_usb_name = self._usb_name + # Pre-set bridge index from dongle features so ping can probe the headset + for _feat, feat_id, idx in self._dongle_features or []: + if feat_id == CenturionCoreFeature.CENT_PP_BRIDGE: + dev._centurion_bridge_index = idx + break + + self._devices[1] = dev + configuration.attach_to(dev) + dev.status_callback = self.status_callback + + # Ping to determine online status. + # Notify UI either way — offline devices show as greyed out (matching receiver behavior). + online = dev.ping() + dev.changed(active=online) + if self.status_callback is not None: + self.status_callback(dev) + + def changed(self, alert=Alert.NOTIFICATION, reason=None): + if self.status_callback is not None: + self.status_callback(self, alert=alert, reason=reason) + + def status_string(self): + count = self.count() + if count == 0: + return "No devices." + return f"{count} device connected." + + def close(self): + handle, self.handle = self.handle, None + for _n, d in self._devices.items(): + if d: + d.close() + self._devices.clear() + for cleanup in self.cleanups: + cleanup(self) + return handle and self.low_level.close(handle) + + def __del__(self): + self.close() + + def __iter__(self): + for dev in self._devices.values(): + if dev is not None: + yield dev + + def __getitem__(self, key): + dev = self._devices.get(key) + if dev is not None: + return dev + raise IndexError(key) + + def __len__(self): + return len([d for d in self._devices.values() if d is not None]) + + def __contains__(self, dev): + if isinstance(dev, int): + return self._devices.get(dev) is not None + return self.__contains__(dev.number) + + def __bool__(self): + return self.handle is not None + + __nonzero__ = __bool__ + + def __eq__(self, other): + return other is not None and self.kind == other.kind and self.path == other.path + + def __ne__(self, other): + return other is None or self.kind != other.kind or self.path != other.path + + def __hash__(self): + return self.path.__hash__() + + def __str__(self): + return "<%s(%s,%s%s)>" % ( + self.name.replace(" ", "") if self.name else "CenturionReceiver", + self.path, + "" if isinstance(self.handle, int) else "T", + self.handle, + ) + + __repr__ = __str__ + + +def create_centurion_receiver(low_level, device_info, setting_callback=None): + """Opens a Centurion dongle and wraps it as a receiver-like container. + + Creates a CenturionReceiver, discovers its features, then checks if + CentPPBridge (0x0003) is among them. If not, this is a direct-connected + device (wired headset) — close and return None so the caller can fall + back to create_device(). + + :returns: A CenturionReceiver, or None. + """ + try: + handle = low_level.open_path(device_info.path) + if handle: + base._centurion_handles.add(int(handle)) + cr = CenturionReceiver(low_level, handle, device_info, setting_callback) + # Check if any discovered feature is CentPPBridge (0x0003) + has_bridge = any(feat_id == CenturionCoreFeature.CENT_PP_BRIDGE for _, feat_id, _ in (cr.dongle_features or [])) + if not has_bridge: + logger.info("Centurion device %s has no bridge, treating as direct device", device_info.path) + base._centurion_handles.discard(int(handle)) + cr.handle = None # prevent __del__ from double-closing + low_level.close(handle) + return None + return cr + except OSError as e: + logger.exception("open %s", device_info) + if e.errno == errno.EACCES: + raise e + except Exception as e: + logger.exception("open %s", device_info) + raise e diff --git a/lib/logitech_receiver/centurion_constants.py b/lib/logitech_receiver/centurion_constants.py new file mode 100644 index 00000000..6e0e3585 --- /dev/null +++ b/lib/logitech_receiver/centurion_constants.py @@ -0,0 +1,38 @@ +"""Centurion transport-specific constants. + +Feature IDs that collide with HID++ 2.0 core features live here +so they can coexist with SupportedFeature (which requires unique values). +""" + +from __future__ import annotations + +from enum import IntEnum + +from .hidpp20_constants import SupportedFeature + + +class CenturionCoreFeature(IntEnum): + """Centurion transport-specific features that collide with HID++ 2.0 core IDs.""" + + CENTURION_ROOT = 0x0000 + CENTURION_FEATURE_SET = 0x0001 + CENT_PP_BRIDGE = 0x0003 + MULTI_HOST_CONTROL = 0x0005 + KEEP_ALIVE = 0x0007 + + def __str__(self): + return self.name.replace("_", " ") + + +def resolve_feature(feat_id: int, centurion: bool = False): + """Resolve a feature ID to the appropriate enum, checking centurion-specific + features first when on the centurion transport.""" + if centurion: + try: + return CenturionCoreFeature(feat_id) + except ValueError: + pass + try: + return SupportedFeature(feat_id) + except ValueError: + return None diff --git a/lib/logitech_receiver/common.py b/lib/logitech_receiver/common.py index 3be447b1..3aa4127e 100644 --- a/lib/logitech_receiver/common.py +++ b/lib/logitech_receiver/common.py @@ -1,6 +1,5 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -15,17 +14,297 @@ ## You should have received a copy of the GNU General Public License along ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from __future__ import annotations -# Some common functions and types. +import binascii +import dataclasses +import typing -from binascii import hexlify as _hexlify -from collections import namedtuple +from enum import Flag +from enum import IntEnum +from typing import Generator +from typing import Iterable +from typing import Optional +from typing import Union -is_string = lambda d: isinstance(d, str) +import yaml -# -# -# +from solaar.i18n import _ + +if typing.TYPE_CHECKING: + from logitech_receiver.hidpp20_constants import FirmwareKind + +LOGITECH_VENDOR_ID = 0x046D + + +def crc16(data: bytes): + """ + CRC-16 (CCITT) implemented with a precomputed lookup table + """ + table = [ + 0x0000, + 0x1021, + 0x2042, + 0x3063, + 0x4084, + 0x50A5, + 0x60C6, + 0x70E7, + 0x8108, + 0x9129, + 0xA14A, + 0xB16B, + 0xC18C, + 0xD1AD, + 0xE1CE, + 0xF1EF, + 0x1231, + 0x0210, + 0x3273, + 0x2252, + 0x52B5, + 0x4294, + 0x72F7, + 0x62D6, + 0x9339, + 0x8318, + 0xB37B, + 0xA35A, + 0xD3BD, + 0xC39C, + 0xF3FF, + 0xE3DE, + 0x2462, + 0x3443, + 0x0420, + 0x1401, + 0x64E6, + 0x74C7, + 0x44A4, + 0x5485, + 0xA56A, + 0xB54B, + 0x8528, + 0x9509, + 0xE5EE, + 0xF5CF, + 0xC5AC, + 0xD58D, + 0x3653, + 0x2672, + 0x1611, + 0x0630, + 0x76D7, + 0x66F6, + 0x5695, + 0x46B4, + 0xB75B, + 0xA77A, + 0x9719, + 0x8738, + 0xF7DF, + 0xE7FE, + 0xD79D, + 0xC7BC, + 0x48C4, + 0x58E5, + 0x6886, + 0x78A7, + 0x0840, + 0x1861, + 0x2802, + 0x3823, + 0xC9CC, + 0xD9ED, + 0xE98E, + 0xF9AF, + 0x8948, + 0x9969, + 0xA90A, + 0xB92B, + 0x5AF5, + 0x4AD4, + 0x7AB7, + 0x6A96, + 0x1A71, + 0x0A50, + 0x3A33, + 0x2A12, + 0xDBFD, + 0xCBDC, + 0xFBBF, + 0xEB9E, + 0x9B79, + 0x8B58, + 0xBB3B, + 0xAB1A, + 0x6CA6, + 0x7C87, + 0x4CE4, + 0x5CC5, + 0x2C22, + 0x3C03, + 0x0C60, + 0x1C41, + 0xEDAE, + 0xFD8F, + 0xCDEC, + 0xDDCD, + 0xAD2A, + 0xBD0B, + 0x8D68, + 0x9D49, + 0x7E97, + 0x6EB6, + 0x5ED5, + 0x4EF4, + 0x3E13, + 0x2E32, + 0x1E51, + 0x0E70, + 0xFF9F, + 0xEFBE, + 0xDFDD, + 0xCFFC, + 0xBF1B, + 0xAF3A, + 0x9F59, + 0x8F78, + 0x9188, + 0x81A9, + 0xB1CA, + 0xA1EB, + 0xD10C, + 0xC12D, + 0xF14E, + 0xE16F, + 0x1080, + 0x00A1, + 0x30C2, + 0x20E3, + 0x5004, + 0x4025, + 0x7046, + 0x6067, + 0x83B9, + 0x9398, + 0xA3FB, + 0xB3DA, + 0xC33D, + 0xD31C, + 0xE37F, + 0xF35E, + 0x02B1, + 0x1290, + 0x22F3, + 0x32D2, + 0x4235, + 0x5214, + 0x6277, + 0x7256, + 0xB5EA, + 0xA5CB, + 0x95A8, + 0x8589, + 0xF56E, + 0xE54F, + 0xD52C, + 0xC50D, + 0x34E2, + 0x24C3, + 0x14A0, + 0x0481, + 0x7466, + 0x6447, + 0x5424, + 0x4405, + 0xA7DB, + 0xB7FA, + 0x8799, + 0x97B8, + 0xE75F, + 0xF77E, + 0xC71D, + 0xD73C, + 0x26D3, + 0x36F2, + 0x0691, + 0x16B0, + 0x6657, + 0x7676, + 0x4615, + 0x5634, + 0xD94C, + 0xC96D, + 0xF90E, + 0xE92F, + 0x99C8, + 0x89E9, + 0xB98A, + 0xA9AB, + 0x5844, + 0x4865, + 0x7806, + 0x6827, + 0x18C0, + 0x08E1, + 0x3882, + 0x28A3, + 0xCB7D, + 0xDB5C, + 0xEB3F, + 0xFB1E, + 0x8BF9, + 0x9BD8, + 0xABBB, + 0xBB9A, + 0x4A75, + 0x5A54, + 0x6A37, + 0x7A16, + 0x0AF1, + 0x1AD0, + 0x2AB3, + 0x3A92, + 0xFD2E, + 0xED0F, + 0xDD6C, + 0xCD4D, + 0xBDAA, + 0xAD8B, + 0x9DE8, + 0x8DC9, + 0x7C26, + 0x6C07, + 0x5C64, + 0x4C45, + 0x3CA2, + 0x2C83, + 0x1CE0, + 0x0CC1, + 0xEF1F, + 0xFF3E, + 0xCF5D, + 0xDF7C, + 0xAF9B, + 0xBFBA, + 0x8FD9, + 0x9FF8, + 0x6E17, + 0x7E36, + 0x4E55, + 0x5E74, + 0x2E93, + 0x3EB2, + 0x0ED1, + 0x1EF0, + ] + + crc = 0xFFFF + for byte in data: + crc = (crc << 8) ^ table[(crc >> 8) ^ byte] + crc &= 0xFFFF # important, crc must stay 16bits all the way through + return crc class NamedInt(int): @@ -35,7 +314,7 @@ class NamedInt(int): (case-insensitive).""" def __new__(cls, value, name): - assert is_string(name) + assert isinstance(name, str) obj = int.__new__(cls, value) obj.name = str(name) return obj @@ -50,11 +329,11 @@ class NamedInt(int): return int(self) == int(other) and self.name == other.name if isinstance(other, int): return int(self) == int(other) - if is_string(other): + if isinstance(other, str): return self.name.lower() == other.lower() # this should catch comparisons with bytes in Py3 if other is not None: - raise TypeError('Unsupported type ' + str(type(other))) + raise TypeError(f"Unsupported type {str(type(other))}") def __ne__(self, other): return not self.__eq__(other) @@ -66,7 +345,20 @@ class NamedInt(int): return self.name def __repr__(self): - return 'NamedInt(%d, %r)' % (int(self), self.name) + return f"NamedInt({int(self)}, {self.name!r})" + + @classmethod + def from_yaml(cls, loader, node): + args = loader.construct_mapping(node) + return cls(value=args["value"], name=args["name"]) + + @classmethod + def to_yaml(cls, dumper, data): + return dumper.represent_mapping("!NamedInt", {"value": int(data), "name": data.name}, flow_style=True) + + +yaml.SafeLoader.add_constructor("!NamedInt", NamedInt.from_yaml) +yaml.add_representer(NamedInt, NamedInt.to_yaml) class NamedInts: @@ -82,17 +374,15 @@ class NamedInts: if the value already exists in the set (int or string), ValueError will be raised. """ - __slots__ = ('__dict__', '_values', '_indexed', '_fallback', '_is_sorted') - def __init__(self, dict=None, **kwargs): + __slots__ = ("__dict__", "_values", "_indexed", "_fallback", "_is_sorted") + def __init__(self, dict_=None, **kwargs): def _readable_name(n): - if not is_string(n): - raise TypeError('expected string, got ' + str(type(n))) - return n.replace('__', '/').replace('_', ' ') + return n.replace("__", "/").replace("_", " ") # print (repr(kwargs)) - elements = dict if dict else kwargs + elements = dict_ if dict_ else kwargs values = {k: NamedInt(v, _readable_name(k)) for (k, v) in elements.items()} self.__dict__ = values self._is_sorted = False @@ -116,13 +406,13 @@ class NamedInts: def flag_names(self, value): unknown_bits = value for k in self._indexed: - assert bin(k).count('1') == 1 + assert bin(k).count("1") == 1 if k & value == k: unknown_bits &= ~k yield str(self._indexed[k]) if unknown_bits: - yield 'unknown:%06X' % unknown_bits + yield f"unknown:{unknown_bits:06X}" def _sort_values(self): self._values = sorted(self._values) @@ -140,10 +430,10 @@ class NamedInts: self._sort_values() return value - elif is_string(index): + elif isinstance(index, str): if index in self.__dict__: return self.__dict__[index] - return (next((x for x in self._values if str(x) == index), None)) + return next((x for x in self._values if str(x) == index), None) elif isinstance(index, slice): values = self._values if self._is_sorted else sorted(self._values) @@ -177,17 +467,17 @@ class NamedInts: def __setitem__(self, index, name): assert isinstance(index, int), type(index) if isinstance(name, NamedInt): - assert int(index) == int(name), repr(index) + ' ' + repr(name) + assert int(index) == int(name), f"{repr(index)} {repr(name)}" value = name - elif is_string(name): + elif isinstance(name, str): value = NamedInt(index, name) else: - raise TypeError('name must be a string') + raise TypeError("name must be a string") if str(value) in self.__dict__: - raise ValueError('%s (%d) already known' % (value, int(value))) + raise ValueError(f"{value} ({int(value)}) already known") if int(value) in self._indexed: - raise ValueError('%d (%s) already known' % (int(value), value)) + raise ValueError(f"{int(value)} ({value}) already known") self._values.append(value) self._is_sorted = False @@ -200,7 +490,7 @@ class NamedInts: return self[value] == value elif isinstance(value, int): return value in self._indexed - elif is_string(value): + elif isinstance(value, str): return value in self.__dict__ or value in self._values def __iter__(self): @@ -210,14 +500,41 @@ class NamedInts: return len(self._values) def __repr__(self): - return 'NamedInts(%s)' % ', '.join(repr(v) for v in self._values) + return f"NamedInts({', '.join(repr(v) for v in self._values)})" def __or__(self, other): return NamedInts(**self.__dict__, **other.__dict__) + def __eq__(self, other): + return isinstance(other, self.__class__) and self._values == other._values + + +def flag_names(enum_class: Iterable, value: int) -> Generator[str]: + """Extracts single bit flags from a (binary) number. + + Parameters + ---------- + enum_class + Enum class to extract flags from. + value + Number to extract binary flags from. + """ + indexed = {item.value: item.name for item in enum_class} + + unknown_bits = value + for k in indexed: + # Ensure that the key (flag value) is a power of 2 (a single bit flag) + assert bin(k).count("1") == 1 + if k & value == k: + unknown_bits &= ~k + yield indexed[k].lower() + + # Yield any remaining unknown bits + if unknown_bits != 0: + yield f"unknown:{unknown_bits:06X}" + class UnsortedNamedInts(NamedInts): - def _sort_values(self): pass @@ -229,18 +546,18 @@ class UnsortedNamedInts(NamedInts): def strhex(x): assert x is not None """Produce a hex-string representation of a sequence of bytes.""" - return _hexlify(x).decode('ascii').upper() + return binascii.hexlify(x).decode("ascii").upper() def bytes2int(x, signed=False): - return int.from_bytes(x, signed=signed, byteorder='big') + return int.from_bytes(x, signed=signed, byteorder="big") def int2bytes(x, count=None, signed=False): if count: - return x.to_bytes(length=count, byteorder='big', signed=signed) + return x.to_bytes(length=count, byteorder="big", signed=signed) else: - return x.to_bytes(length=8, byteorder='big', signed=signed).lstrip(b'\x00') + return x.to_bytes(length=8, byteorder="big", signed=signed).lstrip(b"\x00") class KwException(Exception): @@ -258,9 +575,116 @@ class KwException(Exception): return self.args[0].get(k) # was self.args[0][k] -"""Firmware information.""" -FirmwareInfo = namedtuple('FirmwareInfo', ['kind', 'name', 'version', 'extras']) +class FirmwareKind(IntEnum): + Firmware = 0x00 + Bootloader = 0x01 + Hardware = 0x02 + Other = 0x03 -BATTERY_APPROX = NamedInts(empty=0, critical=5, low=20, good=50, full=90) -del namedtuple +@dataclasses.dataclass +class FirmwareInfo: + kind: FirmwareKind + name: str + version: str + extras: str | None + + +class BatteryStatus(Flag): + DISCHARGING = 0x00 + RECHARGING = 0x01 + ALMOST_FULL = 0x02 + FULL = 0x03 + SLOW_RECHARGE = 0x04 + INVALID_BATTERY = 0x05 + THERMAL_ERROR = 0x06 + + +class BatteryLevelApproximation(IntEnum): + EMPTY = 0 + CRITICAL = 5 + LOW = 20 + GOOD = 50 + FULL = 90 + + +@dataclasses.dataclass +class Battery: + """Information about the current state of a battery""" + + ATTENTION_LEVEL = 5 + + level: Optional[Union[BatteryLevelApproximation, int]] + next_level: Optional[Union[NamedInt, int]] + status: Optional[BatteryStatus] + voltage: Optional[int] + light_level: Optional[int] = None # light level for devices with solaar recharging + + def __post_init__(self): + if self.level is None: # infer level from status if needed and possible + if self.status == BatteryStatus.FULL: + self.level = BatteryLevelApproximation.FULL + elif self.status in (BatteryStatus.ALMOST_FULL, BatteryStatus.RECHARGING): + self.level = BatteryLevelApproximation.GOOD + elif self.status == BatteryStatus.SLOW_RECHARGE: + self.level = BatteryLevelApproximation.LOW + + def ok(self) -> bool: + return self.status not in (BatteryStatus.INVALID_BATTERY, BatteryStatus.THERMAL_ERROR) and ( + self.level is None or self.level > Battery.ATTENTION_LEVEL + ) + + def charging(self) -> bool: + return self.status in ( + BatteryStatus.RECHARGING, + BatteryStatus.ALMOST_FULL, + BatteryStatus.FULL, + BatteryStatus.SLOW_RECHARGE, + ) + + def to_str(self) -> str: + if isinstance(self.level, BatteryLevelApproximation): + level = self.level.name.lower() + status = self.status.name.lower().replace("_", " ") if self.status is not None else "Unknown" + return _("Battery: %(level)s (%(status)s)") % {"level": _(level), "status": _(status)} + elif isinstance(self.level, int): + status = self.status.name.lower().replace("_", " ") if self.status is not None else "Unknown" + return _("Battery: %(percent)d%% (%(status)s)") % {"percent": self.level, "status": _(status)} + return "" + + +class Alert(IntEnum): + NONE = 0x00 + NOTIFICATION = 0x01 + SHOW_WINDOW = 0x02 + ATTENTION = 0x04 + ALL = 0xFF + + +class Notification(IntEnum): + NO_OPERATION = 0x00 + CONNECT_DISCONNECT = 0x40 + DJ_PAIRING = 0x41 + CONNECTED = 0x42 + RAW_INPUT = 0x49 + PAIRING_LOCK = 0x4A + POWER = 0x4B + + +class BusID(IntEnum): + USB = 0x03 + BLUETOOTH = 0x05 + + +def _read_usb_product_string(hidraw_path): + """Read the USB product string from sysfs for a hidraw device path.""" + import pathlib + + try: + # /sys/class/hidraw/hidrawN/device/../../product → USB device product string + hidraw_name = pathlib.Path(hidraw_path).name + product_path = pathlib.Path("/sys/class/hidraw") / hidraw_name / "device" / ".." / ".." / "product" + product = product_path.read_text().strip() + return product if product else None + except (OSError, ValueError): + return None diff --git a/lib/logitech_receiver/descriptors.py b/lib/logitech_receiver/descriptors.py index 522a67ec..f2c9bbd3 100644 --- a/lib/logitech_receiver/descriptors.py +++ b/lib/logitech_receiver/descriptors.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -16,30 +14,44 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# -# Devices (not receivers) known to Solaar. -# Solaar can handle many recent devices without having any entry here. -# An entry should only be added to fix problems, such as -# - the device's device ID or WPID falls outside the range that Solaar searches -# - the device uses a USB interface other than 2 -# - the name or codename should be different from what the device reports -from collections import namedtuple +"""Devices (not receivers) known to Solaar. -from . import settings_templates as _ST -from .common import NamedInts as _NamedInts -from .hidpp10 import DEVICE_KIND as _DK -from .hidpp10 import REGISTERS as _R +Solaar can handle many recent devices without having any entry here. +An entry should only be added to fix problems, such as +- the device's device ID or WPID falls outside the range that Solaar searches +- the device uses a USB interface other than 2 +- the name or codename should be different from what the device reports +""" -# -# -# +from .hidpp10_constants import DEVICE_KIND +from .hidpp10_constants import Registers as Reg + + +class _DeviceDescriptor: + def __init__( + self, + name=None, + kind=None, + wpid=None, + codename=None, + protocol=None, + registers=None, + usbid=None, + interface=None, + btid=None, + ): + self.name = name + self.kind = kind + self.wpid = wpid + self.codename = codename + self.protocol = protocol + self.registers = registers + self.usbid = usbid + self.interface = interface + self.btid = btid + self.settings = None -_DeviceDescriptor = namedtuple( - '_DeviceDescriptor', - ('name', 'kind', 'wpid', 'codename', 'protocol', 'registers', 'settings', 'usbid', 'interface', 'btid') -) -del namedtuple DEVICES_WPID = {} DEVICES = {} @@ -57,24 +69,35 @@ def _D( interface=None, btid=None, ): - if kind is None: kind = ( - _DK.mouse if 'Mouse' in name else _DK.keyboard if 'Keyboard' in name else _DK.numpad - if 'Number Pad' in name else _DK.touchpad if 'Touchpad' in name else _DK.trackball if 'Trackball' in name else None + DEVICE_KIND.mouse + if "Mouse" in name + else DEVICE_KIND.keyboard + if "Keyboard" in name + else DEVICE_KIND.numpad + if "Number Pad" in name + else DEVICE_KIND.touchpad + if "Touchpad" in name + else DEVICE_KIND.trackball + if "Trackball" in name + else None ) - assert kind is not None, 'descriptor for %s does not have kind set' % name + assert kind is not None, f"descriptor for {name} does not have kind set" if protocol is not None: if wpid: - for w in wpid if isinstance(wpid, tuple) else (wpid, ): + for w in wpid if isinstance(wpid, tuple) else (wpid,): if protocol > 1.0: - assert w[0:1] == '4', '%s has protocol %0.1f, wpid %s' % (name, protocol, w) + assert w[0:1] == "4", f"{name} has protocol {protocol:0.1f}, wpid {w}" else: - if w[0:1] == '1': - assert kind == _DK.mouse, '%s has protocol %0.1f, wpid %s' % (name, protocol, w) - elif w[0:1] == '2': - assert kind in (_DK.keyboard, _DK.numpad), '%s has protocol %0.1f, wpid %s' % (name, protocol, w) + if w[0:1] == "1": + assert kind == DEVICE_KIND.mouse, f"{name} has protocol {protocol:0.1f}, wpid {w}" + elif w[0:1] == "2": + assert kind in ( + DEVICE_KIND.keyboard, + DEVICE_KIND.numpad, + ), f"{name} has protocol {protocol:0.1f}, wpid {w}" device_descriptor = _DeviceDescriptor( name=name, @@ -83,26 +106,25 @@ def _D( codename=codename, protocol=protocol, registers=registers, - settings=settings, usbid=usbid, interface=interface, - btid=btid + btid=btid, ) if usbid: found = get_usbid(usbid) - assert found is None, 'duplicate usbid in device descriptors: %s' % (found, ) + assert found is None, f"duplicate usbid in device descriptors: {found}" if btid: found = get_btid(btid) - assert found is None, 'duplicate btid in device descriptors: %s' % (found, ) + assert found is None, f"duplicate btid in device descriptors: {found}" - assert codename not in DEVICES, 'duplicate codename in device descriptors: %s' % (DEVICES[codename], ) + assert codename not in DEVICES, f"duplicate codename in device descriptors: {DEVICES[codename]}" if codename: DEVICES[codename] = device_descriptor if wpid: - for w in wpid if isinstance(wpid, tuple) else (wpid, ): - assert w not in DEVICES_WPID, 'duplicate wpid in device descriptors: %s' % (DEVICES_WPID[w], ) + for w in wpid if isinstance(wpid, tuple) else (wpid,): + assert w not in DEVICES_WPID, f"duplicate wpid in device descriptors: {DEVICES_WPID[w]}" DEVICES_WPID[w] = device_descriptor @@ -157,176 +179,290 @@ def get_btid(btid): # The 'protocol' and 'wpid' fields are optional (they can be discovered at # runtime), but specifying them here speeds up device discovery and reduces the # USB traffic Solaar has to do to fully identify peripherals. -# Same goes for HID++ 2.0 feature settings (like _feature_fn_swap). # # The 'registers' field indicates read-only registers, specifying a state. These # are valid (AFAIK) only to HID++ 1.0 devices. # The 'settings' field indicates a read/write register; based on them Solaar -# generates, at runtime, the settings controls in the device panel. HID++ 1.0 -# devices may only have register-based settings; HID++ 2.0 devices may only have +# generates, at runtime, the settings controls in the device panel. +# Solaar now sets up this field in settings_templates.py to eliminate a imports loop. +# HID++ 1.0 devices may only have register-based settings; HID++ 2.0 devices may only have # feature-based settings. # Devices are organized by kind # Within kind devices are sorted by wpid, then by usbid, then by btid, with missing values sorted later -# yapf: disable - # Keyboards -_D('Wireless Keyboard EX110', codename='EX110', protocol=1.0, wpid='0055', registers=(_R.battery_status, )) -_D('Wireless Keyboard S510', codename='S510', protocol=1.0, wpid='0056', registers=(_R.battery_status, )) -_D('Wireless Wave Keyboard K550', codename='K550', protocol=1.0, wpid='0060', registers=(_R.battery_status, ), - settings=[_ST.RegisterFnSwap]) -_D('Wireless Keyboard EX100', codename='EX100', protocol=1.0, wpid='0065', registers=(_R.battery_status, )) -_D('Wireless Keyboard MK300', codename='MK300', protocol=1.0, wpid='0068', registers=(_R.battery_status, )) -_D('Number Pad N545', codename='N545', protocol=1.0, wpid='2006', registers=(_R.battery_status, )) -_D('Wireless Compact Keyboard K340', codename='K340', protocol=1.0, wpid='2007', registers=(_R.battery_status, )) -_D('Wireless Keyboard MK700', codename='MK700', protocol=1.0, wpid='2008', - registers=(_R.battery_status, ), settings=[_ST.RegisterFnSwap]) -_D('Wireless Wave Keyboard K350', codename='K350', protocol=1.0, wpid='200A', registers=(_R.battery_status, )) -_D('Wireless Keyboard MK320', codename='MK320', protocol=1.0, wpid='200F', registers=(_R.battery_status, )) -_D('Wireless Illuminated Keyboard K800', codename='K800', protocol=1.0, wpid='2010', - registers=(_R.battery_status, _R.three_leds), settings=[_ST.RegisterFnSwap, _ST.RegisterHandDetection]) -_D('Wireless Keyboard K520', codename='K520', protocol=1.0, wpid='2011', - registers=(_R.battery_status, ), settings=[_ST.RegisterFnSwap]) -_D('Wireless Solar Keyboard K750', codename='K750', protocol=2.0, wpid='4002', settings=[_ST.FnSwap]) -_D('Wireless Keyboard K270 (unifying)', codename='K270', protocol=2.0, wpid='4003') -_D('Wireless Keyboard K360', codename='K360', protocol=2.0, wpid='4004', settings=[_ST.FnSwap]) -_D('Wireless Keyboard K230', codename='K230', protocol=2.0, wpid='400D') -_D('Wireless Touch Keyboard K400', codename='K400', protocol=2.0, wpid=('400E', '4024'), settings=[_ST.FnSwap]) -_D('Wireless Keyboard MK270', codename='MK270', protocol=2.0, wpid='4023', settings=[_ST.FnSwap]) -_D('Illuminated Living-Room Keyboard K830', codename='K830', protocol=2.0, wpid='4032', settings=[_ST.NewFnSwap]) -_D('Wireless Touch Keyboard K400 Plus', codename='K400 Plus', protocol=2.0, wpid='404D') -_D('Wireless Multi-Device Keyboard K780', codename='K780', protocol=4.5, wpid='405B', settings=[_ST.NewFnSwap]) -_D('Wireless Keyboard K375s', codename='K375s', protocol=2.0, wpid='4061', settings=[_ST.K375sFnSwap]) -_D('Craft Advanced Keyboard', codename='Craft', protocol=4.5, wpid='4066', btid=0xB350) -_D('Wireless Illuminated Keyboard K800 new', codename='K800 new', protocol=4.5, wpid='406E', settings=[_ST.FnSwap]) -_D('Wireless Keyboard K470', codename='K470', protocol=4.5, wpid='4075', settings=[_ST.FnSwap]) -_D('MX Keys Keyboard', codename='MX Keys', protocol=4.5, wpid='408A', btid=0xB35B) -_D('G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard', codename='G915 TKL', protocol=4.2, wpid='408E', usbid=0xC343) -_D('Illuminated Keyboard', codename='Illuminated', protocol=1.0, usbid=0xc318, interface=1, settings=[_ST.RegisterFnSwap]) -_D('G213 Prodigy Gaming Keyboard', codename='G213', usbid=0xc336, interface=1) -_D('G512 RGB Mechanical Gaming Keyboard', codename='G512', usbid=0xc33c, interface=1) -_D('G815 Mechanical Keyboard', codename='G815', usbid=0xc33f, interface=1) -_D('diNovo Edge Keyboard', codename='diNovo', protocol=1.0, wpid='C714', settings=[_ST.RegisterFnSwap]) -_D('K845 Mechanical Keyboard', codename='K845', usbid=0xc341, interface=3) +_D("Wireless Keyboard EX110", codename="EX110", protocol=1.0, wpid="0055", registers=(Reg.BATTERY_STATUS,)) +_D("Wireless Keyboard S510", codename="S510", protocol=1.0, wpid="0056", registers=(Reg.BATTERY_STATUS,)) +_D("Wireless Wave Keyboard K550", codename="K550", protocol=1.0, wpid="0060", registers=(Reg.BATTERY_STATUS,)) +_D("Wireless Keyboard EX100", codename="EX100", protocol=1.0, wpid="0065", registers=(Reg.BATTERY_STATUS,)) +_D("Wireless Keyboard MK300", codename="MK300", protocol=1.0, wpid="0068", registers=(Reg.BATTERY_STATUS,)) +_D("Number Pad N545", codename="N545", protocol=1.0, wpid="2006", registers=(Reg.BATTERY_STATUS,)) +_D("Wireless Compact Keyboard K340", codename="K340", protocol=1.0, wpid="2007", registers=(Reg.BATTERY_STATUS,)) +_D("Wireless Keyboard MK700", codename="MK700", protocol=1.0, wpid="2008", registers=(Reg.BATTERY_STATUS,)) +_D("Wireless Wave Keyboard K350", codename="K350", protocol=1.0, wpid="200A", registers=(Reg.BATTERY_STATUS,)) +_D("Wireless Keyboard MK320", codename="MK320", protocol=1.0, wpid="200F", registers=(Reg.BATTERY_STATUS,)) +_D( + "Wireless Illuminated Keyboard K800", + codename="K800", + protocol=1.0, + wpid="2010", + registers=(Reg.BATTERY_STATUS, Reg.THREE_LEDS), +) +_D("Wireless Keyboard K520", codename="K520", protocol=1.0, wpid="2011", registers=(Reg.BATTERY_STATUS,)) +_D("Wireless Solar Keyboard K750", codename="K750", protocol=2.0, wpid="4002") +_D("Wireless Keyboard K270 (unifying)", codename="K270", protocol=2.0, wpid="4003") +# incorrect _D("Wireless Keyboard K360", codename="K360", protocol=2.0, wpid="4004") +_D("Wireless Keyboard K230", codename="K230", protocol=2.0, wpid="400D") +_D("Wireless Touch Keyboard K400", codename="K400", protocol=2.0, wpid=("400E", "4024")) +_D("Wireless Keyboard MK270", codename="MK270", protocol=2.0, wpid="4023") +_D("Illuminated Living-Room Keyboard K830", codename="K830", protocol=2.0, wpid="4032") +_D("Wireless Touch Keyboard K400 Plus", codename="K400 Plus", protocol=2.0, wpid="404D") +_D("Wireless Multi-Device Keyboard K780", codename="K780", protocol=4.5, wpid="405B") +_D("Wireless Keyboard K375s", codename="K375s", protocol=2.0, wpid="4061") +_D("Craft Advanced Keyboard", codename="Craft", protocol=4.5, wpid="4066", btid=0xB350) +_D("Wireless Illuminated Keyboard K800 new", codename="K800 new", protocol=4.5, wpid="406E") +_D("Wireless Keyboard K470", codename="K470", protocol=4.5, wpid="4075") +_D("MX Keys Keyboard", codename="MX Keys", protocol=4.5, wpid="408A", btid=0xB35B) +_D( + "G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard", + codename="G915 TKL", + protocol=4.2, + wpid="408E", + usbid=0xC343, +) +_D("Illuminated Keyboard", codename="Illuminated", protocol=1.0, usbid=0xC318, interface=1) +_D("G213 Prodigy Gaming Keyboard", codename="G213", usbid=0xC336, interface=1) +_D("G512 RGB Mechanical Gaming Keyboard", codename="G512", usbid=0xC33C, interface=1) +_D("G815 Mechanical Keyboard", codename="G815", usbid=0xC33F, interface=1) +_D("diNovo Edge Keyboard", codename="diNovo", protocol=1.0, wpid="C714") +_D("K845 Mechanical Keyboard", codename="K845", usbid=0xC341, interface=1) # Mice -_D('LX5 Cordless Mouse', codename='LX5', protocol=1.0, wpid='0036', registers=(_R.battery_status, )) -_D('LX7 Cordless Laser Mouse', codename='LX7', protocol=1.0, wpid='0039', registers=(_R.battery_status, )) -_D('Wireless Wave Mouse M550', codename='M550', protocol=1.0, wpid='003C', registers=(_R.battery_status, )) -_D('Wireless Mouse EX100', codename='EX100m', protocol=1.0, wpid='003F', registers=(_R.battery_status, )) -_D('Wireless Mouse M30', codename='M30', protocol=1.0, wpid='0085', registers=(_R.battery_status, )) -_D('MX610 Laser Cordless Mouse', codename='MX610', protocol=1.0, wpid='1001', registers=(_R.battery_status, )) -_D('G7 Cordless Laser Mouse', codename='G7', protocol=1.0, wpid='1002', registers=(_R.battery_status, )) -_D('V400 Laser Cordless Mouse', codename='V400', protocol=1.0, wpid='1003', registers=(_R.battery_status, )) -_D('MX610 Left-Handled Mouse', codename='MX610L', protocol=1.0, wpid='1004', registers=(_R.battery_status, )) -_D('V450 Laser Cordless Mouse', codename='V450', protocol=1.0, wpid='1005', registers=(_R.battery_status, )) -_D('VX Revolution', codename='VX Revolution', kind=_DK.mouse, protocol=1.0, wpid=('1006', '100D', '0612'), - registers=(_R.battery_charge, )) -_D('MX Air', codename='MX Air', protocol=1.0, kind=_DK.mouse, wpid=('1007', '100E'), registers=(_R.battery_charge, )) -_D('MX Revolution', codename='MX Revolution', protocol=1.0, kind=_DK.mouse, wpid=('1008', '100C'), - registers=(_R.battery_charge, )) -_D('MX620 Laser Cordless Mouse', codename='MX620', protocol=1.0, wpid=('100A', '1016'), registers=(_R.battery_charge, )) -_D('VX Nano Cordless Laser Mouse', codename='VX Nano', protocol=1.0, wpid=('100B', '100F'), - registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) -_D('V450 Nano Cordless Laser Mouse', codename='V450 Nano', protocol=1.0, wpid='1011', registers=(_R.battery_charge, )) -_D('V550 Nano Cordless Laser Mouse', codename='V550 Nano', protocol=1.0, wpid='1013', - registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll, ]) -_D('MX 1100 Cordless Laser Mouse', codename='MX 1100', protocol=1.0, kind=_DK.mouse, wpid='1014', - registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) -_D('Anywhere Mouse MX', codename='Anywhere MX', protocol=1.0, wpid='1017', - registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) +_D("LX5 Cordless Mouse", codename="LX5", protocol=1.0, wpid="0036", registers=(Reg.BATTERY_STATUS,)) +_D("LX7 Cordless Laser Mouse", codename="LX7", protocol=1.0, wpid="0039", registers=(Reg.BATTERY_STATUS,)) +_D("Wireless Wave Mouse M550", codename="M550", protocol=1.0, wpid="003C", registers=(Reg.BATTERY_STATUS,)) +_D("Wireless Mouse EX100", codename="EX100m", protocol=1.0, wpid="003F", registers=(Reg.BATTERY_STATUS,)) +_D("Wireless Mouse M30", codename="M30", protocol=1.0, wpid="0085", registers=(Reg.BATTERY_STATUS,)) +_D("MX610 Laser Cordless Mouse", codename="MX610", protocol=1.0, wpid="1001", registers=(Reg.BATTERY_STATUS,)) +_D("G7 Cordless Laser Mouse", codename="G7", protocol=1.0, wpid="1002", registers=(Reg.BATTERY_STATUS,)) +_D("V400 Laser Cordless Mouse", codename="V400", protocol=1.0, wpid="1003", registers=(Reg.BATTERY_STATUS,)) +_D("MX610 Left-Handled Mouse", codename="MX610L", protocol=1.0, wpid="1004", registers=(Reg.BATTERY_STATUS,)) +_D("V450 Laser Cordless Mouse", codename="V450", protocol=1.0, wpid="1005", registers=(Reg.BATTERY_STATUS,)) +_D( + "VX Revolution", + codename="VX Revolution", + kind=DEVICE_KIND.mouse, + protocol=1.0, + wpid=("1006", "100D", "0612"), + registers=(Reg.BATTERY_CHARGE,), +) +_D( + "MX Air", + codename="MX Air", + protocol=1.0, + kind=DEVICE_KIND.mouse, + wpid=("1007", "100E"), + registers=Reg.BATTERY_CHARGE, +) +_D( + "MX Revolution", + codename="MX Revolution", + protocol=1.0, + kind=DEVICE_KIND.mouse, + wpid=("1008", "100C"), + registers=(Reg.BATTERY_CHARGE,), +) +_D( + "MX620 Laser Cordless Mouse", + codename="MX620", + protocol=1.0, + wpid=("100A", "1016"), + registers=(Reg.BATTERY_CHARGE,), +) +_D( + "VX Nano Cordless Laser Mouse", + codename="VX Nano", + protocol=1.0, + wpid=("100B", "100F"), + registers=(Reg.BATTERY_CHARGE,), +) +_D( + "V450 Nano Cordless Laser Mouse", + codename="V450 Nano", + protocol=1.0, + wpid="1011", + registers=(Reg.BATTERY_CHARGE,), +) +_D( + "V550 Nano Cordless Laser Mouse", + codename="V550 Nano", + protocol=1.0, + wpid="1013", + registers=(Reg.BATTERY_CHARGE,), +) +_D( + "MX 1100 Cordless Laser Mouse", + codename="MX 1100", + protocol=1.0, + kind=DEVICE_KIND.mouse, + wpid="1014", + registers=(Reg.BATTERY_CHARGE,), +) +_D("Anywhere Mouse MX", codename="Anywhere MX", protocol=1.0, wpid="1017", registers=(Reg.BATTERY_CHARGE,)) +_D( + "Performance Mouse MX", + codename="Performance MX", + protocol=1.0, + wpid="101A", + registers=(Reg.BATTERY_STATUS, Reg.THREE_LEDS), +) +_D( + "Marathon Mouse M705 (M-R0009)", + codename="M705 (M-R0009)", + protocol=1.0, + wpid="101B", + registers=(Reg.BATTERY_CHARGE,), +) +_D( + "Wireless Mouse M350", + codename="M350", + protocol=1.0, + wpid="101C", + registers=(Reg.BATTERY_CHARGE,), +) +_D( + "Wireless Mouse M505", + codename="M505/B605", + protocol=1.0, + wpid="101D", + registers=(Reg.BATTERY_CHARGE,), +) +_D( + "Wireless Mouse M305", + codename="M305", + protocol=1.0, + wpid="101F", + registers=(Reg.BATTERY_STATUS,), +) +_D( + "Wireless Mouse M215", + codename="M215", + protocol=1.0, + wpid="1020", +) +_D( + "G700 Gaming Mouse", + codename="G700", + protocol=1.0, + wpid="1023", + usbid=0xC06B, + interface=1, + registers=( + Reg.BATTERY_STATUS, + Reg.THREE_LEDS, + ), +) +_D("Wireless Mouse M310", codename="M310", protocol=1.0, wpid="1024", registers=(Reg.BATTERY_STATUS,)) +_D("Wireless Mouse M510", codename="M510", protocol=1.0, wpid="1025", registers=(Reg.BATTERY_STATUS,)) +_D("Fujitsu Sonic Mouse", codename="Sonic", protocol=1.0, wpid="1029") +_D( + "G700s Gaming Mouse", + codename="G700s", + protocol=1.0, + wpid="102A", + usbid=0xC07C, + interface=1, + registers=( + Reg.BATTERY_STATUS, + Reg.THREE_LEDS, + ), +) +_D("Couch Mouse M515", codename="M515", protocol=2.0, wpid="4007") +# _D("Wireless Mouse M175", codename="M175", protocol=2.0, wpid="4008") +_D("Wireless Mouse M325", codename="M325", protocol=2.0, wpid="400A") +_D("Wireless Mouse M525", codename="M525", protocol=2.0, wpid="4013") +_D("Wireless Mouse M345", codename="M345", protocol=2.0, wpid="4017") +_D("Wireless Mouse M187", codename="M187", protocol=2.0, wpid="4019") +_D("Touch Mouse M600", codename="M600", protocol=2.0, wpid="401A") +_D("Wireless Mouse M150", codename="M150", protocol=2.0, wpid="4022") +_D("Wireless Mouse M185", codename="M185", protocol=2.0, wpid="4038") +_D("Wireless Mouse MX Master", codename="MX Master", protocol=4.5, wpid="4041", btid=0xB012) +_D("Anywhere Mouse MX 2", codename="Anywhere MX 2", protocol=4.5, wpid="404A") +_D("Wireless Mouse M510", codename="M510v2", protocol=2.0, wpid="4051") +_D("Wireless Mouse M185 new", codename="M185n", protocol=4.5, wpid="4054") +_D("Wireless Mouse M185/M235/M310", codename="M185/M235/M310", protocol=4.5, wpid="4055") +_D("Wireless Mouse MX Master 2S", codename="MX Master 2S", protocol=4.5, wpid="4069", btid=0xB019) +_D("Multi Device Silent Mouse M585/M590", codename="M585/M590", protocol=4.5, wpid="406B") +_D( + "Marathon Mouse M705 (M-R0073)", + codename="M705 (M-R0073)", + protocol=4.5, + wpid="406D", +) +_D("MX Vertical Wireless Mouse", codename="MX Vertical", protocol=4.5, wpid="407B", btid=0xB020, usbid=0xC08A) +_D("Wireless Mouse Pebble M350", codename="Pebble", protocol=2.0, wpid="4080") +_D("MX Master 3 Wireless Mouse", codename="MX Master 3", protocol=4.5, wpid="4082", btid=0xB023) +_D("PRO X Wireless", kind="mouse", codename="PRO X", wpid="4093", usbid=0xC094) - -class _PerformanceMXDpi(_ST.RegisterDpi): - choices_universe = _NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 100)) - validator_options = {'choices': choices_universe} - - -_D('Performance Mouse MX', codename='Performance MX', protocol=1.0, wpid='101A', - registers=(_R.battery_status, _R.three_leds), - settings=[_PerformanceMXDpi, _ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) -_D('Marathon Mouse M705 (M-R0009)', codename='M705 (M-R0009)', protocol=1.0, wpid='101B', - registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) -_D('Wireless Mouse M350', codename='M350', protocol=1.0, wpid='101C', registers=(_R.battery_charge, )) -_D('Wireless Mouse M505', codename='M505/B605', protocol=1.0, wpid='101D', - registers=(_R.battery_charge, ), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) -_D('Wireless Mouse M305', codename='M305', protocol=1.0, wpid='101F', - registers=(_R.battery_status, ), settings=[_ST.RegisterSideScroll]) -_D('Wireless Mouse M215', codename='M215', protocol=1.0, wpid='1020') -_D('G700 Gaming Mouse', codename='G700', protocol=1.0, wpid='1023', usbid=0xc06b, interface=1, - registers=(_R.battery_status, _R.three_leds,), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) -_D('Wireless Mouse M310', codename='M310', protocol=1.0, wpid='1024', registers=(_R.battery_status, )) -_D('Wireless Mouse M510', codename='M510', protocol=1.0, wpid='1025', - registers=(_R.battery_status, ), settings=[_ST.RegisterSideScroll]) -_D('Fujitsu Sonic Mouse', codename='Sonic', protocol=1.0, wpid='1029') -_D('G700s Gaming Mouse', codename='G700s', protocol=1.0, wpid='102A', usbid=0xc07c, interface=1, - registers=(_R.battery_status, _R.three_leds,), settings=[_ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) -_D('Couch Mouse M515', codename='M515', protocol=2.0, wpid='4007') -_D('Wireless Mouse M175', codename='M175', protocol=2.0, wpid='4008') -_D('Wireless Mouse M325', codename='M325', protocol=2.0, wpid='400A', settings=[_ST.HiResScroll]) -_D('Wireless Mouse M525', codename='M525', protocol=2.0, wpid='4013') -_D('Wireless Mouse M345', codename='M345', protocol=2.0, wpid='4017') -_D('Wireless Mouse M187', codename='M187', protocol=2.0, wpid='4019') -_D('Touch Mouse M600', codename='M600', protocol=2.0, wpid='401A') -_D('Wireless Mouse M150', codename='M150', protocol=2.0, wpid='4022') -_D('Wireless Mouse M185', codename='M185', protocol=2.0, wpid='4038') -_D('Wireless Mouse MX Master', codename='MX Master', protocol=4.5, wpid='4041', btid=0xb012) -_D('Anywhere Mouse MX 2', codename='Anywhere MX 2', protocol=4.5, wpid='404A', settings=[_ST.HiresSmoothInvert]) -_D('Wireless Mouse M510', codename='M510v2', protocol=2.0, wpid='4051') -_D('Wireless Mouse M185 new', codename='M185n', protocol=4.5, wpid='4054') -_D('Wireless Mouse M185/M235/M310', codename='M185/M235/M310', protocol=4.5, wpid='4055') -_D('Wireless Mouse MX Master 2S', codename='MX Master 2S', protocol=4.5, wpid='4069', btid=0xb019, - settings=[_ST.HiresSmoothInvert]) -_D('Multi Device Silent Mouse M585/M590', codename='M585/M590', protocol=4.5, wpid='406B') -_D('Marathon Mouse M705 (M-R0073)', codename='M705 (M-R0073)', protocol=4.5, wpid='406D', - settings=[_ST.HiresSmoothInvert, _ST.PointerSpeed]) -_D('MX Vertical Wireless Mouse', codename='MX Vertical', protocol=4.5, wpid='407B', btid=0xb020, usbid=0xc08a) -_D('Wireless Mouse Pebble M350', codename='Pebble', protocol=2.0, wpid='4080') -_D('MX Master 3 Wireless Mouse', codename='MX Master 3', protocol=4.5, wpid='4082', btid=0xb023) -_D('PRO X Wireless', kind='mouse', codename='PRO X', wpid='4093', usbid=0xc094) - -_D('G9 Laser Mouse', codename='G9', usbid=0xc048, interface=1, protocol=1.0, - settings=[_PerformanceMXDpi, _ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) -_D('G9x Laser Mouse', codename='G9x', usbid=0xc066, interface=1, protocol=1.0, - settings=[_PerformanceMXDpi, _ST.RegisterSmoothScroll, _ST.RegisterSideScroll]) -_D('G502 Gaming Mouse', codename='G502', usbid=0xc07d, interface=1) -_D('G402 Gaming Mouse', codename='G402', usbid=0xc07e, interface=1) -_D('G900 Chaos Spectrum Gaming Mouse', codename='G900', usbid=0xc081) -_D('G403 Gaming Mouse', codename='G403', usbid=0xc082) -_D('G903 Lightspeed Gaming Mouse', codename='G903', usbid=0xc086) -_D('G703 Lightspeed Gaming Mouse', codename='G703', usbid=0xc087) -_D('GPro Gaming Mouse', codename='GPro', usbid=0xc088) -_D('G502 SE Hero Gaming Mouse', codename='G502 Hero', usbid=0xc08b, interface=1) -_D('G502 Lightspeed Gaming Mouse', codename='G502 Lightspeed', usbid=0xc08d) -_D('MX518 Gaming Mouse', codename='MX518', usbid=0xc08e, interface=1) -_D('G703 Hero Gaming Mouse', codename='G703 Hero', usbid=0xc090) -_D('G903 Hero Gaming Mouse', codename='G903 Hero', usbid=0xc091) -_D(None, kind=_DK.mouse, usbid=0xc092, interface=1) # two mice share this ID -_D('M500S Mouse', codename='M500S', usbid=0xc093, interface=1) +_D("G9 Laser Mouse", codename="G9", usbid=0xC048, interface=1, protocol=1.0) +_D("G9x Laser Mouse", codename="G9x", usbid=0xC066, interface=1, protocol=1.0) +_D("G502 Gaming Mouse", codename="G502", usbid=0xC07D, interface=1) +_D("G402 Gaming Mouse", codename="G402", usbid=0xC07E, interface=1) +_D("G900 Chaos Spectrum Gaming Mouse", codename="G900", usbid=0xC081) +_D("G403 Gaming Mouse", codename="G403", usbid=0xC082) +_D("G903 Lightspeed Gaming Mouse", codename="G903", usbid=0xC086) +_D("G703 Lightspeed Gaming Mouse", codename="G703", usbid=0xC087) +_D("GPro Gaming Mouse", codename="GPro", usbid=0xC088) +_D("G502 SE Hero Gaming Mouse", codename="G502 Hero", usbid=0xC08B, interface=1) +_D("G502 Lightspeed Gaming Mouse", codename="G502 Lightspeed", usbid=0xC08D) +_D("MX518 Gaming Mouse", codename="MX518", usbid=0xC08E, interface=1) +_D("G703 Hero Gaming Mouse", codename="G703 Hero", usbid=0xC090) +_D("G903 Hero Gaming Mouse", codename="G903 Hero", usbid=0xC091) +_D(None, kind=DEVICE_KIND.mouse, usbid=0xC092, interface=1) # two mice share this ID +_D("M500S Mouse", codename="M500S", usbid=0xC093, interface=1) # _D('G600 Gaming Mouse', codename='G600 Gaming', usbid=0xc24a, interface=1) # not an HID++ device -_D('G500s Gaming Mouse', codename='G500s Gaming', usbid=0xc24e, interface=1, protocol=1.0) -_D('G502 Proteus Spectrum Optical Mouse', codename='G502 Proteus Spectrum', usbid=0xc332, interface=1) -_D('Logitech PRO Gaming Keyboard', codename='PRO Gaming Keyboard', usbid=0xc339, interface=1) +_D("G500 Gaming Mouse", codename="G500 Gaming", usbid=0xC068, interface=1, protocol=1.0) +_D("G500s Gaming Mouse", codename="G500s Gaming", usbid=0xC24E, interface=1, protocol=1.0) +_D("G502 Proteus Spectrum Optical Mouse", codename="G502 Proteus Spectrum", usbid=0xC332, interface=1) +_D("Logitech PRO Gaming Keyboard", codename="PRO Gaming Keyboard", usbid=0xC339, interface=1) -_D('Logitech MX Revolution Mouse M-RCL 124', codename='M-RCL 124', btid=0xb007, interface=1) +_D("Logitech MX Revolution Mouse M-RCL 124", codename="M-RCL 124", btid=0xB007, interface=1) # Trackballs -_D('Wireless Trackball M570', codename='M570') +_D("Wireless Trackball M570", codename="M570") # Touchpads -_D('Wireless Touchpad', codename='Wireless Touch', protocol=2.0, wpid='4011') -_D('Wireless Rechargeable Touchpad T650', codename='T650', protocol=2.0, wpid='4101') +_D("Wireless Touchpad", codename="Wireless Touch", protocol=2.0, wpid="4011") +_D("Wireless Rechargeable Touchpad T650", codename="T650", protocol=2.0, wpid="4101") +_D( + "G Powerplay", codename="Powerplay", protocol=2.0, kind=DEVICE_KIND.touchpad, wpid="405F" +) # To override self-identification # Headset -_D('G533 Gaming Headset', codename='G533 Headset', protocol=2.0, interface=3, kind=_DK.headset, usbid=0x0a66) -_D('G535 Gaming Headset', codename='G535 Headset', protocol=2.0, interface=3, kind=_DK.headset, usbid=0x0ac4) -_D('G935 Gaming Headset', codename='G935 Headset', protocol=2.0, interface=3, kind=_DK.headset, usbid=0x0a87) -_D('G733 Gaming Headset', codename='G733 Headset', protocol=2.0, interface=3, kind=_DK.headset, usbid=0x0ab5) -_D('G733 Gaming Headset', codename='G733 Headset New', protocol=2.0, interface=3, kind=_DK.headset, usbid=0x0afe) -_D('PRO X Wireless Gaming Headset', codename='PRO Headset', protocol=2.0, interface=3, kind=_DK.headset, usbid=0x0aba) +_D("G533 Gaming Headset", codename="G533 Headset", protocol=2.0, interface=3, kind=DEVICE_KIND.headset, usbid=0x0A66) +_D("G535 Gaming Headset", codename="G535 Headset", protocol=2.0, interface=3, kind=DEVICE_KIND.headset, usbid=0x0AC4) +_D("G935 Gaming Headset", codename="G935 Headset", protocol=2.0, interface=3, kind=DEVICE_KIND.headset, usbid=0x0A87) +_D("G733 Gaming Headset", codename="G733 Headset", protocol=2.0, interface=3, kind=DEVICE_KIND.headset, usbid=0x0AB5) +_D( + "G733 Gaming Headset", + codename="G733 Headset New", + protocol=2.0, + interface=3, + kind=DEVICE_KIND.headset, + usbid=0x0AFE, +) +_D( + "PRO X Wireless Gaming Headset", + codename="PRO Headset", + protocol=2.0, + interface=3, + kind=DEVICE_KIND.headset, + usbid=0x0ABA, +) +# PRO X 2 LIGHTSPEED Gaming Headset (0x0AF7) — fully probed via Centurion transport, no static descriptor needed diff --git a/lib/logitech_receiver/desktop_notifications.py b/lib/logitech_receiver/desktop_notifications.py new file mode 100644 index 00000000..dfe86ec6 --- /dev/null +++ b/lib/logitech_receiver/desktop_notifications.py @@ -0,0 +1,129 @@ +## Copyright (C) 2024 Solaar contributors +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Implements the desktop notification service.""" + +import importlib +import logging + +logger = logging.getLogger(__name__) + + +def notifications_available(): + """Checks if notification service is available.""" + notifications_supported = False + try: + import gi + + gi.require_version("Notify", "0.7") + gi.require_version("Gtk", "3.0") + + importlib.util.find_spec("gi.repository.GLib") + importlib.util.find_spec("gi.repository.Gtk") + importlib.util.find_spec("gi.repository.Notify") + + notifications_supported = True + except ValueError as e: + logger.warning(f"Notification service is not available: {e}") + return notifications_supported + + +available = notifications_available() + +if available: + from gi.repository import GLib + from gi.repository import Gtk + from gi.repository import Notify + + # cache references to shown notifications here to allow reuse + _notifications = {} + _ICON_LISTS = {} + + def init(): + """Initialize desktop notifications.""" + global available + if available: + if not Notify.is_initted(): + if logger.isEnabledFor(logging.INFO): + logger.info("starting desktop notifications") + try: + return Notify.init("solaar") # replace with better name later + except Exception: + logger.exception("initializing desktop notifications") + available = False + return available and Notify.is_initted() + + def uninit(): + """Stop desktop notifications.""" + if available and Notify.is_initted(): + if logger.isEnabledFor(logging.INFO): + logger.info("stopping desktop notifications") + _notifications.clear() + Notify.uninit() + + def show(dev, message: str, icon=None): + """Show a notification with title and text.""" + if available and (Notify.is_initted() or init()): + summary = dev.name + n = _notifications.get(summary) # reuse notification of same name + if n is None: + n = _notifications[summary] = Notify.Notification() + icon_name = device_icon_name(dev.name, dev.kind) if icon is None else icon + n.update(summary, message, icon_name) + n.set_urgency(Notify.Urgency.NORMAL) + n.set_hint("desktop-entry", GLib.Variant("s", "solaar")) # replace with better name late + try: + return n.show() + except Exception: + logger.exception(f"showing {n}") + + def device_icon_list(name="_", kind=None): + icon_list = _ICON_LISTS.get(name) + if icon_list is None: + # names of possible icons, in reverse order of likelihood + # the theme will hopefully pick up the most appropriate + icon_list = ["preferences-desktop-peripherals"] + kind = str(kind) + if kind: + if kind == "numpad": + icon_list += ("input-keyboard", "input-dialpad") + elif kind == "touchpad": + icon_list += ("input-mouse", "input-tablet") + elif kind == "trackball": + icon_list += ("input-mouse",) + elif kind == "headset": + icon_list += ("audio-headphones", "audio-headset") + icon_list += (f"input-{kind}",) + _ICON_LISTS[name] = icon_list + return icon_list + + def device_icon_name(name, kind=None): + _default_theme = Gtk.IconTheme.get_default() + icon_list = device_icon_list(name, kind) + for n in reversed(icon_list): + if _default_theme.has_icon(n): + return n + +else: + + def init(): + return False + + def uninit(): + return None + + def show(dev, reason=None): + return None diff --git a/lib/logitech_receiver/device.py b/lib/logitech_receiver/device.py index dc21c178..ff3565b7 100644 --- a/lib/logitech_receiver/device.py +++ b/lib/logitech_receiver/device.py @@ -1,214 +1,305 @@ -import errno as _errno -import threading as _threading +## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from logging import INFO as _INFO -from logging import getLogger +from __future__ import annotations + +import errno +import logging +import struct +import threading +import time +import typing + +from typing import Callable from typing import Optional +from typing import Protocol -import hidapi as _hid -import solaar.configuration as _configuration +from solaar import configuration -from . import base as _base -from . import descriptors as _descriptors -from . import hidpp10 as _hidpp10 -from . import hidpp20 as _hidpp20 -from .common import strhex as _strhex -from .settings_templates import check_feature_settings as _check_feature_settings +from . import base +from . import descriptors +from . import exceptions +from . import hidpp10 +from . import hidpp10_constants +from . import hidpp20 +from . import settings +from . import settings_templates +from .common import Alert +from .common import Battery +from .common import _read_usb_product_string +from .hidpp10_constants import NotificationFlag +from .hidpp20_constants import SupportedFeature -_log = getLogger(__name__) -del getLogger +if typing.TYPE_CHECKING: + from logitech_receiver import common -_R = _hidpp10.REGISTERS -_IR = _hidpp10.INFO_SUBREGISTERS +logger = logging.getLogger(__name__) -KIND_MAP = {kind: _hidpp10.DEVICE_KIND[str(kind)] for kind in _hidpp20.DEVICE_KIND} +_hidpp10 = hidpp10.Hidpp10() +_hidpp20 = hidpp20.Hidpp20() -# -# -# + +class LowLevelInterface(Protocol): + def open_path(self, path) -> int: + ... + + def find_paired_node(self, receiver_path: str, index: int, timeout: int): + ... + + def ping(self, handle, number, long_message: bool): + ... + + def request(self, handle, devnumber, request_id, *params, **kwargs): + ... + + def close(self, handle, *args, **kwargs) -> bool: + ... + + +def create_device(low_level: LowLevelInterface, device_info, setting_callback=None): + """Opens a Logitech Device found attached to the machine, by Linux device path. + :returns: An open file handle for the found receiver, or None. + """ + try: + handle = low_level.open_path(device_info.path) + if handle: + if getattr(device_info, "centurion", False): + base._centurion_handles.add(int(handle)) + # a direct connected device might not be online (as reported by user) + return Device( + low_level, + None, + None, + None, + handle=handle, + device_info=device_info, + setting_callback=setting_callback, + ) + except OSError as e: + logger.exception("open %s", device_info) + if e.errno == errno.EACCES: + raise e + except Exception as e: + logger.exception("open %s", device_info) + raise e class Device: instances = [] - read_register = _hidpp10.read_register - write_register = _hidpp10.write_register + read_register: Callable = hidpp10.read_register + write_register: Callable = hidpp10.write_register def __init__( self, + low_level: LowLevelInterface, receiver, number, - link_notification=None, - path=None, + online, + pairing_info=None, handle=None, - short=None, - long=None, - product_id=None, - bus_id=None + device_info=None, + setting_callback=None, ): - assert receiver or handle - Device.instances.append(self) + assert receiver or device_info + if receiver: + assert 0 < number <= 15 # some receivers have devices past their max # of devices + self.low_level = low_level + self.number = number # will be None at this point for directly connected devices + self.online = online # is the device online? - gates many atempts to contact the device + self.descriptor = None self.isDevice = True # some devices act as receiver so we need a property to distinguish them self.may_unpair = False self.receiver = receiver - self.path = path self.handle = handle - self.product_id = product_id - self.hidpp_short = short - self.hidpp_long = long - self.bluetooth = bus_id == 0x0005 # Bluetooth connections need long messages - - if receiver: - assert number > 0 and number <= 15 # some receivers have devices past their max # of devices - self.number = number # will be None at this point for directly connected devices - self.online = None - - self.wpid = None # the Wireless PID is unique per device model - self.descriptor = None - self._kind = None # mouse, keyboard, etc (see _hidpp10.DEVICE_KIND) - self._codename = None # Unifying peripherals report a codename. + self.path = device_info.path if device_info else None + self.product_id = device_info.product_id if device_info else None + self.hidpp_short = device_info.hidpp_short if device_info else None + self.hidpp_long = device_info.hidpp_long if device_info else None + self.centurion = device_info.centurion if device_info else False + self._centurion_usb_name = None + if self.centurion: + self.hidpp_long = True # Centurion devices always use long HID++ messages + # Read USB product string for device name — avoids slow bridge probe via CENTURION_DEVICE_NAME. + # device_info.product is often None (udev reads USB interface attrs, not device attrs), + # so fall back to reading from sysfs. + self._centurion_usb_name = getattr(device_info, "product", None) if device_info else None + if not self._centurion_usb_name and self.path: + self._centurion_usb_name = _read_usb_product_string(self.path) + self.bluetooth = device_info.bus_id == 0x0005 if device_info else False # Bluetooth needs long messages + self.hid_serial = device_info.serial if device_info else None + self.setting_callback = setting_callback # for changes to settings + self.status_callback = None # for changes to other potentially visible aspects + self.wpid = pairing_info["wpid"] if pairing_info else None # the Wireless PID is unique per device model + self._kind = pairing_info["kind"] if pairing_info else None # mouse, keyboard, etc (see hidpp10.DEVICE_KIND) + self._serial = pairing_info["serial"] if pairing_info else None # serial number (an 8-char hex string) + self._polling_rate = pairing_info["polling"] if pairing_info else None + self._power_switch = pairing_info["power_switch"] if pairing_info else None self._name = None # the full name of the model + self._codename = None # Unifying peripherals report a codename. self._protocol = None # HID++ protocol version, 1.0 or 2.0 - self._serial = None # serial number (an 8-char hex string) - self._unitId = None # unit id (distinguishes within a model - the same as serial) + self._unitId = None # unit id (distinguishes within a model - generally the same as serial) self._modelId = None # model id (contains identifiers for the transports of the device) self._tid_map = None # map from transports to product identifiers self._persister = None # persister holds settings + self._led_effects = self._firmware = self._keys = self._remap_keys = self._gestures = self._force_buttons = None + self._keyboard_layout = None # lazy: country code from HID++ 0x4540, None if unsupported + self._profiles = self._backlight = self._settings = None + self.registers = [] + self.notification_flags = None + self.battery_info = None + self.link_encrypted = None + self._active = None # lags self.online - is used to help determine when to setup devices + self.present = True # used for devices that are integral with their receiver but that separately be disconnected - self._firmware = None - self._keys = None - self._remap_keys = None - self._gestures = None - self._gestures_lock = _threading.Lock() - self._registers = None - self._settings = None self._feature_settings_checked = False - self._settings_lock = _threading.Lock() - self._polling_rate = None - self._power_switch = None - - # See `add_notification_handler` - self._notification_handlers = {} + self._gestures_lock = threading.Lock() + self._settings_lock = threading.Lock() + self._persister_lock = threading.Lock() + self._simple_lock = threading.Lock() + self._notification_handlers = {} # See `add_notification_handler` + self.cleanups = [] # functions to run on the device when it is closed if not self.path: - self.path = _hid.find_paired_node(receiver.path, number, 1) if receiver else None + self.path = self.low_level.find_paired_node(receiver.path, number, 1) if receiver else None if not self.handle: try: - self.handle = _base.open_path(self.path) if self.path else None + self.handle = self.low_level.open_path(self.path) if self.path else None except Exception: # maybe the device wasn't set up try: - import time time.sleep(1) - self.handle = _base.open_path(self.path) if self.path else None + self.handle = self.low_level.open_path(self.path) if self.path else None except Exception: # give up - self.handle = None + self.handle = None # should this give up completely? if receiver: - if link_notification is not None: - self.online = not bool(ord(link_notification.data[0:1]) & 0x40) - self.wpid = _strhex(link_notification.data[2:3] + link_notification.data[1:2]) - # assert link_notification.address == (0x04 if unifying else 0x03) - kind = ord(link_notification.data[0:1]) & 0x0F - # get 27Mhz wpid and set kind based on index - if receiver.receiver_kind == '27Mhz': # 27 Mhz receiver - self.wpid = '00' + _strhex(link_notification.data[2:3]) - kind = receiver.get_kind_from_index(number) - self._kind = _hidpp10.DEVICE_KIND[kind] - elif receiver.receiver_kind == '27Mhz': # 27 Mhz receiver doesn't have pairing registers - self.wpid = _hid.find_paired_node_wpid(receiver.path, number) - if not self.wpid: - _log.error('Unable to get wpid from udev for device %d of %s', number, receiver) - raise _base.NoSuchDevice(number=number, receiver=receiver, error='Not present 27Mhz device') - kind = receiver.get_kind_from_index(number) - self._kind = _hidpp10.DEVICE_KIND[kind] - else: # get information from pairing registers - self.online = True - self.update_pairing_information() - self.update_extended_pairing_information() - if not self.wpid and not self._serial: # if neither then the device almost certainly wasn't found - raise _base.NoSuchDevice(number=number, receiver=receiver, error='no wpid or serial') - - # the wpid is set to None on this object when the device is unpaired - assert self.wpid is not None, 'failed to read wpid: device %d of %s' % (number, receiver) - - self.descriptor = _descriptors.get_wpid(self.wpid) + if not self.wpid: + raise exceptions.NoSuchDevice( + number=number, receiver=receiver, error="no wpid for device connected to receiver" + ) + self.descriptor = descriptors.get_wpid(self.wpid) if self.descriptor is None: - # Last chance to correctly identify the device; many Nano receivers do not support this call. - codename = self.receiver.device_codename(self.number) + codename = self.receiver.device_codename(self.number) # Last chance to get a descriptor, may fail if codename: self._codename = codename - self.descriptor = _descriptors.get_codename(self._codename) + self.descriptor = descriptors.get_codename(self._codename) else: - self.online = None # a direct connected device might not be online (as reported by user) - self.descriptor = _descriptors.get_btid(self.product_id) if self.bluetooth else \ - _descriptors.get_usbid(self.product_id) - if self.number is None: # for direct-connected devices get 'number' from descriptor protocol else use 0xFF - self.number = 0x00 if self.descriptor and self.descriptor.protocol and self.descriptor.protocol < 2.0 else 0xFF + self.descriptor = ( + descriptors.get_btid(self.product_id) if self.bluetooth else descriptors.get_usbid(self.product_id) + ) + # for direct-connected devices get 'number' from descriptor protocol else use 0xFF + self.number = 0x00 if self.descriptor and self.descriptor.protocol and self.descriptor.protocol < 2.0 else 0xFF + try: # determine whether a direct-connected device is online + self.ping() + except exceptions.NoSuchDevice as e: + if self.number == 0xFF: # guessed wrong number? + self.number = 0x00 + self.ping() + else: + raise e if self.descriptor: self._name = self.descriptor.name - if self.descriptor.protocol: - self._protocol = self.descriptor.protocol if self._codename is None: self._codename = self.descriptor.codename if self._kind is None: self._kind = self.descriptor.kind + self._protocol = self.descriptor.protocol if self.descriptor.protocol else None + self.registers = self.descriptor.registers if self.descriptor.registers else [] if self._protocol is not None: - self.features = None if self._protocol < 2.0 else _hidpp20.FeaturesArray(self) + self.features = {} if self._protocol < 2.0 else hidpp20.FeaturesArray(self) else: - # may be a 2.0 device; if not, it will fix itself later - self.features = _hidpp20.FeaturesArray(self) + self.features = hidpp20.FeaturesArray(self) # may be a 2.0 device; if not, it will fix itself later - @classmethod - def find(self, serial): - assert serial, 'need serial number or unit ID to find a device' - result = None + Device.instances.append(self) + + def find(self, id): # find a device by serial number or unit ID or name or codename + assert id, "need id to find a device" for device in Device.instances: - if device.online and (device.unitId == serial or device.serial == serial): - result = device - return result + if device.online and (device.unitId == id or device.serial == id or device.name == id or device.codename == id): + return device @property def protocol(self): if not self._protocol: - self.ping() + try: + self.ping() + except exceptions.NoSuchDevice: + logger.warning("device %s inaccessible - no protocol set", self) return self._protocol or 0 @property def codename(self): if not self._codename: - if not self.online: # be very defensive - self.ping() if self.online and self.protocol >= 2.0: - self._codename = _hidpp20.get_friendly_name(self) - if not self._codename: - self._codename = self.name.split(' ', 1)[0] if self.name else None + if not self.centurion: + self._codename = _hidpp20.get_friendly_name(self) + if not self._codename and self.name: + if self.centurion: + self._codename = self.name + else: + names = self.name.split(" ") + self._codename = names[1 if len(names) > 1 and names[0] == "Logitech" else 0] if not self._codename and self.receiver: codename = self.receiver.device_codename(self.number) if codename: self._codename = codename elif self.protocol < 2.0: - self._codename = '? (%s)' % (self.wpid or self.product_id) - return self._codename or '?? (%s)' % (self.wpid or self.product_id) + self._codename = "? (%s)" % (self.wpid or self.product_id) + return self._codename or f"?? ({self.wpid or self.product_id})" @property def name(self): if not self._name: - if not self.online: # be very defensive - try: - self.ping() - except _base.NoSuchDevice: - pass - if self.online and self.protocol >= 2.0: - self._name = _hidpp20.get_name(self) - return self._name or self._codename or ('Unknown device %s' % (self.wpid or self.product_id)) + with self._simple_lock: + if self._name is None: + if self.online and self.centurion: + self._name = _hidpp20.get_name_centurion(self) or getattr(self, "_centurion_usb_name", None) + if not self._name: + self._name = f"Unknown device {self.wpid or self.product_id}" + elif self.online and self.protocol >= 2.0: + self._name = _hidpp20.get_name(self) + return self._name or self._codename or f"Unknown device {self.wpid or self.product_id}" def get_ids(self): + if self.centurion: + self._get_ids_centurion() + return ids = _hidpp20.get_ids(self) if ids: self._unitId, self._modelId, self._tid_map = ids - if _log.isEnabledFor(_INFO) and self._serial and self._serial != self._unitId: - _log.info('%s: unitId %s does not match serial %s', self, self._unitId, self._serial) + if logger.isEnabledFor(logging.INFO) and self._serial and self._serial != self._unitId: + logger.info("%s: unitId %s does not match serial %s", self, self._unitId, self._serial) + + def _get_ids_centurion(self): + if getattr(self, "_centurion_ids_done", False): + return + self._centurion_ids_done = True + serial = _hidpp20.get_serial_centurion(self) + if not serial or not serial.strip() or not serial.strip().isprintable(): + serial = _hidpp20.get_serial_centurion_sub(self) + if serial and serial.strip() and serial.strip().isprintable(): + self._serial = serial.strip() + self._unitId = self._serial + hw_info = _hidpp20.get_hardware_info_centurion(self) + if hw_info: + model_id, hw_revision, product_id = hw_info + self._modelId = f"{product_id:04X}" + self._tid_map = {"usbid": f"{product_id:04X}"} @property def unitId(self): @@ -228,37 +319,35 @@ class Device: self.get_ids() return self._tid_map - def update_pairing_information(self): - if self.receiver and (not self.wpid or self._kind is None or self._polling_rate is None): - wpid, kind, polling_rate = self.receiver.device_pairing_information(self.number) - if not self.wpid: - self.wpid = wpid - if not self._kind: - self._kind = kind - if not self._polling_rate: - self._polling_rate = polling_rate - - def update_extended_pairing_information(self): - if self.receiver: - serial, power_switch = self.receiver.device_extended_pairing_information(self.number) - if not self._serial: - self._serial = serial - if not self._power_switch: - self._power_switch = power_switch - @property def kind(self): - if not self._kind: - self.update_pairing_information() - if not self._kind and self.protocol >= 2.0: - kind = _hidpp20.get_kind(self) - self._kind = KIND_MAP[kind] if kind else None - return self._kind or '?' + if not self._kind and self.online and self.protocol >= 2.0: + if self.centurion: + self._kind = self._infer_kind_centurion() + else: + self._kind = _hidpp20.get_kind(self) + return self._kind or "?" + + def _infer_kind_centurion(self): + """Infer device kind from Centurion features (sub-device or top-level).""" + # Check sub-device features (wireless via bridge) + for feature in getattr(self, "_centurion_sub_features", ()): + if isinstance(feature, int) and 0x0600 <= feature <= 0x06FF: + return hidpp10_constants.DEVICE_KIND.headset + # Check top-level features (direct USB connection, no bridge) + if self.features: + for feature, _index in self.features.enumerate(): + feat_int = int(feature) if isinstance(feature, int) else 0 + if 0x0600 <= feat_int <= 0x06FF: + return hidpp10_constants.DEVICE_KIND.headset + return None @property - def firmware(self): + def firmware(self) -> tuple[common.FirmwareInfo]: if self._firmware is None and self.online: - if self.protocol >= 2.0: + if self.centurion: + self._firmware = _hidpp20.get_firmware_centurion_sub(self) or _hidpp20.get_firmware_centurion(self) + elif self.protocol >= 2.0: self._firmware = _hidpp20.get_firmware(self) else: self._firmware = _hidpp10.get_firmware(self) @@ -266,32 +355,41 @@ class Device: @property def serial(self): - if not self._serial: - self.update_extended_pairing_information() - return self._serial or '' + if not self._serial and self.online and self.centurion: + self.get_ids() + return self._serial or "" @property def id(self): - if not self.serial: - if self.persister and self.persister.get('_serial', None): - self._serial = self.persister.get('_serial', None) return self.unitId or self.serial @property def power_switch_location(self): - if not self._power_switch: - self.update_extended_pairing_information() return self._power_switch @property def polling_rate(self): - if not self._polling_rate: - self.update_pairing_information() - if self.protocol >= 2.0: + if self.online and self.protocol >= 2.0: rate = _hidpp20.get_polling_rate(self) self._polling_rate = rate if rate else self._polling_rate return self._polling_rate + @property + def keyboard_layout(self): + if self._keyboard_layout is None and self.online and self.protocol >= 2.0: + if SupportedFeature.KEYBOARD_LAYOUT_2 in self.features: + self._keyboard_layout = _hidpp20.get_keyboard_layout(self) + return self._keyboard_layout + + @property + def led_effects(self): + if not self._led_effects and self.online and self.protocol >= 2.0: + if SupportedFeature.COLOR_LED_EFFECTS in self.features: + self._led_effects = hidpp20.LEDEffectsInfo(self) + elif SupportedFeature.RGB_EFFECTS in self.features: + self._led_effects = hidpp20.RGBEffectsInfo(self) + return self._led_effects + @property def keys(self): if not self._keys: @@ -316,13 +414,39 @@ class Device: return self._gestures @property - def registers(self): - if not self._registers: - if self.descriptor and self.descriptor.registers: - self._registers = list(self.descriptor.registers) - else: - self._registers = [] - return self._registers + def backlight(self): + if self._backlight is None: + if self.online and self.protocol >= 2.0: + self._backlight = _hidpp20.get_backlight(self) + return self._backlight + + @property + def profiles(self): + if self._profiles is None: + if self.online and self.protocol >= 2.0: + self._profiles = _hidpp20.get_profiles(self) + return self._profiles + + def force_buttons(self): + if self._force_buttons is None: + if self.online and self.protocol >= 2.0: + self._force_buttons = _hidpp20.get_force_buttons(self) or () + return self._force_buttons + + def set_configuration(self, configuration_, no_reply=False): + if self.online and self.protocol >= 2.0: + _hidpp20.config_change(self, configuration_, no_reply=no_reply) + + def reset(self, no_reply=False): + self.set_configuration(0, no_reply) + + @property + def persister(self): + if not self._persister: + with self._persister_lock: + if not self._persister: + self._persister = configuration.persister(self) + return self._persister @property def settings(self): @@ -344,37 +468,87 @@ class Device: if not self._feature_settings_checked: with self._settings_lock: if not self._feature_settings_checked: - self._feature_settings_checked = _check_feature_settings(self, self._settings) + self._feature_settings_checked = settings_templates.check_feature_settings(self, self._settings) return self._settings - def set_configuration(self, configuration, no_reply=False): - if self.online and self.protocol >= 2.0: - _hidpp20.config_change(self, configuration, no_reply=no_reply) - - def reset(self, no_reply=False): - self.set_configuration(0, no_reply) - - @property - def persister(self): - if not self._persister: - self._persister = _configuration.persister(self) - return self._persister - def battery(self): # None or level, next, status, voltage if self.protocol < 2.0: return _hidpp10.get_battery(self) else: - battery_feature = self.persister.get('_battery', None) if self.persister else None + battery_feature = self.persister.get("_battery", None) if self.persister else None if battery_feature != 0: result = _hidpp20.get_battery(self, battery_feature) try: - feature, level, next, status, voltage = result + feature, battery = result if self.persister and battery_feature is None: - self.persister['_battery'] = feature - return level, next, status, voltage + self.persister["_battery"] = feature.value + return battery except Exception: - if self.persister and battery_feature is None: - self.persister['_battery'] = result + if self.persister and battery_feature is None and result is not None and result != 0: + self.persister["_battery"] = result.value + + def set_battery_info(self, info): + """Update battery information for device, calling changed callback if necessary""" + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: battery %s, %s", self, info.level, info.status) + if info.level is None and self.battery_info: # use previous level if missing from new information + info.level = self.battery_info.level + + changed = self.battery_info != info + self.battery_info, old_info = info, self.battery_info + if old_info is None: + old_info = Battery(None, None, None, None) + + alert, reason = Alert.NONE, None + if not info.ok(): + logger.warning("%s: battery %d%%, ALERT %s", self, info.level, info.status) + if old_info.status != info.status: + alert = Alert.NOTIFICATION | Alert.ATTENTION + reason = info.to_str() + + if changed or reason: + # update the leds on the device, if any + _hidpp10.set_3leds(self, info.level, charging=info.charging(), warning=bool(alert)) + self.changed(active=True, alert=alert, reason=reason) + + # Retrieve and regularize battery status + def read_battery(self): + if self.online: + battery = self.battery() + self.set_battery_info(battery if battery is not None else Battery(None, None, None, None)) + + def changed(self, active=None, alert=Alert.NONE, reason=None, push=False): + """The status of the device had changed, so invoke the status callback. + Also push notifications and settings to the device when necessary.""" + if logger.isEnabledFor(logging.DEBUG): + logger.debug("device %d changing: active=%s %s present=%s", self.number, active, self._active, self.present) + if active is not None: + self.online = active + was_active, self._active = self._active, active + if active: + # Push settings for new devices when devices request software reconfiguration + # and when devices become active if they don't have wireless device status feature, + if ( + was_active is None + or not was_active + or push + and (not self.features or SupportedFeature.WIRELESS_DEVICE_STATUS not in self.features) + ): + if logger.isEnabledFor(logging.INFO): + logger.info("%s pushing device settings %s", self, self.settings) + settings.apply_all_settings(self) + if not was_active: + if self.protocol < 2.0: # Make sure to set notification flags on the device + self.notification_flags = self.enable_connection_notifications() + else: + self.set_configuration(0x11) # signal end of configuration + self.read_battery() # battery information may have changed so try to read it now + elif was_active and self.receiver and not isinstance(self.receiver, CenturionReceiver): + hidpp10.set_configuration_pending_flags(self.receiver, 0xFF) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("device %d changed: active=%s %s", self.number, self._active, self.battery_info) + if self.status_callback is not None: + self.status_callback(self, alert, reason) def enable_connection_notifications(self, enable=True): """Enable or disable device (dis)connection notifications on this @@ -383,22 +557,21 @@ class Device: return False if enable: - set_flag_bits = ( - _hidpp10.NOTIFICATION_FLAG.battery_status - | _hidpp10.NOTIFICATION_FLAG.keyboard_illumination - | _hidpp10.NOTIFICATION_FLAG.wireless - | _hidpp10.NOTIFICATION_FLAG.software_present - ) + set_flag_bits = NotificationFlag.BATTERY_STATUS | NotificationFlag.UI | NotificationFlag.CONFIGURATION_COMPLETE else: set_flag_bits = 0 ok = _hidpp10.set_notification_flags(self, set_flag_bits) if not ok: - _log.warn('%s: failed to %s device notifications', self, 'enable' if enable else 'disable') + logger.warning("%s: failed to %s device notifications", self, "enable" if enable else "disable") flag_bits = _hidpp10.get_notification_flags(self) - flag_names = None if flag_bits is None else tuple(_hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits)) - if _log.isEnabledFor(_INFO): - _log.info('%s: device notifications %s %s', self, 'enabled' if enable else 'disabled', flag_names) + if logger.isEnabledFor(logging.INFO): + if flag_bits is None: + flag_names = None + else: + flag_names = hidpp10_constants.NotificationFlag.flag_names(flag_bits) + is_enabled = "enabled" if enable else "disabled" + logger.info(f"{self}: device notifications {is_enabled} {flag_names}") return flag_bits if ok else None def add_notification_handler(self, id: str, fn): @@ -418,8 +591,8 @@ class Device: def remove_notification_handler(self, id: str): """Unregisters the notification handler under name `id`.""" - if id not in self._notification_handlers and _log.isEnabledFor(_INFO): - _log.info(f'Tried to remove nonexistent notification handler {id} from device {self}.') + if id not in self._notification_handlers and logger.isEnabledFor(logging.INFO): + logger.info(f"Tried to remove nonexistent notification handler {id} from device {self}.") else: del self._notification_handlers[id] @@ -435,32 +608,273 @@ class Device: long = self.hidpp_long is True or ( self.hidpp_long is None and (self.bluetooth or self._protocol is not None and self._protocol >= 2.0) ) - return _base.request( - self.handle or self.receiver.handle, - self.number, + # Centurion child: CPL framing strips devnumber and responses always + # have devnumber=0xFF, so we must send 0xFF to match responses. + devnumber = 0xFF if (self.centurion and self.receiver and not self.handle) else self.number + return self.low_level.request( + self.handle or (self.receiver.handle if self.receiver else None), + devnumber, request_id, *params, no_reply=no_reply, long_message=long, - protocol=self.protocol + protocol=self.protocol, ) def feature_request(self, feature, function=0x00, *params, no_reply=False): if self.protocol >= 2.0: - return _hidpp20.feature_request(self, feature, function, *params, no_reply=no_reply) + if self.centurion: + # Ensure sub-device features are discovered before routing decision + if self.features is not None: + self.features._check() + if feature in getattr(self, "_centurion_sub_features", ()): + sub_idx = self.features.get(feature) + if sub_idx is not None: + return self.centurion_bridge_request(sub_idx, function, *params, no_reply=no_reply) + return hidpp20.feature_request(self, feature, function, *params, no_reply=no_reply) + + # Max sub-message bytes in the first bridge fragment: + # 64 - 1 (report ID) - 1 (cpl_len) - 1 (flags) - 2 (bridge prefix) - 2 (bridge hdr) = 57 + # LGHUB uses 56 for first fragment (60 byte payload - 4 bridge overhead) + _BRIDGE_FIRST_CHUNK = 56 + # Continuation fragments carry raw sub_msg data (no bridge prefix/hdr): + # 64 - 1 (report ID) - 1 (cpl_len) - 1 (flags) = 61, but LGHUB uses 60 + _BRIDGE_CONT_CHUNK = 60 + + def centurion_bridge_request(self, sub_feat_idx, sub_function=0x00, *params, no_reply=False): + """Send a request to a Centurion sub-device via CentPPBridge. + + Builds the 4-layer nested message: + Layer 1: [0x51] + Layer 2: [cpl_length, flags] + Layer 3: [bridge_idx, sendFragment_func|swid, bridge_hdr...] + Layer 4: [sub_cpl=0x00, sub_feat_idx, sub_func|swid, params...] + + For multi-fragment sends, only the first fragment includes the bridge + prefix and header. Continuation fragments carry raw sub_msg data. + The CPL flags byte encodes fragment index and continuation: + flags = (fragment_index << 1) | (1 if more_fragments else 0) + Single-frame messages use flags=0x00. + + Returns the sub-device response data (after bridge header), or None. + """ + if not getattr(self, "centurion", False): + raise ValueError("centurion_bridge_request called on non-Centurion device") + bridge_idx = getattr(self, "_centurion_bridge_index", None) + if bridge_idx is None: + raise ValueError("CentPPBridge index not discovered yet") + handle = self.handle or (self.receiver.handle if self.receiver else None) + if not handle: + return None + + sw_id = base._get_next_sw_id() + + # Build sub-device message: [sub_cpl=0x00, sub_feat_idx, sub_func|swid, params...] + # sub_function is in standard HID++ format: func_number << 4 (e.g. 0x10 for function 1) + sub_params = b"".join(struct.pack("B", p) if isinstance(p, int) else p for p in params) if params else b"" + sub_msg = struct.pack("BBB", 0x00, sub_feat_idx, (sub_function & 0xF0) | sw_id) + sub_params + + # Build bridge header: [device_id<<4 | len_hi, len_lo] + # device_id=0 for the headset, len is the total sub-message length + sub_len = len(sub_msg) + bridge_hdr = struct.pack("BB", (0x00 << 4) | ((sub_len >> 8) & 0x0F), sub_len & 0xFF) + bridge_prefix = struct.pack("BB", bridge_idx, (0x01 << 4) | sw_id) + + timeout = base.DEFAULT_TIMEOUT + with base.acquire_timeout(base.handle_lock(handle), handle, timeout): + if sub_len <= self._BRIDGE_FIRST_CHUNK: + # Single-frame path + layer3 = bridge_prefix + bridge_hdr + sub_msg + base.write_centurion_cpl(handle, layer3) + else: + # Multi-fragment send + # Fragment 0: bridge_prefix + bridge_hdr + first chunk of sub_msg + # Fragments 1+: raw sub_msg continuation data (no bridge overhead) + # CPL flags = (frag_index << 1) | (1 if more_fragments else 0) + # All fragments are sent back-to-back without waiting for + # intermediate ACKs (verified via LGHUB pcap). The device + # reassembles internally and sends a single ACK + MessageEvent + # after the last fragment. + frag_index = 0 + offset = 0 + while offset < sub_len: + if frag_index == 0: + chunk_size = self._BRIDGE_FIRST_CHUNK + chunk = sub_msg[offset : offset + chunk_size] + layer3 = bridge_prefix + bridge_hdr + chunk + else: + chunk_size = self._BRIDGE_CONT_CHUNK + chunk = sub_msg[offset : offset + chunk_size] + layer3 = chunk + has_more = (offset + chunk_size) < sub_len + flags = (frag_index << 1) | (1 if has_more else 0) + base.write_centurion_cpl(handle, layer3, flags=flags) + offset += len(chunk) + frag_index += 1 + + if no_reply: + return None + + # Read ACK + MessageEvent response + request_started = time.time() + ack_received = False + while time.time() - request_started < timeout: + reply = base._read(handle, timeout) + if not reply: + continue + _report_id, _devnumber, reply_data = reply + # ACK: short response echoing feat_idx and func|swid + if len(reply_data) >= 2 and reply_data[0] == bridge_idx: + func_sw = reply_data[1] + if (func_sw >> 4) == 0x01 and (func_sw & 0x0F) == sw_id: + ack_received = True + break + if (func_sw >> 4) == 0x01 and (func_sw & 0x0F) == 0: + # MessageEvent arrived before ACK — validate it's for our request + if self._is_bridge_response_for(reply_data, sub_feat_idx): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("bridge idx=%d fn=0x%02X -> OK", sub_feat_idx, sub_function) + return self._parse_bridge_response(reply_data) + # Unsolicited notification, skip it + if not ack_received: + logger.warning("centurion_bridge_request: no ACK received") + return None + + # Read MessageEvent response (bridge function 1 with SW ID 0 = event) + while time.time() - request_started < timeout: + reply = base._read(handle, timeout) + if not reply: + continue + _report_id, _devnumber, reply_data = reply + if len(reply_data) >= 2 and reply_data[0] == bridge_idx: + func_sw = reply_data[1] + if (func_sw >> 4) == 0x01 and (func_sw & 0x0F) == 0: + if self._is_bridge_response_for(reply_data, sub_feat_idx): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("bridge idx=%d fn=0x%02X -> OK", sub_feat_idx, sub_function) + return self._parse_bridge_response(reply_data) + # Unsolicited notification for a different feature, skip it + logger.warning("centurion_bridge_request: no MessageEvent received") + return None + + @staticmethod + def _wait_for_bridge_ack(handle, bridge_idx, sw_id, timeout): + """Wait for a bridge ACK response between multi-fragment sends.""" + started = time.time() + while time.time() - started < timeout: + reply = base._read(handle, timeout) + if not reply: + continue + _report_id, _devnumber, reply_data = reply + if len(reply_data) >= 2 and reply_data[0] == bridge_idx: + func_sw = reply_data[1] + if (func_sw >> 4) == 0x01 and (func_sw & 0x0F) == sw_id: + return True + return False + + @staticmethod + def _is_bridge_response_for(reply_data, expected_sub_feat_idx): + """Check if a bridge MessageEvent is a response for our specific sub-feature request. + + Accepts both normal responses (sub_feat_idx matches) and error responses + (sub_feat_idx=0xFF with original feat_idx in next byte). + Unsolicited notifications (sub_cpl=0xFF) are rejected. + """ + if len(reply_data) < 6: + return False + sub_cpl = reply_data[4] + sub_feat_idx = reply_data[5] + # Notifications have sub_cpl=0xFF; our responses have sub_cpl=0x00 + if sub_cpl != 0x00: + return False + if sub_feat_idx == expected_sub_feat_idx: + return True + # Error response: sub_feat_idx=0xFF, next byte is the original feat_idx that errored + if sub_feat_idx == 0xFF and len(reply_data) >= 7 and reply_data[6] == expected_sub_feat_idx: + return True + return False + + @staticmethod + def _parse_bridge_response(reply_data): + """Extract sub-device response from a CentPPBridge MessageEvent. + + reply_data layout (after report_id and devnumber have been stripped): + [bridge_idx, func_sw, dev_id<<4|len_hi, len_lo, sub_cpl, sub_feat_idx, sub_func_sw, data...] + Returns the sub-device data starting from sub_feat_idx onward. + + Error responses have sub_feat_idx=0xFF: [... sub_cpl, 0xFF, orig_feat_idx, orig_func_sw, error_code] + These return None. + """ + if len(reply_data) < 7: + return None + sub_feat_idx = reply_data[5] + # Error response from sub-device + if sub_feat_idx == 0xFF: + error_code = reply_data[8] if len(reply_data) > 8 else 0 + orig_feat_idx = reply_data[6] if len(reply_data) > 6 else 0 + logger.debug("bridge sub-device error: feat_idx=%d error=0x%02X", orig_feat_idx, error_code) + return None + return reply_data[7:] # response data after sub_cpl, sub_feat_idx, sub_func_sw + + def _record_ping_protocol(self, handle, protocol): + """Record a successful ping's protocol version, including raw Centurion (major, minor).""" + self._protocol = protocol + cent_ver = base._centurion_protocol_versions.get(int(handle)) + if cent_ver: + self._centurion_protocol = cent_ver def ping(self): - """Checks if the device is online, returns True of False""" - # long = self.bluetooth or self.hidpp_short is False or self._protocol is not None and self._protocol >= 2.0 + """Checks if the device is online and present, returns True of False. + Some devices are integral with their receiver but may not be present even if the receiver responds to ping.""" + if self.centurion and self.receiver and not self.handle: + # Centurion child: first check if dongle is reachable + handle = self.receiver.handle + try: + protocol = self.low_level.ping(handle, 0xFF, long_message=True) + except exceptions.NoReceiver: + self.online = False + return False + if protocol: + self._record_ping_protocol(handle, protocol) + # Dongle responded — now check if headset is actually on by probing through bridge. + # Send ROOT.GetFeature(0x0001) to the sub-device via CentPPBridge. + bridge_idx = getattr(self, "_centurion_bridge_index", None) + if bridge_idx is not None: + try: + result = self.centurion_bridge_request(0, 0x00, 0x00, 0x01) + self.online = result is not None and self.present + except Exception: + self.online = False + else: + self.online = False + return self.online long = self.hidpp_long is True or ( self.hidpp_long is None and (self.bluetooth or self._protocol is not None and self._protocol >= 2.0) ) - protocol = _base.ping(self.handle or self.receiver.handle, self.number, long_message=long) - self.online = protocol is not None + handle = self.handle or self.receiver.handle + try: + protocol = self.low_level.ping(handle, self.number, long_message=long) + except exceptions.NoReceiver: # if ping fails, device is offline + protocol = None + self.online = protocol is not None and self.present if protocol: - self._protocol = protocol + self._record_ping_protocol(handle, protocol) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("pinged %s: online %s protocol %s present %s", self.number, self.online, protocol, self.present) return self.online + def notify_devices(self): # no need to notify, as there are none + pass + + def close(self): + handle, self.handle = self.handle, None + if self in Device.instances: + Device.instances.remove(self) + if hasattr(self, "cleanups"): + for cleanup in self.cleanups: + cleanup(self) + return handle and self.low_level.close(handle) + def __index__(self): return self.number @@ -480,48 +894,22 @@ class Device: __nonzero__ = __bool__ + def status_string(self): + return self.battery_info.to_str() if self.battery_info is not None else "" + def __str__(self): try: - name = self.name or self.codename or '?' - except _base.NoSuchDevice: - name = 'name not available' - return '' % (self.number, self.wpid or self.product_id, name, self.serial) + name = self._name or self._codename or "?" + except exceptions.NoSuchDevice: + name = "name not available" + return f"" __repr__ = __str__ - def notify_devices(self): # no need to notify, as there are none - pass - - @classmethod - def open(self, device_info): - """Opens a Logitech Device found attached to the machine, by Linux device path. - :returns: An open file handle for the found receiver, or ``None``. - """ - try: - handle = _base.open_path(device_info.path) - if handle: - return Device( - None, - None, - handle=handle, - path=device_info.path, - short=device_info.hidpp_short, - long=device_info.hidpp_long, - product_id=device_info.product_id, - bus_id=device_info.bus_id - ) - except OSError as e: - _log.exception('open %s', device_info) - if e.errno == _errno.EACCES: - raise - except Exception: - _log.exception('open %s', device_info) - - def close(self): - handle, self.handle = self.handle, None - if self in Device.instances: - Device.instances.remove(self) - return (handle and _base.close(handle)) - def __del__(self): self.close() + + +# Re-export from centurion.py — must be after Device class to avoid circular import +from .centurion import CenturionReceiver # noqa: E402,F401 +from .centurion import create_centurion_receiver # noqa: E402,F401 diff --git a/lib/logitech_receiver/diversion.py b/lib/logitech_receiver/diversion.py index 4dee89f3..e933f5ad 100644 --- a/lib/logitech_receiver/diversion.py +++ b/lib/logitech_receiver/diversion.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2020 Peter Patel-Schneider ## ## This program is free software; you can redistribute it and/or modify @@ -16,46 +14,49 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import ctypes as _ctypes -import os as _os -import os.path as _path -import platform as _platform -import sys as _sys -import time as _time +from __future__ import annotations -from logging import DEBUG as _DEBUG -from logging import INFO as _INFO -from logging import getLogger -from math import sqrt as _sqrt -from struct import unpack as _unpack +import ctypes +import logging +import math +import numbers +import os +import platform +import socket +import struct +import subprocess +import sys +import time +import typing + +from typing import Any +from typing import Dict +from typing import Tuple + +import gi +import psutil +import yaml + +from keysyms import keysymdef # There is no evdev on macOS or Windows. Diversion will not work without # it but other Solaar functionality is available. -if _platform.system() in ('Darwin', 'Windows'): +if platform.system() in ("Darwin", "Windows"): evdev = None else: import evdev -import dbus -import keysyms.keysymdef as _keysymdef -import psutil - -from yaml import add_representer as _yaml_add_representer -from yaml import dump_all as _yaml_dump_all -from yaml import safe_load_all as _yaml_safe_load_all - from .common import NamedInt -from .device import Device as _Device -from .hidpp20 import FEATURE as _F -from .special_keys import CONTROL as _CONTROL +from .hidpp20 import SupportedFeature +from .special_keys import CONTROL -import gi # isort:skip - -gi.require_version('Gdk', '3.0') # isort:skip +gi.require_version("Gdk", "3.0") # isort:skip from gi.repository import Gdk, GLib # NOQA: E402 # isort:skip -_log = getLogger(__name__) -del getLogger +if typing.TYPE_CHECKING: + from .base import HIDPPNotification + +logger = logging.getLogger(__name__) # # See docs/rules.md for documentation @@ -72,9 +73,10 @@ del getLogger # KeyPress action gets the current keyboard group using XkbGetState from libX11.so using ctypes definitions # under Wayland the keyboard group is None resulting in using the first keyboard group # KeyPress action translates keysyms to keycodes using the GDK keymap -# KeyPress, MouseScroll, and MouseClick actions use XTest (under X11) or uinput. +# KeyPress, MouseScroll, and MouseClick actions use uinput. # For uinput to work the user must have write access for /dev/uinput. -# To get this access run sudo setfacl -m u:${user}:rw /dev/uinput +# The Solaar udev rule should set this up +# Otherwise run sudo setfacl -m u:${user}:rw /dev/uinput # # Rule GUI keyname determination uses a local file generated # from http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h @@ -84,10 +86,9 @@ del getLogger # Setting up is complex because there are several systems that each provide partial facilities: # GDK - always available (when running with a window system) but only provides access to keymap # X11 - provides access to active process and process with window under mouse and current modifier keys -# Xtest extension to X11 - provides input simulation, partly works under Wayland -# Wayland - provides input simulation +# uinput and evdev - provides input simulation -XK_KEYS = _keysymdef.keysymdef +XK_KEYS: Dict[str, int] = keysymdef.key_symbols # Event codes - can't use Xlib.X codes because Xlib might not be available _KEY_RELEASE = 0 @@ -95,66 +96,94 @@ _KEY_PRESS = 1 _BUTTON_RELEASE = 2 _BUTTON_PRESS = 3 -CLICK, DEPRESS, RELEASE = 'click', 'depress', 'release' +CLICK, DEPRESS, RELEASE = "click", "depress", "release" gdisplay = Gdk.Display.get_default() # can be None if Solaar is run without a full window system gkeymap = Gdk.Keymap.get_for_display(gdisplay) if gdisplay else None -if _log.isEnabledFor(_INFO): - _log.info('GDK Keymap %sset up', '' if gkeymap else 'not ') +if logger.isEnabledFor(logging.INFO): + logger.info("GDK Keymap %sset up", "" if gkeymap else "not ") -wayland = _os.getenv('WAYLAND_DISPLAY') # is this Wayland? +wayland = os.getenv("WAYLAND_DISPLAY") # is this Wayland? if wayland: - _log.warn( - 'rules cannot access modifier keys in Wayland, ' - 'accessing process only works on GNOME with Solaar Gnome extension installed' + logger.warning( + "rules cannot access modifier keys in Wayland, " + "accessing process only works on GNOME with Solaar Gnome extension installed" ) try: - import Xlib _x11 = None # X11 might be available except Exception: _x11 = False # X11 is not available -xtest_available = True # Xtest might be available +# Globals xdisplay = None + + Xkbdisplay = None # xkb might be available +X11Lib = None + modifier_keycodes = [] XkbUseCoreKbd = 0x100 +NET_ACTIVE_WINDOW = None +NET_WM_PID = None +WM_CLASS = None + + +udevice = None + +key_down = None +key_up = None + +keys_down = [] +g_keys_down = 0 +m_keys_down = 0 +mr_key_down = False +thumb_wheel_displacement = 0 _dbus_interface = None -class XkbDisplay(_ctypes.Structure): - """ opaque struct """ +class XkbDisplay(ctypes.Structure): + """opaque struct""" -class XkbStateRec(_ctypes.Structure): - _fields_ = [('group', _ctypes.c_ubyte), ('locked_group', _ctypes.c_ubyte), ('base_group', _ctypes.c_ushort), - ('latched_group', _ctypes.c_ushort), ('mods', _ctypes.c_ubyte), ('base_mods', _ctypes.c_ubyte), - ('latched_mods', _ctypes.c_ubyte), ('locked_mods', _ctypes.c_ubyte), ('compat_state', _ctypes.c_ubyte), - ('grab_mods', _ctypes.c_ubyte), ('compat_grab_mods', _ctypes.c_ubyte), ('lookup_mods', _ctypes.c_ubyte), - ('compat_lookup_mods', _ctypes.c_ubyte), - ('ptr_buttons', _ctypes.c_ushort)] # something strange is happening here but it is not being used +class XkbStateRec(ctypes.Structure): + _fields_ = [ + ("group", ctypes.c_ubyte), + ("locked_group", ctypes.c_ubyte), + ("base_group", ctypes.c_ushort), + ("latched_group", ctypes.c_ushort), + ("mods", ctypes.c_ubyte), + ("base_mods", ctypes.c_ubyte), + ("latched_mods", ctypes.c_ubyte), + ("locked_mods", ctypes.c_ubyte), + ("compat_state", ctypes.c_ubyte), + ("grab_mods", ctypes.c_ubyte), + ("compat_grab_mods", ctypes.c_ubyte), + ("lookup_mods", ctypes.c_ubyte), + ("compat_lookup_mods", ctypes.c_ubyte), + ("ptr_buttons", ctypes.c_ushort), + ] # something strange is happening here but it is not being used def x11_setup(): - global _x11, xdisplay, modifier_keycodes, NET_ACTIVE_WINDOW, NET_WM_PID, WM_CLASS, xtest_available + global _x11, xdisplay, modifier_keycodes, NET_ACTIVE_WINDOW, NET_WM_PID, WM_CLASS if _x11 is not None: return _x11 try: from Xlib.display import Display + xdisplay = Display() modifier_keycodes = xdisplay.get_modifier_mapping() # there should be a way to do this in Gdk - NET_ACTIVE_WINDOW = xdisplay.intern_atom('_NET_ACTIVE_WINDOW') - NET_WM_PID = xdisplay.intern_atom('_NET_WM_PID') - WM_CLASS = xdisplay.intern_atom('WM_CLASS') + NET_ACTIVE_WINDOW = xdisplay.intern_atom("_NET_ACTIVE_WINDOW") + NET_WM_PID = xdisplay.intern_atom("_NET_WM_PID") + WM_CLASS = xdisplay.intern_atom("WM_CLASS") _x11 = True # X11 available - if _log.isEnabledFor(_INFO): - _log.info('X11 library loaded and display set up') + if logger.isEnabledFor(logging.INFO): + logger.info("X11 library loaded and display set up") except Exception: - _log.warn('X11 not available - some rule capabilities inoperable', exc_info=_sys.exc_info()) + logger.warning("X11 not available - some rule capabilities inoperable", exc_info=sys.exc_info()) _x11 = False - xtest_available = False return _x11 @@ -163,11 +192,16 @@ def gnome_dbus_interface_setup(): if _dbus_interface is not None: return _dbus_interface try: + import dbus + bus = dbus.SessionBus() - remote_object = bus.get_object('org.gnome.Shell', '/io/github/pwr_solaar/solaar') - _dbus_interface = dbus.Interface(remote_object, 'io.github.pwr_solaar.solaar') + remote_object = bus.get_object("org.gnome.Shell", "/io/github/pwr_solaar/solaar") + _dbus_interface = dbus.Interface(remote_object, "io.github.pwr_solaar.solaar") except dbus.exceptions.DBusException: - _log.warn('Solaar Gnome extension not installed - some rule capabilities inoperable', exc_info=_sys.exc_info()) + logger.warning( + "Solaar Gnome extension not installed - some rule capabilities inoperable", + exc_info=sys.exc_info(), + ) _dbus_interface = False return _dbus_interface @@ -177,68 +211,67 @@ def xkb_setup(): if Xkbdisplay is not None: return Xkbdisplay try: # set up to get keyboard state using ctypes interface to libx11 - X11Lib = _ctypes.cdll.LoadLibrary('libX11.so') - X11Lib.XOpenDisplay.restype = _ctypes.POINTER(XkbDisplay) - X11Lib.XkbGetState.argtypes = [_ctypes.POINTER(XkbDisplay), _ctypes.c_uint, _ctypes.POINTER(XkbStateRec)] + X11Lib = ctypes.cdll.LoadLibrary("libX11.so") + X11Lib.XOpenDisplay.restype = ctypes.POINTER(XkbDisplay) + X11Lib.XkbGetState.argtypes = [ctypes.POINTER(XkbDisplay), ctypes.c_uint, ctypes.POINTER(XkbStateRec)] Xkbdisplay = X11Lib.XOpenDisplay(None) - if _log.isEnabledFor(_INFO): - _log.info('XKB display set up') + if logger.isEnabledFor(logging.INFO): + logger.info("XKB display set up") except Exception: - _log.warn('XKB display not available - rules cannot access keyboard group', exc_info=_sys.exc_info()) + logger.warning("XKB display not available - rules cannot access keyboard group", exc_info=sys.exc_info()) Xkbdisplay = False return Xkbdisplay if evdev: buttons = { - 'unknown': (None, None), - 'left': (1, evdev.ecodes.ecodes['BTN_LEFT']), - 'middle': (2, evdev.ecodes.ecodes['BTN_MIDDLE']), - 'right': (3, evdev.ecodes.ecodes['BTN_RIGHT']), - 'scroll_up': (4, evdev.ecodes.ecodes['BTN_4']), - 'scroll_down': (5, evdev.ecodes.ecodes['BTN_5']), - 'scroll_left': (6, evdev.ecodes.ecodes['BTN_6']), - 'scroll_right': (7, evdev.ecodes.ecodes['BTN_7']), - 'button8': (8, evdev.ecodes.ecodes['BTN_8']), - 'button9': (9, evdev.ecodes.ecodes['BTN_9']), + "unknown": (None, None), + "left": (1, evdev.ecodes.ecodes["BTN_LEFT"]), + "middle": (2, evdev.ecodes.ecodes["BTN_MIDDLE"]), + "right": (3, evdev.ecodes.ecodes["BTN_RIGHT"]), + "scroll_up": (4, evdev.ecodes.ecodes["BTN_4"]), + "scroll_down": (5, evdev.ecodes.ecodes["BTN_5"]), + "scroll_left": (6, evdev.ecodes.ecodes["BTN_6"]), + "scroll_right": (7, evdev.ecodes.ecodes["BTN_7"]), + "button8": (8, evdev.ecodes.ecodes["BTN_8"]), + "button9": (9, evdev.ecodes.ecodes["BTN_9"]), + "back": (10, evdev.ecodes.ecodes["BTN_SIDE"]), + "forward": (11, evdev.ecodes.ecodes["BTN_EXTRA"]), } # uinput capability for keyboard keys, mouse buttons, and scrolling - key_events = [c for n, c in evdev.ecodes.ecodes.items() if n.startswith('KEY') and n != 'KEY_CNT'] - for (_, evcode) in buttons.values(): + key_events = [c for n, c in evdev.ecodes.ecodes.items() if n.startswith("KEY") and n != "KEY_CNT"] + for _, evcode in buttons.values(): if evcode: key_events.append(evcode) - devicecap = {evdev.ecodes.EV_KEY: key_events, evdev.ecodes.EV_REL: [evdev.ecodes.REL_WHEEL, evdev.ecodes.REL_HWHEEL]} + devicecap = { + evdev.ecodes.EV_KEY: key_events, + evdev.ecodes.EV_REL: [evdev.ecodes.REL_WHEEL, evdev.ecodes.REL_HWHEEL], + } else: # Just mock these since they won't be useful without evdev anyway buttons = {} key_events = [] devicecap = {} -udevice = None - def setup_uinput(): global udevice if udevice is not None: return udevice try: - udevice = evdev.uinput.UInput(events=devicecap, name='solaar-keyboard') - if _log.isEnabledFor(_INFO): - _log.info('uinput device set up') + udevice = evdev.uinput.UInput(events=devicecap, name="solaar-keyboard") + if logger.isEnabledFor(logging.INFO): + logger.info("uinput device set up") return True except Exception as e: - _log.warn('cannot create uinput device: %s', e) - - -if wayland: # Wayland can't use xtest so may as well set up uinput now - setup_uinput() + logger.warning("cannot create uinput device: %s", e) def kbdgroup(): if xkb_setup(): state = XkbStateRec() - X11Lib.XkbGetState(Xkbdisplay, XkbUseCoreKbd, _ctypes.pointer(state)) + X11Lib.XkbGetState(Xkbdisplay, XkbUseCoreKbd, ctypes.pointer(state)) return state.group else: return None @@ -252,57 +285,35 @@ def modifier_code(keycode): return m -key_down = None -key_up = None - - -def signed(bytes): - return int.from_bytes(bytes, 'big', signed=True) +def signed(bytes_: bytes) -> int: + return int.from_bytes(bytes_, "big", signed=True) def xy_direction(_x, _y): # normalize x and y - m = _sqrt((_x * _x) + (_y * _y)) + m = math.sqrt((_x * _x) + (_y * _y)) if m == 0: - return 'noop' + return "noop" x = round(_x / m) y = round(_y / m) if x < 0 and y < 0: - return 'Mouse Up-left' - elif x > 0 and y < 0: - return 'Mouse Up-right' - elif x < 0 and y > 0: - return 'Mouse Down-left' + return "Mouse Up-left" + elif x > 0 > y: + return "Mouse Up-right" + elif x < 0 < y: + return "Mouse Down-left" elif x > 0 and y > 0: - return 'Mouse Down-right' + return "Mouse Down-right" elif x > 0: - return 'Mouse Right' + return "Mouse Right" elif x < 0: - return 'Mouse Left' + return "Mouse Left" elif y > 0: - return 'Mouse Down' + return "Mouse Down" elif y < 0: - return 'Mouse Up' + return "Mouse Up" else: - return 'noop' - - -def simulate_xtest(code, event): - global xtest_available - if x11_setup() and xtest_available: - try: - event = ( - Xlib.X.KeyPress if event == _KEY_PRESS else Xlib.X.KeyRelease if event == _KEY_RELEASE else - Xlib.X.ButtonPress if event == _BUTTON_PRESS else Xlib.X.ButtonRelease if event == _BUTTON_RELEASE else None - ) - Xlib.ext.xtest.fake_input(xdisplay, event, code) - xdisplay.sync() - if _log.isEnabledFor(_DEBUG): - _log.debug('xtest simulated input %s %s %s', xdisplay, event, code) - return True - except Exception as e: - xtest_available = False - _log.warn('xtest fake input failed: %s', e) + return "noop" def simulate_uinput(what, code, arg): @@ -311,37 +322,18 @@ def simulate_uinput(what, code, arg): try: udevice.write(what, code, arg) udevice.syn() - if _log.isEnabledFor(_DEBUG): - _log.debug('uinput simulated input %s %s %s', what, code, arg) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("uinput simulated input %s %s %s", what, code, arg) return True except Exception as e: udevice = None - _log.warn('uinput write failed: %s', e) + logger.warning("uinput write failed: %s", e) def simulate_key(code, event): # X11 keycode but Solaar event code - if not wayland and simulate_xtest(code, event): + if evdev and simulate_uinput(evdev.ecodes.EV_KEY, code - 8, event): return True - if simulate_uinput(evdev.ecodes.EV_KEY, code - 8, event): - return True - _log.warn('no way to simulate key input') - - -def click_xtest(button, count): - if isinstance(count, int): - for _ in range(count): - if not simulate_xtest(button[0], _BUTTON_PRESS): - return False - if not simulate_xtest(button[0], _BUTTON_RELEASE): - return False - else: - if count != RELEASE: - if not simulate_xtest(button[0], _BUTTON_PRESS): - return False - if count != DEPRESS: - if not simulate_xtest(button[0], _BUTTON_RELEASE): - return False - return True + logger.warning("no way to simulate key input") def click_uinput(button, count): @@ -362,23 +354,13 @@ def click_uinput(button, count): def click(button, count): - if not wayland and click_xtest(button, count): - return True if click_uinput(button, count): return True - _log.warn('no way to simulate mouse click') + logger.warning("no way to simulate mouse click") return False def simulate_scroll(dx, dy): - if not wayland and xtest_available: - success = True - if dx: - success = click_xtest(buttons['scroll_right' if dx > 0 else 'scroll_left'], count=abs(dx)) - if dy and success: - success = click_xtest(buttons['scroll_up' if dy > 0 else 'scroll_down'], count=abs(dy)) - if success: - return True if setup_uinput(): success = True if dx: @@ -387,12 +369,12 @@ def simulate_scroll(dx, dy): success = simulate_uinput(evdev.ecodes.EV_REL, evdev.ecodes.REL_WHEEL, dy) if success: return True - _log.warn('no way to simulate scrolling') + logger.warning("no way to simulate scrolling") def thumb_wheel_up(f, r, d, a): global thumb_wheel_displacement - if f != _F.THUMB_WHEEL or r != 0: + if f != SupportedFeature.THUMB_WHEEL or r != 0: return False if a is None: return signed(d[0:2]) < 0 and signed(d[0:2]) @@ -405,7 +387,7 @@ def thumb_wheel_up(f, r, d, a): def thumb_wheel_down(f, r, d, a): global thumb_wheel_displacement - if f != _F.THUMB_WHEEL or r != 0: + if f != SupportedFeature.THUMB_WHEEL or r != 0: return False if a is None: return signed(d[0:2]) > 0 and signed(d[0:2]) @@ -416,48 +398,61 @@ def thumb_wheel_down(f, r, d, a): return False -def charging(f, r, d, a): - if (f == _F.BATTERY_STATUS and r == 0 and 1 <= d[2] <= 4) or \ - (f == _F.BATTERY_VOLTAGE and r == 0 and d[2] & (1 << 7)) or \ - (f == _F.UNIFIED_BATTERY and r == 0 and 1 <= d[2] <= 3): +def charging(f, r, d, _a): + if ( + (f == SupportedFeature.BATTERY_STATUS and r == 0 and 1 <= d[2] <= 4) + or (f == SupportedFeature.BATTERY_VOLTAGE and r == 0 and d[2] & (1 << 7)) + or (f == SupportedFeature.UNIFIED_BATTERY and r == 0 and 1 <= d[2] <= 3) + ): return 1 else: return False TESTS = { - 'crown_right': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[1] < 128 and d[1], False], - 'crown_left': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[1] >= 128 and 256 - d[1], False], - 'crown_right_ratchet': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[2] < 128 and d[2], False], - 'crown_left_ratchet': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[2] >= 128 and 256 - d[2], False], - 'crown_tap': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[5] == 0x01 and d[5], False], - 'crown_start_press': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[6] == 0x01 and d[6], False], - 'crown_end_press': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[6] == 0x05 and d[6], False], - 'crown_pressed': [lambda f, r, d, a: f == _F.CROWN and r == 0 and d[6] >= 0x01 and d[6] <= 0x04 and d[6], False], - 'thumb_wheel_up': [thumb_wheel_up, True], - 'thumb_wheel_down': [thumb_wheel_down, True], - 'lowres_wheel_up': [lambda f, r, d, a: f == _F.LOWRES_WHEEL and r == 0 and signed(d[0:1]) > 0 and signed(d[0:1]), False], - 'lowres_wheel_down': [lambda f, r, d, a: f == _F.LOWRES_WHEEL and r == 0 and signed(d[0:1]) < 0 and signed(d[0:1]), False], - 'hires_wheel_up': [lambda f, r, d, a: f == _F.HIRES_WHEEL and r == 0 and signed(d[1:3]) > 0 and signed(d[1:3]), False], - 'hires_wheel_down': [lambda f, r, d, a: f == _F.HIRES_WHEEL and r == 0 and signed(d[1:3]) < 0 and signed(d[1:3]), False], - 'charging': [charging, False], - 'False': [lambda f, r, d, a: False, False], - 'True': [lambda f, r, d, a: True, False], + "crown_right": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and d[1] < 128 and d[1], False], + "crown_left": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and d[1] >= 128 and 256 - d[1], False], + "crown_right_ratchet": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and d[2] < 128 and d[2], False], + "crown_left_ratchet": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and d[2] >= 128 and 256 - d[2], False], + "crown_tap": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and d[5] == 0x01 and d[5], False], + "crown_start_press": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and d[6] == 0x01 and d[6], False], + "crown_end_press": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and d[6] == 0x05 and d[6], False], + "crown_pressed": [lambda f, r, d, a: f == SupportedFeature.CROWN and r == 0 and 0x01 <= d[6] <= 0x04 and d[6], False], + "thumb_wheel_up": [thumb_wheel_up, True], + "thumb_wheel_down": [thumb_wheel_down, True], + "lowres_wheel_up": [ + lambda f, r, d, a: f == SupportedFeature.LOWRES_WHEEL and r == 0 and signed(d[0:1]) > 0 and signed(d[0:1]), + False, + ], + "lowres_wheel_down": [ + lambda f, r, d, a: f == SupportedFeature.LOWRES_WHEEL and r == 0 and signed(d[0:1]) < 0 and signed(d[0:1]), + False, + ], + "hires_wheel_up": [ + lambda f, r, d, a: f == SupportedFeature.HIRES_WHEEL and r == 0 and signed(d[1:3]) > 0 and signed(d[1:3]), + False, + ], + "hires_wheel_down": [ + lambda f, r, d, a: f == SupportedFeature.HIRES_WHEEL and r == 0 and signed(d[1:3]) < 0 and signed(d[1:3]), + False, + ], + "charging": [charging, False], + "False": [lambda f, r, d, a: False, False], + "True": [lambda f, r, d, a: True, False], } MOUSE_GESTURE_TESTS = { - 'mouse-down': ['Mouse Down'], - 'mouse-up': ['Mouse Up'], - 'mouse-left': ['Mouse Left'], - 'mouse-right': ['Mouse Right'], - 'mouse-noop': [], + "mouse-down": ["Mouse Down"], + "mouse-up": ["Mouse Up"], + "mouse-left": ["Mouse Left"], + "mouse-right": ["Mouse Right"], + "mouse-noop": [], } -COMPONENTS = {} +# COMPONENTS = {} class RuleComponent: - def compile(self, c): if isinstance(c, RuleComponent): return c @@ -465,56 +460,57 @@ class RuleComponent: k, v = next(iter(c.items())) if k in COMPONENTS: return COMPONENTS[k](v) - _log.warn('illegal component in rule: %s', c) + logger.warning("illegal component in rule: %s", c) return Condition() -class Rule(RuleComponent): +def _evaluate(components, feature, notification: HIDPPNotification, device, result) -> Any: + res = True + for component in components: + res = component.evaluate(feature, notification, device, result) + if not isinstance(component, Action) and res is None: + return None + if isinstance(component, Condition) and not res: + return res + return res + +class Rule(RuleComponent): def __init__(self, args, source=None, warn=True): self.components = [self.compile(a) for a in args] self.source = source def __str__(self): - source = '(' + self.source + ')' if self.source else '' - return 'Rule%s[%s]' % (source, ', '.join([c.__str__() for c in self.components])) + source = f"({self.source})" if self.source else "" + return f"Rule{source}[{', '.join([c.__str__() for c in self.components])}]" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate rule: %s', self) - result = True - for component in self.components: - result = component.evaluate(feature, notification, device, status, result) - if not isinstance(component, Action) and result is None: - return None - if isinstance(component, Condition) and not result: - return result - return result + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate rule: %s", self) + return _evaluate(self.components, feature, notification, device, True) - def once(self, feature, notification, device, status, last_result): - self.evaluate(feature, notification, device, status, last_result) + def once(self, feature, notification: HIDPPNotification, device, last_result): + self.evaluate(feature, notification, device, last_result) return False def data(self): - return {'Rule': [c.data() for c in self.components]} + return {"Rule": [c.data() for c in self.components]} class Condition(RuleComponent): - def __init__(self, *args): pass def __str__(self): - return 'CONDITION' + return "CONDITION" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) return False class Not(Condition): - def __init__(self, op, warn=True): if isinstance(op, list) and len(op) == 1: op = op[0] @@ -522,32 +518,31 @@ class Not(Condition): self.component = self.compile(op) def __str__(self): - return 'Not: ' + str(self.component) + return f"Not: {str(self.component)}" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) - result = self.component.evaluate(feature, notification, device, status, last_result) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) + result = self.component.evaluate(feature, notification, device, last_result) return None if result is None else not result def data(self): - return {'Not': self.component.data()} + return {"Not": self.component.data()} class Or(Condition): - def __init__(self, args, warn=True): self.components = [self.compile(a) for a in args] def __str__(self): - return 'Or: [' + ', '.join(str(c) for c in self.components) + ']' + return "Or: [" + ", ".join(str(c) for c in self.components) + "]" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) result = False for component in self.components: - result = component.evaluate(feature, notification, device, status, last_result) + result = component.evaluate(feature, notification, device, last_result) if not isinstance(component, Action) and result is None: return None if isinstance(component, Condition) and result: @@ -555,31 +550,23 @@ class Or(Condition): return result def data(self): - return {'Or': [c.data() for c in self.components]} + return {"Or": [c.data() for c in self.components]} class And(Condition): - def __init__(self, args, warn=True): self.components = [self.compile(a) for a in args] def __str__(self): - return 'And: [' + ', '.join(str(c) for c in self.components) + ']' + return "And: [" + ", ".join(str(c) for c in self.components) + "]" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) - result = True - for component in self.components: - result = component.evaluate(feature, notification, device, status, last_result) - if not isinstance(component, Action) and result is None: - return None - if isinstance(component, Condition) and not result: - return result - return result + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) + return _evaluate(self.components, feature, notification, device, last_result) def data(self): - return {'And': [c.data() for c in self.components]} + return {"And": [c.data() for c in self.components]} def x11_focus_prog(): @@ -594,10 +581,10 @@ def x11_focus_prog(): break window = window.query_tree().parent try: - name = psutil.Process(pid.value[0]).name() if pid else '' + name = psutil.Process(pid.value[0]).name() if pid else "" except Exception: - name = '' - return (wm_class[0], wm_class[1], name) if wm_class else (name, ) + name = "" + return (wm_class[0], wm_class[1], name) if wm_class else (name,) def x11_pointer_prog(): @@ -610,45 +597,45 @@ def x11_pointer_prog(): wm_class = child.get_wm_class() if wm_class: break - name = psutil.Process(pid.value[0]).name() if pid else '' - return (wm_class[0], wm_class[1], name) if wm_class else (name, ) + name = psutil.Process(pid.value[0]).name() if pid else "" + return (wm_class[0], wm_class[1], name) if wm_class else (name,) def gnome_dbus_focus_prog(): if not gnome_dbus_interface_setup(): return None wm_class = _dbus_interface.ActiveWindow() - return (wm_class, ) if wm_class else None + return (wm_class,) if wm_class else None def gnome_dbus_pointer_prog(): if not gnome_dbus_interface_setup(): return None wm_class = _dbus_interface.PointerOverWindow() - return (wm_class, ) if wm_class else None + return (wm_class,) if wm_class else None class Process(Condition): - def __init__(self, process, warn=True): self.process = process if (not wayland and not x11_setup()) or (wayland and not gnome_dbus_interface_setup()): if warn: - _log.warn( - 'rules can only access active process in X11 or in Wayland under GNOME with Solaar Gnome extension - %s', - self + logger.warning( + "rules can only access active process in X11 or in Wayland under GNOME with Solaar Gnome " + "extension - %s", + self, ) if not isinstance(process, str): if warn: - _log.warn('rule Process argument not a string: %s', process) + logger.warning("rule Process argument not a string: %s", process) self.process = str(process) def __str__(self): - return 'Process: ' + str(self.process) + return f"Process: {str(self.process)}" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) if not isinstance(self.process, str): return False focus = x11_focus_prog() if not wayland else gnome_dbus_focus_prog() @@ -656,30 +643,30 @@ class Process(Condition): return result def data(self): - return {'Process': str(self.process)} + return {"Process": str(self.process)} class MouseProcess(Condition): - def __init__(self, process, warn=True): self.process = process if (not wayland and not x11_setup()) or (wayland and not gnome_dbus_interface_setup()): if warn: - _log.warn( - 'rules cannot access active mouse process ' - 'in X11 or in Wayland under GNOME with Solaar Extension for GNOME - %s', self + logger.warning( + "rules cannot access active mouse process " + "in X11 or in Wayland under GNOME with Solaar Extension for GNOME - %s", + self, ) if not isinstance(process, str): if warn: - _log.warn('rule MouseProcess argument not a string: %s', process) + logger.warning("rule MouseProcess argument not a string: %s", process) self.process = str(process) def __str__(self): - return 'MouseProcess: ' + str(self.process) + return f"MouseProcess: {str(self.process)}" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) if not isinstance(self.process, str): return False pointer_focus = x11_pointer_prog() if not wayland else gnome_dbus_pointer_prog() @@ -687,103 +674,100 @@ class MouseProcess(Condition): return result def data(self): - return {'MouseProcess': str(self.process)} + return {"MouseProcess": str(self.process)} class Feature(Condition): - - def __init__(self, feature, warn=True): - if not (isinstance(feature, str) and feature in _F): - if warn: - _log.warn('rule Feature argument not name of a feature: %s', feature) + def __init__(self, feature: str, warn: bool = True): + try: + self.feature = SupportedFeature[feature.replace(" ", "_")] + except KeyError: self.feature = None - self.feature = _F[feature] + if warn: + logger.warning("rule Feature argument not name of a feature: %s", feature) def __str__(self): - return 'Feature: ' + str(self.feature) + return f"Feature: {str(self.feature)}" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) return feature == self.feature def data(self): - return {'Feature': str(self.feature)} + return {"Feature": str(self.feature)} class Report(Condition): - def __init__(self, report, warn=True): if not (isinstance(report, int)): if warn: - _log.warn('rule Report argument not an integer: %s', report) + logger.warning("rule Report argument not an integer: %s", report) self.report = -1 else: self.report = report def __str__(self): - return 'Report: ' + str(self.report) + return f"Report: {str(self.report)}" - def evaluate(self, report, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) + def evaluate(self, report, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) return (notification.address >> 4) == self.report def data(self): - return {'Report': self.report} + return {"Report": self.report} # Setting(device, setting, [key], value...) class Setting(Condition): - def __init__(self, args, warn=True): if not (isinstance(args, list) and len(args) > 2): if warn: - _log.warn('rule Setting argument not list with minimum length 3: %s', args) + logger.warning("rule Setting argument not list with minimum length 3: %s", args) self.args = [] else: self.args = args def __str__(self): - return 'Setting: ' + ' '.join([str(a) for a in self.args]) + return "Setting: " + " ".join([str(a) for a in self.args]) - def evaluate(self, report, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) + def evaluate(self, report, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) if len(self.args) < 3: return None - dev = _Device.find(self.args[0]) if self.args[0] is not None else device + dev = device.find(self.args[0]) if self.args[0] is not None else device if dev is None: - _log.warn('Setting condition: device %s is not known', self.args[0]) + logger.warning("Setting condition: device %s is not known", self.args[0]) return False setting = next((s for s in dev.settings if s.name == self.args[1]), None) if setting is None: - _log.warn('Setting condition: setting %s is not the name of a setting for %s', self.args[1], dev.name) + logger.warning("Setting condition: setting %s is not the name of a setting for %s", self.args[1], dev.name) return None # should the value argument be checked to be sure it is acceptable?? needs to be careful about boolean toggle # TODO add compare methods for more validators try: result = setting.compare(self.args[2:], setting.read()) except Exception as e: - _log.warn('Setting condition: error when checking setting %s: %s', self.args, e) + logger.warning("Setting condition: error when checking setting %s: %s", self.args, e) result = False return result def data(self): - return {'Setting': self.args[:]} + return {"Setting": self.args[:]} MODIFIERS = { - 'Shift': int(Gdk.ModifierType.SHIFT_MASK), - 'Control': int(Gdk.ModifierType.CONTROL_MASK), - 'Alt': int(Gdk.ModifierType.MOD1_MASK), - 'Super': int(Gdk.ModifierType.MOD4_MASK) + "Shift": int(Gdk.ModifierType.SHIFT_MASK), + "Control": int(Gdk.ModifierType.CONTROL_MASK), + "Alt": int(Gdk.ModifierType.MOD1_MASK), + "Super": int(Gdk.ModifierType.MOD4_MASK), } -MODIFIER_MASK = MODIFIERS['Shift'] + MODIFIERS['Control'] + MODIFIERS['Alt'] + MODIFIERS['Super'] +MODIFIER_MASK = MODIFIERS["Shift"] + MODIFIERS["Control"] + MODIFIERS["Alt"] + MODIFIERS["Super"] class Modifiers(Condition): - def __init__(self, modifiers, warn=True): modifiers = [modifiers] if isinstance(modifiers, str) else modifiers self.desired = 0 @@ -794,28 +778,28 @@ class Modifiers(Condition): self.modifiers.append(k) else: if warn: - _log.warn('unknown rule Modifier value: %s', k) + logger.warning("unknown rule Modifier value: %s", k) def __str__(self): - return 'Modifiers: ' + str(self.desired) + return f"Modifiers: {str(self.desired)}" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) if gkeymap: current = gkeymap.get_modifier_state() # get the current keyboard modifier return self.desired == (current & MODIFIER_MASK) else: - _log.warn('no keymap so cannot determine modifier keys') + logger.warning("no keymap so cannot determine modifier keys") return False def data(self): - return {'Modifiers': [str(m) for m in self.modifiers]} + return {"Modifiers": [str(m) for m in self.modifiers]} class Key(Condition): - DOWN = 'pressed' - UP = 'released' + DOWN = "pressed" + UP = "released" def __init__(self, args, warn=True): default_key = 0 @@ -825,48 +809,50 @@ class Key(Condition): if not args or not isinstance(args, (list, str)): if warn: - _log.warn('rule Key arguments unknown: %s' % args) + logger.warning(f"rule Key arguments unknown: {args}") key = default_key action = default_action elif isinstance(args, str): - _log.debug('rule Key assuming action "%s" for "%s"' % (default_action, args)) + logger.debug(f'rule Key assuming action "{default_action}" for "{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)) + logger.debug(f'rule Key assuming action "{default_action}" for "{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] + if isinstance(key, str) and key in CONTROL: + self.key = CONTROL[key] + elif isinstance(key, str) and key.startswith("unknown:"): + logger.info(f"rule Key key name currently unknown: {key}") + self.key = CONTROL[int(key[-4:], 16)] else: if warn: - _log.warn('rule Key key name not name of a Logitech key: %s' % key) + logger.warning(f"rule Key key name not name of a Logitech key: {key}") self.key = default_key if isinstance(action, str) and action in (self.DOWN, self.UP): self.action = action else: if warn: - _log.warn('rule Key action unknown: %s, assuming %s' % (action, default_action)) + logger.warning(f"rule Key action unknown: {action}, assuming {default_action}") self.action = default_action def __str__(self): - return 'Key: %s (%s)' % ((str(self.key) if self.key else 'None'), self.action) + return f"Key: {str(self.key) if self.key else 'None'} ({self.action})" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) 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), self.action]} + return {"Key": [str(self.key), self.action]} class KeyIsDown(Condition): - def __init__(self, args, warn=True): default_key = 0 @@ -874,59 +860,57 @@ class KeyIsDown(Condition): if not args or not isinstance(args, str): if warn: - _log.warn('rule KeyDown arguments unknown: %s' % args) + logger.warning(f"rule KeyDown arguments unknown: {args}") key = default_key elif isinstance(args, str): key = args - if isinstance(key, str) and key in _CONTROL: - self.key = _CONTROL[key] + if isinstance(key, str) and key in CONTROL: + self.key = CONTROL[key] else: if warn: - _log.warn('rule Key key name not name of a Logitech key: %s' % key) + logger.warning(f"rule Key key name not name of a Logitech key: {key}") self.key = default_key def __str__(self): - return 'KeyIsDown: %s' % (str(self.key) if self.key else 'None') + return f"KeyIsDown: {str(self.key) if self.key else 'None'}" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) return key_is_down(self.key) def data(self): - return {'KeyIsDown': str(self.key)} + return {"KeyIsDown": str(self.key)} def bit_test(start, end, bits): - return lambda f, r, d: int.from_bytes(d[start:end], byteorder='big', signed=True) & bits + return lambda f, r, d: int.from_bytes(d[start:end], byteorder="big", signed=True) & bits def range_test(start, end, min, max): - - def range_test_helper(f, r, d): - value = int.from_bytes(d[start:end], byteorder='big', signed=True) + def range_test_helper(_f, _r, d): + value = int.from_bytes(d[start:end], byteorder="big", signed=True) return min <= value <= max and (value if value else True) return range_test_helper class Test(Condition): - def __init__(self, test, warn=True): - self.test = '' + self.test = "" self.parameter = None if isinstance(test, str): test = [test] if isinstance(test, list) and all(isinstance(t, int) for t in test): if warn: - _log.warn('Test rules consisting of numbers are deprecated, converting to a TestBytes condition') + logger.warning("Test rules consisting of numbers are deprecated, converting to a TestBytes condition") self.__class__ = TestBytes self.__init__(test, warn=warn) elif isinstance(test, list): if test[0] in MOUSE_GESTURE_TESTS: if warn: - _log.warn('mouse movement test %s deprecated, converting to a MouseGesture', test) + logger.warning("mouse movement test %s deprecated, converting to a MouseGesture", test) self.__class__ = MouseGesture self.__init__(MOUSE_GESTURE_TESTS[0][test], warn=warn) elif test[0] in TESTS: @@ -935,79 +919,88 @@ class Test(Condition): self.parameter = test[1] if len(test) > 1 else None else: if warn: - _log.warn('rule Test name not name of a test: %s', test) - self.test = 'False' - self.function = TESTS['False'][0] + logger.warning("rule Test name not name of a test: %s", test) + self.test = "False" + self.function = TESTS["False"][0] else: if warn: - _log.warn('rule Test argument not valid %s', test) + logger.warning("rule Test argument not valid %s", test) def __str__(self): - return 'Test: ' + str(self.test) + return f"Test: {str(self.test)}" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) return self.function(feature, notification.address, notification.data, self.parameter) def data(self): - return {'Test': ([self.test, self.parameter] if self.parameter is not None else [self.test])} + return {"Test": ([self.test, self.parameter] if self.parameter is not None else [self.test])} class TestBytes(Condition): - def __init__(self, test, warn=True): self.test = test if ( - isinstance(test, list) and 2 < len(test) <= 4 and all(isinstance(t, int) for t in test) and test[0] >= 0 - and test[0] <= 16 and test[1] >= 0 and test[1] <= 16 and test[0] < test[1] + isinstance(test, list) + and 2 < len(test) <= 4 + and all(isinstance(t, int) for t in test) + and 0 <= test[0] <= 16 + and 0 <= test[1] <= 16 + and test[0] < test[1] ): self.function = bit_test(*test) if len(test) == 3 else range_test(*test) else: if warn: - _log.warn('rule TestBytes argument not valid %s', test) + logger.warning("rule TestBytes argument not valid %s", test) def __str__(self): - return 'TestBytes: ' + str(self.test) + return f"TestBytes: {str(self.test)}" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) return self.function(feature, notification.address, notification.data) def data(self): - return {'TestBytes': self.test[:]} + return {"TestBytes": self.test[:]} class MouseGesture(Condition): MOVEMENTS = [ - 'Mouse Up', 'Mouse Down', 'Mouse Left', 'Mouse Right', 'Mouse Up-left', 'Mouse Up-right', 'Mouse Down-left', - 'Mouse Down-right' + "Mouse Up", + "Mouse Down", + "Mouse Left", + "Mouse Right", + "Mouse Up-left", + "Mouse Up-right", + "Mouse Down-left", + "Mouse Down-right", ] def __init__(self, movements, warn=True): if isinstance(movements, str): movements = [movements] for x in movements: - if x not in self.MOVEMENTS and x not in _CONTROL: + if x not in self.MOVEMENTS and x not in CONTROL: if warn: - _log.warn('rule Mouse Gesture argument not direction or name of a Logitech key: %s', x) + logger.warning("rule Mouse Gesture argument not direction or name of a Logitech key: %s", x) self.movements = movements def __str__(self): - return 'MouseGesture: ' + ' '.join(self.movements) + return "MouseGesture: " + " ".join(self.movements) - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) - if feature == _F.MOUSE_GESTURE: + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) + if feature == SupportedFeature.MOUSE_GESTURE: d = notification.data - data = _unpack('!' + (int(len(d) / 2) * 'h'), d) + data = struct.unpack("!" + (int(len(d) / 2) * "h"), d) data_offset = 1 movement_offset = 0 if self.movements and self.movements[0] not in self.MOVEMENTS: # matching against initiating key movement_offset = 1 - if self.movements[0] != str(_CONTROL[data[0]]): + if self.movements[0] != str(CONTROL[data[0]]): return False for m in self.movements[movement_offset:]: if data_offset >= len(data): @@ -1018,104 +1011,129 @@ class MouseGesture(Condition): return False data_offset += 3 elif data[data_offset] == 1: - if m != str(_CONTROL[data[data_offset + 1]]): + if m != str(CONTROL[data[data_offset + 1]]): return False data_offset += 2 return data_offset == len(data) return False def data(self): - return {'MouseGesture': [str(m) for m in self.movements]} + return {"MouseGesture": [str(m) for m in self.movements]} class Active(Condition): - def __init__(self, devID, warn=True): if not (isinstance(devID, str)): if warn: - _log.warn('rule Active argument not a string: %s', devID) - self.devID = '' + logger.warning("rule Active argument not a string: %s", devID) + self.devID = "" self.devID = devID def __str__(self): - return 'Active: ' + str(self.devID) + return f"Active: {str(self.devID)}" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) - dev = _Device.find(self.devID) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) + dev = device.find(self.devID) return bool(dev and dev.ping()) def data(self): - return {'Active': self.devID} + return {"Active": self.devID} class Device(Condition): - def __init__(self, devID, warn=True): if not (isinstance(devID, str)): if warn: - _log.warn('rule Device argument not a string: %s', devID) - self.devID = '' + logger.warning("rule Device argument not a string: %s", devID) + self.devID = "" self.devID = devID def __str__(self): - return 'Device: ' + str(self.devID) + return f"Device: {str(self.devID)}" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) - return device.unitId == self.devID or device.serial == self.devID + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) + return ( + device.unitId == self.devID + or device.serial == self.devID + or device.codename == self.devID + or device.name == self.devID + ) def data(self): - return {'Device': self.devID} + return {"Device": self.devID} class Host(Condition): - def __init__(self, host, warn=True): if not (isinstance(host, str)): if warn: - _log.warn('rule Host Name argument not a string: %s', host) - self.host = '' + logger.warning("rule Host Name argument not a string: %s", host) + self.host = "" self.host = host def __str__(self): - return 'Host: ' + str(self.host) + return f"Host: {str(self.host)}" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluate condition: %s', self) - import socket + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluate condition: %s", self) hostname = socket.getfqdn() return hostname.startswith(self.host) def data(self): - return {'Host': self.host} + return {"Host": self.host} class Action(RuleComponent): - def __init__(self, *args): pass - def evaluate(self, feature, notification, device, status, last_result): + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): return None -class KeyPress(Action): +def keysym_to_keycode(keysym, _modifiers) -> Tuple[int, int]: # maybe should take shift into account + """Reverse the keycode to keysym mapping. + Warning: + This is an attempt to reverse the keycode to keysym mappping in XKB. + It may not be completely general. + """ + group = kbdgroup() or 0 + keycodes = gkeymap.get_entries_for_keyval(keysym) + (keycode, level) = (None, None) + for k in keycodes.keys: # mappings that have the correct group + if group == k.group and k.keycode < 256 and (level is None or k.level < level): + keycode = k.keycode + level = k.level + if keycode or group == 0: + return keycode, level + + for k in keycodes.keys: # mappings for group 0 where keycode only has group 0 mappings + if 0 == k.group and k.keycode < 256 and (level is None or k.level < level): + (a, m, vs) = gkeymap.get_entries_for_keycode(k.keycode) + if a and all(mk.group == 0 for mk in m): + keycode = k.keycode + level = k.level + return keycode, level + + +class KeyPress(Action): def __init__(self, args, warn=True): self.key_names, self.action = self.regularize_args(args) if not isinstance(self.key_names, list): if warn: - _log.warn('rule KeyPress keys not key names %s', self.keys_names) + logger.warning("rule KeyPress keys not key names %s", self.keys_names) self.key_symbols = [] else: self.key_symbols = [XK_KEYS.get(k, None) for k in self.key_names] if not all(self.key_symbols): if warn: - _log.warn('rule KeyPress keys not key names %s', self.key_names) + logger.warning("rule KeyPress keys not key names %s", self.key_names) self.key_symbols = [] def regularize_args(self, args): @@ -1128,27 +1146,8 @@ class KeyPress(Action): action = args[1] return keys, action - # WARNING: This is an attempt to reverse the keycode to keysym mappping in XKB. It may not be completely general. - def keysym_to_keycode(self, keysym, modifiers): # maybe should take shift into account - group = kbdgroup() or 0 - keycodes = gkeymap.get_entries_for_keyval(keysym) - (keycode, level) = (None, None) - for k in keycodes.keys: # mappings that have the correct group - if group == k.group and k.keycode < 256 and (level is None or k.level < level): - keycode = k.keycode - level = k.level - if keycode or group == 0: - return (keycode, level) - for k in keycodes.keys: # mappings for group 0 where keycode only has group 0 mappings - if 0 == k.group and k.keycode < 256 and (level is None or k.level < level): - (a, m, vs) = gkeymap.get_entries_for_keycode(k.keycode) - if a and all(mk.group == 0 for mk in m): - keycode = k.keycode - level = k.level - return (keycode, level) - def __str__(self): - return 'KeyPress: ' + ' '.join(self.key_names) + ' ' + self.action + return "KeyPress: " + " ".join(self.key_names) + " " + self.action def needed(self, k, modifiers): code = modifier_code(k) @@ -1156,91 +1155,92 @@ class KeyPress(Action): def mods(self, level, modifiers, direction): if level == 2 or level == 3: - (sk, _) = self.keysym_to_keycode(XK_KEYS.get('ISO_Level3_Shift', None), modifiers) + (sk, _) = keysym_to_keycode(XK_KEYS.get("ISO_Level3_Shift", None), modifiers) if sk and self.needed(sk, modifiers): simulate_key(sk, direction) if level == 1 or level == 3: - (sk, _) = self.keysym_to_keycode(XK_KEYS.get('Shift_L', None), modifiers) + (sk, _) = keysym_to_keycode(XK_KEYS.get("Shift_L", None), modifiers) if sk and self.needed(sk, modifiers): simulate_key(sk, direction) - def keyDown(self, keysyms, modifiers): - for k in keysyms: - (keycode, level) = self.keysym_to_keycode(k, modifiers) + def keyDown(self, keysyms_, modifiers): + for k in keysyms_: + (keycode, level) = keysym_to_keycode(k, modifiers) if keycode is None: - _log.warn('rule KeyPress key symbol not currently available %s', self) + logger.warning("rule KeyPress key symbol not currently available %s", self) elif self.action != CLICK or self.needed(keycode, modifiers): # only check needed when clicking self.mods(level, modifiers, _KEY_PRESS) simulate_key(keycode, _KEY_PRESS) - def keyUp(self, keysyms, modifiers): - for k in keysyms: - (keycode, level) = self.keysym_to_keycode(k, modifiers) + def keyUp(self, keysyms_, modifiers): + for k in keysyms_: + (keycode, level) = keysym_to_keycode(k, modifiers) if keycode and (self.action != CLICK or self.needed(keycode, modifiers)): # only check needed when clicking simulate_key(keycode, _KEY_RELEASE) self.mods(level, modifiers, _KEY_RELEASE) - def evaluate(self, feature, notification, device, status, last_result): + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): if gkeymap: current = gkeymap.get_modifier_state() - if _log.isEnabledFor(_INFO): - _log.info('KeyPress action: %s %s, group %s, modifiers %s', self.key_names, self.action, kbdgroup(), current) + if logger.isEnabledFor(logging.INFO): + logger.info( + "KeyPress action: %s %s, group %s, modifiers %s", + self.key_names, + self.action, + kbdgroup(), + current, + ) if self.action != RELEASE: self.keyDown(self.key_symbols, current) if self.action != DEPRESS: self.keyUp(reversed(self.key_symbols), current) - _time.sleep(0.01) + time.sleep(0.01) else: - _log.warn('no keymap so cannot determine which keycode to send') + logger.warning("no keymap so cannot determine which keycode to send") return None def data(self): - return {'KeyPress': [[str(k) for k in self.key_names], self.action]} + return {"KeyPress": [[str(k) for k in self.key_names], self.action]} # KeyDown is dangerous as the key can auto-repeat and make your system unusable # class KeyDown(KeyPress): -# def evaluate(self, feature, notification, device, status, last_result): +# def evaluate(self, feature, notification: HIDPPNotification, device, last_result): # super().keyDown(self.keys, current_key_modifiers) # class KeyUp(KeyPress): -# def evaluate(self, feature, notification, device, status, last_result): +# def evaluate(self, feature, notification: HIDPPNotification, device, last_result): # super().keyUp(self.keys, current_key_modifiers) class MouseScroll(Action): - def __init__(self, amounts, warn=True): - import numbers if len(amounts) == 1 and isinstance(amounts[0], list): amounts = amounts[0] if not (len(amounts) == 2 and all([isinstance(a, numbers.Number) for a in amounts])): if warn: - _log.warn('rule MouseScroll argument not two numbers %s', amounts) + logger.warning("rule MouseScroll argument not two numbers %s", amounts) amounts = [0, 0] self.amounts = amounts def __str__(self): - return 'MouseScroll: ' + ' '.join([str(a) for a in self.amounts]) + return "MouseScroll: " + " ".join([str(a) for a in self.amounts]) - def evaluate(self, feature, notification, device, status, last_result): - import math - import numbers + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): amounts = self.amounts if isinstance(last_result, numbers.Number): amounts = [math.floor(last_result * a) for a in self.amounts] - if _log.isEnabledFor(_INFO): - _log.info('MouseScroll action: %s %s %s', self.amounts, last_result, amounts) + if logger.isEnabledFor(logging.INFO): + logger.info("MouseScroll action: %s %s %s", self.amounts, last_result, amounts) dx, dy = amounts simulate_scroll(dx, dy) - _time.sleep(0.01) + time.sleep(0.01) return None def data(self): - return {'MouseScroll': self.amounts[:]} + return {"MouseScroll": self.amounts[:]} class MouseClick(Action): - def __init__(self, args, warn=True): if len(args) == 1 and isinstance(args[0], list): args = args[0] @@ -1249,7 +1249,7 @@ class MouseClick(Action): self.button = str(args[0]) if len(args) >= 0 else None if self.button not in buttons: if warn: - _log.warn('rule MouseClick action: button %s not known', self.button) + logger.warning("rule MouseClick action: button %s not known", self.button) self.button = None count = args[1] if len(args) >= 2 else 1 try: @@ -1258,92 +1258,98 @@ class MouseClick(Action): if count in [CLICK, DEPRESS, RELEASE]: self.count = count elif warn: - _log.warn('rule MouseClick action: argument %s should be an integer or CLICK, PRESS, or RELEASE', count) + logger.warning( + "rule MouseClick action: argument %s should be an integer or click, depress, or release", + count, + ) self.count = 1 def __str__(self): - return 'MouseClick: %s (%d)' % (self.button, self.count) + return f"MouseClick: {self.button} ({str(self.count)})" - def evaluate(self, feature, notification, device, status, last_result): - if _log.isEnabledFor(_INFO): - _log.info('MouseClick action: %d %s' % (self.count, self.button)) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.INFO): + logger.info(f"MouseClick action: {str(self.count)} {self.button}") if self.button and self.count: click(buttons[self.button], self.count) - _time.sleep(0.01) + time.sleep(0.01) return None def data(self): - return {'MouseClick': [self.button, self.count]} + return {"MouseClick": [self.button, self.count]} class Set(Action): - def __init__(self, args, warn=True): if not (isinstance(args, list) and len(args) > 2): if warn: - _log.warn('rule Set argument not list with minimum length 3: %s', args) + logger.warning("rule Set argument not list with minimum length 3: %s", args) self.args = [] else: self.args = args def __str__(self): - return 'Set: ' + ' '.join([str(a) for a in self.args]) - - def evaluate(self, feature, notification, device, status, last_result): - # importing here to avoid circular imports - from solaar.ui.config_panel import change_setting as _change_setting + return "Set: " + " ".join([str(a) for a in self.args]) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): if len(self.args) < 3: return None - if _log.isEnabledFor(_INFO): - _log.info('Set action: %s', self.args) - dev = _Device.find(self.args[0]) if self.args[0] is not None else device + if logger.isEnabledFor(logging.INFO): + logger.info("Set action: %s", self.args) + dev = device.find(self.args[0]) if self.args[0] is not None else device if dev is None: - _log.warn('Set action: device %s is not known', self.args[0]) + logger.warning("Set action: device %s is not known", self.args[0]) return None setting = next((s for s in dev.settings if s.name == self.args[1]), None) if setting is None: - _log.warn('Set action: setting %s is not the name of a setting for %s', self.args[1], dev.name) + logger.warning("Set action: setting %s is not the name of a setting for %s", self.args[1], dev.name) return None args = setting.acceptable(self.args[2:], setting.read()) if args is None: - _log.warn('Set Action: invalid args %s for setting %s of %s', self.args[2:], self.args[1], self.args[0]) + logger.warning( + "Set Action: invalid args %s for setting %s of %s", + self.args[2:], + self.args[1], + self.args[0], + ) return None - _change_setting(dev, setting, args) + if len(args) > 1: + setting.write_key_value(args[0], args[1]) + else: + setting.write(args[0]) + if device.setting_callback: + device.setting_callback(device, type(setting), args) return None def data(self): - return {'Set': self.args[:]} + return {"Set": self.args[:]} class Execute(Action): - def __init__(self, args, warn=True): if isinstance(args, str): args = [args] if not (isinstance(args, list) and all(isinstance(arg), str) for arg in args): if warn: - _log.warn('rule Execute argument not list of strings: %s', args) + logger.warning("rule Execute argument not list of strings: %s", args) self.args = [] else: self.args = args def __str__(self): - return 'Execute: ' + ' '.join([a for a in self.args]) + return "Execute: " + " ".join([a for a in self.args]) - def evaluate(self, feature, notification, device, status, last_result): - import subprocess - if _log.isEnabledFor(_INFO): - _log.info('Execute action: %s', self.args) + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): + if logger.isEnabledFor(logging.INFO): + logger.info("Execute action: %s", self.args) subprocess.Popen(self.args) return None def data(self): - return {'Execute': self.args[:]} + return {"Execute": self.args[:]} class Later(Action): - def __init__(self, args, warn=True): self.delay = 0 self.rule = Rule([]) @@ -1352,169 +1358,154 @@ class Later(Action): args = [args] if not (isinstance(args, list) and len(args) >= 1): if warn: - _log.warn('rule Later argument not list with minimum length 1: %s', args) - elif not (isinstance(args[0], int)) or not 0 < args[0] < 101: + logger.warning("rule Later argument not list with minimum length 1: %s", args) + elif not (isinstance(args[0], (int, float))) or not 0.01 <= args[0] <= 100: if warn: - _log.warn('rule Later argument delay not integer between 1 and 100: %s', args) + logger.warning("rule Later delay not between 0.01 and 100: %s", args) else: self.delay = args[0] self.rule = Rule(args[1:], warn=warn) self.components = self.rule.components def __str__(self): - return 'Later: [' + str(self.delay) + ', ' + ', '.join(str(c) for c in self.components) + ']' + return f"Later: [{str(self.delay)}, " + ", ".join(str(c) for c in self.components) + "]" - def evaluate(self, feature, notification, device, status, last_result): + def evaluate(self, feature, notification: HIDPPNotification, device, last_result): if self.delay and self.rule: - GLib.timeout_add_seconds(self.delay, Rule.once, self.rule, feature, notification, device, status, last_result) + if self.delay >= 1: + GLib.timeout_add_seconds(int(self.delay), Rule.once, self.rule, feature, notification, device, last_result) + else: + GLib.timeout_add(int(self.delay * 1000), Rule.once, self.rule, feature, notification, device, last_result) return None def data(self): data = [c.data() for c in self.components] data.insert(0, self.delay) - return {'Later': data} + return {"Later": data} COMPONENTS = { - 'Rule': Rule, - 'Not': Not, - 'Or': Or, - 'And': And, - 'Process': Process, - 'MouseProcess': MouseProcess, - 'Feature': Feature, - 'Report': Report, - 'Setting': Setting, - 'Modifiers': Modifiers, - 'Key': Key, - 'KeyIsDown': KeyIsDown, - 'Test': Test, - 'TestBytes': TestBytes, - 'MouseGesture': MouseGesture, - 'Active': Active, - 'Device': Device, - 'Host': Host, - 'KeyPress': KeyPress, - 'MouseScroll': MouseScroll, - 'MouseClick': MouseClick, - 'Set': Set, - 'Execute': Execute, - 'Later': Later, + "Rule": Rule, + "Not": Not, + "Or": Or, + "And": And, + "Process": Process, + "MouseProcess": MouseProcess, + "Feature": Feature, + "Report": Report, + "Setting": Setting, + "Modifiers": Modifiers, + "Key": Key, + "KeyIsDown": KeyIsDown, + "Test": Test, + "TestBytes": TestBytes, + "MouseGesture": MouseGesture, + "Active": Active, + "Device": Device, + "Host": Host, + "KeyPress": KeyPress, + "MouseScroll": MouseScroll, + "MouseClick": MouseClick, + "Set": Set, + "Execute": Execute, + "Later": Later, } -built_in_rules = Rule([]) -if True: - built_in_rules = Rule([ - {'Rule': [ # Implement problematic keys for Craft and MX Master - {'Rule': [{'Key': ['Brightness Down', 'pressed']}, {'KeyPress': 'XF86_MonBrightnessDown'}]}, - {'Rule': [{'Key': ['Brightness Up', 'pressed']}, {'KeyPress': 'XF86_MonBrightnessUp'}]}, - ]}, - # {'Rule': [ # In firefox, crown emits keys that move up and down if not pressed, rotate through tabs otherwise - # {'Process': 'firefox'}, - # {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_right_ratchet'}, {'KeyPress': ['Control_R', 'Tab']}]}, - # {'Rule': [{'Test': 'crown_pressed'}, - # {'Test': 'crown_left_ratchet'}, - # {'KeyPress': ['Control_R', 'Shift_R', 'Tab']}]}, - # {'Rule': [{'Test': 'crown_right_ratchet'}, {'KeyPress': 'Down'}]}, - # {'Rule': [{'Test': 'crown_left_ratchet'}, {'KeyPress': 'Up'}]}, - # ]}, - # {'Rule': [ # Otherwise, crown movements emit keys that modify volume if not pressed, move between tracks otherwise - # {'Feature': 'CROWN'}, {'Report': 0x0}, - # {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_right_ratchet'}, {'KeyPress': 'XF86_AudioNext'}]}, - # {'Rule': [{'Test': 'crown_pressed'}, {'Test': 'crown_left_ratchet'}, {'KeyPress': 'XF86_AudioPrev'}]}, - # {'Rule': [{'Test': 'crown_right_ratchet'}, {'KeyPress': 'XF86_AudioRaiseVolume'}]}, - # {'Rule': [{'Test': 'crown_left_ratchet'}, {'KeyPress': 'XF86_AudioLowerVolume'}]} - # ]}, - ]) -keys_down = [] -g_keys_down = 0 -m_keys_down = 0 -mr_key_down = False -thumb_wheel_displacement = 0 +built_in_rules = Rule( + [ + { + "Rule": [ # Implement problematic keys for Craft and MX Master + {"Rule": [{"Key": ["Brightness Down", "pressed"]}, {"KeyPress": "XF86_MonBrightnessDown"}]}, + {"Rule": [{"Key": ["Brightness Up", "pressed"]}, {"KeyPress": "XF86_MonBrightnessUp"}]}, + ] + }, + ] +) -def key_is_down(key): - if key == _CONTROL.MR: +def key_is_down(key: NamedInt) -> bool: + """Checks if given key is pressed or not.""" + if key == CONTROL.MR: return mr_key_down - elif _CONTROL.M1 <= key <= _CONTROL.M8: - return bool(m_keys_down & (0x01 << (key - _CONTROL.M1))) - elif _CONTROL.G1 <= key <= _CONTROL.G32: - return bool(g_keys_down & (0x01 << (key - _CONTROL.G1))) - else: - return key in keys_down + elif CONTROL.M1 <= key <= CONTROL.M8: + return bool(m_keys_down & (0x01 << (key - CONTROL.M1))) + elif CONTROL.G1 <= key <= CONTROL.G32: + return bool(g_keys_down & (0x01 << (key - CONTROL.G1))) + return key in keys_down -def evaluate_rules(feature, notification, device, status): - if _log.isEnabledFor(_DEBUG): - _log.debug('evaluating rules on %s', notification) - rules.evaluate(feature, notification, device, status, True) +def evaluate_rules(feature, notification: HIDPPNotification, device): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("evaluating rules on %s %s", feature, notification) + rules.evaluate(feature, notification, device, True) -# process a notification -def process_notification(device, status, notification, feature): +def process_notification(device, notification: HIDPPNotification, feature) -> None: + """Processes HID++ notifications.""" global keys_down, g_keys_down, m_keys_down, mr_key_down, key_down, key_up, thumb_wheel_displacement 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(' Rule: loaded_rules = [] - if _path.isfile(_file_path): - try: - with open(_file_path) as config_file: - loaded_rules = [] - for loaded_rule in _yaml_safe_load_all(config_file): - rule = Rule(loaded_rule, source=_file_path) - if _log.isEnabledFor(_DEBUG): - _log.debug('load rule: %s', rule) - loaded_rules.append(rule) - if _log.isEnabledFor(_INFO): - _log.info('loaded %d rules from %s', len(loaded_rules), config_file.name) - except Exception as e: - _log.error('failed to load from %s\n%s', _file_path, e) - rules = Rule([Rule(loaded_rules, source=_file_path), built_in_rules]) + try: + with open(file_path) as config_file: + loaded_rules = [] + for loaded_rule in yaml.safe_load_all(config_file): + rule = Rule(loaded_rule, source=file_path) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("load rule: %s", rule) + loaded_rules.append(rule) + if logger.isEnabledFor(logging.INFO): + logger.info("loaded %d rules from %s", len(loaded_rules), config_file.name) + except Exception as e: + logger.error("failed to load from %s\n%s", file_path, e) + return Rule([Rule(loaded_rules, source=file_path), built_in_rules]) -_load_config_rule_file() +load_config_rule_file() diff --git a/lib/logitech_receiver/exceptions.py b/lib/logitech_receiver/exceptions.py new file mode 100644 index 00000000..cc893fe8 --- /dev/null +++ b/lib/logitech_receiver/exceptions.py @@ -0,0 +1,53 @@ +## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from .common import KwException + +"""Exceptions that may be raised by this API.""" + + +class NoReceiver(KwException): + """Raised when trying to talk through a previously open handle, when the + receiver is no longer available. Should only happen if the receiver is + physically disconnected from the machine, or its kernel driver module is + unloaded.""" + + pass + + +class NoSuchDevice(KwException): + """Raised when trying to reach a device number not paired to the receiver.""" + + pass + + +class DeviceUnreachable(KwException): + """Raised when a request is made to an unreachable (turned off) device.""" + + pass + + +class FeatureNotSupported(KwException): + """Raised when trying to request a feature not supported by the device.""" + + pass + + +class FeatureCallError(KwException): + """Raised if the device replied to a feature call with an error.""" + + pass diff --git a/lib/logitech_receiver/hidpp10.py b/lib/logitech_receiver/hidpp10.py index 0818ef16..e6ee6380 100644 --- a/lib/logitech_receiver/hidpp10.py +++ b/lib/logitech_receiver/hidpp10.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -15,382 +13,275 @@ ## You should have received a copy of the GNU General Public License along ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from __future__ import annotations -from logging import getLogger # , DEBUG as _DEBUG +import logging -from .common import BATTERY_APPROX as _BATTERY_APPROX -from .common import FirmwareInfo as _FirmwareInfo -from .common import NamedInts as _NamedInts -from .common import bytes2int as _bytes2int -from .common import int2bytes as _int2bytes -from .common import strhex as _strhex -from .hidpp20 import BATTERY_STATUS, FIRMWARE_KIND +from typing import Any -_log = getLogger(__name__) -del getLogger +from typing_extensions import Protocol -# -# Constants - most of them as defined by the official Logitech HID++ 1.0 -# documentation, some of them guessed. -# +from . import common +from .common import Battery +from .common import BatteryLevelApproximation +from .common import BatteryStatus +from .common import FirmwareKind +from .hidpp10_constants import NotificationFlag +from .hidpp10_constants import Registers -DEVICE_KIND = _NamedInts( - unknown=0x00, - keyboard=0x01, - mouse=0x02, - numpad=0x03, - presenter=0x04, - remote=0x07, - trackball=0x08, - touchpad=0x09, - headset=0x0D, # not from Logitech documentation - remote_control=0x0E, # for compatibility with HID++ 2.0 - receiver=0x0F # for compatibility with HID++ 2.0 -) - -POWER_SWITCH_LOCATION = _NamedInts( - base=0x01, - top_case=0x02, - edge_of_top_right_corner=0x03, - top_left_corner=0x05, - bottom_left_corner=0x06, - top_right_corner=0x07, - bottom_right_corner=0x08, - top_edge=0x09, - right_edge=0x0A, - left_edge=0x0B, - bottom_edge=0x0C -) - -# Some flags are used both by devices and receivers. The Logitech documentation -# mentions that the first and last (third) byte are used for devices while the -# second is used for the receiver. In practise, the second byte is also used for -# some device-specific notifications (keyboard illumination level). Do not -# simply set all notification bits if the software does not support it. For -# example, enabling keyboard_sleep_raw makes the Sleep key a no-operation unless -# the software is updated to handle that event. -# Observations: -# - wireless and software present were seen on receivers, reserved_r1b4 as well -# - the rest work only on devices as far as we can tell right now -# In the future would be useful to have separate enums for receiver and device notification flags, -# but right now we don't know enough. -# additional flags taken from https://drive.google.com/file/d/0BxbRzx7vEV7eNDBheWY0UHM5dEU/view?usp=sharing -NOTIFICATION_FLAG = _NamedInts( - numpad_numerical_keys=0x800000, - f_lock_status=0x400000, - roller_H=0x200000, - battery_status=0x100000, # send battery charge notifications (0x07 or 0x0D) - mouse_extra_buttons=0x080000, - roller_V=0x040000, - keyboard_sleep_raw=0x020000, # system control keys such as Sleep - keyboard_multimedia_raw=0x010000, # consumer controls such as Mute and Calculator - # reserved_r1b4= 0x001000, # unknown, seen on a unifying receiver - reserved5=0x008000, - reserved4=0x004000, - reserved3=0x002000, - reserved2=0x001000, - software_present=0x000800, # .. no idea - reserved1=0x000400, - keyboard_illumination=0x000200, # illumination brightness level changes (by pressing keys) - wireless=0x000100, # notify when the device wireless goes on/off-line - mx_air_3d_gesture=0x000001, -) - -ERROR = _NamedInts( - invalid_SubID__command=0x01, - invalid_address=0x02, - invalid_value=0x03, - connection_request_failed=0x04, - too_many_devices=0x05, - already_exists=0x06, - busy=0x07, - unknown_device=0x08, - resource_error=0x09, - request_unavailable=0x0A, - unsupported_parameter_value=0x0B, - wrong_pin_code=0x0C -) - -PAIRING_ERRORS = _NamedInts(device_timeout=0x01, device_not_supported=0x02, too_many_devices=0x03, sequence_timeout=0x06) -BOLT_PAIRING_ERRORS = _NamedInts(device_timeout=0x01, failed=0x02) -"""Known registers. -Devices usually have a (small) sub-set of these. Some registers are only -applicable to certain device kinds (e.g. smooth_scroll only applies to mice.""" -REGISTERS = _NamedInts( - # only apply to receivers - receiver_connection=0x02, - receiver_pairing=0xB2, - devices_activity=0x2B3, - receiver_info=0x2B5, - bolt_device_discovery=0xC0, - bolt_pairing=0x2C1, - bolt_uniqueId=0x02FB, - - # only apply to devices - mouse_button_flags=0x01, - keyboard_hand_detection=0x01, - battery_status=0x07, - keyboard_fn_swap=0x09, - battery_charge=0x0D, - keyboard_illumination=0x17, - three_leds=0x51, - mouse_dpi=0x63, - - # apply to both - notifications=0x00, - firmware=0xF1, - - # notifications - passkey_request_notification=0x4D, - passkey_pressed_notification=0x4E, - device_discovery_notification=0x4F, - discovery_status_notification=0x53, - pairing_status_notification=0x54, -) -# Subregisters for receiver_info register -INFO_SUBREGISTERS = _NamedInts( - serial_number=0x01, # not found on many receivers - fw_version=0x02, - receiver_information=0x03, - pairing_information=0x20, # 0x2N, by connected device - extended_pairing_information=0x30, # 0x3N, by connected device - device_name=0x40, # 0x4N, by connected device - bolt_pairing_information=0x50, # 0x5N, by connected device - bolt_device_name=0x60, # 0x6N01, by connected device, -) - -# Flags taken from https://drive.google.com/file/d/0BxbRzx7vEV7eNDBheWY0UHM5dEU/view?usp=sharing -DEVICE_FEATURES = _NamedInts( - reserved1=0x010000, - special_buttons=0x020000, - enhanced_key_usage=0x040000, - fast_fw_rev=0x080000, - reserved2=0x100000, - reserved3=0x200000, - scroll_accel=0x400000, - buttons_control_resolution=0x800000, - inhibit_lock_key_sound=0x000001, - reserved4=0x000002, - mx_air_3d_engine=0x000004, - host_control_leds=0x000008, - reserved5=0x000010, - reserved6=0x000020, - reserved7=0x000040, - reserved8=0x000080, -) - -# -# functions -# +logger = logging.getLogger(__name__) -def read_register(device, register_number, *params): - assert device is not None, 'tried to read register %02X from invalid device %s' % (register_number, device) +class Device(Protocol): + def request(self, request_id, *params): + ... + + @property + def kind(self) -> Any: + ... + + @property + def online(self) -> bool: + ... + + @property + def protocol(self) -> Any: + ... + + @property + def registers(self) -> list: + ... + + +def read_register(device: Device, register: Registers | int, *params) -> Any: + assert device is not None, f"tried to read register {register:02X} from invalid device {device}" # support long registers by adding a 2 in front of the register number - request_id = 0x8100 | (int(register_number) & 0x2FF) + request_id = 0x8100 | (int(register) & 0x2FF) return device.request(request_id, *params) -def write_register(device, register_number, *value): - assert device is not None, 'tried to write register %02X to invalid device %s' % (register_number, device) +def write_register(device: Device, register: Registers | int, *value) -> Any: + assert device is not None, f"tried to write register {register:02X} to invalid device {device}" # support long registers by adding a 2 in front of the register number - request_id = 0x8000 | (int(register_number) & 0x2FF) + request_id = 0x8000 | (int(register) & 0x2FF) return device.request(request_id, *value) -def get_battery(device): - assert device is not None - assert device.kind is not None - if not device.online: - return - """Reads a device's battery level, if provided by the HID++ 1.0 protocol.""" - if device.protocol and device.protocol >= 2.0: - # let's just assume HID++ 2.0 devices do not provide the battery info in a register - return +def get_configuration_pending_flags(receiver): + assert not receiver.isDevice + result = read_register(receiver, Registers.DEVICES_CONFIGURATION) + if result is not None: + return ord(result[:1]) - for r in (REGISTERS.battery_status, REGISTERS.battery_charge): - if r in device.registers: - reply = read_register(device, r) - if reply: - return parse_battery_status(r, reply) + +def set_configuration_pending_flags(receiver, devices): + assert not receiver.isDevice + result = write_register(receiver, Registers.DEVICES_CONFIGURATION, devices) + return result is not None + + +class Hidpp10: + def get_battery(self, device: Device): + assert device is not None + assert device.kind is not None + if not device.online: + return + """Reads a device's battery level, if provided by the HID++ 1.0 protocol.""" + if device.protocol and device.protocol >= 2.0: + # let's just assume HID++ 2.0 devices do not provide the battery info in a register return - # the descriptor does not tell us which register this device has, try them both - reply = read_register(device, REGISTERS.battery_charge) - if reply: - # remember this for the next time - device.registers.append(REGISTERS.battery_charge) - return parse_battery_status(REGISTERS.battery_charge, reply) + for r in (Registers.BATTERY_STATUS, Registers.BATTERY_CHARGE): + if r in device.registers: + reply = read_register(device, r) + if reply: + return parse_battery_status(r, reply) + return - reply = read_register(device, REGISTERS.battery_status) - if reply: - # remember this for the next time - device.registers.append(REGISTERS.battery_status) - return parse_battery_status(REGISTERS.battery_status, reply) + # the descriptor does not tell us which register this device has, try them both + reply = read_register(device, Registers.BATTERY_CHARGE) + if reply: + # remember this for the next time + device.registers.append(Registers.BATTERY_CHARGE) + return parse_battery_status(Registers.BATTERY_CHARGE, reply) + + reply = read_register(device, Registers.BATTERY_STATUS) + if reply: + # remember this for the next time + device.registers.append(Registers.BATTERY_STATUS) + return parse_battery_status(Registers.BATTERY_STATUS, reply) + + def get_firmware(self, device: Device) -> tuple[common.FirmwareInfo] | None: + assert device is not None + + firmware = [None, None, None] + + reply = read_register(device, Registers.FIRMWARE, 0x01) + if not reply: + # won't be able to read any of it now... + return + + fw_version = common.strhex(reply[1:3]) + fw_version = f"{fw_version[0:2]}.{fw_version[2:4]}" + reply = read_register(device, Registers.FIRMWARE, 0x02) + if reply: + fw_version += ".B" + common.strhex(reply[1:3]) + fw = common.FirmwareInfo(FirmwareKind.Firmware, "", fw_version, None) + firmware[0] = fw + + reply = read_register(device, Registers.FIRMWARE, 0x04) + if reply: + bl_version = common.strhex(reply[1:3]) + bl_version = f"{bl_version[0:2]}.{bl_version[2:4]}" + bl = common.FirmwareInfo(FirmwareKind.Bootloader, "", bl_version, None) + firmware[1] = bl + + reply = read_register(device, Registers.FIRMWARE, 0x03) + if reply: + o_version = common.strhex(reply[1:3]) + o_version = f"{o_version[0:2]}.{o_version[2:4]}" + o = common.FirmwareInfo(FirmwareKind.Other, "", o_version, None) + firmware[2] = o + + if any(firmware): + return tuple(f for f in firmware if f) + + def set_3leds(self, device: Device, battery_level=None, charging=None, warning=None): + assert device is not None + assert device.kind is not None + if not device.online: + return + + if Registers.THREE_LEDS not in device.registers: + return + + if battery_level is not None: + if battery_level < BatteryLevelApproximation.CRITICAL: + # 1 orange, and force blink + v1, v2 = 0x22, 0x00 + warning = True + elif battery_level < BatteryLevelApproximation.LOW: + # 1 orange + v1, v2 = 0x22, 0x00 + elif battery_level < BatteryLevelApproximation.GOOD: + # 1 green + v1, v2 = 0x20, 0x00 + elif battery_level < BatteryLevelApproximation.FULL: + # 2 greens + v1, v2 = 0x20, 0x02 + else: + # all 3 green + v1, v2 = 0x20, 0x22 + if warning: + # set the blinking flag for the leds already set + v1 |= v1 >> 1 + v2 |= v2 >> 1 + elif charging: + # blink all green + v1, v2 = 0x30, 0x33 + elif warning: + # 1 red + v1, v2 = 0x02, 0x00 + else: + # turn off all leds + v1, v2 = 0x11, 0x11 + + write_register(device, Registers.THREE_LEDS, v1, v2) + + def get_notification_flags(self, device: Device): + flags = self._get_register(device, Registers.NOTIFICATIONS) + if flags is not None: + return NotificationFlag(flags) + + def set_notification_flags(self, device: Device, *flag_bits: NotificationFlag): + assert device is not None + + # Avoid a call if the device is not online, + # or the device does not support registers. + if device.kind is not None: + # peripherals with protocol >= 2.0 don't support registers + if device.protocol and device.protocol >= 2.0: + return + + flag_bits = sum(int(b.value) for b in flag_bits) + assert flag_bits & 0x00FFFFFF == flag_bits + result = write_register(device, Registers.NOTIFICATIONS, common.int2bytes(flag_bits, 3)) + return result is not None + + def get_device_features(self, device: Device): + return self._get_register(device, Registers.MOUSE_BUTTON_FLAGS) + + def _get_register(self, device: Device, register: Registers | int): + assert device is not None + + # Avoid a call if the device is not online, + # or the device does not support registers. + if device.kind is not None: + # peripherals with protocol >= 2.0 don't support registers + if device.protocol and device.protocol >= 2.0: + return + + flags = read_register(device, register) + if flags is not None: + assert len(flags) == 3 + return common.bytes2int(flags) -def parse_battery_status(register, reply): - if register == REGISTERS.battery_charge: +def parse_battery_status(register: Registers | int, reply) -> Battery | None: + def status_byte_to_charge(status_byte_: int) -> BatteryLevelApproximation: + if status_byte_ == 7: + charge_ = BatteryLevelApproximation.FULL + elif status_byte_ == 5: + charge_ = BatteryLevelApproximation.GOOD + elif status_byte_ == 3: + charge_ = BatteryLevelApproximation.LOW + elif status_byte_ == 1: + charge_ = BatteryLevelApproximation.CRITICAL + else: + # pure 'charging' notifications may come without a status + charge_ = BatteryLevelApproximation.EMPTY + return charge_ + + def status_byte_to_battery_status(status_byte_: int) -> BatteryStatus: + if status_byte_ == 0x30: + status_text_ = BatteryStatus.DISCHARGING + elif status_byte_ == 0x50: + status_text_ = BatteryStatus.RECHARGING + elif status_byte_ == 0x90: + status_text_ = BatteryStatus.FULL + else: + status_text_ = None + return status_text_ + + def charging_byte_to_status_text(charging_byte_: int) -> BatteryStatus: + if charging_byte_ == 0x00: + status_text_ = BatteryStatus.DISCHARGING + elif charging_byte_ & 0x21 == 0x21: + status_text_ = BatteryStatus.RECHARGING + elif charging_byte_ & 0x22 == 0x22: + status_text_ = BatteryStatus.FULL + else: + logger.warning("could not parse 0x07 battery status: %02X (level %02X)", charging_byte_, status_byte) + status_text_ = None + return status_text_ + + if register == Registers.BATTERY_CHARGE: charge = ord(reply[:1]) status_byte = ord(reply[2:3]) & 0xF0 - status_text = ( - BATTERY_STATUS.discharging if status_byte == 0x30 else - BATTERY_STATUS.recharging if status_byte == 0x50 else BATTERY_STATUS.full if status_byte == 0x90 else None - ) - return charge, None, status_text, None - if register == REGISTERS.battery_status: + battery_status = status_byte_to_battery_status(status_byte) + return Battery(charge, None, battery_status, None) + + if register == Registers.BATTERY_STATUS: status_byte = ord(reply[:1]) - charge = ( - _BATTERY_APPROX.full if status_byte == 7 # full - else _BATTERY_APPROX.good if status_byte == 5 # good - else _BATTERY_APPROX.low if status_byte == 3 # low - else _BATTERY_APPROX.critical if status_byte == 1 # critical - # pure 'charging' notifications may come without a status - else _BATTERY_APPROX.empty - ) - charging_byte = ord(reply[1:2]) - if charging_byte == 0x00: - status_text = BATTERY_STATUS.discharging - elif charging_byte & 0x21 == 0x21: - status_text = BATTERY_STATUS.recharging - elif charging_byte & 0x22 == 0x22: - status_text = BATTERY_STATUS.full - else: - _log.warn('could not parse 0x07 battery status: %02X (level %02X)', charging_byte, status_byte) - status_text = None + + status_text = charging_byte_to_status_text(charging_byte) + charge = status_byte_to_charge(status_byte) if charging_byte & 0x03 and status_byte == 0: # some 'charging' notifications may come with no battery level information charge = None # Return None for next charge level and voltage as these are not in HID++ 1.0 spec - return charge, None, status_text, None - - -def get_firmware(device): - assert device is not None - - firmware = [None, None, None] - - reply = read_register(device, REGISTERS.firmware, 0x01) - if not reply: - # won't be able to read any of it now... - return - - fw_version = _strhex(reply[1:3]) - fw_version = '%s.%s' % (fw_version[0:2], fw_version[2:4]) - reply = read_register(device, REGISTERS.firmware, 0x02) - if reply: - fw_version += '.B' + _strhex(reply[1:3]) - fw = _FirmwareInfo(FIRMWARE_KIND.Firmware, '', fw_version, None) - firmware[0] = fw - - reply = read_register(device, REGISTERS.firmware, 0x04) - if reply: - bl_version = _strhex(reply[1:3]) - bl_version = '%s.%s' % (bl_version[0:2], bl_version[2:4]) - bl = _FirmwareInfo(FIRMWARE_KIND.Bootloader, '', bl_version, None) - firmware[1] = bl - - reply = read_register(device, REGISTERS.firmware, 0x03) - if reply: - o_version = _strhex(reply[1:3]) - o_version = '%s.%s' % (o_version[0:2], o_version[2:4]) - o = _FirmwareInfo(FIRMWARE_KIND.Other, '', o_version, None) - firmware[2] = o - - if any(firmware): - return tuple(f for f in firmware if f) - - -def set_3leds(device, battery_level=None, charging=None, warning=None): - assert device is not None - assert device.kind is not None - if not device.online: - return - - if REGISTERS.three_leds not in device.registers: - return - - if battery_level is not None: - if battery_level < _BATTERY_APPROX.critical: - # 1 orange, and force blink - v1, v2 = 0x22, 0x00 - warning = True - elif battery_level < _BATTERY_APPROX.low: - # 1 orange - v1, v2 = 0x22, 0x00 - elif battery_level < _BATTERY_APPROX.good: - # 1 green - v1, v2 = 0x20, 0x00 - elif battery_level < _BATTERY_APPROX.full: - # 2 greens - v1, v2 = 0x20, 0x02 - else: - # all 3 green - v1, v2 = 0x20, 0x22 - if warning: - # set the blinking flag for the leds already set - v1 |= (v1 >> 1) - v2 |= (v2 >> 1) - elif charging: - # blink all green - v1, v2 = 0x30, 0x33 - elif warning: - # 1 red - v1, v2 = 0x02, 0x00 - else: - # turn off all leds - v1, v2 = 0x11, 0x11 - - write_register(device, REGISTERS.three_leds, v1, v2) - - -def get_notification_flags(device): - assert device is not None - - # Avoid a call if the device is not online, - # or the device does not support registers. - if device.kind is not None: - # peripherals with protocol >= 2.0 don't support registers - if device.protocol and device.protocol >= 2.0: - return - - flags = read_register(device, REGISTERS.notifications) - if flags is not None: - assert len(flags) == 3 - return _bytes2int(flags) - - -def set_notification_flags(device, *flag_bits): - assert device is not None - - # Avoid a call if the device is not online, - # or the device does not support registers. - if device.kind is not None: - # peripherals with protocol >= 2.0 don't support registers - if device.protocol and device.protocol >= 2.0: - return - - flag_bits = sum(int(b) for b in flag_bits) - assert flag_bits & 0x00FFFFFF == flag_bits - result = write_register(device, REGISTERS.notifications, _int2bytes(flag_bits, 3)) - return result is not None - - -def get_device_features(device): - assert device is not None - - # Avoid a call if the device is not online, - # or the device does not support registers. - if device.kind is not None: - # peripherals with protocol >= 2.0 don't support registers - if device.protocol and device.protocol >= 2.0: - return - - flags = read_register(device, REGISTERS.mouse_button_flags) - if flags is not None: - assert len(flags) == 3 - return _bytes2int(flags) + return Battery(charge, None, status_text, None) diff --git a/lib/logitech_receiver/hidpp10_constants.py b/lib/logitech_receiver/hidpp10_constants.py new file mode 100644 index 00000000..3861bcd4 --- /dev/null +++ b/lib/logitech_receiver/hidpp10_constants.py @@ -0,0 +1,256 @@ +## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from __future__ import annotations + +from enum import IntEnum +from enum import IntFlag +from typing import List + +from .common import NamedInts + +"""HID constants for HID++ 1.0. + +Most of them as defined by the official Logitech HID++ 1.0 +documentation, some of them guessed. +""" + +DEVICE_KIND = NamedInts( + unknown=0x00, + keyboard=0x01, + mouse=0x02, + numpad=0x03, + presenter=0x04, + remote=0x07, + trackball=0x08, + touchpad=0x09, + tablet=0x0A, + gamepad=0x0B, + joystick=0x0C, + headset=0x0D, # not from Logitech documentation + remote_control=0x0E, # for compatibility with HID++ 2.0 + receiver=0x0F, # for compatibility with HID++ 2.0 +) + + +class PowerSwitchLocation(IntEnum): + UNKNOWN = 0x00 + BASE = 0x01 + TOP_CASE = 0x02 + EDGE_OF_TOP_RIGHT_CORNER = 0x03 + TOP_LEFT_CORNER = 0x05 + BOTTOM_LEFT_CORNER = 0x06 + TOP_RIGHT_CORNER = 0x07 + BOTTOM_RIGHT_CORNER = 0x08 + TOP_EDGE = 0x09 + RIGHT_EDGE = 0x0A + LEFT_EDGE = 0x0B + BOTTOM_EDGE = 0x0C + + @classmethod + def location(cls, loc: int) -> PowerSwitchLocation: + try: + return cls(loc) + except ValueError: + return cls.UNKNOWN + + +class NotificationFlag(IntFlag): + """Some flags are used both by devices and receivers. + + The Logitech documentation mentions that the first and last (third) + byte are used for devices while the second is used for the receiver. + In practise, the second byte is also used for some device-specific + notifications (keyboard illumination level). Do not simply set all + notification bits if the software does not support it. For example, + enabling keyboard_sleep_raw makes the Sleep key a no-operation + unless the software is updated to handle that event. + + Observations: + - wireless and software present seen on receivers, + reserved_r1b4 as well + - the rest work only on devices as far as we can tell right now + In the future would be useful to have separate enums for receiver + and device notification flags, but right now we don't know enough. + Additional flags taken from https://drive.google.com/file/d/0BxbRzx7vEV7eNDBheWY0UHM5dEU/view?usp=sharing + """ + + @classmethod + def flag_names(cls, flags) -> List[str]: + """Extract the names of the flags from the integer.""" + if flags is None: + return [] + if flags.name is not None: + return flags.name.replace("_", " ").lower().split("|") + # Python < 3.11: .name is None for composite flags, decompose manually + return [m.name.replace("_", " ").lower() for m in cls if m.value and m in flags] + + NUMPAD_NUMERICAL_KEYS = 0x800000 + F_LOCK_STATUS = 0x400000 + ROLLER_H = 0x200000 + BATTERY_STATUS = 0x100000 # send battery charge notifications (0x07 or 0x0D) + MOUSE_EXTRA_BUTTONS = 0x080000 + ROLLER_V = 0x040000 + POWER_KEYS = 0x020000 # system control keys such as Sleep + KEYBOARD_MULTIMEDIA_RAW = 0x010000 # consumer controls such as Mute and Calculator + MULTI_TOUCH = 0x001000 # notify on multi-touch changes + SOFTWARE_PRESENT = 0x000800 # software is controlling part of device behaviour + LINK_QUALITY = 0x000400 # notify on link quality changes + UI = 0x000200 # notify on UI changes + WIRELESS = 0x000100 # notify when the device wireless goes on/off-line + CONFIGURATION_COMPLETE = 0x000004 + VOIP_TELEPHONY = 0x000002 + THREED_GESTURE = 0x000001 + + +def flags_to_str(flags, fallback: str) -> str: + flag_names = [] + if flags is not None and flags is not False: + if flags.value == 0: + flag_names = (fallback,) + else: + flag_names = NotificationFlag.flag_names(flags) + return f"\n{' ':15}".join(sorted(flag_names)) + + +class ErrorCode(IntEnum): + INVALID_SUB_ID_COMMAND = 0x01 + INVALID_ADDRESS = 0x02 + INVALID_VALUE = 0x03 + CONNECTION_REQUEST_FAILED = 0x04 + TOO_MANY_DEVICES = 0x05 + ALREADY_EXISTS = 0x06 + BUSY = 0x07 + UNKNOWN_DEVICE = 0x08 + RESOURCE_ERROR = 0x09 + REQUEST_UNAVAILABLE = 0x0A + UNSUPPORTED_PARAMETER_VALUE = 0x0B + WRONG_PIN_CODE = 0x0C + + +class PairingError(IntEnum): + DEVICE_TIMEOUT = 0x01 + DEVICE_NOT_SUPPORTED = 0x02 + TOO_MANY_DEVICES = 0x03 + SEQUENCE_TIMEOUT = 0x06 + + @property + def label(self) -> str: + return self.name.lower().replace("_", " ") + + +class BoltPairingError(IntEnum): + DEVICE_TIMEOUT = 0x01 + FAILED = 0x02 + + @property + def label(self) -> str: + return self.name.lower().replace("_", " ") + + +class Registers(IntEnum): + """Known HID registers. + + Devices usually have a (small) sub-set of these. Some registers are only + applicable to certain device kinds (e.g. smooth_scroll only applies to mice). + """ + + # Generally applicable + NOTIFICATIONS = 0x00 + FIRMWARE = 0xF1 + + # only apply to receivers + RECEIVER_CONNECTION = 0x02 + RECEIVER_PAIRING = 0xB2 + DEVICES_ACTIVITY = 0x2B3 + RECEIVER_INFO = 0x2B5 + BOLT_DEVICE_DISCOVERY = 0xC0 + BOLT_PAIRING = 0x2C1 + BOLT_UNIQUE_ID = 0x02FB + + # only apply to devices + MOUSE_BUTTON_FLAGS = 0x01 + KEYBOARD_HAND_DETECTION = 0x01 + DEVICES_CONFIGURATION = 0x03 + BATTERY_STATUS = 0x07 + KEYBOARD_FN_SWAP = 0x09 + BATTERY_CHARGE = 0x0D + KEYBOARD_ILLUMINATION = 0x17 + THREE_LEDS = 0x51 + MOUSE_DPI = 0x63 + + # notifications + PASSKEY_REQUEST_NOTIFICATION = 0x4D + PASSKEY_PRESSED_NOTIFICATION = 0x4E + DEVICE_DISCOVERY_NOTIFICATION = 0x4F + DISCOVERY_STATUS_NOTIFICATION = 0x53 + PAIRING_STATUS_NOTIFICATION = 0x54 + + +# Subregisters for receiver_info register +class InfoSubRegisters(IntEnum): + SERIAL_NUMBER = 0x01 # not found on many receivers + FW_VERSION = 0x02 + RECEIVER_INFORMATION = 0x03 + PAIRING_INFORMATION = 0x20 # 0x2N, by connected device + EXTENDED_PAIRING_INFORMATION = 0x30 # 0x3N, by connected device + DEVICE_NAME = 0x40 # 0x4N, by connected device + BOLT_PAIRING_INFORMATION = 0x50 # 0x5N, by connected device + BOLT_DEVICE_NAME = 0x60 # 0x6N01, by connected device + + +class DeviceFeature(IntFlag): + """Features for devices. + + Flags taken from + https://drive.google.com/file/d/0BxbRzx7vEV7eNDBheWY0UHM5dEU/view?usp=sharing + """ + + @classmethod + def flag_names(cls, flag_bits: int) -> List[str]: + """Extract the names of the flags from the integer.""" + indexed = {item.value: item.name for item in cls} + + flag_names = [] + unknown_bits = flag_bits + for k in indexed: + # Ensure that the key (flag value) is a power of 2 (a single bit flag) + assert bin(k).count("1") == 1 + if k & flag_bits == k: + unknown_bits &= ~k + flag_names.append(indexed[k].replace("_", " ").lower()) + + # Yield any remaining unknown bits + if unknown_bits != 0: + flag_names.append(f"unknown:{unknown_bits:06X}") + return flag_names + + RESERVED1 = 0x010000 + SPECIAL_BUTTONS = 0x020000 + ENHANCED_KEY_USAGE = 0x040000 + FAST_FW_REV = 0x080000 + RESERVED2 = 0x100000 + RESERVED3 = 0x200000 + SCROLL_ACCEL = 0x400000 + BUTTONS_CONTROL_RESOLUTION = 0x800000 + INHIBIT_LOCK_KEY_SOUND = 0x000001 + RESERVED4 = 0x000002 + MX_AIR_3D_ENGINE = 0x000004 + HOST_CONTROL_LEDS = 0x000008 + RESERVED5 = 0x000010 + RESERVED6 = 0x000020 + RESERVED7 = 0x000040 + RESERVED8 = 0x000080 diff --git a/lib/logitech_receiver/hidpp20.py b/lib/logitech_receiver/hidpp20.py index e07df110..a377cd44 100644 --- a/lib/logitech_receiver/hidpp20.py +++ b/lib/logitech_receiver/hidpp20.py @@ -1,6 +1,5 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -15,348 +14,390 @@ ## You should have received a copy of the GNU General Public License along ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from __future__ import annotations -# Logitech Unifying Receiver API. +import logging +import socket +import struct +import threading -import threading as _threading +from collections import UserDict +from enum import Flag +from enum import IntEnum +from typing import Any +from typing import Dict +from typing import Generator +from typing import Optional +from typing import Tuple -from logging import DEBUG as _DEBUG -from logging import ERROR as _ERROR -from logging import INFO as _INFO -from logging import WARNING as _WARNING -from logging import getLogger -from struct import pack as _pack -from struct import unpack as _unpack -from typing import List +import yaml +from solaar.i18n import _ +from typing_extensions import Protocol + +from . import centurion as _centurion +from . import common +from . import exceptions +from . import hidpp10_constants from . import special_keys -from .common import BATTERY_APPROX as _BATTERY_APPROX -from .common import FirmwareInfo as _FirmwareInfo -from .common import KwException as _KwException -from .common import NamedInt as _NamedInt -from .common import NamedInts as _NamedInts -from .common import UnsortedNamedInts as _UnsortedNamedInts -from .common import bytes2int as _bytes2int -from .common import int2bytes as _int2bytes +from .centurion_constants import CenturionCoreFeature +from .centurion_constants import resolve_feature +from .common import Battery +from .common import BatteryLevelApproximation +from .common import BatteryStatus +from .common import FirmwareKind +from .common import NamedInt +from .hidpp20_constants import DEVICE_KIND +from .hidpp20_constants import ChargeLevel +from .hidpp20_constants import ChargeType +from .hidpp20_constants import ErrorCode +from .hidpp20_constants import FeatureFlag +from .hidpp20_constants import GestureId +from .hidpp20_constants import ParamId +from .hidpp20_constants import SupportedFeature -_log = getLogger(__name__) -del getLogger +logger = logging.getLogger(__name__) -# -# -# +FixedBytes5 = bytes -# /,/p}' | sort -t= -k2 -# additional features names taken from https://github.com/cvuchener/hidpp and -# https://github.com/Logitech/cpg-docs/tree/master/hidpp20 -"""Possible features available on a Logitech device. - -A particular device might not support all these features, and may support other -unknown features as well. -""" -FEATURE = _NamedInts( - ROOT=0x0000, - FEATURE_SET=0x0001, - FEATURE_INFO=0x0002, - # Common - DEVICE_FW_VERSION=0x0003, - DEVICE_UNIT_ID=0x0004, - DEVICE_NAME=0x0005, - DEVICE_GROUPS=0x0006, - DEVICE_FRIENDLY_NAME=0x0007, - KEEP_ALIVE=0x0008, - CONFIG_CHANGE=0x0020, - CRYPTO_ID=0x0021, - TARGET_SOFTWARE=0x0030, - WIRELESS_SIGNAL_STRENGTH=0x0080, - DFUCONTROL_LEGACY=0x00C0, - DFUCONTROL_UNSIGNED=0x00C1, - DFUCONTROL_SIGNED=0x00C2, - DFUCONTROL=0x00C3, - DFU=0x00D0, - BATTERY_STATUS=0x1000, - BATTERY_VOLTAGE=0x1001, - UNIFIED_BATTERY=0x1004, - CHARGING_CONTROL=0x1010, - LED_CONTROL=0x1300, - GENERIC_TEST=0x1800, - DEVICE_RESET=0x1802, - OOBSTATE=0x1805, - CONFIG_DEVICE_PROPS=0x1806, - CHANGE_HOST=0x1814, - HOSTS_INFO=0x1815, - BACKLIGHT=0x1981, - BACKLIGHT2=0x1982, - BACKLIGHT3=0x1983, - ILLUMINATION=0x1990, - PRESENTER_CONTROL=0x1A00, - SENSOR_3D=0x1A01, - REPROG_CONTROLS=0x1B00, - REPROG_CONTROLS_V2=0x1B01, - REPROG_CONTROLS_V2_2=0x1B02, # LogiOptions 2.10.73 features.xml - REPROG_CONTROLS_V3=0x1B03, - REPROG_CONTROLS_V4=0x1B04, - REPORT_HID_USAGE=0x1BC0, - PERSISTENT_REMAPPABLE_ACTION=0x1C00, - WIRELESS_DEVICE_STATUS=0x1D4B, - REMAINING_PAIRING=0x1DF0, - FIRMWARE_PROPERTIES=0x1F1F, - ADC_MEASUREMENT=0x1F20, - # Mouse - LEFT_RIGHT_SWAP=0x2001, - SWAP_BUTTON_CANCEL=0x2005, - POINTER_AXIS_ORIENTATION=0x2006, - VERTICAL_SCROLLING=0x2100, - SMART_SHIFT=0x2110, - SMART_SHIFT_ENHANCED=0x2111, - HI_RES_SCROLLING=0x2120, - HIRES_WHEEL=0x2121, - LOWRES_WHEEL=0x2130, - THUMB_WHEEL=0x2150, - MOUSE_POINTER=0x2200, - ADJUSTABLE_DPI=0x2201, - POINTER_SPEED=0x2205, - ANGLE_SNAPPING=0x2230, - SURFACE_TUNING=0x2240, - XY_STATS=0x2250, - WHEEL_STATS=0x2251, - HYBRID_TRACKING=0x2400, - # Keyboard - FN_INVERSION=0x40A0, - NEW_FN_INVERSION=0x40A2, - K375S_FN_INVERSION=0x40A3, - ENCRYPTION=0x4100, - LOCK_KEY_STATE=0x4220, - SOLAR_DASHBOARD=0x4301, - KEYBOARD_LAYOUT=0x4520, - KEYBOARD_DISABLE_KEYS=0x4521, - KEYBOARD_DISABLE_BY_USAGE=0x4522, - DUALPLATFORM=0x4530, - MULTIPLATFORM=0x4531, - KEYBOARD_LAYOUT_2=0x4540, - CROWN=0x4600, - # Touchpad - TOUCHPAD_FW_ITEMS=0x6010, - TOUCHPAD_SW_ITEMS=0x6011, - TOUCHPAD_WIN8_FW_ITEMS=0x6012, - TAP_ENABLE=0x6020, - TAP_ENABLE_EXTENDED=0x6021, - CURSOR_BALLISTIC=0x6030, - TOUCHPAD_RESOLUTION=0x6040, - TOUCHPAD_RAW_XY=0x6100, - TOUCHMOUSE_RAW_POINTS=0x6110, - TOUCHMOUSE_6120=0x6120, - GESTURE=0x6500, - GESTURE_2=0x6501, - # Gaming Devices - GKEY=0x8010, - MKEYS=0x8020, - MR=0x8030, - BRIGHTNESS_CONTROL=0x8040, - REPORT_RATE=0x8060, - COLOR_LED_EFFECTS=0x8070, - RGB_EFFECTS=0x8071, - PER_KEY_LIGHTING=0x8080, - PER_KEY_LIGHTING_V2=0x8081, - MODE_STATUS=0x8090, - ONBOARD_PROFILES=0x8100, - MOUSE_BUTTON_SPY=0x8110, - LATENCY_MONITORING=0x8111, - GAMING_ATTACHMENTS=0x8120, - FORCE_FEEDBACK=0x8123, - # Headsets - SIDETONE=0x8300, - EQUALIZER=0x8310, - HEADSET_OUT=0x8320, - # Fake features for Solaar internal use - MOUSE_GESTURE=0xFE00, -) -FEATURE._fallback = lambda x: 'unknown:%04X' % x - -FEATURE_FLAG = _NamedInts(internal=0x20, hidden=0x40, obsolete=0x80) - -DEVICE_KIND = _NamedInts( - keyboard=0x00, remote_control=0x01, numpad=0x02, mouse=0x03, touchpad=0x04, trackball=0x05, presenter=0x06, receiver=0x07 -) - -FIRMWARE_KIND = _NamedInts(Firmware=0x00, Bootloader=0x01, Hardware=0x02, Other=0x03) - -BATTERY_OK = lambda status: status not in (BATTERY_STATUS.invalid_battery, BATTERY_STATUS.thermal_error) - -BATTERY_STATUS = _NamedInts( - discharging=0x00, - recharging=0x01, - almost_full=0x02, - full=0x03, - slow_recharge=0x04, - invalid_battery=0x05, - thermal_error=0x06 -) - -ONBOARD_MODES = _NamedInts(MODE_NO_CHANGE=0x00, MODE_ONBOARD=0x01, MODE_HOST=0x02) - -CHARGE_STATUS = _NamedInts(charging=0x00, full=0x01, not_charging=0x02, error=0x07) - -CHARGE_LEVEL = _NamedInts(average=50, full=90, critical=5) - -CHARGE_TYPE = _NamedInts(standard=0x00, fast=0x01, slow=0x02) - -ERROR = _NamedInts( - unknown=0x01, - invalid_argument=0x02, - out_of_range=0x03, - hardware_error=0x04, - logitech_internal=0x05, - invalid_feature_index=0x06, - invalid_function=0x07, - busy=0x08, - unsupported=0x09 -) - -# -# -# +KIND_MAP = {kind: hidpp10_constants.DEVICE_KIND[str(kind)] for kind in DEVICE_KIND} -class FeatureNotSupported(_KwException): - """Raised when trying to request a feature not supported by the device.""" - pass +class Device(Protocol): + def feature_request(self, feature, function=0x00, *params, no_reply=False) -> Any: + ... + + @property + def features(self) -> Any: + ... + + @property + def _gestures(self) -> Any: + ... + + @property + def _backlight(self) -> Any: + ... + + @property + def _profiles(self) -> Any: + ... -class FeatureCallError(_KwException): - """Raised if the device replied to a feature call with an error.""" - pass +# pfps: Consider adding a class method that sanitizes inputs by removing unknown bits. -# -# -# +class KeyFlag(Flag): + """Capabilities and desired software handling for a control. + + Ref: https://drive.google.com/file/d/10imcbmoxTJ1N510poGdsviEhoFfB_Ua4/view + We treat bytes 4 and 8 of `getCidInfo` as a single bitfield. + """ + + UNUSED_8000 = 0x8000 + UNUSED_4000 = 0x4000 + UNUSED_2000 = 0x2000 + UNUSED_1000 = 0x1000 + RAW_WHEEL = 0x800 + ANALYTICS_KEY_EVENTS = 0x400 + FORCE_RAW_XY = 0x200 + RAW_XY = 0x100 + VIRTUAL = 0x80 + PERSISTENTLY_DIVERTABLE = 0x40 + DIVERTABLE = 0x20 + REPROGRAMMABLE = 0x10 + FN_SENSITIVE = 0x08 + NONSTANDARD = 0x04 + IS_FN = 0x02 + MSE = 0x01 + + +class MappingFlag(Flag): + """Flags describing the reporting method of a control. + + We treat bytes 2 and 5 of `get/setCidReporting` as a single bitfield + """ + + UNUSED_4000 = 0x4000 + UNUSED_1000 = 0x1000 + RAW_WHEEL = 0x400 + UNKNOWN_200 = 0x200 # seen on a Wireless Mouse M510 WPID 4004 + ANALYTICS_KEY_EVENTS_REPORTING = 0x100 + FORCE_RAW_XY_DIVERTED = 0x40 + RAW_XY_DIVERTED = 0x10 + PERSISTENTLY_DIVERTED = 0x04 + DIVERTED = 0x01 + + +class ChargeStatus(Flag): + CHARGING = 0x00 + FULL = 0x01 + NOT_CHARGING = 0x02 + ERROR = 0x07 class FeaturesArray(dict): - def __init__(self, device): assert device is not None - self.supported = True + self.supported = True # Actually don't know whether it is supported yet self.device = device self.inverse = {} + self.sub_inverse = {} self.version = {} + self.flags = {} self.count = 0 - def _check(self): - if self.supported is False or not self.device.online: + def _check(self) -> bool: + if not self.device.online: + return False + if self.supported is False: return False if self.device.protocol and self.device.protocol < 2.0: self.supported = False return False if self.count > 0: return True - reply = self.device.request(0x0000, _pack('!H', FEATURE.FEATURE_SET)) + reply = self.device.request(0x0000, struct.pack("!H", SupportedFeature.FEATURE_SET)) if reply is not None: - fs_index = ord(reply[0:1]) + fs_index = reply[0] if fs_index: count = self.device.request(fs_index << 8) if count is None: - _log.warn('FEATURE_SET found, but failed to read features count') + logger.warning("FEATURE_SET found, but failed to read features count") return False else: - self.count = ord(count[:1]) + 1 # ROOT feature not included in count - self[FEATURE.ROOT] = 0 - self[FEATURE.FEATURE_SET] = fs_index + self[SupportedFeature.ROOT] = 0 + self[SupportedFeature.FEATURE_SET] = fs_index + if getattr(self.device, "centurion", False): + self._check_centurion(fs_index, count) + else: + self.count = count[0] + 1 # ROOT feature not included in count return True else: self.supported = False return False - def get_feature(self, index): + def _check_centurion(self, fs_index, count_response): + """Enumerate features on a Centurion device (parent + sub-device via CentPPBridge). + + Phase A: Enumerate parent device features via CenturionFeatureSet. + Find the CentPPBridge index (feature ID 0x0003 on Centurion = CentPPBridge). + Phase B: Route through CentPPBridge to discover sub-device features. + Use CenturionFeatureSet bulk query to get all sub-device features. + Store sub-device features keyed by SupportedFeature enum. + """ + # Phase A: Parent features + feature_count = count_response[0] # includes ROOT on Centurion + self.count = feature_count + bridge_index = None + for index in range(feature_count): + if self.inverse.get(index) is not None: + continue # already registered (ROOT=0, FEATURE_SET=fs_index) + response = self.device.request((fs_index << 8) | 0x10, index) + if response is None or len(response) < 3: + continue + # Centurion FeatureSet response: [remaining_count, feat_hi, feat_lo, type, flags] + feat_id = struct.unpack("!H", response[1:3])[0] + feature = resolve_feature(feat_id, centurion=True) + if feature is None: + feature = f"unknown:{feat_id:04X}" + self[feature] = index + self.inverse[index] = feature + if feature is CenturionCoreFeature.CENT_PP_BRIDGE: + bridge_index = index + + if bridge_index is not None: + self.device._centurion_bridge_index = bridge_index + self.device._centurion_sub_features = set() + self.device._centurion_sub_indices = {} + self._discover_sub_device_features(bridge_index) + + def _discover_sub_device_features(self, bridge_index): + """Phase B: Discover sub-device features via CentPPBridge. + + Uses CenturionFeatureSet bulk query (function 1, index 0) routed through + the bridge to get all sub-device features at once. + """ + # First, find the sub-device's FeatureSet index via CenturionRoot (sub_feat_idx=0) + # Query: CenturionRoot.GetFeature(0x0001) to find FeatureSet index on sub-device + fs_id_hi = (SupportedFeature.FEATURE_SET >> 8) & 0xFF + fs_id_lo = SupportedFeature.FEATURE_SET & 0xFF + response = self.device.centurion_bridge_request(0x00, 0x00, fs_id_hi, fs_id_lo) + if response is None or len(response) < 1: + logger.warning("Failed to find FeatureSet on Centurion sub-device") + return + sub_fs_index = response[0] + if sub_fs_index == 0: + logger.warning("Sub-device FeatureSet not found (index=0)") + return + + # Bulk enumerate: CenturionFeatureSet.GetFeatureId(func=1=0x10, start_index=0) + # Response: [count, (feat_hi, feat_lo, type, flags) × count] + response = self.device.centurion_bridge_request(sub_fs_index, 0x10, 0x00) + if response is None or len(response) < 1: + logger.warning("Failed to enumerate sub-device features") + return + + entry_count = response[0] + entries = response[1:] + sub_feat_idx = 0 # sub-device feature indices start at 0 + for i in range(entry_count): + offset = i * 4 + if offset + 2 > len(entries): + break + feat_id = struct.unpack("!H", entries[offset : offset + 2])[0] + try: + feature = SupportedFeature(feat_id) + except ValueError: + feature = f"unknown:{feat_id:04X}" + # Store sub-device index for ALL features (including parent overlaps) + # This enables querying the sub-device's copy of shared features via bridge + self.device._centurion_sub_indices[feature] = sub_feat_idx + # Only store unique sub-device features in dict (skip parent overlaps like ROOT, FEATURE_SET) + # This avoids clobbering parent inverse entries via __setitem__ + if dict.get(self, feature) is None: + dict.__setitem__(self, feature, sub_feat_idx) + self.device._centurion_sub_features.add(feature) + # Always store in sub_inverse for sub-device enumerate/display + self.sub_inverse[sub_feat_idx] = feature + if logger.isEnabledFor(logging.DEBUG): + logger.debug("Centurion sub-device feature: %s at sub-index %d", feature, sub_feat_idx) + sub_feat_idx += 1 + self._sub_feature_count = sub_feat_idx + + def get_feature(self, index: int) -> SupportedFeature | None: feature = self.inverse.get(index) if feature is not None: return feature elif self._check(): - response = self.device.feature_request(FEATURE.FEATURE_SET, 0x10, index) + feature = self.inverse.get(index) + if feature is not None: + return feature + # On Centurion devices, all features are discovered upfront (parent + sub-device) + if getattr(self.device, "centurion", False): + return None + try: + response = self.device.feature_request(SupportedFeature.FEATURE_SET, 0x10, index) + except exceptions.FeatureCallError: + logger.warning("failed to retrieve feature at index %d", index) + return None if response: - feature = FEATURE[_unpack('!H', response[:2])[0]] + data = struct.unpack("!H", response[:2])[0] + try: + feature = SupportedFeature(data) + except ValueError: + feature = f"unknown:{data:04X}" self[feature] = index self.version[feature] = response[3] + self.flags[feature] = response[2] return feature def enumerate(self): # return all features and their index, ordered by index if self._check(): for index in range(self.count): feature = self.get_feature(index) - yield feature, index + if feature is not None: + yield feature, index + # Also yield sub-device features for Centurion devices + sub_count = getattr(self, "_sub_feature_count", 0) + for sub_idx in range(sub_count): + feature = self.sub_inverse.get(sub_idx) + if feature is not None: + yield feature, sub_idx - def get_feature_version(self, feature): + def get_feature_version(self, feature: NamedInt) -> Optional[int]: if self[feature]: return self.version.get(feature, 0) - __bool__ = __nonzero__ = _check + def get_flags(self, feature: NamedInt) -> Optional[int]: + if self[feature]: + return self.flags.get(feature, 0) - def __getitem__(self, feature): + def get_hidden(self, feature: NamedInt) -> Optional[bool]: + if self[feature]: + return self.flags.get(feature, 0) & FeatureFlag.INTERNAL + return True + + def __contains__(self, feature: NamedInt) -> bool: + try: + index = self.__getitem__(feature) + return index is not None and index is not False + except exceptions.FeatureCallError: + return False + + def __getitem__(self, feature: NamedInt) -> Optional[int]: index = super().get(feature) if index is not None: return index elif self._check(): - response = self.device.request(0x0000, _pack('!H', feature)) + index = super().get(feature) + if index is not None: + return index + try: + response = self.device.request(0x0000, struct.pack("!H", feature)) + except exceptions.FeatureCallError: + return None if response: index = response[0] self[feature] = index if index else False self.version[feature] = response[2] + self.flags[feature] = response[1] return index if index else False def __setitem__(self, feature, index): - if type(super().get(feature)) == int: + if isinstance(super().get(feature), int): self.inverse.pop(super().get(feature)) super().__setitem__(feature, index) - if type(index) == int: + if index is not False: self.inverse[index] = feature def __delitem__(self, feature): - if type(super().get(feature)) == int: - self.inverse.pop(super().get(feature)) - super().__delitem__(feature) + raise ValueError("Don't delete features from FeatureArray") - def __contains__(self, feature): # is a feature present - index = self.__getitem__(feature) - return index is not None and index is not False + def __len__(self) -> int: + return self.count + getattr(self, "_sub_feature_count", 0) - def __len__(self): - return self.count + __bool__ = __nonzero__ = _check class ReprogrammableKey: """Information about a control present on a device with the `REPROG_CONTROLS` feature. - Ref: https://drive.google.com/file/d/0BxbRzx7vEV7eU3VfMnRuRXktZ3M/view + Read-only properties: - - index {int} -- index in the control ID table - - key {_NamedInt} -- the name of this control - - default_task {_NamedInt} -- the native function of this control - - flags {List[str]} -- capabilities and desired software handling of the control + - index -- index in the control ID table + - key -- the name of this control + - default_task -- the native function of this control + - flags -- capabilities and desired software handling of the control + + Ref: https://drive.google.com/file/d/0BxbRzx7vEV7eU3VfMnRuRXktZ3M/view """ - def __init__(self, device, index, cid, tid, flags): + def __init__(self, device: Device, index: int, cid: int, task_id: int, flags: int): self._device = device self.index = index self._cid = cid - self._tid = tid + self._tid = task_id self._flags = flags @property - def key(self) -> _NamedInt: + def key(self) -> NamedInt: return special_keys.CONTROL[self._cid] @property - def default_task(self) -> _NamedInt: + def default_task(self) -> NamedInt: """NOTE: This NamedInt is a bit mixed up, because its value is the Control ID while the name is the Control ID's native task. But this makes more sense than presenting details of controls vs tasks in the interface. The same convention applies to `mapped_to`, `remappable_to`, `remap` in `ReprogrammableKeyV4`.""" - task = str(special_keys.TASK[self._tid]) - return _NamedInt(self._cid, task) + try: + task = str(special_keys.Task(self._tid)) + except ValueError: + task = f"unknown:{self._tid:04X}" + return NamedInt(self._cid, task) @property - def flags(self) -> List[str]: - return special_keys.KEY_FLAG.flag_names(self._flags) + def flags(self) -> KeyFlag: + return KeyFlag(self._flags) class ReprogrammableKeyV4(ReprogrammableKey): @@ -371,13 +412,13 @@ class ReprogrammableKeyV4(ReprogrammableKey): - group {int} -- the group this control belongs to; other controls with this group in their `group_mask` can be remapped to this control - group_mask {List[str]} -- this control can be remapped to any control ID in these groups - - mapped_to {_NamedInt} -- which action this control is mapped to; usually itself - - remappable_to {List[_NamedInt]} -- list of actions which this control can be remapped to + - mapped_to {NamedInt} -- which action this control is mapped to; usually itself + - remappable_to {List[NamedInt]} -- list of actions which this control can be remapped to - mapping_flags {List[str]} -- mapping flags set on the control """ - def __init__(self, device, index, cid, tid, flags, pos, group, gmask): - ReprogrammableKey.__init__(self, device, index, cid, tid, flags) + def __init__(self, device: Device, index, cid, task_id, flags, pos, group, gmask): + ReprogrammableKey.__init__(self, device, index, cid, task_id, flags) self.pos = pos self.group = group self._gmask = gmask @@ -385,140 +426,151 @@ class ReprogrammableKeyV4(ReprogrammableKey): self._mapped_to = None @property - def group_mask(self): - return special_keys.CID_GROUP_BIT.flag_names(self._gmask) + def group_mask(self) -> Generator[str]: + return common.flag_names(special_keys.CIDGroupBit, self._gmask) @property - def mapped_to(self) -> _NamedInt: + def mapped_to(self) -> NamedInt: if self._mapped_to is None: self._getCidReporting() self._device.keys._ensure_all_keys_queried() - task = str(special_keys.TASK[self._device.keys.cid_to_tid[self._mapped_to]]) - return _NamedInt(self._mapped_to, task) + try: + task = str(special_keys.Task(self._device.keys.cid_to_tid[self._mapped_to])) + except ValueError: + task = f"Unknown_{self._mapped_to:x}" + return NamedInt(self._mapped_to, task) @property - def remappable_to(self) -> _NamedInts: + def remappable_to(self): self._device.keys._ensure_all_keys_queried() - ret = _UnsortedNamedInts() - if self.group_mask != []: # only keys with a non-zero gmask are remappable + ret = common.UnsortedNamedInts() + if self.group_mask: # only keys with a non-zero gmask are remappable ret[self.default_task] = self.default_task # it should always be possible to map the key to itself for g in self.group_mask: - g = special_keys.CID_GROUP[str(g)] + g = special_keys.CidGroup[str(g)] for tgt_cid in self._device.keys.group_cids[g]: - tgt_task = str(special_keys.TASK[self._device.keys.cid_to_tid[tgt_cid]]) - tgt_task = _NamedInt(tgt_cid, tgt_task) + cid = self._device.keys.cid_to_tid[tgt_cid] + try: + tgt_task = str(special_keys.Task(cid)) + except ValueError: + tgt_task = f"unknown:{cid:04X}" + tgt_task = NamedInt(tgt_cid, tgt_task) if tgt_task != self.default_task: # don't put itself in twice ret[tgt_task] = tgt_task return ret @property - def mapping_flags(self) -> List[str]: + def mapping_flags(self) -> MappingFlag: if self._mapping_flags is None: self._getCidReporting() - return special_keys.MAPPING_FLAG.flag_names(self._mapping_flags) + return MappingFlag(self._mapping_flags) - def set_diverted(self, value: bool): + def set_diverted(self, value: bool) -> None: """If set, the control is diverted temporarily and reports presses as HID++ events.""" - flags = {special_keys.MAPPING_FLAG.diverted: value} + flags = {MappingFlag.DIVERTED: value} self._setCidReporting(flags=flags) - def set_persistently_diverted(self, value: bool): + def set_persistently_diverted(self, value: bool) -> None: """If set, the control is diverted permanently and reports presses as HID++ events.""" - flags = {special_keys.MAPPING_FLAG.persistently_diverted: value} + flags = {MappingFlag.PERSISTENTLY_DIVERTED: value} self._setCidReporting(flags=flags) - def set_rawXY_reporting(self, value: bool): + def set_rawXY_reporting(self, value: bool) -> None: """If set, the mouse temporarily reports all its raw XY events while this control is pressed as HID++ events.""" - flags = {special_keys.MAPPING_FLAG.raw_XY_diverted: value} + flags = {MappingFlag.RAW_XY_DIVERTED: value} self._setCidReporting(flags=flags) - def remap(self, to: _NamedInt): + def remap(self, to: NamedInt): """Temporarily remaps this control to another action.""" self._setCidReporting(remap=int(to)) def _getCidReporting(self): try: - mapped_data = feature_request(self._device, FEATURE.REPROG_CONTROLS_V4, 0x20, *tuple(_pack('!H', self._cid))) + mapped_data = self._device.feature_request( + SupportedFeature.REPROG_CONTROLS_V4, + 0x20, + *tuple(struct.pack("!H", self._cid)), + ) if mapped_data: - cid, mapping_flags_1, mapped_to = _unpack('!HBH', mapped_data[:5]) - if cid != self._cid and _log.isEnabledFor(_WARNING): - _log.warn( - f'REPROG_CONTROLS_V4 endpoint getCidReporting on device {self._device} replied ' + - f'with a different control ID ({cid}) than requested ({self._cid}).' + cid, mapping_flags_1, mapped_to = struct.unpack("!HBH", mapped_data[:5]) + if cid != self._cid and logger.isEnabledFor(logging.WARNING): + logger.warning( + f"REPROG_CONTROLS_V4 endpoint getCidReporting on device {self._device} replied " + + f"with a different control ID ({cid}) than requested ({self._cid})." ) self._mapped_to = mapped_to if mapped_to != 0 else self._cid if len(mapped_data) > 5: - mapping_flags_2, = _unpack('!B', mapped_data[5:6]) + (mapping_flags_2,) = struct.unpack("!B", mapped_data[5:6]) else: mapping_flags_2 = 0 self._mapping_flags = mapping_flags_1 | (mapping_flags_2 << 8) else: - raise FeatureCallError(msg='No reply from device.') - except FeatureCallError: # if the key hasn't ever been configured then the read may fail so only produce a warning - if _log.isEnabledFor(_WARNING): - _log.warn( - f'Feature Call Error in _getCidReporting on device {self._device} for cid {self._cid} - use defaults' + raise exceptions.FeatureCallError(msg="No reply from device.") + except exceptions.FeatureCallError: # if the key hasn't ever been configured only produce a warning + if logger.isEnabledFor(logging.WARNING): + logger.warning( + f"Feature Call Error in _getCidReporting on device {self._device} for cid {self._cid} - use defaults" ) # Clear flags and set mapping target to self self._mapping_flags = 0 self._mapped_to = self._cid - def _setCidReporting(self, flags=None, remap=0): - """Sends a `setCidReporting` request with the given parameters. Raises an exception if the parameters are invalid. - Parameters: - - flags {Dict[_NamedInt,bool]} -- a dictionary of which mapping flags to set/unset - - remap {int} -- which control ID to remap to; or 0 to keep current mapping + def _setCidReporting(self, flags: Dict[NamedInt, bool] = None, remap: int = 0): + """Sends a `setCidReporting` request with the given parameters. + + Raises an exception if the parameters are invalid. + + Parameters + ---------- + flags + A dictionary of which mapping flags to set/unset. + remap + Which control ID to remap to; or 0 to keep current mapping. """ flags = flags if flags else {} # See flake8 B006 - # if special_keys.MAPPING_FLAG.raw_XY_diverted in flags and flags[special_keys.MAPPING_FLAG.raw_XY_diverted]: - # We need diversion to report raw XY, so divert temporarily (since XY reporting is also temporary) - # flags[special_keys.MAPPING_FLAG.diverted] = True - # if special_keys.MAPPING_FLAG.diverted in flags and not flags[special_keys.MAPPING_FLAG.diverted]: - # flags[special_keys.MAPPING_FLAG.raw_XY_diverted] = False - # The capability required to set a given reporting flag. FLAG_TO_CAPABILITY = { - special_keys.MAPPING_FLAG.diverted: special_keys.KEY_FLAG.divertable, - special_keys.MAPPING_FLAG.persistently_diverted: special_keys.KEY_FLAG.persistently_divertable, - special_keys.MAPPING_FLAG.analytics_key_events_reporting: special_keys.KEY_FLAG.analytics_key_events, - special_keys.MAPPING_FLAG.force_raw_XY_diverted: special_keys.KEY_FLAG.force_raw_XY, - special_keys.MAPPING_FLAG.raw_XY_diverted: special_keys.KEY_FLAG.raw_XY + MappingFlag.DIVERTED: KeyFlag.DIVERTABLE, + MappingFlag.PERSISTENTLY_DIVERTED: KeyFlag.PERSISTENTLY_DIVERTABLE, + MappingFlag.ANALYTICS_KEY_EVENTS_REPORTING: KeyFlag.ANALYTICS_KEY_EVENTS, + MappingFlag.FORCE_RAW_XY_DIVERTED: KeyFlag.FORCE_RAW_XY, + MappingFlag.RAW_XY_DIVERTED: KeyFlag.RAW_XY, } bfield = 0 - for f, v in flags.items(): - if v and FLAG_TO_CAPABILITY[f] not in self.flags: - raise FeatureNotSupported( - msg=f'Tried to set mapping flag "{f}" on control "{self.key}" ' + - f'which does not support "{FLAG_TO_CAPABILITY[f]}" on device {self._device}.' + for mapping_flag, activated in flags.items(): + key_flag = FLAG_TO_CAPABILITY[mapping_flag] + if activated and key_flag not in self.flags: + raise exceptions.FeatureNotSupported( + msg=f'Tried to set mapping flag "{mapping_flag}" on control "{self.key}" ' + + f'which does not support "{key_flag}" on device {self._device}.' ) - bfield |= int(f) if v else 0 - bfield |= int(f) << 1 # The 'Xvalid' bit + bfield |= mapping_flag.value if activated else 0 + bfield |= mapping_flag.value << 1 # The 'Xvalid' bit if self._mapping_flags: # update flags if already read - if v: - self._mapping_flags |= int(f) + if activated: + self._mapping_flags |= mapping_flag.value else: - self._mapping_flags &= ~int(f) + self._mapping_flags &= ~mapping_flag.value if remap != 0 and remap not in self.remappable_to: - raise FeatureNotSupported( - msg=f'Tried to remap control "{self.key}" to a control ID {remap} which it is not remappable to ' + - f'on device {self._device}.' + raise exceptions.FeatureNotSupported( + msg=f'Tried to remap control "{self.key}" to a control ID {remap} which it is not remappable to ' + + f"on device {self._device}." ) if remap != 0: # update mapping if changing (even if not already read) self._mapped_to = remap - pkt = tuple(_pack('!HBH', self._cid, bfield & 0xff, remap)) + pkt = tuple(struct.pack("!HBH", self._cid, bfield & 0xFF, remap)) # TODO: to fully support version 4 of REPROG_CONTROLS_V4, append `(bfield >> 8) & 0xff` here. # But older devices might behave oddly given that byte, so we don't send it. - ret = feature_request(self._device, FEATURE.REPROG_CONTROLS_V4, 0x30, *pkt) - if ret is None or _unpack('!BBBBB', ret[:5]) != pkt and _log.isEnabledFor(_DEBUG): - _log.debug(f"REPROG_CONTROLS_v4 setCidReporting on device {self._device} didn't echo request packet.") + ret = self._device.feature_request(SupportedFeature.REPROG_CONTROLS_V4, 0x30, *pkt) + if ret is None or struct.unpack("!BBBBB", ret[:5]) != pkt and logger.isEnabledFor(logging.DEBUG): + logger.debug(f"REPROG_CONTROLS_v4 setCidReporting on device {self._device} didn't echo request packet.") -class PersistentRemappableAction(): - +class PersistentRemappableAction: def __init__(self, device, index, cid, actionId, remapped, modifierMask, cidStatus): self._device = device self.index = index @@ -529,37 +581,37 @@ class PersistentRemappableAction(): self.cidStatus = cidStatus @property - def key(self) -> _NamedInt: + def key(self) -> NamedInt: return special_keys.CONTROL[self._cid] @property - def actionType(self) -> _NamedInt: - return special_keys.ACTIONID[self._actionId] + def actionType(self) -> NamedInt: + return special_keys.ACTIONID[self.actionId] @property def action(self): if self.actionId == special_keys.ACTIONID.Empty: return None elif self.actionId == special_keys.ACTIONID.Key: - return 'Key: ' + str(self.modifiers) + str(self.remapped) + return f"Key: {str(self.modifiers)}{str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Mouse: - return 'Mouse Button: ' + str(self.remapped) + return f"Mouse Button: {str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Xdisp: - return 'X Displacement ' + str(self.remapped) + return f"X Displacement {str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Ydisp: - return 'Y Displacement ' + str(self.remapped) + return f"Y Displacement {str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Vscroll: - return 'Vertical Scroll ' + str(self.remapped) + return f"Vertical Scroll {str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Hscroll: - return 'Horizontal Scroll: ' + str(self.remapped) + return f"Horizontal Scroll: {str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Consumer: - return 'Consumer: ' + str(self.remapped) + return f"Consumer: {str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Internal: - return 'Internal Action ' + str(self.remapped) + return f"Internal Action {str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Internal: - return 'Power ' + str(self.remapped) + return f"Power {str(self.remapped)}" else: - return 'Unknown' + return "Unknown" @property def modifiers(self): @@ -567,18 +619,20 @@ class PersistentRemappableAction(): @property def data_bytes(self): - return _int2bytes(self.actionId, 1) + _int2bytes(self.remapped, 2) + _int2bytes(self._modifierMask, 1) + return ( + common.int2bytes(self.actionId, 1) + common.int2bytes(self.remapped, 2) + common.int2bytes(self._modifierMask, 1) + ) def remap(self, data_bytes): - cid = _int2bytes(self._cid, 2) - if _bytes2int(data_bytes) == special_keys.KEYS_Default: # map back to default - feature_request(self._device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x50, cid, 0xFF) + cid = common.int2bytes(self._cid, 2) + if common.bytes2int(data_bytes) == special_keys.KEYS_Default: # map back to default + self._device.feature_request(SupportedFeature.PERSISTENT_REMAPPABLE_ACTION, 0x50, cid, 0xFF) self._device.remap_keys._query_key(self.index) return self._device.remap_keys.keys[self.index].data_bytes else: - self._actionId, self._code, self._modifierMask = _unpack('!BHB', data_bytes) + self.actionId, self.remapped, self._modifierMask = struct.unpack("!BHB", data_bytes) self.cidStatus = 0x01 - feature_request(self._device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x40, cid, 0xFF, data_bytes) + self._device.feature_request(SupportedFeature.PERSISTENT_REMAPPABLE_ACTION, 0x40, cid, 0xFF, data_bytes) return True @@ -588,46 +642,22 @@ class KeysArray: def __init__(self, device, count, version): assert device is not None self.device = device - self.lock = _threading.Lock() - if FEATURE.REPROG_CONTROLS_V4 in self.device.features: - self.keyversion = FEATURE.REPROG_CONTROLS_V4 - elif FEATURE.REPROG_CONTROLS_V2 in self.device.features: - self.keyversion = FEATURE.REPROG_CONTROLS_V2 + self.lock = threading.Lock() + if SupportedFeature.REPROG_CONTROLS_V4 in self.device.features: + self.keyversion = SupportedFeature.REPROG_CONTROLS_V4 + elif SupportedFeature.REPROG_CONTROLS_V2 in self.device.features: + self.keyversion = SupportedFeature.REPROG_CONTROLS_V2 else: - if _log.isEnabledFor(_ERROR): - _log.error(f'Trying to read keys on device {device} which has no REPROG_CONTROLS(_VX) support.') + if logger.isEnabledFor(logging.ERROR): + logger.error(f"Trying to read keys on device {device} which has no REPROG_CONTROLS(_VX) support.") self.keyversion = None self.keys = [None] * count - def _query_key(self, index: int): - """Queries the device for a given key and stores it in self.keys.""" - if index < 0 or index >= len(self.keys): - raise IndexError(index) - - # TODO: add here additional variants for other REPROG_CONTROLS - if self.keyversion == FEATURE.REPROG_CONTROLS_V2: - keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS_V2, 0x10, index) - if keydata: - cid, tid, flags = _unpack('!HHB', keydata[:5]) - self.keys[index] = ReprogrammableKey(self.device, index, cid, tid, flags) - self.cid_to_tid[cid] = tid - elif self.keyversion == FEATURE.REPROG_CONTROLS_V4: - keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS_V4, 0x10, index) - if keydata: - cid, tid, flags1, pos, group, gmask, flags2 = _unpack('!HHBBBBB', keydata[:9]) - flags = flags1 | (flags2 << 8) - self.keys[index] = ReprogrammableKeyV4(self.device, index, cid, tid, flags, pos, group, gmask) - self.cid_to_tid[cid] = tid - if group != 0: # 0 = does not belong to a group - self.group_cids[special_keys.CID_GROUP[group]].append(cid) - elif _log.isEnabledFor(_WARNING): - _log.warn(f"Key with index {index} was expected to exist but device doesn't report it.") - def _ensure_all_keys_queried(self): """The retrieval of key information is lazy, but for certain functionality we need to know all keys. This function makes sure that's the case.""" with self.lock: # don't want two threads doing this - for (i, k) in enumerate(self.keys): + for i, k in enumerate(self.keys): if k is None: self._query_key(i) @@ -659,9 +689,8 @@ class KeysArray: return len(self.keys) -class KeysArrayV1(KeysArray): - - def __init__(self, device, count, version=1): +class KeysArrayV2(KeysArray): + def __init__(self, device: Device, count, version=1): super().__init__(device, count, version) """The mapping from Control IDs to their native Task IDs. For example, Control "Left Button" is mapped to Task "Left Click". @@ -673,43 +702,41 @@ class KeysArrayV1(KeysArray): self.cid_to_tid = {} """The mapping from Control ID groups to Controls IDs that belong to it. A key k can only be remapped to targets in groups within k.group_mask.""" - self.group_cids = {g: [] for g in special_keys.CID_GROUP} + self.group_cids = {g: [] for g in special_keys.CidGroup} def _query_key(self, index: int): if index < 0 or index >= len(self.keys): raise IndexError(index) - keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS, 0x10, index) + keydata = self.device.feature_request(SupportedFeature.REPROG_CONTROLS, 0x10, index) if keydata: - cid, tid, flags = _unpack('!HHB', keydata[:5]) - self.keys[index] = ReprogrammableKey(self.device, index, cid, tid, flags) - self.cid_to_tid[cid] = tid - elif _log.isEnabledFor(_WARNING): - _log.warn(f"Key with index {index} was expected to exist but device doesn't report it.") + cid, task_id, flags = struct.unpack("!HHB", keydata[:5]) + self.keys[index] = ReprogrammableKey(self.device, index, cid, task_id, flags) + self.cid_to_tid[cid] = task_id + elif logger.isEnabledFor(logging.WARNING): + logger.warning(f"Key with index {index} was expected to exist but device doesn't report it.") -class KeysArrayV4(KeysArrayV1): - +class KeysArrayV4(KeysArrayV2): def __init__(self, device, count): super().__init__(device, count, 4) def _query_key(self, index: int): if index < 0 or index >= len(self.keys): raise IndexError(index) - keydata = feature_request(self.device, FEATURE.REPROG_CONTROLS_V4, 0x10, index) + keydata = self.device.feature_request(SupportedFeature.REPROG_CONTROLS_V4, 0x10, index) if keydata: - cid, tid, flags1, pos, group, gmask, flags2 = _unpack('!HHBBBBB', keydata[:9]) + cid, task_id, flags1, pos, group, gmask, flags2 = struct.unpack("!HHBBBBB", keydata[:9]) flags = flags1 | (flags2 << 8) - self.keys[index] = ReprogrammableKeyV4(self.device, index, cid, tid, flags, pos, group, gmask) - self.cid_to_tid[cid] = tid + self.keys[index] = ReprogrammableKeyV4(self.device, index, cid, task_id, flags, pos, group, gmask) + self.cid_to_tid[cid] = task_id if group != 0: # 0 = does not belong to a group - self.group_cids[special_keys.CID_GROUP[group]].append(cid) - elif _log.isEnabledFor(_WARNING): - _log.warn(f"Key with index {index} was expected to exist but device doesn't report it.") + self.group_cids[special_keys.CidGroup(group)].append(cid) + elif logger.isEnabledFor(logging.WARNING): + logger.warning(f"Key with index {index} was expected to exist but device doesn't report it.") # we are only interested in the current host, so use 0xFF for the host throughout class KeysArrayPersistent(KeysArray): - def __init__(self, device, count): super().__init__(device, count, 5) self._capabilities = None @@ -717,24 +744,27 @@ class KeysArrayPersistent(KeysArray): @property def capabilities(self): if self._capabilities is None and self.device.online: - capabilities = self.device.feature_request(FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x00) - assert capabilities, 'Oops, persistent remappable key capabilities cannot be retrieved!' - self._capabilities = _unpack('!H', capabilities[:2])[0] # flags saying what the mappings are possible + capabilities = self.device.feature_request(SupportedFeature.PERSISTENT_REMAPPABLE_ACTION, 0x00) + assert capabilities, "Oops, persistent remappable key capabilities cannot be retrieved!" + self._capabilities = struct.unpack("!H", capabilities[:2])[0] # flags saying what the mappings are possible return self._capabilities def _query_key(self, index: int): if index < 0 or index >= len(self.keys): raise IndexError(index) - keydata = feature_request(self.device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x20, index, 0xff) + keydata = self.device.feature_request(SupportedFeature.PERSISTENT_REMAPPABLE_ACTION, 0x20, index, 0xFF) if keydata: - key = _unpack('!H', keydata[:2])[0] - try: - mapped_data = feature_request( - self.device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x30, key & 0xff00, key & 0xff, 0xff - ) - if mapped_data: - _ignore, _ignore, actionId, remapped, modifiers, status = _unpack('!HBBHBB', mapped_data[:8]) - except Exception: + key = struct.unpack("!H", keydata[:2])[0] + mapped_data = self.device.feature_request( + SupportedFeature.PERSISTENT_REMAPPABLE_ACTION, + 0x30, + key >> 8, + key & 0xFF, + 0xFF, + ) + if mapped_data: + _ignore, _ignore, actionId, remapped, modifiers, status = struct.unpack("!HBBHBB", mapped_data[:8]) + else: actionId = remapped = modifiers = status = 0 actionId = special_keys.ACTIONID[actionId] if actionId == special_keys.ACTIONID.Key: @@ -742,100 +772,36 @@ class KeysArrayPersistent(KeysArray): elif actionId == special_keys.ACTIONID.Mouse: remapped = special_keys.MOUSE_BUTTONS[remapped] elif actionId == special_keys.ACTIONID.Hscroll: - remapped = special_keys.HORIZONTAL_SCROLL[remapped] + try: + remapped = special_keys.HorizontalScroll(remapped) + except ValueError: + remapped = f"unknown horizontal scroll:{remapped:04X}" elif actionId == special_keys.ACTIONID.Consumer: remapped = special_keys.HID_CONSUMERCODES[remapped] elif actionId == special_keys.ACTIONID.Empty: # purge data from empty value remapped = modifiers = 0 - self.keys[index] = PersistentRemappableAction(self.device, index, key, actionId, remapped, modifiers, status) - elif _log.isEnabledFor(_WARNING): - _log.warn(f"Key with index {index} was expected to exist but device doesn't report it.") - - -# Gesture Ids for feature GESTURE_2 -GESTURE = _NamedInts( - Tap1Finger=1, # task Left_Click - Tap2Finger=2, # task Right_Click - Tap3Finger=3, - Click1Finger=4, # task Left_Click - Click2Finger=5, # task Right_Click - Click3Finger=6, - DoubleTap1Finger=10, - DoubleTap2Finger=11, - DoubleTap3Finger=12, - Track1Finger=20, # action MovePointer - TrackingAcceleration=21, - TapDrag1Finger=30, # action Drag - TapDrag2Finger=31, # action SecondaryDrag - Drag3Finger=32, - TapGestures=33, # group all tap gestures under a single UI setting - FnClickGestureSuppression=34, # suppresses Tap and Edge gestures, toggled by Fn+Click - Scroll1Finger=40, # action ScrollOrPageXY / ScrollHorizontal - Scroll2Finger=41, # action ScrollOrPageXY / ScrollHorizontal - Scroll2FingerHoriz=42, # action ScrollHorizontal - Scroll2FingerVert=43, # action WheelScrolling - Scroll2FingerStateless=44, - NaturalScrolling=45, # affects native HID wheel reporting by gestures, not when diverted - Thumbwheel=46, # action WheelScrolling - VScrollInertia=48, - VScrollBallistics=49, - Swipe2FingerHoriz=50, # action PageScreen - Swipe3FingerHoriz=51, # action PageScreen - Swipe4FingerHoriz=52, # action PageScreen - Swipe3FingerVert=53, - Swipe4FingerVert=54, - LeftEdgeSwipe1Finger=60, - RightEdgeSwipe1Finger=61, - BottomEdgeSwipe1Finger=62, - TopEdgeSwipe1Finger=63, - LeftEdgeSwipe1Finger2=64, # task HorzScrollNoRepeatSet - RightEdgeSwipe1Finger2=65, # task 122 ?? - BottomEdgeSwipe1Finger2=66, # - TopEdgeSwipe1Finger2=67, # task 121 ?? - LeftEdgeSwipe2Finger=70, - RightEdgeSwipe2Finger=71, - BottomEdgeSwipe2Finger=72, - TopEdgeSwipe2Finger=73, - Zoom2Finger=80, # action Zoom - Zoom2FingerPinch=81, # ZoomBtnInSet - Zoom2FingerSpread=82, # ZoomBtnOutSet - Zoom3Finger=83, - Zoom2FingerStateless=84, # action Zoom - TwoFingersPresent=85, - Rotate2Finger=87, - Finger1=90, - Finger2=91, - Finger3=92, - Finger4=93, - Finger5=94, - Finger6=95, - Finger7=96, - Finger8=97, - Finger9=98, - Finger10=99, - DeviceSpecificRawData=100, -) -GESTURE._fallback = lambda x: 'unknown:%04X' % x - -# Param Ids for feature GESTURE_2 -PARAM = _NamedInts( - ExtraCapabilities=1, # not suitable for use - PixelZone=2, # 4 2-byte integers, left, bottom, width, height; pixels - RatioZone=3, # 4 bytes, left, bottom, width, height; unit 1/240 pad size - ScaleFactor=4, # 2-byte integer, with 256 as normal scale -) -PARAM._fallback = lambda x: 'unknown:%04X' % x + self.keys[index] = PersistentRemappableAction( + self.device, + index, + key, + actionId, + remapped, + modifiers, + status, + ) + elif logger.isEnabledFor(logging.WARNING): + logger.warning(f"Key with index {index} was expected to exist but device doesn't report it.") class SubParam: - __slots__ = ('id', 'length', 'minimum', 'maximum', 'widget') + __slots__ = ("id", "length", "minimum", "maximum", "widget") def __init__(self, id, length, minimum=None, maximum=None, widget=None): self.id = id self.length = length self.minimum = minimum if minimum is not None else 0 self.maximum = maximum if maximum is not None else ((1 << 8 * length) - 1) - self.widget = widget if widget is not None else 'Scale' + self.widget = widget if widget is not None else "Scale" def __str__(self): return self.id @@ -844,61 +810,64 @@ class SubParam: return self.id -SUB_PARAM = { # (byte count, minimum, maximum) - PARAM['ExtraCapabilities']: None, # ignore - PARAM['PixelZone']: ( # TODO: replace min and max with the correct values - SubParam('left', 2, 0x0000, 0xFFFF, 'SpinButton'), - SubParam('bottom', 2, 0x0000, 0xFFFF, 'SpinButton'), - SubParam('width', 2, 0x0000, 0xFFFF, 'SpinButton'), - SubParam('height', 2, 0x0000, 0xFFFF, 'SpinButton')), - PARAM['RatioZone']: ( # TODO: replace min and max with the correct values - SubParam('left', 1, 0x00, 0xFF, 'SpinButton'), - SubParam('bottom', 1, 0x00, 0xFF, 'SpinButton'), - SubParam('width', 1, 0x00, 0xFF, 'SpinButton'), - SubParam('height', 1, 0x00, 0xFF, 'SpinButton')), - PARAM['ScaleFactor']: ( - SubParam('scale', 2, 0x002E, 0x01FF, 'Scale'), ) +SUB_PARAM = { # (byte count, minimum, maximum) + ParamId.EXTRA_CAPABILITIES: None, # ignore + ParamId.PIXEL_ZONE: ( # TODO: replace min and max with the correct values + SubParam("left", 2, 0x0000, 0xFFFF, "SpinButton"), + SubParam("bottom", 2, 0x0000, 0xFFFF, "SpinButton"), + SubParam("width", 2, 0x0000, 0xFFFF, "SpinButton"), + SubParam("height", 2, 0x0000, 0xFFFF, "SpinButton"), + ), + ParamId.RATIO_ZONE: ( # TODO: replace min and max with the correct values + SubParam("left", 1, 0x00, 0xFF, "SpinButton"), + SubParam("bottom", 1, 0x00, 0xFF, "SpinButton"), + SubParam("width", 1, 0x00, 0xFF, "SpinButton"), + SubParam("height", 1, 0x00, 0xFF, "SpinButton"), + ), + ParamId.SCALE_FACTOR: (SubParam("scale", 2, 0x002E, 0x01FF, "Scale"),), } -# Spec Ids for feature GESTURE_2 -SPEC = _NamedInts( - DVI_field_width=1, - field_widths=2, - period_unit=3, - resolution=4, - multiplier=5, - sensor_size=6, - finger_width_and_height=7, - finger_major_minor_axis=8, - finger_force=9, - zone=10 -) -SPEC._fallback = lambda x: 'unknown:%04X' % x -# Action Ids for feature GESTURE_2 -ACTION_ID = _NamedInts( - MovePointer=1, - ScrollHorizontal=2, - WheelScrolling=3, - ScrollVertial=4, - ScrollOrPageXY=5, - ScrollOrPageHorizontal=6, - PageScreen=7, - Drag=8, - SecondaryDrag=9, - Zoom=10, - ScrollHorizontalOnly=11, - ScrollVerticalOnly=12 -) -ACTION_ID._fallback = lambda x: 'unknown:%04X' % x +class SpecGesture(IntEnum): + """Spec IDs for feature GESTURE_2.""" + + DVI_FIELD_WIDTH = 1 + FIELD_WIDTHS = 2 + PERIOD_UNIT = 3 + RESOLUTION = 4 + MULTIPLIER = 5 + SENSOR_SIZE = 6 + FINGER_WIDTH_AND_HEIGHT = 7 + FINGER_MAJOR_MINOR_AXIS = 8 + FINGER_FORCE = 9 + ZONE = 10 + + def __str__(self): + return f"{self.name.replace('_', ' ').lower()}" + + +class ActionId(IntEnum): + """Action IDs for feature GESTURE_2.""" + + MOVE_POINTER = 1 + SCROLL_HORIZONTAL = 2 + WHEEL_SCROLLING = 3 + SCROLL_VERTICAL = 4 + SCROLL_OR_PAGE_XY = 5 + SCROLL_OR_PAGE_HORIZONTAL = 6 + PAGE_SCREEN = 7 + DRAG = 8 + SECONDARY_DRAG = 9 + ZOOM = 10 + SCROLL_HORIZONTAL_ONLY = 11 + SCROLL_VERTICAL_ONLY = 12 class Gesture: - def __init__(self, device, low, high, next_index, next_diversion_index): self._device = device self.id = low - self.gesture = GESTURE[low] + self.gesture = GestureId(low) self.can_be_enabled = high & 0x01 self.can_be_diverted = high & 0x02 self.show_in_ui = high & 0x04 @@ -914,18 +883,20 @@ class Gesture: if index is not None: offset = index >> 3 # 8 gestures per byte mask = 0x1 << (index % 8) - return (offset, mask) + return offset, mask else: - return (None, None) + return None, None - enable_offset_mask = lambda gesture: gesture._offset_mask(gesture.index) + def enable_offset_mask(self): + return self._offset_mask(self.index) - diversion_offset_mask = lambda gesture: gesture._offset_mask(gesture.diversion_index) + def diversion_offset_mask(self): + return self._offset_mask(self.diversion_index) def enabled(self): # is the gesture enabled? if self._enabled is None and self.index is not None: offset, mask = self.enable_offset_mask() - result = feature_request(self._device, FEATURE.GESTURE_2, 0x10, offset, 0x01, mask) + result = self._device.feature_request(SupportedFeature.GESTURE_2, 0x10, offset, 0x01, mask) self._enabled = bool(result[0] & mask) if result else None return self._enabled @@ -934,13 +905,15 @@ class Gesture: return None if self.index is not None: offset, mask = self.enable_offset_mask() - reply = feature_request(self._device, FEATURE.GESTURE_2, 0x20, offset, 0x01, mask, mask if enable else 0x00) + reply = self._device.feature_request( + SupportedFeature.GESTURE_2, 0x20, offset, 0x01, mask, mask if enable else 0x00 + ) return reply def diverted(self): # is the gesture diverted? if self._diverted is None and self.diversion_index is not None: offset, mask = self.diversion_offset_mask() - result = feature_request(self._device, FEATURE.GESTURE_2, 0x30, offset, 0x01, mask) + result = self._device.feature_request(SupportedFeature.GESTURE_2, 0x30, offset, 0x01, mask) self._diverted = bool(result[0] & mask) if result else None return self._diverted @@ -949,7 +922,14 @@ class Gesture: return None if self.diversion_index is not None: offset, mask = self.diversion_offset_mask() - reply = feature_request(self._device, FEATURE.GESTURE_2, 0x40, offset, 0x01, mask, mask if diverted else 0x00) + reply = self._device.feature_request( + SupportedFeature.GESTURE_2, + 0x40, + offset, + 0x01, + mask, + mask if diverted else 0x00, + ) return reply def as_int(self): @@ -959,7 +939,7 @@ class Gesture: return self.id def __repr__(self): - return f'' + return f"" # allow a gesture to be used as a settings reader/writer to enable and disable the gesture read = enabled @@ -967,18 +947,15 @@ class Gesture: class Param: - param_index = {} - - def __init__(self, device, low, high): + def __init__(self, device, low: int, high, next_param_index): self._device = device self.id = low - self.param = PARAM[low] + self.param = ParamId(low) self.size = high & 0x0F self.show_in_ui = bool(high & 0x1F) self._value = None self._default_value = None - self.index = Param.param_index.get(device, 0) - Param.param_index[device] = self.index + 1 + self.index = next_param_index @property def sub_params(self): @@ -989,9 +966,9 @@ class Param: return self._value if self._value is not None else self.read() def read(self): # returns the bytes for the parameter - result = feature_request(self._device, FEATURE.GESTURE_2, 0x70, self.index, 0xFF) + result = self._device.feature_request(SupportedFeature.GESTURE_2, 0x70, self.index, 0xFF) if result: - self._value = _bytes2int(result[:self.size]) + self._value = common.bytes2int(result[: self.size]) return self._value @property @@ -1001,14 +978,14 @@ class Param: return self._default_value def _read_default(self): - result = feature_request(self._device, FEATURE.GESTURE_2, 0x60, self.index, 0xFF) + result = self._device.feature_request(SupportedFeature.GESTURE_2, 0x60, self.index, 0xFF) if result: - self._default_value = _bytes2int(result[:self.size]) + self._default_value = common.bytes2int(result[: self.size]) return self._default_value def write(self, bytes): self._value = bytes - return feature_request(self._device, FEATURE.GESTURE_2, 0x80, self.index, bytes, 0xFF) + return self._device.feature_request(SupportedFeature.GESTURE_2, 0x80, self.index, bytes, 0xFF) def __str__(self): return str(self.param) @@ -1018,11 +995,13 @@ class Param: class Spec: - - def __init__(self, device, low, high): + def __init__(self, device, low: int, high): self._device = device self.id = low - self.spec = SPEC[low] + try: + self.spec = SpecGesture(low) + except ValueError: + self.spec = f"unknown:{low:04X}" self.byte_count = high & 0x0F self._value = None @@ -1034,15 +1013,17 @@ class Spec: def read(self): try: - value = feature_request(self._device, FEATURE.GESTURE_2, 0x50, self.id, 0xFF) - except FeatureCallError: # some calls produce an error (notably spec 5 multiplier on K400Plus) - if _log.isEnabledFor(_WARNING): - _log.warn(f'Feature Call Error reading Gesture Spec on device {self._device} for spec {self.id} - use None') + value = self._device.feature_request(SupportedFeature.GESTURE_2, 0x50, self.id, 0xFF) + except exceptions.FeatureCallError: # some calls produce an error (notably spec 5 multiplier on K400Plus) + if logger.isEnabledFor(logging.WARNING): + logger.warning( + f"Feature Call Error reading Gesture Spec on device {self._device} for spec {self.id} - use None" + ) return None - return _bytes2int(value[:self.byte_count]) + return common.bytes2int(value[: self.byte_count]) def __repr__(self): - return f'[{self.spec}={self.value}]' + return f"[{self.spec}={self.value}]" class Gestures: @@ -1057,11 +1038,11 @@ class Gestures: self.params = {} self.specs = {} index = 0 - next_gesture_index = next_divsn_index = 0 + next_gesture_index = next_divsn_index = next_param_index = 0 field_high = 0x00 while field_high != 0x01: # end of fields # retrieve the next eight fields - fields = feature_request(device, FEATURE.GESTURE_2, 0x00, index >> 8, index & 0xFF) + fields = device.feature_request(SupportedFeature.GESTURE_2, 0x00, index >> 8, index & 0xFF) if not fields: break for offset in range(8): @@ -1075,16 +1056,17 @@ class Gestures: next_divsn_index = next_divsn_index if gesture.diversion_index is None else next_divsn_index + 1 self.gestures[gesture.gesture] = gesture elif field_high & 0xF0 == 0x30 or field_high & 0xF0 == 0x20: - param = Param(device, field_low, field_high) + param = Param(device, field_low, field_high, next_param_index) + next_param_index = next_param_index + 1 self.params[param.param] = param elif field_high == 0x04: if field_low != 0x00: - _log.error(f'Unimplemented GESTURE_2 grouping {field_low} {field_high} found.') + logger.error(f"Unimplemented GESTURE_2 grouping {field_low} {field_high} found.") elif field_high & 0xF0 == 0x40: spec = Spec(device, field_low, field_high) self.specs[spec.spec] = spec else: - _log.warn(f'Unimplemented GESTURE_2 field {field_low} {field_high} found.') + logger.warning(f"Unimplemented GESTURE_2 field {field_low} {field_high} found.") index += 1 def gesture(self, gesture): @@ -1092,31 +1074,580 @@ class Gestures: def gesture_enabled(self, gesture): # is the gesture enabled? g = self.gestures.get(gesture, None) - return g.enabled(self.device) if g else None + return g.enabled() if g else None def enable_gesture(self, gesture): g = self.gestures.get(gesture, None) - return g.set(self.device, True) if g else None + return g.set(True) if g else None def disable_gesture(self, gesture): g = self.gestures.get(gesture, None) - return g.set(self.device, False) if g else None + return g.set(False) if g else None def param(self, param): return self.params.get(param, None) def get_param(self, param): g = self.params.get(param, None) - return g.get(self.device) if g else None + return g.read() if g else None def set_param(self, param, value): g = self.params.get(param, None) - return g.set(self.device, value) if g else None + return g.write(value) if g else None -# -# -# +class Backlight: + """Information about the current settings of x1982 Backlight2 v3, but also works for previous versions""" + + def __init__(self, device): + response = device.feature_request(SupportedFeature.BACKLIGHT2, 0x00) + if not response: + raise exceptions.FeatureCallError(msg="No reply from device.") + self.device = device + self.enabled, self.options, supported, effects, self.level, self.dho, self.dhi, self.dpow = struct.unpack( + "> 3) & 0x03 + + def write(self): + self.options = (self.options & 0x07) | (self.mode << 3) + level = self.level if self.mode == 0x3 else 0 + data_bytes = struct.pack(" Button: + behavior = bytes_[0] >> 4 + if behavior == ButtonBehavior.MACRO_EXECUTE or behavior == ButtonBehavior.MACRO_STOP: + sector = ((bytes_[0] & 0x0F) << 8) + bytes_[1] + address = (bytes_[2] << 8) + bytes_[3] + result = cls(behavior=behavior, sector=sector, address=address) + elif behavior == ButtonBehavior.SEND: + try: + mapping_type = ButtonMappingType(bytes_[1]).value + if mapping_type == ButtonMappingType.BUTTON: + value = ButtonButtons[(bytes_[2] << 8) + bytes_[3]] + result = cls(behavior=behavior, type=mapping_type, value=value) + elif mapping_type == ButtonMappingType.MODIFIER_AND_KEY: + modifiers = bytes_[2] + value = ButtonKeys[bytes_[3]] + result = cls(behavior=behavior, type=mapping_type, modifiers=modifiers, value=value) + elif mapping_type == ButtonMappingType.CONSUMER_KEY: + value = ButtonConsumerKeys[(bytes_[2] << 8) + bytes_[3]] + result = cls(behavior=behavior, type=mapping_type, value=value) + elif mapping_type == ButtonMappingType.NO_ACTION: + result = cls(behavior=behavior, type=mapping_type) + except Exception: + pass + elif behavior == ButtonBehavior.FUNCTION: + second_byte = bytes_[1] + try: + btn_func = ButtonFunctions(second_byte).value + except ValueError: + btn_func = second_byte + data = bytes_[3] + result = cls(behavior=behavior, value=btn_func, data=data) + else: + result = cls(behavior=bytes_[0] >> 4, bytes=bytes_) + return result + + def to_bytes(self): + bytes = common.int2bytes(self.behavior << 4, 1) if self.behavior is not None else None + if self.behavior == ButtonBehavior.MACRO_EXECUTE.value or self.behavior == ButtonBehavior.MACRO_STOP.value: + bytes = common.int2bytes((self.behavior << 12) + self.sector, 2) + common.int2bytes(self.address, 2) + elif self.behavior == ButtonBehavior.SEND.value: + bytes += common.int2bytes(self.type, 1) + if self.type == ButtonMappingType.BUTTON: + bytes += common.int2bytes(self.value, 2) + elif self.type == ButtonMappingType.MODIFIER_AND_KEY: + bytes += common.int2bytes(self.modifiers, 1) + bytes += common.int2bytes(self.value, 1) + elif self.type == ButtonMappingType.CONSUMER_KEY: + bytes += common.int2bytes(self.value, 2) + elif self.type == ButtonMappingType.NO_ACTION: + bytes += b"\xff\xff" + elif self.behavior == ButtonBehavior.FUNCTION: + data = common.int2bytes(self.data, 1) if self.data else b"\x00" + bytes += common.int2bytes(self.value, 1) + b"\xff" + data + else: + bytes = self.bytes if self.bytes else b"\xff\xff\xff\xff" + return bytes + + def __repr__(self): + return "%s{%s}" % ( + self.__class__.__name__, + ", ".join([f"{str(key)}:{str(val)}" for key, val in self.__dict__.items()]), + ) + + +yaml.SafeLoader.add_constructor("!Button", Button.from_yaml) +yaml.add_representer(Button, Button.to_yaml) + + +class OnboardProfile: + """A single onboard profile""" + + def __init__(self, **kwargs): + for key, val in kwargs.items(): + setattr(self, key, val) + + @classmethod + def from_yaml(cls, loader, node): + args = loader.construct_mapping(node) + return cls(**args) + + @classmethod + def to_yaml(cls, dumper, data): + return dumper.represent_mapping("!OnboardProfile", data.__dict__) + + @classmethod + def from_bytes(cls, sector, enabled, buttons, gbuttons, bytes): + return cls( + sector=sector, + enabled=enabled, + report_rate=bytes[0], + resolution_default_index=bytes[1], + resolution_shift_index=bytes[2], + resolutions=[struct.unpack(" list[tuple[int, int]]: + """Returns profile headers. + + Returns + ------- + list[tuple[int, int]] + Tuples contain (sector, enabled). + """ + i = 0 + headers = [] + chunk = device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x50, 0, 0, 0, i) + s = 0x00 + if chunk[0:4] == b"\x00\x00\x00\x00" or chunk[0:4] == b"\xff\xff\xff\xff": # look in ROM instead + chunk = device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x50, 0x01, 0, 0, i) + s = 0x01 + while chunk[0:2] != b"\xff\xff": + sector, enabled = struct.unpack("!HB", chunk[0:3]) + headers.append((sector, enabled)) + i += 1 + chunk = device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x50, s, 0, 0, i * 4) + return headers + + @classmethod + def from_device(cls, device): + if not device.online: # wake the device up if necessary + device.ping() + response = device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x00) + memory, profile, _macro = struct.unpack("!BBB", response[0:3]) + if memory != 0x01 or profile > 0x05: + return + count, oob, buttons, sectors, size, shift = struct.unpack("!BBBBHB", response[3:10]) + gbuttons = buttons if (shift & 0x3 == 0x2) else 0 + headers = OnboardProfiles.get_profile_headers(device) + profiles = {} + for i, (sector, enabled) in enumerate(headers, start=1): + profiles[i] = OnboardProfile.from_dev(device, i, sector, size, enabled, buttons, gbuttons) + return cls( + version=OnboardProfilesVersion, + name=device.name, + count=count, + buttons=buttons, + gbuttons=gbuttons, + sectors=sectors, + size=size, + profiles=profiles, + ) + + def to_bytes(self): + bytes = b"" + for i in range(1, len(self.profiles) + 1): + profiles_sector = common.int2bytes(self.profiles[i].sector, 2) + profiles_enabled = common.int2bytes(self.profiles[i].enabled, 1) + bytes += profiles_sector + profiles_enabled + b"\x00" + bytes += b"\xff\xff\x00\x00" # marker after last profile + while len(bytes) < self.size - 2: # leave room for CRC + bytes += b"\xff" + bytes += common.int2bytes(common.crc16(bytes), 2) + return bytes + + @classmethod + def read_sector(cls, dev, sector, s): # doesn't check for valid sector or size + bytes = b"" + o = 0 + while o < s - 15: + chunk = dev.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x50, sector >> 8, sector & 0xFF, o >> 8, o & 0xFF) + bytes += chunk + o += 16 + chunk = dev.feature_request( + SupportedFeature.ONBOARD_PROFILES, + 0x50, + sector >> 8, + sector & 0xFF, + (s - 16) >> 8, + (s - 16) & 0xFF, + ) + bytes += chunk[16 + o - s :] # the last chunk has to be read in an awkward way + return bytes + + @classmethod + def write_sector(cls, device, s, bs): # doesn't check for valid sector or size + rbs = OnboardProfiles.read_sector(device, s, len(bs)) + if rbs[:-2] == bs[:-2]: + return False + device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x60, s >> 8, s & 0xFF, 0, 0, len(bs) >> 8, len(bs) & 0xFF) + o = 0 + while o < len(bs) - 1: + device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x70, bs[o : o + 16]) + o += 16 + device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x80) + return True + + def write(self, device): + try: + written = 1 if OnboardProfiles.write_sector(device, 0, self.to_bytes()) else 0 + except Exception as e: + logger.warning("Exception writing onboard profile control sector") + raise e + for p in self.profiles.values(): + try: + if p.sector >= self.sectors: + raise Exception(f"Sector {p.sector} not a writable sector") + written += 1 if OnboardProfiles.write_sector(device, p.sector, p.to_bytes(self.size)) else 0 + except Exception as e: + logger.warning(f"Exception writing onboard profile sector {p.sector}") + raise e + return written + + def show(self): + print(yaml.dump(self)) + + +yaml.SafeLoader.add_constructor("!OnboardProfiles", OnboardProfiles.from_yaml) +yaml.add_representer(OnboardProfiles, OnboardProfiles.to_yaml) def feature_request(device, feature, function=0x00, *params, no_reply=False): @@ -1126,441 +1657,628 @@ def feature_request(device, feature, function=0x00, *params, no_reply=False): return device.request((feature_index << 8) + (function & 0xFF), *params, no_reply=no_reply) -def get_firmware(device): - """Reads a device's firmware info. +class Hidpp20: + def get_firmware(self, device) -> tuple[common.FirmwareInfo] | None: + """Reads a device's firmware info. - :returns: a list of FirmwareInfo tuples, ordered by firmware layer. - """ - count = feature_request(device, FEATURE.DEVICE_FW_VERSION) - if count: - count = ord(count[:1]) + :returns: a list of FirmwareInfo tuples, ordered by firmware layer. + """ + count = device.feature_request(SupportedFeature.DEVICE_FW_VERSION) + if count: + count = ord(count[:1]) - fw = [] - for index in range(0, count): - fw_info = feature_request(device, FEATURE.DEVICE_FW_VERSION, 0x10, index) - if fw_info: - level = ord(fw_info[:1]) & 0x0F - if level == 0 or level == 1: - name, version_major, version_minor, build = _unpack('!3sBBH', fw_info[1:8]) - version = '%02X.%02X' % (version_major, version_minor) - if build: - version += '.B%04X' % build - extras = fw_info[9:].rstrip(b'\x00') or None - fw_info = _FirmwareInfo(FIRMWARE_KIND[level], name.decode('ascii'), version, extras) - elif level == FIRMWARE_KIND.Hardware: - fw_info = _FirmwareInfo(FIRMWARE_KIND.Hardware, '', str(ord(fw_info[1:2])), None) + fw = [] + for index in range(0, count): + fw_info = device.feature_request(SupportedFeature.DEVICE_FW_VERSION, 0x10, index) + if fw_info: + level = ord(fw_info[:1]) & 0x0F + if level == 0 or level == 1: + name, version_major, version_minor, build = struct.unpack("!3sBBH", fw_info[1:8]) + version = f"{version_major:02X}.{version_minor:02X}" + if build: + version += f".B{build:04X}" + extras = fw_info[9:].rstrip(b"\x00") or None + fw_info = common.FirmwareInfo(FirmwareKind(level), name.decode("ascii"), version, extras) + elif level == FirmwareKind.Hardware: + fw_info = common.FirmwareInfo(FirmwareKind.Hardware, "", str(ord(fw_info[1:2])), None) + else: + fw_info = common.FirmwareInfo(FirmwareKind.Other, "", "", None) + + fw.append(fw_info) + return tuple(fw) + + def get_firmware_centurion(self, device): + return _centurion.get_firmware_centurion(device) + + def get_serial_centurion(self, device): + return _centurion.get_serial_centurion(device) + + def get_hardware_info_centurion(self, device): + return _centurion.get_hardware_info_centurion(device) + + def _centurion_sub_device_info_request(self, device, function=0x00, *params): + return _centurion._centurion_sub_device_info_request(device, function, *params) + + def get_firmware_centurion_sub(self, device): + return _centurion.get_firmware_centurion_sub(device) + + def get_serial_centurion_sub(self, device): + return _centurion.get_serial_centurion_sub(device) + + def get_hardware_info_centurion_sub(self, device): + return _centurion.get_hardware_info_centurion_sub(device) + + def get_ids(self, device): + """Reads a device's ids (unit and model numbers)""" + ids = device.feature_request(SupportedFeature.DEVICE_FW_VERSION) + if ids: + unitId = ids[1:5] + modelId = ids[7:13] + transport_bits = ord(ids[6:7]) + offset = 0 + tid_map = {} + for transport, flag in [("btid", 0x1), ("btleid", 0x02), ("wpid", 0x04), ("usbid", 0x08)]: + if transport_bits & flag: + tid_map[transport] = modelId[offset : offset + 2].hex().upper() + offset = offset + 2 + return unitId.hex().upper(), modelId.hex().upper(), tid_map + + def get_kind(self, device: Device): + """Reads a device's type. + + :see DEVICE_KIND: + :returns: a string describing the device type, or ``None`` if the device is + not available or does not support the ``DEVICE_NAME`` feature. + """ + kind = device.feature_request(SupportedFeature.DEVICE_NAME, 0x20) + if kind: + kind = ord(kind[:1]) + try: + return KIND_MAP[DEVICE_KIND[kind]] + except Exception: + return None + + def get_name(self, device: Device): + """Reads a device's name. + + :returns: a string with the device name, or ``None`` if the device is not + available or does not support the ``DEVICE_NAME`` feature. + """ + name_length = device.feature_request(SupportedFeature.DEVICE_NAME) + if name_length: + name_length = ord(name_length[:1]) + + name = b"" + while len(name) < name_length: + fragment = device.feature_request(SupportedFeature.DEVICE_NAME, 0x10, len(name)) + if fragment: + name += fragment[: name_length - len(name)] else: - fw_info = _FirmwareInfo(FIRMWARE_KIND.Other, '', '', None) + logger.error("failed to read whole name of %s (expected %d chars)", device, name_length) + return None - fw.append(fw_info) - # if _log.isEnabledFor(_DEBUG): - # _log.debug("device %d firmware %s", devnumber, fw_info) - return tuple(fw) + return name.decode("utf-8") + def get_name_centurion(self, device): + return _centurion.get_name_centurion(device) -def get_ids(device): - """Reads a device's ids (unit and model numbers)""" - ids = feature_request(device, FEATURE.DEVICE_FW_VERSION) - if ids: - unitId = ids[1:5] - modelId = ids[7:13] - transport_bits = ord(ids[6:7]) - offset = 0 - tid_map = {} - for transport, flag in [('btid', 0x1), ('btleid', 0x02), ('wpid', 0x04), ('usbid', 0x08)]: - if transport_bits & flag: - tid_map[transport] = modelId[offset:offset + 2].hex().upper() - offset = offset + 2 - return (unitId.hex().upper(), modelId.hex().upper(), tid_map) + def get_friendly_name(self, device: Device): + """Reads a device's friendly name. + :returns: a string with the device name, or ``None`` if the device is not + available or does not support the ``DEVICE_NAME`` feature. + """ + name_length = device.feature_request(SupportedFeature.DEVICE_FRIENDLY_NAME) + if name_length: + name_length = ord(name_length[:1]) -def get_kind(device): - """Reads a device's type. + name = b"" + while len(name) < name_length: + fragment = device.feature_request(SupportedFeature.DEVICE_FRIENDLY_NAME, 0x10, len(name)) + if fragment: + name += fragment[1 : name_length - len(name) + 1] + else: + logger.error("failed to read whole name of %s (expected %d chars)", device, name_length) + return None - :see DEVICE_KIND: - :returns: a string describing the device type, or ``None`` if the device is - not available or does not support the ``DEVICE_NAME`` feature. - """ - kind = feature_request(device, FEATURE.DEVICE_NAME, 0x20) - if kind: - kind = ord(kind[:1]) - # if _log.isEnabledFor(_DEBUG): - # _log.debug("device %d type %d = %s", devnumber, kind, DEVICE_KIND[kind]) - return DEVICE_KIND[kind] + return name.decode("utf-8") + def get_battery_status(self, device: Device): + report = device.feature_request(SupportedFeature.BATTERY_STATUS) + if report: + return decipher_battery_status(report) -def get_name(device): - """Reads a device's name. - - :returns: a string with the device name, or ``None`` if the device is not - available or does not support the ``DEVICE_NAME`` feature. - """ - name_length = feature_request(device, FEATURE.DEVICE_NAME) - if name_length: - name_length = ord(name_length[:1]) - - name = b'' - while len(name) < name_length: - fragment = feature_request(device, FEATURE.DEVICE_NAME, 0x10, len(name)) - if fragment: - name += fragment[:name_length - len(name)] - else: - _log.error('failed to read whole name of %s (expected %d chars)', device, name_length) - return None - - return name.decode('utf-8') - - -def get_friendly_name(device): - """Reads a device's friendly name. - - :returns: a string with the device name, or ``None`` if the device is not - available or does not support the ``DEVICE_NAME`` feature. - """ - name_length = feature_request(device, FEATURE.DEVICE_FRIENDLY_NAME) - if name_length: - name_length = ord(name_length[:1]) - - name = b'' - while len(name) < name_length: - fragment = feature_request(device, FEATURE.DEVICE_FRIENDLY_NAME, 0x10, len(name)) - if fragment: - initial_null = 0 if fragment[0] else 1 # initial null actually seen on a device - name += fragment[initial_null:name_length + initial_null - len(name)] - else: - _log.error('failed to read whole name of %s (expected %d chars)', device, name_length) - return None - - return name.decode('utf-8') - - -def get_battery_status(device): - report = feature_request(device, FEATURE.BATTERY_STATUS) - if report: - return decipher_battery_status(report) - - -def decipher_battery_status(report): - discharge, next, status = _unpack('!BBB', report[:3]) - discharge = None if discharge == 0 else discharge - status = BATTERY_STATUS[status] - if _log.isEnabledFor(_DEBUG): - _log.debug('battery status %s%% charged, next %s%%, status %s', discharge, next, status) - return FEATURE.BATTERY_STATUS, discharge, next, status, None - - -def get_battery_unified(device): - report = feature_request(device, FEATURE.UNIFIED_BATTERY, 0x10) - if report is not None: - return decipher_battery_unified(report) - - -def decipher_battery_unified(report): - discharge, level, status, _ignore = _unpack('!BBBB', report[:4]) - status = BATTERY_STATUS[status] - if _log.isEnabledFor(_DEBUG): - _log.debug('battery unified %s%% charged, level %s, charging %s', discharge, level, status) - level = ( - _BATTERY_APPROX.full if level == 8 # full - else _BATTERY_APPROX.good if level == 4 # good - else _BATTERY_APPROX.low if level == 2 # low - else _BATTERY_APPROX.critical if level == 1 # critical - else _BATTERY_APPROX.empty - ) - return FEATURE.UNIFIED_BATTERY, discharge if discharge else level, None, status, None - - -# voltage to remaining charge from Logitech -battery_voltage_remaining = ( - (4186, 100), - (4067, 90), - (3989, 80), - (3922, 70), - (3859, 60), - (3811, 50), - (3778, 40), - (3751, 30), - (3717, 20), - (3671, 10), - (3646, 5), - (3579, 2), - (3500, 0), - (-1000, 0), -) - - -def get_battery_voltage(device): - report = feature_request(device, FEATURE.BATTERY_VOLTAGE) - if report is not None: - return decipher_battery_voltage(report) - - -def decipher_battery_voltage(report): - voltage, flags = _unpack('>HB', report[:3]) - status = BATTERY_STATUS.discharging - charge_sts = ERROR.unknown - charge_lvl = CHARGE_LEVEL.average - charge_type = CHARGE_TYPE.standard - if flags & (1 << 7): - status = BATTERY_STATUS.recharging - charge_sts = CHARGE_STATUS[flags & 0x03] - if charge_sts is None: - charge_sts = ERROR.unknown - elif charge_sts == CHARGE_STATUS.full: - charge_lvl = CHARGE_LEVEL.full - status = BATTERY_STATUS.full - if (flags & (1 << 3)): - charge_type = CHARGE_TYPE.fast - elif (flags & (1 << 4)): - charge_type = CHARGE_TYPE.slow - status = BATTERY_STATUS.slow_recharge - elif (flags & (1 << 5)): - charge_lvl = CHARGE_LEVEL.critical - for level in battery_voltage_remaining: - if level[0] < voltage: - charge_lvl = level[1] - break - if _log.isEnabledFor(_DEBUG): - _log.debug( - 'battery voltage %d mV, charging %s, status %d = %s, level %s, type %s', voltage, status, (flags & 0x03), - charge_sts, charge_lvl, charge_type - ) - return FEATURE.BATTERY_VOLTAGE, charge_lvl, None, status, voltage - - -def get_adc_measurement(device): - try: # this feature call produces an error for headsets that are connected but inactive - report = feature_request(device, FEATURE.ADC_MEASUREMENT) + def get_battery_unified(self, device: Device): + report = device.feature_request(SupportedFeature.UNIFIED_BATTERY, 0x10) if report is not None: - return decipher_adc_measurement(report) - except FeatureCallError: - return FEATURE.ADC_MEASUREMENT if FEATURE.ADC_MEASUREMENT in device.features else None + return decipher_battery_unified(report) + def get_battery_voltage(self, device: Device): + report = device.feature_request(SupportedFeature.BATTERY_VOLTAGE) + if report is not None: + return decipher_battery_voltage(report) -def decipher_adc_measurement(report): - # partial implementation - needs mapping to levels - adc, flags = _unpack('!HB', report[:3]) - for level in battery_voltage_remaining: - if level[0] < adc: - charge_level = level[1] - break - if flags & 0x01: - status = BATTERY_STATUS.recharging if flags & 0x02 else BATTERY_STATUS.discharging - return FEATURE.ADC_MEASUREMENT, charge_level, None, status, adc + def get_adc_measurement(self, device: Device): + try: # this feature call produces an error for headsets that are connected but inactive + report = device.feature_request(SupportedFeature.ADC_MEASUREMENT) + if report is not None: + return decipher_adc_measurement(report) + except exceptions.FeatureCallError: + return SupportedFeature.ADC_MEASUREMENT if SupportedFeature.ADC_MEASUREMENT in device.features else None + + def get_battery_centurion(self, device: Device): + return _centurion.get_battery_centurion(device) + + def get_battery(self, device, feature): + """Return battery information - feature, approximate level, next, charging, voltage + or battery feature if there is one but it is not responding or None for no battery feature""" + + if feature is not None: + battery_function = battery_functions.get(feature, None) + if battery_function: + result = battery_function(self, device) + if result: + return result + else: + for battery_function in battery_functions.values(): + result = battery_function(self, device) + if result: + return result + return 0 + + def get_keys(self, device: Device): + # TODO: add here additional variants for other REPROG_CONTROLS + count = None + if device.features and SupportedFeature.REPROG_CONTROLS_V2 in device.features: + count = device.feature_request(SupportedFeature.REPROG_CONTROLS_V2) + return KeysArrayV2(device, ord(count[:1])) + elif device.features and SupportedFeature.REPROG_CONTROLS_V4 in device.features: + count = device.feature_request(SupportedFeature.REPROG_CONTROLS_V4) + return KeysArrayV4(device, ord(count[:1])) + return None + + def get_remap_keys(self, device: Device): + count = device.feature_request(SupportedFeature.PERSISTENT_REMAPPABLE_ACTION, 0x10) + if count: + return KeysArrayPersistent(device, ord(count[:1])) + + def get_gestures(self, device: Device): + if getattr(device, "_gestures", None) is not None: + return device._gestures + if SupportedFeature.GESTURE_2 in device.features: + return Gestures(device) + + def get_backlight(self, device: Device): + if getattr(device, "_backlight", None) is not None: + return device._backlight + if SupportedFeature.BACKLIGHT2 in device.features: + return Backlight(device) + + def get_force_buttons(self, device: Device): + if getattr(device, "_force_buttons", None) is not None: + return device._force_buttons + if SupportedFeature.FORCE_SENSING_BUTTON in device.features: + return ForceSensingButtonArray(device) + + def get_profiles(self, device: Device): + if getattr(device, "_profiles", None) is not None: + return device._profiles + if SupportedFeature.ONBOARD_PROFILES in device.features: + return OnboardProfiles.from_device(device) + + def get_mouse_pointer_info(self, device: Device): + pointer_info = device.feature_request(SupportedFeature.MOUSE_POINTER) + if pointer_info: + dpi, flags = struct.unpack("!HB", pointer_info[:3]) + acceleration = ("none", "low", "med", "high")[flags & 0x3] + suggest_os_ballistics = (flags & 0x04) != 0 + suggest_vertical_orientation = (flags & 0x08) != 0 + return { + "dpi": dpi, + "acceleration": acceleration, + "suggest_os_ballistics": suggest_os_ballistics, + "suggest_vertical_orientation": suggest_vertical_orientation, + } + + def get_vertical_scrolling_info(self, device: Device): + vertical_scrolling_info = device.feature_request(SupportedFeature.VERTICAL_SCROLLING) + if vertical_scrolling_info: + roller, ratchet, lines = struct.unpack("!BBB", vertical_scrolling_info[:3]) + roller_type = ( + "reserved", + "standard", + "reserved", + "3G", + "micro", + "normal touch pad", + "inverted touch pad", + "reserved", + )[roller] + return {"roller": roller_type, "ratchet": ratchet, "lines": lines} + + def get_hi_res_scrolling_info(self, device: Device): + hi_res_scrolling_info = device.feature_request(SupportedFeature.HI_RES_SCROLLING) + if hi_res_scrolling_info: + mode, resolution = struct.unpack("!BB", hi_res_scrolling_info[:2]) + return mode, resolution + + def get_pointer_speed_info(self, device: Device): + pointer_speed_info = device.feature_request(SupportedFeature.POINTER_SPEED) + if pointer_speed_info: + pointer_speed_hi, pointer_speed_lo = struct.unpack("!BB", pointer_speed_info[:2]) + # if pointer_speed_lo > 0: + # pointer_speed_lo = pointer_speed_lo + return pointer_speed_hi + pointer_speed_lo / 256 + + def get_lowres_wheel_status(self, device: Device): + lowres_wheel_status = device.feature_request(SupportedFeature.LOWRES_WHEEL) + if lowres_wheel_status: + wheel_flag = struct.unpack("!B", lowres_wheel_status[:1])[0] + wheel_reporting = ("HID", "HID++")[wheel_flag & 0x01] + return wheel_reporting + + def get_hires_wheel(self, device: Device): + caps = device.feature_request(SupportedFeature.HIRES_WHEEL, 0x00) + mode = device.feature_request(SupportedFeature.HIRES_WHEEL, 0x10) + ratchet = device.feature_request(SupportedFeature.HIRES_WHEEL, 0x030) + + if caps and mode and ratchet: + # Parse caps + multi, flags = struct.unpack("!BB", caps[:2]) + + has_invert = (flags & 0x08) != 0 + has_ratchet = (flags & 0x04) != 0 + + # Parse mode + wheel_mode, reserved = struct.unpack("!BB", mode[:2]) + + target = (wheel_mode & 0x01) != 0 + res = (wheel_mode & 0x02) != 0 + inv = (wheel_mode & 0x04) != 0 + + # Parse Ratchet switch + ratchet_mode, reserved = struct.unpack("!BB", ratchet[:2]) + + ratchet = (ratchet_mode & 0x01) != 0 + + return multi, has_invert, has_ratchet, inv, res, target, ratchet + + def get_new_fn_inversion(self, device: Device): + state = device.feature_request(SupportedFeature.NEW_FN_INVERSION, 0x00) + if state: + inverted, default_inverted = struct.unpack("!BB", state[:2]) + inverted = (inverted & 0x01) != 0 + default_inverted = (default_inverted & 0x01) != 0 + return inverted, default_inverted + + def get_host_names(self, device: Device): + state = device.feature_request(SupportedFeature.HOSTS_INFO, 0x00) + host_names = {} + if state: + capability_flags, _ignore, numHosts, currentHost = struct.unpack("!BBBB", state[:4]) + if capability_flags & 0x01: # device can get host names + for host in range(0, numHosts): + hostinfo = device.feature_request(SupportedFeature.HOSTS_INFO, 0x10, host) + _ignore, status, _ignore, _ignore, nameLen, _ignore = struct.unpack("!BBBBBB", hostinfo[:6]) + name = "" + remaining = nameLen + while remaining > 0: + name_piece = device.feature_request(SupportedFeature.HOSTS_INFO, 0x30, host, nameLen - remaining) + if name_piece: + name += name_piece[2 : 2 + min(remaining, 14)].decode() + remaining = max(0, remaining - 14) + else: + remaining = 0 + host_names[host] = (bool(status), name) + if host_names: # update the current host's name if it doesn't match the system name + hostname = socket.gethostname().partition(".")[0] + if host_names[currentHost][1] != hostname: + self.set_host_name(device, hostname, host_names[currentHost][1]) + host_names[currentHost] = (host_names[currentHost][0], hostname) + return host_names + + def set_host_name(self, device: Device, name, currentName=""): + name = bytearray(name, "utf-8") + currentName = bytearray(currentName, "utf-8") + if logger.isEnabledFor(logging.INFO): + logger.info("Setting host name to %s", name) + state = device.feature_request(SupportedFeature.HOSTS_INFO, 0x00) + if state: + flags, _ignore, _ignore, currentHost = struct.unpack("!BBBB", state[:4]) + if flags & 0x02: + hostinfo = device.feature_request(SupportedFeature.HOSTS_INFO, 0x10, currentHost) + _ignore, _ignore, _ignore, _ignore, _ignore, maxNameLen = struct.unpack("!BBBBBB", hostinfo[:6]) + if name[:maxNameLen] == currentName[:maxNameLen] and False: + return True + length = min(maxNameLen, len(name)) + chunk = 0 + while chunk < length: + response = device.feature_request( + SupportedFeature.HOSTS_INFO, 0x40, currentHost, chunk, name[chunk : chunk + 14] + ) + if not response: + return False + chunk += 14 + return True + + def get_onboard_mode(self, device: Device): + state = device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x20) + + if state: + mode = struct.unpack("!B", state[:1])[0] + return mode + + def set_onboard_mode(self, device: Device, mode): + state = device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x10, mode) + return state + + def get_polling_rate(self, device: Device): + state = device.feature_request(SupportedFeature.REPORT_RATE, 0x10) + if state: + rate = struct.unpack("!B", state[:1])[0] + return f"{str(rate)}ms" + else: + rates = ["8ms", "4ms", "2ms", "1ms", "500us", "250us", "125us"] + state = device.feature_request(SupportedFeature.EXTENDED_ADJUSTABLE_REPORT_RATE, 0x20) + if state: + rate = struct.unpack("!B", state[:1])[0] + return rates[rate] + + def get_remaining_pairing(self, device: Device): + result = device.feature_request(SupportedFeature.REMAINING_PAIRING, 0x0) + if result: + result = struct.unpack("!B", result[:1])[0] + SupportedFeature._fallback = lambda x: f"unknown:{x:04X}" + return result + + def get_keyboard_layout(self, device: Device): + """Return the device's keyboard layout country code, or None. + + Country code semantics match the HID HUT keyboard country codes that + Logitech's KEYBOARD_LAYOUT_2 (0x4540) feature reports in the first byte. + Used by the per-key painter to pick the matching regional layout. + """ + result = device.feature_request(SupportedFeature.KEYBOARD_LAYOUT_2, 0x00) + if result: + return struct.unpack("!B", result[:1])[0] + return None + + def config_change(self, device: Device, configuration, no_reply=False): + return device.feature_request(SupportedFeature.CONFIG_CHANGE, 0x10, configuration, no_reply=no_reply) battery_functions = { - FEATURE.BATTERY_STATUS: get_battery_status, - FEATURE.BATTERY_VOLTAGE: get_battery_voltage, - FEATURE.UNIFIED_BATTERY: get_battery_unified, - FEATURE.ADC_MEASUREMENT: get_adc_measurement, + SupportedFeature.BATTERY_STATUS: Hidpp20.get_battery_status, + SupportedFeature.BATTERY_VOLTAGE: Hidpp20.get_battery_voltage, + SupportedFeature.UNIFIED_BATTERY: Hidpp20.get_battery_unified, + SupportedFeature.ADC_MEASUREMENT: Hidpp20.get_adc_measurement, + SupportedFeature.CENTURION_BATTERY_SOC: Hidpp20.get_battery_centurion, } -def get_battery(device, feature): - """Return battery information - feature, approximate level, next, charging, voltage - or battery feature if there is one but it is not responding or None for no battery feature""" - if feature is not None: - battery_function = battery_functions.get(feature, None) - if battery_function: - result = battery_function(device) - if result: - return result +def decipher_battery_status(report: FixedBytes5) -> Tuple[Any, Battery]: + battery_discharge_level, battery_discharge_next_level, battery_status = struct.unpack("!BBB", report[:3]) + if battery_discharge_level == 0: + battery_discharge_level = None + try: + status = BatteryStatus(battery_status) + except ValueError: + status = None + logger.debug(f"Unknown battery status byte 0x{battery_status:02X}") + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + "battery status %s%% charged, next %s%%, status %s", battery_discharge_level, battery_discharge_next_level, status + ) + return SupportedFeature.BATTERY_STATUS, Battery(battery_discharge_level, battery_discharge_next_level, status, None) + + +def decipher_battery_voltage(report: bytes): + voltage, flags = struct.unpack(">HB", report[:3]) + status = BatteryStatus.DISCHARGING + charge_sts = ErrorCode.UNKNOWN + charge_lvl = ChargeLevel.AVERAGE + charge_type = ChargeType.STANDARD + if flags & (1 << 7): + status = BatteryStatus.RECHARGING + charge_sts = ChargeStatus(flags & 0x03) + if charge_sts is None: + charge_sts = ErrorCode.UNKNOWN + elif isinstance(charge_sts, ChargeStatus) and ChargeStatus.FULL in charge_sts: + charge_lvl = ChargeLevel.FULL + status = BatteryStatus.FULL + if flags & (1 << 3): + charge_type = ChargeType.FAST + elif flags & (1 << 4): + charge_type = ChargeType.SLOW + status = BatteryStatus.SLOW_RECHARGE + elif flags & (1 << 5): + charge_lvl = ChargeLevel.CRITICAL + charge_level = estimate_battery_level_percentage(voltage) + if charge_level: + charge_lvl = charge_level + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + "battery voltage %d mV, charging %s, status %d = %s, level %s, type %s", + voltage, + status, + (flags & 0x03), + charge_sts, + charge_lvl, + charge_type, + ) + return SupportedFeature.BATTERY_VOLTAGE, Battery(charge_lvl, None, status, voltage) + + +def decipher_battery_unified(report) -> tuple[SupportedFeature, Battery]: + discharge, level, status_byte, _ignore = struct.unpack("!BBBB", report[:4]) + try: + status = BatteryStatus(status_byte) + except ValueError: + status = None + logger.debug(f"Unknown battery status byte 0x{status_byte:02X}") + if logger.isEnabledFor(logging.DEBUG): + logger.debug("battery unified %s%% charged, level %s, charging %s", discharge, level, status) + + if level == 8: + approx_level = BatteryLevelApproximation.FULL + elif level == 4: + approx_level = BatteryLevelApproximation.GOOD + elif level == 2: + approx_level = BatteryLevelApproximation.LOW + elif level == 1: + approx_level = BatteryLevelApproximation.CRITICAL else: - for battery_function in battery_functions.values(): - result = battery_function(device) - if result: - return result + approx_level = BatteryLevelApproximation.EMPTY + + return SupportedFeature.UNIFIED_BATTERY, Battery(discharge if discharge else approx_level, None, status, None) + + +decipher_battery_centurion = _centurion.decipher_battery_centurion + + +def decipher_adc_measurement(report) -> tuple[SupportedFeature, Battery]: + # partial implementation - needs mapping to levels + adc_voltage, flags = struct.unpack("!HB", report[:3]) + charge_level = estimate_battery_level_percentage(adc_voltage) + if flags & 0x01: + status = BatteryStatus.RECHARGING if flags & 0x02 else BatteryStatus.DISCHARGING + return SupportedFeature.ADC_MEASUREMENT, Battery(charge_level, None, status, adc_voltage) + + +def estimate_battery_level_percentage(value_millivolt: int) -> int | None: + """Estimate battery level percentage based on battery voltage. + + Uses linear approximation to estimate the battery level in percent. + + Parameters + ---------- + value_millivolt + Measured battery voltage in millivolt. + """ + battery_voltage_to_percentage = [ + (4186, 100), + (4067, 90), + (3989, 80), + (3922, 70), + (3859, 60), + (3811, 50), + (3778, 40), + (3751, 30), + (3717, 20), + (3671, 10), + (3646, 5), + (3579, 2), + (3500, 0), + ] + + if value_millivolt >= battery_voltage_to_percentage[0][0]: + return battery_voltage_to_percentage[0][1] + if value_millivolt <= battery_voltage_to_percentage[-1][0]: + return battery_voltage_to_percentage[-1][1] + + for i in range(len(battery_voltage_to_percentage) - 1): + v_high, p_high = battery_voltage_to_percentage[i] + v_low, p_low = battery_voltage_to_percentage[i + 1] + if v_low <= value_millivolt <= v_high: + # Linear interpolation + percent = p_low + (p_high - p_low) * (value_millivolt - v_low) / (v_high - v_low) + return round(percent) return 0 -def get_keys(device): - # TODO: add here additional variants for other REPROG_CONTROLS - count = None - if FEATURE.REPROG_CONTROLS_V2 in device.features: - count = feature_request(device, FEATURE.REPROG_CONTROLS_V2) - return KeysArrayV1(device, ord(count[:1])) - elif FEATURE.REPROG_CONTROLS_V4 in device.features: - count = feature_request(device, FEATURE.REPROG_CONTROLS_V4) - return KeysArrayV4(device, ord(count[:1])) - return None +class ForceSensingButton: + """A button that has a force value at which to trigger the button""" + + @classmethod + def create(cls, device, number: int): + buttondata = device.feature_request(SupportedFeature.FORCE_SENSING_BUTTON, 0x10, number) + buttoncurrent = device.feature_request(SupportedFeature.FORCE_SENSING_BUTTON, 0x20, number) + if buttondata is not None and buttoncurrent is not None: + changeable, default, max_value, min_value = struct.unpack("!HHHH", buttondata[:8]) + changeable = changeable & 0x01 + current = struct.unpack("!H", buttoncurrent[:2])[0] + return cls(device, number, changeable, default, max_value, min_value, current) + + def __init__(self, device, number: int, changeable: bool, default: int, max_value: int, min_value: int, current: int): + self._device = device + self.number = number + self.changeable = changeable + self.default = default + self.min_value = min_value + self.max_value = max_value + self._current = current + + def get_current(self) -> int: + return self._current + + def set_current(self, current: int) -> None: + if not self.changeable: + logger.warning(f"FORCE_SENSING_BUTTON on device {self._device} does not allow changing force.") + if self.min_value <= current <= self.max_value: + ret = self._device.feature_request( + SupportedFeature.FORCE_SENSING_BUTTON, 0x30, struct.pack("!BH", self.number, current) + ) + if ret is None and logger.isEnabledFor(logging.DEBUG): + logger.debug(f"FORCE_SENSING_BUTTON setButtonConfig on device {self._device} didn't respond.") + + def acceptable_current(self, value: int) -> bool: + return self.min_value <= value <= self.max_value -def get_remap_keys(device): - count = feature_request(device, FEATURE.PERSISTENT_REMAPPABLE_ACTION, 0x10) - if count: - return KeysArrayPersistent(device, ord(count[:1])) +class ForceSensingButtonArray(UserDict): + """A map of buttons supporting force sensing""" + + def __new__(cls, device: Device): + assert device is not None + count = device.feature_request(SupportedFeature.FORCE_SENSING_BUTTON, 0x00) + if count: + instance = super().__new__(cls) + instance._count = ord(count[:1]) + return instance + + def __init__(self, device: Device): + super().__init__(self) + self.device = device + for index in range(0, self._count): + self[index] = None + + def __getitem__(self, index: int): + item = super().__getitem__(index) + if item is None: + self.query_key(index) + return super().__getitem__(index) + + def query_key(self, index): + if index not in self: + raise IndexError(index) + button = ForceSensingButton.create(self.device, index) + if button: + self[index] = button + return button + + def query(self): + for index in self: + button = ForceSensingButton.create(self.device, index) + if button: + self[index] = button + return self + + # interface for single force button + def get_current(self): + return self[0].get_current() + + def set_current(self, current: int) -> None: + self[0].set_current(current) + + def acceptable(self, value: int) -> bool: + return self[0].acceptable(value) + + def acceptable_current_key(self, index: int, value: int) -> bool: + return self[index].acceptable(value) -def get_gestures(device): - if getattr(device, '_gestures', None) is not None: - return device._gestures - if FEATURE.GESTURE_2 in device.features: - return Gestures(device) - - -def get_mouse_pointer_info(device): - pointer_info = feature_request(device, FEATURE.MOUSE_POINTER) - if pointer_info: - dpi, flags = _unpack('!HB', pointer_info[:3]) - acceleration = ('none', 'low', 'med', 'high')[flags & 0x3] - suggest_os_ballistics = (flags & 0x04) != 0 - suggest_vertical_orientation = (flags & 0x08) != 0 - return { - 'dpi': dpi, - 'acceleration': acceleration, - 'suggest_os_ballistics': suggest_os_ballistics, - 'suggest_vertical_orientation': suggest_vertical_orientation - } - - -def get_vertical_scrolling_info(device): - vertical_scrolling_info = feature_request(device, FEATURE.VERTICAL_SCROLLING) - if vertical_scrolling_info: - roller, ratchet, lines = _unpack('!BBB', vertical_scrolling_info[:3]) - roller_type = ( - 'reserved', 'standard', 'reserved', '3G', 'micro', 'normal touch pad', 'inverted touch pad', 'reserved' - )[roller] - return {'roller': roller_type, 'ratchet': ratchet, 'lines': lines} - - -def get_hi_res_scrolling_info(device): - hi_res_scrolling_info = feature_request(device, FEATURE.HI_RES_SCROLLING) - if hi_res_scrolling_info: - mode, resolution = _unpack('!BB', hi_res_scrolling_info[:2]) - return mode, resolution - - -def get_pointer_speed_info(device): - pointer_speed_info = feature_request(device, FEATURE.POINTER_SPEED) - if pointer_speed_info: - pointer_speed_hi, pointer_speed_lo = _unpack('!BB', pointer_speed_info[:2]) - # if pointer_speed_lo > 0: - # pointer_speed_lo = pointer_speed_lo - return pointer_speed_hi + pointer_speed_lo / 256 - - -def get_lowres_wheel_status(device): - lowres_wheel_status = feature_request(device, FEATURE.LOWRES_WHEEL) - if lowres_wheel_status: - wheel_flag = _unpack('!B', lowres_wheel_status[:1])[0] - wheel_reporting = ('HID', 'HID++')[wheel_flag & 0x01] - return wheel_reporting - - -def get_hires_wheel(device): - caps = feature_request(device, FEATURE.HIRES_WHEEL, 0x00) - mode = feature_request(device, FEATURE.HIRES_WHEEL, 0x10) - ratchet = feature_request(device, FEATURE.HIRES_WHEEL, 0x030) - - if caps and mode and ratchet: - # Parse caps - multi, flags = _unpack('!BB', caps[:2]) - - has_invert = (flags & 0x08) != 0 - has_ratchet = (flags & 0x04) != 0 - - # Parse mode - wheel_mode, reserved = _unpack('!BB', mode[:2]) - - target = (wheel_mode & 0x01) != 0 - res = (wheel_mode & 0x02) != 0 - inv = (wheel_mode & 0x04) != 0 - - # Parse Ratchet switch - ratchet_mode, reserved = _unpack('!BB', ratchet[:2]) - - ratchet = (ratchet_mode & 0x01) != 0 - - return multi, has_invert, has_ratchet, inv, res, target, ratchet - - -def get_new_fn_inversion(device): - state = feature_request(device, FEATURE.NEW_FN_INVERSION, 0x00) - if state: - inverted, default_inverted = _unpack('!BB', state[:2]) - inverted = (inverted & 0x01) != 0 - default_inverted = (default_inverted & 0x01) != 0 - return inverted, default_inverted - - -def get_host_names(device): - state = feature_request(device, FEATURE.HOSTS_INFO, 0x00) - host_names = {} - if state: - capability_flags, _ignore, numHosts, currentHost = _unpack('!BBBB', state[:4]) - if capability_flags & 0x01: # device can get host names - for host in range(0, numHosts): - hostinfo = feature_request(device, FEATURE.HOSTS_INFO, 0x10, host) - _ignore, status, _ignore, _ignore, nameLen, _ignore = _unpack('!BBBBBB', hostinfo[:6]) - name = '' - remaining = nameLen - while remaining > 0: - name_piece = feature_request(device, FEATURE.HOSTS_INFO, 0x30, host, nameLen - remaining) - if name_piece: - name += name_piece[2:2 + min(remaining, 14)].decode() - remaining = max(0, remaining - 14) - else: - remaining = 0 - host_names[host] = (bool(status), name) - # update the current host's name if it doesn't match the system name - import socket - hostname = socket.gethostname().partition('.')[0] - if host_names[currentHost][1] != hostname: - set_host_name(device, hostname, host_names[currentHost][1]) - host_names[currentHost] = (host_names[currentHost][0], hostname) - return host_names - - -def set_host_name(device, name, currentName=''): - name = bytearray(name, 'utf-8') - currentName = bytearray(currentName, 'utf-8') - if _log.isEnabledFor(_INFO): - _log.info('Setting host name to %s', name) - state = feature_request(device, FEATURE.HOSTS_INFO, 0x00) - if state: - flags, _ignore, _ignore, currentHost = _unpack('!BBBB', state[:4]) - if flags & 0x02: - hostinfo = feature_request(device, FEATURE.HOSTS_INFO, 0x10, currentHost) - _ignore, _ignore, _ignore, _ignore, _ignore, maxNameLen = _unpack('!BBBBBB', hostinfo[:6]) - if name[:maxNameLen] == currentName[:maxNameLen] and False: - return True - length = min(maxNameLen, len(name)) - chunk = 0 - while chunk < length: - response = feature_request(device, FEATURE.HOSTS_INFO, 0x40, currentHost, chunk, name[chunk:chunk + 14]) - if not response: - return False - chunk += 14 - return True - - -def get_onboard_mode(device): - state = feature_request(device, FEATURE.ONBOARD_PROFILES, 0x20) - - if state: - mode = _unpack('!B', state[:1])[0] - return mode - - -def set_onboard_mode(device, mode): - state = feature_request(device, FEATURE.ONBOARD_PROFILES, 0x10, mode) - return state - - -def get_polling_rate(device): - state = feature_request(device, FEATURE.REPORT_RATE, 0x10) - if state: - rate = _unpack('!B', state[:1])[0] - return rate - - -def get_remaining_pairing(device): - result = feature_request(device, FEATURE.REMAINING_PAIRING, 0x0) - if result: - result = _unpack('!B', result[:1])[0] - return result - - -def config_change(device, configuration, no_reply=False): - return feature_request(device, FEATURE.CONFIG_CHANGE, 0x00, configuration, no_reply=no_reply) +# --- OnboardEQ (0x0636) — re-exported from onboard_eq.py --- +from .onboard_eq import _build_set_eq_payload # noqa: E402, F401 +from .onboard_eq import get_onboard_eq_info # noqa: E402, F401 +from .onboard_eq import get_onboard_eq_params # noqa: E402, F401 +from .onboard_eq import set_onboard_eq_params # noqa: E402, F401 diff --git a/lib/logitech_receiver/hidpp20_constants.py b/lib/logitech_receiver/hidpp20_constants.py new file mode 100644 index 00000000..be0082c1 --- /dev/null +++ b/lib/logitech_receiver/hidpp20_constants.py @@ -0,0 +1,385 @@ +## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from enum import IntEnum +from enum import IntFlag + +from .common import NamedInts + +# /,/p}' | sort -t= -k2 +# additional features names taken from https://github.com/cvuchener/hidpp and +# https://github.com/Logitech/cpg-docs/tree/master/hidpp20 +"""Possible features available on a Logitech device. + +A particular device might not support all these features, and may support other +unknown features as well. +""" + + +class SupportedFeature(IntEnum): + ROOT = 0x0000 + FEATURE_SET = 0x0001 + FEATURE_INFO = 0x0002 + # Common + DEVICE_FW_VERSION = 0x0003 + DEVICE_UNIT_ID = 0x0004 + DEVICE_NAME = 0x0005 + DEVICE_GROUPS = 0x0006 + DEVICE_FRIENDLY_NAME = 0x0007 + KEEP_ALIVE = 0x0008 + PROPERTY_ACCESS = 0x0011 + CONFIG_CHANGE = 0x0020 + CRYPTO_ID = 0x0021 + TARGET_SOFTWARE = 0x0030 + WIRELESS_SIGNAL_STRENGTH = 0x0080 + DFUCONTROL_LEGACY = 0x00C0 + DFUCONTROL_UNSIGNED = 0x00C1 + DFUCONTROL_SIGNED = 0x00C2 + DFUCONTROL = 0x00C3 + DFU = 0x00D0 + BATTERY_STATUS = 0x1000 + BATTERY_VOLTAGE = 0x1001 + UNIFIED_BATTERY = 0x1004 + CHARGING_CONTROL = 0x1010 + LED_CONTROL = 0x1300 + FORCE_PAIRING = 0x1500 + GENERIC_TEST = 0x1800 + DEVICE_RESET = 0x1802 + OOBSTATE = 0x1805 + CONFIG_DEVICE_PROPS = 0x1806 + CHANGE_HOST = 0x1814 + HOSTS_INFO = 0x1815 + BLE_PRO_PRE_PAIRING = 0x1816 + BACKLIGHT = 0x1981 + BACKLIGHT2 = 0x1982 + BACKLIGHT3 = 0x1983 + ILLUMINATION = 0x1990 + FORCE_SENSING_BUTTON = 0x19C0 + HAPTIC = 0x19B0 + PRESENTER_CONTROL = 0x1A00 + SENSOR_3D = 0x1A01 + REPROG_CONTROLS = 0x1B00 + REPROG_CONTROLS_V2 = 0x1B01 + REPROG_CONTROLS_V2_2 = 0x1B02 # LogiOptions 2.10.73 features.xml + REPROG_CONTROLS_V3 = 0x1B03 + REPROG_CONTROLS_V4 = 0x1B04 + ANALOG_BUTTONS = 0x1B0C # Analog button tuning (actuation point, rapid trigger, haptics) + FULL_KEY_CUSTOMIZATION = 0x1B05 + CONTROL_LIST = 0x1B10 + SWITCH_SWAPABILITY = 0x1B20 + DEVICE_MODE = 0x1B30 + REPORT_HID_USAGE = 0x1BC0 + PERSISTENT_REMAPPABLE_ACTION = 0x1C00 + WIRELESS_DEVICE_STATUS = 0x1D4B + REMAINING_PAIRING = 0x1DF0 + ENABLE_HIDDEN_FEATURES = 0x1E00 + FIRMWARE_PROPERTIES = 0x1F1F + ADC_MEASUREMENT = 0x1F20 + # Mouse + LEFT_RIGHT_SWAP = 0x2001 + SWAP_BUTTON_CANCEL = 0x2005 + POINTER_AXIS_ORIENTATION = 0x2006 + VERTICAL_SCROLLING = 0x2100 + SMART_SHIFT = 0x2110 + SMART_SHIFT_ENHANCED = 0x2111 + HI_RES_SCROLLING = 0x2120 + HIRES_WHEEL = 0x2121 + LOWRES_WHEEL = 0x2130 + THUMB_WHEEL = 0x2150 + MOUSE_POINTER = 0x2200 + ADJUSTABLE_DPI = 0x2201 + EXTENDED_ADJUSTABLE_DPI = 0x2202 + POINTER_SPEED = 0x2205 + ANGLE_SNAPPING = 0x2230 + SURFACE_TUNING = 0x2240 + XY_STATS = 0x2250 + WHEEL_STATS = 0x2251 + HYBRID_TRACKING = 0x2400 + # Keyboard + FN_INVERSION = 0x40A0 + NEW_FN_INVERSION = 0x40A2 + K375S_FN_INVERSION = 0x40A3 + ENCRYPTION = 0x4100 + LOCK_KEY_STATE = 0x4220 + SOLAR_DASHBOARD = 0x4301 + KEYBOARD_LAYOUT = 0x4520 + KEYBOARD_DISABLE_KEYS = 0x4521 + KEYBOARD_DISABLE_BY_USAGE = 0x4522 + KEYBOARD_DISABLE_CONTROLS = 0x4523 + DUALPLATFORM = 0x4530 + MULTIPLATFORM = 0x4531 + KEYBOARD_LAYOUT_2 = 0x4540 + CROWN = 0x4600 + # Touchpad + TOUCHPAD_FW_ITEMS = 0x6010 + TOUCHPAD_SW_ITEMS = 0x6011 + TOUCHPAD_WIN8_FW_ITEMS = 0x6012 + TAP_ENABLE = 0x6020 + TAP_ENABLE_EXTENDED = 0x6021 + CURSOR_BALLISTIC = 0x6030 + TOUCHPAD_RESOLUTION = 0x6040 + TOUCHPAD_RAW_XY = 0x6100 + TOUCHMOUSE_RAW_POINTS = 0x6110 + TOUCHMOUSE_6120 = 0x6120 + GESTURE = 0x6500 + GESTURE_2 = 0x6501 + # Gaming Devices + GKEY = 0x8010 + MKEYS = 0x8020 + MR = 0x8030 + BRIGHTNESS_CONTROL = 0x8040 + LOGI_MODIFIERS = 0x8051 + REPORT_RATE = 0x8060 + EXTENDED_ADJUSTABLE_REPORT_RATE = 0x8061 + COLOR_LED_EFFECTS = 0x8070 + RGB_EFFECTS = 0x8071 + RPM_INDICATOR = 0x807A + RPM_LED_PATTERN = 0x807B + PER_KEY_LIGHTING = 0x8080 + PER_KEY_LIGHTING_V2 = 0x8081 + MODE_STATUS = 0x8090 + LEGACY_AXIS_RESPONSE_CURVE = 0x80A3 + AXIS_RESPONSE_CURVE = 0x80A4 + BANDED_AXIS = 0x80B1 + COMBINED_PEDALS = 0x80D0 + BUNNY_HOPPING = 0x80E0 + ONBOARD_PROFILES = 0x8100 + PROFILE_MANAGEMENT = 0x8101 + MOUSE_BUTTON_SPY = 0x8110 + LATENCY_MONITORING = 0x8111 + GAMING_ATTACHMENTS = 0x8120 + FORCE_FEEDBACK = 0x8123 + DUAL_CLUTCH = 0x8127 + WHEEL_CENTER_POSITION = 0x812C + DISPLAY_GAME_DATA = 0x8130 + CENTER_SPRING = 0x8131 + AXIS_MAPPING = 0x8132 + GLOBAL_DAMPING = 0x8133 + BRAKE_FORCE = 0x8134 + PEDAL_STATUS = 0x8135 + TORQUE_LIMIT = 0x8136 + CONFIGURATION_PROFILES = 0x8137 + OPERATING_RANGE = 0x8138 + TRUE_FORCE = 0x8139 + FFB_FILTER = 0x8140 + # Headsets + SIDETONE = 0x8300 + EQUALIZER = 0x8310 + HEADSET_OUT = 0x8320 + # Centurion core + CENTURION_DEVICE_INFO = 0x0100 + CENTURION_DEVICE_NAME = 0x0101 + CENTURION_ROOT = 0x0102 + CENTURION_MEMFAULT = 0x0103 + CENTURION_BATTERY_SOC = 0x0104 + CENTURION_AUTO_SLEEP = 0x0108 + CENTURION_GENERIC_DFU = 0x010A + CENTURION_LED_BRIGHTNESS = 0x0110 + CENTURION_EU_POWER_MODE = 0x0115 + CENTURION_DEVICE_BOOL_STATE = 0x0116 + # Headsets (Centurion-era) + HEADSET_VOLUME = 0x0200 + HEADSET_EQ = 0x0201 + HEADSET_ADVANCED_PARA_EQ = 0x020D + HEADSET_MIC_TEST = 0x020E + HEADSET_EQ_STYLES = 0x0213 + BT_HOST_INFO = 0x0305 + LIGHTSPEED_PAIRING = 0x0309 + BT_GAMING_MODE = 0x030A + HEADSET_RGB_EFFECTS = 0x0600 + HEADSET_MIC_MUTE = 0x0601 + HEADSET_MIC_SNR = 0x0602 + HEADSET_AUDIO_SIDETONE = 0x0604 + HEADSET_HOST_SWITCH = 0x0607 + HEADSET_MIX = 0x0609 + HEADSET_TONES = 0x060B + HEADSET_NOISE_EXPOSURE = 0x060D + HEADSET_AI_NOISE_REDUCTION = 0x060E + HEADSET_MIC_GAIN = 0x0611 + HEADSET_USAGE_TRACKING = 0x0617 + HEADSET_BATTERY_SAVER = 0x0618 + HEADSET_RGB_HOSTMODE = 0x0620 + HEADSET_RGB_ONBOARD_EFFECTS = 0x0621 + HEADSET_RGB_SIGNATURE_EFFECTS = 0x0622 + HEADSET_DO_NOT_DISTURB = 0x0631 + CENTURION_ONBOARD_PROFILES = 0x0634 + HEADSET_RGB_STREAMING = 0x0635 + HEADSET_ONBOARD_EQ = 0x0636 + # Audio mixing / LogiVoice + MIXER_AUDIO = 0x0800 + MIXER_MIC = 0x0801 + LOGIVOICE = 0x0900 + LOGIVOICE_NOISE_REDUCTION = 0x0901 + LOGIVOICE_NOISE_GATE = 0x0902 + LOGIVOICE_COMPRESSOR = 0x0903 + LOGIVOICE_DE_ESSER = 0x0904 + LOGIVOICE_DE_POPPER = 0x0905 + LOGIVOICE_LIMITER = 0x0906 + LOGIVOICE_HIGH_PASS_FILTER = 0x0907 + LOGIVOICE_EQUALIZER = 0x0908 + LOGIVOICE_AINR = 0x0909 + METERING = 0x0B01 + MIC_GAIN_AUTO_MODE = 0x0B02 + # Fake features for Solaar internal use + MOUSE_GESTURE = 0xFE00 + + def __str__(self): + return self.name.replace("_", " ") + + +class FeatureFlag(IntFlag): + """Single bit flags.""" + + INTERNAL = 0x20 + HIDDEN = 0x40 + OBSOLETE = 0x80 + + +DEVICE_KIND = NamedInts( + keyboard=0x00, + remote_control=0x01, + numpad=0x02, + mouse=0x03, + touchpad=0x04, + trackball=0x05, + presenter=0x06, + receiver=0x07, +) + + +class OnboardMode(IntEnum): + MODE_NO_CHANGE = 0x00 + MODE_ONBOARD = 0x01 + MODE_HOST = 0x02 + + +class ChargeLevel(IntEnum): + AVERAGE = 50 + FULL = 90 + CRITICAL = 5 + + +class ChargeType(IntEnum): + STANDARD = 0x00 + FAST = 0x01 + SLOW = 0x02 + + +class ErrorCode(IntEnum): + UNKNOWN = 0x01 + INVALID_ARGUMENT = 0x02 + OUT_OF_RANGE = 0x03 + HARDWARE_ERROR = 0x04 + LOGITECH_ERROR = 0x05 + INVALID_FEATURE_INDEX = 0x06 + INVALID_FUNCTION = 0x07 + BUSY = 0x08 + UNSUPPORTED = 0x09 + + +class GestureId(IntEnum): + """Gesture IDs for feature GESTURE_2.""" + + TAP_1_FINGER = 1 # task Left_Click + TAP_2_FINGER = 2 # task Right_Click + TAP_3_FINGER = 3 + CLICK_1_FINGER = 4 # task Left_Click + CLICK_2_FINGER = 5 # task Right_Click + CLICK_3_FINGER = 6 + DOUBLE_TAP_1_FINGER = 10 + DOUBLE_TAP_2_FINGER = 11 + DOUBLE_TAP_3_FINGER = 12 + TRACK_1_FINGER = 20 # action MovePointer + TRACKING_ACCELERATION = 21 + TAP_DRAG_1_FINGER = 30 # action Drag + TAP_DRAG_2_FINGER = 31 # action SecondaryDrag + DRAG_3_FINGER = 32 + TAP_GESTURES = 33 # group all tap gestures under a single UI setting + FN_CLICK_GESTURE_SUPPRESSION = 34 # suppresses Tap and Edge gestures, toggled by Fn+Click + SCROLL_1_FINGER = 40 # action ScrollOrPageXY / ScrollHorizontal + SCROLL_2_FINGER = 41 # action ScrollOrPageXY / ScrollHorizontal + SCROLL_2_FINGER_HORIZONTAL = 42 # action ScrollHorizontal + SCROLL_2_FINGER_VERTICAL = 43 # action WheelScrolling + SCROLL_2_FINGER_STATELESS = 44 + NATURAL_SCROLLING = 45 # affects native HID wheel reporting by gestures, not when diverted + THUMBWHEEL = (46,) # action WheelScrolling + V_SCROLL_INTERTIA = 48 + V_SCROLL_BALLISTICS = 49 + SWIPE_2_FINGER_HORIZONTAL = 50 # action PageScreen + SWIPE_3_FINGER_HORIZONTAL = 51 # action PageScreen + SWIPE_4_FINGER_HORIZONTAL = 52 # action PageScreen + SWIPE_3_FINGER_VERTICAL = 53 + SWIPE_4_FINGER_VERTICAL = 54 + LEFT_EDGE_SWIPE_1_FINGER = 60 + RIGHT_EDGE_SWIPE_1_FINGER = 61 + BOTTOM_EDGE_SWIPE_1_FINGER = 62 + TOP_EDGE_SWIPE_1_FINGER = 63 + LEFT_EDGE_SWIPE_1_FINGER_2 = 64 # task HorzScrollNoRepeatSet + RIGHT_EDGE_SWIPE_1_FINGER_2 = 65 + BOTTOM_EDGE_SWIPE_1_FINGER_2 = 66 + TOP_EDGE_SWIPE_1_FINGER_2 = 67 + LEFT_EDGE_SWIPE_2_FINGER = 70 + RIGHT_EDGE_SWIPE_2_FINGER = 71 + BottomEdgeSwipe2Finger = 72 + BOTTOM_EDGE_SWIPE_2_FINGER = 72 + TOP_EDGE_SWIPE_2_FINGER = 73 + ZOOM_2_FINGER = 80 # action Zoom + ZOOM_2_FINGER_PINCH = 81 # ZoomBtnInSet + ZOOM_2_FINGER_SPREAD = 82 # ZoomBtnOutSet + ZOOM_3_FINGER = 83 + ZOOM_2_FINGER_STATELESS = 84 + TWO_FINGERS_PRESENT = 85 + ROTATE_2_FINGER = 87 + FINGER_1 = 90 + FINGER_2 = 91 + FINGER_3 = 92 + FINGER_4 = 93 + FINGER_5 = 94 + FINGER_6 = 95 + FINGER_7 = 96 + FINGER_8 = 97 + FINGER_9 = 98 + FINGER_10 = 99 + DEVICE_SPECIFIC_RAW_DATA = 100 + + +class ParamId(IntEnum): + """Param Ids for feature GESTURE_2""" + + EXTRA_CAPABILITIES = 1 # not suitable for use + PIXEL_ZONE = 2 # 4 2-byte integers, left, bottom, width, height; pixels + RATIO_ZONE = 3 # 4 bytes, left, bottom, width, height; unit 1/240 pad size + SCALE_FACTOR = 4 # 2-byte integer, with 256 as normal scale + + +HapticWaveForms = NamedInts( + SHARP_STATE_CHANGE=0x00, + DAMP_STATE_CHANGE=0x01, + SHARP_COLLISION=0x02, + DAMP_COLLISION=0x03, + SUBTLE_COLLISION=0x04, + HAPPY_ALERT=0x05, + ANGRY_ALERT=0x06, + COMPLETED=0x07, + SQUARE=0x08, + WAVE=0x09, + FIREWORK=0x0A, + MAD=0x0B, + KNOCK=0x0C, + JINGLE=0x0D, + RINGING=0xE, + WHISPER_COLLISION=0x1B, +) diff --git a/lib/logitech_receiver/i18n.py b/lib/logitech_receiver/i18n.py index c2d51cce..c0bedb63 100644 --- a/lib/logitech_receiver/i18n.py +++ b/lib/logitech_receiver/i18n.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -18,70 +16,65 @@ # Translation support for the Logitech receivers library -import gettext as _gettext +import gettext -_ = _gettext.gettext -ngettext = _gettext.ngettext +_ = gettext.gettext +ngettext = gettext.ngettext # A few common strings, not always accessible as such in the code. _DUMMY = ( # approximative battery levels - _('empty'), - _('critical'), - _('low'), - _('average'), - _('good'), - _('full'), - + _("empty"), + _("critical"), + _("low"), + _("average"), + _("good"), + _("full"), # battery charging statuses - _('discharging'), - _('recharging'), - _('charging'), - _('not charging'), - _('almost full'), - _('charged'), - _('slow recharge'), - _('invalid battery'), - _('thermal error'), - _('error'), - _('standard'), - _('fast'), - _('slow'), - + _("discharging"), + _("recharging"), + _("charging"), + _("not charging"), + _("almost full"), + _("charged"), + _("slow recharge"), + _("invalid battery"), + _("thermal error"), + _("error"), + _("standard"), + _("fast"), + _("slow"), # pairing errors - _('device timeout'), - _('device not supported'), - _('too many devices'), - _('sequence timeout'), - + _("device timeout"), + _("device not supported"), + _("too many devices"), + _("sequence timeout"), # firmware kinds - _('Firmware'), - _('Bootloader'), - _('Hardware'), - _('Other'), - + _("Firmware"), + _("Bootloader"), + _("Hardware"), + _("Other"), # common button and task names (from special_keys.py) - _('Left Button'), - _('Right Button'), - _('Middle Button'), - _('Back Button'), - _('Forward Button'), - _('Mouse Gesture Button'), - _('Smart Shift'), - _('DPI Switch'), - _('Left Tilt'), - _('Right Tilt'), - _('Left Click'), - _('Right Click'), - _('Mouse Middle Button'), - _('Mouse Back Button'), - _('Mouse Forward Button'), - _('Gesture Button Navigation'), - _('Mouse Scroll Left Button'), - _('Mouse Scroll Right Button'), - + _("Left Button"), + _("Right Button"), + _("Middle Button"), + _("Back Button"), + _("Forward Button"), + _("Mouse Gesture Button"), + _("Smart Shift"), + _("DPI Switch"), + _("Left Tilt"), + _("Right Tilt"), + _("Left Click"), + _("Right Click"), + _("Mouse Middle Button"), + _("Mouse Back Button"), + _("Mouse Forward Button"), + _("Gesture Button Navigation"), + _("Mouse Scroll Left Button"), + _("Mouse Scroll Right Button"), # key/button statuses - _('pressed'), - _('released'), + _("pressed"), + _("released"), ) diff --git a/lib/logitech_receiver/listener.py b/lib/logitech_receiver/listener.py index 86dc7013..4137afd4 100644 --- a/lib/logitech_receiver/listener.py +++ b/lib/logitech_receiver/listener.py @@ -1,6 +1,5 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -16,37 +15,22 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import threading as _threading +import logging +import queue +import threading -from logging import DEBUG as _DEBUG -from logging import INFO as _INFO -from logging import getLogger +from . import base +from . import exceptions -from . import base as _base - -# from time import time as _timestamp - -# for both Python 2 and 3 -try: - from Queue import Queue as _Queue -except ImportError: - from queue import Queue as _Queue - -_log = getLogger(__name__) -del getLogger - -# -# -# +logger = logging.getLogger(__name__) class _ThreadedHandle: """A thread-local wrapper with different open handles for each thread. - Closing a ThreadedHandle will close all handles. """ - __slots__ = ('path', '_local', '_handles', '_listener') + __slots__ = ("path", "_local", "_handles", "_listener") def __init__(self, listener, path, handle): assert listener is not None @@ -56,18 +40,21 @@ class _ThreadedHandle: self._listener = listener self.path = path - self._local = _threading.local() + self._local = threading.local() # take over the current handle for the thread doing the replacement self._local.handle = handle self._handles = [handle] def _open(self): - handle = _base.open_path(self.path) + handle = base.open_path(self.path) if handle is None: - _log.error('%r failed to open new handle', self) + logger.error("%r failed to open new handle", self) else: - # if _log.isEnabledFor(_DEBUG): - # _log.debug("%r opened new handle %d", self, handle) + # if logger.isEnabledFor(logging.DEBUG): + # logger.debug("%r opened new handle %d", self, handle) + # If original handle was centurion, register new per-thread handle too + if any(h in base._centurion_handles for h in self._handles): + base._centurion_handles.add(handle) self._local.handle = handle self._handles.append(handle) return handle @@ -76,16 +63,16 @@ class _ThreadedHandle: if self._local: self._local = None handles, self._handles = self._handles, [] - if _log.isEnabledFor(_DEBUG): - _log.debug('%r closing %s', self, handles) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%r closing %s", self, handles) for h in handles: - _base.close(h) + base.close(h) @property def notifications_hook(self): if self._listener: - assert isinstance(self._listener, _threading.Thread) - if _threading.current_thread() == self._listener: + assert isinstance(self._listener, threading.Thread) + if threading.current_thread() == self._listener: return self._listener._notifications_hook def __del__(self): @@ -108,7 +95,7 @@ class _ThreadedHandle: return str(int(self)) def __repr__(self): - return '<_ThreadedHandle(%s)>' % self.path + return f"<_ThreadedHandle({self.path})>" def __bool__(self): return bool(self._local) @@ -116,70 +103,60 @@ class _ThreadedHandle: __nonzero__ = __bool__ -# -# -# - -# How long to wait during a read for the next packet, in seconds -# Ideally this should be rather long (10s ?), but the read is blocking -# and this means that when the thread is signalled to stop, it would take -# a while for it to acknowledge it. -# Forcibly closing the file handle on another thread does _not_ interrupt the -# read on Linux systems. -_EVENT_READ_TIMEOUT = 1. # in seconds - -# After this many reads that did not produce a packet, call the tick() method. -# This only happens if tick_period is enabled (>0) for the Listener instance. -# _IDLE_READS = 1 + int(5 // _EVENT_READ_TIMEOUT) # wait at least 5 seconds between ticks +# How long to wait during a read for the next packet, in seconds. +# Ideally this should be rather long (10s ?), but the read is blocking and this means that when the thread +# is signalled to stop, it would take a while for it to acknowledge it. +# Forcibly closing the file handle on another thread does _not_ interrupt the read on Linux systems. +_EVENT_READ_TIMEOUT = 1.0 # in seconds -class EventsListener(_threading.Thread): +class EventsListener(threading.Thread): """Listener thread for notifications from the Unifying Receiver. - Incoming packets will be passed to the callback function in sequence. """ def __init__(self, receiver, notifications_callback): try: - path_name = receiver.path.split('/')[2] + path_name = receiver.path.split("/")[2] except IndexError: path_name = receiver.path - super().__init__(name=self.__class__.__name__ + ':' + path_name) + super().__init__(name=f"{self.__class__.__name__}:{path_name}") self.daemon = True self._active = False self.receiver = receiver - self._queued_notifications = _Queue(16) + self._queued_notifications = queue.Queue(16) self._notifications_callback = notifications_callback def run(self): self._active = True # replace the handle with a threaded one self.receiver.handle = _ThreadedHandle(self, self.receiver.path, self.receiver.handle) - if _log.isEnabledFor(_INFO): - _log.info('started with %s (%d)', self.receiver, int(self.receiver.handle)) + if logger.isEnabledFor(logging.INFO): + logger.info("started with %s (%d)", self.receiver, int(self.receiver.handle)) self.has_started() if self.receiver.isDevice: # ping (wired or BT) devices to see if they are really online if self.receiver.ping(): - self.receiver.status.changed(True, reason='initialization') + self.receiver.changed(active=True, reason="initialization") while self._active: if self._queued_notifications.empty(): try: - n = _base.read(self.receiver.handle, _EVENT_READ_TIMEOUT) - except _base.NoReceiver: - _log.warning('%s disconnected', self.receiver.name) + n = base.read(self.receiver.handle, _EVENT_READ_TIMEOUT) + except exceptions.NoReceiver: + logger.warning("%s disconnected", self.receiver.name) self.receiver.close() break if n: - n = _base.make_notification(*n) + report_id, devnumber, data = n + n = base.make_notification(report_id, devnumber, data) else: n = self._queued_notifications.get() # deliver any queued notifications if n: try: self._notifications_callback(n) except Exception: - _log.exception('processing %s', n) + logger.exception("processing %s", n) del self._queued_notifications self.has_stopped() @@ -197,17 +174,13 @@ class EventsListener(_threading.Thread): """Called right before the thread stops.""" pass - # def tick(self, timestamp): - # """Called about every tick_period seconds.""" - # pass - def _notifications_hook(self, n): # Only consider unhandled notifications that were sent from this thread, # i.e. triggered by a callback handling a previous notification. - assert _threading.current_thread() == self - if self._active: # and _threading.current_thread() == self: - # if _log.isEnabledFor(_DEBUG): - # _log.debug("queueing unhandled %s", n) + assert threading.current_thread() == self + if self._active: # and threading.current_thread() == self: + # if logger.isEnabledFor(logging.DEBUG): + # logger.debug("queueing unhandled %s", n) if not self._queued_notifications.full(): self._queued_notifications.put(n) diff --git a/lib/logitech_receiver/notifications.py b/lib/logitech_receiver/notifications.py index 3e3de9df..693e3afd 100644 --- a/lib/logitech_receiver/notifications.py +++ b/lib/logitech_receiver/notifications.py @@ -1,4 +1,5 @@ ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -14,436 +15,502 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# Handles incoming events from the receiver/devices, updating the related -# status object as appropriate. +"""Handles incoming events from the receiver/devices, updating the +object as appropriate. +""" -import threading as _threading +from __future__ import annotations -from logging import DEBUG as _DEBUG -from logging import INFO as _INFO -from logging import getLogger -from struct import unpack as _unpack +import logging +import struct +import threading +import typing -from . import diversion as _diversion -from . import hidpp10 as _hidpp10 -from . import hidpp20 as _hidpp20 -from . import settings_templates as _st -from .base import DJ_MESSAGE_ID as _DJ_MESSAGE_ID -from .common import strhex as _strhex -from .i18n import _ -from .status import ALERT as _ALERT -from .status import KEYS as _K +from solaar.i18n import _ -_log = getLogger(__name__) -del getLogger +from . import base +from . import common +from . import diversion +from . import hidpp10 +from . import hidpp10_constants +from . import hidpp20 +from . import settings_templates +from .common import Alert +from .common import BatteryStatus +from .common import Notification +from .hidpp10_constants import Registers +from .hidpp20_constants import SupportedFeature -_R = _hidpp10.REGISTERS -_F = _hidpp20.FEATURE +if typing.TYPE_CHECKING: + from .base import HIDPPNotification + from .device import Device + from .receiver import Receiver -# -# -# +logger = logging.getLogger(__name__) -notification_lock = _threading.Lock() +NotificationHandler = typing.Callable[["Receiver", "HIDPPNotification"], bool] + +_hidpp10 = hidpp10.Hidpp10() +_hidpp20 = hidpp20.Hidpp20() + +notification_lock = threading.Lock() -def process(device, notification): +def process(device: Device | Receiver, notification: HIDPPNotification): + """Handle incoming events (notification) from device or receiver.""" assert device assert notification - assert hasattr(device, 'status') - status = device.status - assert status is not None - if not device.isDevice: - return _process_receiver_notification(device, status, notification) - - return _process_device_notification(device, status, notification) + return process_receiver_notification(device, notification) + return process_device_notification(device, notification) -# -# -# +def process_receiver_notification(receiver: Receiver, notification: HIDPPNotification) -> bool | None: + """Process event messages from receivers.""" + event_handler_mapping: dict[int, NotificationHandler] = { + Notification.PAIRING_LOCK: handle_pairing_lock, + Registers.DEVICE_DISCOVERY_NOTIFICATION: handle_device_discovery, + Registers.DISCOVERY_STATUS_NOTIFICATION: handle_discovery_status, + Registers.PAIRING_STATUS_NOTIFICATION: handle_pairing_status, + Registers.PASSKEY_PRESSED_NOTIFICATION: handle_passkey_pressed, + Registers.PASSKEY_REQUEST_NOTIFICATION: handle_passkey_request, + } + + try: + handler_func = event_handler_mapping[notification.sub_id] + return handler_func(receiver, notification) + except KeyError: + pass + + assert notification.sub_id in [ + Notification.CONNECT_DISCONNECT, + Notification.DJ_PAIRING, + Notification.CONNECTED, + Notification.RAW_INPUT, + Notification.POWER, + ] + + logger.warning(f"{receiver}: unhandled notification {notification}") -def _process_receiver_notification(receiver, status, n): - # supposedly only 0x4x notifications arrive for the receiver - assert n.sub_id & 0x40 == 0x40 +def process_device_notification(device: Device, notification: HIDPPNotification): + """Process event messages from devices.""" - if n.sub_id == 0x4A: # pairing lock notification - status.lock_open = bool(n.address & 0x01) - reason = (_('pairing lock is open') if status.lock_open else _('pairing lock is closed')) - if _log.isEnabledFor(_INFO): - _log.info('%s: %s', receiver, reason) - status[_K.ERROR] = None - if status.lock_open: - status.new_device = None - pair_error = ord(n.data[:1]) - if pair_error: - status[_K.ERROR] = error_string = _hidpp10.PAIRING_ERRORS[pair_error] - status.new_device = None - _log.warn('pairing error %d: %s', pair_error, error_string) - status.changed(reason=reason) - return True + # incoming packets with SubId >= 0x80 are supposedly replies from HID++ 1.0 requests, should never get here + assert notification.sub_id & 0x80 == 0 - elif n.sub_id == _R.discovery_status_notification: # Bolt pairing - with notification_lock: - status.discovering = n.address == 0x00 - reason = (_('discovery lock is open') if status.discovering else _('discovery lock is closed')) - if _log.isEnabledFor(_INFO): - _log.info('%s: %s', receiver, reason) - status[_K.ERROR] = None - if status.discovering: - status.counter = status.device_address = status.device_authentication = status.device_name = None - status.device_passkey = None - discover_error = ord(n.data[:1]) - if discover_error: - status[_K.ERROR] = discover_string = _hidpp10.BOLT_PAIRING_ERRORS[discover_error] - _log.warn('bolt discovering error %d: %s', discover_error, discover_string) - status.changed(reason=reason) - return True - - elif n.sub_id == _R.device_discovery_notification: # Bolt pairing - with notification_lock: - counter = n.address + n.data[0] * 256 # notification counter - if status.counter is None: - status.counter = counter - else: - if not status.counter == counter: - return None - if n.data[1] == 0: - status.device_kind = n.data[3] - status.device_address = n.data[6:12] - status.device_authentication = n.data[14] - elif n.data[1] == 1: - status.device_name = n.data[3:3 + n.data[2]].decode('utf-8') - return True - - elif n.sub_id == _R.pairing_status_notification: # Bolt pairing - with notification_lock: - status.device_passkey = None - status.lock_open = n.address == 0x00 - reason = (_('pairing lock is open') if status.lock_open else _('pairing lock is closed')) - if _log.isEnabledFor(_INFO): - _log.info('%s: %s', receiver, reason) - status[_K.ERROR] = None - if not status.lock_open: - status.counter = status.device_address = status.device_authentication = status.device_name = None - pair_error = n.data[0] - if status.lock_open: - status.new_device = None - elif n.address == 0x02 and not pair_error: - status.new_device = receiver.register_new_device(n.data[7]) - if pair_error: - status[_K.ERROR] = error_string = _hidpp10.BOLT_PAIRING_ERRORS[pair_error] - status.new_device = None - _log.warn('pairing error %d: %s', pair_error, error_string) - status.changed(reason=reason) - return True - - elif n.sub_id == _R.passkey_request_notification: # Bolt pairing - with notification_lock: - status.device_passkey = n.data[0:6].decode('utf-8') - return True - - elif n.sub_id == _R.passkey_pressed_notification: # Bolt pairing - return True - - _log.warn('%s: unhandled notification %s', receiver, n) - - -# -# -# - - -def _process_device_notification(device, status, n): - # incoming packets with SubId >= 0x80 are supposedly replies from - # HID++ 1.0 requests, should never get here - assert n.sub_id & 0x80 == 0 - - if n.sub_id == 00: # no-op feature notification, dispose of it quickly + if notification.sub_id == Notification.NO_OPERATION: + # dispose it return False - # Allow the device object to handle the notification using custom - # per-device state. - handling_ret = device.handle_notification(n) + # Allow the device object to handle the notification using custom per-device state. + handling_ret = device.handle_notification(notification) if handling_ret is not None: return handling_ret # 0x40 to 0x7F appear to be HID++ 1.0 or DJ notifications - if n.sub_id >= 0x40: - if n.report_id == _DJ_MESSAGE_ID: - return _process_dj_notification(device, status, n) + if notification.sub_id >= 0x40: + if notification.report_id == base.DJ_MESSAGE_ID: + return _process_dj_notification(device, notification) else: - return _process_hidpp10_notification(device, status, n) + return _process_hidpp10_notification(device, notification) # These notifications are from the device itself, so it must be active device.online = True - # At this point, we need to know the device's protocol, otherwise it's - # possible to not know how to handle it. + # At this point, we need to know the device's protocol, otherwise it's possible to not know how to handle it. assert device.protocol is not None # some custom battery events for HID++ 1.0 devices if device.protocol < 2.0: - return _process_hidpp10_custom_notification(device, status, n) + return _process_hidpp10_custom_notification(device, notification) # assuming 0x00 to 0x3F are feature (HID++ 2.0) notifications if not device.features: - _log.warn('%s: feature notification but features not set up: %02X %s', device, n.sub_id, n) - return False - try: - feature = device.features.get_feature(n.sub_id) - except IndexError: - _log.warn('%s: notification from invalid feature index %02X: %s', device, n.sub_id, n) + logger.warning("%s: feature notification but features not set up: %02X %s", device, notification.sub_id, notification) return False - return _process_feature_notification(device, status, n, feature) + return _process_feature_notification(device, notification) -def _process_dj_notification(device, status, n): - if _log.isEnabledFor(_DEBUG): - _log.debug('%s (%s) DJ %s', device, device.protocol, n) +def _process_dj_notification(device: Device, notification: HIDPPNotification): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s (%s) DJ %s", device, device.protocol, notification) - if n.sub_id == 0x40: + if notification.sub_id == Notification.CONNECT_DISCONNECT: # do all DJ paired notifications also show up as HID++ 1.0 notifications? - if _log.isEnabledFor(_INFO): - _log.info('%s: ignoring DJ unpaired: %s', device, n) + if logger.isEnabledFor(logging.INFO): + logger.info("%s: ignoring DJ unpaired: %s", device, notification) return True - if n.sub_id == 0x41: + if notification.sub_id == Notification.DJ_PAIRING: # do all DJ paired notifications also show up as HID++ 1.0 notifications? - if _log.isEnabledFor(_INFO): - _log.info('%s: ignoring DJ paired: %s', device, n) + if logger.isEnabledFor(logging.INFO): + logger.info("%s: ignoring DJ paired: %s", device, notification) return True - if n.sub_id == 0x42: - connected = not n.address & 0x01 - if _log.isEnabledFor(_INFO): - _log.info('%s: DJ connection: %s %s', device, connected, n) - status.changed(active=connected, alert=_ALERT.NONE, reason=_('connected') if connected else _('disconnected')) + if notification.sub_id == Notification.CONNECTED: + connected = not notification.address & 0x01 + if logger.isEnabledFor(logging.INFO): + logger.info("%s: DJ connection: %s %s", device, connected, notification) + device.changed(active=connected, alert=Alert.NONE, reason=_("connected") if connected else _("disconnected")) return True - _log.warn('%s: unrecognized DJ %s', device, n) + logger.warning("%s: unrecognized DJ %s", device, notification) -def _process_hidpp10_custom_notification(device, status, n): - if _log.isEnabledFor(_DEBUG): - _log.debug('%s (%s) custom notification %s', device, device.protocol, n) +def _process_hidpp10_custom_notification(device: Device, notification: HIDPPNotification): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s (%s) custom notification %s", device, device.protocol, notification) - if n.sub_id in (_R.battery_status, _R.battery_charge): - # message layout: 10 ix <00> - assert n.data[-1:] == b'\x00' - data = chr(n.address).encode() + n.data - charge, next_charge, status_text, voltage = _hidpp10.parse_battery_status(n.sub_id, data) - status.set_battery_info(charge, next_charge, status_text, voltage) + if notification.sub_id in (Registers.BATTERY_STATUS, Registers.BATTERY_CHARGE): + assert notification.data[-1:] == b"\x00" + data = chr(notification.address).encode() + notification.data + device.set_battery_info(hidpp10.parse_battery_status(notification.sub_id, data)) return True - if n.sub_id == _R.keyboard_illumination: - # message layout: 10 ix 17("address") - # TODO anything we can do with this? - if _log.isEnabledFor(_INFO): - _log.info('illumination event: %s', n) - return True - - _log.warn('%s: unrecognized %s', device, n) + logger.warning("%s: unrecognized %s", device, notification) -def _process_hidpp10_notification(device, status, n): - # device unpairing - if n.sub_id == 0x40: - if n.address == 0x02: +def _process_hidpp10_notification(device: Device, notification: HIDPPNotification): + if notification.sub_id == Notification.CONNECT_DISCONNECT: # device unpairing + if notification.address == 0x02: # device un-paired - status.clear() device.wpid = None - device.status = None if device.number in device.receiver: del device.receiver[device.number] - status.changed(active=False, alert=_ALERT.ALL, reason=_('unpaired')) + device.changed(active=False, alert=Alert.ALL, reason=_("unpaired")) + ## device.status = None else: - _log.warn('%s: disconnection with unknown type %02X: %s', device, n.address, n) + logger.warning("%s: disconnection with unknown type %02X: %s", device, notification.address, notification) return True - # device connection (and disconnection) - if n.sub_id == 0x41: - flags = ord(n.data[:1]) & 0xF0 - if n.address == 0x02: # very old 27 MHz protocol - wpid = '00' + _strhex(n.data[2:3]) + if notification.sub_id == Notification.DJ_PAIRING: # device connection (and disconnection) + flags = ord(notification.data[:1]) & 0xF0 + if notification.address == 0x02: # very old 27 MHz protocol + wpid = "00" + common.strhex(notification.data[2:3]) link_established = True link_encrypted = bool(flags & 0x80) - elif n.address > 0x00: # all other protocols are supposed to be almost the same - wpid = _strhex(n.data[2:3] + n.data[1:2]) + elif notification.address > 0x00: # all other protocols are supposed to be almost the same + wpid = common.strhex(notification.data[2:3] + notification.data[1:2]) link_established = not (flags & 0x40) - link_encrypted = bool(flags & 0x20) or n.address == 0x10 # Bolt protocol always encrypted + link_encrypted = bool(flags & 0x20) or notification.address == 0x10 # Bolt protocol always encrypted else: - _log.warn('%s: connection notification with unknown protocol %02X: %s', device.number, n.address, n) + logger.warning( + "%s: connection notification with unknown protocol %02X: %s", device.number, notification.address, notification + ) return True if wpid != device.wpid: - _log.warn('%s wpid mismatch, got %s', device, wpid) - if _log.isEnabledFor(_DEBUG): - _log.debug( - '%s: protocol %s connection notification: software=%s, encrypted=%s, link=%s, payload=%s', device, n.address, - bool(flags & 0x10), link_encrypted, link_established, bool(flags & 0x80) + logger.warning("%s wpid mismatch, got %s", device, wpid) + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + "%s: protocol %s connection notification: software=%s, encrypted=%s, link=%s, payload=%s", + device, + notification.address, + bool(flags & 0x10), + link_encrypted, + link_established, + bool(flags & 0x80), ) - status[_K.LINK_ENCRYPTED] = link_encrypted - status.changed(active=link_established) + device.link_encrypted = link_encrypted + if not link_established and device.receiver: + hidpp10.set_configuration_pending_flags(device.receiver, 0xFF) + device.changed(active=link_established) return True - if n.sub_id == 0x49: + if notification.sub_id == Notification.RAW_INPUT: # raw input event? just ignore it - # if n.address == 0x01, no idea what it is, but they keep on coming - # if n.address == 0x03, appears to be an actual input event, - # because they only come when input happents + # if notification.address == 0x01, no idea what it is, but they keep on coming + # if notification.address == 0x03, appears to be an actual input event, because they only come when input happents return True - # power notification - if n.sub_id == 0x4B: - if n.address == 0x01: - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: device powered on', device) - reason = status.to_string() or _('powered on') - status.changed(active=True, alert=_ALERT.NOTIFICATION, reason=reason) + if notification.sub_id == Notification.POWER: + if notification.address == 0x01: + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: device powered on", device) + reason = device.status_string() or _("powered on") + device.changed(active=True, alert=Alert.NOTIFICATION, reason=reason) else: - _log.warn('%s: unknown %s', device, n) + logger.warning("%s: unknown %s", device, notification) return True - _log.warn('%s: unrecognized %s', device, n) + logger.warning("%s: unrecognized %s", device, notification) -def _process_feature_notification(device, status, n, feature): - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: notification for feature %s, report %s, data %s', device, feature, n.address >> 4, _strhex(n.data)) +def _process_feature_notification(device: Device, notification: HIDPPNotification): + old_present, device.present = device.present, True # the device is generating a feature notification so it must be present + try: + feature = device.features.get_feature(notification.sub_id) + except IndexError: + logger.warning("%s: notification from invalid feature index %02X: %s", device, notification.sub_id, notification) + return False - if feature == _F.BATTERY_STATUS: - if n.address == 0x00: - _ignore, discharge_level, discharge_next_level, battery_status, voltage = _hidpp20.decipher_battery_status(n.data) - status.set_battery_info(discharge_level, discharge_next_level, battery_status, voltage) - elif n.address == 0x10: - if _log.isEnabledFor(_INFO): - _log.info('%s: spurious BATTERY status %s', device, n) + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + "%s: notification for feature %s, report %s, data %s", + device, + feature, + notification.address >> 4, + common.strhex(notification.data), + ) + + if feature == SupportedFeature.BATTERY_STATUS: + if notification.address == 0x00: + device.set_battery_info(hidpp20.decipher_battery_status(notification.data)[1]) + elif notification.address == 0x10: + if logger.isEnabledFor(logging.INFO): + logger.info("%s: spurious BATTERY status %s", device, notification) else: - _log.warn('%s: unknown BATTERY %s', device, n) + logger.warning("%s: unknown BATTERY %s", device, notification) - elif feature == _F.BATTERY_VOLTAGE: - if n.address == 0x00: - _ignore, level, nextl, battery_status, voltage = _hidpp20.decipher_battery_voltage(n.data) - status.set_battery_info(level, nextl, battery_status, voltage) + elif feature == SupportedFeature.BATTERY_VOLTAGE: + if notification.address == 0x00: + device.set_battery_info(hidpp20.decipher_battery_voltage(notification.data)[1]) else: - _log.warn('%s: unknown VOLTAGE %s', device, n) + logger.warning("%s: unknown VOLTAGE %s", device, notification) - elif feature == _F.UNIFIED_BATTERY: - if n.address == 0x00: - _ignore, level, nextl, battery_status, voltage = _hidpp20.decipher_battery_unified(n.data) - status.set_battery_info(level, nextl, battery_status, voltage) + elif feature == SupportedFeature.UNIFIED_BATTERY: + if notification.address == 0x00: + device.set_battery_info(hidpp20.decipher_battery_unified(notification.data)[1]) else: - _log.warn('%s: unknown UNIFIED BATTERY %s', device, n) + logger.warning("%s: unknown UNIFIED BATTERY %s", device, notification) - elif feature == _F.ADC_MEASUREMENT: - if n.address == 0x00: - result = _hidpp20.decipher_adc_measurement(n.data) - if result: - _ignore, level, nextl, battery_status, voltage = result - status.set_battery_info(level, nextl, battery_status, voltage) - else: # this feature is used to signal device becoming inactive - status.changed(active=False) + elif feature == SupportedFeature.ADC_MEASUREMENT: + if notification.address == 0x00: + result = hidpp20.decipher_adc_measurement(notification.data) + if result: # if good data and the device was not present then a push is needed + device.set_battery_info(result[1]) + device.changed(active=True, alert=Alert.NONE, reason=_("ADC measurement notification"), push=not old_present) + else: # this feature is also used to signal device becoming inactive + device.present = False # exception to device presence + device.changed(active=False) else: - _log.warn('%s: unknown ADC MEASUREMENT %s', device, n) + logger.warning("%s: unknown ADC MEASUREMENT %s", device, notification) - elif feature == _F.SOLAR_DASHBOARD: - if n.data[5:9] == b'GOOD': - charge, lux, adc = _unpack('!BHH', n.data[:5]) + elif feature == SupportedFeature.CENTURION_BATTERY_SOC: + device.set_battery_info(hidpp20.decipher_battery_centurion(notification.data)[1]) + + elif feature == SupportedFeature.SOLAR_DASHBOARD: + if notification.data[5:9] == b"GOOD": + charge, lux, adc = struct.unpack("!BHH", notification.data[:5]) # guesstimate the battery voltage, emphasis on 'guess' # status_text = '%1.2fV' % (adc * 2.67793237653 / 0x0672) - status_text = _hidpp20.BATTERY_STATUS.discharging - if n.address == 0x00: - status[_K.LIGHT_LEVEL] = None - status.set_battery_info(charge, None, status_text, None) - elif n.address == 0x10: - status[_K.LIGHT_LEVEL] = lux + status_text = BatteryStatus.DISCHARGING + if notification.address == 0x00: + device.set_battery_info(common.Battery(charge, None, status_text, None)) + elif notification.address == 0x10: if lux > 200: - status_text = _hidpp20.BATTERY_STATUS.recharging - status.set_battery_info(charge, None, status_text, None) - elif n.address == 0x20: - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: Light Check button pressed', device) - status.changed(alert=_ALERT.SHOW_WINDOW) + status_text = BatteryStatus.RECHARGING + device.set_battery_info(common.Battery(charge, None, status_text, None, lux)) + elif notification.address == 0x20: + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: Light Check button pressed", device) + device.changed(alert=Alert.SHOW_WINDOW) # first cancel any reporting - # device.feature_request(_F.SOLAR_DASHBOARD) + # device.feature_request(SupportedFeature.SOLAR_DASHBOARD) # trigger a new report chain reports_count = 15 reports_period = 2 # seconds - device.feature_request(_F.SOLAR_DASHBOARD, 0x00, reports_count, reports_period) + device.feature_request(SupportedFeature.SOLAR_DASHBOARD, 0x00, reports_count, reports_period) else: - _log.warn('%s: unknown SOLAR CHARGE %s', device, n) + logger.warning("%s: unknown SOLAR CHARGE %s", device, notification) else: - _log.warn('%s: SOLAR CHARGE not GOOD? %s', device, n) + logger.warning("%s: SOLAR CHARGE not GOOD? %s", device, notification) - elif feature == _F.WIRELESS_DEVICE_STATUS: - if n.address == 0x00: - if _log.isEnabledFor(_DEBUG): - _log.debug('wireless status: %s', n) - reason = 'powered on' if n.data[2] == 1 else None - if n.data[1] == 1: # device is asking for software reconfiguration so need to change status - alert = _ALERT.NONE - status.changed(active=True, alert=alert, reason=reason, push=True) + elif feature == SupportedFeature.WIRELESS_DEVICE_STATUS: + if notification.address == 0x00: + if logger.isEnabledFor(logging.DEBUG): + logger.debug("wireless status: %s", notification) + reason = "powered on" if notification.data[2] == 1 else None + if notification.data[1] == 1: # device is asking for software reconfiguration so need to change status + alert = Alert.NONE + device.changed(active=True, alert=alert, reason=reason, push=True) else: - _log.warn('%s: unknown WIRELESS %s', device, n) + logger.warning("%s: unknown WIRELESS %s", device, notification) - elif feature == _F.TOUCHMOUSE_RAW_POINTS: - if n.address == 0x00: - if _log.isEnabledFor(_INFO): - _log.info('%s: TOUCH MOUSE points %s', device, n) - elif n.address == 0x10: - touch = ord(n.data[:1]) + elif feature == SupportedFeature.TOUCHMOUSE_RAW_POINTS: + if notification.address == 0x00: + if logger.isEnabledFor(logging.INFO): + logger.info("%s: TOUCH MOUSE points %s", device, notification) + elif notification.address == 0x10: + touch = ord(notification.data[:1]) button_down = bool(touch & 0x02) mouse_lifted = bool(touch & 0x01) - if _log.isEnabledFor(_INFO): - _log.info('%s: TOUCH MOUSE status: button_down=%s mouse_lifted=%s', device, button_down, mouse_lifted) + if logger.isEnabledFor(logging.INFO): + logger.info("%s: TOUCH MOUSE status: button_down=%s mouse_lifted=%s", device, button_down, mouse_lifted) else: - _log.warn('%s: unknown TOUCH MOUSE %s', device, n) + logger.warning("%s: unknown TOUCH MOUSE %s", device, notification) # TODO: what are REPROG_CONTROLS_V{2,3}? - elif feature == _F.REPROG_CONTROLS: - if n.address == 0x00: - if _log.isEnabledFor(_INFO): - _log.info('%s: reprogrammable key: %s', device, n) + elif feature == SupportedFeature.REPROG_CONTROLS: + if notification.address == 0x00: + if logger.isEnabledFor(logging.INFO): + logger.info("%s: reprogrammable key: %s", device, notification) else: - _log.warn('%s: unknown REPROG_CONTROLS %s', device, n) + logger.warning("%s: unknown REPROG_CONTROLS %s", device, notification) - elif feature == _F.REPROG_CONTROLS_V4: - if n.address == 0x00: - if _log.isEnabledFor(_DEBUG): - cid1, cid2, cid3, cid4 = _unpack('!HHHH', n.data[:8]) - _log.debug('%s: diverted controls pressed: 0x%x, 0x%x, 0x%x, 0x%x', device, cid1, cid2, cid3, cid4) - elif n.address == 0x10: - if _log.isEnabledFor(_DEBUG): - dx, dy = _unpack('!hh', n.data[:4]) - _log.debug('%s: rawXY dx=%i dy=%i', device, dx, dy) - elif n.address == 0x20: - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: received analyticsKeyEvents', device) - elif _log.isEnabledFor(_INFO): - _log.info('%s: unknown REPROG_CONTROLS_V4 %s', device, n) + elif feature == SupportedFeature.BACKLIGHT2: + if notification.address == 0x00: + level = struct.unpack("!B", notification.data[1:2])[0] + if device.setting_callback: + device.setting_callback(device, settings_templates.Backlight2Level, [level]) - elif feature == _F.HIRES_WHEEL: - if (n.address == 0x00): - if _log.isEnabledFor(_INFO): - flags, delta_v = _unpack('>bh', n.data[:3]) + elif feature == SupportedFeature.REPROG_CONTROLS_V4: + if notification.address == 0x00: + if logger.isEnabledFor(logging.DEBUG): + cid1, cid2, cid3, cid4 = struct.unpack("!HHHH", notification.data[:8]) + logger.debug("%s: diverted controls pressed: 0x%x, 0x%x, 0x%x, 0x%x", device, cid1, cid2, cid3, cid4) + elif notification.address == 0x10: + if logger.isEnabledFor(logging.DEBUG): + dx, dy = struct.unpack("!hh", notification.data[:4]) + logger.debug("%s: rawXY dx=%i dy=%i", device, dx, dy) + elif notification.address == 0x20: + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: received analyticsKeyEvents", device) + elif logger.isEnabledFor(logging.INFO): + logger.info("%s: unknown REPROG_CONTROLS_V4 %s", device, notification) + + elif feature == SupportedFeature.HIRES_WHEEL: + if notification.address == 0x00: + if logger.isEnabledFor(logging.INFO): + flags, delta_v = struct.unpack(">bh", notification.data[:3]) high_res = (flags & 0x10) != 0 - periods = flags & 0x0f - _log.info('%s: WHEEL: res: %d periods: %d delta V:%-3d', device, high_res, periods, delta_v) - elif (n.address == 0x10): - ratchet = n.data[0] - if _log.isEnabledFor(_INFO): - _log.info('%s: WHEEL: ratchet: %d', device, ratchet) + periods = flags & 0x0F + logger.info("%s: WHEEL: res: %d periods: %d delta V:%-3d", device, high_res, periods, delta_v) + elif notification.address == 0x10: + ratchet = notification.data[0] + if logger.isEnabledFor(logging.INFO): + logger.info("%s: WHEEL: ratchet: %d", device, ratchet) if ratchet < 2: # don't process messages with unusual ratchet values - from solaar.ui.config_panel import record_setting # prevent circular import - setting = next((s for s in device.settings if s.name == _st.ScrollRatchet.name), None) - if setting: - record_setting(device, setting, [2 if ratchet else 1]) + if device.setting_callback: + device.setting_callback(device, settings_templates.ScrollRatchet, [2 if ratchet else 1]) else: - if _log.isEnabledFor(_INFO): - _log.info('%s: unknown WHEEL %s', device, n) + if logger.isEnabledFor(logging.INFO): + logger.info("%s: unknown WHEEL %s", device, notification) - _diversion.process_notification(device, status, n, feature) + elif feature == SupportedFeature.ONBOARD_PROFILES: + if notification.address > 0x10: + if logger.isEnabledFor(logging.INFO): + logger.info("%s: unknown ONBOARD PROFILES %s", device, notification) + else: + if notification.address == 0x00: + profile_sector = struct.unpack("!H", notification.data[:2])[0] + if profile_sector: + settings_templates.profile_change(device, profile_sector) + elif notification.address == 0x10: + resolution_index = struct.unpack("!B", notification.data[:1])[0] + profile_sector = struct.unpack("!H", device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x40)[:2])[0] + if device.setting_callback: + for profile in device.profiles.profiles.values() if device.profiles else []: + if profile.sector == profile_sector: + device.setting_callback( + device, settings_templates.AdjustableDpi, [profile.resolutions[resolution_index]] + ) + break + + elif feature == SupportedFeature.BRIGHTNESS_CONTROL: + if notification.address > 0x10: + if logger.isEnabledFor(logging.INFO): + logger.info("%s: unknown BRIGHTNESS CONTROL %s", device, notification) + else: + if notification.address == 0x00: + brightness = struct.unpack("!H", notification.data[:2])[0] + device.setting_callback(device, settings_templates.BrightnessControl, [brightness]) + elif notification.address == 0x10: + brightness = notification.data[0] & 0x01 + if brightness: + brightness = struct.unpack("!H", device.feature_request(SupportedFeature.BRIGHTNESS_CONTROL, 0x10)[:2])[0] + device.setting_callback(device, settings_templates.BrightnessControl, [brightness]) + + diversion.process_notification(device, notification, feature) + return True + + +def handle_pairing_lock(receiver: Receiver, notification: HIDPPNotification) -> bool: + receiver.pairing.lock_open = bool(notification.address & 0x01) + reason = _("pairing lock is open") if receiver.pairing.lock_open else _("pairing lock is closed") + if logger.isEnabledFor(logging.INFO): + logger.info("%s: %s", receiver, reason) + receiver.pairing.error = None + if receiver.pairing.lock_open: + receiver.pairing.new_device = None + pair_error = ord(notification.data[:1]) + if pair_error: + error_string = hidpp10_constants.PairingError(pair_error).label + receiver.pairing.error = error_string + receiver.pairing.new_device = None + logger.warning("pairing error %d: %s", pair_error, error_string) + receiver.changed(reason=reason) + return True + + +def handle_discovery_status(receiver: Receiver, notification: HIDPPNotification) -> bool: + with notification_lock: + receiver.pairing.discovering = notification.address == 0x00 + reason = _("discovery lock is open") if receiver.pairing.discovering else _("discovery lock is closed") + if logger.isEnabledFor(logging.INFO): + logger.info("%s: %s", receiver, reason) + receiver.pairing.error = None + if receiver.pairing.discovering: + receiver.pairing.counter = receiver.pairing.device_address = None + receiver.pairing.device_authentication = receiver.pairing.device_name = None + receiver.pairing.device_passkey = None + discover_error = ord(notification.data[:1]) + if discover_error: + receiver.pairing.error = discover_string = hidpp10_constants.BoltPairingError(discover_error).label + logger.warning("bolt discovering error %d: %s", discover_error, discover_string) + receiver.changed(reason=reason) + return True + + +def handle_device_discovery(receiver: Receiver, notification: HIDPPNotification) -> bool: + with notification_lock: + counter = notification.address + notification.data[0] * 256 # notification counter + if receiver.pairing.counter is None: + receiver.pairing.counter = counter + else: + if not receiver.pairing.counter == counter: + return None + if notification.data[1] == 0: + receiver.pairing.device_kind = notification.data[3] + receiver.pairing.device_address = notification.data[6:12] + receiver.pairing.device_authentication = notification.data[14] + elif notification.data[1] == 1: + receiver.pairing.device_name = notification.data[3 : 3 + notification.data[2]].decode("utf-8") + return True + + +def handle_pairing_status(receiver: Receiver, notification: HIDPPNotification) -> bool: + with notification_lock: + receiver.pairing.device_passkey = None + receiver.pairing.lock_open = notification.address == 0x00 + reason = _("pairing lock is open") if receiver.pairing.lock_open else _("pairing lock is closed") + if logger.isEnabledFor(logging.INFO): + logger.info("%s: %s", receiver, reason) + receiver.pairing.error = None + if not receiver.pairing.lock_open: + receiver.pairing.counter = None + receiver.pairing.device_address = None + receiver.pairing.device_authentication = None + receiver.pairing.device_name = None + pair_error = notification.data[0] + if receiver.pairing.lock_open: + receiver.pairing.new_device = None + elif notification.address == 0x02 and not pair_error: + receiver.pairing.new_device = receiver.register_new_device(notification.data[7]) + if pair_error: + receiver.pairing.error = error_string = hidpp10_constants.BoltPairingError(pair_error).label + receiver.pairing.new_device = None + logger.warning("pairing error %d: %s", pair_error, error_string) + receiver.changed(reason=reason) + return True + + +def handle_passkey_request(receiver: Receiver, notification: HIDPPNotification) -> bool: + with notification_lock: + receiver.pairing.device_passkey = notification.data[0:6].decode("utf-8") + return True + + +def handle_passkey_pressed(_receiver: Receiver, _hidpp_notification: HIDPPNotification) -> bool: return True diff --git a/lib/logitech_receiver/onboard_eq.py b/lib/logitech_receiver/onboard_eq.py new file mode 100644 index 00000000..107b11f6 --- /dev/null +++ b/lib/logitech_receiver/onboard_eq.py @@ -0,0 +1,186 @@ +## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""OnboardEQ (0x0636) biquad coefficient math and payload builders. + +Pure computation — no device or transport dependencies beyond feature_request(). +""" + +from __future__ import annotations + +import math +import struct + +from .hidpp20_constants import SupportedFeature + +# Mystery bytes observed in every LGHUB pcap EQ write between band params +# and coefficient header. Purpose not fully understood — possibly a null-band +# terminator for DSPs that support >5 bands (advanced 10-band mode). +# First byte matches band_count; bytes 2-3 look like LE16 coeff blob size. +# Hardcoded from pcap for initial bring-up; revisit once device-tested. +_EQ_MYSTERY_BYTES = b"\x05\x5a\xe3\x00" + + +def _peaking_eq_biquad(freq_hz, gain_db, Q, sample_rate=48000.0): + """Compute peaking EQ biquad coefficients (Audio EQ Cookbook). + + Returns (b0/a0, b1/a0, b2/a0, a1/a0, a2/a0) normalised coefficients. + """ + A = 10.0 ** (gain_db / 40.0) + w0 = 2.0 * math.pi * freq_hz / sample_rate + cos_w0 = math.cos(w0) + alpha = math.sin(w0) / (2.0 * Q) + a0 = 1.0 + alpha / A + return ( + (1.0 + alpha * A) / a0, + (-2.0 * cos_w0) / a0, + (1.0 - alpha * A) / a0, + (-2.0 * cos_w0) / a0, + (1.0 - alpha / A) / a0, + ) + + +def _quantize_coeffs(b0, b1, b2, a1, a2): + """Quantize biquad coefficients to mixed Q1.31 / Q2.30 fixed-point. + + b0, b2, a2 use Q1.31 (x 2^31); b1, a1 use Q2.30 (x 2^30). + Values are truncated to 24-bit precision (low byte zeroed) matching + the device DSP's internal format. + Returns list of 10 uint16 values (5 coefficients x 2 LE words each, + high word first). + """ + scales = [2**31, 2**30, 2**31, 2**30, 2**31] # b0, b1, b2, a1, a2 + words = [] + for val, scale in zip([b0, b1, b2, a1, a2], scales): + q = int(round(val * scale)) + q = max(-(1 << 31), min((1 << 31) - 1, q)) + q = q & 0xFFFFFF00 # 24-bit precision (low byte always zero) + words.append((q >> 16) & 0xFFFF) # high word + words.append(q & 0xFFFF) # low word + return words + + +def _build_coeff_section(bands, sample_rate, section_type=1): + """Build one coefficient section for a DSP processing block. + + Returns bytes: 4-byte section header + coefficient data as LE uint16 words. + Section header: [type, 0x00, count_lo, count_hi]. + + Coefficients are normalized by a rescale factor to prevent Q1.31 overflow. + Only feedforward coefficients (b0, b1, b2) are divided by rescale; feedback + coefficients (a1, a2) are left unchanged. The DSP multiplies the output by + rescale to restore correct gain. + """ + _HEADROOM = 1.19 # 19% headroom margin (matches LGHUB) + num_bands = len(bands) + all_words = [num_bands] # first uint16 = num_bands + + # First pass: compute raw biquad coefficients for all bands + raw_coeffs = [] + for freq, gain, Q in bands: + raw_coeffs.append(_peaking_eq_biquad(freq, gain, max(Q, 0.1), sample_rate)) + + # Compute rescale: ensure max |b0| fits in Q1.31 with headroom + max_b0 = max(abs(c[0]) for c in raw_coeffs) + rescale = max(1.0, max_b0) * _HEADROOM + + # Second pass: normalize b-coefficients and quantize + for b0, b1, b2, a1, a2 in raw_coeffs: + all_words.extend(_quantize_coeffs(b0 / rescale, b1 / rescale, b2 / rescale, a1, a2)) + + # Rescale factor as Q6.26, 24-bit precision + rs = int(round(rescale * (1 << 26))) + rs = max(-(1 << 31), min((1 << 31) - 1, rs)) & 0xFFFFFF00 + all_words.append((rs >> 16) & 0xFFFF) + all_words.append(rs & 0xFFFF) + + coeff_count = num_bands * 10 + 3 # num_bands word + 10 per band + 2 rescale words + hdr = bytes([section_type, 0x00, coeff_count & 0xFF, (coeff_count >> 8) & 0xFF]) + data = struct.pack(f"<{len(all_words)}H", *all_words) + return hdr + data + + +def _build_eq_coeffs_payload(bands): + """Build the full EQCoeffs wire payload for SetEQParameters. + + Two coefficient sections: type=1 (48 kHz playback) and type=2 (16 kHz mic). + Returns bytes: 7-byte header + sections (no trailing padding). + """ + section_count = 2 + header = bytes([0x03, 0x0E, 0x00, section_count, 0x00, 0x00, 0x00]) + sections = _build_coeff_section(bands, 48000.0, section_type=1) + sections += _build_coeff_section(bands, 16000.0, section_type=2) + return header + sections + + +def _build_set_eq_payload(slot, bands): + """Build complete SetEQParameters payload: band params + biquad coefficients. + + bands: list of (freq_hz, gain_db, Q) tuples. + Returns bytes ready to send as sub-device params. + """ + params = bytes([slot, len(bands)]) + for freq, gain, Q in bands: + params += struct.pack(">H", freq) + bytes([gain & 0xFF, Q & 0xFF]) + params += _EQ_MYSTERY_BYTES + params += _build_eq_coeffs_payload(bands) + return params + + +def get_onboard_eq_info(device): + """Query HEADSET_ONBOARD_EQ GetEQInfos (function 0). + + Returns (has_hw_eq, num_bands) or None. + """ + result = device.feature_request(SupportedFeature.HEADSET_ONBOARD_EQ, 0x00) + if result is None or len(result) < 5: + return None + has_hw_eq = bool(result[0] & 0x80) + num_bands = result[4] + return (has_hw_eq, num_bands) + + +def get_onboard_eq_params(device, slot=0x00): + """Query HEADSET_ONBOARD_EQ GetEQParameters (function 0x10). + + Returns list of (freq_hz, gain_db, q) tuples, or None. + """ + result = device.feature_request(SupportedFeature.HEADSET_ONBOARD_EQ, 0x10, slot) + if result is None or len(result) < 2: + return None + band_count = result[1] + bands = [] + offset = 2 + for _i in range(band_count): + if offset + 4 > len(result): + break + freq_hz = struct.unpack(">H", result[offset : offset + 2])[0] + gain_db = struct.unpack("b", bytes([result[offset + 2]]))[0] # signed + q = result[offset + 3] + bands.append((freq_hz, gain_db, q)) + offset += 4 + return bands + + +def set_onboard_eq_params(device, bands, slot=0x00): + """Send HEADSET_ONBOARD_EQ SetEQParameters (function 0x20). + + bands: list of (freq_hz, gain_db, Q) tuples. + Returns response or None. + """ + payload = _build_set_eq_payload(slot, bands) + return device.feature_request(SupportedFeature.HEADSET_ONBOARD_EQ, 0x20, payload) diff --git a/lib/logitech_receiver/receiver.py b/lib/logitech_receiver/receiver.py index c0ea3857..42df1c08 100644 --- a/lib/logitech_receiver/receiver.py +++ b/lib/logitech_receiver/receiver.py @@ -1,6 +1,5 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -16,78 +15,174 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import errno as _errno +from __future__ import annotations -from logging import INFO as _INFO -from logging import getLogger +import errno +import logging +import time +import typing -import hidapi as _hid +from dataclasses import dataclass +from typing import Callable +from typing import Optional +from typing import Protocol -from . import base as _base -from . import hidpp10 as _hidpp10 -from .base_usb import product_information as _product_information -from .common import strhex as _strhex +from solaar.i18n import _ +from solaar.i18n import ngettext + +from . import exceptions +from . import hidpp10 +from . import hidpp10_constants +from .common import Alert +from .common import Notification from .device import Device +from .hidpp10_constants import InfoSubRegisters +from .hidpp10_constants import NotificationFlag +from .hidpp10_constants import Registers -_log = getLogger(__name__) -del getLogger +if typing.TYPE_CHECKING: + from logitech_receiver import common -_R = _hidpp10.REGISTERS -_IR = _hidpp10.INFO_SUBREGISTERS + from .base import HIDPPNotification -# -# -# +logger = logging.getLogger(__name__) + +_hidpp10 = hidpp10.Hidpp10() + + +class LowLevelInterface(Protocol): + def open_path(self, path): + ... + + def find_paired_node_wpid(self, receiver_path: str, index: int): + ... + + def ping(self, handle, number, long_message=False): + ... + + def request(self, handle, devnumber, request_id, *params, **kwargs): + ... + + def close(self, handle): + ... + + +@dataclass +class Pairing: + """Information about the current or most recent pairing""" + + lock_open: bool = False + discovering: bool = False + counter: Optional[int] = None + device_address: Optional[bytes] = None + device_authentication: Optional[int] = None + device_kind: Optional[int] = None + device_name: Optional[str] = None + device_passkey: Optional[str] = None + new_device: Optional[Device] = None + error: Optional[any] = None + + +def extract_serial(response: bytes) -> str: + """Extracts serial number from receiver response.""" + return response.hex().upper() + + +def extract_max_devices(response: bytes) -> int: + """Extracts maximum number of supported devices from response.""" + max_devices = response[6] + return int(max_devices) + + +def extract_remaining_pairings(response: bytes) -> int: + ps = ord(response[2:3]) + remaining_pairings = ps - 5 if ps >= 5 else -1 + return int(remaining_pairings) + + +def extract_codename(response: bytes) -> str: + codename = response[2 : 2 + ord(response[1:2])] + return codename.decode("ascii") + + +def extract_power_switch_location(response: bytes) -> str: + """Extracts power switch location from response.""" + index = response[9] & 0x0F + return hidpp10_constants.PowerSwitchLocation.location(index).name.lower() + + +def extract_connection_count(response: bytes) -> int: + """Extract connection count from receiver response.""" + return ord(response[1:2]) + + +def extract_wpid(response: bytes) -> str: + """Extract wpid from receiver response.""" + return response.hex().upper() + + +def extract_polling_rate(response: bytes) -> int: + """Returns polling rate in milliseconds.""" + return int(response[2]) + + +def extract_device_kind(response: int) -> str: + return hidpp10_constants.DEVICE_KIND[response] class Receiver: - """A Unifying Receiver instance. - + """A generic Receiver instance, mostly implementing the interface used on Unifying, Nano, and LightSpeed receivers" The paired devices are available through the sequence interface. """ + + read_register: Callable = hidpp10.read_register + write_register: Callable = hidpp10.write_register number = 0xFF kind = None - def __init__(self, handle, path, product_id): + def __init__( + self, + low_level: LowLevelInterface, + receiver_kind, + product_info, + handle, + path, + product_id, + setting_callback=None, + ): assert handle + self.low_level = low_level self.isDevice = False # some devices act as receiver so we need a property to distinguish them self.handle = handle self.path = path self.product_id = product_id - product_info = _product_information(self.product_id) - if not product_info: - _log.warning('Unknown receiver type: %s', self.product_id) - product_info = {} - self.receiver_kind = product_info.get('receiver_kind', 'unknown') - - # read the serial immediately, so we can find out max_devices - if self.receiver_kind == 'bolt': - serial_reply = self.read_register(_R.bolt_uniqueId) - self.serial = _strhex(serial_reply) - self.max_devices = product_info.get('max_devices', 1) - self.may_unpair = product_info.get('may_unpair', False) - else: - serial_reply = self.read_register(_R.receiver_info, _IR.receiver_information) - if serial_reply: - self.serial = _strhex(serial_reply[1:5]) - self.max_devices = ord(serial_reply[6:7]) - if self.max_devices <= 0 or self.max_devices > 6: - self.max_devices = product_info.get('max_devices', 1) - self.may_unpair = product_info.get('may_unpair', False) - else: # handle receivers that don't have a serial number specially (i.e., c534 and Bolt receivers) - self.serial = None - self.max_devices = product_info.get('max_devices', 1) - self.may_unpair = product_info.get('may_unpair', False) - - self.name = product_info.get('name', 'Receiver') - self.re_pairs = product_info.get('re_pairs', False) - self._str = '<%s(%s,%s%s)>' % ( - self.name.replace(' ', ''), self.path, '' if isinstance(self.handle, int) else 'T', self.handle - ) - + self.setting_callback = setting_callback # for changes to settings + self.status_callback = None # for changes to other potentially visible aspects + self.receiver_kind = receiver_kind + self.serial = None + self.max_devices = None self._firmware = None - self._devices = {} self._remaining_pairings = None + self._devices = {} + self.name = product_info.get("name", "Receiver") + self.may_unpair = product_info.get("may_unpair", False) + self.re_pairs = product_info.get("re_pairs", False) + self.notification_flags = None + self.pairing = Pairing() + self.initialize(product_info) + hidpp10.set_configuration_pending_flags(self, 0xFF) + + def initialize(self, product_info: dict): + # read the receiver information subregister, so we can find out max_devices + serial_reply = self.read_register(Registers.RECEIVER_INFO, InfoSubRegisters.RECEIVER_INFORMATION) + if serial_reply: + self.serial = extract_serial(serial_reply[1:5]) + self.max_devices = extract_max_devices(serial_reply) + if not (1 <= self.max_devices <= 6): + self.max_devices = product_info.get("max_devices", 1) + else: # handle receivers that don't have a serial number specially (i.e., c534) + self.serial = None + self.max_devices = product_info.get("max_devices", 1) def close(self): handle, self.handle = self.handle, None @@ -95,13 +190,18 @@ class Receiver: if d: d.close() self._devices.clear() - return (handle and _base.close(handle)) + return handle and self.low_level.close(handle) def __del__(self): self.close() + def changed(self, alert=Alert.NOTIFICATION, reason=None): + """The status of the device had changed, so invoke the status callback""" + if self.status_callback is not None: + self.status_callback(self, alert=alert, reason=reason) + @property - def firmware(self): + def firmware(self) -> tuple[common.FirmwareInfo]: if self._firmware is None and self.handle: self._firmware = _hidpp10.get_firmware(self) return self._firmware @@ -109,10 +209,9 @@ class Receiver: # how many pairings remain (None for unknown, -1 for unlimited) def remaining_pairings(self, cache=True): if self._remaining_pairings is None or not cache: - ps = self.read_register(_R.receiver_connection) + ps = self.read_register(Registers.RECEIVER_CONNECTION) if ps is not None: - ps = ord(ps[2:3]) - self._remaining_pairings = ps - 5 if ps >= 5 else -1 + self._remaining_pairings = extract_remaining_pairings(ps) return self._remaining_pairings def enable_connection_notifications(self, enable=True): @@ -122,171 +221,128 @@ class Receiver: return False if enable: - set_flag_bits = ( - _hidpp10.NOTIFICATION_FLAG.battery_status - | _hidpp10.NOTIFICATION_FLAG.wireless - | _hidpp10.NOTIFICATION_FLAG.software_present - ) + set_flag_bits = NotificationFlag.WIRELESS | NotificationFlag.SOFTWARE_PRESENT else: set_flag_bits = 0 ok = _hidpp10.set_notification_flags(self, set_flag_bits) if ok is None: - _log.warn('%s: failed to %s receiver notifications', self, 'enable' if enable else 'disable') + logger.warning("%s: failed to %s receiver notifications", self, "enable" if enable else "disable") return None flag_bits = _hidpp10.get_notification_flags(self) - flag_names = None if flag_bits is None else tuple(_hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits)) - if _log.isEnabledFor(_INFO): - _log.info('%s: receiver notifications %s => %s', self, 'enabled' if enable else 'disabled', flag_names) + if flag_bits is None: + flag_names = None + else: + flag_names = hidpp10_constants.NotificationFlag.flag_names(flag_bits) + if logger.isEnabledFor(logging.INFO): + logger.info("%s: receiver notifications %s => %s", self, "enabled" if enable else "disabled", flag_names) return flag_bits def device_codename(self, n): - if self.receiver_kind == 'bolt': - codename = self.read_register(_R.receiver_info, _IR.bolt_device_name + n, 0x01) - if codename: - codename = codename[3:3 + min(14, ord(codename[2:3]))] - return codename.decode('ascii') - return - codename = self.read_register(_R.receiver_info, _IR.device_name + n - 1) + codename = self.read_register(Registers.RECEIVER_INFO, InfoSubRegisters.DEVICE_NAME + n - 1) if codename: - codename = codename[2:2 + ord(codename[1:2])] - return codename.decode('ascii') - - def device_pairing_information(self, n): - if self.receiver_kind == 'bolt': - pair_info = self.read_register(_R.receiver_info, _IR.bolt_pairing_information + n) - if pair_info: - wpid = _strhex(pair_info[3:4]) + _strhex(pair_info[2:3]) - kind = _hidpp10.DEVICE_KIND[ord(pair_info[1:2]) & 0x0F] - return wpid, kind, 0 - else: - raise _base.NoSuchDevice(number=n, receiver=self, error='read Bolt wpid') - wpid = 0 - kind = None - polling_rate = None - pair_info = self.read_register(_R.receiver_info, _IR.pairing_information + n - 1) - if pair_info: # may be either a Unifying receiver, or an Unifying-ready receiver - wpid = _strhex(pair_info[3:5]) - kind = _hidpp10.DEVICE_KIND[ord(pair_info[7:8]) & 0x0F] - polling_rate = ord(pair_info[2:3]) - elif self.receiver_kind == '27Mz': # 27Mhz receiver, fill extracting WPID from udev path - wpid = _hid.find_paired_node_wpid(self.path, n) - if not wpid: - _log.error('Unable to get wpid from udev for device %d of %s', n, self) - raise _base.NoSuchDevice(number=n, receiver=self, error='Not present 27Mhz device') - kind = _hidpp10.DEVICE_KIND[self.get_kind_from_index(n)] - elif not self.receiver_kind == 'unifying': # unifying protocol not supported, may be an old Nano receiver - device_info = self.read_register(_R.receiver_info, 0x04) - if device_info: - wpid = _strhex(device_info[3:5]) - kind = _hidpp10.DEVICE_KIND[0x00] # unknown kind - else: - raise _base.NoSuchDevice(number=n, receiver=self, error='read pairing information - non-unifying') - else: - raise _base.NoSuchDevice(number=n, receiver=self, error='read pairing information') - return wpid, kind, polling_rate - - def device_extended_pairing_information(self, n): - serial = None - power_switch = '(unknown)' - if self.receiver_kind == 'bolt': - pair_info = self.read_register(_R.receiver_info, _IR.bolt_pairing_information + n) - if pair_info: - serial = _strhex(pair_info[4:8]) - return serial, power_switch - else: - return '?', power_switch - pair_info = self.read_register(_R.receiver_info, _IR.extended_pairing_information + n - 1) - if pair_info: - power_switch = _hidpp10.POWER_SWITCH_LOCATION[ord(pair_info[9:10]) & 0x0F] - else: # some Nano receivers? - pair_info = self.read_register(0x2D5) - if pair_info: - serial = _strhex(pair_info[1:5]) - return serial, power_switch - - def get_kind_from_index(self, index): - """Get device kind from 27Mhz device index""" - # accordingly to drivers/hid/hid-logitech-dj.c - # index 1 or 2 always mouse, index 3 always the keyboard, - # index 4 is used for an optional separate numpad - if index == 1: # mouse - kind = 2 - elif index == 2: # mouse - kind = 2 - elif index == 3: # keyboard - kind = 1 - elif index == 4: # numpad - kind = 3 - else: # unknown device number on 27Mhz receiver - _log.error('failed to calculate device kind for device %d of %s', index, self) - raise _base.NoSuchDevice(number=index, receiver=self, error='Unknown 27Mhz device number') - return kind + return extract_codename(codename) def notify_devices(self): """Scan all devices.""" if self.handle: - if not self.write_register(_R.receiver_connection, 0x02): - _log.warn('%s: failed to trigger device link notifications', self) + if not self.write_register(Registers.RECEIVER_CONNECTION, 0x02): + logger.warning("%s: failed to trigger device link notifications", self) + + def notification_information(self, number, notification: HIDPPNotification) -> tuple[bool, bool, typing.Any, str]: + """Extract information from unifying-style notification""" + assert notification.address != 0x02 + online = not bool(notification.data[0] & 0x40) + encrypted = bool(notification.data[0] & 0x20) or notification.address == 0x10 + kind = extract_device_kind(notification.data[0] & 0x0F) + wpid = extract_wpid(notification.data[2:3] + notification.data[1:2]) + return online, encrypted, wpid, kind + + def device_pairing_information(self, n: int) -> dict: + """Return information from pairing registers (and elsewhere when necessary)""" + polling_rate = "" + serial = None + power_switch = "(unknown)" + pair_info = self.read_register(Registers.RECEIVER_INFO, InfoSubRegisters.PAIRING_INFORMATION + n - 1) + if pair_info: # a receiver that uses Unifying-style pairing registers + wpid = extract_wpid(pair_info[3:5]) + kind = extract_device_kind(pair_info[7] & 0x0F) + polling_rate_ms = extract_polling_rate(pair_info) + polling_rate = f"{polling_rate_ms}ms" + elif not self.receiver_kind == "unifying": # may be an old Nano receiver + device_info = self.read_register(Registers.RECEIVER_INFO, 0x04) # undocumented + if device_info: + logger.warning("using undocumented register for device wpid") + wpid = extract_wpid(device_info[3:5]) + kind = extract_device_kind(0x00) # unknown kind + else: + raise exceptions.NoSuchDevice(number=n, receiver=self, error="read pairing information - non-unifying") + else: + raise exceptions.NoSuchDevice(number=n, receiver=self, error="read pairing information") + pair_info = self.read_register(Registers.RECEIVER_INFO, InfoSubRegisters.EXTENDED_PAIRING_INFORMATION + n - 1) + if pair_info: + power_switch = extract_power_switch_location(pair_info) + serial = extract_serial(pair_info[1:5]) + else: # some Nano receivers? + pair_info = self.read_register(0x2D5) # undocumented and questionable + if pair_info: + logger.warning("using undocumented register for device serial number") + serial = extract_serial(pair_info[1:5]) + return {"wpid": wpid, "kind": kind, "polling": polling_rate, "serial": serial, "power_switch": power_switch} def register_new_device(self, number, notification=None): if self._devices.get(number) is not None: - raise IndexError('%s: device number %d already registered' % (self, number)) + raise IndexError(f"{self}: device number {int(number)} already registered") assert notification is None or notification.devnumber == number - assert notification is None or notification.sub_id == 0x41 + assert notification is None or notification.sub_id == Notification.DJ_PAIRING try: - dev = Device(self, number, notification) - if _log.isEnabledFor(_INFO): - _log.info('%s: found new device %d (%s)', self, number, dev.wpid) + time.sleep(0.05) # let receiver settle + info = self.device_pairing_information(number) + if notification is not None: + online, _e, nwpid, nkind = self.notification_information(number, notification) + if info["wpid"] is None: + info["wpid"] = nwpid + elif nwpid is not None and info["wpid"] != nwpid: + logger.warning("mismatch on device WPID %s %s", info["wpid"], nwpid) + if info["kind"] is None: + info["kind"] = nkind + elif nkind is not None and info["kind"] != nkind: + logger.warning("mismatch on device kind %s %s", info["kind"], nkind) + else: + online = True + dev = Device(self.low_level, self, number, online, pairing_info=info, setting_callback=self.setting_callback) + if logger.isEnabledFor(logging.INFO): + logger.info("%s: found new device %d (%s)", self, number, dev.wpid) self._devices[number] = dev return dev - except _base.NoSuchDevice as e: - _log.warn('register new device failed for %s device %d error %s', e.receiver, e.number, e.error) + except exceptions.NoSuchDevice as e: + logger.warning("register new device failed for %s device %d error %s", e.receiver, e.number, e.error) - _log.warning('%s: looked for device %d, not found', self, number) + logger.warning("%s: looked for device %d, not found", self, number) self._devices[number] = None def set_lock(self, lock_closed=True, device=0, timeout=0): if self.handle: action = 0x02 if lock_closed else 0x01 - reply = self.write_register(_R.receiver_pairing, action, device, timeout) + reply = self.write_register(Registers.RECEIVER_PAIRING, action, device, timeout) if reply: return True - _log.warn('%s: failed to %s the receiver lock', self, 'close' if lock_closed else 'open') - - def discover(self, cancel=False, timeout=30): # Bolt device discovery - assert self.receiver_kind == 'bolt' - if self.handle: - action = 0x02 if cancel else 0x01 - reply = self.write_register(_R.bolt_device_discovery, timeout, action) - if reply: - return True - _log.warn('%s: failed to %s device discovery', self, 'cancel' if cancel else 'start') - - def pair_device(self, pair=True, slot=0, address=b'\0\0\0\0\0\0', authentication=0x00, entropy=20): # Bolt pairing - assert self.receiver_kind == 'bolt' - if self.handle: - action = 0x01 if pair is True else 0x03 if pair is False else 0x02 - reply = self.write_register(_R.bolt_pairing, action, slot, address, authentication, entropy) - if reply: - return True - _log.warn('%s: failed to %s device %s', self, 'pair' if pair else 'unpair', address) + logger.warning("%s: failed to %s the receiver lock", self, "close" if lock_closed else "open") def count(self): - count = self.read_register(_R.receiver_connection) - return 0 if count is None else ord(count[1:2]) - - # def has_devices(self): - # return len(self) > 0 or self.count() > 0 + count = self.read_register(Registers.RECEIVER_CONNECTION) + if count is None: + return 0 + return extract_connection_count(count) def request(self, request_id, *params): if bool(self): - return _base.request(self.handle, 0xFF, request_id, *params) + return self.low_level.request(self.handle, 0xFF, request_id, *params) - read_register = _hidpp10.read_register - write_register = _hidpp10.write_register + def reset_pairing(self): + self.pairing = Pairing() def __iter__(self): connected_devices = self.count() @@ -311,7 +367,7 @@ class Receiver: return dev if not isinstance(key, int): - raise TypeError('key must be an integer') + raise TypeError("key must be an integer") if key < 1 or key > 15: # some receivers have devices past their max # devices raise IndexError(key) @@ -338,23 +394,49 @@ class Receiver: dev.wpid = None if key in self._devices: del self._devices[key] - _log.warn('%s removed device %s', self, dev) + logger.warning("%s removed device %s", self, dev) else: - if self.receiver_kind == 'bolt': - reply = self.write_register(_R.bolt_pairing, 0x03, key) - else: - reply = self.write_register(_R.receiver_pairing, 0x03, key) + reply = self._unpair_device_per_receiver(key) if reply: # invalidate the device dev.online = False dev.wpid = None if key in self._devices: del self._devices[key] - if _log.isEnabledFor(_INFO): - _log.info('%s unpaired device %s', self, dev) + if logger.isEnabledFor(logging.INFO): + logger.info("%s unpaired device %s", self, dev) else: - _log.error('%s failed to unpair device %s', self, dev) - raise Exception('failed to unpair device %s: %s' % (dev.name, key)) + logger.error("%s failed to unpair device %s", self, dev) + raise Exception(f"failed to unpair device {dev.name}: {key}") + + def _unpair_device_per_receiver(self, key): + """Receiver specific unpairing.""" + return self.write_register(Registers.RECEIVER_PAIRING, 0x03, key) + + def force_unpair_slot(self, slot: int) -> bool: + """Force-unpair a slot by writing the unpair register, ignoring cache state. + + Intended for clearing stale pairings on receivers (Lightspeed in particular) + where Solaar cannot read pairing info for a slot. Bypasses the ``may_unpair`` + and ``re_pairs`` gates that ``_unpair_device`` applies. Returns True if the + register write was acknowledged by the receiver. + """ + if not self.handle: + return False + slot = int(slot) + reply = self._unpair_device_per_receiver(slot) + if reply: + cached = self._devices.get(slot) + if cached: + cached.online = False + cached.wpid = None + if slot in self._devices: + del self._devices[slot] + if logger.isEnabledFor(logging.INFO): + logger.info("%s force-unpaired slot %d", self, slot) + return True + logger.warning("%s failed to force-unpair slot %d", self, slot) + return False def __len__(self): return len([d for d in self._devices.values() if d is not None]) @@ -374,26 +456,174 @@ class Receiver: def __hash__(self): return self.path.__hash__() + def status_string(self): + count = len(self) + return ( + _("No paired devices.") + if count == 0 + else ngettext("%(count)s paired device.", "%(count)s paired devices.", count) % {"count": count} + ) + def __str__(self): - return self._str + return "<%s(%s,%s%s)>" % ( + self.name.replace(" ", ""), + self.path, + "" if isinstance(self.handle, int) else "T", + self.handle, + ) __repr__ = __str__ __bool__ = __nonzero__ = lambda self: self.handle is not None - @classmethod - def open(self, device_info): - """Opens a Logitech Receiver found attached to the machine, by Linux device path. - :returns: An open file handle for the found receiver, or ``None``. - """ - try: - handle = _base.open_path(device_info.path) - if handle: - return Receiver(handle, device_info.path, device_info.product_id) - except OSError as e: - _log.exception('open %s', device_info) - if e.errno == _errno.EACCES: - raise - except Exception: - _log.exception('open %s', device_info) +class BoltReceiver(Receiver): + """Bolt receivers use a different pairing prototol and have different pairing registers""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def initialize(self, product_info: dict): + serial_reply = self.read_register(Registers.BOLT_UNIQUE_ID) + self.serial = extract_serial(serial_reply) + self.max_devices = product_info.get("max_devices", 1) + + def device_codename(self, n): + codename = self.read_register(Registers.RECEIVER_INFO, InfoSubRegisters.BOLT_DEVICE_NAME + n, 0x01) + if codename: + codename = codename[3 : 3 + min(14, ord(codename[2:3]))] + return codename.decode("ascii") + + def device_pairing_information(self, n: int) -> dict: + pair_info = self.read_register(Registers.RECEIVER_INFO, InfoSubRegisters.BOLT_PAIRING_INFORMATION + n) + if pair_info: + wpid = extract_wpid(pair_info[3:4] + pair_info[2:3]) + kind = extract_device_kind(pair_info[1] & 0x0F) + serial = extract_serial(pair_info[4:8]) + return {"wpid": wpid, "kind": kind, "polling": None, "serial": serial, "power_switch": "(unknown)"} + else: + raise exceptions.NoSuchDevice(number=n, receiver=self, error="can't read Bolt pairing register") + + def discover(self, cancel=False, timeout=30): + """Discover Logitech Bolt devices.""" + if self.handle: + action = 0x02 if cancel else 0x01 + reply = self.write_register(Registers.BOLT_DEVICE_DISCOVERY, timeout, action) + if reply: + return True + logger.warning("%s: failed to %s device discovery", self, "cancel" if cancel else "start") + + def pair_device(self, pair=True, slot=0, address=b"\0\0\0\0\0\0", authentication=0x00, entropy=20): + """Pair a Bolt device.""" + if self.handle: + action = 0x01 if pair is True else 0x03 if pair is False else 0x02 + reply = self.write_register(Registers.BOLT_PAIRING, action, slot, address, authentication, entropy) + if reply: + return True + logger.warning("%s: failed to %s device %s", self, "pair" if pair else "unpair", address) + + def _unpair_device_per_receiver(self, key): + """Receiver specific unpairing.""" + return self.write_register(Registers.BOLT_PAIRING, 0x03, key) + + +class UnifyingReceiver(Receiver): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class NanoReceiver(Receiver): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class LightSpeedReceiver(Receiver): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class Ex100Receiver(Receiver): + """A very old style receiver, somewhat different from newer receivers""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def initialize(self, product_info: dict): + self.serial = None + self.max_devices = product_info.get("max_devices", 1) + + def notification_information(self, number, notification): + """Extract information from 27Mz-style notification and device index""" + assert notification.address == 0x02 + online = True + encrypted = bool(notification.data[0] & 0x80) + kind = extract_device_kind(_get_kind_from_index(self, number)) + wpid = "00" + extract_wpid(notification.data[2:3]) + return online, encrypted, wpid, kind + + def device_pairing_information(self, number: int) -> dict: + # extract WPID from udev path + wpid = self.low_level.find_paired_node_wpid(self.path, number) + if not wpid: + logger.error("Unable to get wpid from udev for device %d of %s", number, self) + raise exceptions.NoSuchDevice(number=number, receiver=self, error="Not present 27Mhz device") + kind = extract_device_kind(_get_kind_from_index(self, number)) + return {"wpid": wpid, "kind": kind, "polling": "", "serial": None, "power_switch": "(unknown)"} + + +def _get_kind_from_index(receiver, index: int) -> int: + """Get device kind from 27Mhz device index""" + # From drivers/hid/hid-logitech-dj.c + if index == 1: # mouse + kind = 2 + elif index == 2: # mouse + kind = 2 + elif index == 3: # keyboard + kind = 1 + elif index == 4: # numpad + kind = 3 + else: # unknown device number on 27Mhz receiver + logger.error("failed to calculate device kind for device %d of %s", index, receiver) + raise exceptions.NoSuchDevice(number=index, receiver=receiver, error="Unknown 27Mhz device number") + return kind + + +receiver_class_mapping = { + "bolt": BoltReceiver, + "unifying": UnifyingReceiver, + "lightspeed": LightSpeedReceiver, + "nano": NanoReceiver, + "27Mhz": Ex100Receiver, +} + + +def create_receiver(low_level: LowLevelInterface, device_info, setting_callback=None) -> Optional[Receiver]: + """Opens a Logitech Receiver found attached to the machine, by Linux device path.""" + + try: + handle = low_level.open_path(device_info.path) + if handle: + usb_id = device_info.product_id + if isinstance(usb_id, str): + usb_id = int(usb_id, 16) + try: + product_info = low_level.product_information(usb_id) + except ValueError: + product_info = {} + kind = product_info.get("receiver_kind", "unknown") + rclass = receiver_class_mapping.get(kind, Receiver) + return rclass( + low_level, + kind, + product_info, + handle, + device_info.path, + device_info.product_id, + setting_callback, + ) + except OSError as e: + logger.exception("open %s", device_info) + if e.errno == errno.EACCES: + raise e + except Exception: + logger.exception("open %s", device_info) diff --git a/lib/logitech_receiver/settings.py b/lib/logitech_receiver/settings.py index 6e7956c3..b5ddd23c 100644 --- a/lib/logitech_receiver/settings.py +++ b/lib/logitech_receiver/settings.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -15,214 +13,70 @@ ## You should have received a copy of the GNU General Public License along ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from __future__ import annotations -import math +import logging +import struct +import time -from logging import DEBUG as _DEBUG -from logging import WARNING as _WARNING -from logging import getLogger -from struct import unpack as _unpack -from time import sleep as _sleep +from enum import IntEnum +from typing import Any -from . import hidpp20 as _hidpp20 -from .common import NamedInt as _NamedInt -from .common import NamedInts as _NamedInts -from .common import bytes2int as _bytes2int -from .common import int2bytes as _int2bytes -from .i18n import _ +from solaar.i18n import _ -_log = getLogger(__name__) -del getLogger +from . import common +from . import hidpp20_constants +from . import settings_validator +from .centurion_constants import CenturionCoreFeature +from .common import NamedInt -# -# -# +logger = logging.getLogger(__name__) -SENSITIVITY_IGNORE = 'ignore' -KIND = _NamedInts( - toggle=0x01, choice=0x02, range=0x04, map_choice=0x0A, multiple_toggle=0x10, packed_range=0x20, multiple_range=0x40 -) +SENSITIVITY_IGNORE = "ignore" -def bool_or_toggle(current, new): - if isinstance(new, bool): - return new - try: - return bool(int(new)) - except (TypeError, ValueError): - new = str(new).lower() - if new in ('true', 'yes', 'on', 't', 'y'): - return True - if new in ('false', 'no', 'off', 'f', 'n'): - return False - if new in ('~', 'toggle'): - return not current - return None - - -# moved first for dependency reasons -class Validator: - - @classmethod - def build(cls, setting_class, device, **kwargs): - return cls(**kwargs) - - @classmethod - def to_string(cls, value): - return (str(value)) - - def compare(self, args, current): - if len(args) != 1: - return False - return args[0] == current - - -class BooleanValidator(Validator): - __slots__ = ('true_value', 'false_value', 'read_skip_byte_count', 'write_prefix_bytes', 'mask', 'needs_current_value') - - kind = KIND.toggle - default_true = 0x01 - default_false = 0x00 - # mask specifies all the affected bits in the value - default_mask = 0xFF - - def __init__( - self, - true_value=default_true, - false_value=default_false, - mask=default_mask, - read_skip_byte_count=0, - write_prefix_bytes=b'' - ): - if isinstance(true_value, int): - assert isinstance(false_value, int) - if mask is None: - mask = self.default_mask - else: - assert isinstance(mask, int) - assert true_value & false_value == 0 - assert true_value & mask == true_value - assert false_value & mask == false_value - self.needs_current_value = (mask != self.default_mask) - elif isinstance(true_value, bytes): - if false_value is None or false_value == self.default_false: - false_value = b'\x00' * len(true_value) - else: - assert isinstance(false_value, bytes) - if mask is None or mask == self.default_mask: - mask = b'\xFF' * len(true_value) - else: - assert isinstance(mask, bytes) - assert len(mask) == len(true_value) == len(false_value) - tv = _bytes2int(true_value) - fv = _bytes2int(false_value) - mv = _bytes2int(mask) - assert tv != fv # true and false might be something other than bit values - assert tv & mv == tv - assert fv & mv == fv - self.needs_current_value = any(m != 0xff for m in mask) - else: - raise Exception("invalid mask '%r', type %s" % (mask, type(mask))) - - self.true_value = true_value - self.false_value = false_value - self.mask = mask - self.read_skip_byte_count = read_skip_byte_count - self.write_prefix_bytes = write_prefix_bytes - - def validate_read(self, reply_bytes): - reply_bytes = reply_bytes[self.read_skip_byte_count:] - if isinstance(self.mask, int): - reply_value = ord(reply_bytes[:1]) & self.mask - if _log.isEnabledFor(_DEBUG): - _log.debug('BooleanValidator: validate read %r => %02X', reply_bytes, reply_value) - if reply_value == self.true_value: - return True - if reply_value == self.false_value: - return False - _log.warn( - 'BooleanValidator: reply %02X mismatched %02X/%02X/%02X', reply_value, self.true_value, self.false_value, - self.mask - ) - return False - - count = len(self.mask) - mask = _bytes2int(self.mask) - reply_value = _bytes2int(reply_bytes[:count]) & mask - - true_value = _bytes2int(self.true_value) - if reply_value == true_value: - return True - - false_value = _bytes2int(self.false_value) - if reply_value == false_value: - return False - - _log.warn('BooleanValidator: reply %r mismatched %r/%r/%r', reply_bytes, self.true_value, self.false_value, self.mask) - return False - - def prepare_write(self, new_value, current_value=None): - if new_value is None: - new_value = False - else: - assert isinstance(new_value, bool), 'New value %s for boolean setting is not a boolean' % new_value - - to_write = self.true_value if new_value else self.false_value - - if isinstance(self.mask, int): - if current_value is not None and self.needs_current_value: - to_write |= ord(current_value[:1]) & (0xFF ^ self.mask) - if current_value is not None and to_write == ord(current_value[:1]): - return None - to_write = bytes([to_write]) - else: - to_write = bytearray(to_write) - count = len(self.mask) - for i in range(0, count): - b = ord(to_write[i:i + 1]) - m = ord(self.mask[i:i + 1]) - assert b & m == b - # b &= m - if current_value is not None and self.needs_current_value: - b |= ord(current_value[i:i + 1]) & (0xFF ^ m) - to_write[i] = b - to_write = bytes(to_write) - - if current_value is not None and to_write == current_value[:len(to_write)]: - return None - - if _log.isEnabledFor(_DEBUG): - _log.debug('BooleanValidator: prepare_write(%s, %s) => %r', new_value, current_value, to_write) - - return self.write_prefix_bytes + to_write - - def acceptable(self, args, current): - if len(args) != 1: - return None - val = bool_or_toggle(current, args[0]) - return [val] if val is not None else None +class Kind(IntEnum): + NONE = 0 + TOGGLE = 0x01 + CHOICE = 0x02 + RANGE = 0x04 + MAP_CHOICE = 0x0A + MULTIPLE_TOGGLE = 0x10 + PACKED_RANGE = 0x20 + GRAPHIC_EQ = 0x21 + MULTIPLE_RANGE = 0x40 + HETERO = 0x80 + MAP_RANGE = 0x102 + COLOR = 0x200 class Setting: """A setting descriptor. Needs to be instantiated for each specific device.""" - name = label = description = '' + + name = label = description = "" feature = register = kind = None + min_version = 0 persist = True rw_options = {} - validator_class = BooleanValidator + validator_class = None validator_options = {} + display = True # display setting in UI + # Optional UI editor override as "module.path:ClassName". Resolved by the + # config panel before the Kind dispatch. Kept as a string so this module + # stays free of GTK imports — the FE/BE seam is preserved. + editor_class: str | None = None def __init__(self, device, rw, validator): self._device = device self._rw = rw self._validator = validator - self.kind = getattr(self._validator, 'kind', None) + self.kind = getattr(self._validator, "kind", None) self._value = None @classmethod def build(cls, device): - assert cls.feature or cls.register, 'Settings require either a feature or a register' - rw_class = cls.rw_class if hasattr(cls, 'rw_class') else FeatureRW if cls.feature else RegisterRW + assert cls.feature or cls.register, "Settings require either a feature or a register" + rw_class = cls.rw_class if hasattr(cls, "rw_class") else FeatureRW if cls.feature else RegisterRW rw = rw_class(cls.feature if cls.feature else cls.register, **cls.rw_options) p = device.protocol if p == 1.0: # HID++ 1.0 devices do not support features @@ -240,50 +94,50 @@ class Setting: @property def choices(self): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") - return self._validator.choices if self._validator and self._validator.kind & KIND.choice else None + return self._validator.choices if self._validator and self._validator.kind & Kind.CHOICE else None @property def range(self): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") - if self._validator.kind == KIND.range: - return (self._validator.min_value, self._validator.max_value) + if self._validator.kind == Kind.RANGE: + return self._validator.min_value, self._validator.max_value def _pre_read(self, cached, key=None): - if self.persist and self._value is None and getattr(self._device, 'persister', None): + if self.persist and self._value is None and getattr(self._device, "persister", None): # We haven't read a value from the device yet, # maybe we have something in the configuration. self._value = self._device.persister.get(self.name) if cached and self._value is not None: - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: # If this is a new device (or a new setting for an old device), # make sure to save its current value for the next time. self._device.persister[self.name] = self._value if self.persist else None def read(self, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") self._pre_read(cached) if cached and self._value is not None: - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: cached value %r on %s', self.name, self._value, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: cached value %r on %s", self.name, self._value, self._device) return self._value if self._device.online: reply = self._rw.read(self._device) if reply: self._value = self._validator.validate_read(reply) - if self._device.persister and self.name not in self._device.persister: + if self._value is not None and self._device.persister and self.name not in self._device.persister: # Don't update the persister if it already has a value, # otherwise the first read might overwrite the value we wanted. self._device.persister[self.name] = self._value if self.persist else None - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: read value %r on %s', self.name, self._value, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: read value %r on %s", self.name, self._value, self._device) return self._value def _pre_write(self, save=True): @@ -298,12 +152,12 @@ class Setting: self._pre_write(save) def write(self, value, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert value is not None - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: write %r to %s', self.name, value, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: write %r to %s", self.name, value, self._device) if self._device.online: if self._value != value: @@ -313,13 +167,13 @@ class Setting: if self._validator.needs_current_value: # the _validator needs the current value, possibly to merge flag values current_value = self._rw.read(self._device) - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: current value %r on %s', self.name, current_value, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: current value %r on %s", self.name, current_value, self._device) data_bytes = self._validator.prepare_write(value, current_value) if data_bytes is not None: - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: prepare write(%s) => %r', self.name, value, data_bytes) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: prepare write(%s) => %r", self.name, value, data_bytes) reply = self._rw.write(self._device, data_bytes) if not reply: @@ -335,25 +189,29 @@ class Setting: return self._validator.compare(args, current) if self._validator else None def apply(self): - assert hasattr(self, '_value') - assert hasattr(self, '_device') - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: apply (%s)', self.name, self._device) - value = self.read(self.persist) # Don't use persisted value if setting doesn't persist - if self.persist and value is not None: # If setting doesn't persist no need to write value just read - try: + assert hasattr(self, "_value") + assert hasattr(self, "_device") + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: apply (%s)", self.name, self._device) + try: + value = self.read(self.persist) # Don't use persisted value if setting doesn't persist + if self.persist and value is not None: # If setting doesn't persist no need to write value just read self.write(value, save=False) - except Exception: - if _log.isEnabledFor(_WARNING): - _log.warn('%s: error applying value %s so ignore it (%s)', self.name, self._value, self._device) + except Exception as e: + if logger.isEnabledFor(logging.WARNING): + logger.warning("%s: error applying %s so ignore it (%s): %s", self.name, self._value, self._device, repr(e)) def __str__(self): - if hasattr(self, '_value'): - assert hasattr(self, '_device') - return '' % ( - self._rw.kind, self._validator.kind if self._validator else None, self._device.codename, self.name, self._value + if hasattr(self, "_value"): + assert hasattr(self, "_device") + return "" % ( + self._rw.kind, + self._validator.kind if self._validator else None, + self._device.codename, + self.name, + self._value, ) - return '' % (self._rw.kind, self._validator.kind if self._validator else None, self.name) + return f"" __repr__ = __str__ @@ -363,10 +221,10 @@ class Settings(Setting): Needs to be instantiated for each specific device.""" def read(self, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings read %r from %s', self.name, self._value, self._device) + assert hasattr(self, "_value") + assert hasattr(self, "_device") + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings read %r from %s", self.name, self._value, self._device) self._pre_read(cached) @@ -380,18 +238,18 @@ class Settings(Setting): if reply: reply_map[int(key)] = self._validator.validate_read(reply, key) self._value = reply_map - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: # Don't update the persister if it already has a value, # otherwise the first read might overwrite the value we wanted. self._device.persister[self.name] = self._value if self.persist else None return self._value def read_key(self, key, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert key is not None - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings read %r key %r from %s', self.name, self._value, key, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings read %r key %r from %s", self.name, self._value, key, self._device) self._pre_read(cached) if cached and self._value is not None: @@ -401,25 +259,25 @@ class Settings(Setting): reply = self._rw.read(self._device, key) if reply: self._value[int(key)] = self._validator.validate_read(reply, key) - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: self._device.persister[self.name] = self._value if self.persist else None return self._value[int(key)] def write(self, map, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert map is not None - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings write %r to %s', self.name, map, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings write %r to %s", self.name, map, self._device) if self._device.online: self.update(map, save) for key, value in map.items(): data_bytes = self._validator.prepare_write(int(key), value) if data_bytes is not None: - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings prepare map write(%s,%s) => %r', self.name, key, value, data_bytes) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings prepare map write(%s,%s) => %r", self.name, key, value, data_bytes) reply = self._rw.write(self._device, int(key), data_bytes) if not reply: return None @@ -429,14 +287,14 @@ class Settings(Setting): self._value[int(key)] = value self._pre_write(save) - def write_key_value(self, key, value, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + def write_key_value(self, key, value, save=True) -> Any | None: + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert key is not None assert value is not None - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings write key %r value %r to %s', self.name, key, value, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings write key %r value %r to %s", self.name, key, value, self._device) if self._device.online: if not self._value: @@ -448,8 +306,8 @@ class Settings(Setting): except ValueError: data_bytes = value = None if data_bytes is not None: - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings prepare key value write(%s,%s) => %r', self.name, key, value, data_bytes) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings prepare key value write(%s,%s) => %r", self.name, key, value, data_bytes) reply = self._rw.write(self._device, int(key), data_bytes) if not reply: return None @@ -463,10 +321,10 @@ class LongSettings(Setting): Needs to be instantiated for each specific device.""" def read(self, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings read %r from %s', self.name, self._value, self._device) + assert hasattr(self, "_value") + assert hasattr(self, "_device") + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings read %r from %s", self.name, self._value, self._device) self._pre_read(cached) @@ -482,18 +340,18 @@ class LongSettings(Setting): if reply: reply_map[int(item)] = self._validator.validate_read_item(reply, item) self._value = reply_map - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: # Don't update the persister if it already has a value, # otherwise the first read might overwrite the value we wanted. self._device.persister[self.name] = self._value if self.persist else None return self._value def read_item(self, item, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert item is not None - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings read %r item %r from %s', self.name, self._value, item, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings read %r item %r from %s", self.name, self._value, item, self._device) self._pre_read(cached) if cached and self._value is not None: @@ -504,17 +362,17 @@ class LongSettings(Setting): reply = self._rw.read(self._device, r) if reply: self._value[int(item)] = self._validator.validate_read_item(reply, item) - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: self._device.persister[self.name] = self._value if self.persist else None return self._value[int(item)] def write(self, map, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert map is not None - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: long settings write %r to %s', self.name, map, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: long settings write %r to %s", self.name, map, self._device) if self._device.online: self.update(map, save) for item, value in map.items(): @@ -522,8 +380,8 @@ class LongSettings(Setting): if data_bytes_list is not None: for data_bytes in data_bytes_list: if data_bytes is not None: - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings prepare map write(%s,%s) => %r', self.name, item, value, data_bytes) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings prepare map write(%s,%s) => %r", self.name, item, value, data_bytes) reply = self._rw.write(self._device, data_bytes) if not reply: return None @@ -534,13 +392,13 @@ class LongSettings(Setting): self._pre_write(save) def write_key_value(self, item, value, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert item is not None assert value is not None - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: long settings write item %r value %r to %s', self.name, item, value, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: long settings write item %r value %r to %s", self.name, item, value, self._device) if self._device.online: if not self._value: @@ -548,8 +406,8 @@ class LongSettings(Setting): data_bytes = self._validator.prepare_write_item(item, value) self.update_key_value(item, value, save) if data_bytes is not None: - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings prepare item value write(%s,%s) => %r', self.name, item, value, data_bytes) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings prepare item value write(%s,%s) => %r", self.name, item, value, data_bytes) reply = self._rw.write(self._device, data_bytes) if not reply: return None @@ -561,10 +419,10 @@ class BitFieldSetting(Setting): Needs to be instantiated for each specific device.""" def read(self, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings read %r from %s', self.name, self._value, self._device) + assert hasattr(self, "_value") + assert hasattr(self, "_device") + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings read %r from %s", self.name, self._value, self._device) self._pre_read(cached) @@ -577,7 +435,7 @@ class BitFieldSetting(Setting): if reply: reply_map = self._validator.validate_read(reply) self._value = reply_map - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: # Don't update the persister if it already has a value, # otherwise the first read might overwrite the value we wanted. self._device.persister[self.name] = self._value if self.persist else None @@ -587,11 +445,11 @@ class BitFieldSetting(Setting): return self._rw.read(self._device) def read_key(self, key, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert key is not None - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings read %r key %r from %s', self.name, self._value, key, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings read %r key %r from %s", self.name, self._value, key, self._device) self._pre_read(cached) @@ -602,7 +460,7 @@ class BitFieldSetting(Setting): reply = self._do_read_key(key) if reply: self._value = self._validator.validate_read(reply) - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: self._device.persister[self.name] = self._value if self.persist else None return self._value[int(key)] @@ -610,18 +468,18 @@ class BitFieldSetting(Setting): return self._rw.read(self._device, key) def write(self, map, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert map is not None - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: bit field settings write %r to %s', self.name, map, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: bit field settings write %r to %s", self.name, map, self._device) if self._device.online: self.update(map, save) data_bytes = self._validator.prepare_write(self._value) if data_bytes is not None: - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings prepare map write(%s) => %r', self.name, self._value, data_bytes) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings prepare map write(%s) => %r", self.name, self._value, data_bytes) # if prepare_write returns a list, write one item at a time seq = data_bytes if isinstance(data_bytes, list) else [data_bytes] for b in seq: @@ -635,13 +493,13 @@ class BitFieldSetting(Setting): self._pre_write(save) def write_key_value(self, key, value, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert key is not None assert value is not None - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: bit field settings write key %r value %r to %s', self.name, key, value, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: bit field settings write key %r value %r to %s", self.name, key, value, self._device) if self._device.online: if not self._value: @@ -651,8 +509,8 @@ class BitFieldSetting(Setting): data_bytes = self._validator.prepare_write(self._value) if data_bytes is not None: - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings prepare key value write(%s,%s) => %r', self.name, key, str(value), data_bytes) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings prepare key value write(%s,%s) => %r", self.name, key, str(value), data_bytes) # if prepare_write returns a list, write one item at a time seq = data_bytes if isinstance(data_bytes, list) else [data_bytes] for b in seq: @@ -681,10 +539,10 @@ class RangeFieldSetting(Setting): Needs to be instantiated for each specific device.""" def read(self, cached=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: settings read %r from %s', self.name, self._value, self._device) + assert hasattr(self, "_value") + assert hasattr(self, "_device") + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings read %r from %s", self.name, self._value, self._device) self._pre_read(cached) if cached and self._value is not None: return self._value @@ -694,7 +552,7 @@ class RangeFieldSetting(Setting): if reply: reply_map = self._validator.validate_read(reply) self._value = reply_map - if getattr(self._device, 'persister', None) and self.name not in self._device.persister: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: # Don't update the persister if it already has a value, # otherwise the first read might overwrite the value we wanted. self._device.persister[self.name] = self._value if self.persist else None @@ -707,29 +565,29 @@ class RangeFieldSetting(Setting): return self.read(cached)[int(key)] def write(self, map, save=True): - assert hasattr(self, '_value') - assert hasattr(self, '_device') + assert hasattr(self, "_value") + assert hasattr(self, "_device") assert map is not None - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: range field setting write %r to %s', self.name, map, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: range field setting write %r to %s", self.name, map, self._device) if self._device.online: self.update(map, save) data_bytes = self._validator.prepare_write(self._value) if data_bytes is not None: - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: range field setting prepare map write(%s) => %r', self.name, self._value, data_bytes) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: range field setting prepare map write(%s) => %r", self.name, self._value, data_bytes) reply = self._rw.write(self._device, data_bytes) if not reply: return None - elif _log.isEnabledFor(_WARNING): - _log.warn('%s: range field setting no data to write', self.name) + elif logger.isEnabledFor(logging.WARNING): + logger.warning("%s: range field setting no data to write", self.name) return map def write_key_value(self, key, value, save=True): assert key is not None assert value is not None - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: range field setting write key %r value %r to %s', self.name, key, value, self._device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: range field setting write key %r value %r to %s", self.name, key, value, self._device) if self._device.online: if not self._value: self.read() @@ -745,11 +603,11 @@ class RangeFieldSetting(Setting): class RegisterRW: - __slots__ = ('register', ) + __slots__ = ("register",) - kind = _NamedInt(0x01, _('register')) + kind = NamedInt(0x01, _("register")) - def __init__(self, register): + def __init__(self, register: int): assert isinstance(register, int) self.register = register @@ -761,12 +619,21 @@ class RegisterRW: class FeatureRW: - kind = _NamedInt(0x02, _('feature')) + kind = NamedInt(0x02, _("feature")) default_read_fnid = 0x00 default_write_fnid = 0x10 - def __init__(self, feature, read_fnid=0x00, write_fnid=0x10, prefix=b'', suffix=b'', read_prefix=b'', no_reply=False): - assert isinstance(feature, _NamedInt) + def __init__( + self, + feature: hidpp20_constants.SupportedFeature, + read_fnid=0x00, + write_fnid=0x10, + prefix=b"", + suffix=b"", + read_prefix=b"", + no_reply=False, + ): + assert isinstance(feature, (hidpp20_constants.SupportedFeature, CenturionCoreFeature)) self.feature = feature self.read_fnid = read_fnid self.write_fnid = write_fnid @@ -775,33 +642,35 @@ class FeatureRW: self.suffix = suffix self.read_prefix = read_prefix - def read(self, device, data_bytes=b''): + def read(self, device, data_bytes=b""): assert self.feature is not None - return device.feature_request(self.feature, self.read_fnid, self.prefix, self.read_prefix, data_bytes) + if self.read_fnid is not None: + return device.feature_request(self.feature, self.read_fnid, self.prefix, self.read_prefix, data_bytes) + else: + return b"" def write(self, device, data_bytes): assert self.feature is not None - reply = device.feature_request( - self.feature, self.write_fnid, self.prefix, data_bytes, self.suffix, no_reply=self.no_reply - ) + write_bytes = self.prefix + (data_bytes.to_bytes(1) if isinstance(data_bytes, int) else data_bytes) + self.suffix + reply = device.feature_request(self.feature, self.write_fnid, write_bytes, no_reply=self.no_reply) return reply if not self.no_reply else True class FeatureRWMap(FeatureRW): - kind = _NamedInt(0x02, _('feature')) + kind = NamedInt(0x02, _("feature")) default_read_fnid = 0x00 default_write_fnid = 0x10 default_key_byte_count = 1 def __init__( self, - feature, + feature: hidpp20_constants.SupportedFeature, read_fnid=default_read_fnid, write_fnid=default_write_fnid, key_byte_count=default_key_byte_count, - no_reply=False + no_reply=False, ): - assert isinstance(feature, _NamedInt) + assert isinstance(feature, (hidpp20_constants.SupportedFeature, CenturionCoreFeature)) self.feature = feature self.read_fnid = read_fnid self.write_fnid = write_fnid @@ -810,543 +679,20 @@ class FeatureRWMap(FeatureRW): def read(self, device, key): assert self.feature is not None - key_bytes = _int2bytes(key, self.key_byte_count) + key_bytes = common.int2bytes(key, self.key_byte_count) return device.feature_request(self.feature, self.read_fnid, key_bytes) def write(self, device, key, data_bytes): assert self.feature is not None - key_bytes = _int2bytes(key, self.key_byte_count) + key_bytes = common.int2bytes(key, self.key_byte_count) reply = device.feature_request(self.feature, self.write_fnid, key_bytes, data_bytes, no_reply=self.no_reply) return reply if not self.no_reply else True -class BitFieldValidator(Validator): - __slots__ = ('byte_count', 'options') - - kind = KIND.multiple_toggle - - def __init__(self, options, byte_count=None): - assert (isinstance(options, list)) - self.options = options - self.byte_count = (max(x.bit_length() for x in options) + 7) // 8 - if byte_count: - assert (isinstance(byte_count, int) and byte_count >= self.byte_count) - self.byte_count = byte_count - - def to_string(self, value): - - def element_to_string(key, val): - k = next((k for k in self.options if int(key) == k), None) - return str(k) + ':' + str(val) if k is not None else '?' - - return '{' + ', '.join([element_to_string(k, value[k]) for k in value]) + '}' - - def validate_read(self, reply_bytes): - r = _bytes2int(reply_bytes[:self.byte_count]) - value = {int(k): False for k in self.options} - m = 1 - for _ignore in range(8 * self.byte_count): - if m in self.options: - value[int(m)] = bool(r & m) - m <<= 1 - return value - - def prepare_write(self, new_value): - assert (isinstance(new_value, dict)) - w = 0 - for k, v in new_value.items(): - if v: - w |= int(k) - return _int2bytes(w, self.byte_count) - - def get_options(self): - return self.options - - def acceptable(self, args, current): - if len(args) != 2: - return None - key = next((key for key in self.options if key == args[0]), None) - if key is None: - return None - val = bool_or_toggle(current[int(key)], args[1]) - return None if val is None else [int(key), val] - - def compare(self, args, current): - if len(args) != 2: - return False - key = next((key for key in self.options if key == args[0]), None) - if key is None: - return False - return args[1] == current[int(key)] - - -class BitFieldWithOffsetAndMaskValidator(Validator): - __slots__ = ('byte_count', 'options', '_option_from_key', '_mask_from_offset', '_option_from_offset_mask') - - kind = KIND.multiple_toggle - sep = 0x01 - - def __init__(self, options, om_method=None, byte_count=None): - assert (isinstance(options, list)) - # each element of options is an instance of a class - # that has an id (which is used as an index in other dictionaries) - # and where om_method is a method that returns a byte offset and byte mask - # that says how to access and modify the bit toggle for the option - self.options = options - self.om_method = om_method - # to retrieve the options efficiently: - self._option_from_key = {} - self._mask_from_offset = {} - self._option_from_offset_mask = {} - for opt in options: - offset, mask = om_method(opt) - self._option_from_key[int(opt)] = opt - try: - self._mask_from_offset[offset] |= mask - except KeyError: - self._mask_from_offset[offset] = mask - try: - mask_to_opt = self._option_from_offset_mask[offset] - except KeyError: - mask_to_opt = {} - self._option_from_offset_mask[offset] = mask_to_opt - mask_to_opt[mask] = opt - self.byte_count = (max(om_method(x)[1].bit_length() for x in options) + 7) // 8 # is this correct?? - if byte_count: - assert (isinstance(byte_count, int) and byte_count >= self.byte_count) - self.byte_count = byte_count - - def prepare_read(self): - r = [] - for offset, mask in self._mask_from_offset.items(): - b = (offset << (8 * (self.byte_count + 1))) - b |= (self.sep << (8 * self.byte_count)) | mask - r.append(_int2bytes(b, self.byte_count + 2)) - return r - - def prepare_read_key(self, key): - option = self._option_from_key.get(key, None) - if option is None: - return None - offset, mask = option.om_method(option) - b = offset << (8 * (self.byte_count + 1)) - b |= (self.sep << (8 * self.byte_count)) | mask - return _int2bytes(b, self.byte_count + 2) - - def validate_read(self, reply_bytes_dict): - values = {int(k): False for k in self.options} - for query, b in reply_bytes_dict.items(): - offset = _bytes2int(query[0:1]) - b += (self.byte_count - len(b)) * b'\x00' - value = _bytes2int(b[:self.byte_count]) - mask_to_opt = self._option_from_offset_mask.get(offset, {}) - m = 1 - for _ignore in range(8 * self.byte_count): - if m in mask_to_opt: - values[int(mask_to_opt[m])] = bool(value & m) - m <<= 1 - return values - - def prepare_write(self, new_value): - assert (isinstance(new_value, dict)) - w = {} - for k, v in new_value.items(): - option = self._option_from_key[int(k)] - offset, mask = self.om_method(option) - if offset not in w: - w[offset] = 0 - if v: - w[offset] |= mask - return [ - _int2bytes((offset << (8 * (2 * self.byte_count + 1))) - | (self.sep << (16 * self.byte_count)) - | (self._mask_from_offset[offset] << (8 * self.byte_count)) - | value, 2 * self.byte_count + 2) for offset, value in w.items() - ] - - def get_options(self): - return [int(opt) if isinstance(opt, int) else opt.as_int() for opt in self.options] - - def acceptable(self, args, current): - if len(args) != 2: - return None - key = next((option.id for option in self.options if option.as_int() == args[0]), None) - if key is None: - return None - val = bool_or_toggle(current[int(key)], args[1]) - return None if val is None else [int(key), val] - - def compare(self, args, current): - if len(args) != 2: - return False - key = next((option.id for option in self.options if option.as_int() == args[0]), None) - if key is None: - return False - return args[1] == current[int(key)] - - -class ChoicesValidator(Validator): - """Translates between NamedInts and a byte sequence. - :param choices: a list of NamedInts - :param byte_count: the size of the derived byte sequence. If None, it - will be calculated from the choices.""" - kind = KIND.choice - - def __init__(self, choices=None, byte_count=None, read_skip_byte_count=0, write_prefix_bytes=b''): - assert choices is not None - assert isinstance(choices, _NamedInts) - assert len(choices) > 1 - self.choices = choices - self.needs_current_value = False - - max_bits = max(x.bit_length() for x in choices) - self._byte_count = (max_bits // 8) + (1 if max_bits % 8 else 0) - if byte_count: - assert self._byte_count <= byte_count - self._byte_count = byte_count - assert self._byte_count < 8 - self._read_skip_byte_count = read_skip_byte_count - self._write_prefix_bytes = write_prefix_bytes if write_prefix_bytes else b'' - assert self._byte_count + self._read_skip_byte_count <= 14 - assert self._byte_count + len(self._write_prefix_bytes) <= 14 - - def to_string(self, value): - return str(self.choices[value]) if isinstance(value, int) else str(value) - - def validate_read(self, reply_bytes): - reply_value = _bytes2int(reply_bytes[self._read_skip_byte_count:self._read_skip_byte_count + self._byte_count]) - valid_value = self.choices[reply_value] - assert valid_value is not None, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value) - return valid_value - - def prepare_write(self, new_value, current_value=None): - if new_value is None: - value = self.choices[:][0] - else: - value = self.choice(new_value) - if value is None: - raise ValueError('invalid choice %r' % new_value) - assert isinstance(value, _NamedInt) - return self._write_prefix_bytes + value.bytes(self._byte_count) - - def choice(self, value): - if isinstance(value, int): - return self.choices[value] - try: - int(value) - if int(value) in self.choices: - return self.choices[int(value)] - except Exception: - pass - if value in self.choices: - return self.choices[value] - else: - return None - - def acceptable(self, args, current): - choice = self.choice(args[0]) if len(args) == 1 else None - return None if choice is None else [choice] - - -class ChoicesMapValidator(ChoicesValidator): - kind = KIND.map_choice - - def __init__( - self, - choices_map, - key_byte_count=0, - key_postfix_bytes=b'', - byte_count=0, - read_skip_byte_count=0, - write_prefix_bytes=b'', - extra_default=None, - mask=-1, - activate=0 - ): - assert choices_map is not None - assert isinstance(choices_map, dict) - max_key_bits = 0 - max_value_bits = 0 - for key, choices in choices_map.items(): - assert isinstance(key, _NamedInt) - assert isinstance(choices, _NamedInts) - max_key_bits = max(max_key_bits, key.bit_length()) - for key_value in choices: - assert isinstance(key_value, _NamedInt) - max_value_bits = max(max_value_bits, key_value.bit_length()) - self._key_byte_count = (max_key_bits + 7) // 8 - if key_byte_count: - assert self._key_byte_count <= key_byte_count - self._key_byte_count = key_byte_count - self._byte_count = (max_value_bits + 7) // 8 - if byte_count: - assert self._byte_count <= byte_count - self._byte_count = byte_count - - self.choices = choices_map - self.needs_current_value = False - self.extra_default = extra_default - self._key_postfix_bytes = key_postfix_bytes - self._read_skip_byte_count = read_skip_byte_count if read_skip_byte_count else 0 - self._write_prefix_bytes = write_prefix_bytes if write_prefix_bytes else b'' - self.activate = activate - self.mask = mask - assert self._byte_count + self._read_skip_byte_count + self._key_byte_count <= 14 - assert self._byte_count + len(self._write_prefix_bytes) + self._key_byte_count <= 14 - - def to_string(self, value): - - def element_to_string(key, val): - k, c = next(((k, c) for k, c in self.choices.items() if int(key) == k), (None, None)) - return str(k) + ':' + str(c[val]) if k is not None else '?' - - return '{' + ', '.join([element_to_string(k, value[k]) for k in sorted(value)]) + '}' - - def validate_read(self, reply_bytes, key): - start = self._key_byte_count + self._read_skip_byte_count - end = start + self._byte_count - reply_value = _bytes2int(reply_bytes[start:end]) & self.mask - # reprogrammable keys starts out as 0, which is not a choice, so don't use assert here - if self.extra_default is not None and self.extra_default == reply_value: - return int(self.choices[key][0]) - if reply_value not in self.choices[key]: - assert reply_value in self.choices[ - key], '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value) - return reply_value - - def prepare_key(self, key): - return key.to_bytes(self._key_byte_count, 'big') + self._key_postfix_bytes - - def prepare_write(self, key, new_value): - choices = self.choices.get(key) - if choices is None or (new_value not in choices and new_value != self.extra_default): - _log.error('invalid choice %r for %s', new_value, key) - return None - new_value = new_value | self.activate - return self._write_prefix_bytes + new_value.to_bytes(self._byte_count, 'big') - - def acceptable(self, args, current): - if len(args) != 2: - return None - key, choices = next(((key, item) for key, item in self.choices.items() if key == args[0]), (None, None)) - if choices is None or args[1] not in choices: - return None - choice = next((item for item in choices if item == args[1]), None) - return [int(key), int(choice)] if choice is not None else None - - def compare(self, args, current): - if len(args) != 2: - return False - key = next((key for key in self.choices if key == int(args[0])), None) - if key is None: - return False - return args[1] == current[int(key)] - - -class RangeValidator(Validator): - kind = KIND.range - """Translates between integers and a byte sequence. - :param min_value: minimum accepted value (inclusive) - :param max_value: maximum accepted value (inclusive) - :param byte_count: the size of the derived byte sequence. If None, it - will be calculated from the range.""" - min_value = 0 - max_value = 255 - - @classmethod - def build(cls, setting_class, device, **kwargs): - kwargs['min_value'] = setting_class.min_value - kwargs['max_value'] = setting_class.max_value - return cls(**kwargs) - - def __init__(self, min_value=0, max_value=255, byte_count=1): - assert max_value > min_value - self.min_value = min_value - self.max_value = max_value - self.needs_current_value = True # read and check before write (needed for ADC power and probably a good idea anyway) - - self._byte_count = math.ceil(math.log(max_value + 1, 256)) - if byte_count: - assert self._byte_count <= byte_count - self._byte_count = byte_count - assert self._byte_count < 8 - - def validate_read(self, reply_bytes): - reply_value = _bytes2int(reply_bytes[:self._byte_count]) - assert reply_value >= self.min_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value) - assert reply_value <= self.max_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value) - return reply_value - - def prepare_write(self, new_value, current_value=None): - if new_value < self.min_value or new_value > self.max_value: - raise ValueError('invalid choice %r' % new_value) - current_value = self.validate_read(current_value) if current_value is not None else None - to_write = _int2bytes(new_value, self._byte_count) - # current value is known and same as value to be written return None to signal not to write it - return None if current_value is not None and current_value == new_value else to_write - - def acceptable(self, args, current): - arg = args[0] - # None if len(args) != 1 or type(arg) != int or arg < self.min_value or arg > self.max_value else args) - return None if len(args) != 1 or type(arg) != int or arg < self.min_value or arg > self.max_value else args - - def compare(self, args, current): - if len(args) == 1: - return args[0] == current - elif len(args) == 2: - return args[0] <= current and current <= args[1] - else: - return False - - -class PackedRangeValidator(Validator): - kind = KIND.packed_range - """Several range values, all the same size, all the same min and max""" - min_value = 0 - max_value = 255 - count = 1 - rsbc = 0 - write_prefix_bytes = b'' - - def __init__( - self, keys, min_value=0, max_value=255, count=1, byte_count=1, read_skip_byte_count=0, write_prefix_bytes=b'' - ): - assert max_value > min_value - self.needs_current_value = True - self.keys = keys - self.min_value = min_value - self.max_value = max_value - self.count = count - self.bc = math.ceil(math.log(max_value + 1 - min(0, min_value), 256)) - if byte_count: - assert self.bc <= byte_count - self.bc = byte_count - assert self.bc * self.count - self.rsbc = read_skip_byte_count - self.write_prefix_bytes = write_prefix_bytes - - def validate_read(self, reply_bytes): - rvs = { - n: _bytes2int(reply_bytes[self.rsbc + n * self.bc:self.rsbc + (n + 1) * self.bc], signed=True) - for n in range(self.count) - } - for n in range(self.count): - assert rvs[n] >= self.min_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, rvs[n]) - assert rvs[n] <= self.max_value, '%s: failed to validate read value %02X' % (self.__class__.__name__, rvs[n]) - return rvs - - def prepare_write(self, new_values): - if len(new_values) != self.count: - raise ValueError('wrong number of values %r' % new_values) - for new_value in new_values: - if new_value < self.min_value or new_value > self.max_value: - raise ValueError('invalid value %r' % new_value) - bytes = self.write_prefix_bytes + b''.join(_int2bytes(new_values[n], self.bc, signed=True) for n in range(self.count)) - return bytes - - def acceptable(self, args, current): - if len(args) != 2 or int(args[0]) < 0 or int(args[0]) >= self.count: - return None - return None if type(args[1]) != int or args[1] < self.min_value or args[1] > self.max_value else args - - def compare(self, args, current): - _log.warn('compare not implemented for packed range settings') - return False - - -class MultipleRangeValidator(Validator): - kind = KIND.multiple_range - - def __init__(self, items, sub_items): - assert isinstance(items, list) # each element must have .index and its __int__ must return its id (not its index) - assert isinstance(sub_items, dict) - # sub_items: items -> class with .minimum, .maximum, .length (in bytes), .id (a string) and .widget (e.g. 'Scale') - self.items = items - self.keys = _NamedInts(**{str(item): int(item) for item in items}) - self._item_from_id = {int(k): k for k in items} - self.sub_items = sub_items - - def prepare_read_item(self, item): - return _int2bytes((self._item_from_id[int(item)].index << 1) | 0xFF, 2) - - def validate_read_item(self, reply_bytes, item): - item = self._item_from_id[int(item)] - start = 0 - value = {} - for sub_item in self.sub_items[item]: - r = reply_bytes[start:start + sub_item.length] - if len(r) < sub_item.length: - r += b'\x00' * (sub_item.length - len(value)) - v = _bytes2int(r) - if not (sub_item.minimum < v < sub_item.maximum): - _log.warn( - f'{self.__class__.__name__}: failed to validate read value for {item}.{sub_item}: ' + - f'{v} not in [{sub_item.minimum}..{sub_item.maximum}]' - ) - value[str(sub_item)] = v - start += sub_item.length - return value - - def prepare_write(self, value): - seq = [] - w = b'' - for item in value.keys(): - _item = self._item_from_id[int(item)] - b = _int2bytes(_item.index, 1) - for sub_item in self.sub_items[_item]: - try: - v = value[int(item)][str(sub_item)] - except KeyError: - return None - if not (sub_item.minimum <= v <= sub_item.maximum): - raise ValueError( - f'invalid choice for {item}.{sub_item}: {v} not in [{sub_item.minimum}..{sub_item.maximum}]' - ) - b += _int2bytes(v, sub_item.length) - if len(w) + len(b) > 15: - seq.append(b + b'\xFF') - w = b'' - w += b - seq.append(w + b'\xFF') - return seq - - def prepare_write_item(self, item, value): - _item = self._item_from_id[int(item)] - w = _int2bytes(_item.index, 1) - for sub_item in self.sub_items[_item]: - try: - v = value[str(sub_item)] - except KeyError: - return None - if not (sub_item.minimum <= v <= sub_item.maximum): - raise ValueError(f'invalid choice for {item}.{sub_item}: {v} not in [{sub_item.minimum}..{sub_item.maximum}]') - w += _int2bytes(v, sub_item.length) - return w + b'\xFF' - - def acceptable(self, args, current): - # just one item, with at least one sub-item - if not isinstance(args, list) or len(args) != 2 or not isinstance(args[1], dict): - return None - item = next((p for p in self.items if p.id == args[0] or str(p) == args[0]), None) - if not item: - return None - for sub_key, value in args[1].items(): - sub_item = next((it for it in self.sub_items[item] if it.id == sub_key), None) - if not sub_item: - return None - if not isinstance(value, int) or not (sub_item.minimum <= value <= sub_item.maximum): - return None - return [int(item), {**args[1]}] - - def compare(self, args, current): - _log.warn('compare not implemented for multiple range settings') - return False - - class ActionSettingRW: """Special RW class for settings that turn on and off special processing when a key or button is depressed""" - def __init__(self, feature, name='', divert_setting_name='divert-keys'): + def __init__(self, feature, name="", divert_setting_name="divert-keys"): self.feature = feature # not used? self.name = name self.divert_setting_name = divert_setting_name @@ -1375,14 +721,16 @@ class ActionSettingRW: pass def read(self, device): # need to return bytes, as if read from device - return _int2bytes(self.key.key, 2) if self.active and self.key else b'\x00\x00' + return common.int2bytes(self.key.key, 2) if self.active and self.key else b"\x00\x00" def write(self, device, data_bytes): - def handler(device, n): # Called on notification events from the device - if n.sub_id < 0x40 and device.features.get_feature(n.sub_id) == _hidpp20.FEATURE.REPROG_CONTROLS_V4: + if ( + n.sub_id < 0x40 + and device.features.get_feature(n.sub_id) == hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4 + ): if n.address == 0x00: - cids = _unpack('!HHHH', n.data[:8]) + cids = struct.unpack("!HHHH", n.data[:8]) if not self.pressed and int(self.key.key) in cids: # trigger key pressed self.pressed = True self.press_action() @@ -1396,59 +744,59 @@ class ActionSettingRW: self.key_action(key) elif n.address == 0x10: if self.pressed: - dx, dy = _unpack('!hh', n.data[:4]) + dx, dy = struct.unpack("!hh", n.data[:4]) self.move_action(dx, dy) divertSetting = next(filter(lambda s: s.name == self.divert_setting_name, device.settings), None) if divertSetting is None: - _log.warn('setting %s not found on %s', self.divert_setting_name, device.name) + logger.warning("setting %s not found on %s", self.divert_setting_name, device.name) return None self.device = device - key = _bytes2int(data_bytes) + key = common.bytes2int(data_bytes) if key: # Enable self.key = next((k for k in device.keys if k.key == key), None) if self.key: self.active = True if divertSetting: divertSetting.write_key_value(int(self.key.key), 1) + if self.device.setting_callback: + self.device.setting_callback(device, type(divertSetting), [self.key.key, 1]) device.add_notification_handler(self.name, handler) - from solaar.ui import status_changed as _status_changed self.activate_action() - _status_changed(device, refresh=True) # update main window else: - _log.error('cannot enable %s on %s for key %s', self.name, device, key) + logger.error("cannot enable %s on %s for key %s", self.name, device, key) else: # Disable if self.active: self.active = False if divertSetting: divertSetting.write_key_value(int(self.key.key), 0) - from solaar.ui import status_changed as _status_changed - _status_changed(device, refresh=True) # update main window + if self.device.setting_callback: + self.device.setting_callback(device, type(divertSetting), [self.key.key, 0]) try: device.remove_notification_handler(self.name) except Exception: - if _log.isEnabledFor(_WARNING): - _log.warn('cannot disable %s on %s', self.name, device) + if logger.isEnabledFor(logging.WARNING): + logger.warning("cannot disable %s on %s", self.name, device) self.deactivate_action() - return True + return data_bytes class RawXYProcessing: """Special class for processing RawXY action messages initiated by pressing a key with rawXY diversion capability""" - def __init__(self, device, name=''): + def __init__(self, device, name=""): self.device = device self.name = name self.keys = [] # the keys that can initiate processing self.initiating_key = None # the key that did initiate processing self.active = False - self.feature_offset = device.features[_hidpp20.FEATURE.REPROG_CONTROLS_V4] + self.feature_offset = device.features[hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4] assert self.feature_offset is not False def handler(self, device, n): # Called on notification events from the device - if n.sub_id < 0x40 and device.features.get_feature(n.sub_id) == _hidpp20.FEATURE.REPROG_CONTROLS_V4: + if n.sub_id < 0x40 and device.features.get_feature(n.sub_id) == hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4: if n.address == 0x00: - cids = _unpack('!HHHH', n.data[:8]) + cids = struct.unpack("!HHHH", n.data[:8]) ## generalize to list of keys if not self.initiating_key: # no initiating key pressed for k in self.keys: @@ -1466,7 +814,7 @@ class RawXYProcessing: self.key_action(key) elif n.address == 0x10: if self.initiating_key: - dx, dy = _unpack('!hh', n.data[:4]) + dx, dy = struct.unpack("!hh", n.data[:4]) self.move_action(dx, dy) def start(self, key): @@ -1489,8 +837,8 @@ class RawXYProcessing: try: self.device.remove_notification_handler(self.name) except Exception: - if _log.isEnabledFor(_WARNING): - _log.warn('cannot disable %s on %s', self.name, self.device) + if logger.isEnabledFor(logging.WARNING): + logger.warning("cannot disable %s on %s", self.name, self.device) self.deactivate_action() self.active = False @@ -1514,11 +862,14 @@ class RawXYProcessing: def apply_all_settings(device): - if device.features and _hidpp20.FEATURE.HIRES_WHEEL in device.features: - _sleep(0.2) # delay to try to get out of race condition with Linux HID++ driver - persister = getattr(device, 'persister', None) - sensitives = persister.get('_sensitive', {}) if persister else {} + if device.features and hidpp20_constants.SupportedFeature.HIRES_WHEEL in device.features: + time.sleep(0.2) # delay to try to get out of race condition with Linux HID++ driver + persister = getattr(device, "persister", None) + sensitives = persister.get("_sensitive", {}) if persister else {} for s in device.settings: ignore = sensitives.get(s.name, False) if ignore != SENSITIVITY_IGNORE: s.apply() + + +Setting.validator_class = settings_validator.BooleanValidator diff --git a/lib/logitech_receiver/settings_new.py b/lib/logitech_receiver/settings_new.py new file mode 100644 index 00000000..9e1161ab --- /dev/null +++ b/lib/logitech_receiver/settings_new.py @@ -0,0 +1,206 @@ +## Copyright (C) 2025 Solaar contributors +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +## A new way of supporting settings, using a feature-specifi device class to store, read, and write relevant information +## The setting uses the device class to interact with the device feature. +## The setting uses a persist class to keep track of the setting. + +## Interface: + +import logging + +from .settings import Kind + +logger = logging.getLogger(__name__) + + +class Setting: + name = None # Solaar internal name for the setting + label = None # Solaar user name for the setting (translatable) + description = None # Solaar extra desciption for the setting (translatable) + feature = None # Logitech feature that the setting uses + min_version = 0 # Minimum version of the feature needed + setup = None # method name on Device class to get the device object + get = None # method name on the device object to get the setting value + set = None # method name on the device object to set the setting value + acceptable = None # method name on the device object to check for acceptable values + choices_universe = None # All possible acceptable keys, for settings with keys + kind = Kind.NONE # What GUI interface to use + persist = True # Whether to remember the setting + display = True # display setting in UI + _device = None # The device that this setting is for + _device_object = None # The object that interacts with the feature for the device + _value = None # Stored value as maintained by Solaar, used for persistence + + def __init__(self, device, device_object): + self._device = device + self._device_object = device_object + + @classmethod + def build(cls, device): + cls.check_properties(cls) + device_object = getattr(device, cls.setup)() + if device_object: + setting = cls(device, device_object) + return setting + + @classmethod + def check_properties(cl, cls): + assert cls.name and cls.label and cls.description, "New settings require a name, label, and description" + assert cls.feature, "New settings require a feature" + assert cls.setup, "New settings require a setup device method" + assert cls.get and cls.set and cls.acceptable, "New settings require get, set, and acceptable methods" + + def setup_from_class(self, clss): + """Copy settings methods for a new setting from a settting class""" + self.name = clss.name + self.label = clss.label + self.description = clss.description + self.feature = clss.feature + self.min_version = clss.min_version + self.setup = clss.setup + self.get = clss.get + self.set = clss.set + self.acceptable = clss.acceptable + self.choices_universe = clss.choices_universe + self.kind = clss.kind + self.persist = clss.persist + + def _pre_read(self, cached): + """Get information from and save information to the persister""" + # Get the persister map if available and not done already + if self.persist and self._value is None and getattr(self._device, "persister", None): + self._value = self._device.persister.get(self.name) + # If this is new save its current value for the next time + if cached and self._value is not None: + if getattr(self._device, "persister", None) and self.name not in self._device.persister: + self._device.persister[self.name] = self._value if self.persist else None + + def read(self, cached=True): + """Get all the data for the setting. If cached is True the data in the _value can be used.""" + self._pre_read(cached) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: setting read %r from %s", self.name, self._value, self._device) + if cached and self._value is not None: + return self._value + if cached: + self._value = getattr(self._device_object, self.get)() + return self._value + if self._device.online: + self._value = getattr(self._device_object.query(), self.get)() + return self._value + + def write(self, value, save=True): + """Write the value to the device. If saved is True also save in the persister""" + pass ## fill out + + def apply(self): + """Write saved data to the device, using persisted data if available""" + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: apply (%s)", self.name, self._device) + value = None + try: + value = self.read(self.persist) # Don't use persisted value if setting doesn't persist + if self.persist and value is not None: # If setting doesn't persist no need to write value just read + self.write(value, save=False) + except Exception as e: + if logger.isEnabledFor(logging.WARNING): + logger.warning("%s: error applying %s so ignore it (%s): %s", self.name, value, self._device, repr(e)) + + @property + def range(self): + if self.kind == Kind.RANGE: + return self.min_value, self.max_value + + def val_to_string(self, value): + return str(value) + + +## key mapping from symbols to values???? + + +class Settings(Setting): + """A setting descriptor for multiple keys. + Supported by a class that provides the interface to the device, see ForceSensingButtonArray in hidpp20.py + Picks out a field from the mapped device feature objects.""" + + # setup creates a dictionary with entries for all the keys + # _value is a map from keys to values + # get, set, and acceptable are methods of dict value objects, not of the device object itself #### FIX THIS! MAYBE?? + + def __init__(self, device, device_object): + super().__init__(device, device_object) + self._value = {} + + def read(self, cached=True): + self._pre_read(cached) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings read %r from %s", self.name, self._value, self._device) + for key in self._device_object: + self.read_key(key, cached) + return self._value + + def read_key(self, key, cached=True): + """Get the data for the key. If cached is True the data in the device_object can be used.""" + self._pre_read(cached) + if key not in self._device_object: + logger.error("%s: settings illegal read key %r for %s", self.name, key, self._device) + return None + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings key %r read %r from %s", self.name, key, self._value, self._device) + if cached and key in self._value and self._value[key] is not None: + return self._value[key] + if cached: + data = self._device_object[key] + self._value[key] = getattr(data, self.get)() + return self._value[key] + if self._device.online: + data = self._device_object.query_key(key) + self._value[key] = getattr(data, self.get)() + return self._value[key] + + def write(self, value, save=True): + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings read %r from %s", self.name, self._value, self._device) + if isinstance(value, dict): + for key, val in value.items(): + self.write_key_value(key, val, save) + else: # to mimic interface for non-dict setting + key = next(iter(self._device_object)) + self.write_key_value(key, value, save) + return value + + def write_key_value(self, key, value, save=True): + """Write the data for the key. If saved is True also save in the persister""" + if key not in self._device_object: + logger.error("%s: settings illegal write key %r for %s", self.name, key, self._device) + return None + if logger.isEnabledFor(logging.DEBUG): + logger.debug("%s: settings write key %r value %r to %s", self.name, key, value, self._device) + if self._device.online: + if self._device_object[key] is None: + self.read_key(key) + if self._device_object[key] is None: + logger.error("%s: settings illegal write key %r for %s", self.name, key, self._device) + return None + if not getattr(self._device_object[key], self.acceptable)(value): + logger.error("%s: settings illegal write key %r value %r for %s", self.name, key, value, self._device) + return None + self._value[key] = value + if self._device.persister and self.persist and save: + self._device.persister[self.name][key] = value + getattr(self._device_object[key], self.set)(value) + return value diff --git a/lib/logitech_receiver/settings_templates.py b/lib/logitech_receiver/settings_templates.py index 5ecc71e0..627aebab 100644 --- a/lib/logitech_receiver/settings_templates.py +++ b/lib/logitech_receiver/settings_templates.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -15,50 +13,49 @@ ## You should have received a copy of the GNU General Public License along ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from __future__ import annotations -from logging import DEBUG as _DEBUG -from logging import INFO as _INFO -from logging import WARN as _WARN -from logging import getLogger -from struct import pack as _pack -from struct import unpack as _unpack -from time import time as _time +import enum +import logging +import socket +import struct +import traceback -from . import hidpp10 as _hidpp10 -from . import hidpp20 as _hidpp20 -from . import special_keys as _special_keys -from .common import NamedInt as _NamedInt -from .common import NamedInts as _NamedInts -from .common import bytes2int as _bytes2int -from .common import int2bytes as _int2bytes -from .i18n import _ -from .settings import ActionSettingRW as _ActionSettingRW -from .settings import BitFieldSetting as _BitFieldSetting -from .settings import BitFieldValidator as _BitFieldV -from .settings import BitFieldWithOffsetAndMaskSetting as _BitFieldOMSetting -from .settings import BitFieldWithOffsetAndMaskValidator as _BitFieldOMV -from .settings import ChoicesMapValidator as _ChoicesMapV -from .settings import ChoicesValidator as _ChoicesV -from .settings import FeatureRW as _FeatureRW -from .settings import LongSettings as _LongSettings -from .settings import MultipleRangeValidator as _MultipleRangeV -from .settings import PackedRangeValidator as _PackedRangeV -from .settings import RangeFieldSetting as _RangeFieldSetting -from .settings import RangeValidator as _RangeV -from .settings import RawXYProcessing as _RawXYProcessing -from .settings import Setting as _Setting -from .settings import Settings as _Settings -from .special_keys import DISABLE as _DKEY +from time import time +from typing import Callable +from typing import Protocol -_log = getLogger(__name__) -del getLogger +from solaar.i18n import _ -_DK = _hidpp10.DEVICE_KIND -_R = _hidpp10.REGISTERS -_F = _hidpp20.FEATURE +from . import base +from . import common +from . import descriptors +from . import desktop_notifications +from . import diversion +from . import exceptions +from . import hidpp20 +from . import hidpp20_constants +from . import settings +from . import settings_new +from . import settings_validator +from . import special_keys +from .hidpp10_constants import Registers +from .hidpp20 import KeyFlag +from .hidpp20 import MappingFlag +from .hidpp20_constants import GestureId +from .hidpp20_constants import ParamId + +logger = logging.getLogger(__name__) + +_hidpp20 = hidpp20.Hidpp20() +_F = hidpp20_constants.SupportedFeature + + +class State(enum.Enum): + IDLE = "idle" + PRESSED = "pressed" + MOVED = "moved" -_GG = _hidpp20.GESTURE -_GP = _hidpp20.PARAM # Setting classes are used to control the settings that the Solaar GUI shows and manipulates. # Each setting class has to several class variables: @@ -74,58 +71,61 @@ _GP = _hidpp20.PARAM # persist (inherited True), which is whether to store the value and apply it when setting up the device. # # The different setting classes imported from settings.py are for different numbers and kinds of arguments. -# _Setting is for settings with a single value (boolean, number in a range, and symbolic choice). -# _Settings is for settings that are maps from keys to values +# Setting is for settings with a single value (boolean, number in a range, and symbolic choice). +# Settings is for settings that are maps from keys to values # and permit reading or writing the entire map or just one key/value pair. -# The _BitFieldSetting class is for settings that have multiple boolean values packed into a bit field. -# _BitFieldOMSetting is similar. -# The _RangeFieldSetting class is for settings that have multiple ranges packed into a byte string. -# _LongSettings is for settings that have an even more complex structure. +# The BitFieldSetting class is for settings that have multiple boolean values packed into a bit field. +# BitFieldWithOffsetAndMaskSetting is similar. +# The RangeFieldSetting class is for settings that have multiple ranges packed into a byte string. +# LongSettings is for settings that have an even more complex structure. # # When settings are created a reader/writer and a validator are created. # If the setting class has a value for rw_class then an instance of that class is created. -# Otherwise if the setting has a register then an instance of settings.RegisterRW is created. -# and if the setting has a feature then then an instance of _FeatureRW is created. +# Otherwise if the setting has a register then an instance of RegisterRW is created. +# and if the setting has a feature then an instance of FeatureRW is created. # The instance is created with the register or feature as the first argument and rw_options as keyword arguments. -# _RegisterRW doesn't use any options. -# _FeatureRW options include +# RegisterRW doesn't use any options. +# FeatureRW options include # read_fnid - the feature function (times 16) to read the value (default 0x00), # write_fnid - the feature function (times 16) to write the value (default 0x10), # prefix - a prefix to add to the data being written and the read request (default b''), used for features # that provide and set multiple settings (e.g., to read and write function key inversion for current host) # no_reply - whether to wait for a reply (default false) (USE WITH EXTREME CAUTION). # -# There are three simple validator classes - _BooleanV, _RangeV, and _ChoicesV -# _BooleanV is for boolean values and is the default. It takes +# There are three simple validator classes - BooleanV, RangeValidator, and ChoicesValidator +# BooleanV is for boolean values and is the default. It takes # true_value is the raw value for true (default 0x01), this can be an integer or a byte string, # false_value is the raw value for false (default 0x00), this can be an integer or a byte string, # mask is used to keep only some bits from a sequence of bits, this can be an integer or a byte string, # read_skip_byte_count is the number of bytes to ignore at the beginning of the read value (default 0), # write_prefix_bytes is a byte string to write before the value (default empty). -# _RangeV is for an integer in a range. It takes -# byte_count is number of bytes that the value is stored in (defaults to size of max_value). -# _RangeV uses min_value and max_value from the setting class as minimum and maximum. -# _ChoicesV is for symbolic choices. It takes one positional and three keyword arguments: +# RangeValidator is for an integer in a range. It takes +# byte_count is number of bytes that the value is stored in (defaults to size of max_value). +# read_skip_byte_count is as for BooleanV +# write_prefix_bytes is as for BooleanV +# RangeValidator uses min_value and max_value from the setting class as minimum and maximum. + +# ChoicesValidator is for symbolic choices. It takes one positional and three keyword arguments: # choices is a list of named integers that are the valid choices, # byte_count is the number of bytes for the integer (default size of largest choice integer), -# read_skip_byte_count is as for _BooleanV, -# write_prefix_bytes is as for _BooleanV. -# Settings that use _ChoicesV should have a choices_universe class variable of the potential choices, +# read_skip_byte_count is as for BooleanV, +# write_prefix_bytes is as for BooleanV. +# Settings that use ChoicesValidator should have a choices_universe class variable of the potential choices, # or None for no limitation and optionally a choices_extra class variable with an extra choice. # The choices_extra is so that there is no need to specially extend a large existing NamedInts. -# _ChoicesMapV validator is for map settings that map onto symbolic choices. It takes +# ChoicesMapValidator validator is for map settings that map onto symbolic choices. It takes # choices_map is a map from keys to possible values -# byte_count is as for _ChoicesV, -# read_skip_byte_count is as for _ChoicesV, -# write_prefix_bytes is as for _ChoicesV, +# byte_count is as for ChoicesValidator, +# read_skip_byte_count is as for ChoicesValidator, +# write_prefix_bytes is as for ChoicesValidator, # key_byte_count is the number of bytes for the key integer (default size of largest key), # extra_default is an extra raw value that is used as a default value (default None). -# Settings that use _ChoicesV should have keys_universe and choices_universe class variable of +# Settings that use ChoicesValidator should have keys_universe and choices_universe class variable of # the potential keys and potential choices or None for no limitation. -# _BitFieldV validator is for bit field settings. It takes one positional and one keyword argument +# BitFieldValidator validator is for bit field settings. It takes one positional and one keyword argument # the positional argument is the number of bits in the bit field # byte_count is the size of the bit field (default size of the bit field) # @@ -133,58 +133,114 @@ _GP = _hidpp20.PARAM # These settings have reader/writer classes that perform special processing instead of sending commands to the device. -# yapf: disable -class FnSwapVirtual(_Setting): # virtual setting to hold fn swap strings - name = 'fn-swap' - label = _('Swap Fx function') - description = (_('When set, the F1..F12 keys will activate their special function,\n' - 'and you must hold the FN key to activate their standard function.') + '\n\n' + - _('When unset, the F1..F12 keys will activate their standard function,\n' - 'and you must hold the FN key to activate their special function.')) -# yapf: enable - - -class RegisterHandDetection(_Setting): - name = 'hand-detection' - label = _('Hand Detection') - description = _('Turn on illumination when the hands hover over the keyboard.') - register = _R.keyboard_hand_detection - validator_options = {'true_value': b'\x00\x00\x00', 'false_value': b'\x00\x00\x30', 'mask': b'\x00\x00\xFF'} - - -class RegisterSmoothScroll(_Setting): - name = 'smooth-scroll' - label = _('Scroll Wheel Smooth Scrolling') - description = _('High-sensitivity mode for vertical scroll with the wheel.') - register = _R.mouse_button_flags - validator_options = {'true_value': 0x40, 'mask': 0x40} - - -class RegisterSideScroll(_Setting): - name = 'side-scroll' - label = _('Side Scrolling') - description = _( - 'When disabled, pushing the wheel sideways sends custom button events\n' - 'instead of the standard side-scrolling events.' +class FnSwapVirtual(settings.Setting): # virtual setting to hold fn swap strings + name = "fn-swap" + label = _("Swap Fx function") + description = ( + _( + "When set, the F1..F12 keys will activate their special function,\n" + "and you must hold the FN key to activate their standard function." + ) + + "\n\n" + + _( + "When unset, the F1..F12 keys will activate their standard function,\n" + "and you must hold the FN key to activate their special function." + ) ) - register = _R.mouse_button_flags - validator_options = {'true_value': 0x02, 'mask': 0x02} + + +class RegisterHandDetection(settings.Setting): + name = "hand-detection" + label = _("Hand Detection") + description = _("Turn on illumination when the hands hover over the keyboard.") + register = Registers.KEYBOARD_HAND_DETECTION + validator_options = {"true_value": b"\x00\x00\x00", "false_value": b"\x00\x00\x30", "mask": b"\x00\x00\xff"} + + +class RegisterSmoothScroll(settings.Setting): + name = "smooth-scroll" + label = _("Scroll Wheel Smooth Scrolling") + description = _("High-sensitivity mode for vertical scroll with the wheel.") + register = Registers.MOUSE_BUTTON_FLAGS + validator_options = {"true_value": 0x40, "mask": 0x40} + + +class RegisterSideScroll(settings.Setting): + name = "side-scroll" + label = _("Side Scrolling") + description = _( + "When disabled, pushing the wheel sideways sends custom button events\n" + "instead of the standard side-scrolling events." + ) + register = Registers.MOUSE_BUTTON_FLAGS + validator_options = {"true_value": 0x02, "mask": 0x02} # different devices have different sets of permissible dpis, so this should be subclassed -class RegisterDpi(_Setting): - name = 'dpi-old' - label = _('Sensitivity (DPI - older mice)') - description = _('Mouse movement sensitivity') - register = _R.mouse_dpi - choices_universe = _NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 100)) - validator_class = _ChoicesV - validator_options = {'choices': choices_universe} +class RegisterDpi(settings.Setting): + name = "dpi-old" + label = _("Sensitivity (DPI - older mice)") + description = _("Mouse movement sensitivity") + register = Registers.MOUSE_DPI + choices_universe = common.NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 100)) + validator_class = settings_validator.ChoicesValidator + validator_options = {"choices": choices_universe} class RegisterFnSwap(FnSwapVirtual): - register = _R.keyboard_fn_swap - validator_options = {'true_value': b'\x00\x01', 'mask': b'\x00\x01'} + register = Registers.KEYBOARD_FN_SWAP + validator_options = {"true_value": b"\x00\x01", "mask": b"\x00\x01"} + + +class _PerformanceMXDpi(RegisterDpi): + choices_universe = common.NamedInts.range(0x81, 0x8F, lambda x: str((x - 0x80) * 100)) + validator_options = {"choices": choices_universe} + + +# set up register settings for devices - this is done here to break up an import loop +descriptors.get_wpid("0060").settings = [RegisterFnSwap] +descriptors.get_wpid("2008").settings = [RegisterFnSwap] +descriptors.get_wpid("2010").settings = [RegisterFnSwap, RegisterHandDetection] +descriptors.get_wpid("2011").settings = [RegisterFnSwap] +descriptors.get_usbid(0xC318).settings = [RegisterFnSwap] +descriptors.get_wpid("C714").settings = [RegisterFnSwap] +descriptors.get_wpid("100B").settings = [RegisterSmoothScroll, RegisterSideScroll] +descriptors.get_wpid("100F").settings = [RegisterSmoothScroll, RegisterSideScroll] +descriptors.get_wpid("1013").settings = [RegisterSmoothScroll, RegisterSideScroll] +descriptors.get_wpid("1014").settings = [RegisterSmoothScroll, RegisterSideScroll] +descriptors.get_wpid("1017").settings = [RegisterSmoothScroll, RegisterSideScroll] +descriptors.get_wpid("1023").settings = [RegisterSmoothScroll, RegisterSideScroll] +# somehow messed up ? descriptors.get_wpid("4004").settings = [_PerformanceMXDpi, RegisterSmoothScroll, RegisterSideScroll] +descriptors.get_wpid("101A").settings = [_PerformanceMXDpi, RegisterSmoothScroll, RegisterSideScroll] +descriptors.get_wpid("101B").settings = [RegisterSmoothScroll, RegisterSideScroll] +descriptors.get_wpid("101D").settings = [RegisterSmoothScroll, RegisterSideScroll] +descriptors.get_wpid("101F").settings = [RegisterSideScroll] +descriptors.get_usbid(0xC06B).settings = [RegisterSmoothScroll, RegisterSideScroll] +descriptors.get_wpid("1025").settings = [RegisterSideScroll] +descriptors.get_wpid("102A").settings = [RegisterSmoothScroll, RegisterSideScroll] +descriptors.get_usbid(0xC048).settings = [_PerformanceMXDpi, RegisterSmoothScroll, RegisterSideScroll] +descriptors.get_usbid(0xC066).settings = [_PerformanceMXDpi, RegisterSmoothScroll, RegisterSideScroll] + + +# ignore the capabilities part of the feature - all devices should be able to swap Fn state +# can't just use the first byte = 0xFF (for current host) because of a bug in the firmware of the MX Keys S +class K375sFnSwap(FnSwapVirtual): + feature = _F.K375S_FN_INVERSION + validator_options = {"true_value": b"\x01", "false_value": b"\x00", "read_skip_byte_count": 1} + + class rw_class(settings.FeatureRW): + def find_current_host(self, device): + if not self.prefix: + response = device.feature_request(_F.HOSTS_INFO, 0x00) + self.prefix = response[3:4] if response else b"\xff" + + def read(self, device, data_bytes=b""): + self.find_current_host(device) + return super().read(device, data_bytes) + + def write(self, device, data_bytes): + self.find_current_host(device) + return super().write(device, data_bytes) class FnSwap(FnSwapVirtual): @@ -195,229 +251,416 @@ class NewFnSwap(FnSwapVirtual): feature = _F.NEW_FN_INVERSION -# ignore the capabilities part of the feature - all devices should be able to swap Fn state -# just use the current host (first byte = 0xFF) part of the feature to read and set the Fn state -class K375sFnSwap(FnSwapVirtual): - feature = _F.K375S_FN_INVERSION - rw_options = {'prefix': b'\xFF'} - validator_options = {'true_value': b'\x01', 'false_value': b'\x00', 'read_skip_byte_count': 1} - - -class Backlight(_Setting): - name = 'backlight-qualitative' - label = _('Backlight') - description = _('Set illumination time for keyboard.') +class Backlight(settings.Setting): + name = "backlight-qualitative" + label = _("Backlight Timed") + description = _("Set illumination time for keyboard.") feature = _F.BACKLIGHT - choices_universe = _NamedInts(Off=0, Varying=2, VeryShort=5, Short=10, Medium=20, Long=60, VeryLong=180) - validator_class = _ChoicesV - validator_options = {'choices': choices_universe} + choices_universe = common.NamedInts(Off=0, Varying=2, VeryShort=5, Short=10, Medium=20, Long=60, VeryLong=180) + validator_class = settings_validator.ChoicesValidator + validator_options = {"choices": choices_universe} -class Backlight2(_Setting): - name = 'backlight' - label = _('Backlight') - description = _('Turn illumination on or off on keyboard.') +# MX Keys S requires some extra values, as in 11 02 0c1a 000dff000b000b003c00000000000000 +# on/off options (from current) effect (FF-no change) level (from current) durations[6] (from current) +class Backlight2(settings.Setting): + name = "backlight" + label = _("Backlight") + description = _("Illumination level on keyboard. Changes made are only applied in Manual mode.") feature = _F.BACKLIGHT2 + choices_universe = common.NamedInts(Disabled=0xFF, Enabled=0x00, Automatic=0x01, Manual=0x02) + min_version = 0 + + class rw_class: + def __init__(self, feature): + self.feature = feature + self.kind = settings.FeatureRW.kind + + def read(self, device): + backlight = device.backlight + if not backlight.enabled: + return b"\xff" + else: + return common.int2bytes(backlight.mode, 1) + + def write(self, device, data_bytes): + backlight = device.backlight + backlight.enabled = data_bytes[0] != 0xFF + if data_bytes[0] != 0xFF: + backlight.mode = data_bytes[0] + backlight.write() + return True + + class validator_class(settings_validator.ChoicesValidator): + @classmethod + def build(cls, setting_class, device): + backlight = device.backlight + choices = common.NamedInts() + choices[0xFF] = _("Disabled") + if backlight.auto_supported: + choices[0x1] = _("Automatic") + if backlight.perm_supported: + choices[0x3] = _("Manual") + if not (backlight.auto_supported or backlight.temp_supported or backlight.perm_supported): + choices[0x0] = _("Enabled") + return cls(choices=choices, byte_count=1) -class Backlight3(_Setting): - name = 'backlight-timed' - label = _('Backlight') - description = _('Set illumination time for keyboard.') +class Backlight2Level(settings.Setting): + name = "backlight_level" + label = _("Backlight Level") + description = _("Illumination level on keyboard when in Manual mode.") + feature = _F.BACKLIGHT2 + min_version = 3 + + class rw_class: + def __init__(self, feature): + self.feature = feature + self.kind = settings.FeatureRW.kind + + def read(self, device): + backlight = device.backlight + return common.int2bytes(backlight.level, 1) + + def write(self, device, data_bytes): + if device.backlight.level != common.bytes2int(data_bytes): + device.backlight.level = common.bytes2int(data_bytes) + device.backlight.write() + return True + + class validator_class(settings_validator.RangeValidator): + @classmethod + def build(cls, setting_class, device): + reply = device.feature_request(_F.BACKLIGHT2, 0x20) + assert reply, "Oops, backlight range cannot be retrieved!" + if reply[0] > 1: + return cls(min_value=0, max_value=reply[0] - 1, byte_count=1) + + +class Backlight2Duration(settings.Setting): + feature = _F.BACKLIGHT2 + min_version = 3 + validator_class = settings_validator.RangeValidator + min_value = 1 + max_value = 600 # 10 minutes - actual maximum is 2 hours + validator_options = {"byte_count": 2} + + class rw_class: + def __init__(self, feature, field): + self.feature = feature + self.kind = settings.FeatureRW.kind + self.field = field + + def read(self, device): + backlight = device.backlight + return common.int2bytes(getattr(backlight, self.field) * 5, 2) # use seconds instead of 5-second units + + def write(self, device, data_bytes): + backlight = device.backlight + new_duration = (int.from_bytes(data_bytes, byteorder="big") + 4) // 5 # use ceiling in 5-second units + if new_duration != getattr(backlight, self.field): + setattr(backlight, self.field, new_duration) + backlight.write() + return True + + +class Backlight2DurationHandsOut(Backlight2Duration): + name = "backlight_duration_hands_out" + label = _("Backlight Delay Hands Out") + description = _("Delay in seconds until backlight fades out with hands away from keyboard.") + feature = _F.BACKLIGHT2 + validator_class = settings_validator.RangeValidator + rw_options = {"field": "dho"} + + +class Backlight2DurationHandsIn(Backlight2Duration): + name = "backlight_duration_hands_in" + label = _("Backlight Delay Hands In") + description = _("Delay in seconds until backlight fades out with hands near keyboard.") + feature = _F.BACKLIGHT2 + validator_class = settings_validator.RangeValidator + rw_options = {"field": "dhi"} + + +class Backlight2DurationPowered(Backlight2Duration): + name = "backlight_duration_powered" + label = _("Backlight Delay Powered") + description = _("Delay in seconds until backlight fades out with external power.") + feature = _F.BACKLIGHT2 + validator_class = settings_validator.RangeValidator + rw_options = {"field": "dpow"} + + +class Backlight3(settings.Setting): + name = "backlight-timed" + label = _("Backlight (Seconds)") + description = _("Set illumination time for keyboard.") feature = _F.BACKLIGHT3 - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20, 'suffix': 0x09} - validator_class = _RangeV + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20, "suffix": b"\x09"} + validator_class = settings_validator.RangeValidator min_value = 0 max_value = 1000 - validator_options = {'byte_count': 2} + validator_options = {"byte_count": 2} -class HiResScroll(_Setting): - name = 'hi-res-scroll' - label = _('Scroll Wheel High Resolution') +class HiResScroll(settings.Setting): + name = "hi-res-scroll" + label = _("Scroll Wheel High Resolution") description = ( - _('High-sensitivity mode for vertical scroll with the wheel.') + '\n' + - _('Set to ignore if scrolling is abnormally fast or slow') + _("High-sensitivity mode for vertical scroll with the wheel.") + + "\n" + + _("Set to ignore if scrolling is abnormally fast or slow") ) feature = _F.HI_RES_SCROLLING -class LowresMode(_Setting): - name = 'lowres-scroll-mode' - label = _('Scroll Wheel Diversion') +class LowresMode(settings.Setting): + name = "lowres-scroll-mode" + label = _("Scroll Wheel Diversion") description = _( - 'Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger Solaar rules but are otherwise ignored).' + "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger Solaar rules but are otherwise ignored)." ) feature = _F.LOWRES_WHEEL -class HiresSmoothInvert(_Setting): - name = 'hires-smooth-invert' - label = _('Scroll Wheel Direction') - description = _('Invert direction for vertical scroll with wheel.') +class HiresSmoothInvert(settings.Setting): + name = "hires-smooth-invert" + label = _("Scroll Wheel Direction") + description = _("Invert direction for vertical scroll with wheel.") feature = _F.HIRES_WHEEL - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'true_value': 0x04, 'mask': 0x04} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"true_value": 0x04, "mask": 0x04} -class HiresSmoothResolution(_Setting): - name = 'hires-smooth-resolution' - label = _('Scroll Wheel Resolution') +class HiresSmoothResolution(settings.Setting): + name = "hires-smooth-resolution" + label = _("Scroll Wheel Resolution") description = ( - _('High-sensitivity mode for vertical scroll with the wheel.') + '\n' + - _('Set to ignore if scrolling is abnormally fast or slow') + _("High-sensitivity mode for vertical scroll with the wheel.") + + "\n" + + _("Set to ignore if scrolling is abnormally fast or slow") ) feature = _F.HIRES_WHEEL - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'true_value': 0x02, 'mask': 0x02} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"true_value": 0x02, "mask": 0x02} -class HiresMode(_Setting): - name = 'hires-scroll-mode' - label = _('Scroll Wheel Diversion') +class HiresMode(settings.Setting): + name = "hires-scroll-mode" + label = _("Scroll Wheel Diversion") description = _( - 'Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar rules but are otherwise ignored).' + "Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar rules but are otherwise ignored)." ) feature = _F.HIRES_WHEEL - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'true_value': 0x01, 'mask': 0x01} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"true_value": 0x01, "mask": 0x01} -class PointerSpeed(_Setting): - name = 'pointer_speed' - label = _('Sensitivity (Pointer Speed)') - description = _('Speed multiplier for mouse (256 is normal multiplier).') +class PointerSpeed(settings.Setting): + name = "pointer_speed" + label = _("Sensitivity (Pointer Speed)") + description = _("Speed multiplier for mouse (256 is normal multiplier).") feature = _F.POINTER_SPEED - validator_class = _RangeV - min_value = 0x002e - max_value = 0x01ff - validator_options = {'byte_count': 2} + validator_class = settings_validator.RangeValidator + min_value = 0x002E + max_value = 0x01FF + validator_options = {"byte_count": 2} -class ThumbMode(_Setting): - name = 'thumb-scroll-mode' - label = _('Thumb Wheel Diversion') +class ThumbMode(settings.Setting): + name = "thumb-scroll-mode" + label = _("Thumb Wheel Diversion") description = _( - 'Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar rules but are otherwise ignored).' + "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar rules but are otherwise ignored)." ) feature = _F.THUMB_WHEEL - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'true_value': b'\x01\x00', 'false_value': b'\x00\x00', 'mask': b'\x01\x00'} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"true_value": b"\x01\x00", "false_value": b"\x00\x00", "mask": b"\x01\x00"} -class ThumbInvert(_Setting): - name = 'thumb-scroll-invert' - label = _('Thumb Wheel Direction') - description = _('Invert thumb wheel scroll direction.') +class ThumbInvert(settings.Setting): + name = "thumb-scroll-invert" + label = _("Thumb Wheel Direction") + description = _("Invert thumb wheel scroll direction.") feature = _F.THUMB_WHEEL - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'true_value': b'\x00\x01', 'false_value': b'\x00\x00', 'mask': b'\x00\x01'} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"true_value": b"\x00\x01", "false_value": b"\x00\x00", "mask": b"\x00\x01"} -class OnboardProfiles(_Setting): - name = 'onboard_profiles' - label = _('Onboard Profiles') - description = _('Enable onboard profiles, which often control report rate and keyboard lighting') +# change UI to show result of onboard profile change +def profile_change(device, profile_sector): + if device.setting_callback: + device.setting_callback(device, OnboardProfiles, [profile_sector]) + for profile in device.profiles.profiles.values() if device.profiles else []: + if profile.sector == profile_sector: + resolution_index = profile.resolution_default_index + device.setting_callback(device, AdjustableDpi, [profile.resolutions[resolution_index]]) + device.setting_callback(device, ReportRate, [profile.report_rate]) + break + + +class OnboardProfiles(settings.Setting): + name = "onboard_profiles" + label = _("Onboard Profiles") + description = _("Enable an onboard profile, which controls report rate, sensitivity, and button actions") feature = _F.ONBOARD_PROFILES - rw_options = {'read_fnid': 0x20, 'write_fnid': 0x10} - choices_universe = _NamedInts(Enable=1, Disable=2) - validator_class = _ChoicesV - validator_options = {'choices': choices_universe} + choices_universe = common.NamedInts(Disabled=0) + for i in range(1, 16): + choices_universe[i] = f"Profile {i}" + choices_universe[i + 0x100] = f"Read-Only Profile {i}" + validator_class = settings_validator.ChoicesValidator + class rw_class: + def __init__(self, feature): + self.feature = feature + self.kind = settings.FeatureRW.kind -class ReportRate(_Setting): - name = 'report_rate' - label = _('Polling Rate (ms)') - description = ( - _('Frequency of device polling, in milliseconds') + '\n' + - _('May need Onboard Profiles set to Disable to be effective.') - ) - feature = _F.REPORT_RATE - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - choices_universe = _NamedInts.range(1, 8) - - class _rw_class(_FeatureRW): # no longer needed - set Onboard Profiles to disable + def read(self, device): + enabled = device.feature_request(_F.ONBOARD_PROFILES, 0x20)[0] + if enabled == 0x01: + active = device.feature_request(_F.ONBOARD_PROFILES, 0x40) + return active[:2] + else: + return b"\x00\x00" def write(self, device, data_bytes): - # Host mode is required for report rate to be adjustable - if _hidpp20.get_onboard_mode(device) != _hidpp20.ONBOARD_MODES.MODE_HOST: - _hidpp20.set_onboard_mode(device, _hidpp20.ONBOARD_MODES.MODE_HOST) - return super().write(device, data_bytes) + if data_bytes == b"\x00\x00": + result = device.feature_request(_F.ONBOARD_PROFILES, 0x10, b"\x02") + else: + device.feature_request(_F.ONBOARD_PROFILES, 0x10, b"\x01") + result = device.feature_request(_F.ONBOARD_PROFILES, 0x30, data_bytes) + profile_change(device, common.bytes2int(data_bytes)) + return result - class validator_class(_ChoicesV): + class validator_class(settings_validator.ChoicesValidator): + @classmethod + def build(cls, setting_class, device): + headers = hidpp20.OnboardProfiles.get_profile_headers(device) + profiles_list = [setting_class.choices_universe[0]] + if headers: + for sector, enabled in headers: + if enabled and setting_class.choices_universe[sector]: + profiles_list.append(setting_class.choices_universe[sector]) + return cls(choices=common.NamedInts.list(profiles_list), byte_count=2) if len(profiles_list) > 1 else None + +class ReportRate(settings.Setting): + name = "report_rate" + label = _("Report Rate") + description = ( + _("Frequency of device movement reports") + "\n" + _("May need Onboard Profiles set to Disable to be effective.") + ) + feature = _F.REPORT_RATE + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + choices_universe = common.NamedInts() + choices_universe[1] = "1ms" + choices_universe[2] = "2ms" + choices_universe[3] = "3ms" + choices_universe[4] = "4ms" + choices_universe[5] = "5ms" + choices_universe[6] = "6ms" + choices_universe[7] = "7ms" + choices_universe[8] = "8ms" + + class validator_class(settings_validator.ChoicesValidator): @classmethod def build(cls, setting_class, device): # if device.wpid == '408E': # return None # host mode borks the function keys on the G915 TKL keyboard reply = device.feature_request(_F.REPORT_RATE, 0x00) - assert reply, 'Oops, report rate choices cannot be retrieved!' + assert reply, "Oops, report rate choices cannot be retrieved!" rate_list = [] - rate_flags = _bytes2int(reply[0:1]) + rate_flags = common.bytes2int(reply[0:1]) for i in range(0, 8): if (rate_flags >> i) & 0x01: - rate_list.append(i + 1) - return cls(choices=_NamedInts.list(rate_list), byte_count=1) if rate_list else None + rate_list.append(setting_class.choices_universe[i + 1]) + return cls(choices=common.NamedInts.list(rate_list), byte_count=1) if rate_list else None -class DivertCrown(_Setting): - name = 'divert-crown' - label = _('Divert crown events') - description = _('Make crown send CROWN HID++ notifications (which trigger Solaar rules but are otherwise ignored).') - feature = _F.CROWN - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'true_value': 0x02, 'false_value': 0x01, 'mask': 0xff} - - -class CrownSmooth(_Setting): - name = 'crown-smooth' - label = _('Crown smooth scroll') - description = _('Set crown smooth scroll') - feature = _F.CROWN - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'true_value': 0x01, 'false_value': 0x02, 'read_skip_byte_count': 1, 'write_prefix_bytes': b'\x00'} - - -class DivertGkeys(_Setting): - name = 'divert-gkeys' - label = _('Divert G Keys') +class ExtendedReportRate(settings.Setting): + name = "report_rate_extended" + label = _("Report Rate") description = ( - _('Make G keys send GKEY HID++ notifications (which trigger Solaar rules but are otherwise ignored).') + '\n' + - _('May also make M keys and MR key send HID++ notifications') + _("Frequency of device movement reports") + "\n" + _("May need Onboard Profiles set to Disable to be effective.") ) + feature = _F.EXTENDED_ADJUSTABLE_REPORT_RATE + rw_options = {"read_fnid": 0x20, "write_fnid": 0x30} + choices_universe = common.NamedInts() + choices_universe[0] = "8ms" + choices_universe[1] = "4ms" + choices_universe[2] = "2ms" + choices_universe[3] = "1ms" + choices_universe[4] = "500us" + choices_universe[5] = "250us" + choices_universe[6] = "125us" + + class validator_class(settings_validator.ChoicesValidator): + @classmethod + def build(cls, setting_class, device): + reply = device.feature_request(_F.EXTENDED_ADJUSTABLE_REPORT_RATE, 0x10) + assert reply, "Oops, report rate choices cannot be retrieved!" + rate_list = [] + rate_flags = common.bytes2int(reply[0:2]) + for i in range(0, 7): + if rate_flags & (0x01 << i): + rate_list.append(setting_class.choices_universe[i]) + return cls(choices=common.NamedInts.list(rate_list), byte_count=1) if rate_list else None + + +class DivertCrown(settings.Setting): + name = "divert-crown" + label = _("Divert crown events") + description = _("Make crown send CROWN HID++ notifications (which trigger Solaar rules but are otherwise ignored).") + feature = _F.CROWN + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"true_value": 0x02, "false_value": 0x01, "mask": 0xFF} + + +class CrownSmooth(settings.Setting): + name = "crown-smooth" + label = _("Crown smooth scroll") + description = _("Set crown smooth scroll") + feature = _F.CROWN + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"true_value": 0x01, "false_value": 0x02, "read_skip_byte_count": 1, "write_prefix_bytes": b"\x00"} + + +class DivertGkeys(settings.Setting): + name = "divert-gkeys" + label = _("Divert G and M Keys") + description = _("Make G and M keys send HID++ notifications (which trigger Solaar rules but are otherwise ignored).") feature = _F.GKEY - validator_options = {'true_value': 0x01, 'false_value': 0x00, 'mask': 0xff} - - class rw_class(_FeatureRW): + validator_options = {"true_value": 0x01, "false_value": 0x00, "mask": 0xFF} + class rw_class(settings.FeatureRW): def __init__(self, feature): super().__init__(feature, write_fnid=0x20) def read(self, device): # no way to read, so just assume not diverted - return b'\x00' + return b"\x00" -class ScrollRatchet(_Setting): - name = 'scroll-ratchet' - label = _('Scroll Wheel Ratcheted') - description = _('Switch the mouse wheel between speed-controlled ratcheting and always freespin.') +class ScrollRatchet(settings.Setting): + name = "scroll-ratchet" + label = _("Scroll Wheel Ratcheted") + description = _("Switch the mouse wheel between speed-controlled ratcheting and always freespin.") feature = _F.SMART_SHIFT - choices_universe = _NamedInts(**{_('Freespinning'): 1, _('Ratcheted'): 2}) - validator_class = _ChoicesV - validator_options = {'choices': choices_universe} + choices_universe = common.NamedInts(**{_("Freespinning"): 1, _("Ratcheted"): 2}) + validator_class = settings_validator.ChoicesValidator + validator_options = {"choices": choices_universe} -class SmartShift(_Setting): - name = 'smart-shift' - label = _('Scroll Wheel Ratchet Speed') +class SmartShift(settings.Setting): + name = "smart-shift" + label = _("Scroll Wheel Ratchet Speed") description = _( - 'Use the mouse wheel speed to switch between ratcheted and freespinning.\n' - 'The mouse wheel is always ratcheted at 50.' + "Use the mouse wheel speed to switch between ratcheted and freespinning.\n" + "The mouse wheel is always ratcheted at 50." ) feature = _F.SMART_SHIFT - rw_options = {'read_fnid': 0x00, 'write_fnid': 0x10} + rw_options = {"read_fnid": 0x00, "write_fnid": 0x10} - class rw_class(_FeatureRW): + class rw_class(settings.FeatureRW): MIN_VALUE = 1 MAX_VALUE = 50 @@ -426,68 +669,106 @@ class SmartShift(_Setting): def read(self, device): value = super().read(device) - if _bytes2int(value[0:1]) == 1: + if common.bytes2int(value[0:1]) == 1: # Mode = Freespin, map to minimum - return _int2bytes(self.MIN_VALUE, count=1) + return common.int2bytes(self.MIN_VALUE, count=1) else: # Mode = smart shift, map to the value, capped at maximum - threshold = min(_bytes2int(value[1:2]), self.MAX_VALUE) - return _int2bytes(threshold, count=1) + threshold = min(common.bytes2int(value[1:2]), self.MAX_VALUE) + return common.int2bytes(threshold, count=1) def write(self, device, data_bytes): - threshold = _bytes2int(data_bytes) + threshold = common.bytes2int(data_bytes) # Freespin at minimum mode = 0 # 1 if threshold <= self.MIN_VALUE else 2 # Ratchet at maximum if threshold >= self.MAX_VALUE: threshold = 255 - data = _int2bytes(mode, count=1) + _int2bytes(max(0, threshold), count=1) + data = common.int2bytes(mode, count=1) + common.int2bytes(max(0, threshold), count=1) return super().write(device, data) min_value = rw_class.MIN_VALUE max_value = rw_class.MAX_VALUE - validator_class = _RangeV + validator_class = settings_validator.RangeValidator class SmartShiftEnhanced(SmartShift): feature = _F.SMART_SHIFT_ENHANCED - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + + +class ScrollRatchetEnhanced(ScrollRatchet): + feature = _F.SMART_SHIFT_ENHANCED + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + + +class ScrollRatchetTorque(settings.Setting): + name = "scroll-ratchet-torque" + label = _("Scroll Wheel Ratchet Torque") + description = _("Change the torque needed to overcome the ratchet.") + feature = _F.SMART_SHIFT_ENHANCED + min_value = 1 + max_value = 100 + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + + class rw_class(settings.FeatureRW): + def write(self, device, data_bytes): + ratchetSetting = next(filter(lambda s: s.name == "scroll-ratchet", device.settings), None) + if ratchetSetting: # for MX Master 4, the ratchet setting needs to be written for changes to take effect + ratchet_value = ratchetSetting.read(True) + data_bytes = ratchet_value.to_bytes(1, "big") + data_bytes[1:] + result = super().write(device, data_bytes) + return result + + class validator_class(settings_validator.RangeValidator): + @classmethod + def build(cls, setting_class, device): + reply = device.feature_request(_F.SMART_SHIFT_ENHANCED, 0x00) + if reply[0] & 0x01: # device supports tunable torque + return cls( + min_value=setting_class.min_value, + max_value=setting_class.max_value, + byte_count=1, + write_prefix_bytes=b"\x00\x00", # don't change mode or disengage, but see above + read_skip_byte_count=2, + ) # the keys for the choice map are Logitech controls (from special_keys) # each choice value is a NamedInt with the string from a task (to be shown to the user) # and the integer being the control number for that task (to be written to the device) # Solaar only remaps keys (controlled by key gmask and group), not other key reprogramming -class ReprogrammableKeys(_Settings): - name = 'reprogrammable-keys' - label = _('Key/Button Actions') +class ReprogrammableKeys(settings.Settings): + name = "reprogrammable-keys" + label = _("Key/Button Actions") description = ( - _('Change the action for the key or button.') + ' ' + _('Overridden by diversion.') + '\n' + - _('Changing important actions (such as for the left mouse button) can result in an unusable system.') + _("Change the action for the key or button.") + + " " + + _("Overridden by diversion.") + + "\n" + + _("Changing important actions (such as for the left mouse button) can result in an unusable system.") ) feature = _F.REPROG_CONTROLS_V4 - keys_universe = _special_keys.CONTROL - choices_universe = _special_keys.CONTROL + keys_universe = special_keys.CONTROL + choices_universe = special_keys.CONTROL class rw_class: - def __init__(self, feature): self.feature = feature - self.kind = _FeatureRW.kind + self.kind = settings.FeatureRW.kind def read(self, device, key): key_index = device.keys.index(key) key_struct = device.keys[key_index] - return b'\x00\x00' + _int2bytes(int(key_struct.mapped_to), 2) + return b"\x00\x00" + common.int2bytes(int(key_struct.mapped_to), 2) def write(self, device, key, data_bytes): key_index = device.keys.index(key) key_struct = device.keys[key_index] - key_struct.remap(_special_keys.CONTROL[_bytes2int(data_bytes)]) + key_struct.remap(special_keys.CONTROL[common.bytes2int(data_bytes)]) return True - class validator_class(_ChoicesMapV): - + class validator_class(settings_validator.ChoicesMapValidator): @classmethod def build(cls, setting_class, device): choices = {} @@ -499,64 +780,71 @@ class ReprogrammableKeys(_Settings): return cls(choices, key_byte_count=2, byte_count=2, extra_default=0) if choices else None -class DpiSlidingXY(_RawXYProcessing): +class DpiSlidingXY(settings.RawXYProcessing): + def __init__( + self, + *args, + show_notification: Callable[[str, str], bool], + **kwargs, + ): + super().__init__(*args, **kwargs) + self.fsmState = None + self._show_notification = show_notification def activate_action(self): - self.dpiSetting = next(filter(lambda s: s.name == 'dpi', self.device.settings), None) + self.dpiSetting = next(filter(lambda s: s.name == "dpi" or s.name == "dpi_extended", self.device.settings), None) self.dpiChoices = list(self.dpiSetting.choices) - self.otherDpiIdx = self.device.persister.get('_dpi-sliding', -1) if self.device.persister else -1 + self.otherDpiIdx = self.device.persister.get("_dpi-sliding", -1) if self.device.persister else -1 if not isinstance(self.otherDpiIdx, int) or self.otherDpiIdx < 0 or self.otherDpiIdx >= len(self.dpiChoices): self.otherDpiIdx = self.dpiChoices.index(self.dpiSetting.read()) - self.fsmState = 'idle' - self.dx = 0. + self.fsmState = State.IDLE + self.dx = 0.0 self.movingDpiIdx = None def setNewDpi(self, newDpiIdx): newDpi = self.dpiChoices[newDpiIdx] self.dpiSetting.write(newDpi) - from solaar.ui import status_changed as _status_changed - _status_changed(self.device, refresh=True) # update main window + if self.device.setting_callback: + self.device.setting_callback(self.device, type(self.dpiSetting), [newDpi]) def displayNewDpi(self, newDpiIdx): - from solaar.ui import notify as _notify # import here to avoid circular import when running `solaar show`, - if _notify.available: - reason = 'DPI %d [min %d, max %d]' % (self.dpiChoices[newDpiIdx], self.dpiChoices[0], self.dpiChoices[-1]) - # if there is a progress percentage then the reason isn't shown - # asPercentage = int(float(newDpiIdx) / float(len(self.dpiChoices) - 1) * 100.) - # _notify.show(self.device, reason=reason, progress=asPercentage) - _notify.show(self.device, reason=reason) + selected_dpi = self.dpiChoices[newDpiIdx] + min_dpi = self.dpiChoices[0] + max_dpi = self.dpiChoices[-1] + reason = f"DPI {selected_dpi} [min {min_dpi}, max {max_dpi}]" + self._show_notification(self.device, reason) def press_action(self, key): # start tracking self.starting = True - if self.fsmState == 'idle': - self.fsmState = 'pressed' - self.dx = 0. + if self.fsmState == State.IDLE: + self.fsmState = State.PRESSED + self.dx = 0.0 # While in 'moved' state, the index into 'dpiChoices' of the currently selected DPI setting self.movingDpiIdx = None def release_action(self): # adjust DPI and stop tracking - if self.fsmState == 'pressed': # Swap with other DPI + if self.fsmState == State.PRESSED: # Swap with other DPI thisIdx = self.dpiChoices.index(self.dpiSetting.read()) newDpiIdx, self.otherDpiIdx = self.otherDpiIdx, thisIdx if self.device.persister: - self.device.persister['_dpi-sliding'] = self.otherDpiIdx + self.device.persister["_dpi-sliding"] = self.otherDpiIdx self.setNewDpi(newDpiIdx) self.displayNewDpi(newDpiIdx) - elif self.fsmState == 'moved': # Set DPI according to displacement + elif self.fsmState == State.MOVED: # Set DPI according to displacement self.setNewDpi(self.movingDpiIdx) - self.fsmState = 'idle' + self.fsmState = State.IDLE def move_action(self, dx, dy): if self.device.features.get_feature_version(_F.REPROG_CONTROLS_V4) >= 5 and self.starting: self.starting = False # hack to ignore strange first movement report from MX Master 3S return currDpi = self.dpiSetting.read() - self.dx += float(dx) / float(currDpi) * 15. # yields a more-or-less DPI-independent dx of about 5/cm - if self.fsmState == 'pressed': - if abs(self.dx) >= 1.: - self.fsmState = 'moved' + self.dx += float(dx) / float(currDpi) * 15.0 # yields a more-or-less DPI-independent dx of about 5/cm + if self.fsmState == State.PRESSED: + if abs(self.dx) >= 1.0: + self.fsmState = State.MOVED self.movingDpiIdx = self.dpiChoices.index(currDpi) - elif self.fsmState == 'moved': + elif self.fsmState == State.MOVED: currIdx = self.dpiChoices.index(self.dpiSetting.read()) newMovingDpiIdx = min(max(currIdx + int(self.dx), 0), len(self.dpiChoices) - 1) if newMovingDpiIdx != self.movingDpiIdx: @@ -564,51 +852,48 @@ class DpiSlidingXY(_RawXYProcessing): self.displayNewDpi(newMovingDpiIdx) -class MouseGesturesXY(_RawXYProcessing): - +class MouseGesturesXY(settings.RawXYProcessing): def activate_action(self): - self.dpiSetting = next(filter(lambda s: s.name == 'dpi', self.device.settings), None) - self.fsmState = 'idle' + self.dpiSetting = next(filter(lambda s: s.name == "dpi" or s.name == "dpi_extended", self.device.settings), None) + self.fsmState = State.IDLE self.initialize_data() def initialize_data(self): - self.dx = 0. - self.dy = 0. + self.dx = 0.0 + self.dy = 0.0 self.lastEv = None self.data = [] def press_action(self, key): self.starting = True - if self.fsmState == 'idle': - self.fsmState = 'pressed' + if self.fsmState == State.IDLE: + self.fsmState = State.PRESSED self.initialize_data() self.data = [key.key] def release_action(self): - if self.fsmState == 'pressed': + if self.fsmState == State.PRESSED: # emit mouse gesture notification - from .base import _HIDPP_Notification as _HIDPP_Notification - from .diversion import process_notification as _process_notification self.push_mouse_event() - if _log.isEnabledFor(_INFO): - _log.info('mouse gesture notification %s', self.data) - payload = _pack('!' + (len(self.data) * 'h'), *self.data) - notification = _HIDPP_Notification(0, 0, 0, 0, payload) - _process_notification(self.device, self.device.status, notification, _hidpp20.FEATURE.MOUSE_GESTURE) - self.fsmState = 'idle' + if logger.isEnabledFor(logging.INFO): + logger.info("mouse gesture notification %s", self.data) + payload = struct.pack("!" + (len(self.data) * "h"), *self.data) + notification = base.HIDPPNotification(0, 0, 0, 0, payload) + diversion.process_notification(self.device, notification, _F.MOUSE_GESTURE) + self.fsmState = State.IDLE def move_action(self, dx, dy): - if self.fsmState == 'pressed': - now = _time() * 1000 # _time_ns() / 1e6 + if self.fsmState == State.PRESSED: + now = time() * 1000 # time_ns() / 1e6 if self.device.features.get_feature_version(_F.REPROG_CONTROLS_V4) >= 5 and self.starting: self.starting = False # hack to ignore strange first movement report from MX Master 3S return - if self.lastEv is not None and now - self.lastEv > 200.: + if self.lastEv is not None and now - self.lastEv > 200.0: self.push_mouse_event() dpi = self.dpiSetting.read() if self.dpiSetting else 1000 - dx = float(dx) / float(dpi) * 15. # This multiplier yields a more-or-less DPI-independent dx of about 5/cm + dx = float(dx) / float(dpi) * 15.0 # This multiplier yields a more-or-less DPI-independent dx of about 5/cm self.dx += dx - dy = float(dy) / float(dpi) * 15. # This multiplier yields a more-or-less DPI-independent dx of about 5/cm + dy = float(dy) / float(dpi) * 15.0 # This multiplier yields a more-or-less DPI-independent dx of about 5/cm self.dy += dy self.lastEv = now @@ -616,9 +901,9 @@ class MouseGesturesXY(_RawXYProcessing): self.push_mouse_event() self.data.append(1) self.data.append(key) - self.lastEv = _time() * 1000 # _time_ns() / 1e6 - if _log.isEnabledFor(_DEBUG): - _log.debug('mouse gesture key event %d %s', key, self.data) + self.lastEv = time() * 1000 # time_ns() / 1e6 + if logger.isEnabledFor(logging.DEBUG): + logger.debug("mouse gesture key event %d %s", key, self.data) def push_mouse_event(self): x = int(self.dx) @@ -628,41 +913,39 @@ class MouseGesturesXY(_RawXYProcessing): self.data.append(0) self.data.append(x) self.data.append(y) - self.dx = 0. - self.dy = 0. - if _log.isEnabledFor(_DEBUG): - _log.debug('mouse gesture move event %d %d %s', x, y, self.data) + self.dx = 0.0 + self.dy = 0.0 + if logger.isEnabledFor(logging.DEBUG): + logger.debug("mouse gesture move event %d %d %s", x, y, self.data) -class DivertKeys(_Settings): - name = 'divert-keys' - label = _('Key/Button Diversion') - description = _('Make the key or button send HID++ notifications (Diverted) or initiate Mouse Gestures or Sliding DPI') +class DivertKeys(settings.Settings): + name = "divert-keys" + label = _("Key/Button Diversion") + description = _("Make the key or button send HID++ notifications (Diverted) or initiate Mouse Gestures or Sliding DPI") feature = _F.REPROG_CONTROLS_V4 - keys_universe = _special_keys.CONTROL - choices_universe = _NamedInts(**{_('Regular'): 0, _('Diverted'): 1, _('Mouse Gestures'): 2, _('Sliding DPI'): 3}) - choices_gesture = _NamedInts(**{_('Regular'): 0, _('Diverted'): 1, _('Mouse Gestures'): 2}) - choices_divert = _NamedInts(**{_('Regular'): 0, _('Diverted'): 1}) + keys_universe = special_keys.CONTROL + choices_universe = common.NamedInts(**{_("Regular"): 0, _("Diverted"): 1, _("Mouse Gestures"): 2, _("Sliding DPI"): 3}) + choices_gesture = common.NamedInts(**{_("Regular"): 0, _("Diverted"): 1, _("Mouse Gestures"): 2}) + choices_divert = common.NamedInts(**{_("Regular"): 0, _("Diverted"): 1}) class rw_class: - def __init__(self, feature): self.feature = feature - self.kind = _FeatureRW.kind + self.kind = settings.FeatureRW.kind def read(self, device, key): key_index = device.keys.index(key) key_struct = device.keys[key_index] - return b'\x00\x00\x01' if 'diverted' in key_struct.mapping_flags else b'\x00\x00\x00' + return b"\x00\x00\x01" if MappingFlag.DIVERTED in key_struct.mapping_flags else b"\x00\x00\x00" def write(self, device, key, data_bytes): key_index = device.keys.index(key) key_struct = device.keys[key_index] - key_struct.set_diverted(_bytes2int(data_bytes) != 0) # not regular + key_struct.set_diverted(common.bytes2int(data_bytes) != 0) # not regular return True - class validator_class(_ChoicesMapV): - + class validator_class(settings_validator.ChoicesMapValidator): def __init__(self, choices, key_byte_count=2, byte_count=1, mask=0x01): super().__init__(choices, key_byte_count, byte_count, mask) @@ -682,18 +965,20 @@ class DivertKeys(_Settings): sliding = gestures = None choices = {} if device.keys: - for k in device.keys: - if 'divertable' in k.flags and 'virtual' not in k.flags: - if 'raw XY' in k.flags: - choices[k.key] = setting_class.choices_gesture + for key in device.keys: + if KeyFlag.DIVERTABLE in key.flags and KeyFlag.VIRTUAL not in key.flags: + if KeyFlag.RAW_XY in key.flags: + choices[key.key] = setting_class.choices_gesture if gestures is None: - gestures = MouseGesturesXY(device, name='MouseGestures') + gestures = MouseGesturesXY(device, name="MouseGestures") if _F.ADJUSTABLE_DPI in device.features: - choices[k.key] = setting_class.choices_universe + choices[key.key] = setting_class.choices_universe if sliding is None: - sliding = DpiSlidingXY(device, name='DpiSlding') + sliding = DpiSlidingXY( + device, name="DpiSliding", show_notification=desktop_notifications.show + ) else: - choices[k.key] = setting_class.choices_divert + choices[key.key] = setting_class.choices_divert if not choices: return None validator = cls(choices, key_byte_count=2, byte_count=1, mask=0x01) @@ -702,333 +987,426 @@ class DivertKeys(_Settings): return validator -class AdjustableDpi(_Setting): - """Pointer Speed feature""" - # Assume sensorIdx 0 (there is only one sensor) - # [2] getSensorDpi(sensorIdx) -> sensorIdx, dpiMSB, dpiLSB - # [3] setSensorDpi(sensorIdx, dpi) - name = 'dpi' - label = _('Sensitivity (DPI)') - description = _('Mouse movement sensitivity') +def produce_dpi_list(feature, function, ignore, device, direction): + dpi_bytes = b"" + for i in range(0, 0x100): # there will be only a very few iterations performed + reply = device.feature_request(feature, function, 0x00, direction, i) + assert reply, "Oops, DPI list cannot be retrieved!" + dpi_bytes += reply[ignore:] + if dpi_bytes[-2:] == b"\x00\x00": + break + dpi_list = [] + i = 0 + while i < len(dpi_bytes): + val = common.bytes2int(dpi_bytes[i : i + 2]) + if val == 0: + break + if val >> 13 == 0b111: + step = val & 0x1FFF + last = common.bytes2int(dpi_bytes[i + 2 : i + 4]) + assert len(dpi_list) > 0 and last > dpi_list[-1], f"Invalid DPI list item: {val!r}" + dpi_list += range(dpi_list[-1] + step, last + 1, step) + i += 4 + else: + dpi_list.append(val) + i += 2 + return dpi_list + + +class AdjustableDpi(settings.Setting): + name = "dpi" + label = _("Sensitivity (DPI)") + description = _("Mouse movement sensitivity") + "\n" + _("May need Onboard Profiles set to Disable to be effective.") feature = _F.ADJUSTABLE_DPI - rw_options = {'read_fnid': 0x20, 'write_fnid': 0x30} - choices_universe = _NamedInts.range(200, 4000, str, 50) - - class validator_class(_ChoicesV): + rw_options = {"read_fnid": 0x20, "write_fnid": 0x30} + choices_universe = common.NamedInts.range(100, 4000, str, 50) + class validator_class(settings_validator.ChoicesValidator): @classmethod def build(cls, setting_class, device): - # [1] getSensorDpiList(sensorIdx) - reply = device.feature_request(_F.ADJUSTABLE_DPI, 0x10) - assert reply, 'Oops, DPI list cannot be retrieved!' - dpi_list = [] - step = None - for val in _unpack('!7H', reply[1:1 + 14]): - if val == 0: - break - if val >> 13 == 0b111: - assert step is None and len(dpi_list) == 1, \ - 'Invalid DPI list item: %r' % val - step = val & 0x1fff - else: - dpi_list.append(val) - if step: - assert len(dpi_list) == 2, 'Invalid DPI list range: %r' % dpi_list - dpi_list = range(dpi_list[0], dpi_list[1] + 1, step) - return cls(choices=_NamedInts.list(dpi_list), byte_count=3) if dpi_list else None + dpilist = produce_dpi_list(setting_class.feature, 0x10, 1, device, 0) + setting = ( + cls(choices=common.NamedInts.list(dpilist), byte_count=2, write_prefix_bytes=b"\x00") if dpilist else None + ) + setting.dpilist = dpilist + return setting def validate_read(self, reply_bytes): # special validator to use default DPI if needed - reply_value = _bytes2int(reply_bytes[1:3]) + reply_value = common.bytes2int(reply_bytes[1:3]) if reply_value == 0: # use default value instead - reply_value = _bytes2int(reply_bytes[3:5]) + reply_value = common.bytes2int(reply_bytes[3:5]) valid_value = self.choices[reply_value] - assert valid_value is not None, '%s: failed to validate read value %02X' % (self.__class__.__name__, reply_value) + assert valid_value is not None, f"{self.__class__.__name__}: failed to validate read value {reply_value:02X}" return valid_value -class SpeedChange(_Setting): +class ExtendedAdjustableDpi(settings.Setting): + # the extended version allows for two dimensions, longer dpi descriptions, but still assume only one sensor + name = "dpi_extended" + label = _("Sensitivity (DPI)") + description = _("Mouse movement sensitivity") + "\n" + _("May need Onboard Profiles set to Disable to be effective.") + feature = _F.EXTENDED_ADJUSTABLE_DPI + rw_options = {"read_fnid": 0x50, "write_fnid": 0x60} + keys_universe = common.NamedInts(X=0, Y=1, LOD=2) + choices_universe = common.NamedInts.range(100, 4000, str, 50) + choices_universe[1] = "LOW" + choices_universe[2] = "MEDIUM" + choices_universe[3] = "HIGH" + keys = common.NamedInts(X=0, Y=1, LOD=2) + + def write_key_value(self, key, value, save=True): + # Force a read to populate the full X/Y/LOD dictionary if it's missing (fixes CLI) + if not isinstance(self._value, dict): + self.read() + + if isinstance(self._value, dict): + self._value[key] = value + else: + self._value = {key: value} + + result = self.write(self._value, save) + return result[key] if isinstance(result, dict) else result + + class validator_class(settings_validator.ChoicesMapValidator): + @classmethod + def build(cls, setting_class, device): + reply = device.feature_request(setting_class.feature, 0x10, 0x00) + y = bool(reply[2] & 0x01) + lod = bool(reply[2] & 0x02) + choices_map = {} + dpilist_x = produce_dpi_list(setting_class.feature, 0x20, 3, device, 0) + choices_map[setting_class.keys["X"]] = common.NamedInts.list(dpilist_x) + if y: + dpilist_y = produce_dpi_list(setting_class.feature, 0x20, 3, device, 1) + choices_map[setting_class.keys["Y"]] = common.NamedInts.list(dpilist_y) + if lod: + choices_map[setting_class.keys["LOD"]] = common.NamedInts(LOW=0, MEDIUM=1, HIGH=2) + validator = cls(choices_map=choices_map, byte_count=2, write_prefix_bytes=b"\x00") + validator.y = y + validator.lod = lod + validator.keys = setting_class.keys + return validator + + def validate_read(self, reply_bytes): # special validator to read entire setting + dpi_x = common.bytes2int(reply_bytes[3:5]) if reply_bytes[1:3] == 0 else common.bytes2int(reply_bytes[1:3]) + assert dpi_x in self.choices[0], f"{self.__class__.__name__}: failed to validate dpi_x value {dpi_x:04X}" + value = {self.keys["X"]: dpi_x} + if self.y: + dpi_y = common.bytes2int(reply_bytes[7:9]) if reply_bytes[5:7] == 0 else common.bytes2int(reply_bytes[5:7]) + assert dpi_y in self.choices[1], f"{self.__class__.__name__}: failed to validate dpi_y value {dpi_y:04X}" + value[self.keys["Y"]] = dpi_y + if self.lod: + lod = reply_bytes[9] + assert lod in self.choices[2], f"{self.__class__.__name__}: failed to validate lod value {lod:02X}" + value[self.keys["LOD"]] = lod + return value + + def prepare_write(self, new_value, current_value=None): # special preparer to write entire setting + data_bytes = self._write_prefix_bytes + if new_value[self.keys["X"]] not in self.choices[self.keys["X"]]: + raise ValueError(f"invalid value {new_value!r}") + data_bytes += common.int2bytes(new_value[0], 2) + if self.y: + if new_value[self.keys["Y"]] not in self.choices[self.keys["Y"]]: + raise ValueError(f"invalid value {new_value!r}") + data_bytes += common.int2bytes(new_value[self.keys["Y"]], 2) + else: + data_bytes += b"\x00\x00" + if self.lod: + if new_value[self.keys["LOD"]] not in self.choices[self.keys["LOD"]]: + raise ValueError(f"invalid value {new_value!r}") + data_bytes += common.int2bytes(new_value[self.keys["LOD"]], 1) + else: + data_bytes += b"\x00" + return data_bytes + + +class SpeedChange(settings.Setting): """Implements the ability to switch Sensitivity by clicking on the DPI_Change button.""" - name = 'speed-change' - label = _('Sensitivity Switching') + + name = "speed-change" + label = _("Sensitivity Switching") description = _( - 'Switch the current sensitivity and the remembered sensitivity when the key or button is pressed.\n' - 'If there is no remembered sensitivity, just remember the current sensitivity' + "Switch the current sensitivity and the remembered sensitivity when the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current sensitivity" ) - choices_universe = _special_keys.CONTROL - choices_extra = _NamedInt(0, _('Off')) + choices_universe = special_keys.CONTROL + choices_extra = common.NamedInt(0, _("Off")) feature = _F.POINTER_SPEED - rw_options = {'name': 'speed change'} - - class rw_class(_ActionSettingRW): + rw_options = {"name": "speed change"} + class rw_class(settings.ActionSettingRW): def press_action(self): # switch sensitivity - currentSpeed = self.device.persister.get('pointer_speed', None) if self.device.persister else None - newSpeed = self.device.persister.get('_speed-change', None) if self.device.persister else None - speed_setting = next(filter(lambda s: s.name == 'pointer_speed', self.device.settings), None) + currentSpeed = self.device.persister.get("pointer_speed", None) if self.device.persister else None + newSpeed = self.device.persister.get("_speed-change", None) if self.device.persister else None + speed_setting = next(filter(lambda s: s.name == "pointer_speed", self.device.settings), None) if newSpeed is not None: if speed_setting: speed_setting.write(newSpeed) + if self.device.setting_callback: + self.device.setting_callback(self.device, type(speed_setting), [newSpeed]) else: - _log.error('cannot save sensitivity setting on %s', self.device) - from solaar.ui import status_changed as _status_changed - _status_changed(self.device, refresh=True) # update main window + logger.error("cannot save sensitivity setting on %s", self.device) if self.device.persister: - self.device.persister['_speed-change'] = currentSpeed - - class validator_class(_ChoicesV): + self.device.persister["_speed-change"] = currentSpeed + class validator_class(settings_validator.ChoicesValidator): @classmethod def build(cls, setting_class, device): - key_index = device.keys.index(_special_keys.CONTROL.DPI_Change) + key_index = device.keys.index(special_keys.CONTROL.DPI_Change) key = device.keys[key_index] if key_index is not None else None - if key is not None and 'divertable' in key.flags: + if key is not None and KeyFlag.DIVERTABLE in key.flags: keys = [setting_class.choices_extra, key.key] - return cls(choices=_NamedInts.list(keys), byte_count=2) + return cls(choices=common.NamedInts.list(keys), byte_count=2) -class DisableKeyboardKeys(_BitFieldSetting): - name = 'disable-keyboard-keys' - label = _('Disable keys') - description = _('Disable specific keyboard keys.') +class DisableKeyboardKeys(settings.BitFieldSetting): + name = "disable-keyboard-keys" + label = _("Disable keys") + description = _("Disable specific keyboard keys.") feature = _F.KEYBOARD_DISABLE_KEYS - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - _labels = {k: (None, _('Disables the %s key.') % k) for k in _DKEY} - choices_universe = _DKEY - - class validator_class(_BitFieldV): + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + _labels = {k: (None, _("Disables the %s key.") % k) for k in special_keys.DISABLE} + choices_universe = special_keys.DISABLE + class validator_class(settings_validator.BitFieldValidator): @classmethod def build(cls, setting_class, device): mask = device.feature_request(_F.KEYBOARD_DISABLE_KEYS, 0x00)[0] - options = [_DKEY[1 << i] for i in range(8) if mask & (1 << i)] + options = [special_keys.DISABLE[1 << i] for i in range(8) if mask & (1 << i)] return cls(options) if options else None -class Multiplatform(_Setting): - name = 'multiplatform' - label = _('Set OS') - description = _('Change keys to match OS.') +class Multiplatform(settings.Setting): + name = "multiplatform" + label = _("Set OS") + description = _("Change keys to match OS.") feature = _F.MULTIPLATFORM - rw_options = {'read_fnid': 0x00, 'write_fnid': 0x30} - choices_universe = _NamedInts(**{'OS ' + str(i + 1): i for i in range(8)}) + rw_options = {"read_fnid": 0x00, "write_fnid": 0x30} + choices_universe = common.NamedInts(**{"OS " + str(i + 1): i for i in range(8)}) # multiplatform OS bits - OSS = [('Linux', 0x0400), ('MacOS', 0x2000), ('Windows', 0x0100), ('iOS', 0x4000), ('Android', 0x1000), ('WebOS', 0x8000), - ('Chrome', 0x0800), ('WinEmb', 0x0200), ('Tizen', 0x0001)] + OSS = [ + ("Linux", 0x0400), + ("MacOS", 0x2000), + ("Windows", 0x0100), + ("iOS", 0x4000), + ("Android", 0x1000), + ("WebOS", 0x8000), + ("Chrome", 0x0800), + ("WinEmb", 0x0200), + ("Tizen", 0x0001), + ] # the problem here is how to construct the right values for the rules Set GUI, # as, for example, the integer value for 'Windows' can be different on different devices - class validator_class(_ChoicesV): - + class validator_class(settings_validator.ChoicesValidator): @classmethod def build(cls, setting_class, device): - def _str_os_versions(low, high): - def _str_os_version(version): if version == 0: - return '' + return "" elif version & 0xFF: - return str(version >> 8) + '.' + str(version & 0xFF) + return f"{str(version >> 8)}.{str(version & 0xFF)}" else: return str(version >> 8) - return '' if low == 0 and high == 0 else ' ' + _str_os_version(low) + '-' + _str_os_version(high) + return "" if low == 0 and high == 0 else f" {_str_os_version(low)}-{_str_os_version(high)}" infos = device.feature_request(_F.MULTIPLATFORM) - assert infos, 'Oops, multiplatform count cannot be retrieved!' - flags, _ignore, num_descriptors = _unpack('!BBB', infos[:3]) + assert infos, "Oops, multiplatform count cannot be retrieved!" + flags, _ignore, num_descriptors = struct.unpack("!BBB", infos[:3]) if not (flags & 0x02): # can't set platform so don't create setting return [] descriptors = [] for index in range(0, num_descriptors): descriptor = device.feature_request(_F.MULTIPLATFORM, 0x10, index) - platform, _ignore, os_flags, low, high = _unpack('!BBHHH', descriptor[:8]) + platform, _ignore, os_flags, low, high = struct.unpack("!BBHHH", descriptor[:8]) descriptors.append((platform, os_flags, low, high)) - choices = _NamedInts() + choices = common.NamedInts() for os_name, os_bit in setting_class.OSS: for platform, os_flags, low, high in descriptors: os = os_name + _str_os_versions(low, high) if os_bit & os_flags and platform not in choices and os not in choices: choices[platform] = os - return cls(choices=choices, read_skip_byte_count=6, write_prefix_bytes=b'\xff') if choices else None + return cls(choices=choices, read_skip_byte_count=6, write_prefix_bytes=b"\xff") if choices else None -class DualPlatform(_Setting): - name = 'multiplatform' - label = _('Set OS') - description = _('Change keys to match OS.') - choices_universe = _NamedInts() - choices_universe[0x00] = 'iOS, MacOS' - choices_universe[0x01] = 'Android, Windows' +class DualPlatform(settings.Setting): + name = "multiplatform" + label = _("Set OS") + description = _("Change keys to match OS.") + choices_universe = common.NamedInts() + choices_universe[0x00] = "iOS, MacOS" + choices_universe[0x01] = "Android, Windows" feature = _F.DUALPLATFORM - rw_options = {'read_fnid': 0x00, 'write_fnid': 0x20} - validator_class = _ChoicesV - validator_options = {'choices': choices_universe} + rw_options = {"read_fnid": 0x00, "write_fnid": 0x20} + validator_class = settings_validator.ChoicesValidator + validator_options = {"choices": choices_universe} -class ChangeHost(_Setting): - name = 'change-host' - label = _('Change Host') - description = _('Switch connection to a different host') +class ChangeHost(settings.Setting): + name = "change-host" + label = _("Change Host") + description = _("Switch connection to a different host") persist = False # persisting this setting is harmful feature = _F.CHANGE_HOST - rw_options = {'read_fnid': 0x00, 'write_fnid': 0x10, 'no_reply': True} - choices_universe = _NamedInts(**{'Host ' + str(i + 1): i for i in range(3)}) - - class validator_class(_ChoicesV): + rw_options = {"read_fnid": 0x00, "write_fnid": 0x10, "no_reply": True} + choices_universe = common.NamedInts(**{"Host " + str(i + 1): i for i in range(3)}) + class validator_class(settings_validator.ChoicesValidator): @classmethod def build(cls, setting_class, device): infos = device.feature_request(_F.CHANGE_HOST) - assert infos, 'Oops, host count cannot be retrieved!' - numHosts, currentHost = _unpack('!BB', infos[:2]) + assert infos, "Oops, host count cannot be retrieved!" + numHosts, currentHost = struct.unpack("!BB", infos[:2]) hostNames = _hidpp20.get_host_names(device) hostNames = hostNames if hostNames is not None else {} - if currentHost not in hostNames or hostNames[currentHost][1] == '': - import socket # find name of current host and use it - hostNames[currentHost] = (True, socket.gethostname().partition('.')[0]) - choices = _NamedInts() + if currentHost not in hostNames or hostNames[currentHost][1] == "": + hostNames[currentHost] = (True, socket.gethostname().partition(".")[0]) + choices = common.NamedInts() for host in range(0, numHosts): - paired, hostName = hostNames.get(host, (True, '')) - choices[host] = str(host + 1) + ':' + hostName if hostName else str(host + 1) + paired, hostName = hostNames.get(host, (True, "")) + choices[host] = f"{str(host + 1)}:{hostName}" if hostName else str(host + 1) return cls(choices=choices, read_skip_byte_count=1) if choices and len(choices) > 1 else None _GESTURE2_GESTURES_LABELS = { - _GG['Tap1Finger']: (_('Single tap'), _('Performs a left click.')), - _GG['Tap2Finger']: (_('Single tap with two fingers'), _('Performs a right click.')), - _GG['Tap3Finger']: (_('Single tap with three fingers'), None), - _GG['Click1Finger']: (None, None), - _GG['Click2Finger']: (None, None), - _GG['Click3Finger']: (None, None), - _GG['DoubleTap1Finger']: (_('Double tap'), _('Performs a double click.')), - _GG['DoubleTap2Finger']: (_('Double tap with two fingers'), None), - _GG['DoubleTap3Finger']: (_('Double tap with three fingers'), None), - _GG['Track1Finger']: (None, None), - _GG['TrackingAcceleration']: (None, None), - _GG['TapDrag1Finger']: (_('Tap and drag'), _('Drags items by dragging the finger after double tapping.')), - _GG['TapDrag2Finger']: - (_('Tap and drag with two fingers'), _('Drags items by dragging the fingers after double tapping.')), - _GG['Drag3Finger']: (_('Tap and drag with three fingers'), None), - _GG['TapGestures']: (None, None), - _GG['FnClickGestureSuppression']: - (_('Suppress tap and edge gestures'), _('Disables tap and edge gestures (equivalent to pressing Fn+LeftClick).')), - _GG['Scroll1Finger']: (_('Scroll with one finger'), _('Scrolls.')), - _GG['Scroll2Finger']: (_('Scroll with two fingers'), _('Scrolls.')), - _GG['Scroll2FingerHoriz']: (_('Scroll horizontally with two fingers'), _('Scrolls horizontally.')), - _GG['Scroll2FingerVert']: (_('Scroll vertically with two fingers'), _('Scrolls vertically.')), - _GG['Scroll2FingerStateless']: (_('Scroll with two fingers'), _('Scrolls.')), - _GG['NaturalScrolling']: (_('Natural scrolling'), _('Inverts the scrolling direction.')), - _GG['Thumbwheel']: (_('Thumbwheel'), _('Enables the thumbwheel.')), - _GG['VScrollInertia']: (None, None), - _GG['VScrollBallistics']: (None, None), - _GG['Swipe2FingerHoriz']: (None, None), - _GG['Swipe3FingerHoriz']: (None, None), - _GG['Swipe4FingerHoriz']: (None, None), - _GG['Swipe3FingerVert']: (None, None), - _GG['Swipe4FingerVert']: (None, None), - _GG['LeftEdgeSwipe1Finger']: (None, None), - _GG['RightEdgeSwipe1Finger']: (None, None), - _GG['BottomEdgeSwipe1Finger']: (None, None), - _GG['TopEdgeSwipe1Finger']: (_('Swipe from the top edge'), None), - _GG['LeftEdgeSwipe1Finger2']: (_('Swipe from the left edge'), None), - _GG['RightEdgeSwipe1Finger2']: (_('Swipe from the right edge'), None), - _GG['BottomEdgeSwipe1Finger2']: (_('Swipe from the bottom edge'), None), - _GG['TopEdgeSwipe1Finger2']: (_('Swipe from the top edge'), None), - _GG['LeftEdgeSwipe2Finger']: (_('Swipe two fingers from the left edge'), None), - _GG['RightEdgeSwipe2Finger']: (_('Swipe two fingers from the right edge'), None), - _GG['BottomEdgeSwipe2Finger']: (_('Swipe two fingers from the bottom edge'), None), - _GG['TopEdgeSwipe2Finger']: (_('Swipe two fingers from the top edge'), None), - _GG['Zoom2Finger']: (_('Zoom with two fingers.'), _('Pinch to zoom out; spread to zoom in.')), - _GG['Zoom2FingerPinch']: (_('Pinch to zoom out.'), _('Pinch to zoom out.')), - _GG['Zoom2FingerSpread']: (_('Spread to zoom in.'), _('Spread to zoom in.')), - _GG['Zoom3Finger']: (_('Zoom with three fingers.'), None), - _GG['Zoom2FingerStateless']: (_('Zoom with two fingers'), _('Pinch to zoom out; spread to zoom in.')), - _GG['TwoFingersPresent']: (None, None), - _GG['Rotate2Finger']: (None, None), - _GG['Finger1']: (None, None), - _GG['Finger2']: (None, None), - _GG['Finger3']: (None, None), - _GG['Finger4']: (None, None), - _GG['Finger5']: (None, None), - _GG['Finger6']: (None, None), - _GG['Finger7']: (None, None), - _GG['Finger8']: (None, None), - _GG['Finger9']: (None, None), - _GG['Finger10']: (None, None), - _GG['DeviceSpecificRawData']: (None, None), + GestureId.TAP_1_FINGER: (_("Single tap"), _("Performs a left click.")), + GestureId.TAP_2_FINGER: (_("Single tap with two fingers"), _("Performs a right click.")), + GestureId.TAP_3_FINGER: (_("Single tap with three fingers"), None), + GestureId.CLICK_1_FINGER: (None, None), + GestureId.CLICK_2_FINGER: (None, None), + GestureId.CLICK_3_FINGER: (None, None), + GestureId.DOUBLE_TAP_1_FINGER: (_("Double tap"), _("Performs a double click.")), + GestureId.DOUBLE_TAP_2_FINGER: (_("Double tap with two fingers"), None), + GestureId.DOUBLE_TAP_3_FINGER: (_("Double tap with three fingers"), None), + GestureId.TRACK_1_FINGER: (None, None), + GestureId.TRACKING_ACCELERATION: (None, None), + GestureId.TAP_DRAG_1_FINGER: (_("Tap and drag"), _("Drags items by dragging the finger after double tapping.")), + GestureId.TAP_DRAG_2_FINGER: ( + _("Tap and drag with two fingers"), + _("Drags items by dragging the fingers after double tapping."), + ), + GestureId.DRAG_3_FINGER: (_("Tap and drag with three fingers"), None), + GestureId.TAP_GESTURES: (None, None), + GestureId.FN_CLICK_GESTURE_SUPPRESSION: ( + _("Suppress tap and edge gestures"), + _("Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)."), + ), + GestureId.SCROLL_1_FINGER: (_("Scroll with one finger"), _("Scrolls.")), + GestureId.SCROLL_2_FINGER: (_("Scroll with two fingers"), _("Scrolls.")), + GestureId.SCROLL_2_FINGER_HORIZONTAL: (_("Scroll horizontally with two fingers"), _("Scrolls horizontally.")), + GestureId.SCROLL_2_FINGER_VERTICAL: (_("Scroll vertically with two fingers"), _("Scrolls vertically.")), + GestureId.SCROLL_2_FINGER_STATELESS: (_("Scroll with two fingers"), _("Scrolls.")), + GestureId.NATURAL_SCROLLING: (_("Natural scrolling"), _("Inverts the scrolling direction.")), + GestureId.THUMBWHEEL: (_("Thumbwheel"), _("Enables the thumbwheel.")), + GestureId.V_SCROLL_INTERTIA: (None, None), + GestureId.V_SCROLL_BALLISTICS: (None, None), + GestureId.SWIPE_2_FINGER_HORIZONTAL: (None, None), + GestureId.SWIPE_3_FINGER_HORIZONTAL: (None, None), + GestureId.SWIPE_4_FINGER_HORIZONTAL: (None, None), + GestureId.SWIPE_3_FINGER_VERTICAL: (None, None), + GestureId.SWIPE_4_FINGER_VERTICAL: (None, None), + GestureId.LEFT_EDGE_SWIPE_1_FINGER: (None, None), + GestureId.RIGHT_EDGE_SWIPE_1_FINGER: (None, None), + GestureId.BOTTOM_EDGE_SWIPE_1_FINGER: (None, None), + GestureId.TOP_EDGE_SWIPE_1_FINGER: (_("Swipe from the top edge"), None), + GestureId.LEFT_EDGE_SWIPE_1_FINGER_2: (_("Swipe from the left edge"), None), + GestureId.RIGHT_EDGE_SWIPE_1_FINGER_2: (_("Swipe from the right edge"), None), + GestureId.BOTTOM_EDGE_SWIPE_1_FINGER_2: (_("Swipe from the bottom edge"), None), + GestureId.TOP_EDGE_SWIPE_1_FINGER_2: (_("Swipe from the top edge"), None), + GestureId.LEFT_EDGE_SWIPE_2_FINGER: (_("Swipe two fingers from the left edge"), None), + GestureId.RIGHT_EDGE_SWIPE_2_FINGER: (_("Swipe two fingers from the right edge"), None), + GestureId.BOTTOM_EDGE_SWIPE_2_FINGER: (_("Swipe two fingers from the bottom edge"), None), + GestureId.TOP_EDGE_SWIPE_2_FINGER: (_("Swipe two fingers from the top edge"), None), + GestureId.ZOOM_2_FINGER: (_("Zoom with two fingers."), _("Pinch to zoom out; spread to zoom in.")), + GestureId.ZOOM_2_FINGER_PINCH: (_("Pinch to zoom out."), _("Pinch to zoom out.")), + GestureId.ZOOM_2_FINGER_SPREAD: (_("Spread to zoom in."), _("Spread to zoom in.")), + GestureId.ZOOM_3_FINGER: (_("Zoom with three fingers."), None), + GestureId.ZOOM_2_FINGER_STATELESS: (_("Zoom with two fingers"), _("Pinch to zoom out; spread to zoom in.")), + GestureId.TWO_FINGERS_PRESENT: (None, None), + GestureId.ROTATE_2_FINGER: (None, None), + GestureId.FINGER_1: (None, None), + GestureId.FINGER_2: (None, None), + GestureId.FINGER_3: (None, None), + GestureId.FINGER_4: (None, None), + GestureId.FINGER_5: (None, None), + GestureId.FINGER_6: (None, None), + GestureId.FINGER_7: (None, None), + GestureId.FINGER_8: (None, None), + GestureId.FINGER_9: (None, None), + GestureId.FINGER_10: (None, None), + GestureId.DEVICE_SPECIFIC_RAW_DATA: (None, None), } _GESTURE2_PARAMS_LABELS = { - _GP['ExtraCapabilities']: (None, None), # not supported - _GP['PixelZone']: (_('Pixel zone'), None), # TO DO: replace None with a short description - _GP['RatioZone']: (_('Ratio zone'), None), # TO DO: replace None with a short description - _GP['ScaleFactor']: (_('Scale factor'), _('Sets the cursor speed.')), + ParamId.EXTRA_CAPABILITIES: (None, None), # not supported + ParamId.PIXEL_ZONE: (_("Pixel zone"), None), # TO DO: replace None with a short description + ParamId.RATIO_ZONE: (_("Ratio zone"), None), # TO DO: replace None with a short description + ParamId.SCALE_FACTOR: (_("Scale factor"), _("Sets the cursor speed.")), } _GESTURE2_PARAMS_LABELS_SUB = { - 'left': (_('Left'), _('Left-most coordinate.')), - 'bottom': (_('Bottom'), _('Bottom coordinate.')), - 'width': (_('Width'), _('Width.')), - 'height': (_('Height'), _('Height.')), - 'scale': (_('Scale'), _('Cursor speed.')), + "left": (_("Left"), _("Left-most coordinate.")), + "bottom": (_("Bottom"), _("Bottom coordinate.")), + "width": (_("Width"), _("Width.")), + "height": (_("Height"), _("Height.")), + "scale": (_("Scale"), _("Cursor speed.")), } -class Gesture2Gestures(_BitFieldOMSetting): - name = 'gesture2-gestures' - label = _('Gestures') - description = _('Tweak the mouse/touchpad behaviour.') +class Gesture2Gestures(settings.BitFieldWithOffsetAndMaskSetting): + name = "gesture2-gestures" + label = _("Gestures") + description = _("Tweak the mouse/touchpad behaviour.") feature = _F.GESTURE_2 - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_options = {'om_method': _hidpp20.Gesture.enable_offset_mask} - choices_universe = _hidpp20.GESTURE + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_options = {"om_method": hidpp20.Gesture.enable_offset_mask} + choices_universe = hidpp20_constants.GestureId _labels = _GESTURE2_GESTURES_LABELS - class validator_class(_BitFieldOMV): - + class validator_class(settings_validator.BitFieldWithOffsetAndMaskValidator): @classmethod def build(cls, setting_class, device, om_method=None): options = [g for g in device.gestures.gestures.values() if g.can_be_enabled or g.default_enabled] return cls(options, om_method=om_method) if options else None -class Gesture2Divert(_BitFieldOMSetting): - name = 'gesture2-divert' - label = _('Gestures Diversion') - description = _('Divert mouse/touchpad gestures.') +class Gesture2Divert(settings.BitFieldWithOffsetAndMaskSetting): + name = "gesture2-divert" + label = _("Gestures Diversion") + description = _("Divert mouse/touchpad gestures.") feature = _F.GESTURE_2 - rw_options = {'read_fnid': 0x30, 'write_fnid': 0x40} - validator_options = {'om_method': _hidpp20.Gesture.diversion_offset_mask} - choices_universe = _hidpp20.GESTURE + rw_options = {"read_fnid": 0x30, "write_fnid": 0x40} + validator_options = {"om_method": hidpp20.Gesture.diversion_offset_mask} + choices_universe = hidpp20_constants.GestureId _labels = _GESTURE2_GESTURES_LABELS - class validator_class(_BitFieldOMV): - + class validator_class(settings_validator.BitFieldWithOffsetAndMaskValidator): @classmethod def build(cls, setting_class, device, om_method=None): options = [g for g in device.gestures.gestures.values() if g.can_be_diverted] return cls(options, om_method=om_method) if options else None -class Gesture2Params(_LongSettings): - name = 'gesture2-params' - label = _('Gesture params') - description = _('Change numerical parameters of a mouse/touchpad.') +class Gesture2Params(settings.LongSettings): + name = "gesture2-params" + label = _("Gesture params") + description = _("Change numerical parameters of a mouse/touchpad.") feature = _F.GESTURE_2 - rw_options = {'read_fnid': 0x70, 'write_fnid': 0x80} - choices_universe = _hidpp20.PARAM - sub_items_universe = _hidpp20.SUB_PARAM + rw_options = {"read_fnid": 0x70, "write_fnid": 0x80} + choices_universe = hidpp20_constants.ParamId + sub_items_universe = hidpp20.SUB_PARAM # item (NamedInt) -> list/tuple of objects that have the following attributes # .id (sub-item text), .length (in bytes), .minimum and .maximum _labels = _GESTURE2_PARAMS_LABELS _labels_sub = _GESTURE2_PARAMS_LABELS_SUB - class validator_class(_MultipleRangeV): - + class validator_class(settings_validator.MultipleRangeValidator): @classmethod def build(cls, setting_class, device): params = _hidpp20.get_gestures(device).params.values() @@ -1039,29 +1417,30 @@ class Gesture2Params(_LongSettings): return cls(items, sub_items) -class MKeyLEDs(_BitFieldSetting): - name = 'm-key-leds' - label = _('M-Key LEDs') +class MKeyLEDs(settings.BitFieldSetting): + name = "m-key-leds" + label = _("M-Key LEDs") description = ( - _('Control the M-Key LEDs.') + '\n' + _('May need Onboard Profiles set to Disable to be effective.') + '\n' + - _('May need G Keys diverted to be effective.') + _("Control the M-Key LEDs.") + + "\n" + + _("May need Onboard Profiles set to Disable to be effective.") + + "\n" + + _("May need G Keys diverted to be effective.") ) feature = _F.MKEYS - choices_universe = _NamedInts() + choices_universe = common.NamedInts() for i in range(8): - choices_universe[1 << i] = 'M' + str(i + 1) - _labels = {k: (None, _('Lights up the %s key.') % k) for k in choices_universe} - - class rw_class(_FeatureRW): + choices_universe[1 << i] = "M" + str(i + 1) + _labels = {k: (None, _("Lights up the %s key.") % k) for k in choices_universe} + class rw_class(settings.FeatureRW): def __init__(self, feature): super().__init__(feature, write_fnid=0x10) def read(self, device): # no way to read, so just assume off - return b'\x00' - - class validator_class(_BitFieldV): + return b"\x00" + class validator_class(settings_validator.BitFieldValidator): @classmethod def build(cls, setting_class, device): number = device.feature_request(setting_class.feature, 0x00)[0] @@ -1069,56 +1448,57 @@ class MKeyLEDs(_BitFieldSetting): return cls(options) if options else None -class MRKeyLED(_Setting): - name = 'mr-key-led' - label = _('MR-Key LED') +class MRKeyLED(settings.Setting): + name = "mr-key-led" + label = _("MR-Key LED") description = ( - _('Control the MR-Key LED.') + '\n' + _('May need Onboard Profiles set to Disable to be effective.') + '\n' + - _('May need G Keys diverted to be effective.') + _("Control the MR-Key LED.") + + "\n" + + _("May need Onboard Profiles set to Disable to be effective.") + + "\n" + + _("May need G Keys diverted to be effective.") ) feature = _F.MR - class rw_class(_FeatureRW): - + class rw_class(settings.FeatureRW): def __init__(self, feature): super().__init__(feature, write_fnid=0x00) def read(self, device): # no way to read, so just assume off - return b'\x00' + return b"\x00" ## Only implemented for devices that can produce Key and Consumer Codes (e.g., Craft) ## and devices that can produce Key, Mouse, and Horizontal Scroll (e.g., M720) ## Only interested in current host, so use 0xFF for it -class PersistentRemappableAction(_Settings): - name = 'persistent-remappable-keys' - label = _('Persistent Key/Button Mapping') +class PersistentRemappableAction(settings.Settings): + name = "persistent-remappable-keys" + label = _("Persistent Key/Button Mapping") description = ( - _('Permanently change the mapping for the key or button.') + '\n' + - _('Changing important keys or buttons (such as for the left mouse button) can result in an unusable system.') + _("Permanently change the mapping for the key or button.") + + "\n" + + _("Changing important keys or buttons (such as for the left mouse button) can result in an unusable system.") ) persist = False # This setting is persistent in the device so no need to persist it here feature = _F.PERSISTENT_REMAPPABLE_ACTION - keys_universe = _special_keys.CONTROL - choices_universe = _special_keys.KEYS + keys_universe = special_keys.CONTROL + choices_universe = special_keys.KEYS class rw_class: - def __init__(self, feature): self.feature = feature - self.kind = _FeatureRW.kind + self.kind = settings.FeatureRW.kind def read(self, device, key): ks = device.remap_keys[device.remap_keys.index(key)] - return b'\x00\x00' + ks.data_bytes + return b"\x00\x00" + ks.data_bytes def write(self, device, key, data_bytes): ks = device.remap_keys[device.remap_keys.index(key)] v = ks.remap(data_bytes) return v - class validator_class(_ChoicesMapV): - + class validator_class(settings_validator.ChoicesMapValidator): @classmethod def build(cls, setting_class, device): remap_keys = device.remap_keys @@ -1126,85 +1506,727 @@ class PersistentRemappableAction(_Settings): return None capabilities = device.remap_keys.capabilities if capabilities & 0x0041 == 0x0041: # Key and Consumer Codes - keys = _special_keys.KEYS_KEYS_CONSUMER + keys = special_keys.KEYS_KEYS_CONSUMER elif capabilities & 0x0023 == 0x0023: # Key, Mouse, and HScroll Codes - keys = _special_keys.KEYS_KEYS_MOUSE_HSCROLL + keys = special_keys.KEYS_KEYS_MOUSE_HSCROLL else: - if _log.isEnabledFor(_WARN): - _log.warn('%s: unimplemented Persistent Remappable capability %s', device.name, hex(capabilities)) + if logger.isEnabledFor(logging.WARNING): + logger.warning("%s: unimplemented Persistent Remappable capability %s", device.name, hex(capabilities)) return None choices = {} for k in remap_keys: if k is not None: - key = _special_keys.CONTROL[k.key] - choices[key] = keys # TO RECOVER FROM BAD VALUES use _special_keys.KEYS + key = special_keys.CONTROL[k.key] + choices[key] = keys # TO RECOVER FROM BAD VALUES use special_keys.KEYS return cls(choices, key_byte_count=2, byte_count=4) if choices else None def validate_read(self, reply_bytes, key): start = self._key_byte_count + self._read_skip_byte_count end = start + self._byte_count - reply_value = _bytes2int(reply_bytes[start:end]) & self.mask + reply_value = common.bytes2int(reply_bytes[start:end]) & self.mask # Craft keyboard has a value that isn't valid so fudge these values if reply_value not in self.choices[key]: - if _log.isEnabledFor(_WARN): - _log.warn('unusual persistent remappable action mapping %x: use Default', reply_value) - reply_value = _special_keys.KEYS_Default + if logger.isEnabledFor(logging.WARNING): + logger.warning("unusual persistent remappable action mapping %x: use Default", reply_value) + reply_value = special_keys.KEYS_Default return reply_value -class Sidetone(_Setting): - name = 'sidetone' - label = _('Sidetone') - description = _('Set sidetone level.') +class Sidetone(settings.Setting): + name = "sidetone" + label = _("Sidetone") + description = _("Set sidetone level.") feature = _F.SIDETONE - validator_class = _RangeV + validator_class = settings_validator.RangeValidator min_value = 0 max_value = 100 -class Equalizer(_RangeFieldSetting): - name = 'equalizer' - label = _('Equalizer') - description = _('Set equalizer levels.') +class Equalizer(settings.RangeFieldSetting): + name = "equalizer" + label = _("Equalizer") + description = _("Set equalizer levels.") feature = _F.EQUALIZER - rw_options = {'read_fnid': 0x20, 'write_fnid': 0x30, 'read_prefix': b'\x00'} + rw_options = {"read_fnid": 0x20, "write_fnid": 0x30, "read_prefix": b"\x00"} keys_universe = [] - class validator_class(_PackedRangeV): - + class validator_class(settings_validator.PackedRangeValidator): @classmethod def build(cls, setting_class, device): data = device.feature_request(_F.EQUALIZER, 0x00) if not data: return None - count, dbRange, _x, dbMin, dbMax = _unpack('!BBBBB', data[:5]) + count, dbRange, _x, dbMin, dbMax = struct.unpack("!BBBBB", data[:5]) if dbMin == 0: dbMin = -dbRange if dbMax == 0: dbMax = dbRange - map = _NamedInts() + map = common.NamedInts() for g in range((count + 6) // 7): freqs = device.feature_request(_F.EQUALIZER, 0x10, g * 7) for b in range(7): if g * 7 + b >= count: break - map[g * 7 + b] = str(int.from_bytes(freqs[2 * b + 1:2 * b + 3], 'big')) + _('Hz') - return cls(map, min_value=dbMin, max_value=dbMax, count=count, write_prefix_bytes=b'\x02') + map[g * 7 + b] = str(int.from_bytes(freqs[2 * b + 1 : 2 * b + 3], "big")) + _("Hz") + return cls(map, min_value=dbMin, max_value=dbMax, count=count, write_prefix_bytes=b"\x02") -class ADCPower(_Setting): - name = 'adc_power_management' - label = _('Power Management') - description = _('Power off in minutes (0 for never).') +class ADCPower(settings.Setting): + name = "adc_power_management" + label = _("Power Management") + description = _("Power off in minutes (0 for never).") feature = _F.ADC_MEASUREMENT - rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} - validator_class = _RangeV + min_version = 2 # documentation for version 1 does not mention this capability + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_class = settings_validator.RangeValidator min_value = 0x00 - max_value = 0xff - validator_options = {'byte_count': 1} + max_value = 0xFF + validator_options = {"byte_count": 1} -SETTINGS = [ +class HeadsetEcoMode(settings.Setting): + name = "headset-eco-mode" + label = _("Eco Mode") + description = _("Battery saver mode.") + feature = _F.HEADSET_BATTERY_SAVER + validator_class = settings_validator.BooleanValidator + + +class HeadsetDoNotDisturb(settings.Setting): + name = "headset-do-not-disturb" + label = _("Do Not Disturb") + description = _("Suppress notification sounds.") + feature = _F.HEADSET_DO_NOT_DISTURB + validator_class = settings_validator.BooleanValidator + + +class HeadsetMicMute(settings.Setting): + name = "headset-mic-mute" + label = _("Mic Mute") + description = _("Mute the microphone.") + feature = _F.HEADSET_MIC_MUTE + validator_class = settings_validator.BooleanValidator + + +class HeadsetMicSNR(settings.Setting): + name = "headset-mic-snr" + label = _("Mic SNR") + description = _("Microphone signal-to-noise ratio enhancement.") + feature = _F.HEADSET_MIC_SNR + validator_class = settings_validator.BooleanValidator + + +class HeadsetAINR(settings.Setting): + name = "headset-ai-nr" + label = _("AI Noise Reduction") + description = _("Enable AI noise reduction.") + feature = _F.HEADSET_AI_NOISE_REDUCTION + validator_class = settings_validator.BooleanValidator + + +class HeadsetAINRLevel(settings.Setting): + name = "headset-ai-nr-level" + label = _("AI Noise Reduction Level") + description = _("AI noise reduction intensity.") + feature = _F.HEADSET_AI_NOISE_REDUCTION + rw_options = {"read_fnid": 0x20, "write_fnid": 0x30} + validator_class = settings_validator.ChoicesValidator + choices_universe = common.NamedInts(Off=0, Low=1, Medium=2, High=3) + + +class HeadsetSidetone(settings.Setting): + name = "headset-sidetone" + label = _("Headset Sidetone") + description = _("Sidetone level (0 = off, 100 = max).") + feature = _F.HEADSET_AUDIO_SIDETONE + rw_options = {"read_fnid": 0x00, "write_fnid": 0x10} + validator_class = settings_validator.RangeValidator + min_value = 0 + max_value = 100 + + @classmethod + def build(cls, device): + # Version <= 1: GetSidetone returns [mic_count, mic_id, level]; SetSidetone takes [mic_id, level] + # Version > 1: GetSidetone returns [mic_count, mic_id, reserved, level]; SetSidetone takes [mic_id, 0xFF, level] + version = device.features.get_feature_version(cls.feature) or 0 + if version > 1: + skip, prefix = 3, b"\x01\xff" + else: + skip, prefix = 2, b"\x01" + rw = settings.FeatureRW(cls.feature, **cls.rw_options) + validator = cls.validator_class.build(cls, device, read_skip_byte_count=skip, write_prefix_bytes=prefix) + if validator: + return cls(device, rw, validator) + + +class HeadsetMicGain(settings.Setting): + name = "headset-mic-gain" + label = _("Mic Gain") + description = _("Microphone gain level.") + feature = _F.HEADSET_MIC_GAIN + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_class = settings_validator.RangeValidator + min_value = -128 + max_value = 127 + validator_options = {"byte_count": 1, "signed": True} + + +class HeadsetMixBalance(settings.Setting): + name = "headset-mix-balance" + label = _("Audio Mix Balance") + description = _("Balance between game and chat audio.") + feature = _F.HEADSET_MIX + validator_class = settings_validator.RangeValidator + min_value = 0 + max_value = 255 + validator_options = {"byte_count": 1} + + +class HeadsetAutoSleep(settings.Setting): + name = "headset-auto-sleep" + label = _("Auto Sleep Timeout") + description = _("Idle time in minutes before the headset enters sleep mode (0 = disabled).") + feature = _F.CENTURION_AUTO_SLEEP + rw_options = {"read_fnid": 0x00, "write_fnid": 0x10} + validator_class = settings_validator.RangeValidator + min_value = 0 + max_value = 255 + validator_options = {"byte_count": 1} + + +class HeadsetOnboardEQ(settings.RangeFieldSetting): + name = "headset-onboard-eq" + label = _("Headset Equalizer") + description = _("Set equalizer levels.") + feature = _F.HEADSET_ONBOARD_EQ + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20, "read_prefix": b"\x00"} + keys_universe = [] + + class validator_class(settings_validator.PackedRangeValidator): + kind = settings.Kind.GRAPHIC_EQ + + @classmethod + def build(cls, setting_class, device): + info = hidpp20.get_onboard_eq_info(device) + if not info: + return None + _has_hw_eq, num_bands = info + bands = hidpp20.get_onboard_eq_params(device, slot=0x00) + if not bands or len(bands) != num_bands: + return None + keys = common.NamedInts() + for i, (freq, _gain, _q) in enumerate(bands): + keys[i] = str(freq) + _("Hz") + v = cls(keys, min_value=-12, max_value=12, count=num_bands, byte_count=1) + v._band_freqs = [freq for freq, _g, _q in bands] + v._band_qs = [q for _f, _g, q in bands] + return v + + def validate_read(self, reply_bytes): + if reply_bytes is None or len(reply_bytes) < 2: + return {} + band_count = reply_bytes[1] + result = {} + offset = 2 + for i in range(band_count): + if offset + 4 > len(reply_bytes): + break + freq = struct.unpack(">H", reply_bytes[offset : offset + 2])[0] + gain = struct.unpack("b", bytes([reply_bytes[offset + 2]]))[0] + q = reply_bytes[offset + 3] + result[i] = gain + # Update stored freq/Q arrays if they exist + if hasattr(self, "_band_freqs") and i < len(self._band_freqs): + self._band_freqs[i] = freq + if hasattr(self, "_band_qs") and i < len(self._band_qs): + self._band_qs[i] = q + offset += 4 + return result + + def prepare_write(self, new_values): + if not hasattr(self, "_band_freqs") or not hasattr(self, "_band_qs"): + return None + bands = [] + for i in range(self.count): + freq = self._band_freqs[i] if i < len(self._band_freqs) else 1000 + q = self._band_qs[i] if i < len(self._band_qs) else 10 + gain = new_values.get(i, 0) + bands.append((freq, gain, q)) + self._pending_bands = bands # stash for persist step + return hidpp20._build_set_eq_payload(0x00, bands) + + def write(self, map, save=True): + result = super().write(map, save) + # Also persist to device flash (slot 0x80) so EQ survives power cycle + if result is not None and hasattr(self._validator, "_pending_bands"): + bands = self._validator._pending_bands + del self._validator._pending_bands + try: + self._device.feature_request(_F.HEADSET_ONBOARD_EQ, 0x20, hidpp20._build_set_eq_payload(0x80, bands)) + except Exception: + logger.warning("HeadsetOnboardEQ: failed to persist EQ to slot 0x80") + return result + + +class BrightnessControl(settings.Setting): + name = "brightness_control" + label = _("Brightness Control") + description = _("Control overall brightness") + feature = _F.BRIGHTNESS_CONTROL + rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} + validator_class = settings_validator.RangeValidator + + def __init__(self, device, rw, validator): + super().__init__(device, rw, validator) + rw.on_off = validator.on_off + rw.min_nonzero_value = validator.min_value + validator.min_value = 0 if validator.on_off else validator.min_value + + class rw_class(settings.FeatureRW): + def read(self, device, data_bytes=b""): + if self.on_off: + reply = device.feature_request(self.feature, 0x30) + if not reply[0] & 0x01: + return b"\x00\x00" + return super().read(device, data_bytes) + + def write(self, device, data_bytes): + if self.on_off: + off = int.from_bytes(data_bytes, byteorder="big") < self.min_nonzero_value + reply = device.feature_request(self.feature, 0x40, b"\x00" if off else b"\x01", no_reply=False) + if off: + return reply + return super().write(device, data_bytes) + + class validator_class(settings_validator.RangeValidator): + @classmethod + def build(cls, setting_class, device): + reply = device.feature_request(_F.BRIGHTNESS_CONTROL) + assert reply, "Oops, brightness range cannot be retrieved!" + if reply: + max_value = int.from_bytes(reply[0:2], byteorder="big") + min_value = int.from_bytes(reply[4:6], byteorder="big") + on_off = bool(reply[3] & 0x04) # separate on/off control + validator = cls(min_value=min_value, max_value=max_value, byte_count=2) + validator.on_off = on_off + return validator + + +class LEDControl(settings.Setting): + name = "led_control" + label = _("LED Control") + description = _("Switch control of LED zones between device and Solaar") + feature = _F.COLOR_LED_EFFECTS + rw_options = {"read_fnid": 0x70, "write_fnid": 0x80} + choices_universe = common.NamedInts(Device=0, Solaar=1) + validator_class = settings_validator.ChoicesValidator + validator_options = {"choices": choices_universe} + + +colors = special_keys.COLORS +_LEDP = hidpp20.LEDParam + + +# an LED Zone has an index, a set of possible LED effects, and an LED effect setting +class LEDZoneSetting(settings.Setting): + name = "led_zone_" # the trailing underscore signals that this setting creates other settings + label = _("LED Zone Effects") + description = _("Set effect for LED Zone") + "\n" + _("LED Control needs to be set to Solaar to be effective.") + feature = _F.COLOR_LED_EFFECTS + color_field = {"name": _LEDP.color, "kind": settings.Kind.COLOR, "label": _("Color")} + speed_field = {"name": _LEDP.speed, "kind": settings.Kind.RANGE, "label": _("Speed"), "min": 0, "max": 255} + period_field = {"name": _LEDP.period, "kind": settings.Kind.RANGE, "label": _("Period"), "min": 100, "max": 5000} + intensity_field = {"name": _LEDP.intensity, "kind": settings.Kind.RANGE, "label": _("Intensity"), "min": 0, "max": 100} + ramp_field = {"name": _LEDP.ramp, "kind": settings.Kind.CHOICE, "label": _("Ramp"), "choices": hidpp20.LedRampChoice} + possible_fields = [color_field, speed_field, period_field, intensity_field, ramp_field] + + @classmethod + def setup(cls, device, read_fnid, write_fnid, suffix): + infos = device.led_effects + settings_ = [] + for zone in infos.zones: + prefix = common.int2bytes(zone.index, 1) + rw = settings.FeatureRW(cls.feature, read_fnid, write_fnid, prefix=prefix, suffix=suffix) + validator = settings_validator.HeteroValidator( + data_class=hidpp20.LEDEffectSetting, options=zone.effects, readable=infos.readable and read_fnid is not None + ) + setting = cls(device, rw, validator) + setting.name = cls.name + str(int(zone.location)) + setting.label = _("LEDs") + " " + str(hidpp20.LEDZoneLocations[zone.location]) + choices = [hidpp20.LEDEffects[e.ID][0] for e in zone.effects if e.ID in hidpp20.LEDEffects] + ID_field = {"name": "ID", "kind": settings.Kind.CHOICE, "label": None, "choices": choices} + setting.possible_fields = [ID_field] + cls.possible_fields + setting.fields_map = hidpp20.LEDEffects + settings_.append(setting) + return settings_ + + @classmethod + def build(cls, device): + return cls.setup(device, 0xE0, 0x30, b"") + + +class RGBControl(settings.Setting): + name = "rgb_control" + label = _("LED Control") + description = _("Switch control of LED zones between device and Solaar") + feature = _F.RGB_EFFECTS + rw_options = {"read_fnid": 0x50, "write_fnid": 0x50} + choices_universe = common.NamedInts(Device=0, Solaar=1) + validator_class = settings_validator.ChoicesValidator + validator_options = {"choices": choices_universe, "write_prefix_bytes": b"\x01", "read_skip_byte_count": 1} + + +class RGBEffectSetting(LEDZoneSetting): + name = "rgb_zone_" # the trailing underscore signals that this setting creates other settings + label = _("LED Zone Effects") + description = _("Set effect for LED Zone") + "\n" + _("LED Control needs to be set to Solaar to be effective.") + feature = _F.RGB_EFFECTS + + @classmethod + def build(cls, device): + return cls.setup(device, None, 0x10, b"\x01") + + +class PerKeyLighting(settings.Settings): + name = "per-key-lighting" + label = _("Per-key Lighting") + description = _("Control per-key lighting.") + feature = _F.PER_KEY_LIGHTING_V2 + keys_universe = special_keys.KEYCODES + editor_class = "solaar.ui.perkey.control:PerKeyControl" + + def read(self, cached=True): + self._pre_read(cached) + if cached and self._value is not None: + return self._value + reply_map = {} + for key in self._validator.choices: + reply_map[int(key)] = special_keys.COLORSPLUS["No change"] # this signals no change + self._value = reply_map + return reply_map + + def write(self, map, save=True): + if self._device.online: + self.update(map, save) + table = {} + for key, value in map.items(): + if value in table: + table[value].append(key) # keys will be in order from small to large + else: + table[value] = [key] + if len(table) == 1: # use range update + for value, keys in table.items(): # only one, of course + if value != special_keys.COLORSPLUS["No change"]: # this signals no change, so don't update at all + data_bytes = keys[0].to_bytes(1, "big") + keys[-1].to_bytes(1, "big") + value.to_bytes(3, "big") + self._device.feature_request(self.feature, 0x50, data_bytes) # range update command to update all keys + self._device.feature_request(self.feature, 0x70, 0x00) # signal device to make the changes + else: + data_bytes = b"" + for value, keys in table.items(): # only one, of course + if value != special_keys.COLORSPLUS["No change"]: # this signals no change, so ignore it + while len(keys) > 3: # use an optimized update command that can update up to 13 keys + data = value.to_bytes(3, "big") + b"".join([key.to_bytes(1, "big") for key in keys[0:13]]) + self._device.feature_request(self.feature, 0x60, data) # single-value multiple-keys update + keys = keys[13:] + for key in keys: + data_bytes += key.to_bytes(1, "big") + value.to_bytes(3, "big") + if len(data_bytes) >= 16: # up to four values are packed into a regular update + self._device.feature_request(self.feature, 0x10, data_bytes) + data_bytes = b"" + if len(data_bytes) > 0: # update any remaining keys + self._device.feature_request(self.feature, 0x10, data_bytes) + self._device.feature_request(self.feature, 0x70, 0x00) # signal device to make the changes + return map + + def write_key_value(self, key, value, save=True): + if value != special_keys.COLORSPLUS["No change"]: # this signals no change + result = super().write_key_value(int(key), value, save) + if self._device.online: + self._device.feature_request(self.feature, 0x70, 0x00) # signal device to make the change + return result + else: + return True + + class rw_class(settings.FeatureRWMap): + pass + + class validator_class(settings_validator.MapRangeValidator): + _COLOR_RANGE = settings_validator.Range(min=0, max=0xFFFFFF, byte_count=3) + + @classmethod + def build(cls, setting_class, device): + choices_map = {} + key_bitmap = device.feature_request(setting_class.feature, 0x00, 0x00, 0x00)[2:] + key_bitmap += device.feature_request(setting_class.feature, 0x00, 0x00, 0x01)[2:] + key_bitmap += device.feature_request(setting_class.feature, 0x00, 0x00, 0x02)[2:] + for i in range(1, 255): + if (key_bitmap[i // 8] >> i % 8) & 0x01: + key = ( + setting_class.keys_universe[i] + if i in setting_class.keys_universe + else common.NamedInt(i, f"KEY {str(i)}") + ) + choices_map[key] = cls._COLOR_RANGE + return cls(choices_map) if choices_map else None + + +# Allow changes to force sensing buttons +class ForceSensing(settings_new.Settings): + name = "force-sensing" + label = _("Force Sensing Buttons") + description = _("Change the force required to activate button.") + feature = _F.FORCE_SENSING_BUTTON + setup = "force_buttons" + get = "get_current" + set = "set_current" + acceptable = "acceptable_current_key" + choices_universe = list(range(0, 256)) + kind = settings.Kind.MAP_RANGE + + @classmethod + def build(cls, device): + cls.check_properties(cls) + device_object = getattr(device, cls.setup)() + if device_object: + setting = cls(device, device_object) + if setting and len(device_object) == 1: + ## If there is only one force button a simpler interface can be used + setting.label = _("Force Sensing Button") + setting.acceptable = "acceptable_current" + setting.min_value = device_object[0].min_value + setting.max_value = device_object[0].max_value + setting.kind = settings.Kind.RANGE + return setting + + +# Analog button tuning settings (actuation point, rapid trigger, haptics) +# +# Bytes 1 (actuation), 2 (rapid trigger), 3 (haptics) of the 0x1B0C config struct +# pack a logical value in bits 7..2 (i.e. wire = logical << 2). Byte 2 bit 0 is a +# firmware-managed sensitivityFlag that must be preserved across writes; all other +# low-order bits are reserved and must be zero. Sending a wire byte that doesn't +# match this layout produces INVALID_ARGUMENT — see issue #3202. + + +class _AnalogButtonActuationRW(settings.FeatureRW): + """RW for analog button actuation point per button.""" + + def __init__(self, feature, button_index): + super().__init__(feature, read_fnid=0x20, write_fnid=0x10) + self.button_index = button_index + + def read(self, device, data_bytes=b""): + res = device.feature_request(self.feature, 0x20, self.button_index) + if not res: + return b"\x05" # default mid-point (logical) + return bytes([res[1] >> 2]) + + def write(self, device, data_bytes): + current = device.feature_request(self.feature, 0x20, self.button_index) + if not current: + return None + wire_act = (data_bytes[0] & 0x3F) << 2 + return device.feature_request(self.feature, 0x10, self.button_index, wire_act, current[2], current[3]) + + +class _AnalogButtonRapidTriggerRW(settings.FeatureRW): + """RW for analog button rapid trigger sensitivity per button.""" + + def __init__(self, feature, button_index): + super().__init__(feature, read_fnid=0x20, write_fnid=0x10) + self.button_index = button_index + + def read(self, device, data_bytes=b""): + res = device.feature_request(self.feature, 0x20, self.button_index) + if not res: + return b"\x03" # default mid-point (logical) + return bytes([res[2] >> 2]) + + def write(self, device, data_bytes): + current = device.feature_request(self.feature, 0x20, self.button_index) + if not current: + return None + # Preserve the firmware-managed sensitivityFlag (byte 2 bit 0). + wire_rt = ((data_bytes[0] & 0x3F) << 2) | (current[2] & 0x01) + return device.feature_request(self.feature, 0x10, self.button_index, current[1], wire_rt, current[3]) + + +class _AnalogButtonHapticsRW(settings.FeatureRW): + """RW for analog button click haptics per button.""" + + def __init__(self, feature, button_index): + super().__init__(feature, read_fnid=0x20, write_fnid=0x10) + self.button_index = button_index + + def read(self, device, data_bytes=b""): + res = device.feature_request(self.feature, 0x20, self.button_index) + if not res: + return b"\x03" # default mid-point (logical) + return bytes([res[3] >> 2]) + + def write(self, device, data_bytes): + current = device.feature_request(self.feature, 0x20, self.button_index) + if not current: + return None + wire_haptics = (data_bytes[0] & 0x3F) << 2 + return device.feature_request(self.feature, 0x10, self.button_index, current[1], current[2], wire_haptics) + + +class _AnalogButtonSetting(settings.Setting): + """Setting subclass that migrates legacy raw-byte persisted values. + + Solaar 1.1.19 stored the wire byte (logical value × 4) under these names. After + the encoding fix, the same key holds the logical value. If a stored value is + above the new max but a divide-by-4 lands inside the valid range, treat it as + legacy raw and migrate in place — this prevents apply() from raising + INVALID_ARGUMENT/ValueError on first run after upgrade. + """ + + def _pre_read(self, cached, key=None): + super()._pre_read(cached, key) + if self._value is None or not isinstance(self._value, int): + return + validator = self._validator + if self._value > validator.max_value and (self._value & 0x03) == 0: + migrated = self._value >> 2 + if validator.min_value <= migrated <= validator.max_value: + if logger.isEnabledFor(logging.INFO): + logger.info( + "%s: migrating legacy raw value %d to logical %d on %s", + self.name, + self._value, + migrated, + self._device, + ) + self._value = migrated + if getattr(self._device, "persister", None) is not None: + self._device.persister[self.name] = self._value + + +class AnalogButtonTuning(settings.Setting): + """Analog button tuning: actuation point, rapid trigger, and haptics configuration.""" + + name = "analog-button-tuning" + label = _("Analog Button Tuning") + description = _("Configure analog button settings including actuation point, rapid trigger, and haptics.") + feature = _F.ANALOG_BUTTONS + + @classmethod + def build(cls, device): + if cls.feature not in device.features: + return None + # Capabilities: [flags, button_count, max_act<<2, max_rt<<2, max_haptics<<2, ...] + caps = device.feature_request(cls.feature, 0x00) + if not caps or len(caps) < 5: + return None + button_count = min(caps[1], 2) # firmware reports 3; only L/R are user-accessible + max_actuation = (caps[2] >> 2) if caps[2] > 0 else 10 + max_rt_level = (caps[3] >> 2) if caps[3] > 0 else 5 + max_haptics = (caps[4] >> 2) if caps[4] > 0 else 5 + + if button_count == 0: + return None + + button_names = [_("Left Button"), _("Right Button")] + all_settings = [] + + for i in range(button_count): + btn_name = button_names[i] if i < len(button_names) else f"Button {i}" + + rw_act = _AnalogButtonActuationRW(cls.feature, i) + val_act = settings_validator.RangeValidator(min_value=1, max_value=max_actuation) + s_act = _AnalogButtonSetting(device, rw_act, val_act) + s_act.name = f"analog-button-tuning_actuation-{i}" + s_act.label = f"{btn_name} Actuation Point" + s_act.description = _("Actuation point depth (1=shallow, %d=deep).") % max_actuation + all_settings.append(s_act) + + rw_rt = _AnalogButtonRapidTriggerRW(cls.feature, i) + val_rt = settings_validator.RangeValidator(min_value=1, max_value=max_rt_level) + s_rt = _AnalogButtonSetting(device, rw_rt, val_rt) + s_rt.name = f"analog-button-tuning_rapid-trigger-{i}" + s_rt.label = f"{btn_name} Rapid Trigger" + s_rt.description = _("Rapid trigger sensitivity (1..%d).") % max_rt_level + all_settings.append(s_rt) + + rw_haptics = _AnalogButtonHapticsRW(cls.feature, i) + val_haptics = settings_validator.RangeValidator(min_value=0, max_value=max_haptics) + s_haptics = _AnalogButtonSetting(device, rw_haptics, val_haptics) + s_haptics.name = f"analog-button-tuning_haptics-{i}" + s_haptics.label = f"{btn_name} Click Haptics" + s_haptics.description = _("Click haptic feedback intensity (0=off, %d=max).") % max_haptics + all_settings.append(s_haptics) + + return all_settings if all_settings else None + + +class HapticLevel(settings.Setting): + name = "haptic-level" + label = _("Haptic Feedback Level") + description = _("Change power of haptic feedback. (Zero to turn off.)") + feature = _F.HAPTIC + choices_universe = common.NamedInts(Off=0, Low=25, Medium=50, High=75, Maximum=100) + min_value = 0 + max_value = 100 + + class rw_class(settings.FeatureRW): + def __init__(self, feature): + super().__init__(feature, read_fnid=0x10, write_fnid=0x20) + + def read(self, device, data_bytes=b""): + result = device.feature_request(self.feature, 0x10) + if result[0] & 0x01 == 0: # disabled, return 0 + return b"\x00" + else: # enabled, return second byte + return result[1:2] + + def write(self, device, data_bytes): + if data_bytes == b"\x00": + write_bytes = b"\x00\x32" # disable, at 50 percent + else: + write_bytes = b"\x01" + data_bytes + reply = device.feature_request(self.feature, 0x20, write_bytes) + return reply + + @classmethod + def build(cls, device): + response = device.feature_request(cls.feature, 0x10) + if response: + rw = cls.rw_class(cls.feature) + levels = response[2] & 0x01 + if levels: # device only has four levels + validator = settings_validator.ChoicesValidator(choices=cls.choices_universe) + else: # device has all levels + validator = settings_validator.RangeValidator(min_value=cls.min_value, max_value=cls.max_value) + return cls(device, rw, validator) + + +# This setting is not displayed in the UI +# Use `solaar config haptic-play
` to play a haptic form +class PlayHapticWaveForm(settings.Setting): + name = "haptic-play" + label = _("Play Haptic Waveform") + description = _("Tell device to play a haptic waveform.") + feature = _F.HAPTIC + choices_universe = hidpp20_constants.HapticWaveForms + rw_options = {"read_fnid": None, "write_fnid": 0x40} # nothing to read + persist = False # persisting this setting is useless + display = False # don't display in UI, interact using `solaar config ...` + + class validator_class(settings_validator.ChoicesValidator): + @classmethod + def build(cls, setting_class, device): + response = device.feature_request(_F.HAPTIC, 0x00) + if response: + waves = common.NamedInts() + waveforms = int.from_bytes(response[4:8]) + for waveform in hidpp20_constants.HapticWaveForms: + if (1 << int(waveform)) & waveforms: + waves[int(waveform)] = str(waveform) + return cls(choices=waves, byte_count=1) + + +SETTINGS: list[settings.Setting] = [ RegisterHandDetection, # simple RegisterSmoothScroll, # simple RegisterSideScroll, # simple @@ -1216,18 +2238,32 @@ SETTINGS = [ HiresSmoothResolution, # working HiresMode, # simple ScrollRatchet, # simple + ScrollRatchetTorque, SmartShift, # working + ScrollRatchetEnhanced, SmartShiftEnhanced, # simple ThumbInvert, # working ThumbMode, # working OnboardProfiles, ReportRate, # working + ExtendedReportRate, PointerSpeed, # simple AdjustableDpi, # working + ExtendedAdjustableDpi, SpeedChange, # Backlight, # not working - disabled temporarily Backlight2, # working + Backlight2Level, + Backlight2DurationHandsOut, + Backlight2DurationHandsIn, + Backlight2DurationPowered, Backlight3, + LEDControl, + LEDZoneSetting, + RGBControl, + RGBEffectSetting, + BrightnessControl, + PerKeyLighting, FnSwap, # simple NewFnSwap, # simple K375sFnSwap, # working @@ -1235,6 +2271,7 @@ SETTINGS = [ PersistentRemappableAction, DivertKeys, # working DisableKeyboardKeys, # working + ForceSensing, CrownSmooth, # working DivertCrown, # working DivertGkeys, # working @@ -1246,60 +2283,198 @@ SETTINGS = [ Gesture2Gestures, # working Gesture2Divert, Gesture2Params, # working + AnalogButtonTuning, + HapticLevel, + PlayHapticWaveForm, Sidetone, Equalizer, ADCPower, + HeadsetEcoMode, + HeadsetDoNotDisturb, + HeadsetMicMute, + HeadsetMicSNR, + HeadsetAINR, + HeadsetAINRLevel, + HeadsetSidetone, + HeadsetMicGain, + HeadsetMixBalance, + HeadsetAutoSleep, + HeadsetOnboardEQ, ] -# -# -# + +class SettingsProtocol(Protocol): + @property + def name(self): + ... + + @property + def label(self): + ... + + @property + def description(self): + ... + + @property + def feature(self): + ... + + @property + def register(self): + ... + + @property + def kind(self): + ... + + @property + def min_version(self): + ... + + @property + def persist(self): + ... + + @property + def rw_options(self): + ... + + @property + def validator_class(self): + ... + + @property + def validator_options(self): + ... + + @classmethod + def build(cls, device): + ... + + def val_to_string(self, value): + ... + + @property + def choices(self): + ... + + @property + def range(self): + ... + + def _pre_read(self, cached, key=None): + ... + + def read(self, cached=True): + ... + + def _pre_write(self, save=True): + ... + + def update(self, value, save=True): + ... + + def write(self, value, save=True): + ... + + def acceptable(self, args, current): + ... + + def compare(self, args, current): + ... + + def apply(self): + ... + + def __str__(self): + ... -def check_feature(device, sclass): - if sclass.feature not in device.features: +def check_feature(device, settings_class: SettingsProtocol) -> None | bool | SettingsProtocol: + if settings_class.feature not in device.features: + return + if settings_class.min_version > device.features.get_feature_version(settings_class.feature): + return + if device.features.get_hidden(settings_class.feature): return try: - detected = sclass.build(device) - if _log.isEnabledFor(_DEBUG): - _log.debug('check_feature %s [%s] detected %s', sclass.name, sclass.feature, detected) + detected = settings_class.build(device) + if logger.isEnabledFor(logging.DEBUG): + logger.debug("check_feature %s [%s] detected", settings_class.name, settings_class.feature) return detected except Exception as e: - from traceback import format_exc - _log.error('check_feature %s [%s] error %s\n%s', sclass.name, sclass.feature, e, format_exc()) - return False # differentiate from an error-free determination that the setting is not supported + logger.error( + "check_feature %s [%s] error %s\n%s", settings_class.name, settings_class.feature, e, traceback.format_exc() + ) + raise e # differentiate from an error-free determination that the setting is not supported -# Returns True if device was queried to find features, False otherwise -def check_feature_settings(device, already_known): - """Auto-detect device settings by the HID++ 2.0 features they have.""" +def check_feature_settings(device, already_known) -> bool: + """Auto-detect device settings by the HID++ 2.0 features they have. + + Returns + ------- + bool + True, if device was fully queried to find features, False otherwise. + """ if not device.features or not device.online: return False if device.protocol and device.protocol < 2.0: return False - absent = device.persister.get('_absent', []) if device.persister else [] - newAbsent = [] + absent = device.persister.get("_absent", []) if device.persister else [] + new_absent = [] for sclass in SETTINGS: if sclass.feature: known_present = device.persister and sclass.name in device.persister if not any(s.name == sclass.name for s in already_known) and (known_present or sclass.name not in absent): - setting = check_feature(device, sclass) - if setting: + try: + setting = check_feature(device, sclass) + except Exception as err: + # on an internal HID++ error, assume offline and stop further checking + if ( + isinstance(err, exceptions.FeatureCallError) + and err.error == hidpp20_constants.ErrorCode.LOGITECH_ERROR + ): + logger.warning(f"HID++ internal error checking feature {sclass.name}: make device not present") + device.online = False + device.present = False + return False + else: + logger.warning(f"ignore feature {sclass.name} because of error {err}") + + if isinstance(setting, list): + for s in setting: + already_known.append(s) + if sclass.name in new_absent: + new_absent.remove(sclass.name) + elif setting: already_known.append(setting) - if sclass.name in newAbsent: - newAbsent.remove(sclass.name) + if sclass.name in new_absent: + new_absent.remove(sclass.name) elif setting is None: - if sclass.name not in newAbsent and sclass.name not in absent and sclass.name not in device.persister: - newAbsent.append(sclass.name) - if device.persister and newAbsent: - absent.extend(newAbsent) - device.persister['_absent'] = absent + if sclass.name not in new_absent and sclass.name not in absent and sclass.name not in device.persister: + new_absent.append(sclass.name) + if device.persister and new_absent: + absent.extend(new_absent) + device.persister["_absent"] = absent return True -def check_feature_setting(device, setting_name): +def check_feature_setting(device, setting_name: str) -> settings.Setting | None: for sclass in SETTINGS: - if sclass.feature and sclass.name == setting_name and device.features: - setting = check_feature(device, sclass) - if setting: + if ( + sclass.feature + and device.features + and (sclass.name == setting_name or sclass.name.endswith("_") and setting_name.startswith(sclass.name)) + ): + try: + setting = check_feature(device, sclass) + except Exception: + return None + if isinstance(setting, list): + for s in setting: + if s.name == setting_name: + return s + elif setting: return setting diff --git a/lib/logitech_receiver/settings_validator.py b/lib/logitech_receiver/settings_validator.py new file mode 100644 index 00000000..56c2c7a8 --- /dev/null +++ b/lib/logitech_receiver/settings_validator.py @@ -0,0 +1,837 @@ +from __future__ import annotations + +import logging +import math + +from dataclasses import dataclass +from enum import IntEnum + +from logitech_receiver import common +from logitech_receiver.common import NamedInt +from logitech_receiver.common import NamedInts + +logger = logging.getLogger(__name__) + + +def bool_or_toggle(current: bool | str, new: bool | str) -> bool: + if isinstance(new, bool): + return new + + try: + return bool(int(new)) + except (TypeError, ValueError): + new = str(new).lower() + + if new in ("true", "yes", "on", "t", "y"): + return True + if new in ("false", "no", "off", "f", "n"): + return False + if new in ("~", "toggle"): + return not current + return None + + +class Kind(IntEnum): + TOGGLE = 0x01 + CHOICE = 0x02 + RANGE = 0x04 + MAP_CHOICE = 0x0A + MULTIPLE_TOGGLE = 0x10 + PACKED_RANGE = 0x20 + MULTIPLE_RANGE = 0x40 + HETERO = 0x80 + + +class Validator: + @classmethod + def build(cls, setting_class, device, **kwargs) -> Validator: + return cls(**kwargs) + + @classmethod + def to_string(cls, value) -> str: + return str(value) + + def compare(self, args, current): + if len(args) != 1: + return False + return args[0] == current + + +class BooleanValidator(Validator): + __slots__ = ("true_value", "false_value", "read_skip_byte_count", "write_prefix_bytes", "mask", "needs_current_value") + + kind = Kind.TOGGLE + default_true = 0x01 + default_false = 0x00 + # mask specifies all the affected bits in the value + default_mask = 0xFF + + def __init__( + self, + true_value=default_true, + false_value=default_false, + mask=default_mask, + read_skip_byte_count=0, + write_prefix_bytes=b"", + ): + if isinstance(true_value, int): + assert isinstance(false_value, int) + if mask is None: + mask = self.default_mask + else: + assert isinstance(mask, int) + assert true_value & false_value == 0 + assert true_value & mask == true_value + assert false_value & mask == false_value + self.needs_current_value = mask != self.default_mask + elif isinstance(true_value, bytes): + if false_value is None or false_value == self.default_false: + false_value = b"\x00" * len(true_value) + else: + assert isinstance(false_value, bytes) + if mask is None or mask == self.default_mask: + mask = b"\xff" * len(true_value) + else: + assert isinstance(mask, bytes) + assert len(mask) == len(true_value) == len(false_value) + tv = common.bytes2int(true_value) + fv = common.bytes2int(false_value) + mv = common.bytes2int(mask) + assert tv != fv # true and false might be something other than bit values + assert tv & mv == tv + assert fv & mv == fv + self.needs_current_value = any(m != 0xFF for m in mask) + else: + raise Exception(f"invalid mask '{mask!r}', type {type(mask)}") + + self.true_value = true_value + self.false_value = false_value + self.mask = mask + self.read_skip_byte_count = read_skip_byte_count + self.write_prefix_bytes = write_prefix_bytes + + def validate_read(self, reply_bytes): + reply_bytes = reply_bytes[self.read_skip_byte_count :] + if isinstance(self.mask, int): + reply_value = ord(reply_bytes[:1]) & self.mask + if logger.isEnabledFor(logging.DEBUG): + logger.debug("BooleanValidator: validate read %r => %02X", reply_bytes, reply_value) + if reply_value == self.true_value: + return True + if reply_value == self.false_value: + return False + logger.warning( + "BooleanValidator: reply %02X mismatched %02X/%02X/%02X", + reply_value, + self.true_value, + self.false_value, + self.mask, + ) + return False + + count = len(self.mask) + mask = common.bytes2int(self.mask) + reply_value = common.bytes2int(reply_bytes[:count]) & mask + + true_value = common.bytes2int(self.true_value) + if reply_value == true_value: + return True + + false_value = common.bytes2int(self.false_value) + if reply_value == false_value: + return False + + logger.warning( + "BooleanValidator: reply %r mismatched %r/%r/%r", reply_bytes, self.true_value, self.false_value, self.mask + ) + return False + + def prepare_write(self, new_value, current_value=None): + if new_value is None: + new_value = False + else: + assert isinstance(new_value, bool), f"New value {new_value} for boolean setting is not a boolean" + + to_write = self.true_value if new_value else self.false_value + + if isinstance(self.mask, int): + if current_value is not None and self.needs_current_value: + to_write |= ord(current_value[:1]) & (0xFF ^ self.mask) + if current_value is not None and to_write == ord(current_value[:1]): + return None + to_write = bytes([to_write]) + else: + to_write = bytearray(to_write) + count = len(self.mask) + for i in range(0, count): + b = ord(to_write[i : i + 1]) + m = ord(self.mask[i : i + 1]) + assert b & m == b + # b &= m + if current_value is not None and self.needs_current_value: + b |= ord(current_value[i : i + 1]) & (0xFF ^ m) + to_write[i] = b + to_write = bytes(to_write) + + if current_value is not None and to_write == current_value[: len(to_write)]: + return None + + if logger.isEnabledFor(logging.DEBUG): + logger.debug("BooleanValidator: prepare_write(%s, %s) => %r", new_value, current_value, to_write) + + return self.write_prefix_bytes + to_write + + def acceptable(self, args, current): + if len(args) != 1: + return None + val = bool_or_toggle(current, args[0]) + return [val] if val is not None else None + + +class BitFieldValidator(Validator): + __slots__ = ("byte_count", "options") + + kind = Kind.MULTIPLE_TOGGLE + + def __init__(self, options, byte_count=None): + assert isinstance(options, list) + self.options = options + self.byte_count = (max(x.bit_length() for x in options) + 7) // 8 + if byte_count: + assert isinstance(byte_count, int) and byte_count >= self.byte_count + self.byte_count = byte_count + + def to_string(self, value) -> str: + def element_to_string(key, val): + k = next((k for k in self.options if int(key) == k), None) + return str(k) + ":" + str(val) if k is not None else "?" + + return "{" + ", ".join([element_to_string(k, value[k]) for k in value]) + "}" + + def validate_read(self, reply_bytes): + r = common.bytes2int(reply_bytes[: self.byte_count]) + value = {int(k): False for k in self.options} + m = 1 + for _ignore in range(8 * self.byte_count): + if m in self.options: + value[int(m)] = bool(r & m) + m <<= 1 + return value + + def prepare_write(self, new_value): + assert isinstance(new_value, dict) + w = 0 + for k, v in new_value.items(): + if v: + w |= int(k) + return common.int2bytes(w, self.byte_count) + + def get_options(self): + return self.options + + def acceptable(self, args, current): + if len(args) != 2: + return None + key = next((key for key in self.options if key == args[0]), None) + if key is None: + return None + val = bool_or_toggle(current[int(key)], args[1]) + return None if val is None else [int(key), val] + + def compare(self, args, current): + if len(args) != 2: + return False + key = next((key for key in self.options if key == args[0]), None) + if key is None: + return False + return args[1] == current[int(key)] + + +class BitFieldWithOffsetAndMaskValidator(Validator): + __slots__ = ("byte_count", "options", "_option_from_key", "_mask_from_offset", "_option_from_offset_mask") + + kind = Kind.MULTIPLE_TOGGLE + sep = 0x01 + + def __init__(self, options, om_method=None, byte_count=None): + assert isinstance(options, list) + # each element of options is an instance of a class + # that has an id (which is used as an index in other dictionaries) + # and where om_method is a method that returns a byte offset and byte mask + # that says how to access and modify the bit toggle for the option + self.options = options + self.om_method = om_method + # to retrieve the options efficiently: + self._option_from_key = {} + self._mask_from_offset = {} + self._option_from_offset_mask = {} + for opt in options: + offset, mask = om_method(opt) + self._option_from_key[int(opt)] = opt + try: + self._mask_from_offset[offset] |= mask + except KeyError: + self._mask_from_offset[offset] = mask + try: + mask_to_opt = self._option_from_offset_mask[offset] + except KeyError: + mask_to_opt = {} + self._option_from_offset_mask[offset] = mask_to_opt + mask_to_opt[mask] = opt + self.byte_count = (max(om_method(x)[1].bit_length() for x in options) + 7) // 8 # is this correct?? + if byte_count: + assert isinstance(byte_count, int) and byte_count >= self.byte_count + self.byte_count = byte_count + + def prepare_read(self): + r = [] + for offset, mask in self._mask_from_offset.items(): + b = offset << (8 * (self.byte_count + 1)) + b |= (self.sep << (8 * self.byte_count)) | mask + r.append(common.int2bytes(b, self.byte_count + 2)) + return r + + def prepare_read_key(self, key): + option = self._option_from_key.get(key, None) + if option is None: + return None + offset, mask = option.om_method(option) + b = offset << (8 * (self.byte_count + 1)) + b |= (self.sep << (8 * self.byte_count)) | mask + return common.int2bytes(b, self.byte_count + 2) + + def validate_read(self, reply_bytes_dict): + values = {int(k): False for k in self.options} + for query, b in reply_bytes_dict.items(): + offset = common.bytes2int(query[0:1]) + b += (self.byte_count - len(b)) * b"\x00" + value = common.bytes2int(b[: self.byte_count]) + mask_to_opt = self._option_from_offset_mask.get(offset, {}) + m = 1 + for _ignore in range(8 * self.byte_count): + if m in mask_to_opt: + values[int(mask_to_opt[m])] = bool(value & m) + m <<= 1 + return values + + def prepare_write(self, new_value): + assert isinstance(new_value, dict) + w = {} + for k, v in new_value.items(): + option = self._option_from_key[int(k)] + offset, mask = self.om_method(option) + if offset not in w: + w[offset] = 0 + if v: + w[offset] |= mask + return [ + common.int2bytes( + (offset << (8 * (2 * self.byte_count + 1))) + | (self.sep << (16 * self.byte_count)) + | (self._mask_from_offset[offset] << (8 * self.byte_count)) + | value, + 2 * self.byte_count + 2, + ) + for offset, value in w.items() + ] + + def get_options(self): + return [int(opt) if isinstance(opt, int) else opt.as_int() for opt in self.options] + + def acceptable(self, args, current): + if len(args) != 2: + return None + key = next((option.id for option in self.options if option.as_int() == args[0]), None) + if key is None: + return None + val = bool_or_toggle(current[int(key)], args[1]) + return None if val is None else [int(key), val] + + def compare(self, args, current): + if len(args) != 2: + return False + key = next((option.id for option in self.options if option.as_int() == args[0]), None) + if key is None: + return False + return args[1] == current[int(key)] + + +class ChoicesValidator(Validator): + """Translates between NamedInts and a byte sequence. + :param choices: a list of NamedInts + :param byte_count: the size of the derived byte sequence. If None, it + will be calculated from the choices.""" + + kind = Kind.CHOICE + + def __init__(self, choices=None, byte_count=None, read_skip_byte_count=0, write_prefix_bytes=b""): + assert choices is not None + assert isinstance(choices, NamedInts) + assert len(choices) > 1 + self.choices = choices + self.needs_current_value = False + + max_bits = max(x.bit_length() for x in choices) + self._byte_count = (max_bits // 8) + (1 if max_bits % 8 else 0) + if byte_count: + assert self._byte_count <= byte_count + self._byte_count = byte_count + assert self._byte_count < 8 + self._read_skip_byte_count = read_skip_byte_count + self._write_prefix_bytes = write_prefix_bytes if write_prefix_bytes else b"" + assert self._byte_count + self._read_skip_byte_count <= 14 + assert self._byte_count + len(self._write_prefix_bytes) <= 14 + + def to_string(self, value) -> str: + return str(self.choices[value]) if isinstance(value, int) else str(value) + + def validate_read(self, reply_bytes): + reply_value = common.bytes2int(reply_bytes[self._read_skip_byte_count : self._read_skip_byte_count + self._byte_count]) + valid_value = self.choices[reply_value] + assert valid_value is not None, f"{self.__class__.__name__}: failed to validate read value {reply_value:02X}" + return valid_value + + def prepare_write(self, new_value, current_value=None): + if new_value is None: + value = self.choices[:][0] + else: + value = self.choice(new_value) + if value is None: + raise ValueError(f"invalid choice {new_value!r}") + assert isinstance(value, NamedInt) + return self._write_prefix_bytes + value.bytes(self._byte_count) + + def choice(self, value): + if isinstance(value, int): + return self.choices[value] + try: + int(value) + if int(value) in self.choices: + return self.choices[int(value)] + except Exception: + pass + if value in self.choices: + return self.choices[value] + else: + return None + + def acceptable(self, args, current): + choice = self.choice(args[0]) if len(args) == 1 else None + return None if choice is None else [choice] + + +class ChoicesMapValidator(ChoicesValidator): + kind = Kind.MAP_CHOICE + + def __init__( + self, + choices_map, + key_byte_count=0, + key_postfix_bytes=b"", + byte_count=0, + read_skip_byte_count=0, + write_prefix_bytes=b"", + extra_default=None, + mask=-1, + activate=0, + ): + assert choices_map is not None + assert isinstance(choices_map, dict) + max_key_bits = 0 + max_value_bits = 0 + for key, choices in choices_map.items(): + assert isinstance(key, NamedInt) + assert isinstance(choices, NamedInts) + max_key_bits = max(max_key_bits, key.bit_length()) + for key_value in choices: + assert isinstance(key_value, NamedInt) + max_value_bits = max(max_value_bits, key_value.bit_length()) + self._key_byte_count = (max_key_bits + 7) // 8 + if key_byte_count: + assert self._key_byte_count <= key_byte_count + self._key_byte_count = key_byte_count + self._byte_count = (max_value_bits + 7) // 8 + if byte_count: + assert self._byte_count <= byte_count + self._byte_count = byte_count + + self.choices = choices_map + self.needs_current_value = False + self.extra_default = extra_default + self._key_postfix_bytes = key_postfix_bytes + self._read_skip_byte_count = read_skip_byte_count if read_skip_byte_count else 0 + self._write_prefix_bytes = write_prefix_bytes if write_prefix_bytes else b"" + self.activate = activate + self.mask = mask + assert self._byte_count + self._read_skip_byte_count + self._key_byte_count <= 14 + assert self._byte_count + len(self._write_prefix_bytes) + self._key_byte_count <= 14 + + def to_string(self, value) -> str: + def element_to_string(key, val): + k, c = next(((k, c) for k, c in self.choices.items() if int(key) == k), (None, None)) + return str(k) + ":" + str(c[val]) if k is not None else "?" + + return "{" + ", ".join([element_to_string(k, value[k]) for k in sorted(value)]) + "}" + + def validate_read(self, reply_bytes, key): + start = self._key_byte_count + self._read_skip_byte_count + end = start + self._byte_count + reply_value = common.bytes2int(reply_bytes[start:end]) & self.mask + # reprogrammable keys starts out as 0, which is not a choice, so don't use assert here + if self.extra_default is not None and self.extra_default == reply_value: + return int(self.choices[key][0]) + if reply_value not in self.choices[key]: + assert reply_value in self.choices[key], "%s: failed to validate read value %02X" % ( + self.__class__.__name__, + reply_value, + ) + return reply_value + + def prepare_key(self, key): + return key.to_bytes(self._key_byte_count, "big") + self._key_postfix_bytes + + def prepare_write(self, key, new_value): + choices = self.choices.get(key) + if choices is None or (new_value not in choices and new_value != self.extra_default): + logger.error("invalid choice %r for %s", new_value, key) + return None + new_value = new_value | self.activate + return self._write_prefix_bytes + new_value.to_bytes(self._byte_count, "big") + + def acceptable(self, args, current): + if len(args) != 2: + return None + key, choices = next(((key, item) for key, item in self.choices.items() if key == args[0]), (None, None)) + if choices is None or args[1] not in choices: + return None + choice = next((item for item in choices if item == args[1]), None) + return [int(key), int(choice)] if choice is not None else None + + def compare(self, args, current): + if len(args) != 2: + return False + key = next((key for key in self.choices if key == int(args[0])), None) + if key is None: + return False + return args[1] == current[int(key)] + + +class RangeValidator(Validator): + kind = Kind.RANGE + """Translates between integers and a byte sequence. + :param min_value: minimum accepted value (inclusive) + :param max_value: maximum accepted value (inclusive) + :param byte_count: the size of the derived byte sequence. If None, it + will be calculated from the range.""" + min_value = 0 + max_value = 255 + + @classmethod + def build(cls, setting_class, device, **kwargs): + kwargs["min_value"] = setting_class.min_value + kwargs["max_value"] = setting_class.max_value + return cls(**kwargs) + + def __init__(self, min_value=0, max_value=255, byte_count=1, read_skip_byte_count=0, write_prefix_bytes=b"", signed=False): + assert max_value > min_value + self.min_value = min_value + self.max_value = max_value + self.read_skip_byte_count = read_skip_byte_count + self.write_prefix_bytes = write_prefix_bytes + self._signed = signed + self.needs_current_value = True # read and check before write (needed for ADC power and probably a good idea anyway) + self._byte_count = math.ceil(math.log(max_value + 1, 256)) + if byte_count: + assert self._byte_count <= byte_count + self._byte_count = byte_count + assert self._byte_count < 8 + + def validate_read(self, reply_bytes): + reply_value = common.bytes2int( + reply_bytes[self.read_skip_byte_count : self.read_skip_byte_count + self._byte_count], signed=self._signed + ) + assert reply_value >= self.min_value, f"{self.__class__.__name__}: failed to validate read value {reply_value:02X}" + assert reply_value <= self.max_value, f"{self.__class__.__name__}: failed to validate read value {reply_value:02X}" + return reply_value + + def prepare_write(self, new_value, current_value=None): + if new_value < self.min_value or new_value > self.max_value: + raise ValueError(f"invalid choice {new_value!r}") + current_value = self.validate_read(current_value) if current_value is not None else None + to_write = self.write_prefix_bytes + common.int2bytes(new_value, self._byte_count, signed=self._signed) + # current value is known and same as value to be written return None to signal not to write it + return None if current_value is not None and current_value == new_value else to_write + + def acceptable(self, args, current): + arg = args[0] + # None if len(args) != 1 or type(arg) != int or arg < self.min_value or arg > self.max_value else args) + return None if len(args) != 1 or isinstance(arg, int) or arg < self.min_value or arg > self.max_value else args + + def compare(self, args, current): + if len(args) == 1: + return args[0] == current + elif len(args) == 2: + return args[0] <= current <= args[1] + else: + return False + + +class HeteroValidator(Validator): + kind = Kind.HETERO + + @classmethod + def build(cls, setting_class, device, **kwargs): + return cls(**kwargs) + + def __init__(self, data_class=None, options=None, readable=True): + assert data_class is not None and options is not None + self.data_class = data_class + self.options = options + self.readable = readable + self.needs_current_value = False + + def validate_read(self, reply_bytes): + if self.readable: + reply_value = self.data_class.from_bytes(reply_bytes, options=self.options) + return reply_value + + def prepare_write(self, new_value, current_value=None): + to_write = new_value.to_bytes(options=self.options) + return to_write + + def acceptable(self, args, current): # should this actually do some checking? + return True + + +class PackedRangeValidator(Validator): + kind = Kind.PACKED_RANGE + """Several range values, all the same size, all the same min and max""" + min_value = 0 + max_value = 255 + count = 1 + rsbc = 0 + write_prefix_bytes = b"" + + def __init__( + self, keys, min_value=0, max_value=255, count=1, byte_count=1, read_skip_byte_count=0, write_prefix_bytes=b"" + ): + assert max_value > min_value + self.needs_current_value = True + self.keys = keys + self.min_value = min_value + self.max_value = max_value + self.count = count + self.bc = math.ceil(math.log(max_value + 1 - min(0, min_value), 256)) + if byte_count: + assert self.bc <= byte_count + self.bc = byte_count + assert self.bc * self.count + self.rsbc = read_skip_byte_count + self.write_prefix_bytes = write_prefix_bytes + + def validate_read(self, reply_bytes): + rvs = { + n: common.bytes2int(reply_bytes[self.rsbc + n * self.bc : self.rsbc + (n + 1) * self.bc], signed=True) + for n in range(self.count) + } + for n in range(self.count): + assert rvs[n] >= self.min_value, f"{self.__class__.__name__}: failed to validate read value {rvs[n]:02X}" + assert rvs[n] <= self.max_value, f"{self.__class__.__name__}: failed to validate read value {rvs[n]:02X}" + return rvs + + def prepare_write(self, new_values): + if len(new_values) != self.count: + raise ValueError(f"wrong number of values {new_values!r}") + for new_value in new_values.values(): + if new_value < self.min_value or new_value > self.max_value: + raise ValueError(f"invalid value {new_value!r}") + bytes = self.write_prefix_bytes + b"".join( + common.int2bytes(new_values[n], self.bc, signed=True) for n in range(self.count) + ) + return bytes + + def acceptable(self, args, current): + if len(args) != 2 or int(args[0]) < 0 or int(args[0]) >= self.count: + return None + return None if not isinstance(args[1], int) or args[1] < self.min_value or args[1] > self.max_value else args + + def compare(self, args, current): + logger.warning("compare not implemented for packed range settings") + return False + + +class MultipleRangeValidator(Validator): + kind = Kind.MULTIPLE_RANGE + + def __init__(self, items, sub_items): + assert isinstance(items, list) # each element must have .index and its __int__ must return its id (not its index) + assert isinstance(sub_items, dict) + # sub_items: items -> class with .minimum, .maximum, .length (in bytes), .id (a string) and .widget (e.g. 'Scale') + self.items = items + self.keys = NamedInts(**{str(item): int(item) for item in items}) + self._item_from_id = {int(k): k for k in items} + self.sub_items = sub_items + + def prepare_read_item(self, item): + return common.int2bytes((self._item_from_id[int(item)].index << 1) | 0xFF, 2) + + def validate_read_item(self, reply_bytes, item): + item = self._item_from_id[int(item)] + start = 0 + value = {} + for sub_item in self.sub_items[item]: + r = reply_bytes[start : start + sub_item.length] + if len(r) < sub_item.length: + r += b"\x00" * (sub_item.length - len(value)) + v = common.bytes2int(r) + if not (sub_item.minimum < v < sub_item.maximum): + logger.warning( + f"{self.__class__.__name__}: failed to validate read value for {item}.{sub_item}: " + + f"{v} not in [{sub_item.minimum}..{sub_item.maximum}]" + ) + value[str(sub_item)] = v + start += sub_item.length + return value + + def prepare_write(self, value): + seq = [] + w = b"" + for item in value.keys(): + _item = self._item_from_id[int(item)] + b = common.int2bytes(_item.index, 1) + for sub_item in self.sub_items[_item]: + try: + v = value[int(item)][str(sub_item)] + except KeyError: + return None + if not (sub_item.minimum <= v <= sub_item.maximum): + raise ValueError( + f"invalid choice for {item}.{sub_item}: {v} not in [{sub_item.minimum}..{sub_item.maximum}]" + ) + b += common.int2bytes(v, sub_item.length) + if len(w) + len(b) > 15: + seq.append(b + b"\xff") + w = b"" + w += b + seq.append(w + b"\xff") + return seq + + def prepare_write_item(self, item, value): + _item = self._item_from_id[int(item)] + w = common.int2bytes(_item.index, 1) + for sub_item in self.sub_items[_item]: + try: + v = value[str(sub_item)] + except KeyError: + return None + if not (sub_item.minimum <= v <= sub_item.maximum): + raise ValueError(f"invalid choice for {item}.{sub_item}: {v} not in [{sub_item.minimum}..{sub_item.maximum}]") + w += common.int2bytes(v, sub_item.length) + return w + b"\xff" + + def acceptable(self, args, current): + # just one item, with at least one sub-item + if not isinstance(args, list) or len(args) != 2 or not isinstance(args[1], dict): + return None + item = next((p for p in self.items if p.id == args[0] or str(p) == args[0]), None) + if not item: + return None + for sub_key, value in args[1].items(): + sub_item = next((it for it in self.sub_items[item] if it.id == sub_key), None) + if not sub_item: + return None + if not isinstance(value, int) or not (sub_item.minimum <= value <= sub_item.maximum): + return None + return [int(item), {**args[1]}] + + def compare(self, args, current): + logger.warning("compare not implemented for multiple range settings") + return False + + +@dataclass(frozen=True) +class Range: + """Inclusive integer range used as the value side of a MapRangeValidator. + + `byte_count` is the wire encoding width. `signed` selects two's-complement. + Settings whose value space is a continuous integer range (e.g. per-key RGB + colors as 24-bit ints) use this in place of a NamedInts choice list. + """ + + min: int + max: int + byte_count: int = 1 + signed: bool = False + + def contains(self, value: int) -> bool: + return isinstance(value, int) and self.min <= value <= self.max + + +class MapRangeValidator(Validator): + """Map of keys → integer in a per-key Range. Open value space (no choice list). + + Reports `kind = Kind.MAP_CHOICE` so the existing config-panel/CLI/rule-engine + dispatch keeps routing without new branches; consumers that need to tell + "choice list" from "open range" check `isinstance(setting.choices[k], Range)`. + + TODO: complete `Kind.MAP_RANGE` infrastructure (UI dispatch, generic rule-UI + handling, generalize `cli/config.py:299`) and migrate this validator's + `kind` over. Today MAP_RANGE is only honored by `ForceSensing` via the + `settings_new` framework; bridging both frameworks is a separate task. + """ + + kind = Kind.MAP_CHOICE + + def __init__(self, choices_map, key_byte_count=1, write_prefix_bytes=b""): + assert isinstance(choices_map, dict) + for k, v in choices_map.items(): + assert isinstance(k, NamedInt), f"MapRangeValidator key must be NamedInt, got {type(k).__name__}" + assert isinstance(v, Range), f"MapRangeValidator value must be Range, got {type(v).__name__}" + self.choices = choices_map + self.needs_current_value = False + self._key_byte_count = key_byte_count + self._write_prefix_bytes = write_prefix_bytes + + def to_string(self, value) -> str: + if not isinstance(value, dict): + return str(value) + return "{" + ", ".join(f"{k}:{value[k]}" for k in sorted(value)) + "}" + + def validate_read(self, reply_bytes, key): + rng = self.choices.get(key) + if rng is None: + return None + end = self._key_byte_count + rng.byte_count + return common.bytes2int(reply_bytes[self._key_byte_count : end], signed=rng.signed) + + def prepare_key(self, key): + return int(key).to_bytes(self._key_byte_count, "big") + + def prepare_write(self, key, new_value): + rng = self.choices.get(key) + if rng is None: + logger.error("invalid key %r for map-range setting", key) + return None + if not rng.contains(new_value): + logger.error("value %r out of range [%d, %d] for key %s", new_value, rng.min, rng.max, key) + return None + return self._write_prefix_bytes + int(new_value).to_bytes(rng.byte_count, "big", signed=rng.signed) + + def acceptable(self, args, current): + if not isinstance(args, list) or len(args) != 2: + return None + key = next((k for k in self.choices if int(k) == int(args[0])), None) + if key is None: + return None + rng = self.choices[key] + if not rng.contains(args[1]): + return None + return [int(key), int(args[1])] + + def compare(self, args, current): + if not isinstance(args, list) or len(args) != 2 or not isinstance(current, dict): + return False + key = next((k for k in self.choices if int(k) == int(args[0])), None) + if key is None: + return False + return current.get(int(key)) == args[1] diff --git a/lib/logitech_receiver/special_keys.py b/lib/logitech_receiver/special_keys.py index 6e2d825b..d1314a63 100644 --- a/lib/logitech_receiver/special_keys.py +++ b/lib/logitech_receiver/special_keys.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -17,603 +15,630 @@ ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # Reprogrammable keys information -# Mostly from Logitech documentation, but with some edits for better Lunix compatibility +# Mostly from Logitech documentation, but with some edits for better Linux compatibility -from .common import NamedInts as _NamedInts -from .common import UnsortedNamedInts as _UnsortedNamedInts +import os +from enum import IntEnum + +import yaml + +from .common import NamedInts +from .common import UnsortedNamedInts + +_XDG_CONFIG_HOME = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser(os.path.join("~", ".config")) +_keys_file_path = os.path.join(_XDG_CONFIG_HOME, "solaar", "keys.yaml") + + +# Original set done as # tasks.py - Switch_Presentation__Switch_Screen=0x0093, # on K400 Plus - Minimize_Window=0x0094, - Maximize_Window=0x0095, # on K400 Plus - MultiPlatform_App_Switch=0x0096, - MultiPlatform_Home=0x0097, - MultiPlatform_Menu=0x0098, - MultiPlatform_Back=0x0099, - Switch_Language=0x009A, # Mac_switch_language - Screen_Capture=0x009B, # Mac_screen_Capture, on Craft Keyboard - Gesture_Button=0x009C, - Smart_Shift=0x009D, - AppExpose=0x009E, - Smart_Zoom=0x009F, - Lookup=0x00A0, - Microphone_on__off=0x00A1, - Wifi_on__off=0x00A2, - Brightness_Down=0x00A3, - Brightness_Up=0x00A4, - Display_Out=0x00A5, - View_Open_Apps=0x00A6, - View_All_Open_Apps=0x00A7, - AppSwitch=0x00A8, - Gesture_Button_Navigation=0x00A9, # Mouse_Thumb_Button on MX Master - Fn_inversion=0x00AA, - Multiplatform_Back=0x00AB, - Multiplatform_Forward=0x00AC, - Multiplatform_Gesture_Button=0x00AD, - HostSwitch_Channel_1=0x00AE, - HostSwitch_Channel_2=0x00AF, - HostSwitch_Channel_3=0x00B0, - Multiplatform_Search=0x00B1, - Multiplatform_Home__Mission_Control=0x00B2, - Multiplatform_Menu__Launchpad=0x00B3, - Virtual_Gesture_Button=0x00B4, - Cursor=0x00B5, - Keyboard_Right_Arrow=0x00B6, - SW_Custom_Highlight=0x00B7, - Keyboard_Left_Arrow=0x00B8, - TBD=0x00B9, - Multiplatform_Language_Switch=0x00BA, - SW_Custom_Highlight_2=0x00BB, - Fast_Forward=0x00BC, - Fast_Backward=0x00BD, - Switch_Highlighting=0x00BE, - Mission_Control__Task_View=0x00BF, # Switch_Workspace on Craft Keyboard - Dashboard_Launchpad__Action_Center=0x00C0, # Application_Launcher on Craft Keyboard - Backlight_Down=0x00C1, # Backlight_Down_FW_internal_function - Backlight_Up=0x00C2, # Backlight_Up_FW_internal_function - Right_Click__App_Contextual_Menu=0x00C3, # Context_Menu on Craft Keyboard - DPI_Change=0x00C4, - New_Tab=0x00C5, - F2=0x00C6, - F3=0x00C7, - F4=0x00C8, - F5=0x00C9, - F6=0x00CA, - F7=0x00CB, - F8=0x00CC, - F1=0x00CD, - Laser_Button=0x00CE, - Laser_Button_Long_Press=0x00CF, - Start_Presentation=0x00D0, - Blank_Screen=0x00D1, - DPI_Switch=0x00D2, # AdjustDPI on MX Vertical - Home__Show_Desktop=0x00D3, - App_Switch__Dashboard=0x00D4, - App_Switch=0x00D5, - Fn_Inversion=0x00D6, - LeftAndRightClick=0x00D7, - Voice_Dictation=0x00D8, - Emoji_Smiling_Face_With_Heart_Shaped_Eyes=0x00D9, - Emoji_Loudly_Crying_Face=0x00DA, - Emoji_Smiley=0x00DB, - Emoji_Smiley_With_Tears=0x00DC, - Open_Emoji_Panel=0x00DD, - Multiplatform_App_Switch__Launchpad=0x00DE, - Snipping_Tool=0x00DF, - Grave_Accent=0x00E0, - Standard_Tab_Key=0x00E1, - Caps_Lock=0x00E2, - Left_Shift=0x00E3, - Left_Control=0x00E4, - Left_Option__Start=0x00E5, - Left_Command__Alt=0x00E6, - Right_Command__Alt=0x00E7, - Right_Option__Start=0x00E8, - Right_Control=0x00E9, - Right_Shift=0x0EA, - Insert=0x00EB, - Delete=0x00EC, - Home=0x00ED, - End=0x00EE, - Page_Up=0x00EF, - Page_Down=0x00F0, - Mute_Microphone=0x00F1, - Do_Not_Disturb=0x00F2, - Backslash=0x00F3, - Refresh=0x00F4, - Close_Tab=0x00F5, - Lang_Switch=0x00F6, - Standard_Alphabetical_Key=0x00F7, - Right_Option__Start__2=0x00F8, - Left_Option=0x00F9, - Right_Option=0x00FA, - Left_Cmd=0x00FB, - Right_Cmd=0x00FC, -) -TASK._fallback = lambda x: 'unknown:%04X' % x -# Capabilities and desired software handling for a control -# Ref: https://drive.google.com/file/d/10imcbmoxTJ1N510poGdsviEhoFfB_Ua4/view -# We treat bytes 4 and 8 of `getCidInfo` as a single bitfield -KEY_FLAG = _NamedInts( - analytics_key_events=0x400, - force_raw_XY=0x200, - raw_XY=0x100, - virtual=0x80, - persistently_divertable=0x40, - divertable=0x20, - reprogrammable=0x10, - FN_sensitive=0x08, - nonstandard=0x04, - is_FN=0x02, - mse=0x01 -) -# Flags describing the reporting method of a control -# We treat bytes 2 and 5 of `get/setCidReporting` as a single bitfield -MAPPING_FLAG = _NamedInts( - analytics_key_events_reporting=0x100, - force_raw_XY_diverted=0x40, - raw_XY_diverted=0x10, - persistently_diverted=0x04, - diverted=0x01 -) -CID_GROUP_BIT = _NamedInts(g8=0x80, g7=0x40, g6=0x20, g5=0x10, g4=0x08, g3=0x04, g2=0x02, g1=0x01) -CID_GROUP = _NamedInts(g8=8, g7=7, g6=6, g5=5, g4=4, g3=3, g2=2, g1=1) -DISABLE = _NamedInts( + SWITCH_PRESENTATION_SWITCH_SCREEN = 0x0093 # on K400 Plus + MINIMIZE_WINDOW = 0x0094 + MAXIMIZE_WINDOW = 0x0095 # on K400 Plus + MULTI_PLATFORM_APP_SWITCH = 0x0096 + MULTI_PLATFORM_HOME = 0x0097 + MULTI_PLATFORM_MENU = 0x0098 + MULTI_PLATFORM_BACK = 0x0099 + SWITCH_LANGUAGE = 0x009A # Mac_switch_language + SCREEN_CAPTURE = 0x009B # Mac_screen_Capture, on Craft Keyboard + GESTURE_BUTTON = 0x009C + SMART_SHIFT = 0x009D + APP_EXPOSE = 0x009E + SMART_ZOOM = 0x009F + LOOKUP = 0x00A0 + MICROPHEON_ON_OFF = 0x00A1 + WIFI_ON_OFF = 0x00A2 + BRIGHTNESS_DOWN = 0x00A3 + BRIGHTNESS_UP = 0x00A4 + DISPLAY_OUT = 0x00A5 + VIEW_OPEN_APPS = 0x00A6 + VIEW_ALL_OPEN_APPS = 0x00A7 + APP_SWITCH = 0x00A8 + GESTURE_BUTTON_NAVIGATION = 0x00A9 # Mouse_Thumb_Button on MX Master + FN_INVERSION = 0x00AA + MULTI_PLATFORM_BACK_2 = 0x00AB # Alternative + MULTI_PLATFORM_FORWARD = 0x00AC + MULTI_PLATFORM_Gesture_Button = 0x00AD + HostSwitch_Channel_1 = 0x00AE + HostSwitch_Channel_2 = 0x00AF + HostSwitch_Channel_3 = 0x00B0 + MULTI_PLATFORM_SEARCH = 0x00B1 + MULTI_PLATFORM_HOME_MISSION_CONTROL = 0x00B2 + MULTI_PLATFORM_MENU_LAUNCHPAD = 0x00B3 + VIRTUAL_GESTURE_BUTTON = 0x00B4 + CURSOR = 0x00B5 + KEYBOARD_RIGHT_ARROW = 0x00B6 + SW_CUSTOM_HIGHLIGHT = 0x00B7 + KEYBOARD_LEFT_ARROW = 0x00B8 + TBD = 0x00B9 + MULTI_PLATFORM_Language_Switch = 0x00BA + SW_CUSTOM_HIGHLIGHT_2 = 0x00BB + FAST_FORWARD = 0x00BC + FAST_BACKWARD = 0x00BD + SWITCH_HIGHLIGHTING = 0x00BE + MISSION_CONTROL_TASK_VIEW = 0x00BF # Switch_Workspace on Craft Keyboard + DASHBOARD_LAUNCHPAD_ACTION_CENTER = 0x00C0 # Application_Launcher on Craft + # Keyboard + BACKLIGHT_DOWN = 0x00C1 # Backlight_Down_FW_internal_function + BACKLIGHT_UP = 0x00C2 # Backlight_Up_FW_internal_function + RIGHT_CLICK_APP_CONTEXT_MENU = 0x00C3 # Context_Menu on Craft Keyboard + DPI_Change = 0x00C4 + NEW_TAB = 0x00C5 + F2 = 0x00C6 + F3 = 0x00C7 + F4 = 0x00C8 + F5 = 0x00C9 + F6 = 0x00CA + F7 = 0x00CB + F8 = 0x00CC + F1 = 0x00CD + LASER_BUTTON = 0x00CE + LASER_BUTTON_LONG_PRESS = 0x00CF + START_PRESENTATION = 0x00D0 + BLANK_SCREEN = 0x00D1 + DPI_Switch = 0x00D2 # AdjustDPI on MX Vertical + HOME_SHOW_DESKTOP = 0x00D3 + APP_SWITCH_DASHBOARD = 0x00D4 + APP_SWITCH_2 = 0x00D5 # Alternative + FN_INVERSION_2 = 0x00D6 # Alternative + LEFT_AND_RIGHT_CLICK = 0x00D7 + VOICE_DICTATION = 0x00D8 + EMOJI_SMILING_FACE_WITH_HEART_SHAPED_EYES = 0x00D9 + EMOJI_LOUDLY_CRYING_FACE = 0x00DA + EMOJI_SMILEY = 0x00DB + EMOJI_SMILE_WITH_TEARS = 0x00DC + OPEN_EMOJI_PANEL = 0x00DD + MULTI_PLATFORM_APP_SWITCH_LAUNCHPAD = 0x00DE + SNIPPING_TOOL = 0x00DF + GRAVE_ACCENT = 0x00E0 + STANDARD_TAB_KEY = 0x00E1 + CAPS_LOCK = 0x00E2 + LEFT_SHIFT = 0x00E3 + LEFT_CONTROL = 0x00E4 + LEFT_OPTION_START = 0x00E5 + LEFT_COMMAND_ALT = 0x00E6 + RIGHT_COMMAND_ALT = 0x00E7 + RIGHT_OPTION_START = 0x00E8 + RIGHT_CONTROL = 0x00E9 + RIGHT_SHIFT = 0x0EA + INSERT = 0x00EB + DELETE = 0x00EC + HOME = 0x00ED + END = 0x00EE + PAGE_UP_2 = 0x00EF # Alternative + PAGE_DOWN_2 = 0x00F0 # Alternative + MUTE_MICROPHONE = 0x00F1 + DO_NOT_DISTURB = 0x00F2 + BACKSLASH = 0x00F3 + REFRESH = 0x00F4 + CLOSE_TAB = 0x00F5 + LANG_SWITCH = 0x00F6 + STANDARD_ALPHABETICAL_KEY = 0x00F7 + RRIGH_OPTION_START_2 = 0x00F8 + LEFT_OPTION = 0x00F9 + RIGHT_OPTION = 0x00FA + LEFT_CMD = 0x00FB + RIGHT_CMD = 0x00FC + + def __str__(self): + return self.name.replace("_", " ").title() + + +class CIDGroupBit(IntEnum): + g1 = 0x01 + g2 = 0x02 + g3 = 0x04 + g4 = 0x08 + g5 = 0x10 + g6 = 0x20 + g7 = 0x40 + g8 = 0x80 + + +class CidGroup(IntEnum): + g1 = 1 + g2 = 2 + g3 = 3 + g4 = 4 + g5 = 5 + g6 = 6 + g7 = 7 + g8 = 8 + + +DISABLE = NamedInts( Caps_Lock=0x01, Num_Lock=0x02, Scroll_Lock=0x04, Insert=0x08, Win=0x10, # aka Super ) -DISABLE._fallback = lambda x: 'unknown:%02X' % x +DISABLE._fallback = lambda x: f"unknown:{x:02X}" # HID USB Keycodes from https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf # Modified by information from Linux HID driver linux/drivers/hid/hid-input.c -USB_HID_KEYCODES = _NamedInts( +USB_HID_KEYCODES = NamedInts( A=0x04, B=0x05, C=0x06, D=0x07, E=0x08, F=0x09, - G=0x0a, - H=0x0b, - I=0x0c, - J=0x0d, - K=0x0e, - L=0x0f, + G=0x0A, + H=0x0B, + I=0x0C, + J=0x0D, + K=0x0E, + L=0x0F, M=0x10, N=0x11, O=0x12, @@ -624,18 +649,18 @@ USB_HID_KEYCODES = _NamedInts( T=0x17, U=0x18, V=0x19, - W=0x1a, - X=0x1b, - Y=0x1c, - Z=0x1d, + W=0x1A, + X=0x1B, + Y=0x1C, + Z=0x1D, ENTER=0x28, ESC=0x29, - BACKSPACE=0x2a, - TAB=0x2b, - SPACE=0x2c, - MINUS=0x2d, - EQUAL=0x2e, - LEFTBRACE=0x2f, + BACKSPACE=0x2A, + TAB=0x2B, + SPACE=0x2C, + MINUS=0x2D, + EQUAL=0x2E, + LEFTBRACE=0x2F, RIGHTBRACE=0x30, BACKSLASH=0x31, HASHTILDE=0x32, @@ -646,12 +671,12 @@ USB_HID_KEYCODES = _NamedInts( DOT=0x37, SLASH=0x38, CAPSLOCK=0x39, - F1=0x3a, - F2=0x3b, - F3=0x3c, - F4=0x3d, - F5=0x3e, - F6=0x3f, + F1=0x3A, + F2=0x3B, + F3=0x3C, + F4=0x3D, + F5=0x3E, + F6=0x3F, F7=0x40, F8=0x41, F9=0x42, @@ -662,12 +687,12 @@ USB_HID_KEYCODES = _NamedInts( SCROLLLOCK=0x47, PAUSE=0x48, INSERT=0x49, - HOME=0x4a, - PAGEUP=0x4b, - DELETE=0x4c, - END=0x4d, - PAGEDOWN=0x4e, - RIGHT=0x4f, + HOME=0x4A, + PAGEUP=0x4B, + DELETE=0x4C, + END=0x4D, + PAGEDOWN=0x4E, + RIGHT=0x4F, LEFT=0x50, DOWN=0x51, UP=0x52, @@ -678,12 +703,12 @@ USB_HID_KEYCODES = _NamedInts( KPPLUS=0x57, KPENTER=0x58, KP1=0x59, - KP2=0x5a, - KP3=0x5b, - KP4=0x5c, - KP5=0x5d, - KP6=0x5e, - KP7=0x5f, + KP2=0x5A, + KP3=0x5B, + KP4=0x5C, + KP5=0x5D, + KP6=0x5E, + KP7=0x5F, KP8=0x60, KP9=0x61, KP0=0x62, @@ -693,12 +718,12 @@ USB_HID_KEYCODES = _NamedInts( KPEQUAL=0x67, F13=0x68, F14=0x69, - F15=0x6a, - F16=0x6b, - F17=0x6c, - F18=0x6d, - F19=0x6e, - F20=0x6f, + F15=0x6A, + F16=0x6B, + F17=0x6C, + F18=0x6D, + F19=0x6E, + F20=0x6F, F21=0x70, F22=0x71, F23=0x72, @@ -709,458 +734,460 @@ USB_HID_KEYCODES = _NamedInts( FRONT=0x77, STOP=0x78, AGAIN=0x79, - UNDO=0x7a, - CUT=0x7b, - COPY=0x7c, - PASTE=0x7d, - FIND=0x7e, - MUTE=0x7f, + UNDO=0x7A, + CUT=0x7B, + COPY=0x7C, + PASTE=0x7D, + FIND=0x7E, + MUTE=0x7F, VOLUMEUP=0x80, VOLUMEDOWN=0x81, KPCOMMA=0x85, RO=0x87, KATAKANAHIRAGANA=0x88, YEN=0x89, - HENKAN=0x8a, - MUHENKAN=0x8b, - KPJPCOMMA=0x8c, + HENKAN=0x8A, + MUHENKAN=0x8B, + KPJPCOMMA=0x8C, HANGEUL=0x90, HANJA=0x91, KATAKANA=0x92, HIRAGANA=0x93, ZENKAKUHANKAKU=0x94, - KPLEFTPAREN=0xb6, - KPRIGHTPAREN=0xb7, - LEFTCTRL=0xe0, - LEFTSHIFT=0xe1, - LEFTALT=0xe2, - LEFTWINDOWS=0xe3, - RIGHTCTRL=0xe4, - RIGHTSHIFT=0xe5, - RIGHTALT=0xe6, - RIGHTMETA=0xe7, - MEDIA_PLAYPAUSE=0xe8, - MEDIA_STOPCD=0xe9, - MEDIA_PREVIOUSSONG=0xea, - MEDIA_NEXTSONG=0xeb, - MEDIA_EJECTCD=0xec, - MEDIA_VOLUMEUP=0xed, - MEDIA_VOLUMEDOWN=0xee, - MEDIA_MUTE=0xef, - MEDIA_WWW=0xf0, - MEDIA_BACK=0xf1, - MEDIA_FORWARD=0xf2, - MEDIA_STOP=0xf3, - MEDIA_FIND=0xf4, - MEDIA_SCROLLUP=0xf5, - MEDIA_SCROLLDOWN=0xf6, - MEDIA_EDIT=0xf7, - MEDIA_SLEEP=0xf8, - MEDIA_COFFEE=0xf9, - MEDIA_REFRESH=0xfa, - MEDIA_CALC=0xfb, + KPLEFTPAREN=0xB6, + KPRIGHTPAREN=0xB7, + LEFTCTRL=0xE0, + LEFTSHIFT=0xE1, + LEFTALT=0xE2, + LEFTWINDOWS=0xE3, + RIGHTCTRL=0xE4, + RIGHTSHIFT=0xE5, + RIGHTALT=0xE6, + RIGHTMETA=0xE7, + MEDIA_PLAYPAUSE=0xE8, + MEDIA_STOPCD=0xE9, + MEDIA_PREVIOUSSONG=0xEA, + MEDIA_NEXTSONG=0xEB, + MEDIA_EJECTCD=0xEC, + MEDIA_VOLUMEUP=0xED, + MEDIA_VOLUMEDOWN=0xEE, + MEDIA_MUTE=0xEF, + MEDIA_WWW=0xF0, + MEDIA_BACK=0xF1, + MEDIA_FORWARD=0xF2, + MEDIA_STOP=0xF3, + MEDIA_FIND=0xF4, + MEDIA_SCROLLUP=0xF5, + MEDIA_SCROLLDOWN=0xF6, + MEDIA_EDIT=0xF7, + MEDIA_SLEEP=0xF8, + MEDIA_COFFEE=0xF9, + MEDIA_REFRESH=0xFA, + MEDIA_CALC=0xFB, ) -USB_HID_KEYCODES[0] = 'No Output' -USB_HID_KEYCODES[0x1e] = '1' -USB_HID_KEYCODES[0x1f] = '2' -USB_HID_KEYCODES[0x20] = '3' -USB_HID_KEYCODES[0x21] = '4' -USB_HID_KEYCODES[0x22] = '5' -USB_HID_KEYCODES[0x23] = '6' -USB_HID_KEYCODES[0x24] = '7' -USB_HID_KEYCODES[0x25] = '8' -USB_HID_KEYCODES[0x26] = '9' -USB_HID_KEYCODES[0x27] = '0' -USB_HID_KEYCODES[0x64] = '102ND' +USB_HID_KEYCODES[0] = "No Output" +USB_HID_KEYCODES[0x1E] = "1" +USB_HID_KEYCODES[0x1F] = "2" +USB_HID_KEYCODES[0x20] = "3" +USB_HID_KEYCODES[0x21] = "4" +USB_HID_KEYCODES[0x22] = "5" +USB_HID_KEYCODES[0x23] = "6" +USB_HID_KEYCODES[0x24] = "7" +USB_HID_KEYCODES[0x25] = "8" +USB_HID_KEYCODES[0x26] = "9" +USB_HID_KEYCODES[0x27] = "0" +USB_HID_KEYCODES[0x64] = "102ND" -HID_CONSUMERCODES = _NamedInts({ - # Unassigned=0x00, - # Consumer_Control=0x01, - # Numeric_Key_Pad=0x02, - # Programmable_Buttons=0x03, - # Microphone=0x04, - # Headphone=0x05, - # Graphic_Equalizer=0x06, - # AM__PM=0x22, - 'Power': 0x30, - 'Reset': 0x31, - 'Sleep': 0x32, - 'Sleep_After': 0x33, - 'Sleep_Mode': 0x34, - 'Illumination': 0x35, - 'Function_Buttons': 0x36, - 'Menu': 0x40, - 'Menu__Pick': 0x41, - 'Menu_Up': 0x42, - 'Menu_Down': 0x43, - 'Menu_Left': 0x44, - 'Menu_Right': 0x45, - 'Menu_Escape': 0x46, - 'Menu_Value_Increase': 0x47, - 'Menu_Value_Decrease': 0x48, - 'Data_On_Screen': 0x60, - 'Closed_Caption': 0x61, - # Closed_Caption_Select=0x62, - 'VCR__TV': 0x63, - # Broadcast_Mode=0x64, - 'Snapshot': 0x65, - # Still=0x66, - 'Red': 0x69, - 'Green': 0x6A, - 'Blue': 0x6B, - 'Yellow': 0x6C, - 'Aspect_Ratio': 0x6D, - 'Brightness_Up': 0x6F, - 'Brightness_Down': 0x70, - 'Brightness_Toggle': 0x72, - 'Brightness_Min': 0x73, - 'Brightness_Max': 0x74, - 'Brightness_Auto': 0x75, - 'Keyboard_Illumination_Up': 0x79, - 'Keyboard_Illumination_Down': 0x7A, - 'Keyboard_Illumination_Toggle': 0x7C, - # Selection=0x80, - # Assign_Selection=0x81, - 'Mode_Step': 0x82, - 'Recall_Last': 0x83, - 'Enter_Channel': 0x84, - # Order_Movie=0x85, - # Channel=0x86, - # Media_Selection=0x87, - 'Media_Select_Computer': 0x88, - 'Media_Select_TV': 0x89, - 'Media_Select_WWW': 0x8A, - 'Media_Select_DVD': 0x8B, - 'Media_Select_Telephone': 0x8C, - 'Media_Select_Program_Guide': 0x8D, - 'Media_Select_Video_Phone': 0x8E, - 'Media_Select_Games': 0x8F, - 'Media_Select_Messages': 0x90, - 'Media_Select_CD': 0x91, - 'Media_Select_VCR': 0x92, - 'Media_Select_Tuner': 0x93, - 'Quit': 0x94, - 'Help': 0x95, - 'Media_Select_Tape': 0x96, - 'Media_Select_Cable': 0x97, - 'Media_Select_Satellite': 0x98, - 'Media_Select_Security': 0x99, - 'Media_Select_Home': 0x9A, - # Media_Select_Call=0x9B, - 'Channel_Increment': 0x9C, - 'Channel_Decrement': 0x9D, - # Media_Select_SAP=0x9E, - 'VCR_Plus': 0xA0, - # Once=0xA1, - # Daily=0xA2, - # Weekly=0xA3, - # Monthly=0xA4, - 'Play': 0xB0, - 'Pause': 0xB1, - 'Record': 0xB2, - 'Fast_Forward': 0xB3, - 'Rewind': 0xB4, - 'Scan_Next_Track': 0xB5, - 'Scan_Previous_Track': 0xB6, - 'Stop': 0xB7, - 'Eject': 0xB8, - 'Random_Play': 0xB9, - 'Select_DisC': 0xBA, - 'Enter_Disc': 0xBB, - 'Repeat': 0xBC, - 'Tracking': 0xBD, - 'Track_Normal': 0xBE, - 'Slow_Tracking': 0xBF, - # Frame_Forward=0xC0, - # Frame_Back=0xC1, - # Mark=0xC2, - # Clear_Mark=0xC3, - # Repeat_From_Mark=0xC4, - # Return_To_Mark=0xC5, - # Search_Mark_Forward=0xC6, - # Search_Mark_Backwards=0xC7, - # Counter_Reset=0xC8, - # Show_Counter=0xC9, - # Tracking_Increment=0xCA, - # Tracking_Decrement=0xCB, - # Stop__Eject=0xCC, - 'Play__Pause': 0xCD, - # Play__Skip=0xCE, - 'Volume': 0xE0, - # Balance=0xE1, - 'Mute': 0xE2, - # Bass=0xE3, - # Treble=0xE4, - 'Bass_Boost': 0xE5, - # Surround_Mode=0xE6, - # Loudness=0xE7, - # MPX=0xE8, - 'Volume_Up': 0xE9, - 'Volume_Down': 0xEA, - # Speed_Select=0xF0, - # Playback_Speed=0xF1, - # Standard_Play=0xF2, - # Long_Play=0xF3, - # Extended_Play=0xF4, - 'Slow': 0xF5, - 'Fan_Enable': 0x100, - 'Fan_Speed': 0x101, - 'Light': 0x102, - 'Light_Illumination_Level': 0x103, - 'Climate_Control_Enable': 0x104, - 'Room_Temperature': 0x105, - 'Security_Enable': 0x106, - 'Fire_Alarm': 0x107, - 'Police_Alarm': 0x108, - 'Proximity': 0x109, - 'Motion': 0x10A, - 'Duress_Alarm': 0x10B, - 'Holdup_Alarm': 0x10C, - 'Medical_Alarm': 0x10D, - 'Balance_Right': 0x150, - 'Balance_Left': 0x151, - 'Bass_Increment': 0x152, - 'Bass_Decrement': 0x153, - 'Treble_Increment': 0x154, - 'Treble_Decrement': 0x155, - 'Speaker_System': 0x160, - 'Channel_Left': 0x161, - 'Channel_Right': 0x162, - 'Channel_Center': 0x163, - 'Channel_Front': 0x164, - 'Channel_Center_Front': 0x165, - 'Channel_Side': 0x166, - 'Channel_Surround': 0x167, - 'Channel_Low_Frequency_Enhancement': 0x168, - 'Channel_Top': 0x169, - 'Channel_Unknown': 0x16A, - 'Subchannel': 0x170, - 'Subchannel_Increment': 0x171, - 'Subchannel_Decrement': 0x172, - 'Alternate_Audio_Increment': 0x173, - 'Alternate_Audio_Decrement': 0x174, - 'Application_Launch_Buttons': 0x180, - 'AL_Launch_Button_Configuration_Tool': 0x181, - 'AL_Programmable_Button_Configuration': 0x182, - 'AL_Consumer_Control_Configuration': 0x183, - 'AL_Word_Processor': 0x184, - 'AL_Text_Editor': 0x185, - 'AL_Spreadsheet': 0x186, - 'AL_Graphics_Editor': 0x187, - 'AL_Presentation_App': 0x188, - 'AL_Database_App': 0x189, - 'AL_Email_Reader': 0x18A, - 'AL_Newsreader': 0x18B, - 'AL_Voicemail': 0x18C, - 'AL_Contacts__Address_Book': 0x18D, - 'AL_Calendar__Schedule': 0x18E, - 'AL_Task__Project_Manager': 0x18F, - 'AL_Log__Journal__Timecard': 0x190, - 'AL_Checkbook__Finance': 0x191, - 'AL_Calculator': 0x192, - 'AL_A__V_Capture__Playback': 0x193, - 'AL_Local_Machine_Browser': 0x194, - 'AL_LAN__WAN_Browser': 0x195, - 'AL_Internet_Browser': 0x196, - 'AL_Remote_Networking__ISP_Connect': 0x197, - 'AL_Network_Conference': 0x198, - 'AL_Network_Chat': 0x199, - 'AL_Telephony__Dialer': 0x19A, - 'AL_Logon': 0x19B, - 'AL_Logoff': 0x19C, - 'AL_Logon__Logoff': 0x19D, - 'AL_Terminal_Lock__Screensaver': 0x19E, - 'AL_Control_Panel': 0x19F, - 'AL_Command_Line_Processor__Run': 0x1A0, - 'AL_Process__Task_Manager': 0x1A1, - 'AL_Select_Tast__Application': 0x1A2, - 'AL_Next_Task__Application': 0x1A3, - 'AL_Previous_Task__Application': 0x1A4, - 'AL_Preemptive_Halt_Task__Application': 0x1A5, - 'AL_Integrated_Help_Center': 0x1A6, - 'AL_Documents': 0x1A7, - 'AL_Thesaurus': 0x1A8, - 'AL_Dictionary': 0x1A9, - 'AL_Desktop': 0x1AA, - 'AL_Spell_Check': 0x1AB, - 'AL_Grammar_Check': 0x1AC, - 'AL_Wireless_Status': 0x1AD, - 'AL_Keyboard_Layout': 0x1AE, - 'AL_Virus_Protection': 0x1AF, - 'AL_Encryption': 0x1B0, - 'AL_Screen_Saver': 0x1B1, - 'AL_Alarms': 0x1B2, - 'AL_Clock': 0x1B3, - 'AL_File_Browser': 0x1B4, - 'AL_Power_Status': 0x1B5, - 'AL_Image_Browser': 0x1B6, - 'AL_Audio_Browser': 0x1B7, - 'AL_Movie_Browser': 0x1B8, - 'AL_Digital_Rights_Manager': 0x1B9, - 'AL_Digital_Wallet': 0x1BA, - 'AL_Instant_Messaging': 0x1BC, - 'AL_OEM_Features___Tips__Tutorial_Browser': 0x1BD, - 'AL_OEM_Help': 0x1BE, - 'AL_Online_Community': 0x1BF, - 'AL_Entertainment_Content_Browser': 0x1C0, - 'AL_Online_Shopping_Browser': 0x1C1, - 'AL_SmartCard_Information__Help': 0x1C2, - 'AL_Market_Monitor__Finance_Browser': 0x1C3, - 'AL_Customized_Corporate_News_Browser': 0x1C4, - 'AL_Online_Activity_Browser': 0x1C5, - 'AL_Research__Search_Browser': 0x1C6, - 'AL_Audio_Player': 0x1C7, - 'Generic_GUI_Application_Controls': 0x200, - 'AC_New': 0x201, - 'AC_Open': 0x202, - 'AC_Close': 0x203, - 'AC_Exit': 0x204, - 'AC_Maximize': 0x205, - 'AC_Minimize': 0x206, - 'AC_Save': 0x207, - 'AC_Print': 0x208, - 'AC_Properties': 0x209, - 'AC_Undo': 0x21A, - 'AC_Copy': 0x21B, - 'AC_Cut': 0x21C, - 'AC_Paste': 0x21D, - 'AC_Select_All': 0x21E, - 'AC_Find': 0x21F, - 'AC_Find_and_Replace': 0x220, - 'AC_Search': 0x221, - 'AC_Go_To': 0x222, - 'AC_Home': 0x223, - 'AC_Back': 0x224, - 'AC_Forward': 0x225, - 'AC_Stop': 0x226, - 'AC_Refresh': 0x227, - 'AC_Previous_Link': 0x228, - 'AC_Next_Link': 0x229, - 'AC_Bookmarks': 0x22A, - 'AC_History': 0x22B, - 'AC_Subscriptions': 0x22C, - 'AC_Zoom_In': 0x22D, - 'AC_Zoom_Out': 0x22E, - 'AC_Zoom': 0x22F, - 'AC_Full_Screen_View': 0x230, - 'AC_Normal_View': 0x231, - 'AC_View_Toggle': 0x232, - 'AC_Scroll_Up': 0x233, - 'AC_Scroll_Down': 0x234, - 'AC_Scroll': 0x235, - 'AC_Pan_Left': 0x236, - 'AC_Pan_Right': 0x237, - 'AC_Pan': 0x238, - 'AC_New_Window': 0x239, - 'AC_Tile_Horizontally': 0x23A, - 'AC_Tile_Vertically': 0x23B, - 'AC_Format': 0x23C, - 'AC_Edit': 0x23D, - 'AC_Bold': 0x23E, - 'AC_Italics': 0x23F, - 'AC_Underline': 0x240, - 'AC_Strikethrough': 0x241, - 'AC_Subscript': 0x242, - 'AC_Superscript': 0x243, - 'AC_All_Caps': 0x244, - 'AC_Rotate': 0x245, - 'AC_Resize': 0x246, - 'AC_Flip_horizontal': 0x247, - 'AC_Flip_Vertical': 0x248, - 'AC_Mirror_Horizontal': 0x249, - 'AC_Mirror_Vertical': 0x24A, - 'AC_Font_Select': 0x24B, - 'AC_Font_Color': 0x24C, - 'AC_Font_Size': 0x24D, - 'AC_Justify_Left': 0x24E, - 'AC_Justify_Center_H': 0x24F, - 'AC_Justify_Right': 0x250, - 'AC_Justify_Block_H': 0x251, - 'AC_Justify_Top': 0x252, - 'AC_Justify_Center_V': 0x253, - 'AC_Justify_Bottom': 0x254, - 'AC_Justify_Block_V': 0x255, - 'AC_Indent_Decrease': 0x256, - 'AC_Indent_Increase': 0x257, - 'AC_Numbered_List': 0x258, - 'AC_Restart_Numbering': 0x259, - 'AC_Bulleted_List': 0x25A, - 'AC_Promote': 0x25B, - 'AC_Demote': 0x25C, - 'AC_Yes': 0x25D, - 'AC_No': 0x25E, - 'AC_Cancel': 0x25F, - 'AC_Catalog': 0x260, - 'AC_Buy__Checkout': 0x261, - 'AC_Add_to_Cart': 0x262, - 'AC_Expand': 0x263, - 'AC_Expand_All': 0x264, - 'AC_Collapse': 0x265, - 'AC_Collapse_All': 0x266, - 'AC_Print_Preview': 0x267, - 'AC_Paste_Special': 0x268, - 'AC_Insert_Mode': 0x269, - 'AC_Delete': 0x26A, - 'AC_Lock': 0x26B, - 'AC_Unlock': 0x26C, - 'AC_Protect': 0x26D, - 'AC_Unprotect': 0x26E, - 'AC_Attach_Comment': 0x26F, - 'AC_Delete_Comment': 0x270, - 'AC_View_Comment': 0x271, - 'AC_Select_Word': 0x272, - 'AC_Select_Sentence': 0x273, - 'AC_Select_Paragraph': 0x274, - 'AC_Select_Column': 0x275, - 'AC_Select_Row': 0x276, - 'AC_Select_Table': 0x277, - 'AC_Select_Object': 0x278, - 'AC_Redo__Repeat': 0x279, - 'AC_Sort': 0x27A, - 'AC_Sort_Ascending': 0x27B, - 'AC_Sort_Descending': 0x27C, - 'AC_Filter': 0x27D, - 'AC_Set_Clock': 0x27E, - 'AC_View_Clock': 0x27F, - 'AC_Select_Time_Zone': 0x280, - 'AC_Edit_Time_Zones': 0x281, - 'AC_Set_Alarm': 0x282, - 'AC_Clear_Alarm': 0x283, - 'AC_Snooze_Alarm': 0x284, - 'AC_Reset_Alarm': 0x285, - 'AC_Synchronize': 0x286, - 'AC_Send__Receive': 0x287, - 'AC_Send_To': 0x288, - 'AC_Reply': 0x289, - 'AC_Reply_All': 0x28A, - 'AC_Forward_Msg': 0x28B, - 'AC_Send': 0x28C, - 'AC_Attach_File': 0x28D, - 'AC_Upload': 0x28E, - 'AC_Download_Save_Target_As': 0x28F, - 'AC_Set_Borders': 0x290, - 'AC_Insert_Row': 0x291, - 'AC_Insert_Column': 0x292, - 'AC_Insert_File': 0x293, - 'AC_Insert_Picture': 0x294, - 'AC_Insert_Object': 0x295, - 'AC_Insert_Symbol': 0x296, - 'AC_Save_and_Close': 0x297, - 'AC_Rename': 0x298, - 'AC_Merge': 0x299, - 'AC_Split': 0x29A, - 'AC_Distribute_Horizontally': 0x29B, - 'AC_Distribute_Vertically': 0x29C, -}) -HID_CONSUMERCODES[0x20] = '+10' -HID_CONSUMERCODES[0x21] = '+100' -HID_CONSUMERCODES._fallback = lambda x: 'unknown:%04X' % x +HID_CONSUMERCODES = NamedInts( + { + # Unassigned=0x00, + # Consumer_Control=0x01, + # Numeric_Key_Pad=0x02, + # Programmable_Buttons=0x03, + # Microphone=0x04, + # Headphone=0x05, + # Graphic_Equalizer=0x06, + # AM__PM=0x22, + "Power": 0x30, + "Reset": 0x31, + "Sleep": 0x32, + "Sleep_After": 0x33, + "Sleep_Mode": 0x34, + "Illumination": 0x35, + "Function_Buttons": 0x36, + "Menu": 0x40, + "Menu__Pick": 0x41, + "Menu_Up": 0x42, + "Menu_Down": 0x43, + "Menu_Left": 0x44, + "Menu_Right": 0x45, + "Menu_Escape": 0x46, + "Menu_Value_Increase": 0x47, + "Menu_Value_Decrease": 0x48, + "Data_On_Screen": 0x60, + "Closed_Caption": 0x61, + # Closed_Caption_Select=0x62, + "VCR__TV": 0x63, + # Broadcast_Mode=0x64, + "Snapshot": 0x65, + # Still=0x66, + "Red": 0x69, + "Green": 0x6A, + "Blue": 0x6B, + "Yellow": 0x6C, + "Aspect_Ratio": 0x6D, + "Brightness_Up": 0x6F, + "Brightness_Down": 0x70, + "Brightness_Toggle": 0x72, + "Brightness_Min": 0x73, + "Brightness_Max": 0x74, + "Brightness_Auto": 0x75, + "Keyboard_Illumination_Up": 0x79, + "Keyboard_Illumination_Down": 0x7A, + "Keyboard_Illumination_Toggle": 0x7C, + # Selection=0x80, + # Assign_Selection=0x81, + "Mode_Step": 0x82, + "Recall_Last": 0x83, + "Enter_Channel": 0x84, + # Order_Movie=0x85, + # Channel=0x86, + # Media_Selection=0x87, + "Media_Select_Computer": 0x88, + "Media_Select_TV": 0x89, + "Media_Select_WWW": 0x8A, + "Media_Select_DVD": 0x8B, + "Media_Select_Telephone": 0x8C, + "Media_Select_Program_Guide": 0x8D, + "Media_Select_Video_Phone": 0x8E, + "Media_Select_Games": 0x8F, + "Media_Select_Messages": 0x90, + "Media_Select_CD": 0x91, + "Media_Select_VCR": 0x92, + "Media_Select_Tuner": 0x93, + "Quit": 0x94, + "Help": 0x95, + "Media_Select_Tape": 0x96, + "Media_Select_Cable": 0x97, + "Media_Select_Satellite": 0x98, + "Media_Select_Security": 0x99, + "Media_Select_Home": 0x9A, + # Media_Select_Call=0x9B, + "Channel_Increment": 0x9C, + "Channel_Decrement": 0x9D, + # Media_Select_SAP=0x9E, + "VCR_Plus": 0xA0, + # Once=0xA1, + # Daily=0xA2, + # Weekly=0xA3, + # Monthly=0xA4, + "Play": 0xB0, + "Pause": 0xB1, + "Record": 0xB2, + "Fast_Forward": 0xB3, + "Rewind": 0xB4, + "Scan_Next_Track": 0xB5, + "Scan_Previous_Track": 0xB6, + "Stop": 0xB7, + "Eject": 0xB8, + "Random_Play": 0xB9, + "Select_DisC": 0xBA, + "Enter_Disc": 0xBB, + "Repeat": 0xBC, + "Tracking": 0xBD, + "Track_Normal": 0xBE, + "Slow_Tracking": 0xBF, + # Frame_Forward=0xC0, + # Frame_Back=0xC1, + # Mark=0xC2, + # Clear_Mark=0xC3, + # Repeat_From_Mark=0xC4, + # Return_To_Mark=0xC5, + # Search_Mark_Forward=0xC6, + # Search_Mark_Backwards=0xC7, + # Counter_Reset=0xC8, + # Show_Counter=0xC9, + # Tracking_Increment=0xCA, + # Tracking_Decrement=0xCB, + # Stop__Eject=0xCC, + "Play__Pause": 0xCD, + # Play__Skip=0xCE, + "Volume": 0xE0, + # Balance=0xE1, + "Mute": 0xE2, + # Bass=0xE3, + # Treble=0xE4, + "Bass_Boost": 0xE5, + # Surround_Mode=0xE6, + # Loudness=0xE7, + # MPX=0xE8, + "Volume_Up": 0xE9, + "Volume_Down": 0xEA, + # Speed_Select=0xF0, + # Playback_Speed=0xF1, + # Standard_Play=0xF2, + # Long_Play=0xF3, + # Extended_Play=0xF4, + "Slow": 0xF5, + "Fan_Enable": 0x100, + "Fan_Speed": 0x101, + "Light": 0x102, + "Light_Illumination_Level": 0x103, + "Climate_Control_Enable": 0x104, + "Room_Temperature": 0x105, + "Security_Enable": 0x106, + "Fire_Alarm": 0x107, + "Police_Alarm": 0x108, + "Proximity": 0x109, + "Motion": 0x10A, + "Duress_Alarm": 0x10B, + "Holdup_Alarm": 0x10C, + "Medical_Alarm": 0x10D, + "Balance_Right": 0x150, + "Balance_Left": 0x151, + "Bass_Increment": 0x152, + "Bass_Decrement": 0x153, + "Treble_Increment": 0x154, + "Treble_Decrement": 0x155, + "Speaker_System": 0x160, + "Channel_Left": 0x161, + "Channel_Right": 0x162, + "Channel_Center": 0x163, + "Channel_Front": 0x164, + "Channel_Center_Front": 0x165, + "Channel_Side": 0x166, + "Channel_Surround": 0x167, + "Channel_Low_Frequency_Enhancement": 0x168, + "Channel_Top": 0x169, + "Channel_Unknown": 0x16A, + "Subchannel": 0x170, + "Subchannel_Increment": 0x171, + "Subchannel_Decrement": 0x172, + "Alternate_Audio_Increment": 0x173, + "Alternate_Audio_Decrement": 0x174, + "Application_Launch_Buttons": 0x180, + "AL_Launch_Button_Configuration_Tool": 0x181, + "AL_Programmable_Button_Configuration": 0x182, + "AL_Consumer_Control_Configuration": 0x183, + "AL_Word_Processor": 0x184, + "AL_Text_Editor": 0x185, + "AL_Spreadsheet": 0x186, + "AL_Graphics_Editor": 0x187, + "AL_Presentation_App": 0x188, + "AL_Database_App": 0x189, + "AL_Email_Reader": 0x18A, + "AL_Newsreader": 0x18B, + "AL_Voicemail": 0x18C, + "AL_Contacts__Address_Book": 0x18D, + "AL_Calendar__Schedule": 0x18E, + "AL_Task__Project_Manager": 0x18F, + "AL_Log__Journal__Timecard": 0x190, + "AL_Checkbook__Finance": 0x191, + "AL_Calculator": 0x192, + "AL_A__V_Capture__Playback": 0x193, + "AL_Local_Machine_Browser": 0x194, + "AL_LAN__WAN_Browser": 0x195, + "AL_Internet_Browser": 0x196, + "AL_Remote_Networking__ISP_Connect": 0x197, + "AL_Network_Conference": 0x198, + "AL_Network_Chat": 0x199, + "AL_Telephony__Dialer": 0x19A, + "AL_Logon": 0x19B, + "AL_Logoff": 0x19C, + "AL_Logon__Logoff": 0x19D, + "AL_Terminal_Lock__Screensaver": 0x19E, + "AL_Control_Panel": 0x19F, + "AL_Command_Line_Processor__Run": 0x1A0, + "AL_Process__Task_Manager": 0x1A1, + "AL_Select_Tast__Application": 0x1A2, + "AL_Next_Task__Application": 0x1A3, + "AL_Previous_Task__Application": 0x1A4, + "AL_Preemptive_Halt_Task__Application": 0x1A5, + "AL_Integrated_Help_Center": 0x1A6, + "AL_Documents": 0x1A7, + "AL_Thesaurus": 0x1A8, + "AL_Dictionary": 0x1A9, + "AL_Desktop": 0x1AA, + "AL_Spell_Check": 0x1AB, + "AL_Grammar_Check": 0x1AC, + "AL_Wireless_Status": 0x1AD, + "AL_Keyboard_Layout": 0x1AE, + "AL_Virus_Protection": 0x1AF, + "AL_Encryption": 0x1B0, + "AL_Screen_Saver": 0x1B1, + "AL_Alarms": 0x1B2, + "AL_Clock": 0x1B3, + "AL_File_Browser": 0x1B4, + "AL_Power_Status": 0x1B5, + "AL_Image_Browser": 0x1B6, + "AL_Audio_Browser": 0x1B7, + "AL_Movie_Browser": 0x1B8, + "AL_Digital_Rights_Manager": 0x1B9, + "AL_Digital_Wallet": 0x1BA, + "AL_Instant_Messaging": 0x1BC, + "AL_OEM_Features___Tips__Tutorial_Browser": 0x1BD, + "AL_OEM_Help": 0x1BE, + "AL_Online_Community": 0x1BF, + "AL_Entertainment_Content_Browser": 0x1C0, + "AL_Online_Shopping_Browser": 0x1C1, + "AL_SmartCard_Information__Help": 0x1C2, + "AL_Market_Monitor__Finance_Browser": 0x1C3, + "AL_Customized_Corporate_News_Browser": 0x1C4, + "AL_Online_Activity_Browser": 0x1C5, + "AL_Research__Search_Browser": 0x1C6, + "AL_Audio_Player": 0x1C7, + "Generic_GUI_Application_Controls": 0x200, + "AC_New": 0x201, + "AC_Open": 0x202, + "AC_Close": 0x203, + "AC_Exit": 0x204, + "AC_Maximize": 0x205, + "AC_Minimize": 0x206, + "AC_Save": 0x207, + "AC_Print": 0x208, + "AC_Properties": 0x209, + "AC_Undo": 0x21A, + "AC_Copy": 0x21B, + "AC_Cut": 0x21C, + "AC_Paste": 0x21D, + "AC_Select_All": 0x21E, + "AC_Find": 0x21F, + "AC_Find_and_Replace": 0x220, + "AC_Search": 0x221, + "AC_Go_To": 0x222, + "AC_Home": 0x223, + "AC_Back": 0x224, + "AC_Forward": 0x225, + "AC_Stop": 0x226, + "AC_Refresh": 0x227, + "AC_Previous_Link": 0x228, + "AC_Next_Link": 0x229, + "AC_Bookmarks": 0x22A, + "AC_History": 0x22B, + "AC_Subscriptions": 0x22C, + "AC_Zoom_In": 0x22D, + "AC_Zoom_Out": 0x22E, + "AC_Zoom": 0x22F, + "AC_Full_Screen_View": 0x230, + "AC_Normal_View": 0x231, + "AC_View_Toggle": 0x232, + "AC_Scroll_Up": 0x233, + "AC_Scroll_Down": 0x234, + "AC_Scroll": 0x235, + "AC_Pan_Left": 0x236, + "AC_Pan_Right": 0x237, + "AC_Pan": 0x238, + "AC_New_Window": 0x239, + "AC_Tile_Horizontally": 0x23A, + "AC_Tile_Vertically": 0x23B, + "AC_Format": 0x23C, + "AC_Edit": 0x23D, + "AC_Bold": 0x23E, + "AC_Italics": 0x23F, + "AC_Underline": 0x240, + "AC_Strikethrough": 0x241, + "AC_Subscript": 0x242, + "AC_Superscript": 0x243, + "AC_All_Caps": 0x244, + "AC_Rotate": 0x245, + "AC_Resize": 0x246, + "AC_Flip_horizontal": 0x247, + "AC_Flip_Vertical": 0x248, + "AC_Mirror_Horizontal": 0x249, + "AC_Mirror_Vertical": 0x24A, + "AC_Font_Select": 0x24B, + "AC_Font_Color": 0x24C, + "AC_Font_Size": 0x24D, + "AC_Justify_Left": 0x24E, + "AC_Justify_Center_H": 0x24F, + "AC_Justify_Right": 0x250, + "AC_Justify_Block_H": 0x251, + "AC_Justify_Top": 0x252, + "AC_Justify_Center_V": 0x253, + "AC_Justify_Bottom": 0x254, + "AC_Justify_Block_V": 0x255, + "AC_Indent_Decrease": 0x256, + "AC_Indent_Increase": 0x257, + "AC_Numbered_List": 0x258, + "AC_Restart_Numbering": 0x259, + "AC_Bulleted_List": 0x25A, + "AC_Promote": 0x25B, + "AC_Demote": 0x25C, + "AC_Yes": 0x25D, + "AC_No": 0x25E, + "AC_Cancel": 0x25F, + "AC_Catalog": 0x260, + "AC_Buy__Checkout": 0x261, + "AC_Add_to_Cart": 0x262, + "AC_Expand": 0x263, + "AC_Expand_All": 0x264, + "AC_Collapse": 0x265, + "AC_Collapse_All": 0x266, + "AC_Print_Preview": 0x267, + "AC_Paste_Special": 0x268, + "AC_Insert_Mode": 0x269, + "AC_Delete": 0x26A, + "AC_Lock": 0x26B, + "AC_Unlock": 0x26C, + "AC_Protect": 0x26D, + "AC_Unprotect": 0x26E, + "AC_Attach_Comment": 0x26F, + "AC_Delete_Comment": 0x270, + "AC_View_Comment": 0x271, + "AC_Select_Word": 0x272, + "AC_Select_Sentence": 0x273, + "AC_Select_Paragraph": 0x274, + "AC_Select_Column": 0x275, + "AC_Select_Row": 0x276, + "AC_Select_Table": 0x277, + "AC_Select_Object": 0x278, + "AC_Redo__Repeat": 0x279, + "AC_Sort": 0x27A, + "AC_Sort_Ascending": 0x27B, + "AC_Sort_Descending": 0x27C, + "AC_Filter": 0x27D, + "AC_Set_Clock": 0x27E, + "AC_View_Clock": 0x27F, + "AC_Select_Time_Zone": 0x280, + "AC_Edit_Time_Zones": 0x281, + "AC_Set_Alarm": 0x282, + "AC_Clear_Alarm": 0x283, + "AC_Snooze_Alarm": 0x284, + "AC_Reset_Alarm": 0x285, + "AC_Synchronize": 0x286, + "AC_Send__Receive": 0x287, + "AC_Send_To": 0x288, + "AC_Reply": 0x289, + "AC_Reply_All": 0x28A, + "AC_Forward_Msg": 0x28B, + "AC_Send": 0x28C, + "AC_Attach_File": 0x28D, + "AC_Upload": 0x28E, + "AC_Download_Save_Target_As": 0x28F, + "AC_Set_Borders": 0x290, + "AC_Insert_Row": 0x291, + "AC_Insert_Column": 0x292, + "AC_Insert_File": 0x293, + "AC_Insert_Picture": 0x294, + "AC_Insert_Object": 0x295, + "AC_Insert_Symbol": 0x296, + "AC_Save_and_Close": 0x297, + "AC_Rename": 0x298, + "AC_Merge": 0x299, + "AC_Split": 0x29A, + "AC_Distribute_Horizontally": 0x29B, + "AC_Distribute_Vertically": 0x29C, + } +) +HID_CONSUMERCODES[0x20] = "+10" +HID_CONSUMERCODES[0x21] = "+100" +HID_CONSUMERCODES._fallback = lambda x: f"unknown:{x:04X}" ## Information for x1c00 Persistent from https://drive.google.com/drive/folders/0BxbRzx7vEV7eWmgwazJ3NUFfQ28 -KEYMOD = _NamedInts(CTRL=0x01, SHIFT=0x02, ALT=0x04, META=0x08, RCTRL=0x10, RSHIFT=0x20, RALT=0x40, RMETA=0x80) +KEYMOD = NamedInts(CTRL=0x01, SHIFT=0x02, ALT=0x04, META=0x08, RCTRL=0x10, RSHIFT=0x20, RALT=0x40, RMETA=0x80) -ACTIONID = _NamedInts( +ACTIONID = NamedInts( Empty=0x00, Key=0x01, Mouse=0x02, @@ -1170,10 +1197,10 @@ ACTIONID = _NamedInts( Hscroll=0x06, Consumer=0x07, Internal=0x08, - Power=0x09 + Power=0x09, ) -MOUSE_BUTTONS = _NamedInts( +MOUSE_BUTTONS = NamedInts( Mouse_Button_Left=0x0001, Mouse_Button_Right=0x0002, Mouse_Button_Middle=0x0004, @@ -1191,33 +1218,33 @@ MOUSE_BUTTONS = _NamedInts( Mouse_Button_15=0x4000, Mouse_Button_16=0x8000, ) -MOUSE_BUTTONS._fallback = lambda x: 'unknown mouse button:%04X' % x +MOUSE_BUTTONS._fallback = lambda x: f"unknown mouse button:{x:04X}" + + +class HorizontalScroll(IntEnum): + Left = 0x4000 + Right = 0x8000 -HORIZONTAL_SCROLL = _NamedInts( - Horizontal_Scroll_Left=0x4000, - Horizontal_Scroll_Right=0x8000, -) -HORIZONTAL_SCROLL._fallback = lambda x: 'unknown horizontal scroll:%04X' % x # Construct universe for Persistent Remappable Keys setting (only for supported values) -KEYS = _UnsortedNamedInts() +KEYS = UnsortedNamedInts() KEYS_Default = 0x7FFFFFFF # Special value to reset key to default - has to be different from all others -KEYS[KEYS_Default] = 'Default' # Value to reset to default -KEYS[0] = 'None' # Value for no output +KEYS[KEYS_Default] = "Default" # Value to reset to default +KEYS[0] = "None" # Value for no output # Add HID keys plus modifiers modifiers = { - 0x00: '', - 0x01: 'Cntrl+', - 0x02: 'Shift+', - 0x04: 'Alt+', - 0x08: 'Meta+', - 0x03: 'Cntrl+Shift+', - 0x05: 'Alt+Cntrl+', - 0x09: 'Meta+Cntrl+', - 0x06: 'Alt+Shift+', - 0x0A: 'Meta+Shift+', - 0x0C: 'Meta+Alt+' + 0x00: "", + 0x01: "Cntrl+", + 0x02: "Shift+", + 0x04: "Alt+", + 0x08: "Meta+", + 0x03: "Cntrl+Shift+", + 0x05: "Alt+Cntrl+", + 0x09: "Meta+Cntrl+", + 0x06: "Alt+Shift+", + 0x0A: "Meta+Shift+", + 0x0C: "Meta+Alt+", } for val, name in modifiers.items(): for key in USB_HID_KEYCODES: @@ -1232,15 +1259,15 @@ for code in MOUSE_BUTTONS: KEYS[(ACTIONID.Mouse << 24) + (int(code) << 8)] = str(code) # Add Horizontal Scroll -for code in HORIZONTAL_SCROLL: +for code in HorizontalScroll: KEYS[(ACTIONID.Hscroll << 24) + (int(code) << 8)] = str(code) # Construct subsets for known devices def persistent_keys(action_ids): - keys = _UnsortedNamedInts() - keys[KEYS_Default] = 'Default' # Value to reset to default - keys[0] = 'No Output (only as default)' + keys = UnsortedNamedInts() + keys[KEYS_Default] = "Default" # Value to reset to default + keys[0] = "No Output (only as default)" for key in KEYS: if (int(key) >> 24) in action_ids: keys[int(key)] = str(key) @@ -1249,3 +1276,285 @@ def persistent_keys(action_ids): KEYS_KEYS_CONSUMER = persistent_keys([ACTIONID.Key, ACTIONID.Consumer]) KEYS_KEYS_MOUSE_HSCROLL = persistent_keys([ACTIONID.Key, ACTIONID.Mouse, ACTIONID.Hscroll]) + +COLORS = UnsortedNamedInts( + { + # from Xorg rgb.txt,v 1.3 2000/08/17 + "red": 0xFF0000, + "orange": 0xFFA500, + "yellow": 0xFFFF00, + "green": 0x00FF00, + "blue": 0x0000FF, + "purple": 0xA020F0, + "violet": 0xEE82EE, + "black": 0x000000, + "white": 0xFFFFFF, + "gray": 0xBEBEBE, + "brown": 0xA52A2A, + "cyan": 0x00FFFF, + "magenta": 0xFF00FF, + "pink": 0xFFC0CB, + "maroon": 0xB03060, + "turquoise": 0x40E0D0, + "gold": 0xFFD700, + "tan": 0xD2B48C, + "snow": 0xFFFAFA, + "ghost white": 0xF8F8FF, + "white smoke": 0xF5F5F5, + "gainsboro": 0xDCDCDC, + "floral white": 0xFFFAF0, + "old lace": 0xFDF5E6, + "linen": 0xFAF0E6, + "antique white": 0xFAEBD7, + "papaya whip": 0xFFEFD5, + "blanched almond": 0xFFEBCD, + "bisque": 0xFFE4C4, + "peach puff": 0xFFDAB9, + "navajo white": 0xFFDEAD, + "moccasin": 0xFFE4B5, + "cornsilk": 0xFFF8DC, + "ivory": 0xFFFFF0, + "lemon chiffon": 0xFFFACD, + "seashell": 0xFFF5EE, + "honeydew": 0xF0FFF0, + "mint cream": 0xF5FFFA, + "azure": 0xF0FFFF, + "alice blue": 0xF0F8FF, + "lavender": 0xE6E6FA, + "lavender blush": 0xFFF0F5, + "misty rose": 0xFFE4E1, + "dark slate gray": 0x2F4F4F, + "dim gray": 0x696969, + "slate gray": 0x708090, + "light slate gray": 0x778899, + "light gray": 0xD3D3D3, + "midnight blue": 0x191970, + "navy blue": 0x000080, + "cornflower blue": 0x6495ED, + "dark slate blue": 0x483D8B, + "slate blue": 0x6A5ACD, + "medium slate blue": 0x7B68EE, + "light slate blue": 0x8470FF, + "medium blue": 0x0000CD, + "royal blue": 0x4169E1, + "dodger blue": 0x1E90FF, + "deep sky blue": 0x00BFFF, + "sky blue": 0x87CEEB, + "light sky blue": 0x87CEFA, + "steel blue": 0x4682B4, + "light steel blue": 0xB0C4DE, + "light blue": 0xADD8E6, + "powder blue": 0xB0E0E6, + "pale turquoise": 0xAFEEEE, + "dark turquoise": 0x00CED1, + "medium turquoise": 0x48D1CC, + "light cyan": 0xE0FFFF, + "cadet blue": 0x5F9EA0, + "medium aquamarine": 0x66CDAA, + "aquamarine": 0x7FFFD4, + "dark green": 0x006400, + "dark olive green": 0x556B2F, + "dark sea green": 0x8FBC8F, + "sea green": 0x2E8B57, + "medium sea green": 0x3CB371, + "light sea green": 0x20B2AA, + "pale green": 0x98FB98, + "spring green": 0x00FF7F, + "lawn green": 0x7CFC00, + "chartreuse": 0x7FFF00, + "medium spring green": 0x00FA9A, + "green yellow": 0xADFF2F, + "lime green": 0x32CD32, + "yellow green": 0x9ACD32, + "forest green": 0x228B22, + "olive drab": 0x6B8E23, + "dark khaki": 0xBDB76B, + "khaki": 0xF0E68C, + "pale goldenrod": 0xEEE8AA, + "light goldenrod yellow": 0xFAFAD2, + "light yellow": 0xFFFFE0, + "light goldenrod": 0xEEDD82, + "goldenrod": 0xDAA520, + "dark goldenrod": 0xB8860B, + "rosy brown": 0xBC8F8F, + "indian red": 0xCD5C5C, + "saddle brown": 0x8B4513, + "sienna": 0xA0522D, + "peru": 0xCD853F, + "burlywood": 0xDEB887, + "beige": 0xF5F5DC, + "wheat": 0xF5DEB3, + "sandy brown": 0xF4A460, + "chocolate": 0xD2691E, + "firebrick": 0xB22222, + "dark salmon": 0xE9967A, + "salmon": 0xFA8072, + "light salmon": 0xFFA07A, + "dark orange": 0xFF8C00, + "coral": 0xFF7F50, + "light coral": 0xF08080, + "tomato": 0xFF6347, + "orange red": 0xFF4500, + "hot pink": 0xFF69B4, + "deep pink": 0xFF1493, + "light pink": 0xFFB6C1, + "pale violet red": 0xDB7093, + "medium violet red": 0xC71585, + "violet red": 0xD02090, + "plum": 0xDDA0DD, + "orchid": 0xDA70D6, + "medium orchid": 0xBA55D3, + "dark orchid": 0x9932CC, + "dark violet": 0x9400D3, + "blue violet": 0x8A2BE2, + "medium purple": 0x9370DB, + "thistle": 0xD8BFD8, + "dark gray": 0xA9A9A9, + "dark blue": 0x00008B, + "dark cyan": 0x008B8B, + "dark magenta": 0x8B008B, + "dark red": 0x8B0000, + "light green": 0x90EE90, + } +) + +COLORSPLUS = UnsortedNamedInts({"No change": -1}) +for i in COLORS: + COLORSPLUS[int(i)] = str(i) + +KEYCODES = NamedInts( + { + "A": 1, + "B": 2, + "C": 3, + "D": 4, + "E": 5, + "F": 6, + "G": 7, + "H": 8, + "I": 9, + "J": 10, + "K": 11, + "L": 12, + "M": 13, + "N": 14, + "O": 15, + "P": 16, + "Q": 17, + "R": 18, + "S": 19, + "T": 20, + "U": 21, + "V": 22, + "W": 23, + "X": 24, + "Y": 25, + "Z": 26, + "1": 27, + "2": 28, + "3": 29, + "4": 30, + "5": 31, + "6": 32, + "7": 33, + "8": 34, + "9": 35, + "0": 36, + "ENTER": 37, + "ESC": 38, + "BACKSPACE": 39, + "TAB": 40, + "SPACE": 41, + "-": 42, + "=": 43, + "[": 44, + "]": 45, + "\\": 45, + "~": 47, + ";": 48, + "'": 49, + "`": 50, + ",": 51, + ".": 52, + "/": 53, + "CAPS LOCK": 54, + "F1": 55, + "F2": 56, + "F3": 57, + "F4": 58, + "F5": 59, + "F6": 60, + "F7": 61, + "F8": 62, + "F9": 63, + "F10": 64, + "F11": 65, + "F12": 66, + "PRINT": 67, + "SCROLL LOCK": 68, + "PASTE": 69, + "INSERT": 70, + "HOME": 71, + "PAGE UP": 72, + "DELETE": 73, + "END": 74, + "PAGE DOWN": 75, + "RIGHT": 76, + "LEFT": 77, + "DOWN": 78, + "UP": 79, + "NUMLOCK": 80, + "KEYPAD /": 81, + "KEYPAD *": 82, + "KEYPAD -": 83, + "KEYPAD +": 84, + "KEYPAD ENTER": 85, + "KEYPAD 1": 86, + "KEYPAD 2": 87, + "KEYPAD 3": 88, + "KEYPAD 4": 89, + "KEYPAD 5": 90, + "KEYPAD 6": 91, + "KEYPAD 7": 92, + "KEYPAD 8": 93, + "KEYPAD 9": 94, + "KEYPAD 0": 95, + "KEYPAD .": 96, + "COMPOSE": 98, + "POWER": 99, + "LEFT CTRL": 104, + "LEFT SHIFT": 105, + "LEFT ALT": 106, + "LEFT WINDOWS": 107, + "RIGHT CTRL": 108, + "RIGHT SHIFT": 109, + "RIGHT ALTGR": 110, + "RIGHT WINDOWS": 111, + "BRIGHTNESS": 153, + "PAUSE": 155, + "MUTE": 156, + "NEXT": 157, + "PREVIOUS": 158, + "G1": 180, + "G2": 181, + "G3": 182, + "G4": 183, + "G5": 184, + "LOGO": 210, + } +) + + +# load in override dictionary for KEYCODES +try: + if os.path.isfile(_keys_file_path): + with open(_keys_file_path) as keys_file: + keys = yaml.safe_load(keys_file) + if isinstance(keys, dict): + keys = NamedInts(**keys) + for k in KEYCODES: + if int(k) not in keys and str(k) not in keys: + keys[int(k)] = str(k) + KEYCODES = keys +except Exception as e: + print(e) diff --git a/lib/logitech_receiver/status.py b/lib/logitech_receiver/status.py deleted file mode 100644 index 99d7343a..00000000 --- a/lib/logitech_receiver/status.py +++ /dev/null @@ -1,354 +0,0 @@ -# -*- python-mode -*- - -## Copyright (C) 2012-2013 Daniel Pavel -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License along -## with this program; if not, write to the Free Software Foundation, Inc., -## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -from logging import DEBUG as _DEBUG -from logging import INFO as _INFO -from logging import getLogger -from time import time as _timestamp - -from . import hidpp10 as _hidpp10 -from . import hidpp20 as _hidpp20 -from . import settings as _settings -from .common import BATTERY_APPROX as _BATTERY_APPROX -from .common import NamedInt as _NamedInt -from .common import NamedInts as _NamedInts -from .i18n import _, ngettext - -_log = getLogger(__name__) -del getLogger - -_R = _hidpp10.REGISTERS - -# -# -# - -ALERT = _NamedInts(NONE=0x00, NOTIFICATION=0x01, SHOW_WINDOW=0x02, ATTENTION=0x04, ALL=0xFF) - -KEYS = _NamedInts( - BATTERY_LEVEL=1, - BATTERY_CHARGING=2, - BATTERY_STATUS=3, - LIGHT_LEVEL=4, - LINK_ENCRYPTED=5, - NOTIFICATION_FLAGS=6, - ERROR=7, - BATTERY_NEXT_LEVEL=8, - BATTERY_VOLTAGE=9, -) - -# If the battery charge is under this percentage, trigger an attention event -# (blink systray icon/notification/whatever). -_BATTERY_ATTENTION_LEVEL = 5 - -# If no updates have been receiver from the device for a while, ping the device -# and update it status accordingly. -# _STATUS_TIMEOUT = 5 * 60 # seconds -_LONG_SLEEP = 15 * 60 # seconds - -# -# -# - - -def attach_to(device, changed_callback): - assert device - assert changed_callback - - if not hasattr(device, 'status') or device.status is None: - if not device.isDevice: - device.status = ReceiverStatus(device, changed_callback) - else: - device.status = DeviceStatus(device, changed_callback) - - -# -# -# - - -class ReceiverStatus(dict): - """The 'runtime' status of a receiver, mostly about the pairing process -- - is the pairing lock open or closed, any pairing errors, etc. - """ - - def __init__(self, receiver, changed_callback): - assert receiver - self._receiver = receiver - - assert changed_callback - self._changed_callback = changed_callback - - # self.updated = 0 - - self.lock_open = False - self.discovering = False - self.counter = None - self.device_address = None - self.device_authentication = None - self.device_kind = None - self.device_name = None - self.device_passkey = None - self.new_device = None - - self[KEYS.ERROR] = None - - def __str__(self): - count = len(self._receiver) - return ( - _('No paired devices.') - if count == 0 else ngettext('%(count)s paired device.', '%(count)s paired devices.', count) % { - 'count': count - } - ) - - def changed(self, alert=ALERT.NOTIFICATION, reason=None): - # self.updated = _timestamp() - self._changed_callback(self._receiver, alert=alert, reason=reason) - - # def poll(self, timestamp): - # r = self._receiver - # assert r - # - # if _log.isEnabledFor(_DEBUG): - # _log.debug("polling status of %s", r) - # - # # make sure to read some stuff that may be read later by the UI - # r.serial, r.firmware, None - # - # # get an update of the notification flags - # # self[KEYS.NOTIFICATION_FLAGS] = _hidpp10.get_notification_flags(r) - - -# -# -# - - -class DeviceStatus(dict): - """Holds the 'runtime' status of a peripheral -- things like - active/inactive, battery charge, lux, etc. It updates them mostly by - processing incoming notification events from the device itself. - """ - - def __init__(self, device, changed_callback): - assert device - self._device = device - - assert changed_callback - self._changed_callback = changed_callback - - # is the device active? - self._active = None - - # timestamp of when this status object was last updated - self.updated = 0 - - def to_string(self): - - def _items(): - comma = False - - battery_level = self.get(KEYS.BATTERY_LEVEL) - if battery_level is not None: - if isinstance(battery_level, _NamedInt): - yield _('Battery: %(level)s') % {'level': _(str(battery_level))} - else: - yield _('Battery: %(percent)d%%') % {'percent': battery_level} - - battery_status = self.get(KEYS.BATTERY_STATUS) - if battery_status is not None: - yield ' (%s)' % _(str(battery_status)) - - comma = True - - light_level = self.get(KEYS.LIGHT_LEVEL) - if light_level is not None: - if comma: - yield ', ' - yield _('Lighting: %(level)s lux') % {'level': light_level} - - return ''.join(i for i in _items()) - - def __repr__(self): - return '{' + ', '.join('\'%s\': %r' % (k, v) for k, v in self.items()) + '}' - - def __bool__(self): - return bool(self._active) - - __nonzero__ = __bool__ - - def set_battery_info(self, level, nextLevel, status, voltage, timestamp=None): - if _log.isEnabledFor(_DEBUG): - _log.debug('%s: battery %s, %s', self._device, level, status) - - if level is None: - # Some notifications may come with no battery level info, just - # charging state info, so do our best to infer a level (even if it is just the last level) - # It is not always possible to do this well - if status == _hidpp20.BATTERY_STATUS.full: - level = _BATTERY_APPROX.full - elif status in (_hidpp20.BATTERY_STATUS.almost_full, _hidpp20.BATTERY_STATUS.recharging): - level = _BATTERY_APPROX.good - elif status == _hidpp20.BATTERY_STATUS.slow_recharge: - level = _BATTERY_APPROX.low - else: - level = self.get(KEYS.BATTERY_LEVEL) - else: - assert isinstance(level, int) - - # TODO: this is also executed when pressing Fn+F7 on K800. - old_level, self[KEYS.BATTERY_LEVEL] = self.get(KEYS.BATTERY_LEVEL), level - old_status, self[KEYS.BATTERY_STATUS] = self.get(KEYS.BATTERY_STATUS), status - self[KEYS.BATTERY_NEXT_LEVEL] = nextLevel - old_voltage, self[KEYS.BATTERY_VOLTAGE] = self.get(KEYS.BATTERY_VOLTAGE), voltage - - charging = status in ( - _hidpp20.BATTERY_STATUS.recharging, _hidpp20.BATTERY_STATUS.almost_full, _hidpp20.BATTERY_STATUS.full, - _hidpp20.BATTERY_STATUS.slow_recharge - ) - old_charging, self[KEYS.BATTERY_CHARGING] = self.get(KEYS.BATTERY_CHARGING), charging - - changed = old_level != level or old_status != status or old_charging != charging or old_voltage != voltage - alert, reason = ALERT.NONE, None - - if _hidpp20.BATTERY_OK(status) and (level is None or level > _BATTERY_ATTENTION_LEVEL): - self[KEYS.ERROR] = None - else: - _log.warn('%s: battery %d%%, ALERT %s', self._device, level, status) - if self.get(KEYS.ERROR) != status: - self[KEYS.ERROR] = status - # only show the notification once - alert = ALERT.NOTIFICATION | ALERT.ATTENTION - if isinstance(level, _NamedInt): - reason = _('Battery: %(level)s (%(status)s)') % {'level': _(level), 'status': _(status)} - else: - reason = _('Battery: %(percent)d%% (%(status)s)') % {'percent': level, 'status': status.name} - - if changed or reason or not self._active: # a battery response means device is active - # update the leds on the device, if any - _hidpp10.set_3leds(self._device, level, charging=charging, warning=bool(alert)) - self.changed(active=True, alert=alert, reason=reason, timestamp=timestamp) - - # Retrieve and regularize battery status - def read_battery(self, timestamp=None): - if self._active: - assert self._device - battery = self._device.battery() - self.set_battery_keys(battery) - - def set_battery_keys(self, battery): - if battery is not None: - level, nextLevel, status, voltage = battery - self.set_battery_info(level, nextLevel, status, voltage) - elif self.get(KEYS.BATTERY_STATUS, None) is not None: - self[KEYS.BATTERY_STATUS] = None - self[KEYS.BATTERY_CHARGING] = None - self[KEYS.BATTERY_VOLTAGE] = None - self.changed() - - def changed(self, active=None, alert=ALERT.NONE, reason=None, push=False, timestamp=None): - assert self._changed_callback - d = self._device - # assert d # may be invalid when processing the 'unpaired' notification - timestamp = timestamp or _timestamp() - - if active is not None: - d.online = active - was_active, self._active = self._active, active - if active: - if not was_active: - # Make sure to set notification flags on the device, they - # get cleared when the device is turned off (but not when the device - # goes idle, and we can't tell the difference right now). - if d.protocol < 2.0: - self[KEYS.NOTIFICATION_FLAGS] = d.enable_connection_notifications() - - # battery information may have changed so try to read it now - self.read_battery(timestamp) - - # Push settings for new devices (was_active is None), - # when devices request software reconfiguration - # and when devices become active if they don't have wireless device status feature, - if was_active is None or push or not was_active and ( - not d.features or _hidpp20.FEATURE.WIRELESS_DEVICE_STATUS not in d.features - ): - if _log.isEnabledFor(_INFO): - _log.info('%s pushing device settings %s', d, d.settings) - _settings.apply_all_settings(d) - - else: - if was_active: # don't clear status when devices go inactive - ## battery = self.get(KEYS.BATTERY_LEVEL) - ## self.clear() - ## # If we had a known battery level before, assume it's not going - ## # to change much while the device is offline. - ## if battery is not None: - ## self[KEYS.BATTERY_LEVEL] = battery - pass - - # A device that is not active on the first status notification - # but becomes active afterwards does not produce a pop-up notification - # so don't produce one here. This cuts off pop-ups when Solaar starts, - # which can be problematic if Solaar is autostarted. - ## if self.updated == 0 and active is True: - ## if the device is active on the very first status notification, - ## (meaning just when the program started or a new receiver was just - ## detected), pop up a notification about it - ## alert |= ALERT.NOTIFICATION - - self.updated = timestamp - - # if _log.isEnabledFor(_DEBUG): - # _log.debug("device %d changed: active=%s %s", d.number, self._active, dict(self)) - self._changed_callback(d, alert, reason) - - # def poll(self, timestamp): - # d = self._device - # if not d: - # _log.error("polling status of invalid device") - # return - # - # if self._active: - # if _log.isEnabledFor(_DEBUG): - # _log.debug("polling status of %s", d) - # - # # read these from the device, the UI may need them later - # d.protocol, d.serial, d.firmware, d.kind, d.name, d.settings, None - # - # # make sure we know all the features of the device - # # if d.features: - # # d.features[:] - # - # # devices may go out-of-range while still active, or the computer - # # may go to sleep and wake up without the devices available - # if timestamp - self.updated > _STATUS_TIMEOUT: - # if d.ping(): - # timestamp = self.updated = _timestamp() - # else: - # self.changed(active=False, reason='out of range') - # - # # if still active, make sure we know the battery level - # if KEYS.BATTERY_LEVEL not in self: - # self.read_battery(timestamp) - # - # elif timestamp - self.updated > _STATUS_TIMEOUT: - # if d.ping(): - # self.changed(active=True) - # else: - # self.updated = _timestamp() diff --git a/lib/solaar/__init__.py b/lib/solaar/__init__.py index 5d443f56..292a70a1 100644 --- a/lib/solaar/__init__.py +++ b/lib/solaar/__init__.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -16,17 +14,28 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import pkgutil as _pkgutil -import subprocess as _subprocess -import sys as _sys +import pkgutil +import subprocess +import sys -NAME = 'solaar' +NAME = "Solaar" try: - __version__ = _subprocess.check_output(['git', 'describe', '--always'], cwd=_sys.path[0], - stderr=_subprocess.DEVNULL).strip().decode() + __version__ = ( + subprocess.check_output( + [ + "git", + "describe", + "--always", + ], + cwd=sys.path[0], + stderr=subprocess.DEVNULL, + ) + .strip() + .decode() + ) except Exception: try: - __version__ = _pkgutil.get_data('solaar', 'commit').strip().decode() + __version__ = pkgutil.get_data("solaar", "commit").strip().decode() except Exception: - __version__ = _pkgutil.get_data('solaar', 'version').strip().decode() + __version__ = pkgutil.get_data("solaar", "version").strip().decode() diff --git a/lib/solaar/cli/__init__.py b/lib/solaar/cli/__init__.py index ace57f90..3e4928c9 100644 --- a/lib/solaar/cli/__init__.py +++ b/lib/solaar/cli/__init__.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -16,79 +14,112 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import argparse as _argparse -import sys as _sys +import argparse +import logging +import sys -from logging import DEBUG as _DEBUG -from logging import getLogger +from importlib import import_module +from traceback import extract_tb +from traceback import format_exc + +from logitech_receiver import base +from logitech_receiver import device +from logitech_receiver import receiver from solaar import NAME -_log = getLogger(__name__) -del getLogger - -# -# -# +logger = logging.getLogger(__name__) def _create_parser(): - parser = _argparse.ArgumentParser( + parser = argparse.ArgumentParser( prog=NAME.lower(), add_help=False, - epilog='For details on individual actions, run `%s --help`.' % NAME.lower() + epilog=f"For details on individual actions, run `{NAME.lower()} --help`.", ) - subparsers = parser.add_subparsers(title='actions', help='optional action to perform') + subparsers = parser.add_subparsers(title="actions", help="command-line action to perform") - sp = subparsers.add_parser('show', help='show information about devices') + sp = subparsers.add_parser("show", description="Show information about device or all devices.") sp.add_argument( - 'device', - nargs='?', - default='all', - help='device to show information about; may be a device number (1..6), a serial number, ' - 'a substring of a device\'s name, or "all" (the default)' + "device", + nargs="?", + default="all", + help="device to show information about; may be a device number (1..6), a serial number, " + 'a substring of a device\'s name, or "all" (the default)', ) - sp.set_defaults(action='show') + sp.set_defaults(action="show") - sp = subparsers.add_parser('probe', help='probe a receiver (debugging use only)') + sp = subparsers.add_parser("probe", description="Probe a receiver (debugging use only).") sp.add_argument( - 'receiver', nargs='?', help='select receiver by name substring or serial number when more than one is present' + "receiver", nargs="?", help="select receiver by name substring or serial number when more than one is present" ) - sp.set_defaults(action='probe') + sp.set_defaults(action="probe") sp = subparsers.add_parser( - 'config', - help='read/write device-specific settings', - epilog='Please note that configuration only works on active devices.' + "profiles", + description="Print or load YAML dump of profiles.", + epilog="Only works on active devices.", ) sp.add_argument( - 'device', - help='device to configure; may be a device number (1..6), a serial number, ' - 'or a substring of a device\'s name' + "device", + help="device to read or load profiles; may be a device number (1..6), a serial number, " + "or a substring of a device's name", ) - sp.add_argument('setting', nargs='?', help='device-specific setting; leave empty to list available settings') - sp.add_argument('value_key', nargs='?', help='new value for the setting or key for keyed settings') - sp.add_argument('extra_subkey', nargs='?', help='value for keyed or subkey for subkeyed settings') - sp.add_argument('extra2', nargs='?', help='value for subkeyed settings') - sp.set_defaults(action='config') + sp.add_argument("profiles", nargs="?", help="file containing YAML dump of profiles to load") + sp.set_defaults(action="profiles") sp = subparsers.add_parser( - 'pair', - help='pair a new device', - epilog='The Logitech Unifying Receiver supports up to 6 paired devices at the same time.' + "config", + description="Print or load device-specific settings. Only some settings can be loaded. " + "Loading complex settings uses the same syntax as in ~/.config/solaar/config.yaml", + epilog="Please note that configuration only works on active devices.", ) sp.add_argument( - 'receiver', nargs='?', help='select receiver by name substring or serial number when more than one is present' + "device", + help="device to configure; may be a device number (1..6), a serial number, or a substring of a device's name", ) - sp.set_defaults(action='pair') + sp.add_argument("setting", nargs="?", help="device-specific setting; leave empty to list available settings") + sp.add_argument("value_key", nargs="?", help="new value for the setting or key for keyed settings") + sp.add_argument("extra_subkey", nargs="?", help="value for keyed or subkey for subkeyed settings") + sp.add_argument("extra2", nargs="?", help="value for subkeyed settings") + sp.set_defaults(action="config") - sp = subparsers.add_parser('unpair', help='unpair a device') - sp.add_argument( - 'device', - help='device to unpair; may be a device number (1..6), a serial number, ' - 'or a substring of a device\'s name.' + sp = subparsers.add_parser( + "pair", + description="Pair a new device with a receiver. The device has to be compatible with the receiver.", + epilog="The Logitech Unifying Receiver supports up to 6 paired devices at the same time.", ) - sp.set_defaults(action='unpair') + sp.add_argument( + "receiver", nargs="?", help="select receiver by name substring or serial number when more than one is present" + ) + sp.set_defaults(action="pair") + + sp = subparsers.add_parser("unpair", description="Unpair a device from its receiver. Not all receivers allow unpairing.") + sp.add_argument( + "device", + nargs="?", + help="device to unpair; may be a device number (1..6), a serial number, " + "or a substring of a device's name. Omit when using --slot.", + ) + sp.add_argument( + "--receiver", + help="select receiver by name substring or serial number when more than one is present; " + "required with --slot if multiple receivers are attached.", + ) + sp.add_argument( + "--slot", + type=int, + help="force-unpair a specific slot number directly, even if Solaar has no cached device there " + "or the device is currently reachable. Lightspeed receivers only. The slot contents are " + "printed before the write so you can confirm what is about to be cleared.", + ) + sp.add_argument( + "--dry-run", + action="store_true", + help="with --slot, run all safety checks but do not issue the unpair register write. " + "Use to verify the active-device guard before committing to a real write.", + ) + sp.set_defaults(action="unpair") return parser, subparsers.choices @@ -98,37 +129,42 @@ print_help = _cli_parser.print_help def _receivers(dev_path=None): - from logitech_receiver import Receiver - from logitech_receiver.base import receivers - for dev_info in receivers(): + for dev_info in base.receivers(): if dev_path is not None and dev_path != dev_info.path: continue try: - r = Receiver.open(dev_info) - if _log.isEnabledFor(_DEBUG): - _log.debug('[%s] => %s', dev_info.path, r) + r = receiver.create_receiver(base, dev_info) + logger.debug("[%s] => %s", dev_info.path, r) if r: yield r except Exception as e: - _log.exception('opening ' + str(dev_info)) - _sys.exit('%s: error: %s' % (NAME, str(e))) + logger.exception("opening " + str(dev_info)) + sys.exit(f"{NAME.lower()}: error: {str(e)}") def _receivers_and_devices(dev_path=None): - from logitech_receiver import Device, Receiver - from logitech_receiver.base import receivers_and_devices - for dev_info in receivers_and_devices(): + for dev_info in base.receivers_and_devices(): if dev_path is not None and dev_path != dev_info.path: continue try: - d = Device.open(dev_info) if dev_info.isDevice else Receiver.open(dev_info) - if _log.isEnabledFor(_DEBUG): - _log.debug('[%s] => %s', dev_info.path, d) + if dev_info.isDevice: + if getattr(dev_info, "centurion", False): + d = device.create_centurion_receiver(base, dev_info) + if d is not None: + d.notify_devices() + else: + d = device.create_device(base, dev_info) + else: + d = device.create_device(base, dev_info) + else: + d = receiver.create_receiver(base, dev_info) + + logger.debug("[%s] => %s", dev_info.path, d) if d is not None: yield d except Exception as e: - _log.exception('opening ' + str(dev_info)) - _sys.exit('%s: error: %s' % (NAME, str(e))) + logger.exception("opening " + str(dev_info)) + sys.exit(f"{NAME.lower()}: error: {str(e)}") def _find_receiver(receivers, name): @@ -169,7 +205,9 @@ def _find_device(receivers, name): for dev in r: if ( - name == dev.serial.lower() or name == dev.codename.lower() or name == str(dev.kind).lower() + name == dev.serial.lower() + or name == dev.codename.lower() + or name == str(dev.kind).lower() or name in dev.name.lower() ): yield dev @@ -178,11 +216,7 @@ def _find_device(receivers, name): break -# raise Exception("no device found matching '%s'" % name) - - def run(cli_args=None, hidraw_path=None): - if cli_args: action = cli_args[0] args = _cli_parser.parse_args(cli_args) @@ -190,29 +224,26 @@ def run(cli_args=None, hidraw_path=None): args = _cli_parser.parse_args() # Python 3 has an undocumented 'feature' that breaks parsing empty args # http://bugs.python.org/issue16308 - if 'cmd' not in args: - _cli_parser.print_usage(_sys.stderr) - _sys.stderr.write('%s: error: too few arguments\n' % NAME.lower()) - _sys.exit(2) + if "cmd" not in args: + _cli_parser.print_usage(sys.stderr) + sys.stderr.write(f"{NAME.lower()}: error: too few arguments\n") + sys.exit(2) action = args.action assert action in actions try: - if action == 'show' or action == 'probe' or action == 'config': + if action == "show" or action == "probe" or action == "config" or action == "profiles": c = list(_receivers_and_devices(hidraw_path)) else: c = list(_receivers(hidraw_path)) if not c: raise Exception( - 'No supported device found. Use "lsusb" and "bluetoothctl devices Connected" to list connected devices.' + 'No supported device found. Use "lsusb" and "bluetoothctl devices Connected" to list connected devices.' ) - from importlib import import_module - m = import_module('.' + action, package=__name__) + m = import_module("." + action, package=__name__) m.run(c, args, _find_receiver, _find_device) except AssertionError: - from traceback import extract_tb - tb_last = extract_tb(_sys.exc_info()[2])[-1] - _sys.exit('%s: assertion failed: %s line %d' % (NAME.lower(), tb_last[0], tb_last[1])) + tb_last = extract_tb(sys.exc_info()[2])[-1] + sys.exit(f"{NAME.lower()}: assertion failed: {tb_last[0]} line {int(tb_last[1])}") except Exception: - from traceback import format_exc - _sys.exit('%s: error: %s' % (NAME.lower(), format_exc())) + sys.exit(f"{NAME.lower()}: error: {format_exc()}") diff --git a/lib/solaar/cli/config.py b/lib/solaar/cli/config.py index 3230a1b2..f9dbb270 100644 --- a/lib/solaar/cli/config.py +++ b/lib/solaar/cli/config.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -16,61 +14,86 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import yaml as _yaml +import yaml -from logitech_receiver import settings as _settings -from logitech_receiver import settings_templates as _settings_templates -from logitech_receiver.common import NamedInts as _NamedInts -from solaar import configuration as _configuration +from logitech_receiver import settings +from logitech_receiver import settings_templates +from logitech_receiver.common import NamedInts +from logitech_receiver.settings_templates import SettingsProtocol +from logitech_receiver.settings_validator import Range + +from solaar import configuration + +APP_ID = "io.github.pwr_solaar.solaar" + + +def _parse_int_or_hex(s) -> int | None: + """Parse 0xRRGGBB / #RRGGBB / decimal int. Returns None on bad input.""" + if not isinstance(s, str): + return None + s = s.strip() + try: + if s.startswith("#"): + return int(s[1:], 16) + if s.lower().startswith("0x"): + return int(s, 16) + return int(s, 10) + except ValueError: + return None def _print_setting(s, verbose=True): - print('#', s.label) + print("#", s.label) if verbose: if s.description: - print('#', s.description.replace('\n', ' ')) - if s.kind == _settings.KIND.toggle: - print('# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0 or Toggle/~') - elif s.kind == _settings.KIND.choice: + print("#", s.description.replace("\n", " ")) + if s.kind == settings.Kind.TOGGLE: + print("# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0 or Toggle/~") + elif s.kind == settings.Kind.CHOICE: print( - '# possible values: one of [', ', '.join(str(v) for v in s.choices), - '], or higher/lower/highest/max/lowest/min' + "# possible values: one of [", + ", ".join(str(v) for v in s.choices), + "], or higher/lower/highest/max/lowest/min", ) else: # wtf? pass value = s.read(cached=False) if value is None: - print(s.name, '= ? (failed to read from device)') + print(s.name, "= ? (failed to read from device)") else: - print(s.name, '=', s.val_to_string(value)) + print(s.name, "=", s.val_to_string(value)) def _print_setting_keyed(s, key, verbose=True): - print('#', s.label) + print("#", s.label) if verbose: if s.description: - print('#', s.description.replace('\n', ' ')) - if s.kind == _settings.KIND.multiple_toggle: + print("#", s.description.replace("\n", " ")) + if s.kind == settings.Kind.MULTIPLE_TOGGLE: k = next((k for k in s._labels if key == k), None) if k is None: - print(s.name, '=? (key not found)') + print(s.name, "=? (key not found)") else: - print('# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0 or Toggle/~') + print("# possible values: on/true/t/yes/y/1 or off/false/f/no/n/0 or Toggle/~") value = s.read(cached=False) if value is None: - print(s.name, '= ? (failed to read from device)') + print(s.name, "= ? (failed to read from device)") else: print(s.name, s.val_to_string({k: value[str(int(k))]})) - elif s.kind == _settings.KIND.map_choice: + elif s.kind == settings.Kind.MAP_CHOICE: k = next((k for k in s.choices.keys() if key == k), None) if k is None: - print(s.name, '=? (key not found)') + print(s.name, "=? (key not found)") else: - print('# possible values: one of [', ', '.join(str(v) for v in s.choices[k]), ']') + value_space = s.choices[k] + if isinstance(value_space, Range): + print(f"# possible values: integer in [{value_space.min}, {value_space.max}] (decimal or 0xHEX)") + else: + print("# possible values: one of [", ", ".join(str(v) for v in value_space), "]") value = s.read(cached=False) if value is None: - print(s.name, '= ? (failed to read from device)') + print(s.name, "= ? (failed to read from device)") else: print(s.name, s.val_to_string({k: value[int(k)]})) @@ -92,55 +115,55 @@ def select_choice(value, choices, setting, key): break if val is not None: value = val - elif ivalue is not None and ivalue >= 1 and ivalue <= len(choices): + elif ivalue is not None and 1 <= ivalue <= len(choices): value = choices[ivalue - 1] - elif lvalue in ('higher', 'lower'): + elif lvalue in ("higher", "lower"): old_value = setting.read() if key is None else setting.read_key(key) if old_value is None: - raise Exception('%s: could not read current value' % setting.name) - if lvalue == 'lower': + raise Exception(f"{setting.name}: could not read current value") + if lvalue == "lower": lower_values = choices[:old_value] value = lower_values[-1] if lower_values else choices[:][0] - elif lvalue == 'higher': - higher_values = choices[old_value + 1:] + elif lvalue == "higher": + higher_values = choices[old_value + 1 :] value = higher_values[0] if higher_values else choices[:][-1] - elif lvalue in ('highest', 'max', 'first'): + elif lvalue in ("highest", "max", "first"): value = choices[:][-1] - elif lvalue in ('lowest', 'min', 'last'): + elif lvalue in ("lowest", "min", "last"): value = choices[:][0] else: - raise Exception('%s: possible values are [%s]' % (setting.name, ', '.join(str(v) for v in choices))) + raise Exception(f"{setting.name}: possible values are [{', '.join(str(v) for v in choices)}]") return value def select_toggle(value, setting, key=None): - if value.lower() in ('toggle', '~'): + if value.lower() in ("toggle", "~"): value = not (setting.read() if key is None else setting.read()[str(int(key))]) else: try: value = bool(int(value)) - except Exception: - if value.lower() in ('true', 'yes', 'on', 't', 'y'): + except Exception as exc: + if value.lower() in ("true", "yes", "on", "t", "y"): value = True - elif value.lower() in ('false', 'no', 'off', 'f', 'n'): + elif value.lower() in ("false", "no", "off", "f", "n"): value = False else: - raise Exception("%s: don't know how to interpret '%s' as boolean" % (setting.name, value)) + raise Exception(f"{setting.name}: don't know how to interpret '{value}' as boolean") from exc return value def select_range(value, setting): try: value = int(value) - except ValueError: - raise Exception("%s: can't interpret '%s' as integer" % (setting.name, value)) - min, max = setting.range - if value < min or value > max: - raise Exception("%s: value '%s' out of bounds" % (setting.name, value)) + except ValueError as exc: + raise Exception(f"{setting.name}: can't interpret '{value}' as integer") from exc + minimum, maximum = setting.range + if value < minimum or value > maximum: + raise Exception(f"{setting.name}: value '{value}' out of bounds") return value -def run(receivers, args, find_receiver, find_device): +def run(receivers, args, _find_receiver, find_device): assert receivers assert args.device @@ -153,21 +176,20 @@ def run(receivers, args, find_receiver, find_device): dev = None if not dev: - raise Exception("no online device found matching '%s'" % device_name) + raise Exception(f"no online device found matching '{device_name}'") if not args.setting: # print all settings, so first set them all up if not dev.settings: - raise Exception('no settings for %s' % dev.name) - _configuration.attach_to(dev) - # _settings.apply_all_settings(dev) - print(dev.name, '(%s) [%s:%s]' % (dev.codename, dev.wpid, dev.serial)) + raise Exception(f"no settings for {dev.name}") + configuration.attach_to(dev) + print(dev.name, f"({dev.codename}) [{dev.wpid}:{dev.serial}]") for s in dev.settings: - print('') + print("") _print_setting(s) return setting_name = args.setting.lower() - setting = _settings_templates.check_feature_setting(dev, setting_name) + setting = settings_templates.check_feature_setting(dev, setting_name) if not setting and dev.descriptor and dev.descriptor.settings: for sclass in dev.descriptor.settings: if sclass.register and sclass.name == setting_name: @@ -176,20 +198,21 @@ def run(receivers, args, find_receiver, find_device): except Exception: setting = None if setting is None: - raise Exception("no setting '%s' for %s" % (args.setting, dev.name)) + raise Exception(f"no setting '{args.setting}' for {dev.name}") if args.value_key is None: - # setting.apply() _print_setting(setting) return remote = False try: import gi - gi.require_version('Gtk', '3.0') - from gi.repository import Gio, Gtk + + gi.require_version("Gtk", "3.0") + from gi.repository import Gio + from gi.repository import Gtk + if Gtk.init_check()[0]: # can Gtk be initialized? - APP_ID = 'io.github.pwr_solaar.solaar' application = Gtk.Application.new(APP_ID, Gio.ApplicationFlags.HANDLES_COMMAND_LINE) application.register() remote = application.get_is_remote() @@ -201,62 +224,72 @@ def run(receivers, args, find_receiver, find_device): if message is not None: print(message) if result is None: - raise Exception("%s: failed to set value '%s' [%r]" % (setting.name, str(value), value)) + raise Exception(f"{setting.name}: failed to set value '{str(value)}' [{value!r}]") # if the Solaar UI is running tell it to also perform the set, otherwise save the change in the configuration file if remote: - argl = ['config', dev.serial or dev.unitId, setting.name] + argl = ["config", dev.serial or dev.unitId, setting.name] argl.extend([a for a in [args.value_key, args.extra_subkey, args.extra2] if a is not None]) - application.run(_yaml.safe_dump(argl)) + args = yaml.dump(argl) + application.run(args) else: if dev.persister and setting.persist: dev.persister[setting.name] = setting._value -def set(dev, setting, args, save): - if setting.kind == _settings.KIND.toggle: +def set(dev, setting: SettingsProtocol, args, save): + if setting.kind == settings.Kind.TOGGLE: value = select_toggle(args.value_key, setting) args.value_key = value - message = 'Setting %s of %s to %s' % (setting.name, dev.name, value) + message = f"Setting {setting.name} of {dev.name} to {value}" result = setting.write(value, save=save) - elif setting.kind == _settings.KIND.range: + elif setting.kind == settings.Kind.RANGE: value = select_range(args.value_key, setting) args.value_key = value - message = 'Setting %s of %s to %s' % (setting.name, dev.name, value) + message = f"Setting {setting.name} of {dev.name} to {value}" result = setting.write(value, save=save) - elif setting.kind == _settings.KIND.choice: + elif setting.kind == settings.Kind.CHOICE: value = select_choice(args.value_key, setting.choices, setting, None) args.value_key = int(value) - message = 'Setting %s of %s to %s' % (setting.name, dev.name, value) + message = f"Setting {setting.name} of {dev.name} to {value}" result = setting.write(value, save=save) - elif setting.kind == _settings.KIND.map_choice: + elif setting.kind == settings.Kind.MAP_CHOICE: if args.extra_subkey is None: _print_setting_keyed(setting, args.value_key) - return (None, None, None) + return None, None, None key = args.value_key ikey = to_int(key) k = next((k for k in setting.choices.keys() if key == k), None) if k is None and ikey is not None: k = next((k for k in setting.choices.keys() if ikey == k), None) - if k is not None: - value = select_choice(args.extra_subkey, setting.choices[k], setting, key) - args.extra_subkey = int(value) - args.value_key = str(int(k)) + if k is None: + raise Exception(f"{setting.name}: key '{key}' not in setting") + value_space = setting.choices[k] + if isinstance(value_space, Range): + ivalue = _parse_int_or_hex(args.extra_subkey) + if ivalue is None or not value_space.contains(ivalue): + raise Exception( + f"{setting.name}: value '{args.extra_subkey}' must be an integer in " + f"[{value_space.min}, {value_space.max}] (decimal or 0xHEX / #HEX)" + ) + value = ivalue else: - raise Exception("%s: key '%s' not in setting" % (setting.name, key)) - message = 'Setting %s of %s key %r to %r' % (setting.name, dev.name, k, value) + value = select_choice(args.extra_subkey, value_space, setting, key) + args.extra_subkey = int(value) + args.value_key = str(int(k)) + message = f"Setting {setting.name} of {dev.name} key {k!r} to {value!r}" result = setting.write_key_value(int(k), value, save=save) - elif setting.kind == _settings.KIND.multiple_toggle: + elif setting.kind == settings.Kind.MULTIPLE_TOGGLE: if args.extra_subkey is None: _print_setting_keyed(setting, args.value_key) - return (None, None, None) + return None, None, None key = args.value_key - all_keys = getattr(setting, 'choices_universe', None) - ikey = all_keys[int(key) if key.isdigit() else key] if isinstance(all_keys, _NamedInts) else to_int(key) + all_keys = getattr(setting, "choices_universe", None) + ikey = all_keys[int(key) if key.isdigit() else key] if isinstance(all_keys, NamedInts) else to_int(key) k = next((k for k in setting._labels if key == k), None) if k is None and ikey is not None: k = next((k for k in setting._labels if ikey == k), None) @@ -265,18 +298,18 @@ def set(dev, setting, args, save): args.extra_subkey = value args.value_key = str(int(k)) else: - raise Exception("%s: key '%s' not in setting" % (setting.name, key)) - message = 'Setting %s key %r to %r' % (setting.name, k, value) + raise Exception(f"{setting.name}: key '{key}' not in setting") + message = f"Setting {setting.name} key {k!r} to {value!r}" result = setting.write_key_value(str(int(k)), value, save=save) - elif setting.kind == _settings.KIND.multiple_range: + elif setting.kind == settings.Kind.MULTIPLE_RANGE: if args.extra_subkey is None: - raise Exception('%s: setting needs both key and value to set' % (setting.name)) + raise Exception(f"{setting.name}: setting needs both key and value to set") key = args.value_key - all_keys = getattr(setting, 'choices_universe', None) - ikey = all_keys[int(key) if key.isdigit() else key] if isinstance(all_keys, _NamedInts) else to_int(key) + all_keys = getattr(setting, "choices_universe", None) + ikey = all_keys[int(key) if key.isdigit() else key] if isinstance(all_keys, NamedInts) else to_int(key) if args.extra2 is None or to_int(args.extra2) is None: - raise Exception('%s: setting needs an integer value, not %s' % (setting.name, args.extra2)) + raise Exception(f"{setting.name}: setting needs an integer value, not {args.extra2}") if not setting._value: # ensure that there are values to look through setting.read() k = next((k for k in setting._value if key == ikey or key.isdigit() and ikey == int(key)), None) @@ -287,12 +320,30 @@ def set(dev, setting, args, save): item[args.extra_subkey] = to_int(args.extra2) args.value_key = str(int(k)) else: - raise Exception("%s: key '%s' not in setting" % (setting.name, key)) - message = 'Setting %s key %s parameter %s to %r' % (setting.name, k, args.extra_subkey, item[args.extra_subkey]) + raise Exception(f"{setting.name}: key '{key}' not in setting") + message = f"Setting {setting.name} key {k} parameter {args.extra_subkey} to {item[args.extra_subkey]!r}" result = setting.write_key_value(int(k), item, save=save) value = item + elif setting.kind == settings.Kind.MAP_RANGE: + if args.extra_subkey is None: + _print_setting_keyed(setting, args.value_key) + return None, None, None + key = int(args.value_key) + value = int(args.extra_subkey) + if key not in setting._device_object: + raise Exception(f"{setting.name}: key '{key}' not in setting") + message = f"Setting {setting.name} of {dev.name} key {key} to {value}" + result = setting.write_key_value(key, value, save=save) + + elif setting.kind == settings.Kind.HETERO: + value = yaml.safe_load(args.value_key) + args.value_key = value + message = f"Setting {setting.name} of {dev.name} to {value}" + result = setting.write(value, save=save) + else: - raise Exception('NotImplemented') + print(f"Setting {setting.name}, with kind {setting.kind.name}, not implemented") + raise Exception("NotImplemented") return result, message, value diff --git a/lib/solaar/cli/pair.py b/lib/solaar/cli/pair.py index b63953ff..b06b4fe6 100644 --- a/lib/solaar/cli/pair.py +++ b/lib/solaar/cli/pair.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -16,14 +14,14 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from time import time as _timestamp +from time import time -from logitech_receiver import base as _base -from logitech_receiver import hidpp10 as _hidpp10 -from logitech_receiver import notifications as _notifications -from logitech_receiver import status as _status +from logitech_receiver import base +from logitech_receiver import hidpp10 +from logitech_receiver import hidpp10_constants +from logitech_receiver import notifications -_R = _hidpp10.REGISTERS +_hidpp10 = hidpp10.Hidpp10() def run(receivers, args, find_receiver, _ignore): @@ -33,105 +31,110 @@ def run(receivers, args, find_receiver, _ignore): receiver_name = args.receiver.lower() receiver = find_receiver(receivers, receiver_name) if not receiver: - raise Exception("no receiver found matching '%s'" % receiver_name) + raise Exception(f"no receiver found matching '{receiver_name}'") else: receiver = receivers[0] assert receiver - receiver.status = _status.ReceiverStatus(receiver, lambda *args, **kwargs: None) # check if it's necessary to set the notification flags - old_notification_flags = _hidpp10.get_notification_flags(receiver) or 0 - if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless): - _hidpp10.set_notification_flags(receiver, old_notification_flags | _hidpp10.NOTIFICATION_FLAG.wireless) + old_notification_flags = _hidpp10.get_notification_flags(receiver) + if not (old_notification_flags & hidpp10_constants.NotificationFlag.WIRELESS): + _hidpp10.set_notification_flags(receiver, old_notification_flags | hidpp10_constants.NotificationFlag.WIRELESS) # get all current devices known_devices = [dev.number for dev in receiver] class _HandleWithNotificationHook(int): - def notifications_hook(self, n): nonlocal known_devices assert n if n.devnumber == 0xFF: - _notifications.process(receiver, n) - elif n.sub_id == 0x41 and len(n.data) == _base._SHORT_MESSAGE_SIZE - 4: + notifications.process(receiver, n) + elif n.sub_id == 0x41 and len(n.data) == base.SHORT_MESSAGE_SIZE - 4: kd, known_devices = known_devices, None # only process one connection notification if kd is not None: if n.devnumber not in kd: - receiver.status.new_device = receiver.register_new_device(n.devnumber, n) + receiver.pairing.new_device = receiver.register_new_device(n.devnumber, n) elif receiver.re_pairs: del receiver[n.devnumber] # get rid of information on device re-paired away - receiver.status.new_device = receiver.register_new_device(n.devnumber, n) + receiver.pairing.new_device = receiver.register_new_device(n.devnumber, n) timeout = 30 # seconds receiver.handle = _HandleWithNotificationHook(receiver.handle) - if receiver.receiver_kind == 'bolt': # Bolt receivers require authentication to pair a device + if receiver.receiver_kind == "bolt": # Bolt receivers require authentication to pair a device receiver.discover(timeout=timeout) - print('Bolt Pairing: long-press the pairing key or button on your device (timing out in', timeout, 'seconds).') - pairing_start = _timestamp() + print("Bolt Pairing: long-press the pairing key or button on your device (timing out in", timeout, "seconds).") + pairing_start = time() patience = 5 # the discovering notification may come slightly later, so be patient - while receiver.status.discovering or _timestamp() - pairing_start < patience: - if receiver.status.device_address and receiver.status.device_authentication and receiver.status.device_name: + while receiver.pairing.discovering or time() - pairing_start < patience: + if receiver.pairing.device_address and receiver.pairing.device_authentication and receiver.pairing.device_name: break - n = _base.read(receiver.handle) - n = _base.make_notification(*n) if n else None + n = base.read(receiver.handle) + n = base.make_notification(*n) if n else None if n: receiver.handle.notifications_hook(n) - address = receiver.status.device_address - name = receiver.status.device_name - authentication = receiver.status.device_authentication - kind = receiver.status.device_kind - print(f'Bolt Pairing: discovered {name}') - receiver.pair_device( - address=address, authentication=authentication, entropy=20 if kind == _hidpp10.DEVICE_KIND.keyboard else 10 - ) - pairing_start = _timestamp() - patience = 5 # the discovering notification may come slightly later, so be patient - while receiver.status.lock_open or _timestamp() - pairing_start < patience: - if receiver.status.device_passkey: - break - n = _base.read(receiver.handle) - n = _base.make_notification(*n) if n else None - if n: - receiver.handle.notifications_hook(n) - if authentication & 0x01: - print(f'Bolt Pairing: type passkey {receiver.status.device_passkey} and then press the enter key') + address = receiver.pairing.device_address + name = receiver.pairing.device_name + authentication = receiver.pairing.device_authentication + kind = receiver.pairing.device_kind + if authentication is None: # no compatible device stepped forward + print("No Bolt-compatible device requested pairing.") else: - passkey = f'{int(receiver.status.device_passkey):010b}' - passkey = ', '.join(['right' if bit == '1' else 'left' for bit in passkey]) - print(f'Bolt Pairing: press {passkey}') - print('and then press left and right buttons simultaneously') - while receiver.status.lock_open: - n = _base.read(receiver.handle) - n = _base.make_notification(*n) if n else None - if n: - receiver.handle.notifications_hook(n) - - else: - receiver.set_lock(False, timeout=timeout) - print('Pairing: turn your new device on (timing out in', timeout, 'seconds).') - pairing_start = _timestamp() - patience = 5 # the lock-open notification may come slightly later, wait for it a bit - while receiver.status.lock_open or _timestamp() - pairing_start < patience: - n = _base.read(receiver.handle) - if n: - n = _base.make_notification(*n) + print(f"Bolt Pairing: discovered {name}") + receiver.pair_device( + address=address, + authentication=authentication, + entropy=20 if kind == hidpp10_constants.DEVICE_KIND.keyboard else 10, + ) + pairing_start = time() + patience = 5 # the discovering notification may come slightly later, so be patient + while receiver.pairing.lock_open or time() - pairing_start < patience: + if receiver.pairing.device_passkey: + break + n = base.read(receiver.handle) + n = base.make_notification(*n) if n else None + if n: + receiver.handle.notifications_hook(n) + if authentication & 0x01: + print(f"Bolt Pairing: type passkey {receiver.pairing.device_passkey} and then press the enter key") + else: + passkey = f"{int(receiver.pairing.device_passkey):010b}" + passkey = ", ".join(["right" if bit == "1" else "left" for bit in passkey]) + print(f"Bolt Pairing: press {passkey}") + print("and then press left and right buttons simultaneously") + while receiver.pairing.lock_open: + n = base.read(receiver.handle) + n = base.make_notification(*n) if n else None if n: receiver.handle.notifications_hook(n) - if not (old_notification_flags & _hidpp10.NOTIFICATION_FLAG.wireless): + else: + receiver.set_lock(False, timeout=timeout) + print("Pairing: Turn your device on or press, hold, and release") + print("a channel button or the channel switch button.") + print("Timing out in", timeout, "seconds.") + pairing_start = time() + patience = 5 # the lock-open notification may come slightly later, wait for it a bit + while receiver.pairing.lock_open or time() - pairing_start < patience: + n = base.read(receiver.handle) + if n: + n = base.make_notification(*n) + if n: + receiver.handle.notifications_hook(n) + + if not (old_notification_flags & hidpp10_constants.NotificationFlag.WIRELESS): # only clear the flags if they weren't set before, otherwise a # concurrently running Solaar app might stop working properly _hidpp10.set_notification_flags(receiver, old_notification_flags) - if receiver.status.new_device: - dev = receiver.status.new_device - print('Paired device %d: %s (%s) [%s:%s]' % (dev.number, dev.name, dev.codename, dev.wpid, dev.serial)) + if receiver.pairing.new_device: + dev = receiver.pairing.new_device + print(f"Paired device {int(dev.number)}: {dev.name} ({dev.codename}) [{dev.wpid}:{dev.serial}]") else: - error = receiver.status.get(_status.KEYS.ERROR) + error = receiver.pairing.error if error: - raise Exception('pairing failed: %s' % error) + raise Exception(f"pairing failed: {error}") else: - print('Paired device') # this is better than an error + print("Paired device") # this is better than an error diff --git a/lib/solaar/cli/probe.py b/lib/solaar/cli/probe.py index 859eb8ff..bcf40b37 100644 --- a/lib/solaar/cli/probe.py +++ b/lib/solaar/cli/probe.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2020 ## ## This program is free software; you can redistribute it and/or modify @@ -16,12 +14,13 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from logitech_receiver import base as _base -from logitech_receiver import hidpp10 as _hidpp10 -from logitech_receiver.common import strhex as _strhex -from solaar.cli.show import _print_device, _print_receiver +from logitech_receiver import base +from logitech_receiver.common import strhex +from logitech_receiver.hidpp10_constants import ErrorCode +from logitech_receiver.hidpp10_constants import Registers -_R = _hidpp10.REGISTERS +from solaar.cli.show import _print_device +from solaar.cli.show import _print_receiver def run(receivers, args, find_receiver, _ignore): @@ -31,7 +30,7 @@ def run(receivers, args, find_receiver, _ignore): receiver_name = args.receiver.lower() receiver = find_receiver(receivers, receiver_name) if not receiver: - raise Exception("no receiver found matching '%s'" % receiver_name) + raise Exception(f"no receiver found matching '{receiver_name}'") else: receiver = receivers[0] @@ -43,75 +42,71 @@ def run(receivers, args, find_receiver, _ignore): _print_receiver(receiver) - print('') - print(' Register Dump') - rgst = receiver.read_register(_R.notifications) - print(' Notifications %#04x: %s' % (_R.notifications % 0x100, '0x' + _strhex(rgst) if rgst else 'None')) - rgst = receiver.read_register(_R.receiver_connection) - print(' Connection State %#04x: %s' % (_R.receiver_connection % 0x100, '0x' + _strhex(rgst) if rgst else 'None')) - rgst = receiver.read_register(_R.devices_activity) - print(' Device Activity %#04x: %s' % (_R.devices_activity % 0x100, '0x' + _strhex(rgst) if rgst else 'None')) + print("") + print(" Register Dump") + rgst = receiver.read_register(Registers.NOTIFICATIONS) + print(" Notifications %#04x: %s" % (Registers.NOTIFICATIONS % 0x100, f"0x{strhex(rgst)}" if rgst else "None")) + rgst = receiver.read_register(Registers.RECEIVER_CONNECTION) + print( + " Connection State %#04x: %s" + % (Registers.RECEIVER_CONNECTION % 0x100, f"0x{strhex(rgst)}" if rgst else "None") + ) + rgst = receiver.read_register(Registers.DEVICES_ACTIVITY) + print( + " Device Activity %#04x: %s" % (Registers.DEVICES_ACTIVITY % 0x100, f"0x{strhex(rgst)}" if rgst else "None") + ) for sub_reg in range(0, 16): - rgst = receiver.read_register(_R.receiver_info, sub_reg) + rgst = receiver.read_register(Registers.RECEIVER_INFO, sub_reg) print( - ' Pairing Register %#04x %#04x: %s' % - (_R.receiver_info % 0x100, sub_reg, '0x' + _strhex(rgst) if rgst else 'None') + " Pairing Register %#04x %#04x: %s" + % (Registers.RECEIVER_INFO % 0x100, sub_reg, f"0x{strhex(rgst)}" if rgst else "None") ) for device in range(0, 7): for sub_reg in [0x10, 0x20, 0x30, 0x50]: - rgst = receiver.read_register(_R.receiver_info, sub_reg + device) + rgst = receiver.read_register(Registers.RECEIVER_INFO, sub_reg + device) print( - ' Pairing Register %#04x %#04x: %s' % - (_R.receiver_info % 0x100, sub_reg + device, '0x' + _strhex(rgst) if rgst else 'None') + " Pairing Register %#04x %#04x: %s" + % (Registers.RECEIVER_INFO % 0x100, sub_reg + device, f"0x{strhex(rgst)}" if rgst else "None") ) - rgst = receiver.read_register(_R.receiver_info, 0x40 + device) + rgst = receiver.read_register(Registers.RECEIVER_INFO, 0x40 + device) print( - ' Pairing Name %#04x %#02x: %s' % - (_R.receiver_info % 0x100, 0x40 + device, rgst[2:2 + ord(rgst[1:2])] if rgst else 'None') + " Pairing Name %#04x %#02x: %s" + % (Registers.RECEIVER_INFO % 0x100, 0x40 + device, rgst[2 : 2 + ord(rgst[1:2])] if rgst else "None") ) for part in range(1, 4): - rgst = receiver.read_register(_R.receiver_info, 0x60 + device, part) + rgst = receiver.read_register(Registers.RECEIVER_INFO, 0x60 + device, part) print( - ' Pairing Name %#04x %#02x %#02x: %2d %s' % ( - _R.receiver_info % 0x100, 0x60 + device, part, ord(rgst[2:3]) if rgst else 0, - rgst[3:3 + ord(rgst[2:3])] if rgst else 'None' + " Pairing Name %#04x %#02x %#02x: %2d %s" + % ( + Registers.RECEIVER_INFO % 0x100, + 0x60 + device, + part, + ord(rgst[2:3]) if rgst else 0, + rgst[3 : 3 + ord(rgst[2:3])] if rgst else "None", ) ) for sub_reg in range(0, 5): - rgst = receiver.read_register(_R.firmware, sub_reg) + rgst = receiver.read_register(Registers.FIRMWARE, sub_reg) print( - ' Firmware %#04x %#04x: %s' % - (_R.firmware % 0x100, sub_reg, '0x' + _strhex(rgst) if rgst is not None else 'None') + " Firmware %#04x %#04x: %s" + % (Registers.FIRMWARE % 0x100, sub_reg, f"0x{strhex(rgst)}" if rgst is not None else "None") ) - print('') + print("") for reg in range(0, 0xFF): - last = None - for sub in range(0, 0xFF): - rgst = _base.request(receiver.handle, 0xFF, 0x8100 | reg, sub, return_error=True) - if isinstance(rgst, int) and rgst == _hidpp10.ERROR.invalid_address: - break - elif isinstance(rgst, int) and rgst == _hidpp10.ERROR.invalid_value: - continue - else: - if not isinstance(last, bytes) or not isinstance(rgst, bytes) or last != rgst: - print( - ' Register Short %#04x %#04x: %s' % - (reg, sub, '0x' + _strhex(rgst) if isinstance(rgst, bytes) else str(rgst)) - ) - last = rgst - last = None - for sub in range(0, 0xFF): - rgst = _base.request(receiver.handle, 0xFF, 0x8100 | (0x200 + reg), sub, return_error=True) - if isinstance(rgst, int) and rgst == _hidpp10.ERROR.invalid_address: - break - elif isinstance(rgst, int) and rgst == _hidpp10.ERROR.invalid_value: - continue - else: - if not isinstance(last, bytes) or not isinstance(rgst, bytes) or last != rgst: - print( - ' Register Long %#04x %#04x: %s' % - (reg, sub, '0x' + _strhex(rgst) if isinstance(rgst, bytes) else str(rgst)) - ) - last = rgst + for offset, reg_type in [(0x00, "Short"), (0x200, "Long")]: + last = None + for sub in range(0, 0xFF): + rgst = base.request(receiver.handle, 0xFF, 0x8100 | (offset + reg), sub, return_error=True) + if isinstance(rgst, int) and rgst == ErrorCode.INVALID_ADDRESS: + break + elif isinstance(rgst, int) and rgst == ErrorCode.INVALID_VALUE: + continue + else: + if not isinstance(last, bytes) or not isinstance(rgst, bytes) or last != rgst: + print( + " Register %s %#04x %#04x: %s" + % (reg_type, reg, sub, "0x" + strhex(rgst) if isinstance(rgst, bytes) else str(rgst)) + ) + last = rgst diff --git a/lib/solaar/cli/profiles.py b/lib/solaar/cli/profiles.py new file mode 100644 index 00000000..d697fbcb --- /dev/null +++ b/lib/solaar/cli/profiles.py @@ -0,0 +1,67 @@ +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import traceback + +import yaml + +from logitech_receiver.hidpp20 import OnboardProfiles +from logitech_receiver.hidpp20 import OnboardProfilesVersion + + +def run(receivers, args, find_receiver, find_device): + assert receivers + assert args.device + + device_name = args.device.lower() + profiles_file = args.profiles + + dev = None + for dev in find_device(receivers, device_name): + if dev.ping(): + break + dev = None + + if not dev: + raise Exception(f"no online device found matching '{device_name}'") + + if not dev.online: + print(f"Device {dev.name} is offline.") + elif not dev.profiles: + print(f"Device {dev.name} has no onboard profiles that Solaar supports.") + elif not profiles_file: + print(f"#Dumping profiles from {dev.name}") + print(yaml.dump(dev.profiles)) + else: + try: + with open(profiles_file, "r") as f: + print(f"Reading profiles from {profiles_file}") + profiles = yaml.safe_load(f) + if not isinstance(profiles, OnboardProfiles): + print("Profiles file does not contain current onboard profiles") + elif getattr(profiles, "version", None) != OnboardProfilesVersion: + version = getattr(profiles, "version", None) + print(f"Missing or incorrect profile version {version} in loaded profile") + elif getattr(profiles, "name", None) != dev.name: + name = getattr(profiles, "name", None) + print(f"Different device name {name} in loaded profile") + else: + print(f"Loading profiles into {dev.name}") + written = profiles.write(dev) + print(f"Wrote {written} sectors to {dev.name}") + except Exception as exc: + print("Profiles not written:", exc) + print(traceback.format_exc()) diff --git a/lib/solaar/cli/show.py b/lib/solaar/cli/show.py index b7fcd009..ab03a5b9 100644 --- a/lib/solaar/cli/show.py +++ b/lib/solaar/cli/show.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -16,299 +14,442 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from logitech_receiver import base as _base -from logitech_receiver import hidpp10 as _hidpp10 -from logitech_receiver import hidpp20 as _hidpp20 -from logitech_receiver import receiver as _receiver -from logitech_receiver import settings_templates as _settings_templates -from logitech_receiver.common import NamedInt as _NamedInt -from logitech_receiver.common import strhex as _strhex -from solaar import NAME, __version__ +from logitech_receiver import common +from logitech_receiver import exceptions +from logitech_receiver import hidpp10 +from logitech_receiver import hidpp10_constants +from logitech_receiver import hidpp20 +from logitech_receiver import hidpp20_constants +from logitech_receiver import receiver +from logitech_receiver import settings_templates +from logitech_receiver.common import LOGITECH_VENDOR_ID +from logitech_receiver.common import NamedInt +from logitech_receiver.common import strhex +from logitech_receiver.device import CenturionReceiver +from logitech_receiver.hidpp20_constants import SupportedFeature -_F = _hidpp20.FEATURE +from solaar import NAME +from solaar import __version__ + +_hidpp10 = hidpp10.Hidpp10() +_hidpp20 = hidpp20.Hidpp20() def _print_receiver(receiver): + is_centurion = isinstance(receiver, CenturionReceiver) paired_count = receiver.count() print(receiver.name) - print(' Device path :', receiver.path) - print(' USB id : 046d:%s' % receiver.product_id) - print(' Serial :', receiver.serial) + print(" Device path :", receiver.path) + print(f" USB id : {LOGITECH_VENDOR_ID:04x}:{receiver.product_id}") + if is_centurion: + print(" Protocol : Centurion") + if receiver.serial: + print(" Serial :", receiver.serial) + if not is_centurion: + pending = hidpp10.get_configuration_pending_flags(receiver) + if pending: + print(f" C Pending : {pending:02x}") if receiver.firmware: for f in receiver.firmware: - print(' %-11s: %s' % (f.kind, f.version)) + print(" %-11s: %s" % (f.kind, f.version)) - print(' Has', paired_count, 'paired device(s) out of a maximum of %d.' % receiver.max_devices) - if receiver.remaining_pairings() and receiver.remaining_pairings() >= 0: - print(' Has %d successful pairing(s) remaining.' % receiver.remaining_pairings()) + if is_centurion: + print(" Has", paired_count, f"device(s) out of a maximum of {int(receiver.max_devices)}.") + else: + print(" Has", paired_count, f"paired device(s) out of a maximum of {int(receiver.max_devices)}.") + if receiver.remaining_pairings() and receiver.remaining_pairings() >= 0: + print(f" Has {int(receiver.remaining_pairings())} successful pairing(s) remaining.") - notification_flags = _hidpp10.get_notification_flags(receiver) - if notification_flags is not None: - if notification_flags: - notification_names = _hidpp10.NOTIFICATION_FLAG.flag_names(notification_flags) - print(' Notifications: %s (0x%06X)' % (', '.join(notification_names), notification_flags)) + if is_centurion: + _print_centurion_dongle_features(receiver) + else: + notification_flags = _hidpp10.get_notification_flags(receiver) + if notification_flags is not None: + if notification_flags: + notification_names = hidpp10_constants.NotificationFlag.flag_names(notification_flags) + print(f" Notifications: {', '.join(notification_names)} (0x{notification_flags.value:06X})") + else: + print(" Notifications: (none)") + + activity = receiver.read_register(hidpp10_constants.Registers.DEVICES_ACTIVITY) + if activity: + activity = [(d, ord(activity[d - 1 : d])) for d in range(1, receiver.max_devices)] + activity_text = ", ".join(f"{int(d)}={int(a)}" for d, a in activity if a > 0) + print(" Device activity counters:", activity_text or "(empty)") + + +def _print_centurion_dongle_features(receiver): + """Print dongle-level features, probed independently on the dongle hardware.""" + features = receiver.dongle_features + if not features: + return + print(f" Supports {len(features)} dongle features:") + for feature, feat_id, index in features: + display_name = "CENTPP BRIDGE" if feat_id == 0x0003 else feature + feat_bytes = feat_id.to_bytes(2, byteorder="big") + try: + flags_resp = receiver.request(0x0000, feat_bytes[0], feat_bytes[1]) + except Exception: + flags_resp = None + if flags_resp is not None and len(flags_resp) >= 2: + flags = flags_resp[1] + flag_names = common.flag_names(hidpp20_constants.FeatureFlag, flags) + print(" %2d: %-22s {%04X} %s " % (index, display_name, feat_id, ", ".join(flag_names))) else: - print(' Notifications: (none)') - - activity = receiver.read_register(_hidpp10.REGISTERS.devices_activity) - if activity: - activity = [(d, ord(activity[d - 1:d])) for d in range(1, receiver.max_devices)] - activity_text = ', '.join(('%d=%d' % (d, a)) for d, a in activity if a > 0) - print(' Device activity counters:', activity_text or '(empty)') + print(" %2d: %-22s {%04X}" % (index, display_name, feat_id)) + if feature == SupportedFeature.CENTURION_DEVICE_INFO: + fw_list = _hidpp20.get_firmware_centurion(receiver) + serial = _hidpp20.get_serial_centurion(receiver) + hw_info = _hidpp20.get_hardware_info_centurion(receiver) + if fw_list: + for fw in fw_list: + print(f" Firmware: {(str(fw.kind) + ' ' + fw.name).strip()} {fw.version}") + if serial and serial.strip() and serial.strip().isprintable(): + print(f" Serial: {serial}") + if hw_info: + model_id, hw_rev, product_id = hw_info + print(f" Hardware: model {model_id}" f" rev {hw_rev} product {product_id:04X}") -def _battery_text(level): +def _battery_text(level) -> str: if level is None: - return 'N/A' - elif isinstance(level, _NamedInt): + return "N/A" + elif isinstance(level, NamedInt): return str(level) else: - return '%d%%' % level + return f"{int(level)}%" def _battery_line(dev): battery = dev.battery() if battery is not None: - level, nextLevel, status, voltage = battery + level, nextLevel, status, voltage = battery.level, battery.next_level, battery.status, battery.voltage text = _battery_text(level) if voltage is not None: - text = text + (' %smV ' % voltage) - nextText = '' if nextLevel is None else ', next level ' + _battery_text(nextLevel) - print(' Battery: %s, %s%s.' % (text, status, nextText)) + text = f"{text} {voltage}mV " + nextText = "" if nextLevel is None else f", next level {_battery_text(nextLevel)}" + print(f" Battery: {text}, {status}{nextText}.") else: - print(' Battery status unavailable.') + print(" Battery status unavailable.") def _print_device(dev, num=None): assert dev is not None + is_centurion = getattr(dev, "centurion", False) + is_centurion_child = is_centurion and isinstance(getattr(dev, "receiver", None), CenturionReceiver) # try to ping the device to see if it actually exists and to wake it up try: - dev.ping() - except _base.NoSuchDevice: - print(' %d: Device not found' % num or dev.number) + online = dev.ping() + except exceptions.NoSuchDevice: + print(f" {num}: Device not found" or dev.number) return - print(' %d: %s' % (num or dev.number, dev.name)) - print(' Device path :', dev.path) - if dev.wpid: - print(' WPID : %s' % dev.wpid) - if dev.product_id: - print(' USB id : 046d:%s' % dev.product_id) - print(' Codename :', dev.codename) - print(' Kind :', dev.kind) - if dev.protocol: - print(' Protocol : HID++ %1.1f' % dev.protocol) + if num or dev.number < 8: + print(f" {int(num or dev.number)}: {dev.name}") else: - print(' Protocol : unknown (device is offline)') - if dev.polling_rate: - print(' Polling rate :', dev.polling_rate, 'ms (%dHz)' % (1000 // dev.polling_rate)) - print(' Serial number:', dev.serial) + print(f"{dev.name}") + + if not online: + print(" Device is offline.") + return + # Centurion child has no separate hidraw path — show receiver's path + device_path = dev.path or (dev.receiver.path if is_centurion_child else None) + print(" Device path :", device_path) + if dev.wpid and not is_centurion_child: + print(f" WPID : {dev.wpid}") + if dev.product_id: + print(f" USB id : {LOGITECH_VENDOR_ID:04x}:{dev.product_id}") + print(" Codename :", dev.codename) + print(" Kind :", dev.kind) + if dev.protocol: + proto_name = "Centurion" if is_centurion else "HID++" + cent_proto = getattr(dev, "_centurion_protocol", None) + if cent_proto: + print(f" Protocol : {proto_name} {cent_proto[0]}.{cent_proto[1]}") + else: + print(f" Protocol : {proto_name} {dev.protocol:1.1f}") + else: + print(" Protocol : unknown (device is offline)") + if not is_centurion and dev.polling_rate: + print(" Report Rate :", dev.polling_rate) + print(" Serial number:", dev.serial) if dev.modelId: - print(' Model ID: ', dev.modelId) + print(" Model ID: ", dev.modelId) if dev.unitId: - print(' Unit ID: ', dev.unitId) + print(" Unit ID: ", dev.unitId) if dev.firmware: for fw in dev.firmware: - print(' %11s:' % fw.kind, (fw.name + ' ' + fw.version).strip()) + print(f" {fw.kind:11}:", (fw.name + " " + fw.version).strip()) if dev.power_switch_location: - print(' The power switch is located on the %s.' % dev.power_switch_location) + print(f" The power switch is located on the {dev.power_switch_location}.") - if dev.online: + # Skip HID++ 1.0 register reads for centurion devices — they don't support these + if dev.online and not is_centurion: notification_flags = _hidpp10.get_notification_flags(dev) if notification_flags is not None: if notification_flags: - notification_names = _hidpp10.NOTIFICATION_FLAG.flag_names(notification_flags) - print(' Notifications: %s (0x%06X).' % (', '.join(notification_names), notification_flags)) + notification_names = hidpp10_constants.NotificationFlag.flag_names(notification_flags) + print(f" Notifications: {', '.join(notification_names)} (0x{notification_flags.value:06X}).") else: - print(' Notifications: (none).') + print(" Notifications: (none).") device_features = _hidpp10.get_device_features(dev) if device_features is not None: if device_features: - device_features_names = _hidpp10.DEVICE_FEATURES.flag_names(device_features) - print(' Features: %s (0x%06X)' % (', '.join(device_features_names), device_features)) + device_features_names = hidpp10_constants.DeviceFeature.flag_names(device_features) + print(f" Features: {', '.join(device_features_names)} (0x{device_features:06X})") else: - print(' Features: (none)') + print(" Features: (none)") if dev.online and dev.features: - print(' Supports %d HID++ 2.0 features:' % len(dev.features)) + is_centurion = getattr(dev, "centurion", False) + parent_count = dev.features.count + sub_count = getattr(dev.features, "_sub_feature_count", 0) + # For centurion child devices, dongle features are shown on the receiver — + # only show sub-device (headset) features here. + if is_centurion_child and sub_count > 0: + print(f" Supports {sub_count} HID++ 2.0 features:") + elif is_centurion and sub_count > 0: + print(f" Supports {parent_count} dongle + {sub_count} headset features:") + else: + print(f" Supports {len(dev.features)} HID++ 2.0 features:") dev_settings = [] - _settings_templates.check_feature_settings(dev, dev_settings) + settings_templates.check_feature_settings(dev, dev_settings) + feature_num = 0 + in_sub_device = False for feature, index in dev.features.enumerate(): - flags = dev.request(0x0000, feature.bytes(2)) - flags = 0 if flags is None else ord(flags[1:2]) - flags = _hidpp20.FEATURE_FLAG.flag_names(flags) - version = dev.features.get_feature_version(int(feature)) - version = version if version else 0 - print(' %2d: %-22s {%04X} V%s %s ' % (index, feature, feature, version, ', '.join(flags))) - if feature == _hidpp20.FEATURE.HIRES_WHEEL: + if is_centurion and not in_sub_device and feature_num >= parent_count: + in_sub_device = True + if not is_centurion_child: + print(" Headset (via CentPPBridge):") + feature_num += 1 + # For centurion child, skip dongle features (already shown on the receiver) + if is_centurion_child and not in_sub_device: + continue + if isinstance(feature, str): + feature_bytes = bytes.fromhex(feature[-4:]) + else: + feature_bytes = feature.to_bytes(2, byteorder="little") + feature_int = int.from_bytes(feature_bytes, byteorder="little") + display_name = feature + if is_centurion_child and in_sub_device: + # Use cached version — skip slow bridge ROOT queries + version = dev.features.get_feature_version(feature_int) or 0 + print(" %2d: %-22s {%04X} V%s" % (index, display_name, feature_int, version)) + else: + try: + flags = dev.request(0x0000, feature_bytes) + except Exception: + flags = None + if flags is not None: + flags = ord(flags[1:2]) + flag_names = common.flag_names(hidpp20_constants.FeatureFlag, flags) + version = dev.features.get_feature_version(feature_int) + version = version if version else 0 + print( + " %2d: %-22s {%04X} V%s %s " + % (index, display_name, feature_int, version, ", ".join(flag_names)) + ) + else: + print(" %2d: %-22s {%04X}" % (index, display_name, feature_int)) + if feature == SupportedFeature.HIRES_WHEEL: wheel = _hidpp20.get_hires_wheel(dev) if wheel: multi, has_invert, has_switch, inv, res, target, ratchet = wheel - print(' Multiplier: %s' % multi) + print(f" Multiplier: {multi}") if has_invert: - print(' Has invert:', 'Inverse wheel motion' if inv else 'Normal wheel motion') + print(" Has invert:", "Inverse wheel motion" if inv else "Normal wheel motion") if has_switch: - print(' Has ratchet switch:', 'Normal wheel mode' if ratchet else 'Free wheel mode') + print(" Has ratchet switch:", "Normal wheel mode" if ratchet else "Free wheel mode") if res: - print(' High resolution mode') + print(" High resolution mode") else: - print(' Low resolution mode') + print(" Low resolution mode") if target: - print(' HID++ notification') + print(" HID++ notification") else: - print(' HID notification') - elif feature == _hidpp20.FEATURE.MOUSE_POINTER: + print(" HID notification") + elif feature == SupportedFeature.MOUSE_POINTER: mouse_pointer = _hidpp20.get_mouse_pointer_info(dev) if mouse_pointer: - print(' DPI: %s' % mouse_pointer['dpi']) - print(' Acceleration: %s' % mouse_pointer['acceleration']) - if mouse_pointer['suggest_os_ballistics']: - print(' Use OS ballistics') + print(f" DPI: {mouse_pointer['dpi']}") + print(f" Acceleration: {mouse_pointer['acceleration']}") + if mouse_pointer["suggest_os_ballistics"]: + print(" Use OS ballistics") else: - print(' Override OS ballistics') - if mouse_pointer['suggest_vertical_orientation']: - print(' Provide vertical tuning, trackball') + print(" Override OS ballistics") + if mouse_pointer["suggest_vertical_orientation"]: + print(" Provide vertical tuning, trackball") else: - print(' No vertical tuning, standard mice') - elif feature == _hidpp20.FEATURE.VERTICAL_SCROLLING: + print(" No vertical tuning, standard mice") + elif feature == SupportedFeature.VERTICAL_SCROLLING: vertical_scrolling_info = _hidpp20.get_vertical_scrolling_info(dev) if vertical_scrolling_info: - print(' Roller type: %s' % vertical_scrolling_info['roller']) - print(' Ratchet per turn: %s' % vertical_scrolling_info['ratchet']) - print(' Scroll lines: %s' % vertical_scrolling_info['lines']) - elif feature == _hidpp20.FEATURE.HI_RES_SCROLLING: + print(f" Roller type: {vertical_scrolling_info['roller']}") + print(f" Ratchet per turn: {vertical_scrolling_info['ratchet']}") + print(f" Scroll lines: {vertical_scrolling_info['lines']}") + elif feature == SupportedFeature.HI_RES_SCROLLING: scrolling_mode, scrolling_resolution = _hidpp20.get_hi_res_scrolling_info(dev) if scrolling_mode: - print(' Hi-res scrolling enabled') + print(" Hi-res scrolling enabled") else: - print(' Hi-res scrolling disabled') + print(" Hi-res scrolling disabled") if scrolling_resolution: - print(' Hi-res scrolling multiplier: %s' % scrolling_resolution) - elif feature == _hidpp20.FEATURE.POINTER_SPEED: + print(f" Hi-res scrolling multiplier: {scrolling_resolution}") + elif feature == SupportedFeature.POINTER_SPEED: pointer_speed = _hidpp20.get_pointer_speed_info(dev) if pointer_speed: - print(' Pointer Speed: %s' % pointer_speed) - elif feature == _hidpp20.FEATURE.LOWRES_WHEEL: + print(f" Pointer Speed: {pointer_speed}") + elif feature == SupportedFeature.LOWRES_WHEEL: wheel_status = _hidpp20.get_lowres_wheel_status(dev) if wheel_status: - print(' Wheel Reports: %s' % wheel_status) - elif feature == _hidpp20.FEATURE.NEW_FN_INVERSION: + print(f" Wheel Reports: {wheel_status}") + elif feature == SupportedFeature.NEW_FN_INVERSION: inversion = _hidpp20.get_new_fn_inversion(dev) if inversion: inverted, default_inverted = inversion - print(' Fn-swap:', 'enabled' if inverted else 'disabled') - print(' Fn-swap default:', 'enabled' if default_inverted else 'disabled') - elif feature == _hidpp20.FEATURE.HOSTS_INFO: + print(" Fn-swap:", "enabled" if inverted else "disabled") + print(" Fn-swap default:", "enabled" if default_inverted else "disabled") + elif feature == SupportedFeature.HOSTS_INFO: host_names = _hidpp20.get_host_names(dev) for host, (paired, name) in host_names.items(): - print(' Host %s (%s): %s' % (host, 'paired' if paired else 'unpaired', name)) - elif feature == _hidpp20.FEATURE.DEVICE_NAME: - print(' Name: %s' % _hidpp20.get_name(dev)) - print(' Kind: %s' % _hidpp20.get_kind(dev)) - elif feature == _hidpp20.FEATURE.DEVICE_FRIENDLY_NAME: - print(' Friendly Name: %s' % _hidpp20.get_friendly_name(dev)) - elif feature == _hidpp20.FEATURE.DEVICE_FW_VERSION: + print(f" Host {host} ({'paired' if paired else 'unpaired'}): {name}") + elif feature == SupportedFeature.DEVICE_NAME: + print(f" Name: {_hidpp20.get_name(dev)}") + print(f" Kind: {_hidpp20.get_kind(dev)}") + elif feature == SupportedFeature.DEVICE_FRIENDLY_NAME: + print(f" Friendly Name: {_hidpp20.get_friendly_name(dev)}") + elif feature == SupportedFeature.CENTURION_DEVICE_INFO: + if in_sub_device: + # Use cached device properties to avoid redundant bridge requests + fw_list = dev.firmware + serial = dev.serial + hw_info = _hidpp20.get_hardware_info_centurion_sub(dev) + else: + fw_list = _hidpp20.get_firmware_centurion(dev) + serial = _hidpp20.get_serial_centurion(dev) + hw_info = _hidpp20.get_hardware_info_centurion(dev) + if fw_list: + for fw in fw_list: + print(f" Firmware: {(str(fw.kind) + ' ' + fw.name).strip()} {fw.version}") + if serial and serial.strip() and serial.strip().isprintable(): + print(f" Serial: {serial}") + if hw_info: + model_id, hw_rev, product_id = hw_info + print(f" Hardware: model {model_id}" f" rev {hw_rev} product {product_id:04X}") + elif isinstance(feature, SupportedFeature) and feature == SupportedFeature.DEVICE_FW_VERSION: for fw in _hidpp20.get_firmware(dev): - extras = _strhex(fw.extras) if fw.extras else '' - print(' Firmware: %s %s %s %s' % (fw.kind, fw.name, fw.version, extras)) + extras = strhex(fw.extras) if fw.extras else "" + print(f" Firmware: {fw.kind} {fw.name} {fw.version} {extras}") ids = _hidpp20.get_ids(dev) if ids: unitId, modelId, tid_map = ids - print(' Unit ID: %s Model ID: %s Transport IDs: %s' % (unitId, modelId, tid_map)) - elif feature == _hidpp20.FEATURE.REPORT_RATE: - print(' Polling Rate (ms): %d' % _hidpp20.get_polling_rate(dev)) - elif feature == _hidpp20.FEATURE.REMAINING_PAIRING: - print(' Remaining Pairings: %d' % _hidpp20.get_remaining_pairing(dev)) - elif feature == _hidpp20.FEATURE.ONBOARD_PROFILES: - if _hidpp20.get_onboard_mode(dev) == _hidpp20.ONBOARD_MODES.MODE_HOST: - mode = 'Host' + print(f" Unit ID: {unitId} Model ID: {modelId} Transport IDs: {tid_map}") + elif feature == SupportedFeature.REPORT_RATE or feature == SupportedFeature.EXTENDED_ADJUSTABLE_REPORT_RATE: + print(f" Report Rate: {_hidpp20.get_polling_rate(dev)}") + elif feature == SupportedFeature.CONFIG_CHANGE: + response = dev.feature_request(SupportedFeature.CONFIG_CHANGE, 0x00) + print(f" Configuration: {response.hex()}") + elif feature == SupportedFeature.REMAINING_PAIRING: + print(f" Remaining Pairings: {int(_hidpp20.get_remaining_pairing(dev))}") + elif feature == SupportedFeature.ONBOARD_PROFILES: + if _hidpp20.get_onboard_mode(dev) == hidpp20_constants.OnboardMode.MODE_HOST: + mode = "Host" else: - mode = 'On-Board' - print(' Device Mode: %s' % mode) - elif _hidpp20.battery_functions.get(feature, None): - print('', end=' ') + mode = "On-Board" + print(f" Device Mode: {mode}") + elif feature == SupportedFeature.HEADSET_ONBOARD_EQ: + bands = hidpp20.get_onboard_eq_params(dev) + if bands: + print(f" EQ: {', '.join(f'{f}Hz:{g:+d}dB' for f, g, _q in bands)}") + elif hidpp20.battery_functions.get(feature, None): + print("", end=" ") _battery_line(dev) for setting in dev_settings: if setting.feature == feature: - if setting._device and getattr(setting._device, 'persister', None) and \ - setting._device.persister.get(setting.name) is not None: + if ( + setting._device + and getattr(setting._device, "persister", None) + and setting._device.persister.get(setting.name) is not None + ): v = setting.val_to_string(setting._device.persister.get(setting.name)) - print(' %s (saved): %s' % (setting.label, v)) + print(f" {setting.label} (saved): {v}") try: - v = setting.val_to_string(setting.read(False)) - except _hidpp20.FeatureCallError as e: - v = 'HID++ error ' + str(e) - print(' %s : %s' % (setting.label, v)) + v = setting.read(False) + v = setting.val_to_string(v) + except exceptions.FeatureCallError as e: + v = "HID++ error " + str(e) + except AssertionError as e: + v = "AssertionError " + str(e) + print(f" {setting.label} : {v}") if dev.online and dev.keys: - print(' Has %d reprogrammable keys:' % len(dev.keys)) + print(f" Has {len(dev.keys)} reprogrammable keys:") for k in dev.keys: # TODO: add here additional variants for other REPROG_CONTROLS - if dev.keys.keyversion == _hidpp20.FEATURE.REPROG_CONTROLS_V2: - print(' %2d: %-26s => %-27s %s' % (k.index, k.key, k.default_task, ', '.join(k.flags))) - if dev.keys.keyversion == _hidpp20.FEATURE.REPROG_CONTROLS_V4: - print(' %2d: %-26s, default: %-27s => %-26s' % (k.index, k.key, k.default_task, k.mapped_to)) - gmask_fmt = ','.join(k.group_mask) - gmask_fmt = gmask_fmt if gmask_fmt else 'empty' - print(' %s, pos:%d, group:%1d, group mask:%s' % (', '.join(k.flags), k.pos, k.group, gmask_fmt)) - report_fmt = ', '.join(k.mapping_flags) - report_fmt = report_fmt if report_fmt else 'default' - print(' reporting: %s' % (report_fmt)) + if dev.keys.keyversion == SupportedFeature.REPROG_CONTROLS_V2: + print(" %2d: %-26s => %-27s %s" % (k.index, k.key, k.default_task, ", ".join(k.flags))) + if dev.keys.keyversion == SupportedFeature.REPROG_CONTROLS_V4: + print(" %2d: %-26s, default: %-27s => %-26s" % (k.index, k.key, k.default_task, k.mapped_to)) + gmask_fmt = ",".join(k.group_mask) + gmask_fmt = gmask_fmt if gmask_fmt else "empty" + flag_names = list(common.flag_names(hidpp20.KeyFlag, k.flags.value)) + print( + f" {', '.join(flag_names)}, pos:{int(k.pos)}, group:{int(k.group):1}, group mask:{gmask_fmt}" + ) + report_fmt = list(common.flag_names(hidpp20.MappingFlag, k.mapping_flags.value)) + report_fmt = report_fmt if report_fmt else "default" + print(f" reporting: {report_fmt}") if dev.online and dev.remap_keys: - print(' Has %d persistent remappable keys:' % len(dev.remap_keys)) + print(f" Has {len(dev.remap_keys)} persistent remappable keys:") for k in dev.remap_keys: - print(' %2d: %-26s => %s%s' % (k.index, k.key, k.action, ' (remapped)' if k.cidStatus else '')) + print(" %2d: %-26s => %s%s" % (k.index, k.key, k.action, " (remapped)" if k.cidStatus else "")) if dev.online and dev.gestures: print( - ' Has %d gesture(s), %d param(s) and %d spec(s):' % - (len(dev.gestures.gestures), len(dev.gestures.params), len(dev.gestures.specs)) + " Has %d gesture(s), %d param(s) and %d spec(s):" + % (len(dev.gestures.gestures), len(dev.gestures.params), len(dev.gestures.specs)) ) for k in dev.gestures.gestures.values(): print( - ' %-26s Enabled(%4s): %-5s Diverted:(%4s) %s' % - (k.gesture, k.index, k.enabled(), k.diversion_index, k.diverted()) + " %-26s Enabled(%4s): %-5s Diverted:(%4s) %s" + % (k.gesture, k.index, k.enabled(), k.diversion_index, k.diverted()) ) for k in dev.gestures.params.values(): - print(' %-26s Value (%4s): %s [Default: %s]' % (k.param, k.index, k.value, k.default_value)) + print(" %-26s Value (%4s): %s [Default: %s]" % (k.param, k.index, k.value, k.default_value)) for k in dev.gestures.specs.values(): - print(' %-26s Spec (%4s): %s' % (k.spec, k.id, k.value)) + print(" %-26s Spec (%4s): %s" % (k.spec, k.id, k.value)) if dev.online: _battery_line(dev) else: - print(' Battery: unknown (device is offline).') + print(" Battery: unknown (device is offline).") def run(devices, args, find_receiver, find_device): assert devices assert args.device - print('%s version %s' % (NAME, __version__)) - print('') + print(f"{NAME.lower()} version {__version__}") + print("") device_name = args.device.lower() - if device_name == 'all': - dev_num = 1 + if device_name == "all": for d in devices: - if isinstance(d, _receiver.Receiver): + if isinstance(d, (receiver.Receiver, CenturionReceiver)): _print_receiver(d) count = d.count() if count: for dev in d: - print('') - _print_device(dev) + print("") + _print_device(dev, dev.number) count -= 1 if not count: break - print('') + print("") else: - if dev_num == 1: - print('USB and Bluetooth Devices') - print('') - _print_device(d, num=dev_num) - dev_num += 1 + _print_device(d) + print("") return dev = find_receiver(devices, device_name) @@ -318,6 +459,6 @@ def run(devices, args, find_receiver, find_device): dev = next(find_device(devices, device_name), None) if not dev: - raise Exception("no device found matching '%s'" % device_name) + raise Exception(f"no device found matching '{device_name}'") _print_device(dev) diff --git a/lib/solaar/cli/unpair.py b/lib/solaar/cli/unpair.py index f4ca2b61..a6148023 100644 --- a/lib/solaar/cli/unpair.py +++ b/lib/solaar/cli/unpair.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -19,22 +17,74 @@ def run(receivers, args, find_receiver, find_device): assert receivers - assert args.device + + if getattr(args, "slot", None) is not None: + _run_slot_unpair(receivers, args, find_receiver) + return + + assert args.device, "unpair requires a device name, or use --slot" device_name = args.device.lower() dev = next(find_device(receivers, device_name), None) if not dev: - raise Exception("no device found matching '%s'" % device_name) + raise Exception(f"no device found matching '{device_name}'") if not dev.receiver.may_unpair: print( - 'Receiver with USB id %s for %s [%s:%s] does not unpair, but attempting anyway.' % - (dev.receiver.product_id, dev.name, dev.wpid, dev.serial) + f"Receiver with USB id {dev.receiver.product_id} for {dev.name} [{dev.wpid}:{dev.serial}] does not unpair,", + "but attempting anyway.", ) try: # query these now, it's last chance to get them number, codename, wpid, serial = dev.number, dev.codename, dev.wpid, dev.serial dev.receiver._unpair_device(number, True) # force an unpair - print('Unpaired %d: %s (%s) [%s:%s]' % (number, dev.name, codename, wpid, serial)) + print(f"Unpaired {int(number)}: {dev.name} ({codename}) [{wpid}:{serial}]") except Exception as e: raise e + + +def _run_slot_unpair(receivers, args, find_receiver): + if args.receiver: + rcv = find_receiver(receivers, args.receiver.lower()) + if not rcv: + raise Exception(f"no receiver found matching '{args.receiver}'") + elif len(receivers) == 1: + rcv = receivers[0] + else: + names = ", ".join(f"{r.name} [{r.serial}]" for r in receivers) + raise Exception(f"multiple receivers present, pass --receiver to pick one (found: {names})") + + if rcv.receiver_kind != "lightspeed": + raise Exception( + f"--slot unpair is currently only supported on Lightspeed receivers " + f"(this is a {rcv.receiver_kind or 'unknown'} receiver: {rcv.name})" + ) + + slot = int(args.slot) + max_slots = rcv.max_devices or 1 + if slot < 1 or slot > max_slots: + raise Exception(f"--slot {slot} out of range (valid: 1..{max_slots} on {rcv.name})") + + # Populate the cache from the receiver's pairing registers so we can report + # what the slot currently holds. Truthy cache does NOT imply the device is + # reachable on RF — it only means the pairing registers are readable. + list(rcv) + cached = rcv._devices.get(slot) + if cached: + slot_desc = f"{cached.name} [{cached.wpid}:{cached.serial}]" + elif slot in rcv._devices: + slot_desc = "cached None sentinel (pairing info unreadable)" + else: + slot_desc = "no pairing info cached" + + print(f"Slot {slot} on {rcv.name} [{rcv.serial}]: {slot_desc}") + + if getattr(args, "dry_run", False): + print(f"[dry-run] would force-unpair slot {slot} — no register write issued") + return + + ok = rcv.force_unpair_slot(slot) + if ok: + print(f"Slot {slot} unpair register write acknowledged by receiver") + else: + print(f"Slot {slot} unpair register write was not acknowledged (may be a no-op)") diff --git a/lib/solaar/configuration.py b/lib/solaar/configuration.py index 18aeea19..dcbd7578 100644 --- a/lib/solaar/configuration.py +++ b/lib/solaar/configuration.py @@ -1,6 +1,5 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -16,60 +15,54 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import json as _json -import os as _os -import os.path as _path +import json +import logging +import os +import threading -from logging import DEBUG as _DEBUG -from logging import INFO as _INFO -from logging import getLogger -from threading import Lock as _Lock -from threading import Timer as _Timer +import yaml -import yaml as _yaml +from logitech_receiver.common import NamedInt -from logitech_receiver.common import NamedInt as _NamedInt from solaar import __version__ -_log = getLogger(__name__) -del getLogger +logger = logging.getLogger(__name__) -_XDG_CONFIG_HOME = _os.environ.get('XDG_CONFIG_HOME') or _path.expanduser(_path.join('~', '.config')) -_yaml_file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'config.yaml') -_json_file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'config.json') +_XDG_CONFIG_HOME = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser(os.path.join("~", ".config")) +_yaml_file_path = os.path.join(_XDG_CONFIG_HOME, "solaar", "config.yaml") +_json_file_path = os.path.join(_XDG_CONFIG_HOME, "solaar", "config.json") -_KEY_VERSION = '_version' -_KEY_NAME = '_NAME' -_KEY_WPID = '_wpid' -_KEY_SERIAL = '_serial' -_KEY_MODEL_ID = '_modelId' -_KEY_UNIT_ID = '_unitId' -_KEY_ABSENT = '_absent' -_KEY_SENSITIVE = '_sensitive' +_KEY_VERSION = "_version" +_KEY_NAME = "_NAME" +_KEY_WPID = "_wpid" +_KEY_SERIAL = "_serial" +_KEY_MODEL_ID = "_modelId" +_KEY_UNIT_ID = "_unitId" +_KEY_ABSENT = "_absent" +_KEY_SENSITIVE = "_sensitive" _config = [] def _load(): loaded_config = [] - if _path.isfile(_yaml_file_path): + if os.path.isfile(_yaml_file_path): path = _yaml_file_path try: with open(_yaml_file_path) as config_file: - loaded_config = _yaml.safe_load(config_file) + loaded_config = yaml.safe_load(config_file) except Exception as e: - _log.error('failed to load from %s: %s', _yaml_file_path, e) - elif _path.isfile(_json_file_path): + logger.error("failed to load from %s: %s", _yaml_file_path, e) + elif os.path.isfile(_json_file_path): path = _json_file_path try: with open(_json_file_path) as config_file: - loaded_config = _json.load(config_file) + loaded_config = json.load(config_file) except Exception as e: - _log.error('failed to load from %s: %s', _json_file_path, e) + logger.error("failed to load from %s: %s", _json_file_path, e) loaded_config = _convert_json(loaded_config) else: path = None - if _log.isEnabledFor(_DEBUG): - _log.debug('load => %s', loaded_config) + logger.debug("load => %s", loaded_config) global _config _config = _parse_config(loaded_config, path) @@ -84,48 +77,49 @@ def _parse_config(loaded_config, config_path): loaded_version = loaded_config[0] discard_derived_properties = loaded_version != current_version if discard_derived_properties: - if _log.isEnabledFor(_INFO): - _log.info( - 'config file \'%s\' was generated by another version of solaar ' - '(config: %s, current: %s). refreshing detected device capabilities', config_path, loaded_version, - current_version - ) + logger.info( + "config file '%s' was generated by another version of solaar " + "(config: %s, current: %s). refreshing detected device capabilities", + config_path, + loaded_version, + current_version, + ) for device in loaded_config[1:]: assert isinstance(device, dict) parsed_config.append(_device_entry_from_config_dict(device, discard_derived_properties)) except Exception as e: - _log.warning('Exception processing config file \'%s\', ignoring contents: %s', config_path, e) + logger.warning("Exception processing config file '%s', ignoring contents: %s", config_path, e) return parsed_config def _device_entry_from_config_dict(data, discard_derived_properties): - divert = data.get('divert-keys') + divert = data.get("divert-keys") if divert: - sliding = data.get('dpi-sliding') + sliding = data.get("dpi-sliding") if sliding: # convert old-style dpi-sliding setting to divert-keys entry divert[int(sliding)] = 3 - data.pop('dpi-sliding', None) - gestures = data.get('mouse-gestures') + data.pop("dpi-sliding", None) + gestures = data.get("mouse-gestures") if gestures: # convert old-style mouse-gestures setting to divert-keys entry divert[int(gestures)] = 2 - data.pop('mouse-gestures', None) + data.pop("mouse-gestures", None) # remove any string entries (from bad conversions) - data['divert-keys'] = {k: v for k, v in divert.items() if isinstance(k, int)} - if data.get('_sensitive', None) is None: # make scroll wheel settings default to ignore - data['_sensitive'] = { - 'hires-smooth-resolution': 'ignore', - 'hires-smooth-invert': 'ignore', - 'hires-scroll-mode': 'ignore' + data["divert-keys"] = {k: v for k, v in divert.items() if isinstance(k, int)} + if data.get("_sensitive", None) is None: # make scroll wheel settings default to ignore + data["_sensitive"] = { + "hires-smooth-resolution": "ignore", + "hires-smooth-invert": "ignore", + "hires-scroll-mode": "ignore", } if discard_derived_properties: - data.pop('_absent', None) - data.pop('_battery', None) + data.pop("_absent", None) + data.pop("_battery", None) return _DeviceEntry(**data) save_timer = None -save_lock = _Lock() +configuration_lock = threading.Lock() defer_saves = False # don't allow configuration saves to be deferred @@ -133,62 +127,59 @@ def save(defer=False): global save_timer if not _config: return - dirname = _os.path.dirname(_yaml_file_path) - if not _path.isdir(dirname): + dirname = os.path.dirname(_yaml_file_path) + if not os.path.isdir(dirname): try: - _os.makedirs(dirname) + os.makedirs(dirname) except Exception: - _log.error('failed to create %s', dirname) + logger.error("failed to create %s", dirname) return if not defer or not defer_saves: do_save() else: - with save_lock: + with configuration_lock: if not save_timer: - from gi.repository import GLib - save_timer = _Timer(5.0, lambda: GLib.idle_add(do_save)) + save_timer = threading.Timer(5.0, do_save) save_timer.start() def do_save(): global save_timer - with save_lock: + with configuration_lock: if save_timer: save_timer.cancel() save_timer = None try: - with open(_yaml_file_path, 'w') as config_file: - _yaml.dump(_config, config_file, default_flow_style=None, width=150) - if _log.isEnabledFor(_INFO): - _log.info('saved %s to %s', _config, _yaml_file_path) + with open(_yaml_file_path, "w") as config_file: + yaml.dump(_config, config_file, default_flow_style=None, width=150) + logger.info("saved %s to %s", _config, _yaml_file_path) except Exception as e: - _log.error('failed to save to %s: %s', _yaml_file_path, e) + logger.error("failed to save to %s: %s", _yaml_file_path, e) def _convert_json(json_dict): config = [json_dict.get(_KEY_VERSION)] for key, dev in json_dict.items(): - key = key.split(':') + key = key.split(":") if len(key) == 2: dev[_KEY_WPID] = dev.get(_KEY_WPID) if dev.get(_KEY_WPID) else key[0] dev[_KEY_SERIAL] = dev.get(_KEY_SERIAL) if dev.get(_KEY_SERIAL) else key[1] for k, v in dev.items(): - if type(k) == str and not k.startswith('_') and type(v) == dict: # convert string keys to ints - v = {int(dk) if type(dk) == str else dk: dv for dk, dv in v.items()} + if isinstance(k, str) and not k.startswith("_") and isinstance(v, dict): # convert string keys to ints + v = {int(dk) if isinstance(dk, str) else dk: dv for dk, dv in v.items()} dev[k] = v - for k in ['mouse-gestures', 'dpi-sliding']: + for k in ["mouse-gestures", "dpi-sliding"]: v = dev.get(k, None) if v is True or v is False: dev.pop(k) - if '_name' in dev: - dev[_KEY_NAME] = dev['_name'] - dev.pop('_name') + if "_name" in dev: + dev[_KEY_NAME] = dev["_name"] + dev.pop("_name") config.append(dev) return config class _DeviceEntry(dict): - def __init__(self, **kwargs): super().__init__(**kwargs) @@ -196,17 +187,17 @@ class _DeviceEntry(dict): super().__setitem__(key, value) save(defer=True) - def update(self, device, modelId): - if device.name and device.name != self.get(_KEY_NAME): - super().__setitem__(_KEY_NAME, device.name) - if device.wpid and device.wpid != self.get(_KEY_WPID): - super().__setitem__(_KEY_WPID, device.wpid) - if device.serial and device.serial != '?' and device.serial != self.get(_KEY_SERIAL): - super().__setitem__(_KEY_SERIAL, device.serial) + def update(self, name, wpid, serial, modelId, unitId): + if name and name != self.get(_KEY_NAME): + super().__setitem__(_KEY_NAME, name) + if wpid and wpid != self.get(_KEY_WPID): + super().__setitem__(_KEY_WPID, wpid) + if serial and serial != self.get(_KEY_SERIAL): + super().__setitem__(_KEY_SERIAL, serial) if modelId and modelId != self.get(_KEY_MODEL_ID): super().__setitem__(_KEY_MODEL_ID, modelId) - if device.unitId and device.unitId != self.get(_KEY_UNIT_ID): - super().__setitem__(_KEY_UNIT_ID, device.unitId) + if unitId and unitId != self.get(_KEY_UNIT_ID): + super().__setitem__(_KEY_UNIT_ID, unitId) def get_sensitivity(self, name): return self.get(_KEY_SENSITIVE, {}).get(name, False) @@ -219,17 +210,17 @@ class _DeviceEntry(dict): def device_representer(dumper, data): - return dumper.represent_mapping('tag:yaml.org,2002:map', data) + return dumper.represent_mapping("tag:yaml.org,2002:map", data) -_yaml.add_representer(_DeviceEntry, device_representer) +yaml.add_representer(_DeviceEntry, device_representer) def named_int_representer(dumper, data): - return dumper.represent_scalar('tag:yaml.org,2002:int', str(int(data))) + return dumper.represent_scalar("tag:yaml.org,2002:int", str(int(data))) -_yaml.add_representer(_NamedInt, named_int_representer) +yaml.add_representer(NamedInt, named_int_representer) # A device can be identified by a combination of WPID and serial number (for receiver-connected devices) @@ -237,34 +228,41 @@ _yaml.add_representer(_NamedInt, named_int_representer) # But some devices have empty (all zero) modelIds and unitIds. Use the device name as a backup for the modelId. # The worst situation is a receiver-connected device that Solaar has never seen on-line # that is directly connected. Here there is no way to realize that the two devices are the same. -# So new entries are not created for unseen off-line receiver-connected devices except for those with protocol 1.0 +# So new entries are not created for unseen off-line receiver-connected devices def persister(device): - def match(wpid, serial, modelId, unitId, c): - return ((wpid and wpid == c.get(_KEY_WPID) and serial and serial == c.get(_KEY_SERIAL)) or ( - modelId and modelId != '000000000000' and modelId == c.get(_KEY_MODEL_ID) and unitId - and unitId == c.get(_KEY_UNIT_ID) - )) + return ( + (wpid and wpid == c.get(_KEY_WPID) and serial and serial == c.get(_KEY_SERIAL)) + or (modelId and modelId == c.get(_KEY_MODEL_ID) and unitId and unitId == c.get(_KEY_UNIT_ID)) + or ( + c.get(_KEY_WPID) is None + and c.get(_KEY_SERIAL) is None + and c.get(_KEY_UNIT_ID) is None + and modelId + and modelId == c.get(_KEY_MODEL_ID) + ) + ) - if not _config: - _load() - entry = None - modelId = device.modelId if device.modelId != '000000000000' else device.name if device.modelId else None - for c in _config: - if isinstance(c, _DeviceEntry) and match(device.wpid, device.serial, modelId, device.unitId, c): - entry = c - break - if not entry: - if not device.online and not device.serial: # don't create entry for offline devices without serial number - if _log.isEnabledFor(_INFO): - _log.info('not setting up persister for offline device %s with missing serial number', device.name) - return - if _log.isEnabledFor(_INFO): - _log.info('setting up persister for device %s', device.name) - entry = _DeviceEntry() - _config.append(entry) - entry.update(device, modelId) - return entry + with configuration_lock: + if not _config: + _load() + entry = None + # some devices report modelId and unitId as zero so use name and serial for them + modelId = device.modelId if device.modelId != "000000000000" else device._name if device._name else None + unitId = device.unitId if device.unitId != "00000000" else device._serial if device._serial else None + for c in _config: + if isinstance(c, _DeviceEntry) and match(device.wpid, device._serial, modelId, unitId, c): + entry = c + break + if not entry: + if not device.online: # don't create entry for offline devices + logger.info("not setting up persister for offline device %s", device._name) + return + logger.info("setting up persister for device %s", device.name) + entry = _DeviceEntry() + _config.append(entry) + entry.update(device.name, device.wpid, device.serial, modelId, unitId) + return entry def attach_to(device): diff --git a/lib/solaar/custom_logger.py b/lib/solaar/custom_logger.py new file mode 100644 index 00000000..a4337bf1 --- /dev/null +++ b/lib/solaar/custom_logger.py @@ -0,0 +1,28 @@ +import logging + + +class CustomLogger(logging.Logger): + """Logger, that avoids unnecessary string computations. + + Does not compute messages for disabled log levels. + """ + + def debug(self, msg, *args, **kwargs): + if self.isEnabledFor(logging.DEBUG): + super().debug(msg, *args, **kwargs) + + def info(self, msg, *args, **kwargs): + if self.isEnabledFor(logging.INFO): + super().info(msg, *args, **kwargs) + + def warning(self, msg, *args, **kwargs): + if self.isEnabledFor(logging.WARNING): + super().warning(msg, *args, **kwargs) + + def error(self, msg, *args, **kwargs): + if self.isEnabledFor(logging.ERROR): + super().error(msg, *args, **kwargs) + + def critical(self, msg, *args, **kwargs): + if self.isEnabledFor(logging.CRITICAL): + super().critical(msg, *args, **kwargs) diff --git a/lib/solaar/dbus.py b/lib/solaar/dbus.py new file mode 100644 index 00000000..142b5904 --- /dev/null +++ b/lib/solaar/dbus.py @@ -0,0 +1,87 @@ +## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from __future__ import annotations + +import logging + +from typing import Callable + +logger = logging.getLogger(__name__) + +try: + import dbus + + from dbus.mainloop.glib import DBusGMainLoop # integration into the main GLib loop + + DBusGMainLoop(set_as_default=True) + bus = dbus.SystemBus() + assert bus + +except Exception: + # Either the dbus library is not available or the system dbus is not running + logger.warning("failed to set up dbus") + bus = None + + +_suspend_callback = None +_resume_callback = None + + +def _suspend_or_resume(suspend): + if suspend and _suspend_callback: + _suspend_callback() + if not suspend and _resume_callback: + _resume_callback() + + +_LOGIND_PATH = "/org/freedesktop/login1" +_LOGIND_INTERFACE = "org.freedesktop.login1.Manager" + + +def watch_suspend_resume( + on_resume_callback: Callable[[], None] | None = None, + on_suspend_callback: Callable[[], None] | None = None, +): + """Register callback for suspend/resume events. + They are called only if the system DBus is running, and the Login daemon is available.""" + global _resume_callback, _suspend_callback + _suspend_callback = on_suspend_callback + _resume_callback = on_resume_callback + if bus is not None and on_resume_callback is not None or on_suspend_callback is not None: + bus.add_signal_receiver( + _suspend_or_resume, + "PrepareForSleep", + dbus_interface=_LOGIND_INTERFACE, + path=_LOGIND_PATH, + ) + logger.info("connected to system dbus, watching for suspend/resume events") + + +_BLUETOOTH_PATH_PREFIX = "/org/bluez/hci0/dev_" +_BLUETOOTH_INTERFACE = "org.freedesktop.DBus.Properties" + +_bluetooth_callbacks = {} + + +def watch_bluez_connect(serial, callback=None): + if _bluetooth_callbacks.get(serial): + _bluetooth_callbacks.get(serial).remove() + path = _BLUETOOTH_PATH_PREFIX + serial.replace(":", "_").upper() + if bus is not None and callback is not None: + _bluetooth_callbacks[serial] = bus.add_signal_receiver( + callback, "PropertiesChanged", path=path, dbus_interface=_BLUETOOTH_INTERFACE + ) diff --git a/lib/solaar/gtk.py b/lib/solaar/gtk.py index f8042365..e0680820 100755 --- a/lib/solaar/gtk.py +++ b/lib/solaar/gtk.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- python-mode -*- -# -*- coding: UTF-8 -*- ## Copyright (C) 2012-2013 Daniel Pavel ## @@ -18,7 +16,10 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import argparse +import faulthandler import importlib +import locale import logging import os.path import platform @@ -26,19 +27,19 @@ import signal import sys import tempfile -from logging import INFO as _INFO -from logging import WARNING as _WARNING +from traceback import format_exc -import solaar.cli as _cli -import solaar.i18n as _i18n +from solaar import NAME +from solaar import __version__ +from solaar import cli +from solaar import configuration +from solaar import dbus +from solaar import listener +from solaar import ui +from solaar.custom_logger import CustomLogger -from solaar import NAME, __version__ - -_log = logging.getLogger(__name__) - -# -# -# +logging.setLoggerClass(CustomLogger) +logger = logging.getLogger(__name__) def _require(module, os_package, gi=None, gi_package=None, gi_version=None): @@ -47,145 +48,165 @@ def _require(module, os_package, gi=None, gi_package=None, gi_version=None): gi.require_version(gi_package, gi_version) return importlib.import_module(module) except (ImportError, ValueError): - sys.exit('%s: missing required system package %s' % (NAME, os_package)) + sys.exit(f"{NAME.lower()}: missing required system package {os_package}") -battery_icons_style = 'regular' -temp = tempfile.NamedTemporaryFile(prefix='Solaar_', mode='w', delete=True) +battery_icons_style = "regular" +tray_icon_size = None +temp = tempfile.NamedTemporaryFile(prefix="Solaar_", mode="w", delete=True) + + +def create_parser(): + arg_parser = argparse.ArgumentParser( + prog=NAME.lower(), + description="Solaar is a program to manage many Logitech devices, " + "changing how they operate and maintaining the changes whenever the device connects.", + epilog="For more information see https://pwr-solaar.github.io/Solaar", + ) + arg_parser.add_argument( + "-d", + "--debug", + action="count", + default=0, + help="print logging messages, for debugging purposes (may be repeated for extra verbosity)", + ) + arg_parser.add_argument( + "-D", + "--hidraw", + action="store", + dest="hidraw_path", + metavar="PATH", + help="device or receiver path to use if needed. Example: /dev/hidraw2", + ) + arg_parser.add_argument( + "--restart-on-wake-up", + action="store_true", + help="restart Solaar on sleep wake-up (experimental)", + ) + arg_parser.add_argument( + "-w", + "--window", + choices=("show", "hide", "only"), + help="start with window showing / hidden / only (no tray icon)", + ) + arg_parser.add_argument( + "-b", + "--battery-icons", + choices=("regular", "symbolic", "solaar"), + help="prefer regular battery / symbolic battery / solaar icons", + ) + arg_parser.add_argument("--tray-icon-size", type=int, help="explicit size for tray icons") + arg_parser.add_argument("-V", "--version", action="version", version="%(prog)s " + __version__) + arg_parser.add_argument("--help-actions", action="store_true", help="describe the command-line actions") + arg_parser.add_argument( + "action", + nargs=argparse.REMAINDER, + choices=cli.actions, + help="command-line action to perform (optional); append ' --help' to show args", + ) + return arg_parser def _parse_arguments(): - import argparse - arg_parser = argparse.ArgumentParser( - prog=NAME.lower(), epilog='For more information see https://pwr-solaar.github.io/Solaar' - ) - arg_parser.add_argument( - '-d', - '--debug', - action='count', - default=0, - help='print logging messages, for debugging purposes (may be repeated for extra verbosity)' - ) - arg_parser.add_argument( - '-D', - '--hidraw', - action='store', - dest='hidraw_path', - metavar='PATH', - help='unifying receiver to use; the first detected receiver if unspecified. Example: /dev/hidraw2' - ) - arg_parser.add_argument('--restart-on-wake-up', action='store_true', help='restart Solaar on sleep wake-up (experimental)') - arg_parser.add_argument( - '-w', '--window', choices=('show', 'hide', 'only'), help='start with window showing / hidden / only (no tray icon)' - ) - arg_parser.add_argument( - '-b', - '--battery-icons', - choices=('regular', 'symbolic', 'solaar'), - help='prefer regular battery / symbolic battery / solaar icons' - ) - arg_parser.add_argument('--tray-icon-size', type=int, help='explicit size for tray icons') - arg_parser.add_argument('-V', '--version', action='version', version='%(prog)s ' + __version__) - arg_parser.add_argument('--help-actions', action='store_true', help='print help for the optional actions') - arg_parser.add_argument('action', nargs=argparse.REMAINDER, choices=_cli.actions, help='optional actions to perform') - + arg_parser = create_parser() args = arg_parser.parse_args() if args.help_actions: - _cli.print_help() + cli.print_help() return if args.window is None: - args.window = 'show' # default behaviour is to show main window + args.window = "show" # default behaviour is to show main window global battery_icons_style - battery_icons_style = args.battery_icons if args.battery_icons is not None else 'regular' + battery_icons_style = args.battery_icons if args.battery_icons is not None else "regular" global tray_icon_size tray_icon_size = args.tray_icon_size - log_format = '%(asctime)s,%(msecs)03d %(levelname)8s [%(threadName)s] %(name)s: %(message)s' + log_format = "%(asctime)s,%(msecs)03d %(levelname)8s [%(threadName)s] %(name)s: %(message)s" log_level = logging.ERROR - 10 * args.debug - logging.getLogger('').setLevel(min(log_level, logging.WARNING)) + logging.getLogger("").setLevel(min(log_level, logging.WARNING)) file_handler = logging.StreamHandler(temp) file_handler.setLevel(max(min(log_level, logging.WARNING), logging.INFO)) file_handler.setFormatter(logging.Formatter(log_format)) - logging.getLogger('').addHandler(file_handler) + logging.getLogger("").addHandler(file_handler) if args.debug > 0: stream_handler = logging.StreamHandler() stream_handler.setFormatter(logging.Formatter(log_format)) stream_handler.setLevel(log_level) - logging.getLogger('').addHandler(stream_handler) + logging.getLogger("").addHandler(stream_handler) if not args.action: - if _log.isEnabledFor(logging.INFO): - logging.info('version %s, language %s (%s)', __version__, _i18n.language, _i18n.encoding) + language, encoding = locale.getlocale() + logger.info("version %s, language %s (%s)", __version__, language, encoding) return args # On first SIGINT, dump threads to stderr; on second, exit def _handlesig(signl, stack): - import faulthandler signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGTERM, signal.SIG_DFL) if signl == int(signal.SIGINT): - if _log.isEnabledFor(_INFO): + if logger.isEnabledFor(logging.INFO): faulthandler.dump_traceback() - sys.exit('%s: exit due to keyboard interrupt' % (NAME.lower())) + sys.exit(f"{NAME.lower()}: exit due to keyboard interrupt") else: sys.exit(0) def main(): - if platform.system() not in ('Darwin', 'Windows'): - _require('pyudev', 'python3-pyudev') + if platform.system() not in ("Darwin", "Windows"): + _require("pyudev", "python3-pyudev") args = _parse_arguments() if not args: + # explicit close before return + temp.close() return if args.action: # if any argument, run comandline and exit - return _cli.run(args.action, args.hidraw_path) + result = cli.run(args.action, args.hidraw_path) + # explicit close before return + temp.close() + return result - gi = _require('gi', 'python3-gi (in Ubuntu) or python3-gobject (in Fedora)') - _require('gi.repository.Gtk', 'gir1.2-gtk-3.0', gi, 'Gtk', '3.0') + gi = _require("gi", "python3-gi (in Ubuntu) or python3-gobject (in Fedora)") + _require("gi.repository.Gtk", "gir1.2-gtk-3.0", gi, "Gtk", "3.0") # handle ^C in console signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGINT, _handlesig) signal.signal(signal.SIGTERM, _handlesig) - udev_file = '42-logitech-unify-permissions.rules' - if _log.isEnabledFor(_WARNING) \ - and not os.path.isfile('/etc/udev/rules.d/' + udev_file) \ - and not os.path.isfile('/usr/lib/udev/rules.d/' + udev_file) \ - and not os.path.isfile('/usr/local/lib/udev/rules.d/' + udev_file): - _log.warning('Solaar udev file not found in expected location') - _log.warning('See https://pwr-solaar.github.io/Solaar/installation for more information') + udev_file = "42-logitech-unify-permissions.rules" + if ( + platform.system() == "Linux" + and logger.isEnabledFor(logging.WARNING) + and not os.path.isfile("/etc/udev/rules.d/" + udev_file) + and not os.path.isfile("/usr/lib/udev/rules.d/" + udev_file) + and not os.path.isfile("/usr/local/lib/udev/rules.d/" + udev_file) + ): + logger.warning("Solaar udev file not found in expected location") + logger.warning("See https://pwr-solaar.github.io/Solaar/installation for more information") try: - import solaar.listener as listener - import solaar.ui as ui + listener.setup_scanner(ui.status_changed, ui.setting_changed, ui.common.error_dialog) - listener.setup_scanner(ui.status_changed, ui.error_dialog) - - import solaar.upower as _upower if args.restart_on_wake_up: - _upower.watch(listener.start_all, listener.stop_all) + dbus.watch_suspend_resume(listener.start_all, listener.stop_all) else: - _upower.watch(lambda: listener.ping_all(True)) + dbus.watch_suspend_resume(lambda: listener.ping_all(True)) - import solaar.configuration as _configuration - _configuration.defer_saves = True # allow configuration saves to be deferred + configuration.defer_saves = True # allow configuration saves to be deferred # main UI event loop - ui.run_loop(listener.start_all, listener.stop_all, args.window != 'only', args.window != 'hide') + ui.run_loop(listener.start_all, listener.stop_all, args.window != "only", args.window != "hide") except Exception: - from traceback import format_exc - sys.exit('%s: error: %s' % (NAME.lower(), format_exc())) + sys.exit(f"{NAME.lower()}: error: {format_exc()}") temp.close() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/lib/solaar/i18n.py b/lib/solaar/i18n.py index 7eddb7ef..d3e53242 100644 --- a/lib/solaar/i18n.py +++ b/lib/solaar/i18n.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -16,47 +14,60 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import gettext as _gettext +import gettext import locale +import logging +import os +import sys -from solaar import NAME as _NAME +from glob import glob -# -# -# +from solaar import NAME + +_LOCALE_DOMAIN = NAME.lower() + +logger = logging.getLogger(__name__) -def _find_locale_path(lc_domain): - import os.path as _path - import sys as _sys - prefix_share = _path.normpath(_path.join(_path.realpath(_sys.path[0]), '..')) - src_share = _path.normpath(_path.join(_path.realpath(_sys.path[0]), '..', 'share')) - del _sys - - from glob import glob as _glob +def _find_locale_path(locale_domain: str) -> str: + prefix_share = os.path.normpath(os.path.join(os.path.realpath(sys.path[0]), "..")) + src_share = os.path.normpath(os.path.join(os.path.realpath(sys.path[0]), "..", "share")) for location in prefix_share, src_share: - mo_files = _glob(_path.join(location, 'locale', '*', 'LC_MESSAGES', lc_domain + '.mo')) + mo_files = glob(os.path.join(location, "locale", "*", "LC_MESSAGES", f"{locale_domain}.mo")) if mo_files: - return _path.join(location, 'locale') - - # del _path + return os.path.join(location, "locale") + raise FileNotFoundError(f"Could not find locale path for {locale_domain}") -try: - locale.setlocale(locale.LC_ALL, '') -except Exception: - pass +def set_locale_to_system_default() -> None: + """Sets locale for translations to the system default. -language, encoding = locale.getlocale() -del locale + If locale is unsupported, fallback to standard English without + translation 'C'. -_LOCALE_DOMAIN = _NAME.lower() -path = _find_locale_path(_LOCALE_DOMAIN) + Set LC_ALL environment variable to enforce a locale setting e.g. + 'de_DE.UTF-8'. Run Solaar with your desired localization, for German + use: + 'LC_ALL=de_DE.UTF-8 solaar' + """ + try: + locale.setlocale(locale.LC_ALL, "") # system default + except locale.Error: + logger.error("User locale not supported by system, using no translation.") + locale.setlocale(locale.LC_ALL, "C") # untranslated (English) + return -_gettext.bindtextdomain(_LOCALE_DOMAIN, path) -_gettext.textdomain(_LOCALE_DOMAIN) -_gettext.install(_LOCALE_DOMAIN) + try: + path = _find_locale_path(_LOCALE_DOMAIN) + except FileNotFoundError: + path = None + gettext.bindtextdomain(_LOCALE_DOMAIN, path) + gettext.textdomain(_LOCALE_DOMAIN) + gettext.install(_LOCALE_DOMAIN) -_ = _gettext.gettext -ngettext = _gettext.ngettext + +set_locale_to_system_default() + +_ = gettext.gettext +ngettext = gettext.ngettext diff --git a/lib/solaar/listener.py b/lib/solaar/listener.py index 93b71ee0..4aee6700 100644 --- a/lib/solaar/listener.py +++ b/lib/solaar/listener.py @@ -1,6 +1,5 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -16,94 +15,83 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import errno as _errno +from __future__ import annotations + +import errno +import logging +import subprocess import time +import typing from collections import namedtuple -from logging import DEBUG as _DEBUG -from logging import INFO as _INFO -from logging import WARNING as _WARNING -from logging import getLogger +from functools import partial +from typing import Callable import gi +import logitech_receiver -from logitech_receiver import Device, Receiver -from logitech_receiver import base as _base -from logitech_receiver import hidpp10 as _hidpp10 -from logitech_receiver import listener as _listener -from logitech_receiver import notifications as _notifications -from logitech_receiver import status as _status +from logitech_receiver import base +from logitech_receiver import exceptions +from logitech_receiver import hidpp10_constants +from logitech_receiver import listener +from logitech_receiver import notifications from . import configuration +from . import dbus +from . import i18n +from .ui import common -gi.require_version('Gtk', '3.0') # NOQA: E402 +if typing.TYPE_CHECKING: + from hidapi.common import DeviceInfo + +gi.require_version("Gtk", "3.0") # NOQA: E402 from gi.repository import GLib # NOQA: E402 # isort:skip -# from solaar.i18n import _ +if typing.TYPE_CHECKING: + from logitech_receiver.device import Device -_log = getLogger(__name__) -del getLogger +logger = logging.getLogger(__name__) -_R = _hidpp10.REGISTERS -_IR = _hidpp10.INFO_SUBREGISTERS +ACTION_ADD = "add" -# -# -# - -_GHOST_DEVICE = namedtuple('_GHOST_DEVICE', ('receiver', 'number', 'name', 'kind', 'status', 'online')) +_GHOST_DEVICE = namedtuple("_GHOST_DEVICE", ("receiver", "number", "name", "kind", "online", "path")) _GHOST_DEVICE.__bool__ = lambda self: False _GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__ -del namedtuple def _ghost(device): return _GHOST_DEVICE( - receiver=device.receiver, number=device.number, name=device.name, kind=device.kind, status=None, online=False + receiver=device.receiver, number=device.number, name=device.name, kind=device.kind, online=False, path=None ) -# -# -# - -# how often to poll devices that haven't updated their statuses on their own -# (through notifications) -# _POLL_TICK = 5 * 60 # seconds - - -class ReceiverListener(_listener.EventsListener): - """Keeps the status of a Receiver. - """ +class SolaarListener(listener.EventsListener): + """Keeps the status of a Receiver or Device (member name is receiver but it can also be a device).""" def __init__(self, receiver, status_changed_callback): - super().__init__(receiver, self._notifications_handler) - # no reason to enable polling yet - # self.tick_period = _POLL_TICK - # self._last_tick = 0 - assert status_changed_callback + super().__init__(receiver, self._notifications_handler) self.status_changed_callback = status_changed_callback - _status.attach_to(receiver, self._status_changed) + receiver.status_callback = self._status_changed def has_started(self): - if _log.isEnabledFor(_INFO): - _log.info('%s: notifications listener has started (%s)', self.receiver, self.receiver.handle) + logger.info("%s: notifications listener has started (%s)", self.receiver, self.receiver.handle) nfs = self.receiver.enable_connection_notifications() - assert self.receiver.isDevice or ((nfs if nfs else 0) & _hidpp10.NOTIFICATION_FLAG.wireless), \ - 'Receiver on %s does not support connection notifications, GUI will not show it' % self.receiver.path - self.receiver.status[_status.KEYS.NOTIFICATION_FLAGS] = nfs + if not self.receiver.isDevice and (not nfs or not (nfs & hidpp10_constants.NotificationFlag.WIRELESS)): + logger.warning( + "Receiver on %s might not support connection notifications, GUI might not show its devices", + self.receiver.path, + ) + self.receiver.notification_flags = nfs self.receiver.notify_devices() - self._status_changed(self.receiver) # , _status.ALERT.NOTIFICATION) + self._status_changed(self.receiver) def has_stopped(self): r, self.receiver = self.receiver, None assert r is not None - if _log.isEnabledFor(_INFO): - _log.info('%s: notifications listener has stopped', r) + logger.info("%s: notifications listener has stopped", r) - # because udev is not notifying us about device removal, - # make sure to clean up in _all_listeners + # because udev is not notifying us about device removal, make sure to clean up in _all_listeners _all_listeners.pop(r.path, None) # this causes problems but what is it doing (pfps) - r.status = _('The receiver was unplugged.') @@ -111,59 +99,32 @@ class ReceiverListener(_listener.EventsListener): try: r.close() except Exception: - _log.exception('closing receiver %s' % r.path) - self.status_changed_callback(r) # , _status.ALERT.NOTIFICATION) + logger.exception(f"closing receiver {r.path}") + self.status_changed_callback(r) - # def tick(self, timestamp): - # if not self.tick_period: - # raise Exception("tick() should not be called without a tick_period: %s", self) - # - # # not necessary anymore, we're now using udev monitor to watch for receiver status - # # if self._last_tick > 0 and timestamp - self._last_tick > _POLL_TICK * 2: - # # # if we missed a couple of polls, most likely the computer went into - # # # sleep, and we have to reinitialize the receiver again - # # _log.warn("%s: possible sleep detected, closing this listener", self.receiver) - # # self.stop() - # # return - # - # self._last_tick = timestamp - # - # try: - # # read these in case they haven't been read already - # # self.receiver.serial, self.receiver.firmware - # if self.receiver.status.lock_open: - # # don't mess with stuff while pairing - # return - # - # self.receiver.status.poll(timestamp) - # - # # Iterating directly through the reciver would unnecessarily probe - # # all possible devices, even unpaired ones. - # # Checking for each device number in turn makes sure only already - # # known devices are polled. - # # This is okay because we should have already known about them all - # # long before the first poll() happents, through notifications. - # for number in range(1, 6): - # if number in self.receiver: - # dev = self.receiver[number] - # if dev and dev.status is not None: - # dev.status.poll(timestamp) - # except Exception as e: - # _log.exception("polling", e) - - def _status_changed(self, device, alert=_status.ALERT.NONE, reason=None): + def _status_changed(self, device, alert=None, reason=None): assert device is not None - if _log.isEnabledFor(_INFO): - if device.kind is None: - _log.info( - 'status_changed %r: %s, %s (%X) %s', device, 'present' if bool(device) else 'removed', device.status, - alert, reason or '' - ) - else: - _log.info( - 'status_changed %r: %s %s, %s (%X) %s', device, 'paired' if bool(device) else 'unpaired', - 'online' if device.online else 'offline', device.status, alert, reason or '' - ) + if logger.isEnabledFor(logging.INFO): + try: + if device.kind is None: + logger.info( + "status_changed %r: %s (%X) %s", + device, + "present" if bool(device) else "removed", + alert if alert is not None else 0, + reason or "", + ) + else: + logger.info( + "status_changed %r: %s %s (%X) %s", + device, + "paired" if bool(device) else "unpaired", + "online" if device.online else "offline", + alert if alert is not None else 0, + reason or "", + ) + except Exception as e: + logger.info("status_changed for unknown device: %s", e) if device.kind is None: assert device == self.receiver @@ -174,53 +135,49 @@ class ReceiverListener(_listener.EventsListener): # not true for wired devices - assert device.receiver == self.receiver if not device: # Device was unpaired, and isn't valid anymore. - # We replace it with a ghost so that the UI has something to work - # with while cleaning up. - if _log.isEnabledFor(_INFO): - _log.info('device %s was unpaired, ghosting', device) + # We replace it with a ghost so that the UI has something to work with while cleaning up. + logger.info("device %s was unpaired, ghosting", device) device = _ghost(device) self.status_changed_callback(device, alert, reason) if not device: - # the device was just unpaired, need to update the - # status of the receiver as well + # the device was just unpaired, need to update the status of the receiver as well self.status_changed_callback(self.receiver) def _notifications_handler(self, n): assert self.receiver - # if _log.isEnabledFor(_DEBUG): - # _log.debug("%s: handling %s", self.receiver, n) if n.devnumber == 0xFF: + # For CenturionReceiver, intercept bridge notifications and dispatch to child device + from logitech_receiver.device import CenturionReceiver + + if isinstance(self.receiver, CenturionReceiver): + self._handle_centurion_notification(n) + return # a receiver notification - _notifications.process(self.receiver, n) + notifications.process(self.receiver, n) return # a notification that came in to the device listener - strange, but nothing needs to be done here if self.receiver.isDevice: - if _log.isEnabledFor(_DEBUG): - _log.debug('Notification %s via device %s being ignored.', n, self.receiver) + logger.debug("Notification %s via device %s being ignored.", n, self.receiver) return # DJ pairing notification - ignore - hid++ 1.0 pairing notification is all that is needed - if n.sub_id == 0x41 and n.report_id == _base.DJ_MESSAGE_ID: - if _log.isEnabledFor(_INFO): - _log.info('ignoring DJ pairing notification %s', n) + if n.sub_id == 0x41 and n.report_id == base.DJ_MESSAGE_ID: + logger.info("ignoring DJ pairing notification %s", n) return # a device notification if not (0 < n.devnumber <= 16): # some receivers have devices past their max # devices - if _log.isEnabledFor(_WARNING): - _log.warning('Unexpected device number (%s) in notification %s.', n.devnumber, n) + logger.warning("Unexpected device number (%s) in notification %s.", n.devnumber, n) return already_known = n.devnumber in self.receiver # FIXME: hacky fix for kernel/hardware race condition - # If the device was just turned on or woken up from sleep, it may not - # be ready to receive commands. The "payload" bit of the wireless - # status notification seems to tell us this. If this is the case, we - # must wait a short amount of time to avoid causing a broken pipe - # error. + # If the device was just turned on or woken up from sleep, it may not be ready to receive commands. + # The "payload" bit of the wireless status notification seems to tell us this. If this is the case, we + # must wait a short amount of time to avoid causing a broken pipe error. device_ready = not bool(ord(n.data[0:1]) & 0x80) or n.sub_id != 0x41 if not device_ready: time.sleep(0.01) @@ -230,172 +187,284 @@ class ReceiverListener(_listener.EventsListener): if n.sub_id == 0x41: if not already_known: - if n.address == 0x0A and not self.receiver.receiver_kind == 'bolt': + if n.address == 0x0A and not self.receiver.receiver_kind == "bolt": # some Nanos send a notification even if no new pairing - check that there really is a device there - if self.receiver.read_register(_R.receiver_info, _IR.pairing_information + n.devnumber - 1) is None: + if ( + self.receiver.read_register( + hidpp10_constants.Registers.RECEIVER_INFO, + hidpp10_constants.InfoSubRegisters.PAIRING_INFORMATION + n.devnumber - 1, + ) + is None + ): return dev = self.receiver.register_new_device(n.devnumber, n) - elif self.receiver.status.lock_open and self.receiver.re_pairs and not ord(n.data[0:1]) & 0x40: + elif self.receiver.pairing.lock_open and self.receiver.re_pairs and not ord(n.data[0:1]) & 0x40: dev = self.receiver[n.devnumber] del self.receiver[n.devnumber] # get rid of information on device re-paired away self._status_changed(dev) # signal that this device has changed dev = self.receiver.register_new_device(n.devnumber, n) - self.receiver.status.new_device = self.receiver[n.devnumber] + self.receiver.pairing.new_device = self.receiver[n.devnumber] else: dev = self.receiver[n.devnumber] else: dev = self.receiver[n.devnumber] if not dev: - _log.warn('%s: received %s for invalid device %d: %r', self.receiver, n, n.devnumber, dev) + logger.warning("%s: received %s for invalid device %d: %r", self.receiver, n, n.devnumber, dev) return # Apply settings every time the device connects if n.sub_id == 0x41: - if _log.isEnabledFor(_INFO): - _log.info('connection %s for %r', n, dev) + logger.info("connection %s for device wpid %s kind %s serial %s", n, dev.wpid, dev.kind, dev._serial) # If there are saved configs, bring the device's settings up-to-date. # They will be applied when the device is marked as online. configuration.attach_to(dev) - _status.attach_to(dev, self._status_changed) + dev.status_callback = self._status_changed # the receiver changed status as well self._status_changed(self.receiver) - if not hasattr(dev, 'status') or dev.status is None: - # notification before device status set up - don't process it - _log.warn('%s before device %s has status', n, dev) - else: - _notifications.process(dev, n) + notifications.process(dev, n) - if self.receiver.status.lock_open and not already_known: + if self.receiver.pairing.lock_open and not already_known: # this should be the first notification after a device was paired - assert n.sub_id == 0x41, 'first notification was not a connection notification' - if _log.isEnabledFor(_INFO): - _log.info('%s: pairing detected new device', self.receiver) - self.receiver.status.new_device = dev + logger.warning("first notification was not a connection notification") + logger.info("%s: pairing detected new device", self.receiver) + self.receiver.pairing.new_device = dev elif dev.online is None: dev.ping() + def _handle_centurion_notification(self, n): + """Handle notifications from a CenturionReceiver dongle. + + Bridge events have sub_id == bridge_index. The event function number + is in bits 7-4 of n.address: + - Function 0: ConnectionStateChangedEvent — sub-device connect/disconnect + - Function 1: MessageEvent — wrapped sub-device HID++ notification + + ConnectionStateChangedEvent payload (same format as getConnectionInfo): + n.data[0]: high nibble = connection type, low nibble = len_hi + n.data[1]: len_lo + n.data[2+]: sub-device descriptors (if any) + Empty sub-device list (length=0) means disconnected. + + MessageEvent data layout: + n.data[0:2] = dev_id<<4|len_hi, len_lo + n.data[2] = sub_cpl (0x00 for both responses and notifications) + n.data[3] = sub_feat_idx + n.data[4] = sub_func_sw (sw_id=0 for unsolicited notifications) + n.data[5:] = payload + """ + child = self.receiver._devices.get(1) + if not child: + if logger.isEnabledFor(logging.DEBUG): + logger.debug("CenturionReceiver: notification ignored (no child device): %s", n) + return + + bridge_idx = getattr(child, "_centurion_bridge_index", None) + if bridge_idx is None or n.sub_id != bridge_idx: + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + "CenturionReceiver: non-bridge notification sub_id=%d addr=0x%02X data=%s", + n.sub_id, + n.address, + n.data[:12].hex() if n.data else "", + ) + return + + event_func = (n.address >> 4) & 0x0F + + if event_func == 0: + # ConnectionStateChangedEvent — parse sub-device list length + if len(n.data) < 2: + return + data_len = ((n.data[0] & 0x0F) << 8) | n.data[1] + if data_len > 0 and not child.online: + if logger.isEnabledFor(logging.INFO): + logger.info("CenturionReceiver: headset connected (ConnectionStateChangedEvent, len=%d)", data_len) + child.changed(active=True) + self._status_changed(child) + elif data_len == 0 and child.online: + if logger.isEnabledFor(logging.INFO): + logger.info("CenturionReceiver: headset disconnected (ConnectionStateChangedEvent, len=0)") + child.changed(active=False) + self._status_changed(child) + return + + if event_func == 1: + # MessageEvent — unwrap sub-device notification + # n.data layout: [dev_id<<4|len_hi, len_lo, sub_cpl, sub_feat_idx, sub_func_sw, payload...] + if len(n.data) < 5: + return + # A MessageEvent from the headset proves it's online. If we missed the + # ConnectionStateChangedEvent (e.g. cold-start power-on), bring it online now. + if not child.online: + if logger.isEnabledFor(logging.INFO): + logger.info("CenturionReceiver: headset online (MessageEvent received while offline)") + child.changed(active=True) + self._status_changed(child) + sub_feat_idx = n.data[3] + sub_func_sw = n.data[4] + payload = n.data[5:] + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + "CenturionReceiver: bridge MessageEvent sub_feat=%d func=0x%02X payload=%s -> child %s", + sub_feat_idx, + sub_func_sw, + payload[:8].hex() if payload else "", + child, + ) + # Create synthetic notification and dispatch directly to feature processing. + # Sub-device features use 0x100 offset in FeaturesArray.inverse. + synthetic = base.HIDPPNotification(n.report_id, child.number, sub_feat_idx + 0x100, sub_func_sw, payload) + child.online = True + if child.features: + notifications._process_feature_notification(child, synthetic) + return + + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + "CenturionReceiver: unhandled bridge event func=%d addr=0x%02X data=%s", + event_func, + n.address, + n.data[:12].hex() if n.data else "", + ) + def __str__(self): - return '' % (self.receiver.path, self.receiver.handle) + return f"" -# -# -# - -# all known receiver listeners -# listeners that stop on their own may remain here -_all_listeners = {} +def _process_bluez_dbus(device: Device, path, dictionary: dict, signature): + """Process bluez dbus property changed signals for device status + changes to discover disconnections and connections. + """ + if device: + if dictionary.get("Connected") is not None: + connected = dictionary.get("Connected") + logger.info("bluez dbus for %s: %s", device, "CONNECTED" if connected else "DISCONNECTED") + device.changed(connected, reason=i18n._("connected") if connected else i18n._("disconnected")) + elif device is not None: + logger.info("bluez cleanup for %s", device) + _cleanup_bluez_dbus(device) -def _start(device_info): - assert _status_callback - isDevice = device_info.isDevice - if not isDevice: - receiver = Receiver.open(device_info) +def _cleanup_bluez_dbus(device: Device): + """Remove dbus signal receiver for device""" + logger.info("bluez cleanup for %s", device) + dbus.watch_bluez_connect(device.hid_serial, None) + + +_all_listeners = {} # all known receiver listeners, listeners that stop on their own may remain here + + +def _start(device_info: DeviceInfo): + assert _status_callback and _setting_callback + + if not device_info.isDevice: + receiver_ = logitech_receiver.receiver.create_receiver(base, device_info, _setting_callback) + elif getattr(device_info, "centurion", False): + receiver_ = logitech_receiver.device.create_centurion_receiver(base, device_info, _setting_callback) + if receiver_ is None: + # No bridge found — treat as a direct-connected centurion device (e.g., wired headset) + receiver_ = logitech_receiver.device.create_device(base, device_info, _setting_callback) + if receiver_: + configuration.attach_to(receiver_) else: - receiver = Device.open(device_info) - configuration.attach_to(receiver) + receiver_ = logitech_receiver.device.create_device(base, device_info, _setting_callback) + if receiver_: + configuration.attach_to(receiver_) + if receiver_.bluetooth and receiver_.hid_serial: + dbus.watch_bluez_connect(receiver_.hid_serial, partial(_process_bluez_dbus, receiver_)) + receiver_.cleanups.append(_cleanup_bluez_dbus) - if receiver: - rl = ReceiverListener(receiver, _status_callback) + if receiver_: + rl = SolaarListener(receiver_, _status_callback) rl.start() _all_listeners[device_info.path] = rl return rl - _log.warn('failed to open %s', device_info) + logger.warning("failed to open %s", device_info) def start_all(): - # just in case this it called twice in a row... - stop_all() - - if _log.isEnabledFor(_INFO): - _log.info('starting receiver listening threads') - for device_info in _base.receivers_and_devices(): - _process_receiver_event('add', device_info) + stop_all() # just in case this it called twice in a row... + logger.info("starting receiver listening threads") + for device_info in base.receivers_and_devices(): + _process_receiver_event(ACTION_ADD, device_info) def stop_all(): listeners = list(_all_listeners.values()) _all_listeners.clear() - if listeners: - if _log.isEnabledFor(_INFO): - _log.info('stopping receiver listening threads %s', listeners) - - for l in listeners: - l.stop() - + logger.info("stopping receiver listening threads %s", listeners) + for listener_thread in listeners: + listener_thread.stop() configuration.save() - if listeners: - for l in listeners: - l.join() + for listener_thread in listeners: + listener_thread.join() -# ping all devices to find out whether they are connected -# after a resume, the device may have been off -# so mark its saved status to ensure that the status is pushed to the device when it comes back +# after a resume, the device may have been off so mark its saved status to ensure +# that the status is pushed to the device when it comes back def ping_all(resuming=False): - if _log.isEnabledFor(_INFO): - _log.info('ping all devices%s', ' when resuming' if resuming else '') - for l in _all_listeners.values(): - if l.receiver.isDevice: - if resuming and hasattr(l.receiver, 'status'): - l.receiver.status._active = None # ensure that settings are pushed - if l.receiver.ping(): - l.receiver.status.changed(active=True, push=True) - l._status_changed(l.receiver) + logger.info("ping all devices%s", " when resuming" if resuming else "") + for listener_thread in _all_listeners.values(): + if listener_thread.receiver.isDevice: + if resuming: + listener_thread.receiver._active = None # ensure that settings are pushed + if listener_thread.receiver.ping(): + listener_thread.receiver.changed(active=True, push=True) + listener_thread._status_changed(listener_thread.receiver) else: - count = l.receiver.count() + count = listener_thread.receiver.count() if count: - for dev in l.receiver: - if resuming and hasattr(dev, 'status'): - dev.status._active = None # ensure that settings are pushed - if dev.ping(): - dev.status.changed(active=True, push=True) - l._status_changed(dev) + for dev in listener_thread.receiver: + if resuming: + dev._active = None # ensure that settings are pushed count -= 1 + try: # sometimes the device is not set up already, it should come back later + if dev.ping(): + dev.changed(active=True, push=True) + listener_thread._status_changed(dev) + except exceptions.NoSuchDevice: + logger.debug("can't ping device on resume: %s", dev) if not count: break -_status_callback = None -_error_callback = None +_status_callback = None # GUI callback to change UI in response to changes to receiver or device status +_setting_callback = None # GUI callback to change UI in response to changes to status +_error_callback = None # GUI callback to report errors -def setup_scanner(status_changed_callback, error_callback): - global _status_callback, _error_callback - assert _status_callback is None, 'scanner was already set-up' - +def setup_scanner(status_changed_callback: Callable, setting_changed_callback: Callable, error_callback: Callable): + global _status_callback, _error_callback, _setting_callback + assert _status_callback is None, "scanner was already set-up" _status_callback = status_changed_callback + _setting_callback = setting_changed_callback _error_callback = error_callback - - _base.notify_on_receivers_glib(_process_receiver_event) + base.notify_on_receivers_glib(GLib, _process_receiver_event) -def _process_add(device_info, retry): +def _process_add(device_info: DeviceInfo, retry): try: _start(device_info) except OSError as e: - if e.errno == _errno.EACCES: + if e.errno == errno.EACCES: try: - import subprocess - output = subprocess.check_output(['/usr/bin/getfacl', '-p', device_info.path], text=True) - if _log.isEnabledFor(_WARNING): - _log.warning('Missing permissions on %s\n%s.', device_info.path, output) + output = subprocess.check_output(["getfacl", "-p", device_info.path], text=True) + logger.warning("Missing permissions on %s\n%s.", device_info.path, output) except Exception: pass if retry: GLib.timeout_add(2000.0, _process_add, device_info, retry - 1) else: - _error_callback('permissions', device_info.path) + _error_callback(common.ErrorReason.PERMISSIONS, device_info.path) else: - _error_callback('nodevice', device_info.path) - except _base.NoReceiver: - _error_callback('nodevice', device_info.path) + _error_callback(common.ErrorReason.NO_DEVICE, device_info.path) + except exceptions.NoReceiver: + _error_callback(common.ErrorReason.NO_DEVICE, device_info.path) # receiver add/remove events will start/stop listener threads @@ -403,17 +472,12 @@ def _process_receiver_event(action, device_info): assert action is not None assert device_info is not None assert _error_callback - - if _log.isEnabledFor(_INFO): - _log.info('receiver event %s %s', action, device_info) - + logger.info("receiver event %s %s", action, device_info) # whatever the action, stop any previous receivers at this path - l = _all_listeners.pop(device_info.path, None) - if l is not None: - assert isinstance(l, ReceiverListener) - l.stop() - - if action == 'add': # a new device was detected + listener_thread = _all_listeners.pop(device_info.path, None) + if listener_thread is not None: + assert isinstance(listener_thread, SolaarListener) + listener_thread.stop() + if action == ACTION_ADD: _process_add(device_info, 3) - return False diff --git a/lib/solaar/tasks.py b/lib/solaar/tasks.py index 1bf45d29..0b13685a 100644 --- a/lib/solaar/tasks.py +++ b/lib/solaar/tasks.py @@ -1,6 +1,4 @@ #!/usr/bin/env python3 -# -*- python-mode -*- -# -*- coding: UTF-8 -*- ## Copyright (C) 2012-2013 Daniel Pavel ## @@ -18,29 +16,23 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from logging import DEBUG as _DEBUG -from logging import getLogger -from threading import Thread as _Thread +import logging -_log = getLogger(__name__) -del getLogger +from threading import Thread + +logger = logging.getLogger(__name__) try: - from Queue import Queue as _Queue + from Queue import Queue except ImportError: - from queue import Queue as _Queue - -# -# -# + from queue import Queue -class TaskRunner(_Thread): - +class TaskRunner(Thread): def __init__(self, name): super().__init__(name=name) self.daemon = True - self.queue = _Queue(16) + self.queue = Queue(16) self.alive = False def __call__(self, function, *args, **kwargs): @@ -54,8 +46,7 @@ class TaskRunner(_Thread): def run(self): self.alive = True - if _log.isEnabledFor(_DEBUG): - _log.debug('started') + logger.debug("started") while self.alive: task = self.queue.get() @@ -65,7 +56,6 @@ class TaskRunner(_Thread): try: function(*args, **kwargs) except Exception: - _log.exception('calling %s', function) + logger.exception("calling %s", function) - if _log.isEnabledFor(_DEBUG): - _log.debug('stopped') + logger.debug("stopped") diff --git a/lib/solaar/ui/__init__.py b/lib/solaar/ui/__init__.py index 61becfaf..d3cc8136 100644 --- a/lib/solaar/ui/__init__.py +++ b/lib/solaar/ui/__init__.py @@ -1,6 +1,5 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -16,112 +15,58 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from logging import DEBUG as _DEBUG -from logging import INFO as _INFO -from logging import getLogger +import logging -import yaml as _yaml +from enum import Enum +from typing import Callable -import gi # isort:skip +import gi +import yaml -gi.require_version('Gtk', '3.0') # NOQA: E402 -from gi.repository import GLib, Gtk, Gio # NOQA: E402 # isort:skip -from logitech_receiver.status import ALERT # NOQA: E402 # isort:skip -from solaar.i18n import _ # NOQA: E402 # isort:skip +from logitech_receiver.common import Alert -_log = getLogger(__name__) -del getLogger +from solaar.i18n import _ +from solaar.ui.config_panel import change_setting +from solaar.ui.config_panel import record_setting +from solaar.ui.window import find_device -# -# -# +from . import common +from . import desktop_notifications +from . import diversion_rules +from . import tray +from . import window -assert Gtk.get_major_version() > 2, 'Solaar requires Gtk 3 python bindings' +gi.require_version("Gtk", "3.0") +from gi.repository import Gio # NOQA: E402 +from gi.repository import GLib # NOQA: E402 +from gi.repository import Gtk # NOQA: E402 -GLib.threads_init() +logger = logging.getLogger(__name__) -# -# -# +assert Gtk.get_major_version() > 2, "Solaar requires Gtk 3 python bindings" -def _error_dialog(reason, object): - _log.error('error: %s %s', reason, object) - - if reason == 'permissions': - title = _('Permissions error') - text = ( - _('Found a Logitech receiver or device (%s), but did not have permission to open it.') % object + '\n\n' + - _("If you've just installed Solaar, try disconnecting the receiver or device and then reconnecting it.") - ) - elif reason == 'nodevice': - title = _('Cannot connect to device error') - text = ( - _('Found a Logitech receiver or device at %s, but encountered an error connecting to it.') % object + '\n\n' + - _('Try disconnecting the device and then reconnecting it or turning it off and then on.') - ) - elif reason == 'unpair': - title = _('Unpairing failed') - text = ( - _('Failed to unpair %{device} from %{receiver}.').format(device=object.name, receiver=object.receiver.name) + - '\n\n' + _('The receiver returned an error, with no further details.') - ) - else: - raise Exception("ui.error_dialog: don't know how to handle (%s, %s)", reason, object) - - assert title - assert text - - m = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, text) - m.set_title(title) - m.run() - m.destroy() +APP_ID = "io.github.pwr_solaar.solaar" -def error_dialog(reason, object): - assert reason is not None - GLib.idle_add(_error_dialog, reason, object) - - -# -# -# - -_task_runner = None - - -def ui_async(function, *args, **kwargs): - if _task_runner: - _task_runner(function, *args, **kwargs) - - -# -# -# - -from . import diversion_rules, notify, tray, window # isort:skip # noqa: E402 +class GtkSignal(Enum): + ACTIVATE = "activate" + COMMAND_LINE = "command-line" + SHUTDOWN = "shutdown" def _startup(app, startup_hook, use_tray, show_window): - if _log.isEnabledFor(_DEBUG): - _log.debug('startup registered=%s, remote=%s', app.get_is_registered(), app.get_is_remote()) - - from solaar.tasks import TaskRunner as _TaskRunner - global _task_runner - _task_runner = _TaskRunner('AsyncUI') - _task_runner.start() - - notify.init() + logger.debug("startup registered=%s, remote=%s", app.get_is_registered(), app.get_is_remote()) + common.start_async() + desktop_notifications.init() if use_tray: tray.init(lambda _ignore: window.destroy()) window.init(show_window, use_tray) - startup_hook() def _activate(app): - if _log.isEnabledFor(_DEBUG): - _log.debug('activate') + logger.debug("activate") if app.get_windows(): window.popup() else: @@ -130,14 +75,11 @@ def _activate(app): def _command_line(app, command_line): args = command_line.get_arguments() - args = _yaml.safe_load(''.join(args)) if args else args + args = yaml.safe_load("".join(args)) if args else args if not args: _activate(app) - elif args[0] == 'config': # config call from remote instance - if _log.isEnabledFor(_INFO): - _log.info('remote command line %s', args) - from solaar.ui.config_panel import change_setting # prevent circular import - from solaar.ui.window import find_device # prevent circular import + elif args[0] == "config": # config call from remote instance + logger.info("remote command line %s", args) dev = find_device(args[1]) if dev: setting = next((s for s in dev.settings if s.name == args[2]), None) @@ -146,59 +88,62 @@ def _command_line(app, command_line): return 0 -def _shutdown(app, shutdown_hook): - if _log.isEnabledFor(_DEBUG): - _log.debug('shutdown') - +def _shutdown(_app, shutdown_hook): + logger.debug("shutdown") shutdown_hook() - - # stop the async UI processor - global _task_runner - _task_runner.stop() - _task_runner = None - + common.stop_async() tray.destroy() - notify.uninit() + desktop_notifications.uninit() -def run_loop(startup_hook, shutdown_hook, use_tray, show_window): - assert use_tray or show_window, 'need either tray or visible window' - # from gi.repository.Gio import ApplicationFlags as _ApplicationFlags - APP_ID = 'io.github.pwr_solaar.solaar' +def run_loop( + startup_hook: Callable[[], None], + shutdown_hook: Callable[[], None], + use_tray: bool, + show_window: bool, +): + assert use_tray or show_window, "need either tray or visible window" + application = Gtk.Application.new(APP_ID, Gio.ApplicationFlags.HANDLES_COMMAND_LINE) - application.connect('startup', lambda app, startup_hook: _startup(app, startup_hook, use_tray, show_window), startup_hook) - application.connect('command-line', _command_line) - application.connect('activate', _activate) - application.connect('shutdown', _shutdown, shutdown_hook) + application.connect( + "startup", + lambda app, startup_hook: _startup(app, startup_hook, use_tray, show_window), + startup_hook, + ) + application.connect(GtkSignal.COMMAND_LINE.value, _command_line) + application.connect(GtkSignal.ACTIVATE.value, _activate) + application.connect(GtkSignal.SHUTDOWN.value, _shutdown, shutdown_hook) application.register() if application.get_is_remote(): - print(_('Another Solaar process is already running so just expose its window')) + print(_("Another Solaar process is already running so just expose its window")) application.run() -# -# -# - - def _status_changed(device, alert, reason, refresh=False): - assert device is not None - if _log.isEnabledFor(_DEBUG): - _log.debug('status changed: %s (%s) %s', device, alert, reason) + if device is None: + logger.debug("status changed on nil device: %s (%s) %s", device, alert, reason) + return + logger.debug("status changed: %s (%s) %s", device, alert, reason) + if alert is None: + alert = Alert.NONE tray.update(device) - if alert & ALERT.ATTENTION: + if alert & Alert.ATTENTION: tray.attention(reason) - need_popup = alert & ALERT.SHOW_WINDOW + need_popup = alert & Alert.SHOW_WINDOW window.update(device, need_popup, refresh) diversion_rules.update_devices() - if alert & (ALERT.NOTIFICATION | ALERT.ATTENTION): - notify.show(device, reason) + if alert & (Alert.NOTIFICATION | Alert.ATTENTION): + desktop_notifications.show(device, reason) -def status_changed(device, alert=ALERT.NONE, reason=None, refresh=False): +def status_changed(device, alert=Alert.NONE, reason=None, refresh=False): GLib.idle_add(_status_changed, device, alert, reason, refresh) + + +def setting_changed(device, setting_class, vals): + record_setting(device, setting_class, vals) diff --git a/lib/solaar/ui/about.py b/lib/solaar/ui/about.py deleted file mode 100644 index bd91adf0..00000000 --- a/lib/solaar/ui/about.py +++ /dev/null @@ -1,100 +0,0 @@ -# -*- python-mode -*- - -## Copyright (C) 2012-2013 Daniel Pavel -## Revisions Copyright (C) Contributors to the Solaar project. -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License along -## with this program; if not, write to the Free Software Foundation, Inc., -## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -from gi.repository import Gtk -from solaar import NAME, __version__ -from solaar.i18n import _ - -# -# -# - -_dialog = None - - -def _create(): - about = Gtk.AboutDialog() - - about.set_program_name(NAME) - about.set_version(__version__) - about.set_comments(_('Manages Logitech receivers,\nkeyboards, mice, and tablets.')) - about.set_logo_icon_name(NAME.lower()) - - about.set_copyright('© 2012-2023 Daniel Pavel and contributors to the Solaar project') - about.set_license_type(Gtk.License.GPL_2_0) - - about.set_authors(('Daniel Pavel http://github.com/pwr', )) - try: - about.add_credit_section(_('Additional Programming'), ('Filipe Laíns', 'Peter F. Patel-Schneider')) - about.add_credit_section(_('GUI design'), ('Julien Gascard', 'Daniel Pavel')) - about.add_credit_section( - _('Testing'), ( - 'Douglas Wagner', - 'Julien Gascard', - 'Peter Wu http://www.lekensteyn.nl/logitech-unifying.html', - ) - ) - about.add_credit_section( - _('Logitech documentation'), ( - 'Julien Danjou http://julien.danjou.info/blog/2012/logitech-unifying-upower', - 'Nestor Lopez Casado http://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28', - ) - ) - except TypeError: - # gtk3 < ~3.6.4 has incorrect gi bindings - import logging - logging.exception('failed to fully create the about dialog') - except Exception: - # the Gtk3 version may be too old, and the function does not exist - import logging - logging.exception('failed to fully create the about dialog') - - about.set_translator_credits( - '\n'.join(( - 'gogo (croatian)', - 'Papoteur, David Geiger, Damien Lallement (français)', - 'Michele Olivo (italiano)', - 'Adrian Piotrowicz (polski)', - 'Drovetto, JrBenito (Portuguese-BR)', - 'Daniel Pavel (română)', - 'Daniel Zippert, Emelie Snecker (svensk)', - 'Dimitriy Ryazantcev (Russian)', - 'El Jinete Sin Cabeza (Español)', - )) - ) - - about.set_website('https://pwr-solaar.github.io/Solaar') - about.set_website_label(NAME) - - about.connect('response', lambda x, y: x.hide()) - - def _hide(dialog, event): - dialog.hide() - return True - - about.connect('delete-event', _hide) - - return about - - -def show_window(trigger=None): - global _dialog - if _dialog is None: - _dialog = _create() - _dialog.present() diff --git a/.gitmodules b/lib/solaar/ui/about/__init__.py similarity index 100% rename from .gitmodules rename to lib/solaar/ui/about/__init__.py diff --git a/lib/solaar/ui/about/about.py b/lib/solaar/ui/about/about.py new file mode 100644 index 00000000..7975bfef --- /dev/null +++ b/lib/solaar/ui/about/about.py @@ -0,0 +1,36 @@ +## Copyright (C) Solaar Contributors +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from solaar.ui.about.model import AboutModel +from solaar.ui.about.presenter import Presenter +from solaar.ui.about.view import AboutView + + +def show(_=None, model=None, view=None): + """Opens the About dialog.""" + if model is None: + model = AboutModel() + if view is None: + view = AboutView() + presenter = Presenter(model, view) + presenter.run() + + +if __name__ == "__main__": + from gi.repository import Gtk + + show(None) + Gtk.main() diff --git a/lib/solaar/ui/about/model.py b/lib/solaar/ui/about/model.py new file mode 100644 index 00000000..e2514b11 --- /dev/null +++ b/lib/solaar/ui/about/model.py @@ -0,0 +1,84 @@ +## Copyright (C) Solaar Contributors +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from __future__ import annotations + +from datetime import datetime +from typing import List +from typing import Tuple + +from solaar import __version__ +from solaar.i18n import _ + + +def _get_current_year() -> int: + return datetime.now().year + + +class AboutModel: + def get_version(self) -> str: + return __version__ + + def get_description(self) -> str: + return _("Manages Logitech receivers,\nkeyboards, mice, and tablets.") + + def get_copyright(self) -> str: + return f"© 2012-{_get_current_year()} Daniel Pavel and contributors to the Solaar project" + + def get_authors(self) -> List[str]: + return [ + "Daniel Pavel http://github.com/pwr", + ] + + def get_translators(self) -> List[str]: + return [ + "gogo (croatian)", + "Papoteur, David Geiger, Damien Lallement (français)", + "Michele Olivo (italiano)", + "Adrian Piotrowicz (polski)", + "Drovetto, JrBenito (Portuguese-BR)", + "Daniel Pavel (română)", + "Daniel Zippert, Emelie Snecker (svensk)", + "Dimitriy Ryazantcev (Russian)", + "El Jinete Sin Cabeza (Español)", + "Ferdina Kusumah (Indonesia)", + "John Erling Blad (Norwegian Bokmål, Norwegian Nynorsk)", + "Oleksandr Afanasiev (Ukrainian)", + ] + + def get_credit_sections(self) -> List[Tuple[str, List[str]]]: + return [ + (_("Additional Programming"), ["Filipe Laíns", "Peter F. Patel-Schneider"]), + (_("GUI design"), ["Julien Gascard", "Daniel Pavel"]), + ( + _("Testing"), + [ + "Douglas Wagner", + "Julien Gascard", + "Peter Wu http://www.lekensteyn.nl/logitech-unifying.html", + ], + ), + ( + _("Logitech documentation"), + [ + "Julien Danjou http://julien.danjou.info/blog/2012/logitech-unifying-upower", + "Nestor Lopez Casado http://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28", + ], + ), + ] + + def get_website(self): + return "https://pwr-solaar.github.io/Solaar" diff --git a/lib/solaar/ui/about/presenter.py b/lib/solaar/ui/about/presenter.py new file mode 100644 index 00000000..bc3454b4 --- /dev/null +++ b/lib/solaar/ui/about/presenter.py @@ -0,0 +1,95 @@ +## Copyright (C) Solaar Contributors +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from __future__ import annotations + +from typing_extensions import Protocol + +from solaar.ui.about.model import AboutModel + + +class AboutViewProtocol(Protocol): + def init_ui(self) -> None: + ... + + def update_version_info(self, version: str) -> None: + ... + + def update_description(self, comments: str) -> None: + ... + + def update_copyright(self, copyright_text: str) -> None: + ... + + def update_authors(self, authors: list[str]) -> None: + ... + + def update_translators(self, translators: list[str]) -> None: + ... + + def update_website(self, website): + ... + + def update_credits(self, credit_sections: list[tuple[str, list[str]]]) -> None: + ... + + def show(self) -> None: + ... + + +class Presenter: + def __init__(self, model: AboutModel, view: AboutViewProtocol) -> None: + self.model = model + self.view = view + + def update_version_info(self) -> None: + version = self.model.get_version() + self.view.update_version_info(version) + + def update_credits(self) -> None: + credit_sections = self.model.get_credit_sections() + self.view.update_credits(credit_sections) + + def update_description(self) -> None: + comments = self.model.get_description() + self.view.update_description(comments) + + def update_copyright(self) -> None: + copyright_text = self.model.get_copyright() + self.view.update_copyright(copyright_text) + + def update_authors(self) -> None: + authors = self.model.get_authors() + self.view.update_authors(authors) + + def update_translators(self) -> None: + translators = self.model.get_translators() + self.view.update_translators(translators) + + def update_website(self) -> None: + website = self.model.get_website() + self.view.update_website(website) + + def run(self) -> None: + self.view.init_ui() + self.update_version_info() + self.update_description() + self.update_website() + self.update_copyright() + self.update_authors() + self.update_credits() + self.update_translators() + self.view.show() diff --git a/lib/solaar/ui/about/view.py b/lib/solaar/ui/about/view.py new file mode 100644 index 00000000..4c0d9084 --- /dev/null +++ b/lib/solaar/ui/about/view.py @@ -0,0 +1,70 @@ +## Copyright (C) Solaar Contributors +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from enum import Enum +from typing import List +from typing import Tuple +from typing import Union + +from gi.repository import Gtk + +from solaar import NAME + + +class GtkSignal(Enum): + RESPONSE = "response" + + +class AboutView: + def __init__(self) -> None: + self.view: Union[Gtk.AboutDialog, None] = None + + def init_ui(self) -> None: + self.view = Gtk.AboutDialog() + self.view.set_program_name(NAME) + self.view.set_logo_icon_name(NAME.lower()) + self.view.set_license_type(Gtk.License.GPL_2_0) + + self.view.connect(GtkSignal.RESPONSE.value, lambda x, y: self.handle_close(x)) + + def update_version_info(self, version: str) -> None: + self.view.set_version(version) + + def update_description(self, comments: str) -> None: + self.view.set_comments(comments) + + def update_copyright(self, copyright_text: str): + self.view.set_copyright(copyright_text) + + def update_authors(self, authors: List[str]) -> None: + self.view.set_authors(authors) + + def update_credits(self, credit_sections: List[Tuple[str, List[str]]]) -> None: + for section_name, people in credit_sections: + self.view.add_credit_section(section_name, people) + + def update_translators(self, translators: List[str]) -> None: + translator_credits = "\n".join(translators) + self.view.set_translator_credits(translator_credits) + + def update_website(self, website): + self.view.set_website_label(NAME) + self.view.set_website(website) + + def show(self) -> None: + self.view.present() + + def handle_close(self, event) -> None: + event.hide() diff --git a/lib/solaar/ui/action.py b/lib/solaar/ui/action.py index 91621f23..6751f92b 100644 --- a/lib/solaar/ui/action.py +++ b/lib/solaar/ui/action.py @@ -1,6 +1,5 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -15,54 +14,55 @@ ## You should have received a copy of the GNU General Public License along ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from enum import Enum + +from gi.repository import Gdk +from gi.repository import Gtk -from gi.repository import Gdk, Gtk from solaar.i18n import _ +from solaar.ui import common -from ..ui import error_dialog from . import pair_window -# from logging import getLogger -# _log = getLogger(__name__) -# del getLogger -# -# -# +class GtkSignal(Enum): + ACTIVATE = "activate" + + +def make_image_menu_item(label, icon_name, function, *args): + box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 6) + label = Gtk.Label(label=label) + icon = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.LARGE_TOOLBAR) if icon_name is not None else Gtk.Image() + box.add(icon) + box.add(label) + menu_item = Gtk.MenuItem() + menu_item.add(box) + menu_item.show_all() + menu_item.connect(GtkSignal.ACTIVATE.value, function, *args) + menu_item.label = label + menu_item.icon = icon + return menu_item def make(name, label, function, stock_id=None, *args): - action = Gtk.Action(name, label, label, None) + action = Gtk.Action(name=name, label=label, tooltip=label, stock_id=None) action.set_icon_name(name) if stock_id is not None: action.set_stock_id(stock_id) if function: - action.connect('activate', function, *args) + action.connect(GtkSignal.ACTIVATE.value, function, *args) return action def make_toggle(name, label, function, stock_id=None, *args): - action = Gtk.ToggleAction(name, label, label, None) + action = Gtk.ToggleAction(name=name, label=label, tooltip=label, stock_id=None) action.set_icon_name(name) if stock_id is not None: action.set_stock_id(stock_id) - action.connect('activate', function, *args) + action.connect(GtkSignal.ACTIVATE.value, function, *args) return action -# -# -# - -# def _toggle_notifications(action): -# if action.get_active(): -# notify.init('Solaar') -# else: -# notify.uninit() -# action.set_sensitive(notify.available) -# toggle_notifications = make_toggle('notifications', 'Notifications', _toggle_notifications) - - def pair(window, receiver): assert receiver assert receiver.kind is None @@ -81,12 +81,15 @@ def unpair(window, device): assert device.kind is not None qdialog = Gtk.MessageDialog( - window, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, - _('Unpair') + ' ' + device.name + ' ?' + transient_for=window, + flags=0, + message_type=Gtk.MessageType.QUESTION, + buttons=Gtk.ButtonsType.NONE, + text=_("Unpair") + " " + device.name + " ?", ) - qdialog.set_icon_name('remove') - qdialog.add_button(_('Cancel'), Gtk.ResponseType.CANCEL) - qdialog.add_button(_('Unpair'), Gtk.ResponseType.ACCEPT) + qdialog.set_icon_name("remove") + qdialog.add_button(_("Cancel"), Gtk.ResponseType.CANCEL) + qdialog.add_button(_("Unpair"), Gtk.ResponseType.ACCEPT) choice = qdialog.run() qdialog.destroy() if choice == Gtk.ResponseType.ACCEPT: @@ -95,7 +98,10 @@ def unpair(window, device): device_number = device.number try: - del receiver[device_number] + # force=True ensures the unpair register write is issued even on + # re_pairs receivers (Lightspeed, Nano); otherwise _unpair_device + # short-circuits to cache-invalidation only and the slot stays + # bound on the hardware. + receiver._unpair_device(device_number, True) except Exception: - # _log.exception("unpairing %s", device) - error_dialog('unpair', device) + common.error_dialog(common.ErrorReason.UNPAIR, device) diff --git a/lib/solaar/ui/common.py b/lib/solaar/ui/common.py new file mode 100644 index 00000000..9ccce1d8 --- /dev/null +++ b/lib/solaar/ui/common.py @@ -0,0 +1,102 @@ +## Copyright (C) 2012-2013 Daniel Pavel +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import logging + +from enum import Enum +from typing import Tuple + +import gi + +from solaar.i18n import _ +from solaar.tasks import TaskRunner + +gi.require_version("Gtk", "3.0") +from gi.repository import GLib # NOQA: E402 +from gi.repository import Gtk # NOQA: E402 + +logger = logging.getLogger(__name__) + + +class ErrorReason(Enum): + PERMISSIONS = "Permissions" + NO_DEVICE = "No device" + UNPAIR = "Unpair" + + +def _create_error_text(reason: ErrorReason, object_) -> Tuple[str, str]: + if reason == ErrorReason.PERMISSIONS: + title = _("Permissions error") + text = ( + _("Found a Logitech receiver or device (%s), but did not have permission to open it.") % object_ + + "\n\n" + + _("If you've just installed Solaar, try disconnecting the receiver or device and then reconnecting it.") + ) + elif reason == ErrorReason.NO_DEVICE: + title = _("Cannot connect to device error") + text = ( + _("Found a Logitech receiver or device at %s, but encountered an error connecting to it.") % object_ + + "\n\n" + + _("Try disconnecting the device and then reconnecting it or turning it off and then on.") + ) + elif reason == ErrorReason.UNPAIR: + title = _("Unpairing failed") + text = ( + _("Failed to unpair %{device} from %{receiver}.").format( + device=object_.name, + receiver=object_.receiver.name, + ) + + "\n\n" + + _("The receiver returned an error, with no further details.") + ) + else: + raise Exception("ui.error_dialog: don't know how to handle (%s, %s)", reason.name, object_) + return title, text + + +def _error_dialog(reason: ErrorReason, object_): + logger.error("error: %s %s", reason, object_) + title, text = _create_error_text(reason, object_) + + m = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, text) + m.set_title(title) + m.run() + m.destroy() + + +def error_dialog(reason: ErrorReason, object_): + GLib.idle_add(_error_dialog, reason, object_) + + +_task_runner = None + + +def start_async(): + global _task_runner + _task_runner = TaskRunner("AsyncUI") + _task_runner.start() + + +def stop_async(): + global _task_runner + _task_runner.stop() + _task_runner = None + + +def ui_async(function, *args, **kwargs): + """Runs a function asynchronously.""" + if _task_runner: + _task_runner(function, *args, **kwargs) diff --git a/lib/solaar/ui/config_panel.py b/lib/solaar/ui/config_panel.py index 7e5801d1..990293da 100644 --- a/lib/solaar/ui/config_panel.py +++ b/lib/solaar/ui/config_panel.py @@ -1,6 +1,5 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -16,38 +15,54 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import traceback +import logging -from logging import WARNING as _WARNING -from logging import getLogger -from threading import Timer as _Timer +from enum import Enum +from threading import Timer -from gi.repository import Gdk, GLib, Gtk -from logitech_receiver.settings import KIND as _SETTING_KIND -from logitech_receiver.settings import SENSITIVITY_IGNORE as _SENSITIVITY_IGNORE -from solaar.i18n import _, ngettext -from solaar.ui import ui_async as _ui_async +import gi -_log = getLogger(__name__) -del getLogger +from logitech_receiver import hidpp20 +from logitech_receiver import settings -# -# -# +from solaar.i18n import _ +from solaar.i18n import ngettext + +from .common import ui_async + +gi.require_version("Gtk", "3.0") +from gi.repository import Gdk # NOQA: E402 +from gi.repository import GLib # NOQA: E402 +from gi.repository import Gtk # NOQA: E402 + +logger = logging.getLogger(__name__) + + +class GtkSignal(Enum): + ACTIVATE = "activate" + CHANGED = "changed" + CLICKED = "clicked" + MATCH_SELECTED = "match_selected" + NOTIFY_ACTIVE = "notify::active" + TOGGLED = "toggled" + VALUE_CHANGED = "value-changed" + COLOR_SET = "color-set" def _read_async(setting, force_read, sbox, device_is_online, sensitive): - def _do_read(s, force, sb, online, sensitive): - v = s.read(not force) - GLib.idle_add(_update_setting_item, sb, v, online, sensitive, priority=99) + try: + v = s.read(not force) + except Exception as e: + v = None + logger.warning("%s: error reading so use None (%s): %s", s.name, s._device, repr(e)) + GLib.idle_add(_update_setting_item, sb, v, online, sensitive, True, priority=99) - _ui_async(_do_read, setting, force_read, sbox, device_is_online, sensitive) + ui_async(_do_read, setting, force_read, sbox, device_is_online, sensitive) def _write_async(setting, value, sbox, sensitive=True, key=None): - - def _do_write(s, v, sb, key): + def _do_write(_s, v, sb, key): try: if key is None: v = setting.write(v) @@ -55,7 +70,6 @@ def _write_async(setting, value, sbox, sensitive=True, key=None): v = setting.write_key_value(key, v) v = {key: v} except Exception: - traceback.print_exc() v = None if sb: GLib.idle_add(_update_setting_item, sb, v, True, sensitive, priority=99) @@ -65,18 +79,26 @@ def _write_async(setting, value, sbox, sensitive=True, key=None): sbox._failed.set_visible(False) sbox._spinner.set_visible(True) sbox._spinner.start() - _ui_async(_do_write, setting, value, sbox, key) + ui_async(_do_write, setting, value, sbox, key) -# -# -# +class ComboBoxText(Gtk.ComboBoxText): + def get_value(self): + return int(self.get_active_id()) + + def set_value(self, value): + return self.set_active_id(str(int(value))) -class Control(): +class Scale(Gtk.Scale): + def get_value(self): + return int(super().get_value()) - def __init__(**kwargs): - pass + +class Control: + def __init__(self, **kwargs): + self.sbox = None + self.delegate = None def init(self, sbox, delegate): self.sbox = sbox @@ -92,28 +114,28 @@ class Control(): def layout(self, sbox, label, change, spinner, failed): sbox.pack_start(label, False, False, 0) sbox.pack_end(change, False, False, 0) - sbox.pack_end(self, sbox.setting.kind == _SETTING_KIND.range, sbox.setting.kind == _SETTING_KIND.range, 0) + fill = sbox.setting.kind == settings.Kind.RANGE or sbox.setting.kind == settings.Kind.HETERO + sbox.pack_end(self, fill, fill, 0) sbox.pack_end(spinner, False, False, 0) sbox.pack_end(failed, False, False, 0) return self class ToggleControl(Gtk.Switch, Control): - def __init__(self, sbox, delegate=None): super().__init__(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER) self.init(sbox, delegate) - self.connect('notify::active', self.changed) + self.connect(GtkSignal.NOTIFY_ACTIVE.value, self.changed) def set_value(self, value): - self.set_state(value) + if value is not None: + self.set_state(value) def get_value(self): return self.get_state() class SliderControl(Gtk.Scale, Control): - def __init__(self, sbox, delegate=None): super().__init__(halign=Gtk.Align.FILL) self.init(sbox, delegate) @@ -122,7 +144,12 @@ class SliderControl(Gtk.Scale, Control): self.set_round_digits(0) self.set_digits(0) self.set_increments(1, 5) - self.connect('value-changed', self.changed) + self.connect(GtkSignal.VALUE_CHANGED.value, self.changed) + + def set_value(self, value): + if isinstance(value, dict): + value = next(iter(value.values())) + return super().set_value(value) def get_value(self): return int(super().get_value()) @@ -131,7 +158,7 @@ class SliderControl(Gtk.Scale, Control): if self.get_sensitive(): if self.timer: self.timer.cancel() - self.timer = _Timer(0.5, lambda: GLib.idle_add(self.do_change)) + self.timer = Timer(0.5, lambda: GLib.idle_add(self.do_change)) self.timer.start() def do_change(self): @@ -148,20 +175,20 @@ def _create_choice_control(sbox, delegate=None, choices=None): # GTK boxes have property lists, but the keys must be strings class ChoiceControlLittle(Gtk.ComboBoxText, Control): - def __init__(self, sbox, delegate=None, choices=None): super().__init__(halign=Gtk.Align.FILL) self.init(sbox, delegate) self.choices = choices if choices is not None else sbox.setting.choices for entry in self.choices: self.append(str(int(entry)), str(entry)) - self.connect('changed', self.changed) + self.connect(GtkSignal.CHANGED.value, self.changed) def get_value(self): return int(self.get_active_id()) if self.get_active_id() is not None else None def set_value(self, value): - self.set_active_id(str(int(value))) + if value is not None: + self.set_active_id(str(int(value))) def get_choice(self): id = self.get_value() @@ -174,7 +201,6 @@ class ChoiceControlLittle(Gtk.ComboBoxText, Control): class ChoiceControlBig(Gtk.Entry, Control): - def __init__(self, sbox, delegate=None, choices=None): super().__init__(halign=Gtk.Align.FILL) self.init(sbox, delegate) @@ -186,46 +212,52 @@ class ChoiceControlBig(Gtk.Entry, Control): liststore.append((int(v), str(v))) completion = Gtk.EntryCompletion() completion.set_model(liststore) - norm = lambda s: s.replace('_', '').replace(' ', '').lower() + + def norm(s): + return s.replace("_", "").replace(" ", "").lower() + completion.set_match_func(lambda completion, key, it: norm(key) in norm(completion.get_model()[it][1])) completion.set_text_column(1) self.set_completion(completion) - self.connect('changed', self.changed) - self.connect('activate', self.activate) - completion.connect('match_selected', self.select) + self.connect(GtkSignal.CHANGED.value, self.changed) + self.connect(GtkSignal.ACTIVATE.value, self.activate) + completion.connect(GtkSignal.MATCH_SELECTED.value, self.select) def get_value(self): choice = self.get_choice() return int(choice) if choice is not None else None def set_value(self, value): - self.set_text(str(next((x for x in self.choices if x == value), None))) + if value is not None: + self.set_text(str(next((x for x in self.choices if x == value), None))) def get_choice(self): key = self.get_text() return next((x for x in self.choices if x == key), None) + def set_choices(self, choices): + self.choices = choices + def changed(self, *args): self.value = self.get_choice() - icon = 'dialog-warning' if self.value is None else 'dialog-question' if self.get_sensitive() else '' + icon = "dialog-warning" if self.value is None else "dialog-question" if self.get_sensitive() else "" self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) - tooltip = _('Incomplete') if self.value is None else _('Complete - ENTER to change') + tooltip = _("Incomplete") if self.value is None else _("Complete - ENTER to change") self.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, tooltip) - def activate(self, *args): + def activate(self, *_args): if self.value is not None and self.get_sensitive(): - self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, '') + self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "") self.delegate.update() - def select(self, completion, model, iter): + def select(self, _completion, model, iter): self.set_value(model.get(iter, 0)[0]) if self.value and self.get_sensitive(): - self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, '') + self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "") self.delegate.update() class MapChoiceControl(Gtk.HBox, Control): - def __init__(self, sbox, delegate=None): super().__init__(homogeneous=False, spacing=6) self.init(sbox, delegate) @@ -238,7 +270,7 @@ class MapChoiceControl(Gtk.HBox, Control): self.valueBox = _create_choice_control(sbox.setting, choices=self.value_choices, delegate=self) self.pack_start(self.keyBox, False, False, 0) self.pack_end(self.valueBox, False, False, 0) - self.keyBox.connect('changed', self.map_value_notify_key) + self.keyBox.connect(GtkSignal.CHANGED.value, self.map_value_notify_key) def get_value(self): key_choice = int(self.keyBox.get_active_id()) @@ -246,6 +278,8 @@ class MapChoiceControl(Gtk.HBox, Control): return self.valueBox.get_value() def set_value(self, value): + if value is None: + return self.valueBox.set_sensitive(self.get_sensitive()) key = int(self.keyBox.get_active_id()) if value.get(key) is not None: @@ -256,13 +290,12 @@ class MapChoiceControl(Gtk.HBox, Control): choices = self.sbox.setting.choices[key_choice] if choices != self.value_choices: self.value_choices = choices - self.valueBox.remove_all() self.valueBox.set_choices(choices) current = self.sbox.setting._value.get(key_choice) if self.sbox.setting._value else None if current is not None: self.valueBox.set_value(current) - def map_value_notify_key(self, *args): + def map_value_notify_key(self, *_args): key_choice = int(self.keyBox.get_active_id()) if self.keyBox.get_sensitive(): self.map_populate_value_box(key_choice) @@ -276,17 +309,15 @@ class MapChoiceControl(Gtk.HBox, Control): class MultipleControl(Gtk.ListBox, Control): - - def __init__(self, sbox, change, button_label='...', delegate=None): + def __init__(self, sbox, change, button_label="...", delegate=None): super().__init__() self.init(sbox, delegate) self.set_selection_mode(Gtk.SelectionMode.NONE) self.set_no_show_all(True) self._showing = True self.setup(sbox.setting) # set up the data and boxes for the sub-controls - btn = Gtk.Button(button_label) - btn.set_alignment(1.0, 0.5) - btn.connect('clicked', self.toggle_display) + btn = Gtk.Button(label=button_label) + btn.connect(GtkSignal.CLICKED.value, self.toggle_display) self._button = btn hbox = Gtk.HBox(homogeneous=False, spacing=6) hbox.pack_end(change, False, False, 0) @@ -307,7 +338,7 @@ class MultipleControl(Gtk.ListBox, Control): sbox._button = self._button return True - def toggle_display(self, *args): + def toggle_display(self, *_args): self._showing = not self._showing if not self._showing: for c in self.get_children(): @@ -320,30 +351,28 @@ class MultipleControl(Gtk.ListBox, Control): class MultipleToggleControl(MultipleControl): - def setup(self, setting): self._label_control_pairs = [] for k in setting._validator.get_options(): h = Gtk.HBox(homogeneous=False, spacing=0) lbl_text = str(k) lbl_tooltip = None - if hasattr(setting, '_labels'): + if hasattr(setting, "_labels"): l1, l2 = setting._labels.get(k, (None, None)) lbl_text = l1 if l1 else lbl_text lbl_tooltip = l2 if l2 else lbl_tooltip - lbl = Gtk.Label(lbl_text) - h.set_tooltip_text(lbl_tooltip or ' ') + lbl = Gtk.Label(label=lbl_text) + h.set_tooltip_text(lbl_tooltip or " ") control = Gtk.Switch() control._setting_key = int(k) - control.connect('notify::active', self.toggle_notify) + control.connect(GtkSignal.NOTIFY_ACTIVE.value, self.toggle_notify) h.pack_start(lbl, False, False, 0) h.pack_end(control, False, False, 0) - lbl.set_alignment(0.0, 0.5) - lbl.set_margin_left(30) + lbl.set_margin_start(30) self.add(h) self._label_control_pairs.append((lbl, control)) - def toggle_notify(self, switch, active): + def toggle_notify(self, switch, _active): if switch.get_sensitive(): key = switch._setting_key new_state = switch.get_state() @@ -352,6 +381,8 @@ class MultipleToggleControl(MultipleControl): _write_async(self.sbox.setting, new_state, self.sbox, key=int(key)) def set_value(self, value): + if value is None: + return active = 0 total = len(self._label_control_pairs) to_join = [] @@ -361,26 +392,25 @@ class MultipleToggleControl(MultipleControl): elem.set_state(v) if elem.get_state(): active += 1 - to_join.append(lbl.get_text() + ': ' + str(elem.get_state())) - b = ', '.join(to_join) - self._button.set_label(f'{active} / {total}') + to_join.append(f"{lbl.get_text()}: {str(elem.get_state())}") + b = ", ".join(to_join) + self._button.set_label(f"{active} / {total}") self._button.set_tooltip_text(b) class MultipleRangeControl(MultipleControl): - def setup(self, setting): self._items = [] for item in setting._validator.items: lbl_text = str(item) lbl_tooltip = None - if hasattr(setting, '_labels'): + if hasattr(setting, "_labels"): l1, l2 = setting._labels.get(int(item), (None, None)) lbl_text = l1 if l1 else lbl_text lbl_tooltip = l2 if l2 else lbl_tooltip - item_lbl = Gtk.Label(lbl_text) + item_lbl = Gtk.Label(label=lbl_text) self.add(item_lbl) - self.set_tooltip_text(lbl_tooltip or ' ') + self.set_tooltip_text(lbl_tooltip or " ") item_lb = Gtk.ListBox() item_lb.set_selection_mode(Gtk.SelectionMode.NONE) item_lb._sub_items = [] @@ -388,27 +418,31 @@ class MultipleRangeControl(MultipleControl): h = Gtk.HBox(homogeneous=False, spacing=20) lbl_text = str(sub_item) lbl_tooltip = None - if hasattr(setting, '_labels_sub'): + if hasattr(setting, "_labels_sub"): l1, l2 = setting._labels_sub.get(str(sub_item), (None, None)) lbl_text = l1 if l1 else lbl_text lbl_tooltip = l2 if l2 else lbl_tooltip - sub_item_lbl = Gtk.Label(lbl_text) - h.set_tooltip_text(lbl_tooltip or ' ') + sub_item_lbl = Gtk.Label(label=lbl_text) + h.set_tooltip_text(lbl_tooltip or " ") h.pack_start(sub_item_lbl, False, False, 0) - sub_item_lbl.set_margin_left(30) - sub_item_lbl.set_alignment(0.0, 0.5) - if sub_item.widget == 'Scale': - control = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, sub_item.minimum, sub_item.maximum, 1) + sub_item_lbl.set_margin_start(30) + if sub_item.widget == "Scale": + control = Gtk.Scale.new_with_range( + Gtk.Orientation.HORIZONTAL, + sub_item.minimum, + sub_item.maximum, + 1, + ) control.set_round_digits(0) control.set_digits(0) h.pack_end(control, True, True, 0) - elif sub_item.widget == 'SpinButton': + elif sub_item.widget == "SpinButton": control = Gtk.SpinButton.new_with_range(sub_item.minimum, sub_item.maximum, 1) control.set_digits(0) h.pack_end(control, False, False, 0) else: raise NotImplementedError - control.connect('value-changed', self.changed, item, sub_item) + control.connect(GtkSignal.VALUE_CHANGED.value, self.changed, item, sub_item) item_lb.add(h) h._setting_sub_item = sub_item h._label, h._control = sub_item_lbl, control @@ -420,27 +454,29 @@ class MultipleRangeControl(MultipleControl): def changed(self, control, item, sub_item): if control.get_sensitive(): - if hasattr(control, '_timer'): + if hasattr(control, "_timer"): control._timer.cancel() - control._timer = _Timer(0.5, lambda: GLib.idle_add(self._write, control, item, sub_item)) + control._timer = Timer(0.5, lambda: GLib.idle_add(self._write, control, item, sub_item)) control._timer.start() def _write(self, control, item, sub_item): control._timer.cancel() - delattr(control, '_timer') + delattr(control, "_timer") new_state = int(control.get_value()) if self.sbox.setting._value[int(item)][str(sub_item)] != new_state: self.sbox.setting._value[int(item)][str(sub_item)] = new_state _write_async(self.sbox.setting, self.sbox.setting._value[int(item)], self.sbox, key=int(item)) def set_value(self, value): - b = '' + if value is None: + return + b = "" n = 0 for ch in self._items: item = ch._setting_item v = value.get(int(item), None) if v is not None: - b += str(item) + ': (' + b += f"{str(item)}: (" to_join = [] for c in ch._sub_items: sub_item = c._setting_sub_item @@ -450,51 +486,51 @@ class MultipleRangeControl(MultipleControl): sub_item_value = c._control.get_value() c._control.set_value(sub_item_value) n += 1 - to_join.append(str(sub_item) + f'={sub_item_value}') - b += ', '.join(to_join) + ') ' - lbl_text = ngettext('%d value', '%d values', n) % n + to_join.append(f"{str(sub_item)}={sub_item_value}") + b += ", ".join(to_join) + ") " + lbl_text = ngettext("%d value", "%d values", n) % n self._button.set_label(lbl_text) self._button.set_tooltip_text(b) class PackedRangeControl(MultipleRangeControl): - def setup(self, setting): - validator = setting._validator self._items = [] + validator = setting._validator for item in range(validator.count): h = Gtk.HBox(homogeneous=False, spacing=0) - lbl = Gtk.Label(str(validator.keys[item])) + lbl = Gtk.Label(label=str(validator.keys[item])) control = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, validator.min_value, validator.max_value, 1) control.set_round_digits(0) control.set_digits(0) - control.connect('value-changed', self.changed, validator.keys[item]) + control.connect(GtkSignal.VALUE_CHANGED.value, self.changed, validator.keys[item]) h.pack_start(lbl, False, False, 0) h.pack_end(control, True, True, 0) h._setting_item = validator.keys[item] h.control = control - lbl.set_alignment(0.0, 0.5) - lbl.set_margin_left(30) + lbl.set_margin_start(30) self.add(h) self._items.append(h) def changed(self, control, item): if control.get_sensitive(): - if hasattr(control, '_timer'): + if hasattr(control, "_timer"): control._timer.cancel() - control._timer = _Timer(0.5, lambda: GLib.idle_add(self._write, control, item)) + control._timer = Timer(0.5, lambda: GLib.idle_add(self._write, control, item)) control._timer.start() def _write(self, control, item): control._timer.cancel() - delattr(control, '_timer') + delattr(control, "_timer") new_state = int(control.get_value()) if self.sbox.setting._value[int(item)] != new_state: self.sbox.setting._value[int(item)] = new_state _write_async(self.sbox.setting, self.sbox.setting._value[int(item)], self.sbox, key=int(item)) def set_value(self, value): - b = '' + if value is None: + return + b = "" n = len(self._items) for h in self._items: item = h._setting_item @@ -503,23 +539,174 @@ class PackedRangeControl(MultipleRangeControl): h.control.set_value(v) else: v = self.sbox.setting._value[int(item)] - b += str(item) + ': (' + str(v) + ') ' - lbl_text = ngettext('%d value', '%d values', n) % n + b += f"{str(item)}: ({str(v)}) " + lbl_text = ngettext("%d value", "%d values", n) % n self._button.set_label(lbl_text) self._button.set_tooltip_text(b) -# -# -# +class GraphicEQControl(MultipleControl): + def setup(self, setting): + self._items = [] + validator = setting._validator + row = Gtk.ListBoxRow() + hbox = Gtk.HBox(homogeneous=True, spacing=8) + for item in range(validator.count): + vbox = Gtk.VBox(homogeneous=False, spacing=2) + scale = Gtk.Scale.new_with_range(Gtk.Orientation.VERTICAL, validator.min_value, validator.max_value, 1) + scale.set_inverted(True) + scale.set_round_digits(0) + scale.set_digits(0) + scale.set_draw_value(True) + scale.connect("format-value", lambda s, v: f"{int(v)} dB") + scale.set_has_origin(True) + scale.set_size_request(-1, 150) + scale.add_mark(0, Gtk.PositionType.LEFT, "0") + scale.connect(GtkSignal.VALUE_CHANGED.value, self._changed, validator.keys[item]) + lbl = Gtk.Label(label=str(validator.keys[item])) + lbl.set_line_wrap(True) + lbl.set_justify(Gtk.Justification.CENTER) + vbox.pack_start(scale, True, True, 0) + vbox.pack_end(lbl, False, False, 0) + vbox._setting_item = validator.keys[item] + vbox.control = scale + hbox.pack_start(vbox, True, True, 0) + self._items.append(vbox) + row.add(hbox) + self.add(row) -_allowables_icons = {True: 'changes-allow', False: 'changes-prevent', _SENSITIVITY_IGNORE: 'dialog-error'} + def _changed(self, control, item): + if control.get_sensitive(): + if hasattr(control, "_timer"): + control._timer.cancel() + control._timer = Timer(0.5, lambda: GLib.idle_add(self._write, control, item)) + control._timer.start() + + def _write(self, control, item): + control._timer.cancel() + delattr(control, "_timer") + new_state = int(control.get_value()) + if self.sbox.setting._value[int(item)] != new_state: + self.sbox.setting._value[int(item)] = new_state + _write_async(self.sbox.setting, self.sbox.setting._value[int(item)], self.sbox, key=int(item)) + + def set_value(self, value): + if value is None: + return + b = "" + n = len(self._items) + for vbox in self._items: + item = vbox._setting_item + v = value.get(int(item), None) + if v is not None: + vbox.control.set_value(v) + else: + v = self.sbox.setting._value[int(item)] + b += f"{str(item)}: ({str(v)}) " + lbl_text = ngettext("%d value", "%d values", n) % n + self._button.set_label(lbl_text) + self._button.set_tooltip_text(b) + + +# control with an ID key that determines what else to show +class HeteroKeyControl(Gtk.HBox, Control): + def __init__(self, sbox, delegate=None): + super().__init__(homogeneous=False, spacing=6) + self.init(sbox, delegate) + self._items = {} + for item in sbox.setting.possible_fields: + if item["label"]: + item_lblbox = Gtk.Label(label=item["label"]) + self.pack_start(item_lblbox, False, False, 0) + item_lblbox.set_visible(False) + else: + item_lblbox = None + + item_box = ComboBoxText() + if item["kind"] == settings.Kind.CHOICE: + for entry in item["choices"]: + item_box.append(str(int(entry)), str(entry)) + item_box.set_active(0) + item_box.connect(GtkSignal.CHANGED.value, self.changed) + self.pack_start(item_box, False, False, 0) + elif item["kind"] == settings.Kind.COLOR: + item_box = Gtk.ColorButton() + item_box.connect(GtkSignal.COLOR_SET.value, self.changed) + self.pack_start(item_box, False, False, 0) + elif item["kind"] == settings.Kind.RANGE: + item_box = Scale() + item_box.set_range(item["min"], item["max"]) + item_box.set_round_digits(0) + item_box.set_digits(0) + item_box.set_increments(1, 5) + item_box.connect(GtkSignal.VALUE_CHANGED.value, self.changed) + self.pack_start(item_box, True, True, 0) + item_box.set_visible(False) + self._items[str(item["name"])] = (item_lblbox, item_box) + + def get_value(self): + result = {} + for k, (_lblbox, box) in self._items.items(): + if isinstance(box, Gtk.ColorButton): + rgba = box.get_rgba() + r = int(rgba.red * 255) + g = int(rgba.green * 255) + b = int(rgba.blue * 255) + result[str(k)] = (r << 16) | (g << 8) | b + else: + result[str(k)] = box.get_value() + result = hidpp20.LEDEffectSetting(**result) + return result + + def set_value(self, value): + self.set_sensitive(False) + if value is not None: + for k, v in value.__dict__.items(): + if k in self._items: + (lblbox, box) = self._items[k] + if isinstance(box, Gtk.ColorButton): + rgba = Gdk.RGBA() + color_string = f"#{v:06X}" # e.g. "#FF0000" + rgba.parse(color_string) + box.set_rgba(rgba) + else: + box.set_value(v) + else: + self.sbox._failed.set_visible(True) + self.setup_visibles(value.ID if value is not None else 0) + + def setup_visibles(self, id_): + fields = self.sbox.setting.fields_map[id_][1] if id_ in self.sbox.setting.fields_map else {} + for name, (lblbox, box) in self._items.items(): + visible = name in fields or name == "ID" + if lblbox: + lblbox.set_visible(visible) + box.set_visible(visible) + + def changed(self, control): + if self.get_sensitive() and control.get_sensitive(): + if "ID" in self._items and control == self._items["ID"][1]: + self.setup_visibles(int(self._items["ID"][1].get_value())) + if hasattr(control, "_timer"): + control._timer.cancel() + control._timer = Timer(0.3, lambda: GLib.idle_add(self._write, control)) + control._timer.start() + + def _write(self, control): + control._timer.cancel() + delattr(control, "_timer") + new_state = self.get_value() + if self.sbox.setting._value != new_state: + _write_async(self.sbox.setting, new_state, self.sbox) + + +_allowables_icons = {True: "changes-allow", False: "changes-prevent", settings.SENSITIVITY_IGNORE: "dialog-error"} _allowables_tooltips = { - True: _('Changes allowed'), - False: _('No changes allowed'), - _SENSITIVITY_IGNORE: _('Ignore this setting') + True: _("Changes allowed"), + False: _("No changes allowed"), + settings.SENSITIVITY_IGNORE: _("Ignore this setting"), } -_next_allowable = {True: False, False: _SENSITIVITY_IGNORE, _SENSITIVITY_IGNORE: True} +_next_allowable = {True: False, False: settings.SENSITIVITY_IGNORE, settings.SENSITIVITY_IGNORE: True} _icons_allowables = {v: k for k, v in _allowables_icons.items()} @@ -533,7 +720,7 @@ def _change_click(button, sbox): _change_icon(new_allowed, icon) if sbox.setting._device.persister: # remember the new setting sensitivity sbox.setting._device.persister.set_sensitivity(sbox.setting.name, new_allowed) - if allowed == _SENSITIVITY_IGNORE: # update setting if it was being ignored + if allowed == settings.SENSITIVITY_IGNORE: # update setting if it was being ignored setting = next((s for s in sbox.setting._device.settings if s.name == sbox.setting.name), None) if setting: persisted = sbox.setting._device.persister.get(setting.name) if sbox.setting._device.persister else None @@ -551,48 +738,69 @@ def _change_icon(allowed, icon): icon.set_tooltip_text(_allowables_tooltips[allowed]) -def _create_sbox(s, device): +def _create_sbox(s, _device): + if not s.display: + return sbox = Gtk.HBox(homogeneous=False, spacing=6) sbox.setting = s sbox.kind = s.kind if s.description: sbox.set_tooltip_text(s.description) - lbl = Gtk.Label(s.label) - lbl.set_alignment(0.0, 0.5) + lbl = Gtk.Label(label=s.label) label = Gtk.EventBox() label.add(lbl) spinner = Gtk.Spinner() - spinner.set_tooltip_text(_('Working') + '...') + spinner.set_tooltip_text(_("Working") + "...") sbox._spinner = spinner - failed = Gtk.Image.new_from_icon_name('dialog-warning', Gtk.IconSize.SMALL_TOOLBAR) - failed.set_tooltip_text(_('Read/write operation failed.')) + failed = Gtk.Image.new_from_icon_name("dialog-warning", Gtk.IconSize.SMALL_TOOLBAR) + failed.set_tooltip_text(_("Read/write operation failed.")) sbox._failed = failed - change_icon = Gtk.Image.new_from_icon_name('changes-prevent', Gtk.IconSize.LARGE_TOOLBAR) + change_icon = Gtk.Image.new_from_icon_name("changes-prevent", Gtk.IconSize.LARGE_TOOLBAR) sbox._change_icon = change_icon _change_icon(False, change_icon) change = Gtk.Button() change.set_relief(Gtk.ReliefStyle.NONE) change.add(change_icon) change.set_sensitive(True) - change.connect('clicked', _change_click, sbox) + change.connect(GtkSignal.CLICKED.value, _change_click, sbox) - if s.kind == _SETTING_KIND.toggle: - control = ToggleControl(sbox) - elif s.kind == _SETTING_KIND.range: - control = SliderControl(sbox) - elif s.kind == _SETTING_KIND.choice: - control = _create_choice_control(sbox) - elif s.kind == _SETTING_KIND.map_choice: - control = MapChoiceControl(sbox) - elif s.kind == _SETTING_KIND.multiple_toggle: - control = MultipleToggleControl(sbox, change) - elif s.kind == _SETTING_KIND.multiple_range: - control = MultipleRangeControl(sbox, change) - elif s.kind == _SETTING_KIND.packed_range: - control = PackedRangeControl(sbox, change) + editor_path = getattr(s, "editor_class", None) + if editor_path: + try: + mod_name, _sep, cls_name = editor_path.partition(":") + import importlib + + mod = importlib.import_module(mod_name) + cls = getattr(mod, cls_name) + control = cls(sbox) + except Exception as e: + logger.warning("setting %s editor_class %r failed (%s); falling back to default", s.name, editor_path, repr(e)) + control = None else: - if _log.isEnabledFor(_WARNING): - _log.warn('setting %s display not implemented', s.label) + control = None + + if control is not None: + pass + elif s.kind == settings.Kind.TOGGLE: + control = ToggleControl(sbox) + elif s.kind == settings.Kind.RANGE: + control = SliderControl(sbox) + elif s.kind == settings.Kind.CHOICE: + control = _create_choice_control(sbox) + elif s.kind == settings.Kind.MAP_CHOICE: + control = MapChoiceControl(sbox) + elif s.kind == settings.Kind.MULTIPLE_TOGGLE: + control = MultipleToggleControl(sbox, change) + elif s.kind == settings.Kind.MULTIPLE_RANGE: + control = MultipleRangeControl(sbox, change) + elif s.kind == settings.Kind.PACKED_RANGE: + control = PackedRangeControl(sbox, change) + elif s.kind == settings.Kind.GRAPHIC_EQ: + control = GraphicEQControl(sbox, change) + elif s.kind == settings.Kind.HETERO: + control = HeteroKeyControl(sbox, change) + else: + logger.warning("setting %s display not implemented", s.label) return None control.set_sensitive(False) # the first read will enable it @@ -604,33 +812,31 @@ def _create_sbox(s, device): return sbox -def _update_setting_item(sbox, value, is_online=True, sensitive=True): - # sbox._spinner.set_visible(False) # don't repack item box +def _update_setting_item(sbox, value, is_online=True, sensitive=True, null_okay=False): sbox._spinner.stop() - if value is None: - sbox._control.set_sensitive(False) - _change_icon(False, sbox._change_icon) + sensitive = sbox._change_icon._allowed if sensitive is None else sensitive + if value is None and not null_okay: + sbox._control.set_sensitive(sensitive is True) + _change_icon(sensitive, sbox._change_icon) sbox._failed.set_visible(is_online) return sbox._failed.set_visible(False) sbox._control.set_sensitive(False) - sbox._control.set_value(value) - sensitive = sbox._change_icon._allowed if sensitive is None else sensitive + try: # a call was producing a TypeError so guard against that + sbox._control.set_value(value) + except TypeError as e: + logger.warning("%s: error setting control value (%s): %s", sbox.setting.name, sbox.setting._device, repr(e)) sbox._control.set_sensitive(sensitive is True) _change_icon(sensitive, sbox._change_icon) def _disable_listbox_highlight_bg(lb): colour = Gdk.RGBA() - colour.parse('rgba(0,0,0,0)') + colour.parse("rgba(0,0,0,0)") for child in lb.get_children(): child.override_background_color(Gtk.StateFlags.PRELIGHT, colour) -# -# -# - # config panel _box = None _items = {} @@ -639,13 +845,13 @@ _items = {} def create(): global _box assert _box is None - _box = Gtk.VBox(homogeneous=False, spacing=8) + _box = Gtk.VBox(homogeneous=False, spacing=4) _box._last_device = None config_scroll = Gtk.ScrolledWindow() config_scroll.add(_box) config_scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) - config_scroll.set_shadow_type(Gtk.ShadowType.IN) + config_scroll.set_shadow_type(Gtk.ShadowType.NONE) # was IN config_scroll.set_size_request(0, 350) # ask for enough vertical space for about eight settings return config_scroll @@ -718,20 +924,29 @@ def _change_setting(device, setting, values): def record_setting(device, setting, values): - """External interface to record a setting that has changed on the device and have the GUI show the change""" - assert device == setting._device + """External interface to have the GUI show a change to a setting. Doesn't write to the device""" GLib.idle_add(_record_setting, device, setting, values, priority=99) -def _record_setting(device, setting, values): - if len(values) > 1: - setting.update_key_value(values[0], values[-1]) - value = {values[0]: values[-1]} - else: - setting.update(values[-1]) - value = values[-1] - device_path = device.receiver.path if device.receiver else device.path - if (device_path, device.number, setting.name) in _items: - sbox = _items[(device_path, device.number, setting.name)] - if sbox: - _update_setting_item(sbox, value) +def _record_setting(device, setting_class, values): + logger.debug("on %s changing setting %s to %s", device, setting_class.name, values) + setting = next((s for s in device.settings if s.name == setting_class.name), None) + if setting is None: + logger.debug( + "No setting for %s found on %s when trying to record a change made elsewhere", + setting_class.name, + device, + ) + if setting: + assert device == setting._device + if len(values) > 1: + setting.update_key_value(values[0], values[-1]) + value = {values[0]: values[-1]} + else: + setting.update(values[-1]) + value = values[-1] + device_path = device.receiver.path if device.receiver else device.path + if (device_path, device.number, setting.name) in _items: + sbox = _items[(device_path, device.number, setting.name)] + if sbox: + _update_setting_item(sbox, value, sensitive=None) diff --git a/lib/solaar/ui/notify.py b/lib/solaar/ui/desktop_notifications.py similarity index 53% rename from lib/solaar/ui/notify.py rename to lib/solaar/ui/desktop_notifications.py index db3e7497..d469a9a3 100644 --- a/lib/solaar/ui/notify.py +++ b/lib/solaar/ui/desktop_notifications.py @@ -1,5 +1,3 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel ## ## This program is free software; you can redistribute it and/or modify @@ -15,92 +13,86 @@ ## You should have received a copy of the GNU General Public License along ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import importlib # Optional desktop notifications. +import logging +from solaar import NAME from solaar.i18n import _ -# -# -# +from . import icons -try: - import gi - gi.require_version('Notify', '0.7') - # this import is allowed to fail, in which case the entire feature is unavailable - from gi.repository import GLib, Notify +logger = logging.getLogger(__name__) - # assumed to be working since the import succeeded - available = True -except (ValueError, ImportError): - available = False +def notifications_available(): + """Checks if notification service is available.""" + notifications_supported = False + try: + import gi + + gi.require_version("Notify", "0.7") + + importlib.util.find_spec("gi.repository.GLib") + importlib.util.find_spec("gi.repository.Notify") + + notifications_supported = True + except ValueError as e: + logger.warning(f"Notification service is not available: {e}") + return notifications_supported + + +available = notifications_available() if available: - from logging import INFO as _INFO - from logging import getLogger - _log = getLogger(__name__) - del getLogger - - from solaar import NAME - - from . import icons as _icons + from gi.repository import GLib + from gi.repository import Notify # cache references to shown notifications here, so if another status comes # while its notification is still visible we don't create another one _notifications = {} def init(): - """Init the notifications system.""" + """Initialize desktop notifications.""" global available if available: if not Notify.is_initted(): - if _log.isEnabledFor(_INFO): - _log.info('starting desktop notifications') + logger.info("starting desktop notifications") try: - return Notify.init(NAME) + return Notify.init(NAME.lower()) except Exception: - _log.exception('initializing desktop notifications') + logger.exception("initializing desktop notifications") available = False return available and Notify.is_initted() def uninit(): + """Stop desktop notifications.""" if available and Notify.is_initted(): - if _log.isEnabledFor(_INFO): - _log.info('stopping desktop notifications') + logger.info("stopping desktop notifications") _notifications.clear() Notify.uninit() - # def toggle(action): - # if action.get_active(): - # init() - # else: - # uninit() - # action.set_sensitive(available) - # return action.get_active() - def alert(reason, icon=None): assert reason if available and Notify.is_initted(): - n = _notifications.get(NAME) + n = _notifications.get(NAME.lower()) if n is None: - n = _notifications[NAME] = Notify.Notification() + n = _notifications[NAME.lower()] = Notify.Notification() # we need to use the filename here because the notifications daemon # is an external application that does not know about our icon sets - icon_file = _icons.icon_file(NAME.lower()) if icon is None else _icons.icon_file(icon) + icon_file = icons.icon_file(NAME.lower()) if icon is None else icons.icon_file(icon) - n.update(NAME, reason, icon_file) + n.update(NAME.lower(), reason, icon_file) n.set_urgency(Notify.Urgency.NORMAL) - n.set_hint('desktop-entry', GLib.Variant('s', NAME.lower())) + n.set_hint("desktop-entry", GLib.Variant("s", NAME.lower())) try: - # if _log.isEnabledFor(_DEBUG): - # _log.debug("showing %s", n) n.show() except Exception: - _log.exception('showing %s', n) + logger.exception("showing %s", n) def show(dev, reason=None, icon=None, progress=None): """Show a notification with title and text. @@ -116,34 +108,35 @@ if available: if reason: message = reason - elif dev.status is None: - message = _('unpaired') - elif bool(dev.status): - message = dev.status.to_string() or _('connected') else: - message = _('offline') + message = _("unspecified reason") # we need to use the filename here because the notifications daemon # is an external application that does not know about our icon sets - icon_file = _icons.device_icon_file(dev.name, dev.kind) if icon is None else _icons.icon_file(icon) + icon_file = icons.device_icon_file(dev.name, dev.kind) if icon is None else icons.icon_file(icon) n.update(summary, message, icon_file) - urgency = Notify.Urgency.LOW if dev.status else Notify.Urgency.NORMAL - n.set_urgency(urgency) - n.set_hint('desktop-entry', GLib.Variant('s', NAME.lower())) + n.set_urgency(Notify.Urgency.NORMAL) + n.set_hint("desktop-entry", GLib.Variant("s", NAME.lower())) if progress: - n.set_hint('value', GLib.Variant('i', progress)) + n.set_hint("value", GLib.Variant("i", progress)) try: - # if _log.isEnabledFor(_DEBUG): - # _log.debug("showing %s", n) - n.show() + return n.show() except Exception: - _log.exception('showing %s', n) + logger.exception(f"showing {n}") else: - init = lambda: False - uninit = lambda: None + + def init(): + return False + + def uninit(): + return None + # toggle = lambda action: False - alert = lambda reason: None - show = lambda dev, reason=None: None + def alert(reason): + return None + + def show(dev, reason=None): + return None diff --git a/lib/solaar/ui/diversion_rules.py b/lib/solaar/ui/diversion_rules.py index 99450999..6e2556fc 100644 --- a/lib/solaar/ui/diversion_rules.py +++ b/lib/solaar/ui/diversion_rules.py @@ -1,6 +1,4 @@ -# -*- python-mode -*- - -## Copyright (C) 2020 Solaar +## Copyright (C) Solaar Contributors ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -15,44 +13,85 @@ ## You should have received a copy of the GNU General Public License along ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from __future__ import annotations + +import dataclasses +import logging import string import threading -from collections import defaultdict, namedtuple -from contextlib import contextmanager as contextlib_contextmanager +from collections import defaultdict from copy import copy -from dataclasses import dataclass, field -from logging import getLogger +from dataclasses import dataclass +from dataclasses import field +from enum import Enum from shlex import quote as shlex_quote +from typing import Any +from typing import Callable from typing import Dict +from typing import Optional + +from gi.repository import Gdk +from gi.repository import GObject +from gi.repository import Gtk +from logitech_receiver import diversion +from logitech_receiver.common import NamedInt +from logitech_receiver.common import NamedInts +from logitech_receiver.common import UnsortedNamedInts +from logitech_receiver.settings import Kind +from logitech_receiver.settings import Setting +from logitech_receiver.settings_templates import SETTINGS +from logitech_receiver.settings_validator import Range -from gi.repository import Gdk, GObject, Gtk -from logitech_receiver import diversion as _DIV -from logitech_receiver.common import NamedInt, NamedInts, UnsortedNamedInts -from logitech_receiver.diversion import CLICK, DEPRESS, RELEASE -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.settings import KIND as _SKIND -from logitech_receiver.settings import Setting as _Setting -from logitech_receiver.settings_templates import SETTINGS as _SETTINGS -from logitech_receiver.special_keys import CONTROL as _CONTROL from solaar.i18n import _ +from solaar.ui import rule_actions +from solaar.ui import rule_conditions +from solaar.ui.rule_base import RuleComponentUI +from solaar.ui.rule_base import norm +from solaar.ui.rule_conditions import ConditionUI -_log = getLogger(__name__) -del getLogger - -# -# -# +logger = logging.getLogger(__name__) _diversion_dialog = None _rule_component_clipboard = None -class RuleComponentWrapper(GObject.GObject): +class GtkSignal(Enum): + ACTIVATE = "activate" + BUTTON_RELEASE_EVENT = "button-release-event" + CHANGED = "changed" + CLICKED = "clicked" + DELETE_EVENT = "delete-event" + KEY_PRESS_EVENT = "key-press-event" + NOTIFY_ACTIVE = "notify::active" + TOGGLED = "toggled" + VALUE_CHANGED = "value_changed" + +def create_all_settings(all_settings: list[Setting]) -> dict[str, list[Setting]]: + settings = {} + for s in sorted(all_settings, key=lambda setting: setting.label): + if s.name not in settings: + settings[s.name] = [s] + else: + prev_setting = settings[s.name][0] + prev_kind = prev_setting.validator_class.kind + if prev_kind != s.validator_class.kind: + logger.warning( + "ignoring setting {} - same name of {}, but different kind ({} != {})".format( + s.__name__, prev_setting.__name__, prev_kind, s.validator_class.kind + ) + ) + continue + settings[s.name].append(s) + return settings + + +ALL_SETTINGS = create_all_settings(SETTINGS) + + +class RuleComponentWrapper(GObject.GObject): def __init__(self, component, level=0, editable=False): self.component = component self.level = level @@ -60,419 +99,254 @@ class RuleComponentWrapper(GObject.GObject): GObject.GObject.__init__(self) def display_left(self): - if isinstance(self.component, _DIV.Rule): + if isinstance(self.component, diversion.Rule): if self.level == 0: - return _('Built-in rules') if not self.editable else _('User-defined rules') + return _("Built-in rules") if not self.editable else _("User-defined rules") if self.level == 1: - return ' ' + _('Rule') - return ' ' + _('Sub-rule') + return " " + _("Rule") + return " " + _("Sub-rule") if self.component is None: - return _('[empty]') - return ' ' + self.__component_ui().left_label(self.component) + return _("[empty]") + return " " + self.__component_ui().left_label(self.component) def display_right(self): if self.component is None: - return '' + return "" return self.__component_ui().right_label(self.component) def display_icon(self): if self.component is None: - return '' - if isinstance(self.component, _DIV.Rule) and self.level == 0: - return 'emblem-system' if not self.editable else 'avatar-default' + return "" + if isinstance(self.component, diversion.Rule) and self.level == 0: + return "emblem-system" if not self.editable else "avatar-default" return self.__component_ui().icon_name() def __component_ui(self): return COMPONENT_UI.get(type(self.component), UnsupportedRuleComponentUI) -class DiversionDialog: +def _create_close_dialog(window: Gtk.Window) -> Gtk.MessageDialog: + """Creates rule editor close dialog, when unsaved changes are present.""" + dialog = Gtk.MessageDialog( + window, + type=Gtk.MessageType.QUESTION, + title=_("Make changes permanent?"), + flags=Gtk.DialogFlags.MODAL, + ) + dialog.set_default_size(400, 100) + dialog.add_buttons( + _("Yes"), + Gtk.ResponseType.YES, + _("No"), + Gtk.ResponseType.NO, + _("Cancel"), + Gtk.ResponseType.CANCEL, + ) + dialog.set_markup(_("If you choose No, changes will be lost when Solaar is closed.")) + return dialog - def __init__(self): - window = Gtk.Window() - window.set_title(_('Solaar Rule Editor')) - window.connect('delete-event', self._closing) - vbox = Gtk.VBox() +def _create_selected_rule_edit_panel() -> Gtk.Grid: + """Creates the edit Condition/Actions panel for a rule. - self.top_panel, self.view = self._create_top_panel() - for col in self._create_view_columns(): - self.view.append_column(col) - vbox.pack_start(self.top_panel, True, True, 0) + Shows the UI for the selected rule component. + """ + grid = Gtk.Grid() + grid.set_margin_start(10) + grid.set_margin_end(10) + grid.set_row_spacing(10) + grid.set_column_spacing(10) + grid.set_halign(Gtk.Align.CENTER) + grid.set_valign(Gtk.Align.CENTER) + grid.set_size_request(0, 120) + return grid - self.dirty = False # if dirty, there are pending changes to be saved - self.type_ui = {} - self.update_ui = {} - self.bottom_panel = self._create_bottom_panel() - self.ui = defaultdict(lambda: UnsupportedRuleComponentUI(self.bottom_panel)) - self.ui.update({ # one instance per type - rc_class: rc_ui_class(self.bottom_panel, on_update=self.on_update) - for rc_class, rc_ui_class in COMPONENT_UI.items() - }) - vbox.pack_start(self.bottom_panel, False, False, 10) +def _populate_model( + model: Gtk.TreeStore, + it: Gtk.TreeIter, + rule_component: Any, + level: int = 0, + pos: int = -1, + editable: Optional[bool] = None, +): + if isinstance(rule_component, list): + for c in rule_component: + _populate_model(model, it, c, level=level, pos=pos, editable=editable) + if pos >= 0: + pos += 1 + return + if editable is None: + editable = model[it][0].editable if it is not None else False + if isinstance(rule_component, diversion.Rule): + editable = editable or (rule_component.source is not None) + wrapped = RuleComponentWrapper(rule_component, level, editable=editable) + piter = model.insert(it, pos, (wrapped,)) + if isinstance(rule_component, (diversion.Rule, diversion.And, diversion.Or, diversion.Later)): + for c in rule_component.components: + ed = editable or (isinstance(c, diversion.Rule) and c.source is not None) + _populate_model(model, piter, c, level + 1, editable=ed) + if len(rule_component.components) == 0: + _populate_model(model, piter, None, level + 1, editable=editable) + elif isinstance(rule_component, diversion.Not): + _populate_model(model, piter, rule_component.component, level + 1, editable=editable) - self.model = self._create_model() - self.view.set_model(self.model) - self.view.expand_all() - window.add(vbox) +@dataclasses.dataclass +class AllowedActions: + c: Any + copy: bool + delete: bool + flatten: bool + insert: bool + insert_only_rule: bool + insert_root: bool + wrap: bool - geometry = Gdk.Geometry() - geometry.min_width = 600 # don't ask for so much space - geometry.min_height = 400 - window.set_geometry_hints(None, geometry, Gdk.WindowHints.MIN_SIZE) - window.set_position(Gtk.WindowPosition.CENTER) - window.show_all() +def allowed_actions(m: Gtk.TreeStore, it: Gtk.TreeIter) -> AllowedActions: + row = m[it] + wrapped = row[0] + c = wrapped.component + parent_it = m.iter_parent(it) + parent_c = m[parent_it][0].component if wrapped.level > 0 else None - window.connect('delete-event', lambda w, e: w.hide_on_delete() or True) + can_wrap = wrapped.editable and wrapped.component is not None and wrapped.level >= 2 + can_delete = wrapped.editable and not isinstance(parent_c, diversion.Not) and c is not None and wrapped.level >= 1 + can_insert = wrapped.editable and not isinstance(parent_c, diversion.Not) and wrapped.level >= 2 + can_insert_only_rule = wrapped.editable and wrapped.level == 1 + can_flatten = ( + wrapped.editable + and not isinstance(parent_c, diversion.Not) + and isinstance(c, (diversion.Rule, diversion.And, diversion.Or)) + and wrapped.level >= 2 + and len(c.components) + ) + can_copy = wrapped.level >= 1 + can_insert_root = wrapped.editable and wrapped.level == 0 + return AllowedActions( + c, + can_copy, + can_delete, + can_flatten, + can_insert, + can_insert_only_rule, + can_insert_root, + can_wrap, + ) - style = window.get_style_context() - style.add_class('solaar') + +class ActionMenu: + def __init__( + self, + window: Gtk.Window, + tree_view: Gtk.TreeView, + populate_model_func: Callable, + on_update: Callable, + ): self.window = window - self._editing_component = None + self.tree_view = tree_view + self._populate_model_func = populate_model_func + self._on_update = on_update - def _closing(self, w, e): - if self.dirty: - dialog = Gtk.MessageDialog( - self.window, - type=Gtk.MessageType.QUESTION, - title=_('Make changes permanent?'), - flags=Gtk.DialogFlags.MODAL, - ) - dialog.set_default_size(400, 100) - dialog.add_buttons( - _('Yes'), - Gtk.ResponseType.YES, - _('No'), - Gtk.ResponseType.NO, - _('Cancel'), - Gtk.ResponseType.CANCEL, - ) - dialog.set_markup(_('If you choose No, changes will be lost when Solaar is closed.')) - dialog.show_all() - response = dialog.run() - dialog.destroy() - if response == Gtk.ResponseType.NO: - w.hide() - elif response == Gtk.ResponseType.YES: - self._save_yaml_file() - w.hide() - else: - # don't close - return True - else: - w.hide() - - def _reload_yaml_file(self): - self.discard_btn.set_sensitive(False) - self.save_btn.set_sensitive(False) - self.dirty = False - for c in self.bottom_panel.get_children(): - self.bottom_panel.remove(c) - _DIV._load_config_rule_file() - self.model = self._create_model() - self.view.set_model(self.model) - self.view.expand_all() - - def _save_yaml_file(self): - if _DIV._save_config_rule_file(): - self.dirty = False - self.save_btn.set_sensitive(False) - self.discard_btn.set_sensitive(False) - - def _create_top_panel(self): - sw = Gtk.ScrolledWindow() - sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.ALWAYS) - view = Gtk.TreeView() - view.set_headers_visible(False) - view.set_enable_tree_lines(True) - view.set_reorderable(False) - - view.connect('key-press-event', self._event_key_pressed) - view.connect('button-release-event', self._event_button_released) - view.get_selection().connect('changed', self._selection_changed) - sw.add(view) - sw.set_size_request(0, 300) # don't ask for so much height - - button_box = Gtk.HBox(spacing=20) - self.save_btn = Gtk.Button.new_from_icon_name('document-save', Gtk.IconSize.BUTTON) - self.save_btn.set_label(_('Save changes')) - self.save_btn.set_always_show_image(True) - self.save_btn.set_sensitive(False) - self.save_btn.set_valign(Gtk.Align.CENTER) - self.discard_btn = Gtk.Button.new_from_icon_name('document-revert', Gtk.IconSize.BUTTON) - self.discard_btn.set_label(_('Discard changes')) - self.discard_btn.set_always_show_image(True) - self.discard_btn.set_sensitive(False) - self.discard_btn.set_valign(Gtk.Align.CENTER) - self.save_btn.connect('clicked', lambda *_args: self._save_yaml_file()) - self.discard_btn.connect('clicked', lambda *_args: self._reload_yaml_file()) - button_box.pack_start(self.save_btn, False, False, 0) - button_box.pack_start(self.discard_btn, False, False, 0) - button_box.set_halign(Gtk.Align.CENTER) - button_box.set_valign(Gtk.Align.CENTER) - button_box.set_size_request(0, 50) - - vbox = Gtk.VBox() - vbox.pack_start(button_box, False, False, 0) - vbox.pack_start(sw, True, True, 0) - - return vbox, view - - def _create_model(self): - model = Gtk.TreeStore(RuleComponentWrapper) - if len(_DIV.rules.components) == 1: - # only built-in rules - add empty user rule list - _DIV.rules.components.insert(0, _DIV.Rule([], source=_DIV._file_path)) - self._populate_model(model, None, _DIV.rules.components) - return model - - def _create_view_columns(self): - cell_icon = Gtk.CellRendererPixbuf() - cell1 = Gtk.CellRendererText() - col1 = Gtk.TreeViewColumn('Type') - col1.pack_start(cell_icon, False) - col1.pack_start(cell1, True) - col1.set_cell_data_func(cell1, lambda _c, c, m, it, _d: c.set_property('text', m.get_value(it, 0).display_left())) - cell2 = Gtk.CellRendererText() - col2 = Gtk.TreeViewColumn('Summary') - col2.pack_start(cell2, True) - col2.set_cell_data_func(cell2, lambda _c, c, m, it, _d: c.set_property('text', m.get_value(it, 0).display_right())) - col2.set_cell_data_func( - cell_icon, lambda _c, c, m, it, _d: c.set_property('icon-name', - m.get_value(it, 0).display_icon()) - ) - return col1, col2 - - def _populate_model(self, model, it, rule_component, level=0, pos=-1, editable=None): - if isinstance(rule_component, list): - for c in rule_component: - self._populate_model(model, it, c, level=level, pos=pos, editable=editable) - if pos >= 0: - pos += 1 - return - if editable is None: - editable = model[it][0].editable if it is not None else False - if isinstance(rule_component, _DIV.Rule): - editable = editable or (rule_component.source is not None) - wrapped = RuleComponentWrapper(rule_component, level, editable=editable) - piter = model.insert(it, pos, (wrapped, )) - if isinstance(rule_component, (_DIV.Rule, _DIV.And, _DIV.Or, _DIV.Later)): - for c in rule_component.components: - ed = editable or (isinstance(c, _DIV.Rule) and c.source is not None) - self._populate_model(model, piter, c, level + 1, editable=ed) - if len(rule_component.components) == 0: - self._populate_model(model, piter, None, level + 1, editable=editable) - elif isinstance(rule_component, _DIV.Not): - self._populate_model(model, piter, rule_component.component, level + 1, editable=editable) - - def _create_bottom_panel(self): - grid = Gtk.Grid() - grid.set_margin_start(10) - grid.set_margin_end(10) - grid.set_row_spacing(10) - grid.set_column_spacing(10) - grid.set_halign(Gtk.Align.CENTER) - grid.set_valign(Gtk.Align.CENTER) - grid.set_size_request(0, 120) - return grid - - def on_update(self): - self.view.queue_draw() - self.dirty = True - self.save_btn.set_sensitive(True) - self.discard_btn.set_sensitive(True) - - def _selection_changed(self, selection): - self.bottom_panel.set_sensitive(False) - (model, it) = selection.get_selected() - if it is None: - return - wrapped = model[it][0] - component = wrapped.component - self._editing_component = component - self.ui[type(component)].show(component, wrapped.editable) - self.bottom_panel.set_sensitive(wrapped.editable) - - def _event_key_pressed(self, v, e): - ''' - Shortcuts: - Ctrl + I insert component - Ctrl + Delete delete row - & wrap with And - | wrap with Or - Shift + R wrap with Rule - ! negate - Ctrl + X cut - Ctrl + C copy - Ctrl + V paste below (or here if empty) - Ctrl + Shift + V paste above - * flatten - Ctrl + S save changes - ''' - state = e.state & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK) - m, it = v.get_selection().get_selected() - wrapped = m[it][0] - c = wrapped.component - parent_it = m.iter_parent(it) - parent_c = m[parent_it][0].component if wrapped.level > 0 else None - can_wrap = wrapped.editable and wrapped.component is not None and wrapped.level >= 2 - can_delete = wrapped.editable and not isinstance(parent_c, _DIV.Not) and c is not None and wrapped.level >= 1 - can_insert = wrapped.editable and not isinstance(parent_c, _DIV.Not) and wrapped.level >= 2 - can_insert_only_rule = wrapped.editable and wrapped.level == 1 - can_flatten = wrapped.editable and not isinstance(parent_c, _DIV.Not) and isinstance( - c, (_DIV.Rule, _DIV.And, _DIV.Or) - ) and wrapped.level >= 2 and len(c.components) - can_copy = wrapped.level >= 1 - can_insert_root = wrapped.editable and wrapped.level == 0 - if state & Gdk.ModifierType.CONTROL_MASK: - if can_delete and e.keyval in [Gdk.KEY_x, Gdk.KEY_X]: - self._menu_do_cut(None, m, it) - elif can_copy and e.keyval in [Gdk.KEY_c, Gdk.KEY_C] and c is not None: - self._menu_do_copy(None, m, it) - elif can_insert and _rule_component_clipboard is not None and e.keyval in [Gdk.KEY_v, Gdk.KEY_V]: - self._menu_do_paste(None, m, it, below=c is not None and not (state & Gdk.ModifierType.SHIFT_MASK)) - elif can_insert_only_rule and isinstance(_rule_component_clipboard, - _DIV.Rule) and e.keyval in [Gdk.KEY_v, Gdk.KEY_V]: - self._menu_do_paste(None, m, it, below=c is not None and not (state & Gdk.ModifierType.SHIFT_MASK)) - elif can_insert_root and isinstance(_rule_component_clipboard, _DIV.Rule) and e.keyval in [Gdk.KEY_v, Gdk.KEY_V]: - self._menu_do_paste(None, m, m.iter_nth_child(it, 0)) - elif can_delete and e.keyval in [Gdk.KEY_KP_Delete, Gdk.KEY_Delete]: - self._menu_do_delete(None, m, it) - elif (can_insert or can_insert_only_rule or can_insert_root) and e.keyval in [Gdk.KEY_i, Gdk.KEY_I]: - menu = Gtk.Menu() - for item in self.__get_insert_menus(m, it, c, can_insert, can_insert_only_rule, can_insert_root): - menu.append(item) - menu.show_all() - rect = self.view.get_cell_area(m.get_path(it), self.view.get_column(1)) - menu.popup_at_rect(self.window.get_window(), rect, Gdk.Gravity.WEST, Gdk.Gravity.CENTER, e) - elif self.dirty and e.keyval in [Gdk.KEY_s, Gdk.KEY_S]: - self._save_yaml_file() - else: - if can_wrap: - if e.keyval == Gdk.KEY_exclam: - self._menu_do_negate(None, m, it) - elif e.keyval == Gdk.KEY_ampersand: - self._menu_do_wrap(None, m, it, _DIV.And) - elif e.keyval == Gdk.KEY_bar: - self._menu_do_wrap(None, m, it, _DIV.Or) - elif e.keyval in [Gdk.KEY_r, Gdk.KEY_R] and (state & Gdk.ModifierType.SHIFT_MASK): - self._menu_do_wrap(None, m, it, _DIV.Rule) - if can_flatten and e.keyval in [Gdk.KEY_asterisk, Gdk.KEY_KP_Multiply]: - self._menu_do_flatten(None, m, it) - - def __get_insert_menus(self, m, it, c, can_insert, can_insert_only_rule, can_insert_root): - items = [] - if can_insert: - ins = self._menu_insert(m, it) - items.append(ins) - if c is None: # just a placeholder - ins.set_label(_('Insert here')) - else: - ins.set_label(_('Insert above')) - ins2 = self._menu_insert(m, it, below=True) - ins2.set_label(_('Insert below')) - items.append(ins2) - elif can_insert_only_rule: - ins = self._menu_create_rule(m, it) - items.append(ins) - if c is None: - ins.set_label(_('Insert new rule here')) - else: - ins.set_label(_('Insert new rule above')) - ins2 = self._menu_create_rule(m, it, below=True) - ins2.set_label(_('Insert new rule below')) - items.append(ins2) - elif can_insert_root: - ins = self._menu_create_rule(m, m.iter_nth_child(it, 0)) - items.append(ins) - return items - - def _event_button_released(self, v, e): - if e.button == 3: # right click - m, it = v.get_selection().get_selected() - wrapped = m[it][0] - c = wrapped.component - parent_it = m.iter_parent(it) - parent_c = m[parent_it][0].component if wrapped.level > 0 else None + def create_menu_event_button_released(self, v, e): + if e.button == Gdk.BUTTON_SECONDARY: # right click menu = Gtk.Menu() - can_wrap = wrapped.editable and wrapped.component is not None and wrapped.level >= 2 - can_delete = wrapped.editable and not isinstance(parent_c, _DIV.Not) and c is not None and wrapped.level >= 1 - can_insert = wrapped.editable and not isinstance(parent_c, _DIV.Not) and wrapped.level >= 2 - can_insert_only_rule = wrapped.editable and wrapped.level == 1 - can_flatten = wrapped.editable and not isinstance(parent_c, _DIV.Not) and isinstance( - c, (_DIV.Rule, _DIV.And, _DIV.Or) - ) and wrapped.level >= 2 and len(c.components) - can_copy = wrapped.level >= 1 - can_insert_root = wrapped.editable and wrapped.level == 0 - for item in self.__get_insert_menus(m, it, c, can_insert, can_insert_only_rule, can_insert_root): + m, it = v.get_selection().get_selected() + enabled_actions = allowed_actions(m, it) + for item in self.get_insert_menus(m, it, enabled_actions): menu.append(item) - if can_flatten: + if enabled_actions.flatten: menu.append(self._menu_flatten(m, it)) - if can_wrap: + if enabled_actions.wrap: menu.append(self._menu_wrap(m, it)) menu.append(self._menu_negate(m, it)) if menu.get_children(): menu.append(Gtk.SeparatorMenuItem(visible=True)) - if can_delete: + if enabled_actions.delete: menu.append(self._menu_cut(m, it)) - if can_copy and c is not None: + if enabled_actions.copy and enabled_actions.c is not None: menu.append(self._menu_copy(m, it)) - if can_insert and _rule_component_clipboard is not None: + if enabled_actions.insert and _rule_component_clipboard is not None: p = self._menu_paste(m, it) menu.append(p) - if c is None: # just a placeholder - p.set_label(_('Paste here')) + if enabled_actions.c is None: # just a placeholder + p.set_label(_("Paste here")) else: - p.set_label(_('Paste above')) + p.set_label(_("Paste above")) p2 = self._menu_paste(m, it, below=True) - p2.set_label(_('Paste below')) + p2.set_label(_("Paste below")) menu.append(p2) - elif can_insert_only_rule and isinstance(_rule_component_clipboard, _DIV.Rule): + elif enabled_actions.insert_only_rule and isinstance(_rule_component_clipboard, diversion.Rule): p = self._menu_paste(m, it) menu.append(p) - if c is None: - p.set_label(_('Paste rule here')) + if enabled_actions.c is None: + p.set_label(_("Paste rule here")) else: - p.set_label(_('Paste rule above')) + p.set_label(_("Paste rule above")) p2 = self._menu_paste(m, it, below=True) - p2.set_label(_('Paste rule below')) + p2.set_label(_("Paste rule below")) menu.append(p2) - elif can_insert_root and isinstance(_rule_component_clipboard, _DIV.Rule): + elif enabled_actions.insert_root and isinstance(_rule_component_clipboard, diversion.Rule): p = self._menu_paste(m, m.iter_nth_child(it, 0)) - p.set_label(_('Paste rule')) + p.set_label(_("Paste rule")) menu.append(p) - if menu.get_children() and can_delete: + if menu.get_children() and enabled_actions.delete: menu.append(Gtk.SeparatorMenuItem(visible=True)) - if can_delete: + if enabled_actions.delete: menu.append(self._menu_delete(m, it)) if menu.get_children(): menu.popup_at_pointer(e) - def _menu_do_flatten(self, _mitem, m, it): + def get_insert_menus(self, m, it, enabled_actions: AllowedActions): + items = [] + if enabled_actions.insert: + ins = self._menu_insert(m, it) + items.append(ins) + if enabled_actions.c is None: # just a placeholder + ins.set_label(_("Insert here")) + else: + ins.set_label(_("Insert above")) + ins2 = self._menu_insert(m, it, below=True) + ins2.set_label(_("Insert below")) + items.append(ins2) + elif enabled_actions.insert_only_rule: + ins = self._menu_create_rule(m, it) + items.append(ins) + if enabled_actions.c is None: + ins.set_label(_("Insert new rule here")) + else: + ins.set_label(_("Insert new rule above")) + ins2 = self._menu_create_rule(m, it, below=True) + ins2.set_label(_("Insert new rule below")) + items.append(ins2) + elif enabled_actions.insert_root: + ins = self._menu_create_rule(m, m.iter_nth_child(it, 0)) + items.append(ins) + return items + + def menu_do_flatten(self, _mitem, m, it): wrapped = m[it][0] c = wrapped.component parent_it = m.iter_parent(it) parent_c = m[parent_it][0].component idx = parent_c.components.index(c) - if isinstance(c, _DIV.Not): - parent_c.components = [*parent_c.components[:idx], c.component, *parent_c.components[idx + 1:]] + if isinstance(c, diversion.Not): + parent_c.components = [*parent_c.components[:idx], c.component, *parent_c.components[idx + 1 :]] children = [next(m[it].iterchildren())[0].component] else: - parent_c.components = [*parent_c.components[:idx], *c.components, *parent_c.components[idx + 1:]] + parent_c.components = [*parent_c.components[:idx], *c.components, *parent_c.components[idx + 1 :]] children = [child[0].component for child in m[it].iterchildren()] m.remove(it) - self._populate_model(m, parent_it, children, level=wrapped.level, pos=idx) + self._populate_model_func(m, parent_it, children, level=wrapped.level, pos=idx) new_iter = m.iter_nth_child(parent_it, idx) - self.view.expand_row(m.get_path(parent_it), True) - self.view.get_selection().select_iter(new_iter) - self.on_update() + self.tree_view.expand_row(m.get_path(parent_it), True) + self.tree_view.get_selection().select_iter(new_iter) + self._on_update() def _menu_flatten(self, m, it): - menu_flatten = Gtk.MenuItem(_('Flatten')) - menu_flatten.connect('activate', self._menu_do_flatten, m, it) + menu_flatten = Gtk.MenuItem(_("Flatten")) + menu_flatten.connect(GtkSignal.ACTIVATE.value, self.menu_do_flatten, m, it) menu_flatten.show() return menu_flatten @@ -485,18 +359,18 @@ class DiversionDialog: idx = 0 else: idx = parent_c.components.index(c) - if isinstance(new_c, _DIV.Rule) and wrapped.level == 1: - new_c.source = _DIV._file_path # new rules will be saved to the YAML file + if isinstance(new_c, diversion.Rule) and wrapped.level == 1: + new_c.source = diversion._file_path # new rules will be saved to the YAML file idx += int(below) parent_c.components.insert(idx, new_c) - self._populate_model(m, parent_it, new_c, level=wrapped.level, pos=idx) - self.on_update() + self._populate_model_func(m, parent_it, new_c, level=wrapped.level, pos=idx) + self._on_update() if len(parent_c.components) == 1: m.remove(it) # remove placeholder in the end new_iter = m.iter_nth_child(parent_it, idx) - self.view.get_selection().select_iter(new_iter) - if isinstance(new_c, (_DIV.Rule, _DIV.And, _DIV.Or, _DIV.Not)): - self.view.expand_row(m.get_path(new_iter), True) + self.tree_view.get_selection().select_iter(new_iter) + if isinstance(new_c, (diversion.Rule, diversion.And, diversion.Or, diversion.Not)): + self.tree_view.expand_row(m.get_path(new_iter), True) def _menu_do_insert_new(self, _mitem, m, it, cls, initial_value, below=False): new_c = cls(initial_value, warn=False) @@ -504,42 +378,42 @@ class DiversionDialog: def _menu_insert(self, m, it, below=False): elements = [ - _('Insert'), + _("Insert"), [ - (_('Sub-rule'), _DIV.Rule, []), - (_('Or'), _DIV.Or, []), - (_('And'), _DIV.And, []), + (_("Sub-rule"), diversion.Rule, []), + (_("Or"), diversion.Or, []), + (_("And"), diversion.And, []), [ - _('Condition'), + _("Condition"), [ - (_('Feature'), _DIV.Feature, FeatureUI.FEATURES_WITH_DIVERSION[0]), - (_('Report'), _DIV.Report, 0), - (_('Process'), _DIV.Process, ''), - (_('Mouse process'), _DIV.MouseProcess, ''), - (_('Modifiers'), _DIV.Modifiers, []), - (_('Key'), _DIV.Key, ''), - (_('KeyIsDown'), _DIV.KeyIsDown, ''), - (_('Active'), _DIV.Active, ''), - (_('Device'), _DIV.Device, ''), - (_('Host'), _DIV.Host, ''), - (_('Setting'), _DIV.Setting, [None, '', None]), - (_('Test'), _DIV.Test, next(iter(_DIV.TESTS))), - (_('Test bytes'), _DIV.TestBytes, [0, 1, 0]), - (_('Mouse Gesture'), _DIV.MouseGesture, ''), - ] + (_("Feature"), diversion.Feature, rule_conditions.FeatureUI.FEATURES_WITH_DIVERSION[0]), + (_("Report"), diversion.Report, 0), + (_("Process"), diversion.Process, ""), + (_("Mouse process"), diversion.MouseProcess, ""), + (_("Modifiers"), diversion.Modifiers, []), + (_("Key"), diversion.Key, ""), + (_("KeyIsDown"), diversion.KeyIsDown, ""), + (_("Active"), diversion.Active, ""), + (_("Device"), diversion.Device, ""), + (_("Host"), diversion.Host, ""), + (_("Setting"), diversion.Setting, [None, "", None]), + (_("Test"), diversion.Test, next(iter(diversion.TESTS))), + (_("Test bytes"), diversion.TestBytes, [0, 1, 0]), + (_("Mouse Gesture"), diversion.MouseGesture, ""), + ], ], [ - _('Action'), + _("Action"), [ - (_('Key press'), _DIV.KeyPress, 'space'), - (_('Mouse scroll'), _DIV.MouseScroll, [0, 0]), - (_('Mouse click'), _DIV.MouseClick, ['left', 1]), - (_('Set'), _DIV.Set, [None, '', None]), - (_('Execute'), _DIV.Execute, ['']), - (_('Later'), _DIV.Later, [1]), - ] + (_("Key press"), diversion.KeyPress, "space"), + (_("Mouse scroll"), diversion.MouseScroll, [0, 0]), + (_("Mouse click"), diversion.MouseClick, ["left", 1]), + (_("Set"), diversion.Set, [None, "", None]), + (_("Execute"), diversion.Execute, [""]), + (_("Later"), diversion.Later, [1]), + ], ], - ] + ], ] def build(spec): @@ -555,7 +429,7 @@ class DiversionDialog: label, feature, *args = spec item = Gtk.MenuItem(label) args = [a.copy() if isinstance(a, list) else a for a in args] - item.connect('activate', self._menu_do_insert_new, m, it, feature, *args, below) + item.connect(GtkSignal.ACTIVATE.value, self._menu_do_insert_new, m, it, feature, *args, below) return item else: return None @@ -564,13 +438,13 @@ class DiversionDialog: menu_insert.show_all() return menu_insert - def _menu_create_rule(self, m, it, below=False): - menu_create_rule = Gtk.MenuItem(_('Insert new rule')) - menu_create_rule.connect('activate', self._menu_do_insert_new, m, it, _DIV.Rule, [], below) + def _menu_create_rule(self, m, it, below=False) -> Gtk.MenuItem: + menu_create_rule = Gtk.MenuItem(_("Insert new rule")) + menu_create_rule.connect(GtkSignal.ACTIVATE.value, self._menu_do_insert_new, m, it, diversion.Rule, [], below) menu_create_rule.show() return menu_create_rule - def _menu_do_delete(self, _mitem, m, it): + def menu_do_delete(self, _mitem, m, it): wrapped = m[it][0] c = wrapped.component parent_it = m.iter_parent(it) @@ -578,67 +452,67 @@ class DiversionDialog: idx = parent_c.components.index(c) parent_c.components.pop(idx) if len(parent_c.components) == 0: # placeholder - self._populate_model(m, parent_it, None, level=wrapped.level) + self._populate_model_func(m, parent_it, None, level=wrapped.level) m.remove(it) - self.view.get_selection().select_iter(m.iter_nth_child(parent_it, max(0, min(idx, len(parent_c.components) - 1)))) - self.on_update() + self.tree_view.get_selection().select_iter(m.iter_nth_child(parent_it, max(0, min(idx, len(parent_c.components) - 1)))) + self._on_update() return c - def _menu_delete(self, m, it): - menu_delete = Gtk.MenuItem(_('Delete')) - menu_delete.connect('activate', self._menu_do_delete, m, it) + def _menu_delete(self, m, it) -> Gtk.MenuItem: + menu_delete = Gtk.MenuItem(_("Delete")) + menu_delete.connect(GtkSignal.ACTIVATE.value, self.menu_do_delete, m, it) menu_delete.show() return menu_delete - def _menu_do_negate(self, _mitem, m, it): + def menu_do_negate(self, _mitem, m, it): wrapped = m[it][0] c = wrapped.component parent_it = m.iter_parent(it) parent_c = m[parent_it][0].component - if isinstance(c, _DIV.Not): # avoid double negation - self._menu_do_flatten(_mitem, m, it) - self.view.expand_row(m.get_path(parent_it), True) - elif isinstance(parent_c, _DIV.Not): # avoid double negation - self._menu_do_flatten(_mitem, m, parent_it) + if isinstance(c, diversion.Not): # avoid double negation + self.menu_do_flatten(_mitem, m, it) + self.tree_view.expand_row(m.get_path(parent_it), True) + elif isinstance(parent_c, diversion.Not): # avoid double negation + self.menu_do_flatten(_mitem, m, parent_it) else: idx = parent_c.components.index(c) - self._menu_do_insert_new(_mitem, m, it, _DIV.Not, c, below=True) - self._menu_do_delete(_mitem, m, m.iter_nth_child(parent_it, idx)) - self.on_update() + self._menu_do_insert_new(_mitem, m, it, diversion.Not, c, below=True) + self.menu_do_delete(_mitem, m, m.iter_nth_child(parent_it, idx)) + self._on_update() - def _menu_negate(self, m, it): - menu_negate = Gtk.MenuItem(_('Negate')) - menu_negate.connect('activate', self._menu_do_negate, m, it) + def _menu_negate(self, m, it) -> Gtk.MenuItem: + menu_negate = Gtk.MenuItem(_("Negate")) + menu_negate.connect(GtkSignal.ACTIVATE.value, self.menu_do_negate, m, it) menu_negate.show() return menu_negate - def _menu_do_wrap(self, _mitem, m, it, cls): + def menu_do_wrap(self, _mitem, m, it, cls): wrapped = m[it][0] c = wrapped.component parent_it = m.iter_parent(it) parent_c = m[parent_it][0].component - if isinstance(parent_c, _DIV.Not): + if isinstance(parent_c, diversion.Not): new_c = cls([c], warn=False) parent_c.component = new_c m.remove(it) - self._populate_model(m, parent_it, new_c, level=wrapped.level, pos=0) - self.view.expand_row(m.get_path(parent_it), True) - self.view.get_selection().select_iter(m.iter_nth_child(parent_it, 0)) + self._populate_model_func(m, parent_it, new_c, level=wrapped.level, pos=0) + self.tree_view.expand_row(m.get_path(parent_it), True) + self.tree_view.get_selection().select_iter(m.iter_nth_child(parent_it, 0)) else: idx = parent_c.components.index(c) self._menu_do_insert_new(_mitem, m, it, cls, [c], below=True) - self._menu_do_delete(_mitem, m, m.iter_nth_child(parent_it, idx)) - self.on_update() + self.menu_do_delete(_mitem, m, m.iter_nth_child(parent_it, idx)) + self._on_update() - def _menu_wrap(self, m, it): - menu_wrap = Gtk.MenuItem(_('Wrap with')) + def _menu_wrap(self, m, it) -> Gtk.MenuItem: + menu_wrap = Gtk.MenuItem(_("Wrap with")) submenu_wrap = Gtk.Menu() - menu_sub_rule = Gtk.MenuItem(_('Sub-rule')) - menu_and = Gtk.MenuItem(_('And')) - menu_or = Gtk.MenuItem(_('Or')) - menu_sub_rule.connect('activate', self._menu_do_wrap, m, it, _DIV.Rule) - menu_and.connect('activate', self._menu_do_wrap, m, it, _DIV.And) - menu_or.connect('activate', self._menu_do_wrap, m, it, _DIV.Or) + menu_sub_rule = Gtk.MenuItem(_("Sub-rule")) + menu_and = Gtk.MenuItem(_("And")) + menu_or = Gtk.MenuItem(_("Or")) + menu_sub_rule.connect(GtkSignal.ACTIVATE.value, self.menu_do_wrap, m, it, diversion.Rule) + menu_and.connect(GtkSignal.ACTIVATE.value, self.menu_do_wrap, m, it, diversion.And) + menu_or.connect(GtkSignal.ACTIVATE.value, self.menu_do_wrap, m, it, diversion.Or) submenu_wrap.append(menu_sub_rule) submenu_wrap.append(menu_and) submenu_wrap.append(menu_or) @@ -646,67 +520,299 @@ class DiversionDialog: menu_wrap.show_all() return menu_wrap - def _menu_do_cut(self, _mitem, m, it): - c = self._menu_do_delete(_mitem, m, it) - self.on_update() + def menu_do_copy(self, _mitem: Gtk.MenuItem, m: Gtk.TreeStore, it: Gtk.TreeIter): global _rule_component_clipboard + + wrapped = m[it][0] + c = wrapped.component + _rule_component_clipboard = diversion.RuleComponent().compile(c.data()) + + def menu_do_cut(self, _mitem, m, it): + global _rule_component_clipboard + + c = self.menu_do_delete(_mitem, m, it) + self._on_update() _rule_component_clipboard = c def _menu_cut(self, m, it): - menu_cut = Gtk.MenuItem(_('Cut')) - menu_cut.connect('activate', self._menu_do_cut, m, it) + menu_cut = Gtk.MenuItem(_("Cut")) + menu_cut.connect(GtkSignal.ACTIVATE.value, self.menu_do_cut, m, it) menu_cut.show() return menu_cut - def _menu_do_paste(self, _mitem, m, it, below=False): + def menu_do_paste(self, _mitem, m, it, below=False): global _rule_component_clipboard + c = _rule_component_clipboard _rule_component_clipboard = None if c: - _rule_component_clipboard = _DIV.RuleComponent().compile(c.data()) + _rule_component_clipboard = diversion.RuleComponent().compile(c.data()) self._menu_do_insert(_mitem, m, it, new_c=c, below=below) - self.on_update() + self._on_update() def _menu_paste(self, m, it, below=False): - menu_paste = Gtk.MenuItem(_('Paste')) - menu_paste.connect('activate', self._menu_do_paste, m, it, below) + menu_paste = Gtk.MenuItem(_("Paste")) + menu_paste.connect(GtkSignal.ACTIVATE.value, self.menu_do_paste, m, it, below) menu_paste.show() return menu_paste - def _menu_do_copy(self, _mitem, m, it): - global _rule_component_clipboard - wrapped = m[it][0] - c = wrapped.component - _rule_component_clipboard = _DIV.RuleComponent().compile(c.data()) - def _menu_copy(self, m, it): - menu_copy = Gtk.MenuItem(_('Copy')) - menu_copy.connect('activate', self._menu_do_copy, m, it) + menu_copy = Gtk.MenuItem(_("Copy")) + menu_copy.connect(GtkSignal.ACTIVATE.value, self.menu_do_copy, m, it) menu_copy.show() return menu_copy + +class DiversionDialog: + def __init__(self, action_menu): + window = Gtk.Window() + window.set_title(_("Solaar Rule Editor")) + window.connect(GtkSignal.DELETE_EVENT.value, self._closing) + vbox = Gtk.VBox() + + self.top_panel, self.view = self._create_top_panel() + for col in self._create_view_columns(): + self.view.append_column(col) + vbox.pack_start(self.top_panel, True, True, 0) + + self._action_menu = action_menu( + window, + self.view, + populate_model_func=_populate_model, + on_update=self.on_update, + ) + + self.dirty = False # if dirty, there are pending changes to be saved + + self.type_ui = {} + self.update_ui = {} + self.selected_rule_edit_panel = _create_selected_rule_edit_panel() + self.ui = defaultdict(lambda: UnsupportedRuleComponentUI(self.selected_rule_edit_panel)) + self.ui.update( + { # one instance per type + rc_class: rc_ui_class(self.selected_rule_edit_panel, on_update=self.on_update) + for rc_class, rc_ui_class in COMPONENT_UI.items() + } + ) + vbox.pack_start(self.selected_rule_edit_panel, False, False, 10) + + self.model = self._create_model() + self.view.set_model(self.model) + self.view.expand_all() + + window.add(vbox) + + geometry = Gdk.Geometry() + geometry.min_width = 600 # don't ask for so much space + geometry.min_height = 400 + window.set_geometry_hints(None, geometry, Gdk.WindowHints.MIN_SIZE) + window.set_position(Gtk.WindowPosition.CENTER) + + window.show_all() + + window.connect(GtkSignal.DELETE_EVENT.value, lambda w, e: w.hide_on_delete() or True) + + style = window.get_style_context() + style.add_class("solaar") + self.window = window + self._editing_component = None + + def _closing(self, window: Gtk.Window, e: Gdk.Event): + if self.dirty: + dialog = _create_close_dialog(window) + response = dialog.run() + dialog.destroy() + if response == Gtk.ResponseType.NO: + window.hide() + elif response == Gtk.ResponseType.YES: + self._save_yaml_file() + window.hide() + else: + # don't close + return True + else: + window.hide() + + def _reload_yaml_file(self): + self.discard_btn.set_sensitive(False) + self.save_btn.set_sensitive(False) + self.dirty = False + for c in self.selected_rule_edit_panel.get_children(): + self.selected_rule_edit_panel.remove(c) + diversion.load_config_rule_file() + self.model = self._create_model() + self.view.set_model(self.model) + self.view.expand_all() + + def _save_yaml_file(self): + if diversion._save_config_rule_file(): + self.dirty = False + self.save_btn.set_sensitive(False) + self.discard_btn.set_sensitive(False) + + def _create_top_panel(self): + sw = Gtk.ScrolledWindow() + sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.ALWAYS) + view = Gtk.TreeView() + view.set_headers_visible(False) + view.set_enable_tree_lines(True) + view.set_reorderable(False) + + view.connect(GtkSignal.KEY_PRESS_EVENT.value, self._event_key_pressed) + view.connect(GtkSignal.BUTTON_RELEASE_EVENT.value, self._event_button_released) + view.get_selection().connect(GtkSignal.CHANGED.value, self._selection_changed) + sw.add(view) + sw.set_size_request(0, 300) # don't ask for so much height + + button_box = Gtk.HBox(spacing=20) + self.save_btn = Gtk.Button.new_from_icon_name("document-save", Gtk.IconSize.BUTTON) + self.save_btn.set_label(_("Save changes")) + self.save_btn.set_always_show_image(True) + self.save_btn.set_sensitive(False) + self.save_btn.set_valign(Gtk.Align.CENTER) + self.discard_btn = Gtk.Button.new_from_icon_name("document-revert", Gtk.IconSize.BUTTON) + self.discard_btn.set_label(_("Discard changes")) + self.discard_btn.set_always_show_image(True) + self.discard_btn.set_sensitive(False) + self.discard_btn.set_valign(Gtk.Align.CENTER) + self.save_btn.connect(GtkSignal.CLICKED.value, lambda *_args: self._save_yaml_file()) + self.discard_btn.connect(GtkSignal.CLICKED.value, lambda *_args: self._reload_yaml_file()) + button_box.pack_start(self.save_btn, False, False, 0) + button_box.pack_start(self.discard_btn, False, False, 0) + button_box.set_halign(Gtk.Align.CENTER) + button_box.set_valign(Gtk.Align.CENTER) + button_box.set_size_request(0, 50) + + vbox = Gtk.VBox() + vbox.pack_start(button_box, False, False, 0) + vbox.pack_start(sw, True, True, 0) + + return vbox, view + + def _create_model(self): + model = Gtk.TreeStore(RuleComponentWrapper) + if len(diversion.rules.components) == 1: + # only built-in rules - add empty user rule list + diversion.rules.components.insert(0, diversion.Rule([], source=diversion._file_path)) + _populate_model(model, None, diversion.rules.components) + return model + + def _create_view_columns(self): + cell_icon = Gtk.CellRendererPixbuf() + cell1 = Gtk.CellRendererText() + col1 = Gtk.TreeViewColumn("Type") + col1.pack_start(cell_icon, False) + col1.pack_start(cell1, True) + col1.set_cell_data_func(cell1, lambda _c, c, m, it, _d: c.set_property("text", m.get_value(it, 0).display_left())) + cell2 = Gtk.CellRendererText() + col2 = Gtk.TreeViewColumn("Summary") + col2.pack_start(cell2, True) + col2.set_cell_data_func(cell2, lambda _c, c, m, it, _d: c.set_property("text", m.get_value(it, 0).display_right())) + col2.set_cell_data_func( + cell_icon, lambda _c, c, m, it, _d: c.set_property("icon-name", m.get_value(it, 0).display_icon()) + ) + return col1, col2 + + def on_update(self): + self.view.queue_draw() + self.dirty = True + self.save_btn.set_sensitive(True) + self.discard_btn.set_sensitive(True) + + def _selection_changed(self, selection): + self.selected_rule_edit_panel.set_sensitive(False) + (model, it) = selection.get_selected() + if it is None: + return + wrapped = model[it][0] + component = wrapped.component + self._editing_component = component + self.ui[type(component)].show(component, wrapped.editable) + self.selected_rule_edit_panel.set_sensitive(wrapped.editable) + + def _event_key_pressed(self, v, e): + """ + Shortcuts: + Ctrl + I insert component + Ctrl + Delete delete row + & wrap with And + | wrap with Or + Shift + R wrap with Rule + ! negate + Ctrl + X cut + Ctrl + C copy + Ctrl + V paste below (or here if empty) + Ctrl + Shift + V paste above + * flatten + Ctrl + S save changes + """ + state = e.state & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK) + m, it = v.get_selection().get_selected() + enabled_actions = allowed_actions(m, it) + if state & Gdk.ModifierType.CONTROL_MASK: + if enabled_actions.delete and e.keyval in [Gdk.KEY_x, Gdk.KEY_X]: + self._action_menu.menu_do_cut(None, m, it) + elif enabled_actions.copy and e.keyval in [Gdk.KEY_c, Gdk.KEY_C] and enabled_actions.c is not None: + self._action_menu.menu_do_copy(None, m, it) + elif enabled_actions.insert and _rule_component_clipboard is not None and e.keyval in [Gdk.KEY_v, Gdk.KEY_V]: + self._action_menu.menu_do_paste( + None, m, it, below=enabled_actions.c is not None and not (state & Gdk.ModifierType.SHIFT_MASK) + ) + elif ( + enabled_actions.insert_only_rule + and isinstance(_rule_component_clipboard, diversion.Rule) + and e.keyval in [Gdk.KEY_v, Gdk.KEY_V] + ): + self._action_menu.menu_do_paste( + None, m, it, below=enabled_actions.c is not None and not (state & Gdk.ModifierType.SHIFT_MASK) + ) + elif ( + enabled_actions.insert_root + and isinstance(_rule_component_clipboard, diversion.Rule) + and e.keyval in [Gdk.KEY_v, Gdk.KEY_V] + ): + self._action_menu.menu_do_paste(None, m, m.iter_nth_child(it, 0)) + elif enabled_actions.delete and e.keyval in [Gdk.KEY_KP_Delete, Gdk.KEY_Delete]: + self._action_menu.menu_do_delete(None, m, it) + elif (enabled_actions.insert or enabled_actions.insert_only_rule or enabled_actions.insert_root) and e.keyval in [ + Gdk.KEY_i, + Gdk.KEY_I, + ]: + menu = Gtk.Menu() + for item in self._action_menu.get_insert_menus( + m, + it, + enabled_actions, + ): + menu.append(item) + menu.show_all() + rect = self.view.get_cell_area(m.get_path(it), self.view.get_column(1)) + menu.popup_at_rect(self.window.get_window(), rect, Gdk.Gravity.WEST, Gdk.Gravity.CENTER, e) + elif self.dirty and e.keyval in [Gdk.KEY_s, Gdk.KEY_S]: + self._save_yaml_file() + else: + if enabled_actions.wrap: + if e.keyval == Gdk.KEY_exclam: + self._action_menu.menu_do_negate(None, m, it) + elif e.keyval == Gdk.KEY_ampersand: + self._action_menu.menu_do_wrap(None, m, it, diversion.And) + elif e.keyval == Gdk.KEY_bar: + self._action_menu.menu_do_wrap(None, m, it, diversion.Or) + elif e.keyval in [Gdk.KEY_r, Gdk.KEY_R] and (state & Gdk.ModifierType.SHIFT_MASK): + self._action_menu.menu_do_wrap(None, m, it, diversion.Rule) + if enabled_actions.flatten and e.keyval in [Gdk.KEY_asterisk, Gdk.KEY_KP_Multiply]: + self._action_menu.menu_do_flatten(None, m, it) + + def _event_button_released(self, v, e): + self._action_menu.create_menu_event_button_released(v, e) + def update_devices(self): for rc in self.ui.values(): rc.update_devices() self.view.queue_draw() -## Not currently used -# -# class HexEntry(Gtk.Entry, Gtk.Editable): -# -# def do_insert_text(self, new_text, length, pos): -# new_text = new_text.upper() -# from string import hexdigits -# if any(c for c in new_text if c not in hexdigits): -# return pos -# else: -# self.get_buffer().insert_text(pos, new_text, length) -# return pos + length - - class CompletionEntry(Gtk.Entry): - def __init__(self, values, *args, **kwargs): super().__init__(*args, **kwargs) CompletionEntry.add_completion_to_entry(self, values) @@ -718,7 +824,6 @@ class CompletionEntry(Gtk.Entry): liststore = Gtk.ListStore(str) completion = Gtk.EntryCompletion() completion.set_model(liststore) - norm = lambda s: s.replace('_', '').replace(' ', '').lower() completion.set_match_func(lambda completion, key, it: norm(key) in norm(completion.get_model()[it][0])) completion.set_text_column(0) entry.set_completion(completion) @@ -726,7 +831,7 @@ class CompletionEntry(Gtk.Entry): liststore = completion.get_model() liststore.clear() for v in sorted(set(values), key=str.casefold): - liststore.append((v, )) + liststore.append((v,)) class SmartComboBox(Gtk.ComboBox): @@ -756,7 +861,7 @@ class SmartComboBox(Gtk.ComboBox): """ def __init__( - self, all_values, blank='', completion=False, case_insensitive=False, replace_with_default_name=False, **kwargs + self, all_values, blank="", completion=False, case_insensitive=False, replace_with_default_name=False, **kwargs ): super().__init__(**kwargs) self._name_to_idx = {} @@ -765,7 +870,7 @@ class SmartComboBox(Gtk.ComboBox): self._all_values = [] self._blank = blank self._model = None - self._commpletion = completion + self._completion = completion self._case_insensitive = case_insensitive self._norm = lambda s: None if s is None else s if not case_insensitive else str(s).upper() self._replace_with_default_name = replace_with_default_name @@ -777,7 +882,7 @@ class SmartComboBox(Gtk.ComboBox): if name != self.get_child().get_text(): self.get_child().set_text(name) - self.connect('changed', lambda *a: replace_with(self.get_value(invalid_as_str=False))) + self.connect(GtkSignal.CHANGED.value, lambda *a: replace_with(self.get_value(invalid_as_str=False))) self.set_id_column(0) if self.get_has_entry(): @@ -785,9 +890,9 @@ class SmartComboBox(Gtk.ComboBox): else: renderer = Gtk.CellRendererText() self.pack_start(renderer, True) - self.add_attribute(renderer, 'text', 1) + self.add_attribute(renderer, "text", 1) self.set_all_values(all_values) - self.set_active_id('') + self.set_active_id("") @classmethod def new_model(cls): @@ -802,22 +907,22 @@ class SmartComboBox(Gtk.ComboBox): self._name_to_idx = {} self._value_to_idx = {} self._hidden_idx = set() - self._all_values = [v if isinstance(v, tuple) else (v, ) for v in all_values] + self._all_values = [v if isinstance(v, tuple) else (v,) for v in all_values] model, filtered_model = SmartComboBox.new_model() # creating a new model seems to be necessary to avoid firing 'changed' event once per inserted item - model.append(('', self._blank, True)) + model.append(("", self._blank, True)) self._model = model to_complete = [self._blank] for idx, item in enumerate(self._all_values): - value, *names = item if isinstance(item, tuple) else (item, ) + value, *names = item if isinstance(item, tuple) else (item,) visible = visible_fn(value) self._include(model, idx, value, visible, *names) if visible: to_complete += names if names else [str(value).strip()] self.set_model(filtered_model) - if self.get_has_entry() and self._commpletion: + if self.get_has_entry() and self._completion: CompletionEntry.add_completion_to_entry(self.get_child(), to_complete) if self._find_idx(old_value) is not None: self.set_value(old_value) @@ -851,7 +956,7 @@ class SmartComboBox(Gtk.ComboBox): if tree_iter is not None: t = self.get_model()[tree_iter] number = t[0] - return self._all_values[int(number)][0] if number != '' and (accept_hidden or t[2]) else None + return self._all_values[int(number)][0] if number != "" and (accept_hidden or t[2]) else None elif self.get_has_entry(): text = self.get_child().get_text().strip() if text == self._blank: @@ -886,14 +991,14 @@ class SmartComboBox(Gtk.ComboBox): If `value` is invalid, then the entry text is set to the provided value if the widget has an entry and `accept_invalid` is True, or else the blank value is set. """ - idx = self._find_idx(value) if value != self._blank else '' + idx = self._find_idx(value) if value != self._blank else "" if idx is not None: self.set_active_id(str(idx)) else: if self.get_has_entry() and accept_invalid: - self.get_child().set_text(str(value or '') if value != '' else self._blank) + self.get_child().set_text(str(value or "") if value != "" else self._blank) else: - self.set_active_id('') + self.set_active_id("") def show_only(self, only, include_new=False): """Hide items not present in `only`. @@ -914,51 +1019,51 @@ class SmartComboBox(Gtk.ComboBox): @dataclass class DeviceInfo: + serial: str = "" + unitId: str = "" + codename: str = "" + settings: Dict[str, Setting] = field(default_factory=dict) - serial: str = '' - unitId: str = '' - codename: str = '' - settings: Dict[str, _Setting] = field(default_factory=dict) + def __post_init__(self): + if self.serial is None or self.serial == "?": + self.serial = "" + if self.unitId is None or self.unitId == "?": + self.unitId = "" @property - def id(self): - return self.serial or self.unitId or '' + def id(self) -> str: + return self.serial or self.unitId or "" @property - def identifiers(self): + def identifiers(self) -> list[str]: return [id for id in (self.serial, self.unitId) if id] @property - def display_name(self): - return f'{self.codename} ({self.id})' + def display_name(self) -> str: + return f"{self.codename} ({self.id})" - def __post_init__(self): - if self.serial is None or self.serial == '?': - self.serial = '' - if self.unitId is None or self.unitId == '?': - self.unitId = '' - - def matches(self, search): + def matches(self, search: str) -> bool: return search and search in (self.serial, self.unitId, self.display_name) - def update(self, device): - for k in ('serial', 'unitId', 'codename', 'settings'): + def update(self, device: DeviceInfo) -> None: + for k in ("serial", "unitId", "codename", "settings"): if not getattr(self, k, None): v = getattr(device, k, None) - if v and v != '?': - setattr(self, k, copy(v) if k != 'settings' else {s.name: s for s in v}) + if v and v != "?": + setattr(self, k, copy(v) if k != "settings" else {s.name: s for s in v}) + +class DeviceInfoFactory: @classmethod - def from_device(cls, device): + def create_device_info(cls, device) -> DeviceInfo: d = DeviceInfo() d.update(device) return d class AllDevicesInfo: - def __init__(self): - self._devices = [] + self._devices: list[DeviceInfo] = [] self._lock = threading.Lock() def __iter__(self): @@ -977,11 +1082,12 @@ class AllDevicesInfo: def dev_in_row(_store, _treepath, row): nonlocal updated device = _dev_model.get_value(row, 7) - if device and device.kind and (device.serial and device.serial != '?' or device.unitId and device.unitId != '?'): + if device and device.kind and (device.serial and device.serial != "?" or device.unitId and device.unitId != "?"): existing = self[device.serial] or self[device.unitId] if not existing: updated = True - self._devices.append(DeviceInfo.from_device(device)) + device_info = DeviceInfoFactory.create_device_info(device) + self._devices.append(device_info) elif not existing.settings and device.settings: updated = True existing.update(device) @@ -991,86 +1097,24 @@ class AllDevicesInfo: return updated -class RuleComponentUI: - - CLASS = _DIV.RuleComponent - - def __init__(self, panel, on_update=None): - self.panel = panel - self.widgets = {} # widget -> coord. in grid - self.component = None - self._ignore_changes = 0 - self._on_update_callback = (lambda: None) if on_update is None else on_update - self.create_widgets() - - def create_widgets(self): - pass - - def show(self, component, editable=True): - self._show_widgets(editable) - self.component = component - - def collect_value(self): - return None - - @contextlib_contextmanager - def ignore_changes(self): - self._ignore_changes += 1 - yield None - self._ignore_changes -= 1 - - def _on_update(self, *_args): - if not self._ignore_changes and self.component is not None: - value = self.collect_value() - self.component.__init__(value, warn=False) - self._on_update_callback() - return value - return None - - def _show_widgets(self, editable): - self._remove_panel_items() - for widget, coord in self.widgets.items(): - self.panel.attach(widget, *coord) - widget.set_sensitive(editable) - widget.show() - - @classmethod - def left_label(cls, component): - return type(component).__name__ - - @classmethod - def right_label(cls, _component): - return '' - - @classmethod - def icon_name(cls): - return '' - - def _remove_panel_items(self): - for c in self.panel.get_children(): - self.panel.remove(c) - - def update_devices(self): - pass - - class UnsupportedRuleComponentUI(RuleComponentUI): - CLASS = None def create_widgets(self): self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('This editor does not support the selected rule component yet.')) + self.label.set_text(_("This editor does not support the selected rule component yet.") if self.component else "") self.widgets[self.label] = (0, 0, 1, 1) + def collect_value(self): + return self.component.components[:] # not editable on the bottom panel + @classmethod def right_label(cls, component): return str(component) class RuleUI(RuleComponentUI): - - CLASS = _DIV.Rule + CLASS = diversion.Rule def create_widgets(self): self.widgets = {} @@ -1080,25 +1124,15 @@ class RuleUI(RuleComponentUI): @classmethod def left_label(cls, component): - return _('Rule') + return _("Rule") @classmethod def icon_name(cls): - return 'format-justify-fill' - - -class ConditionUI(RuleComponentUI): - - CLASS = _DIV.Condition - - @classmethod - def icon_name(cls): - return 'dialog-question' + return "format-justify-fill" class AndUI(RuleComponentUI): - - CLASS = _DIV.And + CLASS = diversion.And def create_widgets(self): self.widgets = {} @@ -1108,12 +1142,11 @@ class AndUI(RuleComponentUI): @classmethod def left_label(cls, component): - return _('And') + return _("And") class OrUI(RuleComponentUI): - - CLASS = _DIV.Or + CLASS = diversion.Or def create_widgets(self): self.widgets = {} @@ -1123,26 +1156,25 @@ class OrUI(RuleComponentUI): @classmethod def left_label(cls, component): - return _('Or') + return _("Or") class LaterUI(RuleComponentUI): - - CLASS = _DIV.Later - MIN_VALUE = 1 + CLASS = diversion.Later + MIN_VALUE = 0.01 MAX_VALUE = 100 def create_widgets(self): self.widgets = {} self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('Number of seconds to delay.')) + self.label.set_text(_("Number of seconds to delay. Delay between 0 and 1 is done with higher precision.")) self.widgets[self.label] = (0, 0, 1, 1) self.field = Gtk.SpinButton.new_with_range(self.MIN_VALUE, self.MAX_VALUE, 1) + self.field.set_digits(3) self.field.set_halign(Gtk.Align.CENTER) self.field.set_valign(Gtk.Align.CENTER) self.field.set_hexpand(True) - # self.field.set_vexpand(True) - self.field.connect('changed', self._on_update) + self.field.connect(GtkSignal.VALUE_CHANGED.value, self._on_update) self.widgets[self.field] = (0, 1, 1, 1) def show(self, component, editable): @@ -1151,11 +1183,11 @@ class LaterUI(RuleComponentUI): self.field.set_value(component.delay) def collect_value(self): - return [int(self.field.get_value())] + self.component.components + return [float(int((self.field.get_value() + 0.0001) * 1000)) / 1000] + self.component.components @classmethod def left_label(cls, component): - return _('Later') + return _("Later") @classmethod def right_label(cls, component): @@ -1163,8 +1195,7 @@ class LaterUI(RuleComponentUI): class NotUI(RuleComponentUI): - - CLASS = _DIV.Not + CLASS = diversion.Not def create_widgets(self): self.widgets = {} @@ -1174,864 +1205,15 @@ class NotUI(RuleComponentUI): @classmethod def left_label(cls, component): - return _('Not') - - -class ProcessUI(ConditionUI): - - CLASS = _DIV.Process - - def create_widgets(self): - self.widgets = {} - self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('X11 active process. For use in X11 only.')) - self.widgets[self.label] = (0, 0, 1, 1) - self.field = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) - self.field.set_size_request(600, 0) - self.field.connect('changed', self._on_update) - self.widgets[self.field] = (0, 1, 1, 1) - - def show(self, component, editable): - super().show(component, editable) - 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 _('Process') - - @classmethod - def right_label(cls, component): - return str(component.process) - - -class MouseProcessUI(ConditionUI): - - CLASS = _DIV.MouseProcess - - def create_widgets(self): - self.widgets = {} - self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('X11 mouse process. For use in X11 only.')) - self.widgets[self.label] = (0, 0, 1, 1) - self.field = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) - self.field.set_size_request(600, 0) - self.field.connect('changed', self._on_update) - self.widgets[self.field] = (0, 1, 1, 1) - - def show(self, component, editable): - super().show(component, editable) - 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 - FEATURES_WITH_DIVERSION = [ - str(_ALL_FEATURES.CROWN), - str(_ALL_FEATURES.THUMB_WHEEL), - str(_ALL_FEATURES.LOWRES_WHEEL), - str(_ALL_FEATURES.HIRES_WHEEL), - str(_ALL_FEATURES.GESTURE_2), - str(_ALL_FEATURES.REPROG_CONTROLS_V4), - str(_ALL_FEATURES.GKEY), - str(_ALL_FEATURES.MKEYS), - str(_ALL_FEATURES.MR), - ] - - def create_widgets(self): - self.widgets = {} - self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('Feature name of notification triggering rule processing.')) - self.widgets[self.label] = (0, 0, 1, 1) - self.field = Gtk.ComboBoxText.new_with_entry() - self.field.append('', '') - for feature in self.FEATURES_WITH_DIVERSION: - self.field.append(feature, feature) - self.field.set_valign(Gtk.Align.CENTER) - # self.field.set_vexpand(True) - self.field.set_size_request(600, 0) - self.field.connect('changed', self._on_update) - all_features = [str(f) for f in _ALL_FEATURES] - CompletionEntry.add_completion_to_entry(self.field.get_child(), all_features) - self.widgets[self.field] = (0, 1, 1, 1) - - def show(self, component, editable): - super().show(component, editable) - with self.ignore_changes(): - f = str(component.feature) if component.feature else '' - self.field.set_active_id(f) - if f not in self.FEATURES_WITH_DIVERSION: - self.field.get_child().set_text(f) - - def collect_value(self): - return (self.field.get_active_text() or '').strip() - - def _on_update(self, *args): - super()._on_update(*args) - icon = 'dialog-warning' if not self.component.feature else '' - self.field.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) - - @classmethod - def left_label(cls, component): - return _('Feature') - - @classmethod - def right_label(cls, component): - return '%s (%04X)' % (str(component.feature), int(component.feature or 0)) - - -class ReportUI(ConditionUI): - - CLASS = _DIV.Report - MIN_VALUE = -1 # for invalid values - MAX_VALUE = 15 - - def create_widgets(self): - self.widgets = {} - self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('Report number of notification triggering rule processing.')) - self.widgets[self.label] = (0, 0, 1, 1) - self.field = Gtk.SpinButton.new_with_range(self.MIN_VALUE, self.MAX_VALUE, 1) - self.field.set_halign(Gtk.Align.CENTER) - self.field.set_valign(Gtk.Align.CENTER) - self.field.set_hexpand(True) - # self.field.set_vexpand(True) - self.field.connect('changed', self._on_update) - self.widgets[self.field] = (0, 1, 1, 1) - - def show(self, component, editable): - super().show(component, editable) - with self.ignore_changes(): - self.field.set_value(component.report) - - def collect_value(self): - return int(self.field.get_value()) - - @classmethod - def left_label(cls, component): - return _('Report') - - @classmethod - def right_label(cls, component): - return str(component.report) - - -class ModifiersUI(ConditionUI): - - CLASS = _DIV.Modifiers - - def create_widgets(self): - self.widgets = {} - self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('Active keyboard modifiers. Not always available in Wayland.')) - self.widgets[self.label] = (0, 0, 5, 1) - self.labels = {} - self.switches = {} - for i, m in enumerate(_DIV.MODIFIERS): - switch = Gtk.Switch(halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) - label = Gtk.Label(m, halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) - self.widgets[label] = (i, 1, 1, 1) - self.widgets[switch] = (i, 2, 1, 1) - self.labels[m] = label - self.switches[m] = switch - switch.connect('notify::active', self._on_update) - - def show(self, component, editable): - super().show(component, editable) - with self.ignore_changes(): - for m in _DIV.MODIFIERS: - self.switches[m].set_active(m in component.modifiers) - - def collect_value(self): - return [m for m, s in self.switches.items() if s.get_active()] - - @classmethod - def left_label(cls, component): - return _('Modifiers') - - @classmethod - def right_label(cls, component): - return '+'.join(component.modifiers) or 'None' - - -class KeyUI(ConditionUI): - - CLASS = _DIV.Key - KEY_NAMES = map(str, _CONTROL) - - def create_widgets(self): - self.widgets = {} - self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text( - _( - 'Diverted key or button depressed or released.\n' - 'Use the Key/Button Diversion and Divert G Keys settings to divert keys and buttons.' - ) - ) - self.widgets[self.label] = (0, 0, 5, 1) - self.key_field = CompletionEntry(self.KEY_NAMES, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) - self.key_field.set_size_request(600, 0) - self.key_field.connect('changed', self._on_update) - self.widgets[self.key_field] = (0, 1, 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, 1, 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, 1, 1, 1) - - def show(self, component, editable): - super().show(component, editable) - with self.ignore_changes(): - 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): - 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 or not self.component.action else '' - self.key_field.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) - - @classmethod - def left_label(cls, component): - return _('Key') - - @classmethod - def right_label(cls, component): - return '%s (%04X) (%s)' % (str(component.key), int(component.key), _(component.action)) if component.key else 'None' - - -class KeyIsDownUI(ConditionUI): - - CLASS = _DIV.KeyIsDown - KEY_NAMES = map(str, _CONTROL) - - def create_widgets(self): - self.widgets = {} - self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text( - _( - 'Diverted key or button is currently down.\n' - 'Use the Key/Button Diversion and Divert G Keys settings to divert keys and buttons.' - ) - ) - self.widgets[self.label] = (0, 0, 5, 1) - self.key_field = CompletionEntry(self.KEY_NAMES, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) - self.key_field.set_size_request(600, 0) - self.key_field.connect('changed', self._on_update) - self.widgets[self.key_field] = (0, 1, 1, 1) - - def show(self, component, editable): - super().show(component, editable) - with self.ignore_changes(): - self.key_field.set_text(str(component.key) if self.component.key else '') - - def collect_value(self): - return self.key_field.get_text() - - def _on_update(self, *args): - super()._on_update(*args) - icon = 'dialog-warning' if not self.component.key else '' - self.key_field.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) - - @classmethod - def left_label(cls, component): - return _('KeyIsDown') - - @classmethod - def right_label(cls, component): - return '%s (%04X)' % (str(component.key), int(component.key)) if component.key else 'None' - - -class TestUI(ConditionUI): - - CLASS = _DIV.Test - - def create_widgets(self): - self.widgets = {} - self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('Test condition on notification triggering rule processing.')) - self.widgets[self.label] = (0, 0, 4, 1) - lbl = Gtk.Label(_('Test'), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=False, vexpand=False) - self.widgets[lbl] = (0, 1, 1, 1) - lbl = Gtk.Label(_('Parameter'), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=False, vexpand=False) - self.widgets[lbl] = (2, 1, 1, 1) - - self.test = Gtk.ComboBoxText.new_with_entry() - self.test.append('', '') - for t in _DIV.TESTS: - self.test.append(t, t) - self.test.set_halign(Gtk.Align.END) - self.test.set_valign(Gtk.Align.CENTER) - self.test.set_hexpand(False) - self.test.set_size_request(300, 0) - CompletionEntry.add_completion_to_entry(self.test.get_child(), _DIV.TESTS) - self.test.connect('changed', self._on_update) - self.widgets[self.test] = (1, 1, 1, 1) - - self.parameter = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) - self.parameter.set_size_request(150, 0) - self.parameter.connect('changed', self._on_update) - self.widgets[self.parameter] = (3, 1, 1, 1) - - def show(self, component, editable): - super().show(component, editable) - with self.ignore_changes(): - self.test.set_active_id(component.test) - self.parameter.set_text(str(component.parameter) if component.parameter is not None else '') - if component.test not in _DIV.TESTS: - self.test.get_child().set_text(component.test) - self._change_status_icon() - - def collect_value(self): - try: - param = int(self.parameter.get_text()) if self.parameter.get_text() else None - except Exception: - param = self.parameter.get_text() - test = (self.test.get_active_text() or '').strip() - return [test, param] if param is not None else [test] - - def _on_update(self, *args): - super()._on_update(*args) - self._change_status_icon() - - def _change_status_icon(self): - icon = 'dialog-warning' if (self.test.get_active_text() or '').strip() not in _DIV.TESTS else '' - self.test.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) - - @classmethod - def left_label(cls, component): - return _('Test') - - @classmethod - def right_label(cls, component): - return component.test + (' ' + repr(component.parameter) if component.parameter is not None else '') - - -_TestBytesElement = namedtuple('TestBytesElement', ['id', 'label', 'min', 'max']) -_TestBytesMode = namedtuple('TestBytesMode', ['label', 'elements', 'label_fn']) - - -class TestBytesUI(ConditionUI): - - CLASS = _DIV.TestBytes - - _common_elements = [ - _TestBytesElement('begin', _('begin (inclusive)'), 0, 16), - _TestBytesElement('end', _('end (exclusive)'), 0, 16) - ] - - _global_min = -2**31 - _global_max = 2**31 - 1 - - _modes = { - 'range': - _TestBytesMode( - _('range'), - _common_elements + [ - _TestBytesElement('minimum', _('minimum'), _global_min, _global_max), # uint32 - _TestBytesElement('maximum', _('maximum'), _global_min, _global_max), - ], - lambda e: _('bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d' % {str(i): v - for i, v in enumerate(e)}) - ), - 'mask': - _TestBytesMode( - _('mask'), _common_elements + [_TestBytesElement('mask', _('mask'), _global_min, _global_max)], - lambda e: _('bytes %(0)d to %(1)d, mask %(2)d' % {str(i): v - for i, v in enumerate(e)}) - ) - } - - def create_widgets(self): - self.fields = {} - self.field_labels = {} - self.widgets = {} - self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('Bit or range test on bytes in notification message triggering rule processing.')) - self.widgets[self.label] = (0, 0, 5, 1) - col = 0 - mode_col = 2 - self.mode_field = Gtk.ComboBox.new_with_model(Gtk.ListStore(str, str)) - mode_renderer = Gtk.CellRendererText() - self.mode_field.set_id_column(0) - self.mode_field.pack_start(mode_renderer, True) - self.mode_field.add_attribute(mode_renderer, 'text', 1) - self.widgets[self.mode_field] = (mode_col, 2, 1, 1) - mode_label = Gtk.Label(_('type'), margin_top=20) - self.widgets[mode_label] = (mode_col, 1, 1, 1) - for mode_id, mode in TestBytesUI._modes.items(): - self.mode_field.get_model().append([mode_id, mode.label]) - for element in mode.elements: - if element.id not in self.fields: - field = Gtk.SpinButton.new_with_range(element.min, element.max, 1) - field.set_value(0) - field.set_size_request(150, 0) - field.connect('value-changed', self._on_update) - label = Gtk.Label(element.label, margin_top=20) - self.fields[element.id] = field - self.field_labels[element.id] = label - self.widgets[label] = (col, 1, 1, 1) - self.widgets[field] = (col, 2, 1, 1) - col += 1 if col != mode_col - 1 else 2 - self.mode_field.connect('changed', lambda cb: (self._on_update(), self._only_mode(cb.get_active_id()))) - self.mode_field.set_active_id('range') - - def show(self, component, editable): - super().show(component, editable) - - with self.ignore_changes(): - mode_id = {3: 'mask', 4: 'range'}.get(len(component.test), None) - self._only_mode(mode_id) - if not mode_id: - return - self.mode_field.set_active_id(mode_id) - if mode_id: - mode = TestBytesUI._modes[mode_id] - for i, element in enumerate(mode.elements): - self.fields[element.id].set_value(component.test[i]) - - def collect_value(self): - mode_id = self.mode_field.get_active_id() - return [self.fields[element.id].get_value_as_int() for element in TestBytesUI._modes[mode_id].elements] - - def _only_mode(self, mode_id): - if not mode_id: - return - keep = {element.id for element in TestBytesUI._modes[mode_id].elements} - for element_id, f in self.fields.items(): - visible = element_id in keep - f.set_visible(visible) - self.field_labels[element_id].set_visible(visible) - - def _on_update(self, *args): - super()._on_update(*args) - if not self.component: - return - begin, end, *etc = self.component.test - icon = 'dialog-warning' if end <= begin else '' - self.fields['end'].set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) - if len(self.component.test) == 4: - *etc, minimum, maximum = self.component.test - icon = 'dialog-warning' if maximum < minimum else '' - self.fields['maximum'].set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) - - @classmethod - def left_label(cls, component): - return _('Test bytes') - - @classmethod - def right_label(cls, component): - mode_id = {3: 'mask', 4: 'range'}.get(len(component.test), None) - if not mode_id: - return str(component.test) - return TestBytesUI._modes[mode_id].label_fn(component.test) - - -class MouseGestureUI(ConditionUI): - - CLASS = _DIV.MouseGesture - MOUSE_GESTURE_NAMES = [ - 'Mouse Up', 'Mouse Down', 'Mouse Left', 'Mouse Right', 'Mouse Up-left', 'Mouse Up-right', 'Mouse Down-left', - 'Mouse Down-right' - ] - MOVE_NAMES = list(map(str, _CONTROL)) + MOUSE_GESTURE_NAMES - - def create_widgets(self): - self.widgets = {} - self.fields = [] - self.label = Gtk.Label( - _('Mouse gesture with optional initiating button followed by zero or more mouse movements.'), - halign=Gtk.Align.CENTER - ) - self.widgets[self.label] = (0, 0, 5, 1) - self.del_btns = [] - self.add_btn = Gtk.Button(_('Add movement'), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) - self.add_btn.connect('clicked', self._clicked_add) - self.widgets[self.add_btn] = (1, 1, 1, 1) - - def _create_field(self): - field = Gtk.ComboBoxText.new_with_entry() - for g in self.MOUSE_GESTURE_NAMES: - field.append(g, g) - CompletionEntry.add_completion_to_entry(field.get_child(), self.MOVE_NAMES) - field.connect('changed', self._on_update) - self.fields.append(field) - self.widgets[field] = (len(self.fields) - 1, 1, 1, 1) - return field - - def _create_del_btn(self): - btn = Gtk.Button(_('Delete'), halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) - self.del_btns.append(btn) - self.widgets[btn] = (len(self.del_btns) - 1, 2, 1, 1) - btn.connect('clicked', self._clicked_del, len(self.del_btns) - 1) - return btn - - def _clicked_add(self, _btn): - self.component.__init__(self.collect_value() + [''], warn=False) - self.show(self.component, editable=True) - self.fields[len(self.component.movements) - 1].grab_focus() - - def _clicked_del(self, _btn, pos): - v = self.collect_value() - v.pop(pos) - self.component.__init__(v, warn=False) - self.show(self.component, editable=True) - self._on_update_callback() - - def _on_update(self, *args): - super()._on_update(*args) - for i, f in enumerate(self.fields): - if f.get_visible(): - icon = 'dialog-warning' if i < len(self.component.movements - ) and self.component.movements[i] not in self.MOVE_NAMES else '' - f.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) - - def show(self, component, editable): - n = len(component.movements) - while len(self.fields) < n: - self._create_field() - self._create_del_btn() - self.widgets[self.add_btn] = (n + 1, 1, 1, 1) - super().show(component, editable) - for i in range(n): - field = self.fields[i] - with self.ignore_changes(): - field.get_child().set_text(component.movements[i]) - field.set_size_request(int(0.3 * self.panel.get_toplevel().get_size()[0]), 0) - field.show_all() - self.del_btns[i].show() - for i in range(n, len(self.fields)): - self.fields[i].hide() - self.del_btns[i].hide() - self.add_btn.set_valign(Gtk.Align.END if n >= 1 else Gtk.Align.CENTER) - - def collect_value(self): - return [f.get_active_text().strip() for f in self.fields if f.get_visible()] - - @classmethod - def left_label(cls, component): - return _('Mouse Gesture') - - @classmethod - def right_label(cls, component): - if len(component.movements) == 0: - return 'No-op' - else: - return ' -> '.join(component.movements) + return _("Not") class ActionUI(RuleComponentUI): - - CLASS = _DIV.Action + CLASS = diversion.Action @classmethod def icon_name(cls): - return 'go-next' - - -class KeyPressUI(ActionUI): - - CLASS = _DIV.KeyPress - KEY_NAMES = [k[3:] if k.startswith('XK_') else k for k, v in _XK_KEYS.items() if isinstance(v, int)] - - def create_widgets(self): - self.widgets = {} - self.fields = [] - self.label = Gtk.Label( - _('Simulate a chorded key click or depress or release.\nOn Wayland requires write access to /dev/uinput.'), - halign=Gtk.Align.CENTER - ) - self.widgets[self.label] = (0, 0, 5, 1) - self.del_btns = [] - self.add_btn = Gtk.Button(_('Add key'), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) - self.add_btn.connect('clicked', self._clicked_add) - self.widgets[self.add_btn] = (1, 1, 1, 1) - self.action_clicked_radio = Gtk.RadioButton.new_with_label_from_widget(None, _('Click')) - self.action_clicked_radio.connect('toggled', self._on_update, CLICK) - self.widgets[self.action_clicked_radio] = (0, 3, 1, 1) - self.action_pressed_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_clicked_radio, _('Depress')) - self.action_pressed_radio.connect('toggled', self._on_update, DEPRESS) - self.widgets[self.action_pressed_radio] = (1, 3, 1, 1) - self.action_released_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_pressed_radio, _('Release')) - self.action_released_radio.connect('toggled', self._on_update, RELEASE) - self.widgets[self.action_released_radio] = (2, 3, 1, 1) - - def _create_field(self): - field = CompletionEntry(self.KEY_NAMES, halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) - field.connect('changed', self._on_update) - self.fields.append(field) - self.widgets[field] = (len(self.fields) - 1, 1, 1, 1) - return field - - def _create_del_btn(self): - btn = Gtk.Button(_('Delete'), halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) - self.del_btns.append(btn) - self.widgets[btn] = (len(self.del_btns) - 1, 2, 1, 1) - btn.connect('clicked', self._clicked_del, len(self.del_btns) - 1) - return btn - - def _clicked_add(self, _btn): - keys, action = self.component.regularize_args(self.collect_value()) - self.component.__init__([keys + [''], action], warn=False) - self.show(self.component, editable=True) - self.fields[len(self.component.key_names) - 1].grab_focus() - - def _clicked_del(self, _btn, pos): - keys, action = self.component.regularize_args(self.collect_value()) - keys.pop(pos) - self.component.__init__([keys, action], warn=False) - self.show(self.component, editable=True) - self._on_update_callback() - - def _on_update(self, *args): - super()._on_update(*args) - for i, f in enumerate(self.fields): - if f.get_visible(): - icon = 'dialog-warning' if i < len(self.component.key_names - ) and self.component.key_names[i] not in self.KEY_NAMES else '' - f.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) - - def show(self, component, editable=True): - n = len(component.key_names) - while len(self.fields) < n: - self._create_field() - self._create_del_btn() - - -# self.widgets[self.add_btn] = (n + 1, 0, 1, 1) - self.widgets[self.add_btn] = (n, 1, 1, 1) - super().show(component, editable) - for i in range(n): - field = self.fields[i] - with self.ignore_changes(): - field.set_text(component.key_names[i]) - field.set_size_request(int(0.3 * self.panel.get_toplevel().get_size()[0]), 0) - field.show_all() - self.del_btns[i].show() - for i in range(n, len(self.fields)): - self.fields[i].hide() - self.del_btns[i].hide() - - def collect_value(self): - action = CLICK if self.action_clicked_radio.get_active() else \ - DEPRESS if self.action_pressed_radio.get_active() else RELEASE - return [[f.get_text().strip() for f in self.fields if f.get_visible()], action] - - @classmethod - def left_label(cls, component): - return _('Key press') - - @classmethod - def right_label(cls, component): - return ' + '.join(component.key_names) + (' (' + component.action + ')' if component.action != CLICK else '') - - -class MouseScrollUI(ActionUI): - - CLASS = _DIV.MouseScroll - MIN_VALUE = -2000 - MAX_VALUE = 2000 - - def create_widgets(self): - self.widgets = {} - self.label = Gtk.Label( - _('Simulate a mouse scroll.\nOn Wayland requires write access to /dev/uinput.'), halign=Gtk.Align.CENTER - ) - self.widgets[self.label] = (0, 0, 4, 1) - self.label_x = Gtk.Label(label='x', halign=Gtk.Align.END, valign=Gtk.Align.END, hexpand=True) - self.label_y = Gtk.Label(label='y', halign=Gtk.Align.END, valign=Gtk.Align.END, hexpand=True) - self.field_x = Gtk.SpinButton.new_with_range(self.MIN_VALUE, self.MAX_VALUE, 1) - self.field_y = Gtk.SpinButton.new_with_range(self.MIN_VALUE, self.MAX_VALUE, 1) - for f in [self.field_x, self.field_y]: - f.set_halign(Gtk.Align.CENTER) - f.set_valign(Gtk.Align.START) - self.field_x.connect('changed', self._on_update) - self.field_y.connect('changed', self._on_update) - self.widgets[self.label_x] = (0, 1, 1, 1) - self.widgets[self.field_x] = (1, 1, 1, 1) - self.widgets[self.label_y] = (2, 1, 1, 1) - self.widgets[self.field_y] = (3, 1, 1, 1) - - @classmethod - def __parse(cls, v): - try: - # allow floats, but round them down - return int(float(v)) - except (TypeError, ValueError): - return 0 - - def show(self, component, editable): - super().show(component, editable) - with self.ignore_changes(): - self.field_x.set_value(self.__parse(component.amounts[0] if len(component.amounts) >= 1 else 0)) - self.field_y.set_value(self.__parse(component.amounts[1] if len(component.amounts) >= 2 else 0)) - - def collect_value(self): - return [int(self.field_x.get_value()), int(self.field_y.get_value())] - - @classmethod - def left_label(cls, component): - return _('Mouse scroll') - - @classmethod - def right_label(cls, component): - x = y = 0 - x = cls.__parse(component.amounts[0] if len(component.amounts) >= 1 else 0) - y = cls.__parse(component.amounts[1] if len(component.amounts) >= 2 else 0) - return f'{x}, {y}' - - -class MouseClickUI(ActionUI): - - CLASS = _DIV.MouseClick - MIN_VALUE = 1 - MAX_VALUE = 9 - BUTTONS = list(_buttons.keys()) - ACTIONS = [CLICK, DEPRESS, RELEASE] - - def create_widgets(self): - self.widgets = {} - self.label = Gtk.Label( - _('Simulate a mouse click.\nOn Wayland requires write access to /dev/uinput.'), halign=Gtk.Align.CENTER - ) - self.widgets[self.label] = (0, 0, 4, 1) - self.label_b = Gtk.Label(label=_('Button'), halign=Gtk.Align.END, valign=Gtk.Align.CENTER, hexpand=True) - self.label_c = Gtk.Label(label=_('Count and Action'), halign=Gtk.Align.END, valign=Gtk.Align.CENTER, hexpand=True) - self.field_b = CompletionEntry(self.BUTTONS) - self.field_c = Gtk.SpinButton.new_with_range(self.MIN_VALUE, self.MAX_VALUE, 1) - self.field_d = CompletionEntry(self.ACTIONS) - for f in [self.field_b, self.field_c]: - f.set_halign(Gtk.Align.CENTER) - f.set_valign(Gtk.Align.START) - self.field_b.connect('changed', self._on_update) - self.field_c.connect('changed', self._on_update) - self.field_d.connect('changed', self._on_update) - self.widgets[self.label_b] = (0, 1, 1, 1) - self.widgets[self.field_b] = (1, 1, 1, 1) - self.widgets[self.label_c] = (2, 1, 1, 1) - self.widgets[self.field_c] = (3, 1, 1, 1) - self.widgets[self.field_d] = (4, 1, 1, 1) - - def show(self, component, editable): - super().show(component, editable) - with self.ignore_changes(): - self.field_b.set_text(component.button) - if isinstance(component.count, int): - self.field_c.set_value(component.count) - self.field_d.set_text(CLICK) - else: - self.field_c.set_value(1) - self.field_d.set_text(component.count) - - def collect_value(self): - b, c, d = self.field_b.get_text(), int(self.field_c.get_value()), self.field_d.get_text() - if b not in self.BUTTONS: - b = 'unknown' - if d != CLICK: - c = d - return [b, c] - - @classmethod - def left_label(cls, component): - return _('Mouse click') - - @classmethod - def right_label(cls, component): - return f'{component.button} ({"x" if isinstance(component.count, int) else ""}{component.count})' - - -class ExecuteUI(ActionUI): - - CLASS = _DIV.Execute - - def create_widgets(self): - self.widgets = {} - self.label = Gtk.Label(_('Execute a command with arguments.'), halign=Gtk.Align.CENTER) - self.widgets[self.label] = (0, 0, 5, 1) - self.fields = [] - self.add_btn = Gtk.Button(_('Add argument'), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) - self.del_btns = [] - self.add_btn.connect('clicked', self._clicked_add) - self.widgets[self.add_btn] = (1, 1, 1, 1) - - def _create_field(self): - field = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) - field.set_size_request(150, 0) - field.connect('changed', self._on_update) - self.fields.append(field) - self.widgets[field] = (len(self.fields) - 1, 1, 1, 1) - return field - - def _create_del_btn(self): - btn = Gtk.Button(_('Delete'), halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) - btn.set_size_request(150, 0) - self.del_btns.append(btn) - self.widgets[btn] = (len(self.del_btns) - 1, 2, 1, 1) - btn.connect('clicked', self._clicked_del, len(self.del_btns) - 1) - return btn - - def _clicked_add(self, *_args): - self.component.__init__(self.collect_value() + [''], warn=False) - self.show(self.component, editable=True) - self.fields[len(self.component.args) - 1].grab_focus() - - def _clicked_del(self, _btn, pos): - v = self.collect_value() - v.pop(pos) - self.component.__init__(v, warn=False) - self.show(self.component, editable=True) - self._on_update_callback() - - def show(self, component, editable): - n = len(component.args) - while len(self.fields) < n: - self._create_field() - self._create_del_btn() - for i in range(n): - field = self.fields[i] - with self.ignore_changes(): - field.set_text(component.args[i]) - self.del_btns[i].show() - self.widgets[self.add_btn] = (n + 1, 1, 1, 1) - super().show(component, editable) - for i in range(n, len(self.fields)): - self.fields[i].hide() - self.del_btns[i].hide() - self.add_btn.set_valign(Gtk.Align.END if n >= 1 else Gtk.Align.CENTER) - - def collect_value(self): - return [f.get_text() for f in self.fields if f.get_visible()] - - @classmethod - def left_label(cls, component): - return _('Execute') - - @classmethod - def right_label(cls, component): - return ' '.join([shlex_quote(a) for a in component.args]) + return "go-next" def _from_named_ints(v, all_values): @@ -2041,28 +1223,35 @@ def _from_named_ints(v, all_values): return v -class SetValueControl(Gtk.HBox): +class SetValueControlKinds(Enum): + TOGGLE = "toggle" + RANGE = "range" + RANGE_WITH_KEY = "range_with_key" + CHOICE = "choice" + +class SetValueControl(Gtk.HBox): def __init__(self, on_change, *args, accept_toggle=True, **kwargs): super().__init__(*args, **kwargs) self.on_change = on_change - self.toggle_widget = SmartComboBox([ - *([('Toggle', _('Toggle'), '~')] if accept_toggle else []), (True, _('True'), 'True', 'yes', 'on', 't', 'y'), - (False, _('False'), 'False', 'no', 'off', 'f', 'n') - ], - case_insensitive=True) - self.toggle_widget.connect('changed', self._changed) + self.toggle_widget = SmartComboBox( + [ + *([("Toggle", _("Toggle"), "~")] if accept_toggle else []), + (True, _("True"), "True", "yes", "on", "t", "y"), + (False, _("False"), "False", "no", "off", "f", "n"), + ], + case_insensitive=True, + ) + self.toggle_widget.connect(GtkSignal.CHANGED.value, self._changed) self.range_widget = Gtk.SpinButton.new_with_range(0, 0xFFFF, 1) - self.range_widget.connect('value-changed', self._changed) - self.choice_widget = SmartComboBox([], - completion=True, - has_entry=True, - case_insensitive=True, - replace_with_default_name=True) - self.choice_widget.connect('changed', self._changed) + self.range_widget.connect(GtkSignal.VALUE_CHANGED.value, self._changed) + self.choice_widget = SmartComboBox( + [], completion=True, has_entry=True, case_insensitive=True, replace_with_default_name=True + ) + self.choice_widget.connect(GtkSignal.CHANGED.value, self._changed) self.sub_key_widget = SmartComboBox([]) - self.sub_key_widget.connect('changed', self._changed) - self.unsupported_label = Gtk.Label(_('Unsupported setting')) + self.sub_key_widget.connect(GtkSignal.CHANGED.value, self._changed) + self.unsupported_label = Gtk.Label(label=_("Unsupported setting")) self.pack_start(self.sub_key_widget, False, False, 0) self.sub_key_widget.set_hexpand(False) self.sub_key_widget.set_size_request(120, 0) @@ -2071,20 +1260,23 @@ class SetValueControl(Gtk.HBox): self.pack_end(w, True, True, 0) w.hide() self.unsupp_value = None - self.current_kind = None + self.current_kind: Optional[SetValueControlKinds] = None self.sub_key_range_items = None def _changed(self, widget, *args): if widget.get_visible(): value = self.get_value() - if self.current_kind == 'choice': + if self.current_kind == SetValueControlKinds.CHOICE: value = widget.get_value() - icon = 'dialog-warning' if widget._allowed_values and (value not in widget._allowed_values) else '' + icon = "dialog-warning" if widget._allowed_values and (value not in widget._allowed_values) else "" widget.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) - elif self.current_kind == 'range_with_key' and widget == self.sub_key_widget: + elif self.current_kind == SetValueControlKinds.RANGE_WITH_KEY and widget == self.sub_key_widget: key = self.sub_key_widget.get_value() - selected_item = next((item for item in self.sub_key_range_items - if key == item.id), None) if self.sub_key_range_items else None + selected_item = ( + next((item for item in self.sub_key_range_items if key == item.id), None) + if self.sub_key_range_items + else None + ) (minimum, maximum) = (selected_item.minimum, selected_item.maximum) if selected_item else (0, 0xFFFF) self.range_widget.set_range(minimum, maximum) self.on_change(value) @@ -2094,63 +1286,64 @@ class SetValueControl(Gtk.HBox): w.hide() def get_value(self): - if self.current_kind == 'toggle': + if self.current_kind == SetValueControlKinds.TOGGLE: return self.toggle_widget.get_value() - if self.current_kind == 'range': + if self.current_kind == SetValueControlKinds.RANGE: return int(self.range_widget.get_value()) - if self.current_kind == 'range_with_key': + if self.current_kind == SetValueControlKinds.RANGE_WITH_KEY: return {self.sub_key_widget.get_value(): int(self.range_widget.get_value())} - if self.current_kind == 'choice': + if self.current_kind == SetValueControlKinds.CHOICE: return self.choice_widget.get_value() return self.unsupp_value def set_value(self, value): - if self.current_kind == 'toggle': - self.toggle_widget.set_value(value if value is not None else '') - elif self.current_kind == 'range': + if self.current_kind == SetValueControlKinds.TOGGLE: + self.toggle_widget.set_value(value if value is not None else "") + elif self.current_kind == SetValueControlKinds.RANGE: minimum, maximum = self.range_widget.get_range() try: v = round(float(value)) except (ValueError, TypeError): v = minimum self.range_widget.set_value(max(minimum, min(maximum, v))) - elif self.current_kind == 'range_with_key': + elif self.current_kind == SetValueControlKinds.RANGE_WITH_KEY: if not (isinstance(value, dict) and len(value) == 1): value = {None: None} key = next(iter(value.keys())) - selected_item = next((item for item in self.sub_key_range_items - if key == item.id), None) if self.sub_key_range_items else None + selected_item = ( + next((item for item in self.sub_key_range_items if key == item.id), None) if self.sub_key_range_items else None + ) (minimum, maximum) = (selected_item.minimum, selected_item.maximum) if selected_item else (0, 0xFFFF) try: v = round(float(next(iter(value.values())))) except (ValueError, TypeError): v = minimum - self.sub_key_widget.set_value(key or '') + self.sub_key_widget.set_value(key or "") self.range_widget.set_value(max(minimum, min(maximum, v))) - elif self.current_kind == 'choice': + elif self.current_kind == SetValueControlKinds.CHOICE: self.choice_widget.set_value(value) else: self.unsupp_value = value - if value is None or value == '': # reset all + if value is None or value == "": # reset all self.range_widget.set_range(0x0000, 0xFFFF) self.range_widget.set_value(0) - self.toggle_widget.set_active_id('') - self.sub_key_widget.set_value('') - self.choice_widget.set_value('') + self.toggle_widget.set_active_id("") + self.sub_key_widget.set_value("") + self.choice_widget.set_value("") def make_toggle(self): - self.current_kind = 'toggle' + self.current_kind = SetValueControlKinds.TOGGLE self._hide_all() self.toggle_widget.show() def make_range(self, minimum, maximum): - self.current_kind = 'range' + self.current_kind = SetValueControlKinds.RANGE self._hide_all() self.range_widget.set_range(minimum, maximum) self.range_widget.show() def make_range_with_key(self, items, labels=None): - self.current_kind = 'range_with_key' + self.current_kind = SetValueControlKinds.RANGE_WITH_KEY self._hide_all() self.sub_key_range_items = items or None if not labels: @@ -2163,7 +1356,7 @@ class SetValueControl(Gtk.HBox): def make_choice(self, values, extra=None): # if extra is not in values, it is ignored - self.current_kind = 'choice' + self.current_kind = SetValueControlKinds.CHOICE self._hide_all() sort_key = int if all((v == extra or str(v).isdigit()) for v in values) else str if extra is not None and extra in values: @@ -2180,54 +1373,35 @@ class SetValueControl(Gtk.HBox): self.unsupported_label.show() -def _all_settings(): - settings = {} - for s in sorted(_SETTINGS, key=lambda setting: setting.label): - - if s.name not in settings: - settings[s.name] = [s] - else: - prev_setting = settings[s.name][0] - prev_kind = prev_setting.validator_class.kind - if prev_kind != s.validator_class.kind: - _log.warning( - 'ignoring setting {} - same name of {}, but different kind ({} != {})'.format( - s.__name__, prev_setting.__name__, prev_kind, s.validator_class.kind - ) - ) - continue - settings[s.name].append(s) - return settings - - class _DeviceUI: - label_text = '' + label_text = "" def show(self, component, editable): super().show(component, editable) with self.ignore_changes(): same = not component.devID device = _all_devices[component.devID] - self.device_field.set_value(device.id if device else '' if same else component.devID or '') + self.device_field.set_value(device.id if device else "" if same else component.devID or "") def create_widgets(self): self.widgets = {} self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) self.label.set_text(self.label_text) self.widgets[self.label] = (0, 0, 5, 1) - lbl = Gtk.Label(_('Device'), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) + lbl = Gtk.Label(label=_("Device"), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) self.widgets[lbl] = (0, 1, 1, 1) - self.device_field = SmartComboBox([], - completion=True, - has_entry=True, - blank=_('Originating device'), - case_insensitive=True, - replace_with_default_name=True) - self.device_field.set_value('') + self.device_field = SmartComboBox( + [], + completion=True, + has_entry=True, + blank=_("Originating device"), + case_insensitive=True, + replace_with_default_name=True, + ) + self.device_field.set_value("") self.device_field.set_valign(Gtk.Align.CENTER) self.device_field.set_size_request(400, 0) - # self.device_field.connect('changed', self._changed_device) - self.device_field.connect('changed', self._on_update) + self.device_field.connect(GtkSignal.CHANGED.value, self._on_update) self.widgets[self.device_field] = (1, 1, 1, 1) def update_devices(self): @@ -2239,7 +1413,7 @@ class _DeviceUI: def collect_value(self): device_str = self.device_field.get_value() - same = device_str in ['', _('Originating device')] + same = device_str in ["", _("Originating device")] device = None if same else _all_devices[device_str] device_value = device.id if device else None if same else device_str return device_value @@ -2251,37 +1425,34 @@ class _DeviceUI: class ActiveUI(_DeviceUI, ConditionUI): - - CLASS = _DIV.Active - label_text = _('Device is active and its settings can be changed.') + CLASS = diversion.Active + label_text = _("Device is active and its settings can be changed.") @classmethod def left_label(cls, component): - return _('Active') + return _("Active") class DeviceUI(_DeviceUI, ConditionUI): - - CLASS = _DIV.Device - label_text = _('Device that originated the current notification.') + CLASS = diversion.Device + label_text = _("Device that originated the current notification.") @classmethod def left_label(cls, component): - return _('Device') + return _("Device") class HostUI(ConditionUI): - - CLASS = _DIV.Host + CLASS = diversion.Host def create_widgets(self): self.widgets = {} self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) - self.label.set_text(_('Name of host computer.')) + self.label.set_text(_("Name of host computer.")) self.widgets[self.label] = (0, 0, 1, 1) self.field = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) self.field.set_size_request(600, 0) - self.field.connect('changed', self._on_update) + self.field.connect(GtkSignal.CHANGED.value, self._on_update) self.widgets[self.field] = (0, 1, 1, 1) def show(self, component, editable): @@ -2294,7 +1465,7 @@ class HostUI(ConditionUI): @classmethod def left_label(cls, component): - return _('Host') + return _("Host") @classmethod def right_label(cls, component): @@ -2302,49 +1473,53 @@ class HostUI(ConditionUI): class _SettingWithValueUI: - - ALL_SETTINGS = _all_settings() - - MULTIPLE = [_SKIND.multiple_toggle, _SKIND.map_choice, _SKIND.multiple_range] - + MULTIPLE = [Kind.MULTIPLE_TOGGLE, Kind.MAP_CHOICE, Kind.MULTIPLE_RANGE] ACCEPT_TOGGLE = True - label_text = '' + label_text = "" def create_widgets(self): - self.widgets = {} - self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True) self.label.set_text(self.label_text) self.widgets[self.label] = (0, 0, 5, 1) m = 20 - lbl = Gtk.Label(_('Device'), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, margin_top=m) + lbl = Gtk.Label(label=_("Device"), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, margin_top=m) self.widgets[lbl] = (0, 1, 1, 1) - self.device_field = SmartComboBox([], - completion=True, - has_entry=True, - blank=_('Originating device'), - case_insensitive=True, - replace_with_default_name=True) - self.device_field.set_value('') + self.device_field = SmartComboBox( + [], + completion=True, + has_entry=True, + blank=_("Originating device"), + case_insensitive=True, + replace_with_default_name=True, + ) + self.device_field.set_value("") self.device_field.set_valign(Gtk.Align.CENTER) self.device_field.set_size_request(400, 0) self.device_field.set_margin_top(m) - self.device_field.connect('changed', self._changed_device) - self.device_field.connect('changed', self._on_update) + self.device_field.connect(GtkSignal.CHANGED.value, self._changed_device) + self.device_field.connect(GtkSignal.CHANGED.value, self._on_update) self.widgets[self.device_field] = (1, 1, 1, 1) - lbl = Gtk.Label(_('Setting'), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, vexpand=False) + lbl = Gtk.Label( + label=_("Setting"), + halign=Gtk.Align.CENTER, + valign=Gtk.Align.CENTER, + hexpand=True, + vexpand=False, + ) self.widgets[lbl] = (0, 2, 1, 1) - self.setting_field = SmartComboBox([(s[0].name, s[0].label) for s in self.ALL_SETTINGS.values()]) + self.setting_field = SmartComboBox([(s[0].name, s[0].label) for s in ALL_SETTINGS.values()]) self.setting_field.set_valign(Gtk.Align.CENTER) - self.setting_field.connect('changed', self._changed_setting) - self.setting_field.connect('changed', self._on_update) + self.setting_field.connect(GtkSignal.CHANGED.value, self._changed_setting) + self.setting_field.connect(GtkSignal.CHANGED.value, self._on_update) self.widgets[self.setting_field] = (1, 2, 1, 1) - self.value_lbl = Gtk.Label(_('Value'), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, vexpand=False) + self.value_lbl = Gtk.Label( + label=_("Value"), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, vexpand=False + ) self.widgets[self.value_lbl] = (2, 2, 1, 1) self.value_field = SetValueControl(self._on_update, accept_toggle=self.ACCEPT_TOGGLE) self.value_field.set_valign(Gtk.Align.CENTER) @@ -2352,20 +1527,18 @@ class _SettingWithValueUI: self.widgets[self.value_field] = (3, 2, 1, 1) self.key_lbl = Gtk.Label( - _('Item'), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, vexpand=False, margin_top=m + label=_("Item"), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True, vexpand=False, margin_top=m ) self.key_lbl.hide() self.widgets[self.key_lbl] = (2, 1, 1, 1) - self.key_field = SmartComboBox([], - has_entry=True, - completion=True, - case_insensitive=True, - replace_with_default_name=True) + self.key_field = SmartComboBox( + [], has_entry=True, completion=True, case_insensitive=True, replace_with_default_name=True + ) self.key_field.set_margin_top(m) self.key_field.hide() self.key_field.set_valign(Gtk.Align.CENTER) - self.key_field.connect('changed', self._changed_key) - self.key_field.connect('changed', self._on_update) + self.key_field.connect(GtkSignal.CHANGED.value, self._changed_key) + self.key_field.connect(GtkSignal.CHANGED.value, self._on_update) self.widgets[self.key_field] = (3, 1, 1, 1) @classmethod @@ -2380,18 +1553,18 @@ class _SettingWithValueUI: (including the extra value if it exists) and the second element is the extra value to be pinned to the start of the list (or `None` if there is no extra value). """ - if isinstance(setting, _Setting): + if isinstance(setting, Setting): setting = type(setting) - if isinstance(setting, type) and issubclass(setting, _Setting): + if isinstance(setting, type) and issubclass(setting, Setting): choices = UnsortedNamedInts() - universe = getattr(setting, 'choices_universe', None) + universe = getattr(setting, "choices_universe", None) if universe: choices |= universe - extra = getattr(setting, 'choices_extra', None) + extra = getattr(setting, "choices_extra", None) if extra is not None: choices |= NamedInts(**{str(extra): int(extra)}) return choices, extra - settings = cls.ALL_SETTINGS.get(setting, []) + settings = ALL_SETTINGS.get(setting, []) choices = UnsortedNamedInts() extra = None for s in settings: @@ -2407,14 +1580,14 @@ class _SettingWithValueUI: setting = device.settings.get(setting_name, None) settings = [type(setting)] if setting else None else: - settings = cls.ALL_SETTINGS.get(setting_name, [None]) + settings = ALL_SETTINGS.get(setting_name, [None]) setting = settings[0] # if settings have the same name, use the first one to get the basic data val_class = setting.validator_class if setting else None kind = val_class.kind if val_class else None if kind in cls.MULTIPLE: keys = UnsortedNamedInts() for s in settings: - universe = getattr(s, 'keys_universe' if kind == _SKIND.map_choice else 'choices_universe', None) + universe = getattr(s, "keys_universe" if kind == Kind.MAP_CHOICE else "choices_universe", None) if universe: keys |= universe # only one key per number is used @@ -2469,11 +1642,15 @@ class _SettingWithValueUI: self.key_lbl.set_visible(multiple) if not multiple: return - labels = getattr(setting, '_labels', {}) + labels = getattr(setting, "_labels", {}) def item(k): lbl = labels.get(k, None) - return (k, lbl[0] if lbl and isinstance(lbl, tuple) and lbl[0] else str(k)) + if lbl and isinstance(lbl, tuple) and lbl[0]: + label = lbl[0] + else: + label = str(k) + return k, label with self.ignore_changes(): self.key_field.set_all_values(sorted(map(item, keys), key=lambda k: k[1])) @@ -2482,12 +1659,12 @@ class _SettingWithValueUI: supported_keys = None if device_setting: val = device_setting._validator - if device_setting.kind == _SKIND.multiple_toggle: + if device_setting.kind == Kind.MULTIPLE_TOGGLE: supported_keys = val.get_options() or None - elif device_setting.kind == _SKIND.map_choice: + elif device_setting.kind == Kind.MAP_CHOICE: choices = val.choices or None supported_keys = choices.keys() if choices else None - elif device_setting.kind == _SKIND.multiple_range: + elif device_setting.kind == Kind.MULTIPLE_RANGE: supported_keys = val.keys self.key_field.show_only(supported_keys, include_new=True) self._update_validation() @@ -2496,27 +1673,36 @@ class _SettingWithValueUI: setting, val_class, kind, keys = self._setting_attributes(setting_name, device) ds = device.settings if device else {} device_setting = ds.get(setting_name, None) - if kind in (_SKIND.toggle, _SKIND.multiple_toggle): + if kind in (Kind.TOGGLE, Kind.MULTIPLE_TOGGLE): self.value_field.make_toggle() - elif kind in (_SKIND.choice, _SKIND.map_choice): + elif kind in (Kind.CHOICE, Kind.MAP_CHOICE): + # Open-value-space MAP_CHOICE settings (per-key RGB) have a Range + # rather than a NamedInts value list — there's no meaningful value + # picker to render in the rule editor, so fall through to unsupported. + if kind == Kind.MAP_CHOICE and device_setting: + val = device_setting._validator + choices = getattr(val, "choices", None) + if isinstance(choices, dict) and any(isinstance(v, Range) for v in choices.values()): + self.value_field.make_unsupported() + return all_values, extra = self._all_choices(device_setting or setting_name) self.value_field.make_choice(all_values, extra) supported_values = None if device_setting: val = device_setting._validator - choices = getattr(val, 'choices', None) or None - if kind == _SKIND.choice: + choices = getattr(val, "choices", None) or None + if kind == Kind.CHOICE: supported_values = choices - elif kind == _SKIND.map_choice and isinstance(choices, dict): + elif kind == Kind.MAP_CHOICE and isinstance(choices, dict): supported_values = choices.get(key, None) or None self.value_field.choice_widget.show_only(supported_values, include_new=True) self._update_validation() - elif kind == _SKIND.range: + elif kind == Kind.RANGE: self.value_field.make_range(val_class.min_value, val_class.max_value) - elif kind == _SKIND.multiple_range: + elif kind == Kind.MULTIPLE_RANGE: self.value_field.make_range_with_key( - getattr(setting, 'sub_items_universe', {}).get(key, {}) if setting else {}, - getattr(setting, '_labels_sub', None) if setting else None + getattr(setting, "sub_items_universe", {}).get(key, {}) if setting else {}, + getattr(setting, "_labels_sub", None) if setting else None, ) else: self.value_field.make_unsupported() @@ -2529,22 +1715,24 @@ class _SettingWithValueUI: device_str = self.device_field.get_value() device = _all_devices[device_str] if device_str and not device: - icon = 'dialog-question' if len(device_str) == 8 and all( - c in string.hexdigits for c in device_str - ) else 'dialog-warning' + icon = ( + "dialog-question" + if len(device_str) == 8 and all(c in string.hexdigits for c in device_str) + else "dialog-warning" + ) else: - icon = '' + icon = "" self.device_field.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) setting_name = self.setting_field.get_value() setting, val_class, kind, keys = self._setting_attributes(setting_name, device) multiple = kind in self.MULTIPLE if multiple: key = self.key_field.get_value(invalid_as_str=False, accept_hidden=False) - icon = 'dialog-warning' if key is None else '' + icon = "dialog-warning" if key is None else "" self.key_field.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) - if kind in (_SKIND.choice, _SKIND.map_choice): + if kind in (Kind.CHOICE, Kind.MAP_CHOICE): value = self.value_field.choice_widget.get_value(invalid_as_str=False, accept_hidden=False) - icon = 'dialog-warning' if value is None else '' + icon = "dialog-warning" if value is None else "" self.value_field.choice_widget.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) def show(self, component, editable): @@ -2553,21 +1741,21 @@ class _SettingWithValueUI: device_str = next(a, None) same = not device_str device = _all_devices[device_str] - self.device_field.set_value(device.id if device else '' if same else device_str or '') - setting_name = next(a, '') + self.device_field.set_value(device.id if device else "" if same else device_str or "") + setting_name = next(a, "") setting, _v, kind, keys = self._setting_attributes(setting_name, device) - self.setting_field.set_value(setting.name if setting else '') + self.setting_field.set_value(setting.name if setting else "") self._changed_setting() key = None if kind in self.MULTIPLE or kind is None and len(self.component.args) > 3: - key = _from_named_ints(next(a, ''), keys) + key = _from_named_ints(next(a, ""), keys) self.key_field.set_value(key) - self.value_field.set_value(next(a, '')) + self.value_field.set_value(next(a, "")) self._update_validation() def collect_value(self): device_str = self.device_field.get_value() - same = device_str in ['', _('Originating device')] + same = device_str in ["", _("Originating device")] device = None if same else _all_devices[device_str] device_value = device.id if device else None if same else device_str setting_name = self.setting_field.get_value() @@ -2585,49 +1773,49 @@ class _SettingWithValueUI: a = iter(component.args) device_str = next(a, None) device = None if not device_str else _all_devices[device_str] - device_disp = _('Originating device') if not device_str else device.display_name if device else shlex_quote(device_str) + device_disp = _("Originating device") if not device_str else device.display_name if device else shlex_quote(device_str) setting_name = next(a, None) setting, val_class, kind, keys = cls._setting_attributes(setting_name, device) device_setting = (device.settings if device else {}).get(setting_name, None) disp = [setting.label or setting.name if setting else setting_name] + key = None if kind in cls.MULTIPLE: key = next(a, None) key = _from_named_ints(key, keys) if keys else key - key_label = getattr(setting, '_labels', {}).get(key, [None])[0] if setting else None + key_label = getattr(setting, "_labels", {}).get(key, [None])[0] if setting else None disp.append(key_label or key) value = next(a, None) - if setting and (kind in (_SKIND.choice, _SKIND.map_choice)): + if setting and (kind in (Kind.CHOICE, Kind.MAP_CHOICE)): all_values = cls._all_choices(setting or setting_name)[0] supported_values = None if device_setting: val = device_setting._validator - choices = getattr(val, 'choices', None) or None - if kind == _SKIND.choice: + choices = getattr(val, "choices", None) or None + if kind == Kind.CHOICE: supported_values = choices - elif kind == _SKIND.map_choice and isinstance(choices, dict): + elif kind == Kind.MAP_CHOICE and isinstance(choices, dict): supported_values = choices.get(key, None) or None if supported_values and isinstance(supported_values, NamedInts): value = supported_values[value] if not supported_values and all_values and isinstance(all_values, NamedInts): value = all_values[value] disp.append(value) - elif kind == _SKIND.multiple_range and isinstance(value, dict) and len(value) == 1: + elif kind == Kind.MULTIPLE_RANGE and isinstance(value, dict) and len(value) == 1: k, v = next(iter(value.items())) - k = (getattr(setting, '_labels_sub', {}).get(k, (None, ))[0] if setting else None) or k - disp.append(f'{k}={v}') - elif kind in (_SKIND.toggle, _SKIND.multiple_toggle): + k = (getattr(setting, "_labels_sub", {}).get(k, (None,))[0] if setting else None) or k + disp.append(f"{k}={v}") + elif kind in (Kind.TOGGLE, Kind.MULTIPLE_TOGGLE): disp.append(_(str(value))) else: disp.append(value) - return device_disp + ' ' + ' '.join(map(lambda s: shlex_quote(str(s)), [*disp, *a])) + return device_disp + " " + " ".join(map(lambda s: shlex_quote(str(s)), [*disp, *a])) class SetUI(_SettingWithValueUI, ActionUI): - - CLASS = _DIV.Set + CLASS = diversion.Set ACCEPT_TOGGLE = True - label_text = _('Change setting on device') + label_text = _("Change setting on device") def show(self, component, editable): ActionUI.show(self, component, editable) @@ -2640,11 +1828,10 @@ class SetUI(_SettingWithValueUI, ActionUI): class SettingUI(_SettingWithValueUI, ConditionUI): - - CLASS = _DIV.Setting + CLASS = diversion.Setting ACCEPT_TOGGLE = False - label_text = _('Setting on device') + label_text = _("Setting on device") def show(self, component, editable): ConditionUI.show(self, component, editable) @@ -2656,32 +1843,32 @@ class SettingUI(_SettingWithValueUI, ConditionUI): _SettingWithValueUI._on_update(self, *_args) -COMPONENT_UI = { - _DIV.Rule: RuleUI, - _DIV.Not: NotUI, - _DIV.Or: OrUI, - _DIV.And: AndUI, - _DIV.Later: LaterUI, - _DIV.Process: ProcessUI, - _DIV.MouseProcess: MouseProcessUI, - _DIV.Active: ActiveUI, - _DIV.Device: DeviceUI, - _DIV.Host: HostUI, - _DIV.Feature: FeatureUI, - _DIV.Report: ReportUI, - _DIV.Modifiers: ModifiersUI, - _DIV.Key: KeyUI, - _DIV.KeyIsDown: KeyIsDownUI, - _DIV.Test: TestUI, - _DIV.TestBytes: TestBytesUI, - _DIV.Setting: SettingUI, - _DIV.MouseGesture: MouseGestureUI, - _DIV.KeyPress: KeyPressUI, - _DIV.MouseScroll: MouseScrollUI, - _DIV.MouseClick: MouseClickUI, - _DIV.Execute: ExecuteUI, - _DIV.Set: SetUI, - type(None): RuleComponentUI, # placeholders for empty rule/And/Or +COMPONENT_UI: dict[Any, RuleComponentUI] = { + diversion.Rule: RuleUI, + diversion.Not: NotUI, + diversion.Or: OrUI, + diversion.And: AndUI, + diversion.Later: LaterUI, + diversion.Process: rule_conditions.ProcessUI, + diversion.MouseProcess: rule_conditions.MouseProcessUI, + diversion.Active: ActiveUI, + diversion.Device: DeviceUI, + diversion.Host: HostUI, + diversion.Feature: rule_conditions.FeatureUI, + diversion.Report: rule_conditions.ReportUI, + diversion.Modifiers: rule_conditions.ModifiersUI, + diversion.Key: rule_conditions.KeyUI, + diversion.KeyIsDown: rule_conditions.KeyIsDownUI, + diversion.Test: rule_conditions.TestUI, + diversion.TestBytes: rule_conditions.TestBytesUI, + diversion.Setting: SettingUI, + diversion.MouseGesture: rule_conditions.MouseGestureUI, + diversion.KeyPress: rule_actions.KeyPressUI, + diversion.MouseScroll: rule_actions.MouseScrollUI, + diversion.MouseClick: rule_actions.MouseClickUI, + diversion.Execute: rule_actions.ExecuteUI, + diversion.Set: SetUI, + # type(None): RuleComponentUI, # placeholders for empty rule/And/Or } _all_devices = AllDevicesInfo() @@ -2696,12 +1883,12 @@ def update_devices(): _diversion_dialog.update_devices() -def show_window(model): +def show_window(model: Gtk.TreeStore): GObject.type_register(RuleComponentWrapper) global _diversion_dialog global _dev_model _dev_model = model if _diversion_dialog is None: - _diversion_dialog = DiversionDialog() + _diversion_dialog = DiversionDialog(ActionMenu) update_devices() _diversion_dialog.window.present() diff --git a/lib/solaar/ui/icons.py b/lib/solaar/ui/icons.py index aadf76ae..e5799b1b 100644 --- a/lib/solaar/ui/icons.py +++ b/lib/solaar/ui/icons.py @@ -1,6 +1,5 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -16,64 +15,52 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from logging import DEBUG as _DEBUG -from logging import getLogger - -import solaar.gtk as gtk +import logging from gi.repository import Gtk -_log = getLogger(__name__) -del getLogger +import solaar.gtk as gtk -# -# -# +logger = logging.getLogger(__name__) -_LARGE_SIZE = 64 -Gtk.IconSize.LARGE = Gtk.icon_size_register('large', _LARGE_SIZE, _LARGE_SIZE) -# Gtk.IconSize.XLARGE = Gtk.icon_size_register('x-large', _LARGE_SIZE * 2, _LARGE_SIZE * 2) - -TRAY_INIT = 'solaar-init' -TRAY_OKAY = 'solaar' -TRAY_ATTENTION = 'solaar-attention' +LARGE_SIZE = Gtk.IconSize.DIALOG # was 64 +TRAY_INIT = "solaar-init" +TRAY_OKAY = "solaar" +TRAY_ATTENTION = "solaar-attention" _default_theme = None +_has_level_icons = False +_has_padded_level_icons = False def _init_icon_paths(): - global _default_theme + global _default_theme, _has_level_icons, _has_padded_level_icons if _default_theme: return - _default_theme = Gtk.IconTheme.get_default() - if _log.isEnabledFor(_DEBUG): - _log.debug('icon theme paths: %s', _default_theme.get_search_path()) - - if gtk.battery_icons_style == 'symbolic': + logger.debug("icon theme paths: %s", _default_theme.get_search_path()) + if gtk.battery_icons_style == "symbolic": global TRAY_OKAY TRAY_OKAY = TRAY_INIT # use monochrome tray icon - if not _default_theme.has_icon('battery-good-symbolic'): - _log.warning('failed to detect symbolic icons') - gtk.battery_icons_style = 'regular' - if gtk.battery_icons_style == 'regular': - if not _default_theme.has_icon('battery-good'): - _log.warning('failed to detect icons') - gtk.battery_icons_style = 'solaar' - - -# -# -# + if not _default_theme.has_icon("battery-good-symbolic"): + logger.warning("failed to detect symbolic icons") + gtk.battery_icons_style = "regular" + if gtk.battery_icons_style == "regular": + if not _default_theme.has_icon("battery-good"): + logger.warning("failed to detect icons") + gtk.battery_icons_style = "solaar" + suffix = "-symbolic" if gtk.battery_icons_style == "symbolic" else "" + _has_level_icons = _default_theme.has_icon(f"battery-level-50{suffix}") + _has_padded_level_icons = not _has_level_icons and _default_theme.has_icon(f"battery-050{suffix}") + logger.debug("battery level icons available: %s (padded scheme: %s)", _has_level_icons, _has_padded_level_icons) def battery(level=None, charging=False): icon_name = _battery_icon_name(level, charging) if not _default_theme.has_icon(icon_name): - _log.warning('icon %s not found in current theme', icon_name) + logger.warning("icon %s not found in current theme", icon_name) return TRAY_OKAY # use Solaar icon if battery icon not available - elif _log.isEnabledFor(_DEBUG): - _log.debug('battery icon for %s:%s = %s', level, charging, icon_name) + logger.debug("battery icon for %s:%s = %s", level, charging, icon_name) return icon_name @@ -85,95 +72,96 @@ def _first_res(val, pairs): def _battery_icon_name(level, charging): _init_icon_paths() + suffix = "-symbolic" if gtk.battery_icons_style == "symbolic" else "" if level is None or level < 0: - return 'battery-missing' + ('-symbolic' if gtk.battery_icons_style == 'symbolic' else '') + return f"battery-missing{suffix}" - level_name = _first_res(level, ((90, 'full'), (30, 'good'), (20, 'low'), (5, 'caution'), (0, 'empty'))) - return 'battery-%s%s%s' % ( - level_name, '-charging' if charging else '', '-symbolic' if gtk.battery_icons_style == 'symbolic' else '' - ) + rounded = min(100, max(0, round(level / 10) * 10)) + # Try precise level icons (battery-level-N or battery-0N0 naming scheme) + if _has_level_icons or _has_padded_level_icons: + if charging and rounded == 100: + charging_str = "-charged" + elif charging: + charging_str = "-charging" + else: + charging_str = "" + if _has_level_icons: + icon_name = f"battery-level-{rounded}{charging_str}{suffix}" + else: + icon_name = f"battery-{rounded:03}{charging_str}{suffix}" + if _default_theme.has_icon(icon_name): + logger.debug("battery level icon for %s:%s = %s", level, charging, icon_name) + return icon_name -# -# -# + # Fall back to semantic names + level_name = _first_res(level, ((90, "full"), (60, "good"), (20, "low"), (5, "caution"), (0, "empty"))) + if level_name: + if charging: + charging_str = "-charging" + else: + charging_str = "" + icon_name = f"battery-{level_name}{charging_str}{suffix}" + if _default_theme.has_icon(icon_name): + logger.debug("battery semantic icon for %s:%s = %s", level, charging, icon_name) + return icon_name + + # Last resort: plain battery icon + icon_name = f"battery{suffix}" + logger.debug("battery generic icon for %s:%s = %s", level, charging, icon_name) + return icon_name def lux(level=None): if level is None or level < 0: - return 'light_unknown' - return 'light_%03d' % (20 * ((level + 50) // 100)) + return "light_unknown" + return f"solaar-light_{int(20 * ((level + 50) // 100)):03}" -# -# -# - _ICON_SETS = {} -def device_icon_set(name='_', kind=None): +def device_icon_set(name="_", kind=None): icon_set = _ICON_SETS.get(name) if icon_set is None: - icon_set = Gtk.IconSet.new() - _ICON_SETS[name] = icon_set - - # names of possible icons, in reverse order of likelihood - # the theme will hopefully pick up the most appropriate - names = ['preferences-desktop-peripherals'] + # names of possible icons, in reverse desirability + icon_set = ["preferences-desktop-peripherals"] if kind: - if str(kind) == 'numpad': - names += ('input-keyboard', 'input-dialpad') - elif str(kind) == 'touchpad': - names += ('input-mouse', 'input-tablet') - elif str(kind) == 'trackball': - names += ('input-mouse', ) - elif str(kind) == 'headset': - names += ('audio-headphones', 'audio-headset') - names += ('input-' + str(kind), ) - # names += (name.replace(' ', '-'),) - - source = Gtk.IconSource.new() - for n in names: - source.set_icon_name(n) - icon_set.add_source(source) - icon_set.names = names - + if str(kind) == "numpad": + icon_set += ("input-keyboard", "input-dialpad") + elif str(kind) == "touchpad": + icon_set += ("input-mouse", "input-tablet") + elif str(kind) == "trackball": + icon_set += ("input-mouse",) + elif str(kind) == "headset": + icon_set += ("audio-headphones", "audio-headset") + icon_set += (f"input-{str(kind)}",) + # icon_set += (name.replace(' ', '-'),) + _ICON_SETS[name] = icon_set return icon_set -def device_icon_file(name, kind=None, size=_LARGE_SIZE): - _init_icon_paths() - - icon_set = device_icon_set(name, kind) - assert icon_set - for n in reversed(icon_set.names): - if _default_theme.has_icon(n): - return _default_theme.lookup_icon(n, size, 0).get_filename() +def device_icon_file(name, kind=None, size=LARGE_SIZE): + icon_name = device_icon_name(name, kind) + return _default_theme.lookup_icon(icon_name, size, 0).get_filename() if icon_name is not None else None def device_icon_name(name, kind=None): _init_icon_paths() - icon_set = device_icon_set(name, kind) assert icon_set - for n in reversed(icon_set.names): + for n in reversed(icon_set): if _default_theme.has_icon(n): return n -def icon_file(name, size=_LARGE_SIZE): +def icon_file(name, size=LARGE_SIZE): _init_icon_paths() - # has_icon() somehow returned False while lookup_icon returns non-None. - # I guess it happens because share/solaar/icons/ has no hicolor and - # resolution subdirs + # I guess it happens because share/solaar/icons/ has no hicolor and resolution subdirs theme_icon = _default_theme.lookup_icon(name, size, 0) if theme_icon: file_name = theme_icon.get_filename() - # if _log.isEnabledFor(_DEBUG): - # _log.debug("icon %s(%d) => %s", name, size, file_name) return file_name - - _log.warn('icon %s(%d) not found in current theme', name, size) + logger.warning("icon %s(%d) not found in current theme", name, size) diff --git a/lib/solaar/ui/pair_window.py b/lib/solaar/ui/pair_window.py index 2ee4303e..c89d7470 100644 --- a/lib/solaar/ui/pair_window.py +++ b/lib/solaar/ui/pair_window.py @@ -1,6 +1,5 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -16,286 +15,265 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from logging import DEBUG as _DEBUG -from logging import getLogger +import logging -from gi.repository import GLib, Gtk -from logitech_receiver import hidpp10 as _hidpp10 -from logitech_receiver.status import KEYS as _K -from solaar.i18n import _, ngettext +from enum import Enum -from . import icons as _icons +from gi.repository import GLib +from gi.repository import Gtk +from logitech_receiver import hidpp10_constants -_log = getLogger(__name__) -del getLogger +from solaar.i18n import _ +from solaar.i18n import ngettext + +from . import icons + +logger = logging.getLogger(__name__) -# -# -# _PAIRING_TIMEOUT = 30 # seconds _STATUS_CHECK = 500 # milliseconds -address = kind = authentication = name = passcode = None + +class GtkSignal(Enum): + CANCEL = "cancel" + CLOSE = "close" -def _create_page(assistant, kind, header=None, icon_name=None, text=None): - p = Gtk.VBox(False, 8) - assistant.append_page(p) - assistant.set_page_type(p, kind) - - if header: - item = Gtk.HBox(False, 16) - p.pack_start(item, False, True, 0) - - label = Gtk.Label(header) - label.set_alignment(0, 0) - label.set_line_wrap(True) - item.pack_start(label, True, True, 0) - - if icon_name: - icon = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.DIALOG) - icon.set_alignment(1, 0) - item.pack_start(icon, False, False, 0) - - if text: - label = Gtk.Label(text) - label.set_alignment(0, 0) - label.set_line_wrap(True) - p.pack_start(label, False, False, 0) - - p.show_all() - return p +def create(receiver): + receiver.reset_pairing() # clear out any information on previous pairing + title = _("%(receiver_name)s: pair new device") % {"receiver_name": receiver.name} + if receiver.receiver_kind == "bolt": + text = _("Bolt receivers are only compatible with Bolt devices.") + text += "\n\n" + text += _("Press a pairing button or key until the pairing light flashes quickly.") + else: + if receiver.receiver_kind == "unifying": + text = _("Unifying receivers are only compatible with Unifying devices.") + else: + text = _("Other receivers are only compatible with a few devices.") + text += "\n\n" + text += _("For most devices, turn on the device you want to pair.") + text += _("If the device is already turned on, turn it off and on again.") + text += "\n" + text += _("The device must not be paired with a nearby powered-on receiver.") + text += "\n" + text += _( + "For devices with multiple channels, " + "press, hold, and release the button for the channel you wish to pair" + "\n" + "or use the channel switch button to select a channel " + "and then press, hold, and release the channel switch button." + ) + text += "\n" + text += _("The channel indicator light should be blinking rapidly.") + if receiver.remaining_pairings() and receiver.remaining_pairings() >= 0: + text += ( + ngettext( + "\n\nThis receiver has %d pairing remaining.", + "\n\nThis receiver has %d pairings remaining.", + receiver.remaining_pairings(), + ) + % receiver.remaining_pairings() + ) + text += _("\nCancelling at this point will not use up a pairing.") + ok = prepare(receiver) + assistant = _create_assistant(receiver, ok, _finish, title, text) + if ok: + GLib.timeout_add(_STATUS_CHECK, check_lock_state, assistant, receiver) + return assistant -def _check_lock_state(assistant, receiver, count=2): - global address, kind, authentication, name, passcode - - if not assistant.is_drawable(): - if _log.isEnabledFor(_DEBUG): - _log.debug('assistant %s destroyed, bailing out', assistant) - return False - - if receiver.status.get(_K.ERROR): - # receiver.status.new_device = _fake_device(receiver) - _pairing_failed(assistant, receiver, receiver.status.pop(_K.ERROR)) - return False - - if receiver.status.new_device: - receiver.remaining_pairings(False) # Update remaining pairings - device, receiver.status.new_device = receiver.status.new_device, None - _pairing_succeeded(assistant, receiver, device) - return False - elif receiver.status.device_address and receiver.status.device_name and not address: - address = receiver.status.device_address - name = receiver.status.device_name - kind = receiver.status.device_kind - authentication = receiver.status.device_authentication - name = receiver.status.device_name - if receiver.pair_device( - address=address, authentication=authentication, entropy=20 if kind == _hidpp10.DEVICE_KIND.keyboard else 10 - ): +def prepare(receiver): + if receiver.receiver_kind == "bolt": + if receiver.discover(timeout=_PAIRING_TIMEOUT): return True else: - _pairing_failed(assistant, receiver, 'failed to open pairing lock') + receiver.pairing.error = "discovery did not start" return False - elif address and receiver.status.device_passkey and not passcode: - passcode = receiver.status.device_passkey - _show_passcode(assistant, receiver, passcode) + elif receiver.set_lock(False, timeout=_PAIRING_TIMEOUT): return True - - if not receiver.status.lock_open and not receiver.status.discovering: - if count > 0: - # the actual device notification may arrive later so have a little patience - GLib.timeout_add(_STATUS_CHECK, _check_lock_state, assistant, receiver, count - 1) - else: - _pairing_failed(assistant, receiver, 'failed to open pairing lock') + else: + receiver.pairing.error = "the pairing lock did not open" return False + +def check_lock_state(assistant, receiver, count=2): + if not assistant.is_drawable(): + logger.debug("assistant %s destroyed, bailing out", assistant) + return False + return _check_lock_state(assistant, receiver, count) + + +def _check_lock_state(assistant, receiver, count): + if receiver.pairing.error: + _pairing_failed(assistant, receiver, receiver.pairing.error) + return False + elif receiver.pairing.new_device: + receiver.remaining_pairings(False) # Update remaining pairings + _pairing_succeeded(assistant, receiver, receiver.pairing.new_device) + return False + elif not receiver.pairing.lock_open and not receiver.pairing.discovering: + if count > 0: + # the actual device notification may arrive later so have a little patience + GLib.timeout_add(_STATUS_CHECK, check_lock_state, assistant, receiver, count - 1) + else: + _pairing_failed(assistant, receiver, "failed to open pairing lock") + return False + elif receiver.pairing.lock_open and receiver.pairing.device_passkey: + _show_passcode(assistant, receiver, receiver.pairing.device_passkey) + return True + elif receiver.pairing.discovering and receiver.pairing.device_address and receiver.pairing.device_name: + add = receiver.pairing.device_address + ent = 20 if receiver.pairing.device_kind == hidpp10_constants.DEVICE_KIND.keyboard else 10 + if receiver.pair_device(address=add, authentication=receiver.pairing.device_authentication, entropy=ent): + return True + else: + _pairing_failed(assistant, receiver, "failed to open pairing lock") + return False return True +def _pairing_failed(assistant, receiver, error): + assistant.remove_page(0) # needed to reset the window size + logger.debug("%s fail: %s", receiver, error) + _create_failure_page(assistant, error) + + +def _pairing_succeeded(assistant, receiver, device): + assistant.remove_page(0) # needed to reset the window size + logger.debug("%s success: %s", receiver, device) + _create_success_page(assistant, device) + + +def _finish(assistant, receiver): + logger.debug("finish %s", assistant) + assistant.destroy() + receiver.pairing.new_device = None + if receiver.pairing.lock_open: + if receiver.receiver_kind == "bolt": + receiver.pair_device("cancel") + else: + receiver.set_lock() + if receiver.pairing.discovering: + receiver.discover(True) + if not receiver.pairing.lock_open and not receiver.pairing.discovering: + receiver.pairing.error = None + + def _show_passcode(assistant, receiver, passkey): - if _log.isEnabledFor(_DEBUG): - _log.debug('%s show passkey: %s', receiver, passkey) - name = receiver.status.device_name - authentication = receiver.status.device_authentication - intro_text = _('%(receiver_name)s: pair new device') % {'receiver_name': receiver.name} - page_text = _('Enter passcode on %(name)s.') % {'name': name} - page_text += '\n' + logger.debug("%s show passkey: %s", receiver, passkey) + name = receiver.pairing.device_name + authentication = receiver.pairing.device_authentication + intro_text = _("%(receiver_name)s: pair new device") % {"receiver_name": receiver.name} + page_text = _("Enter passcode on %(name)s.") % {"name": name} + page_text += "\n" if authentication & 0x01: - page_text += _('Type %(passcode)s and then press the enter key.') % {'passcode': receiver.status.device_passkey} + page_text += _("Type %(passcode)s and then press the enter key.") % { + "passcode": receiver.pairing.device_passkey, + } else: - passcode = ', '.join([ - _('right') if bit == '1' else _('left') for bit in f'{int(receiver.status.device_passkey):010b}' - ]) - page_text += _('Press %(code)s\nand then press left and right buttons simultaneously.') % {'code': passcode} - page = _create_page(assistant, Gtk.AssistantPageType.PROGRESS, intro_text, 'preferences-desktop-peripherals', page_text) + passcode = ", ".join( + [_("right") if bit == "1" else _("left") for bit in f"{int(receiver.pairing.device_passkey):010b}"] + ) + page_text += _("Press %(code)s\nand then press left and right buttons simultaneously.") % {"code": passcode} + page = _create_page( + assistant, + Gtk.AssistantPageType.PROGRESS, + intro_text, + "preferences-desktop-peripherals", + page_text, + ) assistant.set_page_complete(page, True) assistant.next_page() -def _prepare(assistant, page, receiver): - index = assistant.get_current_page() - if _log.isEnabledFor(_DEBUG): - _log.debug('prepare %s %d %s', assistant, index, page) - - if index == 0: - if receiver.receiver_kind == 'bolt': - if receiver.discover(timeout=_PAIRING_TIMEOUT): - assert receiver.status.new_device is None - assert receiver.status.get(_K.ERROR) is None - spinner = page.get_children()[-1] - spinner.start() - GLib.timeout_add(_STATUS_CHECK, _check_lock_state, assistant, receiver) - assistant.set_page_complete(page, True) - else: - GLib.idle_add(_pairing_failed, assistant, receiver, 'discovery did not start') - elif receiver.set_lock(False, timeout=_PAIRING_TIMEOUT): - assert receiver.status.new_device is None - assert receiver.status.get(_K.ERROR) is None - spinner = page.get_children()[-1] - spinner.start() - GLib.timeout_add(_STATUS_CHECK, _check_lock_state, assistant, receiver) - assistant.set_page_complete(page, True) - else: - GLib.idle_add(_pairing_failed, assistant, receiver, 'the pairing lock did not open') - else: - assistant.remove_page(0) - - -def _finish(assistant, receiver): - if _log.isEnabledFor(_DEBUG): - _log.debug('finish %s', assistant) - assistant.destroy() - receiver.status.new_device = None - if receiver.status.lock_open: - if receiver.receiver_kind == 'bolt': - receiver.pair_device('cancel') - else: - receiver.set_lock() - if receiver.status.discovering: - receiver.discover(True) - if not receiver.status.lock_open and not receiver.status.discovering: - receiver.status[_K.ERROR] = None - - -def _pairing_failed(assistant, receiver, error): - if _log.isEnabledFor(_DEBUG): - _log.debug('%s fail: %s', receiver, error) - - assistant.commit() - - header = _('Pairing failed') + ': ' + _(str(error)) + '.' - if 'timeout' in str(error): - text = _('Make sure your device is within range, and has a decent battery charge.') - elif str(error) == 'device not supported': - text = _('A new device was detected, but it is not compatible with this receiver.') - elif 'many' in str(error): - text = _('More paired devices than receiver can support.') - else: - text = _('No further details are available about the error.') - _create_page(assistant, Gtk.AssistantPageType.SUMMARY, header, 'dialog-error', text) - - assistant.next_page() - assistant.commit() - - -def _pairing_succeeded(assistant, receiver, device): - assert device - if _log.isEnabledFor(_DEBUG): - _log.debug('%s success: %s', receiver, device) - - page = _create_page(assistant, Gtk.AssistantPageType.SUMMARY) - - header = Gtk.Label(_('Found a new device:')) - header.set_alignment(0.5, 0) - page.pack_start(header, False, False, 0) - - device_icon = Gtk.Image() - icon_set = _icons.device_icon_set(device.name, device.kind) - device_icon.set_from_icon_set(icon_set, Gtk.IconSize.LARGE) - device_icon.set_alignment(0.5, 1) - page.pack_start(device_icon, True, True, 0) - - device_label = Gtk.Label() - device_label.set_markup('%s' % device.name) - device_label.set_alignment(0.5, 0) - page.pack_start(device_label, True, True, 0) - - hbox = Gtk.HBox(False, 8) - hbox.pack_start(Gtk.Label(' '), False, False, 0) - hbox.set_property('expand', False) - hbox.set_property('halign', Gtk.Align.CENTER) - page.pack_start(hbox, False, False, 0) - - def _check_encrypted(dev): - if assistant.is_drawable(): - if device.status.get(_K.LINK_ENCRYPTED) is False: - hbox.pack_start(Gtk.Image.new_from_icon_name('security-low', Gtk.IconSize.MENU), False, False, 0) - hbox.pack_start(Gtk.Label(_('The wireless link is not encrypted') + '!'), False, False, 0) - hbox.show_all() - else: - return True - - GLib.timeout_add(_STATUS_CHECK, _check_encrypted, device) - - page.show_all() - - assistant.next_page() - assistant.commit() - - -def create(receiver): - assert receiver is not None - assert receiver.kind is None - - global address, kind, authentication, name, passcode - address = name = kind = authentication = passcode = None - +def _create_assistant(receiver, ok, finish, title, text): assistant = Gtk.Assistant() - assistant.set_title(_('%(receiver_name)s: pair new device') % {'receiver_name': receiver.name}) - assistant.set_icon_name('list-add') - + assistant.set_title(title) + assistant.set_icon_name("list-add") assistant.set_size_request(400, 240) assistant.set_resizable(False) - assistant.set_role('pair-device') - - if receiver.receiver_kind == 'unifying': - page_text = _('Unifying receivers are only compatible with Unifying devices.') - elif receiver.receiver_kind == 'bolt': - page_text = _('Bolt receivers are only compatible with Bolt devices.') + assistant.set_role("pair-device") + if ok: + page_intro = _create_page( + assistant, + Gtk.AssistantPageType.PROGRESS, + title, + "preferences-desktop-peripherals", + text, + ) + spinner = Gtk.Spinner() + spinner.set_visible(True) + spinner.start() + page_intro.pack_end(spinner, True, True, 24) + assistant.set_page_complete(page_intro, True) else: - page_text = _('Other receivers are only compatible with a few devices.') - page_text += '\n' - page_text += _('The device must not be paired with a nearby powered-on receiver.') - page_text += '\n\n' - - if receiver.receiver_kind == 'bolt': - page_text += _('Press a pairing button or key until the pairing light flashes quickly.') - page_text += '\n' - page_text += _('You may have to first turn the device off and on again.') - else: - page_text += _('Turn on the device you want to pair.') - page_text += '\n' - page_text += _('If the device is already turned on, turn it off and on again.') - if receiver.remaining_pairings() and receiver.remaining_pairings() >= 0: - page_text += ngettext( - '\n\nThis receiver has %d pairing remaining.', '\n\nThis receiver has %d pairings remaining.', - receiver.remaining_pairings() - ) % receiver.remaining_pairings() - page_text += _('\nCancelling at this point will not use up a pairing.') - - intro_text = _('%(receiver_name)s: pair new device') % {'receiver_name': receiver.name} - - page_intro = _create_page( - assistant, Gtk.AssistantPageType.PROGRESS, intro_text, 'preferences-desktop-peripherals', page_text - ) - spinner = Gtk.Spinner() - spinner.set_visible(True) - page_intro.pack_end(spinner, True, True, 24) - - assistant.connect('prepare', _prepare, receiver) - assistant.connect('cancel', _finish, receiver) - assistant.connect('close', _finish, receiver) - + page_intro = _create_failure_page(assistant, receiver.pairing.error) + assistant.connect(GtkSignal.CANCEL.value, finish, receiver) + assistant.connect(GtkSignal.CLOSE.value, finish, receiver) return assistant + + +def _create_success_page(assistant, device): + def _check_encrypted(device, assistant, hbox): + if assistant.is_drawable() and device.link_encrypted is False: + hbox.pack_start(Gtk.Image.new_from_icon_name("security-low", Gtk.IconSize.MENU), False, False, 0) + hbox.pack_start(Gtk.Label(label=_("The wireless link is not encrypted")), False, False, 0) + hbox.show_all() + return False + + page = _create_page(assistant, Gtk.AssistantPageType.SUMMARY) + header = Gtk.Label(label=_("Found a new device:")) + page.pack_start(header, False, False, 0) + device_icon = Gtk.Image() + icon_name = icons.device_icon_name(device.name, device.kind) + device_icon.set_from_icon_name(icon_name, icons.LARGE_SIZE) + page.pack_start(device_icon, True, True, 0) + device_label = Gtk.Label() + device_label.set_markup(f"{device.name}") + page.pack_start(device_label, True, True, 0) + hbox = Gtk.HBox(homogeneous=False, spacing=8) + hbox.pack_start(Gtk.Label(label=" "), False, False, 0) + hbox.set_property("expand", False) + hbox.set_property("halign", Gtk.Align.CENTER) + page.pack_start(hbox, False, False, 0) + GLib.timeout_add(_STATUS_CHECK, _check_encrypted, device, assistant, hbox) # wait a bit to check link status + page.show_all() + assistant.next_page() + assistant.commit() + + +def _create_failure_page(assistant, error) -> None: + header = _("Pairing failed") + ": " + _(str(error)) + "." + if "timeout" in str(error): + text = _("Make sure your device is within range, and has a decent battery charge.") + elif str(error) == "device not supported": + text = _("A new device was detected, but it is not compatible with this receiver.") + elif "many" in str(error): + text = _("More paired devices than receiver can support.") + else: + text = _("No further details are available about the error.") + _create_page(assistant, Gtk.AssistantPageType.SUMMARY, header, "dialog-error", text) + assistant.next_page() + assistant.commit() + + +def _create_page(assistant, kind, header=None, icon_name=None, text=None) -> Gtk.VBox: + p = Gtk.VBox(homogeneous=False, spacing=8) + assistant.append_page(p) + assistant.set_page_type(p, kind) + if header: + item = Gtk.HBox(homogeneous=False, spacing=16) + p.pack_start(item, False, True, 0) + label = Gtk.Label(label=header) + label.set_line_wrap(True) + item.pack_start(label, True, True, 0) + if icon_name: + icon = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.DIALOG) + item.pack_start(icon, False, False, 0) + if text: + label = Gtk.Label(label=text) + label.set_line_wrap(True) + p.pack_start(label, False, False, 0) + p.show_all() + return p diff --git a/lib/solaar/ui/perkey/__init__.py b/lib/solaar/ui/perkey/__init__.py new file mode 100644 index 00000000..720b3b57 --- /dev/null +++ b/lib/solaar/ui/perkey/__init__.py @@ -0,0 +1,22 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from .layout import Cell +from .layout import Layout +from .layouts import layout_for +from .layouts import register_layout + +__all__ = ("Cell", "Layout", "layout_for", "register_layout") diff --git a/lib/solaar/ui/perkey/binding.py b/lib/solaar/ui/perkey/binding.py new file mode 100644 index 00000000..79cd6d63 --- /dev/null +++ b/lib/solaar/ui/perkey/binding.py @@ -0,0 +1,63 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Bind a Layout to a sink's reported zone list. + +Cells whose `zone_id` the device reports are marked bound. Cells whose zone +the device does not report stay disabled (greyed). Device-reported zones not +covered by any cell get synthesized as strip cells using the sink's labels — +this catches G-keys, logo, media keys and any device-specific extras. +""" + +from __future__ import annotations + +from collections.abc import Callable + +from .layout import BoundCell +from .layout import BoundLayout +from .layout import Cell +from .layout import Layout + + +def bind(layout: Layout, zones: list[int], label_for: Callable[[int], str]) -> BoundLayout: + reported = set(zones) + claimed: set[int] = set() + matrix: list[BoundCell] = [] + strip: list[BoundCell] = [] + for c in layout.matrix_cells(): + bound = c.zone_id in reported + if bound: + claimed.add(c.zone_id) + matrix.append(BoundCell(cell=c, bound=bound)) + for c in layout.strip_cells(): + bound = c.zone_id in reported + if bound: + claimed.add(c.zone_id) + strip.append(BoundCell(cell=c, bound=bound)) + unmapped_all = tuple(z for z in zones if z not in claimed) + # Filter unmapped zones through the layout's curated allowlist. Without + # this, firmware-reported phantoms (G515 reports 47, 97, 99-103, 254) + # would surface as paintable strip cells that don't address any LED. + if layout.extra_zones is None: + showable = unmapped_all + else: + showable = tuple(z for z in unmapped_all if z in layout.extra_zones) + next_col = max((bc.cell.col for bc in strip), default=-1) + 1 + for z in showable: + synth = Cell(zone_id=z, row=0, col=next_col, group="strip", label=label_for(z)) + strip.append(BoundCell(cell=synth, bound=True)) + next_col += 1 + return BoundLayout(matrix=tuple(matrix), strip=tuple(strip), unmapped=unmapped_all) diff --git a/lib/solaar/ui/perkey/canvas.py b/lib/solaar/ui/perkey/canvas.py new file mode 100644 index 00000000..101f21e6 --- /dev/null +++ b/lib/solaar/ui/perkey/canvas.py @@ -0,0 +1,397 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Cairo-rendered keyboard canvas. Renders a BoundLayout as colored rectangles +and dispatches paint events through a configurable Tool to the editor. +""" + +from __future__ import annotations + +import logging + +from enum import Enum +from typing import Callable + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gdk # NOQA: E402 +from gi.repository import GObject # NOQA: E402 +from gi.repository import Gtk # NOQA: E402 + +from .layout import BoundCell # NOQA: E402 +from .layout import BoundLayout # NOQA: E402 +from .tools import TOOLS # NOQA: E402 +from .tools import ToolContext # NOQA: E402 + +logger = logging.getLogger(__name__) + + +class GtkSignal(Enum): + DRAW = "draw" + BUTTON_PRESS_EVENT = "button-press-event" + BUTTON_RELEASE_EVENT = "button-release-event" + MOTION_NOTIFY_EVENT = "motion-notify-event" + LEAVE_NOTIFY_EVENT = "leave-notify-event" + + +CELL_PX = 36 +GUTTER_PX = 4 +STRIP_GAP_PX = 16 +PADDING_PX = 8 + + +class KeyboardCanvas(Gtk.DrawingArea): + __gsignals__ = { + # Emitted on stroke release. delta is dict[zone_id, color]. + "paint": (GObject.SignalFlags.RUN_FIRST, None, (object,)), + } + + def __init__(self) -> None: + super().__init__() + self._bound: BoundLayout | None = None + self._colors: dict[int, int] = {} # zone_id -> packed RGB or -1 (unset) + self._active_color: int = 0xFF0000 + self._gradient_colors_source: Callable[[], tuple[int, int]] | None = None + self._zone_base_color: int | None = None + self._tool_name: str = "single" + self._press_cell: BoundCell | None = None + self._motion_cell: BoundCell | None = None + self._brush_path: list[int] = [] + self._dragging: bool = False + self.set_can_focus(True) + self.add_events( + Gdk.EventMask.BUTTON_PRESS_MASK + | Gdk.EventMask.BUTTON_RELEASE_MASK + | Gdk.EventMask.POINTER_MOTION_MASK + | Gdk.EventMask.LEAVE_NOTIFY_MASK + ) + self.connect(GtkSignal.DRAW.value, self._on_draw) + self.connect(GtkSignal.BUTTON_PRESS_EVENT.value, self._on_press) + self.connect(GtkSignal.BUTTON_RELEASE_EVENT.value, self._on_release) + self.connect(GtkSignal.MOTION_NOTIFY_EVENT.value, self._on_motion) + self.connect(GtkSignal.LEAVE_NOTIFY_EVENT.value, self._on_leave) + + # ---- public API ---- + + def set_layout(self, bound: BoundLayout) -> None: + self._bound = bound + self._update_size() + self.queue_draw() + + def set_colors(self, colors: dict[int, int]) -> None: + self._colors = dict(colors) + self.queue_draw() + + def update_colors(self, deltas: dict[int, int]) -> None: + self._colors.update(deltas) + self.queue_draw() + + def set_active_color(self, color: int) -> None: + self._active_color = int(color) + + def set_gradient_colors_source(self, source: Callable[[], tuple[int, int]] | None) -> None: + self._gradient_colors_source = source + + def set_zone_base_color(self, color: int | None) -> None: + self._zone_base_color = None if color is None else int(color) + self.queue_draw() + + def set_tool(self, name: str) -> None: + if name in TOOLS: + self._tool_name = name + + # ---- size / hit-test ---- + + def _matrix_size(self) -> tuple[int, int]: + if not self._bound: + return 0, 0 + max_col = 0 + max_row = 0 + for bc in self._bound.matrix: + c = bc.cell + max_col = max(max_col, c.col + int(round(c.width))) + max_row = max(max_row, c.row + int(round(c.height))) + return max_row, max_col + + def _strip_size(self) -> int: + if not self._bound: + return 0 + return len(self._bound.strip) + + def _update_size(self) -> None: + rows, cols = self._matrix_size() + strip_n = self._strip_size() + w = PADDING_PX * 2 + cols * CELL_PX + max(0, cols - 1) * GUTTER_PX + matrix_h = rows * CELL_PX + max(0, rows - 1) * GUTTER_PX + strip_h = (CELL_PX + STRIP_GAP_PX) if strip_n else 0 + h = PADDING_PX * 2 + matrix_h + strip_h + # widen if strip is wider than matrix + if strip_n: + sw = PADDING_PX * 2 + strip_n * CELL_PX + max(0, strip_n - 1) * GUTTER_PX + w = max(w, sw) + self.set_size_request(w, h) + + def _cell_rect(self, bc: BoundCell) -> tuple[float, float, float, float]: + c = bc.cell + if self._bound is not None and bc in self._bound.strip: + # strip cells: laid out in a flat row beneath the matrix + rows, _cols = self._matrix_size() + matrix_h = rows * CELL_PX + max(0, rows - 1) * GUTTER_PX + strip_idx = self._bound.strip.index(bc) + x = PADDING_PX + strip_idx * (CELL_PX + GUTTER_PX) + y = PADDING_PX + matrix_h + STRIP_GAP_PX + return (x, y, CELL_PX, CELL_PX) + x = PADDING_PX + c.col * (CELL_PX + GUTTER_PX) + y = PADDING_PX + c.row * (CELL_PX + GUTTER_PX) + w = c.width * CELL_PX + max(0.0, c.width - 1.0) * GUTTER_PX + h = c.height * CELL_PX + max(0.0, c.height - 1.0) * GUTTER_PX + return (x, y, w, h) + + def _cell_at(self, x: float, y: float) -> BoundCell | None: + if not self._bound: + return None + for bc in list(self._bound.matrix) + list(self._bound.strip): + cx, cy, cw, ch = self._cell_rect(bc) + if cx <= x < cx + cw and cy <= y < cy + ch: + return bc + return None + + # ---- draw ---- + + def _on_draw(self, _widget, cr) -> bool: + if not self._bound: + return False + for bc in self._bound.matrix: + self._draw_cell(cr, bc) + for bc in self._bound.strip: + self._draw_cell(cr, bc) + if self._dragging and self._press_cell and self._motion_cell: + tool = TOOLS.get(self._tool_name) + if tool is not None: + if tool.overlay_shape == "rect": + self._draw_rect_overlay(cr, self._press_cell, self._motion_cell) + elif tool.overlay_shape == "line": + self._draw_line_overlay(cr, self._press_cell, self._motion_cell) + return False + + def _draw_cell(self, cr, bc: BoundCell) -> None: + x, y, w, h = self._cell_rect(bc) + color = self._colors.get(bc.cell.zone_id, -1) + # background + if not bc.bound: + cr.set_source_rgba(0.18, 0.18, 0.20, 1.0) + elif color is None or color < 0: + self._fill_checker(cr, x, y, w, h) + cr.set_source_rgba(0, 0, 0, 0) # no overlay fill + else: + r = ((color >> 16) & 0xFF) / 255.0 + g = ((color >> 8) & 0xFF) / 255.0 + b = (color & 0xFF) / 255.0 + cr.set_source_rgba(r, g, b, 1.0) + if bc.bound and (color is not None and color >= 0): + self._round_rect(cr, x, y, w, h, 4) + cr.fill_preserve() + elif not bc.bound: + self._round_rect(cr, x, y, w, h, 4) + cr.fill_preserve() + else: + self._round_rect(cr, x, y, w, h, 4) + # border + cr.set_source_rgba(0, 0, 0, 0.55) + cr.set_line_width(1.0) + cr.stroke() + # label + label = bc.cell.label or str(bc.cell.zone_id) + cr.set_source_rgba(*self._label_color(color, bc.bound)) + cr.select_font_face("Sans") + cr.set_font_size(11.0 if len(label) <= 3 else 9.0) + try: + extents = cr.text_extents(label) + tx = x + (w - extents.width) / 2 - extents.x_bearing + ty = y + (h + extents.height) / 2 - extents.y_bearing - extents.height + cr.move_to(tx, ty) + cr.show_text(label) + except Exception as e: + logger.debug("text rendering failed for %r: %s", label, e) + + def _fill_checker(self, cr, x, y, w, h) -> None: + # Diagonal hash for "no change" cells. Background uses the zone base + # color (what these cells actually display on the keyboard); stripes + # pick a black or white contrast based on luminance. + cr.save() + self._round_rect(cr, x, y, w, h, 4) + cr.clip() + base = self._zone_base_color + if base is not None and base >= 0: + r = ((base >> 16) & 0xFF) / 255.0 + g = ((base >> 8) & 0xFF) / 255.0 + b = (base & 0xFF) / 255.0 + else: + r = g = 0.30 + b = 0.32 + cr.set_source_rgba(r, g, b, 1.0) + cr.rectangle(x, y, w, h) + cr.fill() + if base is not None and base >= 0: + lum = 0.299 * r + 0.587 * g + 0.114 * b + cr.set_source_rgba(0, 0, 0, 0.45) if lum > 0.55 else cr.set_source_rgba(1, 1, 1, 0.35) + else: + cr.set_source_rgba(0.55, 0.55, 0.60, 1.0) + cr.set_line_width(1.5) + step = 5 + d_max = int(w + h) + d = -int(h) + while d <= d_max: + cr.move_to(x + d, y + h) + cr.line_to(x + d + h, y) + cr.stroke() + d += step + cr.restore() + + def _round_rect(self, cr, x, y, w, h, r) -> None: + cr.new_sub_path() + cr.arc(x + w - r, y + r, r, -1.5708, 0) + cr.arc(x + w - r, y + h - r, r, 0, 1.5708) + cr.arc(x + r, y + h - r, r, 1.5708, 3.1416) + cr.arc(x + r, y + r, r, 3.1416, 4.7124) + cr.close_path() + + def _label_color(self, color: int, bound: bool) -> tuple[float, float, float, float]: + if not bound: + return (0.50, 0.50, 0.52, 1.0) + if color is None or color < 0: + return (0.85, 0.85, 0.88, 1.0) + # luminance heuristic + r = ((color >> 16) & 0xFF) / 255.0 + g = ((color >> 8) & 0xFF) / 255.0 + b = (color & 0xFF) / 255.0 + lum = 0.299 * r + 0.587 * g + 0.114 * b + return (0, 0, 0, 1.0) if lum > 0.55 else (1, 1, 1, 1.0) + + def _draw_rect_overlay(self, cr, a: BoundCell, b: BoundCell) -> None: + ax, ay, aw, ah = self._cell_rect(a) + bx, by, bw, bh = self._cell_rect(b) + x0 = min(ax, bx) - 2 + y0 = min(ay, by) - 2 + x1 = max(ax + aw, bx + bw) + 2 + y1 = max(ay + ah, by + bh) + 2 + cr.set_source_rgba(0.30, 0.65, 1.0, 0.85) + cr.set_line_width(1.5) + cr.set_dash([4.0, 3.0]) + cr.rectangle(x0, y0, x1 - x0, y1 - y0) + cr.stroke() + cr.set_dash([]) + + def _draw_line_overlay(self, cr, a: BoundCell, b: BoundCell) -> None: + ax, ay, aw, ah = self._cell_rect(a) + bx, by, bw, bh = self._cell_rect(b) + ax_c, ay_c = ax + aw / 2, ay + ah / 2 + bx_c, by_c = bx + bw / 2, by + bh / 2 + cr.set_source_rgba(0.30, 0.65, 1.0, 0.95) + cr.set_line_width(2.0) + cr.set_dash([5.0, 3.0]) + cr.move_to(ax_c, ay_c) + cr.line_to(bx_c, by_c) + cr.stroke() + cr.set_dash([]) + # endpoint dots — solid so the anchors read clearly + for cx, cy in ((ax_c, ay_c), (bx_c, by_c)): + cr.arc(cx, cy, 4.0, 0, 6.283) + cr.fill() + + # ---- input ---- + + def _on_press(self, _w, event: Gdk.EventButton) -> bool: + if event.button != 1: + return False + bc = self._cell_at(event.x, event.y) + if bc is None or not bc.bound: + return False + self._press_cell = bc + self._motion_cell = bc + self._dragging = True + self._brush_path = [bc.cell.zone_id] + tool = TOOLS.get(self._tool_name) + if tool is not None and tool.is_brush: + self.update_colors({bc.cell.zone_id: self._active_color}) + else: + self.queue_draw() + return True + + def _on_motion(self, _w, event: Gdk.EventMotion) -> bool: + if not self._dragging: + return False + bc = self._cell_at(event.x, event.y) + if bc is None or not bc.bound: + return False + if bc is self._motion_cell: + return False + self._motion_cell = bc + tool = TOOLS.get(self._tool_name) + if tool is not None and tool.is_brush: + if bc.cell.zone_id not in self._brush_path: + self._brush_path.append(bc.cell.zone_id) + self.update_colors({bc.cell.zone_id: self._active_color}) + else: + self.queue_draw() + return True + + def _on_release(self, _w, event: Gdk.EventButton) -> bool: + if event.button != 1 or not self._dragging: + return False + self._dragging = False + if self._press_cell is None: + return False + if self._bound: + bound_zones = {bc.cell.zone_id: bc for bc in list(self._bound.matrix) + list(self._bound.strip)} + strip_zones = frozenset(bc.cell.zone_id for bc in self._bound.strip) + else: + bound_zones = {} + strip_zones = frozenset() + if self._tool_name == "gradient" and self._gradient_colors_source is not None: + grad_active, grad_previous = self._gradient_colors_source() + ctx = ToolContext( + active_color=int(grad_active), + last_color=int(grad_previous), + cells_by_zone=bound_zones, + strip_zones=strip_zones, + current_colors=dict(self._colors), + ) + else: + ctx = ToolContext( + active_color=self._active_color, + last_color=self._active_color, + cells_by_zone=bound_zones, + strip_zones=strip_zones, + current_colors=dict(self._colors), + ) + tool = TOOLS.get(self._tool_name) + delta: dict[int, int] = {} + if tool is not None: + delta = tool.compute(self._press_cell, self._motion_cell, list(self._brush_path), ctx) + self._press_cell = None + self._motion_cell = None + self._brush_path = [] + self.queue_draw() + if delta: + self.update_colors(delta) + self.emit("paint", delta) + return True + + def _on_leave(self, _w, _event) -> bool: + # don't cancel drags on leave; let the user re-enter + return False diff --git a/lib/solaar/ui/perkey/control.py b/lib/solaar/ui/perkey/control.py new file mode 100644 index 00000000..9c3abc3c --- /dev/null +++ b/lib/solaar/ui/perkey/control.py @@ -0,0 +1,252 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Inline placeholder control replacing MapChoiceControl for opted-in settings. + +Renders a summary line + button. Click opens the per-key editor dialog, +backed by a SettingSink adapter that bridges the editor protocol to the +Solaar Setting object. The editor never touches the Setting directly. +""" + +from __future__ import annotations + +import logging + +from enum import Enum + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk # NOQA: E402 + +from solaar.i18n import _ # NOQA: E402 + +from . import dialog as dialog_mod # NOQA: E402 +from .layouts import layout_for # NOQA: E402 + +logger = logging.getLogger(__name__) + + +class GtkSignal(Enum): + CLICKED = "clicked" + + +# Sentinel matching special_keys.COLORSPLUS["No change"]. +NO_CHANGE = -1 + + +class _SettingSink: + """Bridge between a Solaar Setting and the editor's PerKeyColorSink protocol.""" + + def __init__(self, setting, sbox) -> None: + self._setting = setting + self._sbox = sbox + self._listeners: list = [] + + @property + def title(self) -> str: + device = getattr(self._setting, "_device", None) + name = getattr(device, "name", None) or getattr(device, "codename", None) or "" + return name or self._setting.label + + @property + def zones(self) -> list[int]: + return [int(k) for k in self._setting.choices] + + @property + def current(self) -> dict[int, int]: + return dict(self._setting._value or {}) + + def label(self, zone: int) -> str: + for k in self._setting.choices: + if int(k) == int(zone): + return str(k) + return f"KEY {zone}" + + def write_one(self, zone: int, color: int) -> None: + if self._setting._value is None: + self._setting._value = {} + self._setting._value[int(zone)] = int(color) + # Lazy import to avoid a circular module-load between config_panel and perkey. + from solaar.ui.config_panel import _write_async + + _write_async(self._setting, int(color), self._sbox, key=int(zone)) + self._notify() + + def write_bulk(self, deltas: dict[int, int]) -> None: + if not deltas: + return + if self._setting._value is None: + self._setting._value = {} + merged = dict(self._setting._value) + merged.update({int(k): int(v) for k, v in deltas.items()}) + self._setting._value = merged + from solaar.ui.config_panel import _write_async + + _write_async(self._setting, merged, self._sbox, key=None) + self._notify() + + def subscribe(self, listener): + self._listeners.append(listener) + + def unsubscribe() -> None: + # Idempotent: the editor calls this on shutdown, but the listener + # may already be gone if the sink itself was torn down first. + try: + self._listeners.remove(listener) + except ValueError: + pass + + return unsubscribe + + def _palette_key(self) -> str: + return f"_palette:{self._setting.name}" + + def palette_state(self) -> tuple[int, int] | None: + device = getattr(self._setting, "_device", None) + persister = getattr(device, "persister", None) + if persister is None: + return None + entry = persister.get(self._palette_key()) + if not isinstance(entry, dict): + return None + active = entry.get("active") + previous = entry.get("previous", active) + if not isinstance(active, int) or not isinstance(previous, int): + return None + return (int(active), int(previous)) + + def set_palette_state(self, active: int, previous: int) -> None: + device = getattr(self._setting, "_device", None) + persister = getattr(device, "persister", None) + if persister is None: + return + persister[self._palette_key()] = {"active": int(active), "previous": int(previous)} + + def zone_base_color(self) -> int | None: + device = getattr(self._setting, "_device", None) + if device is None or not getattr(device, "settings", None): + return None + for s in device.settings: + if s.name.startswith("rgb_zone_") and s._value is not None: + color = getattr(s._value, "color", None) + if isinstance(color, int): + return int(color) + return None + + def _notify(self) -> None: + snapshot = self.current + for cb in list(self._listeners): + try: + cb(snapshot) + except Exception as e: + logger.warning("perkey listener raised: %s", e) + + def push_external_value(self, value) -> None: + """Called from the inline control when the framework reports a value change.""" + if isinstance(value, dict): + self._notify() + + +class PerKeyControl(Gtk.Box): + """Replaces MapChoiceControl for per-key color settings. + + Ducktypes the four `Control` methods (`set_sensitive`, `set_value`, + `get_value`, `layout`) used by `_create_sbox` / `_update_setting_item`. + """ + + def __init__(self, sbox) -> None: + super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + self.sbox = sbox + self._setting = sbox.setting + self._value: dict | None = None + self._sink = _SettingSink(self._setting, sbox) + + self._summary = Gtk.Label(label=_("(not loaded)")) + self._summary.set_xalign(0.0) + self.pack_start(self._summary, True, True, 0) + + self._open_btn = Gtk.Button(label=_("Open editor…")) + self._open_btn.set_tooltip_text(_("Paint key colors on a keyboard layout")) + self._open_btn.connect(GtkSignal.CLICKED.value, self._on_open) + self.pack_end(self._open_btn, False, False, 0) + + # ---- Control protocol ---- + + def set_sensitive(self, sensitive: bool) -> None: + super().set_sensitive(bool(sensitive)) + self._open_btn.set_sensitive(bool(sensitive)) + + def set_value(self, value) -> None: + if value is None: + return + if not isinstance(value, dict): + return + # _write_async wraps single-key writes as `{key: written_value}` so + # MapChoiceControl can update one combo cell. We need to keep the + # full picture for the summary count, so merge instead of replace + # when a partial dict comes in. + existing = self._setting._value if isinstance(self._setting._value, dict) else None + if existing and len(value) < len(existing): + merged = dict(existing) + merged.update(value) + self._value = merged + else: + self._value = value + self._sink.push_external_value(self._value) + self._refresh_summary() + + def get_value(self): + return self._value + + def layout(self, sbox, label, change, spinner, failed) -> bool: + # Match the standard Control packing order so our button sits where + # every other setting's widget sits, just left of spinner/change-icon. + sbox.pack_start(label, False, False, 0) + sbox.pack_end(change, False, False, 0) + sbox.pack_end(self, False, False, 0) + sbox.pack_end(spinner, False, False, 0) + sbox.pack_end(failed, False, False, 0) + return self + + # ---- internal ---- + + def _refresh_summary(self) -> None: + if not isinstance(self._value, dict): + self._summary.set_text(_("(no zones)")) + return + total = len(self._value) + painted = sum(1 for v in self._value.values() if isinstance(v, int) and v != NO_CHANGE and v >= 0) + self._summary.set_text(_("{painted} / {total} keys painted").format(painted=painted, total=total)) + + def _on_open(self, _btn) -> None: + feature = getattr(self._setting, "feature", None) + feature_int = int(feature) if feature is not None else 0 + device = getattr(self._setting, "_device", None) + kind_obj = getattr(device, "kind", None) + kind_str = str(kind_obj).lower() if kind_obj is not None else "" + hint = { + "kind": kind_str if kind_str else None, + "wpid": getattr(device, "wpid", None), + "codename": getattr(device, "codename", None), + "name": getattr(device, "name", None), + "keyboard_layout": getattr(device, "keyboard_layout", None), + "zones": list(self._sink.zones), + "zone_count": len(self._sink.zones), + } + layout = layout_for(feature_int, hint) + dlg = dialog_mod.get_dialog() + dlg.present(self._sink, layout) diff --git a/lib/solaar/ui/perkey/dialog.py b/lib/solaar/ui/perkey/dialog.py new file mode 100644 index 00000000..b58aaf47 --- /dev/null +++ b/lib/solaar/ui/perkey/dialog.py @@ -0,0 +1,87 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Singleton dialog hosting a PerKeyEditor for one sink at a time.""" + +from __future__ import annotations + +from enum import Enum + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk # NOQA: E402 + +from solaar.i18n import _ # NOQA: E402 + +from .editor import PerKeyEditor # NOQA: E402 +from .layout import Layout # NOQA: E402 +from .protocol import PerKeyColorSink # NOQA: E402 + + +class GtkSignal(Enum): + DELETE_EVENT = "delete-event" + + +class PerKeyEditorDialog: + _instance: "PerKeyEditorDialog | None" = None + + def __init__(self) -> None: + self._window = Gtk.Window() + self._window.set_title(_("Per-key Lighting")) + # No default size or geometry hints — the editor's content size + # (driven by KeyboardCanvas's size_request) determines the window size + # via the ScrolledWindow's propagate_natural_size. Wide keyboards open + # large; small mice open small. + self._window.connect(GtkSignal.DELETE_EVENT.value, self._on_delete) + self._editor: PerKeyEditor | None = None + self._wrapper = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + self._wrapper.set_border_width(8) + self._window.add(self._wrapper) + + def _on_delete(self, _w, _e) -> bool: + self._window.hide() + if self._editor is not None: + self._editor.shutdown() + self._wrapper.remove(self._editor) + self._editor = None + return True + + def present(self, sink: PerKeyColorSink, layout: Layout | None) -> None: + if self._editor is not None: + self._editor.shutdown() + self._wrapper.remove(self._editor) + self._editor = None + self._editor = PerKeyEditor(sink, layout) + self._wrapper.pack_start(self._editor, True, True, 0) + self._wrapper.show_all() + self._window.set_title(_("Per-key Lighting") + " — " + sink.title) + # Resize to fit canvas + toolbar. ScrolledWindow's *minimum* is tiny + # (one row's worth) regardless of propagate_natural_size, so we have + # to compute the target ourselves from the canvas's size_request. + canvas_w, canvas_h = self._editor.canvas_size() + if canvas_w > 0 and canvas_h > 0: + # Toolbar ~50px, wrapper border 8px each side, scrollbar slack ~4. + target_w = canvas_w + 32 + target_h = canvas_h + 80 + self._window.resize(target_w, target_h) + self._window.present() + + +def get_dialog() -> PerKeyEditorDialog: + if PerKeyEditorDialog._instance is None: + PerKeyEditorDialog._instance = PerKeyEditorDialog() + return PerKeyEditorDialog._instance diff --git a/lib/solaar/ui/perkey/editor.py b/lib/solaar/ui/perkey/editor.py new file mode 100644 index 00000000..9b0bab47 --- /dev/null +++ b/lib/solaar/ui/perkey/editor.py @@ -0,0 +1,194 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Editor widget: combines toolbar + palette + canvas into one VBox. + +The editor consumes only the PerKeyColorSink protocol — no device imports, +no Setting imports — preserving the FE/BE seam. +""" + +from __future__ import annotations + +import logging + +from enum import Enum + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk # NOQA: E402 + +from solaar.i18n import _ # NOQA: E402 + +from . import binding # NOQA: E402 +from .canvas import KeyboardCanvas # NOQA: E402 +from .layout import Layout # NOQA: E402 +from .palette import GradientSwatch # NOQA: E402 +from .palette import Palette # NOQA: E402 +from .protocol import PerKeyColorSink # NOQA: E402 + +logger = logging.getLogger(__name__) + + +class GtkSignal(Enum): + COLOR_CHANGED = "color-changed" + PAINT = "paint" + TOGGLED = "toggled" + + +_TOOL_LABELS = { + "single": (_("Brush"), _("Click or drag to paint individual keys")), + "rect": (_("Rect"), _("Drag to select a rectangle of keys, painted on release")), + "bucket": (_("Fill"), _("Flood-fill connected keys of the same color with the active color")), +} +_TOOL_TOOLTIPS = { + "gradient": _("Drag to fade from previous color to active color"), +} + + +class PerKeyEditor(Gtk.Box): + def __init__(self, sink: PerKeyColorSink, layout: Layout | None = None) -> None: + super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=8) + self._sink = sink + self._layout = layout + self._unsubscribe = None + + # toolbar row + toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + self._tool_buttons: dict[str, Gtk.RadioButton] = {} + self._gradient_swatch: GradientSwatch | None = None + first: Gtk.RadioButton | None = None + supported = layout.supported_tools if layout else ("single", "rect", "bucket", "gradient") + for name in supported: + if name == "gradient": + btn = Gtk.RadioButton.new_from_widget(first) + btn.set_mode(False) + self._gradient_swatch = GradientSwatch() + btn.add(self._gradient_swatch) + btn.set_tooltip_text(_TOOL_TOOLTIPS["gradient"]) + else: + label, tip = _TOOL_LABELS.get(name, (name, "")) + btn = Gtk.RadioButton.new_with_label_from_widget(first, label) + btn.set_mode(False) # render as toggle button rather than radio + btn.set_tooltip_text(tip) + btn.connect(GtkSignal.TOGGLED.value, self._on_tool_toggled, name) + if first is None: + first = btn + toolbar.pack_start(btn, False, False, 0) + self._tool_buttons[name] = btn + + initial_active, initial_previous = 0xFF0000, 0xFF0000 + try: + persisted = sink.palette_state() + except Exception as e: + logger.debug("palette_state read failed: %s", e) + persisted = None + if persisted is not None: + initial_active, initial_previous = persisted + self._palette = Palette(active=initial_active, previous=initial_previous) + self._palette.connect(GtkSignal.COLOR_CHANGED.value, self._on_color_changed) + toolbar.pack_end(self._palette, False, False, 0) + if self._gradient_swatch is not None: + self._gradient_swatch.update(self._palette.get_color(), self._palette.get_last_color()) + + self.pack_start(toolbar, False, False, 0) + + # canvas inside a scrolled window so wide layouts can scroll if the + # window is shrunk below content size. propagate_natural_size lets the + # window auto-fit small layouts (e.g. an 8-LED mouse) without forcing + # an oversized minimum. + scroll = Gtk.ScrolledWindow() + scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + scroll.set_propagate_natural_width(True) + scroll.set_propagate_natural_height(True) + self._canvas = KeyboardCanvas() + self._canvas.connect(GtkSignal.PAINT.value, self._on_canvas_paint) + scroll.add(self._canvas) + self.pack_start(scroll, True, True, 0) + + self._canvas.set_active_color(self._palette.get_color()) + if self._gradient_swatch is not None: + self._canvas.set_gradient_colors_source(self._gradient_swatch.get_colors) + try: + base = sink.zone_base_color() + except Exception as e: + logger.debug("zone_base_color read failed: %s", e) + base = None + self._canvas.set_zone_base_color(base) + self._palette.set_zone_base_color(base) + self._refresh_layout() + self._sync_from_sink() + self._unsubscribe = sink.subscribe(self._on_sink_update) + + def shutdown(self) -> None: + if self._unsubscribe: + try: + self._unsubscribe() + except Exception as e: + logger.debug("perkey sink unsubscribe failed: %s", e) + self._unsubscribe = None + + def canvas_size(self) -> tuple[int, int]: + """Return the canvas's pixel size_request — what the dialog should + size its content area to so the layout fits without scrollbars. + """ + return self._canvas.get_size_request() + + def _refresh_layout(self) -> None: + if self._layout is None: + # No registered layout: lay out all reported zones as a flat strip. + from .layout import Cell + + zones = list(self._sink.zones) + cells = tuple(Cell(zone_id=z, row=0, col=i, group="strip", label=self._sink.label(z)) for i, z in enumerate(zones)) + self._layout = Layout(cells=cells, rows=1, cols=max(1, len(zones)), description=f"flat strip ({len(zones)} zones)") + bound = binding.bind( + self._layout, + list(self._sink.zones), + self._sink.label, + ) + self._canvas.set_layout(bound) + + def _sync_from_sink(self) -> None: + self._canvas.set_colors(dict(self._sink.current)) + + def _on_sink_update(self, current: dict[int, int]) -> None: + self._canvas.set_colors(dict(current)) + + def _on_color_changed(self, _palette, color: int) -> None: + self._canvas.set_active_color(color) + # Gradient swatch tracks only real picker colors; toggling unset + # leaves it alone so the gradient setup isn't disturbed. + picker = self._palette.get_picker_color() + if self._gradient_swatch is not None: + self._gradient_swatch.update(picker, self._palette.get_last_color()) + try: + self._sink.set_palette_state(picker, self._palette.get_last_color()) + except Exception as e: + logger.debug("set_palette_state failed: %s", e) + + def _on_tool_toggled(self, btn: Gtk.RadioButton, name: str) -> None: + if btn.get_active(): + self._canvas.set_tool(name) + + def _on_canvas_paint(self, _canvas, delta: dict) -> None: + if not delta: + return + if len(delta) == 1: + zone, color = next(iter(delta.items())) + self._sink.write_one(int(zone), int(color)) + else: + self._sink.write_bulk({int(z): int(c) for z, c in delta.items()}) diff --git a/lib/solaar/ui/perkey/layout.py b/lib/solaar/ui/perkey/layout.py new file mode 100644 index 00000000..d2ad463b --- /dev/null +++ b/lib/solaar/ui/perkey/layout.py @@ -0,0 +1,102 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Visual layout primitives for the per-key color editor. + +This module is pure data. It does not import GTK and does not import from +`lib.logitech_receiver`. It is therefore relocatable into a shared package +when the frontend/backend split happens. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from dataclasses import field + + +@dataclass(frozen=True) +class Cell: + """One paintable cell in a layout. + + `zone_id` is the firmware identifier the device uses for this LED. It is + matched against the device's reported zone list at bind time; cells with + no matching device zone are drawn disabled. + """ + + zone_id: int + row: int + col: int + width: float = 1.0 + height: float = 1.0 + group: str = "main" + label: str = "" + x: float | None = None + y: float | None = None + + +@dataclass(frozen=True) +class Layout: + """A device-class visual layout. + + Cells in `strip_groups` are rendered as a flat row beneath the matrix + region, regardless of their row/col fields. Cells outside `strip_groups` + are placed by row/col on the main matrix. + + `extra_zones` is a curated allowlist of zone ids that may appear in the + bottom strip when the device reports them but they are not covered by a + layout cell. Zones outside the allowlist are dropped — Logitech firmware + bitmaps enumerate phantom/reserved slots (e.g. G515 reports 47, 97, 99-103, + 254) that aren't physical keys. Set to `None` to disable filtering. + """ + + cells: tuple[Cell, ...] + rows: int + cols: int + strip_groups: tuple[str, ...] = ("strip",) + supported_tools: tuple[str, ...] = ("single", "rect", "bucket", "gradient") + extra_zones: frozenset[int] | None = None + description: str = "" + + def matrix_cells(self) -> tuple[Cell, ...]: + return tuple(c for c in self.cells if c.group not in self.strip_groups) + + def strip_cells(self) -> tuple[Cell, ...]: + return tuple(c for c in self.cells if c.group in self.strip_groups) + + def by_zone(self) -> dict[int, Cell]: + return {c.zone_id: c for c in self.cells} + + +@dataclass(frozen=True) +class BoundCell: + """A Cell augmented with bind state, returned by `binding.bind`.""" + + cell: Cell + bound: bool + + +@dataclass(frozen=True) +class BoundLayout: + """Result of binding a Layout against a sink's reported zones. + + `matrix` and `strip` are tuples of BoundCell in render order. `unmapped` + holds zones the device reported that no Layout cell claimed; these get + appended to the strip with synthesized cells. + """ + + matrix: tuple[BoundCell, ...] = field(default_factory=tuple) + strip: tuple[BoundCell, ...] = field(default_factory=tuple) + unmapped: tuple[int, ...] = field(default_factory=tuple) diff --git a/lib/solaar/ui/perkey/layouts/__init__.py b/lib/solaar/ui/perkey/layouts/__init__.py new file mode 100644 index 00000000..b2bde7e2 --- /dev/null +++ b/lib/solaar/ui/perkey/layouts/__init__.py @@ -0,0 +1,142 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Registry of per-key layouts, keyed by feature + a device-class match. + +Layouts register themselves with a matcher callable. `layout_for(feature, hint)` +returns the first matching layout, or None when no model-specific layout is +known — in which case the editor renders a flat strip of all reported zones. +""" + +from __future__ import annotations + +from collections.abc import Callable + +from ..layout import Layout +from . import keyboard_ansi +from . import keyboard_iso_azerty +from . import keyboard_iso_qwerty +from . import keyboard_iso_qwertz +from . import keyboard_jis +from . import mouse_g502x + +# (feature_id, matcher, layout). Matcher receives a `hint` dict the editor +# assembles from the device (kind, wpid, codename, name, zones list, etc.). +_REGISTRY: list[tuple[int, Callable[[dict], bool], Layout]] = [] + + +def register_layout(feature: int, matcher: Callable[[dict], bool], layout: Layout) -> None: + _REGISTRY.append((feature, matcher, layout)) + + +def layout_for(feature: int, hint: dict) -> Layout | None: + for f, match, layout in _REGISTRY: + if f == feature and match(hint): + return layout + return None + + +def _name_contains(*needles: str) -> Callable[[dict], bool]: + """Build a matcher that returns True if any needle is a substring of the + device's name or codename (case-insensitive). Useful for device-family + layouts where multiple wpids share an LED arrangement. + """ + folded = tuple(n.upper() for n in needles) + + def match(hint: dict) -> bool: + for field in ("codename", "name"): + value = hint.get(field) + if not value: + continue + up = str(value).upper() + if any(n in up for n in folded): + return True + return False + + return match + + +# --- Keyboard region routing --- +# Country code → layout family. Codes from HID++ feature 0x4540 KeyboardLayout. +_KEYBOARD_FAMILY_BY_COUNTRY: dict[int, str] = { + 1: "ansi", + # ISO QWERTY (UK + ES/IT/PT/BE/Nordic — same shape, different keycap legends) + 2: "iso_qwerty", + 5: "iso_qwerty", + 8: "iso_qwerty", + 0x0B: "iso_qwerty", + 0x0D: "iso_qwerty", + 0x0E: "iso_qwerty", + 0x0F: "iso_qwerty", + 0x16: "iso_qwerty", + 0x1D: "iso_qwerty", + 0x21: "iso_qwerty", + 0x24: "iso_qwerty", + # ISO QWERTZ (DE/Swiss) + 3: "iso_qwertz", + 7: "iso_qwertz", + # ISO AZERTY (FR) + 4: "iso_azerty", + # JIS + 9: "jis", + 0x3E: "jis", +} + +_FAMILY_LAYOUTS = { + "ansi": (keyboard_ansi.LAYOUT_FULL, keyboard_ansi.LAYOUT_TKL), + "iso_qwerty": (keyboard_iso_qwerty.LAYOUT_FULL, keyboard_iso_qwerty.LAYOUT_TKL), + "iso_qwertz": (keyboard_iso_qwertz.LAYOUT_FULL, keyboard_iso_qwertz.LAYOUT_TKL), + "iso_azerty": (keyboard_iso_azerty.LAYOUT_FULL, keyboard_iso_azerty.LAYOUT_TKL), + "jis": (keyboard_jis.LAYOUT_FULL, keyboard_jis.LAYOUT_TKL), +} + + +def _has_numpad(hint: dict) -> bool: + """Numpad presence is read from the device's reported zone bitmap rather + than counting zones — G515 reports phantom zones (47, 97, 99-103, 254) + that diverge from the keycap count. + """ + zones = set(hint.get("zones", ())) + return 80 in zones or 95 in zones + + +def _keyboard_family(hint: dict) -> str: + """Pick a layout family from the device's HID++ keyboard layout country + code. Defaults to "ansi" when the code is missing or unknown. + """ + code = hint.get("keyboard_layout") + if code is None: + return "ansi" + return _KEYBOARD_FAMILY_BY_COUNTRY.get(int(code), "ansi") + + +def _keyboard_matcher(family: str, full_size: bool) -> Callable[[dict], bool]: + def match(hint: dict) -> bool: + if hint.get("kind") != "keyboard": + return False + if _has_numpad(hint) != full_size: + return False + return _keyboard_family(hint) == family + + return match + + +# PER_KEY_LIGHTING_V2 = 0x8081 +for _family, (_full, _tkl) in _FAMILY_LAYOUTS.items(): + register_layout(0x8081, _keyboard_matcher(_family, full_size=True), _full) + register_layout(0x8081, _keyboard_matcher(_family, full_size=False), _tkl) + +register_layout(0x8081, _name_contains("G502 X"), mouse_g502x.LAYOUT) diff --git a/lib/solaar/ui/perkey/layouts/_keyboard_base.py b/lib/solaar/ui/perkey/layouts/_keyboard_base.py new file mode 100644 index 00000000..2e38c47a --- /dev/null +++ b/lib/solaar/ui/perkey/layouts/_keyboard_base.py @@ -0,0 +1,230 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Shared building blocks for regional keyboard layouts. + +Each region (ANSI, ISO_QWERTY, ISO_QWERTZ, ISO_AZERTY, JIS) shares the function +row, nav-cluster, and numpad blocks; only the main alpha block differs (ANSI +includes the row 2 col 13 backslash, ISO doesn't). Regional label overrides on +top of either main block produce the final layout. + +Cell positions and groupings adapted from OpenRGB's KeyboardLayoutManager. +Zone IDs are firmware values reported by Logitech HID++ feature 0x8081 +(PER_KEY_LIGHTING_V2). +""" + +from __future__ import annotations + +from ..layout import Cell +from ..layout import Layout + +# --- Function row: ESC + F1..F12 (shared across all regions). +FN_ROW: tuple[Cell, ...] = ( + Cell(zone_id=38, row=0, col=0, group="fn_row", label="Esc"), + Cell(zone_id=55, row=0, col=2, group="fn_row", label="F1"), + Cell(zone_id=56, row=0, col=3, group="fn_row", label="F2"), + Cell(zone_id=57, row=0, col=4, group="fn_row", label="F3"), + Cell(zone_id=58, row=0, col=5, group="fn_row", label="F4"), + Cell(zone_id=59, row=0, col=6, group="fn_row", label="F5"), + Cell(zone_id=60, row=0, col=7, group="fn_row", label="F6"), + Cell(zone_id=61, row=0, col=8, group="fn_row", label="F7"), + Cell(zone_id=62, row=0, col=9, group="fn_row", label="F8"), + Cell(zone_id=63, row=0, col=10, group="fn_row", label="F9"), + Cell(zone_id=64, row=0, col=11, group="fn_row", label="F10"), + Cell(zone_id=65, row=0, col=12, group="fn_row", label="F11"), + Cell(zone_id=66, row=0, col=13, group="fn_row", label="F12"), +) + +# --- Nav cluster + arrows (shared). +EXTRAS: tuple[Cell, ...] = ( + Cell(zone_id=67, row=0, col=14, group="extras", label="PrtSc"), + Cell(zone_id=68, row=0, col=15, group="extras", label="ScrLk"), + Cell(zone_id=69, row=0, col=16, group="extras", label="Pause"), + Cell(zone_id=70, row=1, col=14, group="extras", label="Ins"), + Cell(zone_id=71, row=1, col=15, group="extras", label="Home"), + Cell(zone_id=72, row=1, col=16, group="extras", label="PgUp"), + Cell(zone_id=73, row=2, col=14, group="extras", label="Del"), + Cell(zone_id=74, row=2, col=15, group="extras", label="End"), + Cell(zone_id=75, row=2, col=16, group="extras", label="PgDn"), + Cell(zone_id=79, row=4, col=15, group="extras", label="↑"), + Cell(zone_id=77, row=5, col=14, group="extras", label="←"), + Cell(zone_id=78, row=5, col=15, group="extras", label="↓"), + Cell(zone_id=76, row=5, col=16, group="extras", label="→"), +) + +# --- Numpad block (only on full-size keyboards). +NUMPAD: tuple[Cell, ...] = ( + Cell(zone_id=80, row=1, col=17, group="numpad", label="Num"), + Cell(zone_id=81, row=1, col=18, group="numpad", label="/"), + Cell(zone_id=82, row=1, col=19, group="numpad", label="*"), + Cell(zone_id=83, row=1, col=20, group="numpad", label="-"), + Cell(zone_id=92, row=2, col=17, group="numpad", label="7"), + Cell(zone_id=93, row=2, col=18, group="numpad", label="8"), + Cell(zone_id=94, row=2, col=19, group="numpad", label="9"), + Cell(zone_id=84, row=2, col=20, height=2.0, group="numpad", label="+"), + Cell(zone_id=89, row=3, col=17, group="numpad", label="4"), + Cell(zone_id=90, row=3, col=18, group="numpad", label="5"), + Cell(zone_id=91, row=3, col=19, group="numpad", label="6"), + Cell(zone_id=86, row=4, col=17, group="numpad", label="1"), + Cell(zone_id=87, row=4, col=18, group="numpad", label="2"), + Cell(zone_id=88, row=4, col=19, group="numpad", label="3"), + Cell(zone_id=85, row=4, col=20, height=2.0, group="numpad", label="Enter"), + Cell(zone_id=95, row=5, col=17, width=2.0, group="numpad", label="0"), + Cell(zone_id=96, row=5, col=19, group="numpad", label="."), +) + +# --- Main alpha block, ANSI (104-key). Includes row 2 col 13 backslash and +# omits POUND (row 3 col 12) + ISO_BACKSLASH (row 4 col 1). +MAIN_ANSI: tuple[Cell, ...] = ( + # Row 1: backtick + numbers + minus/equals + backspace + Cell(zone_id=50, row=1, col=0, group="main", label="`"), + Cell(zone_id=27, row=1, col=1, group="main", label="1"), + Cell(zone_id=28, row=1, col=2, group="main", label="2"), + Cell(zone_id=29, row=1, col=3, group="main", label="3"), + Cell(zone_id=30, row=1, col=4, group="main", label="4"), + Cell(zone_id=31, row=1, col=5, group="main", label="5"), + Cell(zone_id=32, row=1, col=6, group="main", label="6"), + Cell(zone_id=33, row=1, col=7, group="main", label="7"), + Cell(zone_id=34, row=1, col=8, group="main", label="8"), + Cell(zone_id=35, row=1, col=9, group="main", label="9"), + Cell(zone_id=36, row=1, col=10, group="main", label="0"), + Cell(zone_id=42, row=1, col=11, group="main", label="-"), + Cell(zone_id=43, row=1, col=12, group="main", label="="), + Cell(zone_id=39, row=1, col=13, group="main", label="Bksp"), + # Row 2: tab + qwerty + brackets + backslash + Cell(zone_id=40, row=2, col=0, group="main", label="Tab"), + Cell(zone_id=17, row=2, col=1, group="main", label="Q"), + Cell(zone_id=23, row=2, col=2, group="main", label="W"), + Cell(zone_id=5, row=2, col=3, group="main", label="E"), + Cell(zone_id=18, row=2, col=4, group="main", label="R"), + Cell(zone_id=20, row=2, col=5, group="main", label="T"), + Cell(zone_id=25, row=2, col=6, group="main", label="Y"), + Cell(zone_id=21, row=2, col=7, group="main", label="U"), + Cell(zone_id=9, row=2, col=8, group="main", label="I"), + Cell(zone_id=15, row=2, col=9, group="main", label="O"), + Cell(zone_id=16, row=2, col=10, group="main", label="P"), + Cell(zone_id=44, row=2, col=11, group="main", label="["), + Cell(zone_id=45, row=2, col=12, group="main", label="]"), + Cell(zone_id=46, row=2, col=13, group="main", label="\\"), + # Row 3: caps + asdf-row + semi/quote + enter + Cell(zone_id=54, row=3, col=0, group="main", label="Caps"), + Cell(zone_id=1, row=3, col=1, group="main", label="A"), + Cell(zone_id=19, row=3, col=2, group="main", label="S"), + Cell(zone_id=4, row=3, col=3, group="main", label="D"), + Cell(zone_id=6, row=3, col=4, group="main", label="F"), + Cell(zone_id=7, row=3, col=5, group="main", label="G"), + Cell(zone_id=8, row=3, col=6, group="main", label="H"), + Cell(zone_id=10, row=3, col=7, group="main", label="J"), + Cell(zone_id=11, row=3, col=8, group="main", label="K"), + Cell(zone_id=12, row=3, col=9, group="main", label="L"), + Cell(zone_id=48, row=3, col=10, group="main", label=";"), + Cell(zone_id=49, row=3, col=11, group="main", label="'"), + Cell(zone_id=37, row=3, col=13, group="main", label="Enter"), + # Row 4: shift + zxcv-row + comma/period/slash + rshift + Cell(zone_id=105, row=4, col=0, group="main", label="Shift"), + Cell(zone_id=26, row=4, col=2, group="main", label="Z"), + Cell(zone_id=24, row=4, col=3, group="main", label="X"), + Cell(zone_id=3, row=4, col=4, group="main", label="C"), + Cell(zone_id=22, row=4, col=5, group="main", label="V"), + Cell(zone_id=2, row=4, col=6, group="main", label="B"), + Cell(zone_id=14, row=4, col=7, group="main", label="N"), + Cell(zone_id=13, row=4, col=8, group="main", label="M"), + Cell(zone_id=51, row=4, col=9, group="main", label=","), + Cell(zone_id=52, row=4, col=10, group="main", label="."), + Cell(zone_id=53, row=4, col=11, group="main", label="/"), + Cell(zone_id=109, row=4, col=13, group="main", label="Shift"), + # Row 5: bottom row. Space spans cols 3..9 visually. + Cell(zone_id=104, row=5, col=0, group="main", label="Ctrl"), + Cell(zone_id=107, row=5, col=1, group="main", label="Win"), + Cell(zone_id=106, row=5, col=2, group="main", label="Alt"), + Cell(zone_id=41, row=5, col=3, width=7.0, group="main", label="Space"), + Cell(zone_id=110, row=5, col=10, group="main", label="AltGr"), + Cell(zone_id=111, row=5, col=11, group="main", label="Win"), + Cell(zone_id=98, row=5, col=12, group="main", label="Menu"), + Cell(zone_id=108, row=5, col=13, group="main", label="Ctrl"), +) + +# --- Main alpha block, ISO. Same as ANSI minus the row 2 col 13 backslash; +# on ISO that position is the top half of the L-shape Enter, addressed +# by zone 37 (the main Enter cell at row 3 col 13). Zone 46 is silently +# unaddressable on ISO layouts — same limitation as OpenRGB's UI. +MAIN_ISO: tuple[Cell, ...] = tuple(c for c in MAIN_ANSI if not (c.row == 2 and c.col == 13)) + +# --- Curated allowlist for unmapped device zones surfaced in the bottom strip. +# G-keys, logo, media, brightness — the canonical "extras" Logitech firmware +# actually addresses. Phantom zones (e.g. G515's 47, 97, 99-103, 254) drop. +EXTRAS_ALLOWLIST: frozenset[int] = frozenset( + { + 153, # Brightness + 155, # Play/Pause + 156, # Mute + 157, # Next + 158, # Previous + 180, # G1 + 181, # G2 + 182, # G3 + 183, # G4 + 184, # G5 + 210, # Logo + } +) + + +def _relabel(cells: tuple[Cell, ...], overrides: dict[int, str]) -> tuple[Cell, ...]: + """Return a new tuple where any cell whose zone_id is in `overrides` has + its label replaced. Unaffected cells pass through unchanged. + """ + if not overrides: + return cells + return tuple( + Cell( + zone_id=c.zone_id, + row=c.row, + col=c.col, + width=c.width, + height=c.height, + group=c.group, + label=overrides[c.zone_id] if c.zone_id in overrides else c.label, + x=c.x, + y=c.y, + ) + for c in cells + ) + + +def build_layout( + main_cells: tuple[Cell, ...], + *, + include_numpad: bool, + label_overrides: dict[int, str] | None = None, + description: str = "", +) -> Layout: + """Assemble a regional keyboard layout from a chosen main block + the + shared fn-row / extras / (optionally) numpad blocks. Apply per-zone + label overrides to every cell whose zone matches. + """ + cells = FN_ROW + main_cells + EXTRAS + if include_numpad: + cells = cells + NUMPAD + cells = _relabel(cells, label_overrides or {}) + cols = 21 if include_numpad else 17 + return Layout( + cells=cells, + rows=6, + cols=cols, + extra_zones=EXTRAS_ALLOWLIST, + description=description, + ) diff --git a/lib/solaar/ui/perkey/layouts/keyboard_ansi.py b/lib/solaar/ui/perkey/layouts/keyboard_ansi.py new file mode 100644 index 00000000..8a0509f7 --- /dev/null +++ b/lib/solaar/ui/perkey/layouts/keyboard_ansi.py @@ -0,0 +1,42 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""ANSI QWERTY keyboard layouts (full 104-key and TKL). + +Cell positions and groupings derived from OpenRGB's KeyboardLayoutManager +(KeyboardLayoutManager.cpp), Copyright (C) Chris M (Dr_No), licensed under +GPL-2.0-or-later. This file ports the static ANSI data only; the runtime +opcode interpreter for regional overlays is intentionally not included. +""" + +from __future__ import annotations + +from ..layout import Layout +from ._keyboard_base import MAIN_ANSI +from ._keyboard_base import build_layout + +LAYOUT_FULL: Layout = build_layout( + MAIN_ANSI, + include_numpad=True, + description="ANSI QWERTY 104-key full-size", +) + + +LAYOUT_TKL: Layout = build_layout( + MAIN_ANSI, + include_numpad=False, + description="ANSI QWERTY tenkeyless", +) diff --git a/lib/solaar/ui/perkey/layouts/keyboard_iso_azerty.py b/lib/solaar/ui/perkey/layouts/keyboard_iso_azerty.py new file mode 100644 index 00000000..7f23179b --- /dev/null +++ b/lib/solaar/ui/perkey/layouts/keyboard_iso_azerty.py @@ -0,0 +1,75 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""ISO AZERTY layout (FR). + +ISO shape plus French label overrides — A↔Q, W↔Z, M repositioned, French +digit-row symbols (& é " ' ( - è _ ç à ). Adapted from OpenRGB. +""" + +from __future__ import annotations + +from ..layout import Layout +from ._keyboard_base import MAIN_ISO +from ._keyboard_base import build_layout + +# zone_id → French label +_OVERRIDES: dict[int, str] = { + # Row 1 (digit row → French symbols) + 50: "²", # backtick → super-2 + 27: "&", # 1 + 28: "é", # 2 + 29: '"', # 3 + 30: "'", # 4 + 31: "(", # 5 + 32: "-", # 6 + 33: "è", # 7 + 34: "_", # 8 + 35: "ç", # 9 + 36: "à", # 0 + 42: ")", # minus → close-paren + # Row 2 — Q/A and W/Z swaps, brackets relabeled + 17: "A", # Q-position → A + 23: "Z", # W-position → Z + 44: "^", # [-position → caret + 45: "$", # ]-position → dollar + # Row 3 — A → Q; M moves up to ; position + 1: "Q", # A-position → Q + 48: "M", # ;-position → M + 49: "ù", # '-position → ù + # Row 4 — Z-position becomes W; comma row shifts + 26: "W", # Z-position → W + 13: ",", # M-position → comma + 51: ";", # ,-position → semicolon + 52: ":", # .-position → colon + 53: "!", # /-position → exclamation +} + + +LAYOUT_FULL: Layout = build_layout( + MAIN_ISO, + include_numpad=True, + label_overrides=_OVERRIDES, + description="ISO AZERTY (FR) full-size", +) + + +LAYOUT_TKL: Layout = build_layout( + MAIN_ISO, + include_numpad=False, + label_overrides=_OVERRIDES, + description="ISO AZERTY (FR) tenkeyless", +) diff --git a/lib/solaar/ui/perkey/layouts/keyboard_iso_qwerty.py b/lib/solaar/ui/perkey/layouts/keyboard_iso_qwerty.py new file mode 100644 index 00000000..118b6849 --- /dev/null +++ b/lib/solaar/ui/perkey/layouts/keyboard_iso_qwerty.py @@ -0,0 +1,45 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""ISO_QWERTY keyboard layouts (UK English ISO + other QWERTY ISO regions). + +Same English keycap legends as ANSI; differs only in shape — the row 2 col 13 +backslash on ANSI doesn't exist on ISO (that position is the upper half of the +L-shape Enter, addressed by zone 37). Used for UK and any other region whose +country code maps to "iso_qwerty" without a more specific layout (Spanish, +Italian, Portuguese, Belgian, Nordic — those keyboards have the same shape +as UK ISO; only their physical keycap legends differ, which our painter +doesn't reproduce verbatim). +""" + +from __future__ import annotations + +from ..layout import Layout +from ._keyboard_base import MAIN_ISO +from ._keyboard_base import build_layout + +LAYOUT_FULL: Layout = build_layout( + MAIN_ISO, + include_numpad=True, + description="ISO QWERTY 103-key full-size", +) + + +LAYOUT_TKL: Layout = build_layout( + MAIN_ISO, + include_numpad=False, + description="ISO QWERTY tenkeyless", +) diff --git a/lib/solaar/ui/perkey/layouts/keyboard_iso_qwertz.py b/lib/solaar/ui/perkey/layouts/keyboard_iso_qwertz.py new file mode 100644 index 00000000..04d52bfe --- /dev/null +++ b/lib/solaar/ui/perkey/layouts/keyboard_iso_qwertz.py @@ -0,0 +1,57 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""ISO QWERTZ layout (DE / CH). + +ISO shape plus German label overrides (Y/Z swap, Ü/Ö/Ä/ß placement). +Adapted from OpenRGB. +""" + +from __future__ import annotations + +from ..layout import Layout +from ._keyboard_base import MAIN_ISO +from ._keyboard_base import build_layout + +# zone_id → German label +_OVERRIDES: dict[int, str] = { + 50: "^", # row 1 col 0 — caret/degree (DE keycap) + 42: "ß", # row 1 col 11 — eszett + 43: "´", # row 1 col 12 — acute accent + 25: "Z", # row 2 col 6 — Y/Z swap + 44: "Ü", # row 2 col 11 + 45: "+", # row 2 col 12 + 48: "Ö", # row 3 col 10 + 49: "Ä", # row 3 col 11 + 26: "Y", # row 4 col 2 — Y/Z swap + 53: "-", # row 4 col 11 +} + + +LAYOUT_FULL: Layout = build_layout( + MAIN_ISO, + include_numpad=True, + label_overrides=_OVERRIDES, + description="ISO QWERTZ (DE/CH) full-size", +) + + +LAYOUT_TKL: Layout = build_layout( + MAIN_ISO, + include_numpad=False, + label_overrides=_OVERRIDES, + description="ISO QWERTZ (DE/CH) tenkeyless", +) diff --git a/lib/solaar/ui/perkey/layouts/keyboard_jis.py b/lib/solaar/ui/perkey/layouts/keyboard_jis.py new file mode 100644 index 00000000..e320c822 --- /dev/null +++ b/lib/solaar/ui/perkey/layouts/keyboard_jis.py @@ -0,0 +1,52 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""JIS layout (JP). + +ISO shape with Japanese keycap relabels for the bracket / colon positions. +Adapted from OpenRGB. JIS keyboards also have additional kana-control keys +near the spacebar (henkan/muhenkan/kana) that aren't represented here — +matches OpenRGB's coverage. +""" + +from __future__ import annotations + +from ..layout import Layout +from ._keyboard_base import MAIN_ISO +from ._keyboard_base import build_layout + +# zone_id → JIS label +_OVERRIDES: dict[int, str] = { + 44: "@", # row 2 col 11 — bracket-position becomes at-sign + 45: "[", # row 2 col 12 — bracket shifts left + 49: ":", # row 3 col 11 — quote-position becomes colon +} + + +LAYOUT_FULL: Layout = build_layout( + MAIN_ISO, + include_numpad=True, + label_overrides=_OVERRIDES, + description="JIS (JP) full-size", +) + + +LAYOUT_TKL: Layout = build_layout( + MAIN_ISO, + include_numpad=False, + label_overrides=_OVERRIDES, + description="JIS (JP) tenkeyless", +) diff --git a/lib/solaar/ui/perkey/layouts/mouse_g502x.py b/lib/solaar/ui/perkey/layouts/mouse_g502x.py new file mode 100644 index 00000000..b26fbf41 --- /dev/null +++ b/lib/solaar/ui/perkey/layouts/mouse_g502x.py @@ -0,0 +1,49 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""LED layout for the G502 X family (G502 X, G502 X PLUS, G502 X LIGHTSPEED). + +Eight LEDs (A..H) reported as zones 1..8 by the firmware. Positions may +need revision per actual hardware. + + Row 0: C . . . . . B + Row 1: . D H G F E . + Row 2: . . . . . . A +""" + +from __future__ import annotations + +from ..layout import Cell +from ..layout import Layout + +_CELLS: tuple[Cell, ...] = ( + Cell(zone_id=1, row=2, col=6, group="main", label="A"), + Cell(zone_id=2, row=0, col=6, group="main", label="B"), + Cell(zone_id=3, row=0, col=0, group="main", label="C"), + Cell(zone_id=4, row=1, col=1, group="main", label="D"), + Cell(zone_id=5, row=1, col=5, group="main", label="E"), + Cell(zone_id=6, row=1, col=4, group="main", label="F"), + Cell(zone_id=7, row=1, col=3, group="main", label="G"), + Cell(zone_id=8, row=1, col=2, group="main", label="H"), +) + + +LAYOUT: Layout = Layout( + cells=_CELLS, + rows=3, + cols=7, + description="Logitech G502 X family", +) diff --git a/lib/solaar/ui/perkey/palette.py b/lib/solaar/ui/perkey/palette.py new file mode 100644 index 00000000..1d738e61 --- /dev/null +++ b/lib/solaar/ui/perkey/palette.py @@ -0,0 +1,255 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Palette: active-color picker + a small gradient swatch widget. + +The picker (`Palette`) is just a wrapped `Gtk.ColorButton` that emits +`color-changed` and remembers the previous active color. The previous +color is surfaced visually by the gradient tool button, not in the palette +itself — see `GradientSwatch` below, used by `editor.py`. +""" + +from __future__ import annotations + +from enum import Enum + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gdk # NOQA: E402 +from gi.repository import GObject # NOQA: E402 +from gi.repository import Gtk # NOQA: E402 + +from solaar.i18n import _ # NOQA: E402 + + +class GtkSignal(Enum): + DRAW = "draw" + COLOR_SET = "color-set" + TOGGLED = "toggled" + + +def _rgb_to_int(rgba: Gdk.RGBA) -> int: + r = max(0, min(255, int(round(rgba.red * 255)))) + g = max(0, min(255, int(round(rgba.green * 255)))) + b = max(0, min(255, int(round(rgba.blue * 255)))) + return (r << 16) | (g << 8) | b + + +def _int_to_rgba(c: int) -> Gdk.RGBA: + rgba = Gdk.RGBA() + if c is None or c < 0: + rgba.red = rgba.green = rgba.blue = 0.5 + rgba.alpha = 1.0 + return rgba + rgba.red = ((c >> 16) & 0xFF) / 255.0 + rgba.green = ((c >> 8) & 0xFF) / 255.0 + rgba.blue = (c & 0xFF) / 255.0 + rgba.alpha = 1.0 + return rgba + + +def _draw_hash(cr, x: float, y: float, size: float, base_color: int | None = None) -> None: + """Diagonal hash pattern used as the visual for "no change" / unset. + + Background is the zone base color (the color these cells actually display + on the keyboard) when known; stripes pick a black or white contrast based + on luminance so the texture stays readable on any base. + """ + cr.save() + cr.rectangle(x, y, size, size) + cr.clip() + if base_color is not None and base_color >= 0: + r = ((base_color >> 16) & 0xFF) / 255.0 + g = ((base_color >> 8) & 0xFF) / 255.0 + b = (base_color & 0xFF) / 255.0 + cr.set_source_rgba(r, g, b, 1.0) + else: + r = g = 0.30 + b = 0.32 + cr.set_source_rgba(r, g, b, 1.0) + cr.rectangle(x, y, size, size) + cr.fill() + if base_color is not None and base_color >= 0: + lum = 0.299 * r + 0.587 * g + 0.114 * b + cr.set_source_rgba(0, 0, 0, 0.45) if lum > 0.55 else cr.set_source_rgba(1, 1, 1, 0.35) + else: + cr.set_source_rgba(0.55, 0.55, 0.60, 1.0) + cr.set_line_width(1.2) + step = 4 + d = -int(size) + while d <= int(size): + cr.move_to(x + d, y + size) + cr.line_to(x + d + size, y) + cr.stroke() + d += step + cr.restore() + + +class HashSwatch(Gtk.DrawingArea): + """Square showing the diagonal hash pattern; used as the visual on the + "unset" toggle button and matches how unset cells render on the canvas. + Set the zone base color via `set_base_color` so the swatch reflects what + "no change" cells actually display on the keyboard. + """ + + SIZE = 22 + + def __init__(self) -> None: + super().__init__() + self._base_color: int | None = None + self.set_size_request(self.SIZE, self.SIZE) + self.connect(GtkSignal.DRAW.value, self._on_draw) + + def set_base_color(self, color: int | None) -> None: + self._base_color = None if color is None else int(color) + self.queue_draw() + + def _on_draw(self, _w, cr) -> None: + _draw_hash(cr, 0, 0, float(self.SIZE), self._base_color) + cr.set_source_rgba(0, 0, 0, 0.45) + cr.set_line_width(1.0) + cr.rectangle(0.5, 0.5, self.SIZE - 1, self.SIZE - 1) + cr.stroke() + + +# Sentinel for "no change" / unset paint. Matches special_keys.COLORSPLUS["No change"]. +UNSET_COLOR = -1 + + +class Palette(Gtk.Box): + __gsignals__ = { + "color-changed": (GObject.SignalFlags.RUN_FIRST, None, (int,)), + } + + def __init__(self, active: int = 0xFF0000, previous: int = 0xFF0000) -> None: + super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + # _color/_last_color are always real RGB values; the unset toggle is + # a separate channel so the gradient swatch (which mirrors these) is + # unaffected by switching to "no change" paint mode. + self._color: int = int(active) + self._last_color: int = int(previous) + self._unset_mode: bool = False + + self._color_btn = Gtk.ColorButton() + self._color_btn.set_use_alpha(False) + self._color_btn.set_rgba(_int_to_rgba(self._color)) + self._color_btn.set_tooltip_text(_("Active color")) + self._color_btn.connect(GtkSignal.COLOR_SET.value, self._on_color_set) + self.pack_start(self._color_btn, False, False, 0) + + self._unset_swatch = HashSwatch() + self._unset_btn = Gtk.ToggleButton() + self._unset_btn.set_tooltip_text(_("Paint as 'no change' — clears the cell to the zone base color")) + self._unset_btn.add(self._unset_swatch) + self._unset_btn.connect(GtkSignal.TOGGLED.value, self._on_unset_toggled) + self.pack_start(self._unset_btn, False, False, 0) + + def set_zone_base_color(self, color: int | None) -> None: + self._unset_swatch.set_base_color(color) + + def _on_color_set(self, btn: Gtk.ColorButton) -> None: + c = _rgb_to_int(btn.get_rgba()) + unset_was_on = self._unset_mode + if c == self._color and not unset_was_on: + return + if c != self._color: + self._last_color = self._color + self._color = c + if unset_was_on: + self._unset_mode = False + self._unset_btn.set_active(False) + self.emit("color-changed", self.get_color()) + + def _on_unset_toggled(self, btn: Gtk.ToggleButton) -> None: + new_state = bool(btn.get_active()) + if new_state == self._unset_mode: + return + self._unset_mode = new_state + self.emit("color-changed", self.get_color()) + + def get_color(self) -> int: + return UNSET_COLOR if self._unset_mode else self._color + + def get_picker_color(self) -> int: + """The most recent real RGB pick — independent of the unset toggle. + Use this for visuals that should always reflect actual colors (e.g. + the gradient swatch). + """ + return self._color + + def get_last_color(self) -> int: + return self._last_color + + def is_unset(self) -> bool: + return self._unset_mode + + +class GradientSwatch(Gtk.DrawingArea): + """Small icon: diagonal gradient from `previous` (bottom-left) to `active` (top-right). + + Used as the visual on the gradient tool button so the user can see at a + glance which two colors the next gradient stroke will fade between. + """ + + SIZE = 22 + + def __init__(self) -> None: + super().__init__() + self.set_size_request(self.SIZE, self.SIZE) + self._active: int = 0xFF0000 + self._previous: int = 0xFF0000 + self.connect(GtkSignal.DRAW.value, self._on_draw) + + def update(self, active: int, previous: int) -> None: + self._active = int(active) + self._previous = int(previous) + self.queue_draw() + + def get_active(self) -> int: + return self._active + + def get_previous(self) -> int: + return self._previous + + def get_colors(self) -> tuple[int, int]: + """Return (active, previous) — the colors the gradient tool will paint with.""" + return (self._active, self._previous) + + def _on_draw(self, _w, cr) -> None: + import cairo # local: keeps the module light when GradientSwatch isn't built + + s = self.SIZE + + def rgb(c: int) -> tuple[float, float, float]: + if c is None or c < 0: + return (0.5, 0.5, 0.5) + return (((c >> 16) & 0xFF) / 255.0, ((c >> 8) & 0xFF) / 255.0, (c & 0xFF) / 255.0) + + # Top-left (previous, gradient start) → bottom-right (active, end). + # Matches the directional behavior of dragging the line tool TL → BR. + pat = cairo.LinearGradient(0, 0, s, s) + pat.add_color_stop_rgb(0.0, *rgb(self._previous)) + pat.add_color_stop_rgb(1.0, *rgb(self._active)) + cr.set_source(pat) + cr.rectangle(0, 0, s, s) + cr.fill() + + # Subtle border so the swatch reads as a control even on similar bg. + cr.set_source_rgba(0, 0, 0, 0.45) + cr.set_line_width(1.0) + cr.rectangle(0.5, 0.5, s - 1, s - 1) + cr.stroke() diff --git a/lib/solaar/ui/perkey/protocol.py b/lib/solaar/ui/perkey/protocol.py new file mode 100644 index 00000000..dada0f3c --- /dev/null +++ b/lib/solaar/ui/perkey/protocol.py @@ -0,0 +1,73 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Narrow contract between the per-key editor and any color-map setting. + +The editor consumes only this protocol, never `lib.logitech_receiver` directly. +This is the seam where a future frontend/backend split would cut cleanly. +""" + +from __future__ import annotations + +from typing import Callable +from typing import Protocol + + +class PerKeyColorSink(Protocol): + """A device's per-key color buffer, exposed without device internals. + + Colors are 24-bit packed RGB ints (0xRRGGBB). The sentinel value -1 means + "no change" / "unset" (matches `special_keys.COLORSPLUS["No change"]`). + """ + + @property + def title(self) -> str: + ... + + @property + def zones(self) -> list[int]: + ... + + @property + def current(self) -> dict[int, int]: + ... + + def label(self, zone: int) -> str: + ... + + def write_one(self, zone: int, color: int) -> None: + ... + + def write_bulk(self, deltas: dict[int, int]) -> None: + ... + + def subscribe(self, listener: Callable[[dict[int, int]], None]) -> Callable[[], None]: + """Register a callback for current-value changes; return an unsubscribe handle.""" + ... + + def palette_state(self) -> tuple[int, int] | None: + """Return the persisted (active_color, previous_color) for this device's palette, or None.""" + ... + + def set_palette_state(self, active: int, previous: int) -> None: + """Persist the palette's active and previous colors for this device.""" + ... + + def zone_base_color(self) -> int | None: + """Return the zone base color (what 'no change' cells actually display + on the keyboard), or None if the device has no zone effect. + """ + ... diff --git a/lib/solaar/ui/perkey/tools.py b/lib/solaar/ui/perkey/tools.py new file mode 100644 index 00000000..b9432dbd --- /dev/null +++ b/lib/solaar/ui/perkey/tools.py @@ -0,0 +1,223 @@ +## Copyright (C) 2026 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Paint tools for the per-key editor. + +Each tool is a stateless policy object. The Canvas owns per-stroke state +(press cell, motion cell, brush path) and asks the active tool for the +final delta on release. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Iterable +from typing import Protocol + +from .layout import BoundCell +from .layout import Cell + + +@dataclass +class ToolContext: + active_color: int + last_color: int + cells_by_zone: dict[int, BoundCell] + # zone ids that live in the bottom strip (e.g. logo, G-keys); kept separate + # because their on-screen position is decoupled from the matrix grid. + strip_zones: frozenset = frozenset() + # zone_id -> current packed RGB (or -1 sentinel for unset). Used by tools + # that need to compare colors, like the flood-fill bucket. + current_colors: dict = None # type: ignore[assignment] + + def __post_init__(self) -> None: + if self.current_colors is None: + self.current_colors = {} + + def bound_cells(self) -> list[BoundCell]: + return list(self.cells_by_zone.values()) + + def matrix_cells(self) -> list[BoundCell]: + cells = [bc for bc in self.cells_by_zone.values() if bc.bound and bc.cell.zone_id not in self.strip_zones] + if cells: + return cells + # No matrix region (e.g. a mouse, where every zone lives in the + # strip). Fall back to all bound cells so directional tools still + # have something to project across. + return [bc for bc in self.cells_by_zone.values() if bc.bound] + + def cells_in_bbox(self, a: BoundCell, b: BoundCell) -> list[BoundCell]: + cx_a, cy_a = _cell_center(a.cell) + cx_b, cy_b = _cell_center(b.cell) + x0, x1 = (cx_a, cx_b) if cx_a <= cx_b else (cx_b, cx_a) + y0, y1 = (cy_a, cy_b) if cy_a <= cy_b else (cy_b, cy_a) + result = [] + for bc in self.cells_by_zone.values(): + if not bc.bound: + continue + cx, cy = _cell_center(bc.cell) + if x0 <= cx <= x1 and y0 <= cy <= y1: + result.append(bc) + return result + + def cells_in_bbox_ordered(self, a: BoundCell, b: BoundCell) -> list[BoundCell]: + cells = self.cells_in_bbox(a, b) + return sorted(cells, key=lambda c: (c.cell.row, c.cell.col)) + + def cells_in_group(self, group: str) -> list[BoundCell]: + return [bc for bc in self.cells_by_zone.values() if bc.bound and bc.cell.group == group] + + +def _cell_center(cell: Cell) -> tuple[float, float]: + return (cell.col + cell.width / 2.0, cell.row + cell.height / 2.0) + + +def _cells_touch(a: Cell, b: Cell) -> bool: + """Bounding-box edge adjacency in grid units. Handles variable widths + (Space spans multiple cols, Numpad+ spans multiple rows). + """ + a_c1, a_r1 = a.col + a.width, a.row + a.height + b_c1, b_r1 = b.col + b.width, b.row + b.height + rows_overlap = a.row < b_r1 and b.row < a_r1 + cols_overlap = a.col < b_c1 and b.col < a_c1 + if (a_c1 == b.col or b_c1 == a.col) and rows_overlap: + return True + if (a_r1 == b.row or b_r1 == a.row) and cols_overlap: + return True + return False + + +class Tool(Protocol): + name: str + is_brush: bool + overlay_shape: str # "" | "rect" + + def compute( + self, + start: BoundCell | None, + end: BoundCell | None, + path: Iterable[int], + ctx: ToolContext, + ) -> dict[int, int]: + ... + + +class SingleTool: + name = "single" + is_brush = True + overlay_shape = "" + + def compute(self, start, end, path, ctx): + return {z: ctx.active_color for z in path} + + +class RectTool: + name = "rect" + is_brush = False + overlay_shape = "rect" + + def compute(self, start, end, path, ctx): + if start is None or end is None: + return {} + return {bc.cell.zone_id: ctx.active_color for bc in ctx.cells_in_bbox(start, end)} + + +class BucketTool: + """Flood-fill: replace the clicked cell's color in every connected cell + of the same color (4-adjacent on the matrix grid). Strip cells aren't on + the matrix grid, so clicking one paints just that cell. + """ + + name = "bucket" + is_brush = False + overlay_shape = "" + + def compute(self, start, end, path, ctx): + if start is None: + return {} + new_color = ctx.active_color + target = ctx.current_colors.get(start.cell.zone_id, -1) + if target == new_color: + return {} + if start.cell.zone_id in ctx.strip_zones: + return {start.cell.zone_id: new_color} + # BFS over matrix cells + cells_by_id = {z: bc for z, bc in ctx.cells_by_zone.items() if bc.bound and z not in ctx.strip_zones} + visited = {start.cell.zone_id} + stack = [start] + result: dict[int, int] = {} + while stack: + bc = stack.pop() + result[bc.cell.zone_id] = new_color + for other in cells_by_id.values(): + if other.cell.zone_id in visited: + continue + if ctx.current_colors.get(other.cell.zone_id, -1) != target: + continue + if not _cells_touch(bc.cell, other.cell): + continue + visited.add(other.cell.zone_id) + stack.append(other) + return result + + +class GradientTool: + """Directional gradient: drag a line from A to B, the whole matrix gets a + gradient at that angle. Cells projecting before A clamp to the previous + color; cells past B clamp to the active color. + """ + + name = "gradient" + is_brush = False + overlay_shape = "line" + + def compute(self, start, end, path, ctx): + if start is None or end is None: + return {} + ax, ay = _cell_center(start.cell) + bx, by = _cell_center(end.cell) + vx, vy = bx - ax, by - ay + length_sq = vx * vx + vy * vy + if length_sq == 0: + return {start.cell.zone_id: ctx.active_color} + result: dict[int, int] = {} + for bc in ctx.matrix_cells(): + cx, cy = _cell_center(bc.cell) + t = ((cx - ax) * vx + (cy - ay) * vy) / length_sq + t = 0.0 if t < 0.0 else 1.0 if t > 1.0 else t + result[bc.cell.zone_id] = _lerp_rgb(ctx.last_color, ctx.active_color, t) + return result + + +def _lerp_rgb(c0: int, c1: int, t: float) -> int: + if c0 < 0: + c0 = c1 + if c1 < 0: + c1 = c0 + r0, g0, b0 = (c0 >> 16) & 0xFF, (c0 >> 8) & 0xFF, c0 & 0xFF + r1, g1, b1 = (c1 >> 16) & 0xFF, (c1 >> 8) & 0xFF, c1 & 0xFF + r = int(round(r0 + (r1 - r0) * t)) + g = int(round(g0 + (g1 - g0) * t)) + b = int(round(b0 + (b1 - b0) * t)) + return (r << 16) | (g << 8) | b + + +TOOLS: dict[str, Tool] = { + "single": SingleTool(), + "rect": RectTool(), + "bucket": BucketTool(), + "gradient": GradientTool(), +} diff --git a/lib/solaar/ui/rule_actions.py b/lib/solaar/ui/rule_actions.py new file mode 100644 index 00000000..706479f9 --- /dev/null +++ b/lib/solaar/ui/rule_actions.py @@ -0,0 +1,332 @@ +## Copyright (C) Solaar Contributors +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from enum import Enum +from shlex import quote as shlex_quote + +from gi.repository import Gtk +from logitech_receiver import diversion +from logitech_receiver.diversion import CLICK +from logitech_receiver.diversion import DEPRESS +from logitech_receiver.diversion import RELEASE +from logitech_receiver.diversion import XK_KEYS +from logitech_receiver.diversion import buttons + +from solaar.i18n import _ +from solaar.ui.rule_base import CompletionEntry +from solaar.ui.rule_base import RuleComponentUI + + +class GtkSignal(Enum): + CHANGED = "changed" + CLICKED = "clicked" + TOGGLED = "toggled" + + +class ActionUI(RuleComponentUI): + CLASS = diversion.Action + + @classmethod + def icon_name(cls): + return "go-next" + + +class KeyPressUI(ActionUI): + CLASS = diversion.KeyPress + KEY_NAMES = [k[3:] if k.startswith("XK_") else k for k, v in XK_KEYS.items() if isinstance(v, int)] + + def create_widgets(self): + self.widgets = {} + self.fields = [] + self.label = Gtk.Label( + label=_("Simulate a chorded key click or depress or release.\nOn Wayland requires write access to /dev/uinput."), + halign=Gtk.Align.CENTER, + justify=Gtk.Justification.CENTER, + ) + self.widgets[self.label] = (0, 0, 5, 1) + self.del_btns = [] + self.add_btn = Gtk.Button(label=_("Add key"), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) + self.add_btn.connect(GtkSignal.CLICKED.value, self._clicked_add) + self.widgets[self.add_btn] = (1, 1, 1, 1) + self.action_clicked_radio = Gtk.RadioButton.new_with_label_from_widget(None, _("Click")) + self.action_clicked_radio.connect(GtkSignal.TOGGLED.value, self._on_update, CLICK) + self.widgets[self.action_clicked_radio] = (0, 3, 1, 1) + self.action_pressed_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_clicked_radio, _("Depress")) + self.action_pressed_radio.connect(GtkSignal.TOGGLED.value, self._on_update, DEPRESS) + self.widgets[self.action_pressed_radio] = (1, 3, 1, 1) + self.action_released_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_pressed_radio, _("Release")) + self.action_released_radio.connect(GtkSignal.TOGGLED.value, self._on_update, RELEASE) + self.widgets[self.action_released_radio] = (2, 3, 1, 1) + + def _create_field(self): + field_entry = CompletionEntry(self.KEY_NAMES, halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) + field_entry.connect(GtkSignal.CHANGED.value, self._on_update) + field_entry.set_size_request(250, -1) + self.fields.append(field_entry) + self.widgets[field_entry] = (len(self.fields) - 1, 1, 1, 1) + return field_entry + + def _create_del_btn(self): + btn = Gtk.Button(label=_("Delete"), halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) + self.del_btns.append(btn) + self.widgets[btn] = (len(self.del_btns) - 1, 2, 1, 1) + btn.connect(GtkSignal.CLICKED.value, self._clicked_del, len(self.del_btns) - 1) + return btn + + def _clicked_add(self, _btn): + keys, action = self.component.regularize_args(self.collect_value()) + self.component.__init__([keys + [""], action], warn=False) + self.show(self.component, editable=True) + self.fields[len(self.component.key_names) - 1].grab_focus() + + def _clicked_del(self, _btn, pos): + keys, action = self.component.regularize_args(self.collect_value()) + keys.pop(pos) + self.component.__init__([keys, action], warn=False) + self.show(self.component, editable=True) + self._on_update_callback() + + def _on_update(self, *args): + super()._on_update(*args) + for i, f in enumerate(self.fields): + if f.get_visible(): + icon = ( + "dialog-warning" + if i < len(self.component.key_names) and self.component.key_names[i] not in self.KEY_NAMES + else "" + ) + f.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) + + def show(self, component, editable=True): + n = len(component.key_names) + while len(self.fields) < n: + self._create_field() + self._create_del_btn() + + self.widgets[self.add_btn] = (n, 1, 1, 1) + super().show(component, editable) + for i in range(n): + field_entry = self.fields[i] + with self.ignore_changes(): + field_entry.set_text(component.key_names[i]) + field_entry.show_all() + self.del_btns[i].show() + for i in range(n, len(self.fields)): + self.fields[i].hide() + self.del_btns[i].hide() + + def collect_value(self): + action = ( + CLICK if self.action_clicked_radio.get_active() else DEPRESS if self.action_pressed_radio.get_active() else RELEASE + ) + return [[f.get_text().strip() for f in self.fields if f.get_visible()], action] + + @classmethod + def left_label(cls, component): + return _("Key press") + + @classmethod + def right_label(cls, component): + return " + ".join(component.key_names) + (f" ({component.action})" if component.action != CLICK else "") + + +class MouseScrollUI(ActionUI): + CLASS = diversion.MouseScroll + MIN_VALUE = -2000 + MAX_VALUE = 2000 + + def create_widgets(self): + self.widgets = {} + self.label = Gtk.Label( + label=_("Simulate a mouse scroll.\nOn Wayland requires write access to /dev/uinput."), + halign=Gtk.Align.CENTER, + justify=Gtk.Justification.CENTER, + ) + self.widgets[self.label] = (0, 0, 4, 1) + self.label_x = Gtk.Label(label="x", halign=Gtk.Align.END, valign=Gtk.Align.END, hexpand=True) + self.label_y = Gtk.Label(label="y", halign=Gtk.Align.END, valign=Gtk.Align.END, hexpand=True) + self.field_x = Gtk.SpinButton.new_with_range(self.MIN_VALUE, self.MAX_VALUE, 1) + self.field_y = Gtk.SpinButton.new_with_range(self.MIN_VALUE, self.MAX_VALUE, 1) + for f in [self.field_x, self.field_y]: + f.set_halign(Gtk.Align.CENTER) + f.set_valign(Gtk.Align.START) + self.field_x.connect(GtkSignal.CHANGED.value, self._on_update) + self.field_y.connect(GtkSignal.CHANGED.value, self._on_update) + self.widgets[self.label_x] = (0, 1, 1, 1) + self.widgets[self.field_x] = (1, 1, 1, 1) + self.widgets[self.label_y] = (2, 1, 1, 1) + self.widgets[self.field_y] = (3, 1, 1, 1) + + @classmethod + def __parse(cls, v): + try: + # allow floats, but round them down + return int(float(v)) + except (TypeError, ValueError): + return 0 + + def show(self, component, editable=True): + super().show(component, editable) + with self.ignore_changes(): + self.field_x.set_value(self.__parse(component.amounts[0] if len(component.amounts) >= 1 else 0)) + self.field_y.set_value(self.__parse(component.amounts[1] if len(component.amounts) >= 2 else 0)) + + def collect_value(self): + return [int(self.field_x.get_value()), int(self.field_y.get_value())] + + @classmethod + def left_label(cls, component): + return _("Mouse scroll") + + @classmethod + def right_label(cls, component): + x = y = 0 + x = cls.__parse(component.amounts[0] if len(component.amounts) >= 1 else 0) + y = cls.__parse(component.amounts[1] if len(component.amounts) >= 2 else 0) + return f"{x}, {y}" + + +class MouseClickUI(ActionUI): + CLASS = diversion.MouseClick + MIN_VALUE = 1 + MAX_VALUE = 9 + BUTTONS = list(buttons.keys()) + ACTIONS = [CLICK, DEPRESS, RELEASE] + + def create_widgets(self): + self.widgets = {} + self.label = Gtk.Label( + label=_("Simulate a mouse click.\nOn Wayland requires write access to /dev/uinput."), + halign=Gtk.Align.CENTER, + justify=Gtk.Justification.CENTER, + ) + self.widgets[self.label] = (0, 0, 4, 1) + self.label_b = Gtk.Label(label=_("Button"), halign=Gtk.Align.END, valign=Gtk.Align.CENTER, hexpand=True) + self.label_c = Gtk.Label( + label=_("Action (and Count, if click)"), halign=Gtk.Align.END, valign=Gtk.Align.CENTER, hexpand=True + ) + self.field_b = CompletionEntry(self.BUTTONS) + self.field_c = Gtk.SpinButton.new_with_range(self.MIN_VALUE, self.MAX_VALUE, 1) + self.field_d = CompletionEntry(self.ACTIONS) + for f in [self.field_b, self.field_c]: + f.set_halign(Gtk.Align.CENTER) + f.set_valign(Gtk.Align.START) + self.field_b.connect(GtkSignal.CHANGED.value, self._on_update) + self.field_c.connect(GtkSignal.CHANGED.value, self._on_update) + self.field_d.connect(GtkSignal.CHANGED.value, self._on_update) + self.widgets[self.label_b] = (0, 1, 1, 1) + self.widgets[self.field_b] = (1, 1, 1, 1) + self.widgets[self.label_c] = (2, 1, 1, 1) + self.widgets[self.field_c] = (4, 1, 1, 1) + self.widgets[self.field_d] = (3, 1, 1, 1) + + def show(self, component, editable=True): + super().show(component, editable) + with self.ignore_changes(): + self.field_b.set_text(component.button) + if isinstance(component.count, int): + self.field_c.set_value(component.count) + self.field_d.set_text(CLICK) + else: + self.field_c.set_value(1) + self.field_d.set_text(component.count) + + def collect_value(self): + b, c, d = self.field_b.get_text(), int(self.field_c.get_value()), self.field_d.get_text() + if b not in self.BUTTONS: + b = "unknown" + if d != CLICK: + c = d + return [b, c] + + @classmethod + def left_label(cls, component): + return _("Mouse click") + + @classmethod + def right_label(cls, component): + return f'{component.button} ({"x" if isinstance(component.count, int) else ""}{component.count})' + + +class ExecuteUI(ActionUI): + CLASS = diversion.Execute + + def create_widgets(self): + self.widgets = {} + self.label = Gtk.Label( + label=_("Execute a command with arguments."), halign=Gtk.Align.CENTER, justify=Gtk.Justification.CENTER + ) + self.widgets[self.label] = (0, 0, 5, 1) + self.fields = [] + self.add_btn = Gtk.Button(label=_("Add argument"), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) + self.del_btns = [] + self.add_btn.connect(GtkSignal.CLICKED.value, self._clicked_add) + self.widgets[self.add_btn] = (1, 1, 1, 1) + + def _create_field(self): + field_entry = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) + field_entry.set_size_request(150, 0) + field_entry.connect(GtkSignal.CHANGED.value, self._on_update) + self.fields.append(field_entry) + self.widgets[field_entry] = (len(self.fields) - 1, 1, 1, 1) + return field_entry + + def _create_del_btn(self): + btn = Gtk.Button(label=_("Delete"), halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) + btn.set_size_request(150, 0) + self.del_btns.append(btn) + self.widgets[btn] = (len(self.del_btns) - 1, 2, 1, 1) + btn.connect(GtkSignal.CLICKED.value, self._clicked_del, len(self.del_btns) - 1) + return btn + + def _clicked_add(self, *_args): + self.component.__init__(self.collect_value() + [""], warn=False) + self.show(self.component, editable=True) + self.fields[len(self.component.args) - 1].grab_focus() + + def _clicked_del(self, _btn, pos): + v = self.collect_value() + v.pop(pos) + self.component.__init__(v, warn=False) + self.show(self.component, editable=True) + self._on_update_callback() + + def show(self, component, editable=True): + n = len(component.args) + while len(self.fields) < n: + self._create_field() + self._create_del_btn() + for i in range(n): + field_entry = self.fields[i] + with self.ignore_changes(): + field_entry.set_text(component.args[i]) + self.del_btns[i].show() + self.widgets[self.add_btn] = (n + 1, 1, 1, 1) + super().show(component, editable) + for i in range(n, len(self.fields)): + self.fields[i].hide() + self.del_btns[i].hide() + self.add_btn.set_valign(Gtk.Align.END if n >= 1 else Gtk.Align.CENTER) + + def collect_value(self): + return [f.get_text() for f in self.fields if f.get_visible()] + + @classmethod + def left_label(cls, component): + return _("Execute") + + @classmethod + def right_label(cls, component): + return " ".join([shlex_quote(a) for a in component.args]) diff --git a/lib/solaar/ui/rule_base.py b/lib/solaar/ui/rule_base.py new file mode 100644 index 00000000..1fcc5dcd --- /dev/null +++ b/lib/solaar/ui/rule_base.py @@ -0,0 +1,113 @@ +## Copyright (C) Solaar Contributors +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import abc + +from contextlib import contextmanager as contextlib_contextmanager +from typing import Any +from typing import Callable + +from gi.repository import Gtk +from logitech_receiver import diversion + + +def norm(s): + return s.replace("_", "").replace(" ", "").lower() + + +class CompletionEntry(Gtk.Entry): + def __init__(self, values, *args, **kwargs): + super().__init__(*args, **kwargs) + CompletionEntry.add_completion_to_entry(self, values) + + @classmethod + def add_completion_to_entry(cls, entry, values): + completion = entry.get_completion() + if not completion: + liststore = Gtk.ListStore(str) + completion = Gtk.EntryCompletion() + completion.set_model(liststore) + completion.set_match_func(lambda completion, key, it: norm(key) in norm(completion.get_model()[it][0])) + completion.set_text_column(0) + entry.set_completion(completion) + else: + liststore = completion.get_model() + liststore.clear() + for v in sorted(set(values), key=str.casefold): + liststore.append((v,)) + + +class RuleComponentUI(abc.ABC): + CLASS = diversion.RuleComponent + + def __init__(self, panel, on_update: Callable = None): + self.panel = panel + self.widgets = {} # widget -> coord. in grid + self.component = None + self._ignore_changes = 0 + self._on_update_callback = (lambda: None) if on_update is None else on_update + self.create_widgets() + + @abc.abstractmethod + def create_widgets(self) -> dict: + pass + + def show(self, component, editable=True): + self._show_widgets(editable) + self.component = component + + @abc.abstractmethod + def collect_value(self) -> Any: + pass + + @contextlib_contextmanager + def ignore_changes(self): + self._ignore_changes += 1 + yield None + self._ignore_changes -= 1 + + def _on_update(self, *_args): + if not self._ignore_changes and self.component is not None: + value = self.collect_value() + self.component.__init__(value, warn=False) + self._on_update_callback() + return value + return None + + def _show_widgets(self, editable): + self._remove_panel_items() + for widget, coord in self.widgets.items(): + self.panel.attach(widget, *coord) + widget.set_sensitive(editable) + widget.show() + + @classmethod + def left_label(cls, component) -> str: + return type(component).__name__ + + @classmethod + def right_label(cls, _component) -> str: + return "" + + @classmethod + def icon_name(cls) -> str: + return "" + + def _remove_panel_items(self): + for c in self.panel.get_children(): + self.panel.remove(c) + + def update_devices(self): # noqa: B027 + pass diff --git a/lib/solaar/ui/rule_conditions.py b/lib/solaar/ui/rule_conditions.py new file mode 100644 index 00000000..1f88c58d --- /dev/null +++ b/lib/solaar/ui/rule_conditions.py @@ -0,0 +1,615 @@ +## Copyright (C) Solaar Contributors +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from dataclasses import dataclass +from enum import Enum + +from gi.repository import Gtk +from logitech_receiver import diversion +from logitech_receiver.diversion import Key +from logitech_receiver.hidpp20 import SupportedFeature +from logitech_receiver.special_keys import CONTROL + +from solaar.i18n import _ +from solaar.ui.rule_base import CompletionEntry +from solaar.ui.rule_base import RuleComponentUI + + +class GtkSignal(Enum): + CHANGED = "changed" + CLICKED = "clicked" + NOTIFY_ACTIVE = "notify::active" + TOGGLED = "toggled" + VALUE_CHANGED = "value-changed" + + +class ConditionUI(RuleComponentUI): + CLASS = diversion.Condition + + @classmethod + def icon_name(cls): + return "dialog-question" + + +class ProcessUI(ConditionUI): + CLASS = diversion.Process + + def create_widgets(self): + self.widgets = {} + self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True, justify=Gtk.Justification.CENTER) + self.label.set_text(_("X11 active process. For use in X11 only.")) + self.widgets[self.label] = (0, 0, 1, 1) + self.field = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) + self.field.set_size_request(600, 0) + self.field.connect(GtkSignal.CHANGED.value, self._on_update) + self.widgets[self.field] = (0, 1, 1, 1) + + def show(self, component, editable=True): + super().show(component, editable) + 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 _("Process") + + @classmethod + def right_label(cls, component): + return str(component.process) + + +class MouseProcessUI(ConditionUI): + CLASS = diversion.MouseProcess + + def create_widgets(self): + self.widgets = {} + self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True, justify=Gtk.Justification.CENTER) + self.label.set_text(_("X11 mouse process. For use in X11 only.")) + self.widgets[self.label] = (0, 0, 1, 1) + self.field = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) + self.field.set_size_request(600, 0) + self.field.connect(GtkSignal.CHANGED.value, self._on_update) + self.widgets[self.field] = (0, 1, 1, 1) + + def show(self, component, editable=True): + super().show(component, editable) + 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 = diversion.Feature + FEATURES_WITH_DIVERSION = [ + str(SupportedFeature.CROWN), + str(SupportedFeature.THUMB_WHEEL), + str(SupportedFeature.LOWRES_WHEEL), + str(SupportedFeature.HIRES_WHEEL), + str(SupportedFeature.GESTURE_2), + str(SupportedFeature.REPROG_CONTROLS_V4), + str(SupportedFeature.GKEY), + str(SupportedFeature.MKEYS), + str(SupportedFeature.MR), + ] + + def create_widgets(self): + self.widgets = {} + self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True, justify=Gtk.Justification.CENTER) + self.label.set_text(_("Feature name of notification triggering rule processing.")) + self.widgets[self.label] = (0, 0, 1, 1) + self.field = Gtk.ComboBoxText.new_with_entry() + self.field.append("", "") + for feature in self.FEATURES_WITH_DIVERSION: + self.field.append(feature, feature) + self.field.set_valign(Gtk.Align.CENTER) + self.field.set_size_request(600, 0) + self.field.connect(GtkSignal.CHANGED.value, self._on_update) + all_features = [str(f) for f in SupportedFeature] + CompletionEntry.add_completion_to_entry(self.field.get_child(), all_features) + self.widgets[self.field] = (0, 1, 1, 1) + + def show(self, component, editable=True): + super().show(component, editable) + with self.ignore_changes(): + f = str(component.feature) if component.feature else "" + self.field.set_active_id(f) + if f not in self.FEATURES_WITH_DIVERSION: + self.field.get_child().set_text(f) + + def collect_value(self): + return (self.field.get_active_text() or "").strip() + + def _on_update(self, *args): + super()._on_update(*args) + icon = "dialog-warning" if not self.component.feature else "" + self.field.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) + + @classmethod + def left_label(cls, component): + return _("Feature") + + @classmethod + def right_label(cls, component): + return f"{str(component.feature)} ({int(component.feature or 0):04X})" + + +class ReportUI(ConditionUI): + CLASS = diversion.Report + MIN_VALUE = -1 # for invalid values + MAX_VALUE = 15 + + def create_widgets(self): + self.widgets = {} + self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True, justify=Gtk.Justification.CENTER) + self.label.set_text(_("Report number of notification triggering rule processing.")) + self.widgets[self.label] = (0, 0, 1, 1) + self.field = Gtk.SpinButton.new_with_range(self.MIN_VALUE, self.MAX_VALUE, 1) + self.field.set_halign(Gtk.Align.CENTER) + self.field.set_valign(Gtk.Align.CENTER) + self.field.set_hexpand(True) + self.field.connect(GtkSignal.CHANGED.value, self._on_update) + self.widgets[self.field] = (0, 1, 1, 1) + + def show(self, component, editable=True): + super().show(component, editable) + with self.ignore_changes(): + self.field.set_value(component.report) + + def collect_value(self): + return int(self.field.get_value()) + + @classmethod + def left_label(cls, component): + return _("Report") + + @classmethod + def right_label(cls, component): + return str(component.report) + + +class ModifiersUI(ConditionUI): + CLASS = diversion.Modifiers + + def create_widgets(self): + self.widgets = {} + self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True, justify=Gtk.Justification.CENTER) + self.label.set_text(_("Active keyboard modifiers. Not always available in Wayland.")) + self.widgets[self.label] = (0, 0, 5, 1) + self.labels = {} + self.switches = {} + for i, m in enumerate(diversion.MODIFIERS): + switch = Gtk.Switch(halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) + label = Gtk.Label(label=m, halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) + self.widgets[label] = (i, 1, 1, 1) + self.widgets[switch] = (i, 2, 1, 1) + self.labels[m] = label + self.switches[m] = switch + switch.connect(GtkSignal.NOTIFY_ACTIVE.value, self._on_update) + + def show(self, component, editable=True): + super().show(component, editable) + with self.ignore_changes(): + for m in diversion.MODIFIERS: + self.switches[m].set_active(m in component.modifiers) + + def collect_value(self): + return [m for m, s in self.switches.items() if s.get_active()] + + @classmethod + def left_label(cls, component): + return _("Modifiers") + + @classmethod + def right_label(cls, component): + return "+".join(component.modifiers) or "None" + + +class KeyUI(ConditionUI): + CLASS = diversion.Key + KEY_NAMES = map(str, CONTROL) + + def create_widgets(self): + self.widgets = {} + self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True, justify=Gtk.Justification.CENTER) + self.label.set_text( + _( + "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert keys and buttons." + ) + ) + self.widgets[self.label] = (0, 0, 5, 1) + self.key_field = CompletionEntry(self.KEY_NAMES, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) + self.key_field.set_size_request(600, 0) + self.key_field.connect(GtkSignal.CHANGED.value, self._on_update) + self.widgets[self.key_field] = (0, 1, 2, 1) + self.action_pressed_radio = Gtk.RadioButton.new_with_label_from_widget(None, _("Key down")) + self.action_pressed_radio.connect(GtkSignal.TOGGLED.value, self._on_update, Key.DOWN) + self.widgets[self.action_pressed_radio] = (2, 1, 1, 1) + self.action_released_radio = Gtk.RadioButton.new_with_label_from_widget(self.action_pressed_radio, _("Key up")) + self.action_released_radio.connect(GtkSignal.TOGGLED.value, self._on_update, Key.UP) + self.widgets[self.action_released_radio] = (3, 1, 1, 1) + + def show(self, component, editable=True): + super().show(component, editable) + with self.ignore_changes(): + 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): + 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 or not self.component.action else "" + self.key_field.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) + + @classmethod + def left_label(cls, component): + return _("Key") + + @classmethod + def right_label(cls, component): + return f"{str(component.key)} ({int(component.key):04X}) ({_(component.action)})" if component.key else "None" + + +class KeyIsDownUI(ConditionUI): + CLASS = diversion.KeyIsDown + KEY_NAMES = map(str, CONTROL) + + def create_widgets(self): + self.widgets = {} + self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True, justify=Gtk.Justification.CENTER) + self.label.set_text( + _( + "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert keys and buttons." + ) + ) + self.widgets[self.label] = (0, 0, 5, 1) + self.key_field = CompletionEntry(self.KEY_NAMES, halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, hexpand=True) + self.key_field.set_size_request(600, 0) + self.key_field.connect(GtkSignal.CHANGED.value, self._on_update) + self.widgets[self.key_field] = (0, 1, 1, 1) + + def show(self, component, editable=True): + super().show(component, editable) + with self.ignore_changes(): + self.key_field.set_text(str(component.key) if self.component.key else "") + + def collect_value(self): + return self.key_field.get_text() + + def _on_update(self, *args): + super()._on_update(*args) + icon = "dialog-warning" if not self.component.key else "" + self.key_field.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) + + @classmethod + def left_label(cls, component): + return _("KeyIsDown") + + @classmethod + def right_label(cls, component): + return f"{str(component.key)} ({int(component.key):04X})" if component.key else "None" + + +class TestUI(ConditionUI): + CLASS = diversion.Test + + def create_widgets(self): + self.widgets = {} + self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True, justify=Gtk.Justification.CENTER) + self.label.set_text(_("Test condition on notification triggering rule processing.")) + self.widgets[self.label] = (0, 0, 4, 1) + lbl = Gtk.Label(label=_("Test"), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=False, vexpand=False) + self.widgets[lbl] = (0, 1, 1, 1) + lbl = Gtk.Label(label=_("Parameter"), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=False, vexpand=False) + self.widgets[lbl] = (2, 1, 1, 1) + + self.test = Gtk.ComboBoxText.new_with_entry() + self.test.append("", "") + for t in diversion.TESTS: + self.test.append(t, t) + self.test.set_halign(Gtk.Align.END) + self.test.set_valign(Gtk.Align.CENTER) + self.test.set_hexpand(False) + self.test.set_size_request(300, 0) + CompletionEntry.add_completion_to_entry(self.test.get_child(), diversion.TESTS) + self.test.connect(GtkSignal.CHANGED.value, self._on_update) + self.widgets[self.test] = (1, 1, 1, 1) + + self.parameter = Gtk.Entry(halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) + self.parameter.set_size_request(150, 0) + self.parameter.connect(GtkSignal.CHANGED.value, self._on_update) + self.widgets[self.parameter] = (3, 1, 1, 1) + + def show(self, component, editable=True): + super().show(component, editable) + with self.ignore_changes(): + self.test.set_active_id(component.test) + self.parameter.set_text(str(component.parameter) if component.parameter is not None else "") + if component.test not in diversion.TESTS: + self.test.get_child().set_text(component.test) + self._change_status_icon() + + def collect_value(self): + try: + param = int(self.parameter.get_text()) if self.parameter.get_text() else None + except Exception: + param = self.parameter.get_text() + test = (self.test.get_active_text() or "").strip() + return [test, param] if param is not None else [test] + + def _on_update(self, *args): + super()._on_update(*args) + self._change_status_icon() + + def _change_status_icon(self): + icon = "dialog-warning" if (self.test.get_active_text() or "").strip() not in diversion.TESTS else "" + self.test.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) + + @classmethod + def left_label(cls, component): + return _("Test") + + @classmethod + def right_label(cls, component): + return component.test + (f" {repr(component.parameter)}" if component.parameter is not None else "") + + +@dataclass +class TestBytesElement: + id: str + label: str + min: int + max: int + + +@dataclass +class TestBytesMode: + label: str + elements: list + label_fn: callable + + +class TestBytesUI(ConditionUI): + CLASS = diversion.TestBytes + + _common_elements = [ + TestBytesElement("begin", _("begin (inclusive)"), 0, 16), + TestBytesElement("end", _("end (exclusive)"), 0, 16), + ] + + _global_min = -(2**31) + _global_max = 2**31 - 1 + + _modes = { + "range": TestBytesMode( + _("range"), + _common_elements + + [ + TestBytesElement("minimum", _("minimum"), _global_min, _global_max), # uint32 + TestBytesElement("maximum", _("maximum"), _global_min, _global_max), + ], + lambda e: _("bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" % {str(i): v for i, v in enumerate(e)}), + ), + "mask": TestBytesMode( + _("mask"), + _common_elements + [TestBytesElement("mask", _("mask"), _global_min, _global_max)], + lambda e: _("bytes %(0)d to %(1)d, mask %(2)d" % {str(i): v for i, v in enumerate(e)}), + ), + } + + def create_widgets(self): + self.fields = {} + self.field_labels = {} + self.widgets = {} + self.label = Gtk.Label(valign=Gtk.Align.CENTER, hexpand=True, justify=Gtk.Justification.CENTER) + self.label.set_text(_("Bit or range test on bytes in notification message triggering rule processing.")) + self.widgets[self.label] = (0, 0, 5, 1) + col = 0 + mode_col = 2 + self.mode_field = Gtk.ComboBox.new_with_model(Gtk.ListStore(str, str)) + mode_renderer = Gtk.CellRendererText() + self.mode_field.set_id_column(0) + self.mode_field.pack_start(mode_renderer, True) + self.mode_field.add_attribute(mode_renderer, "text", 1) + self.widgets[self.mode_field] = (mode_col, 2, 1, 1) + mode_label = Gtk.Label(label=_("type"), margin_top=20) + self.widgets[mode_label] = (mode_col, 1, 1, 1) + for mode_id, mode in TestBytesUI._modes.items(): + self.mode_field.get_model().append([mode_id, mode.label]) + for element in mode.elements: + if element.id not in self.fields: + field = Gtk.SpinButton.new_with_range(element.min, element.max, 1) + field.set_value(0) + field.set_size_request(150, 0) + field.connect(GtkSignal.VALUE_CHANGED.value, self._on_update) + label = Gtk.Label(label=element.label, margin_top=20) + self.fields[element.id] = field + self.field_labels[element.id] = label + self.widgets[label] = (col, 1, 1, 1) + self.widgets[field] = (col, 2, 1, 1) + col += 1 if col != mode_col - 1 else 2 + self.mode_field.connect(GtkSignal.CHANGED.value, lambda cb: (self._on_update(), self._only_mode(cb.get_active_id()))) + self.mode_field.set_active_id("range") + + def show(self, component, editable=True): + super().show(component, editable) + + with self.ignore_changes(): + mode_id = {3: "mask", 4: "range"}.get(len(component.test), None) + self._only_mode(mode_id) + if not mode_id: + return + self.mode_field.set_active_id(mode_id) + if mode_id: + mode = TestBytesUI._modes[mode_id] + for i, element in enumerate(mode.elements): + self.fields[element.id].set_value(component.test[i]) + + def collect_value(self): + mode_id = self.mode_field.get_active_id() + return [self.fields[element.id].get_value_as_int() for element in TestBytesUI._modes[mode_id].elements] + + def _only_mode(self, mode_id): + if not mode_id: + return + keep = {element.id for element in TestBytesUI._modes[mode_id].elements} + for element_id, f in self.fields.items(): + visible = element_id in keep + f.set_visible(visible) + self.field_labels[element_id].set_visible(visible) + + def _on_update(self, *args): + super()._on_update(*args) + if not self.component: + return + begin, end, *etc = self.component.test + icon = "dialog-warning" if end <= begin else "" + self.fields["end"].set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) + if len(self.component.test) == 4: + *etc, minimum, maximum = self.component.test + icon = "dialog-warning" if maximum < minimum else "" + self.fields["maximum"].set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) + + @classmethod + def left_label(cls, component): + return _("Test bytes") + + @classmethod + def right_label(cls, component): + mode_id = {3: "mask", 4: "range"}.get(len(component.test), None) + if not mode_id: + return str(component.test) + return TestBytesUI._modes[mode_id].label_fn(component.test) + + +class MouseGestureUI(ConditionUI): + CLASS = diversion.MouseGesture + MOUSE_GESTURE_NAMES = [ + "Mouse Up", + "Mouse Down", + "Mouse Left", + "Mouse Right", + "Mouse Up-left", + "Mouse Up-right", + "Mouse Down-left", + "Mouse Down-right", + ] + MOVE_NAMES = list(map(str, CONTROL)) + MOUSE_GESTURE_NAMES + + def create_widgets(self): + self.widgets = {} + self.fields = [] + self.label = Gtk.Label( + label=_("Mouse gesture with optional initiating button followed by zero or more mouse movements."), + halign=Gtk.Align.CENTER, + justify=Gtk.Justification.CENTER, + ) + self.widgets[self.label] = (0, 0, 5, 1) + self.del_btns = [] + self.add_btn = Gtk.Button(label=_("Add movement"), halign=Gtk.Align.CENTER, valign=Gtk.Align.END, hexpand=True) + self.add_btn.connect(GtkSignal.CLICKED.value, self._clicked_add) + self.widgets[self.add_btn] = (1, 1, 1, 1) + + def _create_field(self): + field = Gtk.ComboBoxText.new_with_entry() + for g in self.MOUSE_GESTURE_NAMES: + field.append(g, g) + CompletionEntry.add_completion_to_entry(field.get_child(), self.MOVE_NAMES) + field.connect(GtkSignal.CHANGED.value, self._on_update) + self.fields.append(field) + self.widgets[field] = (len(self.fields) - 1, 1, 1, 1) + return field + + def _create_del_btn(self): + btn = Gtk.Button(label=_("Delete"), halign=Gtk.Align.CENTER, valign=Gtk.Align.START, hexpand=True) + self.del_btns.append(btn) + self.widgets[btn] = (len(self.del_btns) - 1, 2, 1, 1) + btn.connect(GtkSignal.CLICKED.value, self._clicked_del, len(self.del_btns) - 1) + return btn + + def _clicked_add(self, _btn): + self.component.__init__(self.collect_value() + [""], warn=False) + self.show(self.component, editable=True) + self.fields[len(self.component.movements) - 1].grab_focus() + + def _clicked_del(self, _btn, pos): + v = self.collect_value() + v.pop(pos) + self.component.__init__(v, warn=False) + self.show(self.component, editable=True) + self._on_update_callback() + + def _on_update(self, *args): + super()._on_update(*args) + for i, f in enumerate(self.fields): + if f.get_visible(): + icon = ( + "dialog-warning" + if i < len(self.component.movements) and self.component.movements[i] not in self.MOVE_NAMES + else "" + ) + f.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) + + def show(self, component, editable=True): + n = len(component.movements) + while len(self.fields) < n: + self._create_field() + self._create_del_btn() + self.widgets[self.add_btn] = (n + 1, 1, 1, 1) + super().show(component, editable) + for i in range(n): + field = self.fields[i] + with self.ignore_changes(): + field.get_child().set_text(component.movements[i]) + field.show_all() + self.del_btns[i].show() + for i in range(n, len(self.fields)): + self.fields[i].hide() + self.del_btns[i].hide() + self.add_btn.set_valign(Gtk.Align.END if n >= 1 else Gtk.Align.CENTER) + + def collect_value(self): + return [f.get_active_text().strip() for f in self.fields if f.get_visible()] + + @classmethod + def left_label(cls, component): + return _("Mouse Gesture") + + @classmethod + def right_label(cls, component): + if len(component.movements) == 0: + return "No-op" + else: + return " -> ".join(component.movements) diff --git a/lib/solaar/ui/tray.py b/lib/solaar/ui/tray.py index 298ba28e..d0c77867 100644 --- a/lib/solaar/ui/tray.py +++ b/lib/solaar/ui/tray.py @@ -1,6 +1,5 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -16,57 +15,52 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import logging import os -from logging import DEBUG as _DEBUG -from logging import getLogger -from time import time as _timestamp +from enum import Enum +from time import time + +import gi + +from gi.repository import GLib +from gi.repository import Gtk +from gi.repository.Gdk import ScrollDirection import solaar.gtk as gtk -from gi.repository import GLib, Gtk -from gi.repository.Gdk import ScrollDirection -from logitech_receiver.status import KEYS as _K from solaar import NAME from solaar.i18n import _ -from . import icons as _icons -from .about import show_window as _show_about_window -from .window import popup as _window_popup -from .window import toggle as _window_toggle +from . import action +from . import icons +from . import window +from .about import about -_log = getLogger(__name__) -del getLogger - -# -# constants -# +logger = logging.getLogger(__name__) _TRAY_ICON_SIZE = 48 _MENU_ICON_SIZE = Gtk.IconSize.LARGE_TOOLBAR -# -# -# + +class GtkSignal(Enum): + ACTIVATE = "activate" + SCROLL_EVENT = "scroll-event" def _create_menu(quit_handler): + # per-device menu entries will be generated as-needed menu = Gtk.Menu() - # per-device menu entries will be generated as-needed - - no_receiver = Gtk.MenuItem.new_with_label(_('No supported device found')) + no_receiver = Gtk.MenuItem.new_with_label(_("No supported device found")) no_receiver.set_sensitive(False) menu.append(no_receiver) menu.append(Gtk.SeparatorMenuItem.new()) - from .action import make - menu.append(make('help-about', _('About %s') % NAME, _show_about_window, stock_id='help-about').create_menu_item()) - menu.append(make('application-exit', _('Quit %s') % NAME, quit_handler, stock_id='application-exit').create_menu_item()) - del make + menu.append(action.make_image_menu_item(_("About %s") % NAME, "help-about", about.show)) + menu.append(action.make_image_menu_item(_("Quit %s") % NAME, "application-exit", quit_handler)) menu.show_all() - return menu @@ -84,20 +78,17 @@ def _scroll(tray_icon, event, direction=None): # ignore all other directions return - if sum(map(lambda i: i[1] is not None, _devices_info)) < 2: # don't bother even trying to scroll if less than two devices + # don't bother even trying to scroll if less than two devices + if sum(map(lambda i: i[1] is not None, _devices_info)) < 2: return - # scroll events come way too fast (at least 5-6 at once) - # so take a little break between them + # scroll events come way too fast (at least 5-6 at once) so take a little break between them global _last_scroll - now = now or _timestamp() + now = now or time() if now - _last_scroll < 0.33: # seconds return _last_scroll = now - # if _log.isEnabledFor(_DEBUG): - # _log.debug("scroll direction %s", direction) - global _picked_device candidate = None @@ -141,28 +132,27 @@ def _scroll(tray_icon, event, direction=None): _picked_device = None _picked_device = candidate or _picked_device - if _log.isEnabledFor(_DEBUG): - _log.debug('scroll: picked %s', _picked_device) + logger.debug("scroll: picked %s", _picked_device) _update_tray_icon() try: - import gi try: - gi.require_version('AyatanaAppIndicator3', '0.1') + gi.require_version("AyatanaAppIndicator3", "0.1") from gi.repository import AyatanaAppIndicator3 as AppIndicator3 + ayatana_appindicator_found = True except ValueError: try: - gi.require_version('AppIndicator3', '0.1') + gi.require_version("AppIndicator3", "0.1") from gi.repository import AppIndicator3 - ayatana_appindicator_found = False - except ValueError: - # treat unavailable versions the same as unavailable packages - raise ImportError - if _log.isEnabledFor(_DEBUG): - _log.debug('using %sAppIndicator3' % ('Ayatana ' if ayatana_appindicator_found else '')) + ayatana_appindicator_found = False + except ValueError as exc: + # treat unavailable versions the same as unavailable packages + raise ImportError from exc + + logger.debug(f"using {'Ayatana ' if ayatana_appindicator_found else ''}AppIndicator3") # Defense against AppIndicator3 bug that treats files in current directory as icon files # https://bugs.launchpad.net/ubuntu/+source/libappindicator/+bug/1363277 @@ -176,17 +166,17 @@ try: return icon_info.get_filename() if icon_info else icon_name def _create(menu): - _icons._init_icon_paths() + icons._init_icon_paths() ind = AppIndicator3.Indicator.new( - 'indicator-solaar', _icon_file(_icons.TRAY_INIT), AppIndicator3.IndicatorCategory.HARDWARE + "indicator-solaar", _icon_file(icons.TRAY_INIT), AppIndicator3.IndicatorCategory.HARDWARE ) ind.set_title(NAME) ind.set_status(AppIndicator3.IndicatorStatus.ACTIVE) - # ind.set_attention_icon_full(_icon_file(_icons.TRAY_ATTENTION), '') # works poorly for XFCE 16 - # ind.set_label(NAME, NAME) + # ind.set_attention_icon_full(_icon_file(icons.TRAY_ATTENTION), '') # works poorly for XFCE 16 + # ind.set_label(NAME.lower(), NAME.lower()) ind.set_menu(menu) - ind.connect('scroll-event', _scroll) + ind.connect(GtkSignal.SCROLL_EVENT.value, _scroll) return ind @@ -197,48 +187,42 @@ try: indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE) def _update_tray_icon(): - if _picked_device and gtk.battery_icons_style != 'solaar': - _ignore, _ignore, name, device_status = _picked_device - battery_level = device_status.get(_K.BATTERY_LEVEL) - battery_charging = device_status.get(_K.BATTERY_CHARGING) - tray_icon_name = _icons.battery(battery_level, battery_charging) - - description = '%s: %s' % (name, device_status.to_string()) + if _picked_device and gtk.battery_icons_style != "solaar": + _ignore, _ignore, name, device = _picked_device + battery_level = device.battery_info.level if device.battery_info is not None else None + battery_charging = device.battery_info.charging() if device.battery_info is not None else None + tray_icon_name = icons.battery(battery_level, battery_charging) + description = f"{name}: {device.status_string()}" else: # there may be a receiver, but no peripherals - tray_icon_name = _icons.TRAY_OKAY if _devices_info else _icons.TRAY_INIT + tray_icon_name = icons.TRAY_OKAY if _devices_info else icons.TRAY_INIT description_lines = _generate_description_lines() - description = '\n'.join(description_lines).rstrip('\n') + description = "\n".join(description_lines).rstrip("\n") - # icon_file = _icons.icon_file(icon_name, _TRAY_ICON_SIZE) + # icon_file = icons.icon_file(icon_name, _TRAY_ICON_SIZE) _icon.set_icon_full(_icon_file(tray_icon_name), description) - def _update_menu_icon(image_widget, icon_name): - image_widget.set_from_icon_name(icon_name, _MENU_ICON_SIZE) - # icon_file = _icons.icon_file(icon_name, _MENU_ICON_SIZE) - # image_widget.set_from_file(icon_file) - # image_widget.set_pixel_size(_TRAY_ICON_SIZE) - def attention(reason=None): if _icon.get_status() != AppIndicator3.IndicatorStatus.ATTENTION: - # _icon.set_attention_icon_full(_icon_file(_icons.TRAY_ATTENTION), reason or '') # works poorly for XFCe 16 + # _icon.set_attention_icon_full(_icon_file(icons.TRAY_ATTENTION), reason or '') # works poorly for XFCe 16 _icon.set_status(AppIndicator3.IndicatorStatus.ATTENTION) GLib.timeout_add(10 * 1000, _icon.set_status, AppIndicator3.IndicatorStatus.ACTIVE) except ImportError: - - if _log.isEnabledFor(_DEBUG): - _log.debug('using StatusIcon') + logger.debug("using StatusIcon") def _create(menu): - icon = Gtk.StatusIcon.new_from_icon_name(_icons.TRAY_INIT) - icon.set_name(NAME) + icon = Gtk.StatusIcon.new_from_icon_name(icons.TRAY_INIT) + icon.set_name(NAME.lower()) icon.set_title(NAME) icon.set_tooltip_text(NAME) - icon.connect('activate', _window_toggle) - icon.connect('scroll-event', _scroll) - icon.connect('popup-menu', lambda icon, button, time: menu.popup(None, None, icon.position_menu, icon, button, time)) + icon.connect(GtkSignal.ACTIVATE.value, window.toggle) + icon.connect(GtkSignal.SCROLL_EVENT.value, _scroll) + icon.connect( + "popup-menu", + lambda icon, button, time: menu.popup(None, None, icon.position_menu, icon, button, time), + ) return icon @@ -250,28 +234,25 @@ except ImportError: def _update_tray_icon(): tooltip_lines = _generate_tooltip_lines() - tooltip = '\n'.join(tooltip_lines).rstrip('\n') + tooltip = "\n".join(tooltip_lines).rstrip("\n") _icon.set_tooltip_markup(tooltip) - if _picked_device and gtk.battery_icons_style != 'solaar': - _ignore, _ignore, name, device_status = _picked_device - battery_level = device_status.get(_K.BATTERY_LEVEL) - battery_charging = device_status.get(_K.BATTERY_CHARGING) - tray_icon_name = _icons.battery(battery_level, battery_charging) + if _picked_device and gtk.battery_icons_style != "solaar": + _ignore, _ignore, name, device = _picked_device + battery_level = device.battery_info.level if device.battery_info is not None else None + battery_charging = device.battery_info.charging() if device.battery_info is not None else None + tray_icon_name = icons.battery(battery_level, battery_charging) else: # there may be a receiver, but no peripherals - tray_icon_name = _icons.TRAY_OKAY if _devices_info else _icons.TRAY_ATTENTION + tray_icon_name = icons.TRAY_OKAY if _devices_info else icons.TRAY_ATTENTION _icon.set_from_icon_name(tray_icon_name) - def _update_menu_icon(image_widget, icon_name): - image_widget.set_from_icon_name(icon_name, _MENU_ICON_SIZE) - _icon_before_attention = None def _blink(count): global _icon_before_attention if count % 2: - _icon.set_from_icon_name(_icons.TRAY_ATTENTION) + _icon.set_from_icon_name(icons.TRAY_ATTENTION) else: _icon.set_from_icon_name(_icon_before_attention) @@ -287,14 +268,9 @@ except ImportError: GLib.idle_add(_blink, 9) -# -# -# - - def _generate_tooltip_lines(): if not _devices_info: - yield '%s: ' % NAME + _('no receiver') + yield f"{NAME}: " + _("no receiver") return yield from _generate_description_lines() @@ -302,25 +278,25 @@ def _generate_tooltip_lines(): def _generate_description_lines(): if not _devices_info: - yield _('no receiver') + yield _("no receiver") return - for _ignore, number, name, status in _devices_info: + for _ignore, number, name, device in _devices_info: if number is None: # receiver continue - p = status.to_string() + p = device.status_string() if p: # does it have any properties to print? - yield '%s' % name - if status: - yield '\t%s' % p + yield f"{name}" + if device.online: + yield f"\t{p}" else: - yield '\t%s (' % p + _('offline') + ')' + yield f"\t{p} (" + _("offline") + ")" else: - if status: - yield '%s (' % name + _('no status') + ')' + if device.online: + yield f"{name} (" + _("no status") + ")" else: - yield '%s (' % name + _('offline') + ')' + yield f"{name} (" + _("offline") + ")" def _pick_device_with_lowest_battery(): @@ -333,23 +309,16 @@ def _pick_device_with_lowest_battery(): for info in _devices_info: if info[1] is None: # is receiver continue - level = info[-1].get(_K.BATTERY_LEVEL) - # print ("checking %s -> %s", info, level) + level = info[-1].battery_info.level if info[-1].battery_info is not None else None if level is not None and picked_level > level: picked = info picked_level = level or 0 - if _log.isEnabledFor(_DEBUG): - _log.debug('picked device with lowest battery: %s', picked) + logger.debug("picked device with lowest battery: %s", picked) return picked -# -# -# - - def _add_device(device): assert device @@ -369,14 +338,11 @@ def _add_device(device): break index = index + 1 - new_device_info = (receiver_path, device.number, device.name, device.status) + new_device_info = (receiver_path, device.number, device.name, device) _devices_info.insert(index, new_device_info) - label_prefix = ' ' - new_menu_item = Gtk.ImageMenuItem.new_with_label((label_prefix if device.number else '') + device.name) - new_menu_item.set_image(Gtk.Image()) - new_menu_item.show_all() - new_menu_item.connect('activate', _window_popup, receiver_path, device.number) + label = (" " if device.number else "") + device.name + new_menu_item = action.make_image_menu_item(label, None, window.popup, receiver_path, device.number) _menu.insert(new_menu_item, index) return index @@ -397,17 +363,11 @@ def _remove_device(index): def _add_receiver(receiver): index = len(_devices_info) - new_receiver_info = (receiver.path, None, receiver.name, None) _devices_info.insert(index, new_receiver_info) - - new_menu_item = Gtk.ImageMenuItem.new_with_label(receiver.name) - icon_set = _icons.device_icon_set(receiver.name) - new_menu_item.set_image(Gtk.Image().new_from_icon_name(icon_set.names[0], _MENU_ICON_SIZE)) - new_menu_item.show_all() - new_menu_item.connect('activate', _window_popup, receiver.path) + icon_name = icons.device_icon_name(receiver.name, receiver.kind) + new_menu_item = action.make_image_menu_item(receiver.name, icon_name, window.popup, receiver.path) _menu.insert(new_menu_item, index) - return 0 @@ -423,33 +383,26 @@ def _remove_receiver(receiver): def _update_menu_item(index, device): - if device is None or device.status is None: - _log.warn('updating an inactive device %s, assuming disconnected', device) + if device is None: + logger.warning("updating an inactive device %s, assuming disconnected", device) return None - menu_items = _menu.get_children() menu_item = menu_items[index] - - level = device.status.get(_K.BATTERY_LEVEL) - charging = device.status.get(_K.BATTERY_CHARGING) - icon_name = _icons.battery(level, charging) - - menu_item.set_label((' ' if 0 < device.number <= 6 else '') + device.name + ': ' + device.status.to_string()) - image_widget = menu_item.get_image() + level = device.battery_info.level if device.battery_info is not None else None + charging = device.battery_info.charging() if device.battery_info is not None else None + icon_name = icons.battery(level, charging) + menu_item.label.set_label((" " if 0 < device.number <= 6 else "") + device.name + ": " + device.status_string()) + image_widget = menu_item.icon image_widget.set_sensitive(bool(device.online)) - _update_menu_icon(image_widget, icon_name) + image_widget.set_from_icon_name(icon_name, _MENU_ICON_SIZE) -# -# -# - # for which device to show the battery info in systray, if more than one # it's actually an entry in _devices_info _picked_device = None # cached list of devices and some of their properties -# contains tuples of (receiver path, device number, name, status) +# contains tuples of (receiver path, device number, name, device) _devices_info = [] _menu = None diff --git a/lib/solaar/ui/window.py b/lib/solaar/ui/window.py index d7541640..f2d5ed5f 100644 --- a/lib/solaar/ui/window.py +++ b/lib/solaar/ui/window.py @@ -1,6 +1,5 @@ -# -*- python-mode -*- - ## Copyright (C) 2012-2013 Daniel Pavel +## Copyright (C) 2014-2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -16,32 +15,35 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from logging import DEBUG as _DEBUG -from logging import getLogger +import logging + +from enum import Enum +from enum import IntEnum + +import gi -from gi.repository import Gdk, GLib, Gtk from gi.repository.GObject import TYPE_PYOBJECT -from logitech_receiver import hidpp10 as _hidpp10 -from logitech_receiver.common import NamedInt as _NamedInt -from logitech_receiver.common import NamedInts as _NamedInts -from logitech_receiver.status import KEYS as _K +from logitech_receiver import hidpp10_constants +from logitech_receiver.common import LOGITECH_VENDOR_ID +from logitech_receiver.common import NamedInt + from solaar import NAME -from solaar.i18n import _, ngettext -# from solaar import __version__ as VERSION -from solaar.ui import ui_async as _ui_async +from solaar.i18n import _ +from solaar.i18n import ngettext -from . import action as _action -from . import config_panel as _config_panel -from . import icons as _icons -from .about import show_window as _show_about_window -from .diversion_rules import show_window as _show_diversion_window +from . import action +from . import config_panel +from . import diversion_rules +from . import icons +from .about import about +from .common import ui_async -_log = getLogger(__name__) -del getLogger +gi.require_version("Gdk", "3.0") +from gi.repository import Gdk # NOQA: E402 +from gi.repository import GLib # NOQA: E402 +from gi.repository import Gtk # NOQA: E402 -# -# constants -# +logger = logging.getLogger(__name__) _SMALL_BUTTON_ICON_SIZE = Gtk.IconSize.MENU _NORMAL_BUTTON_ICON_SIZE = Gtk.IconSize.BUTTON @@ -49,22 +51,35 @@ _TREE_ICON_SIZE = Gtk.IconSize.BUTTON _INFO_ICON_SIZE = Gtk.IconSize.LARGE_TOOLBAR _DEVICE_ICON_SIZE = Gtk.IconSize.DND try: - import gi - gi.check_version('3.7.4') + gi.check_version("3.7.4") _CAN_SET_ROW_NONE = None except (ValueError, AttributeError): - _CAN_SET_ROW_NONE = '' + _CAN_SET_ROW_NONE = "" + + +class Column(IntEnum): + """Columns of tree model.""" + + PATH = 0 + NUMBER = 1 + ACTIVE = 2 + NAME = 3 + ICON = 4 + STATUS_TEXT = 5 + STATUS_ICON = 6 + DEVICE = 7 + -# tree model columns -_COLUMN = _NamedInts(PATH=0, NUMBER=1, ACTIVE=2, NAME=3, ICON=4, STATUS_TEXT=5, STATUS_ICON=6, DEVICE=7) _COLUMN_TYPES = (str, int, bool, str, str, str, str, TYPE_PYOBJECT) _TREE_SEPATATOR = (None, 0, False, None, None, None, None, None) assert len(_TREE_SEPATATOR) == len(_COLUMN_TYPES) -assert len(_COLUMN_TYPES) == len(_COLUMN) +assert len(_COLUMN_TYPES) == len(Column) -# -# create UI layout -# + +class GtkSignal(Enum): + CHANGED = "changed" + CLICKED = "clicked" + DELETE_EVENT = "delete-event" def _new_button(label, icon_name=None, icon_size=_NORMAL_BUTTON_ICON_SIZE, tooltip=None, toggle=False, clicked=None): @@ -73,10 +88,10 @@ def _new_button(label, icon_name=None, icon_size=_NORMAL_BUTTON_ICON_SIZE, toolt if icon_name: c.pack_start(Gtk.Image.new_from_icon_name(icon_name, icon_size), True, True, 0) if label: - c.pack_start(Gtk.Label(label), True, True, 0) + c.pack_start(Gtk.Label(label=label), True, True, 0) b.add(c) if clicked is not None: - b.connect('clicked', clicked) + b.connect(GtkSignal.CLICKED.value, clicked) if tooltip: b.set_tooltip_text(tooltip) if not label and icon_size < _NORMAL_BUTTON_ICON_SIZE: @@ -89,15 +104,14 @@ def _create_receiver_panel(): p = Gtk.Box.new(Gtk.Orientation.VERTICAL, 4) p._count = Gtk.Label() - p._count.set_padding(24, 0) - p._count.set_alignment(0, 0.5) + p._count.set_margin_top(24) p.pack_start(p._count, True, True, 0) - p._scanning = Gtk.Label(_('Scanning') + '...') + p._scanning = Gtk.Label(label=_("Scanning") + "...") p._spinner = Gtk.Spinner() bp = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 8) - bp.pack_start(Gtk.Label(' '), True, True, 0) + bp.pack_start(Gtk.Label(label=" "), True, True, 0) bp.pack_start(p._scanning, False, False, 0) bp.pack_end(p._spinner, False, False, 0) p.pack_end(bp, False, False, 0) @@ -112,8 +126,7 @@ def _create_device_panel(): b = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 8) b.set_size_request(10, 28) - b._label = Gtk.Label(label_text) - b._label.set_alignment(0, 0.5) + b._label = Gtk.Label(label=label_text) b._label.set_size_request(170, 10) b.pack_start(b._label, False, False, 0) @@ -121,24 +134,23 @@ def _create_device_panel(): b.pack_start(b._icon, False, False, 0) b._text = Gtk.Label() - b._text.set_alignment(0, 0.5) - b.pack_start(b._text, True, True, 0) + b.pack_start(b._text, False, False, 0) return b - p._battery = _status_line(_('Battery')) + p._battery = _status_line(_("Battery")) p.pack_start(p._battery, False, False, 0) - p._secure = _status_line(_('Wireless Link')) - p._secure._icon.set_from_icon_name('dialog-warning', _INFO_ICON_SIZE) + p._secure = _status_line(_("Wireless Link")) + p._secure._icon.set_from_icon_name("dialog-warning", _INFO_ICON_SIZE) p.pack_start(p._secure, False, False, 0) - p._lux = _status_line(_('Lighting')) + p._lux = _status_line(_("Lighting")) p.pack_start(p._lux, False, False, 0) - # p.pack_start(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL), False, False, 0) # spacer + p.pack_start(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL), False, False, 0) # spacer - p._config = _config_panel.create() + p._config = config_panel.create() p.pack_end(p._config, True, True, 4) return p @@ -151,8 +163,8 @@ def _create_details_panel(): p.set_state_flags(Gtk.StateFlags.ACTIVE, True) p._text = Gtk.Label() - p._text.set_padding(6, 4) - p._text.set_alignment(0, 0) + p._text.set_margin_start(6) + p._text.set_margin_end(4) p._text.set_selectable(True) p.add(p._text) @@ -160,16 +172,16 @@ def _create_details_panel(): def _create_buttons_box(): - bb = Gtk.ButtonBox(Gtk.Orientation.HORIZONTAL) + bb = Gtk.HButtonBox() bb.set_layout(Gtk.ButtonBoxStyle.END) bb._details = _new_button( None, - 'dialog-information', + "dialog-information", _SMALL_BUTTON_ICON_SIZE, - tooltip=_('Show Technical Details'), + tooltip=_("Show Technical Details"), toggle=True, - clicked=_update_details + clicked=_update_details, ) bb.add(bb._details) bb.set_child_secondary(bb._details, True) @@ -181,20 +193,19 @@ def _create_buttons_box(): assert receiver is not None assert bool(receiver) assert receiver.kind is None - _action.pair(_window, receiver) + action.pair(_window, receiver) - bb._pair = _new_button(_('Pair new device'), 'list-add', clicked=_pair_new_device) + bb._pair = _new_button(_("Pair new device"), "list-add", clicked=_pair_new_device) bb.add(bb._pair) def _unpair_current_device(trigger): assert _find_selected_device_id() is not None device = _find_selected_device() assert device is not None - assert bool(device) assert device.kind is not None - _action.unpair(_window, device) + action.unpair(_window, device) - bb._unpair = _new_button(_('Unpair'), 'edit-delete', clicked=_unpair_current_device) + bb._unpair = _new_button(_("Unpair"), "edit-delete", clicked=_unpair_current_device) bb.add(bb._unpair) return bb @@ -202,7 +213,7 @@ def _create_buttons_box(): def _create_empty_panel(): p = Gtk.Label() - p.set_markup('' + _('Select a device') + '') + p.set_markup("" + _("Select a device") + "") p.set_sensitive(False) return p @@ -211,8 +222,7 @@ def _create_empty_panel(): def _create_info_panel(): p = Gtk.Box.new(Gtk.Orientation.VERTICAL, 4) - p._title = Gtk.Label(' ') - p._title.set_alignment(0, 0.5) + p._title = Gtk.Label(label=" ") p._icon = Gtk.Image() b1 = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 4) @@ -242,46 +252,45 @@ def _create_tree(model): tree.set_headers_visible(False) tree.set_show_expanders(False) tree.set_level_indentation(20) - # tree.set_fixed_height_mode(True) tree.set_enable_tree_lines(True) tree.set_reorderable(False) tree.set_enable_search(False) tree.set_model(model) def _is_separator(model, item, _ignore=None): - return model.get_value(item, _COLUMN.PATH) is None + return model.get_value(item, Column.PATH) is None tree.set_row_separator_func(_is_separator, None) icon_cell_renderer = Gtk.CellRendererPixbuf() - icon_cell_renderer.set_property('stock-size', _TREE_ICON_SIZE) - icon_column = Gtk.TreeViewColumn('Icon', icon_cell_renderer) - icon_column.add_attribute(icon_cell_renderer, 'sensitive', _COLUMN.ACTIVE) - icon_column.add_attribute(icon_cell_renderer, 'icon-name', _COLUMN.ICON) + icon_cell_renderer.set_property("stock-size", _TREE_ICON_SIZE) + icon_column = Gtk.TreeViewColumn("Icon", icon_cell_renderer) + icon_column.add_attribute(icon_cell_renderer, "sensitive", Column.ACTIVE) + icon_column.add_attribute(icon_cell_renderer, "icon-name", Column.ICON) tree.append_column(icon_column) name_cell_renderer = Gtk.CellRendererText() - name_column = Gtk.TreeViewColumn('device name', name_cell_renderer) - name_column.add_attribute(name_cell_renderer, 'sensitive', _COLUMN.ACTIVE) - name_column.add_attribute(name_cell_renderer, 'text', _COLUMN.NAME) + name_column = Gtk.TreeViewColumn("device name", name_cell_renderer) + name_column.add_attribute(name_cell_renderer, "sensitive", Column.ACTIVE) + name_column.add_attribute(name_cell_renderer, "text", Column.NAME) name_column.set_expand(True) tree.append_column(name_column) tree.set_expander_column(name_column) status_cell_renderer = Gtk.CellRendererText() - status_cell_renderer.set_property('scale', 0.85) - status_cell_renderer.set_property('xalign', 1) - status_column = Gtk.TreeViewColumn('status text', status_cell_renderer) - status_column.add_attribute(status_cell_renderer, 'sensitive', _COLUMN.ACTIVE) - status_column.add_attribute(status_cell_renderer, 'text', _COLUMN.STATUS_TEXT) + status_cell_renderer.set_property("scale", 0.85) + status_cell_renderer.set_property("xalign", 1) + status_column = Gtk.TreeViewColumn("status text", status_cell_renderer) + status_column.add_attribute(status_cell_renderer, "sensitive", Column.ACTIVE) + status_column.add_attribute(status_cell_renderer, "text", Column.STATUS_TEXT) status_column.set_expand(True) tree.append_column(status_column) battery_cell_renderer = Gtk.CellRendererPixbuf() - battery_cell_renderer.set_property('stock-size', _TREE_ICON_SIZE) - battery_column = Gtk.TreeViewColumn('status icon', battery_cell_renderer) - battery_column.add_attribute(battery_cell_renderer, 'sensitive', _COLUMN.ACTIVE) - battery_column.add_attribute(battery_cell_renderer, 'icon-name', _COLUMN.STATUS_ICON) + battery_cell_renderer.set_property("stock-size", _TREE_ICON_SIZE) + battery_column = Gtk.TreeViewColumn("status icon", battery_cell_renderer) + battery_column.add_attribute(battery_cell_renderer, "sensitive", Column.ACTIVE) + battery_column.add_attribute(battery_cell_renderer, "icon-name", Column.STATUS_ICON) tree.append_column(battery_column) return tree @@ -294,7 +303,7 @@ def _create_window_layout(): assert _empty is not None assert _tree.get_selection().get_mode() == Gtk.SelectionMode.SINGLE - _tree.get_selection().connect('changed', _device_selected) + _tree.get_selection().connect(GtkSignal.CHANGED.value, _device_selected) tree_scroll = Gtk.ScrolledWindow() tree_scroll.add(_tree) @@ -311,24 +320,19 @@ def _create_window_layout(): panel.pack_start(_info, True, True, 0) panel.pack_start(_empty, True, True, 0) - bottom_buttons_box = Gtk.ButtonBox(Gtk.Orientation.HORIZONTAL) + bottom_buttons_box = Gtk.HButtonBox() bottom_buttons_box.set_layout(Gtk.ButtonBoxStyle.START) bottom_buttons_box.set_spacing(20) - quit_button = _new_button(_('Quit %s') % NAME, 'application-exit', _SMALL_BUTTON_ICON_SIZE, clicked=destroy) + quit_button = _new_button(_("Quit %s") % NAME, "application-exit", _SMALL_BUTTON_ICON_SIZE, clicked=destroy) bottom_buttons_box.add(quit_button) - about_button = _new_button(_('About %s') % NAME, 'help-about', _SMALL_BUTTON_ICON_SIZE, clicked=_show_about_window) + about_button = _new_button(_("About %s") % NAME, "help-about", _SMALL_BUTTON_ICON_SIZE, clicked=about.show) bottom_buttons_box.add(about_button) diversion_button = _new_button( - _('Rule Editor'), '', _SMALL_BUTTON_ICON_SIZE, clicked=lambda *_trigger: _show_diversion_window(_model) + _("Rule Editor"), "", _SMALL_BUTTON_ICON_SIZE, clicked=lambda *_trigger: diversion_rules.show_window(_model) ) bottom_buttons_box.add(diversion_button) bottom_buttons_box.set_child_secondary(diversion_button, True) - # solaar_version = Gtk.Label() - # solaar_version.set_markup('' + NAME + ' v' + VERSION + '') - # bottom_buttons_box.add(solaar_version) - # bottom_buttons_box.set_child_secondary(solaar_version, True) - vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 8) vbox.set_border_width(8) vbox.pack_start(panel, True, True, 0) @@ -343,12 +347,8 @@ def _create_window_layout(): def _create(delete_action): window = Gtk.Window() window.set_title(NAME) - window.set_role('status-window') - - # window.set_type_hint(Gdk.WindowTypeHint.UTILITY) - # window.set_skip_taskbar_hint(True) - # window.set_skip_pager_hint(True) - window.connect('delete-event', delete_action) + window.set_role("status-window") + window.connect(GtkSignal.DELETE_EVENT.value, delete_action) vbox = _create_window_layout() window.add(vbox) @@ -360,35 +360,28 @@ def _create(delete_action): window.set_position(Gtk.WindowPosition.CENTER) style = window.get_style_context() - style.add_class('solaar') + style.add_class("solaar") return window -# -# window updates -# - - def _find_selected_device(): selection = _tree.get_selection() model, item = selection.get_selected() - return model.get_value(item, _COLUMN.DEVICE) if item else None + return model.get_value(item, Column.DEVICE) if item else None def _find_selected_device_id(): selection = _tree.get_selection() model, item = selection.get_selected() if item: - return _model.get_value(item, _COLUMN.PATH), _model.get_value(item, _COLUMN.NUMBER) + return _model.get_value(item, Column.PATH), _model.get_value(item, Column.NUMBER) # triggered by changing selection in the tree def _device_selected(selection): model, item = selection.get_selected() - device = model.get_value(item, _COLUMN.DEVICE) if item else None - # if _log.isEnabledFor(_DEBUG): - # _log.debug("window tree selected device %s", device) + device = model.get_value(item, Column.DEVICE) if item else None if device: _update_info_panel(device, full=True) else: @@ -408,18 +401,17 @@ def _receiver_row(receiver_path, receiver=None): item = _model.get_iter_first() while item: # first row matching the path must be the receiver one - if _model.get_value(item, _COLUMN.PATH) == receiver_path: + if _model.get_value(item, Column.PATH) == receiver_path: return item item = _model.iter_next(item) if not item and receiver: - icon_name = _icons.device_icon_name(receiver.name) + icon_name = icons.device_icon_name(receiver.name) status_text = None status_icon = None row_data = (receiver_path, 0, True, receiver.name, icon_name, status_text, status_icon, receiver) assert len(row_data) == len(_TREE_SEPATATOR) - if _log.isEnabledFor(_DEBUG): - _log.debug('new receiver row %s', row_data) + logger.debug("new receiver row %s", row_data) item = _model.append(None, row_data) if _TREE_SEPATATOR: _model.append(None, _TREE_SEPATATOR) @@ -428,11 +420,11 @@ def _receiver_row(receiver_path, receiver=None): def _device_row(receiver_path, device_number, device=None): - assert receiver_path assert device_number is not None + if receiver_path is None: + return None receiver_row = _receiver_row(receiver_path, None if device is None else device.receiver) - if device_number == 0xFF or device_number == 0x0: # direct-connected device, receiver row is device row if receiver_row: return receiver_row @@ -442,12 +434,13 @@ def _device_row(receiver_path, device_number, device=None): item = _model.iter_children(receiver_row) new_child_index = 0 while item: - if _model.get_value(item, _COLUMN.PATH) != receiver_path: - _log.warn( - 'path for device row %s different from path for receiver %s', _model.get_value(item, _COLUMN.PATH), - receiver_path + if _model.get_value(item, Column.PATH) != receiver_path: + logger.warning( + "path for device row %s different from path for receiver %s", + _model.get_value(item, Column.PATH), + receiver_path, ) - item_number = _model.get_value(item, _COLUMN.NUMBER) + item_number = _model.get_value(item, Column.NUMBER) if item_number == device_number: return item if item_number > device_number: @@ -457,25 +450,26 @@ def _device_row(receiver_path, device_number, device=None): item = _model.iter_next(item) if not item and device: - icon_name = _icons.device_icon_name(device.name, device.kind) + icon_name = icons.device_icon_name(device.name, device.kind) status_text = None status_icon = None row_data = ( - receiver_path, device_number, bool(device.online), device.codename, icon_name, status_text, status_icon, device + receiver_path, + device_number, + bool(device.online), + device.codename, + icon_name, + status_text, + status_icon, + device, ) assert len(row_data) == len(_TREE_SEPATATOR) - if _log.isEnabledFor(_DEBUG): - _log.debug('new device row %s at index %d', row_data, new_child_index) + logger.debug("new device row %s at index %d", row_data, new_child_index) item = _model.insert(receiver_row, new_child_index, row_data) return item or None -# -# -# - - def select(receiver_path, device_number=None): assert _window assert receiver_path is not None @@ -487,7 +481,7 @@ def select(receiver_path, device_number=None): selection = _tree.get_selection() selection.select_iter(item) else: - _log.warn('select(%s, %s) failed to find an item', receiver_path, device_number) + logger.warning("select(%s, %s) failed to find an item", receiver_path, device_number) def _hide(w, _ignore=None): @@ -514,11 +508,6 @@ def toggle(trigger=None): _window.present() -# -# -# - - def _update_details(button): assert button visible = button.get_active() @@ -530,62 +519,62 @@ def _update_details(button): # If read_all is False, only return stuff that is ~100% already # cached, and involves no HID++ calls. - yield (_('Path'), device.path) + yield _("Path"), device.path if device.kind is None: - # 046d is the Logitech vendor id - yield (_('USB ID'), '046d:' + device.product_id) + yield _("USB ID"), f"{LOGITECH_VENDOR_ID:04x}:" + device.product_id if read_all: - yield (_('Serial'), device.serial) + yield _("Serial"), device.serial else: - yield (_('Serial'), '...') + yield _("Serial"), "..." else: # yield ('Codename', device.codename) - yield (_('Index'), device.number) + yield _("Index"), device.number if device.wpid: - yield (_('Wireless PID'), device.wpid) + yield _("Wireless PID"), device.wpid if device.product_id: - yield (_('Product ID'), '046d:' + device.product_id) + yield _("Product ID"), f"{LOGITECH_VENDOR_ID:04x}:" + device.product_id hid_version = device.protocol - yield (_('Protocol'), 'HID++ %1.1f' % hid_version if hid_version else _('Unknown')) + cent_proto = getattr(device, "_centurion_protocol", None) + if cent_proto: + yield _("Protocol"), f"Centurion {cent_proto[0]}.{cent_proto[1]}" + elif hid_version: + yield _("Protocol"), f"HID++ {hid_version:1.1f}" + else: + yield _("Protocol"), _("Unknown") if read_all and device.polling_rate: - yield ( - _('Polling rate'), _('%(rate)d ms (%(rate_hz)dHz)') % { - 'rate': device.polling_rate, - 'rate_hz': 1000 // device.polling_rate - } - ) + yield _("Polling rate"), device.polling_rate if read_all or not device.online: - yield (_('Serial'), device.serial) + yield _("Serial"), device.serial else: - yield (_('Serial'), '...') + yield _("Serial"), "..." if read_all and device.unitId and device.unitId != device.serial: - yield (_('Unit ID'), device.unitId) + yield _("Unit ID"), device.unitId if read_all: if device.firmware: for fw in list(device.firmware): - yield (' ' + _(str(fw.kind)), (fw.name + ' ' + fw.version).strip()) + yield " " + _(str(fw.kind)), (fw.name + " " + fw.version).strip() elif device.kind is None or device.online: - yield (' %s' % _('Firmware'), '...') + yield f" {_('Firmware')}", "..." - flag_bits = device.status.get(_K.NOTIFICATION_FLAGS) + flag_bits = device.notification_flags if flag_bits is not None: - flag_names = ('(%s)' % _('none'), ) if flag_bits == 0 else _hidpp10.NOTIFICATION_FLAG.flag_names(flag_bits) - yield (_('Notifications'), ('\n%15s' % ' ').join(flag_names)) + flag_names = hidpp10_constants.flags_to_str(flag_bits, fallback=f"({_('none')})") + yield _("Notifications"), flag_names def _set_details(text): _details._text.set_markup(text) def _make_text(items): - text = '\n'.join('%-13s: %s' % (name, value) for name, value in items) - return '' + text + '' + text = "\n".join("%-13s: %s" % (name, value) for name, value in items) + return f"{text}" def _displayable_items(items): for name, value in items: - value = GLib.markup_escape_text(str(value).replace('\x00', '')).strip() + value = GLib.markup_escape_text(str(value).replace("\x00", "")).strip() if value: yield name, value @@ -607,7 +596,7 @@ def _update_details(button): if read_all: _details._current_device = None else: - _ui_async(_read_slow, selected_device) + ui_async(_read_slow, selected_device) _details.set_visible(visible) @@ -617,31 +606,34 @@ def _update_receiver_panel(receiver, panel, buttons, full=False): devices_count = len(receiver) - paired_text = _( - _('No device paired.') - ) if devices_count == 0 else ngettext('%(count)s paired device.', '%(count)s paired devices.', devices_count) % { - 'count': devices_count - } + paired_text = ( + _(_("No device paired.")) + if devices_count == 0 + else ngettext("%(count)s paired device.", "%(count)s paired devices.", devices_count) % {"count": devices_count} + ) - if (receiver.max_devices > 0): - paired_text += '\n\n%s' % ngettext( - 'Up to %(max_count)s device can be paired to this receiver.', - 'Up to %(max_count)s devices can be paired to this receiver.', receiver.max_devices - ) % { - 'max_count': receiver.max_devices - } + if receiver.max_devices > 0: + paired_text += ( + "\n\n%s" + % ngettext( + "Up to %(max_count)s device can be paired to this receiver.", + "Up to %(max_count)s devices can be paired to this receiver.", + receiver.max_devices, + ) + % {"max_count": receiver.max_devices} + ) elif devices_count > 0: - paired_text += '\n\n%s' % _('Only one device can be paired to this receiver.') + paired_text += f"\n\n{_('Only one device can be paired to this receiver.')}" pairings = receiver.remaining_pairings() - if (pairings is not None and pairings >= 0): - paired_text += '\n%s' % ( - ngettext('This receiver has %d pairing remaining.', 'This receiver has %d pairings remaining.', pairings) % - pairings + if pairings is not None and pairings >= 0: + paired_text += "\n%s" % ( + ngettext("This receiver has %d pairing remaining.", "This receiver has %d pairings remaining.", pairings) + % pairings ) panel._count.set_markup(paired_text) - is_pairing = receiver.status.lock_open + is_pairing = receiver.pairing.lock_open if is_pairing: panel._scanning.set_visible(True) if not panel._spinner.get_visible(): @@ -658,8 +650,11 @@ def _update_receiver_panel(receiver, panel, buttons, full=False): # b._insecure.set_visible(False) buttons._unpair.set_visible(False) - if not is_pairing and (receiver.remaining_pairings() is None or receiver.remaining_pairings() != 0) and \ - (receiver.re_pairs or devices_count < receiver.max_devices): + if ( + not is_pairing + and (receiver.remaining_pairings() is None or receiver.remaining_pairings() != 0) + and (receiver.re_pairs or devices_count < receiver.max_devices) + ): buttons._pair.set_sensitive(True) else: buttons._pair.set_sensitive(False) @@ -672,80 +667,80 @@ def _update_device_panel(device, panel, buttons, full=False): is_online = bool(device.online) panel.set_sensitive(is_online) - if device.status.get(_K.BATTERY_LEVEL) is None: - device.status.read_battery(device) + if device.battery_info is None or device.battery_info.level is None: + device.read_battery() - battery_level = device.status.get(_K.BATTERY_LEVEL) - battery_voltage = device.status.get(_K.BATTERY_VOLTAGE) + battery_level = device.battery_info.level if device.battery_info is not None else None + battery_voltage = device.battery_info.voltage if device.battery_info is not None else None if battery_level is None and battery_voltage is None: panel._battery.set_visible(False) else: panel._battery.set_visible(True) - battery_next_level = device.status.get(_K.BATTERY_NEXT_LEVEL) - charging = device.status.get(_K.BATTERY_CHARGING) - icon_name = _icons.battery(battery_level, charging) + battery_next_level = device.battery_info.next_level + charging = device.battery_info.charging() if device.battery_info is not None else None + icon_name = icons.battery(battery_level, charging) panel._battery._icon.set_from_icon_name(icon_name, _INFO_ICON_SIZE) panel._battery._icon.set_sensitive(True) panel._battery._text.set_sensitive(is_online) if battery_voltage is not None: - panel._battery._label.set_text(_('Battery Voltage')) - text = '%dmV' % battery_voltage - tooltip_text = _('Voltage reported by battery') + panel._battery._label.set_text(_("Battery Voltage")) + text = f"{int(battery_voltage)}mV" + tooltip_text = _("Voltage reported by battery") else: - panel._battery._label.set_text(_('Battery Level')) - text = '' - tooltip_text = _('Approximate level reported by battery') + panel._battery._label.set_text(_("Battery Level")) + text = "" + tooltip_text = _("Approximate level reported by battery") if battery_voltage is not None and battery_level is not None: - text += ', ' + text += ", " if battery_level is not None: - text += _(str(battery_level)) if isinstance(battery_level, _NamedInt) else '%d%%' % battery_level + text += _(str(battery_level)) if isinstance(battery_level, NamedInt) else f"{int(battery_level)}%" if battery_next_level is not None and not charging: - if isinstance(battery_next_level, _NamedInt): - text += ' (' + _('next reported ') + _(str(battery_next_level)) + ')' + if isinstance(battery_next_level, NamedInt): + text += " (" + _("next reported ") + _(str(battery_next_level)) + ")" else: - text += ' (' + _('next reported ') + ('%d%%' % battery_next_level) + ')' - tooltip_text = tooltip_text + _(' and next level to be reported.') + text += " (" + _("next reported ") + f"{int(battery_next_level)}%" + ")" + tooltip_text = tooltip_text + _(" and next level to be reported.") if is_online: if charging: - text += ' (%s)' % _('charging') + text += f" ({_('charging')})" else: - text += ' (%s)' % _('last known') + text += f" ({_('last known')})" panel._battery._text.set_markup(text) panel._battery.set_tooltip_text(tooltip_text) - if device.status.get(_K.LINK_ENCRYPTED) is None: + if device.link_encrypted is None: panel._secure.set_visible(False) elif is_online: panel._secure.set_visible(True) panel._secure._icon.set_visible(True) - if device.status.get(_K.LINK_ENCRYPTED) is True: - panel._secure._text.set_text(_('encrypted')) - panel._secure._icon.set_from_icon_name('security-high', _INFO_ICON_SIZE) - panel._secure.set_tooltip_text(_('The wireless link between this device and its receiver is encrypted.')) + if device.link_encrypted is True: + panel._secure._text.set_text(_("encrypted")) + panel._secure._icon.set_from_icon_name("security-high", _INFO_ICON_SIZE) + panel._secure.set_tooltip_text(_("The wireless link between this device and its receiver is encrypted.")) else: - panel._secure._text.set_text(_('not encrypted')) - panel._secure._icon.set_from_icon_name('security-low', _INFO_ICON_SIZE) + panel._secure._text.set_text(_("not encrypted")) + panel._secure._icon.set_from_icon_name("security-low", _INFO_ICON_SIZE) panel._secure.set_tooltip_text( _( - 'The wireless link between this device and its receiver is not encrypted.\n' - 'This is a security issue for pointing devices, and a major security issue for text-input devices.' + "The wireless link between this device and its receiver is not encrypted.\n" + "This is a security issue for pointing devices, and a major security issue for text-input devices." ) ) else: panel._secure.set_visible(True) panel._secure._icon.set_visible(False) - panel._secure._text.set_markup('%s' % _('offline')) - panel._secure.set_tooltip_text('') + panel._secure._text.set_markup(f"{_('offline')}") + panel._secure.set_tooltip_text("") if is_online: - light_level = device.status.get(_K.LIGHT_LEVEL) + light_level = device.battery_info.light_level if device.battery_info is not None else None if light_level is None: panel._lux.set_visible(False) else: - panel._lux._icon.set_from_icon_name(_icons.lux(light_level), _INFO_ICON_SIZE) - panel._lux._text.set_text(_('%(light_level)d lux') % {'light_level': light_level}) + panel._lux._icon.set_from_icon_name(icons.lux(light_level), _INFO_ICON_SIZE) + panel._lux._text.set_text(_("%(light_level)d lux") % {"light_level": light_level}) panel._lux.set_visible(True) else: panel._lux.set_visible(False) @@ -757,7 +752,7 @@ def _update_device_panel(device, panel, buttons, full=False): panel.set_visible(True) if full: - _config_panel.update(device, is_online) + config_panel.update(device, is_online) def _update_info_panel(device, full=False): @@ -772,8 +767,8 @@ def _update_info_panel(device, full=False): # a device must be paired assert device - _info._title.set_markup('%s' % device.name) - icon_name = _icons.device_icon_name(device.name, device.kind) + _info._title.set_markup(f"{device.name}") + icon_name = icons.device_icon_name(device.name, device.kind) _info._icon.set_from_icon_name(icon_name, _DEVICE_ICON_SIZE) if device.kind is None: @@ -795,7 +790,6 @@ def _update_info_panel(device, full=False): _update_details(_info._buttons._details) -# # window layout: # +--------------------------------+ # | tree | receiver | empty | @@ -835,7 +829,7 @@ def destroy(_ignore1=None, _ignore2=None): w, _window = _window, None w.destroy() w = None - _config_panel.destroy() + config_panel.destroy() _empty = None _info = None @@ -861,9 +855,9 @@ def update(device, need_popup=False, refresh=False): item = _receiver_row(device.path, device if is_alive else None) if is_alive and item: - was_pairing = bool(_model.get_value(item, _COLUMN.STATUS_ICON)) - is_pairing = (not device.isDevice) and bool(device.status.lock_open) - _model.set_value(item, _COLUMN.STATUS_ICON, 'network-wireless' if is_pairing else _CAN_SET_ROW_NONE) + was_pairing = bool(_model.get_value(item, Column.STATUS_ICON)) + is_pairing = (not device.isDevice) and bool(device.pairing.lock_open) + _model.set_value(item, Column.STATUS_ICON, "network-wireless" if is_pairing else _CAN_SET_ROW_NONE) if selected_device_id == (device.path, 0): full_update = need_popup or was_pairing != is_pairing @@ -878,43 +872,43 @@ def update(device, need_popup=False, refresh=False): else: path = device.receiver.path if device.receiver is not None else device.path - assert device.number is not None and device.number >= 0, 'invalid device number' + str(device.number) + assert device.number is not None and device.number >= 0, f"invalid device number{str(device.number)}" item = _device_row(path, device.number, device if bool(device) else None) if bool(device) and item: update_device(device, item, selected_device_id, need_popup, full=refresh) elif item: _model.remove(item) - _config_panel.clean(device) + config_panel.clean(device) # make sure all rows are visible _tree.expand_all() def update_device(device, item, selected_device_id, need_popup, full=False): - was_online = _model.get_value(item, _COLUMN.ACTIVE) + was_online = _model.get_value(item, Column.ACTIVE) is_online = bool(device.online) - _model.set_value(item, _COLUMN.ACTIVE, is_online) + _model.set_value(item, Column.ACTIVE, is_online) - battery_level = device.status.get(_K.BATTERY_LEVEL) - battery_voltage = device.status.get(_K.BATTERY_VOLTAGE) + battery_level = device.battery_info.level if device.battery_info is not None else None + battery_voltage = device.battery_info.voltage if device.battery_info is not None else None if battery_level is None: - _model.set_value(item, _COLUMN.STATUS_TEXT, _CAN_SET_ROW_NONE) - _model.set_value(item, _COLUMN.STATUS_ICON, _CAN_SET_ROW_NONE) + _model.set_value(item, Column.STATUS_TEXT, _CAN_SET_ROW_NONE) + _model.set_value(item, Column.STATUS_ICON, _CAN_SET_ROW_NONE) else: if battery_voltage is not None and False: # Use levels instead of voltage here - status_text = '%(battery_voltage)dmV' % {'battery_voltage': battery_voltage} - elif isinstance(battery_level, _NamedInt): + status_text = f"{int(battery_voltage)}mV" + elif isinstance(battery_level, NamedInt): status_text = _(str(battery_level)) else: - status_text = '%(battery_percent)d%%' % {'battery_percent': battery_level} - _model.set_value(item, _COLUMN.STATUS_TEXT, status_text) + status_text = f"{int(battery_level)}%" + _model.set_value(item, Column.STATUS_TEXT, status_text) - charging = device.status.get(_K.BATTERY_CHARGING) - icon_name = _icons.battery(battery_level, charging) - _model.set_value(item, _COLUMN.STATUS_ICON, icon_name) + charging = device.battery_info.charging() if device.battery_info is not None else None + icon_name = icons.battery(battery_level, charging) + _model.set_value(item, Column.STATUS_ICON, icon_name) - _model.set_value(item, _COLUMN.NAME, device.codename) + _model.set_value(item, Column.NAME, device.codename) if selected_device_id is None or need_popup: select(device.receiver.path if device.receiver else device.path, device.number) @@ -924,12 +918,12 @@ def update_device(device, item, selected_device_id, need_popup, full=False): def find_device(serial): - assert serial, 'need serial number or unit ID to find a device' + assert serial, "need serial number or unit ID to find a device" result = None def check(_store, _treepath, row): nonlocal result - device = _model.get_value(row, _COLUMN.DEVICE) + device = _model.get_value(row, Column.DEVICE) if device and device.kind and (device.unitId == serial or device.serial == serial): result = device return True diff --git a/lib/solaar/upower.py b/lib/solaar/upower.py deleted file mode 100644 index 4bede4c7..00000000 --- a/lib/solaar/upower.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- python-mode -*- - -## Copyright (C) 2012-2013 Daniel Pavel -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License along -## with this program; if not, write to the Free Software Foundation, Inc., -## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -from logging import INFO as _INFO -from logging import getLogger - -_log = getLogger(__name__) -del getLogger - -# -# As suggested here: http://stackoverflow.com/a/13548984 -# - -_suspend_callback = None - - -def _suspend(): - if _suspend_callback: - if _log.isEnabledFor(_INFO): - _log.info('received suspend event') - _suspend_callback() - - -_resume_callback = None - - -def _resume(): - if _resume_callback: - if _log.isEnabledFor(_INFO): - _log.info('received resume event') - _resume_callback() - - -def _suspend_or_resume(suspend): - _suspend() if suspend else _resume() - - -def watch(on_resume_callback=None, on_suspend_callback=None): - """Register callback for suspend/resume events. - They are called only if the system DBus is running, and the Login daemon is available.""" - global _resume_callback, _suspend_callback - _suspend_callback = on_suspend_callback - _resume_callback = on_resume_callback - - -try: - import dbus - - _LOGIND_BUS = 'org.freedesktop.login1' - _LOGIND_INTERFACE = 'org.freedesktop.login1.Manager' - - # integration into the main GLib loop - from dbus.mainloop.glib import DBusGMainLoop - DBusGMainLoop(set_as_default=True) - - bus = dbus.SystemBus() - assert bus - - bus.add_signal_receiver(_suspend_or_resume, 'PrepareForSleep', dbus_interface=_LOGIND_INTERFACE, bus_name=_LOGIND_BUS) - - if _log.isEnabledFor(_INFO): - _log.info('connected to system dbus, watching for suspend/resume events') - -except Exception: - # Either: - # - the dbus library is not available - # - the system dbus is not running - _log.warn('failed to register suspend/resume callbacks') - pass diff --git a/lib/solaar/version b/lib/solaar/version index 5ed5faa5..4e036596 100644 --- a/lib/solaar/version +++ b/lib/solaar/version @@ -1 +1 @@ -1.1.10 +1.1.19 diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..9a267ace --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,32 @@ +site_name: Solaar Documentation +site_description: Linux Device Manager for Logitech Unifying Receivers and Devices. +site_author: pwr-Solaar +repo_url: https://github.com/pwr-Solaar/Solaar +repo_name: Solaar +theme: + name: readthedocs +docs_dir: docs +nav: + - Solaar: index.md + - Usage: usage.md + - Capabilities: capabilities.md + - Issues: issues.md + - Rules: rules.md + - Installation: installation.md + - Uninstallation: uninstallation.md + - Translation: i18n.md + - Features: features.md + - Devices: devices.md + - Implementation: implementation.md + - Debian: debian.md + +plugins: + - search +# - mkdocstrings: +# handlers: +# python: +# setup_commands: +# - python -m pip install . + - mermaid2 +markdown_extensions: + - pymdownx.superfences diff --git a/po/bg.po b/po/bg.po new file mode 100644 index 00000000..f422fd77 --- /dev/null +++ b/po/bg.po @@ -0,0 +1,2063 @@ +# Bulgarian translations for solaar package. +# Copyright (C) 2026 THE solaar'S COPYRIGHT HOLDER +# This file is distributed under the same license as the solaar package. +# Automatically generated, 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: solaar 1.1.19\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-04-04 20:19+0300\n" +"PO-Revision-Date: 2026-04-05 03:37+0300\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: bg\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.8\n" + +#: lib/logitech_receiver/base_usb.py:52 +msgid "Bolt Receiver" +msgstr "Bolt Приемник" + +#: lib/logitech_receiver/base_usb.py:64 +msgid "Unifying Receiver" +msgstr "Unifying Приемник" + +#: lib/logitech_receiver/base_usb.py:75 lib/logitech_receiver/base_usb.py:87 +#: lib/logitech_receiver/base_usb.py:100 lib/logitech_receiver/base_usb.py:113 +msgid "Nano Receiver" +msgstr "Nano Приемник" + +#: lib/logitech_receiver/base_usb.py:125 +msgid "Lightspeed Receiver" +msgstr "Logitech Приемник" + +#: lib/logitech_receiver/base_usb.py:136 +msgid "EX100 Receiver 27 Mhz" +msgstr "EX100 Приемник 27 мегахерца" + +#: lib/logitech_receiver/common.py:649 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Батерия: %(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:652 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Батерия: %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:1049 lib/logitech_receiver/settings_templates.py:299 +msgid "Disabled" +msgstr "Деактивиран" + +#: lib/logitech_receiver/hidpp20.py:1050 +msgid "Static" +msgstr "Статичен" + +#: lib/logitech_receiver/hidpp20.py:1051 +msgid "Pulse" +msgstr "Пулс" + +#: lib/logitech_receiver/hidpp20.py:1052 +msgid "Cycle" +msgstr "Цикъл" + +#: lib/logitech_receiver/hidpp20.py:1053 +msgid "Boot" +msgstr "Зареждане" + +#: lib/logitech_receiver/hidpp20.py:1054 +msgid "Demo" +msgstr "Демонстрация" + +#: lib/logitech_receiver/hidpp20.py:1056 +msgid "Breathe" +msgstr "Дишане" + +#: lib/logitech_receiver/hidpp20.py:1059 +msgid "Ripple" +msgstr "Прекъсване" + +#: lib/logitech_receiver/hidpp20.py:1060 +msgid "Decomposition" +msgstr "Разпадане" + +#: lib/logitech_receiver/hidpp20.py:1061 +msgid "Signature1" +msgstr "Подпис1" + +#: lib/logitech_receiver/hidpp20.py:1062 +msgid "Signature2" +msgstr "Подпис2" + +#: lib/logitech_receiver/hidpp20.py:1063 +msgid "CycleS" +msgstr "Цикли" + +#: lib/logitech_receiver/hidpp20.py:1127 +msgid "Unknown Location" +msgstr "Неизвестно Местонахождение" + +#: lib/logitech_receiver/hidpp20.py:1128 +msgid "Primary" +msgstr "Първичен" + +#: lib/logitech_receiver/hidpp20.py:1129 +msgid "Logo" +msgstr "Лого" + +#: lib/logitech_receiver/hidpp20.py:1130 +msgid "Left Side" +msgstr "Лява Страна" + +#: lib/logitech_receiver/hidpp20.py:1131 +msgid "Right Side" +msgstr "Дясна Страна" + +#: lib/logitech_receiver/hidpp20.py:1132 +msgid "Combined" +msgstr "Комбиниран" + +#: lib/logitech_receiver/hidpp20.py:1133 +msgid "Primary 1" +msgstr "Първичен 1" + +#: lib/logitech_receiver/hidpp20.py:1134 +msgid "Primary 2" +msgstr "Първичен 2" + +#: lib/logitech_receiver/hidpp20.py:1135 +msgid "Primary 3" +msgstr "Първичен 3" + +#: lib/logitech_receiver/hidpp20.py:1136 +msgid "Primary 4" +msgstr "Първичен 4" + +#: lib/logitech_receiver/hidpp20.py:1137 +msgid "Primary 5" +msgstr "Първичен 5" + +#: lib/logitech_receiver/hidpp20.py:1138 +msgid "Primary 6" +msgstr "Първичен 6" + +#: lib/logitech_receiver/i18n.py:28 +msgid "empty" +msgstr "празен" + +#: lib/logitech_receiver/i18n.py:29 +msgid "critical" +msgstr "критично" + +#: lib/logitech_receiver/i18n.py:30 +msgid "low" +msgstr "ниско" + +#: lib/logitech_receiver/i18n.py:31 +msgid "average" +msgstr "средно" + +#: lib/logitech_receiver/i18n.py:32 +msgid "good" +msgstr "добро" + +#: lib/logitech_receiver/i18n.py:33 +msgid "full" +msgstr "пълно" + +#: lib/logitech_receiver/i18n.py:35 +msgid "discharging" +msgstr "разреждане" + +#: lib/logitech_receiver/i18n.py:36 +msgid "recharging" +msgstr "презареждане" + +#: lib/logitech_receiver/i18n.py:37 lib/solaar/ui/window.py:700 +msgid "charging" +msgstr "зареждане" + +#: lib/logitech_receiver/i18n.py:38 +msgid "not charging" +msgstr "не се зарежда" + +#: lib/logitech_receiver/i18n.py:39 +msgid "almost full" +msgstr "почти пълно" + +#: lib/logitech_receiver/i18n.py:40 +msgid "charged" +msgstr "заредено" + +#: lib/logitech_receiver/i18n.py:41 +msgid "slow recharge" +msgstr "бавно презареждане" + +#: lib/logitech_receiver/i18n.py:42 +msgid "invalid battery" +msgstr "неправилна батерия" + +#: lib/logitech_receiver/i18n.py:43 +msgid "thermal error" +msgstr "температурна грешка" + +#: lib/logitech_receiver/i18n.py:44 +msgid "error" +msgstr "грешка" + +#: lib/logitech_receiver/i18n.py:45 +msgid "standard" +msgstr "стандартно" + +#: lib/logitech_receiver/i18n.py:46 +msgid "fast" +msgstr "бърз" + +#: lib/logitech_receiver/i18n.py:47 +msgid "slow" +msgstr "бавен" + +#: lib/logitech_receiver/i18n.py:49 +msgid "device timeout" +msgstr "изтекло време за операция" + +#: lib/logitech_receiver/i18n.py:50 +msgid "device not supported" +msgstr "устройство което не се потдържа" + +#: lib/logitech_receiver/i18n.py:51 +msgid "too many devices" +msgstr "твърде много устройства" + +#: lib/logitech_receiver/i18n.py:52 +msgid "sequence timeout" +msgstr "изтекло време за поредица от операции" + +#: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:555 +msgid "Firmware" +msgstr "Фърмуеър" + +#: lib/logitech_receiver/i18n.py:55 +msgid "Bootloader" +msgstr "Бутлоудър" + +#: lib/logitech_receiver/i18n.py:56 +msgid "Hardware" +msgstr "Хардуер" + +#: lib/logitech_receiver/i18n.py:57 +msgid "Other" +msgstr "Друг" + +#: lib/logitech_receiver/i18n.py:59 +msgid "Left Button" +msgstr "Ляв Бутон" + +#: lib/logitech_receiver/i18n.py:60 +msgid "Right Button" +msgstr "Десен Бутон" + +#: lib/logitech_receiver/i18n.py:61 +msgid "Middle Button" +msgstr "Среден Бутон" + +#: lib/logitech_receiver/i18n.py:62 +msgid "Back Button" +msgstr "Бутон за Назад" + +#: lib/logitech_receiver/i18n.py:63 +msgid "Forward Button" +msgstr "Бутон за Напред" + +#: lib/logitech_receiver/i18n.py:64 +msgid "Mouse Gesture Button" +msgstr "Бутон за жестове с мишката" + +#: lib/logitech_receiver/i18n.py:65 +msgid "Smart Shift" +msgstr "Умно преместване" + +#: lib/logitech_receiver/i18n.py:66 +msgid "DPI Switch" +msgstr "Превключване на чувствителността (DPI)" + +#: lib/logitech_receiver/i18n.py:67 +msgid "Left Tilt" +msgstr "Ляв Наклон" + +#: lib/logitech_receiver/i18n.py:68 +msgid "Right Tilt" +msgstr "Десен Наклон" + +#: lib/logitech_receiver/i18n.py:69 +msgid "Left Click" +msgstr "Ляв Клик" + +#: lib/logitech_receiver/i18n.py:70 +msgid "Right Click" +msgstr "Десен Клик" + +#: lib/logitech_receiver/i18n.py:71 +msgid "Mouse Middle Button" +msgstr "Среден Бутон на Мишката" + +#: lib/logitech_receiver/i18n.py:72 +msgid "Mouse Back Button" +msgstr "Бутон за Назад на Мишката" + +#: lib/logitech_receiver/i18n.py:73 +msgid "Mouse Forward Button" +msgstr "Бутон за Напред на Мишката" + +#: lib/logitech_receiver/i18n.py:74 +msgid "Gesture Button Navigation" +msgstr "Навигационен Бутон за Жестовете" + +#: lib/logitech_receiver/i18n.py:75 +msgid "Mouse Scroll Left Button" +msgstr "Бутон на Мишката за Скролиране Наляво" + +#: lib/logitech_receiver/i18n.py:76 +msgid "Mouse Scroll Right Button" +msgstr "Бутон на Мишката за Скролиране Надясно" + +#: lib/logitech_receiver/i18n.py:78 +msgid "pressed" +msgstr "натистнат" + +#: lib/logitech_receiver/i18n.py:79 +msgid "released" +msgstr "свободен" + +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 +msgid "connected" +msgstr "свързан" + +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 +msgid "disconnected" +msgstr "не е свързан" + +#: lib/logitech_receiver/notifications.py:182 +msgid "unpaired" +msgstr "не е сдвоен" + +#: lib/logitech_receiver/notifications.py:231 +msgid "powered on" +msgstr "включен" + +#: lib/logitech_receiver/notifications.py:283 +msgid "ADC measurement notification" +msgstr "известие за измерване на ADC" + +#: lib/logitech_receiver/notifications.py:428 lib/logitech_receiver/notifications.py:484 +msgid "pairing lock is closed" +msgstr "заключване на сдвояването е затворено" + +#: lib/logitech_receiver/notifications.py:428 lib/logitech_receiver/notifications.py:484 +msgid "pairing lock is open" +msgstr "заключване на сдвояването е отворено" + +#: lib/logitech_receiver/notifications.py:447 +msgid "discovery lock is closed" +msgstr "заключване на откриваемостта е затворено" + +#: lib/logitech_receiver/notifications.py:447 +msgid "discovery lock is open" +msgstr "заключване на откриваемостта е отворено" + +#: lib/logitech_receiver/receiver.py:437 +msgid "No paired devices." +msgstr "Няма сдвоени устройства." + +#: lib/logitech_receiver/receiver.py:439 lib/solaar/ui/window.py:606 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s сдвоени устройства." +msgstr[1] "%(count)s сдвоени устройства." + +#: lib/logitech_receiver/settings.py:602 +msgid "register" +msgstr "регистриране" + +#: lib/logitech_receiver/settings.py:616 lib/logitech_receiver/settings.py:654 +msgid "feature" +msgstr "функционалности" + +#: lib/logitech_receiver/settings_templates.py:138 +msgid "Swap Fx function" +msgstr "Размяна Fх функция" + +#: lib/logitech_receiver/settings_templates.py:141 +msgid "" +"When set, the F1..F12 keys will activate their special function,\n" +"and you must hold the FN key to activate their standard function." +msgstr "" +"Когато е активиран бутоните F1..F12 ще изпълняват своите специални функции,\n" +"и трябва да натиснете Fn бутона, за да активирате техните нормални функции." + +#: lib/logitech_receiver/settings_templates.py:146 +msgid "" +"When unset, the F1..F12 keys will activate their standard function,\n" +"and you must hold the FN key to activate their special function." +msgstr "" +"Когато не е активиран, бутоните F1..F12 ще изпълняват своите нормални функции,\n" +"и трябва да натиснете Fn бутона за да активирате техните специални функции." + +#: lib/logitech_receiver/settings_templates.py:154 +msgid "Hand Detection" +msgstr "Разоизнаване на ръка" + +#: lib/logitech_receiver/settings_templates.py:155 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Включване на осветлението когато ръцете са над клавиатурата." + +#: lib/logitech_receiver/settings_templates.py:162 +msgid "Scroll Wheel Smooth Scrolling" +msgstr "Скрол Плавно скролиране/превъртане" + +#: lib/logitech_receiver/settings_templates.py:163 lib/logitech_receiver/settings_templates.py:410 +#: lib/logitech_receiver/settings_templates.py:439 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Високо-чувствителен режим за вертикално скролиране/превъртане със скролът." + +#: lib/logitech_receiver/settings_templates.py:170 +msgid "Side Scrolling" +msgstr "Странично Скролиране/превъртане" + +#: lib/logitech_receiver/settings_templates.py:172 +msgid "" +"When disabled, pushing the wheel sideways sends custom button events\n" +"instead of the standard side-scrolling events." +msgstr "" +"Когато е деактивиран, страничното натискане на скролът, изпраща модифицирана команда\n" +"вместо стандартното странично скролиране/превъртане." + +#: lib/logitech_receiver/settings_templates.py:182 +msgid "Sensitivity (DPI - older mice)" +msgstr "Чувствителност (DPI - за по-старите мишки )" + +#: lib/logitech_receiver/settings_templates.py:183 lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +msgid "Mouse movement sensitivity" +msgstr "Чувствителност при движение на мишката" + +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Backlight Timed" +msgstr "Време на Подсветката" + +#: lib/logitech_receiver/settings_templates.py:257 lib/logitech_receiver/settings_templates.py:397 +msgid "Set illumination time for keyboard." +msgstr "Задаване на време на осветяване на клавиатурата." + +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Backlight" +msgstr "Подсветка" + +#: lib/logitech_receiver/settings_templates.py:269 +msgid "Illumination level on keyboard. Changes made are only applied in Manual mode." +msgstr "Ниво на осветеност на клавиатурата. Направените промени са приложими само в Ръчен режим." + +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Automatic" +msgstr "Автоматично" + +#: lib/logitech_receiver/settings_templates.py:303 +msgid "Manual" +msgstr "Ръчно" + +#: lib/logitech_receiver/settings_templates.py:305 +msgid "Enabled" +msgstr "Активно" + +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Backlight Level" +msgstr "Ниво на Подсветка" + +#: lib/logitech_receiver/settings_templates.py:312 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "Ниво на подсветка в Ръчен режим." + +#: lib/logitech_receiver/settings_templates.py:369 +msgid "Backlight Delay Hands Out" +msgstr "Закъснение на подсветката при премахване на ръцете" + +#: lib/logitech_receiver/settings_templates.py:370 +msgid "Delay in seconds until backlight fades out with hands away from keyboard." +msgstr "Закъснение в секунди докато подсветката изгасне с премахване на ръцете от клавиатурата." + +#: lib/logitech_receiver/settings_templates.py:378 +msgid "Backlight Delay Hands In" +msgstr "Закъснение на подсветката при засичане на ръце" + +#: lib/logitech_receiver/settings_templates.py:379 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "Закъснение в секунди докато подсветката изгасне с при засичане на ръце около клавиатурата." + +#: lib/logitech_receiver/settings_templates.py:387 +msgid "Backlight Delay Powered" +msgstr "Закъснение на подсветката при Включване" + +#: lib/logitech_receiver/settings_templates.py:388 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "Закъснение в секунди докато подсветката изгасне с външно захранване." + +#: lib/logitech_receiver/settings_templates.py:396 +msgid "Backlight (Seconds)" +msgstr "Подсветка (Секунди)" + +#: lib/logitech_receiver/settings_templates.py:408 +msgid "Scroll Wheel High Resolution" +msgstr "Колело за скролиране/превъртане с Висока Резолюция" + +#: lib/logitech_receiver/settings_templates.py:412 lib/logitech_receiver/settings_templates.py:441 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "Игнориране ако скролирането е извънредно бързо или бавно" + +#: lib/logitech_receiver/settings_templates.py:419 lib/logitech_receiver/settings_templates.py:450 +msgid "Scroll Wheel Diversion" +msgstr "Отклонение на Скролиращото Колело" + +#: lib/logitech_receiver/settings_templates.py:421 +msgid "" +"Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger Solaar rules but are " +"otherwise ignored)." +msgstr "" +"Скролиращото колело изпраща LOWRES_WHEEL HID++ известие ( задействащо правилата на Solaar, което " +"иначе е игнорирано)." + +#: lib/logitech_receiver/settings_templates.py:428 +msgid "Scroll Wheel Direction" +msgstr "Посока на Скролиращото Колело" + +#: lib/logitech_receiver/settings_templates.py:429 +msgid "Invert direction for vertical scroll with wheel." +msgstr "Обръщане на посоката на вертикално скролиране/превъртане с колелото." + +#: lib/logitech_receiver/settings_templates.py:437 +msgid "Scroll Wheel Resolution" +msgstr "Чувствителност на Скролиращото/Превъртащото Колело" + +#: lib/logitech_receiver/settings_templates.py:452 +msgid "" +"Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar rules but are " +"otherwise ignored)." +msgstr "" +"Скролиращото колело изпраща HIRES_WHEEL HID++ известие ( задействащо правилата на Solaar, което " +"иначе е игнорирано)." + +#: lib/logitech_receiver/settings_templates.py:461 +msgid "Sensitivity (Pointer Speed)" +msgstr "Чувствителност (Скорост на курсора)" + +#: lib/logitech_receiver/settings_templates.py:462 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Множител на скоростта за мишка (256 е нормалния множител)." + +#: lib/logitech_receiver/settings_templates.py:472 +msgid "Thumb Wheel Diversion" +msgstr "Колелото на Палеца Отклоняване" + +#: lib/logitech_receiver/settings_templates.py:474 +msgid "" +"Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar rules but are " +"otherwise ignored)." +msgstr "" +"Колелото на Палеца изпраща THUMB_WHEEL HID++ известие ( задействащо правилата на Solaar, което " +"иначе е игнорирано )." + +#: lib/logitech_receiver/settings_templates.py:483 +msgid "Thumb Wheel Direction" +msgstr "Посока на Колелото на Палеца" + +#: lib/logitech_receiver/settings_templates.py:484 +msgid "Invert thumb wheel scroll direction." +msgstr "Обръщане на посоката на колелото на палеца." + +#: lib/logitech_receiver/settings_templates.py:504 +msgid "Onboard Profiles" +msgstr "Вградени Профили" + +#: lib/logitech_receiver/settings_templates.py:505 +msgid "Enable an onboard profile, which controls report rate, sensitivity, and button actions" +msgstr "" +"Активиране на вградените профили, които контролират бързината на рапортуване, чувствителност, и " +"реакциите на бутоните" + +#: lib/logitech_receiver/settings_templates.py:549 lib/logitech_receiver/settings_templates.py:582 +msgid "Report Rate" +msgstr "Бързина на рапортуване" + +#: lib/logitech_receiver/settings_templates.py:551 lib/logitech_receiver/settings_templates.py:584 +msgid "Frequency of device movement reports" +msgstr "Честота на рапортите за движение на устройството" + +#: lib/logitech_receiver/settings_templates.py:551 lib/logitech_receiver/settings_templates.py:584 +#: lib/logitech_receiver/settings_templates.py:1019 lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1421 lib/logitech_receiver/settings_templates.py:1452 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "За по-добра ефективност, е възможно да се наложи да Деактивирате Вградените Профили." + +#: lib/logitech_receiver/settings_templates.py:612 +msgid "Divert crown events" +msgstr "Събития за отклонение на короната" + +#: lib/logitech_receiver/settings_templates.py:613 +msgid "" +"Make crown send CROWN HID++ notifications (which trigger Solaar rules but are otherwise ignored)." +msgstr "" +"Короната изпраща CROWN HID++ известие ( задействащо правилата на Solaar, което иначе е игнорирано)." + +#: lib/logitech_receiver/settings_templates.py:621 +msgid "Crown smooth scroll" +msgstr "Плавно скролиране с Корона" + +#: lib/logitech_receiver/settings_templates.py:622 +msgid "Set crown smooth scroll" +msgstr "Задаване на плавно скролиране с корона" + +#: lib/logitech_receiver/settings_templates.py:630 +msgid "Divert G and M Keys" +msgstr "Отклонение на клавиши G и M" + +#: lib/logitech_receiver/settings_templates.py:631 +msgid "" +"Make G and M keys send HID++ notifications (which trigger Solaar rules but are otherwise ignored)." +msgstr "" +"Клавишите G и M изпращат HID++ известие ( задействащо правилата на Solaar, което иначе е " +"игнорирано)." + +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Scroll Wheel Ratcheted" +msgstr "Тресчотъчно Скролиращо Колело" + +#: lib/logitech_receiver/settings_templates.py:646 +msgid "Switch the mouse wheel between speed-controlled ratcheting and always freespin." +msgstr "" +"Смяна на режима на скролиращото колело на мишката, между контрол с тресчотъчно въртене и свободно " +"въртене." + +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Freespinning" +msgstr "Свободно въртене" + +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Ratcheted" +msgstr "Тресчотъчно" + +#: lib/logitech_receiver/settings_templates.py:655 +msgid "Scroll Wheel Ratchet Speed" +msgstr "Скорост на Тресчтотъчно Скролиране" + +#: lib/logitech_receiver/settings_templates.py:657 +msgid "" +"Use the mouse wheel speed to switch between ratcheted and freespinning.\n" +"The mouse wheel is always ratcheted at 50." +msgstr "" +"Използване на колелото на мишката, за смяна между тресчотъчно и свободно въртене. \n" +"Движението на колелото е винаги тресчотъчно на 50." + +#: lib/logitech_receiver/settings_templates.py:707 +msgid "Scroll Wheel Ratchet Torque" +msgstr "Тресчотъчно Усилие на Скролиращото Колело" + +#: lib/logitech_receiver/settings_templates.py:708 +msgid "Change the torque needed to overcome the ratchet." +msgstr "Промяна на усилието, нужно за преодоляване на тресчотката." + +#: lib/logitech_receiver/settings_templates.py:743 +msgid "Key/Button Actions" +msgstr "Действия на Клавиши / Бутони" + +#: lib/logitech_receiver/settings_templates.py:745 +msgid "Change the action for the key or button." +msgstr "Промяна действието за клавиш или бутон." + +#: lib/logitech_receiver/settings_templates.py:747 +msgid "Overridden by diversion." +msgstr "Управлявано от отклонение." + +#: lib/logitech_receiver/settings_templates.py:749 +msgid "" +"Changing important actions (such as for the left mouse button) can result in an unusable system." +msgstr "" +"Промяна на важни действия ( като това за ляв бутон на мишката ) може да доведе до неизползваема " +"система." + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Key/Button Diversion" +msgstr "Отклоняване на Клавиши / Бутони" + +#: lib/logitech_receiver/settings_templates.py:925 +msgid "" +"Make the key or button send HID++ notifications (Diverted) or initiate Mouse Gestures or Sliding " +"DPI" +msgstr "" +"Клавиш или бутон изпраща HID++ известие (Отклонено) или инициира Жест на Мишката или Скролиране DPI" + +#: lib/logitech_receiver/settings_templates.py:928 lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Diverted" +msgstr "Отклонени" + +#: lib/logitech_receiver/settings_templates.py:928 lib/logitech_receiver/settings_templates.py:929 +msgid "Mouse Gestures" +msgstr "Жестове на Мишката" + +#: lib/logitech_receiver/settings_templates.py:928 lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Regular" +msgstr "Обичайни" + +#: lib/logitech_receiver/settings_templates.py:928 +msgid "Sliding DPI" +msgstr "Скролиране DPI" + +#: lib/logitech_receiver/settings_templates.py:1018 lib/logitech_receiver/settings_templates.py:1046 +msgid "Sensitivity (DPI)" +msgstr "Чувствителност DPI" + +#: lib/logitech_receiver/settings_templates.py:1123 +msgid "Sensitivity Switching" +msgstr "Превключване на Чувствителност" + +#: lib/logitech_receiver/settings_templates.py:1125 +msgid "" +"Switch the current sensitivity and the remembered sensitivity when the key or button is pressed.\n" +"If there is no remembered sensitivity, just remember the current sensitivity" +msgstr "" +"Превключване между настоящата и запаметена чувствителност, когато клавиш или бутон е натиснат.\n" +"Ако все още няма запаметена чувствителност, запаметете настоящата" + +#: lib/logitech_receiver/settings_templates.py:1129 +msgid "Off" +msgstr "Изключване" + +#: lib/logitech_receiver/settings_templates.py:1160 +msgid "Disable keys" +msgstr "Деактивиране Клавиши" + +#: lib/logitech_receiver/settings_templates.py:1161 +msgid "Disable specific keyboard keys." +msgstr "Деактивиране на специфични клавиши от клавиатура." + +#: lib/logitech_receiver/settings_templates.py:1164 +#, python-format +msgid "Disables the %s key." +msgstr "Деактивиране на %s клавиш." + +#: lib/logitech_receiver/settings_templates.py:1177 lib/logitech_receiver/settings_templates.py:1234 +msgid "Set OS" +msgstr "Задаване на Операционна Система" + +#: lib/logitech_receiver/settings_templates.py:1178 lib/logitech_receiver/settings_templates.py:1235 +msgid "Change keys to match OS." +msgstr "Промяна на клавишите за да съответстват на Операционната Система." + +#: lib/logitech_receiver/settings_templates.py:1247 +msgid "Change Host" +msgstr "Промяна на Хост" + +#: lib/logitech_receiver/settings_templates.py:1248 +msgid "Switch connection to a different host" +msgstr "Смяна на връзката на друг Хост" + +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Performs a left click." +msgstr "Изпълнява ляв клик." + +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Single tap" +msgstr "Единично почукване" + +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Performs a right click." +msgstr "Изпълнява десен клик." + +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Single tap with two fingers" +msgstr "Единично почукване с два пръста" + +#: lib/logitech_receiver/settings_templates.py:1274 +msgid "Single tap with three fingers" +msgstr "Единично почукване с три пръста" + +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Double tap" +msgstr "Двойно почукване" + +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Performs a double click." +msgstr "Изпълнява двойно кликане." + +#: lib/logitech_receiver/settings_templates.py:1279 +msgid "Double tap with two fingers" +msgstr "Двойно почукване с два пръста" + +#: lib/logitech_receiver/settings_templates.py:1280 +msgid "Double tap with three fingers" +msgstr "Двойно почукване с три пръста" + +#: lib/logitech_receiver/settings_templates.py:1283 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Издърпва неща чрез издърпване на пръст след двойно почукване." + +#: lib/logitech_receiver/settings_templates.py:1283 +msgid "Tap and drag" +msgstr "Почукване и издърпване" + +#: lib/logitech_receiver/settings_templates.py:1285 +msgid "Tap and drag with two fingers" +msgstr "Почукване и издърпване с два пръста" + +#: lib/logitech_receiver/settings_templates.py:1286 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Издърпва неща чрез издърпване на пръсти след двойно почукване." + +#: lib/logitech_receiver/settings_templates.py:1288 +msgid "Tap and drag with three fingers" +msgstr "Почукване и издърпване с три пръста" + +#: lib/logitech_receiver/settings_templates.py:1291 +msgid "Suppress tap and edge gestures" +msgstr "Подтискане на почукване и жестове по ръба(периферията)" + +#: lib/logitech_receiver/settings_templates.py:1292 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "Деактивиране на почукване и жестове по периферията (равносилни на натискане на Fn+ляв клик)." + +#: lib/logitech_receiver/settings_templates.py:1294 +msgid "Scroll with one finger" +msgstr "Скролиране с един пръст" + +#: lib/logitech_receiver/settings_templates.py:1294 lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 +msgid "Scrolls." +msgstr "Скролове." + +#: lib/logitech_receiver/settings_templates.py:1295 lib/logitech_receiver/settings_templates.py:1298 +msgid "Scroll with two fingers" +msgstr "Скролиране с два пръста" + +#: lib/logitech_receiver/settings_templates.py:1296 +msgid "Scroll horizontally with two fingers" +msgstr "Хоризонтално скролиране с два пръста" + +#: lib/logitech_receiver/settings_templates.py:1296 +msgid "Scrolls horizontally." +msgstr "Хоризонтално скролиране." + +#: lib/logitech_receiver/settings_templates.py:1297 +msgid "Scroll vertically with two fingers" +msgstr "Вертикално скролиране с два пръста" + +#: lib/logitech_receiver/settings_templates.py:1297 +msgid "Scrolls vertically." +msgstr "Вертикално скролиране." + +#: lib/logitech_receiver/settings_templates.py:1299 +msgid "Inverts the scrolling direction." +msgstr "Обръщане на посоката на скролиране/превъртане." + +#: lib/logitech_receiver/settings_templates.py:1299 +msgid "Natural scrolling" +msgstr "Натурално скролиране" + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Enables the thumbwheel." +msgstr "Активиране на колелото на палеца." + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Thumbwheel" +msgstr "Колело на палеца" + +#: lib/logitech_receiver/settings_templates.py:1311 lib/logitech_receiver/settings_templates.py:1315 +msgid "Swipe from the top edge" +msgstr "Превъртане от горния ръб" + +#: lib/logitech_receiver/settings_templates.py:1312 +msgid "Swipe from the left edge" +msgstr "Плъзгане от левия ръб" + +#: lib/logitech_receiver/settings_templates.py:1313 +msgid "Swipe from the right edge" +msgstr "Плъзгане от десния ръб" + +#: lib/logitech_receiver/settings_templates.py:1314 +msgid "Swipe from the bottom edge" +msgstr "Плъзгане от долния ръб" + +#: lib/logitech_receiver/settings_templates.py:1316 +msgid "Swipe two fingers from the left edge" +msgstr "Плъзгане с два пръста от левия ръб" + +#: lib/logitech_receiver/settings_templates.py:1317 +msgid "Swipe two fingers from the right edge" +msgstr "Плъзгане с два пръста от десния ръб" + +#: lib/logitech_receiver/settings_templates.py:1318 +msgid "Swipe two fingers from the bottom edge" +msgstr "Плъзгане с два пръста от долния ръб" + +#: lib/logitech_receiver/settings_templates.py:1319 +msgid "Swipe two fingers from the top edge" +msgstr "Плъзгане с два пръста от горния ръб" + +#: lib/logitech_receiver/settings_templates.py:1320 lib/logitech_receiver/settings_templates.py:1324 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "Щипнване за отдалечаване; разделяне на пръсти за приближаване." + +#: lib/logitech_receiver/settings_templates.py:1320 +msgid "Zoom with two fingers." +msgstr "Прближаване с два пръста." + +#: lib/logitech_receiver/settings_templates.py:1321 +msgid "Pinch to zoom out." +msgstr "Щипване за отдалечаване." + +#: lib/logitech_receiver/settings_templates.py:1322 +msgid "Spread to zoom in." +msgstr "Разделяне на пръсти за приближаване." + +#: lib/logitech_receiver/settings_templates.py:1323 +msgid "Zoom with three fingers." +msgstr "Приближаване с три пръста." + +#: lib/logitech_receiver/settings_templates.py:1324 +msgid "Zoom with two fingers" +msgstr "Приближаване с два пръста" + +#: lib/logitech_receiver/settings_templates.py:1342 +msgid "Pixel zone" +msgstr "Зона на пиксели" + +#: lib/logitech_receiver/settings_templates.py:1343 +msgid "Ratio zone" +msgstr "Съотношение на зона" + +#: lib/logitech_receiver/settings_templates.py:1344 +msgid "Scale factor" +msgstr "Коефициент на мащабиране" + +#: lib/logitech_receiver/settings_templates.py:1344 +msgid "Sets the cursor speed." +msgstr "Задаване на скорост на курсора." + +#: lib/logitech_receiver/settings_templates.py:1348 +msgid "Left" +msgstr "Ляво" + +#: lib/logitech_receiver/settings_templates.py:1348 +msgid "Left-most coordinate." +msgstr "Крайни леви координати." + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom" +msgstr "Долу" + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom coordinate." +msgstr "Долни координати." + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width" +msgstr "Широчина" + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width." +msgstr "Широчина." + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height" +msgstr "Височина" + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height." +msgstr "Височина." + +#: lib/logitech_receiver/settings_templates.py:1352 +msgid "Cursor speed." +msgstr "Скорост на курсора." + +#: lib/logitech_receiver/settings_templates.py:1352 +msgid "Scale" +msgstr "Мащабиране" + +#: lib/logitech_receiver/settings_templates.py:1358 +msgid "Gestures" +msgstr "Жестове" + +#: lib/logitech_receiver/settings_templates.py:1359 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Настройка на поведението на мишка/тъчпад." + +#: lib/logitech_receiver/settings_templates.py:1375 +msgid "Gestures Diversion" +msgstr "Отклонение на Жестове" + +#: lib/logitech_receiver/settings_templates.py:1376 +msgid "Divert mouse/touchpad gestures." +msgstr "Отклоняване на жестове на мишка/тъчпад." + +#: lib/logitech_receiver/settings_templates.py:1392 +msgid "Gesture params" +msgstr "Параметри на жестовете" + +#: lib/logitech_receiver/settings_templates.py:1393 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Промяна на числените параметри на мишка/тъчпад." + +#: lib/logitech_receiver/settings_templates.py:1417 +msgid "M-Key LEDs" +msgstr "M-клавиш Светодиоди" + +#: lib/logitech_receiver/settings_templates.py:1419 +msgid "Control the M-Key LEDs." +msgstr "Контрол на M-клавиш Светодиоди." + +#: lib/logitech_receiver/settings_templates.py:1423 lib/logitech_receiver/settings_templates.py:1454 +msgid "May need G Keys diverted to be effective." +msgstr "Възможно е да има нужда от отклоняване на G-клавиш за ефективност." + +#: lib/logitech_receiver/settings_templates.py:1429 +#, python-format +msgid "Lights up the %s key." +msgstr "Осветяване на %s клавиш." + +#: lib/logitech_receiver/settings_templates.py:1448 +msgid "MR-Key LED" +msgstr "MR-клавиш Светодиод" + +#: lib/logitech_receiver/settings_templates.py:1450 +msgid "Control the MR-Key LED." +msgstr "Контрол на MR-клавиш Светодиод." + +#: lib/logitech_receiver/settings_templates.py:1471 +msgid "Persistent Key/Button Mapping" +msgstr "Присъствие на Пренареждане на клавиш/бутон" + +#: lib/logitech_receiver/settings_templates.py:1473 +msgid "Permanently change the mapping for the key or button." +msgstr "Перманентна промяна на пренареждане за клавиш или бутон." + +#: lib/logitech_receiver/settings_templates.py:1475 +msgid "" +"Changing important keys or buttons (such as for the left mouse button) can result in an unusable " +"system." +msgstr "" +"Промяна на важни клавиши или бутони ( например левият бутон на мишката ) може да доведе до " +"неизползваема система." + +#: lib/logitech_receiver/settings_templates.py:1532 +msgid "Sidetone" +msgstr "Страничен камък" + +#: lib/logitech_receiver/settings_templates.py:1533 +msgid "Set sidetone level." +msgstr "Задаване на ниво на страничен камък." + +#: lib/logitech_receiver/settings_templates.py:1542 +msgid "Equalizer" +msgstr "Еквалайзер (Изравнител)" + +#: lib/logitech_receiver/settings_templates.py:1543 +msgid "Set equalizer levels." +msgstr "Задаване на нива за еквалайзер (изравнител)." + +#: lib/logitech_receiver/settings_templates.py:1565 +msgid "Hz" +msgstr "Херц" + +#: lib/logitech_receiver/settings_templates.py:1571 +msgid "Power Management" +msgstr "Управление на Мощността" + +#: lib/logitech_receiver/settings_templates.py:1572 +msgid "Power off in minutes (0 for never)." +msgstr "Изключване след минути (0 за никога)." + +#: lib/logitech_receiver/settings_templates.py:1584 +msgid "Brightness Control" +msgstr "Контрол на Осветеност" + +#: lib/logitech_receiver/settings_templates.py:1585 +msgid "Control overall brightness" +msgstr "Контрол на обща осветеност" + +#: lib/logitech_receiver/settings_templates.py:1628 lib/logitech_receiver/settings_templates.py:1681 +msgid "LED Control" +msgstr "Светодиоден Контрол" + +#: lib/logitech_receiver/settings_templates.py:1629 lib/logitech_receiver/settings_templates.py:1682 +msgid "Switch control of LED zones between device and Solaar" +msgstr "Превключване контрола на светодиодните зони между устройство и Solaar" + +#: lib/logitech_receiver/settings_templates.py:1644 lib/logitech_receiver/settings_templates.py:1692 +msgid "LED Zone Effects" +msgstr "Ефекти за Светодиодни Зони" + +#: lib/logitech_receiver/settings_templates.py:1645 lib/logitech_receiver/settings_templates.py:1693 +msgid "LED Control needs to be set to Solaar to be effective." +msgstr "Нужно е контрола на светодиодите да бъде зададен от Solaar за да бъде ефективно." + +#: lib/logitech_receiver/settings_templates.py:1645 lib/logitech_receiver/settings_templates.py:1693 +msgid "Set effect for LED Zone" +msgstr "Задаване на ефект на светодиодни зони" + +#: lib/logitech_receiver/settings_templates.py:1647 +msgid "Color" +msgstr "Цвят" + +#: lib/logitech_receiver/settings_templates.py:1648 +msgid "Speed" +msgstr "Скорост" + +#: lib/logitech_receiver/settings_templates.py:1649 +msgid "Period" +msgstr "Период" + +#: lib/logitech_receiver/settings_templates.py:1650 +msgid "Intensity" +msgstr "Интензитет" + +#: lib/logitech_receiver/settings_templates.py:1651 +msgid "Ramp" +msgstr "Ограничение" + +#: lib/logitech_receiver/settings_templates.py:1666 +msgid "LEDs" +msgstr "Светодиоди" + +#: lib/logitech_receiver/settings_templates.py:1703 +msgid "Per-key Lighting" +msgstr "Осветяване според клавиш" + +#: lib/logitech_receiver/settings_templates.py:1704 +msgid "Control per-key lighting." +msgstr "Контрол на осветяване според клавиш." + +#: lib/logitech_receiver/settings_templates.py:1786 +msgid "Force Sensing Buttons" +msgstr "Чувствителност към Сила на бутони" + +#: lib/logitech_receiver/settings_templates.py:1787 +msgid "Change the force required to activate button." +msgstr "Промяна на силата необходима за активиране на бутон." + +#: lib/logitech_receiver/settings_templates.py:1804 +msgid "Force Sensing Button" +msgstr "Чувствителност на сила на Бутон" + +#: lib/logitech_receiver/settings_templates.py:1814 +msgid "Haptic Feedback Level" +msgstr "Ниво на Обратна връзка" + +#: lib/logitech_receiver/settings_templates.py:1815 +msgid "Change power of haptic feedback. (Zero to turn off.)" +msgstr "Промяна на мощността на обратната връзка. (нула за изключване.)" + +#: lib/logitech_receiver/settings_templates.py:1857 +msgid "Play Haptic Waveform" +msgstr "Форма на вълната на обратната връзка" + +#: lib/logitech_receiver/settings_templates.py:1858 +msgid "Tell device to play a haptic waveform." +msgstr "Команда към устройство да възпроизведе обратна връзка." + +#: lib/solaar/ui/__init__.py:120 +msgid "Another Solaar process is already running so just expose its window" +msgstr "Друго подпроцес на Solaar е стартира, за това просто използвайте неговия прозорец" + +#: lib/solaar/ui/about/model.py:36 +msgid "" +"Manages Logitech receivers,\n" +"keyboards, mice, and tablets." +msgstr "" +"Управлява Logitech приемници,\n" +"клавиатури, мишки и таблети." + +#: lib/solaar/ui/about/model.py:64 +msgid "Additional Programming" +msgstr "Допълнително Програмиране" + +#: lib/solaar/ui/about/model.py:65 +msgid "GUI design" +msgstr "Дизай на графичен интерфейс" + +#: lib/solaar/ui/about/model.py:67 +msgid "Testing" +msgstr "Тестване" + +#: lib/solaar/ui/about/model.py:75 +msgid "Logitech documentation" +msgstr "Logitech документация" + +#: lib/solaar/ui/action.py:88 lib/solaar/ui/action.py:92 lib/solaar/ui/window.py:208 +msgid "Unpair" +msgstr "Разсдвояване" + +#: lib/solaar/ui/action.py:91 lib/solaar/ui/diversion_rules.py:141 +msgid "Cancel" +msgstr "Отказ" + +#: lib/solaar/ui/common.py:42 +msgid "Permissions error" +msgstr "Грешка в правата" + +#: lib/solaar/ui/common.py:44 +#, python-format +msgid "Found a Logitech receiver or device (%s), but did not have permission to open it." +msgstr "Открит е Logitech приемник или устройство (%s), но нямате права да го отворите." + +#: lib/solaar/ui/common.py:46 +msgid "" +"If you've just installed Solaar, try disconnecting the receiver or device and then reconnecting it." +msgstr "" +"Ако Solaar е ново инсталиран, опитайте да премахнете приемника или устройството и след това да го " +"включите отново." + +#: lib/solaar/ui/common.py:49 +msgid "Cannot connect to device error" +msgstr "Грешка! Не може да се осъществи свързване с устройство" + +#: lib/solaar/ui/common.py:51 +#, python-format +msgid "Found a Logitech receiver or device at %s, but encountered an error connecting to it." +msgstr "Открит е Logitech приемник или устройство %s, но възникна грешка при свързването." + +#: lib/solaar/ui/common.py:53 +msgid "Try disconnecting the device and then reconnecting it or turning it off and then on." +msgstr "Опитайте да разкачите и включите устройството от USB-то или от самото устройство." + +#: lib/solaar/ui/common.py:56 +msgid "Unpairing failed" +msgstr "Неуспешно разсдвояване" + +#: lib/solaar/ui/common.py:58 +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Неуспех при разсдвояването %{device} от %{receiver}." + +#: lib/solaar/ui/common.py:63 +msgid "The receiver returned an error, with no further details." +msgstr "Приемникът връща грешка, без допълнителни детайли." + +#: lib/solaar/ui/config_panel.py:245 +msgid "Complete - ENTER to change" +msgstr "Готово - Влезте за промяна" + +#: lib/solaar/ui/config_panel.py:245 +msgid "Incomplete" +msgstr "Недовършено" + +#: lib/solaar/ui/config_panel.py:491 lib/solaar/ui/config_panel.py:543 +#, python-format +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d стойност" +msgstr[1] "%d стойност" + +#: lib/solaar/ui/config_panel.py:642 +msgid "Changes allowed" +msgstr "Промените са разрешени" + +#: lib/solaar/ui/config_panel.py:643 +msgid "No changes allowed" +msgstr "Промените са забранени" + +#: lib/solaar/ui/config_panel.py:644 +msgid "Ignore this setting" +msgstr "Игнориране на тази настройка" + +#: lib/solaar/ui/config_panel.py:690 +msgid "Working" +msgstr "Работа" + +#: lib/solaar/ui/config_panel.py:693 +msgid "Read/write operation failed." +msgstr "Read/write неуспешна операция." + +#: lib/solaar/ui/desktop_notifications.py:112 +msgid "unspecified reason" +msgstr "неопределена причина" + +#: lib/solaar/ui/diversion_rules.py:103 +msgid "Built-in rules" +msgstr "Вградени правила" + +#: lib/solaar/ui/diversion_rules.py:103 +msgid "User-defined rules" +msgstr "Правила дефинирани от потребителя" + +#: lib/solaar/ui/diversion_rules.py:105 lib/solaar/ui/diversion_rules.py:1126 +msgid "Rule" +msgstr "Правило" + +#: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:382 +#: lib/solaar/ui/diversion_rules.py:509 +msgid "Sub-rule" +msgstr "Под-правило" + +#: lib/solaar/ui/diversion_rules.py:108 +msgid "[empty]" +msgstr "(празно)" + +#: lib/solaar/ui/diversion_rules.py:132 +msgid "Make changes permanent?" +msgstr "Да направя ли промените постоянни ?" + +#: lib/solaar/ui/diversion_rules.py:137 +msgid "Yes" +msgstr "Да" + +#: lib/solaar/ui/diversion_rules.py:139 +msgid "No" +msgstr "Не" + +#: lib/solaar/ui/diversion_rules.py:144 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "Ако изберете Не, промените ще бъдат загубени и Solaar затворен." + +#: lib/solaar/ui/diversion_rules.py:273 +msgid "Paste here" +msgstr "Поставете тук" + +#: lib/solaar/ui/diversion_rules.py:275 +msgid "Paste above" +msgstr "Поставете отгоре" + +#: lib/solaar/ui/diversion_rules.py:277 +msgid "Paste below" +msgstr "Поставете отдолу" + +#: lib/solaar/ui/diversion_rules.py:283 +msgid "Paste rule here" +msgstr "Поставете правило тук" + +#: lib/solaar/ui/diversion_rules.py:285 +msgid "Paste rule above" +msgstr "Поставете правило отгоре" + +#: lib/solaar/ui/diversion_rules.py:287 +msgid "Paste rule below" +msgstr "Поставете правило отдолу" + +#: lib/solaar/ui/diversion_rules.py:291 +msgid "Paste rule" +msgstr "Поставете правило" + +#: lib/solaar/ui/diversion_rules.py:306 +msgid "Insert here" +msgstr "Вмъкнете тук" + +#: lib/solaar/ui/diversion_rules.py:308 +msgid "Insert above" +msgstr "Вмъкнете отгоре" + +#: lib/solaar/ui/diversion_rules.py:310 +msgid "Insert below" +msgstr "Вмъкнете отдолу" + +#: lib/solaar/ui/diversion_rules.py:316 +msgid "Insert new rule here" +msgstr "Вмъкнете ново правило тук" + +#: lib/solaar/ui/diversion_rules.py:318 +msgid "Insert new rule above" +msgstr "Вмъкнете ново правило отгоре" + +#: lib/solaar/ui/diversion_rules.py:320 +msgid "Insert new rule below" +msgstr "Вмъкнете ново правило отдолу" + +#: lib/solaar/ui/diversion_rules.py:347 +msgid "Flatten" +msgstr "Заравнен" + +#: lib/solaar/ui/diversion_rules.py:380 +msgid "Insert" +msgstr "Вмъкнете" + +#: lib/solaar/ui/diversion_rules.py:383 lib/solaar/ui/diversion_rules.py:511 +#: lib/solaar/ui/diversion_rules.py:1158 +msgid "Or" +msgstr "Или" + +#: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:510 +#: lib/solaar/ui/diversion_rules.py:1144 +msgid "And" +msgstr "И" + +#: lib/solaar/ui/diversion_rules.py:386 +msgid "Condition" +msgstr "Състояние" + +#: lib/solaar/ui/diversion_rules.py:388 lib/solaar/ui/rule_conditions.py:154 +msgid "Feature" +msgstr "Функция" + +#: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:188 +msgid "Report" +msgstr "Рапорт" + +#: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:69 +msgid "Process" +msgstr "Процес" + +#: lib/solaar/ui/diversion_rules.py:391 +msgid "Mouse process" +msgstr "Процес на мишка" + +#: lib/solaar/ui/diversion_rules.py:392 lib/solaar/ui/rule_conditions.py:225 +msgid "Modifiers" +msgstr "Мидификатори" + +#: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:277 +msgid "Key" +msgstr "Клавиш" + +#: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:318 +msgid "KeyIsDown" +msgstr "Натиснат клавиш" + +#: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/diversion_rules.py:1432 +msgid "Active" +msgstr "Активен" + +#: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1390 +#: lib/solaar/ui/diversion_rules.py:1441 lib/solaar/ui/diversion_rules.py:1487 +msgid "Device" +msgstr "Устройство" + +#: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1467 +msgid "Host" +msgstr "Хост" + +#: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1506 +msgid "Setting" +msgstr "Настройки" + +#: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/rule_conditions.py:333 +#: lib/solaar/ui/rule_conditions.py:382 +msgid "Test" +msgstr "Тест" + +#: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:507 +msgid "Test bytes" +msgstr "Тест битове" + +#: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:608 +msgid "Mouse Gesture" +msgstr "Жест с мишка" + +#: lib/solaar/ui/diversion_rules.py:405 +msgid "Action" +msgstr "Действие" + +#: lib/solaar/ui/diversion_rules.py:407 lib/solaar/ui/rule_actions.py:138 +msgid "Key press" +msgstr "Натискане клавиш" + +#: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:191 +msgid "Mouse scroll" +msgstr "Скрол на мишка" + +#: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:256 +msgid "Mouse click" +msgstr "Клик на мишка" + +#: lib/solaar/ui/diversion_rules.py:410 +msgid "Set" +msgstr "Задаване" + +#: lib/solaar/ui/diversion_rules.py:411 lib/solaar/ui/rule_actions.py:328 +msgid "Execute" +msgstr "Изпълнение" + +#: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/diversion_rules.py:1189 +msgid "Later" +msgstr "По-късно" + +#: lib/solaar/ui/diversion_rules.py:441 +msgid "Insert new rule" +msgstr "Вмъкнете ново правило" + +#: lib/solaar/ui/diversion_rules.py:461 lib/solaar/ui/rule_actions.py:82 +#: lib/solaar/ui/rule_actions.py:287 lib/solaar/ui/rule_conditions.py:556 +msgid "Delete" +msgstr "Изтриване" + +#: lib/solaar/ui/diversion_rules.py:483 +msgid "Negate" +msgstr "Отричане" + +#: lib/solaar/ui/diversion_rules.py:507 +msgid "Wrap with" +msgstr "Ограждане с" + +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Cut" +msgstr "Изрязване" + +#: lib/solaar/ui/diversion_rules.py:553 +msgid "Paste" +msgstr "Поставяне" + +#: lib/solaar/ui/diversion_rules.py:559 +msgid "Copy" +msgstr "Копиране" + +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Solaar Rule Editor" +msgstr "Solaar Редактор на правила" + +#: lib/solaar/ui/diversion_rules.py:668 +msgid "Save changes" +msgstr "Запазване на промените" + +#: lib/solaar/ui/diversion_rules.py:673 +msgid "Discard changes" +msgstr "Отхвърляне на промените" + +#: lib/solaar/ui/diversion_rules.py:1104 +msgid "This editor does not support the selected rule component yet." +msgstr "Този редактор не поддържа все още този компонент." + +#: lib/solaar/ui/diversion_rules.py:1169 +msgid "Number of seconds to delay. Delay between 0 and 1 is done with higher precision." +msgstr "" +"Стойност в секунди за закъснение. Закъснение между 0 и 1 се изпълнява с по-голяма прецизност." + +#: lib/solaar/ui/diversion_rules.py:1207 +msgid "Not" +msgstr "Не" + +#: lib/solaar/ui/diversion_rules.py:1238 +msgid "Toggle" +msgstr "Промяна състояние" + +#: lib/solaar/ui/diversion_rules.py:1239 +msgid "True" +msgstr "Вярно" + +#: lib/solaar/ui/diversion_rules.py:1240 +msgid "False" +msgstr "Грешно" + +#: lib/solaar/ui/diversion_rules.py:1253 +msgid "Unsupported setting" +msgstr "Неподдържана настройка" + +#: lib/solaar/ui/diversion_rules.py:1396 lib/solaar/ui/diversion_rules.py:1415 +#: lib/solaar/ui/diversion_rules.py:1493 lib/solaar/ui/diversion_rules.py:1748 +#: lib/solaar/ui/diversion_rules.py:1766 +msgid "Originating device" +msgstr "Ориентация на устройство" + +#: lib/solaar/ui/diversion_rules.py:1428 +msgid "Device is active and its settings can be changed." +msgstr "Устройството е активно и настройката му може да бъде променена." + +#: lib/solaar/ui/diversion_rules.py:1437 +msgid "Device that originated the current notification." +msgstr "Устройството от което произлиза текущото известие." + +#: lib/solaar/ui/diversion_rules.py:1450 +msgid "Name of host computer." +msgstr "Има на хост компютъра." + +#: lib/solaar/ui/diversion_rules.py:1520 +msgid "Value" +msgstr "Стойност" + +#: lib/solaar/ui/diversion_rules.py:1529 +msgid "Item" +msgstr "Артикул" + +#: lib/solaar/ui/diversion_rules.py:1808 +msgid "Change setting on device" +msgstr "Промяна на настройки за устройство" + +#: lib/solaar/ui/diversion_rules.py:1824 +msgid "Setting on device" +msgstr "Настройване на устройство" + +#: lib/solaar/ui/pair_window.py:44 lib/solaar/ui/pair_window.py:167 +#, python-format +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: сдвояване на ново устройство" + +#: lib/solaar/ui/pair_window.py:46 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "Bolt приемниците са съвместими само с Bolt устройства." + +#: lib/solaar/ui/pair_window.py:48 +msgid "Press a pairing button or key until the pairing light flashes quickly." +msgstr "Натиснете бутона за сдвояване докато светлинния индикатор започне да премигва бързо." + +#: lib/solaar/ui/pair_window.py:51 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "Unifyng приемниците са съвместими само с Unifying устройства." + +#: lib/solaar/ui/pair_window.py:53 +msgid "Other receivers are only compatible with a few devices." +msgstr "Други приемници са съвместими с няколко устройсва." + +#: lib/solaar/ui/pair_window.py:55 +msgid "For most devices, turn on the device you want to pair." +msgstr "За повечето устройства, включете устройството което искате да сдвоите." + +#: lib/solaar/ui/pair_window.py:56 +msgid "If the device is already turned on, turn it off and on again." +msgstr "Ако устройството е вече включено, изключете го и го включете отново." + +#: lib/solaar/ui/pair_window.py:58 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "Устройството не бива да е сдвоено с включен приемник наоколо." + +#: lib/solaar/ui/pair_window.py:61 +msgid "" +"For devices with multiple channels, press, hold, and release the button for the channel you wish " +"to pair\n" +"or use the channel switch button to select a channel and then press, hold, and release the channel " +"switch button." +msgstr "" +"За устройства с повече от един канал, натиснете, задръжте и пусната бутона за избор на каналите, " +"на устройството което желаете да сдвоите\n" +"или използвайте бутона за каналите за да изберете канал, след това натиснете, задръжте и пуснене " +"бутона." + +#: lib/solaar/ui/pair_window.py:68 +msgid "The channel indicator light should be blinking rapidly." +msgstr "Индикаторът за каналите трябва да мига бързо." + +#: lib/solaar/ui/pair_window.py:72 +#, python-format +msgid "" +"\n" +"\n" +"This receiver has %d pairing remaining." +msgid_plural "" +"\n" +"\n" +"This receiver has %d pairings remaining." +msgstr[0] "" +"\n" +"\n" +"Този приемник има %d възможни сдвоявания." +msgstr[1] "" +"\n" +"\n" +"Този приемник има %d възможни сдвоявания." + +#: lib/solaar/ui/pair_window.py:78 +msgid "" +"\n" +"Cancelling at this point will not use up a pairing." +msgstr "" +"\n" +"Отказ в този момент, няма да изразходи сдвояване." + +#: lib/solaar/ui/pair_window.py:168 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "Въведете парола за %(name)s." + +#: lib/solaar/ui/pair_window.py:171 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "Въведете %(passcode)s и натиснете клавиш enter." + +#: lib/solaar/ui/pair_window.py:176 +msgid "left" +msgstr "ляво" + +#: lib/solaar/ui/pair_window.py:176 +msgid "right" +msgstr "дясно" + +#: lib/solaar/ui/pair_window.py:178 +#, python-format +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"Натиснете %(code)s\n" +"и натиснете левия и десния бутан едновременно." + +#: lib/solaar/ui/pair_window.py:221 +msgid "The wireless link is not encrypted" +msgstr "Безжичната връзка не е криптирана" + +#: lib/solaar/ui/pair_window.py:226 +msgid "Found a new device:" +msgstr "Открито е ново устройство:" + +#: lib/solaar/ui/pair_window.py:247 +msgid "Pairing failed" +msgstr "Неуспешно сдвояване" + +#: lib/solaar/ui/pair_window.py:249 +msgid "Make sure your device is within range, and has a decent battery charge." +msgstr "Уверете се че устройството ви е в рамките на обхвата и има добър заряд на батерията." + +#: lib/solaar/ui/pair_window.py:251 +msgid "A new device was detected, but it is not compatible with this receiver." +msgstr "Открито е ново устройство, но не е съвместимо с този приемник." + +#: lib/solaar/ui/pair_window.py:253 +msgid "More paired devices than receiver can support." +msgstr "Броя сдвоени устройства надхвърля лимита на приемника." + +#: lib/solaar/ui/pair_window.py:255 +msgid "No further details are available about the error." +msgstr "Няма повече налични детайи, за тази грешка." + +#: lib/solaar/ui/rule_actions.py:54 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Симулиране на клик на кабелен клавиш или натискане или пускане.\n" +"В Wayland са нужни права за запис в /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:60 +msgid "Add key" +msgstr "Добави клавиш" + +#: lib/solaar/ui/rule_actions.py:63 +msgid "Click" +msgstr "Клик" + +#: lib/solaar/ui/rule_actions.py:66 +msgid "Depress" +msgstr "Натискане" + +#: lib/solaar/ui/rule_actions.py:69 +msgid "Release" +msgstr "Отпускане" + +#: lib/solaar/ui/rule_actions.py:153 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "Симулиране на скрол на мишка. В Wayland са нужни права за запис в /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:211 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Симулиране на клик на мишка\n" +"В Wayland са нужни права за запис в /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:216 +msgid "Button" +msgstr "Бутон" + +#: lib/solaar/ui/rule_actions.py:218 +msgid "Action (and Count, if click)" +msgstr "Действие ( и броене, ако кликне)" + +#: lib/solaar/ui/rule_actions.py:269 +msgid "Execute a command with arguments." +msgstr "Изоълнение на команда с аргументи." + +#: lib/solaar/ui/rule_actions.py:273 +msgid "Add argument" +msgstr "Добавиете аргумент" + +#: lib/solaar/ui/rule_conditions.py:52 +msgid "X11 active process. For use in X11 only." +msgstr "X11 активен процес. Само за използване в X11." + +#: lib/solaar/ui/rule_conditions.py:82 +msgid "X11 mouse process. For use in X11 only." +msgstr "Х11 процес мишка. Само за използване в Х11." + +#: lib/solaar/ui/rule_conditions.py:99 +msgid "MouseProcess" +msgstr "Процес мишка" + +#: lib/solaar/ui/rule_conditions.py:123 +msgid "Feature name of notification triggering rule processing." +msgstr "Обработка на име на функцията стартираща правило." + +#: lib/solaar/ui/rule_conditions.py:169 +msgid "Report number of notification triggering rule processing." +msgstr "Рапорт за броя на известията, активиращи обработка на правилата." + +#: lib/solaar/ui/rule_conditions.py:201 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "Активни модификатори на клавиатура. Не винаги са възможни в Wayland." + +#: lib/solaar/ui/rule_conditions.py:241 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and buttons." +msgstr "" +"Отклонен клавиш или натиснат или освободен бутон.\n" +"Използвайте настройката за отклоняване на клавиш/бутон и отклоняването на клавиш-G за да отклоните " +"клавиши или бутони." + +#: lib/solaar/ui/rule_conditions.py:250 +msgid "Key down" +msgstr "Клавиш долу" + +#: lib/solaar/ui/rule_conditions.py:253 +msgid "Key up" +msgstr "Клавиш горе" + +#: lib/solaar/ui/rule_conditions.py:293 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and buttons." +msgstr "" +"Отклонен клавиш или бутон понастоящем е долу.\n" +"Използвайте настройката за отклоняване на клавиш/бутон и отклоняването на клавиш-G за да отклоните " +"клавиши или бутони." + +#: lib/solaar/ui/rule_conditions.py:331 +msgid "Test condition on notification triggering rule processing." +msgstr "Тест на състоянието на известие активиращо правило за обработка." + +#: lib/solaar/ui/rule_conditions.py:335 +msgid "Parameter" +msgstr "Параметър" + +#: lib/solaar/ui/rule_conditions.py:408 +msgid "begin (inclusive)" +msgstr "начало (включително)" + +#: lib/solaar/ui/rule_conditions.py:409 +msgid "end (exclusive)" +msgstr "край (изключаещо)" + +#: lib/solaar/ui/rule_conditions.py:417 +msgid "range" +msgstr "обхват" + +#: lib/solaar/ui/rule_conditions.py:420 +msgid "minimum" +msgstr "минимум" + +#: lib/solaar/ui/rule_conditions.py:421 +msgid "maximum" +msgstr "максимум" + +#: lib/solaar/ui/rule_conditions.py:423 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "байтове от %(0)d до %(1)d, обхващащи от %(2)d до %(3)d" + +#: lib/solaar/ui/rule_conditions.py:426 lib/solaar/ui/rule_conditions.py:427 +msgid "mask" +msgstr "маска" + +#: lib/solaar/ui/rule_conditions.py:428 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "байтове от %(0)d от %(1)d, маска %(2)d" + +#: lib/solaar/ui/rule_conditions.py:437 +msgid "Bit or range test on bytes in notification message triggering rule processing." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:447 +msgid "type" +msgstr "тип" + +#: lib/solaar/ui/rule_conditions.py:535 +msgid "Mouse gesture with optional initiating button followed by zero or more mouse movements." +msgstr "Жест на мишката с опционално инициращ бутон последвано от нола или повече движения на мишка." + +#: lib/solaar/ui/rule_conditions.py:541 +msgid "Add movement" +msgstr "Добавяне движение" + +#: lib/solaar/ui/tray.py:55 +msgid "No supported device found" +msgstr "Открито е непотдържано устройство" + +#: lib/solaar/ui/tray.py:60 lib/solaar/ui/window.py:328 +#, python-format +msgid "About %s" +msgstr "Относно %s" + +#: lib/solaar/ui/tray.py:61 lib/solaar/ui/window.py:326 +#, python-format +msgid "Quit %s" +msgstr "Спиране на „%s“" + +#: lib/solaar/ui/tray.py:273 lib/solaar/ui/tray.py:281 +msgid "no receiver" +msgstr "няма приемник" + +#: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:299 lib/solaar/ui/window.py:728 +msgid "offline" +msgstr "офлайн" + +#: lib/solaar/ui/tray.py:297 +msgid "no status" +msgstr "без статус" + +#: lib/solaar/ui/window.py:110 +msgid "Scanning" +msgstr "Сканиране" + +#: lib/solaar/ui/window.py:141 +msgid "Battery" +msgstr "Батерия" + +#: lib/solaar/ui/window.py:144 +msgid "Wireless Link" +msgstr "Безжична връзка" + +#: lib/solaar/ui/window.py:148 +msgid "Lighting" +msgstr "Осветление" + +#: lib/solaar/ui/window.py:182 +msgid "Show Technical Details" +msgstr "Покажи Технически Детайли" + +#: lib/solaar/ui/window.py:198 +msgid "Pair new device" +msgstr "Сдвояване на ново устройство" + +#: lib/solaar/ui/window.py:216 +msgid "Select a device" +msgstr "Избор на устройство" + +#: lib/solaar/ui/window.py:331 +msgid "Rule Editor" +msgstr "Редактор за правила" + +#: lib/solaar/ui/window.py:522 +msgid "Path" +msgstr "Път" + +#: lib/solaar/ui/window.py:524 +msgid "USB ID" +msgstr "USB идентификатор" + +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 lib/solaar/ui/window.py:544 +#: lib/solaar/ui/window.py:546 +msgid "Serial" +msgstr "Сериен номер" + +#: lib/solaar/ui/window.py:533 +msgid "Index" +msgstr "Индекс" + +#: lib/solaar/ui/window.py:535 +msgid "Wireless PID" +msgstr "Безжичен PID" + +#: lib/solaar/ui/window.py:537 +msgid "Product ID" +msgstr "Идентификатор на продукта" + +#: lib/solaar/ui/window.py:539 +msgid "Protocol" +msgstr "Протокол" + +#: lib/solaar/ui/window.py:539 +msgid "Unknown" +msgstr "Неизвестно" + +#: lib/solaar/ui/window.py:541 +msgid "Polling rate" +msgstr "" + +#: lib/solaar/ui/window.py:548 +msgid "Unit ID" +msgstr "Идентификатор на единица(unit)" + +#: lib/solaar/ui/window.py:559 +msgid "none" +msgstr "няма" + +#: lib/solaar/ui/window.py:560 +msgid "Notifications" +msgstr "Известия" + +#: lib/solaar/ui/window.py:604 +msgid "No device paired." +msgstr "Няма сдвоено устройство." + +#: lib/solaar/ui/window.py:613 +#, python-format +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "С този приемник могат да бъдат сдвоени %(max_count)s устройства." +msgstr[1] "С този приемник могат да бъдат сдвоени %(max_count)s устройства." + +#: lib/solaar/ui/window.py:620 +msgid "Only one device can be paired to this receiver." +msgstr "Само едно устройство може да бъде сдвоено с този приемник." + +#: lib/solaar/ui/window.py:624 +#, python-format +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "Остават %d сдвоявания с този приемник." +msgstr[1] "Остават %d сдвоявания с този приемник." + +#: lib/solaar/ui/window.py:681 +msgid "Battery Voltage" +msgstr "Волтаж на батерия" + +#: lib/solaar/ui/window.py:683 +msgid "Voltage reported by battery" +msgstr "Волтаж рапортуван от батерията" + +#: lib/solaar/ui/window.py:685 +msgid "Battery Level" +msgstr "Ниво на батерията" + +#: lib/solaar/ui/window.py:687 +msgid "Approximate level reported by battery" +msgstr "Приблизително ниво рапортувано от батерията" + +#: lib/solaar/ui/window.py:694 lib/solaar/ui/window.py:696 +msgid "next reported " +msgstr "следващ рапортуван " + +#: lib/solaar/ui/window.py:697 +msgid " and next level to be reported." +msgstr " Очаква се следващ рапорт за ниво." + +#: lib/solaar/ui/window.py:702 +msgid "last known" +msgstr "последно известно" + +#: lib/solaar/ui/window.py:713 +msgid "encrypted" +msgstr "криптирано" + +#: lib/solaar/ui/window.py:715 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "Безжичната връска между това устройство и неговият приемник е криптирана." + +#: lib/solaar/ui/window.py:717 +msgid "not encrypted" +msgstr "не криптирано" + +#: lib/solaar/ui/window.py:721 +msgid "" +"The wireless link between this device and its receiver is not encrypted.\n" +"This is a security issue for pointing devices, and a major security issue for text-input devices." +msgstr "" +"Безжичната връзка между това устройство и неговият приемник не е криптирана\n" +"Това е сериозна заплаха за сигурността за курсорни устройства и голяма заплаха за сигурността на " +"текстово-въвеждащи устройства." + +#: lib/solaar/ui/window.py:737 +#, python-format +msgid "%(light_level)d lux" +msgstr "" diff --git a/po/ca.po b/po/ca.po index 42a3006a..d75b8927 100644 --- a/po/ca.po +++ b/po/ca.po @@ -3,1481 +3,1976 @@ # This file is distributed under the same license as the Solaar package. # Marc Serra , 2021. # -msgid "" -msgstr "" -"Project-Id-Version: solaar 0.9.3\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-09-25 13:56-0400\n" -"PO-Revision-Date: 2021-10-22 19:19+0200\n" -"Language-Team: none\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.3\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"Last-Translator: \n" -"Language: ca\n" +msgid "" +msgstr "Project-Id-Version: solaar 0.9.3\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2023-12-28 17:40+0100\n" + "PO-Revision-Date: 2021-10-22 19:19+0200\n" + "Last-Translator: \n" + "Language-Team: none\n" + "Language: ca\n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + "X-Generator: Poedit 2.3\n" + "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: lib/logitech_receiver/base_usb.py:50 -msgid "Unifying Receiver" -msgstr "Receptor Unifying" +#: lib/logitech_receiver/base_usb.py:46 +msgid "Bolt Receiver" +msgstr "" -#: lib/logitech_receiver/base_usb.py:58 lib/logitech_receiver/base_usb.py:68 -#: lib/logitech_receiver/base_usb.py:79 lib/logitech_receiver/base_usb.py:90 -#: lib/logitech_receiver/base_usb.py:101 -msgid "Nano Receiver" -msgstr "Receptor Nano" +#: lib/logitech_receiver/base_usb.py:57 +msgid "Unifying Receiver" +msgstr "Receptor Unifying" -#: lib/logitech_receiver/base_usb.py:109 -msgid "Lightspeed Receiver" -msgstr "Receptor Lightspeed" +#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 +#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 +#: lib/logitech_receiver/base_usb.py:114 +msgid "Nano Receiver" +msgstr "Receptor Nano" -#: lib/logitech_receiver/base_usb.py:117 -msgid "EX100 Receiver 27 Mhz" -msgstr "Receptor EX100 27 Mhz" +#: lib/logitech_receiver/base_usb.py:124 +msgid "Lightspeed Receiver" +msgstr "Receptor Lightspeed" -#: lib/logitech_receiver/device.py:133 lib/solaar/ui/window.py:693 -msgid "unknown" -msgstr "desconegut" +#: lib/logitech_receiver/base_usb.py:133 +msgid "EX100 Receiver 27 Mhz" +msgstr "Receptor EX100 27 Mhz" + +#: lib/logitech_receiver/i18n.py:30 +msgid "empty" +msgstr "buida" + +#: lib/logitech_receiver/i18n.py:31 +msgid "critical" +msgstr "crítica" + +#: lib/logitech_receiver/i18n.py:32 +msgid "low" +msgstr "baixa" + +#: lib/logitech_receiver/i18n.py:33 +msgid "average" +msgstr "mitja" + +#: lib/logitech_receiver/i18n.py:34 +msgid "good" +msgstr "bona" + +#: lib/logitech_receiver/i18n.py:35 +msgid "full" +msgstr "plena" #: lib/logitech_receiver/i18n.py:38 -msgid "empty" -msgstr "buida" +msgid "discharging" +msgstr "descarregant" #: lib/logitech_receiver/i18n.py:39 -msgid "critical" -msgstr "crítica" +msgid "recharging" +msgstr "recarregant" -#: lib/logitech_receiver/i18n.py:40 -msgid "low" -msgstr "baixa" +#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 +msgid "charging" +msgstr "carregant" #: lib/logitech_receiver/i18n.py:41 -msgid "average" -msgstr "mitja" +msgid "not charging" +msgstr "no carregant" #: lib/logitech_receiver/i18n.py:42 -msgid "good" -msgstr "bona" +msgid "almost full" +msgstr "quasi plena" #: lib/logitech_receiver/i18n.py:43 -msgid "full" -msgstr "plena" +msgid "charged" +msgstr "carregant" + +#: lib/logitech_receiver/i18n.py:44 +msgid "slow recharge" +msgstr "recàrrega lenta" + +#: lib/logitech_receiver/i18n.py:45 +msgid "invalid battery" +msgstr "bateria no vàlida" #: lib/logitech_receiver/i18n.py:46 -msgid "discharging" -msgstr "descarregant" +msgid "thermal error" +msgstr "error tèrmic" #: lib/logitech_receiver/i18n.py:47 -msgid "recharging" -msgstr "recarregant" +msgid "error" +msgstr "error" -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:721 -msgid "charging" -msgstr "carregant" +#: lib/logitech_receiver/i18n.py:48 +msgid "standard" +msgstr "estàndard" #: lib/logitech_receiver/i18n.py:49 -msgid "not charging" -msgstr "no carregant" +msgid "fast" +msgstr "ràpid" #: lib/logitech_receiver/i18n.py:50 -msgid "almost full" -msgstr "quasi plena" - -#: lib/logitech_receiver/i18n.py:51 -msgid "charged" -msgstr "carregant" - -#: lib/logitech_receiver/i18n.py:52 -msgid "slow recharge" -msgstr "recàrrega lenta" +msgid "slow" +msgstr "lent" #: lib/logitech_receiver/i18n.py:53 -msgid "invalid battery" -msgstr "bateria no vàlida" +msgid "device timeout" +msgstr "temps exhaurit pel dispositiu" #: lib/logitech_receiver/i18n.py:54 -msgid "thermal error" -msgstr "error tèrmic" +msgid "device not supported" +msgstr "dispositiu no suportat" #: lib/logitech_receiver/i18n.py:55 -msgid "error" -msgstr "error" +msgid "too many devices" +msgstr "masses dispositius" #: lib/logitech_receiver/i18n.py:56 -msgid "standard" -msgstr "estàndard" +msgid "sequence timeout" +msgstr "temps de seqüència exhaurit" -#: lib/logitech_receiver/i18n.py:57 -msgid "fast" -msgstr "ràpid" +#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 +msgid "Firmware" +msgstr "Firmware" -#: lib/logitech_receiver/i18n.py:58 -msgid "slow" -msgstr "lent" +#: lib/logitech_receiver/i18n.py:60 +msgid "Bootloader" +msgstr "Gestor d'arrencada" #: lib/logitech_receiver/i18n.py:61 -msgid "device timeout" -msgstr "temps exhaurit pel dispositiu" +msgid "Hardware" +msgstr "Hardware" #: lib/logitech_receiver/i18n.py:62 -msgid "device not supported" -msgstr "dispositiu no suportat" +msgid "Other" +msgstr "Altres" -#: lib/logitech_receiver/i18n.py:63 -msgid "too many devices" -msgstr "masses dispositius" +#: lib/logitech_receiver/i18n.py:65 +msgid "Left Button" +msgstr "Botó Esquerra" -#: lib/logitech_receiver/i18n.py:64 -msgid "sequence timeout" -msgstr "temps de seqüència exhaurit" +#: lib/logitech_receiver/i18n.py:66 +msgid "Right Button" +msgstr "Botó Dret" -#: lib/logitech_receiver/i18n.py:67 lib/solaar/ui/window.py:580 -msgid "Firmware" -msgstr "Firmware" +#: lib/logitech_receiver/i18n.py:67 +msgid "Middle Button" +msgstr "Botó Central" #: lib/logitech_receiver/i18n.py:68 -msgid "Bootloader" -msgstr "Gestor d'arrencada" +msgid "Back Button" +msgstr "Botó Endarrere" #: lib/logitech_receiver/i18n.py:69 -msgid "Hardware" -msgstr "Hardware" +msgid "Forward Button" +msgstr "Botó Endavant" #: lib/logitech_receiver/i18n.py:70 -msgid "Other" -msgstr "Altres" +msgid "Mouse Gesture Button" +msgstr "Botó Gestos Ratolí" + +#: lib/logitech_receiver/i18n.py:71 +msgid "Smart Shift" +msgstr "Canvi intel·ligent" + +#: lib/logitech_receiver/i18n.py:72 +msgid "DPI Switch" +msgstr "Selector DPI" #: lib/logitech_receiver/i18n.py:73 -msgid "Left Button" -msgstr "Botó Esquerra" +msgid "Left Tilt" +msgstr "Inclinació a l'Esquerra" #: lib/logitech_receiver/i18n.py:74 -msgid "Right Button" -msgstr "Botó Dret" +msgid "Right Tilt" +msgstr "Inclinació a la Dreta" #: lib/logitech_receiver/i18n.py:75 -msgid "Middle Button" -msgstr "Botó Central" +msgid "Left Click" +msgstr "Clic Esquerra" #: lib/logitech_receiver/i18n.py:76 -msgid "Back Button" -msgstr "Botó Endarrere" +msgid "Right Click" +msgstr "Clic Dreta" #: lib/logitech_receiver/i18n.py:77 -msgid "Forward Button" -msgstr "Botó Endavant" +msgid "Mouse Middle Button" +msgstr "Clic Central Ratolí" #: lib/logitech_receiver/i18n.py:78 -msgid "Mouse Gesture Button" -msgstr "Botó Gestos Ratolí" +msgid "Mouse Back Button" +msgstr "Clic Endarrere Ratolí" #: lib/logitech_receiver/i18n.py:79 -msgid "Smart Shift" -msgstr "Canvi intel·ligent" +msgid "Mouse Forward Button" +msgstr "Clik Endavant Ratolí" #: lib/logitech_receiver/i18n.py:80 -msgid "DPI Switch" -msgstr "Selector DPI" +msgid "Gesture Button Navigation" +msgstr "Navegació amb el Botó de Gestos" #: lib/logitech_receiver/i18n.py:81 -msgid "Left Tilt" -msgstr "Inclinació a l'Esquerra" +msgid "Mouse Scroll Left Button" +msgstr "Botó Scroll a l'esquerra" #: lib/logitech_receiver/i18n.py:82 -msgid "Right Tilt" -msgstr "Inclinació a la Dreta" - -#: lib/logitech_receiver/i18n.py:83 -msgid "Left Click" -msgstr "Clic Esquerra" - -#: lib/logitech_receiver/i18n.py:84 -msgid "Right Click" -msgstr "Clic Dreta" +msgid "Mouse Scroll Right Button" +msgstr "Botó Scroll a la dreta" #: lib/logitech_receiver/i18n.py:85 -msgid "Mouse Middle Button" -msgstr "Clic Central Ratolí" +msgid "pressed" +msgstr "pressionat" #: lib/logitech_receiver/i18n.py:86 -msgid "Mouse Back Button" -msgstr "Clic Endarrere Ratolí" - -#: lib/logitech_receiver/i18n.py:87 -msgid "Mouse Forward Button" -msgstr "Clik Endavant Ratolí" - -#: lib/logitech_receiver/i18n.py:88 -msgid "Gesture Button Navigation" -msgstr "Navegació amb el Botó de Gestos" - -#: lib/logitech_receiver/i18n.py:89 -msgid "Mouse Scroll Left Button" -msgstr "Botó Scroll a l'esquerra" - -#: lib/logitech_receiver/i18n.py:90 -msgid "Mouse Scroll Right Button" -msgstr "Botó Scroll a la dreta" - -#: lib/logitech_receiver/i18n.py:93 -msgid "pressed" -msgstr "pressionat" - -#: lib/logitech_receiver/i18n.py:94 -msgid "released" -msgstr "alliberat" - -#: lib/logitech_receiver/notifications.py:76 -msgid "pairing lock is closed" -msgstr "el bloqueig de vinculació està tancat" - -#: lib/logitech_receiver/notifications.py:76 -msgid "pairing lock is open" -msgstr "el bloqueig de vinculació està obert" - -#: lib/logitech_receiver/notifications.py:160 lib/solaar/ui/notify.py:123 -msgid "connected" -msgstr "connectat" - -#: lib/logitech_receiver/notifications.py:160 -msgid "disconnected" -msgstr "desconnectat" - -#: lib/logitech_receiver/notifications.py:198 lib/solaar/ui/notify.py:121 -msgid "unpaired" -msgstr "desvinculat" - -#: lib/logitech_receiver/notifications.py:248 -msgid "powered on" -msgstr "alimentat" - -#: lib/logitech_receiver/settings.py:526 -msgid "register" -msgstr "registrar" - -#: lib/logitech_receiver/settings.py:542 lib/logitech_receiver/settings.py:568 -msgid "feature" -msgstr "característica" - -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Hand Detection" -msgstr "Detecció de mans" - -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "Encendra la i·il·luminació quan les mans passin pel damunt el teclat." - -#: lib/logitech_receiver/settings_templates.py:72 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "Suavitat Scroll Rodeta" - -#: lib/logitech_receiver/settings_templates.py:73 -#: lib/logitech_receiver/settings_templates.py:78 -#: lib/logitech_receiver/settings_templates.py:85 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "Mode d'alta sensibilitat per l'scroll vertical amb la rodeta." - -#: lib/logitech_receiver/settings_templates.py:74 -msgid "Side Scrolling" -msgstr "Scroll Lateral" - -#: lib/logitech_receiver/settings_templates.py:75 -msgid "" -"When disabled, pushing the wheel sideways sends custom button events\n" -"instead of the standard side-scrolling events." -msgstr "" -"Al desactivar-se, pressionar la rodeta lateralment envia esdeveniments de " -"botons personalitzats\n" -"en lloc dels esdeveniments estàndard d'scroll lateral." - -#: lib/logitech_receiver/settings_templates.py:77 -msgid "Scroll Wheel High Resolution" -msgstr "Rodeta d'Alta Resolució" - -#: lib/logitech_receiver/settings_templates.py:79 -msgid "Scroll Wheel Diversion" -msgstr "Desviar Rodeta" - -#: lib/logitech_receiver/settings_templates.py:80 -msgid "HID++ mode for vertical scroll with the wheel." -msgstr "Mode HID++ pel desplaçament vertical amb la rodeta." - -#: lib/logitech_receiver/settings_templates.py:81 -msgid "Effectively turns off wheel scrolling in Linux." -msgstr "Desactiva eficaçment l'scroll de la rodeta amb Linux." - -#: lib/logitech_receiver/settings_templates.py:82 -msgid "Scroll Wheel Direction" -msgstr "Direcció Desplaçament Rodeta" - -#: lib/logitech_receiver/settings_templates.py:83 -msgid "Invert direction for vertical scroll with wheel." -msgstr "Invertir direcció del desplaçament vertical amb la rodeta." - -#: lib/logitech_receiver/settings_templates.py:84 -msgid "Scroll Wheel Resolution" -msgstr "Resolució Rodeta Desplaçament" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Frequency of device polling, in milliseconds" -msgstr "Freqüència de sondeig del dispositiu, en milisegons" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Polling Rate (ms)" -msgstr "Taxa de Sondeig (ms)" - -#: lib/logitech_receiver/settings_templates.py:87 -msgid "Swap Fx function" -msgstr "Intercanviar funció Fx" - -#: lib/logitech_receiver/settings_templates.py:88 -msgid "" -"When set, the F1..F12 keys will activate their special function,\n" -"and you must hold the FN key to activate their standard function." -msgstr "" -"Al activar-se, les tecles F1..F12 activaran les seves funcions especials,\n" -"i s'ha de mantenir pressionada la tecla FN per activar la seva funció estàndard." - -#: lib/logitech_receiver/settings_templates.py:90 -msgid "" -"When unset, the F1..F12 keys will activate their standard function,\n" -"and you must hold the FN key to activate their special function." -msgstr "" -"Al desactivar-se, les tecles F1..F12 activaran les seves funcions estàndard,\n" -"i s'ha de mantenir pressionada la tecla FN per activar la seva funció especial." - -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Mouse movement sensitivity" -msgstr "Sensibilitat del moviment del ratolí" - -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Sensitivity (DPI)" -msgstr "Sensibilitat (DPI)" - -#: lib/logitech_receiver/settings_templates.py:93 -msgid "Sensitivity (Pointer Speed)" -msgstr "Sensibilitat (Velocitat del punter)" - -#: lib/logitech_receiver/settings_templates.py:94 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "Multiplicador de velocitat pel ratolí (256 és un multiplicador normal)" - -#: lib/logitech_receiver/settings_templates.py:95 -msgid "Scroll Wheel Rachet" -msgstr "Trinquet Rodeta Desplaçament" - -#: lib/logitech_receiver/settings_templates.py:96 -msgid "" -"Automatically switch the mouse wheel between ratchet and freespin mode.\n" -"The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "" -"Canviar automàticament la rodeta del ratolí entre el mode de trinquet i el mode " -"de gir lliure.\n" -"La rodeta del ratolí sempre està lliure a 0 i sempre amb trinquet a 50" - -#: lib/logitech_receiver/settings_templates.py:98 -msgid "Backlight" -msgstr "Retroiluminació" - -#: lib/logitech_receiver/settings_templates.py:98 -msgid "Turn illumination on or off on keyboard." -msgstr "Encendre o apagar la il·luminació del teclat." - -#: lib/logitech_receiver/settings_templates.py:99 -msgid "Key/Button Actions" -msgstr "Accions de Tecla/Botó" - -#: lib/logitech_receiver/settings_templates.py:100 -msgid "Change the action for the key or button." -msgstr "Canviar l'acció per la tecla o botó." - -#: lib/logitech_receiver/settings_templates.py:101 -msgid "" -"Changing important actions (such as for the left mouse button) can result in an " -"unusable system." -msgstr "" -"Canviar accions importants (per exemple el botó esquerra del ratolí) pot deixar " -"el sistema inservible." - -#: lib/logitech_receiver/settings_templates.py:102 -msgid "Key/Button Diversion" -msgstr "Desviament de Tecla/Botó" - -#: lib/logitech_receiver/settings_templates.py:103 -msgid "" -"Make the key or button send HID++ notifications (which trigger Solaar rules but " -"are otherwise ignored)." -msgstr "" -"Fer que la tecla o el botó enviï notificacions HID ++ (que activen les regles " -"de Solaar, del contrari s'ignoren)." - -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable keys" -msgstr "Desactivar tecles" - -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable specific keyboard keys." -msgstr "Desactivar tecles específiques del teclat." - -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Change keys to match OS." -msgstr "Canviar tecles per coincidir amb el S.O." - -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Set OS" -msgstr "Especificar S.O." - -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Change Host" -msgstr "Canviar Host" - -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Switch connection to a different host" -msgstr "Canviar connexió a un host diferent" - -#: lib/logitech_receiver/settings_templates.py:107 -msgid "Thumb Wheel Diversion" -msgstr "Desviament de la Rodeta del Polze" - -#: lib/logitech_receiver/settings_templates.py:108 -msgid "HID++ mode for horizontal scroll with the thumb wheel." -msgstr "Mode HID++ per desplaçament horitzontal amb la rodeta del polze." - -#: lib/logitech_receiver/settings_templates.py:109 -msgid "Effectively turns off thumb scrolling in Linux." -msgstr "Desactiva eficaçment el desplaçament amb el polze en Linux." - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Invert thumb wheel scroll direction." -msgstr "Invertir la direcció de desplaçament de la rodeta del polze." - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Thumb Wheel Direction" -msgstr "Direcció de la Rodeta del Polze" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Gestures" -msgstr "Gestos" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "Modificar el comportament del ratolí/panell tàctil." - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "Canviar paràmetres numèrics de un ratolí/panell tàctil." - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Gesture params" -msgstr "Paràmetres de gestos" - -#: lib/logitech_receiver/settings_templates.py:113 -msgid "DPI Sliding Adjustment" -msgstr "Ajustar DPI lliscant" - -#: lib/logitech_receiver/settings_templates.py:114 -msgid "" -"Adjust the DPI by sliding the mouse horizontally while holding the button down." -msgstr "" -"Ajustar els DPI lliscant el ratolí horitzontalment mentre es manté el botó " -"pressionat." - -#: lib/logitech_receiver/settings_templates.py:115 -msgid "Mouse Gestures" -msgstr "Gestos de Ratolí" - -#: lib/logitech_receiver/settings_templates.py:116 -msgid "Send a gesture by sliding the mouse while holding the button down." -msgstr "Enviar un gest lliscant el ratolí mentre manté pressionat el botó." - -#: lib/logitech_receiver/settings_templates.py:117 -msgid "Divert crown events" -msgstr "Desviar esdeveniments de la corona" - -#: lib/logitech_receiver/settings_templates.py:118 -msgid "" -"Make crown send CROWN HID++ notifications (which trigger Solaar rules but are " -"otherwise ignored)." -msgstr "" -"Fer que la corona enviï notificacions de CROWN HID ++ (que activen les regles " -"de Solaar, del contrari s'ignoren)." - -#: lib/logitech_receiver/settings_templates.py:119 -msgid "Divert G Keys" -msgstr "Desviar Tecles G" - -#: lib/logitech_receiver/settings_templates.py:120 -msgid "" -"Make G keys send GKEY HID++ notifications (which trigger Solaar rules but are " -"otherwise ignored)." -msgstr "" -"Fer que les tecles G enviïn notificacions GKEY HID ++ (que activen les regles " -"de Solaar, del contrari, s'ignoren)." - -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Performs a left click." -msgstr "Realitza un clic esquerra." - -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Single tap" -msgstr "Un toc" - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Performs a right click." -msgstr "Realitza un clic dret." - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Single tap with two fingers" -msgstr "Un toc amb dos dits" - -#: lib/logitech_receiver/settings_templates.py:125 -msgid "Single tap with three fingers" -msgstr "Un toc amb tres dits" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Double tap" -msgstr "Doble toc" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Performs a double click." -msgstr "Realitza un clic doble." - -#: lib/logitech_receiver/settings_templates.py:130 -msgid "Double tap with two fingers" -msgstr "Doble toc amb dos dits" - -#: lib/logitech_receiver/settings_templates.py:131 -msgid "Double tap with three fingers" -msgstr "Doble toc amb tres dits" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Drags items by dragging the finger after double tapping." -msgstr "Arrossega elements arrossegant el dit després de tocar dues vegades." - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Tap and drag" -msgstr "Tocar i arrossegar" - -#: lib/logitech_receiver/settings_templates.py:135 -msgid "Tap and drag with two fingers" -msgstr "Tocar i arrossegar amb dos dits" - -#: lib/logitech_receiver/settings_templates.py:136 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "Arrossega elements arrossegant els dits després de tocar dues vegades." - -#: lib/logitech_receiver/settings_templates.py:137 -msgid "Tap and drag with three fingers" -msgstr "Tocar i arrossegar amb tres dits" +msgid "released" +msgstr "alliberat" + +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 +msgid "pairing lock is closed" +msgstr "el bloqueig de vinculació està tancat" + +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 +msgid "pairing lock is open" +msgstr "el bloqueig de vinculació està obert" + +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is closed" +msgstr "" + +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is open" +msgstr "" + +#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 +msgid "connected" +msgstr "connectat" + +#: lib/logitech_receiver/notifications.py:224 +msgid "disconnected" +msgstr "desconnectat" + +#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 +msgid "unpaired" +msgstr "desvinculat" + +#: lib/logitech_receiver/notifications.py:304 +msgid "powered on" +msgstr "alimentat" + +#: lib/logitech_receiver/settings.py:750 +msgid "register" +msgstr "registrar" + +#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 +msgid "feature" +msgstr "característica" #: lib/logitech_receiver/settings_templates.py:139 -msgid "Suppress tap and edge gestures" -msgstr "Elimina els gestos de toc i marge" +msgid "Swap Fx function" +msgstr "Intercanviar funció Fx" #: lib/logitech_receiver/settings_templates.py:140 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "" -"Desactiva els gestos de toc i marge (equivalent a pressionar FN+LeftClick)." - -#: lib/logitech_receiver/settings_templates.py:141 -msgid "Scroll with one finger" -msgstr "Desplaça amb un dit" - -#: lib/logitech_receiver/settings_templates.py:141 -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scrolls." -msgstr "Desplaçament." +msgid "When set, the F1..F12 keys will activate their special function,\n" + "and you must hold the FN key to activate their standard function." +msgstr "Al activar-se, les tecles F1..F12 activaran les seves funcions " + "especials,\n" + "i s'ha de mantenir pressionada la tecla FN per activar la seva " + "funció estàndard." #: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scroll with two fingers" -msgstr "Desplaça amb dos dits" +msgid "When unset, the F1..F12 keys will activate their standard function,\n" + "and you must hold the FN key to activate their special function." +msgstr "Al desactivar-se, les tecles F1..F12 activaran les seves funcions " + "estàndard,\n" + "i s'ha de mantenir pressionada la tecla FN per activar la seva " + "funció especial." -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scroll horizontally with two fingers" -msgstr "Desplaça horitzontalment amb dos dits" +#: lib/logitech_receiver/settings_templates.py:149 +msgid "Hand Detection" +msgstr "Detecció de mans" -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scrolls horizontally." -msgstr "Desplaça horitzontalment." +#: lib/logitech_receiver/settings_templates.py:150 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Encendra la i·il·luminació quan les mans passin pel damunt el teclat." -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scroll vertically with two fingers" -msgstr "Desplaçar verticalment amb dos dits" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scrolls vertically." -msgstr "Desplaça verticalment." - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Inverts the scrolling direction." -msgstr "Invertir la direcció de desplaçament." - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Natural scrolling" -msgstr "Desplaçament natural" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Enables the thumbwheel." -msgstr "Activa la rodeta del polze." - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Thumbwheel" -msgstr "Rodeta del polze" +#: lib/logitech_receiver/settings_templates.py:157 +msgid "Scroll Wheel Smooth Scrolling" +msgstr "Suavitat Scroll Rodeta" #: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:162 -msgid "Swipe from the top edge" -msgstr "Lliscar des del marge superior" - -#: lib/logitech_receiver/settings_templates.py:159 -msgid "Swipe from the left edge" -msgstr "Lliscar des del marge esquerra" - -#: lib/logitech_receiver/settings_templates.py:160 -msgid "Swipe from the right edge" -msgstr "Lliscar des del marge dret" - -#: lib/logitech_receiver/settings_templates.py:161 -msgid "Swipe from the bottom edge" -msgstr "Lliscar des del marge inferior" - -#: lib/logitech_receiver/settings_templates.py:163 -msgid "Swipe two fingers from the left edge" -msgstr "Lliscar dos dits des del marge esquerra" - -#: lib/logitech_receiver/settings_templates.py:164 -msgid "Swipe two fingers from the right edge" -msgstr "Lliscar dos dits des del marge dret" +#: lib/logitech_receiver/settings_templates.py:239 +#: lib/logitech_receiver/settings_templates.py:267 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Mode d'alta sensibilitat per l'scroll vertical amb la rodeta." #: lib/logitech_receiver/settings_templates.py:165 -msgid "Swipe two fingers from the bottom edge" -msgstr "Lliscar dos dits des del marge inferior" - -#: lib/logitech_receiver/settings_templates.py:166 -msgid "Swipe two fingers from the top edge" -msgstr "Lliscar dos dits des del marge superior" +msgid "Side Scrolling" +msgstr "Scroll Lateral" #: lib/logitech_receiver/settings_templates.py:167 -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "Pessigar per allunyar; Expandir per acostar." +msgid "When disabled, pushing the wheel sideways sends custom button " + "events\n" + "instead of the standard side-scrolling events." +msgstr "Al desactivar-se, pressionar la rodeta lateralment envia " + "esdeveniments de botons personalitzats\n" + "en lloc dels esdeveniments estàndard d'scroll lateral." -#: lib/logitech_receiver/settings_templates.py:167 -msgid "Zoom with two fingers." -msgstr "Zoom amb dos dits." +#: lib/logitech_receiver/settings_templates.py:177 +msgid "Sensitivity (DPI - older mice)" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:168 -msgid "Pinch to zoom out." -msgstr "Pessigar per allunyar." +#: lib/logitech_receiver/settings_templates.py:178 +#: lib/logitech_receiver/settings_templates.py:712 +msgid "Mouse movement sensitivity" +msgstr "Sensibilitat del moviment del ratolí" -#: lib/logitech_receiver/settings_templates.py:169 -msgid "Spread to zoom in." -msgstr "Expandir per acostar." +#: lib/logitech_receiver/settings_templates.py:208 +#: lib/logitech_receiver/settings_templates.py:218 +#: lib/logitech_receiver/settings_templates.py:225 +msgid "Backlight" +msgstr "Retroiluminació" -#: lib/logitech_receiver/settings_templates.py:170 -msgid "Zoom with three fingers." -msgstr "Zoom amb tres dits." +#: lib/logitech_receiver/settings_templates.py:209 +#: lib/logitech_receiver/settings_templates.py:226 +msgid "Set illumination time for keyboard." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Zoom with two fingers" -msgstr "Zoom amb dos dits" +#: lib/logitech_receiver/settings_templates.py:219 +msgid "Turn illumination on or off on keyboard." +msgstr "Encendre o apagar la il·luminació del teclat." -#: lib/logitech_receiver/settings_templates.py:189 -msgid "Pixel zone" -msgstr "Zona de píxels" +#: lib/logitech_receiver/settings_templates.py:237 +msgid "Scroll Wheel High Resolution" +msgstr "Rodeta d'Alta Resolució" -#: lib/logitech_receiver/settings_templates.py:190 -msgid "Ratio zone" -msgstr "Zona de relació" +#: lib/logitech_receiver/settings_templates.py:240 +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Scale factor" -msgstr "Factor d'escalat" +#: lib/logitech_receiver/settings_templates.py:247 +#: lib/logitech_receiver/settings_templates.py:277 +msgid "Scroll Wheel Diversion" +msgstr "Desviar Rodeta" -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Sets the cursor speed." -msgstr "Estableix la velocitat del cursor." +#: lib/logitech_receiver/settings_templates.py:249 +msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left" -msgstr "Esquerra" +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Scroll Wheel Direction" +msgstr "Direcció Desplaçament Rodeta" -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left-most coordinate." -msgstr "Coordenada més a la esquerra." +#: lib/logitech_receiver/settings_templates.py:257 +msgid "Invert direction for vertical scroll with wheel." +msgstr "Invertir direcció del desplaçament vertical amb la rodeta." -#: lib/logitech_receiver/settings_templates.py:196 -msgid "Top-most coordinate." -msgstr "Coordenada superior." +#: lib/logitech_receiver/settings_templates.py:265 +msgid "Scroll Wheel Resolution" +msgstr "Resolució Rodeta Desplaçament" -#: lib/logitech_receiver/settings_templates.py:196 -msgid "top" -msgstr "superior" +#: lib/logitech_receiver/settings_templates.py:279 +msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:197 -msgid "Width." -msgstr "Amplada." +#: lib/logitech_receiver/settings_templates.py:288 +msgid "Sensitivity (Pointer Speed)" +msgstr "Sensibilitat (Velocitat del punter)" -#: lib/logitech_receiver/settings_templates.py:197 -msgid "width" -msgstr "amplada" +#: lib/logitech_receiver/settings_templates.py:289 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Multiplicador de velocitat pel ratolí (256 és un multiplicador " + "normal)" -#: lib/logitech_receiver/settings_templates.py:198 -msgid "Height." -msgstr "Alçada." +#: lib/logitech_receiver/settings_templates.py:299 +msgid "Thumb Wheel Diversion" +msgstr "Desviament de la Rodeta del Polze" -#: lib/logitech_receiver/settings_templates.py:198 -msgid "height" -msgstr "alçada" +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " + "Solaar rules but are otherwise ignored)." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Cursor speed." -msgstr "Velocitat del cursor." +#: lib/logitech_receiver/settings_templates.py:310 +msgid "Thumb Wheel Direction" +msgstr "Direcció de la Rodeta del Polze" -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Scale" -msgstr "Escala" +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Invert thumb wheel scroll direction." +msgstr "Invertir la direcció de desplaçament de la rodeta del polze." -#: lib/logitech_receiver/settings_templates.py:202 +#: lib/logitech_receiver/settings_templates.py:319 +msgid "Onboard Profiles" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:320 +msgid "Enable onboard profiles, which often control report rate and " + "keyboard lighting" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:330 +msgid "Polling Rate (ms)" +msgstr "Taxa de Sondeig (ms)" + +#: lib/logitech_receiver/settings_templates.py:332 +msgid "Frequency of device polling, in milliseconds" +msgstr "Freqüència de sondeig del dispositiu, en milisegons" + +#: lib/logitech_receiver/settings_templates.py:333 +#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:365 +msgid "Divert crown events" +msgstr "Desviar esdeveniments de la corona" + +#: lib/logitech_receiver/settings_templates.py:366 +msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "Fer que la corona enviï notificacions de CROWN HID ++ (que activen " + "les regles de Solaar, del contrari s'ignoren)." + +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:375 +msgid "Set crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:383 +msgid "Divert G Keys" +msgstr "Desviar Tecles G" + +#: lib/logitech_receiver/settings_templates.py:385 +msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "Fer que les tecles G enviïn notificacions GKEY HID ++ (que activen " + "les regles de Solaar, del contrari, s'ignoren)." + +#: lib/logitech_receiver/settings_templates.py:386 +msgid "May also make M keys and MR key send HID++ notifications" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:402 +msgid "Scroll Wheel Ratcheted" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:403 +msgid "Switch the mouse wheel between speed-controlled ratcheting and " + "always freespin." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Freespinning" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Ratcheted" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:412 +msgid "Scroll Wheel Ratchet Speed" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:414 +msgid "Use the mouse wheel speed to switch between ratcheted and " + "freespinning.\n" + "The mouse wheel is always ratcheted at 50." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:463 +msgid "Key/Button Actions" +msgstr "Accions de Tecla/Botó" + +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Change the action for the key or button." +msgstr "Canviar l'acció per la tecla o botó." + +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Overridden by diversion." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:466 +msgid "Changing important actions (such as for the left mouse button) can " + "result in an unusable system." +msgstr "Canviar accions importants (per exemple el botó esquerra del ratolí) " + "pot deixar el sistema inservible." + +#: lib/logitech_receiver/settings_templates.py:639 +msgid "Key/Button Diversion" +msgstr "Desviament de Tecla/Botó" + +#: lib/logitech_receiver/settings_templates.py:640 +msgid "Make the key or button send HID++ notifications (Diverted) or " + "initiate Mouse Gestures or Sliding DPI" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Diverted" +msgstr "Desviat" + +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +msgid "Mouse Gestures" +msgstr "Gestos de Ratolí" + +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Regular" +msgstr "Normal" + +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Sliding DPI" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:711 +msgid "Sensitivity (DPI)" +msgstr "Sensibilitat (DPI)" + +#: lib/logitech_receiver/settings_templates.py:752 +msgid "Sensitivity Switching" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:754 +msgid "Switch the current sensitivity and the remembered sensitivity when " + "the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current " + "sensitivity" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:758 +msgid "Off" +msgstr "Off" + +#: lib/logitech_receiver/settings_templates.py:791 +msgid "Disable keys" +msgstr "Desactivar tecles" + +#: lib/logitech_receiver/settings_templates.py:792 +msgid "Disable specific keyboard keys." +msgstr "Desactivar tecles específiques del teclat." + +#: lib/logitech_receiver/settings_templates.py:795 #, python-format -msgid "Disables the %s key." -msgstr "Desactiva la tecla %s." +msgid "Disables the %s key." +msgstr "Desactiva la tecla %s." -#: lib/logitech_receiver/settings_templates.py:521 -#: lib/logitech_receiver/settings_templates.py:573 -msgid "Off" -msgstr "Off" +#: lib/logitech_receiver/settings_templates.py:809 +#: lib/logitech_receiver/settings_templates.py:860 +msgid "Set OS" +msgstr "Especificar S.O." -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Diverted" -msgstr "Desviat" +#: lib/logitech_receiver/settings_templates.py:810 +#: lib/logitech_receiver/settings_templates.py:861 +msgid "Change keys to match OS." +msgstr "Canviar tecles per coincidir amb el S.O." -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Regular" -msgstr "Normal" +#: lib/logitech_receiver/settings_templates.py:873 +msgid "Change Host" +msgstr "Canviar Host" -#: lib/logitech_receiver/status.py:109 -msgid "No paired devices." -msgstr "Cap dispositiu connectat." +#: lib/logitech_receiver/settings_templates.py:874 +msgid "Switch connection to a different host" +msgstr "Canviar connexió a un host diferent" -#: lib/logitech_receiver/status.py:110 lib/solaar/ui/window.py:623 +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Performs a left click." +msgstr "Realitza un clic esquerra." + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Single tap" +msgstr "Un toc" + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Performs a right click." +msgstr "Realitza un clic dret." + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Single tap with two fingers" +msgstr "Un toc amb dos dits" + +#: lib/logitech_receiver/settings_templates.py:902 +msgid "Single tap with three fingers" +msgstr "Un toc amb tres dits" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Double tap" +msgstr "Doble toc" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Performs a double click." +msgstr "Realitza un clic doble." + +#: lib/logitech_receiver/settings_templates.py:907 +msgid "Double tap with two fingers" +msgstr "Doble toc amb dos dits" + +#: lib/logitech_receiver/settings_templates.py:908 +msgid "Double tap with three fingers" +msgstr "Doble toc amb tres dits" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Arrossega elements arrossegant el dit després de tocar dues vegades." + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Tap and drag" +msgstr "Tocar i arrossegar" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Arrossega elements arrossegant els dits després de tocar dues " + "vegades." + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Tap and drag with two fingers" +msgstr "Tocar i arrossegar amb dos dits" + +#: lib/logitech_receiver/settings_templates.py:914 +msgid "Tap and drag with three fingers" +msgstr "Tocar i arrossegar amb tres dits" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "Desactiva els gestos de toc i marge (equivalent a pressionar " + "FN+LeftClick)." + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Suppress tap and edge gestures" +msgstr "Elimina els gestos de toc i marge" + +#: lib/logitech_receiver/settings_templates.py:918 +msgid "Scroll with one finger" +msgstr "Desplaça amb un dit" + +#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scrolls." +msgstr "Desplaçament." + +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scroll with two fingers" +msgstr "Desplaça amb dos dits" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scroll horizontally with two fingers" +msgstr "Desplaça horitzontalment amb dos dits" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scrolls horizontally." +msgstr "Desplaça horitzontalment." + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scroll vertically with two fingers" +msgstr "Desplaçar verticalment amb dos dits" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scrolls vertically." +msgstr "Desplaça verticalment." + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Inverts the scrolling direction." +msgstr "Invertir la direcció de desplaçament." + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Natural scrolling" +msgstr "Desplaçament natural" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Enables the thumbwheel." +msgstr "Activa la rodeta del polze." + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Thumbwheel" +msgstr "Rodeta del polze" + +#: lib/logitech_receiver/settings_templates.py:935 +#: lib/logitech_receiver/settings_templates.py:939 +msgid "Swipe from the top edge" +msgstr "Lliscar des del marge superior" + +#: lib/logitech_receiver/settings_templates.py:936 +msgid "Swipe from the left edge" +msgstr "Lliscar des del marge esquerra" + +#: lib/logitech_receiver/settings_templates.py:937 +msgid "Swipe from the right edge" +msgstr "Lliscar des del marge dret" + +#: lib/logitech_receiver/settings_templates.py:938 +msgid "Swipe from the bottom edge" +msgstr "Lliscar des del marge inferior" + +#: lib/logitech_receiver/settings_templates.py:940 +msgid "Swipe two fingers from the left edge" +msgstr "Lliscar dos dits des del marge esquerra" + +#: lib/logitech_receiver/settings_templates.py:941 +msgid "Swipe two fingers from the right edge" +msgstr "Lliscar dos dits des del marge dret" + +#: lib/logitech_receiver/settings_templates.py:942 +msgid "Swipe two fingers from the bottom edge" +msgstr "Lliscar dos dits des del marge inferior" + +#: lib/logitech_receiver/settings_templates.py:943 +msgid "Swipe two fingers from the top edge" +msgstr "Lliscar dos dits des del marge superior" + +#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "Pessigar per allunyar; Expandir per acostar." + +#: lib/logitech_receiver/settings_templates.py:944 +msgid "Zoom with two fingers." +msgstr "Zoom amb dos dits." + +#: lib/logitech_receiver/settings_templates.py:945 +msgid "Pinch to zoom out." +msgstr "Pessigar per allunyar." + +#: lib/logitech_receiver/settings_templates.py:946 +msgid "Spread to zoom in." +msgstr "Expandir per acostar." + +#: lib/logitech_receiver/settings_templates.py:947 +msgid "Zoom with three fingers." +msgstr "Zoom amb tres dits." + +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Zoom with two fingers" +msgstr "Zoom amb dos dits" + +#: lib/logitech_receiver/settings_templates.py:966 +msgid "Pixel zone" +msgstr "Zona de píxels" + +#: lib/logitech_receiver/settings_templates.py:967 +msgid "Ratio zone" +msgstr "Zona de relació" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Scale factor" +msgstr "Factor d'escalat" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Sets the cursor speed." +msgstr "Estableix la velocitat del cursor." + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left" +msgstr "Esquerra" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left-most coordinate." +msgstr "Coordenada més a la esquerra." + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width" +msgstr "Amplada" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width." +msgstr "Amplada." + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height" +msgstr "Alçada" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height." +msgstr "Alçada." + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Cursor speed." +msgstr "Velocitat del cursor." + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Scale" +msgstr "Escala" + +#: lib/logitech_receiver/settings_templates.py:982 +msgid "Gestures" +msgstr "Gestos" + +#: lib/logitech_receiver/settings_templates.py:983 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Modificar el comportament del ratolí/panell tàctil." + +#: lib/logitech_receiver/settings_templates.py:1000 +msgid "Gestures Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1001 +msgid "Divert mouse/touchpad gestures." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1018 +msgid "Gesture params" +msgstr "Paràmetres de gestos" + +#: lib/logitech_receiver/settings_templates.py:1019 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Canviar paràmetres numèrics de un ratolí/panell tàctil." + +#: lib/logitech_receiver/settings_templates.py:1044 +msgid "M-Key LEDs" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Control the M-Key LEDs." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1077 +msgid "May need G Keys diverted to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1053 #, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s dispositiu vinculat." -msgstr[1] "%(count)s dispositius vinculats." +msgid "Lights up the %s key." +msgstr "" -#: lib/logitech_receiver/status.py:165 -#, python-format -msgid "Battery: %(level)s" -msgstr "Bateria: %(level)s" +#: lib/logitech_receiver/settings_templates.py:1074 +msgid "MR-Key LED" +msgstr "" -#: lib/logitech_receiver/status.py:167 -#, python-format -msgid "Battery: %(percent)d%%" -msgstr "Bateria: %(percent)d%%" +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "Control the MR-Key LED." +msgstr "" -#: lib/logitech_receiver/status.py:179 -#, python-format -msgid "Lighting: %(level)s lux" -msgstr "Il·luminació: %(level)s lux" +#: lib/logitech_receiver/settings_templates.py:1095 +msgid "Persistent Key/Button Mapping" +msgstr "" -#: lib/logitech_receiver/status.py:234 -#, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "Bateria: %(level)s (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:1097 +msgid "Permanently change the mapping for the key or button." +msgstr "" -#: lib/logitech_receiver/status.py:236 +#: lib/logitech_receiver/settings_templates.py:1098 +msgid "Changing important keys or buttons (such as for the left mouse " + "button) can result in an unusable system." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1157 +msgid "Sidetone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1158 +msgid "Set sidetone level." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1167 +msgid "Equalizer" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1168 +msgid "Set equalizer levels." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1191 +msgid "Hz" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1197 +msgid "Power Management" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1198 +msgid "Power off in minutes (0 for never)." +msgstr "" + +#: lib/logitech_receiver/status.py:114 +msgid "No paired devices." +msgstr "Cap dispositiu connectat." + +#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 #, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "Bateria: %(percent)d%% (%(status)s)" +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s dispositiu vinculat." +msgstr[1] "%(count)s dispositius vinculats." + +#: lib/logitech_receiver/status.py:170 +#, python-format +msgid "Battery: %(level)s" +msgstr "Bateria: %(level)s" + +#: lib/logitech_receiver/status.py:172 +#, python-format +msgid "Battery: %(percent)d%%" +msgstr "Bateria: %(percent)d%%" + +#: lib/logitech_receiver/status.py:184 +#, python-format +msgid "Lighting: %(level)s lux" +msgstr "Il·luminació: %(level)s lux" + +#: lib/logitech_receiver/status.py:239 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Bateria: %(level)s (%(status)s)" + +#: lib/logitech_receiver/status.py:241 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Bateria: %(percent)d%% (%(status)s)" #: lib/solaar/ui/__init__.py:52 -msgid "Permissions error" -msgstr "Error de permisos" +msgid "Permissions error" +msgstr "Error de permisos" #: lib/solaar/ui/__init__.py:54 #, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open it." -msgstr "S'ha trobat receptor Logitech (%s), però no té permisos per obrir-lo." +msgid "Found a Logitech receiver or device (%s), but did not have " + "permission to open it." +msgstr "" #: lib/solaar/ui/__init__.py:55 -msgid "" -"If you've just installed Solaar, try removing the receiver and plugging it back " -"in." -msgstr "" -"Si acaba d'instal·lar Solaar, intenta treure el receptor i connectar-lo de nou." +msgid "If you've just installed Solaar, try disconnecting the receiver or " + "device and then reconnecting it." +msgstr "" #: lib/solaar/ui/__init__.py:58 -msgid "Cannot connect to device error" -msgstr "Error al connectar al dispositiu" +msgid "Cannot connect to device error" +msgstr "Error al connectar al dispositiu" #: lib/solaar/ui/__init__.py:60 #, python-format -msgid "" -"Found a Logitech receiver or device at %s, but encountered an error connecting " -"to it." -msgstr "" -"S'ha trobat un receptor o dispositiu Logitech a %s, però s'ha produït un error " -"al connectar-se." +msgid "Found a Logitech receiver or device at %s, but encountered an error " + "connecting to it." +msgstr "S'ha trobat un receptor o dispositiu Logitech a %s, però s'ha " + "produït un error al connectar-se." #: lib/solaar/ui/__init__.py:61 -msgid "" -"Try removing the device and plugging it back in or turning it off and then on." -msgstr "" -"Intenti treure el dispositiu i tornar a endollar-lo o apagar-lo i encendre'l." +msgid "Try disconnecting the device and then reconnecting it or turning it " + "off and then on." +msgstr "" #: lib/solaar/ui/__init__.py:64 -msgid "Unpairing failed" -msgstr "Ha fallat la desvinculació" +msgid "Unpairing failed" +msgstr "Ha fallat la desvinculació" #: lib/solaar/ui/__init__.py:66 #, python-brace-format -msgid "Failed to unpair %{device} from %{receiver}." -msgstr "Error al desvincular %{device} de %{receiver}." +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Error al desvincular %{device} de %{receiver}." #: lib/solaar/ui/__init__.py:67 -msgid "The receiver returned an error, with no further details." -msgstr "El receptor ha tornat un error, sense detalls addicionals." +msgid "The receiver returned an error, with no further details." +msgstr "El receptor ha tornat un error, sense detalls addicionals." -#: lib/solaar/ui/about.py:39 -msgid "" -"Manages Logitech receivers,\n" -"keyboards, mice, and tablets." -msgstr "" -"Gestiona receptors Logitech,\n" -"teclats, ratolins i tauletes." +#: lib/solaar/ui/__init__.py:177 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" + +#: lib/solaar/ui/about.py:36 +msgid "Manages Logitech receivers,\n" + "keyboards, mice, and tablets." +msgstr "Gestiona receptors Logitech,\n" + "teclats, ratolins i tauletes." + +#: lib/solaar/ui/about.py:44 +msgid "Additional Programming" +msgstr "Programació addicional" + +#: lib/solaar/ui/about.py:45 +msgid "GUI design" +msgstr "Disseny de la interfície gràfica" #: lib/solaar/ui/about.py:47 -msgid "Additional Programming" -msgstr "Programació addicional" +msgid "Testing" +msgstr "Provant" -#: lib/solaar/ui/about.py:48 -msgid "GUI design" -msgstr "Disseny de la interfície gràfica" +#: lib/solaar/ui/about.py:54 +msgid "Logitech documentation" +msgstr "Documentació de Logitech" -#: lib/solaar/ui/about.py:50 -msgid "Testing" -msgstr "Provant" +#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 +#: lib/solaar/ui/window.py:197 +msgid "Unpair" +msgstr "Desvincular" -#: lib/solaar/ui/about.py:57 -msgid "Logitech documentation" -msgstr "Documentació de Logitech" +#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 +msgid "Cancel" +msgstr "Cancel·lar" -#: lib/solaar/ui/action.py:70 lib/solaar/ui/window.py:328 -msgid "About %s" -msgstr "Sobre %s" +#: lib/solaar/ui/config_panel.py:212 +msgid "Complete - ENTER to change" +msgstr "" -#: lib/solaar/ui/action.py:96 lib/solaar/ui/action.py:100 -#: lib/solaar/ui/window.py:205 -msgid "Unpair" -msgstr "Desvincular" +#: lib/solaar/ui/config_panel.py:212 +msgid "Incomplete" +msgstr "" -#: lib/solaar/ui/action.py:99 lib/solaar/ui/diversion_rules.py:144 -msgid "Cancel" -msgstr "Cancel·lar" - -#: lib/solaar/ui/config_panel.py:358 -msgid "Changes allowed" -msgstr "Canvis permesos" - -#: lib/solaar/ui/config_panel.py:359 -msgid "No changes allowed" -msgstr "Canvis no permesos" - -#: lib/solaar/ui/config_panel.py:360 -msgid "Ignore this setting" -msgstr "Ignorar aquesta opció" - -#: lib/solaar/ui/config_panel.py:401 -msgid "Working" -msgstr "Funcionant" - -#: lib/solaar/ui/config_panel.py:404 -msgid "Read/write operation failed." -msgstr "Ha fallat l'operació de lectura/escriptura." - -#: lib/solaar/ui/config_panel.py:522 +#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 #, python-format -msgid "%d value" -msgid_plural "%d values" -msgstr[0] "%d valor" -msgstr[1] "%d valors" +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d valor" +msgstr[1] "%d valors" -#: lib/solaar/ui/diversion_rules.py:57 -msgid "Built-in rules" -msgstr "Regles integrades" +#: lib/solaar/ui/config_panel.py:518 +msgid "Changes allowed" +msgstr "Canvis permesos" -#: lib/solaar/ui/diversion_rules.py:57 -msgid "User-defined rules" -msgstr "Regles definides per l'usuari" +#: lib/solaar/ui/config_panel.py:519 +msgid "No changes allowed" +msgstr "Canvis no permesos" -#: lib/solaar/ui/diversion_rules.py:59 lib/solaar/ui/diversion_rules.py:792 -msgid "Rule" -msgstr "Regla" +#: lib/solaar/ui/config_panel.py:520 +msgid "Ignore this setting" +msgstr "Ignorar aquesta opció" -#: lib/solaar/ui/diversion_rules.py:60 lib/solaar/ui/diversion_rules.py:503 -#: lib/solaar/ui/diversion_rules.py:622 -msgid "Sub-rule" -msgstr "Sub-regla" +#: lib/solaar/ui/config_panel.py:565 +msgid "Working" +msgstr "Funcionant" -#: lib/solaar/ui/diversion_rules.py:62 -msgid "[empty]" -msgstr "[buit]" +#: lib/solaar/ui/config_panel.py:568 +msgid "Read/write operation failed." +msgstr "Ha fallat l'operació de lectura/escriptura." -#: lib/solaar/ui/diversion_rules.py:85 -msgid "Solaar Rule Editor" -msgstr "Editor de Regles Solaar" +#: lib/solaar/ui/diversion_rules.py:65 +msgid "Built-in rules" +msgstr "Regles integrades" -#: lib/solaar/ui/diversion_rules.py:135 -msgid "Make changes permanent?" -msgstr "¿Fer els canvis permanents?" +#: lib/solaar/ui/diversion_rules.py:65 +msgid "User-defined rules" +msgstr "Regles definides per l'usuari" -#: lib/solaar/ui/diversion_rules.py:140 -msgid "Yes" -msgstr "Sí" +#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 +msgid "Rule" +msgstr "Regla" -#: lib/solaar/ui/diversion_rules.py:142 -msgid "No" -msgstr "No" +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 +#: lib/solaar/ui/diversion_rules.py:636 +msgid "Sub-rule" +msgstr "Sub-regla" -#: lib/solaar/ui/diversion_rules.py:147 -msgid "If you choose No, changes will be lost when Solaar is closed." -msgstr "Si escull No, els canvis es perdran quan es tanqui Solaar." +#: lib/solaar/ui/diversion_rules.py:70 +msgid "[empty]" +msgstr "[buit]" -#: lib/solaar/ui/diversion_rules.py:195 -msgid "Save changes" -msgstr "Guardar canvis" +#: lib/solaar/ui/diversion_rules.py:94 +msgid "Solaar Rule Editor" +msgstr "Editor de Regles Solaar" -#: lib/solaar/ui/diversion_rules.py:200 -msgid "Discard changes" -msgstr "Descartar canvis" +#: lib/solaar/ui/diversion_rules.py:141 +msgid "Make changes permanent?" +msgstr "¿Fer els canvis permanents?" -#: lib/solaar/ui/diversion_rules.py:366 -msgid "Insert here" -msgstr "Inserir aquí" +#: lib/solaar/ui/diversion_rules.py:146 +msgid "Yes" +msgstr "Sí" -#: lib/solaar/ui/diversion_rules.py:368 -msgid "Insert above" -msgstr "Inserir a dalt" +#: lib/solaar/ui/diversion_rules.py:148 +msgid "No" +msgstr "No" -#: lib/solaar/ui/diversion_rules.py:370 -msgid "Insert below" -msgstr "Inserir a baix" +#: lib/solaar/ui/diversion_rules.py:153 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "Si escull No, els canvis es perdran quan es tanqui Solaar." + +#: lib/solaar/ui/diversion_rules.py:201 +msgid "Save changes" +msgstr "Guardar canvis" + +#: lib/solaar/ui/diversion_rules.py:206 +msgid "Discard changes" +msgstr "Descartar canvis" + +#: lib/solaar/ui/diversion_rules.py:372 +msgid "Insert here" +msgstr "Inserir aquí" + +#: lib/solaar/ui/diversion_rules.py:374 +msgid "Insert above" +msgstr "Inserir a dalt" #: lib/solaar/ui/diversion_rules.py:376 -msgid "Insert new rule here" -msgstr "Inserir nova regla aquí" +msgid "Insert below" +msgstr "Inserir a baix" -#: lib/solaar/ui/diversion_rules.py:378 -msgid "Insert new rule above" -msgstr "Inserir nova regla a dalt" +#: lib/solaar/ui/diversion_rules.py:382 +msgid "Insert new rule here" +msgstr "Inserir nova regla aquí" -#: lib/solaar/ui/diversion_rules.py:380 -msgid "Insert new rule below" -msgstr "Inserir nova regla a baix" +#: lib/solaar/ui/diversion_rules.py:384 +msgid "Insert new rule above" +msgstr "Inserir nova regla a dalt" -#: lib/solaar/ui/diversion_rules.py:421 -msgid "Paste here" -msgstr "Enganxar aquí" +#: lib/solaar/ui/diversion_rules.py:386 +msgid "Insert new rule below" +msgstr "Inserir nova regla a baix" -#: lib/solaar/ui/diversion_rules.py:423 -msgid "Paste above" -msgstr "Enganxar a dalt" +#: lib/solaar/ui/diversion_rules.py:427 +msgid "Paste here" +msgstr "Enganxar aquí" -#: lib/solaar/ui/diversion_rules.py:425 -msgid "Paste below" -msgstr "Enganxar a baix" +#: lib/solaar/ui/diversion_rules.py:429 +msgid "Paste above" +msgstr "Enganxar a dalt" #: lib/solaar/ui/diversion_rules.py:431 -msgid "Paste rule here" -msgstr "Enganxar regla aquí" +msgid "Paste below" +msgstr "Enganxar a baix" -#: lib/solaar/ui/diversion_rules.py:433 -msgid "Paste rule above" -msgstr "Enganxar regla a dalt" - -#: lib/solaar/ui/diversion_rules.py:435 -msgid "Paste rule below" -msgstr "Enganxar regla a baix" +#: lib/solaar/ui/diversion_rules.py:437 +msgid "Paste rule here" +msgstr "Enganxar regla aquí" #: lib/solaar/ui/diversion_rules.py:439 -msgid "Paste rule" -msgstr "Enganxar regla" +msgid "Paste rule above" +msgstr "Enganxar regla a dalt" -#: lib/solaar/ui/diversion_rules.py:468 -msgid "Flatten" -msgstr "Aplanar" +#: lib/solaar/ui/diversion_rules.py:441 +msgid "Paste rule below" +msgstr "Enganxar regla a baix" -#: lib/solaar/ui/diversion_rules.py:501 -msgid "Insert" -msgstr "Insertar" +#: lib/solaar/ui/diversion_rules.py:445 +msgid "Paste rule" +msgstr "Enganxar regla" -#: lib/solaar/ui/diversion_rules.py:504 lib/solaar/ui/diversion_rules.py:624 -#: lib/solaar/ui/diversion_rules.py:835 -msgid "Or" -msgstr "O" - -#: lib/solaar/ui/diversion_rules.py:505 lib/solaar/ui/diversion_rules.py:623 -#: lib/solaar/ui/diversion_rules.py:820 -msgid "And" -msgstr "I" +#: lib/solaar/ui/diversion_rules.py:474 +msgid "Flatten" +msgstr "Aplanar" #: lib/solaar/ui/diversion_rules.py:507 -msgid "Condition" -msgstr "Condició" +msgid "Insert" +msgstr "Insertar" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:951 -msgid "Feature" -msgstr "Característica" +#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 +#: lib/solaar/ui/diversion_rules.py:1126 +msgid "Or" +msgstr "O" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:874 -msgid "Process" -msgstr "Procés" +#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 +#: lib/solaar/ui/diversion_rules.py:1111 +msgid "And" +msgstr "I" -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:902 -msgid "MouseProcess" -msgstr "ProcésRatolí" +#: lib/solaar/ui/diversion_rules.py:513 +msgid "Condition" +msgstr "Condició" -#: lib/solaar/ui/diversion_rules.py:512 lib/solaar/ui/diversion_rules.py:984 -msgid "Report" -msgstr "Informar" +#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 +msgid "Feature" +msgstr "Característica" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1019 -msgid "Modifiers" -msgstr "Modificadors" +#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 +msgid "Report" +msgstr "Informar" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1066 -msgid "Key" -msgstr "Tecla" +#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 +msgid "Process" +msgstr "Procés" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1111 -msgid "Test" -msgstr "Test" +#: lib/solaar/ui/diversion_rules.py:518 +msgid "Mouse process" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1196 -msgid "Mouse Gesture" -msgstr "Gest de Ratolí" +#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 +msgid "Modifiers" +msgstr "Modificadors" -#: lib/solaar/ui/diversion_rules.py:520 -msgid "Action" -msgstr "Acció" +#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 +msgid "Key" +msgstr "Tecla" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1286 -msgid "Key press" -msgstr "Pulsació de tecla" +#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 +msgid "KeyIsDown" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1335 -msgid "Mouse scroll" -msgstr "Desplaçament rodeta del ratolí" +#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 +msgid "Active" +msgstr "Actiu" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:1383 -msgid "Mouse click" -msgstr "Clic del ratolí" +#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2218 +#: lib/solaar/ui/diversion_rules.py:2270 lib/solaar/ui/diversion_rules.py:2323 +msgid "Device" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1452 -msgid "Execute" -msgstr "Executar" +#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +msgid "Host" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:554 -msgid "Insert new rule" -msgstr "Afegir nova regla" +#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 +msgid "Setting" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/diversion_rules.py:1146 -#: lib/solaar/ui/diversion_rules.py:1236 lib/solaar/ui/diversion_rules.py:1411 -msgid "Delete" -msgstr "Esborrar" +#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 +#: lib/solaar/ui/diversion_rules.py:1526 +msgid "Test" +msgstr "Test" -#: lib/solaar/ui/diversion_rules.py:596 -msgid "Negate" -msgstr "Negar" +#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 +msgid "Test bytes" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:620 -msgid "Wrap with" -msgstr "Embolicar amb" +#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 +msgid "Mouse Gesture" +msgstr "Gest de Ratolí" -#: lib/solaar/ui/diversion_rules.py:642 -msgid "Cut" -msgstr "Tallar" +#: lib/solaar/ui/diversion_rules.py:532 +msgid "Action" +msgstr "Acció" -#: lib/solaar/ui/diversion_rules.py:657 -msgid "Paste" -msgstr "Enganxar" +#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 +msgid "Key press" +msgstr "Pulsació de tecla" -#: lib/solaar/ui/diversion_rules.py:669 -msgid "Copy" -msgstr "Copiar" +#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 +msgid "Mouse scroll" +msgstr "Desplaçament rodeta del ratolí" -#: lib/solaar/ui/diversion_rules.py:772 -msgid "This editor does not support the selected rule component yet." -msgstr "" -"Aquest editor encara no és compatible amb el component de regla seleccionat." +#: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 +msgid "Mouse click" +msgstr "Clic del ratolí" -#: lib/solaar/ui/diversion_rules.py:850 -msgid "Not" -msgstr "No" +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Set" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1039 -msgid "Key down" -msgstr "Tecla avall" +#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 +msgid "Execute" +msgstr "Executar" -#: lib/solaar/ui/diversion_rules.py:1042 -msgid "Key up" -msgstr "Tecla amunt" +#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 +msgid "Later" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1131 -msgid "Add action" -msgstr "Afegir acció" +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Insert new rule" +msgstr "Afegir nova regla" -#: lib/solaar/ui/diversion_rules.py:1224 -msgid "Add key" -msgstr "Afegir tecla" +#: lib/solaar/ui/diversion_rules.py:588 lib/solaar/ui/diversion_rules.py:1686 +#: lib/solaar/ui/diversion_rules.py:1790 lib/solaar/ui/diversion_rules.py:1989 +msgid "Delete" +msgstr "Esborrar" -#: lib/solaar/ui/diversion_rules.py:1354 -msgid "Button" -msgstr "Botó" +#: lib/solaar/ui/diversion_rules.py:610 +msgid "Negate" +msgstr "Negar" -#: lib/solaar/ui/diversion_rules.py:1355 -msgid "Count" -msgstr "Conta" +#: lib/solaar/ui/diversion_rules.py:634 +msgid "Wrap with" +msgstr "Embolicar amb" -#: lib/solaar/ui/diversion_rules.py:1397 -msgid "Add argument" -msgstr "Afegir argument" +#: lib/solaar/ui/diversion_rules.py:656 +msgid "Cut" +msgstr "Tallar" -#: lib/solaar/ui/notify.py:125 lib/solaar/ui/tray.py:319 lib/solaar/ui/tray.py:324 -#: lib/solaar/ui/window.py:749 -msgid "offline" -msgstr "desconectat" +#: lib/solaar/ui/diversion_rules.py:671 +msgid "Paste" +msgstr "Enganxar" -#: lib/solaar/ui/pair_window.py:134 -msgid "Pairing failed" -msgstr "Ha falla la vinculació" +#: lib/solaar/ui/diversion_rules.py:683 +msgid "Copy" +msgstr "Copiar" -#: lib/solaar/ui/pair_window.py:136 -msgid "Make sure your device is within range, and has a decent battery charge." -msgstr "" -"Asseguri's que el seu dispositiu estigui dins l'abast del receptor i que la " -"bateria tingui suficient càrrega." +#: lib/solaar/ui/diversion_rules.py:1063 +msgid "This editor does not support the selected rule component yet." +msgstr "Aquest editor encara no és compatible amb el component de regla " + "seleccionat." -#: lib/solaar/ui/pair_window.py:138 -msgid "A new device was detected, but it is not compatible with this receiver." -msgstr "" -"S'ha detectat un nou dispositiu, però no és compatible amb aquest receptor." +#: lib/solaar/ui/diversion_rules.py:1138 +msgid "Number of seconds to delay." +msgstr "" -#: lib/solaar/ui/pair_window.py:140 -msgid "More paired devices than receiver can support." -msgstr "Més dispositius emparellats de els que admet el receptor." +#: lib/solaar/ui/diversion_rules.py:1177 +msgid "Not" +msgstr "No" -#: lib/solaar/ui/pair_window.py:142 -msgid "No further details are available about the error." -msgstr "No hi ha més detalls disponibles sobre aquest error." +#: lib/solaar/ui/diversion_rules.py:1187 +msgid "X11 active process. For use in X11 only." +msgstr "" -#: lib/solaar/ui/pair_window.py:156 -msgid "Found a new device:" -msgstr "S'ha trobat un nou dispositiu:" +#: lib/solaar/ui/diversion_rules.py:1218 +msgid "X11 mouse process. For use in X11 only." +msgstr "" -#: lib/solaar/ui/pair_window.py:181 -msgid "The wireless link is not encrypted" -msgstr "La connexió sense fils no és xifrada" +#: lib/solaar/ui/diversion_rules.py:1235 +msgid "MouseProcess" +msgstr "ProcésRatolí" -#: lib/solaar/ui/pair_window.py:199 +#: lib/solaar/ui/diversion_rules.py:1260 +msgid "Feature name of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1308 +msgid "Report number of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1342 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1383 +msgid "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1392 +msgid "Key down" +msgstr "Tecla avall" + +#: lib/solaar/ui/diversion_rules.py:1395 +msgid "Key up" +msgstr "Tecla amunt" + +#: lib/solaar/ui/diversion_rules.py:1436 +msgid "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1475 +msgid "Test condition on notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1479 +msgid "Parameter" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1542 +msgid "begin (inclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1543 +msgid "end (exclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1552 +msgid "range" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1554 +msgid "minimum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1555 +msgid "maximum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1557 #, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s: vincular nou dispositiu" +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "" -#: lib/solaar/ui/pair_window.py:206 -msgid "If the device is already turned on, turn it off and on again." -msgstr "Si el dispositiu ja està encès, apagui'l i torni'l a encendre." +#: lib/solaar/ui/diversion_rules.py:1562 +msgid "mask" +msgstr "" -#: lib/solaar/ui/pair_window.py:209 +#: lib/solaar/ui/diversion_rules.py:1563 #, python-format -msgid "" -"\n" -"\n" -"This receiver has %d pairing remaining." -msgid_plural "" -"\n" -"\n" -"This receiver has %d pairings remaining." -msgstr[0] "" -"\n" -"\n" -"Aquest receptor té %d aparellament restant." -msgstr[1] "" -"\n" -"\n" -"Aquest receptor té %d aparellaments restants." +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "" -#: lib/solaar/ui/pair_window.py:212 -msgid "" -"\n" -"Cancelling at this point will not use up a pairing." -msgstr "" -"\n" -"Si cancel·la en aquest punt no es farà servir un aparellament." +#: lib/solaar/ui/diversion_rules.py:1573 +msgid "Bit or range test on bytes in notification message triggering rule " + "processing." +msgstr "" -#: lib/solaar/ui/pair_window.py:215 -msgid "Turn on the device you want to pair." -msgstr "Engegui el dispositiu que vol vincular." +#: lib/solaar/ui/diversion_rules.py:1583 +msgid "type" +msgstr "" -#: lib/solaar/ui/tray.py:61 -msgid "No Logitech receiver found" -msgstr "No s'ha trobat cap receptor Logitech" +#: lib/solaar/ui/diversion_rules.py:1666 +msgid "Mouse gesture with optional initiating button followed by zero or " + "more mouse movements." +msgstr "" -#: lib/solaar/ui/tray.py:68 lib/solaar/ui/window.py:325 -msgid "Quit %s" -msgstr "Sortir %s" +#: lib/solaar/ui/diversion_rules.py:1671 +msgid "Add movement" +msgstr "" -#: lib/solaar/ui/tray.py:298 lib/solaar/ui/tray.py:306 -msgid "no receiver" -msgstr "sense receptor" +#: lib/solaar/ui/diversion_rules.py:1764 +msgid "Simulate a chorded key click or depress or release.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" -#: lib/solaar/ui/tray.py:322 -msgid "no status" -msgstr "sense estat" +#: lib/solaar/ui/diversion_rules.py:1769 +msgid "Add key" +msgstr "Afegir tecla" -#: lib/solaar/ui/window.py:104 -msgid "Scanning" -msgstr "Explorant" +#: lib/solaar/ui/diversion_rules.py:1772 +msgid "Click" +msgstr "" -#: lib/solaar/ui/window.py:137 lib/solaar/ui/window.py:692 -msgid "Battery" -msgstr "Bateria" +#: lib/solaar/ui/diversion_rules.py:1775 +msgid "Depress" +msgstr "" -#: lib/solaar/ui/window.py:140 -msgid "Wireless Link" -msgstr "Enllaç sense fils" +#: lib/solaar/ui/diversion_rules.py:1778 +msgid "Release" +msgstr "" -#: lib/solaar/ui/window.py:144 -msgid "Lighting" -msgstr "Il·luminació" +#: lib/solaar/ui/diversion_rules.py:1861 +msgid "Simulate a mouse scroll.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" -#: lib/solaar/ui/window.py:178 -msgid "Show Technical Details" -msgstr "Mostrar detalls tècnics" +#: lib/solaar/ui/diversion_rules.py:1918 +msgid "Simulate a mouse click.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" -#: lib/solaar/ui/window.py:194 -msgid "Pair new device" -msgstr "Vincular nou dispositiu" +#: lib/solaar/ui/diversion_rules.py:1921 +msgid "Button" +msgstr "Botó" -#: lib/solaar/ui/window.py:213 -msgid "Select a device" -msgstr "Seleccionar un dispositiu" +#: lib/solaar/ui/diversion_rules.py:1922 +msgid "Count and Action" +msgstr "" -#: lib/solaar/ui/window.py:331 -msgid "Rule Editor" -msgstr "Editor de Regles" +#: lib/solaar/ui/diversion_rules.py:1972 +msgid "Execute a command with arguments." +msgstr "" -#: lib/solaar/ui/window.py:541 -msgid "Path" -msgstr "Ruta" +#: lib/solaar/ui/diversion_rules.py:1975 +msgid "Add argument" +msgstr "Afegir argument" -#: lib/solaar/ui/window.py:544 -msgid "USB ID" -msgstr "ID USB" +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "Toggle" +msgstr "" -#: lib/solaar/ui/window.py:547 lib/solaar/ui/window.py:549 -#: lib/solaar/ui/window.py:569 lib/solaar/ui/window.py:571 -msgid "Serial" -msgstr "Serial" +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "True" +msgstr "" -#: lib/solaar/ui/window.py:553 -msgid "Index" -msgstr "Índex" +#: lib/solaar/ui/diversion_rules.py:2051 +msgid "False" +msgstr "" -#: lib/solaar/ui/window.py:555 -msgid "Wireless PID" -msgstr "PID sense fils" +#: lib/solaar/ui/diversion_rules.py:2065 +msgid "Unsupported setting" +msgstr "" -#: lib/solaar/ui/window.py:557 -msgid "Product ID" -msgstr "ID del producte" +#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2242 +#: lib/solaar/ui/diversion_rules.py:2328 lib/solaar/ui/diversion_rules.py:2570 +#: lib/solaar/ui/diversion_rules.py:2588 +msgid "Originating device" +msgstr "" -#: lib/solaar/ui/window.py:559 -msgid "Protocol" -msgstr "Protocol" +#: lib/solaar/ui/diversion_rules.py:2256 +msgid "Device is active and its settings can be changed." +msgstr "" -#: lib/solaar/ui/window.py:559 -msgid "Unknown" -msgstr "Desconegut" +#: lib/solaar/ui/diversion_rules.py:2266 +msgid "Device that originated the current notification." +msgstr "" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/diversion_rules.py:2280 +msgid "Name of host computer." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2347 +msgid "Value" +msgstr "Valor" + +#: lib/solaar/ui/diversion_rules.py:2355 +msgid "Item" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2630 +msgid "Change setting on device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2647 +msgid "Setting on device" +msgstr "" + +#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:318 +#: lib/solaar/ui/tray.py:323 lib/solaar/ui/window.py:739 +msgid "offline" +msgstr "desconectat" + +#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 +#: lib/solaar/ui/pair_window.py:288 #, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d ms (%(rate_hz)dHz)" +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: vincular nou dispositiu" -#: lib/solaar/ui/window.py:562 -msgid "Polling rate" -msgstr "Taxa de sondeig" - -#: lib/solaar/ui/window.py:573 -msgid "Unit ID" -msgstr "ID Unitat" - -#: lib/solaar/ui/window.py:584 -msgid "none" -msgstr "cap" - -#: lib/solaar/ui/window.py:585 -msgid "Notifications" -msgstr "Notificacions" - -#: lib/solaar/ui/window.py:622 -msgid "No device paired." -msgstr "No hi ha dispositius vinculats." - -#: lib/solaar/ui/window.py:629 +#: lib/solaar/ui/pair_window.py:123 #, python-format -msgid "Up to %(max_count)s device can be paired to this receiver." -msgid_plural "Up to %(max_count)s devices can be paired to this receiver." -msgstr[0] "Es pot vincular fins a %(max_count)s dispositiu a aquest receptor." -msgstr[1] "Es pot vincular fins a %(max_count)s dispositius a aquest receptor." +msgid "Enter passcode on %(name)s." +msgstr "" -#: lib/solaar/ui/window.py:635 -msgid "Only one device can be paired to this receiver." -msgstr "Només es pot vincular un dispositiu a aquest receptor." - -#: lib/solaar/ui/window.py:639 +#: lib/solaar/ui/pair_window.py:126 #, python-format -msgid "This receiver has %d pairing remaining." -msgid_plural "This receiver has %d pairings remaining." -msgstr[0] "Aquest receptor té %d aparellament restant." -msgstr[1] "Aquest receptor té %d aparellaments restants." +msgid "Type %(passcode)s and then press the enter key." +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "left" +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "right" +msgstr "" + +#: lib/solaar/ui/pair_window.py:131 +#, python-format +msgid "Press %(code)s\n" + "and then press left and right buttons simultaneously." +msgstr "" + +#: lib/solaar/ui/pair_window.py:188 +msgid "Pairing failed" +msgstr "Ha falla la vinculació" + +#: lib/solaar/ui/pair_window.py:190 +msgid "Make sure your device is within range, and has a decent battery " + "charge." +msgstr "Asseguri's que el seu dispositiu estigui dins l'abast del receptor i " + "que la bateria tingui suficient càrrega." + +#: lib/solaar/ui/pair_window.py:192 +msgid "A new device was detected, but it is not compatible with this " + "receiver." +msgstr "S'ha detectat un nou dispositiu, però no és compatible amb aquest " + "receptor." + +#: lib/solaar/ui/pair_window.py:194 +msgid "More paired devices than receiver can support." +msgstr "Més dispositius emparellats de els que admet el receptor." + +#: lib/solaar/ui/pair_window.py:196 +msgid "No further details are available about the error." +msgstr "No hi ha més detalls disponibles sobre aquest error." + +#: lib/solaar/ui/pair_window.py:210 +msgid "Found a new device:" +msgstr "S'ha trobat un nou dispositiu:" + +#: lib/solaar/ui/pair_window.py:235 +msgid "The wireless link is not encrypted" +msgstr "La connexió sense fils no és xifrada" + +#: lib/solaar/ui/pair_window.py:264 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:266 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:268 +msgid "Other receivers are only compatible with a few devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:270 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" + +#: lib/solaar/ui/pair_window.py:274 +msgid "Press a pairing button or key until the pairing light flashes " + "quickly." +msgstr "" + +#: lib/solaar/ui/pair_window.py:276 +msgid "You may have to first turn the device off and on again." +msgstr "" + +#: lib/solaar/ui/pair_window.py:278 +msgid "Turn on the device you want to pair." +msgstr "Engegui el dispositiu que vol vincular." + +#: lib/solaar/ui/pair_window.py:280 +msgid "If the device is already turned on, turn it off and on again." +msgstr "Si el dispositiu ja està encès, apagui'l i torni'l a encendre." + +#: lib/solaar/ui/pair_window.py:283 +#, python-format +msgid "\n" + "\n" + "This receiver has %d pairing remaining." +msgid_plural "\n" + "\n" + "This receiver has %d pairings remaining." +msgstr[0] "\n" + "\n" + "Aquest receptor té %d aparellament restant." +msgstr[1] "\n" + "\n" + "Aquest receptor té %d aparellaments restants." + +#: lib/solaar/ui/pair_window.py:286 +msgid "\n" + "Cancelling at this point will not use up a pairing." +msgstr "\n" + "Si cancel·la en aquest punt no es farà servir un aparellament." + +#: lib/solaar/ui/tray.py:58 +msgid "No supported device found" +msgstr "" + +#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#, python-format +msgid "About %s" +msgstr "Sobre %s" + +#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 +#, python-format +msgid "Quit %s" +msgstr "Sortir %s" + +#: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 +msgid "no receiver" +msgstr "sense receptor" + +#: lib/solaar/ui/tray.py:321 +msgid "no status" +msgstr "sense estat" + +#: lib/solaar/ui/window.py:96 +msgid "Scanning" +msgstr "Explorant" + +#: lib/solaar/ui/window.py:129 +msgid "Battery" +msgstr "Bateria" + +#: lib/solaar/ui/window.py:132 +msgid "Wireless Link" +msgstr "Enllaç sense fils" + +#: lib/solaar/ui/window.py:136 +msgid "Lighting" +msgstr "Il·luminació" + +#: lib/solaar/ui/window.py:170 +msgid "Show Technical Details" +msgstr "Mostrar detalls tècnics" + +#: lib/solaar/ui/window.py:186 +msgid "Pair new device" +msgstr "Vincular nou dispositiu" + +#: lib/solaar/ui/window.py:205 +msgid "Select a device" +msgstr "Seleccionar un dispositiu" + +#: lib/solaar/ui/window.py:322 +msgid "Rule Editor" +msgstr "Editor de Regles" + +#: lib/solaar/ui/window.py:533 +msgid "Path" +msgstr "Ruta" + +#: lib/solaar/ui/window.py:536 +msgid "USB ID" +msgstr "ID USB" + +#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 +msgid "Serial" +msgstr "Serial" + +#: lib/solaar/ui/window.py:545 +msgid "Index" +msgstr "Índex" + +#: lib/solaar/ui/window.py:547 +msgid "Wireless PID" +msgstr "PID sense fils" + +#: lib/solaar/ui/window.py:549 +msgid "Product ID" +msgstr "ID del producte" + +#: lib/solaar/ui/window.py:551 +msgid "Protocol" +msgstr "Protocol" + +#: lib/solaar/ui/window.py:551 +msgid "Unknown" +msgstr "Desconegut" + +#: lib/solaar/ui/window.py:554 +#, python-format +msgid "%(rate)d ms (%(rate_hz)dHz)" +msgstr "%(rate)d ms (%(rate_hz)dHz)" + +#: lib/solaar/ui/window.py:554 +msgid "Polling rate" +msgstr "Taxa de sondeig" + +#: lib/solaar/ui/window.py:565 +msgid "Unit ID" +msgstr "ID Unitat" + +#: lib/solaar/ui/window.py:576 +msgid "none" +msgstr "cap" + +#: lib/solaar/ui/window.py:577 +msgid "Notifications" +msgstr "Notificacions" + +#: lib/solaar/ui/window.py:621 +msgid "No device paired." +msgstr "No hi ha dispositius vinculats." + +#: lib/solaar/ui/window.py:628 +#, python-format +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "Es pot vincular fins a %(max_count)s dispositiu a aquest " + "receptor." +msgstr[1] "Es pot vincular fins a %(max_count)s dispositius a aquest " + "receptor." + +#: lib/solaar/ui/window.py:634 +msgid "Only one device can be paired to this receiver." +msgstr "Només es pot vincular un dispositiu a aquest receptor." + +#: lib/solaar/ui/window.py:638 +#, python-format +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "Aquest receptor té %d aparellament restant." +msgstr[1] "Aquest receptor té %d aparellaments restants." + +#: lib/solaar/ui/window.py:692 +msgid "Battery Voltage" +msgstr "Voltatge de la bateria" #: lib/solaar/ui/window.py:694 -msgid "Battery information unknown." -msgstr "Informació de la bateria desconeguda." +msgid "Voltage reported by battery" +msgstr "Voltatge informat per la bateria" -#: lib/solaar/ui/window.py:702 -msgid "Battery Voltage" -msgstr "Voltatge de la bateria" +#: lib/solaar/ui/window.py:696 +msgid "Battery Level" +msgstr "Nivell de Bateria" -#: lib/solaar/ui/window.py:704 -msgid "Voltage reported by battery" -msgstr "Voltatge informat per la bateria" +#: lib/solaar/ui/window.py:698 +msgid "Approximate level reported by battery" +msgstr "Nivell aproximat informat per la bateria" -#: lib/solaar/ui/window.py:706 lib/solaar/ui/window.py:710 -msgid "Battery Level" -msgstr "Nivell de Bateria" +#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 +msgid "next reported " +msgstr "següent informe " -#: lib/solaar/ui/window.py:708 lib/solaar/ui/window.py:712 -msgid "Approximate level reported by battery" -msgstr "Nivell aproximat informat per la bateria" +#: lib/solaar/ui/window.py:708 +msgid " and next level to be reported." +msgstr " i següent nivell que serà informat." -#: lib/solaar/ui/window.py:715 lib/solaar/ui/window.py:717 -msgid "next reported " -msgstr "següent informe " +#: lib/solaar/ui/window.py:713 +msgid "last known" +msgstr "últim conegut" -#: lib/solaar/ui/window.py:718 -msgid " and next level to be reported." -msgstr " i següent nivell que serà informat." +#: lib/solaar/ui/window.py:724 +msgid "encrypted" +msgstr "xifrat" -#: lib/solaar/ui/window.py:723 -msgid "last known" -msgstr "últim conegut" +#: lib/solaar/ui/window.py:726 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "La connexió sense fils entre el dispositiu i el seu receptor està " + "xifrada." -#: lib/solaar/ui/window.py:731 -msgid "not encrypted" -msgstr "no xifrat" +#: lib/solaar/ui/window.py:728 +msgid "not encrypted" +msgstr "no xifrat" -#: lib/solaar/ui/window.py:735 -msgid "" -"The wireless link between this device and its receiver is not encrypted.\n" -"\n" -"For pointing devices (mice, trackballs, trackpads), this is a minor security " -"issue.\n" -"\n" -"It is, however, a major security issue for text-input devices (keyboards, " -"numpads),\n" -"because typed text can be sniffed inconspicuously by 3rd parties within range." -msgstr "" -"La conexió inalámbrica entre el dispositiu i el receptor no està cifrada.\n" -"\n" -"Per dispositius apuntadors (ratolins, trackballs, trackpads), aquest és un " -"problema menor de seguretat.\n" -"\n" -"Tot i això, per dispositius d'entrada de text (teclats, teclats numèrics) sí " -"que és un problema greu,\n" -"doncs el text introduït pot ser capturat de forma inadvertida per tercers que " -"estiguin aprop." +#: lib/solaar/ui/window.py:732 +msgid "The wireless link between this device and its receiver is not " + "encrypted.\n" + "This is a security issue for pointing devices, and a major security " + "issue for text-input devices." +msgstr "" -#: lib/solaar/ui/window.py:744 -msgid "encrypted" -msgstr "xifrat" - -#: lib/solaar/ui/window.py:746 -msgid "The wireless link between this device and its receiver is encrypted." -msgstr "" -"La connexió sense fils entre el dispositiu i el seu receptor està xifrada." - -#: lib/solaar/ui/window.py:759 +#: lib/solaar/ui/window.py:748 #, python-format -msgid "%(light_level)d lux" -msgstr "%(light_level)d lux" +msgid "%(light_level)d lux" +msgstr "%(light_level)d lux" -#~ msgid "" -#~ "\n" -#~ "\n" -#~ "This receiver has %d pairing(s) remaining." -#~ msgstr "" -#~ "\n" -#~ "\n" -#~ "Aquest receptor té %d aparellament(s) disponibles." +#~ msgid "\n" +#~ "\n" +#~ "This receiver has %d pairing(s) remaining." +#~ msgstr "\n" +#~ "\n" +#~ "Aquest receptor té %d aparellament(s) disponibles." -#~ msgid "%(battery_level)s" -#~ msgstr "%(battery_level)s" +#~ msgid "%(battery_level)s" +#~ msgstr "%(battery_level)s" -#~ msgid "%(battery_percent)d%%" -#~ msgstr "%(battery_percent)d%%" +#~ msgid "%(battery_percent)d%%" +#~ msgstr "%(battery_percent)d%%" -#~ msgid "" -#~ "Automatically switch the mouse wheel between ratchet and freespin mode.\n" -#~ "The mouse wheel is always free at 0, and always locked at 50" -#~ msgstr "" -#~ "Canvia automàticament el mode de la rodeta del ratolí entre bloquejat i gir " -#~ "lliure.\n" -#~ "La rodeta del ratolí sempre està lliure a 0 i sempre bloquejat a 50" +#~ msgid "Add action" +#~ msgstr "Afegir acció" -#~ msgid "HID++ Scrolling" -#~ msgstr "Desplaçament HID++" +#~ msgid "Adjust the DPI by sliding the mouse horizontally while " +#~ "holding the button down." +#~ msgstr "Ajustar els DPI lliscant el ratolí horitzontalment mentre es " +#~ "manté el botó pressionat." -#~ msgid "High Resolution Scrolling" -#~ msgstr "Desplaçament d'alta resolució" +#~ msgid "Automatically switch the mouse wheel between ratchet and " +#~ "freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always locked at 50" +#~ msgstr "Canvia automàticament el mode de la rodeta del ratolí entre " +#~ "bloquejat i gir lliure.\n" +#~ "La rodeta del ratolí sempre està lliure a 0 i sempre bloquejat a 50" -#~ msgid "High Resolution Wheel Invert" -#~ msgstr "Invertir rodeta a l'alta resolució " +#~ msgid "Automatically switch the mouse wheel between ratchet and " +#~ "freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always ratcheted at 50" +#~ msgstr "Canviar automàticament la rodeta del ratolí entre el mode de " +#~ "trinquet i el mode de gir lliure.\n" +#~ "La rodeta del ratolí sempre està lliure a 0 i sempre amb trinquet a " +#~ "50" -#~ msgid "High-sensitivity wheel invert mode for vertical scroll." -#~ msgstr "Mode invers d'alta resolució a la rodeta per desplaçament vertical." +#~ msgid "Battery information unknown." +#~ msgstr "Informació de la bateria desconeguda." -#~ msgid "If the device is already turned on, turn if off and on again." -#~ msgstr "Si el dispositiu ja està encès, apagui'l i torni'l a encendre." +#~ msgid "Count" +#~ msgstr "Conta" -#~ msgid "" -#~ "Shows status of devices connected\n" -#~ "through wireless Logitech receivers." -#~ msgstr "" -#~ "Mostrar l'estat de dispositius connectats\n" -#~ "mitjançant receptors sense fils Logitech." +#~ msgid "DPI Sliding Adjustment" +#~ msgstr "Ajustar DPI lliscant" -#~ msgid "Smooth Scrolling" -#~ msgstr "Desplaçament suau" +#~ msgid "Effectively turns off thumb scrolling in Linux." +#~ msgstr "Desactiva eficaçment el desplaçament amb el polze en Linux." -#~ msgid "The receiver only supports %d paired device(s)." -#~ msgstr "El receptor només soporta %d dispositiu(s) aparellats(s)." +#~ msgid "Effectively turns off wheel scrolling in Linux." +#~ msgstr "Desactiva eficaçment l'scroll de la rodeta amb Linux." -#~ msgid "The receiver was unplugged." -#~ msgstr "El receptor s'ha desconnectat." +#, python-format +#~ msgid "Found a Logitech Receiver (%s), but did not have permission " +#~ "to open it." +#~ msgstr "S'ha trobat receptor Logitech (%s), però no té permisos per " +#~ "obrir-lo." -#~ msgid "This receiver has %d pairing(s) remaining." -#~ msgstr "Este receptor té %d aparellament(s) disponibles." +#~ msgid "HID++ Scrolling" +#~ msgstr "Desplaçament HID++" -#~ msgid "USB id" -#~ msgstr "id USB" +#~ msgid "HID++ mode for horizontal scroll with the thumb wheel." +#~ msgstr "Mode HID++ per desplaçament horitzontal amb la rodeta del " +#~ "polze." -#~ msgid "Wheel Resolution" -#~ msgstr "Resolució de la rodeta" +#~ msgid "HID++ mode for vertical scroll with the wheel." +#~ msgstr "Mode HID++ pel desplaçament vertical amb la rodeta." + +#~ msgid "High Resolution Scrolling" +#~ msgstr "Desplaçament d'alta resolució" + +#~ msgid "High Resolution Wheel Invert" +#~ msgstr "Invertir rodeta a l'alta resolució " + +#~ msgid "High-sensitivity wheel invert mode for vertical scroll." +#~ msgstr "Mode invers d'alta resolució a la rodeta per desplaçament " +#~ "vertical." + +#~ msgid "If the device is already turned on, turn if off and on again." +#~ msgstr "Si el dispositiu ja està encès, apagui'l i torni'l a " +#~ "encendre." + +#~ msgid "If you've just installed Solaar, try removing the receiver " +#~ "and plugging it back in." +#~ msgstr "Si acaba d'instal·lar Solaar, intenta treure el receptor i " +#~ "connectar-lo de nou." + +#~ msgid "Make the key or button send HID++ notifications (which " +#~ "trigger Solaar rules but are otherwise ignored)." +#~ msgstr "Fer que la tecla o el botó enviï notificacions HID ++ (que " +#~ "activen les regles de Solaar, del contrari s'ignoren)." + +#~ msgid "No Logitech receiver found" +#~ msgstr "No s'ha trobat cap receptor Logitech" + +#~ msgid "Scroll Wheel Rachet" +#~ msgstr "Trinquet Rodeta Desplaçament" + +#~ msgid "Send a gesture by sliding the mouse while holding the button " +#~ "down." +#~ msgstr "Enviar un gest lliscant el ratolí mentre manté pressionat el " +#~ "botó." + +#~ msgid "Shows status of devices connected\n" +#~ "through wireless Logitech receivers." +#~ msgstr "Mostrar l'estat de dispositius connectats\n" +#~ "mitjançant receptors sense fils Logitech." + +#~ msgid "Smooth Scrolling" +#~ msgstr "Desplaçament suau" + +#~ msgid "The receiver only supports %d paired device(s)." +#~ msgstr "El receptor només soporta %d dispositiu(s) aparellats(s)." + +#~ msgid "The receiver was unplugged." +#~ msgstr "El receptor s'ha desconnectat." + +#~ msgid "The wireless link between this device and its receiver is " +#~ "not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices " +#~ "(keyboards, numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties " +#~ "within range." +#~ msgstr "La conexió inalámbrica entre el dispositiu i el receptor no " +#~ "està cifrada.\n" +#~ "\n" +#~ "Per dispositius apuntadors (ratolins, trackballs, trackpads), aquest " +#~ "és un problema menor de seguretat.\n" +#~ "\n" +#~ "Tot i això, per dispositius d'entrada de text (teclats, teclats " +#~ "numèrics) sí que és un problema greu,\n" +#~ "doncs el text introduït pot ser capturat de forma inadvertida per " +#~ "tercers que estiguin aprop." + +#~ msgid "This receiver has %d pairing(s) remaining." +#~ msgstr "Este receptor té %d aparellament(s) disponibles." + +#~ msgid "Top-most coordinate." +#~ msgstr "Coordenada superior." + +#~ msgid "Try removing the device and plugging it back in or turning " +#~ "it off and then on." +#~ msgstr "Intenti treure el dispositiu i tornar a endollar-lo o apagar-" +#~ "lo i encendre'l." + +#~ msgid "USB id" +#~ msgstr "id USB" + +#~ msgid "Wheel Resolution" +#~ msgstr "Resolució de la rodeta" + +#~ msgid "height" +#~ msgstr "alçada" + +#~ msgid "top" +#~ msgstr "superior" + +#~ msgid "unknown" +#~ msgstr "desconegut" + +#~ msgid "width" +#~ msgstr "amplada" diff --git a/po/cs.po b/po/cs.po index 143f1db2..dfc8d4d0 100644 --- a/po/cs.po +++ b/po/cs.po @@ -7,7 +7,7 @@ msgid "" msgstr "Project-Id-Version: solaar 1.0.4\n" "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2021-09-25 13:56-0400\n" + "POT-Creation-Date: 2023-12-28 17:40+0100\n" "PO-Revision-Date: 2020-12-30 17:48+0100\n" "Last-Translator: Marián Kyral \n" "Language-Team: Czech \n" @@ -19,271 +19,299 @@ msgstr "Project-Id-Version: solaar 1.0.4\n" "2;\n" "X-Generator: Lokalize 20.12.0\n" -#: lib/logitech_receiver/base_usb.py:50 +#: lib/logitech_receiver/base_usb.py:46 +msgid "Bolt Receiver" +msgstr "" + +#: lib/logitech_receiver/base_usb.py:57 msgid "Unifying Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:58 lib/logitech_receiver/base_usb.py:68 -#: lib/logitech_receiver/base_usb.py:79 lib/logitech_receiver/base_usb.py:90 -#: lib/logitech_receiver/base_usb.py:101 +#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 +#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 +#: lib/logitech_receiver/base_usb.py:114 msgid "Nano Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:109 +#: lib/logitech_receiver/base_usb.py:124 msgid "Lightspeed Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:117 +#: lib/logitech_receiver/base_usb.py:133 msgid "EX100 Receiver 27 Mhz" msgstr "" -#: lib/logitech_receiver/device.py:133 lib/solaar/ui/window.py:693 -msgid "unknown" -msgstr "neznámý" - -#: lib/logitech_receiver/i18n.py:38 +#: lib/logitech_receiver/i18n.py:30 msgid "empty" msgstr "prázdná" -#: lib/logitech_receiver/i18n.py:39 +#: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "kritická" -#: lib/logitech_receiver/i18n.py:40 +#: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "nízká" -#: lib/logitech_receiver/i18n.py:41 +#: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "" -#: lib/logitech_receiver/i18n.py:42 +#: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "dobrá" -#: lib/logitech_receiver/i18n.py:43 +#: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "plně nabito" -#: lib/logitech_receiver/i18n.py:46 +#: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "vybíjení" -#: lib/logitech_receiver/i18n.py:47 +#: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "nabíjení" -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:721 +#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "nabíjení" -#: lib/logitech_receiver/i18n.py:49 +#: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "" -#: lib/logitech_receiver/i18n.py:50 +#: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "téměř nabito" -#: lib/logitech_receiver/i18n.py:51 +#: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "nabito" -#: lib/logitech_receiver/i18n.py:52 +#: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "pomalé nabíjení" -#: lib/logitech_receiver/i18n.py:53 +#: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "vadná baterie" -#: lib/logitech_receiver/i18n.py:54 +#: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "teplotní chyba" -#: lib/logitech_receiver/i18n.py:55 +#: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "" -#: lib/logitech_receiver/i18n.py:56 +#: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "" -#: lib/logitech_receiver/i18n.py:57 +#: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "" -#: lib/logitech_receiver/i18n.py:58 +#: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "" -#: lib/logitech_receiver/i18n.py:61 +#: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "vypršel časový limit zařízení" -#: lib/logitech_receiver/i18n.py:62 +#: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "nepodporované zařízení" -#: lib/logitech_receiver/i18n.py:63 +#: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "příliš mnoho zařízení" -#: lib/logitech_receiver/i18n.py:64 +#: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "vypršel časový limit sekvence" -#: lib/logitech_receiver/i18n.py:67 lib/solaar/ui/window.py:580 +#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 msgid "Firmware" msgstr "Firmware" -#: lib/logitech_receiver/i18n.py:68 +#: lib/logitech_receiver/i18n.py:60 msgid "Bootloader" msgstr "Bootloader" -#: lib/logitech_receiver/i18n.py:69 +#: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardware" -#: lib/logitech_receiver/i18n.py:70 +#: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Další" -#: lib/logitech_receiver/i18n.py:73 +#: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:74 +#: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:75 +#: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "" -#: lib/logitech_receiver/i18n.py:76 +#: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "" -#: lib/logitech_receiver/i18n.py:77 +#: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "" -#: lib/logitech_receiver/i18n.py:78 +#: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "" -#: lib/logitech_receiver/i18n.py:79 +#: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "" -#: lib/logitech_receiver/i18n.py:80 +#: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "" -#: lib/logitech_receiver/i18n.py:81 +#: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "" -#: lib/logitech_receiver/i18n.py:82 +#: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "" -#: lib/logitech_receiver/i18n.py:83 +#: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "" -#: lib/logitech_receiver/i18n.py:84 +#: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "" -#: lib/logitech_receiver/i18n.py:85 +#: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "" -#: lib/logitech_receiver/i18n.py:86 +#: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "" -#: lib/logitech_receiver/i18n.py:87 +#: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "" -#: lib/logitech_receiver/i18n.py:88 +#: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "" -#: lib/logitech_receiver/i18n.py:89 +#: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:90 +#: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:93 +#: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "" -#: lib/logitech_receiver/i18n.py:94 +#: lib/logitech_receiver/i18n.py:86 msgid "released" msgstr "" -#: lib/logitech_receiver/notifications.py:76 +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is closed" msgstr "zámek připojení byl uzavřen" -#: lib/logitech_receiver/notifications.py:76 +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is open" msgstr "zámek připojení byl otevřen" -#: lib/logitech_receiver/notifications.py:160 lib/solaar/ui/notify.py:123 +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is closed" +msgstr "" + +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is open" +msgstr "" + +#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 msgid "connected" msgstr "připojeno" -#: lib/logitech_receiver/notifications.py:160 +#: lib/logitech_receiver/notifications.py:224 msgid "disconnected" msgstr "odpojeno" -#: lib/logitech_receiver/notifications.py:198 lib/solaar/ui/notify.py:121 +#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "nepřipojeno" -#: lib/logitech_receiver/notifications.py:248 +#: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "zapnuto" -#: lib/logitech_receiver/settings.py:526 +#: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "" -#: lib/logitech_receiver/settings.py:542 lib/logitech_receiver/settings.py:568 +#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 msgid "feature" msgstr "" -#: lib/logitech_receiver/settings_templates.py:71 +#: lib/logitech_receiver/settings_templates.py:139 +msgid "Swap Fx function" +msgstr "Přepnout Fx funkci" + +#: lib/logitech_receiver/settings_templates.py:140 +msgid "When set, the F1..F12 keys will activate their special function,\n" + "and you must hold the FN key to activate their standard function." +msgstr "Pokud je nastaveno, budou klávesy F1..F12 spouštět jejich speciální " + "funkce\n" + "a musíte držet Fn abyste aktivovali jejich standardní funkce." + +#: lib/logitech_receiver/settings_templates.py:142 +msgid "When unset, the F1..F12 keys will activate their standard function,\n" + "and you must hold the FN key to activate their special function." +msgstr "Pokud není nastaveno, budou klávesy F1..F12 spouštět jejich " + "standardní funkce\n" + "a musíte držet Fn abyste aktivovali jejich speciální funkce." + +#: lib/logitech_receiver/settings_templates.py:149 msgid "Hand Detection" msgstr "Detekce rukou" -#: lib/logitech_receiver/settings_templates.py:71 +#: lib/logitech_receiver/settings_templates.py:150 msgid "Turn on illumination when the hands hover over the keyboard." msgstr "Zapne podsvícení pokud se ruce objeví nad klávesnicí." -#: lib/logitech_receiver/settings_templates.py:72 +#: lib/logitech_receiver/settings_templates.py:157 msgid "Scroll Wheel Smooth Scrolling" msgstr "Plynulý posuv kolečkem" -#: lib/logitech_receiver/settings_templates.py:73 -#: lib/logitech_receiver/settings_templates.py:78 -#: lib/logitech_receiver/settings_templates.py:85 +#: lib/logitech_receiver/settings_templates.py:158 +#: lib/logitech_receiver/settings_templates.py:239 +#: lib/logitech_receiver/settings_templates.py:267 msgid "High-sensitivity mode for vertical scroll with the wheel." msgstr "Vysoce citlivý režim svislého posuvu kolečkem." -#: lib/logitech_receiver/settings_templates.py:74 +#: lib/logitech_receiver/settings_templates.py:165 msgid "Side Scrolling" msgstr "Boční posuv" -#: lib/logitech_receiver/settings_templates.py:75 +#: lib/logitech_receiver/settings_templates.py:167 msgid "When disabled, pushing the wheel sideways sends custom button " "events\n" "instead of the standard side-scrolling events." @@ -291,465 +319,592 @@ msgstr "Pokud je zakázáno, boční stlačení kolečka pošle vlastní událo "tlačítek\n" "místo standardních událostí bočního posuvu." -#: lib/logitech_receiver/settings_templates.py:77 -msgid "Scroll Wheel High Resolution" -msgstr "Vysoké rozlišení posuvného kolečka" - -#: lib/logitech_receiver/settings_templates.py:79 -msgid "Scroll Wheel Diversion" +#: lib/logitech_receiver/settings_templates.py:177 +msgid "Sensitivity (DPI - older mice)" msgstr "" -#: lib/logitech_receiver/settings_templates.py:80 -msgid "HID++ mode for vertical scroll with the wheel." -msgstr "HID++ režim pro svislý posuv kolečkem." - -#: lib/logitech_receiver/settings_templates.py:81 -msgid "Effectively turns off wheel scrolling in Linux." -msgstr "Efektivně vypne posuv kolečkem v Linuxu." - -#: lib/logitech_receiver/settings_templates.py:82 -msgid "Scroll Wheel Direction" -msgstr "Směr kolečka" - -#: lib/logitech_receiver/settings_templates.py:83 -msgid "Invert direction for vertical scroll with wheel." -msgstr "Mění směr svislého otáčení kolečka." - -#: lib/logitech_receiver/settings_templates.py:84 -msgid "Scroll Wheel Resolution" -msgstr "Rozlišení posuvného kolečka" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Frequency of device polling, in milliseconds" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Polling Rate (ms)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:87 -msgid "Swap Fx function" -msgstr "Přepnout Fx funkci" - -#: lib/logitech_receiver/settings_templates.py:88 -msgid "When set, the F1..F12 keys will activate their special function,\n" - "and you must hold the FN key to activate their standard function." -msgstr "Pokud je nastaveno, budou klávesy F1..F12 spouštět jejich speciální " - "funkce\n" - "a musíte držet Fn abyste aktivovali jejich standardní funkce." - -#: lib/logitech_receiver/settings_templates.py:90 -msgid "When unset, the F1..F12 keys will activate their standard function,\n" - "and you must hold the FN key to activate their special function." -msgstr "Pokud není nastaveno, budou klávesy F1..F12 spouštět jejich " - "standardní funkce\n" - "a musíte držet Fn abyste aktivovali jejich speciální funkce." - -#: lib/logitech_receiver/settings_templates.py:92 +#: lib/logitech_receiver/settings_templates.py:178 +#: lib/logitech_receiver/settings_templates.py:712 msgid "Mouse movement sensitivity" msgstr "Citlivost kurzoru myši" -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Sensitivity (DPI)" -msgstr "Citlivost (DPI)" - -#: lib/logitech_receiver/settings_templates.py:93 -msgid "Sensitivity (Pointer Speed)" -msgstr "Citlivost (rychlost kurzoru)" - -#: lib/logitech_receiver/settings_templates.py:94 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "Rychlostní násobitel myši (256 je běžný násobitel)." - -#: lib/logitech_receiver/settings_templates.py:95 -msgid "Scroll Wheel Rachet" -msgstr "Krokování kolečka" - -#: lib/logitech_receiver/settings_templates.py:96 -msgid "Automatically switch the mouse wheel between ratchet and freespin " - "mode.\n" - "The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "Automatické přepíná režim kolečka myši mezi krokováním a volným " - "otáčením.\n" - "Kolečko je při hodnotě 0 vždy volné a při hodnotě 50 vždy krokuje." - -#: lib/logitech_receiver/settings_templates.py:98 +#: lib/logitech_receiver/settings_templates.py:208 +#: lib/logitech_receiver/settings_templates.py:218 +#: lib/logitech_receiver/settings_templates.py:225 msgid "Backlight" msgstr "Podsvícení" -#: lib/logitech_receiver/settings_templates.py:98 +#: lib/logitech_receiver/settings_templates.py:209 +#: lib/logitech_receiver/settings_templates.py:226 +msgid "Set illumination time for keyboard." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:219 msgid "Turn illumination on or off on keyboard." msgstr "Vypne/zapne podsvícení klávesnice." -#: lib/logitech_receiver/settings_templates.py:99 -msgid "Key/Button Actions" -msgstr "Akce klávesy/tlačítka" +#: lib/logitech_receiver/settings_templates.py:237 +msgid "Scroll Wheel High Resolution" +msgstr "Vysoké rozlišení posuvného kolečka" -#: lib/logitech_receiver/settings_templates.py:100 -msgid "Change the action for the key or button." -msgstr "Změní akci klávesy nebo tlačítka." +#: lib/logitech_receiver/settings_templates.py:240 +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:101 -msgid "Changing important actions (such as for the left mouse button) can " - "result in an unusable system." -msgstr "Změna důležité akce (například pro levé tlačítko myši) může vést k " - "nepoužitelnému systému." +#: lib/logitech_receiver/settings_templates.py:247 +#: lib/logitech_receiver/settings_templates.py:277 +msgid "Scroll Wheel Diversion" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:102 -msgid "Key/Button Diversion" -msgstr "Odklonění klávesy/tlačítka" +#: lib/logitech_receiver/settings_templates.py:249 +msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:103 -msgid "Make the key or button send HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." -msgstr "Klávesa nebo tlačítko budou zasílat HID++ oznámení (která spustí " - "Solaar pravidla, ale jinak budou ignorována)." +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Scroll Wheel Direction" +msgstr "Směr kolečka" -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable keys" -msgstr "Zakázat klávesy" +#: lib/logitech_receiver/settings_templates.py:257 +msgid "Invert direction for vertical scroll with wheel." +msgstr "Mění směr svislého otáčení kolečka." -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable specific keyboard keys." -msgstr "Zakáže specifické klávesy." +#: lib/logitech_receiver/settings_templates.py:265 +msgid "Scroll Wheel Resolution" +msgstr "Rozlišení posuvného kolečka" -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Change keys to match OS." -msgstr "Změní klávesy dle operačního systému." +#: lib/logitech_receiver/settings_templates.py:279 +msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Set OS" -msgstr "Nastavit OS" +#: lib/logitech_receiver/settings_templates.py:288 +msgid "Sensitivity (Pointer Speed)" +msgstr "Citlivost (rychlost kurzoru)" -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Change Host" -msgstr "Změnit hostitele" +#: lib/logitech_receiver/settings_templates.py:289 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Rychlostní násobitel myši (256 je běžný násobitel)." -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Switch connection to a different host" -msgstr "Přepnout spojení na jiného hostitele" - -#: lib/logitech_receiver/settings_templates.py:107 +#: lib/logitech_receiver/settings_templates.py:299 msgid "Thumb Wheel Diversion" msgstr "" -#: lib/logitech_receiver/settings_templates.py:108 -msgid "HID++ mode for horizontal scroll with the thumb wheel." -msgstr "HID++ režim pro vodorovný posun pomocí kolečka." +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " + "Solaar rules but are otherwise ignored)." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:109 -msgid "Effectively turns off thumb scrolling in Linux." -msgstr "Efektivně vypne rolování palcem v Linuxu." - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Invert thumb wheel scroll direction." -msgstr "Mění směr otáčení kolečka." - -#: lib/logitech_receiver/settings_templates.py:110 +#: lib/logitech_receiver/settings_templates.py:310 msgid "Thumb Wheel Direction" msgstr "Směr posuvného kolečka" -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Gestures" -msgstr "Gesta" +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Invert thumb wheel scroll direction." +msgstr "Mění směr otáčení kolečka." -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "Ladí chování myši/touchpadu." - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "Změňte číselné parametry myši/touchpadu." - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Gesture params" -msgstr "Parametry gesta" - -#: lib/logitech_receiver/settings_templates.py:113 -msgid "DPI Sliding Adjustment" -msgstr "Úprava DPI klouzání" - -#: lib/logitech_receiver/settings_templates.py:114 -msgid "Adjust the DPI by sliding the mouse horizontally while holding the " - "button down." +#: lib/logitech_receiver/settings_templates.py:319 +msgid "Onboard Profiles" msgstr "" -#: lib/logitech_receiver/settings_templates.py:115 -msgid "Mouse Gestures" +#: lib/logitech_receiver/settings_templates.py:320 +msgid "Enable onboard profiles, which often control report rate and " + "keyboard lighting" msgstr "" -#: lib/logitech_receiver/settings_templates.py:116 -msgid "Send a gesture by sliding the mouse while holding the button down." +#: lib/logitech_receiver/settings_templates.py:330 +msgid "Polling Rate (ms)" msgstr "" -#: lib/logitech_receiver/settings_templates.py:117 +#: lib/logitech_receiver/settings_templates.py:332 +msgid "Frequency of device polling, in milliseconds" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:333 +#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:365 msgid "Divert crown events" msgstr "Odklonění událostí korunky" -#: lib/logitech_receiver/settings_templates.py:118 +#: lib/logitech_receiver/settings_templates.py:366 msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "Korunka bude zasílat CROWN HID++ oznámení (která spustí Solaar " "pravidla, ale jinak budou ignorována)." -#: lib/logitech_receiver/settings_templates.py:119 +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:375 +msgid "Set crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:383 msgid "Divert G Keys" msgstr "Odklonit G klávesy" -#: lib/logitech_receiver/settings_templates.py:120 +#: lib/logitech_receiver/settings_templates.py:385 msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "G klávesy budou zasílat GKEY HID++ oznámení (která spustí Solaar " "pravidla, ale jinak budou ignorována)." -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Performs a left click." -msgstr "Provede levý klik." - -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Single tap" -msgstr "Jednoduché kliknutí" - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Performs a right click." -msgstr "Provede pravý klik." - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Single tap with two fingers" -msgstr "Jedno klepnutí dvěma prsty" - -#: lib/logitech_receiver/settings_templates.py:125 -msgid "Single tap with three fingers" -msgstr "Jedno klepnutí třemi prsty" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Double tap" -msgstr "\t" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Performs a double click." -msgstr "Provede dvouklik." - -#: lib/logitech_receiver/settings_templates.py:130 -msgid "Double tap with two fingers" -msgstr "Dvojité klepnutí dvěma prsty" - -#: lib/logitech_receiver/settings_templates.py:131 -msgid "Double tap with three fingers" -msgstr "Dvojité klepnutí třemi prsty" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Drags items by dragging the finger after double tapping." -msgstr "Táhnutí položek tažením prstu po dvojitém poklepání." - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Tap and drag" -msgstr "Táhni a pusť" - -#: lib/logitech_receiver/settings_templates.py:135 -msgid "Tap and drag with two fingers" -msgstr "Klepnutí a táhnutí dvěma prsty" - -#: lib/logitech_receiver/settings_templates.py:136 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "Táhnutí položek tažením prstů po dvojitém poklepání." - -#: lib/logitech_receiver/settings_templates.py:137 -msgid "Tap and drag with three fingers" -msgstr "Klepnutí a táhnutí třemi prsty" - -#: lib/logitech_receiver/settings_templates.py:139 -msgid "Suppress tap and edge gestures" -msgstr "Potlačit gesta klepnutí a hrany" - -#: lib/logitech_receiver/settings_templates.py:140 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "Potlačí gesta klepnutí a hrany (ekvivalent stisknutí Fn + levý klik)." - -#: lib/logitech_receiver/settings_templates.py:141 -msgid "Scroll with one finger" -msgstr "Posuv jedním prstem." - -#: lib/logitech_receiver/settings_templates.py:141 -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scrolls." -msgstr "Posuvy." - -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scroll with two fingers" -msgstr "Posuv dvěma prsty" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scroll horizontally with two fingers" -msgstr "Vodorovný posuv dvěma prsty" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scrolls horizontally." -msgstr "Vodorovný posuv." - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scroll vertically with two fingers" -msgstr "Svislý posuv dvěma prsty" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scrolls vertically." -msgstr "Svislý posuv." - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Inverts the scrolling direction." -msgstr "Mění směr otáčení." - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Natural scrolling" -msgstr "Přirozený posuv" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Enables the thumbwheel." -msgstr "Povolí kolečko." - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Thumbwheel" -msgstr "Kolečko" - -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:162 -msgid "Swipe from the top edge" -msgstr "Táhnutí z horní hrany" - -#: lib/logitech_receiver/settings_templates.py:159 -msgid "Swipe from the left edge" -msgstr "Táhnutí z levé hrany" - -#: lib/logitech_receiver/settings_templates.py:160 -msgid "Swipe from the right edge" -msgstr "Táhnutí z pravé hrany" - -#: lib/logitech_receiver/settings_templates.py:161 -msgid "Swipe from the bottom edge" -msgstr "Táhnutí ze spodní hrany" - -#: lib/logitech_receiver/settings_templates.py:163 -msgid "Swipe two fingers from the left edge" -msgstr "Táhnutí dvěma prsty z levé hrany" - -#: lib/logitech_receiver/settings_templates.py:164 -msgid "Swipe two fingers from the right edge" -msgstr "Táhnutí dvěma prsty z pravé hrany" - -#: lib/logitech_receiver/settings_templates.py:165 -msgid "Swipe two fingers from the bottom edge" -msgstr "Táhnutí dvěma prsty ze spodní hrany" - -#: lib/logitech_receiver/settings_templates.py:166 -msgid "Swipe two fingers from the top edge" -msgstr "Táhnutí dvěma prsty z horní hrany" - -#: lib/logitech_receiver/settings_templates.py:167 -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "Sevření pro přiblížení; rozevření pro oddálení." - -#: lib/logitech_receiver/settings_templates.py:167 -msgid "Zoom with two fingers." -msgstr "Přiblížení dvěma prsty." - -#: lib/logitech_receiver/settings_templates.py:168 -msgid "Pinch to zoom out." -msgstr "Sevřít pro přiblížení." - -#: lib/logitech_receiver/settings_templates.py:169 -msgid "Spread to zoom in." -msgstr "Rozevřít pro oddálení." - -#: lib/logitech_receiver/settings_templates.py:170 -msgid "Zoom with three fingers." -msgstr "Přiblížení třemi prsty." - -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Zoom with two fingers" -msgstr "Přiblížení dvěma prsty" - -#: lib/logitech_receiver/settings_templates.py:189 -msgid "Pixel zone" +#: lib/logitech_receiver/settings_templates.py:386 +msgid "May also make M keys and MR key send HID++ notifications" msgstr "" -#: lib/logitech_receiver/settings_templates.py:190 -msgid "Ratio zone" +#: lib/logitech_receiver/settings_templates.py:402 +msgid "Scroll Wheel Ratcheted" msgstr "" -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Scale factor" -msgstr "Faktor škálování" - -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Sets the cursor speed." +#: lib/logitech_receiver/settings_templates.py:403 +msgid "Switch the mouse wheel between speed-controlled ratcheting and " + "always freespin." msgstr "" -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left" -msgstr "Vlevo" +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Freespinning" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left-most coordinate." -msgstr "Souřadnice nejvíce vlevo." +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Ratcheted" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:196 -msgid "Top-most coordinate." -msgstr "Nejvyšší souřadnice." +#: lib/logitech_receiver/settings_templates.py:412 +msgid "Scroll Wheel Ratchet Speed" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:196 -msgid "top" -msgstr "nahoře" +#: lib/logitech_receiver/settings_templates.py:414 +msgid "Use the mouse wheel speed to switch between ratcheted and " + "freespinning.\n" + "The mouse wheel is always ratcheted at 50." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:197 -msgid "Width." -msgstr "Šířka." +#: lib/logitech_receiver/settings_templates.py:463 +msgid "Key/Button Actions" +msgstr "Akce klávesy/tlačítka" -#: lib/logitech_receiver/settings_templates.py:197 -msgid "width" -msgstr "šířka" +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Change the action for the key or button." +msgstr "Změní akci klávesy nebo tlačítka." -#: lib/logitech_receiver/settings_templates.py:198 -msgid "Height." -msgstr "Výška." +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Overridden by diversion." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:198 -msgid "height" -msgstr "výška" +#: lib/logitech_receiver/settings_templates.py:466 +msgid "Changing important actions (such as for the left mouse button) can " + "result in an unusable system." +msgstr "Změna důležité akce (například pro levé tlačítko myši) může vést k " + "nepoužitelnému systému." -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Cursor speed." -msgstr "Rychlost kurzoru." +#: lib/logitech_receiver/settings_templates.py:639 +msgid "Key/Button Diversion" +msgstr "Odklonění klávesy/tlačítka" -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Scale" -msgstr "Měřítko" +#: lib/logitech_receiver/settings_templates.py:640 +msgid "Make the key or button send HID++ notifications (Diverted) or " + "initiate Mouse Gestures or Sliding DPI" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:202 +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Diverted" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +msgid "Mouse Gestures" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Regular" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Sliding DPI" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:711 +msgid "Sensitivity (DPI)" +msgstr "Citlivost (DPI)" + +#: lib/logitech_receiver/settings_templates.py:752 +msgid "Sensitivity Switching" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:754 +msgid "Switch the current sensitivity and the remembered sensitivity when " + "the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current " + "sensitivity" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:758 +msgid "Off" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:791 +msgid "Disable keys" +msgstr "Zakázat klávesy" + +#: lib/logitech_receiver/settings_templates.py:792 +msgid "Disable specific keyboard keys." +msgstr "Zakáže specifické klávesy." + +#: lib/logitech_receiver/settings_templates.py:795 #, python-format msgid "Disables the %s key." msgstr "Zakáže %s klávesu." -#: lib/logitech_receiver/settings_templates.py:521 -#: lib/logitech_receiver/settings_templates.py:573 -msgid "Off" +#: lib/logitech_receiver/settings_templates.py:809 +#: lib/logitech_receiver/settings_templates.py:860 +msgid "Set OS" +msgstr "Nastavit OS" + +#: lib/logitech_receiver/settings_templates.py:810 +#: lib/logitech_receiver/settings_templates.py:861 +msgid "Change keys to match OS." +msgstr "Změní klávesy dle operačního systému." + +#: lib/logitech_receiver/settings_templates.py:873 +msgid "Change Host" +msgstr "Změnit hostitele" + +#: lib/logitech_receiver/settings_templates.py:874 +msgid "Switch connection to a different host" +msgstr "Přepnout spojení na jiného hostitele" + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Performs a left click." +msgstr "Provede levý klik." + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Single tap" +msgstr "Jednoduché kliknutí" + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Performs a right click." +msgstr "Provede pravý klik." + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Single tap with two fingers" +msgstr "Jedno klepnutí dvěma prsty" + +#: lib/logitech_receiver/settings_templates.py:902 +msgid "Single tap with three fingers" +msgstr "Jedno klepnutí třemi prsty" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Double tap" +msgstr "\t" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Performs a double click." +msgstr "Provede dvouklik." + +#: lib/logitech_receiver/settings_templates.py:907 +msgid "Double tap with two fingers" +msgstr "Dvojité klepnutí dvěma prsty" + +#: lib/logitech_receiver/settings_templates.py:908 +msgid "Double tap with three fingers" +msgstr "Dvojité klepnutí třemi prsty" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Táhnutí položek tažením prstu po dvojitém poklepání." + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Tap and drag" +msgstr "Táhni a pusť" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Táhnutí položek tažením prstů po dvojitém poklepání." + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Tap and drag with two fingers" +msgstr "Klepnutí a táhnutí dvěma prsty" + +#: lib/logitech_receiver/settings_templates.py:914 +msgid "Tap and drag with three fingers" +msgstr "Klepnutí a táhnutí třemi prsty" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "Potlačí gesta klepnutí a hrany (ekvivalent stisknutí Fn + levý klik)." + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Suppress tap and edge gestures" +msgstr "Potlačit gesta klepnutí a hrany" + +#: lib/logitech_receiver/settings_templates.py:918 +msgid "Scroll with one finger" +msgstr "Posuv jedním prstem." + +#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scrolls." +msgstr "Posuvy." + +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scroll with two fingers" +msgstr "Posuv dvěma prsty" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scroll horizontally with two fingers" +msgstr "Vodorovný posuv dvěma prsty" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scrolls horizontally." +msgstr "Vodorovný posuv." + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scroll vertically with two fingers" +msgstr "Svislý posuv dvěma prsty" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scrolls vertically." +msgstr "Svislý posuv." + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Inverts the scrolling direction." +msgstr "Mění směr otáčení." + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Natural scrolling" +msgstr "Přirozený posuv" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Enables the thumbwheel." +msgstr "Povolí kolečko." + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Thumbwheel" +msgstr "Kolečko" + +#: lib/logitech_receiver/settings_templates.py:935 +#: lib/logitech_receiver/settings_templates.py:939 +msgid "Swipe from the top edge" +msgstr "Táhnutí z horní hrany" + +#: lib/logitech_receiver/settings_templates.py:936 +msgid "Swipe from the left edge" +msgstr "Táhnutí z levé hrany" + +#: lib/logitech_receiver/settings_templates.py:937 +msgid "Swipe from the right edge" +msgstr "Táhnutí z pravé hrany" + +#: lib/logitech_receiver/settings_templates.py:938 +msgid "Swipe from the bottom edge" +msgstr "Táhnutí ze spodní hrany" + +#: lib/logitech_receiver/settings_templates.py:940 +msgid "Swipe two fingers from the left edge" +msgstr "Táhnutí dvěma prsty z levé hrany" + +#: lib/logitech_receiver/settings_templates.py:941 +msgid "Swipe two fingers from the right edge" +msgstr "Táhnutí dvěma prsty z pravé hrany" + +#: lib/logitech_receiver/settings_templates.py:942 +msgid "Swipe two fingers from the bottom edge" +msgstr "Táhnutí dvěma prsty ze spodní hrany" + +#: lib/logitech_receiver/settings_templates.py:943 +msgid "Swipe two fingers from the top edge" +msgstr "Táhnutí dvěma prsty z horní hrany" + +#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "Sevření pro přiblížení; rozevření pro oddálení." + +#: lib/logitech_receiver/settings_templates.py:944 +msgid "Zoom with two fingers." +msgstr "Přiblížení dvěma prsty." + +#: lib/logitech_receiver/settings_templates.py:945 +msgid "Pinch to zoom out." +msgstr "Sevřít pro přiblížení." + +#: lib/logitech_receiver/settings_templates.py:946 +msgid "Spread to zoom in." +msgstr "Rozevřít pro oddálení." + +#: lib/logitech_receiver/settings_templates.py:947 +msgid "Zoom with three fingers." +msgstr "Přiblížení třemi prsty." + +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Zoom with two fingers" +msgstr "Přiblížení dvěma prsty" + +#: lib/logitech_receiver/settings_templates.py:966 +msgid "Pixel zone" msgstr "" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Diverted" +#: lib/logitech_receiver/settings_templates.py:967 +msgid "Ratio zone" msgstr "" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Regular" +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Scale factor" +msgstr "Faktor škálování" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Sets the cursor speed." msgstr "" -#: lib/logitech_receiver/status.py:109 +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left" +msgstr "Vlevo" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left-most coordinate." +msgstr "Souřadnice nejvíce vlevo." + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width" +msgstr "Šířka" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width." +msgstr "Šířka." + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height" +msgstr "Výška" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height." +msgstr "Výška." + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Cursor speed." +msgstr "Rychlost kurzoru." + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Scale" +msgstr "Měřítko" + +#: lib/logitech_receiver/settings_templates.py:982 +msgid "Gestures" +msgstr "Gesta" + +#: lib/logitech_receiver/settings_templates.py:983 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Ladí chování myši/touchpadu." + +#: lib/logitech_receiver/settings_templates.py:1000 +msgid "Gestures Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1001 +msgid "Divert mouse/touchpad gestures." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1018 +msgid "Gesture params" +msgstr "Parametry gesta" + +#: lib/logitech_receiver/settings_templates.py:1019 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Změňte číselné parametry myši/touchpadu." + +#: lib/logitech_receiver/settings_templates.py:1044 +msgid "M-Key LEDs" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Control the M-Key LEDs." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1077 +msgid "May need G Keys diverted to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1053 +#, python-format +msgid "Lights up the %s key." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1074 +msgid "MR-Key LED" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "Control the MR-Key LED." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1095 +msgid "Persistent Key/Button Mapping" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1097 +msgid "Permanently change the mapping for the key or button." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1098 +msgid "Changing important keys or buttons (such as for the left mouse " + "button) can result in an unusable system." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1157 +msgid "Sidetone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1158 +msgid "Set sidetone level." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1167 +msgid "Equalizer" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1168 +msgid "Set equalizer levels." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1191 +msgid "Hz" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1197 +msgid "Power Management" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1198 +msgid "Power off in minutes (0 for never)." +msgstr "" + +#: lib/logitech_receiver/status.py:114 msgid "No paired devices." msgstr "Nepřipojené zařízení." -#: lib/logitech_receiver/status.py:110 lib/solaar/ui/window.py:623 +#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 #, python-format msgid "%(count)s paired device." msgid_plural "%(count)s paired devices." @@ -757,27 +912,27 @@ msgstr[0] "%(count)s připojené zařízení." msgstr[1] "%(count)s připojené zařízení." msgstr[2] "%(count)s připojených zařízení." -#: lib/logitech_receiver/status.py:165 +#: lib/logitech_receiver/status.py:170 #, python-format msgid "Battery: %(level)s" msgstr "Baterie: %(level)s" -#: lib/logitech_receiver/status.py:167 +#: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "Baterie: %(percent)d%%" -#: lib/logitech_receiver/status.py:179 +#: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "Osvětlení: %(level)s lux" -#: lib/logitech_receiver/status.py:234 +#: lib/logitech_receiver/status.py:239 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "Baterie: %(level)s (%(status)s)" -#: lib/logitech_receiver/status.py:236 +#: lib/logitech_receiver/status.py:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Baterie: %(percent)d%% (%(status)s)" @@ -788,15 +943,14 @@ msgstr "Chyba oprávnění" #: lib/solaar/ui/__init__.py:54 #, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open " - "it." -msgstr "Nalezen přijímač Logitech (%s), ale chybí práva pro jeho otevření." +msgid "Found a Logitech receiver or device (%s), but did not have " + "permission to open it." +msgstr "" #: lib/solaar/ui/__init__.py:55 -msgid "If you've just installed Solaar, try removing the receiver and " - "plugging it back in." -msgstr "Pokud jste Solaar právě nainstalovali, zkuste přijímač odpojit a " - "znova připojit." +msgid "If you've just installed Solaar, try disconnecting the receiver or " + "device and then reconnecting it." +msgstr "" #: lib/solaar/ui/__init__.py:58 msgid "Cannot connect to device error" @@ -809,8 +963,8 @@ msgid "Found a Logitech receiver or device at %s, but encountered an error " msgstr "" #: lib/solaar/ui/__init__.py:61 -msgid "Try removing the device and plugging it back in or turning it off " - "and then on." +msgid "Try disconnecting the device and then reconnecting it or turning it " + "off and then on." msgstr "" #: lib/solaar/ui/__init__.py:64 @@ -826,61 +980,49 @@ msgstr "Selhalo odpojení %{device} od %{receiver}." msgid "The receiver returned an error, with no further details." msgstr "Přijímač vrátil chybu bez dalších detailů." -#: lib/solaar/ui/about.py:39 +#: lib/solaar/ui/__init__.py:177 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" + +#: lib/solaar/ui/about.py:36 msgid "Manages Logitech receivers,\n" "keyboards, mice, and tablets." msgstr "" -#: lib/solaar/ui/about.py:47 +#: lib/solaar/ui/about.py:44 msgid "Additional Programming" msgstr "" -#: lib/solaar/ui/about.py:48 +#: lib/solaar/ui/about.py:45 msgid "GUI design" msgstr "Vzhled aplikace" -#: lib/solaar/ui/about.py:50 +#: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "Testování" -#: lib/solaar/ui/about.py:57 +#: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Dokumentace Logitechu" -#: lib/solaar/ui/action.py:70 lib/solaar/ui/window.py:328 -msgid "About %s" -msgstr "O aplikaci %s" - -#: lib/solaar/ui/action.py:96 lib/solaar/ui/action.py:100 -#: lib/solaar/ui/window.py:205 +#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 +#: lib/solaar/ui/window.py:197 msgid "Unpair" msgstr "Odpojit" -#: lib/solaar/ui/action.py:99 lib/solaar/ui/diversion_rules.py:144 +#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "" -#: lib/solaar/ui/config_panel.py:358 -msgid "Changes allowed" +#: lib/solaar/ui/config_panel.py:212 +msgid "Complete - ENTER to change" msgstr "" -#: lib/solaar/ui/config_panel.py:359 -msgid "No changes allowed" +#: lib/solaar/ui/config_panel.py:212 +msgid "Incomplete" msgstr "" -#: lib/solaar/ui/config_panel.py:360 -msgid "Ignore this setting" -msgstr "" - -#: lib/solaar/ui/config_panel.py:401 -msgid "Working" -msgstr "Pracuji" - -#: lib/solaar/ui/config_panel.py:404 -msgid "Read/write operation failed." -msgstr "Operace čtení/zápisu selhala." - -#: lib/solaar/ui/config_panel.py:522 +#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 #, python-format msgid "%d value" msgid_plural "%d values" @@ -888,292 +1030,577 @@ msgstr[0] "%d hodnota" msgstr[1] "%d hodnoty" msgstr[2] "%d hodnot" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/config_panel.py:518 +msgid "Changes allowed" +msgstr "" + +#: lib/solaar/ui/config_panel.py:519 +msgid "No changes allowed" +msgstr "" + +#: lib/solaar/ui/config_panel.py:520 +msgid "Ignore this setting" +msgstr "" + +#: lib/solaar/ui/config_panel.py:565 +msgid "Working" +msgstr "Pracuji" + +#: lib/solaar/ui/config_panel.py:568 +msgid "Read/write operation failed." +msgstr "Operace čtení/zápisu selhala." + +#: lib/solaar/ui/diversion_rules.py:65 msgid "Built-in rules" msgstr "Zabudovaná pravidla" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" msgstr "Uživatelem definované pravidla" -#: lib/solaar/ui/diversion_rules.py:59 lib/solaar/ui/diversion_rules.py:792 +#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "Pravidlo" -#: lib/solaar/ui/diversion_rules.py:60 lib/solaar/ui/diversion_rules.py:503 -#: lib/solaar/ui/diversion_rules.py:622 +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 +#: lib/solaar/ui/diversion_rules.py:636 msgid "Sub-rule" msgstr "Podpravidlo" -#: lib/solaar/ui/diversion_rules.py:62 +#: lib/solaar/ui/diversion_rules.py:70 msgid "[empty]" msgstr "[prázdné]" -#: lib/solaar/ui/diversion_rules.py:85 +#: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "Editor pravidel Solaar" -#: lib/solaar/ui/diversion_rules.py:135 +#: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "Nastavit změnu jako trvalou?" -#: lib/solaar/ui/diversion_rules.py:140 +#: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Ano" -#: lib/solaar/ui/diversion_rules.py:142 +#: lib/solaar/ui/diversion_rules.py:148 msgid "No" msgstr "" -#: lib/solaar/ui/diversion_rules.py:147 +#: lib/solaar/ui/diversion_rules.py:153 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "Pokud vyberete Ne, budou změny po ukončení aplikace ztraceny." -#: lib/solaar/ui/diversion_rules.py:195 +#: lib/solaar/ui/diversion_rules.py:201 msgid "Save changes" msgstr "" -#: lib/solaar/ui/diversion_rules.py:200 +#: lib/solaar/ui/diversion_rules.py:206 msgid "Discard changes" msgstr "" -#: lib/solaar/ui/diversion_rules.py:366 +#: lib/solaar/ui/diversion_rules.py:372 msgid "Insert here" msgstr "Vložit tady" -#: lib/solaar/ui/diversion_rules.py:368 +#: lib/solaar/ui/diversion_rules.py:374 msgid "Insert above" msgstr "Vložit nad" -#: lib/solaar/ui/diversion_rules.py:370 +#: lib/solaar/ui/diversion_rules.py:376 msgid "Insert below" msgstr "Vložit pod" -#: lib/solaar/ui/diversion_rules.py:376 +#: lib/solaar/ui/diversion_rules.py:382 msgid "Insert new rule here" msgstr "Vložit nové pravidlo tady" -#: lib/solaar/ui/diversion_rules.py:378 +#: lib/solaar/ui/diversion_rules.py:384 msgid "Insert new rule above" msgstr "Vložit nové pravidlo nad" -#: lib/solaar/ui/diversion_rules.py:380 +#: lib/solaar/ui/diversion_rules.py:386 msgid "Insert new rule below" msgstr "Vložit nové pravidlo pod" -#: lib/solaar/ui/diversion_rules.py:421 +#: lib/solaar/ui/diversion_rules.py:427 msgid "Paste here" msgstr "Vložit tady" -#: lib/solaar/ui/diversion_rules.py:423 +#: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "Vložit nad" -#: lib/solaar/ui/diversion_rules.py:425 +#: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "Vložit pod" -#: lib/solaar/ui/diversion_rules.py:431 +#: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "Vložit pravidlo tady" -#: lib/solaar/ui/diversion_rules.py:433 +#: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "Vložit pravidlo nad" -#: lib/solaar/ui/diversion_rules.py:435 +#: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "Vložit pravidlo pod" -#: lib/solaar/ui/diversion_rules.py:439 +#: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "Vložit pravidlo" -#: lib/solaar/ui/diversion_rules.py:468 +#: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "Srovnání" -#: lib/solaar/ui/diversion_rules.py:501 +#: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "Vložit" -#: lib/solaar/ui/diversion_rules.py:504 lib/solaar/ui/diversion_rules.py:624 -#: lib/solaar/ui/diversion_rules.py:835 +#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 +#: lib/solaar/ui/diversion_rules.py:1126 msgid "Or" msgstr "Nebo" -#: lib/solaar/ui/diversion_rules.py:505 lib/solaar/ui/diversion_rules.py:623 -#: lib/solaar/ui/diversion_rules.py:820 +#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 +#: lib/solaar/ui/diversion_rules.py:1111 msgid "And" msgstr "A" -#: lib/solaar/ui/diversion_rules.py:507 +#: lib/solaar/ui/diversion_rules.py:513 msgid "Condition" msgstr "podmínka" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:951 +#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "Vlastnost" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:874 -msgid "Process" -msgstr "Proces" - -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:902 -msgid "MouseProcess" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:512 lib/solaar/ui/diversion_rules.py:984 +#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "Report" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1019 +#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 +msgid "Process" +msgstr "Proces" + +#: lib/solaar/ui/diversion_rules.py:518 +msgid "Mouse process" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 msgid "Modifiers" msgstr "Modifikátory" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1066 +#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "Klávesa" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1111 +#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 +msgid "KeyIsDown" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 +msgid "Active" +msgstr "Aktivní" + +#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2218 +#: lib/solaar/ui/diversion_rules.py:2270 lib/solaar/ui/diversion_rules.py:2323 +msgid "Device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +msgid "Host" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 +msgid "Setting" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 +#: lib/solaar/ui/diversion_rules.py:1526 msgid "Test" msgstr "Test" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1196 +#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 +msgid "Test bytes" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 msgid "Mouse Gesture" msgstr "" -#: lib/solaar/ui/diversion_rules.py:520 +#: lib/solaar/ui/diversion_rules.py:532 msgid "Action" msgstr "Akce" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1286 +#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 msgid "Key press" msgstr "Stisknutí klávesy" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1335 +#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "Otočení kolečkem" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:1383 +#: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 msgid "Mouse click" msgstr "Kliknutí myši" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1452 +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Set" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 msgid "Execute" msgstr "Vykonat" -#: lib/solaar/ui/diversion_rules.py:554 +#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 +msgid "Later" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:568 msgid "Insert new rule" msgstr "Vložit nové pravidlo" -#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/diversion_rules.py:1146 -#: lib/solaar/ui/diversion_rules.py:1236 lib/solaar/ui/diversion_rules.py:1411 +#: lib/solaar/ui/diversion_rules.py:588 lib/solaar/ui/diversion_rules.py:1686 +#: lib/solaar/ui/diversion_rules.py:1790 lib/solaar/ui/diversion_rules.py:1989 msgid "Delete" msgstr "Smazat" -#: lib/solaar/ui/diversion_rules.py:596 +#: lib/solaar/ui/diversion_rules.py:610 msgid "Negate" msgstr "Negovat" -#: lib/solaar/ui/diversion_rules.py:620 +#: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "Obalit s" -#: lib/solaar/ui/diversion_rules.py:642 +#: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Vyjmout" -#: lib/solaar/ui/diversion_rules.py:657 +#: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Vložit" -#: lib/solaar/ui/diversion_rules.py:669 +#: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Kopírovat" -#: lib/solaar/ui/diversion_rules.py:772 +#: lib/solaar/ui/diversion_rules.py:1063 msgid "This editor does not support the selected rule component yet." msgstr "Vybraná komponenta pravidla není editorem zatím podporována." -#: lib/solaar/ui/diversion_rules.py:850 +#: lib/solaar/ui/diversion_rules.py:1138 +msgid "Number of seconds to delay." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1177 msgid "Not" msgstr "Opak" -#: lib/solaar/ui/diversion_rules.py:1039 +#: lib/solaar/ui/diversion_rules.py:1187 +msgid "X11 active process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1218 +msgid "X11 mouse process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1235 +msgid "MouseProcess" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1260 +msgid "Feature name of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1308 +msgid "Report number of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1342 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1383 +msgid "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1392 msgid "Key down" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1042 +#: lib/solaar/ui/diversion_rules.py:1395 msgid "Key up" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1131 -msgid "Add action" +#: lib/solaar/ui/diversion_rules.py:1436 +msgid "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." msgstr "" -#: lib/solaar/ui/diversion_rules.py:1224 +#: lib/solaar/ui/diversion_rules.py:1475 +msgid "Test condition on notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1479 +msgid "Parameter" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1542 +msgid "begin (inclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1543 +msgid "end (exclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1552 +msgid "range" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1554 +msgid "minimum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1555 +msgid "maximum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1557 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1562 +msgid "mask" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1563 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1573 +msgid "Bit or range test on bytes in notification message triggering rule " + "processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1583 +msgid "type" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1666 +msgid "Mouse gesture with optional initiating button followed by zero or " + "more mouse movements." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1671 +msgid "Add movement" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1764 +msgid "Simulate a chorded key click or depress or release.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1769 msgid "Add key" msgstr "Přidat klávesu" -#: lib/solaar/ui/diversion_rules.py:1354 +#: lib/solaar/ui/diversion_rules.py:1772 +msgid "Click" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1775 +msgid "Depress" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1778 +msgid "Release" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1861 +msgid "Simulate a mouse scroll.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1918 +msgid "Simulate a mouse click.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1921 msgid "Button" msgstr "Tlačítko" -#: lib/solaar/ui/diversion_rules.py:1355 -msgid "Count" -msgstr "Počet" +#: lib/solaar/ui/diversion_rules.py:1922 +msgid "Count and Action" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1397 +#: lib/solaar/ui/diversion_rules.py:1972 +msgid "Execute a command with arguments." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1975 msgid "Add argument" msgstr "Přidat argument" -#: lib/solaar/ui/notify.py:125 lib/solaar/ui/tray.py:319 -#: lib/solaar/ui/tray.py:324 lib/solaar/ui/window.py:749 +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "Toggle" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "True" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2051 +msgid "False" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2065 +msgid "Unsupported setting" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2242 +#: lib/solaar/ui/diversion_rules.py:2328 lib/solaar/ui/diversion_rules.py:2570 +#: lib/solaar/ui/diversion_rules.py:2588 +msgid "Originating device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2256 +msgid "Device is active and its settings can be changed." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2266 +msgid "Device that originated the current notification." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2280 +msgid "Name of host computer." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2347 +msgid "Value" +msgstr "Hodnota" + +#: lib/solaar/ui/diversion_rules.py:2355 +msgid "Item" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2630 +msgid "Change setting on device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2647 +msgid "Setting on device" +msgstr "" + +#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:318 +#: lib/solaar/ui/tray.py:323 lib/solaar/ui/window.py:739 msgid "offline" msgstr "vypnuto" -#: lib/solaar/ui/pair_window.py:134 +#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 +#: lib/solaar/ui/pair_window.py:288 +#, python-format +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: nově připojení zařízení" + +#: lib/solaar/ui/pair_window.py:123 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "" + +#: lib/solaar/ui/pair_window.py:126 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "left" +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "right" +msgstr "" + +#: lib/solaar/ui/pair_window.py:131 +#, python-format +msgid "Press %(code)s\n" + "and then press left and right buttons simultaneously." +msgstr "" + +#: lib/solaar/ui/pair_window.py:188 msgid "Pairing failed" msgstr "Připojení selhalo." -#: lib/solaar/ui/pair_window.py:136 +#: lib/solaar/ui/pair_window.py:190 msgid "Make sure your device is within range, and has a decent battery " "charge." msgstr "Ujistěte se, že je zařízení v dosahu je dostatečně nabito." -#: lib/solaar/ui/pair_window.py:138 +#: lib/solaar/ui/pair_window.py:192 msgid "A new device was detected, but it is not compatible with this " "receiver." msgstr "Nové zařízení bylo detekováno, ale není kompatibilní s tímto " "přijímačem." -#: lib/solaar/ui/pair_window.py:140 +#: lib/solaar/ui/pair_window.py:194 msgid "More paired devices than receiver can support." msgstr "Více připojených zařízení než přijímač dokáže zvládnout." -#: lib/solaar/ui/pair_window.py:142 +#: lib/solaar/ui/pair_window.py:196 msgid "No further details are available about the error." msgstr "Další detaily o chybě nejsou dostupné." -#: lib/solaar/ui/pair_window.py:156 +#: lib/solaar/ui/pair_window.py:210 msgid "Found a new device:" msgstr "Nalezeno nové zařízení:" -#: lib/solaar/ui/pair_window.py:181 +#: lib/solaar/ui/pair_window.py:235 msgid "The wireless link is not encrypted" msgstr "Bezdrátové spojení není šifrováno" -#: lib/solaar/ui/pair_window.py:199 -#, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s: nově připojení zařízení" +#: lib/solaar/ui/pair_window.py:264 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "" -#: lib/solaar/ui/pair_window.py:206 +#: lib/solaar/ui/pair_window.py:266 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:268 +msgid "Other receivers are only compatible with a few devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:270 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" + +#: lib/solaar/ui/pair_window.py:274 +msgid "Press a pairing button or key until the pairing light flashes " + "quickly." +msgstr "" + +#: lib/solaar/ui/pair_window.py:276 +msgid "You may have to first turn the device off and on again." +msgstr "" + +#: lib/solaar/ui/pair_window.py:278 +msgid "Turn on the device you want to pair." +msgstr "Zapněte zařízení které chcete připojit." + +#: lib/solaar/ui/pair_window.py:280 msgid "If the device is already turned on, turn it off and on again." msgstr "" -#: lib/solaar/ui/pair_window.py:209 +#: lib/solaar/ui/pair_window.py:283 #, python-format msgid "\n" "\n" @@ -1191,123 +1618,125 @@ msgstr[2] "\n" "\n" "K přijímači lze připojit ještě %d zařízení." -#: lib/solaar/ui/pair_window.py:212 +#: lib/solaar/ui/pair_window.py:286 msgid "\n" "Cancelling at this point will not use up a pairing." msgstr "\n" "Zrušení v tomto okamžiku nevyužije připojení." -#: lib/solaar/ui/pair_window.py:215 -msgid "Turn on the device you want to pair." -msgstr "Zapněte zařízení které chcete připojit." +#: lib/solaar/ui/tray.py:58 +msgid "No supported device found" +msgstr "" -#: lib/solaar/ui/tray.py:61 -msgid "No Logitech receiver found" -msgstr "Logitech přijímač nebyl nalezen" +#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#, python-format +msgid "About %s" +msgstr "O aplikaci %s" -#: lib/solaar/ui/tray.py:68 lib/solaar/ui/window.py:325 +#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 +#, python-format msgid "Quit %s" msgstr "Ukončit %s" -#: lib/solaar/ui/tray.py:298 lib/solaar/ui/tray.py:306 +#: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "bez přijímače" -#: lib/solaar/ui/tray.py:322 +#: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "bez statusu" -#: lib/solaar/ui/window.py:104 +#: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "Skenování" -#: lib/solaar/ui/window.py:137 lib/solaar/ui/window.py:692 +#: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Baterie" -#: lib/solaar/ui/window.py:140 +#: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Bezdrátové propojení" -#: lib/solaar/ui/window.py:144 +#: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Osvětlení" -#: lib/solaar/ui/window.py:178 +#: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Ukázat technické detaily" -#: lib/solaar/ui/window.py:194 +#: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Připojit nové zařízení" -#: lib/solaar/ui/window.py:213 +#: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Vyberte zařízení" -#: lib/solaar/ui/window.py:331 +#: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "Editor pravidel" -#: lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Cesta" -#: lib/solaar/ui/window.py:544 +#: lib/solaar/ui/window.py:536 msgid "USB ID" msgstr "USB ID" -#: lib/solaar/ui/window.py:547 lib/solaar/ui/window.py:549 -#: lib/solaar/ui/window.py:569 lib/solaar/ui/window.py:571 +#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 msgid "Serial" msgstr "Sériové číslo" -#: lib/solaar/ui/window.py:553 +#: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Index" -#: lib/solaar/ui/window.py:555 +#: lib/solaar/ui/window.py:547 msgid "Wireless PID" msgstr "Bezdrát. PID" -#: lib/solaar/ui/window.py:557 +#: lib/solaar/ui/window.py:549 msgid "Product ID" msgstr "ID výrobku" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:551 msgid "Protocol" msgstr "Protokol" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:551 msgid "Unknown" msgstr "Neznámý" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:554 #, python-format msgid "%(rate)d ms (%(rate_hz)dHz)" msgstr "%(rate)d ms (%(rate_hz)dHz)" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:554 msgid "Polling rate" msgstr "Četn. dotazů" -#: lib/solaar/ui/window.py:573 +#: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "Číslo jednotky" -#: lib/solaar/ui/window.py:584 +#: lib/solaar/ui/window.py:576 msgid "none" msgstr "žádný" -#: lib/solaar/ui/window.py:585 +#: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Oznámení" -#: lib/solaar/ui/window.py:622 +#: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "Žádné připojené zařízení." -#: lib/solaar/ui/window.py:629 +#: lib/solaar/ui/window.py:628 #, python-format msgid "Up to %(max_count)s device can be paired to this receiver." msgid_plural "Up to %(max_count)s devices can be paired to this receiver." @@ -1315,11 +1744,11 @@ msgstr[0] "K přijímači může být připojeno %(max_count)s zařízení msgstr[1] "K přijímači můžou být připojena až %(max_count)s zařízení." msgstr[2] "K přijímači může být připojeno až %(max_count)s zařízení." -#: lib/solaar/ui/window.py:635 +#: lib/solaar/ui/window.py:634 msgid "Only one device can be paired to this receiver." msgstr "K tomuto přijímači je možné připojit jen jedno zařízení." -#: lib/solaar/ui/window.py:639 +#: lib/solaar/ui/window.py:638 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." @@ -1327,72 +1756,54 @@ msgstr[0] "K přijímači je možné připojit ještě %d zařízení." msgstr[1] "K přijímači je možné připojit ještě %d zařízení." msgstr[2] "K přijímači je možné připojit ještě %d zařízení." -#: lib/solaar/ui/window.py:694 -msgid "Battery information unknown." -msgstr "Informace o baterii nejsou známy." - -#: lib/solaar/ui/window.py:702 +#: lib/solaar/ui/window.py:692 msgid "Battery Voltage" msgstr "Napětí baterie" -#: lib/solaar/ui/window.py:704 +#: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "Napětí hlášené baterií" -#: lib/solaar/ui/window.py:706 lib/solaar/ui/window.py:710 +#: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "Úroveň baterie" -#: lib/solaar/ui/window.py:708 lib/solaar/ui/window.py:712 +#: lib/solaar/ui/window.py:698 msgid "Approximate level reported by battery" msgstr "Přibližná úroveň hlášená baterií" -#: lib/solaar/ui/window.py:715 lib/solaar/ui/window.py:717 +#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 msgid "next reported " msgstr "další hlášené " -#: lib/solaar/ui/window.py:718 +#: lib/solaar/ui/window.py:708 msgid " and next level to be reported." msgstr " a další úroveň pro hlášení." -#: lib/solaar/ui/window.py:723 +#: lib/solaar/ui/window.py:713 msgid "last known" msgstr "poslední známý" -#: lib/solaar/ui/window.py:731 -msgid "not encrypted" -msgstr "nešifrováno" - -#: lib/solaar/ui/window.py:735 -msgid "The wireless link between this device and its receiver is not " - "encrypted.\n" - "\n" - "For pointing devices (mice, trackballs, trackpads), this is a minor " - "security issue.\n" - "\n" - "It is, however, a major security issue for text-input devices " - "(keyboards, numpads),\n" - "because typed text can be sniffed inconspicuously by 3rd parties " - "within range." -msgstr "Bezdrátové spojení mezi zařízením a přijímačem není šifrováno.\n" - "\n" - "U ukazovacích zařízení (myši, trackbally, trackpady) je to jen " - "drobný bezpečnostní problém.\n" - "\n" - "Ovšem pro textové vstupní zařízení (klávesnice, numerické " - "klávesnice) se jedná\n" - "o významný bezpečnostní problém, protože psaný text může být\n" - "nepozorovaně odposlechnut třetí stranou, pokud je v dosahu." - -#: lib/solaar/ui/window.py:744 +#: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "šifrováno" -#: lib/solaar/ui/window.py:746 +#: lib/solaar/ui/window.py:726 msgid "The wireless link between this device and its receiver is encrypted." msgstr "Bezdrátové spojení mezi zařízením a přijímačem je šifrováno." -#: lib/solaar/ui/window.py:759 +#: lib/solaar/ui/window.py:728 +msgid "not encrypted" +msgstr "nešifrováno" + +#: lib/solaar/ui/window.py:732 +msgid "The wireless link between this device and its receiver is not " + "encrypted.\n" + "This is a security issue for pointing devices, and a major security " + "issue for text-input devices." +msgstr "" + +#: lib/solaar/ui/window.py:748 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d lux" @@ -1402,23 +1813,73 @@ msgstr "%(light_level)d lux" #~ msgstr "Přizpůsobení DPI vodorovného posunu myši při současném " #~ "držením stisku DPI tlačítka." +#~ msgid "Automatically switch the mouse wheel between ratchet and " +#~ "freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always ratcheted at 50" +#~ msgstr "Automatické přepíná režim kolečka myši mezi krokováním a " +#~ "volným otáčením.\n" +#~ "Kolečko je při hodnotě 0 vždy volné a při hodnotě 50 vždy krokuje." + +#~ msgid "Battery information unknown." +#~ msgstr "Informace o baterii nejsou známy." + #~ msgid "Click to allow changes." #~ msgstr "Klikněte pro povolení změn." #~ msgid "Click to prevent changes." #~ msgstr "Klikněte pro zakázání změn." +#~ msgid "Count" +#~ msgstr "Počet" + +#~ msgid "DPI Sliding Adjustment" +#~ msgstr "Úprava DPI klouzání" + +#~ msgid "Effectively turns off thumb scrolling in Linux." +#~ msgstr "Efektivně vypne rolování palcem v Linuxu." + +#~ msgid "Effectively turns off wheel scrolling in Linux." +#~ msgstr "Efektivně vypne posuv kolečkem v Linuxu." + #~ msgid "For more information see the Solaar installation directions\n" #~ "at https://pwr-solaar.github.io/Solaar/installation" #~ msgstr "Další instalační informace pro Solaar naleznete na \n" #~ "https://pwr-solaar.github.io/Solaar/installation" +#, python-format +#~ msgid "Found a Logitech Receiver (%s), but did not have permission " +#~ "to open it." +#~ msgstr "Nalezen přijímač Logitech (%s), ale chybí práva pro jeho " +#~ "otevření." + +#~ msgid "HID++ mode for horizontal scroll with the thumb wheel." +#~ msgstr "HID++ režim pro vodorovný posun pomocí kolečka." + +#~ msgid "HID++ mode for vertical scroll with the wheel." +#~ msgstr "HID++ režim pro svislý posuv kolečkem." + #~ msgid "If the device is already turned on, turn if off and on again." #~ msgstr "Pokud je zařízení zapnuto, vypněte jej a znovu zapněte" +#~ msgid "If you've just installed Solaar, try removing the receiver " +#~ "and plugging it back in." +#~ msgstr "Pokud jste Solaar právě nainstalovali, zkuste přijímač " +#~ "odpojit a znova připojit." + +#~ msgid "Make the key or button send HID++ notifications (which " +#~ "trigger Solaar rules but are otherwise ignored)." +#~ msgstr "Klávesa nebo tlačítko budou zasílat HID++ oznámení (která " +#~ "spustí Solaar pravidla, ale jinak budou ignorována)." + +#~ msgid "No Logitech receiver found" +#~ msgstr "Logitech přijímač nebyl nalezen" + #~ msgid "Scroll Wheel HID++ Scrolling" #~ msgstr "HID++ posuv kolečka" +#~ msgid "Scroll Wheel Rachet" +#~ msgstr "Krokování kolečka" + #~ msgid "Shows status of devices connected\n" #~ "through wireless Logitech receivers." #~ msgstr "Zobrazí stav zařízení připojených\n" @@ -1427,5 +1888,41 @@ msgstr "%(light_level)d lux" #~ msgid "Solaar depends on a udev file that is not present" #~ msgstr "Solaar závisí na udev souboru, který chybí" +#~ msgid "The wireless link between this device and its receiver is " +#~ "not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices " +#~ "(keyboards, numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties " +#~ "within range." +#~ msgstr "Bezdrátové spojení mezi zařízením a přijímačem není " +#~ "šifrováno.\n" +#~ "\n" +#~ "U ukazovacích zařízení (myši, trackbally, trackpady) je to jen " +#~ "drobný bezpečnostní problém.\n" +#~ "\n" +#~ "Ovšem pro textové vstupní zařízení (klávesnice, numerické " +#~ "klávesnice) se jedná\n" +#~ "o významný bezpečnostní problém, protože psaný text může být\n" +#~ "nepozorovaně odposlechnut třetí stranou, pokud je v dosahu." + #~ msgid "Thumb Wheel HID++ Scrolling" #~ msgstr "HID++ posun kolečkem" + +#~ msgid "Top-most coordinate." +#~ msgstr "Nejvyšší souřadnice." + +#~ msgid "height" +#~ msgstr "výška" + +#~ msgid "top" +#~ msgstr "nahoře" + +#~ msgid "unknown" +#~ msgstr "neznámý" + +#~ msgid "width" +#~ msgstr "šířka" diff --git a/po/da.po b/po/da.po index f2b6ffec..74bfda21 100644 --- a/po/da.po +++ b/po/da.po @@ -6,7 +6,7 @@ msgid "" msgstr "Project-Id-Version: solaar 1.0.3\n" "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2021-09-25 13:56-0400\n" + "POT-Creation-Date: 2023-12-28 17:40+0100\n" "PO-Revision-Date: 2020-08-03 02:47+0200\n" "Last-Translator: John Erling Blad \n" "Language-Team: none\n" @@ -17,271 +17,297 @@ msgstr "Project-Id-Version: solaar 1.0.3\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 2.3\n" -#: lib/logitech_receiver/base_usb.py:50 +#: lib/logitech_receiver/base_usb.py:46 +msgid "Bolt Receiver" +msgstr "" + +#: lib/logitech_receiver/base_usb.py:57 msgid "Unifying Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:58 lib/logitech_receiver/base_usb.py:68 -#: lib/logitech_receiver/base_usb.py:79 lib/logitech_receiver/base_usb.py:90 -#: lib/logitech_receiver/base_usb.py:101 +#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 +#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 +#: lib/logitech_receiver/base_usb.py:114 msgid "Nano Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:109 +#: lib/logitech_receiver/base_usb.py:124 msgid "Lightspeed Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:117 +#: lib/logitech_receiver/base_usb.py:133 msgid "EX100 Receiver 27 Mhz" msgstr "" -#: lib/logitech_receiver/device.py:133 lib/solaar/ui/window.py:693 -msgid "unknown" -msgstr "ukendt" - -#: lib/logitech_receiver/i18n.py:38 +#: lib/logitech_receiver/i18n.py:30 msgid "empty" msgstr "tom" -#: lib/logitech_receiver/i18n.py:39 +#: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "kritisk" -#: lib/logitech_receiver/i18n.py:40 +#: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "lav" -#: lib/logitech_receiver/i18n.py:41 +#: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "" -#: lib/logitech_receiver/i18n.py:42 +#: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "god" -#: lib/logitech_receiver/i18n.py:43 +#: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "fuld" -#: lib/logitech_receiver/i18n.py:46 +#: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "afladning" -#: lib/logitech_receiver/i18n.py:47 +#: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "genopladning" -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:721 +#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "opladning" -#: lib/logitech_receiver/i18n.py:49 +#: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "" -#: lib/logitech_receiver/i18n.py:50 +#: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "næsten fuld" -#: lib/logitech_receiver/i18n.py:51 +#: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "opladt" -#: lib/logitech_receiver/i18n.py:52 +#: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "langsom opladning" -#: lib/logitech_receiver/i18n.py:53 +#: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "batterifejl" -#: lib/logitech_receiver/i18n.py:54 +#: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "termisk fejl" -#: lib/logitech_receiver/i18n.py:55 +#: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "" -#: lib/logitech_receiver/i18n.py:56 +#: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "" -#: lib/logitech_receiver/i18n.py:57 +#: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "" -#: lib/logitech_receiver/i18n.py:58 +#: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "" -#: lib/logitech_receiver/i18n.py:61 +#: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "enheden svarede ikke" -#: lib/logitech_receiver/i18n.py:62 +#: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "enhed understøttes ikke" -#: lib/logitech_receiver/i18n.py:63 +#: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "for mange enheder" -#: lib/logitech_receiver/i18n.py:64 +#: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "sekvens timeout" -#: lib/logitech_receiver/i18n.py:67 lib/solaar/ui/window.py:580 +#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 msgid "Firmware" msgstr "Firmware" -#: lib/logitech_receiver/i18n.py:68 +#: lib/logitech_receiver/i18n.py:60 msgid "Bootloader" msgstr "Opstartsindlæser" -#: lib/logitech_receiver/i18n.py:69 +#: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardware" -#: lib/logitech_receiver/i18n.py:70 +#: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Andet" -#: lib/logitech_receiver/i18n.py:73 +#: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:74 +#: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:75 +#: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "" -#: lib/logitech_receiver/i18n.py:76 +#: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "" -#: lib/logitech_receiver/i18n.py:77 +#: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "" -#: lib/logitech_receiver/i18n.py:78 +#: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "" -#: lib/logitech_receiver/i18n.py:79 +#: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "Smart skift" -#: lib/logitech_receiver/i18n.py:80 +#: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "" -#: lib/logitech_receiver/i18n.py:81 +#: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "" -#: lib/logitech_receiver/i18n.py:82 +#: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "" -#: lib/logitech_receiver/i18n.py:83 +#: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "" -#: lib/logitech_receiver/i18n.py:84 +#: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "" -#: lib/logitech_receiver/i18n.py:85 +#: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "" -#: lib/logitech_receiver/i18n.py:86 +#: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "" -#: lib/logitech_receiver/i18n.py:87 +#: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "" -#: lib/logitech_receiver/i18n.py:88 +#: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "" -#: lib/logitech_receiver/i18n.py:89 +#: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:90 +#: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:93 +#: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "" -#: lib/logitech_receiver/i18n.py:94 +#: lib/logitech_receiver/i18n.py:86 msgid "released" msgstr "" -#: lib/logitech_receiver/notifications.py:76 +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is closed" msgstr "parlåsen er lukket" -#: lib/logitech_receiver/notifications.py:76 +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is open" msgstr "parlåsen er åben" -#: lib/logitech_receiver/notifications.py:160 lib/solaar/ui/notify.py:123 +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is closed" +msgstr "" + +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is open" +msgstr "" + +#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 msgid "connected" msgstr "tilsluttet" -#: lib/logitech_receiver/notifications.py:160 +#: lib/logitech_receiver/notifications.py:224 msgid "disconnected" msgstr "" -#: lib/logitech_receiver/notifications.py:198 lib/solaar/ui/notify.py:121 +#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "uparret" -#: lib/logitech_receiver/notifications.py:248 +#: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "tændt" -#: lib/logitech_receiver/settings.py:526 +#: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "" -#: lib/logitech_receiver/settings.py:542 lib/logitech_receiver/settings.py:568 +#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 msgid "feature" msgstr "" -#: lib/logitech_receiver/settings_templates.py:71 +#: lib/logitech_receiver/settings_templates.py:139 +msgid "Swap Fx function" +msgstr "Byt Fx-funktion" + +#: lib/logitech_receiver/settings_templates.py:140 +msgid "When set, the F1..F12 keys will activate their special function,\n" + "and you must hold the FN key to activate their standard function." +msgstr "Hvis aktiveret, vil F1–F12-tastene aktivere sin spesialfunksjon,\n" + "og du må holde FN-tasten nede for at aktivere jeres standardfunktion." + +#: lib/logitech_receiver/settings_templates.py:142 +msgid "When unset, the F1..F12 keys will activate their standard function,\n" + "and you must hold the FN key to activate their special function." +msgstr "Hvis deaktiveret, vil F1–F12-tastene aktivere sin standardfunktion,\n" + "og du må holde FN-tasten nede for at aktivere deres spesialfunktion." + +#: lib/logitech_receiver/settings_templates.py:149 msgid "Hand Detection" msgstr "Hånddetektion" -#: lib/logitech_receiver/settings_templates.py:71 +#: lib/logitech_receiver/settings_templates.py:150 msgid "Turn on illumination when the hands hover over the keyboard." msgstr "Tænd belysning når hænderne er over tastaturet." -#: lib/logitech_receiver/settings_templates.py:72 +#: lib/logitech_receiver/settings_templates.py:157 msgid "Scroll Wheel Smooth Scrolling" msgstr "" -#: lib/logitech_receiver/settings_templates.py:73 -#: lib/logitech_receiver/settings_templates.py:78 -#: lib/logitech_receiver/settings_templates.py:85 +#: lib/logitech_receiver/settings_templates.py:158 +#: lib/logitech_receiver/settings_templates.py:239 +#: lib/logitech_receiver/settings_templates.py:267 msgid "High-sensitivity mode for vertical scroll with the wheel." msgstr "Højfølsom tilstand til lodret rulling med hjulet." -#: lib/logitech_receiver/settings_templates.py:74 +#: lib/logitech_receiver/settings_templates.py:165 msgid "Side Scrolling" msgstr "Siderulling" -#: lib/logitech_receiver/settings_templates.py:75 +#: lib/logitech_receiver/settings_templates.py:167 msgid "When disabled, pushing the wheel sideways sends custom button " "events\n" "instead of the standard side-scrolling events." @@ -289,485 +315,617 @@ msgstr "Når deaktiveret, sender et tryk på hjulet sidelæns brugerdefinerede "knaphændelser\n" "i stedet for standard siderullehændelser." -#: lib/logitech_receiver/settings_templates.py:77 -msgid "Scroll Wheel High Resolution" +#: lib/logitech_receiver/settings_templates.py:177 +msgid "Sensitivity (DPI - older mice)" msgstr "" -#: lib/logitech_receiver/settings_templates.py:79 -msgid "Scroll Wheel Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:80 -msgid "HID++ mode for vertical scroll with the wheel." -msgstr "HID++ tilstand til lodret rulling med hjulet." - -#: lib/logitech_receiver/settings_templates.py:81 -msgid "Effectively turns off wheel scrolling in Linux." -msgstr "Slår i praksis af hjulrulling i Linux." - -#: lib/logitech_receiver/settings_templates.py:82 -msgid "Scroll Wheel Direction" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:83 -msgid "Invert direction for vertical scroll with wheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:84 -msgid "Scroll Wheel Resolution" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Frequency of device polling, in milliseconds" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Polling Rate (ms)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:87 -msgid "Swap Fx function" -msgstr "Byt Fx-funktion" - -#: lib/logitech_receiver/settings_templates.py:88 -msgid "When set, the F1..F12 keys will activate their special function,\n" - "and you must hold the FN key to activate their standard function." -msgstr "Hvis aktiveret, vil F1–F12-tastene aktivere sin spesialfunksjon,\n" - "og du må holde FN-tasten nede for at aktivere jeres standardfunktion." - -#: lib/logitech_receiver/settings_templates.py:90 -msgid "When unset, the F1..F12 keys will activate their standard function,\n" - "and you must hold the FN key to activate their special function." -msgstr "Hvis deaktiveret, vil F1–F12-tastene aktivere sin standardfunktion,\n" - "og du må holde FN-tasten nede for at aktivere deres spesialfunktion." - -#: lib/logitech_receiver/settings_templates.py:92 +#: lib/logitech_receiver/settings_templates.py:178 +#: lib/logitech_receiver/settings_templates.py:712 msgid "Mouse movement sensitivity" msgstr "" -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Sensitivity (DPI)" -msgstr "Følsomhed (DPI)" - -#: lib/logitech_receiver/settings_templates.py:93 -msgid "Sensitivity (Pointer Speed)" -msgstr "Følsomhed (pegerhastighed)" - -#: lib/logitech_receiver/settings_templates.py:94 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "Hastighedsmultiplikator for mus (256 er normal multiplikator)." - -#: lib/logitech_receiver/settings_templates.py:95 -msgid "Scroll Wheel Rachet" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:96 -msgid "Automatically switch the mouse wheel between ratchet and freespin " - "mode.\n" - "The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:98 +#: lib/logitech_receiver/settings_templates.py:208 +#: lib/logitech_receiver/settings_templates.py:218 +#: lib/logitech_receiver/settings_templates.py:225 msgid "Backlight" msgstr "Bagbelysning" -#: lib/logitech_receiver/settings_templates.py:98 +#: lib/logitech_receiver/settings_templates.py:209 +#: lib/logitech_receiver/settings_templates.py:226 +msgid "Set illumination time for keyboard." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:219 msgid "Turn illumination on or off on keyboard." msgstr "Tænd eller sluk for belysning på tastaturet." -#: lib/logitech_receiver/settings_templates.py:99 +#: lib/logitech_receiver/settings_templates.py:237 +msgid "Scroll Wheel High Resolution" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:240 +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:247 +#: lib/logitech_receiver/settings_templates.py:277 +msgid "Scroll Wheel Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:249 +msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Scroll Wheel Direction" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:257 +msgid "Invert direction for vertical scroll with wheel." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:265 +msgid "Scroll Wheel Resolution" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:279 +msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:288 +msgid "Sensitivity (Pointer Speed)" +msgstr "Følsomhed (pegerhastighed)" + +#: lib/logitech_receiver/settings_templates.py:289 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Hastighedsmultiplikator for mus (256 er normal multiplikator)." + +#: lib/logitech_receiver/settings_templates.py:299 +msgid "Thumb Wheel Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " + "Solaar rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:310 +msgid "Thumb Wheel Direction" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Invert thumb wheel scroll direction." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:319 +msgid "Onboard Profiles" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:320 +msgid "Enable onboard profiles, which often control report rate and " + "keyboard lighting" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:330 +msgid "Polling Rate (ms)" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:332 +msgid "Frequency of device polling, in milliseconds" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:333 +#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:365 +msgid "Divert crown events" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:366 +msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:375 +msgid "Set crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:383 +msgid "Divert G Keys" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:385 +msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:386 +msgid "May also make M keys and MR key send HID++ notifications" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:402 +msgid "Scroll Wheel Ratcheted" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:403 +msgid "Switch the mouse wheel between speed-controlled ratcheting and " + "always freespin." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Freespinning" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Ratcheted" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:412 +msgid "Scroll Wheel Ratchet Speed" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:414 +msgid "Use the mouse wheel speed to switch between ratcheted and " + "freespinning.\n" + "The mouse wheel is always ratcheted at 50." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:463 msgid "Key/Button Actions" msgstr "" -#: lib/logitech_receiver/settings_templates.py:100 +#: lib/logitech_receiver/settings_templates.py:465 msgid "Change the action for the key or button." msgstr "Skift handlingen for tasten eller knappen." -#: lib/logitech_receiver/settings_templates.py:101 +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Overridden by diversion." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:466 msgid "Changing important actions (such as for the left mouse button) can " "result in an unusable system." msgstr "Ændring af vigtige handlinger (for eksempel venstre museknap) kan " "resultere i et ubrugeligt system." -#: lib/logitech_receiver/settings_templates.py:102 +#: lib/logitech_receiver/settings_templates.py:639 msgid "Key/Button Diversion" msgstr "" -#: lib/logitech_receiver/settings_templates.py:103 -msgid "Make the key or button send HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." +#: lib/logitech_receiver/settings_templates.py:640 +msgid "Make the key or button send HID++ notifications (Diverted) or " + "initiate Mouse Gestures or Sliding DPI" msgstr "" -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable keys" -msgstr "Deaktiver taster" - -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable specific keyboard keys." -msgstr "Deaktiver specifikke tastaturtaster." - -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Change keys to match OS." -msgstr "Skift taster for at matche OS." - -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Set OS" -msgstr "Indstil OS" - -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Change Host" -msgstr "Indstil vært" - -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Switch connection to a different host" -msgstr "Skift forbindelse til en anden vært" - -#: lib/logitech_receiver/settings_templates.py:107 -msgid "Thumb Wheel Diversion" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Diverted" msgstr "" -#: lib/logitech_receiver/settings_templates.py:108 -msgid "HID++ mode for horizontal scroll with the thumb wheel." -msgstr "HID ++ -tilstand til vandret rulling med tommelhjulet." - -#: lib/logitech_receiver/settings_templates.py:109 -msgid "Effectively turns off thumb scrolling in Linux." -msgstr "Slår i praksis af tommelrulling i Linux." - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Invert thumb wheel scroll direction." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Thumb Wheel Direction" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Gestures" -msgstr "Gestusser" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Gesture params" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:113 -msgid "DPI Sliding Adjustment" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:114 -msgid "Adjust the DPI by sliding the mouse horizontally while holding the " - "button down." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:115 +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 msgid "Mouse Gestures" msgstr "" -#: lib/logitech_receiver/settings_templates.py:116 -msgid "Send a gesture by sliding the mouse while holding the button down." +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Regular" msgstr "" -#: lib/logitech_receiver/settings_templates.py:117 -msgid "Divert crown events" +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Sliding DPI" msgstr "" -#: lib/logitech_receiver/settings_templates.py:118 -msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." +#: lib/logitech_receiver/settings_templates.py:711 +msgid "Sensitivity (DPI)" +msgstr "Følsomhed (DPI)" + +#: lib/logitech_receiver/settings_templates.py:752 +msgid "Sensitivity Switching" msgstr "" -#: lib/logitech_receiver/settings_templates.py:119 -msgid "Divert G Keys" +#: lib/logitech_receiver/settings_templates.py:754 +msgid "Switch the current sensitivity and the remembered sensitivity when " + "the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current " + "sensitivity" msgstr "" -#: lib/logitech_receiver/settings_templates.py:120 -msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." +#: lib/logitech_receiver/settings_templates.py:758 +msgid "Off" msgstr "" -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Performs a left click." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:791 +msgid "Disable keys" +msgstr "Deaktiver taster" -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Single tap" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:792 +msgid "Disable specific keyboard keys." +msgstr "Deaktiver specifikke tastaturtaster." -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Performs a right click." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Single tap with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:125 -msgid "Single tap with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Double tap" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Performs a double click." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:130 -msgid "Double tap with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:131 -msgid "Double tap with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Drags items by dragging the finger after double tapping." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Tap and drag" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:135 -msgid "Tap and drag with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:136 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:137 -msgid "Tap and drag with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:139 -msgid "Suppress tap and edge gestures" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:140 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:141 -msgid "Scroll with one finger" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:141 -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scrolls." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scroll with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scroll horizontally with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scrolls horizontally." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scroll vertically with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scrolls vertically." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Inverts the scrolling direction." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Natural scrolling" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Enables the thumbwheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Thumbwheel" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:162 -msgid "Swipe from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:159 -msgid "Swipe from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:160 -msgid "Swipe from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:161 -msgid "Swipe from the bottom edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:163 -msgid "Swipe two fingers from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:164 -msgid "Swipe two fingers from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:165 -msgid "Swipe two fingers from the bottom edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:166 -msgid "Swipe two fingers from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:167 -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:167 -msgid "Zoom with two fingers." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:168 -msgid "Pinch to zoom out." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:169 -msgid "Spread to zoom in." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:170 -msgid "Zoom with three fingers." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Zoom with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:189 -msgid "Pixel zone" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:190 -msgid "Ratio zone" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Scale factor" -msgstr "Skaleringsfaktor" - -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Sets the cursor speed." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left-most coordinate." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:196 -msgid "Top-most coordinate." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:196 -msgid "top" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:197 -msgid "Width." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:197 -msgid "width" -msgstr "bredde" - -#: lib/logitech_receiver/settings_templates.py:198 -msgid "Height." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:198 -msgid "height" -msgstr "højde" - -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Cursor speed." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Scale" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:202 +#: lib/logitech_receiver/settings_templates.py:795 #, python-format msgid "Disables the %s key." msgstr "" -#: lib/logitech_receiver/settings_templates.py:521 -#: lib/logitech_receiver/settings_templates.py:573 -msgid "Off" +#: lib/logitech_receiver/settings_templates.py:809 +#: lib/logitech_receiver/settings_templates.py:860 +msgid "Set OS" +msgstr "Indstil OS" + +#: lib/logitech_receiver/settings_templates.py:810 +#: lib/logitech_receiver/settings_templates.py:861 +msgid "Change keys to match OS." +msgstr "Skift taster for at matche OS." + +#: lib/logitech_receiver/settings_templates.py:873 +msgid "Change Host" +msgstr "Indstil vært" + +#: lib/logitech_receiver/settings_templates.py:874 +msgid "Switch connection to a different host" +msgstr "Skift forbindelse til en anden vært" + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Performs a left click." msgstr "" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Diverted" +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Single tap" msgstr "" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Regular" +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Performs a right click." msgstr "" -#: lib/logitech_receiver/status.py:109 +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Single tap with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:902 +msgid "Single tap with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Double tap" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Performs a double click." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:907 +msgid "Double tap with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:908 +msgid "Double tap with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Drags items by dragging the finger after double tapping." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Tap and drag" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Tap and drag with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:914 +msgid "Tap and drag with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Suppress tap and edge gestures" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:918 +msgid "Scroll with one finger" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scrolls." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scroll with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scroll horizontally with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scrolls horizontally." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scroll vertically with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scrolls vertically." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Inverts the scrolling direction." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Natural scrolling" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Enables the thumbwheel." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Thumbwheel" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:935 +#: lib/logitech_receiver/settings_templates.py:939 +msgid "Swipe from the top edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:936 +msgid "Swipe from the left edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:937 +msgid "Swipe from the right edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:938 +msgid "Swipe from the bottom edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:940 +msgid "Swipe two fingers from the left edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:941 +msgid "Swipe two fingers from the right edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:942 +msgid "Swipe two fingers from the bottom edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:943 +msgid "Swipe two fingers from the top edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:944 +msgid "Zoom with two fingers." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:945 +msgid "Pinch to zoom out." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:946 +msgid "Spread to zoom in." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:947 +msgid "Zoom with three fingers." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Zoom with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:966 +msgid "Pixel zone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:967 +msgid "Ratio zone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Scale factor" +msgstr "Skaleringsfaktor" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Sets the cursor speed." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left-most coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width" +msgstr "Bredde" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height" +msgstr "Højde" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Cursor speed." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Scale" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:982 +msgid "Gestures" +msgstr "Gestusser" + +#: lib/logitech_receiver/settings_templates.py:983 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1000 +msgid "Gestures Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1001 +msgid "Divert mouse/touchpad gestures." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1018 +msgid "Gesture params" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1019 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1044 +msgid "M-Key LEDs" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Control the M-Key LEDs." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1077 +msgid "May need G Keys diverted to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1053 +#, python-format +msgid "Lights up the %s key." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1074 +msgid "MR-Key LED" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "Control the MR-Key LED." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1095 +msgid "Persistent Key/Button Mapping" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1097 +msgid "Permanently change the mapping for the key or button." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1098 +msgid "Changing important keys or buttons (such as for the left mouse " + "button) can result in an unusable system." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1157 +msgid "Sidetone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1158 +msgid "Set sidetone level." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1167 +msgid "Equalizer" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1168 +msgid "Set equalizer levels." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1191 +msgid "Hz" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1197 +msgid "Power Management" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1198 +msgid "Power off in minutes (0 for never)." +msgstr "" + +#: lib/logitech_receiver/status.py:114 msgid "No paired devices." msgstr "Ingen forbundne enheder." -#: lib/logitech_receiver/status.py:110 lib/solaar/ui/window.py:623 +#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 #, python-format msgid "%(count)s paired device." msgid_plural "%(count)s paired devices." msgstr[0] "%(count)s parret enhed." msgstr[1] "%(count)s parrede enheder." -#: lib/logitech_receiver/status.py:165 +#: lib/logitech_receiver/status.py:170 #, python-format msgid "Battery: %(level)s" msgstr "Batteri: %(level)s" -#: lib/logitech_receiver/status.py:167 +#: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "Batteri: %(percent)d%%" -#: lib/logitech_receiver/status.py:179 +#: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "Belysning: %(level)s lux" -#: lib/logitech_receiver/status.py:234 +#: lib/logitech_receiver/status.py:239 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "Batteri: %(level)s (%(status)s)" -#: lib/logitech_receiver/status.py:236 +#: lib/logitech_receiver/status.py:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Batteri: %(percent)d%% (%(status)s)" @@ -778,16 +936,14 @@ msgstr "Tilladelsesfejl" #: lib/solaar/ui/__init__.py:54 #, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open " - "it." -msgstr "Fundet en Logitech-modtager (%s), men havde ikke tilladelse til at " - "åbne den." +msgid "Found a Logitech receiver or device (%s), but did not have " + "permission to open it." +msgstr "" #: lib/solaar/ui/__init__.py:55 -msgid "If you've just installed Solaar, try removing the receiver and " - "plugging it back in." -msgstr "Hvis du lige har installeret Solaar, kan du prøve at fjerne " - "modtageren og tilslutte den igen." +msgid "If you've just installed Solaar, try disconnecting the receiver or " + "device and then reconnecting it." +msgstr "" #: lib/solaar/ui/__init__.py:58 msgid "Cannot connect to device error" @@ -800,8 +956,8 @@ msgid "Found a Logitech receiver or device at %s, but encountered an error " msgstr "" #: lib/solaar/ui/__init__.py:61 -msgid "Try removing the device and plugging it back in or turning it off " - "and then on." +msgid "Try disconnecting the device and then reconnecting it or turning it " + "off and then on." msgstr "" #: lib/solaar/ui/__init__.py:64 @@ -817,354 +973,627 @@ msgstr "Mislykkedes med at bryde paret %{device} og %{receiver}." msgid "The receiver returned an error, with no further details." msgstr "Modtageren returnerede en fejl uden yderligere detaljer." -#: lib/solaar/ui/about.py:39 +#: lib/solaar/ui/__init__.py:177 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" + +#: lib/solaar/ui/about.py:36 msgid "Manages Logitech receivers,\n" "keyboards, mice, and tablets." msgstr "" -#: lib/solaar/ui/about.py:47 +#: lib/solaar/ui/about.py:44 msgid "Additional Programming" msgstr "" -#: lib/solaar/ui/about.py:48 +#: lib/solaar/ui/about.py:45 msgid "GUI design" msgstr "GUI-design" -#: lib/solaar/ui/about.py:50 +#: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "Testing" -#: lib/solaar/ui/about.py:57 +#: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Logitech-dokumentation" -#: lib/solaar/ui/action.py:70 lib/solaar/ui/window.py:328 -msgid "About %s" -msgstr "Om %s" - -#: lib/solaar/ui/action.py:96 lib/solaar/ui/action.py:100 -#: lib/solaar/ui/window.py:205 +#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 +#: lib/solaar/ui/window.py:197 msgid "Unpair" msgstr "Fjern parring" -#: lib/solaar/ui/action.py:99 lib/solaar/ui/diversion_rules.py:144 +#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "" -#: lib/solaar/ui/config_panel.py:358 -msgid "Changes allowed" +#: lib/solaar/ui/config_panel.py:212 +msgid "Complete - ENTER to change" msgstr "" -#: lib/solaar/ui/config_panel.py:359 -msgid "No changes allowed" +#: lib/solaar/ui/config_panel.py:212 +msgid "Incomplete" msgstr "" -#: lib/solaar/ui/config_panel.py:360 -msgid "Ignore this setting" -msgstr "" - -#: lib/solaar/ui/config_panel.py:401 -msgid "Working" -msgstr "Succes" - -#: lib/solaar/ui/config_panel.py:404 -msgid "Read/write operation failed." -msgstr "Læse-/skriveoperationen mislykkedes." - -#: lib/solaar/ui/config_panel.py:522 +#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "" msgstr[1] "" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/config_panel.py:518 +msgid "Changes allowed" +msgstr "" + +#: lib/solaar/ui/config_panel.py:519 +msgid "No changes allowed" +msgstr "" + +#: lib/solaar/ui/config_panel.py:520 +msgid "Ignore this setting" +msgstr "" + +#: lib/solaar/ui/config_panel.py:565 +msgid "Working" +msgstr "Succes" + +#: lib/solaar/ui/config_panel.py:568 +msgid "Read/write operation failed." +msgstr "Læse-/skriveoperationen mislykkedes." + +#: lib/solaar/ui/diversion_rules.py:65 msgid "Built-in rules" msgstr "" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" msgstr "" -#: lib/solaar/ui/diversion_rules.py:59 lib/solaar/ui/diversion_rules.py:792 +#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:60 lib/solaar/ui/diversion_rules.py:503 -#: lib/solaar/ui/diversion_rules.py:622 +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 +#: lib/solaar/ui/diversion_rules.py:636 msgid "Sub-rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:62 +#: lib/solaar/ui/diversion_rules.py:70 msgid "[empty]" msgstr "" -#: lib/solaar/ui/diversion_rules.py:85 +#: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "" -#: lib/solaar/ui/diversion_rules.py:135 +#: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "" -#: lib/solaar/ui/diversion_rules.py:140 +#: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Ja" -#: lib/solaar/ui/diversion_rules.py:142 +#: lib/solaar/ui/diversion_rules.py:148 msgid "No" msgstr "" -#: lib/solaar/ui/diversion_rules.py:147 +#: lib/solaar/ui/diversion_rules.py:153 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "" -#: lib/solaar/ui/diversion_rules.py:195 +#: lib/solaar/ui/diversion_rules.py:201 msgid "Save changes" msgstr "" -#: lib/solaar/ui/diversion_rules.py:200 +#: lib/solaar/ui/diversion_rules.py:206 msgid "Discard changes" msgstr "" -#: lib/solaar/ui/diversion_rules.py:366 +#: lib/solaar/ui/diversion_rules.py:372 msgid "Insert here" msgstr "" -#: lib/solaar/ui/diversion_rules.py:368 +#: lib/solaar/ui/diversion_rules.py:374 msgid "Insert above" msgstr "" -#: lib/solaar/ui/diversion_rules.py:370 +#: lib/solaar/ui/diversion_rules.py:376 msgid "Insert below" msgstr "" -#: lib/solaar/ui/diversion_rules.py:376 +#: lib/solaar/ui/diversion_rules.py:382 msgid "Insert new rule here" msgstr "" -#: lib/solaar/ui/diversion_rules.py:378 +#: lib/solaar/ui/diversion_rules.py:384 msgid "Insert new rule above" msgstr "" -#: lib/solaar/ui/diversion_rules.py:380 +#: lib/solaar/ui/diversion_rules.py:386 msgid "Insert new rule below" msgstr "" -#: lib/solaar/ui/diversion_rules.py:421 +#: lib/solaar/ui/diversion_rules.py:427 msgid "Paste here" msgstr "" -#: lib/solaar/ui/diversion_rules.py:423 +#: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "" -#: lib/solaar/ui/diversion_rules.py:425 +#: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "" -#: lib/solaar/ui/diversion_rules.py:431 +#: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "" -#: lib/solaar/ui/diversion_rules.py:433 +#: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "" -#: lib/solaar/ui/diversion_rules.py:435 +#: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "" -#: lib/solaar/ui/diversion_rules.py:439 +#: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:468 +#: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "" -#: lib/solaar/ui/diversion_rules.py:501 +#: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "" -#: lib/solaar/ui/diversion_rules.py:504 lib/solaar/ui/diversion_rules.py:624 -#: lib/solaar/ui/diversion_rules.py:835 +#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 +#: lib/solaar/ui/diversion_rules.py:1126 msgid "Or" msgstr "" -#: lib/solaar/ui/diversion_rules.py:505 lib/solaar/ui/diversion_rules.py:623 -#: lib/solaar/ui/diversion_rules.py:820 +#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 +#: lib/solaar/ui/diversion_rules.py:1111 msgid "And" msgstr "" -#: lib/solaar/ui/diversion_rules.py:507 +#: lib/solaar/ui/diversion_rules.py:513 msgid "Condition" msgstr "" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:951 +#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:874 -msgid "Process" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:902 -msgid "MouseProcess" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:512 lib/solaar/ui/diversion_rules.py:984 +#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1019 +#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 +msgid "Process" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:518 +msgid "Mouse process" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 msgid "Modifiers" msgstr "" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1066 +#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1111 +#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 +msgid "KeyIsDown" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 +msgid "Active" +msgstr "Aktiv" + +#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2218 +#: lib/solaar/ui/diversion_rules.py:2270 lib/solaar/ui/diversion_rules.py:2323 +msgid "Device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +msgid "Host" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 +msgid "Setting" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 +#: lib/solaar/ui/diversion_rules.py:1526 msgid "Test" msgstr "" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1196 +#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 +msgid "Test bytes" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 msgid "Mouse Gesture" msgstr "" -#: lib/solaar/ui/diversion_rules.py:520 +#: lib/solaar/ui/diversion_rules.py:532 msgid "Action" msgstr "Handling" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1286 +#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 msgid "Key press" msgstr "" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1335 +#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:1383 +#: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 msgid "Mouse click" msgstr "" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1452 +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Set" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 msgid "Execute" msgstr "" -#: lib/solaar/ui/diversion_rules.py:554 +#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 +msgid "Later" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:568 msgid "Insert new rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/diversion_rules.py:1146 -#: lib/solaar/ui/diversion_rules.py:1236 lib/solaar/ui/diversion_rules.py:1411 +#: lib/solaar/ui/diversion_rules.py:588 lib/solaar/ui/diversion_rules.py:1686 +#: lib/solaar/ui/diversion_rules.py:1790 lib/solaar/ui/diversion_rules.py:1989 msgid "Delete" msgstr "" -#: lib/solaar/ui/diversion_rules.py:596 +#: lib/solaar/ui/diversion_rules.py:610 msgid "Negate" msgstr "" -#: lib/solaar/ui/diversion_rules.py:620 +#: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "" -#: lib/solaar/ui/diversion_rules.py:642 +#: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Klip" -#: lib/solaar/ui/diversion_rules.py:657 +#: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Indsæt" -#: lib/solaar/ui/diversion_rules.py:669 +#: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Kopiér" -#: lib/solaar/ui/diversion_rules.py:772 +#: lib/solaar/ui/diversion_rules.py:1063 msgid "This editor does not support the selected rule component yet." msgstr "" -#: lib/solaar/ui/diversion_rules.py:850 +#: lib/solaar/ui/diversion_rules.py:1138 +msgid "Number of seconds to delay." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1177 msgid "Not" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1039 +#: lib/solaar/ui/diversion_rules.py:1187 +msgid "X11 active process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1218 +msgid "X11 mouse process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1235 +msgid "MouseProcess" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1260 +msgid "Feature name of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1308 +msgid "Report number of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1342 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1383 +msgid "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1392 msgid "Key down" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1042 +#: lib/solaar/ui/diversion_rules.py:1395 msgid "Key up" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1131 -msgid "Add action" +#: lib/solaar/ui/diversion_rules.py:1436 +msgid "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." msgstr "" -#: lib/solaar/ui/diversion_rules.py:1224 +#: lib/solaar/ui/diversion_rules.py:1475 +msgid "Test condition on notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1479 +msgid "Parameter" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1542 +msgid "begin (inclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1543 +msgid "end (exclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1552 +msgid "range" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1554 +msgid "minimum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1555 +msgid "maximum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1557 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1562 +msgid "mask" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1563 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1573 +msgid "Bit or range test on bytes in notification message triggering rule " + "processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1583 +msgid "type" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1666 +msgid "Mouse gesture with optional initiating button followed by zero or " + "more mouse movements." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1671 +msgid "Add movement" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1764 +msgid "Simulate a chorded key click or depress or release.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1769 msgid "Add key" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1354 +#: lib/solaar/ui/diversion_rules.py:1772 +msgid "Click" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1775 +msgid "Depress" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1778 +msgid "Release" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1861 +msgid "Simulate a mouse scroll.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1918 +msgid "Simulate a mouse click.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1921 msgid "Button" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1355 -msgid "Count" -msgstr "Antal" +#: lib/solaar/ui/diversion_rules.py:1922 +msgid "Count and Action" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1397 +#: lib/solaar/ui/diversion_rules.py:1972 +msgid "Execute a command with arguments." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1975 msgid "Add argument" msgstr "" -#: lib/solaar/ui/notify.py:125 lib/solaar/ui/tray.py:319 -#: lib/solaar/ui/tray.py:324 lib/solaar/ui/window.py:749 +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "Toggle" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "True" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2051 +msgid "False" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2065 +msgid "Unsupported setting" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2242 +#: lib/solaar/ui/diversion_rules.py:2328 lib/solaar/ui/diversion_rules.py:2570 +#: lib/solaar/ui/diversion_rules.py:2588 +msgid "Originating device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2256 +msgid "Device is active and its settings can be changed." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2266 +msgid "Device that originated the current notification." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2280 +msgid "Name of host computer." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2347 +msgid "Value" +msgstr "Værdi" + +#: lib/solaar/ui/diversion_rules.py:2355 +msgid "Item" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2630 +msgid "Change setting on device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2647 +msgid "Setting on device" +msgstr "" + +#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:318 +#: lib/solaar/ui/tray.py:323 lib/solaar/ui/window.py:739 msgid "offline" msgstr "afslået" -#: lib/solaar/ui/pair_window.py:134 +#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 +#: lib/solaar/ui/pair_window.py:288 +#, python-format +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: parre ny enhed" + +#: lib/solaar/ui/pair_window.py:123 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "" + +#: lib/solaar/ui/pair_window.py:126 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "left" +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "right" +msgstr "" + +#: lib/solaar/ui/pair_window.py:131 +#, python-format +msgid "Press %(code)s\n" + "and then press left and right buttons simultaneously." +msgstr "" + +#: lib/solaar/ui/pair_window.py:188 msgid "Pairing failed" msgstr "Parring mislykkedes" -#: lib/solaar/ui/pair_window.py:136 +#: lib/solaar/ui/pair_window.py:190 msgid "Make sure your device is within range, and has a decent battery " "charge." msgstr "Sørg for, at din enhed er inden for rækkevidde og har en anstændig " "batteriopladning." -#: lib/solaar/ui/pair_window.py:138 +#: lib/solaar/ui/pair_window.py:192 msgid "A new device was detected, but it is not compatible with this " "receiver." msgstr "Der blev fundet en ny enhed, men den er ikke kompatibel med denne " "modtager." -#: lib/solaar/ui/pair_window.py:140 +#: lib/solaar/ui/pair_window.py:194 msgid "More paired devices than receiver can support." msgstr "Flere parrede enheder end modtageren kan understøtte." -#: lib/solaar/ui/pair_window.py:142 +#: lib/solaar/ui/pair_window.py:196 msgid "No further details are available about the error." msgstr "Ingen yderligere oplysninger er tilgængelige om fejlen." -#: lib/solaar/ui/pair_window.py:156 +#: lib/solaar/ui/pair_window.py:210 msgid "Found a new device:" msgstr "Fandt en ny enhed:" -#: lib/solaar/ui/pair_window.py:181 +#: lib/solaar/ui/pair_window.py:235 msgid "The wireless link is not encrypted" msgstr "Den trådløse tilslutningen er ukryptert" -#: lib/solaar/ui/pair_window.py:199 -#, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s: parre ny enhed" +#: lib/solaar/ui/pair_window.py:264 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "" -#: lib/solaar/ui/pair_window.py:206 +#: lib/solaar/ui/pair_window.py:266 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:268 +msgid "Other receivers are only compatible with a few devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:270 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" + +#: lib/solaar/ui/pair_window.py:274 +msgid "Press a pairing button or key until the pairing light flashes " + "quickly." +msgstr "" + +#: lib/solaar/ui/pair_window.py:276 +msgid "You may have to first turn the device off and on again." +msgstr "" + +#: lib/solaar/ui/pair_window.py:278 +msgid "Turn on the device you want to pair." +msgstr "Tænd for den enhed, du vil parre." + +#: lib/solaar/ui/pair_window.py:280 msgid "If the device is already turned on, turn it off and on again." msgstr "" -#: lib/solaar/ui/pair_window.py:209 +#: lib/solaar/ui/pair_window.py:283 #, python-format msgid "\n" "\n" @@ -1179,208 +1608,191 @@ msgstr[1] "\n" "\n" "Denne modtager har %d parringer tilbage." -#: lib/solaar/ui/pair_window.py:212 +#: lib/solaar/ui/pair_window.py:286 msgid "\n" "Cancelling at this point will not use up a pairing." msgstr "\n" "Annullering på dette tidspunkt bruger ikke en parring." -#: lib/solaar/ui/pair_window.py:215 -msgid "Turn on the device you want to pair." -msgstr "Tænd for den enhed, du vil parre." +#: lib/solaar/ui/tray.py:58 +msgid "No supported device found" +msgstr "" -#: lib/solaar/ui/tray.py:61 -msgid "No Logitech receiver found" -msgstr "Der blev ikke fundet nogen Logitech-modtager" +#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#, python-format +msgid "About %s" +msgstr "Om %s" -#: lib/solaar/ui/tray.py:68 lib/solaar/ui/window.py:325 +#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 +#, python-format msgid "Quit %s" msgstr "Afslut %s" -#: lib/solaar/ui/tray.py:298 lib/solaar/ui/tray.py:306 +#: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "ingen modtager" -#: lib/solaar/ui/tray.py:322 +#: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "ingen status" -#: lib/solaar/ui/window.py:104 +#: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "Søger" -#: lib/solaar/ui/window.py:137 lib/solaar/ui/window.py:692 +#: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Batteri" -#: lib/solaar/ui/window.py:140 +#: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Trådløs tilslutning" -#: lib/solaar/ui/window.py:144 +#: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Belysning" -#: lib/solaar/ui/window.py:178 +#: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Vis tekniske detaljer" -#: lib/solaar/ui/window.py:194 +#: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Parre ny enhed" -#: lib/solaar/ui/window.py:213 +#: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Vælg en enhed" -#: lib/solaar/ui/window.py:331 +#: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "" -#: lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Sti" -#: lib/solaar/ui/window.py:544 +#: lib/solaar/ui/window.py:536 msgid "USB ID" msgstr "" -#: lib/solaar/ui/window.py:547 lib/solaar/ui/window.py:549 -#: lib/solaar/ui/window.py:569 lib/solaar/ui/window.py:571 +#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 msgid "Serial" msgstr "Seriel" -#: lib/solaar/ui/window.py:553 +#: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Indeks" -#: lib/solaar/ui/window.py:555 +#: lib/solaar/ui/window.py:547 msgid "Wireless PID" msgstr "Trådløs-PID" -#: lib/solaar/ui/window.py:557 +#: lib/solaar/ui/window.py:549 msgid "Product ID" msgstr "Produkt-id" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:551 msgid "Protocol" msgstr "Protokol" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:551 msgid "Unknown" msgstr "Ukendt" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:554 #, python-format msgid "%(rate)d ms (%(rate_hz)dHz)" msgstr "%(rate)d ms (%(rate_hz)dHz)" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:554 msgid "Polling rate" msgstr "Spørgerate" -#: lib/solaar/ui/window.py:573 +#: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "" -#: lib/solaar/ui/window.py:584 +#: lib/solaar/ui/window.py:576 msgid "none" msgstr "ingen" -#: lib/solaar/ui/window.py:585 +#: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Varsler" -#: lib/solaar/ui/window.py:622 +#: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "Ingen enhed parret." -#: lib/solaar/ui/window.py:629 +#: lib/solaar/ui/window.py:628 #, python-format msgid "Up to %(max_count)s device can be paired to this receiver." msgid_plural "Up to %(max_count)s devices can be paired to this receiver." msgstr[0] "Op til %(max_count)s enhed kan parres med denne modtager." msgstr[1] "Op til %(max_count)s enheder kan parres med denne modtager." -#: lib/solaar/ui/window.py:635 +#: lib/solaar/ui/window.py:634 msgid "Only one device can be paired to this receiver." msgstr "Kun en enhed kan parres med denne modtager." -#: lib/solaar/ui/window.py:639 +#: lib/solaar/ui/window.py:638 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." msgstr[0] "Denne modtager har %d parring tilbage." msgstr[1] "Denne modtager har %d parringer tilbage." -#: lib/solaar/ui/window.py:694 -msgid "Battery information unknown." -msgstr "" - -#: lib/solaar/ui/window.py:702 +#: lib/solaar/ui/window.py:692 msgid "Battery Voltage" msgstr "" -#: lib/solaar/ui/window.py:704 +#: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "" -#: lib/solaar/ui/window.py:706 lib/solaar/ui/window.py:710 +#: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "" -#: lib/solaar/ui/window.py:708 lib/solaar/ui/window.py:712 +#: lib/solaar/ui/window.py:698 msgid "Approximate level reported by battery" msgstr "" -#: lib/solaar/ui/window.py:715 lib/solaar/ui/window.py:717 +#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 msgid "next reported " msgstr "" -#: lib/solaar/ui/window.py:718 +#: lib/solaar/ui/window.py:708 msgid " and next level to be reported." msgstr "" -#: lib/solaar/ui/window.py:723 +#: lib/solaar/ui/window.py:713 msgid "last known" msgstr "sidst kendt" -#: lib/solaar/ui/window.py:731 -msgid "not encrypted" -msgstr "ukrypteret" - -#: lib/solaar/ui/window.py:735 -msgid "The wireless link between this device and its receiver is not " - "encrypted.\n" - "\n" - "For pointing devices (mice, trackballs, trackpads), this is a minor " - "security issue.\n" - "\n" - "It is, however, a major security issue for text-input devices " - "(keyboards, numpads),\n" - "because typed text can be sniffed inconspicuously by 3rd parties " - "within range." -msgstr "Den trådløse forbindelse mellem denne enhed og dens modtager er ikke " - "krypteret.\n" - "\n" - "For pegeenheder (mus, trackballs, pegefelter) er dette et mindre " - "sikkerhedsproblem.\n" - "\n" - "Det er imidlertid et vigtigt sikkerhedsproblem for tekstinputenheder " - "(tastaturer, numpads),\n" - "fordi maskinskrevet tekst kan sniffes upåfaldende af 3rd parter " - "inden for rækkevidde." - -#: lib/solaar/ui/window.py:744 +#: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "kryptert" -#: lib/solaar/ui/window.py:746 +#: lib/solaar/ui/window.py:726 msgid "The wireless link between this device and its receiver is encrypted." msgstr "Den trådløse forbindelse mellem denne enhed og dens modtager er " "krypteret." -#: lib/solaar/ui/window.py:759 +#: lib/solaar/ui/window.py:728 +msgid "not encrypted" +msgstr "ukrypteret" + +#: lib/solaar/ui/window.py:732 +msgid "The wireless link between this device and its receiver is not " + "encrypted.\n" + "This is a security issue for pointing devices, and a major security " + "issue for text-input devices." +msgstr "" + +#: lib/solaar/ui/window.py:748 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d lux" @@ -1402,18 +1814,39 @@ msgstr "%(light_level)d lux" #~ "tilstand.\n" #~ "Musehjulet er altid frit ved 0, og altid låst ved 50" +#~ msgid "Count" +#~ msgstr "Antal" + +#~ msgid "Effectively turns off thumb scrolling in Linux." +#~ msgstr "Slår i praksis af tommelrulling i Linux." + +#~ msgid "Effectively turns off wheel scrolling in Linux." +#~ msgstr "Slår i praksis af hjulrulling i Linux." + #~ msgid "For more information see the Solaar installation directions\n" #~ "at https://pwr-solaar.github.io/Solaar/installation" #~ msgstr "Yderligere oplysninger finder du i Installationsvejledningen " #~ "til Solaar\n" #~ "på https://pwr-solaar.github.io/Solaar/installation" +#, python-format +#~ msgid "Found a Logitech Receiver (%s), but did not have permission " +#~ "to open it." +#~ msgstr "Fundet en Logitech-modtager (%s), men havde ikke tilladelse " +#~ "til at åbne den." + #~ msgid "HID++ Scrolling" #~ msgstr "HID++ rulling" #~ msgid "HID++ Thumb Scrolling" #~ msgstr "HID++ tommelrulling" +#~ msgid "HID++ mode for horizontal scroll with the thumb wheel." +#~ msgstr "HID ++ -tilstand til vandret rulling med tommelhjulet." + +#~ msgid "HID++ mode for vertical scroll with the wheel." +#~ msgstr "HID++ tilstand til lodret rulling med hjulet." + #~ msgid "High Resolution Scrolling" #~ msgstr "Høyfølsom rulling" @@ -1426,9 +1859,17 @@ msgstr "%(light_level)d lux" #~ msgid "If the device is already turned on, turn if off and on again." #~ msgstr "Hvis enheden allerede er tændt, slå den fra og til igen." +#~ msgid "If you've just installed Solaar, try removing the receiver " +#~ "and plugging it back in." +#~ msgstr "Hvis du lige har installeret Solaar, kan du prøve at fjerne " +#~ "modtageren og tilslutte den igen." + #~ msgid "Invert thumb scroll direction." #~ msgstr "Inverter retning på tommelrulling." +#~ msgid "No Logitech receiver found" +#~ msgstr "Der blev ikke fundet nogen Logitech-modtager" + #~ msgid "Shows status of devices connected\n" #~ "through wireless Logitech receivers." #~ msgstr "Viser status for enheder, der er tilsluttet\n" @@ -1446,6 +1887,27 @@ msgstr "%(light_level)d lux" #~ msgid "The receiver was unplugged." #~ msgstr "Mottakeren fjernedes." +#~ msgid "The wireless link between this device and its receiver is " +#~ "not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices " +#~ "(keyboards, numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties " +#~ "within range." +#~ msgstr "Den trådløse forbindelse mellem denne enhed og dens modtager " +#~ "er ikke krypteret.\n" +#~ "\n" +#~ "For pegeenheder (mus, trackballs, pegefelter) er dette et mindre " +#~ "sikkerhedsproblem.\n" +#~ "\n" +#~ "Det er imidlertid et vigtigt sikkerhedsproblem for tekstinputenheder " +#~ "(tastaturer, numpads),\n" +#~ "fordi maskinskrevet tekst kan sniffes upåfaldende af 3rd parter " +#~ "inden for rækkevidde." + #~ msgid "This receiver has %d pairing(s) remaining." #~ msgstr "Denne modtager har %d parring(er) tilbage." @@ -1461,5 +1923,14 @@ msgstr "%(light_level)d lux" #~ msgid "Wheel Resolution" #~ msgstr "Hjulopløsning" +#~ msgid "height" +#~ msgstr "højde" + #~ msgid "next " #~ msgstr "næste " + +#~ msgid "unknown" +#~ msgstr "ukendt" + +#~ msgid "width" +#~ msgstr "bredde" diff --git a/po/de.po b/po/de.po index 1fef181c..7325e4af 100644 --- a/po/de.po +++ b/po/de.po @@ -1519,7 +1519,7 @@ msgstr "Das Gerät ist aktiv und die Einstellungen können geändert werden." #: lib/solaar/ui/diversion_rules.py:2266 msgid "Device that originated the current notification." -msgstr "Gerät, von dem die aktuelle Be­nach­rich­ti­gung stammt." +msgstr "Gerät, von dem die aktuelle Benachrichtigung stammt." #: lib/solaar/ui/diversion_rules.py:2280 msgid "Name of host computer." diff --git a/po/el.po b/po/el.po index a112881a..dcbcc989 100644 --- a/po/el.po +++ b/po/el.po @@ -4,13 +4,13 @@ # Automatically generated, 2014. # Giannis Tsagatakis # Vangelis Skarmoutsos , 2017 -# +# Athanasios Nektarios Karachalios Stagkas 2023-2024 msgid "" msgstr "" "Project-Id-Version: solaar 0.9.2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-04-02 16:37+0300\n" -"PO-Revision-Date: 2023-04-02 23:47+0300\n" +"POT-Creation-Date: 2024-03-14 13:23+0700\n" +"PO-Revision-Date: 2024-04-01 03:51+0300\n" "Last-Translator: Vangelis Skarmoutsos \n" "Language-Team: none\n" "Language: el\n" @@ -18,265 +18,367 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.1.1\n" +"X-Generator: Poedit 3.4.2\n" -#: lib/logitech_receiver/base_usb.py:46 +#: lib/logitech_receiver/base_usb.py:45 msgid "Bolt Receiver" msgstr "Δέκτης Bolt" -#: lib/logitech_receiver/base_usb.py:57 +#: lib/logitech_receiver/base_usb.py:58 msgid "Unifying Receiver" msgstr "Δέκτης Unifying" -#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 -#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 -#: lib/logitech_receiver/base_usb.py:114 +#: lib/logitech_receiver/base_usb.py:70 lib/logitech_receiver/base_usb.py:83 +#: lib/logitech_receiver/base_usb.py:97 lib/logitech_receiver/base_usb.py:111 +#: lib/logitech_receiver/base_usb.py:125 msgid "Nano Receiver" msgstr "Δέκτης Nano" -#: lib/logitech_receiver/base_usb.py:124 +#: lib/logitech_receiver/base_usb.py:138 msgid "Lightspeed Receiver" msgstr "Δέκτης Lightspeed" -#: lib/logitech_receiver/base_usb.py:133 +#: lib/logitech_receiver/base_usb.py:149 msgid "EX100 Receiver 27 Mhz" msgstr "Δέκτης EX100 27 Mhz" -#: lib/logitech_receiver/i18n.py:30 +#: lib/logitech_receiver/common.py:604 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Μπαταρία: %(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:606 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Μπαταρία: %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:926 +#: lib/logitech_receiver/settings_templates.py:291 +msgid "Disabled" +msgstr "Απενεργοποιημένο" + +#: lib/logitech_receiver/hidpp20.py:927 +msgid "Static" +msgstr "Στατικό" + +#: lib/logitech_receiver/hidpp20.py:928 +msgid "Pulse" +msgstr "Παλμός" + +#: lib/logitech_receiver/hidpp20.py:929 +msgid "Cycle" +msgstr "Κύκλος" + +#: lib/logitech_receiver/hidpp20.py:930 +msgid "Boot" +msgstr "Εκκίνηση" + +#: lib/logitech_receiver/hidpp20.py:931 +msgid "Demo" +msgstr "Ντέμο" + +#: lib/logitech_receiver/hidpp20.py:932 +msgid "Breathe" +msgstr "Αναπνοή" + +#: lib/logitech_receiver/hidpp20.py:933 +msgid "Ripple" +msgstr "Κυματισμός" + +#: lib/logitech_receiver/hidpp20.py:994 +msgid "Unknown Location" +msgstr "Άγνωστη τοποθεσία" + +#: lib/logitech_receiver/hidpp20.py:995 +msgid "Primary" +msgstr "Πρωτεύον" + +#: lib/logitech_receiver/hidpp20.py:996 +msgid "Logo" +msgstr "Λογότυπο" + +#: lib/logitech_receiver/hidpp20.py:997 +msgid "Left Side" +msgstr "Αριστερή Πλεύση" + +#: lib/logitech_receiver/hidpp20.py:998 +msgid "Right Side" +msgstr "Δεξιά Πλεύση" + +#: lib/logitech_receiver/hidpp20.py:999 +msgid "Combined" +msgstr "Συνδυασμένο" + +#: lib/logitech_receiver/hidpp20.py:1000 +msgid "Primary 1" +msgstr "Πρωτεύον 1" + +#: lib/logitech_receiver/hidpp20.py:1001 +msgid "Primary 2" +msgstr "Πρωτεύον 2" + +#: lib/logitech_receiver/hidpp20.py:1002 +msgid "Primary 3" +msgstr "Πρωτεύον 3" + +#: lib/logitech_receiver/hidpp20.py:1003 +msgid "Primary 4" +msgstr "Πρωτεύον 4" + +#: lib/logitech_receiver/hidpp20.py:1004 +msgid "Primary 5" +msgstr "Πρωτεύον 5" + +#: lib/logitech_receiver/hidpp20.py:1005 +msgid "Primary 6" +msgstr "Πρωτεύον 6" + +#: lib/logitech_receiver/i18n.py:28 msgid "empty" msgstr "άδεια" -#: lib/logitech_receiver/i18n.py:31 +#: lib/logitech_receiver/i18n.py:29 msgid "critical" msgstr "κρίσιμη" -#: lib/logitech_receiver/i18n.py:32 +#: lib/logitech_receiver/i18n.py:30 msgid "low" msgstr "χαμηλή" -#: lib/logitech_receiver/i18n.py:33 +#: lib/logitech_receiver/i18n.py:31 msgid "average" msgstr "μέση τιμή" -#: lib/logitech_receiver/i18n.py:34 +#: lib/logitech_receiver/i18n.py:32 msgid "good" msgstr "καλή" -#: lib/logitech_receiver/i18n.py:35 +#: lib/logitech_receiver/i18n.py:33 msgid "full" msgstr "γεμάτη" -#: lib/logitech_receiver/i18n.py:38 +#: lib/logitech_receiver/i18n.py:35 msgid "discharging" msgstr "εκφορτίζεται" -#: lib/logitech_receiver/i18n.py:39 +#: lib/logitech_receiver/i18n.py:36 msgid "recharging" msgstr "επαναφορτίζεται" -#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 +#: lib/logitech_receiver/i18n.py:37 msgid "charging" msgstr "φορτίζεται" -#: lib/logitech_receiver/i18n.py:41 +#: lib/logitech_receiver/i18n.py:38 msgid "not charging" msgstr "χωρίς φόρτιση" -#: lib/logitech_receiver/i18n.py:42 +#: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "σχεδόν γεμάτη" -#: lib/logitech_receiver/i18n.py:43 +#: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "φορτισμένο" -#: lib/logitech_receiver/i18n.py:44 +#: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "αργή επαναφόρτιση" -#: lib/logitech_receiver/i18n.py:45 +#: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "μη έγκυρη μπαταρία" -#: lib/logitech_receiver/i18n.py:46 +#: lib/logitech_receiver/i18n.py:43 msgid "thermal error" msgstr "θερμικό σφάλμα" -#: lib/logitech_receiver/i18n.py:47 +#: lib/logitech_receiver/i18n.py:44 msgid "error" msgstr "σφάλμα" -#: lib/logitech_receiver/i18n.py:48 +#: lib/logitech_receiver/i18n.py:45 msgid "standard" msgstr "πρότυπο" -#: lib/logitech_receiver/i18n.py:49 +#: lib/logitech_receiver/i18n.py:46 msgid "fast" msgstr "γρήγορο" -#: lib/logitech_receiver/i18n.py:50 +#: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "αργό" -#: lib/logitech_receiver/i18n.py:53 +#: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "ορίου χρόνου συσκευής" -#: lib/logitech_receiver/i18n.py:54 +#: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "η συσκευή δεν υποστηρίζεται" -#: lib/logitech_receiver/i18n.py:55 +#: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "πάρα πολλές συσκευές" -#: lib/logitech_receiver/i18n.py:56 +#: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "ορίου χρόνου ακολουθίας" -#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 +#: lib/logitech_receiver/i18n.py:54 msgid "Firmware" msgstr "Firmware" -#: lib/logitech_receiver/i18n.py:60 +#: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Bootloader" -#: lib/logitech_receiver/i18n.py:61 +#: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Υλισμικό" -#: lib/logitech_receiver/i18n.py:62 +#: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "Άλλο" -#: lib/logitech_receiver/i18n.py:65 +#: lib/logitech_receiver/i18n.py:59 msgid "Left Button" msgstr "Αριστερό κουμπί" -#: lib/logitech_receiver/i18n.py:66 +#: lib/logitech_receiver/i18n.py:60 msgid "Right Button" msgstr "Δεξί κουμπί" -#: lib/logitech_receiver/i18n.py:67 +#: lib/logitech_receiver/i18n.py:61 msgid "Middle Button" msgstr "Κεντρικό κουμπί" -#: lib/logitech_receiver/i18n.py:68 +#: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "Πίσω κουμπί" -#: lib/logitech_receiver/i18n.py:69 +#: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "Εμπρός Κουμπί" -#: lib/logitech_receiver/i18n.py:70 +#: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "Κουμπί χειρονομιών ποντικιού" -#: lib/logitech_receiver/i18n.py:71 +#: lib/logitech_receiver/i18n.py:65 msgid "Smart Shift" msgstr "Έξυπνος Διακόπτης" -#: lib/logitech_receiver/i18n.py:72 +#: lib/logitech_receiver/i18n.py:66 msgid "DPI Switch" msgstr "Διακόπτης DPI" -#: lib/logitech_receiver/i18n.py:73 +#: lib/logitech_receiver/i18n.py:67 msgid "Left Tilt" msgstr "Αριστερή κλίση" -#: lib/logitech_receiver/i18n.py:74 +#: lib/logitech_receiver/i18n.py:68 msgid "Right Tilt" msgstr "Δεξιά κλίση" -#: lib/logitech_receiver/i18n.py:75 +#: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "Αριστερό κλικ" -#: lib/logitech_receiver/i18n.py:76 +#: lib/logitech_receiver/i18n.py:70 msgid "Right Click" msgstr "Δεξί κλικ" -#: lib/logitech_receiver/i18n.py:77 +#: lib/logitech_receiver/i18n.py:71 msgid "Mouse Middle Button" msgstr "Μεσαίο κουμπί ποντικιού" -#: lib/logitech_receiver/i18n.py:78 +#: lib/logitech_receiver/i18n.py:72 msgid "Mouse Back Button" msgstr "Πίσω κουμπί ποντικιού" -#: lib/logitech_receiver/i18n.py:79 +#: lib/logitech_receiver/i18n.py:73 msgid "Mouse Forward Button" msgstr "Εμπρός κουμπί ποντικιού" -#: lib/logitech_receiver/i18n.py:80 +#: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "Πλοήγηση με κουμπιά χειρονομιών" -#: lib/logitech_receiver/i18n.py:81 +#: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "Κύλιση ποντικιού Αριστερό κουμπί" -#: lib/logitech_receiver/i18n.py:82 +#: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "Κύλιση ποντικιού Δεξί κουμπί" -#: lib/logitech_receiver/i18n.py:85 +#: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "πατημένο" -#: lib/logitech_receiver/i18n.py:86 +#: lib/logitech_receiver/i18n.py:79 msgid "released" msgstr "αφημένο" -#: lib/logitech_receiver/notifications.py:75 -#: lib/logitech_receiver/notifications.py:126 +#: lib/logitech_receiver/notifications.py:63 +#: lib/logitech_receiver/notifications.py:115 msgid "pairing lock is closed" msgstr "η δικλείδα σύζευξης είναι κλειστή" -#: lib/logitech_receiver/notifications.py:75 -#: lib/logitech_receiver/notifications.py:126 +#: lib/logitech_receiver/notifications.py:63 +#: lib/logitech_receiver/notifications.py:115 msgid "pairing lock is open" msgstr "η δικλείδα σύζευξης είναι ανοιχτή" -#: lib/logitech_receiver/notifications.py:92 +#: lib/logitech_receiver/notifications.py:80 msgid "discovery lock is closed" msgstr "η δικλείδα ανακάλυψης είναι κλειστή" -#: lib/logitech_receiver/notifications.py:92 +#: lib/logitech_receiver/notifications.py:80 msgid "discovery lock is open" msgstr "η δικλείδα ανακάλυψης είναι ανοιχτή" -#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 +#: lib/logitech_receiver/notifications.py:207 msgid "connected" msgstr "συνδεμένη" -#: lib/logitech_receiver/notifications.py:224 +#: lib/logitech_receiver/notifications.py:207 msgid "disconnected" msgstr "αποσυνδεδεμένη" -#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 +#: lib/logitech_receiver/notifications.py:233 msgid "unpaired" msgstr "διαχωρισμένη" -#: lib/logitech_receiver/notifications.py:304 +#: lib/logitech_receiver/notifications.py:280 msgid "powered on" msgstr "ανοιχτή" -#: lib/logitech_receiver/settings.py:750 +#: lib/logitech_receiver/receiver.py:345 +msgid "No paired devices." +msgstr "Δεν έχω συνταιριασμένες συσκευές." + +#: lib/logitech_receiver/receiver.py:347 lib/solaar/ui/window.py:630 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s συζευγμένη συσκευή." +msgstr[1] "%(count)s συζευγμένες συσκευές." + +#: lib/logitech_receiver/settings.py:617 msgid "register" msgstr "καταχώρηση" -#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 +#: lib/logitech_receiver/settings.py:631 lib/logitech_receiver/settings.py:658 msgid "feature" msgstr "χαρακτηριστικό" -#: lib/logitech_receiver/settings_templates.py:139 +#: lib/logitech_receiver/settings_templates.py:144 msgid "Swap Fx function" msgstr "Αντιστροφή λειτουργιών Fx" -#: lib/logitech_receiver/settings_templates.py:140 +#: lib/logitech_receiver/settings_templates.py:147 msgid "" "When set, the F1..F12 keys will activate their special function,\n" "and you must hold the FN key to activate their standard function." @@ -286,7 +388,7 @@ msgstr "" "και πρέπει να κρατήσετε το πλήκτρο FN για να ενεργοποιήσετε την τυπική " "λειτουργία τους." -#: lib/logitech_receiver/settings_templates.py:142 +#: lib/logitech_receiver/settings_templates.py:152 msgid "" "When unset, the F1..F12 keys will activate their standard function,\n" "and you must hold the FN key to activate their special function." @@ -296,29 +398,29 @@ msgstr "" "και πρέπει να κρατήσετε το πλήκτρο FN για να ενεργοποιήσετε την ειδική " "λειτουργία τους." -#: lib/logitech_receiver/settings_templates.py:149 +#: lib/logitech_receiver/settings_templates.py:160 msgid "Hand Detection" msgstr "Ανίχνευση χεριού" -#: lib/logitech_receiver/settings_templates.py:150 +#: lib/logitech_receiver/settings_templates.py:161 msgid "Turn on illumination when the hands hover over the keyboard." msgstr "Άνοιγμα φωτισμού όταν τα χέρια βρεθούν πάνω από το πληκτρολόγιο." -#: lib/logitech_receiver/settings_templates.py:157 +#: lib/logitech_receiver/settings_templates.py:168 msgid "Scroll Wheel Smooth Scrolling" msgstr "Ομαλή κύλιση του τροχού κύλισης" -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:239 -#: lib/logitech_receiver/settings_templates.py:267 +#: lib/logitech_receiver/settings_templates.py:169 +#: lib/logitech_receiver/settings_templates.py:402 +#: lib/logitech_receiver/settings_templates.py:431 msgid "High-sensitivity mode for vertical scroll with the wheel." msgstr "Κατάσταση υψηλής ευαισθησίας για κατακόρυφη ολίσθηση με τον τροχό." -#: lib/logitech_receiver/settings_templates.py:165 +#: lib/logitech_receiver/settings_templates.py:176 msgid "Side Scrolling" msgstr "Πλάγια κύλιση" -#: lib/logitech_receiver/settings_templates.py:167 +#: lib/logitech_receiver/settings_templates.py:178 msgid "" "When disabled, pushing the wheel sideways sends custom button events\n" "instead of the standard side-scrolling events." @@ -327,45 +429,101 @@ msgstr "" "προσαρμοσμένα γεγονότα κουμπιού\n" "αντί των τυπικών γεγονότων πλάγιας ολίσθησης." -#: lib/logitech_receiver/settings_templates.py:177 +#: lib/logitech_receiver/settings_templates.py:188 msgid "Sensitivity (DPI - older mice)" msgstr "Ευαισθησία (DPI - παλαιά ποντίκια)" -#: lib/logitech_receiver/settings_templates.py:178 -#: lib/logitech_receiver/settings_templates.py:712 +#: lib/logitech_receiver/settings_templates.py:189 +#: lib/logitech_receiver/settings_templates.py:958 msgid "Mouse movement sensitivity" msgstr "Ευαισθησία κίνησης ποντικιού" -#: lib/logitech_receiver/settings_templates.py:208 -#: lib/logitech_receiver/settings_templates.py:218 -#: lib/logitech_receiver/settings_templates.py:225 +#: lib/logitech_receiver/settings_templates.py:249 +#: lib/logitech_receiver/settings_templates.py:261 +#: lib/logitech_receiver/settings_templates.py:388 msgid "Backlight" msgstr "Οπίσθιος φωτισμός" -#: lib/logitech_receiver/settings_templates.py:209 -#: lib/logitech_receiver/settings_templates.py:226 +#: lib/logitech_receiver/settings_templates.py:250 +#: lib/logitech_receiver/settings_templates.py:389 msgid "Set illumination time for keyboard." msgstr "Ρύθμιση χρόνου φωτισμού για το πληκτρολόγιο." -#: lib/logitech_receiver/settings_templates.py:219 -msgid "Turn illumination on or off on keyboard." -msgstr "Ενεργοποίηση ή απενεργοποίηση του φωτισμού στο πληκτρολόγιο." +#: lib/logitech_receiver/settings_templates.py:262 +msgid "" +"Illumination level on keyboard. Changes made are only applied in Manual " +"mode." +msgstr "" +"Επίπεδο φωτισμού στο πληκτρολόγιο. Οι αλλαγές που πραγματοποιούνται " +"εφαρμόζονται μόνο στη χειροκίνητη λειτουργία." -#: lib/logitech_receiver/settings_templates.py:237 +#: lib/logitech_receiver/settings_templates.py:293 +msgid "Automatic" +msgstr "Αυτόματο" + +#: lib/logitech_receiver/settings_templates.py:295 +msgid "Manual" +msgstr "Χειροκίνητο" + +#: lib/logitech_receiver/settings_templates.py:297 +msgid "Enabled" +msgstr "Ενεργοποιημένο" + +#: lib/logitech_receiver/settings_templates.py:303 +msgid "Backlight Level" +msgstr "Επίπεδο οπίσθιου φωτισμού" + +#: lib/logitech_receiver/settings_templates.py:304 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "" +"Επίπεδο φωτισμού στο πληκτρολόγιο όταν βρίσκεται σε χειροκίνητη λειτουργία." + +#: lib/logitech_receiver/settings_templates.py:361 +msgid "Backlight Delay Hands Out" +msgstr "Καθυστέρηση οπίσθιου φωτισμού Χέρια Εκτός" + +#: lib/logitech_receiver/settings_templates.py:362 +msgid "" +"Delay in seconds until backlight fades out with hands away from keyboard." +msgstr "" +"Καθυστέρηση σε δευτερόλεπτα μέχρι να σβήσει ο οπίσθιος φωτισμός με τα χέρια " +"μακριά από το πληκτρολόγιο." + +#: lib/logitech_receiver/settings_templates.py:370 +msgid "Backlight Delay Hands In" +msgstr "Καθυστέρηση οπίσθιου φωτισμού Χέρια Eντός" + +#: lib/logitech_receiver/settings_templates.py:371 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "" +"Καθυστέρηση σε δευτερόλεπτα μέχρι να σβήσει ο οπίσθιος φωτισμός με τα χέρια " +"κοντά στο πληκτρολόγιο." + +#: lib/logitech_receiver/settings_templates.py:379 +msgid "Backlight Delay Powered" +msgstr "Καθυστέρηση οπίσθιου φωτισμού Με τροφοδοσία" + +#: lib/logitech_receiver/settings_templates.py:380 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "" +"Καθυστέρηση σε δευτερόλεπτα μέχρι να σβήσει ο οπίσθιος φωτισμός με εξωτερική " +"τροφοδοσία." + +#: lib/logitech_receiver/settings_templates.py:400 msgid "Scroll Wheel High Resolution" msgstr "Τροχός κύλισης υψηλής ανάλυσης" -#: lib/logitech_receiver/settings_templates.py:240 -#: lib/logitech_receiver/settings_templates.py:268 +#: lib/logitech_receiver/settings_templates.py:404 +#: lib/logitech_receiver/settings_templates.py:433 msgid "Set to ignore if scrolling is abnormally fast or slow" msgstr "Ρύθμιση για να αγνοείται εάν η κύλιση είναι ασυνήθιστα γρήγορη ή αργή" -#: lib/logitech_receiver/settings_templates.py:247 -#: lib/logitech_receiver/settings_templates.py:277 +#: lib/logitech_receiver/settings_templates.py:411 +#: lib/logitech_receiver/settings_templates.py:442 msgid "Scroll Wheel Diversion" msgstr "Εκτροπή τροχού κύλισης" -#: lib/logitech_receiver/settings_templates.py:249 +#: lib/logitech_receiver/settings_templates.py:413 msgid "" "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " "Solaar rules but are otherwise ignored)." @@ -373,19 +531,19 @@ msgstr "" "Κάντε τον τροχό κύλισης να στέλνει ειδοποιήσεις LOWRES_WHEEL HID++ (οι " "οποίες ενεργοποιούν τους κανόνες Solaar αλλά αγνοούνται κατά τα άλλα)." -#: lib/logitech_receiver/settings_templates.py:256 +#: lib/logitech_receiver/settings_templates.py:420 msgid "Scroll Wheel Direction" msgstr "Κατεύθυνση τροχού κύλισης" -#: lib/logitech_receiver/settings_templates.py:257 +#: lib/logitech_receiver/settings_templates.py:421 msgid "Invert direction for vertical scroll with wheel." msgstr "Αντιστροφή κατεύθυνσης για κάθετη κύλιση με τον τροχό." -#: lib/logitech_receiver/settings_templates.py:265 +#: lib/logitech_receiver/settings_templates.py:429 msgid "Scroll Wheel Resolution" msgstr "Ανάλυση τροχού κύλισης" -#: lib/logitech_receiver/settings_templates.py:279 +#: lib/logitech_receiver/settings_templates.py:444 msgid "" "Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." @@ -393,21 +551,21 @@ msgstr "" "Κάντε τον τροχό κύλισης να στέλνει ειδοποιήσεις HIRES_WHEEL HID++ (οι οποίες " "ενεργοποιούν τους κανόνες Solaar αλλά αγνοούνται διαφορετικά)." -#: lib/logitech_receiver/settings_templates.py:288 +#: lib/logitech_receiver/settings_templates.py:453 msgid "Sensitivity (Pointer Speed)" msgstr "Ευαισθησία (ταχύτητα δείκτη)" -#: lib/logitech_receiver/settings_templates.py:289 +#: lib/logitech_receiver/settings_templates.py:454 msgid "Speed multiplier for mouse (256 is normal multiplier)." msgstr "" "Πολλαπλασιαστής ταχύτητας για το ποντίκι (256 είναι ο κανονικός " "πολλαπλασιαστής)." -#: lib/logitech_receiver/settings_templates.py:299 +#: lib/logitech_receiver/settings_templates.py:464 msgid "Thumb Wheel Diversion" msgstr "Εκτροπή τροχού αντίχειρα" -#: lib/logitech_receiver/settings_templates.py:301 +#: lib/logitech_receiver/settings_templates.py:466 msgid "" "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." @@ -415,47 +573,50 @@ msgstr "" "Κάντε τον τροχό αντίχειρα να στέλνει ειδοποιήσεις THUMB_WHEEL HID++ (οι " "οποίες ενεργοποιούν τους κανόνες του Solaar αλλά αγνοούνται διαφορετικά)." -#: lib/logitech_receiver/settings_templates.py:310 +#: lib/logitech_receiver/settings_templates.py:475 msgid "Thumb Wheel Direction" msgstr "Κατεύθυνση τροχού αντίχειρα" -#: lib/logitech_receiver/settings_templates.py:311 +#: lib/logitech_receiver/settings_templates.py:476 msgid "Invert thumb wheel scroll direction." msgstr "Αντιστροφή της κατεύθυνσης κύλισης του τροχού αντίχειρα." -#: lib/logitech_receiver/settings_templates.py:319 +#: lib/logitech_receiver/settings_templates.py:496 msgid "Onboard Profiles" msgstr "Ενσωματωμένα Προφίλ" -#: lib/logitech_receiver/settings_templates.py:320 +#: lib/logitech_receiver/settings_templates.py:497 msgid "" -"Enable onboard profiles, which often control report rate and keyboard " -"lighting" +"Enable an onboard profile, which controls report rate, sensitivity, and " +"button actions" msgstr "" -"Ενεργοποίηση ενσωματωμένων προφίλ, τα οποία συχνά ελέγχουν το ρυθμό αναφοράς " -"και το φωτισμό του πληκτρολογίου" +"Ενεργοποιήστε ένα ενσωματωμένο προφίλ, το οποίο ελέγχει το ρυθμό αναφοράς, " +"την ευαισθησία και τις ενέργειες των κουμπιών" -#: lib/logitech_receiver/settings_templates.py:330 -msgid "Polling Rate (ms)" -msgstr "Ρυθμός κλήσεων (ms)" +#: lib/logitech_receiver/settings_templates.py:541 +#: lib/logitech_receiver/settings_templates.py:581 +msgid "Report Rate" +msgstr "Ρυθμός αναφοράς" -#: lib/logitech_receiver/settings_templates.py:332 -msgid "Frequency of device polling, in milliseconds" -msgstr "Συχνότητα κλήσης της συσκευής, σε χιλιοστά του δευτερολέπτου" +#: lib/logitech_receiver/settings_templates.py:543 +#: lib/logitech_receiver/settings_templates.py:583 +msgid "Frequency of device movement reports" +msgstr "Συχνότητα των αναφορών κίνησης της συσκευής" -#: lib/logitech_receiver/settings_templates.py:333 -#: lib/logitech_receiver/settings_templates.py:1046 -#: lib/logitech_receiver/settings_templates.py:1076 +#: lib/logitech_receiver/settings_templates.py:543 +#: lib/logitech_receiver/settings_templates.py:583 +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1326 msgid "May need Onboard Profiles set to Disable to be effective." msgstr "" "Ενδέχεται να χρειαστεί να θέσετε τα Προφίλ Ενσωματωμένου σε Απενεργοποίηση " "για να είναι αποτελεσματικά." -#: lib/logitech_receiver/settings_templates.py:365 +#: lib/logitech_receiver/settings_templates.py:623 msgid "Divert crown events" msgstr "Εκτροπή εκδηλώσεων crown" -#: lib/logitech_receiver/settings_templates.py:366 +#: lib/logitech_receiver/settings_templates.py:624 msgid "" "Make crown send CROWN HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." @@ -463,36 +624,31 @@ msgstr "" "Κάντε το crown να στέλνει ειδοποιήσεις CROWN HID++ (οι οποίες ενεργοποιούν " "τους κανόνες Solaar αλλά αγνοούνται κατά τα άλλα)." -#: lib/logitech_receiver/settings_templates.py:374 +#: lib/logitech_receiver/settings_templates.py:632 msgid "Crown smooth scroll" msgstr "Ομαλή κύλιση Crown" -#: lib/logitech_receiver/settings_templates.py:375 +#: lib/logitech_receiver/settings_templates.py:633 msgid "Set crown smooth scroll" msgstr "Ρύθμιση ομαλής κύλισης Crown" -#: lib/logitech_receiver/settings_templates.py:383 -msgid "Divert G Keys" -msgstr "Εκτροπή πλήκτρων G" +#: lib/logitech_receiver/settings_templates.py:641 +msgid "Divert G and M Keys" +msgstr "Εκτροπή πλήκτρων G και M" -#: lib/logitech_receiver/settings_templates.py:385 +#: lib/logitech_receiver/settings_templates.py:642 msgid "" -"Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " +"Make G and M keys send HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." msgstr "" -"Κάντε τα κλειδιά G να στέλνουν ειδοποιήσεις GKEY HID++ (οι οποίες " +"Κάντε τα πλήκτρα G και M να στέλνουν ειδοποιήσεις HID++ (οι οποίες " "ενεργοποιούν τους κανόνες Solaar αλλά αγνοούνται κατά τα άλλα)." -#: lib/logitech_receiver/settings_templates.py:386 -msgid "May also make M keys and MR key send HID++ notifications" -msgstr "" -"Μπορεί επίσης να κάνει τα κλειδιά M και MR να στέλνουν ειδοποιήσεις HID++" - -#: lib/logitech_receiver/settings_templates.py:402 +#: lib/logitech_receiver/settings_templates.py:656 msgid "Scroll Wheel Ratcheted" msgstr "Τροχός κύλισης Αγκιστρωμένος" -#: lib/logitech_receiver/settings_templates.py:403 +#: lib/logitech_receiver/settings_templates.py:657 msgid "" "Switch the mouse wheel between speed-controlled ratcheting and always " "freespin." @@ -500,19 +656,19 @@ msgstr "" "Αλλάξτε τον τροχό του ποντικιού μεταξύ ρυθμιζόμενης ταχύτητας Αγκιστρωμένου " "και πάντα ελεύθερης περιστροφής." -#: lib/logitech_receiver/settings_templates.py:405 +#: lib/logitech_receiver/settings_templates.py:659 msgid "Freespinning" msgstr "Ελεύθερη περιστροφή" -#: lib/logitech_receiver/settings_templates.py:405 +#: lib/logitech_receiver/settings_templates.py:659 msgid "Ratcheted" msgstr "Αγκιστρωμένη" -#: lib/logitech_receiver/settings_templates.py:412 +#: lib/logitech_receiver/settings_templates.py:666 msgid "Scroll Wheel Ratchet Speed" msgstr "Ταχύτητα Αγκιστρωμένου Τροχού Κύλισης" -#: lib/logitech_receiver/settings_templates.py:414 +#: lib/logitech_receiver/settings_templates.py:668 msgid "" "Use the mouse wheel speed to switch between ratcheted and freespinning.\n" "The mouse wheel is always ratcheted at 50." @@ -521,19 +677,19 @@ msgstr "" "ανάμεσα σε Αγκιστρωμένου και ελεύθερη περιστροφή.\n" "Ο τροχός του ποντικιού είναι πάντα ρυθμισμένος στο 50." -#: lib/logitech_receiver/settings_templates.py:463 +#: lib/logitech_receiver/settings_templates.py:717 msgid "Key/Button Actions" msgstr "Ενέργειες πλήκτρων/κουμπιών" -#: lib/logitech_receiver/settings_templates.py:465 +#: lib/logitech_receiver/settings_templates.py:719 msgid "Change the action for the key or button." msgstr "Αλλαγή της ενέργειας για το πλήκτρο ή το κουμπί." -#: lib/logitech_receiver/settings_templates.py:465 +#: lib/logitech_receiver/settings_templates.py:721 msgid "Overridden by diversion." msgstr "Παρακάμπτεται από εκτροπή." -#: lib/logitech_receiver/settings_templates.py:466 +#: lib/logitech_receiver/settings_templates.py:723 msgid "" "Changing important actions (such as for the left mouse button) can result in " "an unusable system." @@ -541,11 +697,11 @@ msgstr "" "Η αλλαγή σημαντικών ενεργειών (όπως για το αριστερό κουμπί του ποντικιού) " "μπορεί να οδηγήσει σε ένα σύστημα που δεν μπορεί να χρησιμοποιηθεί." -#: lib/logitech_receiver/settings_templates.py:639 +#: lib/logitech_receiver/settings_templates.py:886 msgid "Key/Button Diversion" msgstr "Εκτροπή πλήκτρων/κουμπιών" -#: lib/logitech_receiver/settings_templates.py:640 +#: lib/logitech_receiver/settings_templates.py:887 msgid "" "Make the key or button send HID++ notifications (Diverted) or initiate Mouse " "Gestures or Sliding DPI" @@ -553,36 +709,36 @@ msgstr "" "Κάντε το πλήκτρο ή το κουμπί να στέλνει ειδοποιήσεις HID++ (εκτροπή) ή να " "ξεκινάει χειρονομίες ποντικιού ή συρόμενο DPI" -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 -#: lib/logitech_receiver/settings_templates.py:645 +#: lib/logitech_receiver/settings_templates.py:890 +#: lib/logitech_receiver/settings_templates.py:891 +#: lib/logitech_receiver/settings_templates.py:892 msgid "Diverted" msgstr "Εκτρεπόμενο" -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:890 +#: lib/logitech_receiver/settings_templates.py:891 msgid "Mouse Gestures" msgstr "Χειρονομίες ποντικιού" -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 -#: lib/logitech_receiver/settings_templates.py:645 +#: lib/logitech_receiver/settings_templates.py:890 +#: lib/logitech_receiver/settings_templates.py:891 +#: lib/logitech_receiver/settings_templates.py:892 msgid "Regular" msgstr "Κανονικό" -#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:890 msgid "Sliding DPI" msgstr "Ολίσθαινόμενο DPI" -#: lib/logitech_receiver/settings_templates.py:711 +#: lib/logitech_receiver/settings_templates.py:957 msgid "Sensitivity (DPI)" msgstr "Ευαισθησία (DPI)" -#: lib/logitech_receiver/settings_templates.py:752 +#: lib/logitech_receiver/settings_templates.py:997 msgid "Sensitivity Switching" msgstr "Εναλλαγή ευαισθησίας" -#: lib/logitech_receiver/settings_templates.py:754 +#: lib/logitech_receiver/settings_templates.py:999 msgid "" "Switch the current sensitivity and the remembered sensitivity when the key " "or button is pressed.\n" @@ -593,328 +749,328 @@ msgstr "" "Εάν δεν υπάρχει απομνημονευμένη ευαισθησία, απλά θυμηθείτε την τρέχουσα " "ευαισθησία" -#: lib/logitech_receiver/settings_templates.py:758 +#: lib/logitech_receiver/settings_templates.py:1003 msgid "Off" msgstr "Απενεργοποίηση" -#: lib/logitech_receiver/settings_templates.py:791 +#: lib/logitech_receiver/settings_templates.py:1034 msgid "Disable keys" msgstr "Απενεργοποίηση πλήκτρων" -#: lib/logitech_receiver/settings_templates.py:792 +#: lib/logitech_receiver/settings_templates.py:1035 msgid "Disable specific keyboard keys." msgstr "Απενεργοποίηση συγκεκριμένων πλήκτρων πληκτρολογίου." -#: lib/logitech_receiver/settings_templates.py:795 +#: lib/logitech_receiver/settings_templates.py:1038 #, python-format msgid "Disables the %s key." msgstr "Απενεργοποίηση του πλήκτρου %s." -#: lib/logitech_receiver/settings_templates.py:809 -#: lib/logitech_receiver/settings_templates.py:860 +#: lib/logitech_receiver/settings_templates.py:1051 +#: lib/logitech_receiver/settings_templates.py:1108 msgid "Set OS" msgstr "Ορισμός λειτουργικού συστήματος" -#: lib/logitech_receiver/settings_templates.py:810 -#: lib/logitech_receiver/settings_templates.py:861 +#: lib/logitech_receiver/settings_templates.py:1052 +#: lib/logitech_receiver/settings_templates.py:1109 msgid "Change keys to match OS." msgstr "Αλλάξτε τα πλήκτρα για να ταιριάζουν με το λειτουργικό σύστημα." -#: lib/logitech_receiver/settings_templates.py:873 +#: lib/logitech_receiver/settings_templates.py:1121 msgid "Change Host" msgstr "Αλλαγή κεντρικού υπολογιστή" -#: lib/logitech_receiver/settings_templates.py:874 +#: lib/logitech_receiver/settings_templates.py:1122 msgid "Switch connection to a different host" msgstr "Αλλαγή σύνδεσης σε διαφορετικό κεντρικό υπολογιστή" -#: lib/logitech_receiver/settings_templates.py:900 +#: lib/logitech_receiver/settings_templates.py:1146 msgid "Performs a left click." msgstr "Πραγματοποιεί αριστερό κλικ." -#: lib/logitech_receiver/settings_templates.py:900 +#: lib/logitech_receiver/settings_templates.py:1146 msgid "Single tap" msgstr "Απλό πάτημα" -#: lib/logitech_receiver/settings_templates.py:901 +#: lib/logitech_receiver/settings_templates.py:1147 msgid "Performs a right click." msgstr "Πραγματοποιεί δεξί κλικ." -#: lib/logitech_receiver/settings_templates.py:901 +#: lib/logitech_receiver/settings_templates.py:1147 msgid "Single tap with two fingers" msgstr "Απλό πάτημα με δύο δάχτυλα" -#: lib/logitech_receiver/settings_templates.py:902 +#: lib/logitech_receiver/settings_templates.py:1148 msgid "Single tap with three fingers" msgstr "Απλό πάτημα με τρία δάχτυλα" -#: lib/logitech_receiver/settings_templates.py:906 +#: lib/logitech_receiver/settings_templates.py:1152 msgid "Double tap" msgstr "Διπλό πάτημα" -#: lib/logitech_receiver/settings_templates.py:906 +#: lib/logitech_receiver/settings_templates.py:1152 msgid "Performs a double click." msgstr "Πραγματοποιεί διπλό κλικ." -#: lib/logitech_receiver/settings_templates.py:907 +#: lib/logitech_receiver/settings_templates.py:1153 msgid "Double tap with two fingers" msgstr "Διπλό πάτημα με δύο δάχτυλα" -#: lib/logitech_receiver/settings_templates.py:908 +#: lib/logitech_receiver/settings_templates.py:1154 msgid "Double tap with three fingers" msgstr "Διπλό πάτημα με τρία δάχτυλα" -#: lib/logitech_receiver/settings_templates.py:911 +#: lib/logitech_receiver/settings_templates.py:1157 msgid "Drags items by dragging the finger after double tapping." msgstr "Σύρετε αντικείμενα σύροντας το δάχτυλο μετά από διπλό πάτημα." -#: lib/logitech_receiver/settings_templates.py:911 +#: lib/logitech_receiver/settings_templates.py:1157 msgid "Tap and drag" msgstr "Πάτημα και σύρσιμο" -#: lib/logitech_receiver/settings_templates.py:913 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "Σύρετε αντικείμενα σύροντας τα δάχτυλα μετά από διπλό πάτημα." - -#: lib/logitech_receiver/settings_templates.py:913 +#: lib/logitech_receiver/settings_templates.py:1159 msgid "Tap and drag with two fingers" msgstr "Πάτημα και σύρσιμο με δύο δάχτυλα" -#: lib/logitech_receiver/settings_templates.py:914 +#: lib/logitech_receiver/settings_templates.py:1160 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Σύρετε αντικείμενα σύροντας τα δάχτυλα μετά από διπλό πάτημα." + +#: lib/logitech_receiver/settings_templates.py:1162 msgid "Tap and drag with three fingers" msgstr "Πάτημα και σύρσιμο με τρία δάχτυλα" -#: lib/logitech_receiver/settings_templates.py:917 +#: lib/logitech_receiver/settings_templates.py:1165 +msgid "Suppress tap and edge gestures" +msgstr "Καταστολή χειρονομιών tap και edge" + +#: lib/logitech_receiver/settings_templates.py:1166 msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." msgstr "" "Απενεργοποιεί τις χειρονομίες tap και edge (ισοδύναμο με το πάτημα των " "πλήκτρων Fn+LeftClick)." -#: lib/logitech_receiver/settings_templates.py:917 -msgid "Suppress tap and edge gestures" -msgstr "Καταστολή χειρονομιών tap και edge" - -#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:1168 msgid "Scroll with one finger" msgstr "Κύλιση με ένα δάχτυλο" -#: lib/logitech_receiver/settings_templates.py:918 -#: lib/logitech_receiver/settings_templates.py:919 -#: lib/logitech_receiver/settings_templates.py:922 +#: lib/logitech_receiver/settings_templates.py:1168 +#: lib/logitech_receiver/settings_templates.py:1169 +#: lib/logitech_receiver/settings_templates.py:1172 msgid "Scrolls." msgstr "Κύλιση." -#: lib/logitech_receiver/settings_templates.py:919 -#: lib/logitech_receiver/settings_templates.py:922 +#: lib/logitech_receiver/settings_templates.py:1169 +#: lib/logitech_receiver/settings_templates.py:1172 msgid "Scroll with two fingers" msgstr "Κύλιση με δύο δάχτυλα" -#: lib/logitech_receiver/settings_templates.py:920 +#: lib/logitech_receiver/settings_templates.py:1170 msgid "Scroll horizontally with two fingers" msgstr "Οριζόντια κύλιση με δύο δάχτυλα" -#: lib/logitech_receiver/settings_templates.py:920 +#: lib/logitech_receiver/settings_templates.py:1170 msgid "Scrolls horizontally." msgstr "Κύλιση οριζόντια." -#: lib/logitech_receiver/settings_templates.py:921 +#: lib/logitech_receiver/settings_templates.py:1171 msgid "Scroll vertically with two fingers" msgstr "Κατακόρυφη κύλιση με δύο δάχτυλα" -#: lib/logitech_receiver/settings_templates.py:921 +#: lib/logitech_receiver/settings_templates.py:1171 msgid "Scrolls vertically." msgstr "Κατακόρυφη Κύλιση." -#: lib/logitech_receiver/settings_templates.py:923 +#: lib/logitech_receiver/settings_templates.py:1173 msgid "Inverts the scrolling direction." msgstr "Αναστροφή της κατεύθυνσης κύλισης." -#: lib/logitech_receiver/settings_templates.py:923 +#: lib/logitech_receiver/settings_templates.py:1173 msgid "Natural scrolling" msgstr "Φυσική κύλιση" -#: lib/logitech_receiver/settings_templates.py:924 +#: lib/logitech_receiver/settings_templates.py:1174 msgid "Enables the thumbwheel." msgstr "Ενεργοποίηση του τροχού αντίχειρα." -#: lib/logitech_receiver/settings_templates.py:924 +#: lib/logitech_receiver/settings_templates.py:1174 msgid "Thumbwheel" msgstr "Τροχός Αντίχειρα" -#: lib/logitech_receiver/settings_templates.py:935 -#: lib/logitech_receiver/settings_templates.py:939 +#: lib/logitech_receiver/settings_templates.py:1185 +#: lib/logitech_receiver/settings_templates.py:1189 msgid "Swipe from the top edge" msgstr "Σύρσιμο από την επάνω άκρη" -#: lib/logitech_receiver/settings_templates.py:936 +#: lib/logitech_receiver/settings_templates.py:1186 msgid "Swipe from the left edge" msgstr "Σύρσιμο από την αριστερή άκρη" -#: lib/logitech_receiver/settings_templates.py:937 +#: lib/logitech_receiver/settings_templates.py:1187 msgid "Swipe from the right edge" msgstr "Σύρσιμο από τη δεξιά άκρη" -#: lib/logitech_receiver/settings_templates.py:938 +#: lib/logitech_receiver/settings_templates.py:1188 msgid "Swipe from the bottom edge" msgstr "Σύρσιμο από την κάτω άκρη" -#: lib/logitech_receiver/settings_templates.py:940 +#: lib/logitech_receiver/settings_templates.py:1190 msgid "Swipe two fingers from the left edge" msgstr "Σύρσιμο δύο δάχτυλων από την αριστερή άκρη" -#: lib/logitech_receiver/settings_templates.py:941 +#: lib/logitech_receiver/settings_templates.py:1191 msgid "Swipe two fingers from the right edge" msgstr "Σύρσιμο δύο δάχτυλων από τη δεξιά άκρη" -#: lib/logitech_receiver/settings_templates.py:942 +#: lib/logitech_receiver/settings_templates.py:1192 msgid "Swipe two fingers from the bottom edge" msgstr "Σύρσιμο δύο δάχτυλων από την κάτω άκρη" -#: lib/logitech_receiver/settings_templates.py:943 +#: lib/logitech_receiver/settings_templates.py:1193 msgid "Swipe two fingers from the top edge" msgstr "Σύρσιμο δύο δάχτυλων από την επάνω άκρη" -#: lib/logitech_receiver/settings_templates.py:944 -#: lib/logitech_receiver/settings_templates.py:948 +#: lib/logitech_receiver/settings_templates.py:1194 +#: lib/logitech_receiver/settings_templates.py:1198 msgid "Pinch to zoom out; spread to zoom in." msgstr "Τσιμπήστε για σμίκρυνση, απλώστε για μεγέθυνση." -#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:1194 msgid "Zoom with two fingers." msgstr "Ζουμ με δύο δάχτυλα." -#: lib/logitech_receiver/settings_templates.py:945 +#: lib/logitech_receiver/settings_templates.py:1195 msgid "Pinch to zoom out." msgstr "Τσιμπήστε για σμίκρυνση." -#: lib/logitech_receiver/settings_templates.py:946 +#: lib/logitech_receiver/settings_templates.py:1196 msgid "Spread to zoom in." msgstr "Απλώστε για μεγέθυνση." -#: lib/logitech_receiver/settings_templates.py:947 +#: lib/logitech_receiver/settings_templates.py:1197 msgid "Zoom with three fingers." msgstr "Ζουμ με τρία δάχτυλα." -#: lib/logitech_receiver/settings_templates.py:948 +#: lib/logitech_receiver/settings_templates.py:1198 msgid "Zoom with two fingers" msgstr "Ζουμ με δύο δάχτυλα" -#: lib/logitech_receiver/settings_templates.py:966 +#: lib/logitech_receiver/settings_templates.py:1216 msgid "Pixel zone" msgstr "Ζώνη pixel" -#: lib/logitech_receiver/settings_templates.py:967 +#: lib/logitech_receiver/settings_templates.py:1217 msgid "Ratio zone" msgstr "Ζώνη αναλογίας" -#: lib/logitech_receiver/settings_templates.py:968 +#: lib/logitech_receiver/settings_templates.py:1218 msgid "Scale factor" msgstr "Συντελεστής κλίμακας" -#: lib/logitech_receiver/settings_templates.py:968 +#: lib/logitech_receiver/settings_templates.py:1218 msgid "Sets the cursor speed." msgstr "Ορίζει την ταχύτητα του δρομέα." -#: lib/logitech_receiver/settings_templates.py:972 +#: lib/logitech_receiver/settings_templates.py:1222 msgid "Left" msgstr "Αριστερά" -#: lib/logitech_receiver/settings_templates.py:972 +#: lib/logitech_receiver/settings_templates.py:1222 msgid "Left-most coordinate." msgstr "Αριστερότερη συντεταγμένη." -#: lib/logitech_receiver/settings_templates.py:973 +#: lib/logitech_receiver/settings_templates.py:1223 msgid "Bottom" msgstr "Κάτω" -#: lib/logitech_receiver/settings_templates.py:973 +#: lib/logitech_receiver/settings_templates.py:1223 msgid "Bottom coordinate." msgstr "Κάτωθεν συντεταγμένη." -#: lib/logitech_receiver/settings_templates.py:974 +#: lib/logitech_receiver/settings_templates.py:1224 msgid "Width" msgstr "Πλάτος" -#: lib/logitech_receiver/settings_templates.py:974 +#: lib/logitech_receiver/settings_templates.py:1224 msgid "Width." msgstr "Πλάτος." -#: lib/logitech_receiver/settings_templates.py:975 +#: lib/logitech_receiver/settings_templates.py:1225 msgid "Height" msgstr "Ύψος" -#: lib/logitech_receiver/settings_templates.py:975 +#: lib/logitech_receiver/settings_templates.py:1225 msgid "Height." msgstr "Ύψος." -#: lib/logitech_receiver/settings_templates.py:976 +#: lib/logitech_receiver/settings_templates.py:1226 msgid "Cursor speed." msgstr "Ταχύτητα δρομέα." -#: lib/logitech_receiver/settings_templates.py:976 +#: lib/logitech_receiver/settings_templates.py:1226 msgid "Scale" msgstr "Κλίμακα" -#: lib/logitech_receiver/settings_templates.py:982 +#: lib/logitech_receiver/settings_templates.py:1232 msgid "Gestures" msgstr "Χειρονομίες" -#: lib/logitech_receiver/settings_templates.py:983 +#: lib/logitech_receiver/settings_templates.py:1233 msgid "Tweak the mouse/touchpad behaviour." msgstr "Ρύθμιση της συμπεριφοράς του ποντικιού/του touchpad." -#: lib/logitech_receiver/settings_templates.py:1000 +#: lib/logitech_receiver/settings_templates.py:1249 msgid "Gestures Diversion" msgstr "Χειρονομίες εκτροπής" -#: lib/logitech_receiver/settings_templates.py:1001 +#: lib/logitech_receiver/settings_templates.py:1250 msgid "Divert mouse/touchpad gestures." msgstr "Εκτροπή χειρονομιών του ποντικιού/του touchpad." -#: lib/logitech_receiver/settings_templates.py:1018 +#: lib/logitech_receiver/settings_templates.py:1266 msgid "Gesture params" msgstr "Παράμετροι χειρονομιών" -#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1267 msgid "Change numerical parameters of a mouse/touchpad." msgstr "Αλλαγή των αριθμητικών παραμέτρων ενός ποντικιού/του touchpad." -#: lib/logitech_receiver/settings_templates.py:1044 +#: lib/logitech_receiver/settings_templates.py:1291 msgid "M-Key LEDs" msgstr "Λυχνίες LED M-Key" -#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1293 msgid "Control the M-Key LEDs." msgstr "Έλεγχος των λυχνιών LED του M-Key." -#: lib/logitech_receiver/settings_templates.py:1047 -#: lib/logitech_receiver/settings_templates.py:1077 +#: lib/logitech_receiver/settings_templates.py:1297 +#: lib/logitech_receiver/settings_templates.py:1328 msgid "May need G Keys diverted to be effective." msgstr "" "Μπορεί να χρειαστεί εκτροπή των πλήκτρων G για να είναι αποτελεσματική." -#: lib/logitech_receiver/settings_templates.py:1053 +#: lib/logitech_receiver/settings_templates.py:1303 #, python-format msgid "Lights up the %s key." msgstr "Ανάβει το πλήκτρο %s." -#: lib/logitech_receiver/settings_templates.py:1074 +#: lib/logitech_receiver/settings_templates.py:1322 msgid "MR-Key LED" msgstr "MR-Πλήκτρο LED" -#: lib/logitech_receiver/settings_templates.py:1076 +#: lib/logitech_receiver/settings_templates.py:1324 msgid "Control the MR-Key LED." msgstr "Έλεγχος της λυχνίας LED πλήκτρου MR." -#: lib/logitech_receiver/settings_templates.py:1095 +#: lib/logitech_receiver/settings_templates.py:1345 msgid "Persistent Key/Button Mapping" msgstr "Μόνιμη αντιστοίχιση πλήκτρων/κουμπιών" -#: lib/logitech_receiver/settings_templates.py:1097 +#: lib/logitech_receiver/settings_templates.py:1347 msgid "Permanently change the mapping for the key or button." msgstr "Μόνιμη αλλαγή της αντιστοίχισης για το πλήκτρο ή το κουμπί." -#: lib/logitech_receiver/settings_templates.py:1098 +#: lib/logitech_receiver/settings_templates.py:1349 msgid "" "Changing important keys or buttons (such as for the left mouse button) can " "result in an unusable system." @@ -922,75 +1078,113 @@ msgstr "" "Η αλλαγή σημαντικών πλήκτρων ή κουμπιών (όπως για παράδειγμα του αριστερού " "κουμπιού του ποντικιού) μπορεί να οδηγήσει σε ένα άχρηστο σύστημα." -#: lib/logitech_receiver/settings_templates.py:1157 +#: lib/logitech_receiver/settings_templates.py:1406 msgid "Sidetone" msgstr "Sidetone" -#: lib/logitech_receiver/settings_templates.py:1158 +#: lib/logitech_receiver/settings_templates.py:1407 msgid "Set sidetone level." msgstr "Ορισμός επιπέδου sidetone." -#: lib/logitech_receiver/settings_templates.py:1167 +#: lib/logitech_receiver/settings_templates.py:1416 msgid "Equalizer" msgstr "Ισοσταθμιστής" -#: lib/logitech_receiver/settings_templates.py:1168 +#: lib/logitech_receiver/settings_templates.py:1417 msgid "Set equalizer levels." msgstr "Ρύθμιση επιπέδων ισοσταθμιστή." -#: lib/logitech_receiver/settings_templates.py:1191 +#: lib/logitech_receiver/settings_templates.py:1439 msgid "Hz" msgstr "Hz" -#: lib/logitech_receiver/settings_templates.py:1197 +#: lib/logitech_receiver/settings_templates.py:1445 msgid "Power Management" msgstr "Διαχείριση ενέργειας" -#: lib/logitech_receiver/settings_templates.py:1198 +#: lib/logitech_receiver/settings_templates.py:1446 msgid "Power off in minutes (0 for never)." msgstr "Απενεργοποίηση σε λεπτά (0 για ποτέ)." -#: lib/logitech_receiver/status.py:114 -msgid "No paired devices." -msgstr "Δεν έχω συνταιριασμένες συσκευές." +#: lib/logitech_receiver/settings_templates.py:1457 +msgid "LED Control" +msgstr "Έλεγχος LED" -#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 -#, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s συζευγμένη συσκευή." -msgstr[1] "%(count)s συζευγμένες συσκευές." +#: lib/logitech_receiver/settings_templates.py:1458 +msgid "Switch control of LEDs between device and Solaar" +msgstr "Εναλλαγή ελέγχου των LED μεταξύ συσκευής και Solaar" -#: lib/logitech_receiver/status.py:170 -#, python-format -msgid "Battery: %(level)s" -msgstr "Μπαταρία: %(level)s" +#: lib/logitech_receiver/settings_templates.py:1473 +msgid "LED Zone Effects" +msgstr "Εφέ ζώνης LED" -#: lib/logitech_receiver/status.py:172 -#, python-format -msgid "Battery: %(percent)d%%" -msgstr "Μπαταρία: %(percent)d%%" +#: lib/logitech_receiver/settings_templates.py:1474 +msgid "Set effect for LED Zone" +msgstr "Ρύθμιση εφέ για τη ζώνη LED" -#: lib/logitech_receiver/status.py:184 -#, python-format -msgid "Lighting: %(level)s lux" -msgstr "Φωτισμός: %(level)s lux" +#: lib/logitech_receiver/settings_templates.py:1477 +msgid "Speed" +msgstr "Ταχύτητα" -#: lib/logitech_receiver/status.py:239 -#, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "Μπαταρία: %(level)s (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:1478 +msgid "Period" +msgstr "Περίοδος" -#: lib/logitech_receiver/status.py:241 -#, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "Μπαταρία: %(percent)d%% (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:1479 +msgid "Intensity" +msgstr "Ένταση" -#: lib/solaar/ui/__init__.py:52 +#: lib/logitech_receiver/settings_templates.py:1480 +msgid "Ramp" +msgstr "Κλίμακα" + +#: lib/logitech_receiver/settings_templates.py:1494 +msgid "LEDs" +msgstr "Λυχνίες LED" + +#: lib/solaar/ui/__init__.py:112 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" +"Μια άλλη διεργασία Solaar εκτελείται ήδη, οπότε απλά ανοίξτε το παράθυρό της" + +#: lib/solaar/ui/about.py:38 +msgid "" +"Manages Logitech receivers,\n" +"keyboards, mice, and tablets." +msgstr "" +"Διαχειρίζεται δέκτες Logitech,\n" +"πληκτρολόγια, ποντίκια και τάμπλετ." + +#: lib/solaar/ui/about.py:46 +msgid "Additional Programming" +msgstr "Πρόσθετος προγραμματισμός" + +#: lib/solaar/ui/about.py:47 +msgid "GUI design" +msgstr "Σχεδιασμός GUI" + +#: lib/solaar/ui/about.py:49 +msgid "Testing" +msgstr "Δοκιμές" + +#: lib/solaar/ui/about.py:57 +msgid "Logitech documentation" +msgstr "Τεκμηρίωση Logitech" + +#: lib/solaar/ui/action.py:83 lib/solaar/ui/action.py:87 +#: lib/solaar/ui/window.py:199 +msgid "Unpair" +msgstr "Διαχωρισμός" + +#: lib/solaar/ui/action.py:86 lib/solaar/ui/diversion_rules.py:104 +msgid "Cancel" +msgstr "Ακύρωση" + +#: lib/solaar/ui/common.py:35 msgid "Permissions error" msgstr "Σφάλμα δικαιωμάτων" -#: lib/solaar/ui/__init__.py:54 +#: lib/solaar/ui/common.py:37 #, python-format msgid "" "Found a Logitech receiver or device (%s), but did not have permission to " @@ -999,7 +1193,7 @@ msgstr "" "Βρέθηκε ένας δέκτης ή μια συσκευή Logitech (%s), αλλά δεν είχατε την άδεια " "να την ανοίξετε." -#: lib/solaar/ui/__init__.py:55 +#: lib/solaar/ui/common.py:39 msgid "" "If you've just installed Solaar, try disconnecting the receiver or device " "and then reconnecting it." @@ -1007,11 +1201,11 @@ msgstr "" "Εάν μόλις εγκαταστήσατε το Solaar, δοκιμάστε να αποσυνδέσετε το δέκτη ή τη " "συσκευή και στη συνέχεια να το επανασυνδέσετε." -#: lib/solaar/ui/__init__.py:58 +#: lib/solaar/ui/common.py:42 msgid "Cannot connect to device error" msgstr "Δεν είναι δυνατή η σύνδεση σφάλμα συσκευής" -#: lib/solaar/ui/__init__.py:60 +#: lib/solaar/ui/common.py:44 #, python-format msgid "" "Found a Logitech receiver or device at %s, but encountered an error " @@ -1020,7 +1214,7 @@ msgstr "" "Βρέθηκε ένας δέκτης ή μια συσκευή Logitech στη διεύθυνση %s, αλλά " "παρουσιάστηκε σφάλμα σύνδεσης." -#: lib/solaar/ui/__init__.py:61 +#: lib/solaar/ui/common.py:46 msgid "" "Try disconnecting the device and then reconnecting it or turning it off and " "then on." @@ -1028,340 +1222,306 @@ msgstr "" "Δοκιμάστε να αποσυνδέσετε τη συσκευή και στη συνέχεια να την επανασυνδέσετε " "ή να την απενεργοποιήσετε και στη συνέχεια να την ενεργοποιήσετε." -#: lib/solaar/ui/__init__.py:64 +#: lib/solaar/ui/common.py:49 msgid "Unpairing failed" msgstr "Αποτυχία διαχωρισμού" -#: lib/solaar/ui/__init__.py:66 +#: lib/solaar/ui/common.py:51 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Απέτυχα να διαχωρίσω την συσκευή %{device} από τον %{receiver}." -#: lib/solaar/ui/__init__.py:67 +#: lib/solaar/ui/common.py:53 msgid "The receiver returned an error, with no further details." msgstr "Ο δέκτης επέστρεψε ένα λάθος, χωρίς άλλες πληροφορίες." -#: lib/solaar/ui/__init__.py:177 -msgid "Another Solaar process is already running so just expose its window" -msgstr "" -"Μια άλλη διεργασία Solaar εκτελείται ήδη, οπότε απλά ανοίξτε το παράθυρό της" - -#: lib/solaar/ui/about.py:36 -msgid "" -"Manages Logitech receivers,\n" -"keyboards, mice, and tablets." -msgstr "" -"Διαχειρίζεται δέκτες Logitech,\n" -"πληκτρολόγια, ποντίκια και τάμπλετ." - -#: lib/solaar/ui/about.py:44 -msgid "Additional Programming" -msgstr "Πρόσθετος προγραμματισμός" - -#: lib/solaar/ui/about.py:45 -msgid "GUI design" -msgstr "Σχεδιασμός GUI" - -#: lib/solaar/ui/about.py:47 -msgid "Testing" -msgstr "Δοκιμές" - -#: lib/solaar/ui/about.py:54 -msgid "Logitech documentation" -msgstr "Τεκμηρίωση Logitech" - -#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 -#: lib/solaar/ui/window.py:197 -msgid "Unpair" -msgstr "Διαχωρισμός" - -#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 -msgid "Cancel" -msgstr "Ακύρωση" - -#: lib/solaar/ui/config_panel.py:212 +#: lib/solaar/ui/config_panel.py:231 msgid "Complete - ENTER to change" msgstr "Ολοκλήρωση - ENTER για αλλαγή" -#: lib/solaar/ui/config_panel.py:212 +#: lib/solaar/ui/config_panel.py:231 msgid "Incomplete" msgstr "Ελλιπής" -#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 +#: lib/solaar/ui/config_panel.py:476 lib/solaar/ui/config_panel.py:529 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "%d Τιμή" msgstr[1] "%d τιμές" -#: lib/solaar/ui/config_panel.py:518 +#: lib/solaar/ui/config_panel.py:612 msgid "Changes allowed" msgstr "Επιτρεπτές αλλαγές" -#: lib/solaar/ui/config_panel.py:519 +#: lib/solaar/ui/config_panel.py:613 msgid "No changes allowed" msgstr "Μη επιτρεπτές αλλαγές" -#: lib/solaar/ui/config_panel.py:520 +#: lib/solaar/ui/config_panel.py:614 msgid "Ignore this setting" msgstr "Αγνόηση αυτής της ρύθμισης" -#: lib/solaar/ui/config_panel.py:565 +#: lib/solaar/ui/config_panel.py:659 msgid "Working" msgstr "Εργάζεται" -#: lib/solaar/ui/config_panel.py:568 +#: lib/solaar/ui/config_panel.py:662 msgid "Read/write operation failed." msgstr "Αποτυχία λειτουργίας ανάγνωσης/εγγραφής." -#: lib/solaar/ui/diversion_rules.py:65 +#: lib/solaar/ui/diversion_rules.py:66 msgid "Built-in rules" msgstr "Ενσωματωμένοι κανόνες" -#: lib/solaar/ui/diversion_rules.py:65 +#: lib/solaar/ui/diversion_rules.py:66 msgid "User-defined rules" msgstr "Κανόνες που ορίζονται από το χρήστη" -#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1082 +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:1089 msgid "Rule" msgstr "Κανόνας" -#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 -#: lib/solaar/ui/diversion_rules.py:635 +#: lib/solaar/ui/diversion_rules.py:69 lib/solaar/ui/diversion_rules.py:538 +#: lib/solaar/ui/diversion_rules.py:665 msgid "Sub-rule" msgstr "Υποκανόνας" -#: lib/solaar/ui/diversion_rules.py:70 +#: lib/solaar/ui/diversion_rules.py:71 msgid "[empty]" msgstr "[κενό]" -#: lib/solaar/ui/diversion_rules.py:94 -msgid "Solaar Rule Editor" -msgstr "Συντάκτης κανόνων Solaar" - -#: lib/solaar/ui/diversion_rules.py:141 +#: lib/solaar/ui/diversion_rules.py:95 msgid "Make changes permanent?" msgstr "Να κάνετε τις αλλαγές μόνιμες;" -#: lib/solaar/ui/diversion_rules.py:146 +#: lib/solaar/ui/diversion_rules.py:100 msgid "Yes" msgstr "Ναι" -#: lib/solaar/ui/diversion_rules.py:148 +#: lib/solaar/ui/diversion_rules.py:102 msgid "No" msgstr "Όχι" -#: lib/solaar/ui/diversion_rules.py:153 +#: lib/solaar/ui/diversion_rules.py:107 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "Αν επιλέξετε Όχι, οι αλλαγές θα χαθούν όταν κλείσει το Solaar." -#: lib/solaar/ui/diversion_rules.py:201 +#: lib/solaar/ui/diversion_rules.py:138 +msgid "Solaar Rule Editor" +msgstr "Συντάκτης κανόνων Solaar" + +#: lib/solaar/ui/diversion_rules.py:231 msgid "Save changes" msgstr "Αποθήκευση αλλαγών" -#: lib/solaar/ui/diversion_rules.py:206 +#: lib/solaar/ui/diversion_rules.py:236 msgid "Discard changes" msgstr "Απόρριψη αλλαγών" -#: lib/solaar/ui/diversion_rules.py:372 +#: lib/solaar/ui/diversion_rules.py:397 msgid "Insert here" msgstr "Εισαγωγή εδώ" -#: lib/solaar/ui/diversion_rules.py:374 +#: lib/solaar/ui/diversion_rules.py:399 msgid "Insert above" msgstr "Εισαγωγή επάνω" -#: lib/solaar/ui/diversion_rules.py:376 +#: lib/solaar/ui/diversion_rules.py:401 msgid "Insert below" msgstr "Εισαγωγή από κάτω" -#: lib/solaar/ui/diversion_rules.py:382 +#: lib/solaar/ui/diversion_rules.py:407 msgid "Insert new rule here" msgstr "Εισαγωγή νέου κανόνα εδώ" -#: lib/solaar/ui/diversion_rules.py:384 +#: lib/solaar/ui/diversion_rules.py:409 msgid "Insert new rule above" msgstr "Εισαγωγή νέου κανόνα επάνω" -#: lib/solaar/ui/diversion_rules.py:386 +#: lib/solaar/ui/diversion_rules.py:411 msgid "Insert new rule below" msgstr "Εισαγωγή νέου κανόνα από κάτω" -#: lib/solaar/ui/diversion_rules.py:427 +#: lib/solaar/ui/diversion_rules.py:456 msgid "Paste here" msgstr "Επικόλληση εδώ" -#: lib/solaar/ui/diversion_rules.py:429 +#: lib/solaar/ui/diversion_rules.py:458 msgid "Paste above" msgstr "Επικόλληση επάνω" -#: lib/solaar/ui/diversion_rules.py:431 +#: lib/solaar/ui/diversion_rules.py:460 msgid "Paste below" msgstr "Επικόλληση από κάτω" -#: lib/solaar/ui/diversion_rules.py:437 +#: lib/solaar/ui/diversion_rules.py:466 msgid "Paste rule here" msgstr "Επικόλληση κανόνα εδώ" -#: lib/solaar/ui/diversion_rules.py:439 +#: lib/solaar/ui/diversion_rules.py:468 msgid "Paste rule above" msgstr "Επικόλληση κανόνα επάνω" -#: lib/solaar/ui/diversion_rules.py:441 +#: lib/solaar/ui/diversion_rules.py:470 msgid "Paste rule below" msgstr "Επικόλληση κανόνα από κάτω" -#: lib/solaar/ui/diversion_rules.py:445 +#: lib/solaar/ui/diversion_rules.py:474 msgid "Paste rule" msgstr "Επικόλληση κανόνα" -#: lib/solaar/ui/diversion_rules.py:474 +#: lib/solaar/ui/diversion_rules.py:503 msgid "Flatten" msgstr "Ισοπέδωση" -#: lib/solaar/ui/diversion_rules.py:507 +#: lib/solaar/ui/diversion_rules.py:536 msgid "Insert" msgstr "Εισαγωγή" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:637 -#: lib/solaar/ui/diversion_rules.py:1125 +#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:667 +#: lib/solaar/ui/diversion_rules.py:1129 msgid "Or" msgstr "Ή" -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:636 -#: lib/solaar/ui/diversion_rules.py:1110 +#: lib/solaar/ui/diversion_rules.py:540 lib/solaar/ui/diversion_rules.py:666 +#: lib/solaar/ui/diversion_rules.py:1115 msgid "And" msgstr "Και" -#: lib/solaar/ui/diversion_rules.py:513 +#: lib/solaar/ui/diversion_rules.py:542 msgid "Condition" msgstr "Συνθήκη" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1291 +#: lib/solaar/ui/diversion_rules.py:544 lib/solaar/ui/diversion_rules.py:1290 msgid "Feature" msgstr "Χαρακτηριστικό" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1327 +#: lib/solaar/ui/diversion_rules.py:545 lib/solaar/ui/diversion_rules.py:1325 msgid "Report" msgstr "Αναφορά" -#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1203 +#: lib/solaar/ui/diversion_rules.py:546 lib/solaar/ui/diversion_rules.py:1204 msgid "Process" msgstr "Διεργασία" -#: lib/solaar/ui/diversion_rules.py:518 +#: lib/solaar/ui/diversion_rules.py:547 msgid "Mouse process" msgstr "Διεργασία ποντικιού" -#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1365 +#: lib/solaar/ui/diversion_rules.py:548 lib/solaar/ui/diversion_rules.py:1362 msgid "Modifiers" msgstr "Τροποποιητικοί Παράγοντες" -#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1418 +#: lib/solaar/ui/diversion_rules.py:549 lib/solaar/ui/diversion_rules.py:1414 msgid "Key" msgstr "Πλήκτρο" -#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1460 +#: lib/solaar/ui/diversion_rules.py:550 lib/solaar/ui/diversion_rules.py:1455 msgid "KeyIsDown" msgstr "ΤοΠλήκτροΕίναιΠατημένο" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2249 +#: lib/solaar/ui/diversion_rules.py:551 lib/solaar/ui/diversion_rules.py:2260 msgid "Active" msgstr "Ενεργό" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2207 -#: lib/solaar/ui/diversion_rules.py:2259 lib/solaar/ui/diversion_rules.py:2281 +#: lib/solaar/ui/diversion_rules.py:552 lib/solaar/ui/diversion_rules.py:2217 +#: lib/solaar/ui/diversion_rules.py:2269 lib/solaar/ui/diversion_rules.py:2319 msgid "Device" msgstr "Συσκευή" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +#: lib/solaar/ui/diversion_rules.py:553 lib/solaar/ui/diversion_rules.py:2295 +msgid "Host" +msgstr "ΟικοδεσπότηςΥποδοχέας" + +#: lib/solaar/ui/diversion_rules.py:554 lib/solaar/ui/diversion_rules.py:2337 msgid "Setting" msgstr "Ρύθμιση" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1476 -#: lib/solaar/ui/diversion_rules.py:1525 +#: lib/solaar/ui/diversion_rules.py:555 lib/solaar/ui/diversion_rules.py:1470 +#: lib/solaar/ui/diversion_rules.py:1519 msgid "Test" msgstr "Δοκιμή" -#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1642 +#: lib/solaar/ui/diversion_rules.py:556 lib/solaar/ui/diversion_rules.py:1633 msgid "Test bytes" msgstr "Δοκιμαστικά bytes" -#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1735 +#: lib/solaar/ui/diversion_rules.py:557 lib/solaar/ui/diversion_rules.py:1734 msgid "Mouse Gesture" msgstr "Χειρονομία ποντικιού" -#: lib/solaar/ui/diversion_rules.py:531 +#: lib/solaar/ui/diversion_rules.py:561 msgid "Action" msgstr "Ενέργεια" -#: lib/solaar/ui/diversion_rules.py:533 lib/solaar/ui/diversion_rules.py:1844 +#: lib/solaar/ui/diversion_rules.py:563 lib/solaar/ui/diversion_rules.py:1844 msgid "Key press" msgstr "Πάτημα πλήκτρου" -#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1897 +#: lib/solaar/ui/diversion_rules.py:564 lib/solaar/ui/diversion_rules.py:1895 msgid "Mouse scroll" msgstr "Κύλιση ποντικιού" -#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1948 +#: lib/solaar/ui/diversion_rules.py:565 lib/solaar/ui/diversion_rules.py:1956 msgid "Mouse click" msgstr "Κλικ ποντικιού" -#: lib/solaar/ui/diversion_rules.py:536 +#: lib/solaar/ui/diversion_rules.py:566 msgid "Set" msgstr "Ορισμός" -#: lib/solaar/ui/diversion_rules.py:537 lib/solaar/ui/diversion_rules.py:2019 +#: lib/solaar/ui/diversion_rules.py:567 lib/solaar/ui/diversion_rules.py:2026 msgid "Execute" msgstr "Εκτέλεση" -#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:1157 +#: lib/solaar/ui/diversion_rules.py:568 lib/solaar/ui/diversion_rules.py:1160 msgid "Later" msgstr "Αργότερα" -#: lib/solaar/ui/diversion_rules.py:567 +#: lib/solaar/ui/diversion_rules.py:597 msgid "Insert new rule" msgstr "Εισαγωγή νέου κανόνα" -#: lib/solaar/ui/diversion_rules.py:587 lib/solaar/ui/diversion_rules.py:1685 -#: lib/solaar/ui/diversion_rules.py:1789 lib/solaar/ui/diversion_rules.py:1978 +#: lib/solaar/ui/diversion_rules.py:617 lib/solaar/ui/diversion_rules.py:1681 +#: lib/solaar/ui/diversion_rules.py:1786 lib/solaar/ui/diversion_rules.py:1985 msgid "Delete" msgstr "Διαγραφή" -#: lib/solaar/ui/diversion_rules.py:609 +#: lib/solaar/ui/diversion_rules.py:639 msgid "Negate" msgstr "Αρνηση" -#: lib/solaar/ui/diversion_rules.py:633 +#: lib/solaar/ui/diversion_rules.py:663 msgid "Wrap with" msgstr "Τύλιγμα με" -#: lib/solaar/ui/diversion_rules.py:655 +#: lib/solaar/ui/diversion_rules.py:685 msgid "Cut" msgstr "Αποκοπή" -#: lib/solaar/ui/diversion_rules.py:670 +#: lib/solaar/ui/diversion_rules.py:700 msgid "Paste" msgstr "Επικόλληση" -#: lib/solaar/ui/diversion_rules.py:682 +#: lib/solaar/ui/diversion_rules.py:706 msgid "Copy" msgstr "Αντιγραφή" -#: lib/solaar/ui/diversion_rules.py:1062 +#: lib/solaar/ui/diversion_rules.py:1070 msgid "This editor does not support the selected rule component yet." msgstr "" "Αυτός ο συντάκτης δεν υποστηρίζει ακόμη την επιλεγμένη συνιστώσα κανόνα." -#: lib/solaar/ui/diversion_rules.py:1137 +#: lib/solaar/ui/diversion_rules.py:1140 msgid "Number of seconds to delay." msgstr "Αριθμός δευτερολέπτων καθυστέρησης." -#: lib/solaar/ui/diversion_rules.py:1176 +#: lib/solaar/ui/diversion_rules.py:1178 msgid "Not" msgstr "Μη" -#: lib/solaar/ui/diversion_rules.py:1186 +#: lib/solaar/ui/diversion_rules.py:1187 msgid "X11 active process. For use in X11 only." msgstr "Ενεργή διαδικασία X11. Για χρήση μόνο στο X11." @@ -1373,23 +1533,23 @@ msgstr "Διαδικασία ποντικιού X11. Για χρήση μόνο msgid "MouseProcess" msgstr "ΔιαδικασίαΠοντικιού" -#: lib/solaar/ui/diversion_rules.py:1259 +#: lib/solaar/ui/diversion_rules.py:1258 msgid "Feature name of notification triggering rule processing." msgstr "" "Όνομα χαρακτηριστικού της ειδοποίησης που ενεργοποιεί την επεξεργασία κανόνα." -#: lib/solaar/ui/diversion_rules.py:1307 +#: lib/solaar/ui/diversion_rules.py:1305 msgid "Report number of notification triggering rule processing." msgstr "" "Αριθμός αναφοράς της ειδοποίησης που ενεργοποιεί την επεξεργασία του κανόνα." -#: lib/solaar/ui/diversion_rules.py:1341 +#: lib/solaar/ui/diversion_rules.py:1338 msgid "Active keyboard modifiers. Not always available in Wayland." msgstr "" "Ενεργοί τροποποιούμενοι παράγοντες πληκτρολογίου. Δεν είναι πάντα διαθέσιμο " "στο Wayland." -#: lib/solaar/ui/diversion_rules.py:1382 +#: lib/solaar/ui/diversion_rules.py:1378 msgid "" "Diverted key or button depressed or released.\n" "Use the Key/Button Diversion and Divert G Keys settings to divert keys and " @@ -1399,15 +1559,15 @@ msgstr "" "Χρησιμοποιήστε τις ρυθμίσεις εκτροπής πλήκτρων/κουμπιών και εκτροπής " "πλήκτρων G για να εκτρέψετε πλήκτρα και κουμπιά." -#: lib/solaar/ui/diversion_rules.py:1391 +#: lib/solaar/ui/diversion_rules.py:1387 msgid "Key down" msgstr "Πλήκτρο κάτω" -#: lib/solaar/ui/diversion_rules.py:1394 +#: lib/solaar/ui/diversion_rules.py:1390 msgid "Key up" msgstr "Πλήκτρο πάνω" -#: lib/solaar/ui/diversion_rules.py:1435 +#: lib/solaar/ui/diversion_rules.py:1430 msgid "" "Diverted key or button is currently down.\n" "Use the Key/Button Diversion and Divert G Keys settings to divert keys and " @@ -1417,50 +1577,50 @@ msgstr "" "Χρησιμοποιήστε τις ρυθμίσεις εκτροπής πλήκτρων/κουμπιών και εκτροπής " "πλήκτρων G για να εκτρέψετε πλήκτρα και κουμπιά." -#: lib/solaar/ui/diversion_rules.py:1474 +#: lib/solaar/ui/diversion_rules.py:1468 msgid "Test condition on notification triggering rule processing." msgstr "" "Συνθήκη δοκιμής για την ειδοποίηση που ενεργοποιεί την επεξεργασία κανόνα." -#: lib/solaar/ui/diversion_rules.py:1478 +#: lib/solaar/ui/diversion_rules.py:1472 msgid "Parameter" msgstr "Παράμετρος" -#: lib/solaar/ui/diversion_rules.py:1541 +#: lib/solaar/ui/diversion_rules.py:1534 msgid "begin (inclusive)" msgstr "αρχή ( συμπεριλαμβανομένου)" -#: lib/solaar/ui/diversion_rules.py:1542 +#: lib/solaar/ui/diversion_rules.py:1535 msgid "end (exclusive)" msgstr "τέλος ( αποκλειστικό)" -#: lib/solaar/ui/diversion_rules.py:1551 +#: lib/solaar/ui/diversion_rules.py:1543 msgid "range" msgstr "εύρος" -#: lib/solaar/ui/diversion_rules.py:1553 +#: lib/solaar/ui/diversion_rules.py:1546 msgid "minimum" msgstr "ελάχιστο" -#: lib/solaar/ui/diversion_rules.py:1554 +#: lib/solaar/ui/diversion_rules.py:1547 msgid "maximum" msgstr "μέγιστο" -#: lib/solaar/ui/diversion_rules.py:1556 +#: lib/solaar/ui/diversion_rules.py:1549 #, python-format msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" msgstr "bytes %(0)d έως %(1)d, από %(2)d έως %(3)d" -#: lib/solaar/ui/diversion_rules.py:1561 +#: lib/solaar/ui/diversion_rules.py:1552 lib/solaar/ui/diversion_rules.py:1553 msgid "mask" msgstr "μάσκα" -#: lib/solaar/ui/diversion_rules.py:1562 +#: lib/solaar/ui/diversion_rules.py:1554 #, python-format msgid "bytes %(0)d to %(1)d, mask %(2)d" msgstr "bytes %(0)d έως %(1)d, μάσκα %(2)d" -#: lib/solaar/ui/diversion_rules.py:1572 +#: lib/solaar/ui/diversion_rules.py:1563 msgid "" "Bit or range test on bytes in notification message triggering rule " "processing." @@ -1468,11 +1628,11 @@ msgstr "" "Δοκιμή bit ή εύρους σε bytes στο μήνυμα ειδοποίησης που ενεργοποιεί την " "επεξεργασία κανόνα." -#: lib/solaar/ui/diversion_rules.py:1582 +#: lib/solaar/ui/diversion_rules.py:1573 msgid "type" msgstr "τύπος" -#: lib/solaar/ui/diversion_rules.py:1665 +#: lib/solaar/ui/diversion_rules.py:1661 msgid "" "Mouse gesture with optional initiating button followed by zero or more mouse " "movements." @@ -1480,11 +1640,11 @@ msgstr "" "Χειρονομία ποντικιού με προαιρετικό κουμπί εκκίνησης που ακολουθείται από " "μηδέν ή περισσότερες κινήσεις του ποντικιού." -#: lib/solaar/ui/diversion_rules.py:1670 +#: lib/solaar/ui/diversion_rules.py:1666 msgid "Add movement" msgstr "Προσθήκη κίνησης" -#: lib/solaar/ui/diversion_rules.py:1763 +#: lib/solaar/ui/diversion_rules.py:1760 msgid "" "Simulate a chorded key click or depress or release.\n" "On Wayland requires write access to /dev/uinput." @@ -1492,23 +1652,23 @@ msgstr "" "Προσομοίωση ενός χορδισμένου πλήκτρου κλικ ή πάτημα ή απελευθέρωση.\n" "Στο Wayland απαιτεί πρόσβαση εγγραφής στο /dev/uinput." -#: lib/solaar/ui/diversion_rules.py:1768 +#: lib/solaar/ui/diversion_rules.py:1765 msgid "Add key" msgstr "Προσθήκη πλήκτρου" -#: lib/solaar/ui/diversion_rules.py:1771 +#: lib/solaar/ui/diversion_rules.py:1768 msgid "Click" msgstr "Κλικ" -#: lib/solaar/ui/diversion_rules.py:1774 +#: lib/solaar/ui/diversion_rules.py:1771 msgid "Depress" msgstr "Απελευθέρωση" -#: lib/solaar/ui/diversion_rules.py:1777 +#: lib/solaar/ui/diversion_rules.py:1774 msgid "Release" msgstr "Αποδέσμευση" -#: lib/solaar/ui/diversion_rules.py:1861 +#: lib/solaar/ui/diversion_rules.py:1859 msgid "" "Simulate a mouse scroll.\n" "On Wayland requires write access to /dev/uinput." @@ -1516,7 +1676,7 @@ msgstr "" "Προσομοίωση κύλισης ποντικιού.\n" "Στο Wayland απαιτεί πρόσβαση εγγραφής στο /dev/uinput." -#: lib/solaar/ui/diversion_rules.py:1917 +#: lib/solaar/ui/diversion_rules.py:1915 msgid "" "Simulate a mouse click.\n" "On Wayland requires write access to /dev/uinput." @@ -1524,99 +1684,102 @@ msgstr "" "Προσομοίωση ενός κλικ του ποντικιού.\n" "Στο Wayland απαιτεί πρόσβαση εγγραφής στο /dev/uinput." -#: lib/solaar/ui/diversion_rules.py:1920 +#: lib/solaar/ui/diversion_rules.py:1918 msgid "Button" msgstr "Κουμπί" -#: lib/solaar/ui/diversion_rules.py:1921 -msgid "Count" -msgstr "Πλήθος" +#: lib/solaar/ui/diversion_rules.py:1919 +msgid "Count and Action" +msgstr "Καταμέτρηση και Δράση" -#: lib/solaar/ui/diversion_rules.py:1961 +#: lib/solaar/ui/diversion_rules.py:1968 msgid "Execute a command with arguments." msgstr "Εκτέλεση μιας εντολής με ορίσματα." -#: lib/solaar/ui/diversion_rules.py:1964 +#: lib/solaar/ui/diversion_rules.py:1971 msgid "Add argument" msgstr "Προσθήκη ορίσματος" -#: lib/solaar/ui/diversion_rules.py:2039 +#: lib/solaar/ui/diversion_rules.py:2046 msgid "Toggle" msgstr "Εναλλαγή" -#: lib/solaar/ui/diversion_rules.py:2039 +#: lib/solaar/ui/diversion_rules.py:2047 msgid "True" msgstr "Αληθές" -#: lib/solaar/ui/diversion_rules.py:2040 +#: lib/solaar/ui/diversion_rules.py:2048 msgid "False" msgstr "Ψευδές" -#: lib/solaar/ui/diversion_rules.py:2054 +#: lib/solaar/ui/diversion_rules.py:2061 msgid "Unsupported setting" msgstr "Μη υποστηριζόμενη ρύθμιση" -#: lib/solaar/ui/diversion_rules.py:2212 lib/solaar/ui/diversion_rules.py:2231 -#: lib/solaar/ui/diversion_rules.py:2286 lib/solaar/ui/diversion_rules.py:2528 -#: lib/solaar/ui/diversion_rules.py:2546 +#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2243 +#: lib/solaar/ui/diversion_rules.py:2325 lib/solaar/ui/diversion_rules.py:2568 +#: lib/solaar/ui/diversion_rules.py:2586 msgid "Originating device" msgstr "Συσκευή προέλευσης" -#: lib/solaar/ui/diversion_rules.py:2245 +#: lib/solaar/ui/diversion_rules.py:2256 msgid "Device is active and its settings can be changed." msgstr "Η συσκευή είναι ενεργή και οι ρυθμίσεις της μπορούν να αλλάξουν." -#: lib/solaar/ui/diversion_rules.py:2255 -msgid "Device originated the current notification." -msgstr "Η συσκευή από την οποία προήλθε η τρέχουσα ειδοποίηση." +#: lib/solaar/ui/diversion_rules.py:2265 +msgid "Device that originated the current notification." +msgstr "Συσκευή από την οποία προήλθε η τρέχουσα ειδοποίηση." -#: lib/solaar/ui/diversion_rules.py:2305 +#: lib/solaar/ui/diversion_rules.py:2278 +msgid "Name of host computer." +msgstr "Όνομα του υπολογιστή υποδοχής." + +#: lib/solaar/ui/diversion_rules.py:2345 msgid "Value" msgstr "Τιμή" -#: lib/solaar/ui/diversion_rules.py:2313 +#: lib/solaar/ui/diversion_rules.py:2353 msgid "Item" msgstr "Στοιχείο" -#: lib/solaar/ui/diversion_rules.py:2588 +#: lib/solaar/ui/diversion_rules.py:2627 msgid "Change setting on device" msgstr "Αλλαγή ρύθμισης στη συσκευή" -#: lib/solaar/ui/diversion_rules.py:2605 +#: lib/solaar/ui/diversion_rules.py:2643 msgid "Setting on device" msgstr "Ρύθμιση στη συσκευή" -#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:323 -#: lib/solaar/ui/tray.py:328 lib/solaar/ui/window.py:739 -msgid "offline" -msgstr "εκτός σύνδεσης" +#: lib/solaar/ui/notify.py:118 +msgid "unspecified reason" +msgstr "αδιευκρίνιστος λόγος" -#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 -#: lib/solaar/ui/pair_window.py:288 +#: lib/solaar/ui/pair_window.py:126 lib/solaar/ui/pair_window.py:260 +#: lib/solaar/ui/pair_window.py:296 #, python-format msgid "%(receiver_name)s: pair new device" msgstr "%(receiver_name)s: ζεύξη νέας συσκευής" -#: lib/solaar/ui/pair_window.py:123 +#: lib/solaar/ui/pair_window.py:127 #, python-format msgid "Enter passcode on %(name)s." msgstr "Πληκτρολογήστε τον κωδικό πρόσβασης στο %(name)s." -#: lib/solaar/ui/pair_window.py:126 +#: lib/solaar/ui/pair_window.py:130 #, python-format msgid "Type %(passcode)s and then press the enter key." msgstr "" "Πληκτρολογήστε %(passcode)s και, στη συνέχεια, πατήστε το πλήκτρο enter." -#: lib/solaar/ui/pair_window.py:129 +#: lib/solaar/ui/pair_window.py:133 msgid "left" msgstr "αριστερά" -#: lib/solaar/ui/pair_window.py:129 +#: lib/solaar/ui/pair_window.py:133 msgid "right" msgstr "δεξιά" -#: lib/solaar/ui/pair_window.py:131 +#: lib/solaar/ui/pair_window.py:135 #, python-format msgid "" "Press %(code)s\n" @@ -1625,78 +1788,78 @@ msgstr "" "Πατήστε %(code)s\n" "και στη συνέχεια πατήστε ταυτόχρονα το αριστερό και το δεξί κουμπί." -#: lib/solaar/ui/pair_window.py:188 +#: lib/solaar/ui/pair_window.py:192 msgid "Pairing failed" msgstr "Αποτυχία συνταιρίασματος" -#: lib/solaar/ui/pair_window.py:190 +#: lib/solaar/ui/pair_window.py:194 msgid "Make sure your device is within range, and has a decent battery charge." msgstr "" "Σιγουρέψου ότι η συσκευή είναι εντός εμβέλειας, και έχει επαρκώς φορτισμένη " "μπαταρία." -#: lib/solaar/ui/pair_window.py:192 +#: lib/solaar/ui/pair_window.py:196 msgid "A new device was detected, but it is not compatible with this receiver." msgstr "Εντοπίστηκε μια συσκευή, αλλά δεν είναι συμβατή με αυτόν τον δέκτη." -#: lib/solaar/ui/pair_window.py:194 +#: lib/solaar/ui/pair_window.py:198 msgid "More paired devices than receiver can support." msgstr "" "Περισσότερες συζευγμένες συσκευές από όσες μπορεί να υποστηρίξει ο δέκτης." -#: lib/solaar/ui/pair_window.py:196 +#: lib/solaar/ui/pair_window.py:200 msgid "No further details are available about the error." msgstr "Δεν υπάρχουν άλλες πληροφορίες για το σφάλμα." -#: lib/solaar/ui/pair_window.py:210 +#: lib/solaar/ui/pair_window.py:214 msgid "Found a new device:" msgstr "Βρήκα μια νέα συσκευή:" -#: lib/solaar/ui/pair_window.py:235 +#: lib/solaar/ui/pair_window.py:239 msgid "The wireless link is not encrypted" msgstr "Η ασύρματη σύνδεση είναι χωρίς κρυπτογράφηση" -#: lib/solaar/ui/pair_window.py:264 +#: lib/solaar/ui/pair_window.py:268 msgid "Unifying receivers are only compatible with Unifying devices." msgstr "Οι δέκτες Unifying είναι συμβατοί μόνο με συσκευές Unifying." -#: lib/solaar/ui/pair_window.py:266 +#: lib/solaar/ui/pair_window.py:270 msgid "Bolt receivers are only compatible with Bolt devices." msgstr "Οι δέκτες Bolt είναι συμβατοί μόνο με συσκευές Bolt." -#: lib/solaar/ui/pair_window.py:268 +#: lib/solaar/ui/pair_window.py:272 msgid "Other receivers are only compatible with a few devices." msgstr "Άλλοι δέκτες είναι συμβατοί μόνο με λίγες συσκευές." -#: lib/solaar/ui/pair_window.py:270 +#: lib/solaar/ui/pair_window.py:274 msgid "The device must not be paired with a nearby powered-on receiver." msgstr "" "Η συσκευή δεν πρέπει να είναι συνδεδεμένη με έναν κοντινό ενεργοποιημένο " "δέκτη." -#: lib/solaar/ui/pair_window.py:274 +#: lib/solaar/ui/pair_window.py:278 msgid "Press a pairing button or key until the pairing light flashes quickly." msgstr "" "Πιέστε ένα κουμπί ή πλήκτρο ζεύξης μέχρι να αναβοσβήσει γρήγορα η φωτεινή " "ένδειξη ζεύξης." -#: lib/solaar/ui/pair_window.py:276 +#: lib/solaar/ui/pair_window.py:280 msgid "You may have to first turn the device off and on again." msgstr "" "Ίσως χρειαστεί πρώτα να απενεργοποιήσετε και να ενεργοποιήσετε ξανά τη " "συσκευή." -#: lib/solaar/ui/pair_window.py:278 +#: lib/solaar/ui/pair_window.py:282 msgid "Turn on the device you want to pair." msgstr "Άνοιξε την συσκευή που θέλεις να συνταιριάξεις." -#: lib/solaar/ui/pair_window.py:280 +#: lib/solaar/ui/pair_window.py:284 msgid "If the device is already turned on, turn it off and on again." msgstr "" "Εάν η συσκευή είναι ήδη ενεργοποιημένη, απενεργοποιήστε την και " "ενεργοποιήστε την ξανά." -#: lib/solaar/ui/pair_window.py:283 +#: lib/solaar/ui/pair_window.py:288 #, python-format msgid "" "\n" @@ -1715,7 +1878,7 @@ msgstr[1] "" "\n" "Αυτός ο δέκτης έχει %d ζεύξεις που απομένουν." -#: lib/solaar/ui/pair_window.py:286 +#: lib/solaar/ui/pair_window.py:294 msgid "" "\n" "Cancelling at this point will not use up a pairing." @@ -1724,118 +1887,113 @@ msgstr "" "Η ακύρωση σε αυτό το σημείο δεν θα χρησιμοποιήσει μια αντιστοίχιση." #: lib/solaar/ui/tray.py:58 -msgid "No Logitech device found" -msgstr "Δεν βρέθηκε συσκευή Logitech" +msgid "No supported device found" +msgstr "Δεν βρέθηκε υποστηριζόμενη συσκευή" -#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#: lib/solaar/ui/tray.py:63 lib/solaar/ui/window.py:321 #, python-format msgid "About %s" msgstr "Περί %s" -#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 +#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 #, python-format msgid "Quit %s" msgstr "Έξοδος %s" -#: lib/solaar/ui/tray.py:302 lib/solaar/ui/tray.py:310 +#: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:302 msgid "no receiver" msgstr "χωρίς δέκτη" -#: lib/solaar/ui/tray.py:326 +#: lib/solaar/ui/tray.py:315 lib/solaar/ui/tray.py:320 +msgid "offline" +msgstr "εκτός σύνδεσης" + +#: lib/solaar/ui/tray.py:318 msgid "no status" msgstr "χωρίς κατάσταση" -#: lib/solaar/ui/window.py:96 +#: lib/solaar/ui/window.py:99 msgid "Scanning" msgstr "Σάρωση" -#: lib/solaar/ui/window.py:129 +#: lib/solaar/ui/window.py:132 msgid "Battery" msgstr "Μπαταρία" -#: lib/solaar/ui/window.py:132 +#: lib/solaar/ui/window.py:135 msgid "Wireless Link" msgstr "Ασύρματη ζεύξη" -#: lib/solaar/ui/window.py:136 +#: lib/solaar/ui/window.py:139 msgid "Lighting" msgstr "Φωτισμός" -#: lib/solaar/ui/window.py:170 +#: lib/solaar/ui/window.py:173 msgid "Show Technical Details" msgstr "Εμφάνιση τεχνικών λεπτομερειών" -#: lib/solaar/ui/window.py:186 +#: lib/solaar/ui/window.py:189 msgid "Pair new device" msgstr "Συνταίριασμα νέας συσκευής" -#: lib/solaar/ui/window.py:205 +#: lib/solaar/ui/window.py:207 msgid "Select a device" msgstr "Επέλεξε μια συσκευή" -#: lib/solaar/ui/window.py:322 +#: lib/solaar/ui/window.py:324 msgid "Rule Editor" msgstr "Συντάκτης κανόνων" -#: lib/solaar/ui/window.py:533 +#: lib/solaar/ui/window.py:543 msgid "Path" msgstr "Διαδρομή" -#: lib/solaar/ui/window.py:536 +#: lib/solaar/ui/window.py:546 msgid "USB ID" msgstr "USB ID" -#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 -#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 +#: lib/solaar/ui/window.py:549 lib/solaar/ui/window.py:551 +#: lib/solaar/ui/window.py:566 lib/solaar/ui/window.py:568 msgid "Serial" msgstr "Serial" -#: lib/solaar/ui/window.py:545 +#: lib/solaar/ui/window.py:555 msgid "Index" msgstr "Index" -#: lib/solaar/ui/window.py:547 +#: lib/solaar/ui/window.py:557 msgid "Wireless PID" msgstr "Wireless PID" -#: lib/solaar/ui/window.py:549 +#: lib/solaar/ui/window.py:559 msgid "Product ID" msgstr "Αναγνωριστικό προϊόντος" -#: lib/solaar/ui/window.py:551 +#: lib/solaar/ui/window.py:561 msgid "Protocol" msgstr "Πρωτόκολλο" -#: lib/solaar/ui/window.py:551 +#: lib/solaar/ui/window.py:561 msgid "Unknown" msgstr "Άγνωστο" -#: lib/solaar/ui/window.py:554 -#, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d ms (%(rate_hz)dHz)" - -#: lib/solaar/ui/window.py:554 +#: lib/solaar/ui/window.py:563 msgid "Polling rate" msgstr "Ρυθμός ανανέωσης" -#: lib/solaar/ui/window.py:565 +#: lib/solaar/ui/window.py:570 msgid "Unit ID" msgstr "Unit ID" -#: lib/solaar/ui/window.py:576 -msgid "none" -msgstr "καμία" - -#: lib/solaar/ui/window.py:577 +#: lib/solaar/ui/window.py:584 msgid "Notifications" msgstr "Ειδοποιήσεις" -#: lib/solaar/ui/window.py:621 +#: lib/solaar/ui/window.py:628 msgid "No device paired." msgstr "Δεν υπάρχει αντιστοιχισμένη συσκευή." -#: lib/solaar/ui/window.py:628 +#: lib/solaar/ui/window.py:637 #, python-format msgid "Up to %(max_count)s device can be paired to this receiver." msgid_plural "Up to %(max_count)s devices can be paired to this receiver." @@ -1844,60 +2002,52 @@ msgstr[0] "" msgstr[1] "" "Έως και %(max_count)s συσκευές μπορούν να αντιστοιχιστούν σε αυτόν τον δέκτη." -#: lib/solaar/ui/window.py:634 -msgid "Only one device can be paired to this receiver." -msgstr "Μόνο μία συσκευή μπορεί να συνδεθεί με αυτόν τον δέκτη." - -#: lib/solaar/ui/window.py:638 +#: lib/solaar/ui/window.py:648 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." msgstr[0] "Αυτός ο δέκτης έχει %d ζεύξη που απομένει." msgstr[1] "Αυτός ο δέκτης έχει %d ζεύξεις που απομένουν." -#: lib/solaar/ui/window.py:692 +#: lib/solaar/ui/window.py:705 msgid "Battery Voltage" msgstr "Τάση μπαταρίας" -#: lib/solaar/ui/window.py:694 +#: lib/solaar/ui/window.py:707 msgid "Voltage reported by battery" msgstr "Τάση που αναφέρεται από την μπαταρία" -#: lib/solaar/ui/window.py:696 +#: lib/solaar/ui/window.py:709 msgid "Battery Level" msgstr "Στάθμη μπαταρίας" -#: lib/solaar/ui/window.py:698 +#: lib/solaar/ui/window.py:711 msgid "Approximate level reported by battery" msgstr "Προσεγγιστικό επίπεδο που αναφέρεται από την μπαταρία" -#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 +#: lib/solaar/ui/window.py:718 lib/solaar/ui/window.py:720 msgid "next reported " msgstr "επόμενο αναφερόμενο " -#: lib/solaar/ui/window.py:708 +#: lib/solaar/ui/window.py:721 msgid " and next level to be reported." msgstr " και το επόμενο επίπεδο που θα αναφερθεί." -#: lib/solaar/ui/window.py:713 -msgid "last known" -msgstr "τελευταία γνωστή" - -#: lib/solaar/ui/window.py:724 +#: lib/solaar/ui/window.py:737 msgid "encrypted" msgstr "κρυπτογραφημένη" -#: lib/solaar/ui/window.py:726 +#: lib/solaar/ui/window.py:739 msgid "The wireless link between this device and its receiver is encrypted." msgstr "" "Η ασύρματη ζεύξη μεταξύ αυτής της συσκευής και του δέκτη είναι " "κρυπτογραφημένη." -#: lib/solaar/ui/window.py:728 +#: lib/solaar/ui/window.py:741 msgid "not encrypted" msgstr "χωρίς κρυπτογράφηση" -#: lib/solaar/ui/window.py:732 +#: lib/solaar/ui/window.py:745 msgid "" "The wireless link between this device and its receiver is not encrypted.\n" "This is a security issue for pointing devices, and a major security issue " @@ -1908,17 +2058,58 @@ msgstr "" "Αυτό είναι ένα ζήτημα ασφάλειας για τις συσκευές επισήμανσης και ένα " "σημαντικό ζήτημα ασφάλειας για τις συσκευές εισαγωγής κειμένου." -#: lib/solaar/ui/window.py:748 +#: lib/solaar/ui/window.py:761 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d lux" +#~ msgid "Turn illumination on or off on keyboard." +#~ msgstr "Ενεργοποίηση ή απενεργοποίηση του φωτισμού στο πληκτρολόγιο." + +#~ msgid "Polling Rate (ms)" +#~ msgstr "Ρυθμός κλήσεων (ms)" + +#~ msgid "May also make M keys and MR key send HID++ notifications" +#~ msgstr "" +#~ "Μπορεί επίσης να κάνει τα κλειδιά M και MR να στέλνουν ειδοποιήσεις HID++" + +#, python-format +#~ msgid "Battery: %(level)s" +#~ msgstr "Μπαταρία: %(level)s" + +#, python-format +#~ msgid "Battery: %(percent)d%%" +#~ msgstr "Μπαταρία: %(percent)d%%" + +#, python-format +#~ msgid "Lighting: %(level)s lux" +#~ msgstr "Φωτισμός: %(level)s lux" + +#, python-format +#~ msgid "%(rate)d ms (%(rate_hz)dHz)" +#~ msgstr "%(rate)d ms (%(rate_hz)dHz)" + +#~ msgid "none" +#~ msgstr "καμία" + +#~ msgid "Only one device can be paired to this receiver." +#~ msgstr "Μόνο μία συσκευή μπορεί να συνδεθεί με αυτόν τον δέκτη." + +#~ msgid "last known" +#~ msgstr "τελευταία γνωστή" + #~ msgid " paired devices." #~ msgstr " συνταιριασμένες συσκευές." #~ msgid "1 paired device." #~ msgstr "Μία συνταιριασμένη συσκευή" +#~ msgid "Count" +#~ msgstr "Πλήθος" + +#~ msgid "Device originated the current notification." +#~ msgstr "Η συσκευή από την οποία προήλθε η τρέχουσα ειδοποίηση." + #, python-format #~ msgid "" #~ "Found a Logitech Receiver (%s), but did not have permission to open it." @@ -1943,6 +2134,9 @@ msgstr "%(light_level)d lux" #~ "Εάν μόλις κάνατε εγκατάσταση του Solaar, δοκιμάστε να αφαιρέσετε τον " #~ "δέκτη και να τον ξανασυνδέσετε." +#~ msgid "No Logitech device found" +#~ msgstr "Δεν βρέθηκε συσκευή Logitech" + #~ msgid "No Logitech receiver found" #~ msgstr "Δεν βρέθηκε δέκτης Logitech" diff --git a/po/es.po b/po/es.po index 7f2c1634..b8cf898f 100644 --- a/po/es.po +++ b/po/es.po @@ -3,1931 +3,2097 @@ # This file is distributed under the same license as the Solaar package. # Marc Serra , 2021. # -msgid "" -msgstr "Project-Id-Version: solaar 1.1.7\n" - "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2022-11-16 15:52-0300\n" - "PO-Revision-Date: 2022-11-17 13:56-0300\n" - "Last-Translator: \n" - "Language-Team: none\n" - "Language: es\n" - "MIME-Version: 1.0\n" - "Content-Type: text/plain; charset=UTF-8\n" - "Content-Transfer-Encoding: 8bit\n" - "X-Generator: Poedit 2.3\n" - "Plural-Forms: nplurals=2; plural=(n != 1);\n" +msgid "" +msgstr "" +"Project-Id-Version: solaar 1.1.7\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-12-28 17:40+0100\n" +"PO-Revision-Date: 2023-12-28 18:53+0100\n" +"Last-Translator: Jose Luis Tirado \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.4.1\n" #: lib/logitech_receiver/base_usb.py:46 -msgid "Bolt Receiver" -msgstr "Receptor Bolt" +msgid "Bolt Receiver" +msgstr "Receptor Bolt" #: lib/logitech_receiver/base_usb.py:57 -msgid "Unifying Receiver" -msgstr "Receptor Unifying" +msgid "Unifying Receiver" +msgstr "Receptor Unifying" #: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 #: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 #: lib/logitech_receiver/base_usb.py:114 -msgid "Nano Receiver" -msgstr "Receptor Nano" +msgid "Nano Receiver" +msgstr "Receptor Nano" #: lib/logitech_receiver/base_usb.py:124 -msgid "Lightspeed Receiver" -msgstr "Receptor Lightspeed" +msgid "Lightspeed Receiver" +msgstr "Receptor Lightspeed" #: lib/logitech_receiver/base_usb.py:133 -msgid "EX100 Receiver 27 Mhz" -msgstr "Receptor EX100 27 Mhz" +msgid "EX100 Receiver 27 Mhz" +msgstr "Receptor EX100 27 Mhz" #: lib/logitech_receiver/i18n.py:30 -msgid "empty" -msgstr "vacía" +msgid "empty" +msgstr "vacía" #: lib/logitech_receiver/i18n.py:31 -msgid "critical" -msgstr "crítica" +msgid "critical" +msgstr "crítica" #: lib/logitech_receiver/i18n.py:32 -msgid "low" -msgstr "baja" +msgid "low" +msgstr "baja" #: lib/logitech_receiver/i18n.py:33 -msgid "average" -msgstr "media" +msgid "average" +msgstr "media" #: lib/logitech_receiver/i18n.py:34 -msgid "good" -msgstr "buena" +msgid "good" +msgstr "buena" #: lib/logitech_receiver/i18n.py:35 -msgid "full" -msgstr "llena" +msgid "full" +msgstr "llena" #: lib/logitech_receiver/i18n.py:38 -msgid "discharging" -msgstr "descargando" +msgid "discharging" +msgstr "descargando" #: lib/logitech_receiver/i18n.py:39 -msgid "recharging" -msgstr "recargando" +msgid "recharging" +msgstr "recargando" -#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:718 -msgid "charging" -msgstr "cargando" +#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 +msgid "charging" +msgstr "cargando" #: lib/logitech_receiver/i18n.py:41 -msgid "not charging" -msgstr "no cargando" +msgid "not charging" +msgstr "no cargando" #: lib/logitech_receiver/i18n.py:42 -msgid "almost full" -msgstr "casi llena" +msgid "almost full" +msgstr "casi llena" #: lib/logitech_receiver/i18n.py:43 -msgid "charged" -msgstr "cargado" +msgid "charged" +msgstr "cargado" #: lib/logitech_receiver/i18n.py:44 -msgid "slow recharge" -msgstr "recarga lenta" +msgid "slow recharge" +msgstr "recarga lenta" #: lib/logitech_receiver/i18n.py:45 -msgid "invalid battery" -msgstr "batería no válida" +msgid "invalid battery" +msgstr "batería no válida" #: lib/logitech_receiver/i18n.py:46 -msgid "thermal error" -msgstr "error térmico" +msgid "thermal error" +msgstr "error térmico" #: lib/logitech_receiver/i18n.py:47 -msgid "error" -msgstr "error" +msgid "error" +msgstr "error" #: lib/logitech_receiver/i18n.py:48 -msgid "standard" -msgstr "estándar" +msgid "standard" +msgstr "estándar" #: lib/logitech_receiver/i18n.py:49 -msgid "fast" -msgstr "rápido" +msgid "fast" +msgstr "rápido" #: lib/logitech_receiver/i18n.py:50 -msgid "slow" -msgstr "lento" +msgid "slow" +msgstr "lento" #: lib/logitech_receiver/i18n.py:53 -msgid "device timeout" -msgstr "tiempo agotado para el dispositivo" +msgid "device timeout" +msgstr "tiempo agotado para el dispositivo" #: lib/logitech_receiver/i18n.py:54 -msgid "device not supported" -msgstr "dispositivo no soportado" +msgid "device not supported" +msgstr "dispositivo no soportado" #: lib/logitech_receiver/i18n.py:55 -msgid "too many devices" -msgstr "demasiados dispositivos" +msgid "too many devices" +msgstr "demasiados dispositivos" #: lib/logitech_receiver/i18n.py:56 -msgid "sequence timeout" -msgstr "tiempo agotado de secuencia" +msgid "sequence timeout" +msgstr "tiempo agotado de secuencia" -#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:577 -msgid "Firmware" -msgstr "Firmware" +#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 +msgid "Firmware" +msgstr "Firmware" #: lib/logitech_receiver/i18n.py:60 -msgid "Bootloader" -msgstr "Gestor de arranque" +msgid "Bootloader" +msgstr "Gestor de arranque" #: lib/logitech_receiver/i18n.py:61 -msgid "Hardware" -msgstr "Hardware" +msgid "Hardware" +msgstr "Hardware" #: lib/logitech_receiver/i18n.py:62 -msgid "Other" -msgstr "Otros" +msgid "Other" +msgstr "Otros" #: lib/logitech_receiver/i18n.py:65 -msgid "Left Button" -msgstr "Botón Izquierdo" +msgid "Left Button" +msgstr "Botón Izquierdo" #: lib/logitech_receiver/i18n.py:66 -msgid "Right Button" -msgstr "Botón Derecho" +msgid "Right Button" +msgstr "Botón Derecho" #: lib/logitech_receiver/i18n.py:67 -msgid "Middle Button" -msgstr "Botón Central" +msgid "Middle Button" +msgstr "Botón Central" #: lib/logitech_receiver/i18n.py:68 -msgid "Back Button" -msgstr "Botón Atrás" +msgid "Back Button" +msgstr "Botón Atrás" #: lib/logitech_receiver/i18n.py:69 -msgid "Forward Button" -msgstr "Botón Adelante" +msgid "Forward Button" +msgstr "Botón Adelante" #: lib/logitech_receiver/i18n.py:70 -msgid "Mouse Gesture Button" -msgstr "Botón Gestos Ratón" +msgid "Mouse Gesture Button" +msgstr "Botón Gestos Ratón" #: lib/logitech_receiver/i18n.py:71 -msgid "Smart Shift" -msgstr "Cambio inteligente" +msgid "Smart Shift" +msgstr "Cambio inteligente" #: lib/logitech_receiver/i18n.py:72 -msgid "DPI Switch" -msgstr "Selector DPI" +msgid "DPI Switch" +msgstr "Selector DPI" #: lib/logitech_receiver/i18n.py:73 -msgid "Left Tilt" -msgstr "Inclinación a la Izquierda" +msgid "Left Tilt" +msgstr "Inclinación a la Izquierda" #: lib/logitech_receiver/i18n.py:74 -msgid "Right Tilt" -msgstr "Inclinación a la Derecha" +msgid "Right Tilt" +msgstr "Inclinación a la Derecha" #: lib/logitech_receiver/i18n.py:75 -msgid "Left Click" -msgstr "Click Izquierdo" +msgid "Left Click" +msgstr "Clic Izquierdo" #: lib/logitech_receiver/i18n.py:76 -msgid "Right Click" -msgstr "Click Derecho" +msgid "Right Click" +msgstr "Clic Derecho" #: lib/logitech_receiver/i18n.py:77 -msgid "Mouse Middle Button" -msgstr "Click Central Ratón" +msgid "Mouse Middle Button" +msgstr "Clic Central Ratón" #: lib/logitech_receiver/i18n.py:78 -msgid "Mouse Back Button" -msgstr "Click Atrás Ratón" +msgid "Mouse Back Button" +msgstr "Clic Atrás Ratón" #: lib/logitech_receiver/i18n.py:79 -msgid "Mouse Forward Button" -msgstr "Click Adelante Ratón" +msgid "Mouse Forward Button" +msgstr "Clic Adelante Ratón" #: lib/logitech_receiver/i18n.py:80 -msgid "Gesture Button Navigation" -msgstr "Navegación con Botón de Gestos" +msgid "Gesture Button Navigation" +msgstr "Navegación con Botón de Gestos" #: lib/logitech_receiver/i18n.py:81 -msgid "Mouse Scroll Left Button" -msgstr "Botón Scroll a la Izquierda" +msgid "Mouse Scroll Left Button" +msgstr "Botón desplazamiento a la Izquierda" #: lib/logitech_receiver/i18n.py:82 -msgid "Mouse Scroll Right Button" -msgstr "Botón Scroll a la Derecha" +msgid "Mouse Scroll Right Button" +msgstr "Botón de desplazamiento a la Derecha" #: lib/logitech_receiver/i18n.py:85 -msgid "pressed" -msgstr "pressionado" +msgid "pressed" +msgstr "pulsado" #: lib/logitech_receiver/i18n.py:86 -msgid "released" -msgstr "liberado" +msgid "released" +msgstr "liberado" #: lib/logitech_receiver/notifications.py:75 #: lib/logitech_receiver/notifications.py:126 -msgid "pairing lock is closed" -msgstr "el bloqueo de vinculación está cerrado" +msgid "pairing lock is closed" +msgstr "el bloqueo de vinculación está cerrado" #: lib/logitech_receiver/notifications.py:75 #: lib/logitech_receiver/notifications.py:126 -msgid "pairing lock is open" -msgstr "el bloqueo de vinculación está abierto" +msgid "pairing lock is open" +msgstr "el bloqueo de vinculación está abierto" #: lib/logitech_receiver/notifications.py:92 -msgid "discovery lock is closed" -msgstr "el bloqueo de descubrimiento está cerrado" +msgid "discovery lock is closed" +msgstr "el bloqueo de descubrimiento está cerrado" #: lib/logitech_receiver/notifications.py:92 -msgid "discovery lock is open" -msgstr "el bloqueo de detección está abierto" +msgid "discovery lock is open" +msgstr "el bloqueo de detección está abierto" -#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:120 -msgid "connected" -msgstr "conectado" +#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 +msgid "connected" +msgstr "conectado" #: lib/logitech_receiver/notifications.py:224 -msgid "disconnected" -msgstr "desconectado" +msgid "disconnected" +msgstr "desconectado" -#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:118 -msgid "unpaired" -msgstr "desvinculado" +#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 +msgid "unpaired" +msgstr "desvinculado" #: lib/logitech_receiver/notifications.py:304 -msgid "powered on" -msgstr "alimentado" +msgid "powered on" +msgstr "encendido" -#: lib/logitech_receiver/settings.py:735 -msgid "register" -msgstr "registrar" +#: lib/logitech_receiver/settings.py:750 +msgid "register" +msgstr "registrar" -#: lib/logitech_receiver/settings.py:749 lib/logitech_receiver/settings.py:776 -msgid "feature" -msgstr "característica" +#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 +msgid "feature" +msgstr "característica" #: lib/logitech_receiver/settings_templates.py:139 -msgid "Swap Fx function" -msgstr "Intercambiar función Fx" +msgid "Swap Fx function" +msgstr "Intercambiar función Fx" #: lib/logitech_receiver/settings_templates.py:140 -msgid "When set, the F1..F12 keys will activate their special function,\n" - "and you must hold the FN key to activate their standard function." -msgstr "Al activarse, las teclas F1..F12 activarán sus funciones " - "especiales,\n" - "y debe mantener pulsada la tecla FN para activar su función estándar." +msgid "" +"When set, the F1..F12 keys will activate their special function,\n" +"and you must hold the FN key to activate their standard function." +msgstr "" +"Al activarse, las teclas F1..F12 activarán sus funciones especiales,\n" +"y debe mantener pulsada la tecla FN para activar su función estándar." #: lib/logitech_receiver/settings_templates.py:142 -msgid "When unset, the F1..F12 keys will activate their standard function,\n" - "and you must hold the FN key to activate their special function." -msgstr "Al desactivarse, las teclas F1..F12 activarán sus funciones " - "estándar,\n" - "y debe mantener pulsada la tecla FN para activar su función especial." +msgid "" +"When unset, the F1..F12 keys will activate their standard function,\n" +"and you must hold the FN key to activate their special function." +msgstr "" +"Al desactivarse, las teclas F1..F12 activarán sus funciones estándar,\n" +"y debe mantener pulsada la tecla FN para activar su función especial." #: lib/logitech_receiver/settings_templates.py:149 -msgid "Hand Detection" -msgstr "Detección de manos" +msgid "Hand Detection" +msgstr "Detección de manos" #: lib/logitech_receiver/settings_templates.py:150 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "Encender la iluminación cuando las manos pasen sobre el teclado." +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Encender la iluminación cuando las manos pasen sobre el teclado." #: lib/logitech_receiver/settings_templates.py:157 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "Suavidad Desplazamiento Rueda" +msgid "Scroll Wheel Smooth Scrolling" +msgstr "Desplazamiento suave de la rueda de desplazamiento" #: lib/logitech_receiver/settings_templates.py:158 #: lib/logitech_receiver/settings_templates.py:239 #: lib/logitech_receiver/settings_templates.py:267 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "Modo de alta sensibilidad para desplazamiento vertical con la rueda." +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Modo de alta sensibilidad para desplazamiento vertical con la rueda." #: lib/logitech_receiver/settings_templates.py:165 -msgid "Side Scrolling" -msgstr "Desplazamiento lateral" +msgid "Side Scrolling" +msgstr "Desplazamiento lateral" #: lib/logitech_receiver/settings_templates.py:167 -msgid "When disabled, pushing the wheel sideways sends custom button " - "events\n" - "instead of the standard side-scrolling events." -msgstr "Al desactivarse, presionar la rueda lateralmente envía eventos de " - "botones personalizados\n" - "en vez de los eventos estándar de desplazamiento lateral." +msgid "" +"When disabled, pushing the wheel sideways sends custom button events\n" +"instead of the standard side-scrolling events." +msgstr "" +"Al desactivarse, presionar la rueda lateralmente envía eventos de botones " +"personalizados\n" +"en vez de los eventos estándar de desplazamiento lateral." #: lib/logitech_receiver/settings_templates.py:177 -msgid "Sensitivity (DPI - older mice)" -msgstr "Sensibilidad (DPI - ratones más viejos)" +msgid "Sensitivity (DPI - older mice)" +msgstr "Sensibilidad (DPI - ratones más viejos)" #: lib/logitech_receiver/settings_templates.py:178 -#: lib/logitech_receiver/settings_templates.py:703 -msgid "Mouse movement sensitivity" -msgstr "Sensibildad de movimientro del ratón" +#: lib/logitech_receiver/settings_templates.py:712 +msgid "Mouse movement sensitivity" +msgstr "Sensibildad de movimientro del ratón" #: lib/logitech_receiver/settings_templates.py:208 #: lib/logitech_receiver/settings_templates.py:218 #: lib/logitech_receiver/settings_templates.py:225 -msgid "Backlight" -msgstr "Retroiluminación" +msgid "Backlight" +msgstr "Retroiluminación" #: lib/logitech_receiver/settings_templates.py:209 #: lib/logitech_receiver/settings_templates.py:226 -msgid "Set illumination time for keyboard." -msgstr "Establecer el tiempo de iluminación para el teclado." +msgid "Set illumination time for keyboard." +msgstr "Establecer el tiempo de iluminación para el teclado." #: lib/logitech_receiver/settings_templates.py:219 -msgid "Turn illumination on or off on keyboard." -msgstr "Encencer o apagar la iluminación del teclado." +msgid "Turn illumination on or off on keyboard." +msgstr "Encencer o apagar la iluminación del teclado." #: lib/logitech_receiver/settings_templates.py:237 -msgid "Scroll Wheel High Resolution" -msgstr "Rueda Alta Resolución" +msgid "Scroll Wheel High Resolution" +msgstr "Rueda de Desplazamiento de Alta Resolución" #: lib/logitech_receiver/settings_templates.py:240 #: lib/logitech_receiver/settings_templates.py:268 -msgid "Set to ignore if scrolling is abnormally fast or slow" -msgstr "Establecido para ignorar si el desplazamiento es anormalmente rápido o lento" +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "" +"Establecido para ignorar si el desplazamiento es anormalmente rápido o lento" #: lib/logitech_receiver/settings_templates.py:247 #: lib/logitech_receiver/settings_templates.py:277 -msgid "Scroll Wheel Diversion" -msgstr "Desvio Rueda Desplazamiento" +msgid "Scroll Wheel Diversion" +msgstr "Desvío de la Rueda de Desplazamiento" #: lib/logitech_receiver/settings_templates.py:249 -msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " - "trigger Solaar rules but are otherwise ignored)." -msgstr "Hacer que la rueda de desplazamiento envíe notificaciones LOWRES_WHEEL HID++ (que " - "activan las reglas de Solaar pero se ignoran)." +msgid "" +"Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " +"Solaar rules but are otherwise ignored)." +msgstr "" +"Hacer que la rueda de desplazamiento envíe notificaciones HID++ de " +"LOWRES_WHEEL (que activan reglas de Solaar, pero que, por lo demás, se " +"ignoran)." #: lib/logitech_receiver/settings_templates.py:256 -msgid "Scroll Wheel Direction" -msgstr "Dirección Rueda Desplazamiento" +msgid "Scroll Wheel Direction" +msgstr "Dirección de la Rueda de Desplazamiento" #: lib/logitech_receiver/settings_templates.py:257 -msgid "Invert direction for vertical scroll with wheel." -msgstr "Invertir dirección para desplazamiento vertical con la rueda." +msgid "Invert direction for vertical scroll with wheel." +msgstr "Invertir dirección para desplazamiento vertical con la rueda." #: lib/logitech_receiver/settings_templates.py:265 -msgid "Scroll Wheel Resolution" -msgstr "Resolución Rueda Desplazamiento" +msgid "Scroll Wheel Resolution" +msgstr "Resolución de la Rueda de Desplazamiento" #: lib/logitech_receiver/settings_templates.py:279 -msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " - "trigger Solaar rules but are otherwise ignored)." -msgstr "Hacer que la rueda de desplazamiento envíe notificaciones HIRES_WHEEL HID++ (que " - "activan las reglas de Solaar pero se ignoran)." +msgid "" +"Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"Hacer que la rueda de desplazamiento envíe notificaciones HID++ de " +"HIRES_WHEEL (que activan las reglas de Solaar, pero que, por lo demás, se " +"ignoran)." #: lib/logitech_receiver/settings_templates.py:288 -msgid "Sensitivity (Pointer Speed)" -msgstr "Sensibilidad (Velocidad del puntero)" +msgid "Sensitivity (Pointer Speed)" +msgstr "Sensibilidad (Velocidad del puntero)" #: lib/logitech_receiver/settings_templates.py:289 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "Multiplicador de velocidad para el ratón (256 es un multiplicador " - "normal)" +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "" +"Multiplicador de velocidad para el ratón (256 es un multiplicador normal)" #: lib/logitech_receiver/settings_templates.py:299 -msgid "Thumb Wheel Diversion" -msgstr "Desvío de la Rueda del Pulgar" +msgid "Thumb Wheel Diversion" +msgstr "Desvío de la Rueda del Pulgar" #: lib/logitech_receiver/settings_templates.py:301 -msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." -msgstr "Hacer que la ruedecilla envíe notificaciones THUMB_WHEEL HID++ (que desencadenan " - "reglas de Solaar, pero por lo demás se ignoran)." +msgid "" +"Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"Hacer que la rueda del pulgar envíe notificaciones THUMB_WHEEL HID++ (que " +"desencadenan reglas de Solaar, pero que, por lo demás, se ignoran)." #: lib/logitech_receiver/settings_templates.py:310 -msgid "Thumb Wheel Direction" -msgstr "Dirección de la Rueda del Pulgar" +msgid "Thumb Wheel Direction" +msgstr "Dirección de la Rueda del Pulgar" #: lib/logitech_receiver/settings_templates.py:311 -msgid "Invert thumb wheel scroll direction." -msgstr "Invertir la dirección de desplazamiento de la rueda del pulgar." +msgid "Invert thumb wheel scroll direction." +msgstr "Invertir la dirección de desplazamiento de la rueda del pulgar." #: lib/logitech_receiver/settings_templates.py:319 -msgid "Onboard Profiles" -msgstr "Perfiles a bordo" +msgid "Onboard Profiles" +msgstr "Perfiles integrados" #: lib/logitech_receiver/settings_templates.py:320 -msgid "Enable onboard profiles, which often control report rate and " - "keyboard lighting" -msgstr "Habilite los perfiles integrados, que a menudo controlan la tasa de informes y " - "iluminación del teclado" +msgid "" +"Enable onboard profiles, which often control report rate and keyboard " +"lighting" +msgstr "" +"Habilite los perfiles integrados, que a menudo controlan la tasa de informes " +"y iluminación del teclado" #: lib/logitech_receiver/settings_templates.py:330 -msgid "Polling Rate (ms)" -msgstr "Tasa de Sondeo (ms)" +msgid "Polling Rate (ms)" +msgstr "Tasa de Sondeo (ms)" #: lib/logitech_receiver/settings_templates.py:332 -msgid "Frequency of device polling, in milliseconds" -msgstr "Frecuencia de sondeo del dispositivo, en milisegundos" +msgid "Frequency of device polling, in milliseconds" +msgstr "Frecuencia de sondeo del dispositivo, en milisegundos" #: lib/logitech_receiver/settings_templates.py:333 -#: lib/logitech_receiver/settings_templates.py:1026 -#: lib/logitech_receiver/settings_templates.py:1054 -msgid "May need Onboard Profiles set to Disable to be effective." -msgstr "Es posible que los Perfiles integrados se desactiven para que sean efectivos." +#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" +"Es posible que los Perfiles integrados se desactiven para que sean efectivos." -#: lib/logitech_receiver/settings_templates.py:363 -msgid "Divert crown events" -msgstr "Desviar eventos de la corona" +#: lib/logitech_receiver/settings_templates.py:365 +msgid "Divert crown events" +msgstr "Desviar eventos de la corona" -#: lib/logitech_receiver/settings_templates.py:364 -msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "Hacer que la corona envíe notificaciones de CROWN HID ++ (que " - "activan las reglas de Solaar, de lo contrario se ignoran)." +#: lib/logitech_receiver/settings_templates.py:366 +msgid "" +"Make crown send CROWN HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"Hacer que la corona envíe notificaciones de CROWN HID ++ (que activan las " +"reglas de Solaar, pero que, por lo demás, se ignoran)." -#: lib/logitech_receiver/settings_templates.py:372 -msgid "Crown smooth scroll" -msgstr "Desplazamiento suave de la corona" +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Crown smooth scroll" +msgstr "Desplazamiento suave de la corona" -#: lib/logitech_receiver/settings_templates.py:373 -msgid "Set crown smooth scroll" -msgstr "Establecer desplazamiento suave de la corona" - -#: lib/logitech_receiver/settings_templates.py:381 -msgid "Divert G Keys" -msgstr "Desviar Teclas G" +#: lib/logitech_receiver/settings_templates.py:375 +msgid "Set crown smooth scroll" +msgstr "Establecer desplazamiento suave de la corona" #: lib/logitech_receiver/settings_templates.py:383 -msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "Hacer que las teclas G envíen notificaciones GKEY HID ++ (que " - "activan las reglas de Solaar, de otra forma, se ignoran)." +msgid "Divert G Keys" +msgstr "Desviar Teclas G" -#: lib/logitech_receiver/settings_templates.py:384 -msgid "May also make M keys and MR key send HID++ notifications" -msgstr "También puede hacer que las teclas M y la tecla MR envíen notificaciones HID++" +#: lib/logitech_receiver/settings_templates.py:385 +msgid "" +"Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"Hacer que las teclas G envíen notificaciones GKEY HID ++ (que activan las " +"reglas de Solaar, pero que, por lo demás, se ignoran)." -#: lib/logitech_receiver/settings_templates.py:399 -msgid "Scroll Wheel Ratcheted" -msgstr "Rueda de desplazamiento con trinquete" - -#: lib/logitech_receiver/settings_templates.py:400 -msgid "Switch the mouse wheel between speed-controlled ratcheting and " - "always freespin." -msgstr "Cambie la rueda del ratón entre trinquete controlado por velocidad y " - "siempre giro libre." +#: lib/logitech_receiver/settings_templates.py:386 +msgid "May also make M keys and MR key send HID++ notifications" +msgstr "También puede hacer que las teclas M y MR envíen notificaciones HID++" #: lib/logitech_receiver/settings_templates.py:402 -msgid "Freespinning" -msgstr "Giro libre" +msgid "Scroll Wheel Ratcheted" +msgstr "Rueda de desplazamiento con carraca" -#: lib/logitech_receiver/settings_templates.py:402 -msgid "Ratcheted" -msgstr "Trinquete" +#: lib/logitech_receiver/settings_templates.py:403 +msgid "" +"Switch the mouse wheel between speed-controlled ratcheting and always " +"freespin." +msgstr "" +"Cambie la rueda del ratón entre carraca controlada por velocidad y siempre " +"giro libre." -#: lib/logitech_receiver/settings_templates.py:409 -msgid "Scroll Wheel Ratchet Speed" -msgstr "Rueda de desplazamiento Velocidad de trinquete" +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Freespinning" +msgstr "Giro libre" -#: lib/logitech_receiver/settings_templates.py:411 -msgid "Use the mouse wheel speed to switch between ratcheted and " - "freespinning.\n" - "The mouse wheel is always ratcheted at 50." -msgstr "Utilice la velocidad de la rueda del ratón para cambiar entre trinquete y " - "giro libre.\n" - "La rueda del mouse siempre está ajustada a 50." +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Ratcheted" +msgstr "Carraca" -#: lib/logitech_receiver/settings_templates.py:460 -msgid "Key/Button Actions" -msgstr "Acciones de Tecla/Botón" +#: lib/logitech_receiver/settings_templates.py:412 +msgid "Scroll Wheel Ratchet Speed" +msgstr "Rueda de desplazamiento Velocidad de carraca" -#: lib/logitech_receiver/settings_templates.py:462 -msgid "Change the action for the key or button." -msgstr "Cambiar la acción para la tecla o botón." - -#: lib/logitech_receiver/settings_templates.py:462 -msgid "Overridden by diversion." -msgstr "Anulado por la diversión." +#: lib/logitech_receiver/settings_templates.py:414 +msgid "" +"Use the mouse wheel speed to switch between ratcheted and freespinning.\n" +"The mouse wheel is always ratcheted at 50." +msgstr "" +"Utilice la velocidad de la rueda del ratón para cambiar entre carraca y giro " +"libre.\n" +"La rueda del mouse siempre está ajustada a 50." #: lib/logitech_receiver/settings_templates.py:463 -msgid "Changing important actions (such as for the left mouse button) can " - "result in an unusable system." -msgstr "Cambiar acciones importantes (por ejemplo el botón izquierdo del " - "ratón) puede dejar su sistema inutilizable." +msgid "Key/Button Actions" +msgstr "Acciones de Tecla/Botón" -#: lib/logitech_receiver/settings_templates.py:632 -msgid "Key/Button Diversion" -msgstr "Desvio de Tecla/Botón" +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Change the action for the key or button." +msgstr "Cambiar la acción para la tecla o botón." -#: lib/logitech_receiver/settings_templates.py:633 -msgid "Make the key or button send HID++ notifications (Diverted) or " - "initiate Mouse Gestures or Sliding DPI" -msgstr "Haga que la tecla o el botón envíe notificaciones HID++ (Desviado) o " - "iniciar gestos de mouse o DPI deslizante" +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Overridden by diversion." +msgstr "Anulado por la diversión." -#: lib/logitech_receiver/settings_templates.py:636 -#: lib/logitech_receiver/settings_templates.py:637 -#: lib/logitech_receiver/settings_templates.py:638 -msgid "Diverted" -msgstr "Desviado" +#: lib/logitech_receiver/settings_templates.py:466 +msgid "" +"Changing important actions (such as for the left mouse button) can result in " +"an unusable system." +msgstr "" +"Cambiar acciones importantes (por ejemplo el botón izquierdo del ratón) " +"puede dejar su sistema inutilizable." -#: lib/logitech_receiver/settings_templates.py:636 -#: lib/logitech_receiver/settings_templates.py:637 -msgid "Mouse Gestures" -msgstr "Gestos de Ratón" +#: lib/logitech_receiver/settings_templates.py:639 +msgid "Key/Button Diversion" +msgstr "Desvio de Tecla/Botón" -#: lib/logitech_receiver/settings_templates.py:636 -#: lib/logitech_receiver/settings_templates.py:637 -#: lib/logitech_receiver/settings_templates.py:638 -msgid "Regular" -msgstr "Normal" +#: lib/logitech_receiver/settings_templates.py:640 +msgid "" +"Make the key or button send HID++ notifications (Diverted) or initiate Mouse " +"Gestures or Sliding DPI" +msgstr "" +"Haga que la tecla o el botón envíe notificaciones HID++ (Desviado) o iniciar " +"gestos de mouse o DPI deslizante" -#: lib/logitech_receiver/settings_templates.py:636 -msgid "Sliding DPI" -msgstr "DPI deslizante" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Diverted" +msgstr "Desviado" -#: lib/logitech_receiver/settings_templates.py:702 -msgid "Sensitivity (DPI)" -msgstr "Sensibilidad (PPP)" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +msgid "Mouse Gestures" +msgstr "Gestos de Ratón" -#: lib/logitech_receiver/settings_templates.py:742 -msgid "Sensitivity Switching" -msgstr "Cambio de sensibilidad" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Regular" +msgstr "Normal" -#: lib/logitech_receiver/settings_templates.py:744 -msgid "Switch the current sensitivity and the remembered sensitivity when " - "the key or button is pressed.\n" - "If there is no remembered sensitivity, just remember the current " - "sensitivity" -msgstr "Cambie la sensibilidad actual y la sensibilidad recordada cuando " - "se presiona la tecla o el botón.\n" - "Si no hay sensibilidad recordada, solo recuerde la corriente " - "sensibilidad" +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Sliding DPI" +msgstr "DPI deslizante" -#: lib/logitech_receiver/settings_templates.py:748 -msgid "Off" -msgstr "Off" +#: lib/logitech_receiver/settings_templates.py:711 +msgid "Sensitivity (DPI)" +msgstr "Sensibilidad (PPP)" -#: lib/logitech_receiver/settings_templates.py:779 -msgid "Disable keys" -msgstr "Desactivar teclas" +#: lib/logitech_receiver/settings_templates.py:752 +msgid "Sensitivity Switching" +msgstr "Cambio de sensibilidad" -#: lib/logitech_receiver/settings_templates.py:780 -msgid "Disable specific keyboard keys." -msgstr "Desactivar teclas específicas del teclado." +#: lib/logitech_receiver/settings_templates.py:754 +msgid "" +"Switch the current sensitivity and the remembered sensitivity when the key " +"or button is pressed.\n" +"If there is no remembered sensitivity, just remember the current sensitivity" +msgstr "" +"Cambie la sensibilidad actual y la sensibilidad recordada cuando se presiona " +"la tecla o el botón.\n" +"Si no hay sensibilidad recordada, solo recuerde la corriente sensibilidad" -#: lib/logitech_receiver/settings_templates.py:783 +#: lib/logitech_receiver/settings_templates.py:758 +msgid "Off" +msgstr "Off" + +#: lib/logitech_receiver/settings_templates.py:791 +msgid "Disable keys" +msgstr "Desactivar teclas" + +#: lib/logitech_receiver/settings_templates.py:792 +msgid "Disable specific keyboard keys." +msgstr "Desactivar teclas específicas del teclado." + +#: lib/logitech_receiver/settings_templates.py:795 #, python-format -msgid "Disables the %s key." -msgstr "Desactiva la tecla %s." +msgid "Disables the %s key." +msgstr "Desactiva la tecla %s." -#: lib/logitech_receiver/settings_templates.py:796 -#: lib/logitech_receiver/settings_templates.py:844 -msgid "Set OS" -msgstr "Especificar SO" +#: lib/logitech_receiver/settings_templates.py:809 +#: lib/logitech_receiver/settings_templates.py:860 +msgid "Set OS" +msgstr "Especificar SO" -#: lib/logitech_receiver/settings_templates.py:797 -#: lib/logitech_receiver/settings_templates.py:845 -msgid "Change keys to match OS." -msgstr "Cambiar teclas para coincidir son el S.O." +#: lib/logitech_receiver/settings_templates.py:810 +#: lib/logitech_receiver/settings_templates.py:861 +msgid "Change keys to match OS." +msgstr "Cambiar teclas para coincidir son el S.O." -#: lib/logitech_receiver/settings_templates.py:857 -msgid "Change Host" -msgstr "Cambiar Host" +#: lib/logitech_receiver/settings_templates.py:873 +msgid "Change Host" +msgstr "Cambiar Equipo" -#: lib/logitech_receiver/settings_templates.py:858 -msgid "Switch connection to a different host" -msgstr "Cambiar conexión a un host diferente" - -#: lib/logitech_receiver/settings_templates.py:883 -msgid "Performs a left click." -msgstr "Realiza un click izquierdo." - -#: lib/logitech_receiver/settings_templates.py:883 -msgid "Single tap" -msgstr "Un toque" - -#: lib/logitech_receiver/settings_templates.py:884 -msgid "Performs a right click." -msgstr "Realiza un click derecho." - -#: lib/logitech_receiver/settings_templates.py:884 -msgid "Single tap with two fingers" -msgstr "Un toque con dos dedos" - -#: lib/logitech_receiver/settings_templates.py:885 -msgid "Single tap with three fingers" -msgstr "Un toque con tres dedos" - -#: lib/logitech_receiver/settings_templates.py:889 -msgid "Double tap" -msgstr "Doble toque" - -#: lib/logitech_receiver/settings_templates.py:889 -msgid "Performs a double click." -msgstr "Realiza un click doble." - -#: lib/logitech_receiver/settings_templates.py:890 -msgid "Double tap with two fingers" -msgstr "Doble toque con dos dedos" - -#: lib/logitech_receiver/settings_templates.py:891 -msgid "Double tap with three fingers" -msgstr "Doble toque con tres dedos" - -#: lib/logitech_receiver/settings_templates.py:894 -msgid "Drags items by dragging the finger after double tapping." -msgstr "Arrastra elementos arrastrando el dedo después de tocar dos veces." - -#: lib/logitech_receiver/settings_templates.py:894 -msgid "Tap and drag" -msgstr "Tocar y arrastrar" - -#: lib/logitech_receiver/settings_templates.py:896 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "Arrastra elementos arrastrando los dedos después de tocar dos veces." - -#: lib/logitech_receiver/settings_templates.py:896 -msgid "Tap and drag with two fingers" -msgstr "Tocar y arrastrar con dos dedos" - -#: lib/logitech_receiver/settings_templates.py:897 -msgid "Tap and drag with three fingers" -msgstr "Tocar y arrastrar con tres dedos" +#: lib/logitech_receiver/settings_templates.py:874 +msgid "Switch connection to a different host" +msgstr "Cambiar la conexión a un equipo diferente" #: lib/logitech_receiver/settings_templates.py:900 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "Desactiva los gestos de toque y borde (equivalente a pulsar " - "FN+LeftClick)." +msgid "Performs a left click." +msgstr "Realiza un clic izquierdo." #: lib/logitech_receiver/settings_templates.py:900 -msgid "Suppress tap and edge gestures" -msgstr "Elimina los gestos de toque y borde" +msgid "Single tap" +msgstr "Un toque" #: lib/logitech_receiver/settings_templates.py:901 -msgid "Scroll with one finger" -msgstr "Desplaza con un dedo" +msgid "Performs a right click." +msgstr "Realiza un click derecho." #: lib/logitech_receiver/settings_templates.py:901 -#: lib/logitech_receiver/settings_templates.py:902 -#: lib/logitech_receiver/settings_templates.py:905 -msgid "Scrolls." -msgstr "Desplazamiento." +msgid "Single tap with two fingers" +msgstr "Un toque con dos dedos" #: lib/logitech_receiver/settings_templates.py:902 -#: lib/logitech_receiver/settings_templates.py:905 -msgid "Scroll with two fingers" -msgstr "Desplaza con dos dedos" - -#: lib/logitech_receiver/settings_templates.py:903 -msgid "Scroll horizontally with two fingers" -msgstr "Desplaza horizontalmente con dos dedos" - -#: lib/logitech_receiver/settings_templates.py:903 -msgid "Scrolls horizontally." -msgstr "Desplaza horizontalmente." - -#: lib/logitech_receiver/settings_templates.py:904 -msgid "Scroll vertically with two fingers" -msgstr "Desplazar verticalmente con dos dedos" - -#: lib/logitech_receiver/settings_templates.py:904 -msgid "Scrolls vertically." -msgstr "Desplaza verticalmente." +msgid "Single tap with three fingers" +msgstr "Un toque con tres dedos" #: lib/logitech_receiver/settings_templates.py:906 -msgid "Inverts the scrolling direction." -msgstr "Invierte la dirección de desplazamiento." +msgid "Double tap" +msgstr "Doble toque" #: lib/logitech_receiver/settings_templates.py:906 -msgid "Natural scrolling" -msgstr "Desplazamiento natural" +msgid "Performs a double click." +msgstr "Realiza un click doble." #: lib/logitech_receiver/settings_templates.py:907 -msgid "Enables the thumbwheel." -msgstr "Activa la rueda del pulgar." +msgid "Double tap with two fingers" +msgstr "Doble toque con dos dedos" -#: lib/logitech_receiver/settings_templates.py:907 -msgid "Thumbwheel" -msgstr "Rueda del pulgar" +#: lib/logitech_receiver/settings_templates.py:908 +msgid "Double tap with three fingers" +msgstr "Doble toque con tres dedos" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Arrastra elementos arrastrando el dedo después de tocar dos veces." + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Tap and drag" +msgstr "Tocar y arrastrar" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Arrastra elementos arrastrando los dedos después de tocar dos veces." + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Tap and drag with two fingers" +msgstr "Tocar y arrastrar con dos dedos" + +#: lib/logitech_receiver/settings_templates.py:914 +msgid "Tap and drag with three fingers" +msgstr "Tocar y arrastrar con tres dedos" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "" +"Desactiva los gestos de toque y borde (equivalente a pulsar " +"Fn+ClicIzquierdo)." + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Suppress tap and edge gestures" +msgstr "Elimina los gestos de toque y borde" #: lib/logitech_receiver/settings_templates.py:918 +msgid "Scroll with one finger" +msgstr "Desplaza con un dedo" + +#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:919 #: lib/logitech_receiver/settings_templates.py:922 -msgid "Swipe from the top edge" -msgstr "Deslizar desde el borde superior" +msgid "Scrolls." +msgstr "Desplazamiento." #: lib/logitech_receiver/settings_templates.py:919 -msgid "Swipe from the left edge" -msgstr "Deslizar desde el borde izquierdo" +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scroll with two fingers" +msgstr "Desplaza con dos dedos" #: lib/logitech_receiver/settings_templates.py:920 -msgid "Swipe from the right edge" -msgstr "Deslizar desde el borde derecho" +msgid "Scroll horizontally with two fingers" +msgstr "Desplaza horizontalmente con dos dedos" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scrolls horizontally." +msgstr "Desplaza horizontalmente." #: lib/logitech_receiver/settings_templates.py:921 -msgid "Swipe from the bottom edge" -msgstr "Deslizar desde el borde inferior" +msgid "Scroll vertically with two fingers" +msgstr "Desplazar verticalmente con dos dedos" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scrolls vertically." +msgstr "Desplaza verticalmente." #: lib/logitech_receiver/settings_templates.py:923 -msgid "Swipe two fingers from the left edge" -msgstr "Deslizar dos dedos desde el borde izquierdo" +msgid "Inverts the scrolling direction." +msgstr "Invierte la dirección de desplazamiento." + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Natural scrolling" +msgstr "Desplazamiento natural" #: lib/logitech_receiver/settings_templates.py:924 -msgid "Swipe two fingers from the right edge" -msgstr "Deslizar dos dedos desde el borde derecho" +msgid "Enables the thumbwheel." +msgstr "Activa la rueda del pulgar." -#: lib/logitech_receiver/settings_templates.py:925 -msgid "Swipe two fingers from the bottom edge" -msgstr "Deslizar dos dedos desde el borde inferior" +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Thumbwheel" +msgstr "Rueda del pulgar" -#: lib/logitech_receiver/settings_templates.py:926 -msgid "Swipe two fingers from the top edge" -msgstr "Deslizar dos dedos desde el borde superior" +#: lib/logitech_receiver/settings_templates.py:935 +#: lib/logitech_receiver/settings_templates.py:939 +msgid "Swipe from the top edge" +msgstr "Deslizar desde el borde superior" -#: lib/logitech_receiver/settings_templates.py:927 -#: lib/logitech_receiver/settings_templates.py:931 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "Pellizcar para alejar; Extender para acercar." +#: lib/logitech_receiver/settings_templates.py:936 +msgid "Swipe from the left edge" +msgstr "Deslizar desde el borde izquierdo" -#: lib/logitech_receiver/settings_templates.py:927 -msgid "Zoom with two fingers." -msgstr "Zoom con dos dedos." +#: lib/logitech_receiver/settings_templates.py:937 +msgid "Swipe from the right edge" +msgstr "Deslizar desde el borde derecho" -#: lib/logitech_receiver/settings_templates.py:928 -msgid "Pinch to zoom out." -msgstr "Pellizcar para alejar." +#: lib/logitech_receiver/settings_templates.py:938 +msgid "Swipe from the bottom edge" +msgstr "Deslizar desde el borde inferior" -#: lib/logitech_receiver/settings_templates.py:929 -msgid "Spread to zoom in." -msgstr "Extender para acercar." +#: lib/logitech_receiver/settings_templates.py:940 +msgid "Swipe two fingers from the left edge" +msgstr "Deslizar dos dedos desde el borde izquierdo" -#: lib/logitech_receiver/settings_templates.py:930 -msgid "Zoom with three fingers." -msgstr "Zoom con tres dedos." +#: lib/logitech_receiver/settings_templates.py:941 +msgid "Swipe two fingers from the right edge" +msgstr "Deslizar dos dedos desde el borde derecho" -#: lib/logitech_receiver/settings_templates.py:931 -msgid "Zoom with two fingers" -msgstr "Zoom con dos dedos" +#: lib/logitech_receiver/settings_templates.py:942 +msgid "Swipe two fingers from the bottom edge" +msgstr "Deslizar dos dedos desde el borde inferior" -#: lib/logitech_receiver/settings_templates.py:949 -msgid "Pixel zone" -msgstr "Zona de píxeles" +#: lib/logitech_receiver/settings_templates.py:943 +msgid "Swipe two fingers from the top edge" +msgstr "Deslizar dos dedos desde el borde superior" -#: lib/logitech_receiver/settings_templates.py:950 -msgid "Ratio zone" -msgstr "Zona de relación" +#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "Pellizcar para alejar; Extender para acercar." -#: lib/logitech_receiver/settings_templates.py:951 -msgid "Scale factor" -msgstr "Factor de escalado" +#: lib/logitech_receiver/settings_templates.py:944 +msgid "Zoom with two fingers." +msgstr "Zoom con dos dedos." -#: lib/logitech_receiver/settings_templates.py:951 -msgid "Sets the cursor speed." -msgstr "Establece la velocidad del cursor." +#: lib/logitech_receiver/settings_templates.py:945 +msgid "Pinch to zoom out." +msgstr "Pellizcar para alejar." -#: lib/logitech_receiver/settings_templates.py:955 -msgid "Left" -msgstr "Izquierda" +#: lib/logitech_receiver/settings_templates.py:946 +msgid "Spread to zoom in." +msgstr "Extender para acercar." -#: lib/logitech_receiver/settings_templates.py:955 -msgid "Left-most coordinate." -msgstr "Coordenada más a la izquierda." +#: lib/logitech_receiver/settings_templates.py:947 +msgid "Zoom with three fingers." +msgstr "Zoom con tres dedos." -#: lib/logitech_receiver/settings_templates.py:956 -msgid "Bottom" -msgstr "Abajo" - -#: lib/logitech_receiver/settings_templates.py:956 -msgid "Bottom coordinate." -msgstr "Coordenada inferior." - -#: lib/logitech_receiver/settings_templates.py:957 -msgid "Width" -msgstr "Anchura" - -#: lib/logitech_receiver/settings_templates.py:957 -msgid "Width." -msgstr "Anchura." - -#: lib/logitech_receiver/settings_templates.py:958 -msgid "Height" -msgstr "Altura" - -#: lib/logitech_receiver/settings_templates.py:958 -msgid "Height." -msgstr "Altura." - -#: lib/logitech_receiver/settings_templates.py:959 -msgid "Cursor speed." -msgstr "Velocidad del cursor." - -#: lib/logitech_receiver/settings_templates.py:959 -msgid "Scale" -msgstr "Escala" - -#: lib/logitech_receiver/settings_templates.py:965 -msgid "Gestures" -msgstr "Gestos" +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Zoom with two fingers" +msgstr "Zoom con dos dedos" #: lib/logitech_receiver/settings_templates.py:966 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "Modificar el comportamiento del mouse/panel táctil." +msgid "Pixel zone" +msgstr "Zona de píxeles" + +#: lib/logitech_receiver/settings_templates.py:967 +msgid "Ratio zone" +msgstr "Zona de relación" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Scale factor" +msgstr "Factor de escalado" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Sets the cursor speed." +msgstr "Establece la velocidad del cursor." + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left" +msgstr "Izquierda" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left-most coordinate." +msgstr "Coordenada más a la izquierda." + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom" +msgstr "Abajo" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom coordinate." +msgstr "Coordenada inferior." + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width" +msgstr "Anchura" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width." +msgstr "Anchura." + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height" +msgstr "Altura" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height." +msgstr "Altura." + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Cursor speed." +msgstr "Velocidad del cursor." + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Scale" +msgstr "Escala" #: lib/logitech_receiver/settings_templates.py:982 -msgid "Gestures Diversion" -msgstr "Desvío de gestos" +msgid "Gestures" +msgstr "Gestos" #: lib/logitech_receiver/settings_templates.py:983 -msgid "Divert mouse/touchpad gestures." -msgstr "Desviar los gestos del mouse/panel táctil." - -#: lib/logitech_receiver/settings_templates.py:999 -msgid "Gesture params" -msgstr "Parámetros de gestos" +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Modificar el comportamiento del mouse/panel táctil." #: lib/logitech_receiver/settings_templates.py:1000 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "Cambiar parámetros numéricos de un mouse/panel táctil." +msgid "Gestures Diversion" +msgstr "Desvío de gestos" -#: lib/logitech_receiver/settings_templates.py:1024 -msgid "M-Key LEDs" -msgstr "LED de tecla M" +#: lib/logitech_receiver/settings_templates.py:1001 +msgid "Divert mouse/touchpad gestures." +msgstr "Desviar los gestos del mouse/panel táctil." -#: lib/logitech_receiver/settings_templates.py:1026 -msgid "Control the M-Key LEDs." -msgstr "Controle los LED de la M-Key" +#: lib/logitech_receiver/settings_templates.py:1018 +msgid "Gesture params" +msgstr "Parámetros de gestos" -#: lib/logitech_receiver/settings_templates.py:1027 -#: lib/logitech_receiver/settings_templates.py:1055 -msgid "May need G Keys diverted to be effective." -msgstr "Puede necesitar desviar las llaves G para ser efectiva." +#: lib/logitech_receiver/settings_templates.py:1019 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Cambiar parámetros numéricos de un mouse/panel táctil." -#: lib/logitech_receiver/settings_templates.py:1033 +#: lib/logitech_receiver/settings_templates.py:1044 +msgid "M-Key LEDs" +msgstr "LEDs de teclas M" + +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Control the M-Key LEDs." +msgstr "Control de los LEDs de teclas M." + +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1077 +msgid "May need G Keys diverted to be effective." +msgstr "Puede necesitar desviar las llaves G para ser efectiva." + +#: lib/logitech_receiver/settings_templates.py:1053 #, python-format -msgid "Lights up the %s key." -msgstr "Se enciende la tecla %s." - -#: lib/logitech_receiver/settings_templates.py:1052 -msgid "MR-Key LED" -msgstr "MR-Key LED" - -#: lib/logitech_receiver/settings_templates.py:1054 -msgid "Control the MR-Key LED." -msgstr "Controle el LED MR-Key." - -#: lib/logitech_receiver/settings_templates.py:1072 -msgid "Persistent Key/Button Mapping" -msgstr "Asignación persistente de teclas/botones" +msgid "Lights up the %s key." +msgstr "Se enciende la tecla %s." #: lib/logitech_receiver/settings_templates.py:1074 -msgid "Permanently change the mapping for the key or button." -msgstr "Cambie permanentemente la asignación de la tecla o el botón." +msgid "MR-Key LED" +msgstr "MR-Key LED" -#: lib/logitech_receiver/settings_templates.py:1075 -msgid "Changing important keys or buttons (such as for the left mouse " - "button) can result in an unusable system." -msgstr "Cambiar teclas o botones importantes (como el botón izquierdo del mouse) " - "puede resultar en un sistema inutilizable." +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "Control the MR-Key LED." +msgstr "Controle el LED MR-Key." -#: lib/logitech_receiver/settings_templates.py:1132 -msgid "Sidetone" -msgstr "Tono lateral" +#: lib/logitech_receiver/settings_templates.py:1095 +msgid "Persistent Key/Button Mapping" +msgstr "Asignación persistente de teclas/botones" -#: lib/logitech_receiver/settings_templates.py:1133 -msgid "Set sidetone level." -msgstr "Establezca el nivel de tono local." +#: lib/logitech_receiver/settings_templates.py:1097 +msgid "Permanently change the mapping for the key or button." +msgstr "Cambie permanentemente la asignación de la tecla o el botón." -#: lib/logitech_receiver/settings_templates.py:1142 -msgid "Equalizer" -msgstr "Igualada" +#: lib/logitech_receiver/settings_templates.py:1098 +msgid "" +"Changing important keys or buttons (such as for the left mouse button) can " +"result in an unusable system." +msgstr "" +"Cambiar teclas o botones importantes (como el botón izquierdo del ratón) " +"puede resultar en un sistema inutilizable." -#: lib/logitech_receiver/settings_templates.py:1143 -msgid "Set equalizer levels." -msgstr "Establecer niveles de ecualizador." +#: lib/logitech_receiver/settings_templates.py:1157 +msgid "Sidetone" +msgstr "Tono lateral" -#: lib/logitech_receiver/settings_templates.py:1165 -msgid "Hz" -msgstr "Hz" +#: lib/logitech_receiver/settings_templates.py:1158 +msgid "Set sidetone level." +msgstr "Establezca el nivel de tono lateral." -#: lib/logitech_receiver/status.py:113 -msgid "No paired devices." -msgstr "Ningún dispositivo conectado." +#: lib/logitech_receiver/settings_templates.py:1167 +msgid "Equalizer" +msgstr "Ecualizador" -#: lib/logitech_receiver/status.py:114 lib/solaar/ui/window.py:620 +#: lib/logitech_receiver/settings_templates.py:1168 +msgid "Set equalizer levels." +msgstr "Establecer niveles del ecualizador." + +#: lib/logitech_receiver/settings_templates.py:1191 +msgid "Hz" +msgstr "Hz" + +#: lib/logitech_receiver/settings_templates.py:1197 +msgid "Power Management" +msgstr "Gestión de energía" + +#: lib/logitech_receiver/settings_templates.py:1198 +msgid "Power off in minutes (0 for never)." +msgstr "Apagar a los minutos (0 para nunca)" + +#: lib/logitech_receiver/status.py:114 +msgid "No paired devices." +msgstr "Ningún dispositivo conectado." + +#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 #, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s dispositivo vinculado." -msgstr[1] "%(count)s dispositivos vinculados." +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s dispositivo vinculado." +msgstr[1] "%(count)s dispositivos vinculados." -#: lib/logitech_receiver/status.py:167 +#: lib/logitech_receiver/status.py:170 #, python-format -msgid "Battery: %(level)s" -msgstr "Batería: %(level)s" +msgid "Battery: %(level)s" +msgstr "Batería: %(level)s" -#: lib/logitech_receiver/status.py:169 +#: lib/logitech_receiver/status.py:172 #, python-format -msgid "Battery: %(percent)d%%" -msgstr "Batería: %(percent)d%%" +msgid "Battery: %(percent)d%%" +msgstr "Batería: %(percent)d%%" -#: lib/logitech_receiver/status.py:181 +#: lib/logitech_receiver/status.py:184 #, python-format -msgid "Lighting: %(level)s lux" -msgstr "Iluminación: %(level)s lux" +msgid "Lighting: %(level)s lux" +msgstr "Iluminación: %(level)s lux" -#: lib/logitech_receiver/status.py:236 +#: lib/logitech_receiver/status.py:239 #, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "Batería: %(level)s (%(status)s)" +msgid "Battery: %(level)s (%(status)s)" +msgstr "Batería: %(level)s (%(status)s)" -#: lib/logitech_receiver/status.py:238 +#: lib/logitech_receiver/status.py:241 #, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "Batería: %(percent)d%% (%(status)s)" +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Batería: %(percent)d%% (%(status)s)" -#: lib/solaar/ui/__init__.py:51 -msgid "Permissions error" -msgstr "Error de permisos" - -#: lib/solaar/ui/__init__.py:53 -#, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open " - "it." -msgstr "Se encontró un receptor Logitech (%s), pero no tiene permisos para " - "abrirlo." +#: lib/solaar/ui/__init__.py:52 +msgid "Permissions error" +msgstr "Error de permisos" #: lib/solaar/ui/__init__.py:54 -msgid "If you've just installed Solaar, try removing the receiver and " - "plugging it back in." -msgstr "Si acaba de instalar Solaar, intente quitar el receptor y conectarlo " - "de nuevo." - -#: lib/solaar/ui/__init__.py:57 -msgid "Cannot connect to device error" -msgstr "Error al conectar al dispositivo" - -#: lib/solaar/ui/__init__.py:59 #, python-format -msgid "Found a Logitech receiver or device at %s, but encountered an error " - "connecting to it." -msgstr "Se encontró un receptor o dispositivo Logitech en %s, pero se " - "produjo un error al conectarse." +msgid "" +"Found a Logitech receiver or device (%s), but did not have permission to " +"open it." +msgstr "" +"Se ha encontrado un receptor Logitech o dispositivo (%s), pero no se dispone " +"de permisos para abrirlo." + +#: lib/solaar/ui/__init__.py:55 +msgid "" +"If you've just installed Solaar, try disconnecting the receiver or device " +"and then reconnecting it." +msgstr "" +"Si acaba de instalar Solaar, pruebe a desconectar y volver a conectar el " +"receptor o dispositivo." + +#: lib/solaar/ui/__init__.py:58 +msgid "Cannot connect to device error" +msgstr "Error al conectar al dispositivo" #: lib/solaar/ui/__init__.py:60 -msgid "Try removing the device and plugging it back in or turning it off " - "and then on." -msgstr "Intente quitar el dispositivo y volver a enchufarlo o apagarlo y " - "encenderlo." +#, python-format +msgid "" +"Found a Logitech receiver or device at %s, but encountered an error " +"connecting to it." +msgstr "" +"Se encontró un receptor o dispositivo Logitech en %s, pero se produjo un " +"error al conectarse." -#: lib/solaar/ui/__init__.py:63 -msgid "Unpairing failed" -msgstr "Desvinculación fallida" +#: lib/solaar/ui/__init__.py:61 +msgid "" +"Try disconnecting the device and then reconnecting it or turning it off and " +"then on." +msgstr "" +"Pruebe a desconectar y volver a conectar el dispositivo o a apagarlo y " +"encenderlo de nuevo." -#: lib/solaar/ui/__init__.py:65 -#, python-brace-format -msgid "Failed to unpair %{device} from %{receiver}." -msgstr "Fallo al desvincular %{device} de %{receiver}." +#: lib/solaar/ui/__init__.py:64 +msgid "Unpairing failed" +msgstr "Desvinculación fallida" #: lib/solaar/ui/__init__.py:66 -msgid "The receiver returned an error, with no further details." -msgstr "El receptor devolvió un error, sin detalles adicionales." +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Fallo al desvincular %{device} de %{receiver}." -#: lib/solaar/ui/__init__.py:176 -msgid "Another Solaar process is already running so just expose its window" -msgstr "Ya se está ejecutando otro proceso de Solaar, así que simplemente exponga su ventana" +#: lib/solaar/ui/__init__.py:67 +msgid "The receiver returned an error, with no further details." +msgstr "El receptor devolvió un error, sin detalles adicionales." + +#: lib/solaar/ui/__init__.py:177 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" +"Ya se está ejecutando otro proceso de Solaar, así que simplemente se muestra " +"su ventana" #: lib/solaar/ui/about.py:36 -msgid "Manages Logitech receivers,\n" - "keyboards, mice, and tablets." -msgstr "Gestiona receptores Logitech,\n" - "teclados, ratones y tabletas." +msgid "" +"Manages Logitech receivers,\n" +"keyboards, mice, and tablets." +msgstr "" +"Gestiona receptores Logitech,\n" +"teclados, ratones y tabletas." #: lib/solaar/ui/about.py:44 -msgid "Additional Programming" -msgstr "Programación adicional" +msgid "Additional Programming" +msgstr "Programación adicional" #: lib/solaar/ui/about.py:45 -msgid "GUI design" -msgstr "Diseño de la interfaz gráfica" +msgid "GUI design" +msgstr "Diseño de la interfaz gráfica" #: lib/solaar/ui/about.py:47 -msgid "Testing" -msgstr "Prueba" +msgid "Testing" +msgstr "Prueba" #: lib/solaar/ui/about.py:54 -msgid "Logitech documentation" -msgstr "Documentación de Logitech" +msgid "Logitech documentation" +msgstr "Documentación de Logitech" #: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 -#: lib/solaar/ui/window.py:202 -msgid "Unpair" -msgstr "Desvincular" +#: lib/solaar/ui/window.py:197 +msgid "Unpair" +msgstr "Desvincular" -#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:148 -msgid "Cancel" -msgstr "Cancelar" +#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 +msgid "Cancel" +msgstr "Cancelar" -#: lib/solaar/ui/config_panel.py:205 -msgid "Complete - ENTER to change" -msgstr "Completa - ENTER para cambiar" +#: lib/solaar/ui/config_panel.py:212 +msgid "Complete - ENTER to change" +msgstr "Completa - ENTER para cambiar" -#: lib/solaar/ui/config_panel.py:205 -msgid "Incomplete" -msgstr "Incompleta" +#: lib/solaar/ui/config_panel.py:212 +msgid "Incomplete" +msgstr "Incompleta" -#: lib/solaar/ui/config_panel.py:444 lib/solaar/ui/config_panel.py:495 +#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 #, python-format -msgid "%d value" -msgid_plural "%d values" -msgstr[0] "%d valor" -msgstr[1] "%d valores" +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d valor" +msgstr[1] "%d valores" -#: lib/solaar/ui/config_panel.py:506 -msgid "Changes allowed" -msgstr "Cambios permitidos" +#: lib/solaar/ui/config_panel.py:518 +msgid "Changes allowed" +msgstr "Cambios permitidos" -#: lib/solaar/ui/config_panel.py:507 -msgid "No changes allowed" -msgstr "Cambios no permitidos" +#: lib/solaar/ui/config_panel.py:519 +msgid "No changes allowed" +msgstr "Cambios no permitidos" -#: lib/solaar/ui/config_panel.py:508 -msgid "Ignore this setting" -msgstr "Ignorar esta opción" +#: lib/solaar/ui/config_panel.py:520 +msgid "Ignore this setting" +msgstr "Ignorar esta opción" -#: lib/solaar/ui/config_panel.py:553 -msgid "Working" -msgstr "Funcionando" +#: lib/solaar/ui/config_panel.py:565 +msgid "Working" +msgstr "Funcionando" -#: lib/solaar/ui/config_panel.py:556 -msgid "Read/write operation failed." -msgstr "Operación de lectura/escritura fallida." +#: lib/solaar/ui/config_panel.py:568 +msgid "Read/write operation failed." +msgstr "Operación de lectura/escritura fallida." -#: lib/solaar/ui/diversion_rules.py:64 -msgid "Built-in rules" -msgstr "Reglas integradas" +#: lib/solaar/ui/diversion_rules.py:65 +msgid "Built-in rules" +msgstr "Reglas integradas" -#: lib/solaar/ui/diversion_rules.py:64 -msgid "User-defined rules" -msgstr "Reglas definidas por el usuario" +#: lib/solaar/ui/diversion_rules.py:65 +msgid "User-defined rules" +msgstr "Reglas definidas por el usuario" -#: lib/solaar/ui/diversion_rules.py:66 lib/solaar/ui/diversion_rules.py:1075 -msgid "Rule" -msgstr "Regla" +#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 +msgid "Rule" +msgstr "Regla" -#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:507 -#: lib/solaar/ui/diversion_rules.py:631 -msgid "Sub-rule" -msgstr "Subregla" +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 +#: lib/solaar/ui/diversion_rules.py:636 +msgid "Sub-rule" +msgstr "Subregla" -#: lib/solaar/ui/diversion_rules.py:69 -msgid "[empty]" -msgstr "[vacío]" +#: lib/solaar/ui/diversion_rules.py:70 +msgid "[empty]" +msgstr "[vacío]" -#: lib/solaar/ui/diversion_rules.py:92 -msgid "Solaar Rule Editor" -msgstr "Editor de Reglas Solaar" +#: lib/solaar/ui/diversion_rules.py:94 +msgid "Solaar Rule Editor" +msgstr "Editor de Reglas Solaar" -#: lib/solaar/ui/diversion_rules.py:139 -msgid "Make changes permanent?" -msgstr "¿Hacer los cambios permanentes?" - -#: lib/solaar/ui/diversion_rules.py:144 -msgid "Yes" -msgstr "Sí" +#: lib/solaar/ui/diversion_rules.py:141 +msgid "Make changes permanent?" +msgstr "¿Hacer los cambios permanentes?" #: lib/solaar/ui/diversion_rules.py:146 -msgid "No" -msgstr "No" +msgid "Yes" +msgstr "Sí" -#: lib/solaar/ui/diversion_rules.py:151 -msgid "If you choose No, changes will be lost when Solaar is closed." -msgstr "Si elige No, los cambios se perderán cuando se cierre Solaar." +#: lib/solaar/ui/diversion_rules.py:148 +msgid "No" +msgstr "No" -#: lib/solaar/ui/diversion_rules.py:199 -msgid "Save changes" -msgstr "Guardar cambios" +#: lib/solaar/ui/diversion_rules.py:153 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "Si elige No, los cambios se perderán cuando se cierre Solaar." -#: lib/solaar/ui/diversion_rules.py:204 -msgid "Discard changes" -msgstr "Descartar cambios" +#: lib/solaar/ui/diversion_rules.py:201 +msgid "Save changes" +msgstr "Guardar cambios" -#: lib/solaar/ui/diversion_rules.py:370 -msgid "Insert here" -msgstr "Insertar aquí" +#: lib/solaar/ui/diversion_rules.py:206 +msgid "Discard changes" +msgstr "Descartar cambios" #: lib/solaar/ui/diversion_rules.py:372 -msgid "Insert above" -msgstr "Insertar arriba" +msgid "Insert here" +msgstr "Insertar aquí" #: lib/solaar/ui/diversion_rules.py:374 -msgid "Insert below" -msgstr "Insertar abajo" +msgid "Insert above" +msgstr "Insertar encima" -#: lib/solaar/ui/diversion_rules.py:380 -msgid "Insert new rule here" -msgstr "Insertar nueva regla aquí" +#: lib/solaar/ui/diversion_rules.py:376 +msgid "Insert below" +msgstr "Insertar debajo" #: lib/solaar/ui/diversion_rules.py:382 -msgid "Insert new rule above" -msgstr "Insertar nueva regla arriba" +msgid "Insert new rule here" +msgstr "Insertar nueva regla aquí" #: lib/solaar/ui/diversion_rules.py:384 -msgid "Insert new rule below" -msgstr "Insertar nueva regla abajo" +msgid "Insert new rule above" +msgstr "Insertar nueva regla encima" -#: lib/solaar/ui/diversion_rules.py:425 -msgid "Paste here" -msgstr "Pegar aquí" +#: lib/solaar/ui/diversion_rules.py:386 +msgid "Insert new rule below" +msgstr "Insertar nueva regla debajo" #: lib/solaar/ui/diversion_rules.py:427 -msgid "Paste above" -msgstr "Pegar arriba" +msgid "Paste here" +msgstr "Pegar aquí" #: lib/solaar/ui/diversion_rules.py:429 -msgid "Paste below" -msgstr "Pegar abajo" +msgid "Paste above" +msgstr "Pegar encima" -#: lib/solaar/ui/diversion_rules.py:435 -msgid "Paste rule here" -msgstr "Pegar regla aquí" +#: lib/solaar/ui/diversion_rules.py:431 +msgid "Paste below" +msgstr "Pegar debajo" #: lib/solaar/ui/diversion_rules.py:437 -msgid "Paste rule above" -msgstr "Pegar regla arriba" +msgid "Paste rule here" +msgstr "Pegar regla aquí" #: lib/solaar/ui/diversion_rules.py:439 -msgid "Paste rule below" -msgstr "Pegar regla abajo" +msgid "Paste rule above" +msgstr "Pegar regla encima" -#: lib/solaar/ui/diversion_rules.py:443 -msgid "Paste rule" -msgstr "Pegar regla" +#: lib/solaar/ui/diversion_rules.py:441 +msgid "Paste rule below" +msgstr "Pegar regla debajo" -#: lib/solaar/ui/diversion_rules.py:472 -msgid "Flatten" -msgstr "Allanar" +#: lib/solaar/ui/diversion_rules.py:445 +msgid "Paste rule" +msgstr "Pegar regla" -#: lib/solaar/ui/diversion_rules.py:505 -msgid "Insert" -msgstr "Insertar" +#: lib/solaar/ui/diversion_rules.py:474 +msgid "Flatten" +msgstr "Aplanar" -#: lib/solaar/ui/diversion_rules.py:508 lib/solaar/ui/diversion_rules.py:633 -#: lib/solaar/ui/diversion_rules.py:1118 -msgid "Or" -msgstr "O" +#: lib/solaar/ui/diversion_rules.py:507 +msgid "Insert" +msgstr "Insertar" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:632 -#: lib/solaar/ui/diversion_rules.py:1103 -msgid "And" -msgstr "Y" +#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 +#: lib/solaar/ui/diversion_rules.py:1126 +msgid "Or" +msgstr "O" -#: lib/solaar/ui/diversion_rules.py:511 -msgid "Condition" -msgstr "Condición" +#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 +#: lib/solaar/ui/diversion_rules.py:1111 +msgid "And" +msgstr "Y" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1284 -msgid "Feature" -msgstr "Característica" +#: lib/solaar/ui/diversion_rules.py:513 +msgid "Condition" +msgstr "Condición" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1320 -msgid "Report" -msgstr "Informar" +#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 +msgid "Feature" +msgstr "Característica" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1196 -msgid "Process" -msgstr "Proceso" +#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 +msgid "Report" +msgstr "Informar" -#: lib/solaar/ui/diversion_rules.py:516 -msgid "Mouse process" -msgstr "Proceso de ratón" +#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 +msgid "Process" +msgstr "Proceso" -#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1358 -msgid "Modifiers" -msgstr "Modificadores" +#: lib/solaar/ui/diversion_rules.py:518 +msgid "Mouse process" +msgstr "Proceso de ratón" -#: lib/solaar/ui/diversion_rules.py:518 lib/solaar/ui/diversion_rules.py:1411 -msgid "Key" -msgstr "Tecla" +#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 +msgid "Modifiers" +msgstr "Modificadores" -#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:2177 -msgid "Active" -msgstr "Activo" +#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 +msgid "Key" +msgstr "Tecla" -#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:2220 -msgid "Setting" -msgstr "Ajuste" +#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 +msgid "KeyIsDown" +msgstr "TeclaPulsada" -#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1459 -msgid "Test" -msgstr "Test" +#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 +msgid "Active" +msgstr "Activo" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1576 -msgid "Test bytes" -msgstr "bytes de prueba" +#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2218 +#: lib/solaar/ui/diversion_rules.py:2270 lib/solaar/ui/diversion_rules.py:2323 +msgid "Device" +msgstr "Dispositivo" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1669 -msgid "Mouse Gesture" -msgstr "Gesto de Ratón" +#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +msgid "Host" +msgstr "Equipo" -#: lib/solaar/ui/diversion_rules.py:527 -msgid "Action" -msgstr "Acción" +#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 +msgid "Setting" +msgstr "Ajuste" -#: lib/solaar/ui/diversion_rules.py:529 lib/solaar/ui/diversion_rules.py:1778 -msgid "Key press" -msgstr "Pulsación de tecla" +#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 +#: lib/solaar/ui/diversion_rules.py:1526 +msgid "Test" +msgstr "Prueba" -#: lib/solaar/ui/diversion_rules.py:530 lib/solaar/ui/diversion_rules.py:1831 -msgid "Mouse scroll" -msgstr "Desplazamiento rueda del ratón" +#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 +msgid "Test bytes" +msgstr "Bytes de prueba" -#: lib/solaar/ui/diversion_rules.py:531 lib/solaar/ui/diversion_rules.py:1882 -msgid "Mouse click" -msgstr "Click del ratón" +#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 +msgid "Mouse Gesture" +msgstr "Gesto de Ratón" #: lib/solaar/ui/diversion_rules.py:532 -msgid "Set" -msgstr "Establecer" +msgid "Action" +msgstr "Acción" -#: lib/solaar/ui/diversion_rules.py:533 lib/solaar/ui/diversion_rules.py:1953 -msgid "Execute" -msgstr "Ejecutar" +#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 +msgid "Key press" +msgstr "Pulsación de tecla" -#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1150 -msgid "Later" -msgstr "Luego" +#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 +msgid "Mouse scroll" +msgstr "Desplazamiento rueda del ratón" -#: lib/solaar/ui/diversion_rules.py:563 -msgid "Insert new rule" -msgstr "Añadir nueva regla" +#: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 +msgid "Mouse click" +msgstr "Click del ratón" -#: lib/solaar/ui/diversion_rules.py:583 lib/solaar/ui/diversion_rules.py:1619 -#: lib/solaar/ui/diversion_rules.py:1723 lib/solaar/ui/diversion_rules.py:1912 -msgid "Delete" -msgstr "Borrar" +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Set" +msgstr "Establecer" -#: lib/solaar/ui/diversion_rules.py:605 -msgid "Negate" -msgstr "Negar" +#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 +msgid "Execute" +msgstr "Ejecutar" -#: lib/solaar/ui/diversion_rules.py:629 -msgid "Wrap with" -msgstr "Envolver con" +#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 +msgid "Later" +msgstr "Luego" -#: lib/solaar/ui/diversion_rules.py:651 -msgid "Cut" -msgstr "Cortar" +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Insert new rule" +msgstr "Añadir nueva regla" -#: lib/solaar/ui/diversion_rules.py:666 -msgid "Paste" -msgstr "Pegar" +#: lib/solaar/ui/diversion_rules.py:588 lib/solaar/ui/diversion_rules.py:1686 +#: lib/solaar/ui/diversion_rules.py:1790 lib/solaar/ui/diversion_rules.py:1989 +msgid "Delete" +msgstr "Borrar" -#: lib/solaar/ui/diversion_rules.py:678 -msgid "Copy" -msgstr "Copiar" +#: lib/solaar/ui/diversion_rules.py:610 +msgid "Negate" +msgstr "Negar" -#: lib/solaar/ui/diversion_rules.py:1055 -msgid "This editor does not support the selected rule component yet." -msgstr "Este editor aún no es compatible con el componente de regla " - "seleccionado." +#: lib/solaar/ui/diversion_rules.py:634 +msgid "Wrap with" +msgstr "Envolver con" -#: lib/solaar/ui/diversion_rules.py:1130 -msgid "Number of seconds to delay." -msgstr "Número de segundos de retraso." +#: lib/solaar/ui/diversion_rules.py:656 +msgid "Cut" +msgstr "Cortar" -#: lib/solaar/ui/diversion_rules.py:1169 -msgid "Not" -msgstr "No" +#: lib/solaar/ui/diversion_rules.py:671 +msgid "Paste" +msgstr "Pegar" -#: lib/solaar/ui/diversion_rules.py:1179 -msgid "X11 active process. For use in X11 only." -msgstr "Proceso activo X11. Solo para uso en X11." +#: lib/solaar/ui/diversion_rules.py:683 +msgid "Copy" +msgstr "Copiar" -#: lib/solaar/ui/diversion_rules.py:1210 -msgid "X11 mouse process. For use in X11 only." -msgstr "Proceso de ratón X11. Solo para uso en X11." +#: lib/solaar/ui/diversion_rules.py:1063 +msgid "This editor does not support the selected rule component yet." +msgstr "" +"Este editor aún no es compatible con el componente de regla seleccionado." -#: lib/solaar/ui/diversion_rules.py:1227 -msgid "MouseProcess" -msgstr "ProcesoRatón" +#: lib/solaar/ui/diversion_rules.py:1138 +msgid "Number of seconds to delay." +msgstr "Número de segundos a retrasar." -#: lib/solaar/ui/diversion_rules.py:1252 -msgid "Feature name of notification triggering rule processing." -msgstr "Nombre de la característica del procesamiento de la regla de activación de notificaciones." +#: lib/solaar/ui/diversion_rules.py:1177 +msgid "Not" +msgstr "No" -#: lib/solaar/ui/diversion_rules.py:1300 -msgid "Report number of notification triggering rule processing." -msgstr "Número de informe de procesamiento de regla de activación de notificación." +#: lib/solaar/ui/diversion_rules.py:1187 +msgid "X11 active process. For use in X11 only." +msgstr "Proceso activo X11. Solo para uso en X11." -#: lib/solaar/ui/diversion_rules.py:1334 -msgid "Active keyboard modifiers. Not always available in Wayland." -msgstr "Modificadores de teclado activos. No siempre disponible en Wayland." +#: lib/solaar/ui/diversion_rules.py:1218 +msgid "X11 mouse process. For use in X11 only." +msgstr "Proceso de ratón X11. Solo para uso en X11." -#: lib/solaar/ui/diversion_rules.py:1375 -msgid "Diverted key or button depressed or released.\n" - "Use the Key/Button Diversion setting to divert keys and buttons." -msgstr "Tecla desviada o botón presionado o liberado.\n" - "Use la configuración Desvío de teclas/botones para desviar teclas y botones." +#: lib/solaar/ui/diversion_rules.py:1235 +msgid "MouseProcess" +msgstr "ProcesoRatón" -#: lib/solaar/ui/diversion_rules.py:1384 -msgid "Key down" -msgstr "Tecla abajo" +#: lib/solaar/ui/diversion_rules.py:1260 +msgid "Feature name of notification triggering rule processing." +msgstr "" +"Nombre de la característica del procesamiento de la regla de activación de " +"notificaciones." -#: lib/solaar/ui/diversion_rules.py:1387 -msgid "Key up" -msgstr "Tecla arriba" +#: lib/solaar/ui/diversion_rules.py:1308 +msgid "Report number of notification triggering rule processing." +msgstr "" +"Número de informe de procesamiento de regla de activación de notificación." -#: lib/solaar/ui/diversion_rules.py:1425 -msgid "Test condition on notification triggering rule processing." -msgstr "Condición de prueba en el procesamiento de la regla de activación de notificación." +#: lib/solaar/ui/diversion_rules.py:1342 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "Modificadores de teclado activos. No siempre disponible en Wayland." + +#: lib/solaar/ui/diversion_rules.py:1383 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"Tecla desviada o botón pulsado o liberado.\n" +"Use Desvío de Tecla/Botón y Desviar de Teclas G para desviar teclas y " +"botones." + +#: lib/solaar/ui/diversion_rules.py:1392 +msgid "Key down" +msgstr "Tecla abajo" + +#: lib/solaar/ui/diversion_rules.py:1395 +msgid "Key up" +msgstr "Tecla arriba" + +#: lib/solaar/ui/diversion_rules.py:1436 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"La tecla o botón desviado está actualmente pulsado.\n" +"Use los ajustes Desvío de Tecla/Botón y Desviar Teclas G para desviar teclas " +"y botones." #: lib/solaar/ui/diversion_rules.py:1475 -msgid "begin (inclusive)" -msgstr "empezar (inclusive)" +msgid "Test condition on notification triggering rule processing." +msgstr "" +"Condición de prueba en el procesamiento de la regla de activación de " +"notificación." -#: lib/solaar/ui/diversion_rules.py:1476 -msgid "end (exclusive)" -msgstr "Final (exclusiva)" +#: lib/solaar/ui/diversion_rules.py:1479 +msgid "Parameter" +msgstr "Parámetro" -#: lib/solaar/ui/diversion_rules.py:1485 -msgid "range" -msgstr "rango" +#: lib/solaar/ui/diversion_rules.py:1542 +msgid "begin (inclusive)" +msgstr "inicio (incluido)" -#: lib/solaar/ui/diversion_rules.py:1487 -msgid "minimum" -msgstr "mínimo" +#: lib/solaar/ui/diversion_rules.py:1543 +msgid "end (exclusive)" +msgstr "fin (excluido)" -#: lib/solaar/ui/diversion_rules.py:1488 -msgid "maximum" -msgstr "máximo" +#: lib/solaar/ui/diversion_rules.py:1552 +msgid "range" +msgstr "rango" -#: lib/solaar/ui/diversion_rules.py:1490 +#: lib/solaar/ui/diversion_rules.py:1554 +msgid "minimum" +msgstr "mínimo" + +#: lib/solaar/ui/diversion_rules.py:1555 +msgid "maximum" +msgstr "máximo" + +#: lib/solaar/ui/diversion_rules.py:1557 #, python-format -msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" -msgstr "bytes %(0)d a %(1)d, que van desde %(2)d a %(3)d" +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "bytes %(0)d a %(1)d, que van desde %(2)d a %(3)d" -#: lib/solaar/ui/diversion_rules.py:1495 -msgid "mask" -msgstr "máscara" +#: lib/solaar/ui/diversion_rules.py:1562 +msgid "mask" +msgstr "máscara" -#: lib/solaar/ui/diversion_rules.py:1496 +#: lib/solaar/ui/diversion_rules.py:1563 #, python-format -msgid "bytes %(0)d to %(1)d, mask %(2)d" -msgstr "bytes %(0)d a %(1)d, máscara %(2)d" +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "bytes %(0)d a %(1)d, máscara %(2)d" -#: lib/solaar/ui/diversion_rules.py:1506 -msgid "Bit or range test on bytes in notification message triggering rule " - "processing." -msgstr "Prueba de bit o rango en bytes en el mensaje de notificación que " - "activa el procesamiento de reglas." +#: lib/solaar/ui/diversion_rules.py:1573 +msgid "" +"Bit or range test on bytes in notification message triggering rule " +"processing." +msgstr "" +"Prueba de bit o rango en bytes en el mensaje de notificación que activa el " +"procesamiento de reglas." -#: lib/solaar/ui/diversion_rules.py:1516 -msgid "type" -msgstr "tipo" +#: lib/solaar/ui/diversion_rules.py:1583 +msgid "type" +msgstr "tipo" -#: lib/solaar/ui/diversion_rules.py:1599 -msgid "Mouse gesture with optional initiating button followed by zero or " - "more mouse movements." -msgstr "Gesto del mouse con botón de inicio opcional seguido de cero o " - "más movimientos del mouse." +#: lib/solaar/ui/diversion_rules.py:1666 +msgid "" +"Mouse gesture with optional initiating button followed by zero or more mouse " +"movements." +msgstr "" +"Gesto del mouse con botón de inicio opcional seguido de cero o más " +"movimientos del mouse." -#: lib/solaar/ui/diversion_rules.py:1604 -msgid "Add movement" -msgstr "Añadir movimiento" +#: lib/solaar/ui/diversion_rules.py:1671 +msgid "Add movement" +msgstr "Añadir movimiento" -#: lib/solaar/ui/diversion_rules.py:1697 -msgid "Simulate a chorded key click or depress or release.\n" - "On Wayland requires write access to /dev/uinput." -msgstr "Simule un clic de tecla acorde o presione o suelte.\n" - "En Wayland requiere acceso de escritura a /dev/uinput." +#: lib/solaar/ui/diversion_rules.py:1764 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simular un clic de teclas combinadas o pulsación o liberación.\n" +"En Wayland requiere acceso de escritura a /dev/uinput." -#: lib/solaar/ui/diversion_rules.py:1702 -msgid "Add key" -msgstr "Añadir tecla" +#: lib/solaar/ui/diversion_rules.py:1769 +msgid "Add key" +msgstr "Añadir tecla" -#: lib/solaar/ui/diversion_rules.py:1705 -msgid "Click" -msgstr "Hacer clic" +#: lib/solaar/ui/diversion_rules.py:1772 +msgid "Click" +msgstr "Hacer clic" -#: lib/solaar/ui/diversion_rules.py:1708 -msgid "Depress" -msgstr "Deprimir" +#: lib/solaar/ui/diversion_rules.py:1775 +msgid "Depress" +msgstr "Pulsar" -#: lib/solaar/ui/diversion_rules.py:1711 -msgid "Release" -msgstr "Liberar" +#: lib/solaar/ui/diversion_rules.py:1778 +msgid "Release" +msgstr "Liberar" -#: lib/solaar/ui/diversion_rules.py:1795 -msgid "Simulate a mouse scroll.\n" - "On Wayland requires write access to /dev/uinput." -msgstr "Simule un desplazamiento del mouse.\n" - "En Wayland requiere acceso de escritura a /dev/uinput." +#: lib/solaar/ui/diversion_rules.py:1861 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simular un desplazamiento del ratón.\n" +"En Wayland requiere acceso de escritura a /dev/uinput." -#: lib/solaar/ui/diversion_rules.py:1851 -msgid "Simulate a mouse click.\n" - "On Wayland requires write access to /dev/uinput." -msgstr "Simule un clic del mouse.\n" - "En Wayland requiere acceso de escritura a /dev/uinput." +#: lib/solaar/ui/diversion_rules.py:1918 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simular un clic del ratón.\n" +"En Wayland requiere acceso de escritura a /dev/uinput." -#: lib/solaar/ui/diversion_rules.py:1854 -msgid "Button" -msgstr "Botón" +#: lib/solaar/ui/diversion_rules.py:1921 +msgid "Button" +msgstr "Botón" -#: lib/solaar/ui/diversion_rules.py:1855 -msgid "Count" -msgstr "Cuenta" - -#: lib/solaar/ui/diversion_rules.py:1895 -msgid "Execute a command with arguments." -msgstr "Ejecutar un comando con argumentos." - -#: lib/solaar/ui/diversion_rules.py:1898 -msgid "Add argument" -msgstr "Añadir argumento" +#: lib/solaar/ui/diversion_rules.py:1922 +msgid "Count and Action" +msgstr "Pulsaciones y Acción" #: lib/solaar/ui/diversion_rules.py:1972 -msgid "Toggle" -msgstr "Palanca" +msgid "Execute a command with arguments." +msgstr "Ejecutar un comando con parámetros." -#: lib/solaar/ui/diversion_rules.py:1972 -msgid "True" -msgstr "Verdadero" +#: lib/solaar/ui/diversion_rules.py:1975 +msgid "Add argument" +msgstr "Añadir parámetro" -#: lib/solaar/ui/diversion_rules.py:1973 -msgid "False" -msgstr "Falso" +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "Toggle" +msgstr "Invertir" -#: lib/solaar/ui/diversion_rules.py:1987 -msgid "Unsupported setting" -msgstr "Configuración no admitida" +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "True" +msgstr "Verdadero" -#: lib/solaar/ui/diversion_rules.py:2133 lib/solaar/ui/diversion_rules.py:2204 -msgid "Device" -msgstr "Dispositivo" +#: lib/solaar/ui/diversion_rules.py:2051 +msgid "False" +msgstr "Falso" -#: lib/solaar/ui/diversion_rules.py:2138 lib/solaar/ui/diversion_rules.py:2170 -#: lib/solaar/ui/diversion_rules.py:2209 lib/solaar/ui/diversion_rules.py:2451 -#: lib/solaar/ui/diversion_rules.py:2469 -msgid "Originating device" -msgstr "Dispositivo de origen" +#: lib/solaar/ui/diversion_rules.py:2065 +msgid "Unsupported setting" +msgstr "Configuración no admitida" -#: lib/solaar/ui/diversion_rules.py:2159 -msgid "Device is active and its settings can be changed." -msgstr "El dispositivo está activo y su configuración se puede cambiar." +#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2242 +#: lib/solaar/ui/diversion_rules.py:2328 lib/solaar/ui/diversion_rules.py:2570 +#: lib/solaar/ui/diversion_rules.py:2588 +msgid "Originating device" +msgstr "Dispositivo emisor" -#: lib/solaar/ui/diversion_rules.py:2228 -msgid "Value" -msgstr "Valor" +#: lib/solaar/ui/diversion_rules.py:2256 +msgid "Device is active and its settings can be changed." +msgstr "El dispositivo está activo y su configuración se puede cambiar." -#: lib/solaar/ui/diversion_rules.py:2236 -msgid "Item" -msgstr "Artículo" +#: lib/solaar/ui/diversion_rules.py:2266 +msgid "Device that originated the current notification." +msgstr "Dispositivo que ha causado la notificación actual." -#: lib/solaar/ui/diversion_rules.py:2511 -msgid "Change setting on device" -msgstr "Cambiar la configuración en el dispositivo" +#: lib/solaar/ui/diversion_rules.py:2280 +msgid "Name of host computer." +msgstr "Nombre del equipo." -#: lib/solaar/ui/diversion_rules.py:2528 -msgid "Setting on device" -msgstr "Configuración en el dispositivo" +#: lib/solaar/ui/diversion_rules.py:2347 +msgid "Value" +msgstr "Valor" -#: lib/solaar/ui/notify.py:122 lib/solaar/ui/tray.py:323 -#: lib/solaar/ui/tray.py:328 lib/solaar/ui/window.py:742 -msgid "offline" -msgstr "desconectado" +#: lib/solaar/ui/diversion_rules.py:2355 +msgid "Item" +msgstr "Artículo" + +#: lib/solaar/ui/diversion_rules.py:2630 +msgid "Change setting on device" +msgstr "Cambiar la configuración en el dispositivo" + +#: lib/solaar/ui/diversion_rules.py:2647 +msgid "Setting on device" +msgstr "Configuración en el dispositivo" + +#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:318 +#: lib/solaar/ui/tray.py:323 lib/solaar/ui/window.py:739 +msgid "offline" +msgstr "desconectado" #: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 -#: lib/solaar/ui/pair_window.py:278 +#: lib/solaar/ui/pair_window.py:288 #, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s: vincular nuevo dispositivo" +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: vincular nuevo dispositivo" #: lib/solaar/ui/pair_window.py:123 #, python-format -msgid "Enter passcode on %(name)s." -msgstr "Ingrese el código de acceso en %(name)s." +msgid "Enter passcode on %(name)s." +msgstr "Introduzca el código de acceso en %(name)s." #: lib/solaar/ui/pair_window.py:126 #, python-format -msgid "Type %(passcode)s and then press the enter key." -msgstr "Escriba %(passcode)s y luego presione la tecla Enter." +msgid "Type %(passcode)s and then press the enter key." +msgstr "Escriba %(passcode)s y luego presione la tecla Enter." #: lib/solaar/ui/pair_window.py:129 -msgid "left" -msgstr "izquierda" +msgid "left" +msgstr "izquierda" #: lib/solaar/ui/pair_window.py:129 -msgid "right" -msgstr "derecha" +msgid "right" +msgstr "derecha" #: lib/solaar/ui/pair_window.py:131 #, python-format -msgid "Press %(code)s\n" - "and then press left and right buttons simultaneously." -msgstr "Presiona %(code)s\n" - "y luego presione los botones izquierdo y derecho simultáneamente." +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"Pulse %(code)s\n" +"y luego los botones izquierdo y derecho simultáneamente." #: lib/solaar/ui/pair_window.py:188 -msgid "Pairing failed" -msgstr "Vinculación fallida" +msgid "Pairing failed" +msgstr "Vinculación fallida" #: lib/solaar/ui/pair_window.py:190 -msgid "Make sure your device is within range, and has a decent battery " - "charge." -msgstr "Asegúrese de que su dispositivo esté dentro del alcance del receptor " - "y que la batería tenga suficiente carga." +msgid "Make sure your device is within range, and has a decent battery charge." +msgstr "" +"Asegúrese de que su dispositivo esté dentro del alcance del receptor y que " +"la batería tenga suficiente carga." #: lib/solaar/ui/pair_window.py:192 -msgid "A new device was detected, but it is not compatible with this " - "receiver." -msgstr "Se detectó un nuevo dispositivo, pero no es compatible con este " - "receptor." +msgid "A new device was detected, but it is not compatible with this receiver." +msgstr "" +"Se detectó un nuevo dispositivo, pero no es compatible con este receptor." #: lib/solaar/ui/pair_window.py:194 -msgid "More paired devices than receiver can support." -msgstr "Más dispositivos emparejados de los que admite el receptor." +msgid "More paired devices than receiver can support." +msgstr "Más dispositivos emparejados de los que admite el receptor." #: lib/solaar/ui/pair_window.py:196 -msgid "No further details are available about the error." -msgstr "No hay más detalles disponibles sobre este error." +msgid "No further details are available about the error." +msgstr "No hay más detalles disponibles sobre este error." #: lib/solaar/ui/pair_window.py:210 -msgid "Found a new device:" -msgstr "Se encontró un nuevo dispositivo:" +msgid "Found a new device:" +msgstr "Se encontró un nuevo dispositivo:" #: lib/solaar/ui/pair_window.py:235 -msgid "The wireless link is not encrypted" -msgstr "La conexión inalámbrica no está cifrada" +msgid "The wireless link is not encrypted" +msgstr "La conexión inalámbrica no está cifrada" #: lib/solaar/ui/pair_window.py:264 -msgid "Press a pairing button or key until the pairing light flashes " - "quickly." -msgstr "Presione un botón o tecla de emparejamiento hasta que la luz de emparejamiento parpadee " - "rápidamente." +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "" +"Los receptores Unifying solo son compatibles con dispositivos Unifying." #: lib/solaar/ui/pair_window.py:266 -msgid "You may have to first turn the device off and on again." -msgstr "Es posible que primero deba apagar el dispositivo y volver a encenderlo." +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "Los receptores Bolt solo son compatibles con dispositivos Bolt." #: lib/solaar/ui/pair_window.py:268 -msgid "Turn on the device you want to pair." -msgstr "Encienda el dispositivo que desea vincular." +msgid "Other receivers are only compatible with a few devices." +msgstr "Otros receptores son solo compatibles con unos pocos dispositivos." #: lib/solaar/ui/pair_window.py:270 -msgid "If the device is already turned on, turn it off and on again." -msgstr "Si el dispositivo ya está encendido, apáguelo y vuelva a encenderlo." +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" +"El dispositivo no se debe estar emparejado con un receptor cercano encendido." -#: lib/solaar/ui/pair_window.py:273 -#, python-format -msgid "\n" - "\n" - "This receiver has %d pairing remaining." -msgid_plural "\n" - "\n" - "This receiver has %d pairings remaining." -msgstr[0] "\n" - "\n" - "Este receptor tiene %d emparejamiento restante." -msgstr[1] "\n" - "\n" - "Este receptor tiene %d emparejamientos restantes." +#: lib/solaar/ui/pair_window.py:274 +msgid "Press a pairing button or key until the pairing light flashes quickly." +msgstr "" +"Presione un botón o tecla de emparejamiento hasta que la luz de " +"emparejamiento parpadee rápidamente." #: lib/solaar/ui/pair_window.py:276 -msgid "\n" - "Cancelling at this point will not use up a pairing." -msgstr "\n" - "Si cancela en este punto no se usará un vínculo." +msgid "You may have to first turn the device off and on again." +msgstr "" +"Es posible que primero deba apagar el dispositivo y volver a encenderlo." + +#: lib/solaar/ui/pair_window.py:278 +msgid "Turn on the device you want to pair." +msgstr "Encienda el dispositivo que desea vincular." + +#: lib/solaar/ui/pair_window.py:280 +msgid "If the device is already turned on, turn it off and on again." +msgstr "Si el dispositivo ya está encendido, apague y vuelva a encenderlo." + +#: lib/solaar/ui/pair_window.py:283 +#, python-format +msgid "" +"\n" +"\n" +"This receiver has %d pairing remaining." +msgid_plural "" +"\n" +"\n" +"This receiver has %d pairings remaining." +msgstr[0] "" +"\n" +"\n" +"Este receptor tiene %d emparejamiento restante." +msgstr[1] "" +"\n" +"\n" +"Este receptor tiene %d emparejamientos restantes." + +#: lib/solaar/ui/pair_window.py:286 +msgid "" +"\n" +"Cancelling at this point will not use up a pairing." +msgstr "" +"\n" +"Si cancela en este punto no se usará un vínculo." #: lib/solaar/ui/tray.py:58 -msgid "No Logitech device found" -msgstr "No se encontró ningún dispositivo Logitech" +msgid "No supported device found" +msgstr "No se ha encontrado ningún dispositivo compatible" -#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:324 +#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 #, python-format -msgid "About %s" -msgstr "Acerca de %s" +msgid "About %s" +msgstr "Acerca de %s" -#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:322 +#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 #, python-format -msgid "Quit %s" -msgstr "Salir %s" +msgid "Quit %s" +msgstr "Salir de %s" -#: lib/solaar/ui/tray.py:302 lib/solaar/ui/tray.py:310 -msgid "no receiver" -msgstr "sin receptor" +#: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 +msgid "no receiver" +msgstr "sin receptor" -#: lib/solaar/ui/tray.py:326 -msgid "no status" -msgstr "sin estado" +#: lib/solaar/ui/tray.py:321 +msgid "no status" +msgstr "sin estado" -#: lib/solaar/ui/window.py:101 -msgid "Scanning" -msgstr "Explorando" +#: lib/solaar/ui/window.py:96 +msgid "Scanning" +msgstr "Explorando" -#: lib/solaar/ui/window.py:134 lib/solaar/ui/window.py:687 -msgid "Battery" -msgstr "Batería" +#: lib/solaar/ui/window.py:129 +msgid "Battery" +msgstr "Batería" -#: lib/solaar/ui/window.py:137 -msgid "Wireless Link" -msgstr "Enlace inalámbrico" +#: lib/solaar/ui/window.py:132 +msgid "Wireless Link" +msgstr "Enlace inalámbrico" -#: lib/solaar/ui/window.py:141 -msgid "Lighting" -msgstr "Iluminación" +#: lib/solaar/ui/window.py:136 +msgid "Lighting" +msgstr "Iluminación" -#: lib/solaar/ui/window.py:175 -msgid "Show Technical Details" -msgstr "Mostrar detalles técnicos" +#: lib/solaar/ui/window.py:170 +msgid "Show Technical Details" +msgstr "Mostrar detalles técnicos" -#: lib/solaar/ui/window.py:191 -msgid "Pair new device" -msgstr "Vincular nuevo dispositivo" +#: lib/solaar/ui/window.py:186 +msgid "Pair new device" +msgstr "Vincular nuevo dispositivo" -#: lib/solaar/ui/window.py:210 -msgid "Select a device" -msgstr "Seleccionar un dispositivo" +#: lib/solaar/ui/window.py:205 +msgid "Select a device" +msgstr "Seleccionar un dispositivo" -#: lib/solaar/ui/window.py:327 -msgid "Rule Editor" -msgstr "Editor de Reglas" +#: lib/solaar/ui/window.py:322 +msgid "Rule Editor" +msgstr "Editor de Reglas" -#: lib/solaar/ui/window.py:538 -msgid "Path" -msgstr "Ruta" +#: lib/solaar/ui/window.py:533 +msgid "Path" +msgstr "Ruta" -#: lib/solaar/ui/window.py:541 -msgid "USB ID" -msgstr "ID USB" +#: lib/solaar/ui/window.py:536 +msgid "USB ID" +msgstr "ID USB" -#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 -#: lib/solaar/ui/window.py:566 lib/solaar/ui/window.py:568 -msgid "Serial" -msgstr "Serial" +#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 +msgid "Serial" +msgstr "Serie" -#: lib/solaar/ui/window.py:550 -msgid "Index" -msgstr "Índice" +#: lib/solaar/ui/window.py:545 +msgid "Index" +msgstr "Índice" -#: lib/solaar/ui/window.py:552 -msgid "Wireless PID" -msgstr "PID inalámbrico" +#: lib/solaar/ui/window.py:547 +msgid "Wireless PID" +msgstr "PID inalámbrico" + +#: lib/solaar/ui/window.py:549 +msgid "Product ID" +msgstr "ID del producto" + +#: lib/solaar/ui/window.py:551 +msgid "Protocol" +msgstr "Protocolo" + +#: lib/solaar/ui/window.py:551 +msgid "Unknown" +msgstr "Desconocido" #: lib/solaar/ui/window.py:554 -msgid "Product ID" -msgstr "ID del producto" - -#: lib/solaar/ui/window.py:556 -msgid "Protocol" -msgstr "Protocolo" - -#: lib/solaar/ui/window.py:556 -msgid "Unknown" -msgstr "Desconocido" - -#: lib/solaar/ui/window.py:559 #, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d ms (%(rate_hz)dHz)" +msgid "%(rate)d ms (%(rate_hz)dHz)" +msgstr "%(rate)d ms (%(rate_hz)dHz)" -#: lib/solaar/ui/window.py:559 -msgid "Polling rate" -msgstr "Tasa de sondeo" +#: lib/solaar/ui/window.py:554 +msgid "Polling rate" +msgstr "Tasa de sondeo" -#: lib/solaar/ui/window.py:570 -msgid "Unit ID" -msgstr "ID Unidad" +#: lib/solaar/ui/window.py:565 +msgid "Unit ID" +msgstr "ID Unidad" -#: lib/solaar/ui/window.py:581 -msgid "none" -msgstr "ninguno" +#: lib/solaar/ui/window.py:576 +msgid "none" +msgstr "ninguno" -#: lib/solaar/ui/window.py:582 -msgid "Notifications" -msgstr "Notificaciones" +#: lib/solaar/ui/window.py:577 +msgid "Notifications" +msgstr "Notificaciones" -#: lib/solaar/ui/window.py:619 -msgid "No device paired." -msgstr "No hay dispositivos vinculados." +#: lib/solaar/ui/window.py:621 +msgid "No device paired." +msgstr "No hay dispositivos vinculados." -#: lib/solaar/ui/window.py:626 +#: lib/solaar/ui/window.py:628 #, python-format -msgid "Up to %(max_count)s device can be paired to this receiver." -msgid_plural "Up to %(max_count)s devices can be paired to this receiver." -msgstr[0] "Se puede vincular hasta %(max_count)s dispositivo a este " - "receptor." -msgstr[1] "Se pueden vincular hasta %(max_count)s dispositivos a este " - "receptor." +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "Se puede vincular hasta %(max_count)s dispositivo a este receptor." +msgstr[1] "" +"Se pueden vincular hasta %(max_count)s dispositivos a este receptor." -#: lib/solaar/ui/window.py:632 -msgid "Only one device can be paired to this receiver." -msgstr "Solo un dispositivo se puede vincular a este receptor." +#: lib/solaar/ui/window.py:634 +msgid "Only one device can be paired to this receiver." +msgstr "Solo un dispositivo se puede vincular a este receptor." -#: lib/solaar/ui/window.py:636 +#: lib/solaar/ui/window.py:638 #, python-format -msgid "This receiver has %d pairing remaining." -msgid_plural "This receiver has %d pairings remaining." -msgstr[0] "Este receptor tiene %d emparejamiento restante." -msgstr[1] "Este receptor tiene %d emparejamientos restantes." +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "Este receptor tiene %d emparejamiento restante." +msgstr[1] "Este receptor tiene %d emparejamientos restantes." -#: lib/solaar/ui/window.py:688 -msgid "unknown" -msgstr "desconocido" +#: lib/solaar/ui/window.py:692 +msgid "Battery Voltage" +msgstr "Voltaje de la batería" -#: lib/solaar/ui/window.py:689 -msgid "Battery information unknown." -msgstr "Información de la batería desconocida." +#: lib/solaar/ui/window.py:694 +msgid "Voltage reported by battery" +msgstr "Voltaje informado por la batería" -#: lib/solaar/ui/window.py:699 -msgid "Battery Voltage" -msgstr "Voltaje de la batería" +#: lib/solaar/ui/window.py:696 +msgid "Battery Level" +msgstr "Nivel de Batería" -#: lib/solaar/ui/window.py:701 -msgid "Voltage reported by battery" -msgstr "Voltaje informado por la batería" +#: lib/solaar/ui/window.py:698 +msgid "Approximate level reported by battery" +msgstr "Nivel aproximado informado por batería" -#: lib/solaar/ui/window.py:703 -msgid "Battery Level" -msgstr "Nivel de Batería" +#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 +msgid "next reported " +msgstr "siguiente informe " -#: lib/solaar/ui/window.py:705 -msgid "Approximate level reported by battery" -msgstr "Nivel aproximado informado por batería" +#: lib/solaar/ui/window.py:708 +msgid " and next level to be reported." +msgstr " y siguiente nivel que será reportado." -#: lib/solaar/ui/window.py:712 lib/solaar/ui/window.py:714 -msgid "next reported " -msgstr "siguiente informe " +#: lib/solaar/ui/window.py:713 +msgid "last known" +msgstr "último conocido" -#: lib/solaar/ui/window.py:715 -msgid " and next level to be reported." -msgstr " y siguiente nivel que será reportado." +#: lib/solaar/ui/window.py:724 +msgid "encrypted" +msgstr "cifrado" -#: lib/solaar/ui/window.py:720 -msgid "last known" -msgstr "último conocido" +#: lib/solaar/ui/window.py:726 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "" +"La conexión inalámbrica entre el dispositivo y su receptor está cifrada." #: lib/solaar/ui/window.py:728 -msgid "not encrypted" -msgstr "no cifrado" +msgid "not encrypted" +msgstr "no cifrado" #: lib/solaar/ui/window.py:732 -msgid "The wireless link between this device and its receiver is not " - "encrypted.\n" - "This is a security issue for pointing devices, and a major security " - "issue for text-input devices." -msgstr "El enlace inalámbrico entre este dispositivo y su receptor " - "no está encriptado.\n" - "Este es un problema de seguridad para los dispositivos " - "señaladores y un problema de seguridad " - "importante para los dispositivos de entrada de texto." +msgid "" +"The wireless link between this device and its receiver is not encrypted.\n" +"This is a security issue for pointing devices, and a major security issue " +"for text-input devices." +msgstr "" +"El enlace inalámbrico entre este dispositivo y su receptor no está " +"encriptado.\n" +"Este es un problema de seguridad para los dispositivos señaladores y un " +"problema de seguridad importante para los dispositivos de entrada de texto." -#: lib/solaar/ui/window.py:737 -msgid "encrypted" -msgstr "cifrado" - -#: lib/solaar/ui/window.py:739 -msgid "The wireless link between this device and its receiver is encrypted." -msgstr "La conexión inalámbrica entre el dispositivo y su receptor está " - "cifrada." - -#: lib/solaar/ui/window.py:752 +#: lib/solaar/ui/window.py:748 #, python-format -msgid "%(light_level)d lux" -msgstr "%(light_level)d lux" +msgid "%(light_level)d lux" +msgstr "%(light_level)d lux" -#~ msgid "\n" -#~ "\n" -#~ "This receiver has %d pairing(s) remaining." -#~ msgstr "\n" -#~ "\n" -#~ "Este receptor tiene %d vínculo(s) disponibles." +#~ msgid "" +#~ "\n" +#~ "\n" +#~ "This receiver has %d pairing(s) remaining." +#~ msgstr "" +#~ "\n" +#~ "\n" +#~ "Este receptor tiene %d vínculo(s) disponibles." -#~ msgid "%(battery_level)s" -#~ msgstr "%(battery_level)s" +#~ msgid "%(battery_level)s" +#~ msgstr "%(battery_level)s" -#~ msgid "%(battery_percent)d%%" -#~ msgstr "%(battery_percent)d%%" +#~ msgid "%(battery_percent)d%%" +#~ msgstr "%(battery_percent)d%%" -#~ msgid "Add action" -#~ msgstr "Añadir acción" +#~ msgid "Add action" +#~ msgstr "Añadir acción" -#~ msgid "Adjust the DPI by sliding the mouse horizontally while " -#~ "holding the button down." -#~ msgstr "Ajustar los DPI deslizando el ratón horizontalmente mientras " -#~ "se mantiene el botón presionado." +#~ msgid "" +#~ "Adjust the DPI by sliding the mouse horizontally while holding the button " +#~ "down." +#~ msgstr "" +#~ "Ajustar los DPI deslizando el ratón horizontalmente mientras se mantiene " +#~ "el botón presionado." -#~ msgid "Automatically switch the mouse wheel between ratchet and " -#~ "freespin mode.\n" -#~ "The mouse wheel is always free at 0, and always locked at 50" -#~ msgstr "Cambia automáticamente el modo de la rueda del ratón entre " -#~ "bloqueado y giro libre.\n" -#~ "La rueda del ratón siempre está libre a 0 y siempre bloqueado a 50" +#~ msgid "" +#~ "Automatically switch the mouse wheel between ratchet and freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always locked at 50" +#~ msgstr "" +#~ "Cambia automáticamente el modo de la rueda del ratón entre bloqueado y " +#~ "giro libre.\n" +#~ "La rueda del ratón siempre está libre a 0 y siempre bloqueado a 50" -#~ msgid "Automatically switch the mouse wheel between ratchet and " -#~ "freespin mode.\n" -#~ "The mouse wheel is always free at 0, and always ratcheted at 50" -#~ msgstr "Cambiar automáticamente la rueda del mouse entre el modo de " -#~ "trinquete y el modo de giro libre.\n" -#~ "La rueda del mouse siempre está libre en 0 y siempre con trinquete " -#~ "en 50" +#~ msgid "" +#~ "Automatically switch the mouse wheel between ratchet and freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always ratcheted at 50" +#~ msgstr "" +#~ "Cambiar automáticamente la rueda del mouse entre el modo de trinquete y " +#~ "el modo de giro libre.\n" +#~ "La rueda del mouse siempre está libre en 0 y siempre con trinquete en 50" -#~ msgid "DPI Sliding Adjustment" -#~ msgstr "Ajustar DPI deslizando" +#~ msgid "Battery information unknown." +#~ msgstr "Información de la batería desconocida." -#~ msgid "Effectively turns off thumb scrolling in Linux." -#~ msgstr "Desactiva eficazmente el desplazamiento con el pulgar en " -#~ "Linux." +#~ msgid "Count" +#~ msgstr "Cuenta" -#~ msgid "Effectively turns off wheel scrolling in Linux." -#~ msgstr "Desactiva eficazmente el desplazamiento de la rueda en Linux." +#~ msgid "DPI Sliding Adjustment" +#~ msgstr "Ajustar DPI deslizando" -#~ msgid "HID++ Scrolling" -#~ msgstr "Desplazamiento HID++" +#~ msgid "" +#~ "Diverted key or button depressed or released.\n" +#~ "Use the Key/Button Diversion setting to divert keys and buttons." +#~ msgstr "" +#~ "Tecla desviada o botón presionado o liberado.\n" +#~ "Use la configuración Desvío de teclas/botones para desviar teclas y " +#~ "botones." -#~ msgid "HID++ mode for horizontal scroll with the thumb wheel." -#~ msgstr "Modo HID++ para desplazamientor horizontal con la rueda del " -#~ "pulgar." +#~ msgid "Effectively turns off thumb scrolling in Linux." +#~ msgstr "Desactiva eficazmente el desplazamiento con el pulgar en Linux." -#~ msgid "HID++ mode for vertical scroll with the wheel." -#~ msgstr "Modo HID++ para el desplazamiento vertical con la rueda." +#~ msgid "Effectively turns off wheel scrolling in Linux." +#~ msgstr "Desactiva eficazmente el desplazamiento de la rueda en Linux." -#~ msgid "High Resolution Scrolling" -#~ msgstr "Desplazamiento de alta resolución" +#, python-format +#~ msgid "" +#~ "Found a Logitech Receiver (%s), but did not have permission to open it." +#~ msgstr "" +#~ "Se encontró un receptor Logitech (%s), pero no tiene permisos para " +#~ "abrirlo." -#~ msgid "High Resolution Wheel Invert" -#~ msgstr "Invertir rueda en alta resolución " +#~ msgid "HID++ Scrolling" +#~ msgstr "Desplazamiento HID++" -#~ msgid "High-sensitivity wheel invert mode for vertical scroll." -#~ msgstr "Modo inverso de alta resolución en la rueda para " -#~ "desplazamiento vertical." +#~ msgid "HID++ mode for horizontal scroll with the thumb wheel." +#~ msgstr "Modo HID++ para desplazamientor horizontal con la rueda del pulgar." -#~ msgid "If the device is already turned on, turn if off and on again." -#~ msgstr "Si el dispositivo ya está encendido, apáguelo y vuelva a " -#~ "encenderlo." +#~ msgid "HID++ mode for vertical scroll with the wheel." +#~ msgstr "Modo HID++ para el desplazamiento vertical con la rueda." -#~ msgid "Make the key or button send HID++ notifications (which " -#~ "trigger Solaar rules but are otherwise ignored)." -#~ msgstr "Haga que la tecla o el botón envíe notificaciones HID ++ " -#~ "(que activan las reglas de Solaar, de lo contrario se ignoran)." +#~ msgid "High Resolution Scrolling" +#~ msgstr "Desplazamiento de alta resolución" -#~ msgid "No Logitech receiver found" -#~ msgstr "No se encontró ningún receptor Logitech" +#~ msgid "High Resolution Wheel Invert" +#~ msgstr "Invertir rueda en alta resolución " -#~ msgid "Scroll Wheel Rachet" -#~ msgstr "Trinquete Rueda Desplazamiento" +#~ msgid "High-sensitivity wheel invert mode for vertical scroll." +#~ msgstr "" +#~ "Modo inverso de alta resolución en la rueda para desplazamiento vertical." -#~ msgid "Send a gesture by sliding the mouse while holding the button " -#~ "down." -#~ msgstr "Envíe un gesto deslizando el ratón mientras mantiene " -#~ "presionado el botón." +#~ msgid "If the device is already turned on, turn if off and on again." +#~ msgstr "" +#~ "Si el dispositivo ya está encendido, apáguelo y vuelva a encenderlo." -#~ msgid "Shows status of devices connected\n" -#~ "through wireless Logitech receivers." -#~ msgstr "Mostrar el estado de dispositivos conectados\n" -#~ "mediante receptores inalámbricos Logitech." +#~ msgid "" +#~ "If you've just installed Solaar, try removing the receiver and plugging " +#~ "it back in." +#~ msgstr "" +#~ "Si acaba de instalar Solaar, intente quitar el receptor y conectarlo de " +#~ "nuevo." -#~ msgid "Smooth Scrolling" -#~ msgstr "Desplazamiento suave" +#~ msgid "" +#~ "Make the key or button send HID++ notifications (which trigger Solaar " +#~ "rules but are otherwise ignored)." +#~ msgstr "" +#~ "Haga que la tecla o el botón envíe notificaciones HID ++ (que activan las " +#~ "reglas de Solaar, de lo contrario se ignoran)." -#~ msgid "The receiver only supports %d paired device(s)." -#~ msgstr "El receptor solo soporta %d dispositivo(s) vinculado(s)." +#~ msgid "No Logitech device found" +#~ msgstr "No se encontró ningún dispositivo Logitech" -#~ msgid "The receiver was unplugged." -#~ msgstr "El receptor se desconectó." +#~ msgid "No Logitech receiver found" +#~ msgstr "No se encontró ningún receptor Logitech" -#~ msgid "The wireless link between this device and its receiver is " -#~ "not encrypted.\n" -#~ "\n" -#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " -#~ "security issue.\n" -#~ "\n" -#~ "It is, however, a major security issue for text-input devices " -#~ "(keyboards, numpads),\n" -#~ "because typed text can be sniffed inconspicuously by 3rd parties " -#~ "within range." -#~ msgstr "La conexión inalámbrica entre el dispositivo y su receptor " -#~ "no está cifrada.\n" -#~ "\n" -#~ "Para dispositivos apuntadores (ratones, trackballs, trackpads), este " -#~ "es un problema menor de seguridad.\n" -#~ "\n" -#~ "Sin embargo, para dispositivos de entrada de texto (teclados, " -#~ "teclados numéricos) sí es un problema grave,\n" -#~ "pues el texto introducido puede ser capturado de forma inadvertida " -#~ "por terceros que estén cerca." +#~ msgid "Scroll Wheel Rachet" +#~ msgstr "Trinquete Rueda Desplazamiento" -#~ msgid "This receiver has %d pairing(s) remaining." -#~ msgstr "Este receptor tiene %d vínculo(s) disponibles." +#~ msgid "Send a gesture by sliding the mouse while holding the button down." +#~ msgstr "" +#~ "Envíe un gesto deslizando el ratón mientras mantiene presionado el botón." -#~ msgid "Top-most coordinate." -#~ msgstr "Coordenada superior." +#~ msgid "" +#~ "Shows status of devices connected\n" +#~ "through wireless Logitech receivers." +#~ msgstr "" +#~ "Mostrar el estado de dispositivos conectados\n" +#~ "mediante receptores inalámbricos Logitech." -#~ msgid "USB id" -#~ msgstr "id USB" +#~ msgid "Smooth Scrolling" +#~ msgstr "Desplazamiento suave" -#~ msgid "Wheel Resolution" -#~ msgstr "Resolución de la rueda" +#~ msgid "The receiver only supports %d paired device(s)." +#~ msgstr "El receptor solo soporta %d dispositivo(s) vinculado(s)." -#~ msgid "height" -#~ msgstr "altura" +#~ msgid "The receiver was unplugged." +#~ msgstr "El receptor se desconectó." -#~ msgid "top" -#~ msgstr "superior" +#~ msgid "" +#~ "The wireless link between this device and its receiver is not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices (keyboards, " +#~ "numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties within " +#~ "range." +#~ msgstr "" +#~ "La conexión inalámbrica entre el dispositivo y su receptor no está " +#~ "cifrada.\n" +#~ "\n" +#~ "Para dispositivos apuntadores (ratones, trackballs, trackpads), este es " +#~ "un problema menor de seguridad.\n" +#~ "\n" +#~ "Sin embargo, para dispositivos de entrada de texto (teclados, teclados " +#~ "numéricos) sí es un problema grave,\n" +#~ "pues el texto introducido puede ser capturado de forma inadvertida por " +#~ "terceros que estén cerca." -#~ msgid "width" -#~ msgstr "anchura" +#~ msgid "This receiver has %d pairing(s) remaining." +#~ msgstr "Este receptor tiene %d vínculo(s) disponibles." + +#~ msgid "Top-most coordinate." +#~ msgstr "Coordenada superior." + +#~ msgid "" +#~ "Try removing the device and plugging it back in or turning it off and " +#~ "then on." +#~ msgstr "" +#~ "Intente quitar el dispositivo y volver a enchufarlo o apagarlo y " +#~ "encenderlo." + +#~ msgid "USB id" +#~ msgstr "id USB" + +#~ msgid "Wheel Resolution" +#~ msgstr "Resolución de la rueda" + +#~ msgid "height" +#~ msgstr "altura" + +#~ msgid "top" +#~ msgstr "superior" + +#~ msgid "unknown" +#~ msgstr "desconocido" + +#~ msgid "width" +#~ msgstr "anchura" diff --git a/po/fi.po b/po/fi.po index 00f1c610..85ac6308 100644 --- a/po/fi.po +++ b/po/fi.po @@ -3,1440 +3,2251 @@ # This file is distributed under the same license as the solaar package. # Automatically generated, 2013. # -msgid "" -msgstr "Project-Id-Version: solaar 0.9.2\n" - "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2021-09-25 13:56-0400\n" - "PO-Revision-Date: 2013-08-05 18:49+0300\n" - "Last-Translator: Tomi Leppänen\n" - "Language-Team: none\n" - "Language: fi\n" - "MIME-Version: 1.0\n" - "Content-Type: text/plain; charset=UTF-8\n" - "Content-Transfer-Encoding: 8bit\n" - "Plural-Forms: nplurals=2; plural=(n != 1);\n" +msgid "" +msgstr "" +"Project-Id-Version: solaar 0.9.2\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-03-05 13:08+0200\n" +"PO-Revision-Date: 2013-08-05 18:49+0300\n" +"Last-Translator: Niko Savola\n" +"Language-Team: none\n" +"Language: fi\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: lib/logitech_receiver/base_usb.py:50 -msgid "Unifying Receiver" -msgstr "" +#: lib/logitech_receiver/base_usb.py:52 +msgid "Bolt Receiver" +msgstr "Bolt-vastaanotin" -#: lib/logitech_receiver/base_usb.py:58 lib/logitech_receiver/base_usb.py:68 -#: lib/logitech_receiver/base_usb.py:79 lib/logitech_receiver/base_usb.py:90 -#: lib/logitech_receiver/base_usb.py:101 -msgid "Nano Receiver" -msgstr "" +#: lib/logitech_receiver/base_usb.py:64 +msgid "Unifying Receiver" +msgstr "Unifying-vastaanotin" -#: lib/logitech_receiver/base_usb.py:109 -msgid "Lightspeed Receiver" -msgstr "" +#: lib/logitech_receiver/base_usb.py:75 lib/logitech_receiver/base_usb.py:87 +#: lib/logitech_receiver/base_usb.py:100 lib/logitech_receiver/base_usb.py:113 +msgid "Nano Receiver" +msgstr "Nano-vastaanotin" -#: lib/logitech_receiver/base_usb.py:117 -msgid "EX100 Receiver 27 Mhz" -msgstr "" +#: lib/logitech_receiver/base_usb.py:125 +msgid "Lightspeed Receiver" +msgstr "Lightspeed-vastaanotin" -#: lib/logitech_receiver/device.py:133 lib/solaar/ui/window.py:693 -msgid "unknown" -msgstr "tuntematon" +#: lib/logitech_receiver/base_usb.py:136 +msgid "EX100 Receiver 27 Mhz" +msgstr "EX100 27 MHz -vastaanotin" + +#: lib/logitech_receiver/common.py:649 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Akku: %(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:652 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Akku: %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:1048 +#: lib/logitech_receiver/settings_templates.py:299 +msgid "Disabled" +msgstr "Pois käytöstä" + +#: lib/logitech_receiver/hidpp20.py:1049 +msgid "Static" +msgstr "Muuttumaton" + +#: lib/logitech_receiver/hidpp20.py:1050 +msgid "Pulse" +msgstr "Pulssi" + +#: lib/logitech_receiver/hidpp20.py:1051 +msgid "Cycle" +msgstr "Jakso" + +#: lib/logitech_receiver/hidpp20.py:1052 +msgid "Boot" +msgstr "Käynnistys" + +#: lib/logitech_receiver/hidpp20.py:1053 +msgid "Demo" +msgstr "Esittely" + +#: lib/logitech_receiver/hidpp20.py:1055 +msgid "Breathe" +msgstr "Hengitys" + +#: lib/logitech_receiver/hidpp20.py:1058 +msgid "Ripple" +msgstr "Väreily" + +#: lib/logitech_receiver/hidpp20.py:1059 +msgid "Decomposition" +msgstr "Hajoaminen" + +#: lib/logitech_receiver/hidpp20.py:1060 +msgid "Signature1" +msgstr "Allekirjoitus1" + +#: lib/logitech_receiver/hidpp20.py:1061 +msgid "Signature2" +msgstr "Allekirjoitus2" + +#: lib/logitech_receiver/hidpp20.py:1062 +msgid "CycleS" +msgstr "SykliS" + +#: lib/logitech_receiver/hidpp20.py:1126 +msgid "Unknown Location" +msgstr "Tuntematon sijainti" + +#: lib/logitech_receiver/hidpp20.py:1127 +msgid "Primary" +msgstr "Ensisijainen" + +#: lib/logitech_receiver/hidpp20.py:1128 +msgid "Logo" +msgstr "Logo" + +#: lib/logitech_receiver/hidpp20.py:1129 +msgid "Left Side" +msgstr "Vasen puoli" + +#: lib/logitech_receiver/hidpp20.py:1130 +msgid "Right Side" +msgstr "Oikea puoli" + +#: lib/logitech_receiver/hidpp20.py:1131 +msgid "Combined" +msgstr "Yhdistetty" + +#: lib/logitech_receiver/hidpp20.py:1132 +msgid "Primary 1" +msgstr "Ensisijainen 1" + +#: lib/logitech_receiver/hidpp20.py:1133 +msgid "Primary 2" +msgstr "Ensisijainen 2" + +#: lib/logitech_receiver/hidpp20.py:1134 +msgid "Primary 3" +msgstr "Ensisijainen 3" + +#: lib/logitech_receiver/hidpp20.py:1135 +msgid "Primary 4" +msgstr "Ensisijainen 4" + +#: lib/logitech_receiver/hidpp20.py:1136 +msgid "Primary 5" +msgstr "Ensisijainen 5" + +#: lib/logitech_receiver/hidpp20.py:1137 +msgid "Primary 6" +msgstr "Ensisijainen 6" + +#: lib/logitech_receiver/i18n.py:28 +msgid "empty" +msgstr "tyhjä" + +#: lib/logitech_receiver/i18n.py:29 +msgid "critical" +msgstr "kriittinen" + +#: lib/logitech_receiver/i18n.py:30 +msgid "low" +msgstr "matala" + +#: lib/logitech_receiver/i18n.py:31 +msgid "average" +msgstr "keskiarvo" + +#: lib/logitech_receiver/i18n.py:32 +msgid "good" +msgstr "hyvä" + +#: lib/logitech_receiver/i18n.py:33 +msgid "full" +msgstr "täysi" + +#: lib/logitech_receiver/i18n.py:35 +msgid "discharging" +msgstr "tyhjenee" + +#: lib/logitech_receiver/i18n.py:36 +msgid "recharging" +msgstr "latautuu" + +#: lib/logitech_receiver/i18n.py:37 lib/solaar/ui/window.py:700 +msgid "charging" +msgstr "lataa" #: lib/logitech_receiver/i18n.py:38 -msgid "empty" -msgstr "tyhjä" +msgid "not charging" +msgstr "ei latauksessa" #: lib/logitech_receiver/i18n.py:39 -msgid "critical" -msgstr "kriittinen" +msgid "almost full" +msgstr "lähes täysi" #: lib/logitech_receiver/i18n.py:40 -msgid "low" -msgstr "matala" +msgid "charged" +msgstr "ladattu" #: lib/logitech_receiver/i18n.py:41 -msgid "average" -msgstr "" +msgid "slow recharge" +msgstr "hidas lataus" #: lib/logitech_receiver/i18n.py:42 -msgid "good" -msgstr "hyvä" +#, fuzzy +msgid "invalid battery" +msgstr "virheellinen akku" #: lib/logitech_receiver/i18n.py:43 -msgid "full" -msgstr "täysi" +msgid "thermal error" +msgstr "lämpötilasta johtuva virhe" + +#: lib/logitech_receiver/i18n.py:44 +msgid "error" +msgstr "virhe" + +#: lib/logitech_receiver/i18n.py:45 +msgid "standard" +msgstr "vakio" #: lib/logitech_receiver/i18n.py:46 -msgid "discharging" -msgstr "tyhjenee" +msgid "fast" +msgstr "nopea" #: lib/logitech_receiver/i18n.py:47 -msgid "recharging" -msgstr "latautuu" - -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:721 -msgid "charging" -msgstr "lataa" +msgid "slow" +msgstr "hidas" #: lib/logitech_receiver/i18n.py:49 -msgid "not charging" -msgstr "" +msgid "device timeout" +msgstr "laitteen aikakatkaisu" #: lib/logitech_receiver/i18n.py:50 -msgid "almost full" -msgstr "lähes täysi" +msgid "device not supported" +msgstr "laite ei ole tuettu" #: lib/logitech_receiver/i18n.py:51 -msgid "charged" -msgstr "" +msgid "too many devices" +msgstr "liian monta laitetta" #: lib/logitech_receiver/i18n.py:52 -msgid "slow recharge" -msgstr "hidas lataus" +msgid "sequence timeout" +msgstr "sarjan aikakatkaisu" -#: lib/logitech_receiver/i18n.py:53 -#, fuzzy -msgid "invalid battery" -msgstr "virheellinen akku" - -#: lib/logitech_receiver/i18n.py:54 -msgid "thermal error" -msgstr "lämpötilasta johtuva virhe" +#: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:555 +msgid "Firmware" +msgstr "Laiteohjelmisto" #: lib/logitech_receiver/i18n.py:55 -msgid "error" -msgstr "" +msgid "Bootloader" +msgstr "Alkulatausohjelma" #: lib/logitech_receiver/i18n.py:56 -msgid "standard" -msgstr "" +msgid "Hardware" +msgstr "Laitteisto" #: lib/logitech_receiver/i18n.py:57 -msgid "fast" -msgstr "" +msgid "Other" +msgstr "Muu" -#: lib/logitech_receiver/i18n.py:58 -msgid "slow" -msgstr "" +#: lib/logitech_receiver/i18n.py:59 +msgid "Left Button" +msgstr "Vasen painike" + +#: lib/logitech_receiver/i18n.py:60 +msgid "Right Button" +msgstr "Oikea painike" #: lib/logitech_receiver/i18n.py:61 -msgid "device timeout" -msgstr "laitteen aikakatkaisu" +msgid "Middle Button" +msgstr "Keskipainike" #: lib/logitech_receiver/i18n.py:62 -msgid "device not supported" -msgstr "laite ei ole tuettu" +msgid "Back Button" +msgstr "Edellinen-painike" #: lib/logitech_receiver/i18n.py:63 -msgid "too many devices" -msgstr "liian monta laitetta" +msgid "Forward Button" +msgstr "Seuraava-painike" #: lib/logitech_receiver/i18n.py:64 -msgid "sequence timeout" -msgstr "sarjan aikakatkaisu" +msgid "Mouse Gesture Button" +msgstr "Hiiren elepainike" -#: lib/logitech_receiver/i18n.py:67 lib/solaar/ui/window.py:580 -msgid "Firmware" -msgstr "Laiteohjelmisto" +#: lib/logitech_receiver/i18n.py:65 +msgid "Smart Shift" +msgstr "Smart Shift" + +#: lib/logitech_receiver/i18n.py:66 +msgid "DPI Switch" +msgstr "DPI-kytkin" + +#: lib/logitech_receiver/i18n.py:67 +msgid "Left Tilt" +msgstr "Kallistus vasemmalle" #: lib/logitech_receiver/i18n.py:68 -msgid "Bootloader" -msgstr "Alkulatausohjelma" +msgid "Right Tilt" +msgstr "Kallistus oikealle" #: lib/logitech_receiver/i18n.py:69 -msgid "Hardware" -msgstr "Laitteisto" +msgid "Left Click" +msgstr "Vasen napsautus" #: lib/logitech_receiver/i18n.py:70 -msgid "Other" -msgstr "Muu" +msgid "Right Click" +msgstr "Oikea napsautus" + +#: lib/logitech_receiver/i18n.py:71 +msgid "Mouse Middle Button" +msgstr "Hiiren keskipainike" + +#: lib/logitech_receiver/i18n.py:72 +msgid "Mouse Back Button" +msgstr "Hiiren edellinen-painike" #: lib/logitech_receiver/i18n.py:73 -msgid "Left Button" -msgstr "" +msgid "Mouse Forward Button" +msgstr "Hiiren seuraava-painike" #: lib/logitech_receiver/i18n.py:74 -msgid "Right Button" -msgstr "" +msgid "Gesture Button Navigation" +msgstr "Elepainikkeen navigointi" #: lib/logitech_receiver/i18n.py:75 -msgid "Middle Button" -msgstr "" +msgid "Mouse Scroll Left Button" +msgstr "Hiiren vieritys vasemmalle -painike" #: lib/logitech_receiver/i18n.py:76 -msgid "Back Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:77 -msgid "Forward Button" -msgstr "" +msgid "Mouse Scroll Right Button" +msgstr "Hiiren vieritys oikealle -painike" #: lib/logitech_receiver/i18n.py:78 -msgid "Mouse Gesture Button" -msgstr "" +msgid "pressed" +msgstr "painettu" #: lib/logitech_receiver/i18n.py:79 -msgid "Smart Shift" -msgstr "" - -#: lib/logitech_receiver/i18n.py:80 -msgid "DPI Switch" -msgstr "" - -#: lib/logitech_receiver/i18n.py:81 -msgid "Left Tilt" -msgstr "" - -#: lib/logitech_receiver/i18n.py:82 -msgid "Right Tilt" -msgstr "" - -#: lib/logitech_receiver/i18n.py:83 -msgid "Left Click" -msgstr "" - -#: lib/logitech_receiver/i18n.py:84 -msgid "Right Click" -msgstr "" - -#: lib/logitech_receiver/i18n.py:85 -msgid "Mouse Middle Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:86 -msgid "Mouse Back Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:87 -msgid "Mouse Forward Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:88 -msgid "Gesture Button Navigation" -msgstr "" - -#: lib/logitech_receiver/i18n.py:89 -msgid "Mouse Scroll Left Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:90 -msgid "Mouse Scroll Right Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:93 -msgid "pressed" -msgstr "" - -#: lib/logitech_receiver/i18n.py:94 -msgid "released" -msgstr "" - -#: lib/logitech_receiver/notifications.py:76 -msgid "pairing lock is closed" -msgstr "" - -#: lib/logitech_receiver/notifications.py:76 -msgid "pairing lock is open" -msgstr "" - -#: lib/logitech_receiver/notifications.py:160 lib/solaar/ui/notify.py:123 -msgid "connected" -msgstr "yhdistetty" - -#: lib/logitech_receiver/notifications.py:160 -msgid "disconnected" -msgstr "" - -#: lib/logitech_receiver/notifications.py:198 lib/solaar/ui/notify.py:121 -msgid "unpaired" -msgstr "parittamaton" - -#: lib/logitech_receiver/notifications.py:248 -msgid "powered on" -msgstr "virta päällä" - -#: lib/logitech_receiver/settings.py:526 -msgid "register" -msgstr "" - -#: lib/logitech_receiver/settings.py:542 lib/logitech_receiver/settings.py:568 -msgid "feature" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Hand Detection" -msgstr "Käsien tunnistus" - -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "Käynnistä valaistus, kun kädet ovat näppämistön yläpuolella." - -#: lib/logitech_receiver/settings_templates.py:72 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:73 -#: lib/logitech_receiver/settings_templates.py:78 -#: lib/logitech_receiver/settings_templates.py:85 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "Korkean herkkyyden tila pystyvieritykseen hiiren rullalla." - -#: lib/logitech_receiver/settings_templates.py:74 -msgid "Side Scrolling" -msgstr "Sivuttaisvieritys" - -#: lib/logitech_receiver/settings_templates.py:75 -msgid "When disabled, pushing the wheel sideways sends custom button " - "events\n" - "instead of the standard side-scrolling events." -msgstr "Pois päältä ollessaan, rullan painaminen sivuttain lähettää " - "muokattuja\n" - "toimintoja normaalien sivuttaisvieritystoimintojen sijaan." - -#: lib/logitech_receiver/settings_templates.py:77 -msgid "Scroll Wheel High Resolution" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:79 -msgid "Scroll Wheel Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:80 -msgid "HID++ mode for vertical scroll with the wheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:81 -msgid "Effectively turns off wheel scrolling in Linux." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:82 -msgid "Scroll Wheel Direction" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:83 -msgid "Invert direction for vertical scroll with wheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:84 -msgid "Scroll Wheel Resolution" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Frequency of device polling, in milliseconds" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Polling Rate (ms)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:87 -msgid "Swap Fx function" -msgstr "Vaihda Fx" - -#: lib/logitech_receiver/settings_templates.py:88 -msgid "When set, the F1..F12 keys will activate their special function,\n" - "and you must hold the FN key to activate their standard function." -msgstr "Asetettu: F1..F12 näppäimet aktivoivat erityistoimintonsa ja \n" - "FN-näppäintä on painettava, jotta ne aktivoisivat normaalin\n" - "toimintonsa." - -#: lib/logitech_receiver/settings_templates.py:90 -msgid "When unset, the F1..F12 keys will activate their standard function,\n" - "and you must hold the FN key to activate their special function." -msgstr "Ei asetettu: F1..F12 näppäimet aktivoivat tavallisen toimintonsa\n" - "ja FN-näppäintä on painettava, jotta ne aktivoisivat\n" - "erityistoimintonsa." - -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Mouse movement sensitivity" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Sensitivity (DPI)" -msgstr "Herkkyys (DPI)" - -#: lib/logitech_receiver/settings_templates.py:93 -msgid "Sensitivity (Pointer Speed)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:94 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:95 -msgid "Scroll Wheel Rachet" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:96 -msgid "Automatically switch the mouse wheel between ratchet and freespin " - "mode.\n" - "The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:98 -msgid "Backlight" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:98 -msgid "Turn illumination on or off on keyboard." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:99 -msgid "Key/Button Actions" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:100 -msgid "Change the action for the key or button." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:101 -msgid "Changing important actions (such as for the left mouse button) can " - "result in an unusable system." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:102 -msgid "Key/Button Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:103 -msgid "Make the key or button send HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable keys" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable specific keyboard keys." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Change keys to match OS." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Set OS" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Change Host" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Switch connection to a different host" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:107 -msgid "Thumb Wheel Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:108 -msgid "HID++ mode for horizontal scroll with the thumb wheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:109 -msgid "Effectively turns off thumb scrolling in Linux." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Invert thumb wheel scroll direction." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Thumb Wheel Direction" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Gestures" -msgstr "Eleet" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Gesture params" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:113 -msgid "DPI Sliding Adjustment" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:114 -msgid "Adjust the DPI by sliding the mouse horizontally while holding the " - "button down." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:115 -msgid "Mouse Gestures" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:116 -msgid "Send a gesture by sliding the mouse while holding the button down." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:117 -msgid "Divert crown events" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:118 -msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:119 -msgid "Divert G Keys" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:120 -msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Performs a left click." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Single tap" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Performs a right click." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Single tap with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:125 -msgid "Single tap with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Double tap" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Performs a double click." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:130 -msgid "Double tap with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:131 -msgid "Double tap with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Drags items by dragging the finger after double tapping." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Tap and drag" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:135 -msgid "Tap and drag with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:136 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:137 -msgid "Tap and drag with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:139 -msgid "Suppress tap and edge gestures" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:140 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "" +msgid "released" +msgstr "vapautettu" + +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 +msgid "connected" +msgstr "yhdistetty" + +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 +msgid "disconnected" +msgstr "yhteys katkaistu" + +#: lib/logitech_receiver/notifications.py:182 +msgid "unpaired" +msgstr "parittamaton" + +#: lib/logitech_receiver/notifications.py:231 +msgid "powered on" +msgstr "virta päällä" + +#: lib/logitech_receiver/notifications.py:283 +msgid "ADC measurement notification" +msgstr "ADC-mittausilmoitus" + +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:484 +msgid "pairing lock is closed" +msgstr "parituslukko on suljettu" + +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:484 +msgid "pairing lock is open" +msgstr "parituslukko on auki" + +#: lib/logitech_receiver/notifications.py:447 +msgid "discovery lock is closed" +msgstr "etsintälukko on suljettu" + +#: lib/logitech_receiver/notifications.py:447 +msgid "discovery lock is open" +msgstr "etsintälukko on auki" + +#: lib/logitech_receiver/receiver.py:437 +msgid "No paired devices." +msgstr "Ei paritettuja laitteita." + +#: lib/logitech_receiver/receiver.py:439 lib/solaar/ui/window.py:606 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s paritettu laite." +msgstr[1] "%(count)s paritettua laitetta." + +#: lib/logitech_receiver/settings.py:602 +msgid "register" +msgstr "rekisteri" + +#: lib/logitech_receiver/settings.py:616 lib/logitech_receiver/settings.py:654 +msgid "feature" +msgstr "ominaisuus" + +#: lib/logitech_receiver/settings_templates.py:138 +msgid "Swap Fx function" +msgstr "Vaihda Fx" #: lib/logitech_receiver/settings_templates.py:141 -msgid "Scroll with one finger" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:141 -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scrolls." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scroll with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scroll horizontally with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scrolls horizontally." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scroll vertically with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scrolls vertically." -msgstr "" +msgid "" +"When set, the F1..F12 keys will activate their special function,\n" +"and you must hold the FN key to activate their standard function." +msgstr "" +"Asetettu: F1..F12 näppäimet aktivoivat erityistoimintonsa ja \n" +"FN-näppäintä on painettava, jotta ne aktivoisivat normaalin\n" +"toimintonsa." #: lib/logitech_receiver/settings_templates.py:146 -msgid "Inverts the scrolling direction." -msgstr "" +msgid "" +"When unset, the F1..F12 keys will activate their standard function,\n" +"and you must hold the FN key to activate their special function." +msgstr "" +"Ei asetettu: F1..F12 näppäimet aktivoivat tavallisen toimintonsa\n" +"ja FN-näppäintä on painettava, jotta ne aktivoisivat\n" +"erityistoimintonsa." -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Natural scrolling" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:154 +msgid "Hand Detection" +msgstr "Käsien tunnistus" -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Enables the thumbwheel." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:155 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Käynnistä valaistus, kun kädet ovat näppämistön yläpuolella." -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Thumbwheel" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:158 #: lib/logitech_receiver/settings_templates.py:162 -msgid "Swipe from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:159 -msgid "Swipe from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:160 -msgid "Swipe from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:161 -msgid "Swipe from the bottom edge" -msgstr "" +msgid "Scroll Wheel Smooth Scrolling" +msgstr "Vierityspyörän sulava vieritys" #: lib/logitech_receiver/settings_templates.py:163 -msgid "Swipe two fingers from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:164 -msgid "Swipe two fingers from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:165 -msgid "Swipe two fingers from the bottom edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:166 -msgid "Swipe two fingers from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:167 -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:167 -msgid "Zoom with two fingers." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:168 -msgid "Pinch to zoom out." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:169 -msgid "Spread to zoom in." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:410 +#: lib/logitech_receiver/settings_templates.py:439 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Korkean herkkyyden tila pystyvieritykseen hiiren rullalla." #: lib/logitech_receiver/settings_templates.py:170 -msgid "Zoom with three fingers." -msgstr "" +msgid "Side Scrolling" +msgstr "Sivuttaisvieritys" -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Zoom with two fingers" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:172 +msgid "" +"When disabled, pushing the wheel sideways sends custom button events\n" +"instead of the standard side-scrolling events." +msgstr "" +"Pois päältä ollessaan, rullan painaminen sivuttain lähettää muokattuja\n" +"toimintoja normaalien sivuttaisvieritystoimintojen sijaan." -#: lib/logitech_receiver/settings_templates.py:189 -msgid "Pixel zone" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:182 +msgid "Sensitivity (DPI - older mice)" +msgstr "Herkkyys (DPI - vanhemmat hiiret)" -#: lib/logitech_receiver/settings_templates.py:190 -msgid "Ratio zone" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:183 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +msgid "Mouse movement sensitivity" +msgstr "Hiiren liikkeen herkkyys" -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Scale factor" -msgstr "Skaalauskerroin" +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Backlight Timed" +msgstr "Ajastettu taustavalo" -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Sets the cursor speed." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:257 +#: lib/logitech_receiver/settings_templates.py:397 +msgid "Set illumination time for keyboard." +msgstr "Aseta näppäimistön valaistusaika." -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Backlight" +msgstr "Taustavalo" -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left-most coordinate." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:269 +msgid "" +"Illumination level on keyboard. Changes made are only applied in Manual " +"mode." +msgstr "" +"Näppäimistön valaistustaso. Tehdyt muutokset otetaan käyttöön vain " +"manuaalisessa tilassa." -#: lib/logitech_receiver/settings_templates.py:196 -msgid "Top-most coordinate." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Automatic" +msgstr "Automaattinen" -#: lib/logitech_receiver/settings_templates.py:196 -msgid "top" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:303 +msgid "Manual" +msgstr "Manuaalinen" -#: lib/logitech_receiver/settings_templates.py:197 -msgid "Width." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:305 +msgid "Enabled" +msgstr "Käytössä" -#: lib/logitech_receiver/settings_templates.py:197 -msgid "width" -msgstr "leveys" +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Backlight Level" +msgstr "Taustavalon taso" -#: lib/logitech_receiver/settings_templates.py:198 -msgid "Height." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:312 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "Näppäimistön valaistustaso manuaalisessa tilassa." -#: lib/logitech_receiver/settings_templates.py:198 -msgid "height" -msgstr "korkeus" +#: lib/logitech_receiver/settings_templates.py:369 +msgid "Backlight Delay Hands Out" +msgstr "Taustavalon viive, kädet poissa" -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Cursor speed." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:370 +msgid "" +"Delay in seconds until backlight fades out with hands away from keyboard." +msgstr "Taustavalon viive sekunneissa, kun kädet ovat poissa näppäimistöltä." -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Scale" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:378 +msgid "Backlight Delay Hands In" +msgstr "Taustavalon viive, kädet lähellä" -#: lib/logitech_receiver/settings_templates.py:202 +#: lib/logitech_receiver/settings_templates.py:379 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "Taustavalon viive sekunneissa, kun kädet ovat näppäimistön lähellä." + +#: lib/logitech_receiver/settings_templates.py:387 +msgid "Backlight Delay Powered" +msgstr "Taustavalon viive kytkettynä" + +#: lib/logitech_receiver/settings_templates.py:388 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "Taustavalon viive sekunneissa ulkoiseen virtaan kytkettynä." + +#: lib/logitech_receiver/settings_templates.py:396 +msgid "Backlight (Seconds)" +msgstr "Taustavalo (sekuntia)" + +#: lib/logitech_receiver/settings_templates.py:408 +msgid "Scroll Wheel High Resolution" +msgstr "Vierityspyörän korkea resoluutio" + +#: lib/logitech_receiver/settings_templates.py:412 +#: lib/logitech_receiver/settings_templates.py:441 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "Aseta sivuuttamaan, jos vieritys on epänormaalin nopeaa tai hidasta" + +#: lib/logitech_receiver/settings_templates.py:419 +#: lib/logitech_receiver/settings_templates.py:450 +msgid "Scroll Wheel Diversion" +msgstr "Vierityspyörän uudelleenohjaus" + +#: lib/logitech_receiver/settings_templates.py:421 +msgid "" +"Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " +"Solaar rules but are otherwise ignored)." +msgstr "" +"Aseta vierityspyörä lähettämään LOWRES_WHEEL HID++ -ilmoituksia (jotka " +"laukaisevat Solaar-sääntöjä mutta muuten ohitetaan)." + +#: lib/logitech_receiver/settings_templates.py:428 +msgid "Scroll Wheel Direction" +msgstr "Vierityspyörän suunta" + +#: lib/logitech_receiver/settings_templates.py:429 +msgid "Invert direction for vertical scroll with wheel." +msgstr "Käännä vierityspyörän pystysuuntainen vierityssuunta." + +#: lib/logitech_receiver/settings_templates.py:437 +msgid "Scroll Wheel Resolution" +msgstr "Vierityspyörän resoluutio" + +#: lib/logitech_receiver/settings_templates.py:452 +msgid "" +"Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar" +" rules but are otherwise ignored)." +msgstr "" +"Aseta vierityspyörä lähettämään HIRES_WHEEL HID++ -ilmoituksia (jotka " +"laukaisevat Solaar-sääntöjä mutta muuten ohitetaan)." + +#: lib/logitech_receiver/settings_templates.py:461 +msgid "Sensitivity (Pointer Speed)" +msgstr "Herkkyys (osoittimen nopeus)" + +#: lib/logitech_receiver/settings_templates.py:462 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Hiiren nopeuskerroin (256 on tavallinen kerroin)." + +#: lib/logitech_receiver/settings_templates.py:472 +msgid "Thumb Wheel Diversion" +msgstr "Peukalopyörän uudelleenohjaus" + +#: lib/logitech_receiver/settings_templates.py:474 +msgid "" +"Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"Aseta peukalopyörä lähettämään THUMB_WHEEL HID++ -ilmoituksia (jotka " +"laukaisevat Solaar-sääntöjä mutta muuten ohitetaan)." + +#: lib/logitech_receiver/settings_templates.py:483 +msgid "Thumb Wheel Direction" +msgstr "Peukalopyörän suunta" + +#: lib/logitech_receiver/settings_templates.py:484 +msgid "Invert thumb wheel scroll direction." +msgstr "Käännä peukalopyörän vierityssuunta." + +#: lib/logitech_receiver/settings_templates.py:504 +msgid "Onboard Profiles" +msgstr "Laitteen omat profiilit" + +#: lib/logitech_receiver/settings_templates.py:505 +msgid "" +"Enable an onboard profile, which controls report rate, sensitivity, and " +"button actions" +msgstr "" +"Ota käyttöön laitteen oma profiili, joka hallitsee päivitysnopeutta, " +"herkkyyttä ja painiketoimintoja" + +#: lib/logitech_receiver/settings_templates.py:549 +#: lib/logitech_receiver/settings_templates.py:582 +msgid "Report Rate" +msgstr "Päivitysnopeus" + +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +msgid "Frequency of device movement reports" +msgstr "Laitteen liikeraporttien taajuus" + +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1421 +#: lib/logitech_receiver/settings_templates.py:1452 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" +"Voi edellyttää, että Laitteen omat profiilit -asetus on pois käytöstä " +"ollakseen voimassa." + +#: lib/logitech_receiver/settings_templates.py:612 +msgid "Divert crown events" +msgstr "Uudelleenohjaa kruunun tapahtumat" + +#: lib/logitech_receiver/settings_templates.py:613 +msgid "" +"Make crown send CROWN HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"Aseta kruunu lähettämään CROWN HID++ -ilmoituksia (jotka laukaisevat Solaar-" +"sääntöjä mutta muuten ohitetaan)." + +#: lib/logitech_receiver/settings_templates.py:621 +msgid "Crown smooth scroll" +msgstr "Kruunun sulava vieritys" + +#: lib/logitech_receiver/settings_templates.py:622 +msgid "Set crown smooth scroll" +msgstr "Aseta kruunun sulava vieritys" + +#: lib/logitech_receiver/settings_templates.py:630 +msgid "Divert G and M Keys" +msgstr "Uudelleenohjaa G- ja M-näppäimet" + +#: lib/logitech_receiver/settings_templates.py:631 +msgid "" +"Make G and M keys send HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"Aseta G- ja M-näppäimet lähettämään HID++ -ilmoituksia (jotka laukaisevat " +"Solaar-sääntöjä mutta muuten ohitetaan)." + +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Scroll Wheel Ratcheted" +msgstr "Vierityspyörän pykäläinen asetus" + +#: lib/logitech_receiver/settings_templates.py:646 +msgid "" +"Switch the mouse wheel between speed-controlled ratcheting and always " +"freespin." +msgstr "" +"Vaihda hiiren pyörää nopeusohjatun pykäläisen ja aina vapaasti pyörivän " +"välillä." + +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Freespinning" +msgstr "Vapaasti pyörivä" + +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Ratcheted" +msgstr "Pykäläinen" + +#: lib/logitech_receiver/settings_templates.py:655 +msgid "Scroll Wheel Ratchet Speed" +msgstr "Vierityspyörän pykäläisen asetuksen nopeus" + +#: lib/logitech_receiver/settings_templates.py:657 +msgid "" +"Use the mouse wheel speed to switch between ratcheted and freespinning.\n" +"The mouse wheel is always ratcheted at 50." +msgstr "" +"Käytä hiiren pyörän nopeutta vaihtaaksesi pykäläisen ja vapaasti pyörivän välillä.\n" +"Hiiren pyörä on aina pykäläinen nopeudella 50." + +#: lib/logitech_receiver/settings_templates.py:707 +msgid "Scroll Wheel Ratchet Torque" +msgstr "Vierityspyörän pykälän vääntömomentti" + +#: lib/logitech_receiver/settings_templates.py:708 +msgid "Change the torque needed to overcome the ratchet." +msgstr "Muuta pykälän ylittämiseen tarvittavaa vääntömomenttia." + +#: lib/logitech_receiver/settings_templates.py:743 +msgid "Key/Button Actions" +msgstr "Näppäin-/painiketoiminnot" + +#: lib/logitech_receiver/settings_templates.py:745 +msgid "Change the action for the key or button." +msgstr "Muuta näppäimen tai painikkeen toimintoa." + +#: lib/logitech_receiver/settings_templates.py:747 +msgid "Overridden by diversion." +msgstr "Uudelleenohjauksen ohittama." + +#: lib/logitech_receiver/settings_templates.py:749 +msgid "" +"Changing important actions (such as for the left mouse button) can result in" +" an unusable system." +msgstr "" +"Tärkeiden toimintojen (kuten hiiren vasemman painikkeen) muuttaminen voi " +"johtaa käyttökelvottomaan järjestelmään." + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Key/Button Diversion" +msgstr "Näppäimen/painikkeen uudelleenohjaus" + +#: lib/logitech_receiver/settings_templates.py:925 +msgid "" +"Make the key or button send HID++ notifications (Diverted) or initiate Mouse" +" Gestures or Sliding DPI" +msgstr "" +"Aseta näppäin tai painike lähettämään HID++ -ilmoituksia (Uudelleenohjattu) " +"tai aloita hiirieleet tai liukuva DPI" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Diverted" +msgstr "Uudelleenohjattu" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +msgid "Mouse Gestures" +msgstr "Hiirieleet" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Regular" +msgstr "Säännöllinen" + +#: lib/logitech_receiver/settings_templates.py:928 +msgid "Sliding DPI" +msgstr "Liukuva DPI" + +#: lib/logitech_receiver/settings_templates.py:1018 +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Sensitivity (DPI)" +msgstr "Herkkyys (DPI)" + +#: lib/logitech_receiver/settings_templates.py:1123 +msgid "Sensitivity Switching" +msgstr "Herkkyyden vaihtaminen" + +#: lib/logitech_receiver/settings_templates.py:1125 +msgid "" +"Switch the current sensitivity and the remembered sensitivity when the key or button is pressed.\n" +"If there is no remembered sensitivity, just remember the current sensitivity" +msgstr "" +"Vaihda nykyinen herkkyys ja muistettu herkkyys, kun näppäintä tai painiketta painetaan.\n" +"Jos muistettua herkkyyttä ei ole, muista vain nykyinen herkkyys" + +#: lib/logitech_receiver/settings_templates.py:1129 +msgid "Off" +msgstr "Pois" + +#: lib/logitech_receiver/settings_templates.py:1160 +msgid "Disable keys" +msgstr "Poista näppäimiä käytöstä" + +#: lib/logitech_receiver/settings_templates.py:1161 +msgid "Disable specific keyboard keys." +msgstr "Poista tiettyjä näppäimistön näppäimiä käytöstä." + +#: lib/logitech_receiver/settings_templates.py:1164 #, python-format -msgid "Disables the %s key." -msgstr "" +msgid "Disables the %s key." +msgstr "Poistaa %s-näppäimen käytöstä." -#: lib/logitech_receiver/settings_templates.py:521 -#: lib/logitech_receiver/settings_templates.py:573 -msgid "Off" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1177 +#: lib/logitech_receiver/settings_templates.py:1234 +msgid "Set OS" +msgstr "Aseta käyttöjärjestelmä" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Diverted" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1178 +#: lib/logitech_receiver/settings_templates.py:1235 +msgid "Change keys to match OS." +msgstr "Muuta näppäimet vastaamaan käyttöjärjestelmää." -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Regular" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1247 +msgid "Change Host" +msgstr "Vaihda isäntälaitetta" -#: lib/logitech_receiver/status.py:109 -msgid "No paired devices." -msgstr "Ei paritettuja laitteita." +#: lib/logitech_receiver/settings_templates.py:1248 +msgid "Switch connection to a different host" +msgstr "Vaihda yhteys toiseen isäntälaitteeseen" -#: lib/logitech_receiver/status.py:110 lib/solaar/ui/window.py:623 +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Performs a left click." +msgstr "Suorittaa vasemman napsautuksen." + +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Single tap" +msgstr "Yksi napautus" + +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Performs a right click." +msgstr "Suorittaa oikean napsautuksen." + +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Single tap with two fingers" +msgstr "Yksi napautus kahdella sormella" + +#: lib/logitech_receiver/settings_templates.py:1274 +msgid "Single tap with three fingers" +msgstr "Yksi napautus kolmella sormella" + +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Double tap" +msgstr "Kaksoisnapautus" + +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Performs a double click." +msgstr "Suorittaa kaksoisnapsautuksen." + +#: lib/logitech_receiver/settings_templates.py:1279 +msgid "Double tap with two fingers" +msgstr "Kaksoisnapautus kahdella sormella" + +#: lib/logitech_receiver/settings_templates.py:1280 +msgid "Double tap with three fingers" +msgstr "Kaksoisnapautus kolmella sormella" + +#: lib/logitech_receiver/settings_templates.py:1283 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Raahaa kohteita vetämällä sormea kaksoisnapautuksen jälkeen." + +#: lib/logitech_receiver/settings_templates.py:1283 +msgid "Tap and drag" +msgstr "Napauta ja raahaa" + +#: lib/logitech_receiver/settings_templates.py:1285 +msgid "Tap and drag with two fingers" +msgstr "Napauta ja raahaa kahdella sormella" + +#: lib/logitech_receiver/settings_templates.py:1286 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Raahaa kohteita vetämällä sormia kaksoisnapautuksen jälkeen." + +#: lib/logitech_receiver/settings_templates.py:1288 +msgid "Tap and drag with three fingers" +msgstr "Napauta ja raahaa kolmella sormella" + +#: lib/logitech_receiver/settings_templates.py:1291 +msgid "Suppress tap and edge gestures" +msgstr "Estä napautus- ja reunaeleet" + +#: lib/logitech_receiver/settings_templates.py:1292 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "Poistaa napautus- ja reunaeleet käytöstä (vastaa Fn+Vasen napsautus)." + +#: lib/logitech_receiver/settings_templates.py:1294 +msgid "Scroll with one finger" +msgstr "Vieritä yhdellä sormella" + +#: lib/logitech_receiver/settings_templates.py:1294 +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 +msgid "Scrolls." +msgstr "Vierittää." + +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 +msgid "Scroll with two fingers" +msgstr "Vieritä kahdella sormella" + +#: lib/logitech_receiver/settings_templates.py:1296 +msgid "Scroll horizontally with two fingers" +msgstr "Vieritä vaakasuunnassa kahdella sormella" + +#: lib/logitech_receiver/settings_templates.py:1296 +msgid "Scrolls horizontally." +msgstr "Vierittää vaakasuunnassa." + +#: lib/logitech_receiver/settings_templates.py:1297 +msgid "Scroll vertically with two fingers" +msgstr "Vieritä pystysuunnassa kahdella sormella" + +#: lib/logitech_receiver/settings_templates.py:1297 +msgid "Scrolls vertically." +msgstr "Vierittää pystysuunnassa." + +#: lib/logitech_receiver/settings_templates.py:1299 +msgid "Inverts the scrolling direction." +msgstr "Kääntää vierityssuunnan." + +#: lib/logitech_receiver/settings_templates.py:1299 +msgid "Natural scrolling" +msgstr "Luonnollinen vieritys" + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Enables the thumbwheel." +msgstr "Ottaa peukalopyörän käyttöön." + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Thumbwheel" +msgstr "Peukalopyörä" + +#: lib/logitech_receiver/settings_templates.py:1311 +#: lib/logitech_receiver/settings_templates.py:1315 +msgid "Swipe from the top edge" +msgstr "Pyyhkäise yläreunasta" + +#: lib/logitech_receiver/settings_templates.py:1312 +msgid "Swipe from the left edge" +msgstr "Pyyhkäise vasemmasta reunasta" + +#: lib/logitech_receiver/settings_templates.py:1313 +msgid "Swipe from the right edge" +msgstr "Pyyhkäise oikeasta reunasta" + +#: lib/logitech_receiver/settings_templates.py:1314 +msgid "Swipe from the bottom edge" +msgstr "Pyyhkäise alareunasta" + +#: lib/logitech_receiver/settings_templates.py:1316 +msgid "Swipe two fingers from the left edge" +msgstr "Pyyhkäise kahdella sormella vasemmasta reunasta" + +#: lib/logitech_receiver/settings_templates.py:1317 +msgid "Swipe two fingers from the right edge" +msgstr "Pyyhkäise kahdella sormella oikeasta reunasta" + +#: lib/logitech_receiver/settings_templates.py:1318 +msgid "Swipe two fingers from the bottom edge" +msgstr "Pyyhkäise kahdella sormella alareunasta" + +#: lib/logitech_receiver/settings_templates.py:1319 +msgid "Swipe two fingers from the top edge" +msgstr "Pyyhkäise kahdella sormella yläreunasta" + +#: lib/logitech_receiver/settings_templates.py:1320 +#: lib/logitech_receiver/settings_templates.py:1324 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "Nipistä loitontaaksesi; levitä lähentääksesi." + +#: lib/logitech_receiver/settings_templates.py:1320 +msgid "Zoom with two fingers." +msgstr "Zoomaa kahdella sormella." + +#: lib/logitech_receiver/settings_templates.py:1321 +msgid "Pinch to zoom out." +msgstr "Nipistä loitontaaksesi." + +#: lib/logitech_receiver/settings_templates.py:1322 +msgid "Spread to zoom in." +msgstr "Levitä lähentääksesi." + +#: lib/logitech_receiver/settings_templates.py:1323 +msgid "Zoom with three fingers." +msgstr "Zoomaa kolmella sormella." + +#: lib/logitech_receiver/settings_templates.py:1324 +msgid "Zoom with two fingers" +msgstr "Zoomaa kahdella sormella" + +#: lib/logitech_receiver/settings_templates.py:1342 +msgid "Pixel zone" +msgstr "Pikselialue" + +#: lib/logitech_receiver/settings_templates.py:1343 +msgid "Ratio zone" +msgstr "Suhdealue" + +#: lib/logitech_receiver/settings_templates.py:1344 +msgid "Scale factor" +msgstr "Skaalauskerroin" + +#: lib/logitech_receiver/settings_templates.py:1344 +msgid "Sets the cursor speed." +msgstr "Asettaa osoittimen nopeuden." + +#: lib/logitech_receiver/settings_templates.py:1348 +msgid "Left" +msgstr "Vasen" + +#: lib/logitech_receiver/settings_templates.py:1348 +msgid "Left-most coordinate." +msgstr "Vasemmanpuoleisin koordinaatti." + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom" +msgstr "Alareuna" + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom coordinate." +msgstr "Alakoordinaatti." + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width" +msgstr "Leveys" + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width." +msgstr "Leveys." + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height" +msgstr "Korkeus" + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height." +msgstr "Korkeus." + +#: lib/logitech_receiver/settings_templates.py:1352 +msgid "Cursor speed." +msgstr "Osoittimen nopeus." + +#: lib/logitech_receiver/settings_templates.py:1352 +msgid "Scale" +msgstr "Skaala" + +#: lib/logitech_receiver/settings_templates.py:1358 +msgid "Gestures" +msgstr "Eleet" + +#: lib/logitech_receiver/settings_templates.py:1359 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Säädä hiiren/kosketuslevyn toimintaa." + +#: lib/logitech_receiver/settings_templates.py:1375 +msgid "Gestures Diversion" +msgstr "Eleiden uudelleenohjaus" + +#: lib/logitech_receiver/settings_templates.py:1376 +msgid "Divert mouse/touchpad gestures." +msgstr "Uudelleenohjaa hiiren/kosketuslevyn eleet." + +#: lib/logitech_receiver/settings_templates.py:1392 +msgid "Gesture params" +msgstr "Eleiden parametrit" + +#: lib/logitech_receiver/settings_templates.py:1393 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Muuta hiiren/kosketuslevyn numeerisia parametreja." + +#: lib/logitech_receiver/settings_templates.py:1417 +msgid "M-Key LEDs" +msgstr "M-näppäinten LEDit" + +#: lib/logitech_receiver/settings_templates.py:1419 +msgid "Control the M-Key LEDs." +msgstr "Hallitse M-näppäinten LEDejä." + +#: lib/logitech_receiver/settings_templates.py:1423 +#: lib/logitech_receiver/settings_templates.py:1454 +msgid "May need G Keys diverted to be effective." +msgstr "Saattaa edellyttää G-näppäinten uudelleenohjausta toimiakseen." + +#: lib/logitech_receiver/settings_templates.py:1429 #, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "" -msgstr[1] "" +msgid "Lights up the %s key." +msgstr "Sytyttää %s-näppäimen." -#: lib/logitech_receiver/status.py:165 -#, python-format -msgid "Battery: %(level)s" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1448 +msgid "MR-Key LED" +msgstr "MR-näppäimen LED" -#: lib/logitech_receiver/status.py:167 -#, python-format -msgid "Battery: %(percent)d%%" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1450 +msgid "Control the MR-Key LED." +msgstr "Hallitse MR-näppäimen LEDiä." -#: lib/logitech_receiver/status.py:179 -#, python-format -msgid "Lighting: %(level)s lux" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1471 +msgid "Persistent Key/Button Mapping" +msgstr "Pysyvä näppäin-/painikemääritys" -#: lib/logitech_receiver/status.py:234 -#, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1473 +msgid "Permanently change the mapping for the key or button." +msgstr "Muuta näppäimen tai painikkeen määritystä pysyvästi." -#: lib/logitech_receiver/status.py:236 -#, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1475 +msgid "" +"Changing important keys or buttons (such as for the left mouse button) can " +"result in an unusable system." +msgstr "" +"Tärkeiden näppäinten tai painikkeiden (kuten hiiren vasemman painikkeen) " +"muuttaminen voi johtaa käyttökelvottomaan järjestelmään." -#: lib/solaar/ui/__init__.py:52 -msgid "Permissions error" -msgstr "Virheelliset käyttöoikeudet" +#: lib/logitech_receiver/settings_templates.py:1532 +msgid "Sidetone" +msgstr "Sivuääni" -#: lib/solaar/ui/__init__.py:54 -#, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open " - "it." -msgstr "Logitechin vastaanotin (%s) löydettiin, mutta sitä ei ollut oikeutta " - "avata." +#: lib/logitech_receiver/settings_templates.py:1533 +msgid "Set sidetone level." +msgstr "Aseta sivuäänen taso." -#: lib/solaar/ui/__init__.py:55 -msgid "If you've just installed Solaar, try removing the receiver and " - "plugging it back in." -msgstr "Jos olet juuri asentanut Solaarin, yritä vastaanottimen " - "irroittamista ja liittämistä uudelleen." +#: lib/logitech_receiver/settings_templates.py:1542 +msgid "Equalizer" +msgstr "Taajuuskorjain" -#: lib/solaar/ui/__init__.py:58 -msgid "Cannot connect to device error" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1543 +msgid "Set equalizer levels." +msgstr "Aseta taajuuskorjaimen tasot." -#: lib/solaar/ui/__init__.py:60 -#, python-format -msgid "Found a Logitech receiver or device at %s, but encountered an error " - "connecting to it." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1565 +msgid "Hz" +msgstr "Hz" -#: lib/solaar/ui/__init__.py:61 -msgid "Try removing the device and plugging it back in or turning it off " - "and then on." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1571 +msgid "Power Management" +msgstr "Virranhallinta" -#: lib/solaar/ui/__init__.py:64 -msgid "Unpairing failed" -msgstr "Parituksen poisto epäonnistui" +#: lib/logitech_receiver/settings_templates.py:1572 +msgid "Power off in minutes (0 for never)." +msgstr "Virrankatkaisu minuuteissa (0 = ei koskaan)." -#: lib/solaar/ui/__init__.py:66 -#, python-brace-format -msgid "Failed to unpair %{device} from %{receiver}." -msgstr "%{device}n parituksen poisto epäonnistui %{receiver}sta." +#: lib/logitech_receiver/settings_templates.py:1584 +msgid "Brightness Control" +msgstr "Kirkkauden säätö" -#: lib/solaar/ui/__init__.py:67 -msgid "The receiver returned an error, with no further details." -msgstr "Vastaanotin palautti virheen ilman lisätietoja." +#: lib/logitech_receiver/settings_templates.py:1585 +msgid "Control overall brightness" +msgstr "Hallitse yleistä kirkkautta" -#: lib/solaar/ui/about.py:39 -msgid "Manages Logitech receivers,\n" - "keyboards, mice, and tablets." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1628 +#: lib/logitech_receiver/settings_templates.py:1681 +msgid "LED Control" +msgstr "LED-hallinta" -#: lib/solaar/ui/about.py:47 -msgid "Additional Programming" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1629 +#: lib/logitech_receiver/settings_templates.py:1682 +msgid "Switch control of LED zones between device and Solaar" +msgstr "Vaihda LED-alueiden hallintaa laitteen ja Solaarin välillä" -#: lib/solaar/ui/about.py:48 -msgid "GUI design" -msgstr "Käyttöliittymäsuunnittelu" +#: lib/logitech_receiver/settings_templates.py:1644 +#: lib/logitech_receiver/settings_templates.py:1692 +msgid "LED Zone Effects" +msgstr "LED-alueiden efektit" -#: lib/solaar/ui/about.py:50 -msgid "Testing" -msgstr "Testaus" +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "LED Control needs to be set to Solaar to be effective." +msgstr "LED-hallinta on asetettava Solaariin ollakseen voimassa." -#: lib/solaar/ui/about.py:57 +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "Set effect for LED Zone" +msgstr "Aseta efekti LED-alueelle" + +#: lib/logitech_receiver/settings_templates.py:1647 +msgid "Color" +msgstr "Väri" + +#: lib/logitech_receiver/settings_templates.py:1648 +msgid "Speed" +msgstr "Nopeus" + +#: lib/logitech_receiver/settings_templates.py:1649 +msgid "Period" +msgstr "Jakso" + +#: lib/logitech_receiver/settings_templates.py:1650 +msgid "Intensity" +msgstr "Voimakkuus" + +#: lib/logitech_receiver/settings_templates.py:1651 +msgid "Ramp" +msgstr "Nousu" + +#: lib/logitech_receiver/settings_templates.py:1666 +msgid "LEDs" +msgstr "LEDit" + +#: lib/logitech_receiver/settings_templates.py:1703 +msgid "Per-key Lighting" +msgstr "Näppäinkohtainen valaistus" + +#: lib/logitech_receiver/settings_templates.py:1704 +msgid "Control per-key lighting." +msgstr "Hallitse näppäinkohtaista valaistusta." + +#: lib/logitech_receiver/settings_templates.py:1786 +msgid "Force Sensing Buttons" +msgstr "Voimaa tunnistavat painikkeet" + +#: lib/logitech_receiver/settings_templates.py:1787 +msgid "Change the force required to activate button." +msgstr "Muuta painikkeen aktivointiin tarvittavaa voimaa." + +#: lib/logitech_receiver/settings_templates.py:1804 +msgid "Force Sensing Button" +msgstr "Voimaa tunnistava painike" + +#: lib/logitech_receiver/settings_templates.py:1814 +msgid "Haptic Feedback Level" +msgstr "Haptisen palautteen taso" + +#: lib/logitech_receiver/settings_templates.py:1815 +msgid "Change power of haptic feedback. (Zero to turn off.)" +msgstr "Muuta haptisen palautteen voimakkuutta. (Nolla poistaa käytöstä.)" + +#: lib/logitech_receiver/settings_templates.py:1857 +msgid "Play Haptic Waveform" +msgstr "Toista haptinen aaltomuoto" + +#: lib/logitech_receiver/settings_templates.py:1858 +msgid "Tell device to play a haptic waveform." +msgstr "Käske laitetta toistamaan haptinen aaltomuoto." + +#: lib/solaar/ui/__init__.py:120 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" +"Toinen Solaar-prosessi on jo käynnissä, joten sen ikkuna tuodaan näkyviin" + +#: lib/solaar/ui/about/model.py:36 +msgid "" +"Manages Logitech receivers,\n" +"keyboards, mice, and tablets." +msgstr "" +"Hallitsee Logitech-vastaanottimia,\n" +"näppäimistöjä, hiiriä ja piirtopöytiä." + +#: lib/solaar/ui/about/model.py:64 +msgid "Additional Programming" +msgstr "Lisäohjelmointi" + +#: lib/solaar/ui/about/model.py:65 +msgid "GUI design" +msgstr "Käyttöliittymäsuunnittelu" + +#: lib/solaar/ui/about/model.py:67 +msgid "Testing" +msgstr "Testaus" + +#: lib/solaar/ui/about/model.py:75 #, fuzzy -msgid "Logitech documentation" -msgstr "Logitechin dokumentointi" +msgid "Logitech documentation" +msgstr "Logitechin dokumentointi" -#: lib/solaar/ui/action.py:70 lib/solaar/ui/window.py:328 -msgid "About %s" -msgstr "Tietoja %s" +#: lib/solaar/ui/action.py:88 lib/solaar/ui/action.py:92 +#: lib/solaar/ui/window.py:208 +msgid "Unpair" +msgstr "Poista paritus" -#: lib/solaar/ui/action.py:96 lib/solaar/ui/action.py:100 -#: lib/solaar/ui/window.py:205 -msgid "Unpair" -msgstr "Poista paritus" +#: lib/solaar/ui/action.py:91 lib/solaar/ui/diversion_rules.py:141 +msgid "Cancel" +msgstr "Peruuta" -#: lib/solaar/ui/action.py:99 lib/solaar/ui/diversion_rules.py:144 -msgid "Cancel" -msgstr "" +#: lib/solaar/ui/common.py:42 +msgid "Permissions error" +msgstr "Virheelliset käyttöoikeudet" -#: lib/solaar/ui/config_panel.py:358 -msgid "Changes allowed" -msgstr "" - -#: lib/solaar/ui/config_panel.py:359 -msgid "No changes allowed" -msgstr "" - -#: lib/solaar/ui/config_panel.py:360 -msgid "Ignore this setting" -msgstr "" - -#: lib/solaar/ui/config_panel.py:401 -msgid "Working" -msgstr "Työskentelee" - -#: lib/solaar/ui/config_panel.py:404 -msgid "Read/write operation failed." -msgstr "Luku/kirjoitus operaatio epäonnistui." - -#: lib/solaar/ui/config_panel.py:522 +#: lib/solaar/ui/common.py:44 #, python-format -msgid "%d value" -msgid_plural "%d values" -msgstr[0] "" -msgstr[1] "" +msgid "" +"Found a Logitech receiver or device (%s), but did not have permission to " +"open it." +msgstr "" +"Löydettiin Logitech-vastaanotin tai -laite (%s), mutta siihen ei ollut " +"avaamisoikeutta." -#: lib/solaar/ui/diversion_rules.py:57 -msgid "Built-in rules" -msgstr "" +#: lib/solaar/ui/common.py:46 +msgid "" +"If you've just installed Solaar, try disconnecting the receiver or device " +"and then reconnecting it." +msgstr "" +"Jos asensit Solaarin juuri, kokeile irrottaa vastaanotin tai laite ja " +"yhdistää se sitten uudelleen." -#: lib/solaar/ui/diversion_rules.py:57 -msgid "User-defined rules" -msgstr "" +#: lib/solaar/ui/common.py:49 +msgid "Cannot connect to device error" +msgstr "Virhe yhdistettäessä laitteeseen" -#: lib/solaar/ui/diversion_rules.py:59 lib/solaar/ui/diversion_rules.py:792 -msgid "Rule" -msgstr "" +#: lib/solaar/ui/common.py:51 +#, python-format +msgid "" +"Found a Logitech receiver or device at %s, but encountered an error " +"connecting to it." +msgstr "" +"Löydettiin Logitech-vastaanotin tai -laite polusta %s, mutta ilmeni virhe " +"yhdistettäessä siihen." -#: lib/solaar/ui/diversion_rules.py:60 lib/solaar/ui/diversion_rules.py:503 -#: lib/solaar/ui/diversion_rules.py:622 -msgid "Sub-rule" -msgstr "" +#: lib/solaar/ui/common.py:53 +msgid "" +"Try disconnecting the device and then reconnecting it or turning it off and " +"then on." +msgstr "" +"Kokeile laitteen irrottamista ja yhdistämistä uudelleen tai sen " +"sammuttamista ja käynnistämistä uudelleen." -#: lib/solaar/ui/diversion_rules.py:62 -msgid "[empty]" -msgstr "" +#: lib/solaar/ui/common.py:56 +msgid "Unpairing failed" +msgstr "Parituksen poisto epäonnistui" -#: lib/solaar/ui/diversion_rules.py:85 -msgid "Solaar Rule Editor" -msgstr "" +#: lib/solaar/ui/common.py:58 +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "%{device}n parituksen poisto epäonnistui %{receiver}sta." -#: lib/solaar/ui/diversion_rules.py:135 -msgid "Make changes permanent?" -msgstr "" +#: lib/solaar/ui/common.py:63 +msgid "The receiver returned an error, with no further details." +msgstr "Vastaanotin palautti virheen ilman lisätietoja." -#: lib/solaar/ui/diversion_rules.py:140 -msgid "Yes" -msgstr "Kyllä" +#: lib/solaar/ui/config_panel.py:245 +msgid "Complete - ENTER to change" +msgstr "Valmis - paina ENTER muuttaaksesi" -#: lib/solaar/ui/diversion_rules.py:142 -msgid "No" -msgstr "" +#: lib/solaar/ui/config_panel.py:245 +msgid "Incomplete" +msgstr "Keskeneräinen" -#: lib/solaar/ui/diversion_rules.py:147 -msgid "If you choose No, changes will be lost when Solaar is closed." -msgstr "" +#: lib/solaar/ui/config_panel.py:491 lib/solaar/ui/config_panel.py:543 +#, python-format +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d arvo" +msgstr[1] "%d arvoa" -#: lib/solaar/ui/diversion_rules.py:195 -msgid "Save changes" -msgstr "" +#: lib/solaar/ui/config_panel.py:642 +msgid "Changes allowed" +msgstr "Muutokset sallittu" -#: lib/solaar/ui/diversion_rules.py:200 -msgid "Discard changes" -msgstr "" +#: lib/solaar/ui/config_panel.py:643 +msgid "No changes allowed" +msgstr "Ei muutoksia sallittu" -#: lib/solaar/ui/diversion_rules.py:366 -msgid "Insert here" -msgstr "" +#: lib/solaar/ui/config_panel.py:644 +msgid "Ignore this setting" +msgstr "Ohita tämä asetus" -#: lib/solaar/ui/diversion_rules.py:368 -msgid "Insert above" -msgstr "" +#: lib/solaar/ui/config_panel.py:690 +msgid "Working" +msgstr "Työskentelee" -#: lib/solaar/ui/diversion_rules.py:370 -msgid "Insert below" -msgstr "" +#: lib/solaar/ui/config_panel.py:693 +msgid "Read/write operation failed." +msgstr "Luku/kirjoitus operaatio epäonnistui." -#: lib/solaar/ui/diversion_rules.py:376 -msgid "Insert new rule here" -msgstr "" +#: lib/solaar/ui/desktop_notifications.py:112 +msgid "unspecified reason" +msgstr "määrittämätön syy" -#: lib/solaar/ui/diversion_rules.py:378 -msgid "Insert new rule above" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:103 +msgid "Built-in rules" +msgstr "Sisäänrakennetut säännöt" + +#: lib/solaar/ui/diversion_rules.py:103 +msgid "User-defined rules" +msgstr "Käyttäjän määrittämät säännöt" + +#: lib/solaar/ui/diversion_rules.py:105 lib/solaar/ui/diversion_rules.py:1126 +msgid "Rule" +msgstr "Sääntö" + +#: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:382 +#: lib/solaar/ui/diversion_rules.py:509 +msgid "Sub-rule" +msgstr "Alisääntö" + +#: lib/solaar/ui/diversion_rules.py:108 +msgid "[empty]" +msgstr "[tyhjä]" + +#: lib/solaar/ui/diversion_rules.py:132 +msgid "Make changes permanent?" +msgstr "Tehdäänkö muutoksista pysyviä?" + +#: lib/solaar/ui/diversion_rules.py:137 +msgid "Yes" +msgstr "Kyllä" + +#: lib/solaar/ui/diversion_rules.py:139 +msgid "No" +msgstr "Ei" + +#: lib/solaar/ui/diversion_rules.py:144 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "Jos valitset Ei, muutokset menetetään, kun Solaar suljetaan." + +#: lib/solaar/ui/diversion_rules.py:273 +msgid "Paste here" +msgstr "Liitä tähän" + +#: lib/solaar/ui/diversion_rules.py:275 +msgid "Paste above" +msgstr "Liitä yläpuolelle" + +#: lib/solaar/ui/diversion_rules.py:277 +msgid "Paste below" +msgstr "Liitä alapuolelle" + +#: lib/solaar/ui/diversion_rules.py:283 +msgid "Paste rule here" +msgstr "Liitä sääntö tähän" + +#: lib/solaar/ui/diversion_rules.py:285 +msgid "Paste rule above" +msgstr "Liitä sääntö yläpuolelle" + +#: lib/solaar/ui/diversion_rules.py:287 +msgid "Paste rule below" +msgstr "Liitä sääntö alapuolelle" + +#: lib/solaar/ui/diversion_rules.py:291 +msgid "Paste rule" +msgstr "Liitä sääntö" + +#: lib/solaar/ui/diversion_rules.py:306 +msgid "Insert here" +msgstr "Lisää tähän" + +#: lib/solaar/ui/diversion_rules.py:308 +msgid "Insert above" +msgstr "Lisää yläpuolelle" + +#: lib/solaar/ui/diversion_rules.py:310 +msgid "Insert below" +msgstr "Lisää alapuolelle" + +#: lib/solaar/ui/diversion_rules.py:316 +msgid "Insert new rule here" +msgstr "Lisää uusi sääntö tähän" + +#: lib/solaar/ui/diversion_rules.py:318 +msgid "Insert new rule above" +msgstr "Lisää uusi sääntö yläpuolelle" + +#: lib/solaar/ui/diversion_rules.py:320 +msgid "Insert new rule below" +msgstr "Lisää uusi sääntö alapuolelle" + +#: lib/solaar/ui/diversion_rules.py:347 +msgid "Flatten" +msgstr "Litistä" #: lib/solaar/ui/diversion_rules.py:380 -msgid "Insert new rule below" -msgstr "" +msgid "Insert" +msgstr "Lisää" -#: lib/solaar/ui/diversion_rules.py:421 -msgid "Paste here" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:383 lib/solaar/ui/diversion_rules.py:511 +#: lib/solaar/ui/diversion_rules.py:1158 +msgid "Or" +msgstr "Tai" -#: lib/solaar/ui/diversion_rules.py:423 -msgid "Paste above" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:510 +#: lib/solaar/ui/diversion_rules.py:1144 +msgid "And" +msgstr "Ja" -#: lib/solaar/ui/diversion_rules.py:425 -msgid "Paste below" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:386 +msgid "Condition" +msgstr "Ehto" -#: lib/solaar/ui/diversion_rules.py:431 -msgid "Paste rule here" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:388 lib/solaar/ui/rule_conditions.py:154 +msgid "Feature" +msgstr "Ominaisuus" -#: lib/solaar/ui/diversion_rules.py:433 -msgid "Paste rule above" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:188 +msgid "Report" +msgstr "Raportti" -#: lib/solaar/ui/diversion_rules.py:435 -msgid "Paste rule below" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:69 +msgid "Process" +msgstr "Prosessi" -#: lib/solaar/ui/diversion_rules.py:439 -msgid "Paste rule" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:391 +msgid "Mouse process" +msgstr "Hiiriprosessi" -#: lib/solaar/ui/diversion_rules.py:468 -msgid "Flatten" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:392 lib/solaar/ui/rule_conditions.py:225 +msgid "Modifiers" +msgstr "Muuntajat" -#: lib/solaar/ui/diversion_rules.py:501 -msgid "Insert" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:277 +msgid "Key" +msgstr "Näppäin" -#: lib/solaar/ui/diversion_rules.py:504 lib/solaar/ui/diversion_rules.py:624 -#: lib/solaar/ui/diversion_rules.py:835 -msgid "Or" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:318 +msgid "KeyIsDown" +msgstr "Näppäin on painettu" -#: lib/solaar/ui/diversion_rules.py:505 lib/solaar/ui/diversion_rules.py:623 -#: lib/solaar/ui/diversion_rules.py:820 -msgid "And" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/diversion_rules.py:1432 +msgid "Active" +msgstr "Aktiivinen" + +#: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1390 +#: lib/solaar/ui/diversion_rules.py:1441 lib/solaar/ui/diversion_rules.py:1487 +msgid "Device" +msgstr "Laite" + +#: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1467 +msgid "Host" +msgstr "Isäntä" + +#: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1506 +msgid "Setting" +msgstr "Asetus" + +#: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/rule_conditions.py:333 +#: lib/solaar/ui/rule_conditions.py:382 +msgid "Test" +msgstr "Testi" + +#: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:507 +msgid "Test bytes" +msgstr "Testaa tavut" + +#: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:608 +msgid "Mouse Gesture" +msgstr "Hiiriele" + +#: lib/solaar/ui/diversion_rules.py:405 +msgid "Action" +msgstr "Toiminto" + +#: lib/solaar/ui/diversion_rules.py:407 lib/solaar/ui/rule_actions.py:138 +msgid "Key press" +msgstr "Näppäimen painallus" + +#: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:191 +msgid "Mouse scroll" +msgstr "Hiiren vieritys" + +#: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:256 +msgid "Mouse click" +msgstr "Hiiren napsautus" + +#: lib/solaar/ui/diversion_rules.py:410 +msgid "Set" +msgstr "Aseta" + +#: lib/solaar/ui/diversion_rules.py:411 lib/solaar/ui/rule_actions.py:328 +msgid "Execute" +msgstr "Suorita" + +#: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/diversion_rules.py:1189 +msgid "Later" +msgstr "Myöhemmin" + +#: lib/solaar/ui/diversion_rules.py:441 +msgid "Insert new rule" +msgstr "Lisää uusi sääntö" + +#: lib/solaar/ui/diversion_rules.py:461 lib/solaar/ui/rule_actions.py:82 +#: lib/solaar/ui/rule_actions.py:287 lib/solaar/ui/rule_conditions.py:556 +msgid "Delete" +msgstr "Poista" + +#: lib/solaar/ui/diversion_rules.py:483 +msgid "Negate" +msgstr "Käännä" #: lib/solaar/ui/diversion_rules.py:507 -msgid "Condition" -msgstr "" +msgid "Wrap with" +msgstr "Kääri" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:951 -msgid "Feature" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Cut" +msgstr "Leikkaa" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:874 -msgid "Process" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:553 +msgid "Paste" +msgstr "Liitä" -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:902 -msgid "MouseProcess" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:559 +msgid "Copy" +msgstr "Kopioi" -#: lib/solaar/ui/diversion_rules.py:512 lib/solaar/ui/diversion_rules.py:984 -msgid "Report" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Solaar Rule Editor" +msgstr "Solaarin sääntöeditori" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1019 -msgid "Modifiers" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:668 +msgid "Save changes" +msgstr "Tallenna muutokset" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1066 -msgid "Key" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:673 +msgid "Discard changes" +msgstr "Hylkää muutokset" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1111 -msgid "Test" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1104 +msgid "This editor does not support the selected rule component yet." +msgstr "Tämä editori ei vielä tue valittua sääntökomponenttia." -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1196 -msgid "Mouse Gesture" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1169 +msgid "" +"Number of seconds to delay. Delay between 0 and 1 is done with higher " +"precision." +msgstr "" +"Viive sekunteina. Viive 0:n ja 1:n välillä tehdään suuremmalla " +"tarkkuudella." -#: lib/solaar/ui/diversion_rules.py:520 -msgid "Action" -msgstr "Toiminto" +#: lib/solaar/ui/diversion_rules.py:1207 +msgid "Not" +msgstr "Ei" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1286 -msgid "Key press" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1238 +msgid "Toggle" +msgstr "Vaihda" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1335 -msgid "Mouse scroll" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1239 +msgid "True" +msgstr "Tosi" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:1383 -msgid "Mouse click" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1240 +msgid "False" +msgstr "Epätosi" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1452 -msgid "Execute" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1253 +msgid "Unsupported setting" +msgstr "Ei tuettu asetus" -#: lib/solaar/ui/diversion_rules.py:554 -msgid "Insert new rule" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1396 lib/solaar/ui/diversion_rules.py:1415 +#: lib/solaar/ui/diversion_rules.py:1493 lib/solaar/ui/diversion_rules.py:1748 +#: lib/solaar/ui/diversion_rules.py:1766 +msgid "Originating device" +msgstr "Alkuperäinen laite" -#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/diversion_rules.py:1146 -#: lib/solaar/ui/diversion_rules.py:1236 lib/solaar/ui/diversion_rules.py:1411 -msgid "Delete" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1428 +msgid "Device is active and its settings can be changed." +msgstr "Laite on aktiivinen ja sen asetuksia voidaan muuttaa." -#: lib/solaar/ui/diversion_rules.py:596 -msgid "Negate" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1437 +msgid "Device that originated the current notification." +msgstr "Laite, josta nykyinen ilmoitus on peräisin." -#: lib/solaar/ui/diversion_rules.py:620 -msgid "Wrap with" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1450 +msgid "Name of host computer." +msgstr "Isäntätietokoneen nimi." -#: lib/solaar/ui/diversion_rules.py:642 -msgid "Cut" -msgstr "Leikkaa" +#: lib/solaar/ui/diversion_rules.py:1520 +msgid "Value" +msgstr "Arvo" -#: lib/solaar/ui/diversion_rules.py:657 -msgid "Paste" -msgstr "Liitä" +#: lib/solaar/ui/diversion_rules.py:1529 +msgid "Item" +msgstr "Kohde" -#: lib/solaar/ui/diversion_rules.py:669 -msgid "Copy" -msgstr "Kopioi" +#: lib/solaar/ui/diversion_rules.py:1808 +msgid "Change setting on device" +msgstr "Muuta asetusta laitteessa" -#: lib/solaar/ui/diversion_rules.py:772 -msgid "This editor does not support the selected rule component yet." -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1824 +msgid "Setting on device" +msgstr "Asetus laitteessa" -#: lib/solaar/ui/diversion_rules.py:850 -msgid "Not" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1039 -msgid "Key down" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1042 -msgid "Key up" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1131 -msgid "Add action" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1224 -msgid "Add key" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1354 -msgid "Button" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1355 -msgid "Count" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1397 -msgid "Add argument" -msgstr "" - -#: lib/solaar/ui/notify.py:125 lib/solaar/ui/tray.py:319 -#: lib/solaar/ui/tray.py:324 lib/solaar/ui/window.py:749 -msgid "offline" -msgstr "ei yhdistetty" - -#: lib/solaar/ui/pair_window.py:134 -msgid "Pairing failed" -msgstr "Paritus epäonnistui." - -#: lib/solaar/ui/pair_window.py:136 -msgid "Make sure your device is within range, and has a decent battery " - "charge." -msgstr "Varmista, että laitteesi on kantaman sisällä ja siinä on riittävä " - "lataus." - -#: lib/solaar/ui/pair_window.py:138 -msgid "A new device was detected, but it is not compatible with this " - "receiver." -msgstr "Uusi laite tunnistettiin, mutta se ei ole yhteensopiva käytetyn " - "vastaanottimen kanssa." - -#: lib/solaar/ui/pair_window.py:140 -msgid "More paired devices than receiver can support." -msgstr "" - -#: lib/solaar/ui/pair_window.py:142 -msgid "No further details are available about the error." -msgstr "Virheestä ei ole saatavilla enempää tietoja." - -#: lib/solaar/ui/pair_window.py:156 -msgid "Found a new device:" -msgstr "" - -#: lib/solaar/ui/pair_window.py:181 -msgid "The wireless link is not encrypted" -msgstr "Langaton yhteys ei ole salattu" - -#: lib/solaar/ui/pair_window.py:199 +#: lib/solaar/ui/pair_window.py:44 lib/solaar/ui/pair_window.py:167 #, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "" +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: parita uusi laite" -#: lib/solaar/ui/pair_window.py:206 -msgid "If the device is already turned on, turn it off and on again." -msgstr "" +#: lib/solaar/ui/pair_window.py:46 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "Bolt-vastaanottimet ovat yhteensopivia vain Bolt-laitteiden kanssa." -#: lib/solaar/ui/pair_window.py:209 +#: lib/solaar/ui/pair_window.py:48 +msgid "Press a pairing button or key until the pairing light flashes quickly." +msgstr "" +"Paina parituspainiketta tai -näppäintä, kunnes paritusvalo vilkkuu nopeasti." + +#: lib/solaar/ui/pair_window.py:51 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "" +"Unifying-vastaanottimet ovat yhteensopivia vain Unifying-laitteiden kanssa." + +#: lib/solaar/ui/pair_window.py:53 +msgid "Other receivers are only compatible with a few devices." +msgstr "" +"Muut vastaanottimet ovat yhteensopivia vain joidenkin laitteiden kanssa." + +#: lib/solaar/ui/pair_window.py:55 +msgid "For most devices, turn on the device you want to pair." +msgstr "Useimmissa laitteissa kytke päälle laite, jonka haluat parittaa." + +#: lib/solaar/ui/pair_window.py:56 +msgid "If the device is already turned on, turn it off and on again." +msgstr "Jos laite on jo päällä, sammuta se ja käynnistä uudelleen." + +#: lib/solaar/ui/pair_window.py:58 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" +"Laitetta ei saa olla paritettu lähellä olevaan päällä olevaan " +"vastaanottimeen." + +#: lib/solaar/ui/pair_window.py:61 +msgid "" +"For devices with multiple channels, press, hold, and release the button for the channel you wish to pair\n" +"or use the channel switch button to select a channel and then press, hold, and release the channel switch button." +msgstr "" +"Jos laitteessa on useita kanavia, paina, pidä painettuna ja vapauta sen kanavan painike, jonka haluat parittaa\n" +"tai käytä kanavanvaihtopainiketta valitaksesi kanavan ja paina, pidä painettuna ja vapauta sitten kanavanvaihtopainike." + +#: lib/solaar/ui/pair_window.py:68 +msgid "The channel indicator light should be blinking rapidly." +msgstr "Kanavan merkkivalon pitäisi vilkkua nopeasti." + +#: lib/solaar/ui/pair_window.py:72 #, python-format -msgid "\n" - "\n" - "This receiver has %d pairing remaining." -msgid_plural "\n" - "\n" - "This receiver has %d pairings remaining." -msgstr[0] "" -msgstr[1] "" +msgid "" +"\n" +"\n" +"This receiver has %d pairing remaining." +msgid_plural "" +"\n" +"\n" +"This receiver has %d pairings remaining." +msgstr[0] "" +"\n" +"\n" +"Tässä vastaanottimessa on %d paritus jäljellä." +msgstr[1] "" +"\n" +"\n" +"Tässä vastaanottimessa on %d paritusta jäljellä." -#: lib/solaar/ui/pair_window.py:212 -msgid "\n" - "Cancelling at this point will not use up a pairing." -msgstr "" +#: lib/solaar/ui/pair_window.py:78 +msgid "" +"\n" +"Cancelling at this point will not use up a pairing." +msgstr "" +"\n" +"Peruuttaminen tässä vaiheessa ei kuluta paritusta." -#: lib/solaar/ui/pair_window.py:215 -msgid "Turn on the device you want to pair." -msgstr "Käynnistä laite, jonka haluat parittaa." +#: lib/solaar/ui/pair_window.py:168 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "Anna pääsykoodi laitteella %(name)s." -#: lib/solaar/ui/tray.py:61 -msgid "No Logitech receiver found" -msgstr "Ei löydetty Logitechin vastaanottimia." +#: lib/solaar/ui/pair_window.py:171 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "Kirjoita %(passcode)s ja paina sitten enter-näppäintä." -#: lib/solaar/ui/tray.py:68 lib/solaar/ui/window.py:325 -msgid "Quit %s" -msgstr "Lopeta %s" +#: lib/solaar/ui/pair_window.py:176 +msgid "left" +msgstr "vasen" -#: lib/solaar/ui/tray.py:298 lib/solaar/ui/tray.py:306 -msgid "no receiver" -msgstr "ei vastaanotinta" +#: lib/solaar/ui/pair_window.py:176 +msgid "right" +msgstr "oikea" -#: lib/solaar/ui/tray.py:322 -msgid "no status" -msgstr "ei tietoja" +#: lib/solaar/ui/pair_window.py:178 +#, python-format +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"Paina %(code)s\n" +"ja paina sitten vasenta ja oikeaa painiketta samanaikaisesti." -#: lib/solaar/ui/window.py:104 -msgid "Scanning" -msgstr "Skannataan" +#: lib/solaar/ui/pair_window.py:221 +msgid "The wireless link is not encrypted" +msgstr "Langaton yhteys ei ole salattu" -#: lib/solaar/ui/window.py:137 lib/solaar/ui/window.py:692 -msgid "Battery" -msgstr "Akku" +#: lib/solaar/ui/pair_window.py:226 +msgid "Found a new device:" +msgstr "Löytyi uusi laite:" -#: lib/solaar/ui/window.py:140 -msgid "Wireless Link" -msgstr "Langaton yhteys" +#: lib/solaar/ui/pair_window.py:247 +msgid "Pairing failed" +msgstr "Paritus epäonnistui." + +#: lib/solaar/ui/pair_window.py:249 +msgid "" +"Make sure your device is within range, and has a decent battery charge." +msgstr "" +"Varmista, että laitteesi on kantaman sisällä ja siinä on riittävä lataus." + +#: lib/solaar/ui/pair_window.py:251 +msgid "" +"A new device was detected, but it is not compatible with this receiver." +msgstr "" +"Uusi laite tunnistettiin, mutta se ei ole yhteensopiva käytetyn " +"vastaanottimen kanssa." + +#: lib/solaar/ui/pair_window.py:253 +msgid "More paired devices than receiver can support." +msgstr "Enemmän paritettuja laitteita kuin vastaanotin tukee." + +#: lib/solaar/ui/pair_window.py:255 +msgid "No further details are available about the error." +msgstr "Virheestä ei ole saatavilla enempää tietoja." + +#: lib/solaar/ui/rule_actions.py:54 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simuloi sointunäppäimen napsautusta, painallusta tai vapautusta.\n" +"Waylandissa vaatii kirjoitusoikeuden laitteeseen /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:60 +msgid "Add key" +msgstr "Lisää näppäin" + +#: lib/solaar/ui/rule_actions.py:63 +msgid "Click" +msgstr "Napsautus" + +#: lib/solaar/ui/rule_actions.py:66 +msgid "Depress" +msgstr "Paina" + +#: lib/solaar/ui/rule_actions.py:69 +msgid "Release" +msgstr "Vapauta" + +#: lib/solaar/ui/rule_actions.py:153 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simuloi hiiren vieritystä.\n" +"Waylandissa vaatii kirjoitusoikeuden laitteeseen /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:211 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simuloi hiiren napsautusta.\n" +"Waylandissa vaatii kirjoitusoikeuden laitteeseen /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:216 +msgid "Button" +msgstr "Painike" + +#: lib/solaar/ui/rule_actions.py:218 +msgid "Action (and Count, if click)" +msgstr "Toiminto (ja määrä, jos napsautus)" + +#: lib/solaar/ui/rule_actions.py:269 +msgid "Execute a command with arguments." +msgstr "Suorita komento argumenteilla." + +#: lib/solaar/ui/rule_actions.py:273 +msgid "Add argument" +msgstr "Lisää argumentti" + +#: lib/solaar/ui/rule_conditions.py:52 +msgid "X11 active process. For use in X11 only." +msgstr "X11 aktiivinen prosessi. Vain X11-käyttöön." + +#: lib/solaar/ui/rule_conditions.py:82 +msgid "X11 mouse process. For use in X11 only." +msgstr "X11 hiiriprosessi. Vain X11-käyttöön." + +#: lib/solaar/ui/rule_conditions.py:99 +msgid "MouseProcess" +msgstr "Hiiriprosessi" + +#: lib/solaar/ui/rule_conditions.py:123 +msgid "Feature name of notification triggering rule processing." +msgstr "Säännönkäsittelyn laukaisevan ilmoituksen ominaisuuden nimi." + +#: lib/solaar/ui/rule_conditions.py:169 +msgid "Report number of notification triggering rule processing." +msgstr "Säännönkäsittelyn laukaisevan ilmoituksen raporttinumero." + +#: lib/solaar/ui/rule_conditions.py:201 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "" +"Aktiiviset näppäimistön muuntajat. Eivät ole aina saatavilla Waylandissa." + +#: lib/solaar/ui/rule_conditions.py:241 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and buttons." +msgstr "" +"Uudelleenohjattu näppäin tai painike painettu tai vapautettu.\n" +"Käytä näppäimen/painikkeen uudelleenohjaus- ja uudelleenohjaa G-näppäimet -asetuksia ohjataksesi näppäimiä ja painikkeita." + +#: lib/solaar/ui/rule_conditions.py:250 +msgid "Key down" +msgstr "Näppäin painettuna" + +#: lib/solaar/ui/rule_conditions.py:253 +msgid "Key up" +msgstr "Näppäin vapautettuna" + +#: lib/solaar/ui/rule_conditions.py:293 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and buttons." +msgstr "" +"Uudelleenohjattu näppäin tai painike on parhaillaan painettuna.\n" +"Käytä näppäimen/painikkeen uudelleenohjaus- ja uudelleenohjaa G-näppäimet -asetuksia ohjataksesi näppäimiä ja painikkeita." + +#: lib/solaar/ui/rule_conditions.py:331 +msgid "Test condition on notification triggering rule processing." +msgstr "Testaa ehto säännönkäsittelyn laukaisevassa ilmoituksessa." + +#: lib/solaar/ui/rule_conditions.py:335 +msgid "Parameter" +msgstr "Parametri" + +#: lib/solaar/ui/rule_conditions.py:408 +msgid "begin (inclusive)" +msgstr "alku (sisältyy)" + +#: lib/solaar/ui/rule_conditions.py:409 +msgid "end (exclusive)" +msgstr "loppu (ei sisälly)" + +#: lib/solaar/ui/rule_conditions.py:417 +msgid "range" +msgstr "alue" + +#: lib/solaar/ui/rule_conditions.py:420 +msgid "minimum" +msgstr "minimi" + +#: lib/solaar/ui/rule_conditions.py:421 +msgid "maximum" +msgstr "maksimi" + +#: lib/solaar/ui/rule_conditions.py:423 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "tavut %(0)d - %(1)d, väliltä %(2)d - %(3)d" + +#: lib/solaar/ui/rule_conditions.py:426 lib/solaar/ui/rule_conditions.py:427 +msgid "mask" +msgstr "maski" + +#: lib/solaar/ui/rule_conditions.py:428 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "tavut %(0)d - %(1)d, maski %(2)d" + +#: lib/solaar/ui/rule_conditions.py:437 +msgid "" +"Bit or range test on bytes in notification message triggering rule " +"processing." +msgstr "" +"Bitti- tai aluetesti tavuille säännönkäsittelyn laukaisevassa " +"ilmoitusviestissä." + +#: lib/solaar/ui/rule_conditions.py:447 +msgid "type" +msgstr "tyyppi" + +#: lib/solaar/ui/rule_conditions.py:535 +msgid "" +"Mouse gesture with optional initiating button followed by zero or more mouse" +" movements." +msgstr "" +"Hiiriele valinnaisella aloituspainikkeella, jota seuraa nolla tai useampi " +"hiiriliike." + +#: lib/solaar/ui/rule_conditions.py:541 +msgid "Add movement" +msgstr "Lisää liike" + +#: lib/solaar/ui/tray.py:55 +msgid "No supported device found" +msgstr "Ei löydetty tuettua laitetta" + +#: lib/solaar/ui/tray.py:60 lib/solaar/ui/window.py:328 +#, python-format +msgid "About %s" +msgstr "Tietoja %s" + +#: lib/solaar/ui/tray.py:61 lib/solaar/ui/window.py:326 +#, python-format +msgid "Quit %s" +msgstr "Lopeta %s" + +#: lib/solaar/ui/tray.py:273 lib/solaar/ui/tray.py:281 +msgid "no receiver" +msgstr "ei vastaanotinta" + +#: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:299 +#: lib/solaar/ui/window.py:728 +msgid "offline" +msgstr "ei yhdistetty" + +#: lib/solaar/ui/tray.py:297 +msgid "no status" +msgstr "ei tietoja" + +#: lib/solaar/ui/window.py:110 +msgid "Scanning" +msgstr "Skannataan" + +#: lib/solaar/ui/window.py:141 +msgid "Battery" +msgstr "Akku" #: lib/solaar/ui/window.py:144 -msgid "Lighting" -msgstr "Valaistus" +msgid "Wireless Link" +msgstr "Langaton yhteys" -#: lib/solaar/ui/window.py:178 -msgid "Show Technical Details" -msgstr "Näytä tekniset tiedot" +#: lib/solaar/ui/window.py:148 +msgid "Lighting" +msgstr "Valaistus" -#: lib/solaar/ui/window.py:194 -msgid "Pair new device" -msgstr "Parita uusi laite" +#: lib/solaar/ui/window.py:182 +msgid "Show Technical Details" +msgstr "Näytä tekniset tiedot" -#: lib/solaar/ui/window.py:213 -msgid "Select a device" -msgstr "Valitse laite" +#: lib/solaar/ui/window.py:198 +msgid "Pair new device" +msgstr "Parita uusi laite" + +#: lib/solaar/ui/window.py:216 +msgid "Select a device" +msgstr "Valitse laite" #: lib/solaar/ui/window.py:331 -msgid "Rule Editor" -msgstr "" +msgid "Rule Editor" +msgstr "Sääntöeditori" + +#: lib/solaar/ui/window.py:522 +msgid "Path" +msgstr "Polku" + +#: lib/solaar/ui/window.py:524 +msgid "USB ID" +msgstr "USB-tunnus" + +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 +#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 +msgid "Serial" +msgstr "Sarjanumero" + +#: lib/solaar/ui/window.py:533 +msgid "Index" +msgstr "Järjestysnumero" + +#: lib/solaar/ui/window.py:535 +msgid "Wireless PID" +msgstr "Langaton PID" + +#: lib/solaar/ui/window.py:537 +msgid "Product ID" +msgstr "Tuotteen tunnistekoodi" + +#: lib/solaar/ui/window.py:539 +msgid "Protocol" +msgstr "Yhteyskäytäntö" + +#: lib/solaar/ui/window.py:539 +msgid "Unknown" +msgstr "Tuntematon" #: lib/solaar/ui/window.py:541 -msgid "Path" -msgstr "Polku" +msgid "Polling rate" +msgstr "Päivitysnopeus" -#: lib/solaar/ui/window.py:544 -msgid "USB ID" -msgstr "" - -#: lib/solaar/ui/window.py:547 lib/solaar/ui/window.py:549 -#: lib/solaar/ui/window.py:569 lib/solaar/ui/window.py:571 -msgid "Serial" -msgstr "Sarjanumero" - -#: lib/solaar/ui/window.py:553 -msgid "Index" -msgstr "Järjestysnumero" - -#: lib/solaar/ui/window.py:555 -msgid "Wireless PID" -msgstr "Langaton PID" - -#: lib/solaar/ui/window.py:557 -msgid "Product ID" -msgstr "" +#: lib/solaar/ui/window.py:548 +msgid "Unit ID" +msgstr "Yksikön tunnus" #: lib/solaar/ui/window.py:559 -msgid "Protocol" -msgstr "Yhteyskäytäntö" +msgid "none" +msgstr "ei mitään" -#: lib/solaar/ui/window.py:559 -msgid "Unknown" -msgstr "Tuntematon" +#: lib/solaar/ui/window.py:560 +msgid "Notifications" +msgstr "Ilmoitukset" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:604 +msgid "No device paired." +msgstr "Ei paritettua laitetta." + +#: lib/solaar/ui/window.py:613 #, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "" +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "" +"Enintään %(max_count)s laite voidaan parittaa tähän vastaanottimeen." +msgstr[1] "" +"Enintään %(max_count)s laitetta voidaan parittaa tähän vastaanottimeen." -#: lib/solaar/ui/window.py:562 -msgid "Polling rate" -msgstr "Päivitysnopeus" +#: lib/solaar/ui/window.py:620 +msgid "Only one device can be paired to this receiver." +msgstr "Vain yksi laite voidaan parittaa tähän vastaanottimeen." -#: lib/solaar/ui/window.py:573 -msgid "Unit ID" -msgstr "" - -#: lib/solaar/ui/window.py:584 -msgid "none" -msgstr "ei mitään" - -#: lib/solaar/ui/window.py:585 -msgid "Notifications" -msgstr "Ilmoitukset" - -#: lib/solaar/ui/window.py:622 -msgid "No device paired." -msgstr "" - -#: lib/solaar/ui/window.py:629 +#: lib/solaar/ui/window.py:624 #, python-format -msgid "Up to %(max_count)s device can be paired to this receiver." -msgid_plural "Up to %(max_count)s devices can be paired to this receiver." -msgstr[0] "" -msgstr[1] "" +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "Tässä vastaanottimessa on %d paritus jäljellä." +msgstr[1] "Tässä vastaanottimessa on %d paritusta jäljellä." -#: lib/solaar/ui/window.py:635 -msgid "Only one device can be paired to this receiver." -msgstr "" +#: lib/solaar/ui/window.py:681 +msgid "Battery Voltage" +msgstr "Akun jännite" -#: lib/solaar/ui/window.py:639 -#, python-format -msgid "This receiver has %d pairing remaining." -msgid_plural "This receiver has %d pairings remaining." -msgstr[0] "" -msgstr[1] "" +#: lib/solaar/ui/window.py:683 +msgid "Voltage reported by battery" +msgstr "Akun ilmoittama jännite" -#: lib/solaar/ui/window.py:694 -msgid "Battery information unknown." -msgstr "" +#: lib/solaar/ui/window.py:685 +msgid "Battery Level" +msgstr "Akun taso" + +#: lib/solaar/ui/window.py:687 +msgid "Approximate level reported by battery" +msgstr "Akun ilmoittama arvioitu taso" + +#: lib/solaar/ui/window.py:694 lib/solaar/ui/window.py:696 +msgid "next reported " +msgstr "seuraavaksi ilmoitettu " + +#: lib/solaar/ui/window.py:697 +msgid " and next level to be reported." +msgstr " ja seuraava ilmoitettava taso." #: lib/solaar/ui/window.py:702 -msgid "Battery Voltage" -msgstr "" +msgid "last known" +msgstr "viimeisin tunnettu" -#: lib/solaar/ui/window.py:704 -msgid "Voltage reported by battery" -msgstr "" +#: lib/solaar/ui/window.py:713 +msgid "encrypted" +msgstr "salattu" -#: lib/solaar/ui/window.py:706 lib/solaar/ui/window.py:710 -msgid "Battery Level" -msgstr "" +#: lib/solaar/ui/window.py:715 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "Langaton yhteys laitteen ja vastaanottimen välillä on salattu." -#: lib/solaar/ui/window.py:708 lib/solaar/ui/window.py:712 -msgid "Approximate level reported by battery" -msgstr "" +#: lib/solaar/ui/window.py:717 +msgid "not encrypted" +msgstr "salaamaton" -#: lib/solaar/ui/window.py:715 lib/solaar/ui/window.py:717 -msgid "next reported " -msgstr "" +#: lib/solaar/ui/window.py:721 +msgid "" +"The wireless link between this device and its receiver is not encrypted.\n" +"This is a security issue for pointing devices, and a major security issue for text-input devices." +msgstr "" +"Langaton yhteys tämän laitteen ja sen vastaanottimen välillä ei ole salattu.\n" +"Tämä on tietoturvaongelma osoitinlaitteille ja suuri tietoturvariski tekstinsyöttölaitteille." -#: lib/solaar/ui/window.py:718 -msgid " and next level to be reported." -msgstr "" - -#: lib/solaar/ui/window.py:723 -msgid "last known" -msgstr "viimeisin tunnettu" - -#: lib/solaar/ui/window.py:731 -msgid "not encrypted" -msgstr "salaamaton" - -#: lib/solaar/ui/window.py:735 -msgid "The wireless link between this device and its receiver is not " - "encrypted.\n" - "\n" - "For pointing devices (mice, trackballs, trackpads), this is a minor " - "security issue.\n" - "\n" - "It is, however, a major security issue for text-input devices " - "(keyboards, numpads),\n" - "because typed text can be sniffed inconspicuously by 3rd parties " - "within range." -msgstr "Langaton yhteys laitteen ja vastaanottimen välillä ei ole salattu.\n" - "\n" - "Osoitinlaitteita (hiiret, ohjainpallot, tasohiiret) käytettäessä se " - "on pieni tietoturvallisuusriski.\n" - "\n" - "Kuitenkin, näppäimistöjen kanssa se on suuri tietoturvallisuusriski " - "sillä kolmannet osapuolet\n" - "voivat lukea huomaamattomasti kirjoitetun tekstin yhteyden kantaman " - "sisällä." - -#: lib/solaar/ui/window.py:744 -msgid "encrypted" -msgstr "salattu" - -#: lib/solaar/ui/window.py:746 -msgid "The wireless link between this device and its receiver is encrypted." -msgstr "Langaton yhteys laitteen ja vastaanottimen välillä on salattu." - -#: lib/solaar/ui/window.py:759 +#: lib/solaar/ui/window.py:737 #, python-format -msgid "%(light_level)d lux" -msgstr "" +msgid "%(light_level)d lux" +msgstr "%(light_level)d luksia" -#~ msgid " paired devices." -#~ msgstr " paritettua laitetta" +#~ msgid " paired devices." +#~ msgstr " paritettua laitetta" -#~ msgid "1 paired device." -#~ msgstr "Yksi paritettu laite." - -#~ msgid "Found a new device" -#~ msgstr "Löydetty uusi laite." - -#~ msgid "If the device is already turned on,\n" -#~ "turn if off and on again." -#~ msgstr "Jos laite on jo päällä, sammuta se\n" -#~ "ja kytke virta uudelleen päälle." - -#~ msgid "No device paired" -#~ msgstr "Ei paritettuja laitteita" - -#~ msgid "Only one device can be paired to this receiver" -#~ msgstr "Vain yksi laite voidaan parittaa tähän vastaanottimeen" - -#~ msgid "Shows status of devices connected\n" -#~ "through wireless Logitech receivers." -#~ msgstr "Näyttää Logitechin langattoman \n" -#~ "vastaanottimen kautta liitettyjen\n" -#~ "laitteiden tilan." - -#~ msgid "Smooth Scrolling" -#~ msgstr "Pehmeä vieritys" +#~ msgid "1 paired device." +#~ msgstr "Yksi paritettu laite." #, python-format -#~ msgid "The receiver only supports %d paired device(s)." -#~ msgstr "Vastaanotin tukee vain %d liitettyä laitetta." +#~ msgid "" +#~ "Found a Logitech Receiver (%s), but did not have permission to open it." +#~ msgstr "" +#~ "Logitechin vastaanotin (%s) löydettiin, mutta sitä ei ollut oikeutta avata." -#~ msgid "The receiver was unplugged." -#~ msgstr "Vastaanotin irroitettiin." +#~ msgid "Found a new device" +#~ msgstr "Löydetty uusi laite." -#~ msgid "USB id" -#~ msgstr "USB id" +#~ msgid "" +#~ "If the device is already turned on,\n" +#~ "turn if off and on again." +#~ msgstr "" +#~ "Jos laite on jo päällä, sammuta se\n" +#~ "ja kytke virta uudelleen päälle." -#~ msgid "Up to %d devices can be paired to this receiver" -#~ msgstr "Korkeintaan %d laitetta voidaan parittaa tähän " -#~ "vastaanottimeen" +#~ msgid "" +#~ "If you've just installed Solaar, try removing the receiver and plugging it " +#~ "back in." +#~ msgstr "" +#~ "Jos olet juuri asentanut Solaarin, yritä vastaanottimen irroittamista ja " +#~ "liittämistä uudelleen." -#~ msgid "closed" -#~ msgstr "suljettu" +#~ msgid "No Logitech receiver found" +#~ msgstr "Ei löydetty Logitechin vastaanottimia." -#~ msgid "lux" -#~ msgstr "luksia" +#~ msgid "No device paired" +#~ msgstr "Ei paritettuja laitteita" -#~ msgid "open" -#~ msgstr "avoin" +#~ msgid "Only one device can be paired to this receiver" +#~ msgstr "Vain yksi laite voidaan parittaa tähän vastaanottimeen" -#~ msgid "pair new device" -#~ msgstr "liitä uusi laite" +#~ msgid "" +#~ "Shows status of devices connected\n" +#~ "through wireless Logitech receivers." +#~ msgstr "" +#~ "Näyttää Logitechin langattoman \n" +#~ "vastaanottimen kautta liitettyjen\n" +#~ "laitteiden tilan." -#~ msgid "paired devices" -#~ msgstr "paritettua laitetta" +#~ msgid "Smooth Scrolling" +#~ msgstr "Sulava vieritys" -#~ msgid "pairing lock is " -#~ msgstr "parituslukko on " +#, python-format +#~ msgid "The receiver only supports %d paired device(s)." +#~ msgstr "Vastaanotin tukee vain %d liitettyä laitetta." + +#~ msgid "The receiver was unplugged." +#~ msgstr "Vastaanotin irroitettiin." + +#~ msgid "" +#~ "The wireless link between this device and its receiver is not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices (keyboards, numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties within range." +#~ msgstr "" +#~ "Langaton yhteys laitteen ja vastaanottimen välillä ei ole salattu.\n" +#~ "\n" +#~ "Osoitinlaitteita (hiiret, ohjainpallot, tasohiiret) käytettäessä se on pieni tietoturvallisuusriski.\n" +#~ "\n" +#~ "Kuitenkin, näppäimistöjen kanssa se on suuri tietoturvallisuusriski sillä kolmannet osapuolet\n" +#~ "voivat lukea huomaamattomasti kirjoitetun tekstin yhteyden kantaman sisällä." + +#~ msgid "Turn on the device you want to pair." +#~ msgstr "Käynnistä laite, jonka haluat parittaa." + +#~ msgid "USB id" +#~ msgstr "USB-tunnus" + +#~ msgid "Up to %d devices can be paired to this receiver" +#~ msgstr "Korkeintaan %d laitetta voidaan parittaa tähän vastaanottimeen" + +#~ msgid "closed" +#~ msgstr "suljettu" + +#~ msgid "height" +#~ msgstr "korkeus" + +#~ msgid "lux" +#~ msgstr "luksia" + +#~ msgid "open" +#~ msgstr "avoin" + +#~ msgid "pair new device" +#~ msgstr "liitä uusi laite" + +#~ msgid "paired devices" +#~ msgstr "paritettua laitetta" + +#~ msgid "pairing lock is " +#~ msgstr "parituslukko on " + +#~ msgid "unknown" +#~ msgstr "tuntematon" + +#~ msgid "width" +#~ msgstr "leveys" diff --git a/po/fr.po b/po/fr.po index a16a9a10..0f92ba0f 100644 --- a/po/fr.po +++ b/po/fr.po @@ -5,10 +5,10 @@ # msgid "" msgstr "" -"Project-Id-Version: solaar 1.1.10\n" +"Project-Id-Version: solaar 1.1.19\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-09-25 06:09+0200\n" -"PO-Revision-Date: 2023-09-25 18:51+0200\n" +"POT-Creation-Date: 2026-01-08 19:21+0100\n" +"PO-Revision-Date: 2026-01-08 19:38+0100\n" "Last-Translator: David Geiger \n" "Language-Team: Language: fr\n" "Language: fr\n" @@ -16,265 +16,386 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Poedit 3.3.2\n" +"X-Generator: Poedit 3.8\n" -#: lib/logitech_receiver/base_usb.py:46 +#: lib/logitech_receiver/base_usb.py:52 msgid "Bolt Receiver" msgstr "Récepteur Bolt" -#: lib/logitech_receiver/base_usb.py:57 +#: lib/logitech_receiver/base_usb.py:64 msgid "Unifying Receiver" msgstr "Récepteur Unifying" -#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 -#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 -#: lib/logitech_receiver/base_usb.py:114 +#: lib/logitech_receiver/base_usb.py:75 lib/logitech_receiver/base_usb.py:87 +#: lib/logitech_receiver/base_usb.py:100 lib/logitech_receiver/base_usb.py:113 msgid "Nano Receiver" msgstr "Récepteur Nano" -#: lib/logitech_receiver/base_usb.py:124 +#: lib/logitech_receiver/base_usb.py:125 msgid "Lightspeed Receiver" msgstr "Récepteur Lightspeed" -#: lib/logitech_receiver/base_usb.py:133 +#: lib/logitech_receiver/base_usb.py:136 msgid "EX100 Receiver 27 Mhz" msgstr "Récepteur EX100 27 Mhz" -#: lib/logitech_receiver/i18n.py:30 +#: lib/logitech_receiver/common.py:649 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Batterie : %(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:652 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Batterie : %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:1048 +#: lib/logitech_receiver/settings_templates.py:299 +msgid "Disabled" +msgstr "Désactivé" + +#: lib/logitech_receiver/hidpp20.py:1049 +msgid "Static" +msgstr "Statique" + +#: lib/logitech_receiver/hidpp20.py:1050 +msgid "Pulse" +msgstr "Pulsation" + +#: lib/logitech_receiver/hidpp20.py:1051 +msgid "Cycle" +msgstr "Cycle" + +#: lib/logitech_receiver/hidpp20.py:1052 +msgid "Boot" +msgstr "Boot" + +#: lib/logitech_receiver/hidpp20.py:1053 +msgid "Demo" +msgstr "Démo" + +#: lib/logitech_receiver/hidpp20.py:1055 +msgid "Breathe" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1058 +msgid "Ripple" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1059 +msgid "Decomposition" +msgstr "Décomposition" + +#: lib/logitech_receiver/hidpp20.py:1060 +msgid "Signature1" +msgstr "Signature 1" + +#: lib/logitech_receiver/hidpp20.py:1061 +msgid "Signature2" +msgstr "Signature 2" + +#: lib/logitech_receiver/hidpp20.py:1062 +msgid "CycleS" +msgstr "Cycle S" + +#: lib/logitech_receiver/hidpp20.py:1126 +msgid "Unknown Location" +msgstr "Emplacement inconnu" + +#: lib/logitech_receiver/hidpp20.py:1127 +msgid "Primary" +msgstr "Primaire" + +#: lib/logitech_receiver/hidpp20.py:1128 +msgid "Logo" +msgstr "Logo" + +#: lib/logitech_receiver/hidpp20.py:1129 +msgid "Left Side" +msgstr "Côté gauche" + +#: lib/logitech_receiver/hidpp20.py:1130 +msgid "Right Side" +msgstr "Côté droit" + +#: lib/logitech_receiver/hidpp20.py:1131 +msgid "Combined" +msgstr "Combiné" + +#: lib/logitech_receiver/hidpp20.py:1132 +msgid "Primary 1" +msgstr "Primaire 1" + +#: lib/logitech_receiver/hidpp20.py:1133 +msgid "Primary 2" +msgstr "Primaire 2" + +#: lib/logitech_receiver/hidpp20.py:1134 +msgid "Primary 3" +msgstr "Primaire 3" + +#: lib/logitech_receiver/hidpp20.py:1135 +msgid "Primary 4" +msgstr "Primaire 4" + +#: lib/logitech_receiver/hidpp20.py:1136 +msgid "Primary 5" +msgstr "Primaire 5" + +#: lib/logitech_receiver/hidpp20.py:1137 +msgid "Primary 6" +msgstr "Primaire 6" + +#: lib/logitech_receiver/i18n.py:28 msgid "empty" msgstr "vide" -#: lib/logitech_receiver/i18n.py:31 +#: lib/logitech_receiver/i18n.py:29 msgid "critical" msgstr "critique" -#: lib/logitech_receiver/i18n.py:32 +#: lib/logitech_receiver/i18n.py:30 msgid "low" msgstr "faible" -#: lib/logitech_receiver/i18n.py:33 +#: lib/logitech_receiver/i18n.py:31 msgid "average" msgstr "moyenne" -#: lib/logitech_receiver/i18n.py:34 +#: lib/logitech_receiver/i18n.py:32 msgid "good" msgstr "bonne" -#: lib/logitech_receiver/i18n.py:35 +#: lib/logitech_receiver/i18n.py:33 msgid "full" msgstr "pleine" -#: lib/logitech_receiver/i18n.py:38 +#: lib/logitech_receiver/i18n.py:35 msgid "discharging" msgstr "En décharge" -#: lib/logitech_receiver/i18n.py:39 +#: lib/logitech_receiver/i18n.py:36 msgid "recharging" msgstr "En charge" -#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 +#: lib/logitech_receiver/i18n.py:37 lib/solaar/ui/window.py:700 msgid "charging" msgstr "en charge" -#: lib/logitech_receiver/i18n.py:41 +#: lib/logitech_receiver/i18n.py:38 msgid "not charging" msgstr "pas en charge" -#: lib/logitech_receiver/i18n.py:42 +#: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "presque pleine" -#: lib/logitech_receiver/i18n.py:43 +#: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "chargée" -#: lib/logitech_receiver/i18n.py:44 +#: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "recharge lente" -#: lib/logitech_receiver/i18n.py:45 +#: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "batterie invalide" -#: lib/logitech_receiver/i18n.py:46 +#: lib/logitech_receiver/i18n.py:43 msgid "thermal error" msgstr "erreur thermique" -#: lib/logitech_receiver/i18n.py:47 +#: lib/logitech_receiver/i18n.py:44 msgid "error" msgstr "erreur" -#: lib/logitech_receiver/i18n.py:48 +#: lib/logitech_receiver/i18n.py:45 msgid "standard" msgstr "standard" -#: lib/logitech_receiver/i18n.py:49 +#: lib/logitech_receiver/i18n.py:46 msgid "fast" msgstr "rapide" -#: lib/logitech_receiver/i18n.py:50 +#: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "lente" -#: lib/logitech_receiver/i18n.py:53 +#: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "le périphérique ne répond pas" -#: lib/logitech_receiver/i18n.py:54 +#: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "périphérique non pris en charge" -#: lib/logitech_receiver/i18n.py:55 +#: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "trop de périphériques" -#: lib/logitech_receiver/i18n.py:56 +#: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "délai dépassé" -#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 +#: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:555 msgid "Firmware" msgstr "Micrologiciel" -#: lib/logitech_receiver/i18n.py:60 +#: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Chargeur d'amorçage" -#: lib/logitech_receiver/i18n.py:61 +#: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Matériel" -#: lib/logitech_receiver/i18n.py:62 +#: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "Autre" -#: lib/logitech_receiver/i18n.py:65 +#: lib/logitech_receiver/i18n.py:59 msgid "Left Button" msgstr "Bouton gauche" -#: lib/logitech_receiver/i18n.py:66 +#: lib/logitech_receiver/i18n.py:60 msgid "Right Button" msgstr "Bouton droit" -#: lib/logitech_receiver/i18n.py:67 +#: lib/logitech_receiver/i18n.py:61 msgid "Middle Button" msgstr "Bouton central" -#: lib/logitech_receiver/i18n.py:68 +#: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "Bouton retour" -#: lib/logitech_receiver/i18n.py:69 +#: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "Bouton avant" -#: lib/logitech_receiver/i18n.py:70 +#: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "Bouton gestuel de la souris" -#: lib/logitech_receiver/i18n.py:71 +#: lib/logitech_receiver/i18n.py:65 msgid "Smart Shift" msgstr "Changement intelligent" -#: lib/logitech_receiver/i18n.py:72 +#: lib/logitech_receiver/i18n.py:66 msgid "DPI Switch" msgstr "Commutateur DPI" -#: lib/logitech_receiver/i18n.py:73 +#: lib/logitech_receiver/i18n.py:67 msgid "Left Tilt" msgstr "Inclinaison à gauche" -#: lib/logitech_receiver/i18n.py:74 +#: lib/logitech_receiver/i18n.py:68 msgid "Right Tilt" msgstr "Inclinaison à droite" -#: lib/logitech_receiver/i18n.py:75 +#: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "Clic gauche" -#: lib/logitech_receiver/i18n.py:76 +#: lib/logitech_receiver/i18n.py:70 msgid "Right Click" msgstr "Clic droit" -#: lib/logitech_receiver/i18n.py:77 +#: lib/logitech_receiver/i18n.py:71 msgid "Mouse Middle Button" msgstr "Bouton central de la souris" -#: lib/logitech_receiver/i18n.py:78 +#: lib/logitech_receiver/i18n.py:72 msgid "Mouse Back Button" msgstr "Bouton de retour de la souris" -#: lib/logitech_receiver/i18n.py:79 +#: lib/logitech_receiver/i18n.py:73 msgid "Mouse Forward Button" msgstr "Bouton avant de la souris" -#: lib/logitech_receiver/i18n.py:80 +#: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "Navigation par bouton de gestuelle" -#: lib/logitech_receiver/i18n.py:81 +#: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "Bouton de défilement vers la gauche de la souris" -#: lib/logitech_receiver/i18n.py:82 +#: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "Bouton de défilement vers la droite de la souris" -#: lib/logitech_receiver/i18n.py:85 +#: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "pressé" -#: lib/logitech_receiver/i18n.py:86 +#: lib/logitech_receiver/i18n.py:79 msgid "released" msgstr "relâché" -#: lib/logitech_receiver/notifications.py:75 -#: lib/logitech_receiver/notifications.py:126 -msgid "pairing lock is closed" -msgstr "le verrou de jumelage est fermé" - -#: lib/logitech_receiver/notifications.py:75 -#: lib/logitech_receiver/notifications.py:126 -msgid "pairing lock is open" -msgstr "le verrou de jumelage est ouvert" - -#: lib/logitech_receiver/notifications.py:92 -msgid "discovery lock is closed" -msgstr "le verrou de détection est fermé" - -#: lib/logitech_receiver/notifications.py:92 -msgid "discovery lock is open" -msgstr "le verrou de détection est ouvert" - -#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 msgid "connected" msgstr "connecté" -#: lib/logitech_receiver/notifications.py:224 +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 msgid "disconnected" msgstr "déconnecté" -#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 +#: lib/logitech_receiver/notifications.py:182 msgid "unpaired" msgstr "non jumelé" -#: lib/logitech_receiver/notifications.py:304 +#: lib/logitech_receiver/notifications.py:231 msgid "powered on" msgstr "sous tension" -#: lib/logitech_receiver/settings.py:750 +#: lib/logitech_receiver/notifications.py:283 +msgid "ADC measurement notification" +msgstr "Notification de mesure ADC" + +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:484 +msgid "pairing lock is closed" +msgstr "le verrou de jumelage est fermé" + +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:484 +msgid "pairing lock is open" +msgstr "le verrou de jumelage est ouvert" + +#: lib/logitech_receiver/notifications.py:447 +msgid "discovery lock is closed" +msgstr "le verrou de détection est fermé" + +#: lib/logitech_receiver/notifications.py:447 +msgid "discovery lock is open" +msgstr "le verrou de détection est ouvert" + +#: lib/logitech_receiver/receiver.py:437 +msgid "No paired devices." +msgstr "Aucun périphérique jumelé." + +#: lib/logitech_receiver/receiver.py:439 lib/solaar/ui/window.py:606 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s périphérique jumelé." +msgstr[1] "%(count)s périphériques jumelés." + +#: lib/logitech_receiver/settings.py:602 msgid "register" msgstr "registre" -#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 +#: lib/logitech_receiver/settings.py:616 lib/logitech_receiver/settings.py:654 msgid "feature" msgstr "fonctionnalité" -#: lib/logitech_receiver/settings_templates.py:139 +#: lib/logitech_receiver/settings_templates.py:138 msgid "Swap Fx function" msgstr "Fonction Swap Fx" -#: lib/logitech_receiver/settings_templates.py:140 +#: lib/logitech_receiver/settings_templates.py:141 msgid "" "When set, the F1..F12 keys will activate their special function,\n" "and you must hold the FN key to activate their standard function." @@ -282,7 +403,7 @@ msgstr "" "Lorsque défini, les touches F1..F12 activeront leurs fonctions spéciales,\n" "et vous devez maintenir la touche FN pour activer leurs fonctions standards." -#: lib/logitech_receiver/settings_templates.py:142 +#: lib/logitech_receiver/settings_templates.py:146 msgid "" "When unset, the F1..F12 keys will activate their standard function,\n" "and you must hold the FN key to activate their special function." @@ -291,29 +412,29 @@ msgstr "" "standards,\n" "et vous devez maintenir la touche FN pour activer leurs fonctions spéciales." -#: lib/logitech_receiver/settings_templates.py:149 +#: lib/logitech_receiver/settings_templates.py:154 msgid "Hand Detection" msgstr "Détection manuelle" -#: lib/logitech_receiver/settings_templates.py:150 +#: lib/logitech_receiver/settings_templates.py:155 msgid "Turn on illumination when the hands hover over the keyboard." msgstr "Allume l'éclairage lorsque les mains passent au-dessus du clavier." -#: lib/logitech_receiver/settings_templates.py:157 +#: lib/logitech_receiver/settings_templates.py:162 msgid "Scroll Wheel Smooth Scrolling" msgstr "Défilement fluide à la molette" -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:239 -#: lib/logitech_receiver/settings_templates.py:267 +#: lib/logitech_receiver/settings_templates.py:163 +#: lib/logitech_receiver/settings_templates.py:410 +#: lib/logitech_receiver/settings_templates.py:439 msgid "High-sensitivity mode for vertical scroll with the wheel." msgstr "Mode haute sensibilité pour défilement vertical avec la roulette." -#: lib/logitech_receiver/settings_templates.py:165 +#: lib/logitech_receiver/settings_templates.py:170 msgid "Side Scrolling" msgstr "Défilement latéral" -#: lib/logitech_receiver/settings_templates.py:167 +#: lib/logitech_receiver/settings_templates.py:172 msgid "" "When disabled, pushing the wheel sideways sends custom button events\n" "instead of the standard side-scrolling events." @@ -323,45 +444,107 @@ msgstr "" " de bouton personnalisés à la place des évènements standards de défilement " "latéral." -#: lib/logitech_receiver/settings_templates.py:177 +#: lib/logitech_receiver/settings_templates.py:182 msgid "Sensitivity (DPI - older mice)" msgstr "Sensibilité (DPI - souris plus anciennne)" -#: lib/logitech_receiver/settings_templates.py:178 -#: lib/logitech_receiver/settings_templates.py:712 +#: lib/logitech_receiver/settings_templates.py:183 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 msgid "Mouse movement sensitivity" msgstr "Sensibilité du mouvement de la souris" -#: lib/logitech_receiver/settings_templates.py:208 -#: lib/logitech_receiver/settings_templates.py:218 -#: lib/logitech_receiver/settings_templates.py:225 -msgid "Backlight" -msgstr "Rétroéclairage" +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Backlight Timed" +msgstr "Rétroéclairage chronométré" -#: lib/logitech_receiver/settings_templates.py:209 -#: lib/logitech_receiver/settings_templates.py:226 +#: lib/logitech_receiver/settings_templates.py:257 +#: lib/logitech_receiver/settings_templates.py:397 msgid "Set illumination time for keyboard." msgstr "Définir le temps d'éclairage du clavier." -#: lib/logitech_receiver/settings_templates.py:219 -msgid "Turn illumination on or off on keyboard." -msgstr "Activez ou désactivez l'éclairage sur le clavier." +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Backlight" +msgstr "Rétroéclairage" -#: lib/logitech_receiver/settings_templates.py:237 +#: lib/logitech_receiver/settings_templates.py:269 +msgid "" +"Illumination level on keyboard. Changes made are only applied in Manual " +"mode." +msgstr "" +"Niveau d'éclairage du clavier. Les modifications apportées ne sont " +"appliquées qu'en mode manuel." + +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Automatic" +msgstr "Automatique" + +#: lib/logitech_receiver/settings_templates.py:303 +msgid "Manual" +msgstr "Manuel" + +#: lib/logitech_receiver/settings_templates.py:305 +msgid "Enabled" +msgstr "Activé" + +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Backlight Level" +msgstr "Niveau de rétroéclairage" + +#: lib/logitech_receiver/settings_templates.py:312 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "Niveau d'éclairage du clavier en mode manuel." + +#: lib/logitech_receiver/settings_templates.py:369 +msgid "Backlight Delay Hands Out" +msgstr "Délai de rétroéclairage sans les mains" + +#: lib/logitech_receiver/settings_templates.py:370 +msgid "" +"Delay in seconds until backlight fades out with hands away from keyboard." +msgstr "" +"Délai jusqu'à ce que le rétroéclairage s'éteigne avec les mains éloignées du " +"clavier." + +#: lib/logitech_receiver/settings_templates.py:378 +msgid "Backlight Delay Hands In" +msgstr "Délai de rétroéclairage avec les mains" + +#: lib/logitech_receiver/settings_templates.py:379 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "" +"Délai jusqu'à ce que le rétroéclairage s'éteigne avec les mains près du " +"clavier." + +#: lib/logitech_receiver/settings_templates.py:387 +msgid "Backlight Delay Powered" +msgstr "Délai de rétroéclairage alimenté" + +#: lib/logitech_receiver/settings_templates.py:388 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "" +"Délai jusqu'à ce que le rétroéclairage s'éteigne avec une alimentation " +"externe." + +#: lib/logitech_receiver/settings_templates.py:396 +msgid "Backlight (Seconds)" +msgstr "Rétroéclairage (Secondes)" + +#: lib/logitech_receiver/settings_templates.py:408 msgid "Scroll Wheel High Resolution" msgstr "Molette haute résolution" -#: lib/logitech_receiver/settings_templates.py:240 -#: lib/logitech_receiver/settings_templates.py:268 +#: lib/logitech_receiver/settings_templates.py:412 +#: lib/logitech_receiver/settings_templates.py:441 msgid "Set to ignore if scrolling is abnormally fast or slow" msgstr "Définir pour ignorer si le défilement est anormalement rapide ou lent" -#: lib/logitech_receiver/settings_templates.py:247 -#: lib/logitech_receiver/settings_templates.py:277 +#: lib/logitech_receiver/settings_templates.py:419 +#: lib/logitech_receiver/settings_templates.py:450 msgid "Scroll Wheel Diversion" msgstr "Interception de la molette" -#: lib/logitech_receiver/settings_templates.py:249 +#: lib/logitech_receiver/settings_templates.py:421 msgid "" "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " "Solaar rules but are otherwise ignored)." @@ -369,19 +552,19 @@ msgstr "" "Active l'envoi par la molette de notifications LOWRES_WHEEL HID++ (ce qui " "déclenche les règles Solaar qui sont ignorées sinon)." -#: lib/logitech_receiver/settings_templates.py:256 +#: lib/logitech_receiver/settings_templates.py:428 msgid "Scroll Wheel Direction" msgstr "Direction de la molette" -#: lib/logitech_receiver/settings_templates.py:257 +#: lib/logitech_receiver/settings_templates.py:429 msgid "Invert direction for vertical scroll with wheel." msgstr "Direction inversée pour le défilement vertical avec la molette." -#: lib/logitech_receiver/settings_templates.py:265 +#: lib/logitech_receiver/settings_templates.py:437 msgid "Scroll Wheel Resolution" msgstr "Résolution de la molette" -#: lib/logitech_receiver/settings_templates.py:279 +#: lib/logitech_receiver/settings_templates.py:452 msgid "" "Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." @@ -389,20 +572,20 @@ msgstr "" "Active l'envoi par la molette de notifications HIRES_WHEEL HID++ (ce qui " "déclenche les règles Solaar qui sont ignorées sinon)." -#: lib/logitech_receiver/settings_templates.py:288 +#: lib/logitech_receiver/settings_templates.py:461 msgid "Sensitivity (Pointer Speed)" msgstr "Sensibilité (vitesse du pointeur)" -#: lib/logitech_receiver/settings_templates.py:289 +#: lib/logitech_receiver/settings_templates.py:462 msgid "Speed multiplier for mouse (256 is normal multiplier)." msgstr "" "Multiplicateur de vitesse pour la souris (256 est le multiplicateur normal)." -#: lib/logitech_receiver/settings_templates.py:299 +#: lib/logitech_receiver/settings_templates.py:472 msgid "Thumb Wheel Diversion" msgstr "Interception de la molette de pouce" -#: lib/logitech_receiver/settings_templates.py:301 +#: lib/logitech_receiver/settings_templates.py:474 msgid "" "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." @@ -410,46 +593,51 @@ msgstr "" "Active l'envoi par la molette de pouce de notifications THUMB_WHEEL HID++ " "(ce qui déclenche les règles Solaar qui sont ignorées sinon)." -#: lib/logitech_receiver/settings_templates.py:310 +#: lib/logitech_receiver/settings_templates.py:483 msgid "Thumb Wheel Direction" msgstr "Direction de la molette de pouce" -#: lib/logitech_receiver/settings_templates.py:311 +#: lib/logitech_receiver/settings_templates.py:484 msgid "Invert thumb wheel scroll direction." msgstr "Inverser la direction du défilement de la molette de pouce." -#: lib/logitech_receiver/settings_templates.py:319 +#: lib/logitech_receiver/settings_templates.py:504 msgid "Onboard Profiles" msgstr "Profils embarqués" -#: lib/logitech_receiver/settings_templates.py:320 +#: lib/logitech_receiver/settings_templates.py:505 msgid "" -"Enable onboard profiles, which often control report rate and keyboard " -"lighting" +"Enable an onboard profile, which controls report rate, sensitivity, and " +"button actions" msgstr "" -"Active les profils embarqués, qui contrôlent souvent le taux de rapport et " -"l'éclairage du clavier" +"Activer un profil intégré, qui contrôle le taux de rapport, la sensibilité " +"et les actions des boutons" -#: lib/logitech_receiver/settings_templates.py:330 -msgid "Polling Rate (ms)" -msgstr "Taux de scrutation (ms)" +#: lib/logitech_receiver/settings_templates.py:549 +#: lib/logitech_receiver/settings_templates.py:582 +msgid "Report Rate" +msgstr "Taux de rapport" -#: lib/logitech_receiver/settings_templates.py:332 -msgid "Frequency of device polling, in milliseconds" -msgstr "Fréquence de scrutation du périphérique, en millisecondes" +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +msgid "Frequency of device movement reports" +msgstr "Fréquence des rapports de mouvement des périphériques" -#: lib/logitech_receiver/settings_templates.py:333 -#: lib/logitech_receiver/settings_templates.py:1046 -#: lib/logitech_receiver/settings_templates.py:1076 +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1421 +#: lib/logitech_receiver/settings_templates.py:1452 msgid "May need Onboard Profiles set to Disable to be effective." msgstr "" "Peut nécessiter de désactiver les profils embarqués pour être effectif." -#: lib/logitech_receiver/settings_templates.py:365 +#: lib/logitech_receiver/settings_templates.py:612 msgid "Divert crown events" msgstr "Définir les évènements de la couronne" -#: lib/logitech_receiver/settings_templates.py:366 +#: lib/logitech_receiver/settings_templates.py:613 msgid "" "Make crown send CROWN HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." @@ -457,37 +645,31 @@ msgstr "" "Active l'envoi par la couronne de notifications CROWN HID++ (ce qui " "déclenche les règles Solaar qui sont ignorées sinon)." -#: lib/logitech_receiver/settings_templates.py:374 +#: lib/logitech_receiver/settings_templates.py:621 msgid "Crown smooth scroll" msgstr "Défilement fluide de la couronne" -#: lib/logitech_receiver/settings_templates.py:375 +#: lib/logitech_receiver/settings_templates.py:622 msgid "Set crown smooth scroll" msgstr "Définir le défilement fluide de la couronne" -#: lib/logitech_receiver/settings_templates.py:383 -msgid "Divert G Keys" -msgstr "Définir les touches G" +#: lib/logitech_receiver/settings_templates.py:630 +msgid "Divert G and M Keys" +msgstr "Interception des touches G et M" -#: lib/logitech_receiver/settings_templates.py:385 +#: lib/logitech_receiver/settings_templates.py:631 msgid "" -"Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " +"Make G and M keys send HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." msgstr "" -"Active l'envoi par les touches G de notifications GKEY HID++ (ce qui " +"Active l'envoi par les touches G et M de notifications HID++ (ce qui " "déclenche les règles Solaar qui sont ignorées sinon)." -#: lib/logitech_receiver/settings_templates.py:386 -msgid "May also make M keys and MR key send HID++ notifications" -msgstr "" -"Peut aussi permettre que les touches M et la touche MR envoient des " -"notifications HID++" - -#: lib/logitech_receiver/settings_templates.py:402 +#: lib/logitech_receiver/settings_templates.py:645 msgid "Scroll Wheel Ratcheted" msgstr "Molette mode cliquet" -#: lib/logitech_receiver/settings_templates.py:403 +#: lib/logitech_receiver/settings_templates.py:646 msgid "" "Switch the mouse wheel between speed-controlled ratcheting and always " "freespin." @@ -495,19 +677,19 @@ msgstr "" "Basculez la molette de la souris entre le mode cliquet à vitesse contrôlée " "et toujours en roue libre." -#: lib/logitech_receiver/settings_templates.py:405 +#: lib/logitech_receiver/settings_templates.py:648 msgid "Freespinning" msgstr "Roue libre" -#: lib/logitech_receiver/settings_templates.py:405 +#: lib/logitech_receiver/settings_templates.py:648 msgid "Ratcheted" msgstr "Cliquet" -#: lib/logitech_receiver/settings_templates.py:412 +#: lib/logitech_receiver/settings_templates.py:655 msgid "Scroll Wheel Ratchet Speed" msgstr "Vitesse de la molette mode cliquet" -#: lib/logitech_receiver/settings_templates.py:414 +#: lib/logitech_receiver/settings_templates.py:657 msgid "" "Use the mouse wheel speed to switch between ratcheted and freespinning.\n" "The mouse wheel is always ratcheted at 50." @@ -516,19 +698,27 @@ msgstr "" "cliquet et le mode roue libre.\n" "La molette est toujours en mode cliquet à 50." -#: lib/logitech_receiver/settings_templates.py:463 +#: lib/logitech_receiver/settings_templates.py:707 +msgid "Scroll Wheel Ratchet Torque" +msgstr "Couple de la molette mode cliquet" + +#: lib/logitech_receiver/settings_templates.py:708 +msgid "Change the torque needed to overcome the ratchet." +msgstr "Modifiez le couple nécessaire pour vaincre le mode cliquet." + +#: lib/logitech_receiver/settings_templates.py:743 msgid "Key/Button Actions" msgstr "Actions des boutons" -#: lib/logitech_receiver/settings_templates.py:465 +#: lib/logitech_receiver/settings_templates.py:745 msgid "Change the action for the key or button." msgstr "Modifiez l'action pour la touche ou pour le bouton." -#: lib/logitech_receiver/settings_templates.py:465 +#: lib/logitech_receiver/settings_templates.py:747 msgid "Overridden by diversion." msgstr "Remplacé par interception." -#: lib/logitech_receiver/settings_templates.py:466 +#: lib/logitech_receiver/settings_templates.py:749 msgid "" "Changing important actions (such as for the left mouse button) can result in " "an unusable system." @@ -536,11 +726,11 @@ msgstr "" "La modification d'importantes actions (comme le bouton gauche de la souris) " "pourrait rendre le système inutilisable." -#: lib/logitech_receiver/settings_templates.py:639 +#: lib/logitech_receiver/settings_templates.py:924 msgid "Key/Button Diversion" msgstr "Interception des boutons/touches" -#: lib/logitech_receiver/settings_templates.py:640 +#: lib/logitech_receiver/settings_templates.py:925 msgid "" "Make the key or button send HID++ notifications (Diverted) or initiate Mouse " "Gestures or Sliding DPI" @@ -548,36 +738,37 @@ msgstr "" "Active l'envoi par la touche ou le bouton de notifications HID++ " "(interception), ou une gestuelle de souris ou l'échelle de déplacement" -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 -#: lib/logitech_receiver/settings_templates.py:645 +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 msgid "Diverted" msgstr "Interception" -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 msgid "Mouse Gestures" msgstr "Gestuelle à la souris" -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 -#: lib/logitech_receiver/settings_templates.py:645 +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 msgid "Regular" msgstr "Normal" -#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:928 msgid "Sliding DPI" msgstr "Amplification du déplacement" -#: lib/logitech_receiver/settings_templates.py:711 +#: lib/logitech_receiver/settings_templates.py:1018 +#: lib/logitech_receiver/settings_templates.py:1046 msgid "Sensitivity (DPI)" msgstr "Sensibilité (DPI)" -#: lib/logitech_receiver/settings_templates.py:752 +#: lib/logitech_receiver/settings_templates.py:1123 msgid "Sensitivity Switching" msgstr "Basculement de la sensibilité" -#: lib/logitech_receiver/settings_templates.py:754 +#: lib/logitech_receiver/settings_templates.py:1125 msgid "" "Switch the current sensitivity and the remembered sensitivity when the key " "or button is pressed.\n" @@ -588,328 +779,328 @@ msgstr "" "S'il n'y aucune sensibilité enregistrée, rappelle uniquement la sensibilité " "courante" -#: lib/logitech_receiver/settings_templates.py:758 +#: lib/logitech_receiver/settings_templates.py:1129 msgid "Off" msgstr "Eteint" -#: lib/logitech_receiver/settings_templates.py:791 +#: lib/logitech_receiver/settings_templates.py:1160 msgid "Disable keys" msgstr "Désactiver les touches" -#: lib/logitech_receiver/settings_templates.py:792 +#: lib/logitech_receiver/settings_templates.py:1161 msgid "Disable specific keyboard keys." msgstr "Désactiver des touches spécifiques du clavier." -#: lib/logitech_receiver/settings_templates.py:795 +#: lib/logitech_receiver/settings_templates.py:1164 #, python-format msgid "Disables the %s key." msgstr "Désactive la touche %s." -#: lib/logitech_receiver/settings_templates.py:809 -#: lib/logitech_receiver/settings_templates.py:860 +#: lib/logitech_receiver/settings_templates.py:1177 +#: lib/logitech_receiver/settings_templates.py:1234 msgid "Set OS" msgstr "Définir le système d'exploitation" -#: lib/logitech_receiver/settings_templates.py:810 -#: lib/logitech_receiver/settings_templates.py:861 +#: lib/logitech_receiver/settings_templates.py:1178 +#: lib/logitech_receiver/settings_templates.py:1235 msgid "Change keys to match OS." msgstr "Modifier les touches pour correspondre au système d'exploitation." -#: lib/logitech_receiver/settings_templates.py:873 +#: lib/logitech_receiver/settings_templates.py:1247 msgid "Change Host" msgstr "Changer d'hôte" -#: lib/logitech_receiver/settings_templates.py:874 +#: lib/logitech_receiver/settings_templates.py:1248 msgid "Switch connection to a different host" msgstr "Commuter la connexion vers un autre hôte" -#: lib/logitech_receiver/settings_templates.py:900 +#: lib/logitech_receiver/settings_templates.py:1272 msgid "Performs a left click." msgstr "Réalise un clic gauche." -#: lib/logitech_receiver/settings_templates.py:900 +#: lib/logitech_receiver/settings_templates.py:1272 msgid "Single tap" msgstr "Une tape simple" -#: lib/logitech_receiver/settings_templates.py:901 +#: lib/logitech_receiver/settings_templates.py:1273 msgid "Performs a right click." msgstr "Réalise un clic droit." -#: lib/logitech_receiver/settings_templates.py:901 +#: lib/logitech_receiver/settings_templates.py:1273 msgid "Single tap with two fingers" msgstr "Une tape simple avec deux doigts" -#: lib/logitech_receiver/settings_templates.py:902 +#: lib/logitech_receiver/settings_templates.py:1274 msgid "Single tap with three fingers" msgstr "Une tape simple avec trois doigts" -#: lib/logitech_receiver/settings_templates.py:906 +#: lib/logitech_receiver/settings_templates.py:1278 msgid "Double tap" msgstr "Une double tape" -#: lib/logitech_receiver/settings_templates.py:906 +#: lib/logitech_receiver/settings_templates.py:1278 msgid "Performs a double click." msgstr "Réalise un double clic." -#: lib/logitech_receiver/settings_templates.py:907 +#: lib/logitech_receiver/settings_templates.py:1279 msgid "Double tap with two fingers" msgstr "Une double tape avec deux doigts" -#: lib/logitech_receiver/settings_templates.py:908 +#: lib/logitech_receiver/settings_templates.py:1280 msgid "Double tap with three fingers" msgstr "Une double tape avec trois doigts" -#: lib/logitech_receiver/settings_templates.py:911 +#: lib/logitech_receiver/settings_templates.py:1283 msgid "Drags items by dragging the finger after double tapping." msgstr "Déplace des items en faisant glisser le doigt après la double tape." -#: lib/logitech_receiver/settings_templates.py:911 +#: lib/logitech_receiver/settings_templates.py:1283 msgid "Tap and drag" msgstr "Taper-glisser" -#: lib/logitech_receiver/settings_templates.py:913 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "Déplace des items en faisant glisser les doigts après la double tape." - -#: lib/logitech_receiver/settings_templates.py:913 +#: lib/logitech_receiver/settings_templates.py:1285 msgid "Tap and drag with two fingers" msgstr "Taper-glisser avec deux doigts" -#: lib/logitech_receiver/settings_templates.py:914 +#: lib/logitech_receiver/settings_templates.py:1286 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Déplace des items en faisant glisser les doigts après la double tape." + +#: lib/logitech_receiver/settings_templates.py:1288 msgid "Tap and drag with three fingers" msgstr "Taper-glisser avec trois doigts" -#: lib/logitech_receiver/settings_templates.py:917 +#: lib/logitech_receiver/settings_templates.py:1291 +msgid "Suppress tap and edge gestures" +msgstr "Supprimer les tapes et les gestes" + +#: lib/logitech_receiver/settings_templates.py:1292 msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." msgstr "" "Désactive les tapes et les gestes de bords (équivaut à presser " "Fn+ClicGauche)." -#: lib/logitech_receiver/settings_templates.py:917 -msgid "Suppress tap and edge gestures" -msgstr "Supprimer les tapes et les gestes" - -#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:1294 msgid "Scroll with one finger" msgstr "Défilement à un doigt" -#: lib/logitech_receiver/settings_templates.py:918 -#: lib/logitech_receiver/settings_templates.py:919 -#: lib/logitech_receiver/settings_templates.py:922 +#: lib/logitech_receiver/settings_templates.py:1294 +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 msgid "Scrolls." msgstr "Défilements." -#: lib/logitech_receiver/settings_templates.py:919 -#: lib/logitech_receiver/settings_templates.py:922 +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 msgid "Scroll with two fingers" msgstr "Défilement à deux doigts" -#: lib/logitech_receiver/settings_templates.py:920 +#: lib/logitech_receiver/settings_templates.py:1296 msgid "Scroll horizontally with two fingers" msgstr "Défilement horizontal à deux doigts" -#: lib/logitech_receiver/settings_templates.py:920 +#: lib/logitech_receiver/settings_templates.py:1296 msgid "Scrolls horizontally." msgstr "Défile horizontalement." -#: lib/logitech_receiver/settings_templates.py:921 +#: lib/logitech_receiver/settings_templates.py:1297 msgid "Scroll vertically with two fingers" msgstr "Défilement vertical à deux doigts" -#: lib/logitech_receiver/settings_templates.py:921 +#: lib/logitech_receiver/settings_templates.py:1297 msgid "Scrolls vertically." msgstr "Défile verticalement." -#: lib/logitech_receiver/settings_templates.py:923 +#: lib/logitech_receiver/settings_templates.py:1299 msgid "Inverts the scrolling direction." msgstr "Inverse la direction de défilement." -#: lib/logitech_receiver/settings_templates.py:923 +#: lib/logitech_receiver/settings_templates.py:1299 msgid "Natural scrolling" msgstr "Défilement naturel" -#: lib/logitech_receiver/settings_templates.py:924 +#: lib/logitech_receiver/settings_templates.py:1300 msgid "Enables the thumbwheel." msgstr "Active la molette de pouce." -#: lib/logitech_receiver/settings_templates.py:924 +#: lib/logitech_receiver/settings_templates.py:1300 msgid "Thumbwheel" msgstr "Molette de pouce" -#: lib/logitech_receiver/settings_templates.py:935 -#: lib/logitech_receiver/settings_templates.py:939 +#: lib/logitech_receiver/settings_templates.py:1311 +#: lib/logitech_receiver/settings_templates.py:1315 msgid "Swipe from the top edge" msgstr "Glissement depuis le bord supérieur" -#: lib/logitech_receiver/settings_templates.py:936 +#: lib/logitech_receiver/settings_templates.py:1312 msgid "Swipe from the left edge" msgstr "Glissement depuis le bord gauche" -#: lib/logitech_receiver/settings_templates.py:937 +#: lib/logitech_receiver/settings_templates.py:1313 msgid "Swipe from the right edge" msgstr "Glissement depuis le bord droit" -#: lib/logitech_receiver/settings_templates.py:938 +#: lib/logitech_receiver/settings_templates.py:1314 msgid "Swipe from the bottom edge" msgstr "Glissement depuis le bord inférieur" -#: lib/logitech_receiver/settings_templates.py:940 +#: lib/logitech_receiver/settings_templates.py:1316 msgid "Swipe two fingers from the left edge" msgstr "Glissement à deux doigts depuis le bord gauche" -#: lib/logitech_receiver/settings_templates.py:941 +#: lib/logitech_receiver/settings_templates.py:1317 msgid "Swipe two fingers from the right edge" msgstr "Glissement à deux doigts depuis le bord droit" -#: lib/logitech_receiver/settings_templates.py:942 +#: lib/logitech_receiver/settings_templates.py:1318 msgid "Swipe two fingers from the bottom edge" msgstr "Glissement à deux doigts depuis le bord inférieur" -#: lib/logitech_receiver/settings_templates.py:943 +#: lib/logitech_receiver/settings_templates.py:1319 msgid "Swipe two fingers from the top edge" msgstr "Glissement à deux doigts depuis le bord supérieur" -#: lib/logitech_receiver/settings_templates.py:944 -#: lib/logitech_receiver/settings_templates.py:948 +#: lib/logitech_receiver/settings_templates.py:1320 +#: lib/logitech_receiver/settings_templates.py:1324 msgid "Pinch to zoom out; spread to zoom in." msgstr "Pincer pour réduire le zoom; écarter pour agrandir le zoom." -#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:1320 msgid "Zoom with two fingers." msgstr "Zoom à deux doigts." -#: lib/logitech_receiver/settings_templates.py:945 +#: lib/logitech_receiver/settings_templates.py:1321 msgid "Pinch to zoom out." msgstr "Pincer pour réduire le zoom." -#: lib/logitech_receiver/settings_templates.py:946 +#: lib/logitech_receiver/settings_templates.py:1322 msgid "Spread to zoom in." msgstr "Écarter pour agrandir le zoom." -#: lib/logitech_receiver/settings_templates.py:947 +#: lib/logitech_receiver/settings_templates.py:1323 msgid "Zoom with three fingers." msgstr "Zoom à trois doigts." -#: lib/logitech_receiver/settings_templates.py:948 +#: lib/logitech_receiver/settings_templates.py:1324 msgid "Zoom with two fingers" msgstr "Zoom à deux doigts" -#: lib/logitech_receiver/settings_templates.py:966 +#: lib/logitech_receiver/settings_templates.py:1342 msgid "Pixel zone" msgstr "Zone de pixels" -#: lib/logitech_receiver/settings_templates.py:967 +#: lib/logitech_receiver/settings_templates.py:1343 msgid "Ratio zone" msgstr "Zone de rapport" -#: lib/logitech_receiver/settings_templates.py:968 +#: lib/logitech_receiver/settings_templates.py:1344 msgid "Scale factor" msgstr "Facteur d’échelle" -#: lib/logitech_receiver/settings_templates.py:968 +#: lib/logitech_receiver/settings_templates.py:1344 msgid "Sets the cursor speed." msgstr "Définit la vitesse du curseur." -#: lib/logitech_receiver/settings_templates.py:972 +#: lib/logitech_receiver/settings_templates.py:1348 msgid "Left" msgstr "Gauche" -#: lib/logitech_receiver/settings_templates.py:972 +#: lib/logitech_receiver/settings_templates.py:1348 msgid "Left-most coordinate." msgstr "Coordonnée la plus à gauche." -#: lib/logitech_receiver/settings_templates.py:973 +#: lib/logitech_receiver/settings_templates.py:1349 msgid "Bottom" msgstr "Bas" -#: lib/logitech_receiver/settings_templates.py:973 +#: lib/logitech_receiver/settings_templates.py:1349 msgid "Bottom coordinate." msgstr "Coordonnée du bas." -#: lib/logitech_receiver/settings_templates.py:974 +#: lib/logitech_receiver/settings_templates.py:1350 msgid "Width" msgstr "Largeur" -#: lib/logitech_receiver/settings_templates.py:974 +#: lib/logitech_receiver/settings_templates.py:1350 msgid "Width." msgstr "Largeur." -#: lib/logitech_receiver/settings_templates.py:975 +#: lib/logitech_receiver/settings_templates.py:1351 msgid "Height" msgstr "Hauteur" -#: lib/logitech_receiver/settings_templates.py:975 +#: lib/logitech_receiver/settings_templates.py:1351 msgid "Height." msgstr "Hauteur." -#: lib/logitech_receiver/settings_templates.py:976 +#: lib/logitech_receiver/settings_templates.py:1352 msgid "Cursor speed." msgstr "Vitesse du curseur." -#: lib/logitech_receiver/settings_templates.py:976 +#: lib/logitech_receiver/settings_templates.py:1352 msgid "Scale" msgstr "Échelle" -#: lib/logitech_receiver/settings_templates.py:982 +#: lib/logitech_receiver/settings_templates.py:1358 msgid "Gestures" msgstr "Gestes" -#: lib/logitech_receiver/settings_templates.py:983 +#: lib/logitech_receiver/settings_templates.py:1359 msgid "Tweak the mouse/touchpad behaviour." msgstr "Personnaliser le comportement de la souris ou du pavé tactile." -#: lib/logitech_receiver/settings_templates.py:1000 +#: lib/logitech_receiver/settings_templates.py:1375 msgid "Gestures Diversion" msgstr "Interception de la gestuelle" -#: lib/logitech_receiver/settings_templates.py:1001 +#: lib/logitech_receiver/settings_templates.py:1376 msgid "Divert mouse/touchpad gestures." msgstr "Interception de la gestuelle de la souris ou du pavé tactile." -#: lib/logitech_receiver/settings_templates.py:1018 +#: lib/logitech_receiver/settings_templates.py:1392 msgid "Gesture params" msgstr "Paramètres de la gestuelle" -#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1393 msgid "Change numerical parameters of a mouse/touchpad." msgstr "Modifier les paramètres numérique de la souris ou du pavé tactile." -#: lib/logitech_receiver/settings_templates.py:1044 +#: lib/logitech_receiver/settings_templates.py:1417 msgid "M-Key LEDs" msgstr "LEDs de touche M" -#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1419 msgid "Control the M-Key LEDs." msgstr "Contrôler les LEDs de touche M." -#: lib/logitech_receiver/settings_templates.py:1047 -#: lib/logitech_receiver/settings_templates.py:1077 +#: lib/logitech_receiver/settings_templates.py:1423 +#: lib/logitech_receiver/settings_templates.py:1454 msgid "May need G Keys diverted to be effective." msgstr "" "Peut nécessiter que les touches G soient interceptées pour être effectif." -#: lib/logitech_receiver/settings_templates.py:1053 +#: lib/logitech_receiver/settings_templates.py:1429 #, python-format msgid "Lights up the %s key." msgstr "Allume la touche %s." -#: lib/logitech_receiver/settings_templates.py:1074 +#: lib/logitech_receiver/settings_templates.py:1448 msgid "MR-Key LED" msgstr "LED de touche MR" -#: lib/logitech_receiver/settings_templates.py:1076 +#: lib/logitech_receiver/settings_templates.py:1450 msgid "Control the MR-Key LED." msgstr "Contrôler la LED de touche MR." -#: lib/logitech_receiver/settings_templates.py:1095 +#: lib/logitech_receiver/settings_templates.py:1471 msgid "Persistent Key/Button Mapping" msgstr "Mappage touche/bouton persistant" -#: lib/logitech_receiver/settings_templates.py:1097 +#: lib/logitech_receiver/settings_templates.py:1473 msgid "Permanently change the mapping for the key or button." msgstr "Modifiez de manière permanente le mappage de la touche ou du bouton." -#: lib/logitech_receiver/settings_templates.py:1098 +#: lib/logitech_receiver/settings_templates.py:1475 msgid "" "Changing important keys or buttons (such as for the left mouse button) can " "result in an unusable system." @@ -917,75 +1108,171 @@ msgstr "" "La modification de touches ou de boutons importants (comme le bouton gauche " "de la souris) pourrait rendre le système inutilisable." -#: lib/logitech_receiver/settings_templates.py:1157 +#: lib/logitech_receiver/settings_templates.py:1532 msgid "Sidetone" msgstr "Tonalité latérale" -#: lib/logitech_receiver/settings_templates.py:1158 +#: lib/logitech_receiver/settings_templates.py:1533 msgid "Set sidetone level." msgstr "Régler le niveau de tonalité latérale." -#: lib/logitech_receiver/settings_templates.py:1167 +#: lib/logitech_receiver/settings_templates.py:1542 msgid "Equalizer" msgstr "Égaliseur" -#: lib/logitech_receiver/settings_templates.py:1168 +#: lib/logitech_receiver/settings_templates.py:1543 msgid "Set equalizer levels." msgstr "Définir les niveaux de l'égaliseur." -#: lib/logitech_receiver/settings_templates.py:1191 +#: lib/logitech_receiver/settings_templates.py:1565 msgid "Hz" msgstr "Hz" -#: lib/logitech_receiver/settings_templates.py:1197 +#: lib/logitech_receiver/settings_templates.py:1571 msgid "Power Management" msgstr "Gestion de l'alimentation" -#: lib/logitech_receiver/settings_templates.py:1198 +#: lib/logitech_receiver/settings_templates.py:1572 msgid "Power off in minutes (0 for never)." msgstr "Éteindre en quelques minutes (0 pour jamais)." -#: lib/logitech_receiver/status.py:114 -msgid "No paired devices." -msgstr "Aucun périphérique jumelé." +#: lib/logitech_receiver/settings_templates.py:1584 +msgid "Brightness Control" +msgstr "Contrôle de la luminosité" -#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 -#, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s périphérique jumelé." -msgstr[1] "%(count)s périphériques jumelés." +#: lib/logitech_receiver/settings_templates.py:1585 +msgid "Control overall brightness" +msgstr "Contrôle de la luminosité globale" -#: lib/logitech_receiver/status.py:170 -#, python-format -msgid "Battery: %(level)s" -msgstr "Batterie : %(level)s" +#: lib/logitech_receiver/settings_templates.py:1628 +#: lib/logitech_receiver/settings_templates.py:1681 +msgid "LED Control" +msgstr "Contrôle de LED" -#: lib/logitech_receiver/status.py:172 -#, python-format -msgid "Battery: %(percent)d%%" -msgstr "Batterie : %(percent)d%%" +#: lib/logitech_receiver/settings_templates.py:1629 +#: lib/logitech_receiver/settings_templates.py:1682 +msgid "Switch control of LED zones between device and Solaar" +msgstr "Basculer le contrôle des zones LED entre le périphérique et Solaar" -#: lib/logitech_receiver/status.py:184 -#, python-format -msgid "Lighting: %(level)s lux" -msgstr "Éclairage : %(level)s lux" +#: lib/logitech_receiver/settings_templates.py:1644 +#: lib/logitech_receiver/settings_templates.py:1692 +msgid "LED Zone Effects" +msgstr "Effet de zone LED" -#: lib/logitech_receiver/status.py:239 -#, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "Batterie : %(level)s (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "LED Control needs to be set to Solaar to be effective." +msgstr "Le contrôle de LED doit être defini sur Solaar pour être effectif." -#: lib/logitech_receiver/status.py:241 -#, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "Batterie : %(percent)d%% (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "Set effect for LED Zone" +msgstr "Définir l'effet pour la zone LED" -#: lib/solaar/ui/__init__.py:52 +#: lib/logitech_receiver/settings_templates.py:1647 +msgid "Color" +msgstr "Couleur" + +#: lib/logitech_receiver/settings_templates.py:1648 +msgid "Speed" +msgstr "Vitesse" + +#: lib/logitech_receiver/settings_templates.py:1649 +msgid "Period" +msgstr "Période" + +#: lib/logitech_receiver/settings_templates.py:1650 +msgid "Intensity" +msgstr "Intensité" + +#: lib/logitech_receiver/settings_templates.py:1651 +msgid "Ramp" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1666 +msgid "LEDs" +msgstr "LEDs" + +#: lib/logitech_receiver/settings_templates.py:1703 +msgid "Per-key Lighting" +msgstr "Éclairage par touche" + +#: lib/logitech_receiver/settings_templates.py:1704 +msgid "Control per-key lighting." +msgstr "Contrôler l’éclairage par touche." + +#: lib/logitech_receiver/settings_templates.py:1786 +msgid "Force Sensing Buttons" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1787 +msgid "Change the force required to activate button." +msgstr "Modifier la force nécessaire pour activer le bouton." + +#: lib/logitech_receiver/settings_templates.py:1804 +msgid "Force Sensing Button" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1814 +msgid "Haptic Feeback Level" +msgstr "Niveau de retour haptique" + +#: lib/logitech_receiver/settings_templates.py:1815 +msgid "Change power of haptic feedback. (Zero to turn off.)" +msgstr "Modifiez l'intensité du retour haptique. (Zéro pour éteindre.)" + +#: lib/logitech_receiver/settings_templates.py:1857 +msgid "Play Haptic Waveform" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1858 +msgid "Tell device to play a haptic waveform." +msgstr "" + +#: lib/solaar/ui/__init__.py:120 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" +"Un autre processus Solaar est déjà en cours d'exécution, il suffit donc " +"d'afficher sa fenêtre" + +#: lib/solaar/ui/about/model.py:36 +msgid "" +"Manages Logitech receivers,\n" +"keyboards, mice, and tablets." +msgstr "" +"Gère les récepteurs, claviers,\n" +"souris et tablettes Logitech." + +#: lib/solaar/ui/about/model.py:63 +msgid "Additional Programming" +msgstr "Programmation supplémentaire" + +#: lib/solaar/ui/about/model.py:64 +msgid "GUI design" +msgstr "Interface graphique" + +#: lib/solaar/ui/about/model.py:66 +msgid "Testing" +msgstr "Testeur" + +#: lib/solaar/ui/about/model.py:74 +msgid "Logitech documentation" +msgstr "Documentațion Logitech" + +#: lib/solaar/ui/action.py:88 lib/solaar/ui/action.py:92 +#: lib/solaar/ui/window.py:208 +msgid "Unpair" +msgstr "Déconnecter" + +#: lib/solaar/ui/action.py:91 lib/solaar/ui/diversion_rules.py:141 +msgid "Cancel" +msgstr "Annuler" + +#: lib/solaar/ui/common.py:42 msgid "Permissions error" msgstr "Erreur de permissions" -#: lib/solaar/ui/__init__.py:54 +#: lib/solaar/ui/common.py:44 #, python-format msgid "" "Found a Logitech receiver or device (%s), but did not have permission to " @@ -994,7 +1281,7 @@ msgstr "" "Un récepteur ou un périphérique Logitech (%s) a été trouvé, mais sans " "l'autorisation de l'ouvrir." -#: lib/solaar/ui/__init__.py:55 +#: lib/solaar/ui/common.py:46 msgid "" "If you've just installed Solaar, try disconnecting the receiver or device " "and then reconnecting it." @@ -1002,11 +1289,11 @@ msgstr "" "Si vous venez d'installer Solaar, essayez de déconnecter le récepteur ou le " "périphérique, puis de le reconnecter." -#: lib/solaar/ui/__init__.py:58 +#: lib/solaar/ui/common.py:49 msgid "Cannot connect to device error" msgstr "Impossible de se connecter au périphérique" -#: lib/solaar/ui/__init__.py:60 +#: lib/solaar/ui/common.py:51 #, python-format msgid "" "Found a Logitech receiver or device at %s, but encountered an error " @@ -1015,7 +1302,7 @@ msgstr "" "Un récepteur ou un périphérique Logitech a été trouvé à %s, mais une erreur " "s'est produite lors de la connexion." -#: lib/solaar/ui/__init__.py:61 +#: lib/solaar/ui/common.py:53 msgid "" "Try disconnecting the device and then reconnecting it or turning it off and " "then on." @@ -1023,686 +1310,428 @@ msgstr "" "Essayez de déconnecter le périphérique, puis de le reconnecter ou de " "l'éteindre puis de le rallumer." -#: lib/solaar/ui/__init__.py:64 +#: lib/solaar/ui/common.py:56 msgid "Unpairing failed" msgstr "La déconnexion a échouée" -#: lib/solaar/ui/__init__.py:66 +#: lib/solaar/ui/common.py:58 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Impossible de dissocier %{device} de %{receiver}." -#: lib/solaar/ui/__init__.py:67 +#: lib/solaar/ui/common.py:63 msgid "The receiver returned an error, with no further details." msgstr "Le récepteur a retourné une erreur, sans plus de détails." -#: lib/solaar/ui/__init__.py:177 -msgid "Another Solaar process is already running so just expose its window" -msgstr "" -"Un autre processus Solaar est déjà en cours d'exécution, il suffit donc " -"d'afficher sa fenêtre" - -#: lib/solaar/ui/about.py:36 -msgid "" -"Manages Logitech receivers,\n" -"keyboards, mice, and tablets." -msgstr "" -"Gère les récepteurs, claviers,\n" -"souris et tablettes Logitech." - -#: lib/solaar/ui/about.py:44 -msgid "Additional Programming" -msgstr "Programmation supplémentaire" - -#: lib/solaar/ui/about.py:45 -msgid "GUI design" -msgstr "Interface graphique" - -#: lib/solaar/ui/about.py:47 -msgid "Testing" -msgstr "Testeur" - -#: lib/solaar/ui/about.py:54 -msgid "Logitech documentation" -msgstr "Documentațion Logitech" - -#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 -#: lib/solaar/ui/window.py:197 -msgid "Unpair" -msgstr "Déconnecter" - -#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 -msgid "Cancel" -msgstr "Annuler" - -#: lib/solaar/ui/config_panel.py:212 +#: lib/solaar/ui/config_panel.py:245 msgid "Complete - ENTER to change" msgstr "Complet - ENTRER pour modifier" -#: lib/solaar/ui/config_panel.py:212 +#: lib/solaar/ui/config_panel.py:245 msgid "Incomplete" msgstr "Incomplet" -#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 +#: lib/solaar/ui/config_panel.py:491 lib/solaar/ui/config_panel.py:543 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "valeur %d" msgstr[1] "valeurs %d" -#: lib/solaar/ui/config_panel.py:518 +#: lib/solaar/ui/config_panel.py:642 msgid "Changes allowed" msgstr "Modifications autorisées" -#: lib/solaar/ui/config_panel.py:519 +#: lib/solaar/ui/config_panel.py:643 msgid "No changes allowed" msgstr "Aucune modification autorisée" -#: lib/solaar/ui/config_panel.py:520 +#: lib/solaar/ui/config_panel.py:644 msgid "Ignore this setting" msgstr "Ignorer ce réglage" -#: lib/solaar/ui/config_panel.py:565 +#: lib/solaar/ui/config_panel.py:690 msgid "Working" msgstr "En fonctionnement" -#: lib/solaar/ui/config_panel.py:568 +#: lib/solaar/ui/config_panel.py:693 msgid "Read/write operation failed." msgstr "Les opérations de lecture/écriture ont échouée." -#: lib/solaar/ui/diversion_rules.py:65 +#: lib/solaar/ui/desktop_notifications.py:112 +msgid "unspecified reason" +msgstr "raison non précisée" + +#: lib/solaar/ui/diversion_rules.py:103 msgid "Built-in rules" msgstr "Règles pré-définies" -#: lib/solaar/ui/diversion_rules.py:65 +#: lib/solaar/ui/diversion_rules.py:103 msgid "User-defined rules" msgstr "Règles définies par l'utilisateur" -#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 +#: lib/solaar/ui/diversion_rules.py:105 lib/solaar/ui/diversion_rules.py:1126 msgid "Rule" msgstr "Règle" -#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 -#: lib/solaar/ui/diversion_rules.py:636 +#: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:382 +#: lib/solaar/ui/diversion_rules.py:509 msgid "Sub-rule" msgstr "Sous-règle" -#: lib/solaar/ui/diversion_rules.py:70 +#: lib/solaar/ui/diversion_rules.py:108 msgid "[empty]" msgstr "[vide]" -#: lib/solaar/ui/diversion_rules.py:94 -msgid "Solaar Rule Editor" -msgstr "Éditeur de règle Solaar" - -#: lib/solaar/ui/diversion_rules.py:141 +#: lib/solaar/ui/diversion_rules.py:132 msgid "Make changes permanent?" msgstr "Rendre les modifications permanentes ?" -#: lib/solaar/ui/diversion_rules.py:146 +#: lib/solaar/ui/diversion_rules.py:137 msgid "Yes" msgstr "Oui" -#: lib/solaar/ui/diversion_rules.py:148 +#: lib/solaar/ui/diversion_rules.py:139 msgid "No" msgstr "Non" -#: lib/solaar/ui/diversion_rules.py:153 +#: lib/solaar/ui/diversion_rules.py:144 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "" "Si vous choisissez Non, les changements seront perdus à la fermeture de " "Solaar." -#: lib/solaar/ui/diversion_rules.py:201 -msgid "Save changes" -msgstr "Sauvegarder les modifications" - -#: lib/solaar/ui/diversion_rules.py:206 -msgid "Discard changes" -msgstr "Annuler les modifications" - -#: lib/solaar/ui/diversion_rules.py:372 -msgid "Insert here" -msgstr "Insérer ici" - -#: lib/solaar/ui/diversion_rules.py:374 -msgid "Insert above" -msgstr "Insérer ci-dessus" - -#: lib/solaar/ui/diversion_rules.py:376 -msgid "Insert below" -msgstr "Insérer ci-dessous" - -#: lib/solaar/ui/diversion_rules.py:382 -msgid "Insert new rule here" -msgstr "Insérer une nouvelle règle ici" - -#: lib/solaar/ui/diversion_rules.py:384 -msgid "Insert new rule above" -msgstr "Insérer une nouvelle règle ci-dessus" - -#: lib/solaar/ui/diversion_rules.py:386 -msgid "Insert new rule below" -msgstr "Insérer une nouvelle règle ci-dessous" - -#: lib/solaar/ui/diversion_rules.py:427 +#: lib/solaar/ui/diversion_rules.py:273 msgid "Paste here" msgstr "Coller ici" -#: lib/solaar/ui/diversion_rules.py:429 +#: lib/solaar/ui/diversion_rules.py:275 msgid "Paste above" msgstr "Coller ci-dessus" -#: lib/solaar/ui/diversion_rules.py:431 +#: lib/solaar/ui/diversion_rules.py:277 msgid "Paste below" msgstr "Coller ci-dessous" -#: lib/solaar/ui/diversion_rules.py:437 +#: lib/solaar/ui/diversion_rules.py:283 msgid "Paste rule here" msgstr "Coller la règle ici" -#: lib/solaar/ui/diversion_rules.py:439 +#: lib/solaar/ui/diversion_rules.py:285 msgid "Paste rule above" msgstr "Coller la règle ci-dessus" -#: lib/solaar/ui/diversion_rules.py:441 +#: lib/solaar/ui/diversion_rules.py:287 msgid "Paste rule below" msgstr "Coller la règle ci-dessous" -#: lib/solaar/ui/diversion_rules.py:445 +#: lib/solaar/ui/diversion_rules.py:291 msgid "Paste rule" msgstr "Coller la règle" -#: lib/solaar/ui/diversion_rules.py:474 +#: lib/solaar/ui/diversion_rules.py:306 +msgid "Insert here" +msgstr "Insérer ici" + +#: lib/solaar/ui/diversion_rules.py:308 +msgid "Insert above" +msgstr "Insérer ci-dessus" + +#: lib/solaar/ui/diversion_rules.py:310 +msgid "Insert below" +msgstr "Insérer ci-dessous" + +#: lib/solaar/ui/diversion_rules.py:316 +msgid "Insert new rule here" +msgstr "Insérer une nouvelle règle ici" + +#: lib/solaar/ui/diversion_rules.py:318 +msgid "Insert new rule above" +msgstr "Insérer une nouvelle règle ci-dessus" + +#: lib/solaar/ui/diversion_rules.py:320 +msgid "Insert new rule below" +msgstr "Insérer une nouvelle règle ci-dessous" + +#: lib/solaar/ui/diversion_rules.py:347 msgid "Flatten" msgstr "Aplanir" -#: lib/solaar/ui/diversion_rules.py:507 +#: lib/solaar/ui/diversion_rules.py:380 msgid "Insert" msgstr "Insérer" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 -#: lib/solaar/ui/diversion_rules.py:1126 +#: lib/solaar/ui/diversion_rules.py:383 lib/solaar/ui/diversion_rules.py:511 +#: lib/solaar/ui/diversion_rules.py:1158 msgid "Or" msgstr "Ou" -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 -#: lib/solaar/ui/diversion_rules.py:1111 +#: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:510 +#: lib/solaar/ui/diversion_rules.py:1144 msgid "And" msgstr "Et" -#: lib/solaar/ui/diversion_rules.py:513 +#: lib/solaar/ui/diversion_rules.py:386 msgid "Condition" msgstr "Condition" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 +#: lib/solaar/ui/diversion_rules.py:388 lib/solaar/ui/rule_conditions.py:154 msgid "Feature" msgstr "Fonctionnalité" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 +#: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:188 msgid "Report" msgstr "Rapport" -#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 +#: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:69 msgid "Process" msgstr "Processus" -#: lib/solaar/ui/diversion_rules.py:518 +#: lib/solaar/ui/diversion_rules.py:391 msgid "Mouse process" msgstr "Processus de la souris" -#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 +#: lib/solaar/ui/diversion_rules.py:392 lib/solaar/ui/rule_conditions.py:225 msgid "Modifiers" msgstr "Modificateurs" -#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 +#: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:277 msgid "Key" msgstr "Touche" -#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 +#: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:318 msgid "KeyIsDown" msgstr "" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 +#: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/diversion_rules.py:1432 msgid "Active" msgstr "Actif" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2218 -#: lib/solaar/ui/diversion_rules.py:2270 lib/solaar/ui/diversion_rules.py:2323 +#: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1390 +#: lib/solaar/ui/diversion_rules.py:1441 lib/solaar/ui/diversion_rules.py:1487 msgid "Device" msgstr "Périphérique" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +#: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1467 msgid "Host" msgstr "Hôte" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 +#: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1506 msgid "Setting" msgstr "Réglage" -#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 -#: lib/solaar/ui/diversion_rules.py:1526 +#: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/rule_conditions.py:333 +#: lib/solaar/ui/rule_conditions.py:382 msgid "Test" msgstr "Test" -#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 +#: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:507 msgid "Test bytes" msgstr "Octets de test" -#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 +#: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:608 msgid "Mouse Gesture" msgstr "Geste à la souris" -#: lib/solaar/ui/diversion_rules.py:532 +#: lib/solaar/ui/diversion_rules.py:405 msgid "Action" msgstr "Action" -#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 +#: lib/solaar/ui/diversion_rules.py:407 lib/solaar/ui/rule_actions.py:138 msgid "Key press" msgstr "Appui de touche" -#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 +#: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:191 msgid "Mouse scroll" msgstr "Défilement de la souris" -#: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 +#: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:256 msgid "Mouse click" msgstr "Clic de la souris" -#: lib/solaar/ui/diversion_rules.py:537 +#: lib/solaar/ui/diversion_rules.py:410 msgid "Set" msgstr "Définir" -#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 +#: lib/solaar/ui/diversion_rules.py:411 lib/solaar/ui/rule_actions.py:328 msgid "Execute" msgstr "Exécuter" -#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 +#: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/diversion_rules.py:1189 msgid "Later" msgstr "Plus tard" -#: lib/solaar/ui/diversion_rules.py:568 +#: lib/solaar/ui/diversion_rules.py:441 msgid "Insert new rule" msgstr "Insérer nouvelle règle" -#: lib/solaar/ui/diversion_rules.py:588 lib/solaar/ui/diversion_rules.py:1686 -#: lib/solaar/ui/diversion_rules.py:1790 lib/solaar/ui/diversion_rules.py:1989 +#: lib/solaar/ui/diversion_rules.py:461 lib/solaar/ui/rule_actions.py:82 +#: lib/solaar/ui/rule_actions.py:287 lib/solaar/ui/rule_conditions.py:556 msgid "Delete" msgstr "Effacer" -#: lib/solaar/ui/diversion_rules.py:610 +#: lib/solaar/ui/diversion_rules.py:483 msgid "Negate" msgstr "Nier" -#: lib/solaar/ui/diversion_rules.py:634 +#: lib/solaar/ui/diversion_rules.py:507 msgid "Wrap with" msgstr "Entoure" -#: lib/solaar/ui/diversion_rules.py:656 +#: lib/solaar/ui/diversion_rules.py:537 msgid "Cut" msgstr "Couper" -#: lib/solaar/ui/diversion_rules.py:671 +#: lib/solaar/ui/diversion_rules.py:553 msgid "Paste" msgstr "Coller" -#: lib/solaar/ui/diversion_rules.py:683 +#: lib/solaar/ui/diversion_rules.py:559 msgid "Copy" msgstr "Copier" -#: lib/solaar/ui/diversion_rules.py:1063 +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Solaar Rule Editor" +msgstr "Éditeur de règle Solaar" + +#: lib/solaar/ui/diversion_rules.py:668 +msgid "Save changes" +msgstr "Sauvegarder les modifications" + +#: lib/solaar/ui/diversion_rules.py:673 +msgid "Discard changes" +msgstr "Annuler les modifications" + +#: lib/solaar/ui/diversion_rules.py:1104 msgid "This editor does not support the selected rule component yet." msgstr "Cet éditeur ne gère pas encore le composant de règle sélectionné." -#: lib/solaar/ui/diversion_rules.py:1138 -msgid "Number of seconds to delay." -msgstr "Nombre de secondes de report." +#: lib/solaar/ui/diversion_rules.py:1169 +msgid "" +"Number of seconds to delay. Delay between 0 and 1 is done with higher " +"precision." +msgstr "" +"Nombre de secondes de report. Le délai entre 0 et 1 est effectué avec une " +"plus grande précision." -#: lib/solaar/ui/diversion_rules.py:1177 +#: lib/solaar/ui/diversion_rules.py:1207 msgid "Not" msgstr "Non" -#: lib/solaar/ui/diversion_rules.py:1187 -msgid "X11 active process. For use in X11 only." -msgstr "Processus actif X11. Pour une utilisation dans X11 uniquement." - -#: lib/solaar/ui/diversion_rules.py:1218 -msgid "X11 mouse process. For use in X11 only." -msgstr "Processus de la souris X11. Pour une utilisation dans X11 uniquement." - -#: lib/solaar/ui/diversion_rules.py:1235 -msgid "MouseProcess" -msgstr "Processus de la souris" - -#: lib/solaar/ui/diversion_rules.py:1260 -msgid "Feature name of notification triggering rule processing." -msgstr "" -"Nom de la fonction de notification déclenchant le traitement de la règle." - -#: lib/solaar/ui/diversion_rules.py:1308 -msgid "Report number of notification triggering rule processing." -msgstr "" -"Indique le nombre de notifications déclenchant le traitement de la règle." - -#: lib/solaar/ui/diversion_rules.py:1342 -msgid "Active keyboard modifiers. Not always available in Wayland." -msgstr "Modificateurs de clavier actifs. Pas toujours disponible sous Wayland." - -#: lib/solaar/ui/diversion_rules.py:1383 -msgid "" -"Diverted key or button depressed or released.\n" -"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " -"buttons." -msgstr "" -"Interception de touche ou bouton appuyé ou relâché.\n" -"Utiliser l'interception de touche/bouton et les paramètres Interception des " -"touches G pour intercepter les touches et les boutons." - -#: lib/solaar/ui/diversion_rules.py:1392 -msgid "Key down" -msgstr "Enfoncement de touche" - -#: lib/solaar/ui/diversion_rules.py:1395 -msgid "Key up" -msgstr "Relâchement de touche" - -#: lib/solaar/ui/diversion_rules.py:1436 -msgid "" -"Diverted key or button is currently down.\n" -"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " -"buttons." -msgstr "" -"La touche/le bouton intercepté est actuellement enfoncé.\n" -"Utiliser l'interception de touche/bouton et les paramètres Interception des " -"touches G pour intercepter les touches et les boutons." - -#: lib/solaar/ui/diversion_rules.py:1475 -msgid "Test condition on notification triggering rule processing." -msgstr "" -"Condition de test déclenchant le traitement de la règle en cas de " -"notification." - -#: lib/solaar/ui/diversion_rules.py:1479 -msgid "Parameter" -msgstr "Paramètre" - -#: lib/solaar/ui/diversion_rules.py:1542 -msgid "begin (inclusive)" -msgstr "début (inclusif)" - -#: lib/solaar/ui/diversion_rules.py:1543 -msgid "end (exclusive)" -msgstr "fin (exclusif)" - -#: lib/solaar/ui/diversion_rules.py:1552 -msgid "range" -msgstr "plage" - -#: lib/solaar/ui/diversion_rules.py:1554 -msgid "minimum" -msgstr "minimum" - -#: lib/solaar/ui/diversion_rules.py:1555 -msgid "maximum" -msgstr "maximum" - -#: lib/solaar/ui/diversion_rules.py:1557 -#, python-format -msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" -msgstr "octets %(0)d à %(1)d, allant de %(2)d à %(3)d" - -#: lib/solaar/ui/diversion_rules.py:1562 -msgid "mask" -msgstr "masque" - -#: lib/solaar/ui/diversion_rules.py:1563 -#, python-format -msgid "bytes %(0)d to %(1)d, mask %(2)d" -msgstr "octets %(0)d à %(1)d, masque %(2)d" - -#: lib/solaar/ui/diversion_rules.py:1573 -msgid "" -"Bit or range test on bytes in notification message triggering rule " -"processing." -msgstr "" -"Bit ou plage d'octets dans un message de notification déclenchant le " -"traitement d'une règle." - -#: lib/solaar/ui/diversion_rules.py:1583 -msgid "type" -msgstr "type" - -#: lib/solaar/ui/diversion_rules.py:1666 -msgid "" -"Mouse gesture with optional initiating button followed by zero or more mouse " -"movements." -msgstr "" -"Geste de la souris avec bouton d'initialisation optionnel suivi ou non par " -"des déplacements de souris." - -#: lib/solaar/ui/diversion_rules.py:1671 -msgid "Add movement" -msgstr "Ajouter un mouvement" - -#: lib/solaar/ui/diversion_rules.py:1764 -msgid "" -"Simulate a chorded key click or depress or release.\n" -"On Wayland requires write access to /dev/uinput." -msgstr "" -"Simule un clic combiné ou une pression ou un relâchement.\n" -"Sous Wayland cela nécessite un accès en écriture au périphérique /dev/uinput." - -#: lib/solaar/ui/diversion_rules.py:1769 -msgid "Add key" -msgstr "Ajouter une touche" - -#: lib/solaar/ui/diversion_rules.py:1772 -msgid "Click" -msgstr "Clic" - -#: lib/solaar/ui/diversion_rules.py:1775 -msgid "Depress" -msgstr "Appuyer" - -#: lib/solaar/ui/diversion_rules.py:1778 -msgid "Release" -msgstr "Relâcher" - -#: lib/solaar/ui/diversion_rules.py:1861 -msgid "" -"Simulate a mouse scroll.\n" -"On Wayland requires write access to /dev/uinput." -msgstr "" -"Simule un défilement de souris.\n" -"Sous Wayland cela nécessite un accès en écriture au périphérique /dev/uinput." - -#: lib/solaar/ui/diversion_rules.py:1918 -msgid "" -"Simulate a mouse click.\n" -"On Wayland requires write access to /dev/uinput." -msgstr "" -"Simule un clic de souris.\n" -"Sous Wayland cela nécessite un accès en écriture au périphérique /dev/uinput." - -#: lib/solaar/ui/diversion_rules.py:1921 -msgid "Button" -msgstr "Bouton" - -#: lib/solaar/ui/diversion_rules.py:1922 -msgid "Count and Action" -msgstr "Nombre et action" - -#: lib/solaar/ui/diversion_rules.py:1972 -msgid "Execute a command with arguments." -msgstr "Exécuter une commande avec paramètres." - -#: lib/solaar/ui/diversion_rules.py:1975 -msgid "Add argument" -msgstr "Ajouter un paramètre" - -#: lib/solaar/ui/diversion_rules.py:2050 +#: lib/solaar/ui/diversion_rules.py:1238 msgid "Toggle" msgstr "Basculer" -#: lib/solaar/ui/diversion_rules.py:2050 +#: lib/solaar/ui/diversion_rules.py:1239 msgid "True" msgstr "Vrai" -#: lib/solaar/ui/diversion_rules.py:2051 +#: lib/solaar/ui/diversion_rules.py:1240 msgid "False" msgstr "Faux" -#: lib/solaar/ui/diversion_rules.py:2065 +#: lib/solaar/ui/diversion_rules.py:1253 msgid "Unsupported setting" msgstr "Réglage non pris en charge" -#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2242 -#: lib/solaar/ui/diversion_rules.py:2328 lib/solaar/ui/diversion_rules.py:2570 -#: lib/solaar/ui/diversion_rules.py:2588 +#: lib/solaar/ui/diversion_rules.py:1396 lib/solaar/ui/diversion_rules.py:1415 +#: lib/solaar/ui/diversion_rules.py:1493 lib/solaar/ui/diversion_rules.py:1748 +#: lib/solaar/ui/diversion_rules.py:1766 msgid "Originating device" msgstr "Périphérique d'origine" -#: lib/solaar/ui/diversion_rules.py:2256 +#: lib/solaar/ui/diversion_rules.py:1428 msgid "Device is active and its settings can be changed." msgstr "Le périphérique est actif et ses paramètres peuvent être modifiés." -#: lib/solaar/ui/diversion_rules.py:2266 +#: lib/solaar/ui/diversion_rules.py:1437 msgid "Device that originated the current notification." msgstr "Périphérique à l’origine de la notification actuelle." -#: lib/solaar/ui/diversion_rules.py:2280 +#: lib/solaar/ui/diversion_rules.py:1450 msgid "Name of host computer." msgstr "Nom de l'ordinateur hôte." -#: lib/solaar/ui/diversion_rules.py:2347 +#: lib/solaar/ui/diversion_rules.py:1520 msgid "Value" msgstr "Valeur" -#: lib/solaar/ui/diversion_rules.py:2355 +#: lib/solaar/ui/diversion_rules.py:1529 msgid "Item" msgstr "Item" -#: lib/solaar/ui/diversion_rules.py:2630 +#: lib/solaar/ui/diversion_rules.py:1808 msgid "Change setting on device" msgstr "Modifier le réglage sur le périphérique" -#: lib/solaar/ui/diversion_rules.py:2647 +#: lib/solaar/ui/diversion_rules.py:1824 msgid "Setting on device" msgstr "Réglage sur le périphérique" -#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:320 -#: lib/solaar/ui/tray.py:325 lib/solaar/ui/window.py:739 -msgid "offline" -msgstr "non connecté" - -#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 -#: lib/solaar/ui/pair_window.py:288 +#: lib/solaar/ui/pair_window.py:44 lib/solaar/ui/pair_window.py:167 #, python-format msgid "%(receiver_name)s: pair new device" msgstr "%(receiver_name)s : jumele le nouveau périphérique" -#: lib/solaar/ui/pair_window.py:123 -#, python-format -msgid "Enter passcode on %(name)s." -msgstr "Entrer le mot de passe sur %(name)s." - -#: lib/solaar/ui/pair_window.py:126 -#, python-format -msgid "Type %(passcode)s and then press the enter key." -msgstr "Taper %(passcode)s puis appuyez sur la touche Entrée." - -#: lib/solaar/ui/pair_window.py:129 -msgid "left" -msgstr "gauche" - -#: lib/solaar/ui/pair_window.py:129 -msgid "right" -msgstr "droit" - -#: lib/solaar/ui/pair_window.py:131 -#, python-format -msgid "" -"Press %(code)s\n" -"and then press left and right buttons simultaneously." -msgstr "" -"Appuyez %(code)s\n" -"puis appuyez simultanément sur les boutons gauche et droit." - -#: lib/solaar/ui/pair_window.py:188 -msgid "Pairing failed" -msgstr "Le jumelage a échoué" - -#: lib/solaar/ui/pair_window.py:190 -msgid "Make sure your device is within range, and has a decent battery charge." -msgstr "" -"Assurez-vous que votre périphérique soit à portée, et que sa batterie soit " -"suffisamment chargée." - -#: lib/solaar/ui/pair_window.py:192 -msgid "A new device was detected, but it is not compatible with this receiver." -msgstr "" -"Un nouveau périphérique a été détecté, mais il n'est pas compatible avec ce " -"récepteur." - -#: lib/solaar/ui/pair_window.py:194 -msgid "More paired devices than receiver can support." -msgstr "" -"Il y a plus de périphériques jumelés que le récepteur ne peut en supporter." - -#: lib/solaar/ui/pair_window.py:196 -msgid "No further details are available about the error." -msgstr "Aucun autre détail n'est disponible à propos de l'erreur." - -#: lib/solaar/ui/pair_window.py:210 -msgid "Found a new device:" -msgstr "Nouveau périphérique disponible :" - -#: lib/solaar/ui/pair_window.py:235 -msgid "The wireless link is not encrypted" -msgstr "La connexion sans fil n'est pas chiffrée" - -#: lib/solaar/ui/pair_window.py:264 -msgid "Unifying receivers are only compatible with Unifying devices." -msgstr "" -"Les récepteurs Unifying ne sont compatibles qu'avec des périphériques " -"Unifying." - -#: lib/solaar/ui/pair_window.py:266 +#: lib/solaar/ui/pair_window.py:46 msgid "Bolt receivers are only compatible with Bolt devices." msgstr "" "Les récepteurs Bolt ne sont compatibles qu'avec des périphériques Bolt." -#: lib/solaar/ui/pair_window.py:268 -msgid "Other receivers are only compatible with a few devices." -msgstr "" -"Les autres récepteurs sont uniquement compatibles avec quelques " -"périphériques." - -#: lib/solaar/ui/pair_window.py:270 -msgid "The device must not be paired with a nearby powered-on receiver." -msgstr "" -"Le périphérique doit être jumelé avec un récepteur de proximité allumé." - -#: lib/solaar/ui/pair_window.py:274 +#: lib/solaar/ui/pair_window.py:48 msgid "Press a pairing button or key until the pairing light flashes quickly." msgstr "" "Appuyez sur un bouton ou une touche d'appairage jusqu'à ce que le voyant " "clignote rapidement." -#: lib/solaar/ui/pair_window.py:276 -msgid "You may have to first turn the device off and on again." -msgstr "Vous devriez tout d' abord éteindre le périphérique et le rallumer." +#: lib/solaar/ui/pair_window.py:51 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "" +"Les récepteurs Unifying ne sont compatibles qu'avec des périphériques " +"Unifying." -#: lib/solaar/ui/pair_window.py:278 -msgid "Turn on the device you want to pair." -msgstr "Allumez le périphérique que vous souhaitez jumeler." +#: lib/solaar/ui/pair_window.py:53 +msgid "Other receivers are only compatible with a few devices." +msgstr "" +"Les autres récepteurs sont uniquement compatibles avec quelques " +"périphériques." -#: lib/solaar/ui/pair_window.py:280 +#: lib/solaar/ui/pair_window.py:55 +msgid "For most devices, turn on the device you want to pair." +msgstr "" +"Pour la plupart des périphériques, allumez le périphérique que vous " +"souhaitez jumeler." + +#: lib/solaar/ui/pair_window.py:56 msgid "If the device is already turned on, turn it off and on again." msgstr "" "Si le périphérique est déjà allumé, éteignez-le et rallumez-le à nouveau." -#: lib/solaar/ui/pair_window.py:283 +#: lib/solaar/ui/pair_window.py:58 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" +"Le périphérique doit être jumelé avec un récepteur de proximité allumé." + +#: lib/solaar/ui/pair_window.py:61 +msgid "" +"For devices with multiple channels, press, hold, and release the button for " +"the channel you wish to pair\n" +"or use the channel switch button to select a channel and then press, hold, " +"and release the channel switch button." +msgstr "" +"Pour les périphériques disposant de plusieurs canaux, appuyez sur le bouton " +"du canal que vous souhaitez appairer, maintenez-le enfoncé, puis relâchez-" +"le.\n" +"\n" +"Ou utilisez le bouton de changement de canal pour sélectionner un canal, " +"puis appuyez sur le bouton de changement de canal, maintenez-le enfoncé, " +"puis relâchez-le." + +#: lib/solaar/ui/pair_window.py:68 +msgid "The channel indicator light should be blinking rapidly." +msgstr "Le voyant indicateur du canal doit clignoter rapidement." + +#: lib/solaar/ui/pair_window.py:72 #, python-format msgid "" "\n" @@ -1721,7 +1750,7 @@ msgstr[1] "" "\n" "Ce récepteur a %d jumelages restants." -#: lib/solaar/ui/pair_window.py:286 +#: lib/solaar/ui/pair_window.py:78 msgid "" "\n" "Cancelling at this point will not use up a pairing." @@ -1729,119 +1758,357 @@ msgstr "" "\n" "L'annulation à ce stade n'utilisera pas de jumelage." -#: lib/solaar/ui/tray.py:58 +#: lib/solaar/ui/pair_window.py:168 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "Entrer le mot de passe sur %(name)s." + +#: lib/solaar/ui/pair_window.py:171 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "Taper %(passcode)s puis appuyez sur la touche Entrée." + +#: lib/solaar/ui/pair_window.py:176 +msgid "left" +msgstr "gauche" + +#: lib/solaar/ui/pair_window.py:176 +msgid "right" +msgstr "droit" + +#: lib/solaar/ui/pair_window.py:178 +#, python-format +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"Appuyez %(code)s\n" +"puis appuyez simultanément sur les boutons gauche et droit." + +#: lib/solaar/ui/pair_window.py:221 +msgid "The wireless link is not encrypted" +msgstr "La connexion sans fil n'est pas chiffrée" + +#: lib/solaar/ui/pair_window.py:226 +msgid "Found a new device:" +msgstr "Nouveau périphérique disponible :" + +#: lib/solaar/ui/pair_window.py:247 +msgid "Pairing failed" +msgstr "Le jumelage a échoué" + +#: lib/solaar/ui/pair_window.py:249 +msgid "Make sure your device is within range, and has a decent battery charge." +msgstr "" +"Assurez-vous que votre périphérique soit à portée, et que sa batterie soit " +"suffisamment chargée." + +#: lib/solaar/ui/pair_window.py:251 +msgid "A new device was detected, but it is not compatible with this receiver." +msgstr "" +"Un nouveau périphérique a été détecté, mais il n'est pas compatible avec ce " +"récepteur." + +#: lib/solaar/ui/pair_window.py:253 +msgid "More paired devices than receiver can support." +msgstr "" +"Il y a plus de périphériques jumelés que le récepteur ne peut en supporter." + +#: lib/solaar/ui/pair_window.py:255 +msgid "No further details are available about the error." +msgstr "Aucun autre détail n'est disponible à propos de l'erreur." + +#: lib/solaar/ui/rule_actions.py:54 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simule un clic combiné ou une pression ou un relâchement.\n" +"Sous Wayland cela nécessite un accès en écriture au périphérique /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:60 +msgid "Add key" +msgstr "Ajouter une touche" + +#: lib/solaar/ui/rule_actions.py:63 +msgid "Click" +msgstr "Clic" + +#: lib/solaar/ui/rule_actions.py:66 +msgid "Depress" +msgstr "Appuyer" + +#: lib/solaar/ui/rule_actions.py:69 +msgid "Release" +msgstr "Relâcher" + +#: lib/solaar/ui/rule_actions.py:153 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simule un défilement de souris.\n" +"Sous Wayland cela nécessite un accès en écriture au périphérique /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:211 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simule un clic de souris.\n" +"Sous Wayland cela nécessite un accès en écriture au périphérique /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:216 +msgid "Button" +msgstr "Bouton" + +#: lib/solaar/ui/rule_actions.py:218 +msgid "Action (and Count, if click)" +msgstr "Action (et nombre, en cas de clic)" + +#: lib/solaar/ui/rule_actions.py:269 +msgid "Execute a command with arguments." +msgstr "Exécuter une commande avec paramètres." + +#: lib/solaar/ui/rule_actions.py:273 +msgid "Add argument" +msgstr "Ajouter un paramètre" + +#: lib/solaar/ui/rule_conditions.py:52 +msgid "X11 active process. For use in X11 only." +msgstr "Processus actif X11. Pour une utilisation dans X11 uniquement." + +#: lib/solaar/ui/rule_conditions.py:82 +msgid "X11 mouse process. For use in X11 only." +msgstr "Processus de la souris X11. Pour une utilisation dans X11 uniquement." + +#: lib/solaar/ui/rule_conditions.py:99 +msgid "MouseProcess" +msgstr "Processus de la souris" + +#: lib/solaar/ui/rule_conditions.py:123 +msgid "Feature name of notification triggering rule processing." +msgstr "" +"Nom de la fonction de notification déclenchant le traitement de la règle." + +#: lib/solaar/ui/rule_conditions.py:169 +msgid "Report number of notification triggering rule processing." +msgstr "" +"Indique le nombre de notifications déclenchant le traitement de la règle." + +#: lib/solaar/ui/rule_conditions.py:201 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "Modificateurs de clavier actifs. Pas toujours disponible sous Wayland." + +#: lib/solaar/ui/rule_conditions.py:241 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"Interception de touche ou bouton appuyé ou relâché.\n" +"Utiliser l'interception de touche/bouton et les paramètres Interception des " +"touches G pour intercepter les touches et les boutons." + +#: lib/solaar/ui/rule_conditions.py:250 +msgid "Key down" +msgstr "Enfoncement de touche" + +#: lib/solaar/ui/rule_conditions.py:253 +msgid "Key up" +msgstr "Relâchement de touche" + +#: lib/solaar/ui/rule_conditions.py:293 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"La touche/le bouton intercepté est actuellement enfoncé.\n" +"Utiliser l'interception de touche/bouton et les paramètres Interception des " +"touches G pour intercepter les touches et les boutons." + +#: lib/solaar/ui/rule_conditions.py:331 +msgid "Test condition on notification triggering rule processing." +msgstr "" +"Condition de test déclenchant le traitement de la règle en cas de " +"notification." + +#: lib/solaar/ui/rule_conditions.py:335 +msgid "Parameter" +msgstr "Paramètre" + +#: lib/solaar/ui/rule_conditions.py:408 +msgid "begin (inclusive)" +msgstr "début (inclusif)" + +#: lib/solaar/ui/rule_conditions.py:409 +msgid "end (exclusive)" +msgstr "fin (exclusif)" + +#: lib/solaar/ui/rule_conditions.py:417 +msgid "range" +msgstr "plage" + +#: lib/solaar/ui/rule_conditions.py:420 +msgid "minimum" +msgstr "minimum" + +#: lib/solaar/ui/rule_conditions.py:421 +msgid "maximum" +msgstr "maximum" + +#: lib/solaar/ui/rule_conditions.py:423 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "octets %(0)d à %(1)d, allant de %(2)d à %(3)d" + +#: lib/solaar/ui/rule_conditions.py:426 lib/solaar/ui/rule_conditions.py:427 +msgid "mask" +msgstr "masque" + +#: lib/solaar/ui/rule_conditions.py:428 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "octets %(0)d à %(1)d, masque %(2)d" + +#: lib/solaar/ui/rule_conditions.py:437 +msgid "" +"Bit or range test on bytes in notification message triggering rule " +"processing." +msgstr "" +"Bit ou plage d'octets dans un message de notification déclenchant le " +"traitement d'une règle." + +#: lib/solaar/ui/rule_conditions.py:447 +msgid "type" +msgstr "type" + +#: lib/solaar/ui/rule_conditions.py:535 +msgid "" +"Mouse gesture with optional initiating button followed by zero or more mouse " +"movements." +msgstr "" +"Geste de la souris avec bouton d'initialisation optionnel suivi ou non par " +"des déplacements de souris." + +#: lib/solaar/ui/rule_conditions.py:541 +msgid "Add movement" +msgstr "Ajouter un mouvement" + +#: lib/solaar/ui/tray.py:55 msgid "No supported device found" msgstr "Aucun périphérique pris en charge trouvé" -#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#: lib/solaar/ui/tray.py:60 lib/solaar/ui/window.py:328 #, python-format msgid "About %s" msgstr "À propos de %s" -#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 +#: lib/solaar/ui/tray.py:61 lib/solaar/ui/window.py:326 #, python-format msgid "Quit %s" msgstr "Quitter %s" -#: lib/solaar/ui/tray.py:299 lib/solaar/ui/tray.py:307 +#: lib/solaar/ui/tray.py:273 lib/solaar/ui/tray.py:281 msgid "no receiver" msgstr "aucun récepteur" -#: lib/solaar/ui/tray.py:323 +#: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:299 +#: lib/solaar/ui/window.py:728 +msgid "offline" +msgstr "non connecté" + +#: lib/solaar/ui/tray.py:297 msgid "no status" msgstr "aucun statut" -#: lib/solaar/ui/window.py:96 +#: lib/solaar/ui/window.py:110 msgid "Scanning" msgstr "Balayage" -#: lib/solaar/ui/window.py:129 +#: lib/solaar/ui/window.py:141 msgid "Battery" msgstr "Batterie" -#: lib/solaar/ui/window.py:132 +#: lib/solaar/ui/window.py:144 msgid "Wireless Link" msgstr "Connexion sans fil" -#: lib/solaar/ui/window.py:136 +#: lib/solaar/ui/window.py:148 msgid "Lighting" msgstr "Éclairage" -#: lib/solaar/ui/window.py:170 +#: lib/solaar/ui/window.py:182 msgid "Show Technical Details" msgstr "Afficher les détails techniques" -#: lib/solaar/ui/window.py:186 +#: lib/solaar/ui/window.py:198 msgid "Pair new device" msgstr "Jumeler un nouveau périphérique" -#: lib/solaar/ui/window.py:205 +#: lib/solaar/ui/window.py:216 msgid "Select a device" msgstr "Sélectionner un périphérique" -#: lib/solaar/ui/window.py:322 +#: lib/solaar/ui/window.py:331 msgid "Rule Editor" msgstr "Éditeur de règles" -#: lib/solaar/ui/window.py:533 +#: lib/solaar/ui/window.py:522 msgid "Path" msgstr "Chemin" -#: lib/solaar/ui/window.py:536 +#: lib/solaar/ui/window.py:524 msgid "USB ID" msgstr "ID USB" -#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 -#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 +#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 msgid "Serial" msgstr "Numéro de série" -#: lib/solaar/ui/window.py:545 +#: lib/solaar/ui/window.py:533 msgid "Index" msgstr "Index" -#: lib/solaar/ui/window.py:547 +#: lib/solaar/ui/window.py:535 msgid "Wireless PID" msgstr "PID sans fil" -#: lib/solaar/ui/window.py:549 +#: lib/solaar/ui/window.py:537 msgid "Product ID" msgstr "Identifiant produit" -#: lib/solaar/ui/window.py:551 +#: lib/solaar/ui/window.py:539 msgid "Protocol" msgstr "Protocole" -#: lib/solaar/ui/window.py:551 +#: lib/solaar/ui/window.py:539 msgid "Unknown" msgstr "Inconnu" -#: lib/solaar/ui/window.py:554 -#, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d ms (%(rate_hz)dHz)" - -#: lib/solaar/ui/window.py:554 +#: lib/solaar/ui/window.py:541 msgid "Polling rate" msgstr "Taux de scrutation" -#: lib/solaar/ui/window.py:565 +#: lib/solaar/ui/window.py:548 msgid "Unit ID" msgstr "ID d'unité" -#: lib/solaar/ui/window.py:576 +#: lib/solaar/ui/window.py:559 msgid "none" msgstr "aucun" -#: lib/solaar/ui/window.py:577 +#: lib/solaar/ui/window.py:560 msgid "Notifications" msgstr "Notifications" -#: lib/solaar/ui/window.py:621 +#: lib/solaar/ui/window.py:604 msgid "No device paired." msgstr "Aucun périphérique jumelé." -#: lib/solaar/ui/window.py:628 +#: lib/solaar/ui/window.py:613 #, python-format msgid "Up to %(max_count)s device can be paired to this receiver." msgid_plural "Up to %(max_count)s devices can be paired to this receiver." @@ -1849,59 +2116,59 @@ msgstr[0] "Jusqu'à %(max_count)s périphérique peut être jumelé à ce récep msgstr[1] "" "Jusqu'à %(max_count)s périphériques peuvent être jumelés à ce récepteur." -#: lib/solaar/ui/window.py:634 +#: lib/solaar/ui/window.py:620 msgid "Only one device can be paired to this receiver." msgstr "Un seul périphérique peut être jumelé à ce récepteur." -#: lib/solaar/ui/window.py:638 +#: lib/solaar/ui/window.py:624 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." msgstr[0] "Ce récepteur a %d jumelage restant." msgstr[1] "Ce récepteur a %d jumelages restants." -#: lib/solaar/ui/window.py:692 +#: lib/solaar/ui/window.py:681 msgid "Battery Voltage" msgstr "Tension de la batterie" -#: lib/solaar/ui/window.py:694 +#: lib/solaar/ui/window.py:683 msgid "Voltage reported by battery" msgstr "Tension indiquée par la batterie" -#: lib/solaar/ui/window.py:696 +#: lib/solaar/ui/window.py:685 msgid "Battery Level" msgstr "Niveau de la batterie" -#: lib/solaar/ui/window.py:698 +#: lib/solaar/ui/window.py:687 msgid "Approximate level reported by battery" msgstr "Niveau approximatif indiqué par la batterie" -#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 +#: lib/solaar/ui/window.py:694 lib/solaar/ui/window.py:696 msgid "next reported " msgstr "prochain rapport " -#: lib/solaar/ui/window.py:708 +#: lib/solaar/ui/window.py:697 msgid " and next level to be reported." msgstr " et prochain niveau à rapporter." -#: lib/solaar/ui/window.py:713 +#: lib/solaar/ui/window.py:702 msgid "last known" msgstr "dernière valeur connue" -#: lib/solaar/ui/window.py:724 +#: lib/solaar/ui/window.py:713 msgid "encrypted" msgstr "chiffrée" -#: lib/solaar/ui/window.py:726 +#: lib/solaar/ui/window.py:715 msgid "The wireless link between this device and its receiver is encrypted." msgstr "" "La connexion sans fil entre ce périphérique et son récepteur est chiffrée." -#: lib/solaar/ui/window.py:728 +#: lib/solaar/ui/window.py:717 msgid "not encrypted" msgstr "non chiffrée" -#: lib/solaar/ui/window.py:732 +#: lib/solaar/ui/window.py:721 msgid "" "The wireless link between this device and its receiver is not encrypted.\n" "This is a security issue for pointing devices, and a major security issue " @@ -1912,7 +2179,66 @@ msgstr "" "Ceci est un problème de sécurité pour les dispositifs de pointage, et un " "problème majeur pour les périphériques de saisie de texte." -#: lib/solaar/ui/window.py:748 +#: lib/solaar/ui/window.py:737 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d lux" + +#, python-format +#~ msgid "%(rate)d ms (%(rate_hz)dHz)" +#~ msgstr "%(rate)d ms (%(rate_hz)dHz)" + +#, python-format +#~ msgid "Battery: %(level)s" +#~ msgstr "Batterie : %(level)s" + +#, python-format +#~ msgid "Battery: %(percent)d%%" +#~ msgstr "Batterie : %(percent)d%%" + +#~ msgid "Count and Action" +#~ msgstr "Nombre et action" + +#~ msgid "Divert G Keys" +#~ msgstr "Définir les touches G" + +#~ msgid "" +#~ "Enable onboard profiles, which often control report rate and keyboard " +#~ "lighting" +#~ msgstr "" +#~ "Active les profils embarqués, qui contrôlent souvent le taux de rapport " +#~ "et l'éclairage du clavier" + +#~ msgid "Frequency of device polling, in milliseconds" +#~ msgstr "Fréquence de scrutation du périphérique, en millisecondes" + +#, python-format +#~ msgid "Lighting: %(level)s lux" +#~ msgstr "Éclairage : %(level)s lux" + +#~ msgid "" +#~ "Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " +#~ "are otherwise ignored)." +#~ msgstr "" +#~ "Active l'envoi par les touches G de notifications GKEY HID++ (ce qui " +#~ "déclenche les règles Solaar qui sont ignorées sinon)." + +#~ msgid "May also make M keys and MR key send HID++ notifications" +#~ msgstr "" +#~ "Peut aussi permettre que les touches M et la touche MR envoient des " +#~ "notifications HID++" + +#~ msgid "Number of seconds to delay." +#~ msgstr "Nombre de secondes de report." + +#~ msgid "Polling Rate (ms)" +#~ msgstr "Taux de scrutation (ms)" + +#~ msgid "Turn illumination on or off on keyboard." +#~ msgstr "Activez ou désactivez l'éclairage sur le clavier." + +#~ msgid "Turn on the device you want to pair." +#~ msgstr "Allumez le périphérique que vous souhaitez jumeler." + +#~ msgid "You may have to first turn the device off and on again." +#~ msgstr "Vous devriez tout d' abord éteindre le périphérique et le rallumer." diff --git a/po/hr.po b/po/hr.po index e91a1b02..8f5e7330 100644 --- a/po/hr.po +++ b/po/hr.po @@ -3,2010 +3,2025 @@ # This file is distributed under the same license as the solaar package. # gogo , 2014. # -msgid "" -msgstr "" -"Project-Id-Version: solaar 1.0.1\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-28 10:28+0200\n" -"PO-Revision-Date: 2022-10-28 10:37+0200\n" -"Last-Translator: gogo \n" -"Language-Team: Croatian \n" -"Language: hr\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " -"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 3.0.1\n" +msgid "" +msgstr "Project-Id-Version: solaar 1.0.1\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2023-12-28 17:40+0100\n" + "PO-Revision-Date: 2022-10-28 10:37+0200\n" + "Last-Translator: gogo \n" + "Language-Team: Croatian \n" + "Language: hr\n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 " + "&& n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + "X-Generator: Poedit 3.0.1\n" #: lib/logitech_receiver/base_usb.py:46 -msgid "Bolt Receiver" -msgstr "Bolt prijemnik" +msgid "Bolt Receiver" +msgstr "Bolt prijemnik" #: lib/logitech_receiver/base_usb.py:57 -msgid "Unifying Receiver" -msgstr "Unifying prijemnik" +msgid "Unifying Receiver" +msgstr "Unifying prijemnik" #: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 #: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 #: lib/logitech_receiver/base_usb.py:114 -msgid "Nano Receiver" -msgstr "Nano prijemnik" +msgid "Nano Receiver" +msgstr "Nano prijemnik" #: lib/logitech_receiver/base_usb.py:124 -msgid "Lightspeed Receiver" -msgstr "Lightspeed prijemnik" +msgid "Lightspeed Receiver" +msgstr "Lightspeed prijemnik" #: lib/logitech_receiver/base_usb.py:133 -msgid "EX100 Receiver 27 Mhz" -msgstr "EX100 prijemnik 27 Mhz" +msgid "EX100 Receiver 27 Mhz" +msgstr "EX100 prijemnik 27 Mhz" #: lib/logitech_receiver/i18n.py:30 -msgid "empty" -msgstr "prazna" +msgid "empty" +msgstr "prazna" #: lib/logitech_receiver/i18n.py:31 -msgid "critical" -msgstr "kritično" +msgid "critical" +msgstr "kritično" #: lib/logitech_receiver/i18n.py:32 -msgid "low" -msgstr "slaba" +msgid "low" +msgstr "slaba" #: lib/logitech_receiver/i18n.py:33 -msgid "average" -msgstr "prosječna" +msgid "average" +msgstr "prosječna" #: lib/logitech_receiver/i18n.py:34 -msgid "good" -msgstr "dobra" +msgid "good" +msgstr "dobra" #: lib/logitech_receiver/i18n.py:35 -msgid "full" -msgstr "napunjena" +msgid "full" +msgstr "napunjena" #: lib/logitech_receiver/i18n.py:38 -msgid "discharging" -msgstr "pražnjenje" +msgid "discharging" +msgstr "pražnjenje" #: lib/logitech_receiver/i18n.py:39 -msgid "recharging" -msgstr "punjenje" +msgid "recharging" +msgstr "punjenje" -#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:718 -msgid "charging" -msgstr "punjenje" +#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 +msgid "charging" +msgstr "punjenje" #: lib/logitech_receiver/i18n.py:41 -msgid "not charging" -msgstr "ne puni se" +msgid "not charging" +msgstr "ne puni se" #: lib/logitech_receiver/i18n.py:42 -msgid "almost full" -msgstr "uskoro puno" +msgid "almost full" +msgstr "uskoro puno" #: lib/logitech_receiver/i18n.py:43 -msgid "charged" -msgstr "napunjena" +msgid "charged" +msgstr "napunjena" #: lib/logitech_receiver/i18n.py:44 -msgid "slow recharge" -msgstr "sporo punjenje" +msgid "slow recharge" +msgstr "sporo punjenje" #: lib/logitech_receiver/i18n.py:45 -msgid "invalid battery" -msgstr "neispravna baterija" +msgid "invalid battery" +msgstr "neispravna baterija" #: lib/logitech_receiver/i18n.py:46 -msgid "thermal error" -msgstr "toplinska greška" +msgid "thermal error" +msgstr "toplinska greška" #: lib/logitech_receiver/i18n.py:47 -msgid "error" -msgstr "greška" +msgid "error" +msgstr "greška" #: lib/logitech_receiver/i18n.py:48 -msgid "standard" -msgstr "standardno" +msgid "standard" +msgstr "standardno" #: lib/logitech_receiver/i18n.py:49 -msgid "fast" -msgstr "brzo" +msgid "fast" +msgstr "brzo" #: lib/logitech_receiver/i18n.py:50 -msgid "slow" -msgstr "sporo" +msgid "slow" +msgstr "sporo" #: lib/logitech_receiver/i18n.py:53 -msgid "device timeout" -msgstr "istek čekanja uređaja" +msgid "device timeout" +msgstr "istek čekanja uređaja" #: lib/logitech_receiver/i18n.py:54 -msgid "device not supported" -msgstr "uređaj nije podržan" +msgid "device not supported" +msgstr "uređaj nije podržan" #: lib/logitech_receiver/i18n.py:55 -msgid "too many devices" -msgstr "previše uređaja" +msgid "too many devices" +msgstr "previše uređaja" #: lib/logitech_receiver/i18n.py:56 -msgid "sequence timeout" -msgstr "redoslijed isteka vremena" +msgid "sequence timeout" +msgstr "redoslijed isteka vremena" -#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:577 -msgid "Firmware" -msgstr "Firmver" +#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 +msgid "Firmware" +msgstr "Firmver" #: lib/logitech_receiver/i18n.py:60 -msgid "Bootloader" -msgstr "Učitač pokretanja" +msgid "Bootloader" +msgstr "Učitač pokretanja" #: lib/logitech_receiver/i18n.py:61 -msgid "Hardware" -msgstr "Hardver" +msgid "Hardware" +msgstr "Hardver" #: lib/logitech_receiver/i18n.py:62 -msgid "Other" -msgstr "Ostalo" +msgid "Other" +msgstr "Ostalo" #: lib/logitech_receiver/i18n.py:65 -msgid "Left Button" -msgstr "Lijeva tipka" +msgid "Left Button" +msgstr "Lijeva tipka" #: lib/logitech_receiver/i18n.py:66 -msgid "Right Button" -msgstr "Desna tipka" +msgid "Right Button" +msgstr "Desna tipka" #: lib/logitech_receiver/i18n.py:67 -msgid "Middle Button" -msgstr "Srednja tipka" +msgid "Middle Button" +msgstr "Srednja tipka" #: lib/logitech_receiver/i18n.py:68 -msgid "Back Button" -msgstr "Tipka za natrag" +msgid "Back Button" +msgstr "Tipka za natrag" #: lib/logitech_receiver/i18n.py:69 -msgid "Forward Button" -msgstr "Tipka za naprijed" +msgid "Forward Button" +msgstr "Tipka za naprijed" #: lib/logitech_receiver/i18n.py:70 -msgid "Mouse Gesture Button" -msgstr "Tipka gesta miša" +msgid "Mouse Gesture Button" +msgstr "Tipka gesta miša" #: lib/logitech_receiver/i18n.py:71 -msgid "Smart Shift" -msgstr "Pametan pomak" +msgid "Smart Shift" +msgstr "Pametan pomak" #: lib/logitech_receiver/i18n.py:72 -msgid "DPI Switch" -msgstr "DPI prebacivač" +msgid "DPI Switch" +msgstr "DPI prebacivač" #: lib/logitech_receiver/i18n.py:73 -msgid "Left Tilt" -msgstr "Lijevi nagib" +msgid "Left Tilt" +msgstr "Lijevi nagib" #: lib/logitech_receiver/i18n.py:74 -msgid "Right Tilt" -msgstr "Desni nagib" +msgid "Right Tilt" +msgstr "Desni nagib" #: lib/logitech_receiver/i18n.py:75 -msgid "Left Click" -msgstr "Lijevi klik" +msgid "Left Click" +msgstr "Lijevi klik" #: lib/logitech_receiver/i18n.py:76 -msgid "Right Click" -msgstr "Desni klik" +msgid "Right Click" +msgstr "Desni klik" #: lib/logitech_receiver/i18n.py:77 -msgid "Mouse Middle Button" -msgstr "Srednja tipka miša" +msgid "Mouse Middle Button" +msgstr "Srednja tipka miša" #: lib/logitech_receiver/i18n.py:78 -msgid "Mouse Back Button" -msgstr "Tipka miša za natrag" +msgid "Mouse Back Button" +msgstr "Tipka miša za natrag" #: lib/logitech_receiver/i18n.py:79 -msgid "Mouse Forward Button" -msgstr "Tipka miša za naprijed" +msgid "Mouse Forward Button" +msgstr "Tipka miša za naprijed" #: lib/logitech_receiver/i18n.py:80 -msgid "Gesture Button Navigation" -msgstr "Tipka navigacije gesta" +msgid "Gesture Button Navigation" +msgstr "Tipka navigacije gesta" #: lib/logitech_receiver/i18n.py:81 -msgid "Mouse Scroll Left Button" -msgstr "Lijeva tipka pomicanja miša" +msgid "Mouse Scroll Left Button" +msgstr "Lijeva tipka pomicanja miša" #: lib/logitech_receiver/i18n.py:82 -msgid "Mouse Scroll Right Button" -msgstr "Desna tipka pomicanja miša" +msgid "Mouse Scroll Right Button" +msgstr "Desna tipka pomicanja miša" #: lib/logitech_receiver/i18n.py:85 -msgid "pressed" -msgstr "pritisak" +msgid "pressed" +msgstr "pritisak" #: lib/logitech_receiver/i18n.py:86 -msgid "released" -msgstr "otpuštanje" +msgid "released" +msgstr "otpuštanje" #: lib/logitech_receiver/notifications.py:75 #: lib/logitech_receiver/notifications.py:126 -msgid "pairing lock is closed" -msgstr "uparivanje je zatvoreno" +msgid "pairing lock is closed" +msgstr "uparivanje je zatvoreno" #: lib/logitech_receiver/notifications.py:75 #: lib/logitech_receiver/notifications.py:126 -msgid "pairing lock is open" -msgstr "uparivanje je otvoreno" +msgid "pairing lock is open" +msgstr "uparivanje je otvoreno" #: lib/logitech_receiver/notifications.py:92 -msgid "discovery lock is closed" -msgstr "otkrivanje je zatvoreno" +msgid "discovery lock is closed" +msgstr "otkrivanje je zatvoreno" #: lib/logitech_receiver/notifications.py:92 -msgid "discovery lock is open" -msgstr "otkrivanje je otvoreno" +msgid "discovery lock is open" +msgstr "otkrivanje je otvoreno" -#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:120 -msgid "connected" -msgstr "povezano" +#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 +msgid "connected" +msgstr "povezano" #: lib/logitech_receiver/notifications.py:224 -msgid "disconnected" -msgstr "nije povezano" +msgid "disconnected" +msgstr "nije povezano" -#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:118 -msgid "unpaired" -msgstr "neupareno" +#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 +msgid "unpaired" +msgstr "neupareno" #: lib/logitech_receiver/notifications.py:304 -msgid "powered on" -msgstr "uključen" +msgid "powered on" +msgstr "uključen" -#: lib/logitech_receiver/settings.py:735 -msgid "register" -msgstr "registar" +#: lib/logitech_receiver/settings.py:750 +msgid "register" +msgstr "registar" -#: lib/logitech_receiver/settings.py:749 lib/logitech_receiver/settings.py:776 -msgid "feature" -msgstr "značajka" +#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 +msgid "feature" +msgstr "značajka" #: lib/logitech_receiver/settings_templates.py:139 -msgid "Swap Fx function" -msgstr "Zamijeni Fx funkciju" +msgid "Swap Fx function" +msgstr "Zamijeni Fx funkciju" #: lib/logitech_receiver/settings_templates.py:140 -msgid "" -"When set, the F1..F12 keys will activate their special function,\n" -"and you must hold the FN key to activate their standard function." -msgstr "" -"Kada je omogućeno, tipke F1..F12 će aktivirati svoje dodatne funkcije,\n" -"a za aktivaciju njihovih osnovnih funkcija morate držati FN tipku." +msgid "When set, the F1..F12 keys will activate their special function,\n" + "and you must hold the FN key to activate their standard function." +msgstr "Kada je omogućeno, tipke F1..F12 će aktivirati svoje dodatne " + "funkcije,\n" + "a za aktivaciju njihovih osnovnih funkcija morate držati FN tipku." #: lib/logitech_receiver/settings_templates.py:142 -msgid "" -"When unset, the F1..F12 keys will activate their standard function,\n" -"and you must hold the FN key to activate their special function." -msgstr "" -"Kada nije omogućeno, tipke F1..F12 će aktivirati svoje osnovne funkcije,\n" -"a za aktivaciju njihovih dodatnih funkcija morate držati FN tipku." +msgid "When unset, the F1..F12 keys will activate their standard function,\n" + "and you must hold the FN key to activate their special function." +msgstr "Kada nije omogućeno, tipke F1..F12 će aktivirati svoje osnovne " + "funkcije,\n" + "a za aktivaciju njihovih dodatnih funkcija morate držati FN tipku." #: lib/logitech_receiver/settings_templates.py:149 -msgid "Hand Detection" -msgstr "Otkrivanje ruku" +msgid "Hand Detection" +msgstr "Otkrivanje ruku" #: lib/logitech_receiver/settings_templates.py:150 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "Uključite osvjetljenje kada ruke lebde nad tipkovnicom." +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Uključite osvjetljenje kada ruke lebde nad tipkovnicom." #: lib/logitech_receiver/settings_templates.py:157 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "Glatko pomicanje kotačićem" +msgid "Scroll Wheel Smooth Scrolling" +msgstr "Glatko pomicanje kotačićem" #: lib/logitech_receiver/settings_templates.py:158 #: lib/logitech_receiver/settings_templates.py:239 #: lib/logitech_receiver/settings_templates.py:267 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "Visokoosjetljivi način okomitog pomicanja kotačićem." +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Visokoosjetljivi način okomitog pomicanja kotačićem." #: lib/logitech_receiver/settings_templates.py:165 -msgid "Side Scrolling" -msgstr "Bočno pomicanje kotačićem" +msgid "Side Scrolling" +msgstr "Bočno pomicanje kotačićem" #: lib/logitech_receiver/settings_templates.py:167 -msgid "" -"When disabled, pushing the wheel sideways sends custom button events\n" -"instead of the standard side-scrolling events." -msgstr "" -"Kad je onemogućeno, guranje kotačića ustranu šalje prilagođeni događaj " -"tipke\n" -"umjesto uobičajanog događaja bočnog pomicanja." +msgid "When disabled, pushing the wheel sideways sends custom button " + "events\n" + "instead of the standard side-scrolling events." +msgstr "Kad je onemogućeno, guranje kotačića ustranu šalje prilagođeni " + "događaj tipke\n" + "umjesto uobičajanog događaja bočnog pomicanja." #: lib/logitech_receiver/settings_templates.py:177 -msgid "Sensitivity (DPI - older mice)" -msgstr "Osjetljivost (DPI - stariji miševi)" +msgid "Sensitivity (DPI - older mice)" +msgstr "Osjetljivost (DPI - stariji miševi)" #: lib/logitech_receiver/settings_templates.py:178 -#: lib/logitech_receiver/settings_templates.py:703 -msgid "Mouse movement sensitivity" -msgstr "Osjetljivost pomicanja miša" +#: lib/logitech_receiver/settings_templates.py:712 +msgid "Mouse movement sensitivity" +msgstr "Osjetljivost pomicanja miša" #: lib/logitech_receiver/settings_templates.py:208 #: lib/logitech_receiver/settings_templates.py:218 #: lib/logitech_receiver/settings_templates.py:225 -msgid "Backlight" -msgstr "Osvjetljenje" +msgid "Backlight" +msgstr "Osvjetljenje" #: lib/logitech_receiver/settings_templates.py:209 #: lib/logitech_receiver/settings_templates.py:226 -msgid "Set illumination time for keyboard." -msgstr "Postavite vrijeme osvjetljenja tipkovnice." +msgid "Set illumination time for keyboard." +msgstr "Postavite vrijeme osvjetljenja tipkovnice." #: lib/logitech_receiver/settings_templates.py:219 -msgid "Turn illumination on or off on keyboard." -msgstr "Uključite ili isključite osvjetljenje tipkovnice." +msgid "Turn illumination on or off on keyboard." +msgstr "Uključite ili isključite osvjetljenje tipkovnice." #: lib/logitech_receiver/settings_templates.py:237 -msgid "Scroll Wheel High Resolution" -msgstr "Visokoosjetljivi način pomicanja kotačićem" +msgid "Scroll Wheel High Resolution" +msgstr "Visokoosjetljivi način pomicanja kotačićem" #: lib/logitech_receiver/settings_templates.py:240 #: lib/logitech_receiver/settings_templates.py:268 -msgid "Set to ignore if scrolling is abnormally fast or slow" -msgstr "Postavite na zanemareno ako je pomicanje prebrzo ili presporo" +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "Postavite na zanemareno ako je pomicanje prebrzo ili presporo" #: lib/logitech_receiver/settings_templates.py:247 #: lib/logitech_receiver/settings_templates.py:277 -msgid "Scroll Wheel Diversion" -msgstr "Preusmjeravanje pomicanja kotačića" +msgid "Scroll Wheel Diversion" +msgstr "Preusmjeravanje pomicanja kotačića" #: lib/logitech_receiver/settings_templates.py:249 -msgid "" -"Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " -"Solaar rules but are otherwise ignored)." -msgstr "" -"Neka kotačić pomicanja šalje LOWRES_WHEEL HID++ obavijesti (što pokreće " -"Solaar pravila, u suprotnome ih zanemaruje)." +msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "Neka kotačić pomicanja šalje LOWRES_WHEEL HID++ obavijesti (što " + "pokreće Solaar pravila, u suprotnome ih zanemaruje)." #: lib/logitech_receiver/settings_templates.py:256 -msgid "Scroll Wheel Direction" -msgstr "Smjer pomicanja kotačića" +msgid "Scroll Wheel Direction" +msgstr "Smjer pomicanja kotačića" #: lib/logitech_receiver/settings_templates.py:257 -msgid "Invert direction for vertical scroll with wheel." -msgstr "Obrnuti smjer za okomito pomicanje kotačićem." +msgid "Invert direction for vertical scroll with wheel." +msgstr "Obrnuti smjer za okomito pomicanje kotačićem." #: lib/logitech_receiver/settings_templates.py:265 -msgid "Scroll Wheel Resolution" -msgstr "Osjetljivost pomicanja kotačićem" +msgid "Scroll Wheel Resolution" +msgstr "Osjetljivost pomicanja kotačićem" #: lib/logitech_receiver/settings_templates.py:279 -msgid "" -"Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " -"rules but are otherwise ignored)." -msgstr "" -"Neka kotačić pomicanja šalje HIRES_WHEEL HID++ obavijesti (što pokreće " -"Solaar pravila, u suprotnome ih zanemaruje)." +msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "Neka kotačić pomicanja šalje HIRES_WHEEL HID++ obavijesti (što " + "pokreće Solaar pravila, u suprotnome ih zanemaruje)." #: lib/logitech_receiver/settings_templates.py:288 -msgid "Sensitivity (Pointer Speed)" -msgstr "Osjetljivost (Brzina pokazivača)" +msgid "Sensitivity (Pointer Speed)" +msgstr "Osjetljivost (Brzina pokazivača)" #: lib/logitech_receiver/settings_templates.py:289 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "Množitelj brzine za miš (256 je normalan množitelj)." +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Množitelj brzine za miš (256 je normalan množitelj)." #: lib/logitech_receiver/settings_templates.py:299 -msgid "Thumb Wheel Diversion" -msgstr "Preusmjeravanje pomicanja kotačića palca" +msgid "Thumb Wheel Diversion" +msgstr "Preusmjeravanje pomicanja kotačića palca" #: lib/logitech_receiver/settings_templates.py:301 -msgid "" -"Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " -"rules but are otherwise ignored)." -msgstr "" -"Neka kotačić palca šalje THUMB_WHEEL HID++ obavijesti (što pokreće Solaar " -"pravila, u suprotnome ih zanemaruje)." +msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " + "Solaar rules but are otherwise ignored)." +msgstr "Neka kotačić palca šalje THUMB_WHEEL HID++ obavijesti (što pokreće " + "Solaar pravila, u suprotnome ih zanemaruje)." #: lib/logitech_receiver/settings_templates.py:310 -msgid "Thumb Wheel Direction" -msgstr "Smjer pomicanja kotačića palca" +msgid "Thumb Wheel Direction" +msgstr "Smjer pomicanja kotačića palca" #: lib/logitech_receiver/settings_templates.py:311 -msgid "Invert thumb wheel scroll direction." -msgstr "Obrnuti smjer pomicanja kotačića palca." +msgid "Invert thumb wheel scroll direction." +msgstr "Obrnuti smjer pomicanja kotačića palca." #: lib/logitech_receiver/settings_templates.py:319 -msgid "Onboard Profiles" -msgstr "Onboard profili" +msgid "Onboard Profiles" +msgstr "Onboard profili" #: lib/logitech_receiver/settings_templates.py:320 -msgid "" -"Enable onboard profiles, which often control report rate and keyboard " -"lighting" -msgstr "" -"Omogućite onboard profile, koji najčešće upravljaju brzinom pozivanja i " -"osvjetljenjem tipkovnice" +msgid "Enable onboard profiles, which often control report rate and " + "keyboard lighting" +msgstr "Omogućite onboard profile, koji najčešće upravljaju brzinom " + "pozivanja i osvjetljenjem tipkovnice" #: lib/logitech_receiver/settings_templates.py:330 -msgid "Polling Rate (ms)" -msgstr "Brzina pozivanja (ms)" +msgid "Polling Rate (ms)" +msgstr "Brzina pozivanja (ms)" #: lib/logitech_receiver/settings_templates.py:332 -msgid "Frequency of device polling, in milliseconds" -msgstr "Učestalost pozivanja uređaja, u milisekundama" +msgid "Frequency of device polling, in milliseconds" +msgstr "Učestalost pozivanja uređaja, u milisekundama" #: lib/logitech_receiver/settings_templates.py:333 -#: lib/logitech_receiver/settings_templates.py:1026 -#: lib/logitech_receiver/settings_templates.py:1054 -msgid "May need Onboard Profiles set to Disable to be effective." -msgstr "" -"Možda je potrebno postaviti Onboard profile na 'Onemogućeno' kako bi bili " -"učinkoviti." +#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "Možda je potrebno postaviti Onboard profile na 'Onemogućeno' kako bi " + "bili učinkoviti." -#: lib/logitech_receiver/settings_templates.py:363 -msgid "Divert crown events" -msgstr "Preusmjeri crown događaje" +#: lib/logitech_receiver/settings_templates.py:365 +msgid "Divert crown events" +msgstr "Preusmjeri crown događaje" -#: lib/logitech_receiver/settings_templates.py:364 -msgid "" -"Make crown send CROWN HID++ notifications (which trigger Solaar rules but " -"are otherwise ignored)." -msgstr "" -"Neka crown šalje CROWN HID++ obavijesti (što pokreće Solaar pravila, u " -"suprotnome ih zanemaruje)." +#: lib/logitech_receiver/settings_templates.py:366 +msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "Neka crown šalje CROWN HID++ obavijesti (što pokreće Solaar pravila, " + "u suprotnome ih zanemaruje)." -#: lib/logitech_receiver/settings_templates.py:372 -msgid "Crown smooth scroll" -msgstr "Crown glatko pomicanje" +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Crown smooth scroll" +msgstr "Crown glatko pomicanje" -#: lib/logitech_receiver/settings_templates.py:373 -msgid "Set crown smooth scroll" -msgstr "Postavite Crown glatko pomicanje" - -#: lib/logitech_receiver/settings_templates.py:381 -msgid "Divert G Keys" -msgstr "Preusmjeri G tipke" +#: lib/logitech_receiver/settings_templates.py:375 +msgid "Set crown smooth scroll" +msgstr "Postavite Crown glatko pomicanje" #: lib/logitech_receiver/settings_templates.py:383 -msgid "" -"Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " -"are otherwise ignored)." -msgstr "" -"Neka G tipke šalju GKEY HID++ obavijesti (što pokreće Solaar pravila, u " -"suprotnome ih zanemaruje)." +msgid "Divert G Keys" +msgstr "Preusmjeri G tipke" -#: lib/logitech_receiver/settings_templates.py:384 -msgid "May also make M keys and MR key send HID++ notifications" -msgstr "Neka isto M tipke i MR tipka šalje HID++ obavijesti" +#: lib/logitech_receiver/settings_templates.py:385 +msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "Neka G tipke šalju GKEY HID++ obavijesti (što pokreće Solaar " + "pravila, u suprotnome ih zanemaruje)." -#: lib/logitech_receiver/settings_templates.py:399 -msgid "Scroll Wheel Ratcheted" -msgstr "Ustavljanje pomicanja kotačića" - -#: lib/logitech_receiver/settings_templates.py:400 -msgid "" -"Switch the mouse wheel between speed-controlled ratcheting and always " -"freespin." -msgstr "" -"Prebaci kotačić miša između ustavljačkog brzinom upravljanog načina i " -"slobodnog načina vrtnje kotačića." +#: lib/logitech_receiver/settings_templates.py:386 +msgid "May also make M keys and MR key send HID++ notifications" +msgstr "Neka isto M tipke i MR tipka šalje HID++ obavijesti" #: lib/logitech_receiver/settings_templates.py:402 -msgid "Freespinning" -msgstr "Slobodna vrtnja" +msgid "Scroll Wheel Ratcheted" +msgstr "Ustavljanje pomicanja kotačića" -#: lib/logitech_receiver/settings_templates.py:402 -msgid "Ratcheted" -msgstr "Ustavljanje" +#: lib/logitech_receiver/settings_templates.py:403 +msgid "Switch the mouse wheel between speed-controlled ratcheting and " + "always freespin." +msgstr "Prebaci kotačić miša između ustavljačkog brzinom upravljanog načina " + "i slobodnog načina vrtnje kotačića." -#: lib/logitech_receiver/settings_templates.py:409 -msgid "Scroll Wheel Ratchet Speed" -msgstr "Brzina ustavljanja pomicanja kotačića" +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Freespinning" +msgstr "Slobodna vrtnja" -#: lib/logitech_receiver/settings_templates.py:411 -msgid "" -"Use the mouse wheel speed to switch between ratcheted and freespinning.\n" -"The mouse wheel is always ratcheted at 50." -msgstr "" -"Koristite brzinu kotačića miša za prebacivanje između ustavljačkog i " -"slobodnog načina vrtnje kotačića.\n" -"Kotačić miša je uvijek ustavljen pri 50." +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Ratcheted" +msgstr "Ustavljanje" -#: lib/logitech_receiver/settings_templates.py:460 -msgid "Key/Button Actions" -msgstr "Radnje tipki" +#: lib/logitech_receiver/settings_templates.py:412 +msgid "Scroll Wheel Ratchet Speed" +msgstr "Brzina ustavljanja pomicanja kotačića" -#: lib/logitech_receiver/settings_templates.py:462 -msgid "Change the action for the key or button." -msgstr "Promijeni radnju za tipku." - -#: lib/logitech_receiver/settings_templates.py:462 -msgid "Overridden by diversion." -msgstr "Preusmjereno skretanjem." +#: lib/logitech_receiver/settings_templates.py:414 +msgid "Use the mouse wheel speed to switch between ratcheted and " + "freespinning.\n" + "The mouse wheel is always ratcheted at 50." +msgstr "Koristite brzinu kotačića miša za prebacivanje između ustavljačkog i " + "slobodnog načina vrtnje kotačića.\n" + "Kotačić miša je uvijek ustavljen pri 50." #: lib/logitech_receiver/settings_templates.py:463 -msgid "" -"Changing important actions (such as for the left mouse button) can result in " -"an unusable system." -msgstr "" -"Promjena bitnih radnji (poput lijeve tipke miša) može prouzrokovati " -"nestabilnosti u sustavu." +msgid "Key/Button Actions" +msgstr "Radnje tipki" -#: lib/logitech_receiver/settings_templates.py:632 -msgid "Key/Button Diversion" -msgstr "Preusmjeravanje tipki" +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Change the action for the key or button." +msgstr "Promijeni radnju za tipku." -#: lib/logitech_receiver/settings_templates.py:633 -msgid "" -"Make the key or button send HID++ notifications (Diverted) or initiate Mouse " -"Gestures or Sliding DPI" -msgstr "" -"Neka tipka ili šalje HID++ obavijesti (preusmjerene) ili pokreće gete mišem " -"ili klizni DPI" +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Overridden by diversion." +msgstr "Preusmjereno skretanjem." -#: lib/logitech_receiver/settings_templates.py:636 -#: lib/logitech_receiver/settings_templates.py:637 -#: lib/logitech_receiver/settings_templates.py:638 -msgid "Diverted" -msgstr "Preusmjerno" +#: lib/logitech_receiver/settings_templates.py:466 +msgid "Changing important actions (such as for the left mouse button) can " + "result in an unusable system." +msgstr "Promjena bitnih radnji (poput lijeve tipke miša) može prouzrokovati " + "nestabilnosti u sustavu." -#: lib/logitech_receiver/settings_templates.py:636 -#: lib/logitech_receiver/settings_templates.py:637 -msgid "Mouse Gestures" -msgstr "Geste miša" +#: lib/logitech_receiver/settings_templates.py:639 +msgid "Key/Button Diversion" +msgstr "Preusmjeravanje tipki" -#: lib/logitech_receiver/settings_templates.py:636 -#: lib/logitech_receiver/settings_templates.py:637 -#: lib/logitech_receiver/settings_templates.py:638 -msgid "Regular" -msgstr "Normalna" +#: lib/logitech_receiver/settings_templates.py:640 +msgid "Make the key or button send HID++ notifications (Diverted) or " + "initiate Mouse Gestures or Sliding DPI" +msgstr "Neka tipka ili šalje HID++ obavijesti (preusmjerene) ili pokreće " + "gete mišem ili klizni DPI" -#: lib/logitech_receiver/settings_templates.py:636 -msgid "Sliding DPI" -msgstr "Klizni DPI" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Diverted" +msgstr "Preusmjerno" -#: lib/logitech_receiver/settings_templates.py:702 -msgid "Sensitivity (DPI)" -msgstr "Osjetljivost (DPI)" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +msgid "Mouse Gestures" +msgstr "Geste miša" -#: lib/logitech_receiver/settings_templates.py:742 -msgid "Sensitivity Switching" -msgstr "Prebacivanje osjetljivosti" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Regular" +msgstr "Normalna" -#: lib/logitech_receiver/settings_templates.py:744 -msgid "" -"Switch the current sensitivity and the remembered sensitivity when the key " -"or button is pressed.\n" -"If there is no remembered sensitivity, just remember the current sensitivity" -msgstr "" -"Prebacivanje trenutne osjetljivosti i zapamćene osjetljivosti kada se tipka " -"pritisne.\n" -"Ako ne postoji zapamćena osjetljivost, zapamti trenutnu osjetljivost" +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Sliding DPI" +msgstr "Klizni DPI" -#: lib/logitech_receiver/settings_templates.py:748 -msgid "Off" -msgstr "Isključeno" +#: lib/logitech_receiver/settings_templates.py:711 +msgid "Sensitivity (DPI)" +msgstr "Osjetljivost (DPI)" -#: lib/logitech_receiver/settings_templates.py:779 -msgid "Disable keys" -msgstr "Onemogući tipke" +#: lib/logitech_receiver/settings_templates.py:752 +msgid "Sensitivity Switching" +msgstr "Prebacivanje osjetljivosti" -#: lib/logitech_receiver/settings_templates.py:780 -msgid "Disable specific keyboard keys." -msgstr "Onemogući određene tipke tipkovnice." +#: lib/logitech_receiver/settings_templates.py:754 +msgid "Switch the current sensitivity and the remembered sensitivity when " + "the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current " + "sensitivity" +msgstr "Prebacivanje trenutne osjetljivosti i zapamćene osjetljivosti kada " + "se tipka pritisne.\n" + "Ako ne postoji zapamćena osjetljivost, zapamti trenutnu osjetljivost" -#: lib/logitech_receiver/settings_templates.py:783 +#: lib/logitech_receiver/settings_templates.py:758 +msgid "Off" +msgstr "Isključeno" + +#: lib/logitech_receiver/settings_templates.py:791 +msgid "Disable keys" +msgstr "Onemogući tipke" + +#: lib/logitech_receiver/settings_templates.py:792 +msgid "Disable specific keyboard keys." +msgstr "Onemogući određene tipke tipkovnice." + +#: lib/logitech_receiver/settings_templates.py:795 #, python-format -msgid "Disables the %s key." -msgstr "Onemogućuje %s tipku." +msgid "Disables the %s key." +msgstr "Onemogućuje %s tipku." -#: lib/logitech_receiver/settings_templates.py:796 -#: lib/logitech_receiver/settings_templates.py:844 -msgid "Set OS" -msgstr "Postavi OS" +#: lib/logitech_receiver/settings_templates.py:809 +#: lib/logitech_receiver/settings_templates.py:860 +msgid "Set OS" +msgstr "Postavi OS" -#: lib/logitech_receiver/settings_templates.py:797 -#: lib/logitech_receiver/settings_templates.py:845 -msgid "Change keys to match OS." -msgstr "Promijeni tipke tako da se podudaraju sa OS-om." +#: lib/logitech_receiver/settings_templates.py:810 +#: lib/logitech_receiver/settings_templates.py:861 +msgid "Change keys to match OS." +msgstr "Promijeni tipke tako da se podudaraju sa OS-om." -#: lib/logitech_receiver/settings_templates.py:857 -msgid "Change Host" -msgstr "Promijeni računalo" +#: lib/logitech_receiver/settings_templates.py:873 +msgid "Change Host" +msgstr "Promijeni računalo" -#: lib/logitech_receiver/settings_templates.py:858 -msgid "Switch connection to a different host" -msgstr "Prebaci povezivanje na drugo računalo" - -#: lib/logitech_receiver/settings_templates.py:883 -msgid "Performs a left click." -msgstr "Izvodi lijevi klik." - -#: lib/logitech_receiver/settings_templates.py:883 -msgid "Single tap" -msgstr "Jednostruki dodir" - -#: lib/logitech_receiver/settings_templates.py:884 -msgid "Performs a right click." -msgstr "Izvodi desni klik." - -#: lib/logitech_receiver/settings_templates.py:884 -msgid "Single tap with two fingers" -msgstr "Jednostruki dodir s dva prsta" - -#: lib/logitech_receiver/settings_templates.py:885 -msgid "Single tap with three fingers" -msgstr "Jednostruki dodir s tri prsta" - -#: lib/logitech_receiver/settings_templates.py:889 -msgid "Double tap" -msgstr "Dvostruki dodir" - -#: lib/logitech_receiver/settings_templates.py:889 -msgid "Performs a double click." -msgstr "Izvodi dvostruki klik." - -#: lib/logitech_receiver/settings_templates.py:890 -msgid "Double tap with two fingers" -msgstr "Dvostruki dodir s dva prsta" - -#: lib/logitech_receiver/settings_templates.py:891 -msgid "Double tap with three fingers" -msgstr "Dvostruki dodir s tri prsta" - -#: lib/logitech_receiver/settings_templates.py:894 -msgid "Drags items by dragging the finger after double tapping." -msgstr "Povlačite stavke povlačenjem prsta nakon dvostrukog dodira." - -#: lib/logitech_receiver/settings_templates.py:894 -msgid "Tap and drag" -msgstr "Dodirni i povuci" - -#: lib/logitech_receiver/settings_templates.py:896 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "Povlačite stavke povlačenjem prsta nakon dvostrukog dodira." - -#: lib/logitech_receiver/settings_templates.py:896 -msgid "Tap and drag with two fingers" -msgstr "Dodirni i povuci s dva prsta" - -#: lib/logitech_receiver/settings_templates.py:897 -msgid "Tap and drag with three fingers" -msgstr "Dodirni i povuci s tri prsta" +#: lib/logitech_receiver/settings_templates.py:874 +msgid "Switch connection to a different host" +msgstr "Prebaci povezivanje na drugo računalo" #: lib/logitech_receiver/settings_templates.py:900 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "" -"Onemogućuje dodir i rubne geste (ekvivalent pritiska na Fn+Lijevi klik)" +msgid "Performs a left click." +msgstr "Izvodi lijevi klik." #: lib/logitech_receiver/settings_templates.py:900 -msgid "Suppress tap and edge gestures" -msgstr "Onemogući dodir i rubne geste" +msgid "Single tap" +msgstr "Jednostruki dodir" #: lib/logitech_receiver/settings_templates.py:901 -msgid "Scroll with one finger" -msgstr "Pomicanje s jednim prstom" +msgid "Performs a right click." +msgstr "Izvodi desni klik." #: lib/logitech_receiver/settings_templates.py:901 -#: lib/logitech_receiver/settings_templates.py:902 -#: lib/logitech_receiver/settings_templates.py:905 -msgid "Scrolls." -msgstr "Pomicanje." +msgid "Single tap with two fingers" +msgstr "Jednostruki dodir s dva prsta" #: lib/logitech_receiver/settings_templates.py:902 -#: lib/logitech_receiver/settings_templates.py:905 -msgid "Scroll with two fingers" -msgstr "Pomicanje s dva prsta" - -#: lib/logitech_receiver/settings_templates.py:903 -msgid "Scroll horizontally with two fingers" -msgstr "Vodoravno pomicanje s dva prsta" - -#: lib/logitech_receiver/settings_templates.py:903 -msgid "Scrolls horizontally." -msgstr "Vodoravno pomicanje." - -#: lib/logitech_receiver/settings_templates.py:904 -msgid "Scroll vertically with two fingers" -msgstr "Okomito pomicanje s dva prsta" - -#: lib/logitech_receiver/settings_templates.py:904 -msgid "Scrolls vertically." -msgstr "Okomito pomicanje." +msgid "Single tap with three fingers" +msgstr "Jednostruki dodir s tri prsta" #: lib/logitech_receiver/settings_templates.py:906 -msgid "Inverts the scrolling direction." -msgstr "Obrnite smjer pomicanja." +msgid "Double tap" +msgstr "Dvostruki dodir" #: lib/logitech_receiver/settings_templates.py:906 -msgid "Natural scrolling" -msgstr "Prirodno pomicanje" +msgid "Performs a double click." +msgstr "Izvodi dvostruki klik." #: lib/logitech_receiver/settings_templates.py:907 -msgid "Enables the thumbwheel." -msgstr "Omogućite kotačić palca." +msgid "Double tap with two fingers" +msgstr "Dvostruki dodir s dva prsta" -#: lib/logitech_receiver/settings_templates.py:907 -msgid "Thumbwheel" -msgstr "Kotačić palca" +#: lib/logitech_receiver/settings_templates.py:908 +msgid "Double tap with three fingers" +msgstr "Dvostruki dodir s tri prsta" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Povlačite stavke povlačenjem prsta nakon dvostrukog dodira." + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Tap and drag" +msgstr "Dodirni i povuci" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Povlačite stavke povlačenjem prsta nakon dvostrukog dodira." + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Tap and drag with two fingers" +msgstr "Dodirni i povuci s dva prsta" + +#: lib/logitech_receiver/settings_templates.py:914 +msgid "Tap and drag with three fingers" +msgstr "Dodirni i povuci s tri prsta" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "Onemogućuje dodir i rubne geste (ekvivalent pritiska na Fn+Lijevi " + "klik)" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Suppress tap and edge gestures" +msgstr "Onemogući dodir i rubne geste" #: lib/logitech_receiver/settings_templates.py:918 +msgid "Scroll with one finger" +msgstr "Pomicanje s jednim prstom" + +#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:919 #: lib/logitech_receiver/settings_templates.py:922 -msgid "Swipe from the top edge" -msgstr "Prijeđite prstom od gornjeg ruba" +msgid "Scrolls." +msgstr "Pomicanje." #: lib/logitech_receiver/settings_templates.py:919 -msgid "Swipe from the left edge" -msgstr "Prijeđite prstom od lijevog ruba" +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scroll with two fingers" +msgstr "Pomicanje s dva prsta" #: lib/logitech_receiver/settings_templates.py:920 -msgid "Swipe from the right edge" -msgstr "Prijeđite prstom od desnog ruba" +msgid "Scroll horizontally with two fingers" +msgstr "Vodoravno pomicanje s dva prsta" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scrolls horizontally." +msgstr "Vodoravno pomicanje." #: lib/logitech_receiver/settings_templates.py:921 -msgid "Swipe from the bottom edge" -msgstr "Prijeđite prstom od donjeg ruba" +msgid "Scroll vertically with two fingers" +msgstr "Okomito pomicanje s dva prsta" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scrolls vertically." +msgstr "Okomito pomicanje." #: lib/logitech_receiver/settings_templates.py:923 -msgid "Swipe two fingers from the left edge" -msgstr "Prijeđite s dva prsta od lijevog ruba" +msgid "Inverts the scrolling direction." +msgstr "Obrnite smjer pomicanja." + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Natural scrolling" +msgstr "Prirodno pomicanje" #: lib/logitech_receiver/settings_templates.py:924 -msgid "Swipe two fingers from the right edge" -msgstr "Prijeđite s dva prsta od desnog ruba" +msgid "Enables the thumbwheel." +msgstr "Omogućite kotačić palca." -#: lib/logitech_receiver/settings_templates.py:925 -msgid "Swipe two fingers from the bottom edge" -msgstr "Prijeđite s dva prsta od donjeg ruba" +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Thumbwheel" +msgstr "Kotačić palca" -#: lib/logitech_receiver/settings_templates.py:926 -msgid "Swipe two fingers from the top edge" -msgstr "Prijeđite s dva prsta od gornjeg ruba" +#: lib/logitech_receiver/settings_templates.py:935 +#: lib/logitech_receiver/settings_templates.py:939 +msgid "Swipe from the top edge" +msgstr "Prijeđite prstom od gornjeg ruba" -#: lib/logitech_receiver/settings_templates.py:927 -#: lib/logitech_receiver/settings_templates.py:931 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "Skupite prste za smanjivanje; raširite prste za uvećanje." +#: lib/logitech_receiver/settings_templates.py:936 +msgid "Swipe from the left edge" +msgstr "Prijeđite prstom od lijevog ruba" -#: lib/logitech_receiver/settings_templates.py:927 -msgid "Zoom with two fingers." -msgstr "Uvećanje/Smanjenje s dva prsta." +#: lib/logitech_receiver/settings_templates.py:937 +msgid "Swipe from the right edge" +msgstr "Prijeđite prstom od desnog ruba" -#: lib/logitech_receiver/settings_templates.py:928 -msgid "Pinch to zoom out." -msgstr "Skupite prste za smanjivanje." +#: lib/logitech_receiver/settings_templates.py:938 +msgid "Swipe from the bottom edge" +msgstr "Prijeđite prstom od donjeg ruba" -#: lib/logitech_receiver/settings_templates.py:929 -msgid "Spread to zoom in." -msgstr "Raširite prste za uvećanje." +#: lib/logitech_receiver/settings_templates.py:940 +msgid "Swipe two fingers from the left edge" +msgstr "Prijeđite s dva prsta od lijevog ruba" -#: lib/logitech_receiver/settings_templates.py:930 -msgid "Zoom with three fingers." -msgstr "Uvećanje/Smanjenje s tri prsta." +#: lib/logitech_receiver/settings_templates.py:941 +msgid "Swipe two fingers from the right edge" +msgstr "Prijeđite s dva prsta od desnog ruba" -#: lib/logitech_receiver/settings_templates.py:931 -msgid "Zoom with two fingers" -msgstr "Uvećanje/Smanjenje s dva prsta" +#: lib/logitech_receiver/settings_templates.py:942 +msgid "Swipe two fingers from the bottom edge" +msgstr "Prijeđite s dva prsta od donjeg ruba" -#: lib/logitech_receiver/settings_templates.py:949 -msgid "Pixel zone" -msgstr "Zona piksela" +#: lib/logitech_receiver/settings_templates.py:943 +msgid "Swipe two fingers from the top edge" +msgstr "Prijeđite s dva prsta od gornjeg ruba" -#: lib/logitech_receiver/settings_templates.py:950 -msgid "Ratio zone" -msgstr "Zona omjera" +#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "Skupite prste za smanjivanje; raširite prste za uvećanje." -#: lib/logitech_receiver/settings_templates.py:951 -msgid "Scale factor" -msgstr "Faktor veličine" +#: lib/logitech_receiver/settings_templates.py:944 +msgid "Zoom with two fingers." +msgstr "Uvećanje/Smanjenje s dva prsta." -#: lib/logitech_receiver/settings_templates.py:951 -msgid "Sets the cursor speed." -msgstr "Postavlja brzinu pokazivača." +#: lib/logitech_receiver/settings_templates.py:945 +msgid "Pinch to zoom out." +msgstr "Skupite prste za smanjivanje." -#: lib/logitech_receiver/settings_templates.py:955 -msgid "Left" -msgstr "Lijevo" +#: lib/logitech_receiver/settings_templates.py:946 +msgid "Spread to zoom in." +msgstr "Raširite prste za uvećanje." -#: lib/logitech_receiver/settings_templates.py:955 -msgid "Left-most coordinate." -msgstr "Najčešća lijeva koordinata." +#: lib/logitech_receiver/settings_templates.py:947 +msgid "Zoom with three fingers." +msgstr "Uvećanje/Smanjenje s tri prsta." -#: lib/logitech_receiver/settings_templates.py:956 -msgid "Bottom" -msgstr "Dno" - -#: lib/logitech_receiver/settings_templates.py:956 -msgid "Bottom coordinate." -msgstr "Koordinata na dnu." - -#: lib/logitech_receiver/settings_templates.py:957 -msgid "Width" -msgstr "Širina" - -#: lib/logitech_receiver/settings_templates.py:957 -msgid "Width." -msgstr "Širina." - -#: lib/logitech_receiver/settings_templates.py:958 -msgid "Height" -msgstr "Visina" - -#: lib/logitech_receiver/settings_templates.py:958 -msgid "Height." -msgstr "Visina." - -#: lib/logitech_receiver/settings_templates.py:959 -msgid "Cursor speed." -msgstr "Brzina pokazivača." - -#: lib/logitech_receiver/settings_templates.py:959 -msgid "Scale" -msgstr "Prilagodba veličine" - -#: lib/logitech_receiver/settings_templates.py:965 -msgid "Gestures" -msgstr "Geste" +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Zoom with two fingers" +msgstr "Uvećanje/Smanjenje s dva prsta" #: lib/logitech_receiver/settings_templates.py:966 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "Prilagodite ponašanje miša/touchpada." +msgid "Pixel zone" +msgstr "Zona piksela" + +#: lib/logitech_receiver/settings_templates.py:967 +msgid "Ratio zone" +msgstr "Zona omjera" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Scale factor" +msgstr "Faktor veličine" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Sets the cursor speed." +msgstr "Postavlja brzinu pokazivača." + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left" +msgstr "Lijevo" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left-most coordinate." +msgstr "Najčešća lijeva koordinata." + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom" +msgstr "Dno" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom coordinate." +msgstr "Koordinata na dnu." + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width" +msgstr "Širina" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width." +msgstr "Širina." + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height" +msgstr "Visina" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height." +msgstr "Visina." + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Cursor speed." +msgstr "Brzina pokazivača." + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Scale" +msgstr "Prilagodba veličine" #: lib/logitech_receiver/settings_templates.py:982 -msgid "Gestures Diversion" -msgstr "Preusmjeravanje gesta" +msgid "Gestures" +msgstr "Geste" #: lib/logitech_receiver/settings_templates.py:983 -msgid "Divert mouse/touchpad gestures." -msgstr "Preusmjerite ponašanje miša/touchpada." - -#: lib/logitech_receiver/settings_templates.py:999 -msgid "Gesture params" -msgstr "Parametri gesta" +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Prilagodite ponašanje miša/touchpada." #: lib/logitech_receiver/settings_templates.py:1000 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "Promijenite brojčane parametre miša/touchpada." +msgid "Gestures Diversion" +msgstr "Preusmjeravanje gesta" -#: lib/logitech_receiver/settings_templates.py:1024 -msgid "M-Key LEDs" -msgstr "LED osvjetljenje M-tipke" +#: lib/logitech_receiver/settings_templates.py:1001 +msgid "Divert mouse/touchpad gestures." +msgstr "Preusmjerite ponašanje miša/touchpada." -#: lib/logitech_receiver/settings_templates.py:1026 -msgid "Control the M-Key LEDs." -msgstr "Upravljajte LED osvjetljenjem M-tipke." +#: lib/logitech_receiver/settings_templates.py:1018 +msgid "Gesture params" +msgstr "Parametri gesta" -#: lib/logitech_receiver/settings_templates.py:1027 -#: lib/logitech_receiver/settings_templates.py:1055 -msgid "May need G Keys diverted to be effective." -msgstr "Možda je potrebno preusmjeravanje G tipka kako bi bile učinkovite." +#: lib/logitech_receiver/settings_templates.py:1019 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Promijenite brojčane parametre miša/touchpada." -#: lib/logitech_receiver/settings_templates.py:1033 +#: lib/logitech_receiver/settings_templates.py:1044 +msgid "M-Key LEDs" +msgstr "LED osvjetljenje M-tipke" + +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Control the M-Key LEDs." +msgstr "Upravljajte LED osvjetljenjem M-tipke." + +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1077 +msgid "May need G Keys diverted to be effective." +msgstr "Možda je potrebno preusmjeravanje G tipka kako bi bile učinkovite." + +#: lib/logitech_receiver/settings_templates.py:1053 #, python-format -msgid "Lights up the %s key." -msgstr "Osvijetli %s tipku." - -#: lib/logitech_receiver/settings_templates.py:1052 -msgid "MR-Key LED" -msgstr "LED osvjetljenje MR-tipke" - -#: lib/logitech_receiver/settings_templates.py:1054 -msgid "Control the MR-Key LED." -msgstr "Upravljajte LED osvjetljenjem MR-tipke." - -#: lib/logitech_receiver/settings_templates.py:1072 -msgid "Persistent Key/Button Mapping" -msgstr "Trajno mapiranje tipka" +msgid "Lights up the %s key." +msgstr "Osvijetli %s tipku." #: lib/logitech_receiver/settings_templates.py:1074 -msgid "Permanently change the mapping for the key or button." -msgstr "Trajno promijenite mapiranje za tipku." +msgid "MR-Key LED" +msgstr "LED osvjetljenje MR-tipke" -#: lib/logitech_receiver/settings_templates.py:1075 -msgid "" -"Changing important keys or buttons (such as for the left mouse button) can " -"result in an unusable system." -msgstr "" -"Promjena bitnih tipka (poput lijeve tipke miša) može prouzrokovati " -"nestabilnosti u sustavu." +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "Control the MR-Key LED." +msgstr "Upravljajte LED osvjetljenjem MR-tipke." -#: lib/logitech_receiver/settings_templates.py:1132 -msgid "Sidetone" -msgstr "Bočni ton" +#: lib/logitech_receiver/settings_templates.py:1095 +msgid "Persistent Key/Button Mapping" +msgstr "Trajno mapiranje tipka" -#: lib/logitech_receiver/settings_templates.py:1133 -msgid "Set sidetone level." -msgstr "Postavi razinu bočnog tona." +#: lib/logitech_receiver/settings_templates.py:1097 +msgid "Permanently change the mapping for the key or button." +msgstr "Trajno promijenite mapiranje za tipku." -#: lib/logitech_receiver/settings_templates.py:1142 -msgid "Equalizer" -msgstr "Ekvalizator" +#: lib/logitech_receiver/settings_templates.py:1098 +msgid "Changing important keys or buttons (such as for the left mouse " + "button) can result in an unusable system." +msgstr "Promjena bitnih tipka (poput lijeve tipke miša) može prouzrokovati " + "nestabilnosti u sustavu." -#: lib/logitech_receiver/settings_templates.py:1143 -msgid "Set equalizer levels." -msgstr "Postavi razine ekvilizatora." +#: lib/logitech_receiver/settings_templates.py:1157 +msgid "Sidetone" +msgstr "Bočni ton" -#: lib/logitech_receiver/settings_templates.py:1165 -msgid "Hz" -msgstr "Hz" +#: lib/logitech_receiver/settings_templates.py:1158 +msgid "Set sidetone level." +msgstr "Postavi razinu bočnog tona." -#: lib/logitech_receiver/status.py:113 -msgid "No paired devices." -msgstr "Nema uparenih uređaja." +#: lib/logitech_receiver/settings_templates.py:1167 +msgid "Equalizer" +msgstr "Ekvalizator" -#: lib/logitech_receiver/status.py:114 lib/solaar/ui/window.py:620 +#: lib/logitech_receiver/settings_templates.py:1168 +msgid "Set equalizer levels." +msgstr "Postavi razine ekvilizatora." + +#: lib/logitech_receiver/settings_templates.py:1191 +msgid "Hz" +msgstr "Hz" + +#: lib/logitech_receiver/settings_templates.py:1197 +msgid "Power Management" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1198 +msgid "Power off in minutes (0 for never)." +msgstr "" + +#: lib/logitech_receiver/status.py:114 +msgid "No paired devices." +msgstr "Nema uparenih uređaja." + +#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 #, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s upareni uređaj." -msgstr[1] "%(count)s uparena uređaja." -msgstr[2] "%(count)s uparenih uređaj." +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s upareni uređaj." +msgstr[1] "%(count)s uparena uređaja." +msgstr[2] "%(count)s uparenih uređaj." -#: lib/logitech_receiver/status.py:167 +#: lib/logitech_receiver/status.py:170 #, python-format -msgid "Battery: %(level)s" -msgstr "Baterija: %(level)s" +msgid "Battery: %(level)s" +msgstr "Baterija: %(level)s" -#: lib/logitech_receiver/status.py:169 +#: lib/logitech_receiver/status.py:172 #, python-format -msgid "Battery: %(percent)d%%" -msgstr "Baterija: %(percent)d%%" +msgid "Battery: %(percent)d%%" +msgstr "Baterija: %(percent)d%%" -#: lib/logitech_receiver/status.py:181 +#: lib/logitech_receiver/status.py:184 #, python-format -msgid "Lighting: %(level)s lux" -msgstr "Osvjetljenje: %(level)s lux" +msgid "Lighting: %(level)s lux" +msgstr "Osvjetljenje: %(level)s lux" -#: lib/logitech_receiver/status.py:236 +#: lib/logitech_receiver/status.py:239 #, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "Baterija: %(level)s (%(status)s)" +msgid "Battery: %(level)s (%(status)s)" +msgstr "Baterija: %(level)s (%(status)s)" -#: lib/logitech_receiver/status.py:238 +#: lib/logitech_receiver/status.py:241 #, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "Baterija: %(percent)d%% (%(status)s)" +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Baterija: %(percent)d%% (%(status)s)" -#: lib/solaar/ui/__init__.py:51 -msgid "Permissions error" -msgstr "Greška dozvole" - -#: lib/solaar/ui/__init__.py:53 -#, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open it." -msgstr "Pronađen je (%s) Logitech Receiver, ali nemate ovlasti za otvoriti ga." +#: lib/solaar/ui/__init__.py:52 +msgid "Permissions error" +msgstr "Greška dozvole" #: lib/solaar/ui/__init__.py:54 -msgid "" -"If you've just installed Solaar, try removing the receiver and plugging it " -"back in." -msgstr "" -"Ako ste upravo instalirali Solaar, pokušajte odspojiti prijemnik i ponovno " -"ga spojiti." - -#: lib/solaar/ui/__init__.py:57 -msgid "Cannot connect to device error" -msgstr "Nemoguće povezivanje s uređajem, greška" - -#: lib/solaar/ui/__init__.py:59 #, python-format -msgid "" -"Found a Logitech receiver or device at %s, but encountered an error " -"connecting to it." -msgstr "" -"Pronađen je Logitech prijemnik ili uređaj na %s, ali je naišao na grešku pri " -"povezivanju." +msgid "Found a Logitech receiver or device (%s), but did not have " + "permission to open it." +msgstr "" + +#: lib/solaar/ui/__init__.py:55 +msgid "If you've just installed Solaar, try disconnecting the receiver or " + "device and then reconnecting it." +msgstr "" + +#: lib/solaar/ui/__init__.py:58 +msgid "Cannot connect to device error" +msgstr "Nemoguće povezivanje s uređajem, greška" #: lib/solaar/ui/__init__.py:60 -msgid "" -"Try removing the device and plugging it back in or turning it off and then " -"on." -msgstr "" -"Pokušajte uređaj ukloniti pa ponovno priključiti s računalom ili ga " -"isključiti pa ponovno uključiti." +#, python-format +msgid "Found a Logitech receiver or device at %s, but encountered an error " + "connecting to it." +msgstr "Pronađen je Logitech prijemnik ili uređaj na %s, ali je naišao na " + "grešku pri povezivanju." -#: lib/solaar/ui/__init__.py:63 -msgid "Unpairing failed" -msgstr "Uparivanje neuspjelo" +#: lib/solaar/ui/__init__.py:61 +msgid "Try disconnecting the device and then reconnecting it or turning it " + "off and then on." +msgstr "" -#: lib/solaar/ui/__init__.py:65 -#, python-brace-format -msgid "Failed to unpair %{device} from %{receiver}." -msgstr "Neuspjelo uparivanje %{device} s %{receiver}." +#: lib/solaar/ui/__init__.py:64 +msgid "Unpairing failed" +msgstr "Uparivanje neuspjelo" #: lib/solaar/ui/__init__.py:66 -msgid "The receiver returned an error, with no further details." -msgstr "Došlo je do greške kod prijemnika, bez više pojedinosti." +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Neuspjelo uparivanje %{device} s %{receiver}." -#: lib/solaar/ui/__init__.py:176 -msgid "Another Solaar process is already running so just expose its window" -msgstr "Drugi Solaar proces je već pokrenut stoga samo prikaži njegov prozor" +#: lib/solaar/ui/__init__.py:67 +msgid "The receiver returned an error, with no further details." +msgstr "Došlo je do greške kod prijemnika, bez više pojedinosti." + +#: lib/solaar/ui/__init__.py:177 +msgid "Another Solaar process is already running so just expose its window" +msgstr "Drugi Solaar proces je već pokrenut stoga samo prikaži njegov prozor" #: lib/solaar/ui/about.py:36 -msgid "" -"Manages Logitech receivers,\n" -"keyboards, mice, and tablets." -msgstr "" -"Upravljajte Logitechovim prijemnicima,\n" -"tipkovnicama, miševima, i tabletima." +msgid "Manages Logitech receivers,\n" + "keyboards, mice, and tablets." +msgstr "Upravljajte Logitechovim prijemnicima,\n" + "tipkovnicama, miševima, i tabletima." #: lib/solaar/ui/about.py:44 -msgid "Additional Programming" -msgstr "Dodatno programiranje" +msgid "Additional Programming" +msgstr "Dodatno programiranje" #: lib/solaar/ui/about.py:45 -msgid "GUI design" -msgstr "GUI dizajn" +msgid "GUI design" +msgstr "GUI dizajn" #: lib/solaar/ui/about.py:47 -msgid "Testing" -msgstr "Testiranje" +msgid "Testing" +msgstr "Testiranje" #: lib/solaar/ui/about.py:54 -msgid "Logitech documentation" -msgstr "Logitech dokumentacija" +msgid "Logitech documentation" +msgstr "Logitech dokumentacija" #: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 -#: lib/solaar/ui/window.py:202 -msgid "Unpair" -msgstr "Odpari" +#: lib/solaar/ui/window.py:197 +msgid "Unpair" +msgstr "Odpari" -#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:148 -msgid "Cancel" -msgstr "Odustani" +#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 +msgid "Cancel" +msgstr "Odustani" -#: lib/solaar/ui/config_panel.py:205 -msgid "Complete - ENTER to change" -msgstr "Potpuno - ENTER za promjenu" +#: lib/solaar/ui/config_panel.py:212 +msgid "Complete - ENTER to change" +msgstr "Potpuno - ENTER za promjenu" -#: lib/solaar/ui/config_panel.py:205 -msgid "Incomplete" -msgstr "Nepotpuno" +#: lib/solaar/ui/config_panel.py:212 +msgid "Incomplete" +msgstr "Nepotpuno" -#: lib/solaar/ui/config_panel.py:444 lib/solaar/ui/config_panel.py:495 +#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 #, python-format -msgid "%d value" -msgid_plural "%d values" -msgstr[0] "%d vrijednost" -msgstr[1] "%d vrijednosti" -msgstr[2] "%d vrijednosti" +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d vrijednost" +msgstr[1] "%d vrijednosti" +msgstr[2] "%d vrijednosti" -#: lib/solaar/ui/config_panel.py:506 -msgid "Changes allowed" -msgstr "Promjene su dopuštene" +#: lib/solaar/ui/config_panel.py:518 +msgid "Changes allowed" +msgstr "Promjene su dopuštene" -#: lib/solaar/ui/config_panel.py:507 -msgid "No changes allowed" -msgstr "Promjene nisu dopuštene" +#: lib/solaar/ui/config_panel.py:519 +msgid "No changes allowed" +msgstr "Promjene nisu dopuštene" -#: lib/solaar/ui/config_panel.py:508 -msgid "Ignore this setting" -msgstr "Zanemari ovu postavku" +#: lib/solaar/ui/config_panel.py:520 +msgid "Ignore this setting" +msgstr "Zanemari ovu postavku" -#: lib/solaar/ui/config_panel.py:553 -msgid "Working" -msgstr "Izvodi se" +#: lib/solaar/ui/config_panel.py:565 +msgid "Working" +msgstr "Izvodi se" -#: lib/solaar/ui/config_panel.py:556 -msgid "Read/write operation failed." -msgstr "Radnja čitanja/pisanja neuspjela." +#: lib/solaar/ui/config_panel.py:568 +msgid "Read/write operation failed." +msgstr "Radnja čitanja/pisanja neuspjela." -#: lib/solaar/ui/diversion_rules.py:64 -msgid "Built-in rules" -msgstr "Ugrađena pravila" +#: lib/solaar/ui/diversion_rules.py:65 +msgid "Built-in rules" +msgstr "Ugrađena pravila" -#: lib/solaar/ui/diversion_rules.py:64 -msgid "User-defined rules" -msgstr "Korisnički određena pravila" +#: lib/solaar/ui/diversion_rules.py:65 +msgid "User-defined rules" +msgstr "Korisnički određena pravila" -#: lib/solaar/ui/diversion_rules.py:66 lib/solaar/ui/diversion_rules.py:1075 -msgid "Rule" -msgstr "Pravilo" +#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 +msgid "Rule" +msgstr "Pravilo" -#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:507 -#: lib/solaar/ui/diversion_rules.py:631 -msgid "Sub-rule" -msgstr "Podpravilo" +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 +#: lib/solaar/ui/diversion_rules.py:636 +msgid "Sub-rule" +msgstr "Podpravilo" -#: lib/solaar/ui/diversion_rules.py:69 -msgid "[empty]" -msgstr "[prazno]" +#: lib/solaar/ui/diversion_rules.py:70 +msgid "[empty]" +msgstr "[prazno]" -#: lib/solaar/ui/diversion_rules.py:92 -msgid "Solaar Rule Editor" -msgstr "Solaar uređivač pravila" +#: lib/solaar/ui/diversion_rules.py:94 +msgid "Solaar Rule Editor" +msgstr "Solaar uređivač pravila" -#: lib/solaar/ui/diversion_rules.py:139 -msgid "Make changes permanent?" -msgstr "Učini promjene trajnima?" - -#: lib/solaar/ui/diversion_rules.py:144 -msgid "Yes" -msgstr "Da" +#: lib/solaar/ui/diversion_rules.py:141 +msgid "Make changes permanent?" +msgstr "Učini promjene trajnima?" #: lib/solaar/ui/diversion_rules.py:146 -msgid "No" -msgstr "Ne" +msgid "Yes" +msgstr "Da" -#: lib/solaar/ui/diversion_rules.py:151 -msgid "If you choose No, changes will be lost when Solaar is closed." -msgstr "" -"Ako odaberete 'Ne', promjene će biti izgubljene kada se Solaar zatvori." +#: lib/solaar/ui/diversion_rules.py:148 +msgid "No" +msgstr "Ne" -#: lib/solaar/ui/diversion_rules.py:199 -msgid "Save changes" -msgstr "Spremi promjene" +#: lib/solaar/ui/diversion_rules.py:153 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "Ako odaberete 'Ne', promjene će biti izgubljene kada se Solaar " + "zatvori." -#: lib/solaar/ui/diversion_rules.py:204 -msgid "Discard changes" -msgstr "Odbaci promjene" +#: lib/solaar/ui/diversion_rules.py:201 +msgid "Save changes" +msgstr "Spremi promjene" -#: lib/solaar/ui/diversion_rules.py:370 -msgid "Insert here" -msgstr "Umetni ovdje" +#: lib/solaar/ui/diversion_rules.py:206 +msgid "Discard changes" +msgstr "Odbaci promjene" #: lib/solaar/ui/diversion_rules.py:372 -msgid "Insert above" -msgstr "Umetni iznad" +msgid "Insert here" +msgstr "Umetni ovdje" #: lib/solaar/ui/diversion_rules.py:374 -msgid "Insert below" -msgstr "Umetni ispod" +msgid "Insert above" +msgstr "Umetni iznad" -#: lib/solaar/ui/diversion_rules.py:380 -msgid "Insert new rule here" -msgstr "Umetni novo pravilo ovdje" +#: lib/solaar/ui/diversion_rules.py:376 +msgid "Insert below" +msgstr "Umetni ispod" #: lib/solaar/ui/diversion_rules.py:382 -msgid "Insert new rule above" -msgstr "Umetni novo pravilo iznad" +msgid "Insert new rule here" +msgstr "Umetni novo pravilo ovdje" #: lib/solaar/ui/diversion_rules.py:384 -msgid "Insert new rule below" -msgstr "Umetni novo pravilo ispod" +msgid "Insert new rule above" +msgstr "Umetni novo pravilo iznad" -#: lib/solaar/ui/diversion_rules.py:425 -msgid "Paste here" -msgstr "Zalijepi ovdje" +#: lib/solaar/ui/diversion_rules.py:386 +msgid "Insert new rule below" +msgstr "Umetni novo pravilo ispod" #: lib/solaar/ui/diversion_rules.py:427 -msgid "Paste above" -msgstr "Zalijepi iznad" +msgid "Paste here" +msgstr "Zalijepi ovdje" #: lib/solaar/ui/diversion_rules.py:429 -msgid "Paste below" -msgstr "Zalijepi ispod" +msgid "Paste above" +msgstr "Zalijepi iznad" -#: lib/solaar/ui/diversion_rules.py:435 -msgid "Paste rule here" -msgstr "Zalijepi pravilo ovdje" +#: lib/solaar/ui/diversion_rules.py:431 +msgid "Paste below" +msgstr "Zalijepi ispod" #: lib/solaar/ui/diversion_rules.py:437 -msgid "Paste rule above" -msgstr "Zalijepi pravilo iznad" +msgid "Paste rule here" +msgstr "Zalijepi pravilo ovdje" #: lib/solaar/ui/diversion_rules.py:439 -msgid "Paste rule below" -msgstr "Zalijepi pravilo ispod" +msgid "Paste rule above" +msgstr "Zalijepi pravilo iznad" -#: lib/solaar/ui/diversion_rules.py:443 -msgid "Paste rule" -msgstr "Zalijepi pravilo" +#: lib/solaar/ui/diversion_rules.py:441 +msgid "Paste rule below" +msgstr "Zalijepi pravilo ispod" -#: lib/solaar/ui/diversion_rules.py:472 -msgid "Flatten" -msgstr "Poravnaj" +#: lib/solaar/ui/diversion_rules.py:445 +msgid "Paste rule" +msgstr "Zalijepi pravilo" -#: lib/solaar/ui/diversion_rules.py:505 -msgid "Insert" -msgstr "Umetni" +#: lib/solaar/ui/diversion_rules.py:474 +msgid "Flatten" +msgstr "Poravnaj" -#: lib/solaar/ui/diversion_rules.py:508 lib/solaar/ui/diversion_rules.py:633 -#: lib/solaar/ui/diversion_rules.py:1118 -msgid "Or" -msgstr "Ili" +#: lib/solaar/ui/diversion_rules.py:507 +msgid "Insert" +msgstr "Umetni" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:632 -#: lib/solaar/ui/diversion_rules.py:1103 -msgid "And" -msgstr "I" +#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 +#: lib/solaar/ui/diversion_rules.py:1126 +msgid "Or" +msgstr "Ili" -#: lib/solaar/ui/diversion_rules.py:511 -msgid "Condition" -msgstr "Stanje" +#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 +#: lib/solaar/ui/diversion_rules.py:1111 +msgid "And" +msgstr "I" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1284 -msgid "Feature" -msgstr "Značajka" +#: lib/solaar/ui/diversion_rules.py:513 +msgid "Condition" +msgstr "Stanje" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1320 -msgid "Report" -msgstr "Prijavak" +#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 +msgid "Feature" +msgstr "Značajka" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1196 -msgid "Process" -msgstr "Proces" +#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 +msgid "Report" +msgstr "Prijavak" -#: lib/solaar/ui/diversion_rules.py:516 -msgid "Mouse process" -msgstr "Proces miša" +#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 +msgid "Process" +msgstr "Proces" -#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1358 -msgid "Modifiers" -msgstr "Tipkovnički izmjenjivači" +#: lib/solaar/ui/diversion_rules.py:518 +msgid "Mouse process" +msgstr "Proces miša" -#: lib/solaar/ui/diversion_rules.py:518 lib/solaar/ui/diversion_rules.py:1411 -msgid "Key" -msgstr "Ključ" +#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 +msgid "Modifiers" +msgstr "Tipkovnički izmjenjivači" -#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:2177 -msgid "Active" -msgstr "Aktivan" +#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 +msgid "Key" +msgstr "Ključ" -#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:2220 -msgid "Setting" -msgstr "Postavka" +#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 +msgid "KeyIsDown" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1459 -msgid "Test" -msgstr "Test" +#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 +msgid "Active" +msgstr "Aktivan" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1576 -msgid "Test bytes" -msgstr "Testni bajtovi" +#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2218 +#: lib/solaar/ui/diversion_rules.py:2270 lib/solaar/ui/diversion_rules.py:2323 +msgid "Device" +msgstr "Uređaj" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1669 -msgid "Mouse Gesture" -msgstr "Gesta miša" +#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +msgid "Host" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:527 -msgid "Action" -msgstr "Radnja" +#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 +msgid "Setting" +msgstr "Postavka" -#: lib/solaar/ui/diversion_rules.py:529 lib/solaar/ui/diversion_rules.py:1778 -msgid "Key press" -msgstr "Pritisak tipke" +#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 +#: lib/solaar/ui/diversion_rules.py:1526 +msgid "Test" +msgstr "Test" -#: lib/solaar/ui/diversion_rules.py:530 lib/solaar/ui/diversion_rules.py:1831 -msgid "Mouse scroll" -msgstr "Pomicanje kotačića" +#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 +msgid "Test bytes" +msgstr "Testni bajtovi" -#: lib/solaar/ui/diversion_rules.py:531 lib/solaar/ui/diversion_rules.py:1882 -msgid "Mouse click" -msgstr "Klik kotačića" +#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 +msgid "Mouse Gesture" +msgstr "Gesta miša" #: lib/solaar/ui/diversion_rules.py:532 -msgid "Set" -msgstr "Postavka" +msgid "Action" +msgstr "Radnja" -#: lib/solaar/ui/diversion_rules.py:533 lib/solaar/ui/diversion_rules.py:1953 -msgid "Execute" -msgstr "Pokretanje" +#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 +msgid "Key press" +msgstr "Pritisak tipke" -#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1150 -msgid "Later" -msgstr "Odgoda" +#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 +msgid "Mouse scroll" +msgstr "Pomicanje kotačića" -#: lib/solaar/ui/diversion_rules.py:563 -msgid "Insert new rule" -msgstr "Umetni novo pravilo" +#: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 +msgid "Mouse click" +msgstr "Klik kotačića" -#: lib/solaar/ui/diversion_rules.py:583 lib/solaar/ui/diversion_rules.py:1619 -#: lib/solaar/ui/diversion_rules.py:1723 lib/solaar/ui/diversion_rules.py:1912 -msgid "Delete" -msgstr "Obriši" +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Set" +msgstr "Postavka" -#: lib/solaar/ui/diversion_rules.py:605 -msgid "Negate" -msgstr "Negacija" +#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 +msgid "Execute" +msgstr "Pokretanje" -#: lib/solaar/ui/diversion_rules.py:629 -msgid "Wrap with" -msgstr "Umetni iznad" +#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 +msgid "Later" +msgstr "Odgoda" -#: lib/solaar/ui/diversion_rules.py:651 -msgid "Cut" -msgstr "Izreži" +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Insert new rule" +msgstr "Umetni novo pravilo" -#: lib/solaar/ui/diversion_rules.py:666 -msgid "Paste" -msgstr "Zalijepi" +#: lib/solaar/ui/diversion_rules.py:588 lib/solaar/ui/diversion_rules.py:1686 +#: lib/solaar/ui/diversion_rules.py:1790 lib/solaar/ui/diversion_rules.py:1989 +msgid "Delete" +msgstr "Obriši" -#: lib/solaar/ui/diversion_rules.py:678 -msgid "Copy" -msgstr "Kopiraj" +#: lib/solaar/ui/diversion_rules.py:610 +msgid "Negate" +msgstr "Negacija" -#: lib/solaar/ui/diversion_rules.py:1055 -msgid "This editor does not support the selected rule component yet." -msgstr "Ovaj uređivač još ne podržava komponentu odabira pravila." +#: lib/solaar/ui/diversion_rules.py:634 +msgid "Wrap with" +msgstr "Umetni iznad" -#: lib/solaar/ui/diversion_rules.py:1130 -msgid "Number of seconds to delay." -msgstr "Broj sekundi odgode." +#: lib/solaar/ui/diversion_rules.py:656 +msgid "Cut" +msgstr "Izreži" -#: lib/solaar/ui/diversion_rules.py:1169 -msgid "Not" -msgstr "Ne" +#: lib/solaar/ui/diversion_rules.py:671 +msgid "Paste" +msgstr "Zalijepi" -#: lib/solaar/ui/diversion_rules.py:1179 -msgid "X11 active process. For use in X11 only." -msgstr "X11 aktivni proces. Samo za korištenje u X11." +#: lib/solaar/ui/diversion_rules.py:683 +msgid "Copy" +msgstr "Kopiraj" -#: lib/solaar/ui/diversion_rules.py:1210 -msgid "X11 mouse process. For use in X11 only." -msgstr "X11 proces miša. Samo za korištenje u X11." +#: lib/solaar/ui/diversion_rules.py:1063 +msgid "This editor does not support the selected rule component yet." +msgstr "Ovaj uređivač još ne podržava komponentu odabira pravila." -#: lib/solaar/ui/diversion_rules.py:1227 -msgid "MouseProcess" -msgstr "Proces miša" +#: lib/solaar/ui/diversion_rules.py:1138 +msgid "Number of seconds to delay." +msgstr "Broj sekundi odgode." -#: lib/solaar/ui/diversion_rules.py:1252 -msgid "Feature name of notification triggering rule processing." -msgstr "Naziv značajke obrade pravila za pokretanje obavijesti." +#: lib/solaar/ui/diversion_rules.py:1177 +msgid "Not" +msgstr "Ne" -#: lib/solaar/ui/diversion_rules.py:1300 -msgid "Report number of notification triggering rule processing." -msgstr "Prijavi broj obrade pravila za pokretanje obavijesti." +#: lib/solaar/ui/diversion_rules.py:1187 +msgid "X11 active process. For use in X11 only." +msgstr "X11 aktivni proces. Samo za korištenje u X11." -#: lib/solaar/ui/diversion_rules.py:1334 -msgid "Active keyboard modifiers. Not always available in Wayland." -msgstr "Aktivni tipkovnički izmjenjivači. Nije uvijek dostupno u Waylandu." +#: lib/solaar/ui/diversion_rules.py:1218 +msgid "X11 mouse process. For use in X11 only." +msgstr "X11 proces miša. Samo za korištenje u X11." -#: lib/solaar/ui/diversion_rules.py:1375 -msgid "" -"Diverted key or button depressed or released.\n" -"Use the Key/Button Diversion setting to divert keys and buttons." -msgstr "" -"Preusmjerena tipka pritisnuta ili otpuštena.\n" -"Koristite postavku Preusmjeravanje tipke za preusmjeravanje tipki." +#: lib/solaar/ui/diversion_rules.py:1235 +msgid "MouseProcess" +msgstr "Proces miša" -#: lib/solaar/ui/diversion_rules.py:1384 -msgid "Key down" -msgstr "Tipka za dolje" +#: lib/solaar/ui/diversion_rules.py:1260 +msgid "Feature name of notification triggering rule processing." +msgstr "Naziv značajke obrade pravila za pokretanje obavijesti." -#: lib/solaar/ui/diversion_rules.py:1387 -msgid "Key up" -msgstr "Tipka za gore" +#: lib/solaar/ui/diversion_rules.py:1308 +msgid "Report number of notification triggering rule processing." +msgstr "Prijavi broj obrade pravila za pokretanje obavijesti." -#: lib/solaar/ui/diversion_rules.py:1425 -msgid "Test condition on notification triggering rule processing." -msgstr "Uvjet testiranja za obradu pravila pokretanja obavijesti." +#: lib/solaar/ui/diversion_rules.py:1342 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "Aktivni tipkovnički izmjenjivači. Nije uvijek dostupno u Waylandu." + +#: lib/solaar/ui/diversion_rules.py:1383 +msgid "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1392 +msgid "Key down" +msgstr "Tipka za dolje" + +#: lib/solaar/ui/diversion_rules.py:1395 +msgid "Key up" +msgstr "Tipka za gore" + +#: lib/solaar/ui/diversion_rules.py:1436 +msgid "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" #: lib/solaar/ui/diversion_rules.py:1475 -msgid "begin (inclusive)" -msgstr "početak (uključiv)" +msgid "Test condition on notification triggering rule processing." +msgstr "Uvjet testiranja za obradu pravila pokretanja obavijesti." -#: lib/solaar/ui/diversion_rules.py:1476 -msgid "end (exclusive)" -msgstr "završetak (isključiv)" +#: lib/solaar/ui/diversion_rules.py:1479 +msgid "Parameter" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1485 -msgid "range" -msgstr "raspon" +#: lib/solaar/ui/diversion_rules.py:1542 +msgid "begin (inclusive)" +msgstr "početak (uključiv)" -#: lib/solaar/ui/diversion_rules.py:1487 -msgid "minimum" -msgstr "minimum" +#: lib/solaar/ui/diversion_rules.py:1543 +msgid "end (exclusive)" +msgstr "završetak (isključiv)" -#: lib/solaar/ui/diversion_rules.py:1488 -msgid "maximum" -msgstr "maksimum" +#: lib/solaar/ui/diversion_rules.py:1552 +msgid "range" +msgstr "raspon" -#: lib/solaar/ui/diversion_rules.py:1490 +#: lib/solaar/ui/diversion_rules.py:1554 +msgid "minimum" +msgstr "minimum" + +#: lib/solaar/ui/diversion_rules.py:1555 +msgid "maximum" +msgstr "maksimum" + +#: lib/solaar/ui/diversion_rules.py:1557 #, python-format -msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" -msgstr "bajtovi %(0)d do %(1)d, u rasponu od %(2)d do %(3)d" +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "bajtovi %(0)d do %(1)d, u rasponu od %(2)d do %(3)d" -#: lib/solaar/ui/diversion_rules.py:1495 -msgid "mask" -msgstr "maska" +#: lib/solaar/ui/diversion_rules.py:1562 +msgid "mask" +msgstr "maska" -#: lib/solaar/ui/diversion_rules.py:1496 +#: lib/solaar/ui/diversion_rules.py:1563 #, python-format -msgid "bytes %(0)d to %(1)d, mask %(2)d" -msgstr "bajtovi %(0)d to %(1)d, maska %(2)d" +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "bajtovi %(0)d to %(1)d, maska %(2)d" -#: lib/solaar/ui/diversion_rules.py:1506 -msgid "" -"Bit or range test on bytes in notification message triggering rule " -"processing." -msgstr "" -"Test bita ili raspona na bajtovima u poruci obavijesti koja pokreće obradu " -"pravila." +#: lib/solaar/ui/diversion_rules.py:1573 +msgid "Bit or range test on bytes in notification message triggering rule " + "processing." +msgstr "Test bita ili raspona na bajtovima u poruci obavijesti koja pokreće " + "obradu pravila." -#: lib/solaar/ui/diversion_rules.py:1516 -msgid "type" -msgstr "vrsta" +#: lib/solaar/ui/diversion_rules.py:1583 +msgid "type" +msgstr "vrsta" -#: lib/solaar/ui/diversion_rules.py:1599 -msgid "" -"Mouse gesture with optional initiating button followed by zero or more mouse " -"movements." -msgstr "" -"Geste mišem s neobaveznom tipkom pokretanja nakon kojeg slijedi nula ili " -"više pokreta mišem." +#: lib/solaar/ui/diversion_rules.py:1666 +msgid "Mouse gesture with optional initiating button followed by zero or " + "more mouse movements." +msgstr "Geste mišem s neobaveznom tipkom pokretanja nakon kojeg slijedi nula " + "ili više pokreta mišem." -#: lib/solaar/ui/diversion_rules.py:1604 -msgid "Add movement" -msgstr "Dodaj pokret" +#: lib/solaar/ui/diversion_rules.py:1671 +msgid "Add movement" +msgstr "Dodaj pokret" -#: lib/solaar/ui/diversion_rules.py:1697 -msgid "" -"Simulate a chorded key click or depress or release.\n" -"On Wayland requires write access to /dev/uinput." -msgstr "" -"Simuliraj klik tipke s akordima ili pritisnite ili pustite.\n" -"Na Waylandu je potrebna dozvola pisanje u /dev/uinput." +#: lib/solaar/ui/diversion_rules.py:1764 +msgid "Simulate a chorded key click or depress or release.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "Simuliraj klik tipke s akordima ili pritisnite ili pustite.\n" + "Na Waylandu je potrebna dozvola pisanje u /dev/uinput." -#: lib/solaar/ui/diversion_rules.py:1702 -msgid "Add key" -msgstr "Dodaj tipku" +#: lib/solaar/ui/diversion_rules.py:1769 +msgid "Add key" +msgstr "Dodaj tipku" -#: lib/solaar/ui/diversion_rules.py:1705 -msgid "Click" -msgstr "Klik" +#: lib/solaar/ui/diversion_rules.py:1772 +msgid "Click" +msgstr "Klik" -#: lib/solaar/ui/diversion_rules.py:1708 -msgid "Depress" -msgstr "Pritisak" +#: lib/solaar/ui/diversion_rules.py:1775 +msgid "Depress" +msgstr "Pritisak" -#: lib/solaar/ui/diversion_rules.py:1711 -msgid "Release" -msgstr "Otpuštanje" +#: lib/solaar/ui/diversion_rules.py:1778 +msgid "Release" +msgstr "Otpuštanje" -#: lib/solaar/ui/diversion_rules.py:1795 -msgid "" -"Simulate a mouse scroll.\n" -"On Wayland requires write access to /dev/uinput." -msgstr "" -"Simuliraj pomicanje kotačića miša.\n" -"Na Waylandu je potrebna dozvola pisanje u /dev/uinput." +#: lib/solaar/ui/diversion_rules.py:1861 +msgid "Simulate a mouse scroll.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "Simuliraj pomicanje kotačića miša.\n" + "Na Waylandu je potrebna dozvola pisanje u /dev/uinput." -#: lib/solaar/ui/diversion_rules.py:1851 -msgid "" -"Simulate a mouse click.\n" -"On Wayland requires write access to /dev/uinput." -msgstr "" -"Simuliraj klik miša.\n" -"Na Waylandu je potrebna dozvola pisanje u /dev/uinput." +#: lib/solaar/ui/diversion_rules.py:1918 +msgid "Simulate a mouse click.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "Simuliraj klik miša.\n" + "Na Waylandu je potrebna dozvola pisanje u /dev/uinput." -#: lib/solaar/ui/diversion_rules.py:1854 -msgid "Button" -msgstr "Tipka" +#: lib/solaar/ui/diversion_rules.py:1921 +msgid "Button" +msgstr "Tipka" -#: lib/solaar/ui/diversion_rules.py:1855 -msgid "Count" -msgstr "Brojač" - -#: lib/solaar/ui/diversion_rules.py:1895 -msgid "Execute a command with arguments." -msgstr "Pokreni naredbu s argumentima." - -#: lib/solaar/ui/diversion_rules.py:1898 -msgid "Add argument" -msgstr "Dodaj argument" +#: lib/solaar/ui/diversion_rules.py:1922 +msgid "Count and Action" +msgstr "" #: lib/solaar/ui/diversion_rules.py:1972 -msgid "Toggle" -msgstr "Uklj/Isklj" +msgid "Execute a command with arguments." +msgstr "Pokreni naredbu s argumentima." -#: lib/solaar/ui/diversion_rules.py:1972 -msgid "True" -msgstr "Istina" +#: lib/solaar/ui/diversion_rules.py:1975 +msgid "Add argument" +msgstr "Dodaj argument" -#: lib/solaar/ui/diversion_rules.py:1973 -msgid "False" -msgstr "Laž" +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "Toggle" +msgstr "Uklj/Isklj" -#: lib/solaar/ui/diversion_rules.py:1987 -msgid "Unsupported setting" -msgstr "Nepodržana postavka" +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "True" +msgstr "Istina" -#: lib/solaar/ui/diversion_rules.py:2133 lib/solaar/ui/diversion_rules.py:2204 -msgid "Device" -msgstr "Uređaj" +#: lib/solaar/ui/diversion_rules.py:2051 +msgid "False" +msgstr "Laž" -#: lib/solaar/ui/diversion_rules.py:2138 lib/solaar/ui/diversion_rules.py:2170 -#: lib/solaar/ui/diversion_rules.py:2209 lib/solaar/ui/diversion_rules.py:2451 -#: lib/solaar/ui/diversion_rules.py:2469 -msgid "Originating device" -msgstr "Izvorni uređaj" +#: lib/solaar/ui/diversion_rules.py:2065 +msgid "Unsupported setting" +msgstr "Nepodržana postavka" -#: lib/solaar/ui/diversion_rules.py:2159 -msgid "Device is active and its settings can be changed." -msgstr "Uređaj je aktivan i njegove postavke se mogu promijeniti." +#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2242 +#: lib/solaar/ui/diversion_rules.py:2328 lib/solaar/ui/diversion_rules.py:2570 +#: lib/solaar/ui/diversion_rules.py:2588 +msgid "Originating device" +msgstr "Izvorni uređaj" -#: lib/solaar/ui/diversion_rules.py:2228 -msgid "Value" -msgstr "Vrijednost" +#: lib/solaar/ui/diversion_rules.py:2256 +msgid "Device is active and its settings can be changed." +msgstr "Uređaj je aktivan i njegove postavke se mogu promijeniti." -#: lib/solaar/ui/diversion_rules.py:2236 -msgid "Item" -msgstr "Stavka" +#: lib/solaar/ui/diversion_rules.py:2266 +msgid "Device that originated the current notification." +msgstr "" -#: lib/solaar/ui/diversion_rules.py:2511 -msgid "Change setting on device" -msgstr "Promjena postavke na uređaju" +#: lib/solaar/ui/diversion_rules.py:2280 +msgid "Name of host computer." +msgstr "" -#: lib/solaar/ui/diversion_rules.py:2528 -msgid "Setting on device" -msgstr "Postavka na uređaju" +#: lib/solaar/ui/diversion_rules.py:2347 +msgid "Value" +msgstr "Vrijednost" -#: lib/solaar/ui/notify.py:122 lib/solaar/ui/tray.py:323 -#: lib/solaar/ui/tray.py:328 lib/solaar/ui/window.py:742 -msgid "offline" -msgstr "nespojivo" +#: lib/solaar/ui/diversion_rules.py:2355 +msgid "Item" +msgstr "Stavka" + +#: lib/solaar/ui/diversion_rules.py:2630 +msgid "Change setting on device" +msgstr "Promjena postavke na uređaju" + +#: lib/solaar/ui/diversion_rules.py:2647 +msgid "Setting on device" +msgstr "Postavka na uređaju" + +#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:318 +#: lib/solaar/ui/tray.py:323 lib/solaar/ui/window.py:739 +msgid "offline" +msgstr "nespojivo" #: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 -#: lib/solaar/ui/pair_window.py:278 +#: lib/solaar/ui/pair_window.py:288 #, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s: upari novi uređaj" +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: upari novi uređaj" #: lib/solaar/ui/pair_window.py:123 #, python-format -msgid "Enter passcode on %(name)s." -msgstr "Upišite lozinku na %(name)s." +msgid "Enter passcode on %(name)s." +msgstr "Upišite lozinku na %(name)s." #: lib/solaar/ui/pair_window.py:126 #, python-format -msgid "Type %(passcode)s and then press the enter key." -msgstr "Upišite %(passcode)s i zatim pritisnite Enter tipku." +msgid "Type %(passcode)s and then press the enter key." +msgstr "Upišite %(passcode)s i zatim pritisnite Enter tipku." #: lib/solaar/ui/pair_window.py:129 -msgid "left" -msgstr "lijevo" +msgid "left" +msgstr "lijevo" #: lib/solaar/ui/pair_window.py:129 -msgid "right" -msgstr "desno" +msgid "right" +msgstr "desno" #: lib/solaar/ui/pair_window.py:131 #, python-format -msgid "" -"Press %(code)s\n" -"and then press left and right buttons simultaneously." -msgstr "" -"Pritisnite %(code)s\n" -"i zatim istovremeno pritisnite lijevu i desnu tipku." +msgid "Press %(code)s\n" + "and then press left and right buttons simultaneously." +msgstr "Pritisnite %(code)s\n" + "i zatim istovremeno pritisnite lijevu i desnu tipku." #: lib/solaar/ui/pair_window.py:188 -msgid "Pairing failed" -msgstr "Neuspjelo uparivanje" +msgid "Pairing failed" +msgstr "Neuspjelo uparivanje" #: lib/solaar/ui/pair_window.py:190 -msgid "Make sure your device is within range, and has a decent battery charge." -msgstr "" -"Pobrinite se da je vaš uređaj u dometu bežičnog povezivanja i da je dovoljno " -"napunjen." +msgid "Make sure your device is within range, and has a decent battery " + "charge." +msgstr "Pobrinite se da je vaš uređaj u dometu bežičnog povezivanja i da je " + "dovoljno napunjen." #: lib/solaar/ui/pair_window.py:192 -msgid "A new device was detected, but it is not compatible with this receiver." -msgstr "Novi uređaj je otkriven, ali nije kompatibilan s ovim uređajem." +msgid "A new device was detected, but it is not compatible with this " + "receiver." +msgstr "Novi uređaj je otkriven, ali nije kompatibilan s ovim uređajem." #: lib/solaar/ui/pair_window.py:194 -msgid "More paired devices than receiver can support." -msgstr "Postoji više uparenih uređaja nego što prijemnik podržava." +msgid "More paired devices than receiver can support." +msgstr "Postoji više uparenih uređaja nego što prijemnik podržava." #: lib/solaar/ui/pair_window.py:196 -msgid "No further details are available about the error." -msgstr "Nema dostupnih više pojedinosti o ovoj grešci." +msgid "No further details are available about the error." +msgstr "Nema dostupnih više pojedinosti o ovoj grešci." #: lib/solaar/ui/pair_window.py:210 -msgid "Found a new device:" -msgstr "Novi uređaj je pronađen:" +msgid "Found a new device:" +msgstr "Novi uređaj je pronađen:" #: lib/solaar/ui/pair_window.py:235 -msgid "The wireless link is not encrypted" -msgstr "Bežično povezivanje nije šifrirano" +msgid "The wireless link is not encrypted" +msgstr "Bežično povezivanje nije šifrirano" #: lib/solaar/ui/pair_window.py:264 -msgid "Press a pairing button or key until the pairing light flashes quickly." -msgstr "" -"Pritisnite tipku uparivanja sve dok svjetlo uparivanja ne počne brzo " -"treptati." +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "" #: lib/solaar/ui/pair_window.py:266 -msgid "You may have to first turn the device off and on again." -msgstr "Možda trebate prvo isključiti uređaj pa ponovno uključiti." +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "" #: lib/solaar/ui/pair_window.py:268 -msgid "Turn on the device you want to pair." -msgstr "Uključite uređaj koji želite upariti." +msgid "Other receivers are only compatible with a few devices." +msgstr "" #: lib/solaar/ui/pair_window.py:270 -msgid "If the device is already turned on, turn it off and on again." -msgstr "Ako je uređaj već uključen, isključite ga i ponovno uključite." +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" -#: lib/solaar/ui/pair_window.py:273 -#, python-format -msgid "" -"\n" -"\n" -"This receiver has %d pairing remaining." -msgid_plural "" -"\n" -"\n" -"This receiver has %d pairings remaining." -msgstr[0] "" -"\n" -"\n" -"Ovaj prijemnik ima još %d preostalo uparivanje." -msgstr[1] "" -"\n" -"\n" -"Ovaj prijemnik ima još %d preostala uparivanja." -msgstr[2] "" -"\n" -"\n" -"Ovaj prijemnik ima još %d preostalih uparivanja." +#: lib/solaar/ui/pair_window.py:274 +msgid "Press a pairing button or key until the pairing light flashes " + "quickly." +msgstr "Pritisnite tipku uparivanja sve dok svjetlo uparivanja ne počne brzo " + "treptati." #: lib/solaar/ui/pair_window.py:276 -msgid "" -"\n" -"Cancelling at this point will not use up a pairing." -msgstr "" -"\n" -"Prekidanje u ovom trenutku neće izvršiti uparivanje." +msgid "You may have to first turn the device off and on again." +msgstr "Možda trebate prvo isključiti uređaj pa ponovno uključiti." + +#: lib/solaar/ui/pair_window.py:278 +msgid "Turn on the device you want to pair." +msgstr "Uključite uređaj koji želite upariti." + +#: lib/solaar/ui/pair_window.py:280 +msgid "If the device is already turned on, turn it off and on again." +msgstr "Ako je uređaj već uključen, isključite ga i ponovno uključite." + +#: lib/solaar/ui/pair_window.py:283 +#, python-format +msgid "\n" + "\n" + "This receiver has %d pairing remaining." +msgid_plural "\n" + "\n" + "This receiver has %d pairings remaining." +msgstr[0] "\n" + "\n" + "Ovaj prijemnik ima još %d preostalo uparivanje." +msgstr[1] "\n" + "\n" + "Ovaj prijemnik ima još %d preostala uparivanja." +msgstr[2] "\n" + "\n" + "Ovaj prijemnik ima još %d preostalih uparivanja." + +#: lib/solaar/ui/pair_window.py:286 +msgid "\n" + "Cancelling at this point will not use up a pairing." +msgstr "\n" + "Prekidanje u ovom trenutku neće izvršiti uparivanje." #: lib/solaar/ui/tray.py:58 -msgid "No Logitech device found" -msgstr "Nema pronađenih Logitechovih uređaja" +msgid "No supported device found" +msgstr "" -#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:324 +#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 #, python-format -msgid "About %s" -msgstr "O %su" +msgid "About %s" +msgstr "O %su" -#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:322 +#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 #, python-format -msgid "Quit %s" -msgstr "Zatvori %s" +msgid "Quit %s" +msgstr "Zatvori %s" -#: lib/solaar/ui/tray.py:302 lib/solaar/ui/tray.py:310 -msgid "no receiver" -msgstr "nema prijemnika" +#: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 +msgid "no receiver" +msgstr "nema prijemnika" -#: lib/solaar/ui/tray.py:326 -msgid "no status" -msgstr "bez stanja" +#: lib/solaar/ui/tray.py:321 +msgid "no status" +msgstr "bez stanja" -#: lib/solaar/ui/window.py:101 -msgid "Scanning" -msgstr "Pretraživanje" +#: lib/solaar/ui/window.py:96 +msgid "Scanning" +msgstr "Pretraživanje" -#: lib/solaar/ui/window.py:134 lib/solaar/ui/window.py:687 -msgid "Battery" -msgstr "Baterija" +#: lib/solaar/ui/window.py:129 +msgid "Battery" +msgstr "Baterija" -#: lib/solaar/ui/window.py:137 -msgid "Wireless Link" -msgstr "Bežično povezivanje" +#: lib/solaar/ui/window.py:132 +msgid "Wireless Link" +msgstr "Bežično povezivanje" -#: lib/solaar/ui/window.py:141 -msgid "Lighting" -msgstr "Osvjetljenje" +#: lib/solaar/ui/window.py:136 +msgid "Lighting" +msgstr "Osvjetljenje" -#: lib/solaar/ui/window.py:175 -msgid "Show Technical Details" -msgstr "Prikaži tehničke pojedinosti" +#: lib/solaar/ui/window.py:170 +msgid "Show Technical Details" +msgstr "Prikaži tehničke pojedinosti" -#: lib/solaar/ui/window.py:191 -msgid "Pair new device" -msgstr "Upari novi uređaj" +#: lib/solaar/ui/window.py:186 +msgid "Pair new device" +msgstr "Upari novi uređaj" -#: lib/solaar/ui/window.py:210 -msgid "Select a device" -msgstr "Odaberi uređaj" +#: lib/solaar/ui/window.py:205 +msgid "Select a device" +msgstr "Odaberi uređaj" -#: lib/solaar/ui/window.py:327 -msgid "Rule Editor" -msgstr "Uređivač pravila" +#: lib/solaar/ui/window.py:322 +msgid "Rule Editor" +msgstr "Uređivač pravila" -#: lib/solaar/ui/window.py:538 -msgid "Path" -msgstr "Putanja" +#: lib/solaar/ui/window.py:533 +msgid "Path" +msgstr "Putanja" -#: lib/solaar/ui/window.py:541 -msgid "USB ID" -msgstr "USB ID" +#: lib/solaar/ui/window.py:536 +msgid "USB ID" +msgstr "USB ID" -#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 -#: lib/solaar/ui/window.py:566 lib/solaar/ui/window.py:568 -msgid "Serial" -msgstr "Serijski broj" +#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 +msgid "Serial" +msgstr "Serijski broj" -#: lib/solaar/ui/window.py:550 -msgid "Index" -msgstr "Indeks" +#: lib/solaar/ui/window.py:545 +msgid "Index" +msgstr "Indeks" -#: lib/solaar/ui/window.py:552 -msgid "Wireless PID" -msgstr "PID bežične mreže" +#: lib/solaar/ui/window.py:547 +msgid "Wireless PID" +msgstr "PID bežične mreže" + +#: lib/solaar/ui/window.py:549 +msgid "Product ID" +msgstr "ID proizvoda" + +#: lib/solaar/ui/window.py:551 +msgid "Protocol" +msgstr "Protokol" + +#: lib/solaar/ui/window.py:551 +msgid "Unknown" +msgstr "Nepoznato" #: lib/solaar/ui/window.py:554 -msgid "Product ID" -msgstr "ID proizvoda" - -#: lib/solaar/ui/window.py:556 -msgid "Protocol" -msgstr "Protokol" - -#: lib/solaar/ui/window.py:556 -msgid "Unknown" -msgstr "Nepoznato" - -#: lib/solaar/ui/window.py:559 #, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d ms (%(rate_hz)dHz)" +msgid "%(rate)d ms (%(rate_hz)dHz)" +msgstr "%(rate)d ms (%(rate_hz)dHz)" -#: lib/solaar/ui/window.py:559 -msgid "Polling rate" -msgstr "Cikličko osvježavanje" +#: lib/solaar/ui/window.py:554 +msgid "Polling rate" +msgstr "Cikličko osvježavanje" -#: lib/solaar/ui/window.py:570 -msgid "Unit ID" -msgstr "ID jedinice" +#: lib/solaar/ui/window.py:565 +msgid "Unit ID" +msgstr "ID jedinice" -#: lib/solaar/ui/window.py:581 -msgid "none" -msgstr "nepoznato" +#: lib/solaar/ui/window.py:576 +msgid "none" +msgstr "nepoznato" -#: lib/solaar/ui/window.py:582 -msgid "Notifications" -msgstr "Obavijesti" +#: lib/solaar/ui/window.py:577 +msgid "Notifications" +msgstr "Obavijesti" -#: lib/solaar/ui/window.py:619 -msgid "No device paired." -msgstr "Nema uparenih uređaja." +#: lib/solaar/ui/window.py:621 +msgid "No device paired." +msgstr "Nema uparenih uređaja." -#: lib/solaar/ui/window.py:626 +#: lib/solaar/ui/window.py:628 #, python-format -msgid "Up to %(max_count)s device can be paired to this receiver." -msgid_plural "Up to %(max_count)s devices can be paired to this receiver." -msgstr[0] "Najviše %(max_count)s uređaj može biti uparen s ovim prijemnikom." -msgstr[1] "Najviše %(max_count)s uređaja može biti upareno s ovim prijemnikom." -msgstr[2] "Najviše %(max_count)s uređaja može biti upareno s ovim prijemnikom." +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "Najviše %(max_count)s uređaj može biti uparen s ovim " + "prijemnikom." +msgstr[1] "Najviše %(max_count)s uređaja može biti upareno s ovim " + "prijemnikom." +msgstr[2] "Najviše %(max_count)s uređaja može biti upareno s ovim " + "prijemnikom." -#: lib/solaar/ui/window.py:632 -msgid "Only one device can be paired to this receiver." -msgstr "Samo jedan uređaj može biti uparen s ovim prijemnikom." +#: lib/solaar/ui/window.py:634 +msgid "Only one device can be paired to this receiver." +msgstr "Samo jedan uređaj može biti uparen s ovim prijemnikom." -#: lib/solaar/ui/window.py:636 +#: lib/solaar/ui/window.py:638 #, python-format -msgid "This receiver has %d pairing remaining." -msgid_plural "This receiver has %d pairings remaining." -msgstr[0] "Ovaj prijemnik ima još %d preostalo uparivanje." -msgstr[1] "Ovaj prijemnik ima još %d preostala uparivanja." -msgstr[2] "Ovaj prijemnik ima još %d preostalih uparivanja." +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "Ovaj prijemnik ima još %d preostalo uparivanje." +msgstr[1] "Ovaj prijemnik ima još %d preostala uparivanja." +msgstr[2] "Ovaj prijemnik ima još %d preostalih uparivanja." -#: lib/solaar/ui/window.py:688 -msgid "unknown" -msgstr "nepoznato" +#: lib/solaar/ui/window.py:692 +msgid "Battery Voltage" +msgstr "Napon baterije" -#: lib/solaar/ui/window.py:689 -msgid "Battery information unknown." -msgstr "Informacije baterije nepoznate." +#: lib/solaar/ui/window.py:694 +msgid "Voltage reported by battery" +msgstr "Napon prijavljen od strane baterije" -#: lib/solaar/ui/window.py:699 -msgid "Battery Voltage" -msgstr "Napon baterije" +#: lib/solaar/ui/window.py:696 +msgid "Battery Level" +msgstr "Razina baterije" -#: lib/solaar/ui/window.py:701 -msgid "Voltage reported by battery" -msgstr "Napon prijavljen od strane baterije" +#: lib/solaar/ui/window.py:698 +msgid "Approximate level reported by battery" +msgstr "Približna razina prijavljena od strane baterije" -#: lib/solaar/ui/window.py:703 -msgid "Battery Level" -msgstr "Razina baterije" +#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 +msgid "next reported " +msgstr "sljedeće poznato " -#: lib/solaar/ui/window.py:705 -msgid "Approximate level reported by battery" -msgstr "Približna razina prijavljena od strane baterije" +#: lib/solaar/ui/window.py:708 +msgid " and next level to be reported." +msgstr " i sljedeća razina za prijaviti." -#: lib/solaar/ui/window.py:712 lib/solaar/ui/window.py:714 -msgid "next reported " -msgstr "sljedeće poznato " +#: lib/solaar/ui/window.py:713 +msgid "last known" +msgstr "posljednje poznato" -#: lib/solaar/ui/window.py:715 -msgid " and next level to be reported." -msgstr " i sljedeća razina za prijaviti." +#: lib/solaar/ui/window.py:724 +msgid "encrypted" +msgstr "šifrirano" -#: lib/solaar/ui/window.py:720 -msgid "last known" -msgstr "posljednje poznato" +#: lib/solaar/ui/window.py:726 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "Bežično povezivanje između ovog uređaja i njegovog prijemnika je " + "šifrirano." #: lib/solaar/ui/window.py:728 -msgid "not encrypted" -msgstr "nije šifrirano" +msgid "not encrypted" +msgstr "nije šifrirano" #: lib/solaar/ui/window.py:732 -msgid "" -"The wireless link between this device and its receiver is not encrypted.\n" -"This is a security issue for pointing devices, and a major security issue " -"for text-input devices." -msgstr "" -"Bežično povezivanje između ovog uređaja i njegovog prijemnika nije " -"šifrirano.\n" -"Ovo je sigurnosni problem za pokazivačke uređaje i veliki sigurnosni problem " -"za uređaje za unos teksta." +msgid "The wireless link between this device and its receiver is not " + "encrypted.\n" + "This is a security issue for pointing devices, and a major security " + "issue for text-input devices." +msgstr "Bežično povezivanje između ovog uređaja i njegovog prijemnika nije " + "šifrirano.\n" + "Ovo je sigurnosni problem za pokazivačke uređaje i veliki sigurnosni " + "problem za uređaje za unos teksta." -#: lib/solaar/ui/window.py:737 -msgid "encrypted" -msgstr "šifrirano" - -#: lib/solaar/ui/window.py:739 -msgid "The wireless link between this device and its receiver is encrypted." -msgstr "" -"Bežično povezivanje između ovog uređaja i njegovog prijemnika je šifrirano." - -#: lib/solaar/ui/window.py:752 +#: lib/solaar/ui/window.py:748 #, python-format -msgid "%(light_level)d lux" -msgstr "%(light_level)d lux" +msgid "%(light_level)d lux" +msgstr "%(light_level)d lux" -#~ msgid " paired devices." -#~ msgstr " uparena uređaja." +#~ msgid " paired devices." +#~ msgstr " uparena uređaja." -#~ msgid "%(battery_level)s" -#~ msgstr "%(battery_level)s" +#~ msgid "%(battery_level)s" +#~ msgstr "%(battery_level)s" -#~ msgid "%(battery_percent)d%%" -#~ msgstr "%(battery_percent)d%%" +#~ msgid "%(battery_percent)d%%" +#~ msgstr "%(battery_percent)d%%" -#~ msgid "1 paired device." -#~ msgstr "1 upareni uređaj." +#~ msgid "1 paired device." +#~ msgstr "1 upareni uređaj." -#~ msgid "Actions" -#~ msgstr "Radnje" +#~ msgid "Actions" +#~ msgstr "Radnje" -#~ msgid "Add action" -#~ msgstr "Dodaj radnju" +#~ msgid "Add action" +#~ msgstr "Dodaj radnju" -#~ msgid "" -#~ "Adjust the DPI by sliding the mouse horizontally while holding the button " -#~ "down." -#~ msgstr "Prilagodite DPI pomicanjem miša vodoravno držeći pritisnutu tipku." +#~ msgid "Adjust the DPI by sliding the mouse horizontally while " +#~ "holding the button down." +#~ msgstr "Prilagodite DPI pomicanjem miša vodoravno držeći pritisnutu " +#~ "tipku." -#~ msgid "" -#~ "Automatically switch the mouse wheel between ratchet and freespin mode.\n" -#~ "The mouse wheel is always free at 0, and always locked at 50" -#~ msgstr "" -#~ "Automatski prebaci kotačić miša između ustavljačkog i slobodnog načina " -#~ "vrtnje kotačića.\n" -#~ "Kotačić miša je uvijek slobodan pri 0 i uvijek zaključan pri 50" +#~ msgid "Automatically switch the mouse wheel between ratchet and " +#~ "freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always locked at 50" +#~ msgstr "Automatski prebaci kotačić miša između ustavljačkog i " +#~ "slobodnog načina vrtnje kotačića.\n" +#~ "Kotačić miša je uvijek slobodan pri 0 i uvijek zaključan pri 50" -#~ msgid "" -#~ "Automatically switch the mouse wheel between ratchet and freespin mode.\n" -#~ "The mouse wheel is always free at 0, and always ratcheted at 50" -#~ msgstr "" -#~ "Automatski prebaci kotačić miša između ustavljačkog i slobodnog načina " -#~ "vrtnje kotačića.\n" -#~ "Kotačić miša je uvijek slobodan pri 0 i uvijek zaključan pri 50" +#~ msgid "Automatically switch the mouse wheel between ratchet and " +#~ "freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always ratcheted at 50" +#~ msgstr "Automatski prebaci kotačić miša između ustavljačkog i " +#~ "slobodnog načina vrtnje kotačića.\n" +#~ "Kotačić miša je uvijek slobodan pri 0 i uvijek zaključan pri 50" -#~ msgid "DPI Sliding Adjustment" -#~ msgstr "Prilagodba DPI pomicanjem" +#~ msgid "Battery information unknown." +#~ msgstr "Informacije baterije nepoznate." -#~ msgid "Effectively turns off thumb scrolling in Linux." -#~ msgstr "Učinkovito isključuje pomicanje palcem u Linuxu." +#~ msgid "Count" +#~ msgstr "Brojač" -#~ msgid "Effectively turns off wheel scrolling in Linux." -#~ msgstr "Učinkovito isključuje pomicanje kotačića u Linuxu." +#~ msgid "DPI Sliding Adjustment" +#~ msgstr "Prilagodba DPI pomicanjem" -#~ msgid "" -#~ "For more information see the Solaar installation directions\n" -#~ "at https://pwr-solaar.github.io/Solaar/installation" -#~ msgstr "" -#~ "Za više informacija pogledajte upute Solaar instalacije\n" -#~ "na https://pwr-solaar.github.io/Solaar/installation" +#~ msgid "Diverted key or button depressed or released.\n" +#~ "Use the Key/Button Diversion setting to divert keys and buttons." +#~ msgstr "Preusmjerena tipka pritisnuta ili otpuštena.\n" +#~ "Koristite postavku Preusmjeravanje tipke za preusmjeravanje tipki." -#~ msgid "HID++ Scrolling" -#~ msgstr "HID++ Scrolling" +#~ msgid "Effectively turns off thumb scrolling in Linux." +#~ msgstr "Učinkovito isključuje pomicanje palcem u Linuxu." -#~ msgid "HID++ Thumb Scrolling" -#~ msgstr "HID++ pomicanje palcem" +#~ msgid "Effectively turns off wheel scrolling in Linux." +#~ msgstr "Učinkovito isključuje pomicanje kotačića u Linuxu." -#~ msgid "HID++ mode for horizontal scroll with the thumb wheel." -#~ msgstr "HID++ način rada za okomito pomicanje kotačića palca." +#~ msgid "For more information see the Solaar installation directions\n" +#~ "at https://pwr-solaar.github.io/Solaar/installation" +#~ msgstr "Za više informacija pogledajte upute Solaar instalacije\n" +#~ "na https://pwr-solaar.github.io/Solaar/installation" -#~ msgid "HID++ mode for vertical scroll with the wheel." -#~ msgstr "HID++ način rada za okomito pomicanje kotačića." +#, python-format +#~ msgid "Found a Logitech Receiver (%s), but did not have permission " +#~ "to open it." +#~ msgstr "Pronađen je (%s) Logitech Receiver, ali nemate ovlasti za " +#~ "otvoriti ga." -#~ msgid "High Resolution Scrolling" -#~ msgstr "Pomicanje kotačićem visoke razlučivosti" +#~ msgid "HID++ Scrolling" +#~ msgstr "HID++ Scrolling" -#~ msgid "High Resolution Wheel Invert" -#~ msgstr "Obrnuto pomicanje kotačićem visoke razlučivosti" +#~ msgid "HID++ Thumb Scrolling" +#~ msgstr "HID++ pomicanje palcem" -#~ msgid "High-sensitivity wheel invert direction for vertical scroll." -#~ msgstr "Obrnuti visoko osjetljivi smjer kotačića za okomito pomicanje." +#~ msgid "HID++ mode for horizontal scroll with the thumb wheel." +#~ msgstr "HID++ način rada za okomito pomicanje kotačića palca." -#~ msgid "" -#~ "If the device is already turned on,\n" -#~ "turn if off and on again." -#~ msgstr "" -#~ "Ako je uređaj već uključen,\n" -#~ "isključite ga i ponovno uključite." +#~ msgid "HID++ mode for vertical scroll with the wheel." +#~ msgstr "HID++ način rada za okomito pomicanje kotačića." -#~ msgid "If the device is already turned on, turn if off and on again." -#~ msgstr "Ako je uređaj već uključen, isključite ga i ponovno uključite." +#~ msgid "High Resolution Scrolling" +#~ msgstr "Pomicanje kotačićem visoke razlučivosti" -#~ msgid "Invert thumb scroll direction." -#~ msgstr "Obrnuti smjer pomicanja kotačića palca." +#~ msgid "High Resolution Wheel Invert" +#~ msgstr "Obrnuto pomicanje kotačićem visoke razlučivosti" -#~ msgid "" -#~ "Make the key or button send HID++ notifications (which trigger Solaar " -#~ "rules but are otherwise ignored)." -#~ msgstr "" -#~ "Neka tipka šalje HID++ obavijesti (što pokreće Solaar pravila, u " -#~ "suprotnome ih zanemaruje)." +#~ msgid "High-sensitivity wheel invert direction for vertical scroll." +#~ msgstr "Obrnuti visoko osjetljivi smjer kotačića za okomito " +#~ "pomicanje." -#~ msgid "Scroll Wheel Rachet" -#~ msgstr "Ustavljanje pomicanja kotačića" +#~ msgid "If the device is already turned on,\n" +#~ "turn if off and on again." +#~ msgstr "Ako je uređaj već uključen,\n" +#~ "isključite ga i ponovno uključite." -#~ msgid "Send a gesture by sliding the mouse while holding the button down." -#~ msgstr "Prilagodite geste pomicanjem miša držeći pritisnutu tipku." +#~ msgid "If the device is already turned on, turn if off and on again." +#~ msgstr "Ako je uređaj već uključen, isključite ga i ponovno " +#~ "uključite." -#~ msgid "" -#~ "Shows status of devices connected\n" -#~ "through wireless Logitech receivers." -#~ msgstr "" -#~ "Prikazuje stanje uređaja povezanih\n" -#~ "putem Logitech bežičnih prijemnika." +#~ msgid "If you've just installed Solaar, try removing the receiver " +#~ "and plugging it back in." +#~ msgstr "Ako ste upravo instalirali Solaar, pokušajte odspojiti " +#~ "prijemnik i ponovno ga spojiti." -#~ msgid "Solaar depends on a udev file that is not present" -#~ msgstr "Solaar ovisi o udev datoteci koja nije prisutna" +#~ msgid "Invert thumb scroll direction." +#~ msgstr "Obrnuti smjer pomicanja kotačića palca." -#~ msgid "The receiver only supports %d paired device(s)." -#~ msgstr "Prijemnik samo podržava %d upareni uređaj(e)." +#~ msgid "Make the key or button send HID++ notifications (which " +#~ "trigger Solaar rules but are otherwise ignored)." +#~ msgstr "Neka tipka šalje HID++ obavijesti (što pokreće Solaar " +#~ "pravila, u suprotnome ih zanemaruje)." -#~ msgid "The receiver was unplugged." -#~ msgstr "Prijemnik je odspojen." +#~ msgid "No Logitech device found" +#~ msgstr "Nema pronađenih Logitechovih uređaja" -#~ msgid "" -#~ "The wireless link between this device and its receiver is not encrypted.\n" -#~ "\n" -#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " -#~ "security issue.\n" -#~ "\n" -#~ "It is, however, a major security issue for text-input devices (keyboards, " -#~ "numpads),\n" -#~ "because typed text can be sniffed inconspicuously by 3rd parties within " -#~ "range." -#~ msgstr "" -#~ "Bežično povezivanje između ovog uređaja i njegovog prijemnika nije " -#~ "šifrirano.\n" -#~ "\n" -#~ "Za uređaje s pokazivačem (miš, trackballs, trackpads), to je manji " -#~ "sigurnosni problem.\n" -#~ "\n" -#~ "A za uređaje za unos teksta, je veći sigurnosni problem (tipkovnice, " -#~ "numpads),\n" -#~ "zato jer upisani tekst može biti neprimjentno nadziran u dometu treće " -#~ "strane." +#~ msgid "Scroll Wheel Rachet" +#~ msgstr "Ustavljanje pomicanja kotačića" -#~ msgid "Thumb Scroll Invert" -#~ msgstr "Obrnuto pomicanje kotačića palca" +#~ msgid "Send a gesture by sliding the mouse while holding the button " +#~ "down." +#~ msgstr "Prilagodite geste pomicanjem miša držeći pritisnutu tipku." -#~ msgid "USB id" -#~ msgstr "USB id" +#~ msgid "Shows status of devices connected\n" +#~ "through wireless Logitech receivers." +#~ msgstr "Prikazuje stanje uređaja povezanih\n" +#~ "putem Logitech bežičnih prijemnika." -#~ msgid "Wheel Resolution" -#~ msgstr "Pomicanje kotačićem" +#~ msgid "Solaar depends on a udev file that is not present" +#~ msgstr "Solaar ovisi o udev datoteci koja nije prisutna" -#~ msgid "closed" -#~ msgstr "završeno" +#~ msgid "The receiver only supports %d paired device(s)." +#~ msgstr "Prijemnik samo podržava %d upareni uređaj(e)." -#~ msgid "lux" -#~ msgstr "lux" +#~ msgid "The receiver was unplugged." +#~ msgstr "Prijemnik je odspojen." -#~ msgid "next " -#~ msgstr "sljedeće " +#~ msgid "The wireless link between this device and its receiver is " +#~ "not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices " +#~ "(keyboards, numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties " +#~ "within range." +#~ msgstr "Bežično povezivanje između ovog uređaja i njegovog " +#~ "prijemnika nije šifrirano.\n" +#~ "\n" +#~ "Za uređaje s pokazivačem (miš, trackballs, trackpads), to je manji " +#~ "sigurnosni problem.\n" +#~ "\n" +#~ "A za uređaje za unos teksta, je veći sigurnosni problem (tipkovnice, " +#~ "numpads),\n" +#~ "zato jer upisani tekst može biti neprimjentno nadziran u dometu " +#~ "treće strane." -#~ msgid "open" -#~ msgstr "pokrenuto" +#~ msgid "Thumb Scroll Invert" +#~ msgstr "Obrnuto pomicanje kotačića palca" -#~ msgid "paired devices" -#~ msgstr "upareni uređaji" +#~ msgid "Try removing the device and plugging it back in or turning " +#~ "it off and then on." +#~ msgstr "Pokušajte uređaj ukloniti pa ponovno priključiti s računalom " +#~ "ili ga isključiti pa ponovno uključiti." + +#~ msgid "USB id" +#~ msgstr "USB id" + +#~ msgid "Wheel Resolution" +#~ msgstr "Pomicanje kotačićem" + +#~ msgid "closed" +#~ msgstr "završeno" + +#~ msgid "lux" +#~ msgstr "lux" + +#~ msgid "next " +#~ msgstr "sljedeće " + +#~ msgid "open" +#~ msgstr "pokrenuto" + +#~ msgid "paired devices" +#~ msgstr "upareni uređaji" + +#~ msgid "unknown" +#~ msgstr "nepoznato" diff --git a/po/id.po b/po/id.po new file mode 100644 index 00000000..bbc3cff5 --- /dev/null +++ b/po/id.po @@ -0,0 +1,1967 @@ +# Indonesian translations for solaar package. +# Copyright (C) 2024 THE solaar'S COPYRIGHT HOLDER +# This file is distributed under the same license as the solaar package. +# Ferdina kusumah , 2024. +# +msgid "" +msgstr "Project-Id-Version: solaar 1.1.11\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2024-03-14 13:11+0700\n" + "PO-Revision-Date: 2024-03-14 09:00+0700\n" + "Last-Translator: Ferdina Kusumah \n" + "Language-Team: none\n" + "Language: id\n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=1; plural=0;\n" + "X-Generator: Poedit 3.0\n" + +#: lib/logitech_receiver/base_usb.py:45 +msgid "Bolt Receiver" +msgstr "Reseptor bolt" + +#: lib/logitech_receiver/base_usb.py:58 +msgid "Unifying Receiver" +msgstr "Reseptor unifying" + +#: lib/logitech_receiver/base_usb.py:70 lib/logitech_receiver/base_usb.py:83 +#: lib/logitech_receiver/base_usb.py:97 lib/logitech_receiver/base_usb.py:111 +#: lib/logitech_receiver/base_usb.py:125 +msgid "Nano Receiver" +msgstr "Reseptor nano" + +#: lib/logitech_receiver/base_usb.py:138 +msgid "Lightspeed Receiver" +msgstr "Reseptor lightspeed" + +#: lib/logitech_receiver/base_usb.py:149 +msgid "EX100 Receiver 27 Mhz" +msgstr "Reseptor EX100 27 Mhz" + +#: lib/logitech_receiver/common.py:604 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Baterai: %(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:606 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Baterai: %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:926 +#: lib/logitech_receiver/settings_templates.py:291 +msgid "Disabled" +msgstr "Dinonaktifkan" + +#: lib/logitech_receiver/hidpp20.py:927 +msgid "Static" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:928 +msgid "Pulse" +msgstr "Pulsa" + +#: lib/logitech_receiver/hidpp20.py:929 +msgid "Cycle" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:930 +msgid "Boot" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:931 +msgid "Demo" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:932 +msgid "Breathe" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:933 +msgid "Ripple" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:994 +msgid "Unknown Location" +msgstr "Lokasi tidak ditemukan" + +#: lib/logitech_receiver/hidpp20.py:995 +msgid "Primary" +msgstr "Utama" + +#: lib/logitech_receiver/hidpp20.py:996 +msgid "Logo" +msgstr "Logo" + +#: lib/logitech_receiver/hidpp20.py:997 +msgid "Left Side" +msgstr "Sisi kiri" + +#: lib/logitech_receiver/hidpp20.py:998 +msgid "Right Side" +msgstr "Sisi kanan" + +#: lib/logitech_receiver/hidpp20.py:999 +msgid "Combined" +msgstr "Kombinasi" + +#: lib/logitech_receiver/hidpp20.py:1000 +msgid "Primary 1" +msgstr "Utama 1" + +#: lib/logitech_receiver/hidpp20.py:1001 +msgid "Primary 2" +msgstr "Utama 2" + +#: lib/logitech_receiver/hidpp20.py:1002 +msgid "Primary 3" +msgstr "Utama 3" + +#: lib/logitech_receiver/hidpp20.py:1003 +msgid "Primary 4" +msgstr "Utama 4" + +#: lib/logitech_receiver/hidpp20.py:1004 +msgid "Primary 5" +msgstr "Utama 5" + +#: lib/logitech_receiver/hidpp20.py:1005 +msgid "Primary 6" +msgstr "Utama 6" + +#: lib/logitech_receiver/i18n.py:28 +msgid "empty" +msgstr "habis" + +#: lib/logitech_receiver/i18n.py:29 +msgid "critical" +msgstr "hampir habis" + +#: lib/logitech_receiver/i18n.py:30 +msgid "low" +msgstr "rendah" + +#: lib/logitech_receiver/i18n.py:31 +msgid "average" +msgstr "setengah" + +#: lib/logitech_receiver/i18n.py:32 +msgid "good" +msgstr "baik" + +#: lib/logitech_receiver/i18n.py:33 +msgid "full" +msgstr "penuh" + +#: lib/logitech_receiver/i18n.py:35 +msgid "discharging" +msgstr "pemakaian" + +#: lib/logitech_receiver/i18n.py:36 +msgid "recharging" +msgstr "pengisian ulang" + +#: lib/logitech_receiver/i18n.py:37 +msgid "charging" +msgstr "pengisian daya" + +#: lib/logitech_receiver/i18n.py:38 +msgid "not charging" +msgstr "tidak mengisi daya" + +#: lib/logitech_receiver/i18n.py:39 +msgid "almost full" +msgstr "hampir penuh" + +#: lib/logitech_receiver/i18n.py:40 +msgid "charged" +msgstr "mengisi" + +#: lib/logitech_receiver/i18n.py:41 +msgid "slow recharge" +msgstr "pengisian lambat" + +#: lib/logitech_receiver/i18n.py:42 +msgid "invalid battery" +msgstr "baterai tidak valid" + +#: lib/logitech_receiver/i18n.py:43 +msgid "thermal error" +msgstr "kesalahan termal" + +#: lib/logitech_receiver/i18n.py:44 +msgid "error" +msgstr "" + +#: lib/logitech_receiver/i18n.py:45 +msgid "standard" +msgstr "" + +#: lib/logitech_receiver/i18n.py:46 +msgid "fast" +msgstr "cepat" + +#: lib/logitech_receiver/i18n.py:47 +msgid "slow" +msgstr "lambat" + +#: lib/logitech_receiver/i18n.py:49 +msgid "device timeout" +msgstr "batas waktu perangkat habis" + +#: lib/logitech_receiver/i18n.py:50 +msgid "device not supported" +msgstr "perangkat tidak didukung" + +#: lib/logitech_receiver/i18n.py:51 +msgid "too many devices" +msgstr "terlalu banyak perangkat" + +#: lib/logitech_receiver/i18n.py:52 +msgid "sequence timeout" +msgstr "batas waktu urutan habis" + +#: lib/logitech_receiver/i18n.py:54 +msgid "Firmware" +msgstr "" + +#: lib/logitech_receiver/i18n.py:55 +msgid "Bootloader" +msgstr "" + +#: lib/logitech_receiver/i18n.py:56 +msgid "Hardware" +msgstr "Perangkat keras" + +#: lib/logitech_receiver/i18n.py:57 +msgid "Other" +msgstr "Lainnya" + +#: lib/logitech_receiver/i18n.py:59 +msgid "Left Button" +msgstr "Tombol Kiri" + +#: lib/logitech_receiver/i18n.py:60 +msgid "Right Button" +msgstr "Tombol Kanan" + +#: lib/logitech_receiver/i18n.py:61 +msgid "Middle Button" +msgstr "Tombol Tengah" + +#: lib/logitech_receiver/i18n.py:62 +msgid "Back Button" +msgstr "Tombol kembali" + +#: lib/logitech_receiver/i18n.py:63 +msgid "Forward Button" +msgstr "Tombol Depan" + +#: lib/logitech_receiver/i18n.py:64 +msgid "Mouse Gesture Button" +msgstr "Tombol Gerakan Mouse" + +#: lib/logitech_receiver/i18n.py:65 +msgid "Smart Shift" +msgstr "Pergeseran Cerdas" + +#: lib/logitech_receiver/i18n.py:66 +msgid "DPI Switch" +msgstr "" + +#: lib/logitech_receiver/i18n.py:67 +msgid "Left Tilt" +msgstr "Kemiringan Kiri" + +#: lib/logitech_receiver/i18n.py:68 +msgid "Right Tilt" +msgstr "Kemiringan Kanan" + +#: lib/logitech_receiver/i18n.py:69 +msgid "Left Click" +msgstr "Klik Kiri" + +#: lib/logitech_receiver/i18n.py:70 +msgid "Right Click" +msgstr "Klik Kanan" + +#: lib/logitech_receiver/i18n.py:71 +msgid "Mouse Middle Button" +msgstr "Tombol Tengah Mouse" + +#: lib/logitech_receiver/i18n.py:72 +msgid "Mouse Back Button" +msgstr "Tombol Kembali Mouse" + +#: lib/logitech_receiver/i18n.py:73 +msgid "Mouse Forward Button" +msgstr "Tombol Depan Mouse" + +#: lib/logitech_receiver/i18n.py:74 +msgid "Gesture Button Navigation" +msgstr "Navigasi Tombol Gerakan" + +#: lib/logitech_receiver/i18n.py:75 +msgid "Mouse Scroll Left Button" +msgstr "Tombol Gulir Kiri Mouse" + +#: lib/logitech_receiver/i18n.py:76 +msgid "Mouse Scroll Right Button" +msgstr "Tombol Gulir Kanan Mouse" + +#: lib/logitech_receiver/i18n.py:78 +msgid "pressed" +msgstr "ditekan" + +#: lib/logitech_receiver/i18n.py:79 +msgid "released" +msgstr "dilepaskan" + +#: lib/logitech_receiver/notifications.py:63 +#: lib/logitech_receiver/notifications.py:115 +msgid "pairing lock is closed" +msgstr "kunci penyandingan ditutup" + +#: lib/logitech_receiver/notifications.py:63 +#: lib/logitech_receiver/notifications.py:115 +msgid "pairing lock is open" +msgstr "kunci penyandingan terbuka" + +#: lib/logitech_receiver/notifications.py:80 +msgid "discovery lock is closed" +msgstr "kunci penemuan ditutup" + +#: lib/logitech_receiver/notifications.py:80 +msgid "discovery lock is open" +msgstr "kunci penemuan terbuka" + +#: lib/logitech_receiver/notifications.py:207 +msgid "connected" +msgstr "terhubung" + +#: lib/logitech_receiver/notifications.py:207 +msgid "disconnected" +msgstr "terputus" + +#: lib/logitech_receiver/notifications.py:233 +msgid "unpaired" +msgstr "tidak berpasangan" + +#: lib/logitech_receiver/notifications.py:280 +msgid "powered on" +msgstr "dihidupkan" + +#: lib/logitech_receiver/receiver.py:345 +msgid "No paired devices." +msgstr "Tidak ada perangkat yang dipasangkan" + +#: lib/logitech_receiver/receiver.py:347 lib/solaar/ui/window.py:630 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s perangkat yang dipasangkan." + +#: lib/logitech_receiver/settings.py:617 +msgid "register" +msgstr "daftar" + +#: lib/logitech_receiver/settings.py:631 lib/logitech_receiver/settings.py:658 +msgid "feature" +msgstr "fitur" + +#: lib/logitech_receiver/settings_templates.py:144 +msgid "Swap Fx function" +msgstr "Tukar fungsi Fx" + +#: lib/logitech_receiver/settings_templates.py:147 +msgid "When set, the F1..F12 keys will activate their special function,\n" + "and you must hold the FN key to activate their standard function." +msgstr "Jika disetel, tombol F1..F12 akan mengaktifkan fungsi khususnya,\n" + "dan Anda harus menahan tombol FN untuk mengaktifkan fungsi " + "standarnya." + +#: lib/logitech_receiver/settings_templates.py:152 +msgid "When unset, the F1..F12 keys will activate their standard function,\n" + "and you must hold the FN key to activate their special function." +msgstr "Jika tidak disetel, tombol F1..F12 akan mengaktifkan fungsi " + "standarnya,\n" + "dan Anda harus menahan tombol FN untuk mengaktifkan fungsi khususnya." + +#: lib/logitech_receiver/settings_templates.py:160 +msgid "Hand Detection" +msgstr "Deteksi Tangan" + +#: lib/logitech_receiver/settings_templates.py:161 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Nyalakan penerangan saat tangan melayang di atas keyboard." + +#: lib/logitech_receiver/settings_templates.py:168 +msgid "Scroll Wheel Smooth Scrolling" +msgstr "Roda Gulir Pengguliran Halus" + +#: lib/logitech_receiver/settings_templates.py:169 +#: lib/logitech_receiver/settings_templates.py:402 +#: lib/logitech_receiver/settings_templates.py:431 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Mode sensitivitas tinggi untuk gulir vertikal dengan roda." + +#: lib/logitech_receiver/settings_templates.py:176 +msgid "Side Scrolling" +msgstr "Menggulir Sisi" + +#: lib/logitech_receiver/settings_templates.py:178 +msgid "When disabled, pushing the wheel sideways sends custom button " + "events\n" + "instead of the standard side-scrolling events." +msgstr "Saat dinonaktifkan, mendorong roda ke samping akan mengirimkan " + "tombol khususacara\n" + "bukannya acara side-scrolling standar." + +#: lib/logitech_receiver/settings_templates.py:188 +msgid "Sensitivity (DPI - older mice)" +msgstr "Sensitivitas (DPI - older mouse)" + +#: lib/logitech_receiver/settings_templates.py:189 +#: lib/logitech_receiver/settings_templates.py:958 +msgid "Mouse movement sensitivity" +msgstr "Sensitivitas gerakan mouse" + +#: lib/logitech_receiver/settings_templates.py:249 +#: lib/logitech_receiver/settings_templates.py:261 +#: lib/logitech_receiver/settings_templates.py:388 +msgid "Backlight" +msgstr "Lampu latar" + +#: lib/logitech_receiver/settings_templates.py:250 +#: lib/logitech_receiver/settings_templates.py:389 +msgid "Set illumination time for keyboard." +msgstr "Atur waktu penerangan untuk keyboard." + +#: lib/logitech_receiver/settings_templates.py:262 +msgid "Illumination level on keyboard. Changes made are only applied in " + "Manual mode." +msgstr "Tingkat penerangan pada keyboard. Perubahan yang dilakukan hanya " + "diterapkan di Mode manual." + +#: lib/logitech_receiver/settings_templates.py:293 +msgid "Automatic" +msgstr "Otomatis" + +#: lib/logitech_receiver/settings_templates.py:295 +msgid "Manual" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:297 +msgid "Enabled" +msgstr "Difungsikan" + +#: lib/logitech_receiver/settings_templates.py:303 +msgid "Backlight Level" +msgstr "Tingkat Lampu Latar" + +#: lib/logitech_receiver/settings_templates.py:304 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "Tingkat penerangan pada keyboard saat dalam mode Manual." + +#: lib/logitech_receiver/settings_templates.py:361 +msgid "Backlight Delay Hands Out" +msgstr "Penundaan Lampu Latar Hands Out" + +#: lib/logitech_receiver/settings_templates.py:362 +msgid "Delay in seconds until backlight fades out with hands away from " + "keyboard." +msgstr "Tunda dalam hitungan detik hingga cahaya latar memudar dengan tangan " + "menjauhipapan ketik." + +#: lib/logitech_receiver/settings_templates.py:370 +msgid "Backlight Delay Hands In" +msgstr "Penundaan Lampu Latar Masuk" + +#: lib/logitech_receiver/settings_templates.py:371 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "Tunda dalam hitungan detik hingga lampu latar memudar dengan tangan " + "di dekat keyboard." + +#: lib/logitech_receiver/settings_templates.py:379 +msgid "Backlight Delay Powered" +msgstr "Penundaan Lampu Latar Didukung" + +#: lib/logitech_receiver/settings_templates.py:380 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "Penundaan dalam hitungan detik hingga lampu latar memudar dengan " + "daya eksternal." + +#: lib/logitech_receiver/settings_templates.py:400 +msgid "Scroll Wheel High Resolution" +msgstr "Resolusi Tinggi Scroll Wheel" + +#: lib/logitech_receiver/settings_templates.py:404 +#: lib/logitech_receiver/settings_templates.py:433 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "Setel ke abaikan jika pengguliran terlalu cepat atau lambat" + +#: lib/logitech_receiver/settings_templates.py:411 +#: lib/logitech_receiver/settings_templates.py:442 +msgid "Scroll Wheel Diversion" +msgstr "Pengalihan Scroll Wheel" + +#: lib/logitech_receiver/settings_templates.py:413 +msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "Buat roda gulir mengirimkan notifikasi LOWRES_WHEEL HID++ (yang mana " + "memicu aturan Solaar tetapi sebaliknya diabaikan)." + +#: lib/logitech_receiver/settings_templates.py:420 +msgid "Scroll Wheel Direction" +msgstr "Arah Scroll Wheel" + +#: lib/logitech_receiver/settings_templates.py:421 +msgid "Invert direction for vertical scroll with wheel." +msgstr "Balikkan arah untuk gulir vertikal scroll with wheel." + +#: lib/logitech_receiver/settings_templates.py:429 +msgid "Scroll Wheel Resolution" +msgstr "Resolusi Scroll Wheel" + +#: lib/logitech_receiver/settings_templates.py:444 +msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "Buat roda gulir mengirimkan notifikasi HIRES_WHEEL HID++ (yang mana " + "memicu aturan Solaar tetapi sebaliknya diabaikan)." + +#: lib/logitech_receiver/settings_templates.py:453 +msgid "Sensitivity (Pointer Speed)" +msgstr "Sensitivitas (Kecepatan Pointer)" + +#: lib/logitech_receiver/settings_templates.py:454 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Pengganda kecepatan untuk mouse (256 adalah pengganda normal)." + +#: lib/logitech_receiver/settings_templates.py:464 +msgid "Thumb Wheel Diversion" +msgstr "Pengalihan Roda Jempol" + +#: lib/logitech_receiver/settings_templates.py:466 +msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " + "Solaar rules but are otherwise ignored)." +msgstr "Buat roda jempol mengirimkan notifikasi THUMB_WHEEL HID++ (yang " + "memicuPeraturan Solar tetapi sebaliknya diabaikan)." + +#: lib/logitech_receiver/settings_templates.py:475 +msgid "Thumb Wheel Direction" +msgstr "Arah Roda Ibu Jari" + +#: lib/logitech_receiver/settings_templates.py:476 +msgid "Invert thumb wheel scroll direction." +msgstr "Balikkan arah gulir roda ibu jari." + +#: lib/logitech_receiver/settings_templates.py:496 +msgid "Onboard Profiles" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:497 +msgid "Enable an onboard profile, which controls report rate, sensitivity, " + "and button actions" +msgstr "Aktifkan profil onboard, yang mengontrol kecepatan laporan, " + "sensitivitas, dan tindakan tombol" + +#: lib/logitech_receiver/settings_templates.py:541 +#: lib/logitech_receiver/settings_templates.py:581 +msgid "Report Rate" +msgstr "Tingkat Laporan" + +#: lib/logitech_receiver/settings_templates.py:543 +#: lib/logitech_receiver/settings_templates.py:583 +msgid "Frequency of device movement reports" +msgstr "Frekuensi laporan pergerakan perangkat" + +#: lib/logitech_receiver/settings_templates.py:543 +#: lib/logitech_receiver/settings_templates.py:583 +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1326 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "Mungkin perlu Profil Onboard diatur ke Nonaktifkan agar efektif." + +#: lib/logitech_receiver/settings_templates.py:623 +msgid "Divert crown events" +msgstr "Alihkan acara mahkota" + +#: lib/logitech_receiver/settings_templates.py:624 +msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "Buat mahkota mengirimkan notifikasi CROWN HID++ (yang memicu " + "Solaarperaturan tetapi sebaliknya diabaikan)." + +#: lib/logitech_receiver/settings_templates.py:632 +msgid "Crown smooth scroll" +msgstr "Gulir halus mahkota" + +#: lib/logitech_receiver/settings_templates.py:633 +msgid "Set crown smooth scroll" +msgstr "Setel mahkota gulir halus" + +#: lib/logitech_receiver/settings_templates.py:641 +msgid "Divert G and M Keys" +msgstr "Alihkan Tombol G dan M" + +#: lib/logitech_receiver/settings_templates.py:642 +msgid "Make G and M keys send HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "Buat kunci G dan M mengirimkan notifikasi HID++ (yang memicu " + "Solaarperaturan tetapi sebaliknya diabaikan)." + +#: lib/logitech_receiver/settings_templates.py:656 +msgid "Scroll Wheel Ratcheted" +msgstr "Roda Gulir Bergerigi" + +#: lib/logitech_receiver/settings_templates.py:657 +msgid "Switch the mouse wheel between speed-controlled ratcheting and " + "always freespin." +msgstr "Ganti roda mouse antara ratcheting yang dikontrol kecepatan dan " + "selalu berputar bebas." + +#: lib/logitech_receiver/settings_templates.py:659 +msgid "Freespinning" +msgstr "Putar bebas" + +#: lib/logitech_receiver/settings_templates.py:659 +msgid "Ratcheted" +msgstr "Digeser" + +#: lib/logitech_receiver/settings_templates.py:666 +msgid "Scroll Wheel Ratchet Speed" +msgstr "Kecepatan Ratchet Roda Gulir" + +#: lib/logitech_receiver/settings_templates.py:668 +msgid "Use the mouse wheel speed to switch between ratcheted and " + "freespinning.\n" + "The mouse wheel is always ratcheted at 50." +msgstr "Gunakan kecepatan roda mouse untuk beralih antara ratcheted dan " + "berputar bebas.\n" + "Roda tikus selalu diputar pada kecepatan 50." + +#: lib/logitech_receiver/settings_templates.py:717 +msgid "Key/Button Actions" +msgstr "Tindakan Kunci/Tombol" + +#: lib/logitech_receiver/settings_templates.py:719 +msgid "Change the action for the key or button." +msgstr "Ubah tindakan untuk kunci atau tombol tersebut." + +#: lib/logitech_receiver/settings_templates.py:721 +msgid "Overridden by diversion." +msgstr "Ditimpa oleh pengalihan." + +#: lib/logitech_receiver/settings_templates.py:723 +msgid "Changing important actions (such as for the left mouse button) can " + "result in an unusable system." +msgstr "Mengubah tindakan penting (misalnya tombol kiri mouse) dapat " + "menghasilkan sistem yang tidak dapat digunakan." + +#: lib/logitech_receiver/settings_templates.py:886 +msgid "Key/Button Diversion" +msgstr "Pengalihan Kunci/Tombol" + +#: lib/logitech_receiver/settings_templates.py:887 +msgid "Make the key or button send HID++ notifications (Diverted) or " + "initiate Mouse Gestures or Sliding DPI" +msgstr "Buat kunci atau tombol mengirimkan notifikasi HID++ (Dialihkan) " + "ataumemulai Gerakan Mouse atau DPI Geser" + +#: lib/logitech_receiver/settings_templates.py:890 +#: lib/logitech_receiver/settings_templates.py:891 +#: lib/logitech_receiver/settings_templates.py:892 +msgid "Diverted" +msgstr "Dialihkan" + +#: lib/logitech_receiver/settings_templates.py:890 +#: lib/logitech_receiver/settings_templates.py:891 +msgid "Mouse Gestures" +msgstr "Gerakan Mouse" + +#: lib/logitech_receiver/settings_templates.py:890 +#: lib/logitech_receiver/settings_templates.py:891 +#: lib/logitech_receiver/settings_templates.py:892 +msgid "Regular" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:890 +msgid "Sliding DPI" +msgstr "DPI geser" + +#: lib/logitech_receiver/settings_templates.py:957 +msgid "Sensitivity (DPI)" +msgstr "Sensitivitas (DPI)" + +#: lib/logitech_receiver/settings_templates.py:997 +msgid "Sensitivity Switching" +msgstr "Peralihan Sensitivitas" + +#: lib/logitech_receiver/settings_templates.py:999 +msgid "Switch the current sensitivity and the remembered sensitivity when " + "the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current " + "sensitivity" +msgstr "Ganti sensitivitas saat ini dan sensitivitas yang diingat ketika " + "tombol atau tombol ditekan.\n" + "Jika tidak ada kepekaan yang diingat, ingat saja arusnyakepekaan" + +#: lib/logitech_receiver/settings_templates.py:1003 +msgid "Off" +msgstr "Mati" + +#: lib/logitech_receiver/settings_templates.py:1034 +msgid "Disable keys" +msgstr "Nonaktifkan kunci" + +#: lib/logitech_receiver/settings_templates.py:1035 +msgid "Disable specific keyboard keys." +msgstr "Nonaktifkan tombol keyboard tertentu." + +#: lib/logitech_receiver/settings_templates.py:1038 +#, python-format +msgid "Disables the %s key." +msgstr "Menonaktifkan kunci %s." + +#: lib/logitech_receiver/settings_templates.py:1051 +#: lib/logitech_receiver/settings_templates.py:1108 +msgid "Set OS" +msgstr "Atur OS" + +#: lib/logitech_receiver/settings_templates.py:1052 +#: lib/logitech_receiver/settings_templates.py:1109 +msgid "Change keys to match OS." +msgstr "Ubah kunci agar sesuai dengan OS." + +#: lib/logitech_receiver/settings_templates.py:1121 +msgid "Change Host" +msgstr "Ganti Host" + +#: lib/logitech_receiver/settings_templates.py:1122 +msgid "Switch connection to a different host" +msgstr "Alihkan koneksi ke host lain" + +#: lib/logitech_receiver/settings_templates.py:1146 +msgid "Performs a left click." +msgstr "Melakukan klik kiri." + +#: lib/logitech_receiver/settings_templates.py:1146 +msgid "Single tap" +msgstr "Ketuk sekali" + +#: lib/logitech_receiver/settings_templates.py:1147 +msgid "Performs a right click." +msgstr "Melakukan klik kanan." + +#: lib/logitech_receiver/settings_templates.py:1147 +msgid "Single tap with two fingers" +msgstr "Ketuk satu kali dengan dua jari" + +#: lib/logitech_receiver/settings_templates.py:1148 +msgid "Single tap with three fingers" +msgstr "Ketuk satu kali dengan tiga jari" + +#: lib/logitech_receiver/settings_templates.py:1152 +msgid "Double tap" +msgstr "Ketuk dua kali" + +#: lib/logitech_receiver/settings_templates.py:1152 +msgid "Performs a double click." +msgstr "Melakukan klik dua kali." + +#: lib/logitech_receiver/settings_templates.py:1153 +msgid "Double tap with two fingers" +msgstr "Ketuk dua kali dengan dua jari" + +#: lib/logitech_receiver/settings_templates.py:1154 +msgid "Double tap with three fingers" +msgstr "Ketuk dua kali dengan tiga jari" + +#: lib/logitech_receiver/settings_templates.py:1157 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Menyeret item dengan menyeret jari setelah mengetuk dua kali." + +#: lib/logitech_receiver/settings_templates.py:1157 +msgid "Tap and drag" +msgstr "Ketuk dan seret" + +#: lib/logitech_receiver/settings_templates.py:1159 +msgid "Tap and drag with two fingers" +msgstr "Ketuk dan seret dengan dua jari" + +#: lib/logitech_receiver/settings_templates.py:1160 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Menyeret item dengan menyeret jari setelah mengetuk dua kali." + +#: lib/logitech_receiver/settings_templates.py:1162 +msgid "Tap and drag with three fingers" +msgstr "Ketuk dan seret dengan tiga jari" + +#: lib/logitech_receiver/settings_templates.py:1165 +msgid "Suppress tap and edge gestures" +msgstr "Menekan gerakan ketuk dan tepi" + +#: lib/logitech_receiver/settings_templates.py:1166 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "Menonaktifkan gerakan ketuk dan tepi (setara dengan menekan Fn+Klik " + "Kiri)." + +#: lib/logitech_receiver/settings_templates.py:1168 +msgid "Scroll with one finger" +msgstr "Gulir dengan satu jari" + +#: lib/logitech_receiver/settings_templates.py:1168 +#: lib/logitech_receiver/settings_templates.py:1169 +#: lib/logitech_receiver/settings_templates.py:1172 +msgid "Scrolls." +msgstr "Penggulir" + +#: lib/logitech_receiver/settings_templates.py:1169 +#: lib/logitech_receiver/settings_templates.py:1172 +msgid "Scroll with two fingers" +msgstr "Gulir dengan dua jari" + +#: lib/logitech_receiver/settings_templates.py:1170 +msgid "Scroll horizontally with two fingers" +msgstr "Gulir secara horizontal dengan dua jari" + +#: lib/logitech_receiver/settings_templates.py:1170 +msgid "Scrolls horizontally." +msgstr "Menggulir secara horizontal." + +#: lib/logitech_receiver/settings_templates.py:1171 +msgid "Scroll vertically with two fingers" +msgstr "Gulir secara vertikal dengan dua jari" + +#: lib/logitech_receiver/settings_templates.py:1171 +msgid "Scrolls vertically." +msgstr "Gulir secara vertikal." + +#: lib/logitech_receiver/settings_templates.py:1173 +msgid "Inverts the scrolling direction." +msgstr "Membalikkan arah pengguliran." + +#: lib/logitech_receiver/settings_templates.py:1173 +msgid "Natural scrolling" +msgstr "Pengguliran alami" + +#: lib/logitech_receiver/settings_templates.py:1174 +msgid "Enables the thumbwheel." +msgstr "Mengaktifkan roda jempol." + +#: lib/logitech_receiver/settings_templates.py:1174 +msgid "Thumbwheel" +msgstr "Roda jempol" + +#: lib/logitech_receiver/settings_templates.py:1185 +#: lib/logitech_receiver/settings_templates.py:1189 +msgid "Swipe from the top edge" +msgstr "Geser dari tepi atas" + +#: lib/logitech_receiver/settings_templates.py:1186 +msgid "Swipe from the left edge" +msgstr "Geser dari tepi kiri" + +#: lib/logitech_receiver/settings_templates.py:1187 +msgid "Swipe from the right edge" +msgstr "Geser dari tepi kanan" + +#: lib/logitech_receiver/settings_templates.py:1188 +msgid "Swipe from the bottom edge" +msgstr "Geser dari tepi bawah" + +#: lib/logitech_receiver/settings_templates.py:1190 +msgid "Swipe two fingers from the left edge" +msgstr "Geser dua jari dari tepi kiri" + +#: lib/logitech_receiver/settings_templates.py:1191 +msgid "Swipe two fingers from the right edge" +msgstr "Geser dua jari dari tepi kanan" + +#: lib/logitech_receiver/settings_templates.py:1192 +msgid "Swipe two fingers from the bottom edge" +msgstr "Geser dua jari dari tepi bawah" + +#: lib/logitech_receiver/settings_templates.py:1193 +msgid "Swipe two fingers from the top edge" +msgstr "Geser dua jari dari tepi atas" + +#: lib/logitech_receiver/settings_templates.py:1194 +#: lib/logitech_receiver/settings_templates.py:1198 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "Jepit untuk memperkecil; sebarkan untuk memperbesar." + +#: lib/logitech_receiver/settings_templates.py:1194 +msgid "Zoom with two fingers." +msgstr "Perbesar dengan dua jari." + +#: lib/logitech_receiver/settings_templates.py:1195 +msgid "Pinch to zoom out." +msgstr "Jepit untuk memperkecil." + +#: lib/logitech_receiver/settings_templates.py:1196 +msgid "Spread to zoom in." +msgstr "Sebarkan untuk memperbesar." + +#: lib/logitech_receiver/settings_templates.py:1197 +msgid "Zoom with three fingers." +msgstr "Perbesar dengan tiga jari." + +#: lib/logitech_receiver/settings_templates.py:1198 +msgid "Zoom with two fingers" +msgstr "Zoom dengan dua jari" + +#: lib/logitech_receiver/settings_templates.py:1216 +msgid "Pixel zone" +msgstr "Zona piksel" + +#: lib/logitech_receiver/settings_templates.py:1217 +msgid "Ratio zone" +msgstr "Zona rasio" + +#: lib/logitech_receiver/settings_templates.py:1218 +msgid "Scale factor" +msgstr "Faktor skala" + +#: lib/logitech_receiver/settings_templates.py:1218 +msgid "Sets the cursor speed." +msgstr "Mengatur kecepatan kursor." + +#: lib/logitech_receiver/settings_templates.py:1222 +msgid "Left" +msgstr "Kiri" + +#: lib/logitech_receiver/settings_templates.py:1222 +msgid "Left-most coordinate." +msgstr "Koordinat paling kiri." + +#: lib/logitech_receiver/settings_templates.py:1223 +msgid "Bottom" +msgstr "Dasar" + +#: lib/logitech_receiver/settings_templates.py:1223 +msgid "Bottom coordinate." +msgstr "Koordinat bawah." + +#: lib/logitech_receiver/settings_templates.py:1224 +msgid "Width" +msgstr "Lebar" + +#: lib/logitech_receiver/settings_templates.py:1224 +msgid "Width." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1225 +msgid "Height" +msgstr "Tinggi" + +#: lib/logitech_receiver/settings_templates.py:1225 +msgid "Height." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1226 +msgid "Cursor speed." +msgstr "Kecepatan kursor." + +#: lib/logitech_receiver/settings_templates.py:1226 +msgid "Scale" +msgstr "Skala" + +#: lib/logitech_receiver/settings_templates.py:1232 +msgid "Gestures" +msgstr "Gestur" + +#: lib/logitech_receiver/settings_templates.py:1233 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Sesuaikan perilaku mouse/touchpad." + +#: lib/logitech_receiver/settings_templates.py:1249 +msgid "Gestures Diversion" +msgstr "Pengalihan Gerakan" + +#: lib/logitech_receiver/settings_templates.py:1250 +msgid "Divert mouse/touchpad gestures." +msgstr "Alihkan gerakan mouse/touchpad." + +#: lib/logitech_receiver/settings_templates.py:1266 +msgid "Gesture params" +msgstr "Param isyarat" + +#: lib/logitech_receiver/settings_templates.py:1267 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Ubah parameter numerik mouse/touchpad." + +#: lib/logitech_receiver/settings_templates.py:1291 +msgid "M-Key LEDs" +msgstr "LED M-Key" + +#: lib/logitech_receiver/settings_templates.py:1293 +msgid "Control the M-Key LEDs." +msgstr "Kontrol LED M-Key." + +#: lib/logitech_receiver/settings_templates.py:1297 +#: lib/logitech_receiver/settings_templates.py:1328 +msgid "May need G Keys diverted to be effective." +msgstr "Mungkin perlu tombol G dialihkan agar efektif." + +#: lib/logitech_receiver/settings_templates.py:1303 +#, python-format +msgid "Lights up the %s key." +msgstr "Nyalakan tombol %s." + +#: lib/logitech_receiver/settings_templates.py:1322 +msgid "MR-Key LED" +msgstr "LED Kunci MR" + +#: lib/logitech_receiver/settings_templates.py:1324 +msgid "Control the MR-Key LED." +msgstr "Kontrol LED MR-Key." + +#: lib/logitech_receiver/settings_templates.py:1345 +msgid "Persistent Key/Button Mapping" +msgstr "Pemetaan Kunci/Tombol Persisten" + +#: lib/logitech_receiver/settings_templates.py:1347 +msgid "Permanently change the mapping for the key or button." +msgstr "Ubah pemetaan kunci atau tombol secara permanen." + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Changing important keys or buttons (such as for the left mouse " + "button) can result in an unusable system." +msgstr "Mengganti tombol atau tombol penting (misalnya untuk mouse sebelah " + "kiritombol) dapat mengakibatkan sistem tidak dapat digunakan." + +#: lib/logitech_receiver/settings_templates.py:1406 +msgid "Sidetone" +msgstr "Nada samping" + +#: lib/logitech_receiver/settings_templates.py:1407 +msgid "Set sidetone level." +msgstr "Setel level nada samping." + +#: lib/logitech_receiver/settings_templates.py:1416 +msgid "Equalizer" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1417 +msgid "Set equalizer levels." +msgstr "Tetapkan level equalizer." + +#: lib/logitech_receiver/settings_templates.py:1439 +msgid "Hz" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1445 +msgid "Power Management" +msgstr "Manajemen daya" + +#: lib/logitech_receiver/settings_templates.py:1446 +msgid "Power off in minutes (0 for never)." +msgstr "Matikan dalam hitungan menit (0 untuk tidak pernah)." + +#: lib/logitech_receiver/settings_templates.py:1457 +msgid "LED Control" +msgstr "Kontrol LED" + +#: lib/logitech_receiver/settings_templates.py:1458 +msgid "Switch control of LEDs between device and Solaar" +msgstr "Ganti kontrol LED antara perangkat dan Solar" + +#: lib/logitech_receiver/settings_templates.py:1473 +msgid "LED Zone Effects" +msgstr "Efek Zona LED" + +#: lib/logitech_receiver/settings_templates.py:1474 +msgid "Set effect for LED Zone" +msgstr "Atur efek untuk Zona LED" + +#: lib/logitech_receiver/settings_templates.py:1477 +msgid "Speed" +msgstr "Kecepatan" + +#: lib/logitech_receiver/settings_templates.py:1478 +msgid "Period" +msgstr "Periode" + +#: lib/logitech_receiver/settings_templates.py:1479 +msgid "Intensity" +msgstr "Intensitas" + +#: lib/logitech_receiver/settings_templates.py:1480 +msgid "Ramp" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1494 +msgid "LEDs" +msgstr "" + +#: lib/solaar/ui/__init__.py:112 +msgid "Another Solaar process is already running so just expose its window" +msgstr "Proses Solaar lain sudah berjalan jadi buka saja jendelanya" + +#: lib/solaar/ui/about.py:38 +msgid "Manages Logitech receivers,\n" + "keyboards, mice, and tablets." +msgstr "Mengelola receiver Logitech,\n" + "keyboard, mouse, dan tablet." + +#: lib/solaar/ui/about.py:46 +msgid "Additional Programming" +msgstr "Pemrograman Tambahan" + +#: lib/solaar/ui/about.py:47 +msgid "GUI design" +msgstr "desain GUI" + +#: lib/solaar/ui/about.py:49 +msgid "Testing" +msgstr "Pengujian" + +#: lib/solaar/ui/about.py:57 +msgid "Logitech documentation" +msgstr "Dokumentasi Logitech" + +#: lib/solaar/ui/action.py:83 lib/solaar/ui/action.py:87 +#: lib/solaar/ui/window.py:199 +msgid "Unpair" +msgstr "Lepaskan pasangan" + +#: lib/solaar/ui/action.py:86 lib/solaar/ui/diversion_rules.py:104 +msgid "Cancel" +msgstr "Membatalkan" + +#: lib/solaar/ui/common.py:35 +msgid "Permissions error" +msgstr "Kesalahan izin" + +#: lib/solaar/ui/common.py:37 +#, python-format +msgid "Found a Logitech receiver or device (%s), but did not have " + "permission to open it." +msgstr "Menemukan receiver atau perangkat Logitech (%s), namun tidak " + "memiliki izin untuk membukanya." + +#: lib/solaar/ui/common.py:39 +msgid "If you've just installed Solaar, try disconnecting the receiver or " + "device and then reconnecting it." +msgstr "Kalau baru install Solaar, coba cabut receivernya atauperangkat dan " + "kemudian menyambungkannya kembali." + +#: lib/solaar/ui/common.py:42 +msgid "Cannot connect to device error" +msgstr "Tidak dapat terhubung ke perangkat kesalahan" + +#: lib/solaar/ui/common.py:44 +#, python-format +msgid "Found a Logitech receiver or device at %s, but encountered an error " + "connecting to it." +msgstr "Menemukan receiver atau perangkat Logitech di %s, namun mengalami " + "kesalahanmenghubungkannya." + +#: lib/solaar/ui/common.py:46 +msgid "Try disconnecting the device and then reconnecting it or turning it " + "off and then on." +msgstr "Coba putuskan sambungan perangkat lalu sambungkan kembali atau " + "putarmati lalu hidupkan." + +#: lib/solaar/ui/common.py:49 +msgid "Unpairing failed" +msgstr "Pembatalan pasangan gagal" + +#: lib/solaar/ui/common.py:51 +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Gagal melepaskan pasangan %{device} dari %{receiver}." + +#: lib/solaar/ui/common.py:53 +msgid "The receiver returned an error, with no further details." +msgstr "Penerima mengembalikan kesalahan, tanpa rincian lebih lanjut." + +#: lib/solaar/ui/config_panel.py:231 +msgid "Complete - ENTER to change" +msgstr "Selesai - ENTER untuk mengubah" + +#: lib/solaar/ui/config_panel.py:231 +msgid "Incomplete" +msgstr "Tidak lengkap" + +#: lib/solaar/ui/config_panel.py:476 lib/solaar/ui/config_panel.py:529 +#, python-format +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d nilai" + +#: lib/solaar/ui/config_panel.py:612 +msgid "Changes allowed" +msgstr "Perubahan diperbolehkan" + +#: lib/solaar/ui/config_panel.py:613 +msgid "No changes allowed" +msgstr "Tidak ada perubahan yang diizinkan" + +#: lib/solaar/ui/config_panel.py:614 +msgid "Ignore this setting" +msgstr "Abaikan pengaturan ini" + +#: lib/solaar/ui/config_panel.py:659 +msgid "Working" +msgstr "Bekerja" + +#: lib/solaar/ui/config_panel.py:662 +msgid "Read/write operation failed." +msgstr "Operasi baca/tulis gagal." + +#: lib/solaar/ui/diversion_rules.py:66 +msgid "Built-in rules" +msgstr "Aturan bawaan" + +#: lib/solaar/ui/diversion_rules.py:66 +msgid "User-defined rules" +msgstr "Aturan yang ditentukan pengguna" + +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:1089 +msgid "Rule" +msgstr "Aturan" + +#: lib/solaar/ui/diversion_rules.py:69 lib/solaar/ui/diversion_rules.py:538 +#: lib/solaar/ui/diversion_rules.py:665 +msgid "Sub-rule" +msgstr "Sub-aturan" + +#: lib/solaar/ui/diversion_rules.py:71 +msgid "[empty]" +msgstr "[kosong]" + +#: lib/solaar/ui/diversion_rules.py:95 +msgid "Make changes permanent?" +msgstr "Membuat perubahan permanen?" + +#: lib/solaar/ui/diversion_rules.py:100 +msgid "Yes" +msgstr "Ya" + +#: lib/solaar/ui/diversion_rules.py:102 +msgid "No" +msgstr "Tidak" + +#: lib/solaar/ui/diversion_rules.py:107 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "Jika Anda memilih Tidak, perubahan akan hilang saat Solaar ditutup." + +#: lib/solaar/ui/diversion_rules.py:138 +msgid "Solaar Rule Editor" +msgstr "Editor Aturan Surya" + +#: lib/solaar/ui/diversion_rules.py:231 +msgid "Save changes" +msgstr "Simpan perubahan" + +#: lib/solaar/ui/diversion_rules.py:236 +msgid "Discard changes" +msgstr "Membuang perubahan" + +#: lib/solaar/ui/diversion_rules.py:397 +msgid "Insert here" +msgstr "Masukkan di sini" + +#: lib/solaar/ui/diversion_rules.py:399 +msgid "Insert above" +msgstr "Masukkan di atas" + +#: lib/solaar/ui/diversion_rules.py:401 +msgid "Insert below" +msgstr "Masuk di bawah" + +#: lib/solaar/ui/diversion_rules.py:407 +msgid "Insert new rule here" +msgstr "Masukkan aturan baru di sini" + +#: lib/solaar/ui/diversion_rules.py:409 +msgid "Insert new rule above" +msgstr "Masukkan aturan baru di atas" + +#: lib/solaar/ui/diversion_rules.py:411 +msgid "Insert new rule below" +msgstr "Masukkan aturan baru di bawah" + +#: lib/solaar/ui/diversion_rules.py:456 +msgid "Paste here" +msgstr "Tempel di sini" + +#: lib/solaar/ui/diversion_rules.py:458 +msgid "Paste above" +msgstr "Tempel di atas" + +#: lib/solaar/ui/diversion_rules.py:460 +msgid "Paste below" +msgstr "Tempel di bawah" + +#: lib/solaar/ui/diversion_rules.py:466 +msgid "Paste rule here" +msgstr "Tempel aturan di sini" + +#: lib/solaar/ui/diversion_rules.py:468 +msgid "Paste rule above" +msgstr "Tempel aturan di atas" + +#: lib/solaar/ui/diversion_rules.py:470 +msgid "Paste rule below" +msgstr "Tempel aturan di bawah" + +#: lib/solaar/ui/diversion_rules.py:474 +msgid "Paste rule" +msgstr "Tempel aturan" + +#: lib/solaar/ui/diversion_rules.py:503 +msgid "Flatten" +msgstr "Meratakan" + +#: lib/solaar/ui/diversion_rules.py:536 +msgid "Insert" +msgstr "Menyisipkan" + +#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:667 +#: lib/solaar/ui/diversion_rules.py:1129 +msgid "Or" +msgstr "Atau" + +#: lib/solaar/ui/diversion_rules.py:540 lib/solaar/ui/diversion_rules.py:666 +#: lib/solaar/ui/diversion_rules.py:1115 +msgid "And" +msgstr "Dan" + +#: lib/solaar/ui/diversion_rules.py:542 +msgid "Condition" +msgstr "Kondisi" + +#: lib/solaar/ui/diversion_rules.py:544 lib/solaar/ui/diversion_rules.py:1290 +msgid "Feature" +msgstr "Fitur" + +#: lib/solaar/ui/diversion_rules.py:545 lib/solaar/ui/diversion_rules.py:1325 +msgid "Report" +msgstr "Laporan" + +#: lib/solaar/ui/diversion_rules.py:546 lib/solaar/ui/diversion_rules.py:1204 +msgid "Process" +msgstr "Proses" + +#: lib/solaar/ui/diversion_rules.py:547 +msgid "Mouse process" +msgstr "Proses tikus" + +#: lib/solaar/ui/diversion_rules.py:548 lib/solaar/ui/diversion_rules.py:1362 +msgid "Modifiers" +msgstr "Pengubah" + +#: lib/solaar/ui/diversion_rules.py:549 lib/solaar/ui/diversion_rules.py:1414 +msgid "Key" +msgstr "Kunci" + +#: lib/solaar/ui/diversion_rules.py:550 lib/solaar/ui/diversion_rules.py:1455 +msgid "KeyIsDown" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:551 lib/solaar/ui/diversion_rules.py:2260 +msgid "Active" +msgstr "Aktif" + +#: lib/solaar/ui/diversion_rules.py:552 lib/solaar/ui/diversion_rules.py:2217 +#: lib/solaar/ui/diversion_rules.py:2269 lib/solaar/ui/diversion_rules.py:2319 +msgid "Device" +msgstr "Perangkat" + +#: lib/solaar/ui/diversion_rules.py:553 lib/solaar/ui/diversion_rules.py:2295 +msgid "Host" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:554 lib/solaar/ui/diversion_rules.py:2337 +msgid "Setting" +msgstr "Pengaturan" + +#: lib/solaar/ui/diversion_rules.py:555 lib/solaar/ui/diversion_rules.py:1470 +#: lib/solaar/ui/diversion_rules.py:1519 +msgid "Test" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:556 lib/solaar/ui/diversion_rules.py:1633 +msgid "Test bytes" +msgstr "Uji byte" + +#: lib/solaar/ui/diversion_rules.py:557 lib/solaar/ui/diversion_rules.py:1734 +msgid "Mouse Gesture" +msgstr "Gerakan Mouse" + +#: lib/solaar/ui/diversion_rules.py:561 +msgid "Action" +msgstr "Aksi" + +#: lib/solaar/ui/diversion_rules.py:563 lib/solaar/ui/diversion_rules.py:1844 +msgid "Key press" +msgstr "Tekan tombol" + +#: lib/solaar/ui/diversion_rules.py:564 lib/solaar/ui/diversion_rules.py:1895 +msgid "Mouse scroll" +msgstr "Gulir mouse" + +#: lib/solaar/ui/diversion_rules.py:565 lib/solaar/ui/diversion_rules.py:1956 +msgid "Mouse click" +msgstr "Klik mouse" + +#: lib/solaar/ui/diversion_rules.py:566 +msgid "Set" +msgstr "Mengatur" + +#: lib/solaar/ui/diversion_rules.py:567 lib/solaar/ui/diversion_rules.py:2026 +msgid "Execute" +msgstr "Menjalankan" + +#: lib/solaar/ui/diversion_rules.py:568 lib/solaar/ui/diversion_rules.py:1160 +msgid "Later" +msgstr "Nanti" + +#: lib/solaar/ui/diversion_rules.py:597 +msgid "Insert new rule" +msgstr "Masukkan aturan baru" + +#: lib/solaar/ui/diversion_rules.py:617 lib/solaar/ui/diversion_rules.py:1681 +#: lib/solaar/ui/diversion_rules.py:1786 lib/solaar/ui/diversion_rules.py:1985 +msgid "Delete" +msgstr "Menghapus" + +#: lib/solaar/ui/diversion_rules.py:639 +msgid "Negate" +msgstr "Meniadakan" + +#: lib/solaar/ui/diversion_rules.py:663 +msgid "Wrap with" +msgstr "Bungkus dengan" + +#: lib/solaar/ui/diversion_rules.py:685 +msgid "Cut" +msgstr "Potong" + +#: lib/solaar/ui/diversion_rules.py:700 +msgid "Paste" +msgstr "Tempel" + +#: lib/solaar/ui/diversion_rules.py:706 +msgid "Copy" +msgstr "Salin" + +#: lib/solaar/ui/diversion_rules.py:1070 +msgid "This editor does not support the selected rule component yet." +msgstr "Editor ini belum mendukung komponen aturan yang dipilih." + +#: lib/solaar/ui/diversion_rules.py:1140 +msgid "Number of seconds to delay." +msgstr "Jumlah detik untuk ditunda." + +#: lib/solaar/ui/diversion_rules.py:1178 +msgid "Not" +msgstr "Bukan" + +#: lib/solaar/ui/diversion_rules.py:1187 +msgid "X11 active process. For use in X11 only." +msgstr "Proses aktif X11. Hanya untuk digunakan di X11." + +#: lib/solaar/ui/diversion_rules.py:1217 +msgid "X11 mouse process. For use in X11 only." +msgstr "Proses mouse X11. Hanya untuk digunakan di X11." + +#: lib/solaar/ui/diversion_rules.py:1234 +msgid "MouseProcess" +msgstr "Proses Tikus" + +#: lib/solaar/ui/diversion_rules.py:1258 +msgid "Feature name of notification triggering rule processing." +msgstr "Nama fitur notifikasi yang memicu pemrosesan aturan." + +#: lib/solaar/ui/diversion_rules.py:1305 +msgid "Report number of notification triggering rule processing." +msgstr "Laporkan jumlah pemberitahuan yang memicu pemrosesan aturan." + +#: lib/solaar/ui/diversion_rules.py:1338 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "Pengubah keyboard aktif. Tidak selalu tersedia di Wayland." + +#: lib/solaar/ui/diversion_rules.py:1378 +msgid "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "Tombol atau tombol yang dialihkan ditekan atau dilepaskan.\n" + "Gunakan pengaturan Pengalihan Tombol/Tombol dan Pengalihan Tombol G " + "untuk mengalihkankunci dan tombol." + +#: lib/solaar/ui/diversion_rules.py:1387 +msgid "Key down" +msgstr "Kunci bawah" + +#: lib/solaar/ui/diversion_rules.py:1390 +msgid "Key up" +msgstr "Kunci atas" + +#: lib/solaar/ui/diversion_rules.py:1430 +msgid "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "Tombol atau tombol yang dialihkan sedang tidak aktif.\n" + "Gunakan pengaturan Pengalihan Tombol/Tombol dan Pengalihan Tombol G " + "untuk mengalihkankunci dan tombol." + +#: lib/solaar/ui/diversion_rules.py:1468 +msgid "Test condition on notification triggering rule processing." +msgstr "Uji kondisi pada notifikasi yang memicu pemrosesan aturan." + +#: lib/solaar/ui/diversion_rules.py:1472 +msgid "Parameter" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1534 +msgid "begin (inclusive)" +msgstr "mulai (inklusif)" + +#: lib/solaar/ui/diversion_rules.py:1535 +msgid "end (exclusive)" +msgstr "akhir (eksklusif)" + +#: lib/solaar/ui/diversion_rules.py:1543 +msgid "range" +msgstr "jangkauan" + +#: lib/solaar/ui/diversion_rules.py:1546 +msgid "minimum" +msgstr "minimum" + +#: lib/solaar/ui/diversion_rules.py:1547 +msgid "maximum" +msgstr "maksimum" + +#: lib/solaar/ui/diversion_rules.py:1549 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "byte %(0)d hingga %(1)d, mulai dari %(2)d hingga %(3)d" + +#: lib/solaar/ui/diversion_rules.py:1552 lib/solaar/ui/diversion_rules.py:1553 +msgid "mask" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1554 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "byte %(0)d hingga %(1)d, sembunyikan %(2)d" + +#: lib/solaar/ui/diversion_rules.py:1563 +msgid "Bit or range test on bytes in notification message triggering rule " + "processing." +msgstr "Pengujian bit atau rentang pada byte dalam aturan pemicu pesan " + "notifikasipengolahan." + +#: lib/solaar/ui/diversion_rules.py:1573 +msgid "type" +msgstr "jenis" + +#: lib/solaar/ui/diversion_rules.py:1661 +msgid "Mouse gesture with optional initiating button followed by zero or " + "more mouse movements." +msgstr "Gerakan mouse dengan tombol memulai opsional diikuti dengan nol atau " + "lebih banyak gerakan tikus." + +#: lib/solaar/ui/diversion_rules.py:1666 +msgid "Add movement" +msgstr "Tambahkan gerakan" + +#: lib/solaar/ui/diversion_rules.py:1760 +msgid "Simulate a chorded key click or depress or release.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "Simulasikan klik atau tekan atau lepaskan kunci chord.\n" + "On Wayland memerlukan akses tulis ke /dev/uinput." + +#: lib/solaar/ui/diversion_rules.py:1765 +msgid "Add key" +msgstr "Tambahkan kunci" + +#: lib/solaar/ui/diversion_rules.py:1768 +msgid "Click" +msgstr "Klik" + +#: lib/solaar/ui/diversion_rules.py:1771 +msgid "Depress" +msgstr "Menekan" + +#: lib/solaar/ui/diversion_rules.py:1774 +msgid "Release" +msgstr "Melepaskan" + +#: lib/solaar/ui/diversion_rules.py:1859 +msgid "Simulate a mouse scroll.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "Simulasikan gulir mouse.\n" + "On Wayland memerlukan akses tulis ke /dev/uinput." + +#: lib/solaar/ui/diversion_rules.py:1915 +msgid "Simulate a mouse click.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "Simulasikan klik mouse.\n" + "On Wayland memerlukan akses tulis ke /dev/uinput." + +#: lib/solaar/ui/diversion_rules.py:1918 +msgid "Button" +msgstr "Tombol" + +#: lib/solaar/ui/diversion_rules.py:1919 +msgid "Count and Action" +msgstr "Hitungan dan Tindakan" + +#: lib/solaar/ui/diversion_rules.py:1968 +msgid "Execute a command with arguments." +msgstr "Jalankan perintah dengan argumen." + +#: lib/solaar/ui/diversion_rules.py:1971 +msgid "Add argument" +msgstr "Tambahkan argumen" + +#: lib/solaar/ui/diversion_rules.py:2046 +msgid "Toggle" +msgstr "Beralih" + +#: lib/solaar/ui/diversion_rules.py:2047 +msgid "True" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2048 +msgid "False" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2061 +msgid "Unsupported setting" +msgstr "Pengaturan tidak didukung" + +#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2243 +#: lib/solaar/ui/diversion_rules.py:2325 lib/solaar/ui/diversion_rules.py:2568 +#: lib/solaar/ui/diversion_rules.py:2586 +msgid "Originating device" +msgstr "Perangkat asal" + +#: lib/solaar/ui/diversion_rules.py:2256 +msgid "Device is active and its settings can be changed." +msgstr "Perangkat aktif dan pengaturannya dapat diubah." + +#: lib/solaar/ui/diversion_rules.py:2265 +msgid "Device that originated the current notification." +msgstr "Perangkat yang memunculkan notifikasi saat ini." + +#: lib/solaar/ui/diversion_rules.py:2278 +msgid "Name of host computer." +msgstr "Nama komputer induk." + +#: lib/solaar/ui/diversion_rules.py:2345 +msgid "Value" +msgstr "Nilai" + +#: lib/solaar/ui/diversion_rules.py:2353 +msgid "Item" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2627 +msgid "Change setting on device" +msgstr "Ubah pengaturan pada perangkat" + +#: lib/solaar/ui/diversion_rules.py:2643 +msgid "Setting on device" +msgstr "Pengaturan pada perangkat" + +#: lib/solaar/ui/notify.py:118 +msgid "unspecified reason" +msgstr "alasan yang tidak ditentukan" + +#: lib/solaar/ui/pair_window.py:126 lib/solaar/ui/pair_window.py:260 +#: lib/solaar/ui/pair_window.py:296 +#, python-format +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: memasangkan perangkat baru" + +#: lib/solaar/ui/pair_window.py:127 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "Masukkan kode sandi pada %(name)s." + +#: lib/solaar/ui/pair_window.py:130 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "Ketik %(passcode)s lalu tekan tombol enter." + +#: lib/solaar/ui/pair_window.py:133 +msgid "left" +msgstr "Kiri" + +#: lib/solaar/ui/pair_window.py:133 +msgid "right" +msgstr "Kanan" + +#: lib/solaar/ui/pair_window.py:135 +#, python-format +msgid "Press %(code)s\n" + "and then press left and right buttons simultaneously." +msgstr "Tekan %(code)s\n" + "lalu tekan tombol kiri dan kanan secara bersamaan." + +#: lib/solaar/ui/pair_window.py:192 +msgid "Pairing failed" +msgstr "Pemasangan gagal" + +#: lib/solaar/ui/pair_window.py:194 +msgid "Make sure your device is within range, and has a decent battery " + "charge." +msgstr "Pastikan perangkat Anda berada dalam jangkauan dan memiliki baterai " + "yang layakmengenakan biaya." + +#: lib/solaar/ui/pair_window.py:196 +msgid "A new device was detected, but it is not compatible with this " + "receiver." +msgstr "Perangkat baru terdeteksi, tetapi tidak kompatibel dengan " + "inipenerima." + +#: lib/solaar/ui/pair_window.py:198 +msgid "More paired devices than receiver can support." +msgstr "Lebih banyak perangkat berpasangan daripada yang dapat didukung oleh " + "receiver." + +#: lib/solaar/ui/pair_window.py:200 +msgid "No further details are available about the error." +msgstr "Tidak ada rincian lebih lanjut yang tersedia tentang kesalahan " + "tersebut." + +#: lib/solaar/ui/pair_window.py:214 +msgid "Found a new device:" +msgstr "Menemukan perangkat baru:" + +#: lib/solaar/ui/pair_window.py:239 +msgid "The wireless link is not encrypted" +msgstr "Tautan nirkabel tidak dienkripsi" + +#: lib/solaar/ui/pair_window.py:268 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "Penerima Unifying hanya kompatibel dengan perangkat Unifying." + +#: lib/solaar/ui/pair_window.py:270 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "Penerima Bolt hanya kompatibel dengan perangkat Bolt." + +#: lib/solaar/ui/pair_window.py:272 +msgid "Other receivers are only compatible with a few devices." +msgstr "Receiver lain hanya kompatibel dengan beberapa perangkat." + +#: lib/solaar/ui/pair_window.py:274 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "Perangkat tidak boleh dipasangkan dengan receiver terdekat yang " + "aktif." + +#: lib/solaar/ui/pair_window.py:278 +msgid "Press a pairing button or key until the pairing light flashes " + "quickly." +msgstr "Tekan tombol atau kunci penyandingan hingga lampu penyandingan " + "berkedipdengan cepat." + +#: lib/solaar/ui/pair_window.py:280 +msgid "You may have to first turn the device off and on again." +msgstr "Anda mungkin harus mematikan dan menghidupkan kembali perangkat " + "terlebih dahulu." + +#: lib/solaar/ui/pair_window.py:282 +msgid "Turn on the device you want to pair." +msgstr "Nyalakan perangkat yang ingin Anda pasangkan." + +#: lib/solaar/ui/pair_window.py:284 +msgid "If the device is already turned on, turn it off and on again." +msgstr "Jika perangkat sudah dihidupkan, matikan dan hidupkan kembali." + +#: lib/solaar/ui/pair_window.py:288 +#, python-format +msgid "\n" + "\n" + "This receiver has %d pairing remaining." +msgid_plural "\n" + "\n" + "This receiver has %d pairings remaining." +msgstr[0] "\n" + "\n" + "Penerima ini memiliki %d sisa penyandingan." + +#: lib/solaar/ui/pair_window.py:294 +msgid "\n" + "Cancelling at this point will not use up a pairing." +msgstr "\n" + "Membatalkan pada saat ini tidak akan menghabiskan pasangan." + +#: lib/solaar/ui/tray.py:58 +msgid "No supported device found" +msgstr "Tidak ditemukan perangkat yang didukung" + +#: lib/solaar/ui/tray.py:63 lib/solaar/ui/window.py:321 +#, python-format +msgid "About %s" +msgstr "Tentang %s" + +#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#, python-format +msgid "Quit %s" +msgstr "Keluar %s" + +#: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:302 +msgid "no receiver" +msgstr "tidak ada penerima" + +#: lib/solaar/ui/tray.py:315 lib/solaar/ui/tray.py:320 +msgid "offline" +msgstr "" + +#: lib/solaar/ui/tray.py:318 +msgid "no status" +msgstr "tidak ada status" + +#: lib/solaar/ui/window.py:99 +msgid "Scanning" +msgstr "Memindai" + +#: lib/solaar/ui/window.py:132 +msgid "Battery" +msgstr "Baterai" + +#: lib/solaar/ui/window.py:135 +msgid "Wireless Link" +msgstr "Tautan Nirkabel" + +#: lib/solaar/ui/window.py:139 +msgid "Lighting" +msgstr "" + +#: lib/solaar/ui/window.py:173 +msgid "Show Technical Details" +msgstr "Tampilkan Detail Teknis" + +#: lib/solaar/ui/window.py:189 +msgid "Pair new device" +msgstr "Pasangkan perangkat baru" + +#: lib/solaar/ui/window.py:207 +msgid "Select a device" +msgstr "Pilih perangkat" + +#: lib/solaar/ui/window.py:324 +msgid "Rule Editor" +msgstr "Editor Aturan" + +#: lib/solaar/ui/window.py:543 +msgid "Path" +msgstr "Path" + +#: lib/solaar/ui/window.py:546 +msgid "USB ID" +msgstr "ID USB" + +#: lib/solaar/ui/window.py:549 lib/solaar/ui/window.py:551 +#: lib/solaar/ui/window.py:566 lib/solaar/ui/window.py:568 +msgid "Serial" +msgstr "" + +#: lib/solaar/ui/window.py:555 +msgid "Index" +msgstr "Indeks" + +#: lib/solaar/ui/window.py:557 +msgid "Wireless PID" +msgstr "PID Nirkabel" + +#: lib/solaar/ui/window.py:559 +msgid "Product ID" +msgstr "ID Produk" + +#: lib/solaar/ui/window.py:561 +msgid "Protocol" +msgstr "Protokol" + +#: lib/solaar/ui/window.py:561 +msgid "Unknown" +msgstr "Tidak Diketahui" + +#: lib/solaar/ui/window.py:563 +msgid "Polling rate" +msgstr "Tingkat pemungutan suara" + +#: lib/solaar/ui/window.py:570 +msgid "Unit ID" +msgstr "ID Satuan" + +#: lib/solaar/ui/window.py:584 +msgid "Notifications" +msgstr "Pemberitahuan" + +#: lib/solaar/ui/window.py:628 +msgid "No device paired." +msgstr "Tidak ada perangkat yang dipasangkan." + +#: lib/solaar/ui/window.py:637 +#, python-format +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "Hingga %(max_count)s perangkat dapat dipasangkan ke receiver " + "ini." + +#: lib/solaar/ui/window.py:648 +#, python-format +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "Penerima ini memiliki %d sisa penyandingan." + +#: lib/solaar/ui/window.py:705 +msgid "Battery Voltage" +msgstr "Tegangan Baterai" + +#: lib/solaar/ui/window.py:707 +msgid "Voltage reported by battery" +msgstr "Tegangan dilaporkan oleh baterai" + +#: lib/solaar/ui/window.py:709 +msgid "Battery Level" +msgstr "Tingkat Baterai" + +#: lib/solaar/ui/window.py:711 +msgid "Approximate level reported by battery" +msgstr "Perkiraan level yang dilaporkan oleh baterai" + +#: lib/solaar/ui/window.py:718 lib/solaar/ui/window.py:720 +msgid "next reported " +msgstr "selanjutnya dilaporkan " + +#: lib/solaar/ui/window.py:721 +msgid " and next level to be reported." +msgstr " dan tingkat selanjutnya yang akan dilaporkan." + +#: lib/solaar/ui/window.py:737 +msgid "encrypted" +msgstr "terenkripsi" + +#: lib/solaar/ui/window.py:739 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "Hubungan nirkabel antara perangkat ini dan penerimanya dienkripsi." + +#: lib/solaar/ui/window.py:741 +msgid "not encrypted" +msgstr "tidak dienkripsi" + +#: lib/solaar/ui/window.py:745 +msgid "The wireless link between this device and its receiver is not " + "encrypted.\n" + "This is a security issue for pointing devices, and a major security " + "issue for text-input devices." +msgstr "Hubungan nirkabel antara perangkat ini dan penerimanya tidak " + "terenkripsi.\n" + "Ini adalah masalah keamanan untuk perangkat penunjuk, dan merupakan " + "masalah keamanan utamamasalah untuk perangkat input teks." + +#: lib/solaar/ui/window.py:761 +#, python-format +msgid "%(light_level)d lux" +msgstr "%(light_level)d mewah" diff --git a/po/it.po b/po/it.po index c134bc37..4f095249 100644 --- a/po/it.po +++ b/po/it.po @@ -6,7 +6,7 @@ msgid "" msgstr "Project-Id-Version: solaar 0.9.2\n" "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2021-09-25 13:56-0400\n" + "POT-Creation-Date: 2023-12-28 17:40+0100\n" "PO-Revision-Date: 2021-01-28 19:21+0000\n" "Last-Translator: Lorenzo \n" "Language-Team: none\n" @@ -19,272 +19,299 @@ msgstr "Project-Id-Version: solaar 0.9.2\n" "X-Generator: Launchpad (build " "b3a93345a124168b715ec9ae0945884caa15f58f)\n" -#: lib/logitech_receiver/base_usb.py:50 +#: lib/logitech_receiver/base_usb.py:46 +msgid "Bolt Receiver" +msgstr "" + +#: lib/logitech_receiver/base_usb.py:57 msgid "Unifying Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:58 lib/logitech_receiver/base_usb.py:68 -#: lib/logitech_receiver/base_usb.py:79 lib/logitech_receiver/base_usb.py:90 -#: lib/logitech_receiver/base_usb.py:101 +#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 +#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 +#: lib/logitech_receiver/base_usb.py:114 msgid "Nano Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:109 +#: lib/logitech_receiver/base_usb.py:124 msgid "Lightspeed Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:117 +#: lib/logitech_receiver/base_usb.py:133 msgid "EX100 Receiver 27 Mhz" msgstr "" -#: lib/logitech_receiver/device.py:133 lib/solaar/ui/window.py:693 -msgid "unknown" -msgstr "sconosciuto" - -#: lib/logitech_receiver/i18n.py:38 +#: lib/logitech_receiver/i18n.py:30 msgid "empty" msgstr "vuoto" -#: lib/logitech_receiver/i18n.py:39 +#: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "critico" -#: lib/logitech_receiver/i18n.py:40 +#: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "bassa" -#: lib/logitech_receiver/i18n.py:41 +#: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "" -#: lib/logitech_receiver/i18n.py:42 +#: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "buono" -#: lib/logitech_receiver/i18n.py:43 +#: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "piena" -#: lib/logitech_receiver/i18n.py:46 +#: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "in scarica" -#: lib/logitech_receiver/i18n.py:47 +#: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "in ricarica" -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:721 +#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "in ricarica" -#: lib/logitech_receiver/i18n.py:49 +#: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "" -#: lib/logitech_receiver/i18n.py:50 +#: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "quasi piena" -#: lib/logitech_receiver/i18n.py:51 +#: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "carico" -#: lib/logitech_receiver/i18n.py:52 +#: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "ricarica lenta" -#: lib/logitech_receiver/i18n.py:53 +#: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "batteria non valida" -#: lib/logitech_receiver/i18n.py:54 +#: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "errore di temperatura" -#: lib/logitech_receiver/i18n.py:55 +#: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "" -#: lib/logitech_receiver/i18n.py:56 +#: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "" -#: lib/logitech_receiver/i18n.py:57 +#: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "" -#: lib/logitech_receiver/i18n.py:58 +#: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "" -#: lib/logitech_receiver/i18n.py:61 +#: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "timeout del dispositivo" -#: lib/logitech_receiver/i18n.py:62 +#: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "dispositivo non supportato" -#: lib/logitech_receiver/i18n.py:63 +#: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "troppe periferiche" -#: lib/logitech_receiver/i18n.py:64 +#: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "timeout della sequenza" -#: lib/logitech_receiver/i18n.py:67 lib/solaar/ui/window.py:580 +#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 msgid "Firmware" msgstr "Firmware" -#: lib/logitech_receiver/i18n.py:68 +#: lib/logitech_receiver/i18n.py:60 msgid "Bootloader" msgstr "Bootloader" -#: lib/logitech_receiver/i18n.py:69 +#: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardware" -#: lib/logitech_receiver/i18n.py:70 +#: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Altro" -#: lib/logitech_receiver/i18n.py:73 +#: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:74 +#: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:75 +#: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "" -#: lib/logitech_receiver/i18n.py:76 +#: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "" -#: lib/logitech_receiver/i18n.py:77 +#: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "" -#: lib/logitech_receiver/i18n.py:78 +#: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "" -#: lib/logitech_receiver/i18n.py:79 +#: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "" -#: lib/logitech_receiver/i18n.py:80 +#: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "" -#: lib/logitech_receiver/i18n.py:81 +#: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "" -#: lib/logitech_receiver/i18n.py:82 +#: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "" -#: lib/logitech_receiver/i18n.py:83 +#: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "" -#: lib/logitech_receiver/i18n.py:84 +#: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "" -#: lib/logitech_receiver/i18n.py:85 +#: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "" -#: lib/logitech_receiver/i18n.py:86 +#: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "" -#: lib/logitech_receiver/i18n.py:87 +#: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "" -#: lib/logitech_receiver/i18n.py:88 +#: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "" -#: lib/logitech_receiver/i18n.py:89 +#: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:90 +#: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:93 +#: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "" -#: lib/logitech_receiver/i18n.py:94 +#: lib/logitech_receiver/i18n.py:86 msgid "released" msgstr "" -#: lib/logitech_receiver/notifications.py:76 +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is closed" msgstr "" -#: lib/logitech_receiver/notifications.py:76 +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is open" msgstr "" -#: lib/logitech_receiver/notifications.py:160 lib/solaar/ui/notify.py:123 +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is closed" +msgstr "" + +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is open" +msgstr "" + +#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 msgid "connected" msgstr "connesso" -#: lib/logitech_receiver/notifications.py:160 +#: lib/logitech_receiver/notifications.py:224 msgid "disconnected" msgstr "disconnesso" -#: lib/logitech_receiver/notifications.py:198 lib/solaar/ui/notify.py:121 +#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "non associato" -#: lib/logitech_receiver/notifications.py:248 +#: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "acceso" -#: lib/logitech_receiver/settings.py:526 +#: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "" -#: lib/logitech_receiver/settings.py:542 lib/logitech_receiver/settings.py:568 +#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 msgid "feature" msgstr "" -#: lib/logitech_receiver/settings_templates.py:71 +#: lib/logitech_receiver/settings_templates.py:139 +msgid "Swap Fx function" +msgstr "Inverti funzioni Fx" + +#: lib/logitech_receiver/settings_templates.py:140 +msgid "When set, the F1..F12 keys will activate their special function,\n" + "and you must hold the FN key to activate their standard function." +msgstr "Quando abilitato, i tasti F1..F12 attivano le funzioni speciali,\n" + "e dovrai tenere premuto il tasto FN per attivare le funzioni " + "standard." + +#: lib/logitech_receiver/settings_templates.py:142 +msgid "When unset, the F1..F12 keys will activate their standard function,\n" + "and you must hold the FN key to activate their special function." +msgstr "Quando disabilitato, i tasti F1..F12 attivano le funzioni standard,\n" + "dovrai tenere premuto il tasto FN per attivare le funzioni speciali." + +#: lib/logitech_receiver/settings_templates.py:149 msgid "Hand Detection" msgstr "Rilevamento mano" -#: lib/logitech_receiver/settings_templates.py:71 +#: lib/logitech_receiver/settings_templates.py:150 msgid "Turn on illumination when the hands hover over the keyboard." msgstr "Accendi l'illuminazione quando le mani sono sopra la tastiera." -#: lib/logitech_receiver/settings_templates.py:72 +#: lib/logitech_receiver/settings_templates.py:157 msgid "Scroll Wheel Smooth Scrolling" msgstr "" -#: lib/logitech_receiver/settings_templates.py:73 -#: lib/logitech_receiver/settings_templates.py:78 -#: lib/logitech_receiver/settings_templates.py:85 +#: lib/logitech_receiver/settings_templates.py:158 +#: lib/logitech_receiver/settings_templates.py:239 +#: lib/logitech_receiver/settings_templates.py:267 msgid "High-sensitivity mode for vertical scroll with the wheel." msgstr "Modalità di alta sensibilità per lo scorrimento verticale con la " "rotella." -#: lib/logitech_receiver/settings_templates.py:74 +#: lib/logitech_receiver/settings_templates.py:165 msgid "Side Scrolling" msgstr "Scorrimento Laterale" -#: lib/logitech_receiver/settings_templates.py:75 +#: lib/logitech_receiver/settings_templates.py:167 msgid "When disabled, pushing the wheel sideways sends custom button " "events\n" "instead of the standard side-scrolling events." @@ -292,487 +319,618 @@ msgstr "Quando disabilitato, spingendo la rotella lateralmente vengono " "inviati eventi personalizzati\n" "invece degli eventi standard di scorrimento laterale." -#: lib/logitech_receiver/settings_templates.py:77 -msgid "Scroll Wheel High Resolution" +#: lib/logitech_receiver/settings_templates.py:177 +msgid "Sensitivity (DPI - older mice)" msgstr "" -#: lib/logitech_receiver/settings_templates.py:79 -msgid "Scroll Wheel Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:80 -msgid "HID++ mode for vertical scroll with the wheel." -msgstr "Modalità HID++ per lo scorrimento verticale con la rotella" - -#: lib/logitech_receiver/settings_templates.py:81 -msgid "Effectively turns off wheel scrolling in Linux." -msgstr "Di fatto in Linux disattiva lo scorrimento con la rotella" - -#: lib/logitech_receiver/settings_templates.py:82 -msgid "Scroll Wheel Direction" -msgstr "Direzione di scorrimento con la rotella" - -#: lib/logitech_receiver/settings_templates.py:83 -msgid "Invert direction for vertical scroll with wheel." -msgstr "Inverti la direzione per lo scorrimento verticale con la rotella" - -#: lib/logitech_receiver/settings_templates.py:84 -msgid "Scroll Wheel Resolution" -msgstr "Risoluzione dello scorrimento con la rotella" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Frequency of device polling, in milliseconds" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Polling Rate (ms)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:87 -msgid "Swap Fx function" -msgstr "Inverti funzioni Fx" - -#: lib/logitech_receiver/settings_templates.py:88 -msgid "When set, the F1..F12 keys will activate their special function,\n" - "and you must hold the FN key to activate their standard function." -msgstr "Quando abilitato, i tasti F1..F12 attivano le funzioni speciali,\n" - "e dovrai tenere premuto il tasto FN per attivare le funzioni " - "standard." - -#: lib/logitech_receiver/settings_templates.py:90 -msgid "When unset, the F1..F12 keys will activate their standard function,\n" - "and you must hold the FN key to activate their special function." -msgstr "Quando disabilitato, i tasti F1..F12 attivano le funzioni standard,\n" - "dovrai tenere premuto il tasto FN per attivare le funzioni speciali." - -#: lib/logitech_receiver/settings_templates.py:92 +#: lib/logitech_receiver/settings_templates.py:178 +#: lib/logitech_receiver/settings_templates.py:712 msgid "Mouse movement sensitivity" msgstr "Sensibilità al movimento del mouse" -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Sensitivity (DPI)" -msgstr "Sensibilità (DPI)" +#: lib/logitech_receiver/settings_templates.py:208 +#: lib/logitech_receiver/settings_templates.py:218 +#: lib/logitech_receiver/settings_templates.py:225 +msgid "Backlight" +msgstr "Retroilluminazione" -#: lib/logitech_receiver/settings_templates.py:93 +#: lib/logitech_receiver/settings_templates.py:209 +#: lib/logitech_receiver/settings_templates.py:226 +msgid "Set illumination time for keyboard." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:219 +msgid "Turn illumination on or off on keyboard." +msgstr "Accendi o spegni l'illuminazione della tastiera" + +#: lib/logitech_receiver/settings_templates.py:237 +msgid "Scroll Wheel High Resolution" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:240 +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:247 +#: lib/logitech_receiver/settings_templates.py:277 +msgid "Scroll Wheel Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:249 +msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Scroll Wheel Direction" +msgstr "Direzione di scorrimento con la rotella" + +#: lib/logitech_receiver/settings_templates.py:257 +msgid "Invert direction for vertical scroll with wheel." +msgstr "Inverti la direzione per lo scorrimento verticale con la rotella" + +#: lib/logitech_receiver/settings_templates.py:265 +msgid "Scroll Wheel Resolution" +msgstr "Risoluzione dello scorrimento con la rotella" + +#: lib/logitech_receiver/settings_templates.py:279 +msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:288 msgid "Sensitivity (Pointer Speed)" msgstr "Sensibilità (velocità del puntatore)" -#: lib/logitech_receiver/settings_templates.py:94 +#: lib/logitech_receiver/settings_templates.py:289 msgid "Speed multiplier for mouse (256 is normal multiplier)." msgstr "Moltiplicatore della velocità del mouse (256 è il moltiplicatore " "normale)" -#: lib/logitech_receiver/settings_templates.py:95 -msgid "Scroll Wheel Rachet" +#: lib/logitech_receiver/settings_templates.py:299 +msgid "Thumb Wheel Diversion" msgstr "" -#: lib/logitech_receiver/settings_templates.py:96 -msgid "Automatically switch the mouse wheel between ratchet and freespin " - "mode.\n" - "The mouse wheel is always free at 0, and always ratcheted at 50" +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " + "Solaar rules but are otherwise ignored)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:98 -msgid "Backlight" -msgstr "Retroilluminazione" +#: lib/logitech_receiver/settings_templates.py:310 +msgid "Thumb Wheel Direction" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:98 -msgid "Turn illumination on or off on keyboard." -msgstr "Accendi o spegni l'illuminazione della tastiera" +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Invert thumb wheel scroll direction." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:99 +#: lib/logitech_receiver/settings_templates.py:319 +msgid "Onboard Profiles" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:320 +msgid "Enable onboard profiles, which often control report rate and " + "keyboard lighting" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:330 +msgid "Polling Rate (ms)" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:332 +msgid "Frequency of device polling, in milliseconds" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:333 +#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:365 +msgid "Divert crown events" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:366 +msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:375 +msgid "Set crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:383 +msgid "Divert G Keys" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:385 +msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:386 +msgid "May also make M keys and MR key send HID++ notifications" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:402 +msgid "Scroll Wheel Ratcheted" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:403 +msgid "Switch the mouse wheel between speed-controlled ratcheting and " + "always freespin." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Freespinning" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Ratcheted" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:412 +msgid "Scroll Wheel Ratchet Speed" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:414 +msgid "Use the mouse wheel speed to switch between ratcheted and " + "freespinning.\n" + "The mouse wheel is always ratcheted at 50." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:463 msgid "Key/Button Actions" msgstr "Azioni tasto/pulsante" -#: lib/logitech_receiver/settings_templates.py:100 +#: lib/logitech_receiver/settings_templates.py:465 msgid "Change the action for the key or button." msgstr "Cambia l'azione per il tasto o il pulsante" -#: lib/logitech_receiver/settings_templates.py:101 +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Overridden by diversion." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:466 msgid "Changing important actions (such as for the left mouse button) can " "result in an unusable system." msgstr "Cambiare azioni importanti (come per il bottone sinistro del mouse) " "può rendere inutilizzabile il sistema" -#: lib/logitech_receiver/settings_templates.py:102 +#: lib/logitech_receiver/settings_templates.py:639 msgid "Key/Button Diversion" msgstr "" -#: lib/logitech_receiver/settings_templates.py:103 -msgid "Make the key or button send HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." +#: lib/logitech_receiver/settings_templates.py:640 +msgid "Make the key or button send HID++ notifications (Diverted) or " + "initiate Mouse Gestures or Sliding DPI" msgstr "" -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable keys" -msgstr "Disabilita i pulsanti" - -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable specific keyboard keys." -msgstr "Disabilita dei pulsanti specifici della tastiera" - -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Change keys to match OS." -msgstr "Cambia i pulsanti in funzione del sistema operativo" - -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Set OS" -msgstr "Specifica il sistema operativo" - -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Change Host" -msgstr "Cambia Host" - -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Switch connection to a different host" -msgstr "Connetti ad un host differente" - -#: lib/logitech_receiver/settings_templates.py:107 -msgid "Thumb Wheel Diversion" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Diverted" msgstr "" -#: lib/logitech_receiver/settings_templates.py:108 -msgid "HID++ mode for horizontal scroll with the thumb wheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:109 -msgid "Effectively turns off thumb scrolling in Linux." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Invert thumb wheel scroll direction." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Thumb Wheel Direction" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Gestures" -msgstr "Gesti" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "Personalizza il comportamento del mouse/touchpad" - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "Cambia i paramentri numerici di un mouse/touchpad" - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Gesture params" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:113 -msgid "DPI Sliding Adjustment" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:114 -msgid "Adjust the DPI by sliding the mouse horizontally while holding the " - "button down." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:115 +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 msgid "Mouse Gestures" msgstr "" -#: lib/logitech_receiver/settings_templates.py:116 -msgid "Send a gesture by sliding the mouse while holding the button down." +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Regular" msgstr "" -#: lib/logitech_receiver/settings_templates.py:117 -msgid "Divert crown events" +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Sliding DPI" msgstr "" -#: lib/logitech_receiver/settings_templates.py:118 -msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." +#: lib/logitech_receiver/settings_templates.py:711 +msgid "Sensitivity (DPI)" +msgstr "Sensibilità (DPI)" + +#: lib/logitech_receiver/settings_templates.py:752 +msgid "Sensitivity Switching" msgstr "" -#: lib/logitech_receiver/settings_templates.py:119 -msgid "Divert G Keys" +#: lib/logitech_receiver/settings_templates.py:754 +msgid "Switch the current sensitivity and the remembered sensitivity when " + "the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current " + "sensitivity" msgstr "" -#: lib/logitech_receiver/settings_templates.py:120 -msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." +#: lib/logitech_receiver/settings_templates.py:758 +msgid "Off" msgstr "" -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Performs a left click." -msgstr "Clicca con il pulsante sinistro" +#: lib/logitech_receiver/settings_templates.py:791 +msgid "Disable keys" +msgstr "Disabilita i pulsanti" -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Single tap" -msgstr "tocco singolo" +#: lib/logitech_receiver/settings_templates.py:792 +msgid "Disable specific keyboard keys." +msgstr "Disabilita dei pulsanti specifici della tastiera" -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Performs a right click." -msgstr "Clicca con il pulsante destro" - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Single tap with two fingers" -msgstr "tocco singolo con due dita" - -#: lib/logitech_receiver/settings_templates.py:125 -msgid "Single tap with three fingers" -msgstr "tocco singolo con tre dita" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Double tap" -msgstr "doppio tocco" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Performs a double click." -msgstr "Esegue un doppio click" - -#: lib/logitech_receiver/settings_templates.py:130 -msgid "Double tap with two fingers" -msgstr "doppio tocco con due dita" - -#: lib/logitech_receiver/settings_templates.py:131 -msgid "Double tap with three fingers" -msgstr "doppio tocco con tre dita" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Drags items by dragging the finger after double tapping." -msgstr "Trascina gli elementi muovendo il dito dopo aver fatto un doppio tap" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Tap and drag" -msgstr "tocca e trascina" - -#: lib/logitech_receiver/settings_templates.py:135 -msgid "Tap and drag with two fingers" -msgstr "tocca e trascina con due dita" - -#: lib/logitech_receiver/settings_templates.py:136 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "Trascina gli elementi muovendo il dito dopo aver fatto un doppio tap" - -#: lib/logitech_receiver/settings_templates.py:137 -msgid "Tap and drag with three fingers" -msgstr "tocca e trascina con tre dita" - -#: lib/logitech_receiver/settings_templates.py:139 -msgid "Suppress tap and edge gestures" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:140 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:141 -msgid "Scroll with one finger" -msgstr "scorri con un dito" - -#: lib/logitech_receiver/settings_templates.py:141 -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scrolls." -msgstr "Scorre." - -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scroll with two fingers" -msgstr "scorri con due dita" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scroll horizontally with two fingers" -msgstr "scorri orizzontalmente con due dita" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scrolls horizontally." -msgstr "scorre orizzontalmente" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scroll vertically with two fingers" -msgstr "scorri verticalmente con due dita" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scrolls vertically." -msgstr "scorre verticalmente" - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Inverts the scrolling direction." -msgstr "inverte la direzione di scorrimento" - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Natural scrolling" -msgstr "Scorrimento naturale" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Enables the thumbwheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Thumbwheel" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:162 -msgid "Swipe from the top edge" -msgstr "scorri dal bordo superiore" - -#: lib/logitech_receiver/settings_templates.py:159 -msgid "Swipe from the left edge" -msgstr "scorri dal bordo sinistro" - -#: lib/logitech_receiver/settings_templates.py:160 -msgid "Swipe from the right edge" -msgstr "scorri dal bordo destro" - -#: lib/logitech_receiver/settings_templates.py:161 -msgid "Swipe from the bottom edge" -msgstr "scorri dal bordo inferiore" - -#: lib/logitech_receiver/settings_templates.py:163 -msgid "Swipe two fingers from the left edge" -msgstr "scorri con due dita dal bordo sinistro" - -#: lib/logitech_receiver/settings_templates.py:164 -msgid "Swipe two fingers from the right edge" -msgstr "scorri con due dita dal bordo detro" - -#: lib/logitech_receiver/settings_templates.py:165 -msgid "Swipe two fingers from the bottom edge" -msgstr "scorri con due dita dal bordo inferiore" - -#: lib/logitech_receiver/settings_templates.py:166 -msgid "Swipe two fingers from the top edge" -msgstr "scorri con due dita dal bordo superiore" - -#: lib/logitech_receiver/settings_templates.py:167 -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:167 -msgid "Zoom with two fingers." -msgstr "fai lo zoom con due dita" - -#: lib/logitech_receiver/settings_templates.py:168 -msgid "Pinch to zoom out." -msgstr "pizzica per rimpicciolire" - -#: lib/logitech_receiver/settings_templates.py:169 -msgid "Spread to zoom in." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:170 -msgid "Zoom with three fingers." -msgstr "fai lo zoom con tre dita" - -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Zoom with two fingers" -msgstr "fai lo zoom con due dita" - -#: lib/logitech_receiver/settings_templates.py:189 -msgid "Pixel zone" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:190 -msgid "Ratio zone" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Scale factor" -msgstr "Fattore di scala" - -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Sets the cursor speed." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left" -msgstr "Sinistra" - -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left-most coordinate." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:196 -msgid "Top-most coordinate." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:196 -msgid "top" -msgstr "in alto" - -#: lib/logitech_receiver/settings_templates.py:197 -msgid "Width." -msgstr "Larghezza" - -#: lib/logitech_receiver/settings_templates.py:197 -msgid "width" -msgstr "larghezza" - -#: lib/logitech_receiver/settings_templates.py:198 -msgid "Height." -msgstr "Altezza" - -#: lib/logitech_receiver/settings_templates.py:198 -msgid "height" -msgstr "altezza" - -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Cursor speed." -msgstr "Velocità del cursore" - -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Scale" -msgstr "Scala" - -#: lib/logitech_receiver/settings_templates.py:202 +#: lib/logitech_receiver/settings_templates.py:795 #, python-format msgid "Disables the %s key." msgstr "Disabilita il tasto %s" -#: lib/logitech_receiver/settings_templates.py:521 -#: lib/logitech_receiver/settings_templates.py:573 -msgid "Off" +#: lib/logitech_receiver/settings_templates.py:809 +#: lib/logitech_receiver/settings_templates.py:860 +msgid "Set OS" +msgstr "Specifica il sistema operativo" + +#: lib/logitech_receiver/settings_templates.py:810 +#: lib/logitech_receiver/settings_templates.py:861 +msgid "Change keys to match OS." +msgstr "Cambia i pulsanti in funzione del sistema operativo" + +#: lib/logitech_receiver/settings_templates.py:873 +msgid "Change Host" +msgstr "Cambia Host" + +#: lib/logitech_receiver/settings_templates.py:874 +msgid "Switch connection to a different host" +msgstr "Connetti ad un host differente" + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Performs a left click." +msgstr "Clicca con il pulsante sinistro" + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Single tap" +msgstr "tocco singolo" + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Performs a right click." +msgstr "Clicca con il pulsante destro" + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Single tap with two fingers" +msgstr "tocco singolo con due dita" + +#: lib/logitech_receiver/settings_templates.py:902 +msgid "Single tap with three fingers" +msgstr "tocco singolo con tre dita" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Double tap" +msgstr "doppio tocco" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Performs a double click." +msgstr "Esegue un doppio click" + +#: lib/logitech_receiver/settings_templates.py:907 +msgid "Double tap with two fingers" +msgstr "doppio tocco con due dita" + +#: lib/logitech_receiver/settings_templates.py:908 +msgid "Double tap with three fingers" +msgstr "doppio tocco con tre dita" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Trascina gli elementi muovendo il dito dopo aver fatto un doppio tap" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Tap and drag" +msgstr "tocca e trascina" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Trascina gli elementi muovendo il dito dopo aver fatto un doppio tap" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Tap and drag with two fingers" +msgstr "tocca e trascina con due dita" + +#: lib/logitech_receiver/settings_templates.py:914 +msgid "Tap and drag with three fingers" +msgstr "tocca e trascina con tre dita" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Diverted" +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Suppress tap and edge gestures" msgstr "" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Regular" +#: lib/logitech_receiver/settings_templates.py:918 +msgid "Scroll with one finger" +msgstr "scorri con un dito" + +#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scrolls." +msgstr "Scorre." + +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scroll with two fingers" +msgstr "scorri con due dita" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scroll horizontally with two fingers" +msgstr "scorri orizzontalmente con due dita" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scrolls horizontally." +msgstr "scorre orizzontalmente" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scroll vertically with two fingers" +msgstr "scorri verticalmente con due dita" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scrolls vertically." +msgstr "scorre verticalmente" + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Inverts the scrolling direction." +msgstr "inverte la direzione di scorrimento" + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Natural scrolling" +msgstr "Scorrimento naturale" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Enables the thumbwheel." msgstr "" -#: lib/logitech_receiver/status.py:109 +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Thumbwheel" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:935 +#: lib/logitech_receiver/settings_templates.py:939 +msgid "Swipe from the top edge" +msgstr "scorri dal bordo superiore" + +#: lib/logitech_receiver/settings_templates.py:936 +msgid "Swipe from the left edge" +msgstr "scorri dal bordo sinistro" + +#: lib/logitech_receiver/settings_templates.py:937 +msgid "Swipe from the right edge" +msgstr "scorri dal bordo destro" + +#: lib/logitech_receiver/settings_templates.py:938 +msgid "Swipe from the bottom edge" +msgstr "scorri dal bordo inferiore" + +#: lib/logitech_receiver/settings_templates.py:940 +msgid "Swipe two fingers from the left edge" +msgstr "scorri con due dita dal bordo sinistro" + +#: lib/logitech_receiver/settings_templates.py:941 +msgid "Swipe two fingers from the right edge" +msgstr "scorri con due dita dal bordo detro" + +#: lib/logitech_receiver/settings_templates.py:942 +msgid "Swipe two fingers from the bottom edge" +msgstr "scorri con due dita dal bordo inferiore" + +#: lib/logitech_receiver/settings_templates.py:943 +msgid "Swipe two fingers from the top edge" +msgstr "scorri con due dita dal bordo superiore" + +#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:944 +msgid "Zoom with two fingers." +msgstr "fai lo zoom con due dita" + +#: lib/logitech_receiver/settings_templates.py:945 +msgid "Pinch to zoom out." +msgstr "pizzica per rimpicciolire" + +#: lib/logitech_receiver/settings_templates.py:946 +msgid "Spread to zoom in." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:947 +msgid "Zoom with three fingers." +msgstr "fai lo zoom con tre dita" + +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Zoom with two fingers" +msgstr "fai lo zoom con due dita" + +#: lib/logitech_receiver/settings_templates.py:966 +msgid "Pixel zone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:967 +msgid "Ratio zone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Scale factor" +msgstr "Fattore di scala" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Sets the cursor speed." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left" +msgstr "Sinistra" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left-most coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width" +msgstr "Larghezza" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width." +msgstr "Larghezza" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height" +msgstr "Altezza" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height." +msgstr "Altezza" + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Cursor speed." +msgstr "Velocità del cursore" + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Scale" +msgstr "Scala" + +#: lib/logitech_receiver/settings_templates.py:982 +msgid "Gestures" +msgstr "Gesti" + +#: lib/logitech_receiver/settings_templates.py:983 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Personalizza il comportamento del mouse/touchpad" + +#: lib/logitech_receiver/settings_templates.py:1000 +msgid "Gestures Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1001 +msgid "Divert mouse/touchpad gestures." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1018 +msgid "Gesture params" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1019 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Cambia i paramentri numerici di un mouse/touchpad" + +#: lib/logitech_receiver/settings_templates.py:1044 +msgid "M-Key LEDs" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Control the M-Key LEDs." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1077 +msgid "May need G Keys diverted to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1053 +#, python-format +msgid "Lights up the %s key." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1074 +msgid "MR-Key LED" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "Control the MR-Key LED." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1095 +msgid "Persistent Key/Button Mapping" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1097 +msgid "Permanently change the mapping for the key or button." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1098 +msgid "Changing important keys or buttons (such as for the left mouse " + "button) can result in an unusable system." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1157 +msgid "Sidetone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1158 +msgid "Set sidetone level." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1167 +msgid "Equalizer" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1168 +msgid "Set equalizer levels." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1191 +msgid "Hz" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1197 +msgid "Power Management" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1198 +msgid "Power off in minutes (0 for never)." +msgstr "" + +#: lib/logitech_receiver/status.py:114 msgid "No paired devices." msgstr "Nessun dispositivo associato." -#: lib/logitech_receiver/status.py:110 lib/solaar/ui/window.py:623 +#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 #, python-format msgid "%(count)s paired device." msgid_plural "%(count)s paired devices." msgstr[0] "%(count)s dispositivo connesso." msgstr[1] "%(count)s dispositivi connessi." -#: lib/logitech_receiver/status.py:165 +#: lib/logitech_receiver/status.py:170 #, python-format msgid "Battery: %(level)s" msgstr "Batteria: %(level)s" -#: lib/logitech_receiver/status.py:167 +#: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "Batteria: %(percent)d%%" -#: lib/logitech_receiver/status.py:179 +#: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "Illuminazione: %(level)s lux" -#: lib/logitech_receiver/status.py:234 +#: lib/logitech_receiver/status.py:239 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "Batteria: %(level)s (%(status)s)" -#: lib/logitech_receiver/status.py:236 +#: lib/logitech_receiver/status.py:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Batteria: %(percent)d%% (%(status)s)" @@ -783,16 +941,14 @@ msgstr "Errore di permessi" #: lib/solaar/ui/__init__.py:54 #, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open " - "it." -msgstr "Rilevato un ricevitore Logitech (%s), ma non si hanno i permessi per " - "aprirlo." +msgid "Found a Logitech receiver or device (%s), but did not have " + "permission to open it." +msgstr "" #: lib/solaar/ui/__init__.py:55 -msgid "If you've just installed Solaar, try removing the receiver and " - "plugging it back in." -msgstr "Se hai appena installato Solaar prova a scollegare e ricollegare il " - "ricevitore." +msgid "If you've just installed Solaar, try disconnecting the receiver or " + "device and then reconnecting it." +msgstr "" #: lib/solaar/ui/__init__.py:58 msgid "Cannot connect to device error" @@ -805,8 +961,8 @@ msgid "Found a Logitech receiver or device at %s, but encountered an error " msgstr "" #: lib/solaar/ui/__init__.py:61 -msgid "Try removing the device and plugging it back in or turning it off " - "and then on." +msgid "Try disconnecting the device and then reconnecting it or turning it " + "off and then on." msgstr "" #: lib/solaar/ui/__init__.py:64 @@ -822,354 +978,627 @@ msgstr "Disassociazione fallita di %{device} da %{receiver}." msgid "The receiver returned an error, with no further details." msgstr "Il ricevitore ha ritornato un errore senza ulteriori dettagli." -#: lib/solaar/ui/about.py:39 +#: lib/solaar/ui/__init__.py:177 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" + +#: lib/solaar/ui/about.py:36 msgid "Manages Logitech receivers,\n" "keyboards, mice, and tablets." msgstr "" -#: lib/solaar/ui/about.py:47 +#: lib/solaar/ui/about.py:44 msgid "Additional Programming" msgstr "" -#: lib/solaar/ui/about.py:48 +#: lib/solaar/ui/about.py:45 msgid "GUI design" msgstr "GUI design" -#: lib/solaar/ui/about.py:50 +#: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "Testing" -#: lib/solaar/ui/about.py:57 +#: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Documentazione Logitech" -#: lib/solaar/ui/action.py:70 lib/solaar/ui/window.py:328 -msgid "About %s" -msgstr "Informazioni %s" - -#: lib/solaar/ui/action.py:96 lib/solaar/ui/action.py:100 -#: lib/solaar/ui/window.py:205 +#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 +#: lib/solaar/ui/window.py:197 msgid "Unpair" msgstr "Disassocia" -#: lib/solaar/ui/action.py:99 lib/solaar/ui/diversion_rules.py:144 +#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "" -#: lib/solaar/ui/config_panel.py:358 -msgid "Changes allowed" +#: lib/solaar/ui/config_panel.py:212 +msgid "Complete - ENTER to change" msgstr "" -#: lib/solaar/ui/config_panel.py:359 -msgid "No changes allowed" +#: lib/solaar/ui/config_panel.py:212 +msgid "Incomplete" msgstr "" -#: lib/solaar/ui/config_panel.py:360 -msgid "Ignore this setting" -msgstr "" - -#: lib/solaar/ui/config_panel.py:401 -msgid "Working" -msgstr "Lavorando" - -#: lib/solaar/ui/config_panel.py:404 -msgid "Read/write operation failed." -msgstr "Operazione di Lettura/Scrittura fallita." - -#: lib/solaar/ui/config_panel.py:522 +#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "%d valore" msgstr[1] "%d valori" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/config_panel.py:518 +msgid "Changes allowed" +msgstr "" + +#: lib/solaar/ui/config_panel.py:519 +msgid "No changes allowed" +msgstr "" + +#: lib/solaar/ui/config_panel.py:520 +msgid "Ignore this setting" +msgstr "" + +#: lib/solaar/ui/config_panel.py:565 +msgid "Working" +msgstr "Lavorando" + +#: lib/solaar/ui/config_panel.py:568 +msgid "Read/write operation failed." +msgstr "Operazione di Lettura/Scrittura fallita." + +#: lib/solaar/ui/diversion_rules.py:65 msgid "Built-in rules" msgstr "Regole integrate" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" msgstr "Regole definite dall'utente" -#: lib/solaar/ui/diversion_rules.py:59 lib/solaar/ui/diversion_rules.py:792 +#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "Regola" -#: lib/solaar/ui/diversion_rules.py:60 lib/solaar/ui/diversion_rules.py:503 -#: lib/solaar/ui/diversion_rules.py:622 +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 +#: lib/solaar/ui/diversion_rules.py:636 msgid "Sub-rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:62 +#: lib/solaar/ui/diversion_rules.py:70 msgid "[empty]" msgstr "[vuoto]" -#: lib/solaar/ui/diversion_rules.py:85 +#: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "Editor di regole di Solaar" -#: lib/solaar/ui/diversion_rules.py:135 +#: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "Rendi le modifiche permanenti?" -#: lib/solaar/ui/diversion_rules.py:140 +#: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Sì" -#: lib/solaar/ui/diversion_rules.py:142 +#: lib/solaar/ui/diversion_rules.py:148 msgid "No" msgstr "" -#: lib/solaar/ui/diversion_rules.py:147 +#: lib/solaar/ui/diversion_rules.py:153 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "Se scegli No, le modifiche saranno perse quando Solaar viene chiuso" -#: lib/solaar/ui/diversion_rules.py:195 +#: lib/solaar/ui/diversion_rules.py:201 msgid "Save changes" msgstr "" -#: lib/solaar/ui/diversion_rules.py:200 +#: lib/solaar/ui/diversion_rules.py:206 msgid "Discard changes" msgstr "" -#: lib/solaar/ui/diversion_rules.py:366 +#: lib/solaar/ui/diversion_rules.py:372 msgid "Insert here" msgstr "Inserisci qui" -#: lib/solaar/ui/diversion_rules.py:368 +#: lib/solaar/ui/diversion_rules.py:374 msgid "Insert above" msgstr "Inserisci sopra" -#: lib/solaar/ui/diversion_rules.py:370 +#: lib/solaar/ui/diversion_rules.py:376 msgid "Insert below" msgstr "Inserisci sotto" -#: lib/solaar/ui/diversion_rules.py:376 +#: lib/solaar/ui/diversion_rules.py:382 msgid "Insert new rule here" msgstr "Inserisci una nuova regola qui" -#: lib/solaar/ui/diversion_rules.py:378 +#: lib/solaar/ui/diversion_rules.py:384 msgid "Insert new rule above" msgstr "Inserisci una nuova regola sopra" -#: lib/solaar/ui/diversion_rules.py:380 +#: lib/solaar/ui/diversion_rules.py:386 msgid "Insert new rule below" msgstr "Inserisci una nuova regola sotto" -#: lib/solaar/ui/diversion_rules.py:421 +#: lib/solaar/ui/diversion_rules.py:427 msgid "Paste here" msgstr "Incolla qui" -#: lib/solaar/ui/diversion_rules.py:423 +#: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "Incolla sopra" -#: lib/solaar/ui/diversion_rules.py:425 +#: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "Incolla sotto" -#: lib/solaar/ui/diversion_rules.py:431 +#: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "Incolla la regola qui" -#: lib/solaar/ui/diversion_rules.py:433 +#: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "Incolla la regola sopra" -#: lib/solaar/ui/diversion_rules.py:435 +#: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "Incolla la regola sotto" -#: lib/solaar/ui/diversion_rules.py:439 +#: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "Incolla la regola" -#: lib/solaar/ui/diversion_rules.py:468 +#: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "Appiattisci" -#: lib/solaar/ui/diversion_rules.py:501 +#: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "Inserisci" -#: lib/solaar/ui/diversion_rules.py:504 lib/solaar/ui/diversion_rules.py:624 -#: lib/solaar/ui/diversion_rules.py:835 +#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 +#: lib/solaar/ui/diversion_rules.py:1126 msgid "Or" msgstr "Oppure" -#: lib/solaar/ui/diversion_rules.py:505 lib/solaar/ui/diversion_rules.py:623 -#: lib/solaar/ui/diversion_rules.py:820 +#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 +#: lib/solaar/ui/diversion_rules.py:1111 msgid "And" msgstr "E" -#: lib/solaar/ui/diversion_rules.py:507 +#: lib/solaar/ui/diversion_rules.py:513 msgid "Condition" msgstr "Condizione" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:951 +#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "Funzionalità" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:874 -msgid "Process" -msgstr "Processo" - -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:902 -msgid "MouseProcess" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:512 lib/solaar/ui/diversion_rules.py:984 +#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "Segnala" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1019 +#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 +msgid "Process" +msgstr "Processo" + +#: lib/solaar/ui/diversion_rules.py:518 +msgid "Mouse process" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 msgid "Modifiers" msgstr "Modificatori" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1066 +#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "Chiave" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1111 +#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 +msgid "KeyIsDown" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 +msgid "Active" +msgstr "Attivo" + +#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2218 +#: lib/solaar/ui/diversion_rules.py:2270 lib/solaar/ui/diversion_rules.py:2323 +msgid "Device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +msgid "Host" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 +msgid "Setting" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 +#: lib/solaar/ui/diversion_rules.py:1526 msgid "Test" msgstr "Test" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1196 +#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 +msgid "Test bytes" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 msgid "Mouse Gesture" msgstr "" -#: lib/solaar/ui/diversion_rules.py:520 +#: lib/solaar/ui/diversion_rules.py:532 msgid "Action" msgstr "Azione" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1286 +#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 msgid "Key press" msgstr "Pressione del tasto" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1335 +#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "Scorrimento del mouse" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:1383 +#: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 msgid "Mouse click" msgstr "Click del mouse" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1452 +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Set" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 msgid "Execute" msgstr "Esegui" -#: lib/solaar/ui/diversion_rules.py:554 +#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 +msgid "Later" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:568 msgid "Insert new rule" msgstr "Inserisci una nuova regola" -#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/diversion_rules.py:1146 -#: lib/solaar/ui/diversion_rules.py:1236 lib/solaar/ui/diversion_rules.py:1411 +#: lib/solaar/ui/diversion_rules.py:588 lib/solaar/ui/diversion_rules.py:1686 +#: lib/solaar/ui/diversion_rules.py:1790 lib/solaar/ui/diversion_rules.py:1989 msgid "Delete" msgstr "Elimina" -#: lib/solaar/ui/diversion_rules.py:596 +#: lib/solaar/ui/diversion_rules.py:610 msgid "Negate" msgstr "Nega" -#: lib/solaar/ui/diversion_rules.py:620 +#: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "" -#: lib/solaar/ui/diversion_rules.py:642 +#: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Taglia" -#: lib/solaar/ui/diversion_rules.py:657 +#: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Incolla" -#: lib/solaar/ui/diversion_rules.py:669 +#: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Copia" -#: lib/solaar/ui/diversion_rules.py:772 +#: lib/solaar/ui/diversion_rules.py:1063 msgid "This editor does not support the selected rule component yet." msgstr "Questo editor ancora non supporta la componente selezionata" -#: lib/solaar/ui/diversion_rules.py:850 +#: lib/solaar/ui/diversion_rules.py:1138 +msgid "Number of seconds to delay." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1177 msgid "Not" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1039 +#: lib/solaar/ui/diversion_rules.py:1187 +msgid "X11 active process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1218 +msgid "X11 mouse process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1235 +msgid "MouseProcess" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1260 +msgid "Feature name of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1308 +msgid "Report number of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1342 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1383 +msgid "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1392 msgid "Key down" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1042 +#: lib/solaar/ui/diversion_rules.py:1395 msgid "Key up" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1131 -msgid "Add action" +#: lib/solaar/ui/diversion_rules.py:1436 +msgid "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." msgstr "" -#: lib/solaar/ui/diversion_rules.py:1224 +#: lib/solaar/ui/diversion_rules.py:1475 +msgid "Test condition on notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1479 +msgid "Parameter" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1542 +msgid "begin (inclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1543 +msgid "end (exclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1552 +msgid "range" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1554 +msgid "minimum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1555 +msgid "maximum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1557 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1562 +msgid "mask" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1563 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1573 +msgid "Bit or range test on bytes in notification message triggering rule " + "processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1583 +msgid "type" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1666 +msgid "Mouse gesture with optional initiating button followed by zero or " + "more mouse movements." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1671 +msgid "Add movement" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1764 +msgid "Simulate a chorded key click or depress or release.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1769 msgid "Add key" msgstr "Aggiungi chiave" -#: lib/solaar/ui/diversion_rules.py:1354 +#: lib/solaar/ui/diversion_rules.py:1772 +msgid "Click" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1775 +msgid "Depress" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1778 +msgid "Release" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1861 +msgid "Simulate a mouse scroll.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1918 +msgid "Simulate a mouse click.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1921 msgid "Button" msgstr "Pulsante" -#: lib/solaar/ui/diversion_rules.py:1355 -msgid "Count" -msgstr "Numero" +#: lib/solaar/ui/diversion_rules.py:1922 +msgid "Count and Action" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1397 +#: lib/solaar/ui/diversion_rules.py:1972 +msgid "Execute a command with arguments." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1975 msgid "Add argument" msgstr "" -#: lib/solaar/ui/notify.py:125 lib/solaar/ui/tray.py:319 -#: lib/solaar/ui/tray.py:324 lib/solaar/ui/window.py:749 +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "Toggle" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "True" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2051 +msgid "False" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2065 +msgid "Unsupported setting" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2242 +#: lib/solaar/ui/diversion_rules.py:2328 lib/solaar/ui/diversion_rules.py:2570 +#: lib/solaar/ui/diversion_rules.py:2588 +msgid "Originating device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2256 +msgid "Device is active and its settings can be changed." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2266 +msgid "Device that originated the current notification." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2280 +msgid "Name of host computer." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2347 +msgid "Value" +msgstr "Valore" + +#: lib/solaar/ui/diversion_rules.py:2355 +msgid "Item" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2630 +msgid "Change setting on device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2647 +msgid "Setting on device" +msgstr "" + +#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:318 +#: lib/solaar/ui/tray.py:323 lib/solaar/ui/window.py:739 msgid "offline" msgstr "disconnesso" -#: lib/solaar/ui/pair_window.py:134 +#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 +#: lib/solaar/ui/pair_window.py:288 +#, python-format +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: accoppia un nuovo dispositivo" + +#: lib/solaar/ui/pair_window.py:123 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "" + +#: lib/solaar/ui/pair_window.py:126 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "left" +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "right" +msgstr "" + +#: lib/solaar/ui/pair_window.py:131 +#, python-format +msgid "Press %(code)s\n" + "and then press left and right buttons simultaneously." +msgstr "" + +#: lib/solaar/ui/pair_window.py:188 msgid "Pairing failed" msgstr "Associazione fallita" -#: lib/solaar/ui/pair_window.py:136 +#: lib/solaar/ui/pair_window.py:190 msgid "Make sure your device is within range, and has a decent battery " "charge." msgstr "Assicurati che il dispositivo sia vicino al ricevitore e che abbia " "le batterie cariche." -#: lib/solaar/ui/pair_window.py:138 +#: lib/solaar/ui/pair_window.py:192 msgid "A new device was detected, but it is not compatible with this " "receiver." msgstr "Un nuovo dispositivo è stato rilevato, ma non è compatibile con " "questo ricevitore." -#: lib/solaar/ui/pair_window.py:140 +#: lib/solaar/ui/pair_window.py:194 msgid "More paired devices than receiver can support." msgstr "Più dispositivi accoppiati di quanti il ricevitore possa supportarne" -#: lib/solaar/ui/pair_window.py:142 +#: lib/solaar/ui/pair_window.py:196 msgid "No further details are available about the error." msgstr "Non sono disponibili ulteriori dettagli per l'errore." -#: lib/solaar/ui/pair_window.py:156 +#: lib/solaar/ui/pair_window.py:210 msgid "Found a new device:" msgstr "Trovato un nuovo dispositivo:" -#: lib/solaar/ui/pair_window.py:181 +#: lib/solaar/ui/pair_window.py:235 msgid "The wireless link is not encrypted" msgstr "Il collegamento wireless non è ciftrato." -#: lib/solaar/ui/pair_window.py:199 -#, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s: accoppia un nuovo dispositivo" +#: lib/solaar/ui/pair_window.py:264 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "" -#: lib/solaar/ui/pair_window.py:206 +#: lib/solaar/ui/pair_window.py:266 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:268 +msgid "Other receivers are only compatible with a few devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:270 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" + +#: lib/solaar/ui/pair_window.py:274 +msgid "Press a pairing button or key until the pairing light flashes " + "quickly." +msgstr "" + +#: lib/solaar/ui/pair_window.py:276 +msgid "You may have to first turn the device off and on again." +msgstr "" + +#: lib/solaar/ui/pair_window.py:278 +msgid "Turn on the device you want to pair." +msgstr "Accendi il dispositivo che vuoi associare." + +#: lib/solaar/ui/pair_window.py:280 msgid "If the device is already turned on, turn it off and on again." msgstr "" -#: lib/solaar/ui/pair_window.py:209 +#: lib/solaar/ui/pair_window.py:283 #, python-format msgid "\n" "\n" @@ -1184,123 +1613,125 @@ msgstr[1] "\n" "\n" "Questo ricevitore ha %d accoppiamenti rimanenti." -#: lib/solaar/ui/pair_window.py:212 +#: lib/solaar/ui/pair_window.py:286 msgid "\n" "Cancelling at this point will not use up a pairing." msgstr "\n" "Annullando a questo punto non costerà un accoppiamento" -#: lib/solaar/ui/pair_window.py:215 -msgid "Turn on the device you want to pair." -msgstr "Accendi il dispositivo che vuoi associare." +#: lib/solaar/ui/tray.py:58 +msgid "No supported device found" +msgstr "" -#: lib/solaar/ui/tray.py:61 -msgid "No Logitech receiver found" -msgstr "Nessun ricevitore Logitech trovato" +#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#, python-format +msgid "About %s" +msgstr "Informazioni %s" -#: lib/solaar/ui/tray.py:68 lib/solaar/ui/window.py:325 +#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 +#, python-format msgid "Quit %s" msgstr "Esci %s" -#: lib/solaar/ui/tray.py:298 lib/solaar/ui/tray.py:306 +#: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "nessun ricevitore" -#: lib/solaar/ui/tray.py:322 +#: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "nessuno stato" -#: lib/solaar/ui/window.py:104 +#: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "Ricerca" -#: lib/solaar/ui/window.py:137 lib/solaar/ui/window.py:692 +#: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Batteria" -#: lib/solaar/ui/window.py:140 +#: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Collegamento Wireless" -#: lib/solaar/ui/window.py:144 +#: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Luminosità" -#: lib/solaar/ui/window.py:178 +#: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Mostra Dettagli Tecnici" -#: lib/solaar/ui/window.py:194 +#: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Associa un nuovo dispositivo" -#: lib/solaar/ui/window.py:213 +#: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Seleziona un dispositivo" -#: lib/solaar/ui/window.py:331 +#: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "Editor regole" -#: lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Percorso" -#: lib/solaar/ui/window.py:544 +#: lib/solaar/ui/window.py:536 msgid "USB ID" msgstr "ID USB" -#: lib/solaar/ui/window.py:547 lib/solaar/ui/window.py:549 -#: lib/solaar/ui/window.py:569 lib/solaar/ui/window.py:571 +#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 msgid "Serial" msgstr "Seriale" -#: lib/solaar/ui/window.py:553 +#: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Indice" -#: lib/solaar/ui/window.py:555 +#: lib/solaar/ui/window.py:547 msgid "Wireless PID" msgstr "PID Wireless" -#: lib/solaar/ui/window.py:557 +#: lib/solaar/ui/window.py:549 msgid "Product ID" msgstr "ID Prodotto" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:551 msgid "Protocol" msgstr "Protocollo" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:551 msgid "Unknown" msgstr "Sconosciuto" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:554 #, python-format msgid "%(rate)d ms (%(rate_hz)dHz)" msgstr "%(rate)d ms (%(rate_hz)dHz)" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:554 msgid "Polling rate" msgstr "Frequenza di aggiornamento" -#: lib/solaar/ui/window.py:573 +#: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "ID unità" -#: lib/solaar/ui/window.py:584 +#: lib/solaar/ui/window.py:576 msgid "none" msgstr "nessuno" -#: lib/solaar/ui/window.py:585 +#: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Notifiche" -#: lib/solaar/ui/window.py:622 +#: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "Nessun dispositivo accoppiato" -#: lib/solaar/ui/window.py:629 +#: lib/solaar/ui/window.py:628 #, python-format msgid "Up to %(max_count)s device can be paired to this receiver." msgid_plural "Up to %(max_count)s devices can be paired to this receiver." @@ -1309,85 +1740,66 @@ msgstr[0] "Fino a %(max_count)s dispositivo può essere accoppiato con " msgstr[1] "Fino a %(max_count)s dispositivi possono essere accoppiati " "con questo ricevitore" -#: lib/solaar/ui/window.py:635 +#: lib/solaar/ui/window.py:634 msgid "Only one device can be paired to this receiver." msgstr "Solo un dispositivo può essere collegato a questo ricevitore" -#: lib/solaar/ui/window.py:639 +#: lib/solaar/ui/window.py:638 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." msgstr[0] "Questo ricevitore ha %d accoppiamento rimanente" msgstr[1] "Questo ricevitore ha %d accoppiamenti rimanenti" -#: lib/solaar/ui/window.py:694 -msgid "Battery information unknown." -msgstr "Informazioni sulla batteria sconosciute" - -#: lib/solaar/ui/window.py:702 +#: lib/solaar/ui/window.py:692 msgid "Battery Voltage" msgstr "Tensione della batteria" -#: lib/solaar/ui/window.py:704 +#: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "Tensione riportata dalla batteria" -#: lib/solaar/ui/window.py:706 lib/solaar/ui/window.py:710 +#: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "Livello batteria" -#: lib/solaar/ui/window.py:708 lib/solaar/ui/window.py:712 +#: lib/solaar/ui/window.py:698 msgid "Approximate level reported by battery" msgstr "Livello approssimativo riportato dalla batteria" -#: lib/solaar/ui/window.py:715 lib/solaar/ui/window.py:717 +#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 msgid "next reported " msgstr "Prossimo livello " -#: lib/solaar/ui/window.py:718 +#: lib/solaar/ui/window.py:708 msgid " and next level to be reported." msgstr " e il prossimo livello che verrà riportato" -#: lib/solaar/ui/window.py:723 +#: lib/solaar/ui/window.py:713 msgid "last known" msgstr "ultimo conosciuto" -#: lib/solaar/ui/window.py:731 -msgid "not encrypted" -msgstr "non cifrato" - -#: lib/solaar/ui/window.py:735 -msgid "The wireless link between this device and its receiver is not " - "encrypted.\n" - "\n" - "For pointing devices (mice, trackballs, trackpads), this is a minor " - "security issue.\n" - "\n" - "It is, however, a major security issue for text-input devices " - "(keyboards, numpads),\n" - "because typed text can be sniffed inconspicuously by 3rd parties " - "within range." -msgstr "Il collegamento wireless tra il dispositivo e il suo ricevitore non " - "è cifrato.\n" - "\n" - "Per i dispositivi di puntamento (mouse, trackball, trackpad) non è " - "un grave problema.\n" - "\n" - "E' un problema maggiore per i dispositivi di immissione di testo " - "(tastiera, numpad),\n" - "perché il testo digitato potrebbe essere intercettato da dispositivi " - "di terze parti vicini." - -#: lib/solaar/ui/window.py:744 +#: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "cifrato" -#: lib/solaar/ui/window.py:746 +#: lib/solaar/ui/window.py:726 msgid "The wireless link between this device and its receiver is encrypted." msgstr "Il collegamento wireless tra il dispositivo e il suo ricevitore è " "cifrato." -#: lib/solaar/ui/window.py:759 +#: lib/solaar/ui/window.py:728 +msgid "not encrypted" +msgstr "non cifrato" + +#: lib/solaar/ui/window.py:732 +msgid "The wireless link between this device and its receiver is not " + "encrypted.\n" + "This is a security issue for pointing devices, and a major security " + "issue for text-input devices." +msgstr "" + +#: lib/solaar/ui/window.py:748 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d lux" @@ -1397,22 +1809,48 @@ msgstr "%(light_level)d lux" #~ msgstr "Modifica i DPI facendo scorrere il mouse orizzontalmente " #~ "mentre si preme il pulsante DPI" +#~ msgid "Battery information unknown." +#~ msgstr "Informazioni sulla batteria sconosciute" + #~ msgid "Click to allow changes." #~ msgstr "Premi per consentire modifiche" #~ msgid "Click to prevent changes." #~ msgstr "Clicca per evitare modifiche" +#~ msgid "Count" +#~ msgstr "Numero" + +#~ msgid "Effectively turns off wheel scrolling in Linux." +#~ msgstr "Di fatto in Linux disattiva lo scorrimento con la rotella" + #~ msgid "For more information see the Solaar installation directions\n" #~ "at https://pwr-solaar.github.io/Solaar/installation" #~ msgstr "Per ulteriori informazioni consulta le istruzioni " #~ "all'installazione di Solaar\n" #~ "su https://pwr-solaar.github.io/Solaar/installation" +#, python-format +#~ msgid "Found a Logitech Receiver (%s), but did not have permission " +#~ "to open it." +#~ msgstr "Rilevato un ricevitore Logitech (%s), ma non si hanno i " +#~ "permessi per aprirlo." + +#~ msgid "HID++ mode for vertical scroll with the wheel." +#~ msgstr "Modalità HID++ per lo scorrimento verticale con la rotella" + #~ msgid "If the device is already turned on, turn if off and on again." #~ msgstr "Se il dispositivo è già acceso, spegnilo e accendilo " #~ "nuovamente." +#~ msgid "If you've just installed Solaar, try removing the receiver " +#~ "and plugging it back in." +#~ msgstr "Se hai appena installato Solaar prova a scollegare e " +#~ "ricollegare il ricevitore." + +#~ msgid "No Logitech receiver found" +#~ msgstr "Nessun ricevitore Logitech trovato" + #~ msgid "Shows status of devices connected\n" #~ "through wireless Logitech receivers." #~ msgstr "Mostra lo stato dei dispositivi connessi\n" @@ -1421,5 +1859,38 @@ msgstr "%(light_level)d lux" #~ msgid "Solaar depends on a udev file that is not present" #~ msgstr "Solaar dipende da un file udev non presente" +#~ msgid "The wireless link between this device and its receiver is " +#~ "not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices " +#~ "(keyboards, numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties " +#~ "within range." +#~ msgstr "Il collegamento wireless tra il dispositivo e il suo " +#~ "ricevitore non è cifrato.\n" +#~ "\n" +#~ "Per i dispositivi di puntamento (mouse, trackball, trackpad) non è " +#~ "un grave problema.\n" +#~ "\n" +#~ "E' un problema maggiore per i dispositivi di immissione di testo " +#~ "(tastiera, numpad),\n" +#~ "perché il testo digitato potrebbe essere intercettato da dispositivi " +#~ "di terze parti vicini." + #~ msgid "Thumb Wheel HID++ Scrolling" #~ msgstr "Rotella di scorrimento Scorrimento HID++" + +#~ msgid "height" +#~ msgstr "altezza" + +#~ msgid "top" +#~ msgstr "in alto" + +#~ msgid "unknown" +#~ msgstr "sconosciuto" + +#~ msgid "width" +#~ msgstr "larghezza" diff --git a/po/ja.po b/po/ja.po index 020d9db3..53227625 100644 --- a/po/ja.po +++ b/po/ja.po @@ -5,7 +5,7 @@ msgid "" msgstr "Project-Id-Version: solaar 1.0.6\n" "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2021-09-25 13:56-0400\n" + "POT-Creation-Date: 2023-12-28 17:40+0100\n" "PO-Revision-Date: 2021-09-24 00:25+0900\n" "Last-Translator: Ryunosuke Toda \n" "Language-Team: \n" @@ -16,761 +16,915 @@ msgstr "Project-Id-Version: solaar 1.0.6\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 3.0\n" -#: lib/logitech_receiver/base_usb.py:50 +#: lib/logitech_receiver/base_usb.py:46 +msgid "Bolt Receiver" +msgstr "" + +#: lib/logitech_receiver/base_usb.py:57 msgid "Unifying Receiver" msgstr "Unifying レシーバー" -#: lib/logitech_receiver/base_usb.py:58 lib/logitech_receiver/base_usb.py:68 -#: lib/logitech_receiver/base_usb.py:79 lib/logitech_receiver/base_usb.py:90 -#: lib/logitech_receiver/base_usb.py:101 +#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 +#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 +#: lib/logitech_receiver/base_usb.py:114 msgid "Nano Receiver" msgstr "Nano レシーバー" -#: lib/logitech_receiver/base_usb.py:109 +#: lib/logitech_receiver/base_usb.py:124 msgid "Lightspeed Receiver" msgstr "Lightspeed レシーバー" -#: lib/logitech_receiver/base_usb.py:117 +#: lib/logitech_receiver/base_usb.py:133 msgid "EX100 Receiver 27 Mhz" msgstr "EX100 レシーバー 27 Mhz" -#: lib/logitech_receiver/device.py:133 lib/solaar/ui/window.py:693 -msgid "unknown" -msgstr "不明" - -#: lib/logitech_receiver/i18n.py:38 +#: lib/logitech_receiver/i18n.py:30 msgid "empty" msgstr "空" -#: lib/logitech_receiver/i18n.py:39 +#: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "わずか" -#: lib/logitech_receiver/i18n.py:40 +#: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "低" -#: lib/logitech_receiver/i18n.py:41 +#: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "中" -#: lib/logitech_receiver/i18n.py:42 +#: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "良好" -#: lib/logitech_receiver/i18n.py:43 +#: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "満充電" -#: lib/logitech_receiver/i18n.py:46 +#: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "低下" -#: lib/logitech_receiver/i18n.py:47 +#: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "再充電中" -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:721 +#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "充電中" -#: lib/logitech_receiver/i18n.py:49 +#: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "充電していません" -#: lib/logitech_receiver/i18n.py:50 +#: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "ほぼ満充電" -#: lib/logitech_receiver/i18n.py:51 +#: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "充電完了" -#: lib/logitech_receiver/i18n.py:52 +#: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "低速充電" -#: lib/logitech_receiver/i18n.py:53 +#: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "バッテリー不正" -#: lib/logitech_receiver/i18n.py:54 +#: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "温度エラー" -#: lib/logitech_receiver/i18n.py:55 +#: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "エラー" -#: lib/logitech_receiver/i18n.py:56 +#: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "標準" -#: lib/logitech_receiver/i18n.py:57 +#: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "高速" -#: lib/logitech_receiver/i18n.py:58 +#: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "低速" -#: lib/logitech_receiver/i18n.py:61 +#: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "デバイス応答なし" -#: lib/logitech_receiver/i18n.py:62 +#: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "非対応のデバイス" -#: lib/logitech_receiver/i18n.py:63 +#: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "デバイス数超過" -#: lib/logitech_receiver/i18n.py:64 +#: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "シーケンス応答なし" -#: lib/logitech_receiver/i18n.py:67 lib/solaar/ui/window.py:580 +#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 msgid "Firmware" msgstr "Firmware" -#: lib/logitech_receiver/i18n.py:68 +#: lib/logitech_receiver/i18n.py:60 msgid "Bootloader" msgstr "Bootloader" -#: lib/logitech_receiver/i18n.py:69 +#: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardware" -#: lib/logitech_receiver/i18n.py:70 +#: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Other" -#: lib/logitech_receiver/i18n.py:73 +#: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "左ボタン" -#: lib/logitech_receiver/i18n.py:74 +#: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "右ボタン" -#: lib/logitech_receiver/i18n.py:75 +#: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "中ボタン" -#: lib/logitech_receiver/i18n.py:76 +#: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "戻るボタン" -#: lib/logitech_receiver/i18n.py:77 +#: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "進むボタン" -#: lib/logitech_receiver/i18n.py:78 +#: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "マウスジェスチャーボタン" -#: lib/logitech_receiver/i18n.py:79 +#: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "SmartShift" -#: lib/logitech_receiver/i18n.py:80 +#: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "DPIスイッチ" -#: lib/logitech_receiver/i18n.py:81 +#: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "左チルト" -#: lib/logitech_receiver/i18n.py:82 +#: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "右チルト" -#: lib/logitech_receiver/i18n.py:83 +#: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "左クリック" -#: lib/logitech_receiver/i18n.py:84 +#: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "右クリック" -#: lib/logitech_receiver/i18n.py:85 +#: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "マウス中ボタン" -#: lib/logitech_receiver/i18n.py:86 +#: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "マウス戻るボタン" -#: lib/logitech_receiver/i18n.py:87 +#: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "マウス進むボタン" -#: lib/logitech_receiver/i18n.py:88 +#: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "" -#: lib/logitech_receiver/i18n.py:89 +#: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:90 +#: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:93 +#: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "" -#: lib/logitech_receiver/i18n.py:94 +#: lib/logitech_receiver/i18n.py:86 msgid "released" msgstr "" -#: lib/logitech_receiver/notifications.py:76 +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is closed" msgstr "ペアリング試行終了" -#: lib/logitech_receiver/notifications.py:76 +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is open" msgstr "ペアリング試行開始" -#: lib/logitech_receiver/notifications.py:160 lib/solaar/ui/notify.py:123 +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is closed" +msgstr "" + +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is open" +msgstr "" + +#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 msgid "connected" msgstr "接続" -#: lib/logitech_receiver/notifications.py:160 +#: lib/logitech_receiver/notifications.py:224 msgid "disconnected" msgstr "切断" -#: lib/logitech_receiver/notifications.py:198 lib/solaar/ui/notify.py:121 +#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "ペアリング解除" -#: lib/logitech_receiver/notifications.py:248 +#: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "電源オン" -#: lib/logitech_receiver/settings.py:526 +#: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "" -#: lib/logitech_receiver/settings.py:542 lib/logitech_receiver/settings.py:568 +#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 msgid "feature" msgstr "" -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Hand Detection" -msgstr "手の検出" - -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "手がキーボードの上にあるとき照明をオンにします。" - -#: lib/logitech_receiver/settings_templates.py:72 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:73 -#: lib/logitech_receiver/settings_templates.py:78 -#: lib/logitech_receiver/settings_templates.py:85 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "ホイール縦スクロール高感度モード。" - -#: lib/logitech_receiver/settings_templates.py:74 -msgid "Side Scrolling" -msgstr "サイドスクロール" - -#: lib/logitech_receiver/settings_templates.py:75 -msgid "When disabled, pushing the wheel sideways sends custom button " - "events\n" - "instead of the standard side-scrolling events." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:77 -msgid "Scroll Wheel High Resolution" -msgstr "高解像度スクロールホイール" - -#: lib/logitech_receiver/settings_templates.py:79 -msgid "Scroll Wheel Diversion" -msgstr "スクロールホイール迂回" - -#: lib/logitech_receiver/settings_templates.py:80 -msgid "HID++ mode for vertical scroll with the wheel." -msgstr "ホイールでのHID++モード縦スクロール。" - -#: lib/logitech_receiver/settings_templates.py:81 -msgid "Effectively turns off wheel scrolling in Linux." -msgstr "事実上Linuxでのホイールスクロールを無効にします。" - -#: lib/logitech_receiver/settings_templates.py:82 -msgid "Scroll Wheel Direction" -msgstr "ホイールのスクロール方向" - -#: lib/logitech_receiver/settings_templates.py:83 -msgid "Invert direction for vertical scroll with wheel." -msgstr "ホイールでの縦スクロール方向を逆にします。" - -#: lib/logitech_receiver/settings_templates.py:84 -msgid "Scroll Wheel Resolution" -msgstr "ホイールのスクロール解像度" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Frequency of device polling, in milliseconds" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Polling Rate (ms)" -msgstr "ポーリングレート(ミリ秒)" - -#: lib/logitech_receiver/settings_templates.py:87 +#: lib/logitech_receiver/settings_templates.py:139 msgid "Swap Fx function" msgstr "ファンクションキー動作を入替" -#: lib/logitech_receiver/settings_templates.py:88 +#: lib/logitech_receiver/settings_templates.py:140 msgid "When set, the F1..F12 keys will activate their special function,\n" "and you must hold the FN key to activate their standard function." msgstr "オンにすると、F1〜F12をカスタマイズキーとして使用します。\n" "標準のファンクションキーとして使用するには、fnキーを押しながら操作し" "ます。" -#: lib/logitech_receiver/settings_templates.py:90 +#: lib/logitech_receiver/settings_templates.py:142 msgid "When unset, the F1..F12 keys will activate their standard function,\n" "and you must hold the FN key to activate their special function." msgstr "オフにすると、F1〜F12を標準のファンクションキーとして使用します。\n" "カスタマイズキーとして使用するには、fnキーを押しながら操作します。" -#: lib/logitech_receiver/settings_templates.py:92 +#: lib/logitech_receiver/settings_templates.py:149 +msgid "Hand Detection" +msgstr "手の検出" + +#: lib/logitech_receiver/settings_templates.py:150 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "手がキーボードの上にあるとき照明をオンにします。" + +#: lib/logitech_receiver/settings_templates.py:157 +msgid "Scroll Wheel Smooth Scrolling" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:158 +#: lib/logitech_receiver/settings_templates.py:239 +#: lib/logitech_receiver/settings_templates.py:267 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "ホイール縦スクロール高感度モード。" + +#: lib/logitech_receiver/settings_templates.py:165 +msgid "Side Scrolling" +msgstr "サイドスクロール" + +#: lib/logitech_receiver/settings_templates.py:167 +msgid "When disabled, pushing the wheel sideways sends custom button " + "events\n" + "instead of the standard side-scrolling events." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:177 +msgid "Sensitivity (DPI - older mice)" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:178 +#: lib/logitech_receiver/settings_templates.py:712 msgid "Mouse movement sensitivity" msgstr "マウスの動きの感度" -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Sensitivity (DPI)" -msgstr "感度(DPI)" - -#: lib/logitech_receiver/settings_templates.py:93 -msgid "Sensitivity (Pointer Speed)" -msgstr "感度(ポインタ速度)" - -#: lib/logitech_receiver/settings_templates.py:94 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "マウス速度の倍率(256で標準)。" - -#: lib/logitech_receiver/settings_templates.py:95 -msgid "Scroll Wheel Rachet" -msgstr "スクロールホイールラチェット" - -#: lib/logitech_receiver/settings_templates.py:96 -msgid "Automatically switch the mouse wheel between ratchet and freespin " - "mode.\n" - "The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "マウスホイールのラチェットモードとフリースピンモードを自動的に切り替" - "えます。\n" - "0で常にフリー、50で常にラチェットモードになります" - -#: lib/logitech_receiver/settings_templates.py:98 +#: lib/logitech_receiver/settings_templates.py:208 +#: lib/logitech_receiver/settings_templates.py:218 +#: lib/logitech_receiver/settings_templates.py:225 msgid "Backlight" msgstr "バックライト" -#: lib/logitech_receiver/settings_templates.py:98 +#: lib/logitech_receiver/settings_templates.py:209 +#: lib/logitech_receiver/settings_templates.py:226 +msgid "Set illumination time for keyboard." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:219 msgid "Turn illumination on or off on keyboard." msgstr "バックライトのON/OFFを切り替えます。" -#: lib/logitech_receiver/settings_templates.py:99 -msgid "Key/Button Actions" -msgstr "キー/ボタン動作" +#: lib/logitech_receiver/settings_templates.py:237 +msgid "Scroll Wheel High Resolution" +msgstr "高解像度スクロールホイール" -#: lib/logitech_receiver/settings_templates.py:100 -msgid "Change the action for the key or button." -msgstr "キーやボタンの動作を変更します。" +#: lib/logitech_receiver/settings_templates.py:240 +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:101 -msgid "Changing important actions (such as for the left mouse button) can " - "result in an unusable system." -msgstr "重要なアクション(マウスの左ボタンなど)を変更すると、システムが使用" - "できなくなる可能性があります。" +#: lib/logitech_receiver/settings_templates.py:247 +#: lib/logitech_receiver/settings_templates.py:277 +msgid "Scroll Wheel Diversion" +msgstr "スクロールホイール迂回" -#: lib/logitech_receiver/settings_templates.py:102 -msgid "Key/Button Diversion" -msgstr "キー/ボタンをルールへ迂回" +#: lib/logitech_receiver/settings_templates.py:249 +msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:103 -msgid "Make the key or button send HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." -msgstr "キーまたはボタンにHID ++通知を送信させます(Solaarルールをトリガーし" - "ますが、それ以外の場合は無視されます)。" +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Scroll Wheel Direction" +msgstr "ホイールのスクロール方向" -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable keys" -msgstr "無効キー" +#: lib/logitech_receiver/settings_templates.py:257 +msgid "Invert direction for vertical scroll with wheel." +msgstr "ホイールでの縦スクロール方向を逆にします。" -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable specific keyboard keys." -msgstr "特定のキーを無効にします。" +#: lib/logitech_receiver/settings_templates.py:265 +msgid "Scroll Wheel Resolution" +msgstr "ホイールのスクロール解像度" -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Change keys to match OS." -msgstr "OSに合わせたキーに変更します。" +#: lib/logitech_receiver/settings_templates.py:279 +msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Set OS" -msgstr "OS設定" +#: lib/logitech_receiver/settings_templates.py:288 +msgid "Sensitivity (Pointer Speed)" +msgstr "感度(ポインタ速度)" -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Change Host" -msgstr "ホスト変更" +#: lib/logitech_receiver/settings_templates.py:289 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "マウス速度の倍率(256で標準)。" -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Switch connection to a different host" -msgstr "接続を他のホストへ切り替えます" - -#: lib/logitech_receiver/settings_templates.py:107 +#: lib/logitech_receiver/settings_templates.py:299 msgid "Thumb Wheel Diversion" msgstr "" -#: lib/logitech_receiver/settings_templates.py:108 -msgid "HID++ mode for horizontal scroll with the thumb wheel." +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " + "Solaar rules but are otherwise ignored)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:109 -msgid "Effectively turns off thumb scrolling in Linux." -msgstr "事実上、Linuxでサムスクロールをオフにします。" - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Invert thumb wheel scroll direction." -msgstr "サムホイールのスクロール方向を反転します。" - -#: lib/logitech_receiver/settings_templates.py:110 +#: lib/logitech_receiver/settings_templates.py:310 msgid "Thumb Wheel Direction" msgstr "サムホイールの方向" -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Gestures" -msgstr "ジェスチャー" +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Invert thumb wheel scroll direction." +msgstr "サムホイールのスクロール方向を反転します。" -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "マウス/タッチパッドの動作を微調整します。" +#: lib/logitech_receiver/settings_templates.py:319 +msgid "Onboard Profiles" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "マウス/タッチパッドの数値パラメータを変更します。" +#: lib/logitech_receiver/settings_templates.py:320 +msgid "Enable onboard profiles, which often control report rate and " + "keyboard lighting" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Gesture params" -msgstr "ジェスチャーパラメータ" +#: lib/logitech_receiver/settings_templates.py:330 +msgid "Polling Rate (ms)" +msgstr "ポーリングレート(ミリ秒)" -#: lib/logitech_receiver/settings_templates.py:113 -msgid "DPI Sliding Adjustment" -msgstr "DPIスライディング調整" +#: lib/logitech_receiver/settings_templates.py:332 +msgid "Frequency of device polling, in milliseconds" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:114 -msgid "Adjust the DPI by sliding the mouse horizontally while holding the " - "button down." -msgstr "ボタンを押したままマウスを横に動かすことで感度(DPI)を調整します。" +#: lib/logitech_receiver/settings_templates.py:333 +#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:115 -msgid "Mouse Gestures" -msgstr "マウスジェスチャー" - -#: lib/logitech_receiver/settings_templates.py:116 -msgid "Send a gesture by sliding the mouse while holding the button down." -msgstr "ボタンを押したままマウスをスライドさせることで任意のジェスチャーを送" - "信します。" - -#: lib/logitech_receiver/settings_templates.py:117 +#: lib/logitech_receiver/settings_templates.py:365 msgid "Divert crown events" msgstr "クラウンをルールへ迂回" -#: lib/logitech_receiver/settings_templates.py:118 +#: lib/logitech_receiver/settings_templates.py:366 msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "CrownにCROWNHID++通知を送信させます(Solaarルールをトリガーしますが、" "それ以外の場合は無視されます)。" -#: lib/logitech_receiver/settings_templates.py:119 +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:375 +msgid "Set crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:383 msgid "Divert G Keys" msgstr "" -#: lib/logitech_receiver/settings_templates.py:120 +#: lib/logitech_receiver/settings_templates.py:385 msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Performs a left click." +#: lib/logitech_receiver/settings_templates.py:386 +msgid "May also make M keys and MR key send HID++ notifications" msgstr "" -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Single tap" +#: lib/logitech_receiver/settings_templates.py:402 +msgid "Scroll Wheel Ratcheted" msgstr "" -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Performs a right click." +#: lib/logitech_receiver/settings_templates.py:403 +msgid "Switch the mouse wheel between speed-controlled ratcheting and " + "always freespin." msgstr "" -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Single tap with two fingers" +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Freespinning" msgstr "" -#: lib/logitech_receiver/settings_templates.py:125 -msgid "Single tap with three fingers" +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Ratcheted" msgstr "" -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Double tap" +#: lib/logitech_receiver/settings_templates.py:412 +msgid "Scroll Wheel Ratchet Speed" msgstr "" -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Performs a double click." +#: lib/logitech_receiver/settings_templates.py:414 +msgid "Use the mouse wheel speed to switch between ratcheted and " + "freespinning.\n" + "The mouse wheel is always ratcheted at 50." msgstr "" -#: lib/logitech_receiver/settings_templates.py:130 -msgid "Double tap with two fingers" +#: lib/logitech_receiver/settings_templates.py:463 +msgid "Key/Button Actions" +msgstr "キー/ボタン動作" + +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Change the action for the key or button." +msgstr "キーやボタンの動作を変更します。" + +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Overridden by diversion." msgstr "" -#: lib/logitech_receiver/settings_templates.py:131 -msgid "Double tap with three fingers" +#: lib/logitech_receiver/settings_templates.py:466 +msgid "Changing important actions (such as for the left mouse button) can " + "result in an unusable system." +msgstr "重要なアクション(マウスの左ボタンなど)を変更すると、システムが使用" + "できなくなる可能性があります。" + +#: lib/logitech_receiver/settings_templates.py:639 +msgid "Key/Button Diversion" +msgstr "キー/ボタンをルールへ迂回" + +#: lib/logitech_receiver/settings_templates.py:640 +msgid "Make the key or button send HID++ notifications (Diverted) or " + "initiate Mouse Gestures or Sliding DPI" msgstr "" -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Drags items by dragging the finger after double tapping." +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Diverted" +msgstr "迂回する" + +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +msgid "Mouse Gestures" +msgstr "マウスジェスチャー" + +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Regular" +msgstr "通常通り" + +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Sliding DPI" msgstr "" -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Tap and drag" +#: lib/logitech_receiver/settings_templates.py:711 +msgid "Sensitivity (DPI)" +msgstr "感度(DPI)" + +#: lib/logitech_receiver/settings_templates.py:752 +msgid "Sensitivity Switching" msgstr "" -#: lib/logitech_receiver/settings_templates.py:135 -msgid "Tap and drag with two fingers" +#: lib/logitech_receiver/settings_templates.py:754 +msgid "Switch the current sensitivity and the remembered sensitivity when " + "the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current " + "sensitivity" msgstr "" -#: lib/logitech_receiver/settings_templates.py:136 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:758 +msgid "Off" +msgstr "オフ" -#: lib/logitech_receiver/settings_templates.py:137 -msgid "Tap and drag with three fingers" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:791 +msgid "Disable keys" +msgstr "無効キー" -#: lib/logitech_receiver/settings_templates.py:139 -msgid "Suppress tap and edge gestures" -msgstr "タップとエッジジェスチャーの無効化" +#: lib/logitech_receiver/settings_templates.py:792 +msgid "Disable specific keyboard keys." +msgstr "特定のキーを無効にします。" -#: lib/logitech_receiver/settings_templates.py:140 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "タップおよびエッジジェスチャを無効にします(Fn + LeftClickを押すのと" - "同等)。" - -#: lib/logitech_receiver/settings_templates.py:141 -msgid "Scroll with one finger" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:141 -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scrolls." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scroll with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scroll horizontally with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scrolls horizontally." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scroll vertically with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scrolls vertically." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Inverts the scrolling direction." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Natural scrolling" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Enables the thumbwheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Thumbwheel" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:162 -msgid "Swipe from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:159 -msgid "Swipe from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:160 -msgid "Swipe from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:161 -msgid "Swipe from the bottom edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:163 -msgid "Swipe two fingers from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:164 -msgid "Swipe two fingers from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:165 -msgid "Swipe two fingers from the bottom edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:166 -msgid "Swipe two fingers from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:167 -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:167 -msgid "Zoom with two fingers." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:168 -msgid "Pinch to zoom out." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:169 -msgid "Spread to zoom in." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:170 -msgid "Zoom with three fingers." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Zoom with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:189 -msgid "Pixel zone" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:190 -msgid "Ratio zone" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Scale factor" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Sets the cursor speed." -msgstr "カーソル速度を設定します。" - -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left-most coordinate." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:196 -msgid "Top-most coordinate." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:196 -msgid "top" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:197 -msgid "Width." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:197 -msgid "width" -msgstr "幅" - -#: lib/logitech_receiver/settings_templates.py:198 -msgid "Height." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:198 -msgid "height" -msgstr "高さ" - -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Cursor speed." -msgstr "カーソル速度。" - -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Scale" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:202 +#: lib/logitech_receiver/settings_templates.py:795 #, python-format msgid "Disables the %s key." msgstr "キー %s を無効にする。" -#: lib/logitech_receiver/settings_templates.py:521 -#: lib/logitech_receiver/settings_templates.py:573 -msgid "Off" -msgstr "オフ" +#: lib/logitech_receiver/settings_templates.py:809 +#: lib/logitech_receiver/settings_templates.py:860 +msgid "Set OS" +msgstr "OS設定" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Diverted" -msgstr "迂回する" +#: lib/logitech_receiver/settings_templates.py:810 +#: lib/logitech_receiver/settings_templates.py:861 +msgid "Change keys to match OS." +msgstr "OSに合わせたキーに変更します。" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Regular" -msgstr "通常通り" +#: lib/logitech_receiver/settings_templates.py:873 +msgid "Change Host" +msgstr "ホスト変更" -#: lib/logitech_receiver/status.py:109 +#: lib/logitech_receiver/settings_templates.py:874 +msgid "Switch connection to a different host" +msgstr "接続を他のホストへ切り替えます" + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Performs a left click." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Single tap" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Performs a right click." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Single tap with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:902 +msgid "Single tap with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Double tap" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Performs a double click." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:907 +msgid "Double tap with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:908 +msgid "Double tap with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Drags items by dragging the finger after double tapping." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Tap and drag" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Tap and drag with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:914 +msgid "Tap and drag with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "タップおよびエッジジェスチャを無効にします(Fn + LeftClickを押すのと" + "同等)。" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Suppress tap and edge gestures" +msgstr "タップとエッジジェスチャーの無効化" + +#: lib/logitech_receiver/settings_templates.py:918 +msgid "Scroll with one finger" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scrolls." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scroll with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scroll horizontally with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scrolls horizontally." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scroll vertically with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scrolls vertically." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Inverts the scrolling direction." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Natural scrolling" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Enables the thumbwheel." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Thumbwheel" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:935 +#: lib/logitech_receiver/settings_templates.py:939 +msgid "Swipe from the top edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:936 +msgid "Swipe from the left edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:937 +msgid "Swipe from the right edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:938 +msgid "Swipe from the bottom edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:940 +msgid "Swipe two fingers from the left edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:941 +msgid "Swipe two fingers from the right edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:942 +msgid "Swipe two fingers from the bottom edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:943 +msgid "Swipe two fingers from the top edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:944 +msgid "Zoom with two fingers." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:945 +msgid "Pinch to zoom out." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:946 +msgid "Spread to zoom in." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:947 +msgid "Zoom with three fingers." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Zoom with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:966 +msgid "Pixel zone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:967 +msgid "Ratio zone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Scale factor" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Sets the cursor speed." +msgstr "カーソル速度を設定します。" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left-most coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width" +msgstr "幅" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height" +msgstr "高さ" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Cursor speed." +msgstr "カーソル速度。" + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Scale" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:982 +msgid "Gestures" +msgstr "ジェスチャー" + +#: lib/logitech_receiver/settings_templates.py:983 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "マウス/タッチパッドの動作を微調整します。" + +#: lib/logitech_receiver/settings_templates.py:1000 +msgid "Gestures Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1001 +msgid "Divert mouse/touchpad gestures." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1018 +msgid "Gesture params" +msgstr "ジェスチャーパラメータ" + +#: lib/logitech_receiver/settings_templates.py:1019 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "マウス/タッチパッドの数値パラメータを変更します。" + +#: lib/logitech_receiver/settings_templates.py:1044 +msgid "M-Key LEDs" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Control the M-Key LEDs." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1077 +msgid "May need G Keys diverted to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1053 +#, python-format +msgid "Lights up the %s key." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1074 +msgid "MR-Key LED" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "Control the MR-Key LED." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1095 +msgid "Persistent Key/Button Mapping" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1097 +msgid "Permanently change the mapping for the key or button." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1098 +msgid "Changing important keys or buttons (such as for the left mouse " + "button) can result in an unusable system." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1157 +msgid "Sidetone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1158 +msgid "Set sidetone level." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1167 +msgid "Equalizer" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1168 +msgid "Set equalizer levels." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1191 +msgid "Hz" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1197 +msgid "Power Management" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1198 +msgid "Power off in minutes (0 for never)." +msgstr "" + +#: lib/logitech_receiver/status.py:114 msgid "No paired devices." msgstr "ペアリングされたデバイスはありません。" -#: lib/logitech_receiver/status.py:110 lib/solaar/ui/window.py:623 +#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 #, python-format msgid "%(count)s paired device." msgid_plural "%(count)s paired devices." msgstr[0] "%(count)s デバイスとペアリングされています。" -#: lib/logitech_receiver/status.py:165 +#: lib/logitech_receiver/status.py:170 #, python-format msgid "Battery: %(level)s" msgstr "バッテリー残量: %(level)s" -#: lib/logitech_receiver/status.py:167 +#: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "バッテリー残量: %(percent)d%%" -#: lib/logitech_receiver/status.py:179 +#: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "照明: %(level)s ルクス" -#: lib/logitech_receiver/status.py:234 +#: lib/logitech_receiver/status.py:239 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "バッテリー残量: %(level)s (%(status)s)" -#: lib/logitech_receiver/status.py:236 +#: lib/logitech_receiver/status.py:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "バッテリー残量: %(percent)d%% (%(status)s)" @@ -781,16 +935,14 @@ msgstr "権限エラー" #: lib/solaar/ui/__init__.py:54 #, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open " - "it." -msgstr "Logitechレシーバー (%s) が見つかりましたが、開く権限がありませんでし" - "た。" +msgid "Found a Logitech receiver or device (%s), but did not have " + "permission to open it." +msgstr "" #: lib/solaar/ui/__init__.py:55 -msgid "If you've just installed Solaar, try removing the receiver and " - "plugging it back in." -msgstr "Solaarをインストールしたばかりの場合は、レシーバーを取り外して、再び" - "接続してみてください。" +msgid "If you've just installed Solaar, try disconnecting the receiver or " + "device and then reconnecting it." +msgstr "" #: lib/solaar/ui/__init__.py:58 msgid "Cannot connect to device error" @@ -804,10 +956,9 @@ msgstr "%s で Logitech のレシーバーまたはデバイスが見つかり "エラーが発生しました。" #: lib/solaar/ui/__init__.py:61 -msgid "Try removing the device and plugging it back in or turning it off " - "and then on." -msgstr "デバイスを取り外して再度接続するか、電源をオフにしてからオンにしてみ" - "てください。" +msgid "Try disconnecting the device and then reconnecting it or turning it " + "off and then on." +msgstr "" #: lib/solaar/ui/__init__.py:64 msgid "Unpairing failed" @@ -822,356 +973,629 @@ msgstr "%{receiver} からの %{device} のペアリング解除に失敗しま msgid "The receiver returned an error, with no further details." msgstr "レシーバーがこれ以上の詳細なしでエラーを返しました。" -#: lib/solaar/ui/about.py:39 +#: lib/solaar/ui/__init__.py:177 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" + +#: lib/solaar/ui/about.py:36 msgid "Manages Logitech receivers,\n" "keyboards, mice, and tablets." msgstr "Logitechのレシーバー、キーボード、\n" "マウス、タブレットを管理。" -#: lib/solaar/ui/about.py:47 +#: lib/solaar/ui/about.py:44 msgid "Additional Programming" msgstr "追加のプログラミング" -#: lib/solaar/ui/about.py:48 +#: lib/solaar/ui/about.py:45 msgid "GUI design" msgstr "GUIデザイン" -#: lib/solaar/ui/about.py:50 +#: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "テスト" -#: lib/solaar/ui/about.py:57 +#: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "" -#: lib/solaar/ui/action.py:70 lib/solaar/ui/window.py:328 -msgid "About %s" -msgstr "%sについて" - -#: lib/solaar/ui/action.py:96 lib/solaar/ui/action.py:100 -#: lib/solaar/ui/window.py:205 +#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 +#: lib/solaar/ui/window.py:197 msgid "Unpair" msgstr "ペアリング解除" -#: lib/solaar/ui/action.py:99 lib/solaar/ui/diversion_rules.py:144 +#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "キャンセル" -#: lib/solaar/ui/config_panel.py:358 -msgid "Changes allowed" -msgstr "変更を許可する" +#: lib/solaar/ui/config_panel.py:212 +msgid "Complete - ENTER to change" +msgstr "" -#: lib/solaar/ui/config_panel.py:359 -msgid "No changes allowed" -msgstr "変更を許可しない" +#: lib/solaar/ui/config_panel.py:212 +msgid "Incomplete" +msgstr "" -#: lib/solaar/ui/config_panel.py:360 -msgid "Ignore this setting" -msgstr "この設定を無視する" - -#: lib/solaar/ui/config_panel.py:401 -msgid "Working" -msgstr "処理中" - -#: lib/solaar/ui/config_panel.py:404 -msgid "Read/write operation failed." -msgstr "読取または書込処理に失敗しました。" - -#: lib/solaar/ui/config_panel.py:522 +#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/config_panel.py:518 +msgid "Changes allowed" +msgstr "変更を許可する" + +#: lib/solaar/ui/config_panel.py:519 +msgid "No changes allowed" +msgstr "変更を許可しない" + +#: lib/solaar/ui/config_panel.py:520 +msgid "Ignore this setting" +msgstr "この設定を無視する" + +#: lib/solaar/ui/config_panel.py:565 +msgid "Working" +msgstr "処理中" + +#: lib/solaar/ui/config_panel.py:568 +msgid "Read/write operation failed." +msgstr "読取または書込処理に失敗しました。" + +#: lib/solaar/ui/diversion_rules.py:65 msgid "Built-in rules" msgstr "組み込みのルール" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" msgstr "ユーザー定義のルール" -#: lib/solaar/ui/diversion_rules.py:59 lib/solaar/ui/diversion_rules.py:792 +#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "ルール" -#: lib/solaar/ui/diversion_rules.py:60 lib/solaar/ui/diversion_rules.py:503 -#: lib/solaar/ui/diversion_rules.py:622 +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 +#: lib/solaar/ui/diversion_rules.py:636 msgid "Sub-rule" msgstr "サブルール" -#: lib/solaar/ui/diversion_rules.py:62 +#: lib/solaar/ui/diversion_rules.py:70 msgid "[empty]" msgstr "[空]" -#: lib/solaar/ui/diversion_rules.py:85 +#: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "Solaarルールエディタ" -#: lib/solaar/ui/diversion_rules.py:135 +#: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "変更を保存しますか?" -#: lib/solaar/ui/diversion_rules.py:140 +#: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "はい" -#: lib/solaar/ui/diversion_rules.py:142 +#: lib/solaar/ui/diversion_rules.py:148 msgid "No" msgstr "いいえ" -#: lib/solaar/ui/diversion_rules.py:147 +#: lib/solaar/ui/diversion_rules.py:153 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "[いいえ]を選択すると、Solaarを閉じるときに変更が失われます。" -#: lib/solaar/ui/diversion_rules.py:195 +#: lib/solaar/ui/diversion_rules.py:201 msgid "Save changes" msgstr "保存" -#: lib/solaar/ui/diversion_rules.py:200 +#: lib/solaar/ui/diversion_rules.py:206 msgid "Discard changes" msgstr "変更を破棄" -#: lib/solaar/ui/diversion_rules.py:366 +#: lib/solaar/ui/diversion_rules.py:372 msgid "Insert here" msgstr "ここに挿入" -#: lib/solaar/ui/diversion_rules.py:368 +#: lib/solaar/ui/diversion_rules.py:374 msgid "Insert above" msgstr "上に挿入" -#: lib/solaar/ui/diversion_rules.py:370 +#: lib/solaar/ui/diversion_rules.py:376 msgid "Insert below" msgstr "下に挿入" -#: lib/solaar/ui/diversion_rules.py:376 +#: lib/solaar/ui/diversion_rules.py:382 msgid "Insert new rule here" msgstr "新しいルールをここに挿入" -#: lib/solaar/ui/diversion_rules.py:378 +#: lib/solaar/ui/diversion_rules.py:384 msgid "Insert new rule above" msgstr "新しいルールを上に挿入" -#: lib/solaar/ui/diversion_rules.py:380 +#: lib/solaar/ui/diversion_rules.py:386 msgid "Insert new rule below" msgstr "新しいルールを下に挿入" -#: lib/solaar/ui/diversion_rules.py:421 +#: lib/solaar/ui/diversion_rules.py:427 msgid "Paste here" msgstr "ここに貼り付け" -#: lib/solaar/ui/diversion_rules.py:423 +#: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "上に貼り付け" -#: lib/solaar/ui/diversion_rules.py:425 +#: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "下に貼り付け" -#: lib/solaar/ui/diversion_rules.py:431 +#: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "ここにルールを貼り付け" -#: lib/solaar/ui/diversion_rules.py:433 +#: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "上にルールを貼り付け" -#: lib/solaar/ui/diversion_rules.py:435 +#: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "下にルールを貼り付け" -#: lib/solaar/ui/diversion_rules.py:439 +#: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "ルールを貼り付け" -#: lib/solaar/ui/diversion_rules.py:468 +#: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "フラット化" -#: lib/solaar/ui/diversion_rules.py:501 +#: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "挿入" -#: lib/solaar/ui/diversion_rules.py:504 lib/solaar/ui/diversion_rules.py:624 -#: lib/solaar/ui/diversion_rules.py:835 +#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 +#: lib/solaar/ui/diversion_rules.py:1126 msgid "Or" msgstr "どれか" -#: lib/solaar/ui/diversion_rules.py:505 lib/solaar/ui/diversion_rules.py:623 -#: lib/solaar/ui/diversion_rules.py:820 +#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 +#: lib/solaar/ui/diversion_rules.py:1111 msgid "And" msgstr "すべて" -#: lib/solaar/ui/diversion_rules.py:507 +#: lib/solaar/ui/diversion_rules.py:513 msgid "Condition" msgstr "状態" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:951 +#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "機能" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:874 -msgid "Process" -msgstr "プロセス" - -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:902 -msgid "MouseProcess" -msgstr "マウスプロセス" - -#: lib/solaar/ui/diversion_rules.py:512 lib/solaar/ui/diversion_rules.py:984 +#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "通知" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1019 +#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 +msgid "Process" +msgstr "プロセス" + +#: lib/solaar/ui/diversion_rules.py:518 +msgid "Mouse process" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 msgid "Modifiers" msgstr "修飾キー" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1066 +#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "キー" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1111 +#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 +msgid "KeyIsDown" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 +msgid "Active" +msgstr "有効" + +#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2218 +#: lib/solaar/ui/diversion_rules.py:2270 lib/solaar/ui/diversion_rules.py:2323 +msgid "Device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +msgid "Host" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 +msgid "Setting" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 +#: lib/solaar/ui/diversion_rules.py:1526 msgid "Test" msgstr "評価" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1196 +#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 +msgid "Test bytes" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 msgid "Mouse Gesture" msgstr "マウスジェスチャー" -#: lib/solaar/ui/diversion_rules.py:520 +#: lib/solaar/ui/diversion_rules.py:532 msgid "Action" msgstr "アクション" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1286 +#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 msgid "Key press" msgstr "キー押下" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1335 +#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "マウススクロール" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:1383 +#: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 msgid "Mouse click" msgstr "マウスクリック" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1452 +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Set" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 msgid "Execute" msgstr "実行" -#: lib/solaar/ui/diversion_rules.py:554 +#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 +msgid "Later" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:568 msgid "Insert new rule" msgstr "新しいルールを挿入" -#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/diversion_rules.py:1146 -#: lib/solaar/ui/diversion_rules.py:1236 lib/solaar/ui/diversion_rules.py:1411 +#: lib/solaar/ui/diversion_rules.py:588 lib/solaar/ui/diversion_rules.py:1686 +#: lib/solaar/ui/diversion_rules.py:1790 lib/solaar/ui/diversion_rules.py:1989 msgid "Delete" msgstr "削除" -#: lib/solaar/ui/diversion_rules.py:596 +#: lib/solaar/ui/diversion_rules.py:610 msgid "Negate" msgstr "否" -#: lib/solaar/ui/diversion_rules.py:620 +#: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "次で覆う" -#: lib/solaar/ui/diversion_rules.py:642 +#: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "切り取り" -#: lib/solaar/ui/diversion_rules.py:657 +#: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "貼り付け" -#: lib/solaar/ui/diversion_rules.py:669 +#: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "コピー" -#: lib/solaar/ui/diversion_rules.py:772 +#: lib/solaar/ui/diversion_rules.py:1063 msgid "This editor does not support the selected rule component yet." msgstr "このエディタは、選択したルールコンポーネントをまだサポートしていませ" "ん。" -#: lib/solaar/ui/diversion_rules.py:850 +#: lib/solaar/ui/diversion_rules.py:1138 +msgid "Number of seconds to delay." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1177 msgid "Not" msgstr "否" -#: lib/solaar/ui/diversion_rules.py:1039 +#: lib/solaar/ui/diversion_rules.py:1187 +msgid "X11 active process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1218 +msgid "X11 mouse process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1235 +msgid "MouseProcess" +msgstr "マウスプロセス" + +#: lib/solaar/ui/diversion_rules.py:1260 +msgid "Feature name of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1308 +msgid "Report number of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1342 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1383 +msgid "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1392 msgid "Key down" msgstr "押す" -#: lib/solaar/ui/diversion_rules.py:1042 +#: lib/solaar/ui/diversion_rules.py:1395 msgid "Key up" msgstr "離す" -#: lib/solaar/ui/diversion_rules.py:1131 -msgid "Add action" -msgstr "操作追加" +#: lib/solaar/ui/diversion_rules.py:1436 +msgid "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1224 +#: lib/solaar/ui/diversion_rules.py:1475 +msgid "Test condition on notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1479 +msgid "Parameter" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1542 +msgid "begin (inclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1543 +msgid "end (exclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1552 +msgid "range" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1554 +msgid "minimum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1555 +msgid "maximum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1557 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1562 +msgid "mask" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1563 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1573 +msgid "Bit or range test on bytes in notification message triggering rule " + "processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1583 +msgid "type" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1666 +msgid "Mouse gesture with optional initiating button followed by zero or " + "more mouse movements." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1671 +msgid "Add movement" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1764 +msgid "Simulate a chorded key click or depress or release.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1769 msgid "Add key" msgstr "キー追加" -#: lib/solaar/ui/diversion_rules.py:1354 +#: lib/solaar/ui/diversion_rules.py:1772 +msgid "Click" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1775 +msgid "Depress" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1778 +msgid "Release" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1861 +msgid "Simulate a mouse scroll.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1918 +msgid "Simulate a mouse click.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1921 msgid "Button" msgstr "ボタン" -#: lib/solaar/ui/diversion_rules.py:1355 -msgid "Count" -msgstr "回数" +#: lib/solaar/ui/diversion_rules.py:1922 +msgid "Count and Action" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1397 +#: lib/solaar/ui/diversion_rules.py:1972 +msgid "Execute a command with arguments." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1975 msgid "Add argument" msgstr "引数を追加" -#: lib/solaar/ui/notify.py:125 lib/solaar/ui/tray.py:319 -#: lib/solaar/ui/tray.py:324 lib/solaar/ui/window.py:749 +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "Toggle" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "True" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2051 +msgid "False" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2065 +msgid "Unsupported setting" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2242 +#: lib/solaar/ui/diversion_rules.py:2328 lib/solaar/ui/diversion_rules.py:2570 +#: lib/solaar/ui/diversion_rules.py:2588 +msgid "Originating device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2256 +msgid "Device is active and its settings can be changed." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2266 +msgid "Device that originated the current notification." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2280 +msgid "Name of host computer." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2347 +msgid "Value" +msgstr "値" + +#: lib/solaar/ui/diversion_rules.py:2355 +msgid "Item" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2630 +msgid "Change setting on device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2647 +msgid "Setting on device" +msgstr "" + +#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:318 +#: lib/solaar/ui/tray.py:323 lib/solaar/ui/window.py:739 msgid "offline" msgstr "未接続" -#: lib/solaar/ui/pair_window.py:134 +#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 +#: lib/solaar/ui/pair_window.py:288 +#, python-format +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: 新しいデバイスをペアリング" + +#: lib/solaar/ui/pair_window.py:123 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "" + +#: lib/solaar/ui/pair_window.py:126 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "left" +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "right" +msgstr "" + +#: lib/solaar/ui/pair_window.py:131 +#, python-format +msgid "Press %(code)s\n" + "and then press left and right buttons simultaneously." +msgstr "" + +#: lib/solaar/ui/pair_window.py:188 msgid "Pairing failed" msgstr "ペアリング失敗" -#: lib/solaar/ui/pair_window.py:136 +#: lib/solaar/ui/pair_window.py:190 msgid "Make sure your device is within range, and has a decent battery " "charge." msgstr "デバイスが範囲内にあり、適切なバッテリー充電が行われていることを確認" "してください。" -#: lib/solaar/ui/pair_window.py:138 +#: lib/solaar/ui/pair_window.py:192 msgid "A new device was detected, but it is not compatible with this " "receiver." msgstr "新しいデバイスが検出されましたが、このレシーバーと互換性がありませ" "ん。" -#: lib/solaar/ui/pair_window.py:140 +#: lib/solaar/ui/pair_window.py:194 msgid "More paired devices than receiver can support." msgstr "レシーバーのサポートより多くペアリングされたデバイス。" -#: lib/solaar/ui/pair_window.py:142 +#: lib/solaar/ui/pair_window.py:196 msgid "No further details are available about the error." msgstr "エラーに関するこれ以上の詳細はありません。" -#: lib/solaar/ui/pair_window.py:156 +#: lib/solaar/ui/pair_window.py:210 msgid "Found a new device:" msgstr "新しいデバイスが見つかりました:" -#: lib/solaar/ui/pair_window.py:181 +#: lib/solaar/ui/pair_window.py:235 msgid "The wireless link is not encrypted" msgstr "この無線通信は暗号化されていません" -#: lib/solaar/ui/pair_window.py:199 -#, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s: 新しいデバイスをペアリング" +#: lib/solaar/ui/pair_window.py:264 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "" -#: lib/solaar/ui/pair_window.py:206 +#: lib/solaar/ui/pair_window.py:266 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:268 +msgid "Other receivers are only compatible with a few devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:270 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" + +#: lib/solaar/ui/pair_window.py:274 +msgid "Press a pairing button or key until the pairing light flashes " + "quickly." +msgstr "" + +#: lib/solaar/ui/pair_window.py:276 +msgid "You may have to first turn the device off and on again." +msgstr "" + +#: lib/solaar/ui/pair_window.py:278 +msgid "Turn on the device you want to pair." +msgstr "ペアリングしたいデバイスの電源を入れて下さい。" + +#: lib/solaar/ui/pair_window.py:280 msgid "If the device is already turned on, turn it off and on again." msgstr "デバイスの電源が既にオンになっている場合、オフにしてからもう一度オン" "にします。" -#: lib/solaar/ui/pair_window.py:209 +#: lib/solaar/ui/pair_window.py:283 #, python-format msgid "\n" "\n" @@ -1183,218 +1607,297 @@ msgstr[0] "\n" "\n" "このレシーバーはあと %d デバイスとペアリングできます。" -#: lib/solaar/ui/pair_window.py:212 +#: lib/solaar/ui/pair_window.py:286 msgid "\n" "Cancelling at this point will not use up a pairing." msgstr "" -#: lib/solaar/ui/pair_window.py:215 -msgid "Turn on the device you want to pair." -msgstr "ペアリングしたいデバイスの電源を入れて下さい。" +#: lib/solaar/ui/tray.py:58 +msgid "No supported device found" +msgstr "" -#: lib/solaar/ui/tray.py:61 -msgid "No Logitech receiver found" -msgstr "Logitechレシーバーが見つかりません" +#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#, python-format +msgid "About %s" +msgstr "%sについて" -#: lib/solaar/ui/tray.py:68 lib/solaar/ui/window.py:325 +#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 +#, python-format msgid "Quit %s" msgstr "%sを終了" -#: lib/solaar/ui/tray.py:298 lib/solaar/ui/tray.py:306 +#: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "レシーバーなし" -#: lib/solaar/ui/tray.py:322 +#: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "ステータスなし" -#: lib/solaar/ui/window.py:104 +#: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "スキャン中" -#: lib/solaar/ui/window.py:137 lib/solaar/ui/window.py:692 +#: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "バッテリー" -#: lib/solaar/ui/window.py:140 +#: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "無線通信" -#: lib/solaar/ui/window.py:144 +#: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "照明" -#: lib/solaar/ui/window.py:178 +#: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "技術的な詳細を表示" -#: lib/solaar/ui/window.py:194 +#: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "デバイスの追加" -#: lib/solaar/ui/window.py:213 +#: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "デバイスを選択して下さい" -#: lib/solaar/ui/window.py:331 +#: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "ルールエディタ" -#: lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Path" -#: lib/solaar/ui/window.py:544 +#: lib/solaar/ui/window.py:536 msgid "USB ID" msgstr "USB ID" -#: lib/solaar/ui/window.py:547 lib/solaar/ui/window.py:549 -#: lib/solaar/ui/window.py:569 lib/solaar/ui/window.py:571 +#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 msgid "Serial" msgstr "Serial" -#: lib/solaar/ui/window.py:553 +#: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Index" -#: lib/solaar/ui/window.py:555 +#: lib/solaar/ui/window.py:547 msgid "Wireless PID" msgstr "Wireless PID" -#: lib/solaar/ui/window.py:557 +#: lib/solaar/ui/window.py:549 msgid "Product ID" msgstr "Product ID" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:551 msgid "Protocol" msgstr "Protocol" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:551 msgid "Unknown" msgstr "Unknown" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:554 #, python-format msgid "%(rate)d ms (%(rate_hz)dHz)" msgstr "%(rate)d ms (%(rate_hz)dHz)" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:554 msgid "Polling rate" msgstr "Polling rate" -#: lib/solaar/ui/window.py:573 +#: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "Unit ID" -#: lib/solaar/ui/window.py:584 +#: lib/solaar/ui/window.py:576 msgid "none" msgstr "none" -#: lib/solaar/ui/window.py:585 +#: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Notifications" -#: lib/solaar/ui/window.py:622 +#: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "ペアリングされたデバイスはありません。" -#: lib/solaar/ui/window.py:629 +#: lib/solaar/ui/window.py:628 #, python-format msgid "Up to %(max_count)s device can be paired to this receiver." msgid_plural "Up to %(max_count)s devices can be paired to this receiver." msgstr[0] "このレシーバーは最大 %(max_count)s デバイスとペアリングできま" "す。" -#: lib/solaar/ui/window.py:635 +#: lib/solaar/ui/window.py:634 msgid "Only one device can be paired to this receiver." msgstr "このレシーバーは1つのデバイスのみとペアリングできます。" -#: lib/solaar/ui/window.py:639 +#: lib/solaar/ui/window.py:638 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." msgstr[0] "このレシーバーはあと %d デバイスとペアリングできます。" -#: lib/solaar/ui/window.py:694 -msgid "Battery information unknown." -msgstr "バッテリー情報は不明です。" - -#: lib/solaar/ui/window.py:702 +#: lib/solaar/ui/window.py:692 msgid "Battery Voltage" msgstr "バッテリー電圧" -#: lib/solaar/ui/window.py:704 +#: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "バッテリーから報告された電圧" -#: lib/solaar/ui/window.py:706 lib/solaar/ui/window.py:710 +#: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "バッテリー残量" -#: lib/solaar/ui/window.py:708 lib/solaar/ui/window.py:712 +#: lib/solaar/ui/window.py:698 msgid "Approximate level reported by battery" msgstr "バッテリーから報告されたおおよその残量" -#: lib/solaar/ui/window.py:715 lib/solaar/ui/window.py:717 +#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 msgid "next reported " msgstr "〜 " -#: lib/solaar/ui/window.py:718 +#: lib/solaar/ui/window.py:708 msgid " and next level to be reported." msgstr " と 次に報告される残量。" -#: lib/solaar/ui/window.py:723 +#: lib/solaar/ui/window.py:713 msgid "last known" msgstr "最後に認識された値" -#: lib/solaar/ui/window.py:731 -msgid "not encrypted" -msgstr "暗号化なし" - -#: lib/solaar/ui/window.py:735 -msgid "The wireless link between this device and its receiver is not " - "encrypted.\n" - "\n" - "For pointing devices (mice, trackballs, trackpads), this is a minor " - "security issue.\n" - "\n" - "It is, however, a major security issue for text-input devices " - "(keyboards, numpads),\n" - "because typed text can be sniffed inconspicuously by 3rd parties " - "within range." -msgstr "このデバイスとレシーバー間の無線通信は暗号化されていません。\n" - "\n" - "ポインティングデバイス(マウス、トラックボール、トラックパッド)の場" - "合、重大ではありませんがセキュリティの問題があります。\n" - "\n" - "テキスト入力デバイス(キーボード、テンキー)の場合、重要なセキュリ" - "ティの問題があります。\n" - "入力した文字は、範囲内の第三者によって盗聴される可能性があります。" - -#: lib/solaar/ui/window.py:744 +#: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "暗号化あり" -#: lib/solaar/ui/window.py:746 +#: lib/solaar/ui/window.py:726 msgid "The wireless link between this device and its receiver is encrypted." msgstr "このデバイスとレシーバー間の通信は暗号化されています。" -#: lib/solaar/ui/window.py:759 +#: lib/solaar/ui/window.py:728 +msgid "not encrypted" +msgstr "暗号化なし" + +#: lib/solaar/ui/window.py:732 +msgid "The wireless link between this device and its receiver is not " + "encrypted.\n" + "This is a security issue for pointing devices, and a major security " + "issue for text-input devices." +msgstr "" + +#: lib/solaar/ui/window.py:748 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d ルクス" +#~ msgid "Add action" +#~ msgstr "操作追加" + +#~ msgid "Adjust the DPI by sliding the mouse horizontally while " +#~ "holding the button down." +#~ msgstr "ボタンを押したままマウスを横に動かすことで感度(DPI)を調整しま" +#~ "す。" + +#~ msgid "Automatically switch the mouse wheel between ratchet and " +#~ "freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always ratcheted at 50" +#~ msgstr "マウスホイールのラチェットモードとフリースピンモードを自動的" +#~ "に切り替えます。\n" +#~ "0で常にフリー、50で常にラチェットモードになります" + +#~ msgid "Battery information unknown." +#~ msgstr "バッテリー情報は不明です。" + +#~ msgid "Count" +#~ msgstr "回数" + +#~ msgid "DPI Sliding Adjustment" +#~ msgstr "DPIスライディング調整" + #~ msgid "ERROR: " #~ msgstr "エラー: " +#~ msgid "Effectively turns off thumb scrolling in Linux." +#~ msgstr "事実上、Linuxでサムスクロールをオフにします。" + +#~ msgid "Effectively turns off wheel scrolling in Linux." +#~ msgstr "事実上Linuxでのホイールスクロールを無効にします。" + #~ msgid "For more information see the Solaar installation directions\n" #~ "at https://pwr-solaar.github.io/Solaar/installation" #~ msgstr "詳細については、下記よりSolaarのインストール手順を参照してく" #~ "ださい\n" #~ "https://pwr-solaar.github.io/Solaar/installation" +#, python-format +#~ msgid "Found a Logitech Receiver (%s), but did not have permission " +#~ "to open it." +#~ msgstr "Logitechレシーバー (%s) が見つかりましたが、開く権限がありま" +#~ "せんでした。" + +#~ msgid "HID++ mode for vertical scroll with the wheel." +#~ msgstr "ホイールでのHID++モード縦スクロール。" + +#~ msgid "If you've just installed Solaar, try removing the receiver " +#~ "and plugging it back in." +#~ msgstr "Solaarをインストールしたばかりの場合は、レシーバーを取り外し" +#~ "て、再び接続してみてください。" + +#~ msgid "Make the key or button send HID++ notifications (which " +#~ "trigger Solaar rules but are otherwise ignored)." +#~ msgstr "キーまたはボタンにHID ++通知を送信させます(Solaarルールをト" +#~ "リガーしますが、それ以外の場合は無視されます)。" + +#~ msgid "No Logitech receiver found" +#~ msgstr "Logitechレシーバーが見つかりません" + #~ msgid "Scroll Wheel HID++ Scrolling" #~ msgstr "HID++スクロールホイール" +#~ msgid "Scroll Wheel Rachet" +#~ msgstr "スクロールホイールラチェット" + +#~ msgid "Send a gesture by sliding the mouse while holding the button " +#~ "down." +#~ msgstr "ボタンを押したままマウスをスライドさせることで任意のジェス" +#~ "チャーを送信します。" + #~ msgid "Solaar depends on a udev file that is not present" #~ msgstr "Solaarは存在しないudevファイルに依存しています" + +#~ msgid "The wireless link between this device and its receiver is " +#~ "not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices " +#~ "(keyboards, numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties " +#~ "within range." +#~ msgstr "このデバイスとレシーバー間の無線通信は暗号化されていませ" +#~ "ん。\n" +#~ "\n" +#~ "ポインティングデバイス(マウス、トラックボール、トラックパッド)の場" +#~ "合、重大ではありませんがセキュリティの問題があります。\n" +#~ "\n" +#~ "テキスト入力デバイス(キーボード、テンキー)の場合、重要なセキュリ" +#~ "ティの問題があります。\n" +#~ "入力した文字は、範囲内の第三者によって盗聴される可能性があります。" + +#~ msgid "Try removing the device and plugging it back in or turning " +#~ "it off and then on." +#~ msgstr "デバイスを取り外して再度接続するか、電源をオフにしてからオン" +#~ "にしてみてください。" + +#~ msgid "height" +#~ msgstr "高さ" + +#~ msgid "unknown" +#~ msgstr "不明" + +#~ msgid "width" +#~ msgstr "幅" diff --git a/po/ka.po b/po/ka.po new file mode 100644 index 00000000..6faa2fc1 --- /dev/null +++ b/po/ka.po @@ -0,0 +1,2162 @@ +# translation of solaar to Georgian +# Copyright (C) 2026 solaar's authors +# This file is distributed under the same license as the solaar package. +# Ekaterine Papava , 2026. +# +msgid "" +msgstr "" +"Project-Id-Version: solaar 1.1.17rc3\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-01 21:43+0300\n" +"PO-Revision-Date: 2026-01-07 16:55+0100\n" +"Last-Translator: Ekaterine Papava \n" +"Language-Team: Georgian <(nothing)>\n" +"Language: ka\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.8\n" + +#: lib/logitech_receiver/base_usb.py:52 +msgid "Bolt Receiver" +msgstr "Bolt მიმღები" + +#: lib/logitech_receiver/base_usb.py:64 +msgid "Unifying Receiver" +msgstr "Unifying მიმღები" + +#: lib/logitech_receiver/base_usb.py:75 lib/logitech_receiver/base_usb.py:87 +#: lib/logitech_receiver/base_usb.py:100 lib/logitech_receiver/base_usb.py:113 +msgid "Nano Receiver" +msgstr "Nano მიმღები" + +#: lib/logitech_receiver/base_usb.py:125 +msgid "Lightspeed Receiver" +msgstr "Lightspeed მიმღები" + +#: lib/logitech_receiver/base_usb.py:135 +msgid "EX100 Receiver 27 Mhz" +msgstr "EX100 მიმღები 27 მჰც" + +#: lib/logitech_receiver/common.py:649 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "ელემენტი: %(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:652 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "ელემენტი: %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:1048 +#: lib/logitech_receiver/settings_templates.py:299 +msgid "Disabled" +msgstr "გამორთულია" + +#: lib/logitech_receiver/hidpp20.py:1049 +msgid "Static" +msgstr "სტატიკური" + +#: lib/logitech_receiver/hidpp20.py:1050 +msgid "Pulse" +msgstr "პულსი" + +#: lib/logitech_receiver/hidpp20.py:1051 +msgid "Cycle" +msgstr "ციკლი" + +#: lib/logitech_receiver/hidpp20.py:1052 +msgid "Boot" +msgstr "ჩატვირთვა" + +#: lib/logitech_receiver/hidpp20.py:1053 +msgid "Demo" +msgstr "დემო" + +#: lib/logitech_receiver/hidpp20.py:1055 +msgid "Breathe" +msgstr "სუნთქვა" + +#: lib/logitech_receiver/hidpp20.py:1058 +msgid "Ripple" +msgstr "ჭავლი" + +#: lib/logitech_receiver/hidpp20.py:1059 +msgid "Decomposition" +msgstr "დაშლა" + +#: lib/logitech_receiver/hidpp20.py:1060 +msgid "Signature1" +msgstr "ხელმოწერა1" + +#: lib/logitech_receiver/hidpp20.py:1061 +msgid "Signature2" +msgstr "ხელმოწერა2" + +#: lib/logitech_receiver/hidpp20.py:1062 +msgid "CycleS" +msgstr "ციკლები" + +#: lib/logitech_receiver/hidpp20.py:1126 +msgid "Unknown Location" +msgstr "უცნობი მდებარეობა" + +#: lib/logitech_receiver/hidpp20.py:1127 +msgid "Primary" +msgstr "ძირითადი" + +#: lib/logitech_receiver/hidpp20.py:1128 +msgid "Logo" +msgstr "ლოგო" + +#: lib/logitech_receiver/hidpp20.py:1129 +msgid "Left Side" +msgstr "მარცხენა მხარე" + +#: lib/logitech_receiver/hidpp20.py:1130 +msgid "Right Side" +msgstr "მარჯვენა მხარე" + +#: lib/logitech_receiver/hidpp20.py:1131 +msgid "Combined" +msgstr "კომბინირებული" + +#: lib/logitech_receiver/hidpp20.py:1132 +msgid "Primary 1" +msgstr "ძირითადი 1" + +#: lib/logitech_receiver/hidpp20.py:1133 +msgid "Primary 2" +msgstr "ძირითადი 2" + +#: lib/logitech_receiver/hidpp20.py:1134 +msgid "Primary 3" +msgstr "ძირითადი 3" + +#: lib/logitech_receiver/hidpp20.py:1135 +msgid "Primary 4" +msgstr "ძირითადი 4" + +#: lib/logitech_receiver/hidpp20.py:1136 +msgid "Primary 5" +msgstr "ძირითადი 5" + +#: lib/logitech_receiver/hidpp20.py:1137 +msgid "Primary 6" +msgstr "ძირითადი 6" + +#: lib/logitech_receiver/i18n.py:28 +msgid "empty" +msgstr "ცარიელია" + +#: lib/logitech_receiver/i18n.py:29 +msgid "critical" +msgstr "კრიტიკული" + +#: lib/logitech_receiver/i18n.py:30 +msgid "low" +msgstr "დაბალი" + +#: lib/logitech_receiver/i18n.py:31 +msgid "average" +msgstr "საშუალო" + +#: lib/logitech_receiver/i18n.py:32 +msgid "good" +msgstr "კარგი" + +#: lib/logitech_receiver/i18n.py:33 +msgid "full" +msgstr "მთლიანი" + +#: lib/logitech_receiver/i18n.py:35 +msgid "discharging" +msgstr "იცლება" + +#: lib/logitech_receiver/i18n.py:36 +msgid "recharging" +msgstr "თავიდან დატენვა" + +#: lib/logitech_receiver/i18n.py:37 lib/solaar/ui/window.py:700 +msgid "charging" +msgstr "იტენება" + +#: lib/logitech_receiver/i18n.py:38 +msgid "not charging" +msgstr "არ იტენება" + +#: lib/logitech_receiver/i18n.py:39 +msgid "almost full" +msgstr "თითქმის სავსეა" + +#: lib/logitech_receiver/i18n.py:40 +msgid "charged" +msgstr "დამუხტული" + +#: lib/logitech_receiver/i18n.py:41 +msgid "slow recharge" +msgstr "ნელი თავიდან დატენვა" + +#: lib/logitech_receiver/i18n.py:42 +msgid "invalid battery" +msgstr "არასწორი ელემენტი" + +#: lib/logitech_receiver/i18n.py:43 +msgid "thermal error" +msgstr "თერმალის შეცდომა" + +#: lib/logitech_receiver/i18n.py:44 +msgid "error" +msgstr "შეცდომა" + +#: lib/logitech_receiver/i18n.py:45 +msgid "standard" +msgstr "სტანდარტული" + +#: lib/logitech_receiver/i18n.py:46 +msgid "fast" +msgstr "სწრაფი" + +#: lib/logitech_receiver/i18n.py:47 +msgid "slow" +msgstr "ნელი" + +#: lib/logitech_receiver/i18n.py:49 +msgid "device timeout" +msgstr "მოწყობილობის მოლოდინის დრო ამოიწურა" + +#: lib/logitech_receiver/i18n.py:50 +msgid "device not supported" +msgstr "მოწყობილობა მხარდაჭერილი არაა" + +#: lib/logitech_receiver/i18n.py:51 +msgid "too many devices" +msgstr "მეტისმეტად ბევრი მოწყობილობა" + +#: lib/logitech_receiver/i18n.py:52 +msgid "sequence timeout" +msgstr "მიმდევრობის მოლოდინის ვადა ამოიწურა" + +#: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:555 +msgid "Firmware" +msgstr "მიკროკოდი" + +#: lib/logitech_receiver/i18n.py:55 +msgid "Bootloader" +msgstr "ჩამტვირთავი" + +#: lib/logitech_receiver/i18n.py:56 +msgid "Hardware" +msgstr "აპარატურა" + +#: lib/logitech_receiver/i18n.py:57 +msgid "Other" +msgstr "სხვა" + +#: lib/logitech_receiver/i18n.py:59 +msgid "Left Button" +msgstr "მარცხენა ღილაკი" + +#: lib/logitech_receiver/i18n.py:60 +msgid "Right Button" +msgstr "მარჯვენა ღილაკი" + +#: lib/logitech_receiver/i18n.py:61 +msgid "Middle Button" +msgstr "შუა ღილაკი" + +#: lib/logitech_receiver/i18n.py:62 +msgid "Back Button" +msgstr "ღილაკი უკან" + +#: lib/logitech_receiver/i18n.py:63 +msgid "Forward Button" +msgstr "ღილაკი წინ" + +#: lib/logitech_receiver/i18n.py:64 +msgid "Mouse Gesture Button" +msgstr "თაგუნას ჟესტის ღილაკი" + +#: lib/logitech_receiver/i18n.py:65 +msgid "Smart Shift" +msgstr "ჭკვიანი გადასვლა" + +#: lib/logitech_receiver/i18n.py:66 +msgid "DPI Switch" +msgstr "DPI გადამრთველი" + +#: lib/logitech_receiver/i18n.py:67 +msgid "Left Tilt" +msgstr "მარცხნივ დახრა" + +#: lib/logitech_receiver/i18n.py:68 +msgid "Right Tilt" +msgstr "მარჯვნივ დახრა" + +#: lib/logitech_receiver/i18n.py:69 +msgid "Left Click" +msgstr "მარცხენა-წკაპი" + +#: lib/logitech_receiver/i18n.py:70 +msgid "Right Click" +msgstr "მარჯვენა-წკაპი" + +#: lib/logitech_receiver/i18n.py:71 +msgid "Mouse Middle Button" +msgstr "თაგუნას შუა ღილაკი" + +#: lib/logitech_receiver/i18n.py:72 +msgid "Mouse Back Button" +msgstr "თაგუნას ღილაკი 'უკან'" + +#: lib/logitech_receiver/i18n.py:73 +msgid "Mouse Forward Button" +msgstr "თაგუნას ღილაკი 'წუნ'" + +#: lib/logitech_receiver/i18n.py:74 +msgid "Gesture Button Navigation" +msgstr "ნავიგაცია ჟესტის ღილაკით" + +#: lib/logitech_receiver/i18n.py:75 +msgid "Mouse Scroll Left Button" +msgstr "თაგუნას მარცხნივ გადახვევის ღილაკი" + +#: lib/logitech_receiver/i18n.py:76 +msgid "Mouse Scroll Right Button" +msgstr "თაგუნას მარჯვნივ გადახვევის ღილაკი" + +#: lib/logitech_receiver/i18n.py:78 +msgid "pressed" +msgstr "დაწოლილი" + +#: lib/logitech_receiver/i18n.py:79 +msgid "released" +msgstr "აშვებულია" + +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 +msgid "connected" +msgstr "დაკავშირებულია" + +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 +msgid "disconnected" +msgstr "გათიშულია" + +#: lib/logitech_receiver/notifications.py:182 +msgid "unpaired" +msgstr "დაუწყვილებელია" + +#: lib/logitech_receiver/notifications.py:231 +msgid "powered on" +msgstr "ჩართულია" + +#: lib/logitech_receiver/notifications.py:283 +msgid "ADC measurement notification" +msgstr "ADC გაზომვის გაფრთხილება" + +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:483 +msgid "pairing lock is closed" +msgstr "დაწყვილება დაბლოკილია" + +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:483 +msgid "pairing lock is open" +msgstr "დაწყვილება ჩართულია" + +#: lib/logitech_receiver/notifications.py:446 +msgid "discovery lock is closed" +msgstr "აღმოჩენა დაბლოკილია" + +#: lib/logitech_receiver/notifications.py:446 +msgid "discovery lock is open" +msgstr "აღმოჩენა ჩართულია" + +#: lib/logitech_receiver/receiver.py:437 +msgid "No paired devices." +msgstr "შეწყვილებული მოწყობილობების გარეშე." + +#: lib/logitech_receiver/receiver.py:439 lib/solaar/ui/window.py:606 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s დაწყვილებული მოწყობილობა." +msgstr[1] "%(count)s დაწყვილებული მოწყობილობა." + +#: lib/logitech_receiver/settings.py:602 +msgid "register" +msgstr "რეგისტრაცია" + +#: lib/logitech_receiver/settings.py:616 lib/logitech_receiver/settings.py:654 +msgid "feature" +msgstr "თვისება" + +#: lib/logitech_receiver/settings_templates.py:138 +msgid "Swap Fx function" +msgstr "Fx-ის ფუნქციის გაცვლა" + +#: lib/logitech_receiver/settings_templates.py:141 +msgid "" +"When set, the F1..F12 keys will activate their special function,\n" +"and you must hold the FN key to activate their standard function." +msgstr "" +"როცა დაყენებულია, ღილაკები F1..F12 თავიანთ სპეციალურ ფუნქციას გაააქტიურებენ\n" +"და თქვენ უნდა გეჭიროთ FN ღილაკზე, რომ მათი სტანდარტული ფუნქცია გაააქტიუროთ." + +#: lib/logitech_receiver/settings_templates.py:146 +msgid "" +"When unset, the F1..F12 keys will activate their standard function,\n" +"and you must hold the FN key to activate their special function." +msgstr "" +"როცა დაყენებულია, ღილაკები F1..F12 თავიანთ სტანდარტული ფუნქციას " +"გაააქტიურებენ\n" +"და თქვენ უნდა გეჭიროთ FN ღილაკზე, რომ მათი სპეციალურ ფუნქცია გაააქტიუროთ." + +#: lib/logitech_receiver/settings_templates.py:154 +msgid "Hand Detection" +msgstr "ხელის ამოცნობა" + +#: lib/logitech_receiver/settings_templates.py:155 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "ილუმინაციის ჩართვა, როცა ხელებს კლავიატურაზე გააჩერებთ." + +#: lib/logitech_receiver/settings_templates.py:162 +msgid "Scroll Wheel Smooth Scrolling" +msgstr "გლუვი გადახვევა თაგუნას ბორბლით" + +#: lib/logitech_receiver/settings_templates.py:163 +#: lib/logitech_receiver/settings_templates.py:410 +#: lib/logitech_receiver/settings_templates.py:439 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "მაღალი მგრძნობელობის რეჟიმი ბორბლით ვერტიკალური გადახვევისთვის." + +#: lib/logitech_receiver/settings_templates.py:170 +msgid "Side Scrolling" +msgstr "გვერდზე გადახვევა" + +#: lib/logitech_receiver/settings_templates.py:172 +msgid "" +"When disabled, pushing the wheel sideways sends custom button events\n" +"instead of the standard side-scrolling events." +msgstr "" +"როცა გამორთულია, ბორბლის გვერდზე დაჭერა მომხმარებლის ღილაკის მოვლენებს\n" +"აგზავნის სტანდარტული გვერდზე გადახვევის მოვლენების მაგიერ." + +#: lib/logitech_receiver/settings_templates.py:182 +msgid "Sensitivity (DPI - older mice)" +msgstr "მგრძნობელობა (DPI - ძველი თაგუნები)" + +#: lib/logitech_receiver/settings_templates.py:183 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +msgid "Mouse movement sensitivity" +msgstr "თაგუნას მოძრაობის მგრძნობელობა" + +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Backlight Timed" +msgstr "შენათების დრო" + +#: lib/logitech_receiver/settings_templates.py:257 +#: lib/logitech_receiver/settings_templates.py:397 +msgid "Set illumination time for keyboard." +msgstr "განათების დროის დაყენება კლავიატურისთვის." + +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Backlight" +msgstr "შენათება" + +#: lib/logitech_receiver/settings_templates.py:269 +msgid "" +"Illumination level on keyboard. Changes made are only applied in Manual " +"mode." +msgstr "" +"განათების დონე კლავიატურაზე. ცვლილებები ძალაში, მხოლოდ, 'ხელით' რეჟიმში " +"შედის." + +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Automatic" +msgstr "ავტომატური" + +#: lib/logitech_receiver/settings_templates.py:303 +msgid "Manual" +msgstr "ხელით" + +#: lib/logitech_receiver/settings_templates.py:305 +msgid "Enabled" +msgstr "ჩართულია" + +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Backlight Level" +msgstr "შენათების დონე" + +#: lib/logitech_receiver/settings_templates.py:312 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "განათების დონე კლავიატურაზე რეჟიმში 'ხელით'." + +#: lib/logitech_receiver/settings_templates.py:369 +msgid "Backlight Delay Hands Out" +msgstr "შენათების დაყოვნება ხელების გაწევისას" + +#: lib/logitech_receiver/settings_templates.py:370 +msgid "" +"Delay in seconds until backlight fades out with hands away from keyboard." +msgstr "" +"დაყოვნება წამებში, სანამ მოხდება შენათების მინავლება, როცა ხელებს " +"კლავიატურიდან შორს წაიღებთ." + +#: lib/logitech_receiver/settings_templates.py:378 +msgid "Backlight Delay Hands In" +msgstr "შენათების დაყოვნება ხელების მიახლოებისას" + +#: lib/logitech_receiver/settings_templates.py:379 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "" +"დაყოვნება წამებში, სანამ მოხდება შენათების გამოჩენა, როცა ხელებს " +"კლავიატურასთან მიიტანთ." + +#: lib/logitech_receiver/settings_templates.py:387 +msgid "Backlight Delay Powered" +msgstr "შენათების ჩართვის დაყოვნება" + +#: lib/logitech_receiver/settings_templates.py:388 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "დაყოვნება წამებში, სანამ მოხდება შენათების მინავლება გარე კვებით." + +#: lib/logitech_receiver/settings_templates.py:396 +msgid "Backlight (Seconds)" +msgstr "შენათება (წმ)" + +#: lib/logitech_receiver/settings_templates.py:408 +msgid "Scroll Wheel High Resolution" +msgstr "გადახვევის ბორბლის მაღალი გარჩევადობა" + +#: lib/logitech_receiver/settings_templates.py:412 +#: lib/logitech_receiver/settings_templates.py:441 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "დააყენეთ დაიგნორებაზე, თუ გადახვევა არანორმალურად სწრაფი, ან ნელია" + +#: lib/logitech_receiver/settings_templates.py:419 +#: lib/logitech_receiver/settings_templates.py:450 +msgid "Scroll Wheel Diversion" +msgstr "გადახვევის ბორბლის ქცევა" + +#: lib/logitech_receiver/settings_templates.py:421 +msgid "" +"Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " +"Solaar rules but are otherwise ignored)." +msgstr "" +"ბორბლისთვის LOWRES_WHEEL HID++ გაფრთხილების გაგზავნის იძულება (რომელიც " +"გაუშვებს Solaar-ის წესებს, მაგრამ სხვაგვარად ყურადღებას არაფერი აქცევს)." + +#: lib/logitech_receiver/settings_templates.py:428 +msgid "Scroll Wheel Direction" +msgstr "გადახვევის ბორბლის მიმართულება" + +#: lib/logitech_receiver/settings_templates.py:429 +msgid "Invert direction for vertical scroll with wheel." +msgstr "ბორბლით ვერტიკალური გადახვევის მიმართულების ინვერსია." + +#: lib/logitech_receiver/settings_templates.py:437 +msgid "Scroll Wheel Resolution" +msgstr "გადახვევის ბორბლის გარჩევადობა" + +#: lib/logitech_receiver/settings_templates.py:452 +msgid "" +"Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"ბორბლისთვის HIRES_WHEEL HID++ გაფრთხილების გაგზავნის იძულება (რომელიც " +"გაუშვებს Solaar-ის წესებს, მაგრამ სხვაგვარად ყურადღებას არაფერი აქცევს)." + +#: lib/logitech_receiver/settings_templates.py:461 +msgid "Sensitivity (Pointer Speed)" +msgstr "მგრძნობელობა (მაჩვენებლის სიჩქარე)" + +#: lib/logitech_receiver/settings_templates.py:462 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "სიჩქარის მამრავლი თაგუნასთვის (256 ნორმალური მამრავლია)." + +#: lib/logitech_receiver/settings_templates.py:472 +msgid "Thumb Wheel Diversion" +msgstr "ცერა თითის ბორბლის ქცევა" + +#: lib/logitech_receiver/settings_templates.py:474 +msgid "" +"Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"ბორბლისთვის THUMB_WHEEL HID++ გაფრთხილების გაგზავნის იძულება (რომელიც " +"გაუშვებს Solaar-ის წესებს, მაგრამ სხვაგვარად ყურადღებას არაფერი აქცევს)." + +#: lib/logitech_receiver/settings_templates.py:483 +msgid "Thumb Wheel Direction" +msgstr "ცერა თითის ბორბლის მიმართულება" + +#: lib/logitech_receiver/settings_templates.py:484 +msgid "Invert thumb wheel scroll direction." +msgstr "ცერა თითის ბორბლით გადახვევის მიმართულების ინვერსია." + +#: lib/logitech_receiver/settings_templates.py:504 +msgid "Onboard Profiles" +msgstr "ჩაშენებული პროფილები" + +#: lib/logitech_receiver/settings_templates.py:505 +msgid "" +"Enable an onboard profile, which controls report rate, sensitivity, and " +"button actions" +msgstr "" +"ჩაშენებული პროფილის ჩართვა, რომელიც მართავს ანგარიშების სიხშირეს, " +"მგრძნობელობას და ღილაკის ქმედებებს" + +#: lib/logitech_receiver/settings_templates.py:549 +#: lib/logitech_receiver/settings_templates.py:582 +msgid "Report Rate" +msgstr "ანგარიშის სიხშირე" + +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +msgid "Frequency of device movement reports" +msgstr "მოწყობილობის მოძრაობის ანგარიშების სიხშირე" + +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1421 +#: lib/logitech_receiver/settings_templates.py:1452 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" +"იმისათვის, რომ ეფექტური იყოს, შეიძლება, ჩაშენებული პროფილების გამორთვა " +"დასჭირდეს." + +#: lib/logitech_receiver/settings_templates.py:612 +msgid "Divert crown events" +msgstr "ვენიერის ქცევა" + +#: lib/logitech_receiver/settings_templates.py:613 +msgid "" +"Make crown send CROWN HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"ვერნიერისთვის CROWN WHEEL HID++ გაფრთხილების გაგზავნის იძულება (რომელიც " +"გაუშვებს Solaar-ის წესებს, მაგრამ სხვაგვარად ყურადღებას არაფერი აქცევს)." + +#: lib/logitech_receiver/settings_templates.py:621 +msgid "Crown smooth scroll" +msgstr "ვენიერის გლუვი გადახვევა" + +#: lib/logitech_receiver/settings_templates.py:622 +msgid "Set crown smooth scroll" +msgstr "ვენიერის გლუვი გადახვევის დაყენება" + +#: lib/logitech_receiver/settings_templates.py:630 +msgid "Divert G and M Keys" +msgstr "G და M ღილაკების გადამისამართება" + +#: lib/logitech_receiver/settings_templates.py:631 +msgid "" +"Make G and M keys send HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"G და M ღილაკებისთვის WHEEL HID++ გაფრთხილების გაგზავნის იძულება (რომელიც " +"გაუშვებს Solaar-ის წესებს, მაგრამ სხვაგვარად ყურადღებას არაფერი აქცევს)." + +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Scroll Wheel Ratcheted" +msgstr "ხრუტუნა მექანიზმით გადახვევის ბორბალი" + +#: lib/logitech_receiver/settings_templates.py:646 +msgid "" +"Switch the mouse wheel between speed-controlled ratcheting and always " +"freespin." +msgstr "" +"თაგუნას ბორბლის გადართვა სიჩქარით მართულ ხრუტუნა მექანიზმსა და ყოველთვის " +"თავისუფალ ტრიალს შორის." + +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Freespinning" +msgstr "თავისუფალი ბრუნვა" + +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Ratcheted" +msgstr "ბრუნვა ხრუტუნა მექანიზმით" + +#: lib/logitech_receiver/settings_templates.py:655 +msgid "Scroll Wheel Ratchet Speed" +msgstr "თაგუნას ბორბლის ტრიალის სიჩქარე" + +#: lib/logitech_receiver/settings_templates.py:657 +msgid "" +"Use the mouse wheel speed to switch between ratcheted and freespinning.\n" +"The mouse wheel is always ratcheted at 50." +msgstr "" +"თაგუნას ბორბლის სიჩქარის გამოყენება ხრუტუნა მექანიზმსა და თავისუფლად ტრიალს " +"შორის გადასართავად.\n" +"თაგუნას ბორბლის ხრუტუნა მექანიზმის სიჩქარე ყოველთვის 50-ს უდრის." + +#: lib/logitech_receiver/settings_templates.py:707 +msgid "Scroll Wheel Ratchet Torque" +msgstr "ხრუტუნა ბორბლით გადახვევის მოჭიდება" + +#: lib/logitech_receiver/settings_templates.py:708 +msgid "Change the torque needed to overcome the ratchet." +msgstr "ხრუტუნა ბორბლის გადასაფარად მოჭიდების შეცვლაა საწირო." + +#: lib/logitech_receiver/settings_templates.py:743 +msgid "Key/Button Actions" +msgstr "ღილაკის ქმედებები" + +#: lib/logitech_receiver/settings_templates.py:745 +msgid "Change the action for the key or button." +msgstr "ქმედების შეცვლა ღილაკისთვის." + +#: lib/logitech_receiver/settings_templates.py:747 +msgid "Overridden by diversion." +msgstr "გადაფარულია გადამისამარტებით." + +#: lib/logitech_receiver/settings_templates.py:749 +msgid "" +"Changing important actions (such as for the left mouse button) can result in " +"an unusable system." +msgstr "" +"მნიშვნელოვანი ქმედებების შეცვლამ (მაგ: მარცხენა თაგუნას ღილაკისთვის), " +"შეიძლება, სისტემის არასტაბილურობამდე მიგიყვანოთ." + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Key/Button Diversion" +msgstr "ღილაკის გადამისამართება" + +#: lib/logitech_receiver/settings_templates.py:925 +msgid "" +"Make the key or button send HID++ notifications (Diverted) or initiate Mouse " +"Gestures or Sliding DPI" +msgstr "" +"ღილაკისთვის იძულება, HD++ გაფრთხილებები გააგზავნოს (გადამისამართებული) ან " +"მოახდინოს თაგუნას ჟესტების ინიციაცია, ან სრიალა DPI-ის" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Diverted" +msgstr "გადამისამართებულია" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +msgid "Mouse Gestures" +msgstr "თაგუნას ჟესტები" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Regular" +msgstr "ჩვეულებრივი" + +#: lib/logitech_receiver/settings_templates.py:928 +msgid "Sliding DPI" +msgstr "მცურავი DPI" + +#: lib/logitech_receiver/settings_templates.py:1018 +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Sensitivity (DPI)" +msgstr "მგრძნობელობა (DPI)" + +#: lib/logitech_receiver/settings_templates.py:1123 +msgid "Sensitivity Switching" +msgstr "მგრძნობელობის გადართვა" + +#: lib/logitech_receiver/settings_templates.py:1125 +msgid "" +"Switch the current sensitivity and the remembered sensitivity when the key " +"or button is pressed.\n" +"If there is no remembered sensitivity, just remember the current sensitivity" +msgstr "" +"მიმდინარე მგრძნობელობასა და დამახსოვრებულ მგრძნობელობას შორის გადართვა, როცა " +"ღილაკს დააწვებით.\n" +"თუ დამახსოვრებული მგრძნობელობა არ არსებობს, მოხდება მიმდინარე მგრძნობელობის " +"დამახსოვრება" + +#: lib/logitech_receiver/settings_templates.py:1129 +msgid "Off" +msgstr "გამორთ" + +#: lib/logitech_receiver/settings_templates.py:1160 +msgid "Disable keys" +msgstr "ღილაკების გათიშვა" + +#: lib/logitech_receiver/settings_templates.py:1161 +msgid "Disable specific keyboard keys." +msgstr "მითითებული კლავიატურის ღილაკების გამორთვა." + +#: lib/logitech_receiver/settings_templates.py:1164 +#, python-format +msgid "Disables the %s key." +msgstr "გამორთავს ღილაკს %s." + +#: lib/logitech_receiver/settings_templates.py:1177 +#: lib/logitech_receiver/settings_templates.py:1234 +msgid "Set OS" +msgstr "ოპერაციული სისტემა" + +#: lib/logitech_receiver/settings_templates.py:1178 +#: lib/logitech_receiver/settings_templates.py:1235 +msgid "Change keys to match OS." +msgstr "ღილაკების შეცვლა ოს-თან შესაბამისად." + +#: lib/logitech_receiver/settings_templates.py:1247 +msgid "Change Host" +msgstr "ჰოსტის შეცვლა" + +#: lib/logitech_receiver/settings_templates.py:1248 +msgid "Switch connection to a different host" +msgstr "კავშირის გადართვა სხვა ჰოსტზე" + +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Performs a left click." +msgstr "შეასრულებს მარცხენა წკაპს." + +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Single tap" +msgstr "ერთი ტყაპი" + +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Performs a right click." +msgstr "შეასრულებს მარჯვენა წკაპს." + +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Single tap with two fingers" +msgstr "ერთი ტყაპი ორი თითით" + +#: lib/logitech_receiver/settings_templates.py:1274 +msgid "Single tap with three fingers" +msgstr "ერთი ტყაპი სამი თითით" + +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Double tap" +msgstr "ორმაგი ტყაპი" + +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Performs a double click." +msgstr "შეასრულებს ორმაგ წკაპს." + +#: lib/logitech_receiver/settings_templates.py:1279 +msgid "Double tap with two fingers" +msgstr "ორმაგი ტყაპი ორი თითით" + +#: lib/logitech_receiver/settings_templates.py:1280 +msgid "Double tap with three fingers" +msgstr "ორმაგი ტყაპი სამი თითით" + +#: lib/logitech_receiver/settings_templates.py:1283 +msgid "Drags items by dragging the finger after double tapping." +msgstr "ელემენტების გადათრევა თითის გაწევით ორი ტყაპის შემდეგ." + +#: lib/logitech_receiver/settings_templates.py:1283 +msgid "Tap and drag" +msgstr "დატყაპუნება და გადათრევა" + +#: lib/logitech_receiver/settings_templates.py:1285 +msgid "Tap and drag with two fingers" +msgstr "დატყაპუნება და გადათრევა ორი თითით" + +#: lib/logitech_receiver/settings_templates.py:1286 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "გადაათრიეთ ელემენტები თითების გადათრევით ორმაგი დატყაპუნების შემდეგ." + +#: lib/logitech_receiver/settings_templates.py:1288 +msgid "Tap and drag with three fingers" +msgstr "დატყაპუნება და გადათრევა სამი თითით" + +#: lib/logitech_receiver/settings_templates.py:1291 +msgid "Suppress tap and edge gestures" +msgstr "ტყაპუნის და წიბოს ჟესტების ჩახშობა" + +#: lib/logitech_receiver/settings_templates.py:1292 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "" +"გამორთავს დატყაპუნების და წიბოს ჟესტებს (იგივეა, რაც Fn+მარცხენაწკაპი)." + +#: lib/logitech_receiver/settings_templates.py:1294 +msgid "Scroll with one finger" +msgstr "გადახვევა ერთი თითით" + +#: lib/logitech_receiver/settings_templates.py:1294 +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 +msgid "Scrolls." +msgstr "ვერტიკალური გადახვევა." + +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 +msgid "Scroll with two fingers" +msgstr "გადახვევა ორი თითით" + +#: lib/logitech_receiver/settings_templates.py:1296 +msgid "Scroll horizontally with two fingers" +msgstr "ჰორიზონტალური გადახვევა ორი თითით" + +#: lib/logitech_receiver/settings_templates.py:1296 +msgid "Scrolls horizontally." +msgstr "ჰორიზონტალური გადახვევა." + +#: lib/logitech_receiver/settings_templates.py:1297 +msgid "Scroll vertically with two fingers" +msgstr "ვერტიკალური გადახვევა ორი თითით" + +#: lib/logitech_receiver/settings_templates.py:1297 +msgid "Scrolls vertically." +msgstr "ვერტიკალური გადახვევა." + +#: lib/logitech_receiver/settings_templates.py:1299 +msgid "Inverts the scrolling direction." +msgstr "გადახვევის მიმართულების ინვერსია." + +#: lib/logitech_receiver/settings_templates.py:1299 +msgid "Natural scrolling" +msgstr "ჩვეულებრივი გადახვევა" + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Enables the thumbwheel." +msgstr "ჩართავს ცერა თითის ბორბალს." + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Thumbwheel" +msgstr "ცერა თითის ბორბალი" + +#: lib/logitech_receiver/settings_templates.py:1311 +#: lib/logitech_receiver/settings_templates.py:1315 +msgid "Swipe from the top edge" +msgstr "გასმა ზედა წიბოდან" + +#: lib/logitech_receiver/settings_templates.py:1312 +msgid "Swipe from the left edge" +msgstr "გასმა მარცხენა წიბოდან" + +#: lib/logitech_receiver/settings_templates.py:1313 +msgid "Swipe from the right edge" +msgstr "გასმა მარჯვენა წიბოდან" + +#: lib/logitech_receiver/settings_templates.py:1314 +msgid "Swipe from the bottom edge" +msgstr "გასმა ქვედა წიბოდან" + +#: lib/logitech_receiver/settings_templates.py:1316 +msgid "Swipe two fingers from the left edge" +msgstr "ორი თითის გასმა მარცხენა წიბოდან" + +#: lib/logitech_receiver/settings_templates.py:1317 +msgid "Swipe two fingers from the right edge" +msgstr "ორი თითის გასმა მარჯვენა წიბოდან" + +#: lib/logitech_receiver/settings_templates.py:1318 +msgid "Swipe two fingers from the bottom edge" +msgstr "ორი თითის გასმა ქვედა წიბოდან" + +#: lib/logitech_receiver/settings_templates.py:1319 +msgid "Swipe two fingers from the top edge" +msgstr "ორი თითის გასმა ზედა წიბოდან" + +#: lib/logitech_receiver/settings_templates.py:1320 +#: lib/logitech_receiver/settings_templates.py:1324 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "ჩქმეტა დასაპატარავებლად. გაწევა გასადიდებლად." + +#: lib/logitech_receiver/settings_templates.py:1320 +msgid "Zoom with two fingers." +msgstr "გადიდება ორი თითით." + +#: lib/logitech_receiver/settings_templates.py:1321 +msgid "Pinch to zoom out." +msgstr "ჩქმეტა დასაპატარავებლად." + +#: lib/logitech_receiver/settings_templates.py:1322 +msgid "Spread to zoom in." +msgstr "გადაშლა გასადიდებლად." + +#: lib/logitech_receiver/settings_templates.py:1323 +msgid "Zoom with three fingers." +msgstr "გადიდება სამი თითით." + +#: lib/logitech_receiver/settings_templates.py:1324 +msgid "Zoom with two fingers" +msgstr "გადიდება ორი თითით" + +#: lib/logitech_receiver/settings_templates.py:1342 +msgid "Pixel zone" +msgstr "პიქსელების ზონა" + +#: lib/logitech_receiver/settings_templates.py:1343 +msgid "Ratio zone" +msgstr "ფარდობის დონე" + +#: lib/logitech_receiver/settings_templates.py:1344 +msgid "Scale factor" +msgstr "გადიდების კოეფიციენტი" + +#: lib/logitech_receiver/settings_templates.py:1344 +msgid "Sets the cursor speed." +msgstr "აყენებს კურსორის სიჩქარეს." + +#: lib/logitech_receiver/settings_templates.py:1348 +msgid "Left" +msgstr "მარცხენა" + +#: lib/logitech_receiver/settings_templates.py:1348 +msgid "Left-most coordinate." +msgstr "ყველაზე მარცხენა კოორდინატი." + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom" +msgstr "ქვემოთ" + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom coordinate." +msgstr "ქვედა კოორდინატი." + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width" +msgstr "სიგანე" + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width." +msgstr "სიგანე." + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height" +msgstr "სიმაღლე" + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height." +msgstr "სიმაღლე." + +#: lib/logitech_receiver/settings_templates.py:1352 +msgid "Cursor speed." +msgstr "კურსორის სიჩქარე." + +#: lib/logitech_receiver/settings_templates.py:1352 +msgid "Scale" +msgstr "მასშტაბი" + +#: lib/logitech_receiver/settings_templates.py:1358 +msgid "Gestures" +msgstr "ჟესტები" + +#: lib/logitech_receiver/settings_templates.py:1359 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "თაგუნას/თაჩპედის ქცევის შეცვლა." + +#: lib/logitech_receiver/settings_templates.py:1375 +msgid "Gestures Diversion" +msgstr "ჟესტების ქცევა" + +#: lib/logitech_receiver/settings_templates.py:1376 +msgid "Divert mouse/touchpad gestures." +msgstr "თაჩპედის/თაგუნას ჟესტების მორგება." + +#: lib/logitech_receiver/settings_templates.py:1392 +msgid "Gesture params" +msgstr "ჟესტის პარამეტრები" + +#: lib/logitech_receiver/settings_templates.py:1393 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "თაგუნას/თაჩპედის რიცხვითი პარამეტრების შეცვლა." + +#: lib/logitech_receiver/settings_templates.py:1417 +msgid "M-Key LEDs" +msgstr "M-ღილაკის LED-ები" + +#: lib/logitech_receiver/settings_templates.py:1419 +msgid "Control the M-Key LEDs." +msgstr "M-ღილაკის LED-ების მართვა." + +#: lib/logitech_receiver/settings_templates.py:1423 +#: lib/logitech_receiver/settings_templates.py:1454 +msgid "May need G Keys diverted to be effective." +msgstr "" +"შეიძლება, იმისათვის, რომ ეფექტური იყოს, G ღილაკების გადამისამართება " +"დაგჭირდეთ." + +#: lib/logitech_receiver/settings_templates.py:1429 +#, python-format +msgid "Lights up the %s key." +msgstr "აანთებს ღილაკს %s." + +#: lib/logitech_receiver/settings_templates.py:1448 +msgid "MR-Key LED" +msgstr "MR ღილაკის LED" + +#: lib/logitech_receiver/settings_templates.py:1450 +msgid "Control the MR-Key LED." +msgstr "MR ღილაკის LED-ის მართვა." + +#: lib/logitech_receiver/settings_templates.py:1471 +msgid "Persistent Key/Button Mapping" +msgstr "მუდმივი ღილაკის ასახვა" + +#: lib/logitech_receiver/settings_templates.py:1473 +msgid "Permanently change the mapping for the key or button." +msgstr "ღილაკის ასახვის მუდმივად შეცვლა." + +#: lib/logitech_receiver/settings_templates.py:1475 +msgid "" +"Changing important keys or buttons (such as for the left mouse button) can " +"result in an unusable system." +msgstr "" +"მნიშვნელოვანი ღილაკების შეცვლამ (მაგ: მარცხენა თაგუნას ღილაკისთვის), " +"შეიძლება, სისტემის არასტაბილურობამდე მიგიყვანოთ." + +#: lib/logitech_receiver/settings_templates.py:1532 +msgid "Sidetone" +msgstr "თვითმოსმენა" + +#: lib/logitech_receiver/settings_templates.py:1533 +msgid "Set sidetone level." +msgstr "თვითმოსმენის დონის დაყენება." + +#: lib/logitech_receiver/settings_templates.py:1542 +msgid "Equalizer" +msgstr "ეკვალაიზერი" + +#: lib/logitech_receiver/settings_templates.py:1543 +msgid "Set equalizer levels." +msgstr "ეკვალაიზერის დონეების დაყენება." + +#: lib/logitech_receiver/settings_templates.py:1565 +msgid "Hz" +msgstr "ჰც" + +#: lib/logitech_receiver/settings_templates.py:1571 +msgid "Power Management" +msgstr "კვების მართვა" + +#: lib/logitech_receiver/settings_templates.py:1572 +msgid "Power off in minutes (0 for never)." +msgstr "კვების გამორთვა მითითებულ წუთში (0 ნიშნავს 'არასდროს')." + +#: lib/logitech_receiver/settings_templates.py:1584 +msgid "Brightness Control" +msgstr "სიკაშკაშის კონტროლი" + +#: lib/logitech_receiver/settings_templates.py:1585 +msgid "Control overall brightness" +msgstr "საერთო სიკაშკაშის მართვა" + +#: lib/logitech_receiver/settings_templates.py:1628 +#: lib/logitech_receiver/settings_templates.py:1681 +msgid "LED Control" +msgstr "LED-ების მართვა" + +#: lib/logitech_receiver/settings_templates.py:1629 +#: lib/logitech_receiver/settings_templates.py:1682 +msgid "Switch control of LED zones between device and Solaar" +msgstr "LED-ის ზონების კონტროლის გადართვა მოწყობილობასა და Solaar-ს შორის" + +#: lib/logitech_receiver/settings_templates.py:1644 +#: lib/logitech_receiver/settings_templates.py:1692 +msgid "LED Zone Effects" +msgstr "LED-ის ზონის ეფექტები" + +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "LED Control needs to be set to Solaar to be effective." +msgstr "იმისათვის, რომ ეფექტური იყოს, LED-ის მართვა Solaar-ზე უნდა დააყენოთ." + +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "Set effect for LED Zone" +msgstr "ეფექტის დაყენება LED ზონისთვის" + +#: lib/logitech_receiver/settings_templates.py:1647 +msgid "Color" +msgstr "ფერი" + +#: lib/logitech_receiver/settings_templates.py:1648 +msgid "Speed" +msgstr "სიჩქარე" + +#: lib/logitech_receiver/settings_templates.py:1649 +msgid "Period" +msgstr "პერიოდი" + +#: lib/logitech_receiver/settings_templates.py:1650 +msgid "Intensity" +msgstr "ინტენსივობა" + +#: lib/logitech_receiver/settings_templates.py:1651 +msgid "Ramp" +msgstr "პანდუსი" + +#: lib/logitech_receiver/settings_templates.py:1666 +msgid "LEDs" +msgstr "LED-ები" + +#: lib/logitech_receiver/settings_templates.py:1703 +msgid "Per-key Lighting" +msgstr "ინდივიდუალური განათება ღილაკებისთვის" + +#: lib/logitech_receiver/settings_templates.py:1704 +msgid "Control per-key lighting." +msgstr "ინდივიდუალური განათების კონტროლი ღილაკებისთვის." + +#: lib/logitech_receiver/settings_templates.py:1786 +msgid "Force Sensing Buttons" +msgstr "დაჭერის სენსორის მქონე ღილაკები" + +#: lib/logitech_receiver/settings_templates.py:1787 +msgid "Change the force required to activate button." +msgstr "აირჩიეთ ძალა, რომელიც საჭიროა ღილაკის გასააქტიურებლად." + +#: lib/logitech_receiver/settings_templates.py:1804 +msgid "Force Sensing Button" +msgstr "დაჭერის სენსორის მქონე ღილაკი" + +#: lib/logitech_receiver/settings_templates.py:1814 +msgid "Haptic Feeback Level" +msgstr "ტაქტილური უკუკავშირის დონე" + +#: lib/logitech_receiver/settings_templates.py:1815 +msgid "Change power of haptic feedback. (Zero to turn off.)" +msgstr "ტაქტილური უკუკავშირის სიმძლავრის შეცვლა. (0 გამოსართავად.)" + +#: lib/logitech_receiver/settings_templates.py:1857 +msgid "Play Haptic Waveform" +msgstr "სიგნალის ტაქტილური ფორმის დაკვრა" + +#: lib/logitech_receiver/settings_templates.py:1858 +msgid "Tell device to play a haptic waveform." +msgstr "მოწყობილობისთვის ტაქტილური ფორმის სიგნალის ბრძანების გადაცემა." + +#: lib/solaar/ui/__init__.py:120 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" +"უკვე გაშვებულია Solaar-ის სხვა პროცესი, ასე რომ, უბრალოდ ამოკეცეთ ის ფანჯარა" + +#: lib/solaar/ui/about/model.py:36 +msgid "" +"Manages Logitech receivers,\n" +"keyboards, mice, and tablets." +msgstr "" +"მართავს Logitech-ის მიმღებებს,\n" +"კლავიატურებს, თაგუნებს და ტაბლეტებს." + +#: lib/solaar/ui/about/model.py:63 +msgid "Additional Programming" +msgstr "დამატებითი პროგრამირება" + +#: lib/solaar/ui/about/model.py:64 +msgid "GUI design" +msgstr "GUI-ის დიზაინი" + +#: lib/solaar/ui/about/model.py:66 +msgid "Testing" +msgstr "ტესტირება" + +#: lib/solaar/ui/about/model.py:74 +msgid "Logitech documentation" +msgstr "Logitech-ის დოკუმენტაცია" + +#: lib/solaar/ui/action.py:88 lib/solaar/ui/action.py:92 +#: lib/solaar/ui/window.py:208 +msgid "Unpair" +msgstr "დაწყვილების მოხსნა" + +#: lib/solaar/ui/action.py:91 lib/solaar/ui/diversion_rules.py:141 +msgid "Cancel" +msgstr "გაუქმება" + +#: lib/solaar/ui/common.py:42 +msgid "Permissions error" +msgstr "წვდომების შეცდომა" + +#: lib/solaar/ui/common.py:44 +#, python-format +msgid "" +"Found a Logitech receiver or device (%s), but did not have permission to " +"open it." +msgstr "" +"აღმოჩენილია Logitech-ის მიმღები, ან მოწყობილობა (%s), მაგრამ მისი გახსნის " +"უფლება არ მაქვს." + +#: lib/solaar/ui/common.py:46 +msgid "" +"If you've just installed Solaar, try disconnecting the receiver or device " +"and then reconnecting it." +msgstr "" +"თუ Solaar ახლახან დააყენეთ, სცადეთ, გათიშოთ მიმღები, ან მოწყობილობა და " +"შემდეგ თავიდან მიაერთეთ." + +#: lib/solaar/ui/common.py:49 +msgid "Cannot connect to device error" +msgstr "მოწყობილობასთან დაკავშირების შეცდომა" + +#: lib/solaar/ui/common.py:51 +#, python-format +msgid "" +"Found a Logitech receiver or device at %s, but encountered an error " +"connecting to it." +msgstr "" +"ვიპოვნე Logitech მიმღები ან მოწყობილობა %s-ზე, მაგრამ დაკავშირებისას " +"აღმოჩენილია შეცდომა." + +#: lib/solaar/ui/common.py:53 +msgid "" +"Try disconnecting the device and then reconnecting it or turning it off and " +"then on." +msgstr "" +"სცადეთ, გათიშოთ მოწყობილობა და თავიდან მიაერთოთ, ან გამორთეთ ის და თავიდან " +"ჩართეთ." + +#: lib/solaar/ui/common.py:56 +msgid "Unpairing failed" +msgstr "დაწყვილების გაუქმება ჩავარდა" + +#: lib/solaar/ui/common.py:58 +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "%{receiver}-დან %{device}-ის დაწყვილების გაუმების შეცდომა." + +#: lib/solaar/ui/common.py:63 +msgid "The receiver returned an error, with no further details." +msgstr "მიმღებმა დააბრუნა შეცდომა, მაგრამ დეტალების გარეშე." + +#: lib/solaar/ui/config_panel.py:245 +msgid "Complete - ENTER to change" +msgstr "დასრულდა - Enter შესაცვლელად" + +#: lib/solaar/ui/config_panel.py:245 +msgid "Incomplete" +msgstr "არასრული" + +#: lib/solaar/ui/config_panel.py:491 lib/solaar/ui/config_panel.py:543 +#, python-format +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d მნიშვნელობა" +msgstr[1] "%d მნიშვნელობა" + +#: lib/solaar/ui/config_panel.py:642 +msgid "Changes allowed" +msgstr "ცვლილებები დაშვებულია" + +#: lib/solaar/ui/config_panel.py:643 +msgid "No changes allowed" +msgstr "ცვლილებები დაშვებული არაა" + +#: lib/solaar/ui/config_panel.py:644 +msgid "Ignore this setting" +msgstr "ამ პარამეტრის გამოტოვება" + +#: lib/solaar/ui/config_panel.py:690 +msgid "Working" +msgstr "მუშაობს" + +#: lib/solaar/ui/config_panel.py:693 +msgid "Read/write operation failed." +msgstr "წაკითხვა/ჩაწერის ოპერაცია ჩავარდა." + +#: lib/solaar/ui/desktop_notifications.py:112 +msgid "unspecified reason" +msgstr "დაუზუსტებელი მიზეზი" + +#: lib/solaar/ui/diversion_rules.py:103 +msgid "Built-in rules" +msgstr "ჩაშენებული წესები" + +#: lib/solaar/ui/diversion_rules.py:103 +msgid "User-defined rules" +msgstr "მომხმარებლის აღწერილი წესები" + +#: lib/solaar/ui/diversion_rules.py:105 lib/solaar/ui/diversion_rules.py:1126 +msgid "Rule" +msgstr "წესი" + +#: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:382 +#: lib/solaar/ui/diversion_rules.py:509 +msgid "Sub-rule" +msgstr "ქვეწესი" + +#: lib/solaar/ui/diversion_rules.py:108 +msgid "[empty]" +msgstr "[ცარიელი]" + +#: lib/solaar/ui/diversion_rules.py:132 +msgid "Make changes permanent?" +msgstr "გახდეს ცვლილებები მუდმივი?" + +#: lib/solaar/ui/diversion_rules.py:137 +msgid "Yes" +msgstr "დიახ" + +#: lib/solaar/ui/diversion_rules.py:139 +msgid "No" +msgstr "არა" + +#: lib/solaar/ui/diversion_rules.py:144 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "თუ აირჩევთ 'არას', ცვლილებები დაიკარგება, როცა Solaar-ს დახურავთ." + +#: lib/solaar/ui/diversion_rules.py:273 +msgid "Paste here" +msgstr "აქ ჩასმა" + +#: lib/solaar/ui/diversion_rules.py:275 +msgid "Paste above" +msgstr "ზემოთ ჩასმა" + +#: lib/solaar/ui/diversion_rules.py:277 +msgid "Paste below" +msgstr "ქვემოთ ჩასმა" + +#: lib/solaar/ui/diversion_rules.py:283 +msgid "Paste rule here" +msgstr "წესის ჩასმა აქ" + +#: lib/solaar/ui/diversion_rules.py:285 +msgid "Paste rule above" +msgstr "წესის ჩასმა ზემოთ" + +#: lib/solaar/ui/diversion_rules.py:287 +msgid "Paste rule below" +msgstr "წესის ჩასმა ქვემოთ" + +#: lib/solaar/ui/diversion_rules.py:291 +msgid "Paste rule" +msgstr "წესის ჩასმა" + +#: lib/solaar/ui/diversion_rules.py:306 +msgid "Insert here" +msgstr "აქ ჩასმა" + +#: lib/solaar/ui/diversion_rules.py:308 +msgid "Insert above" +msgstr "ზემოთ ჩასმა" + +#: lib/solaar/ui/diversion_rules.py:310 +msgid "Insert below" +msgstr "ქვემოთ ჩასმა" + +#: lib/solaar/ui/diversion_rules.py:316 +msgid "Insert new rule here" +msgstr "ახალი წესის ჩასმა აქ" + +#: lib/solaar/ui/diversion_rules.py:318 +msgid "Insert new rule above" +msgstr "ახალი წესის ჩასმა ზემოთ" + +#: lib/solaar/ui/diversion_rules.py:320 +msgid "Insert new rule below" +msgstr "ახალი წესის ჩასმა ქვემოთ" + +#: lib/solaar/ui/diversion_rules.py:347 +msgid "Flatten" +msgstr "გაბრტყელება" + +#: lib/solaar/ui/diversion_rules.py:380 +msgid "Insert" +msgstr "ჩასმა" + +#: lib/solaar/ui/diversion_rules.py:383 lib/solaar/ui/diversion_rules.py:511 +#: lib/solaar/ui/diversion_rules.py:1158 +msgid "Or" +msgstr "ან" + +#: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:510 +#: lib/solaar/ui/diversion_rules.py:1144 +msgid "And" +msgstr "და" + +#: lib/solaar/ui/diversion_rules.py:386 +msgid "Condition" +msgstr "პირობა" + +#: lib/solaar/ui/diversion_rules.py:388 lib/solaar/ui/rule_conditions.py:154 +msgid "Feature" +msgstr "თვისება" + +#: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:188 +msgid "Report" +msgstr "ანგარიში" + +#: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:69 +msgid "Process" +msgstr "პროცესი" + +#: lib/solaar/ui/diversion_rules.py:391 +msgid "Mouse process" +msgstr "თაგუნას ქმედება" + +#: lib/solaar/ui/diversion_rules.py:392 lib/solaar/ui/rule_conditions.py:225 +msgid "Modifiers" +msgstr "მოდიფიკატორები" + +#: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:277 +msgid "Key" +msgstr "ღილაკი" + +#: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:318 +msgid "KeyIsDown" +msgstr "ღილაკი დაჭერილია" + +#: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/diversion_rules.py:1432 +msgid "Active" +msgstr "აქტიური" + +#: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1390 +#: lib/solaar/ui/diversion_rules.py:1441 lib/solaar/ui/diversion_rules.py:1487 +msgid "Device" +msgstr "მოწყობილობა" + +#: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1467 +msgid "Host" +msgstr "ჰოსტი" + +#: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1506 +msgid "Setting" +msgstr "პარამეტრი" + +#: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/rule_conditions.py:333 +#: lib/solaar/ui/rule_conditions.py:382 +msgid "Test" +msgstr "შემოწმება" + +#: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:507 +msgid "Test bytes" +msgstr "სატესტო ბაიტები" + +#: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:608 +msgid "Mouse Gesture" +msgstr "თაგუნას ჟესტი" + +#: lib/solaar/ui/diversion_rules.py:405 +msgid "Action" +msgstr "ქმედება" + +#: lib/solaar/ui/diversion_rules.py:407 lib/solaar/ui/rule_actions.py:138 +msgid "Key press" +msgstr "ღილაკის დაწოლა" + +#: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:191 +msgid "Mouse scroll" +msgstr "გადახვევა თაგუნათი" + +#: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:256 +msgid "Mouse click" +msgstr "თაგუნას წკაპი" + +#: lib/solaar/ui/diversion_rules.py:410 +msgid "Set" +msgstr "დაყენება" + +#: lib/solaar/ui/diversion_rules.py:411 lib/solaar/ui/rule_actions.py:328 +msgid "Execute" +msgstr "შესრულება" + +#: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/diversion_rules.py:1189 +msgid "Later" +msgstr "მოგვიანებით" + +#: lib/solaar/ui/diversion_rules.py:441 +msgid "Insert new rule" +msgstr "ახალი წესის ჩასმა" + +#: lib/solaar/ui/diversion_rules.py:461 lib/solaar/ui/rule_actions.py:82 +#: lib/solaar/ui/rule_actions.py:287 lib/solaar/ui/rule_conditions.py:556 +msgid "Delete" +msgstr "წაშლა" + +#: lib/solaar/ui/diversion_rules.py:483 +msgid "Negate" +msgstr "უარყოფა" + +#: lib/solaar/ui/diversion_rules.py:507 +msgid "Wrap with" +msgstr "ჩაკეცვა სად" + +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Cut" +msgstr "ამოჭრა" + +#: lib/solaar/ui/diversion_rules.py:553 +msgid "Paste" +msgstr "ჩასმა" + +#: lib/solaar/ui/diversion_rules.py:559 +msgid "Copy" +msgstr "კოპირება" + +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Solaar Rule Editor" +msgstr "Solaar-ის წესების რედაქტორი" + +#: lib/solaar/ui/diversion_rules.py:668 +msgid "Save changes" +msgstr "ცვლილებების შენახვა" + +#: lib/solaar/ui/diversion_rules.py:673 +msgid "Discard changes" +msgstr "ცვლილებების გაუქმება" + +#: lib/solaar/ui/diversion_rules.py:1104 +msgid "This editor does not support the selected rule component yet." +msgstr "ამ რედაქტორს ჯერ არ აქვს მონიშნული წესის კომპონენტის მხარდაჭერა." + +#: lib/solaar/ui/diversion_rules.py:1169 +msgid "" +"Number of seconds to delay. Delay between 0 and 1 is done with higher " +"precision." +msgstr "" +"დაყოვნების დრო წამებში. დაყოვნება 0-სა და 1-ს შორის უფრო დიდი სიზუსტით " +"ხდება." + +#: lib/solaar/ui/diversion_rules.py:1207 +msgid "Not" +msgstr "არ" + +#: lib/solaar/ui/diversion_rules.py:1238 +msgid "Toggle" +msgstr "გადართვა" + +#: lib/solaar/ui/diversion_rules.py:1239 +msgid "True" +msgstr "ჭეშმარიტი" + +#: lib/solaar/ui/diversion_rules.py:1240 +msgid "False" +msgstr "მცდარი" + +#: lib/solaar/ui/diversion_rules.py:1253 +msgid "Unsupported setting" +msgstr "მხარდაუჭერელი პარამეტრი" + +#: lib/solaar/ui/diversion_rules.py:1396 lib/solaar/ui/diversion_rules.py:1415 +#: lib/solaar/ui/diversion_rules.py:1493 lib/solaar/ui/diversion_rules.py:1748 +#: lib/solaar/ui/diversion_rules.py:1766 +msgid "Originating device" +msgstr "წყარო მოწყობილობა" + +#: lib/solaar/ui/diversion_rules.py:1428 +msgid "Device is active and its settings can be changed." +msgstr "მოწყობილობა აქტიურია და შეგიძლიათ, მისი პარამეტრები შეცვალოთ." + +#: lib/solaar/ui/diversion_rules.py:1437 +msgid "Device that originated the current notification." +msgstr "მოწყობილობა, რომელიც მიმდინარე გაფრთხილებიდან დაიწყო." + +#: lib/solaar/ui/diversion_rules.py:1450 +msgid "Name of host computer." +msgstr "მთავარი კომპიუტერის სახელი." + +#: lib/solaar/ui/diversion_rules.py:1520 +msgid "Value" +msgstr "მნიშვნელობა" + +#: lib/solaar/ui/diversion_rules.py:1529 +msgid "Item" +msgstr "ელემენტი" + +#: lib/solaar/ui/diversion_rules.py:1808 +msgid "Change setting on device" +msgstr "პარამეტრის შეცვლა მოწყობილობაზე" + +#: lib/solaar/ui/diversion_rules.py:1824 +msgid "Setting on device" +msgstr "პარამეტრი მოწყობილობაზე" + +#: lib/solaar/ui/pair_window.py:44 lib/solaar/ui/pair_window.py:167 +#, python-format +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: ახალი მოწყობილობის დაწყვილება" + +#: lib/solaar/ui/pair_window.py:46 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "ორივე მიმღები, მხოლოდ, Bolt მოწყობილობასთანაა თავსებადი." + +#: lib/solaar/ui/pair_window.py:48 +msgid "Press a pairing button or key until the pairing light flashes quickly." +msgstr "" +"დააჭირეთ დაწყვილების ღილაკს, სანამ დაწყვილების ნათურა სწრაფად არ აციმციმდება." + +#: lib/solaar/ui/pair_window.py:51 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "" +"გაერთიანებული მიმღებები, მხოლოდ, გაერთიანებულ მოწყობილობებთანაა თავსებადი." + +#: lib/solaar/ui/pair_window.py:53 +msgid "Other receivers are only compatible with a few devices." +msgstr "სხვა მიმღებები, მხოლოდ, რამდენიმე მოწყობილობასთანაა თავსებადი." + +#: lib/solaar/ui/pair_window.py:55 +msgid "For most devices, turn on the device you want to pair." +msgstr "" +"მოწყობილობების უმრავლესობისთვის, უბრალოდ ჩართეთ მოწყობილობა, რომლის " +"დაწყვილებაც გნებავთ." + +#: lib/solaar/ui/pair_window.py:56 +msgid "If the device is already turned on, turn it off and on again." +msgstr "თუ მოწყობილობა უკვე ჩართულია, გამორთეთ და ისევ ჩართეთ." + +#: lib/solaar/ui/pair_window.py:58 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "მოწყობილობა არ უნდა იყოს დაწყვილებული ახლომდებარე ჩართულ მიმღებთან." + +#: lib/solaar/ui/pair_window.py:61 +msgid "" +"For devices with multiple channels, press, hold, and release the button for " +"the channel you wish to pair\n" +"or use the channel switch button to select a channel and then press, hold, " +"and release the channel switch button." +msgstr "" +"მრავალი არხის მქონე მოწყობილობებისთვის დააჭირეთ ღილაკს, გააჩერეთ და აუშვით " +"ღილაკს იმ არხისთვის, რომლის დაწყვილებაც გსურთ\n" +"ან გამოიყენეთ არხის გადამრთველი ღილაკი არხის ასარჩევად და შემდეგ დააჭირეთ " +"ღილაკს და აუშვით არხის გადართვის ღილაკს." + +#: lib/solaar/ui/pair_window.py:68 +msgid "The channel indicator light should be blinking rapidly." +msgstr "არხის ინდიკატორის სინათლე სწრაფად უნდა ციმციმებდეს." + +#: lib/solaar/ui/pair_window.py:72 +#, python-format +msgid "" +"\n" +"\n" +"This receiver has %d pairing remaining." +msgid_plural "" +"\n" +"\n" +"This receiver has %d pairings remaining." +msgstr[0] "" +"\n" +"\n" +"ამ მიმღებს %d დაწყვილება დარჩა." +msgstr[1] "" +"\n" +"\n" +"ამ მიმღებს %d დაწყვილება დარჩა." + +#: lib/solaar/ui/pair_window.py:78 +msgid "" +"\n" +"Cancelling at this point will not use up a pairing." +msgstr "" +"\n" +"ამ მომენტში გაუქმება დაწყვილებას არ გამოიყენებს." + +#: lib/solaar/ui/pair_window.py:168 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "შეიყვანეთ პაროლი %(name)s-ზე." + +#: lib/solaar/ui/pair_window.py:171 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "აკრიფეთ %(passcode)s და შემდეგ დააჭირეთ ღილაკს Enter." + +#: lib/solaar/ui/pair_window.py:176 +msgid "left" +msgstr "მარცხენა" + +#: lib/solaar/ui/pair_window.py:176 +msgid "right" +msgstr "მარჯვენა" + +#: lib/solaar/ui/pair_window.py:178 +#, python-format +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"დააჭირეთ ღილაკს %(code)s\n" +"და შემდეგ დააჭირეთ მარცხენა და მარჯვენა ღილაკებს ერთდროულად." + +#: lib/solaar/ui/pair_window.py:221 +msgid "The wireless link is not encrypted" +msgstr "უსადენო კავშირი დაშიფრული არაა" + +#: lib/solaar/ui/pair_window.py:226 +msgid "Found a new device:" +msgstr "აღმოჩენილია ახალი მოწყობილობა:" + +#: lib/solaar/ui/pair_window.py:247 +msgid "Pairing failed" +msgstr "დაწყვილება ჩავარდა" + +#: lib/solaar/ui/pair_window.py:249 +msgid "Make sure your device is within range, and has a decent battery charge." +msgstr "" +"დარწმუნდით, რომ თქვენი მოწყობილობა ხელმისაწვდომ დაშორებაზეა და აქვს ელემენტი " +"საკმარისადაა დამუხტული." + +#: lib/solaar/ui/pair_window.py:251 +msgid "A new device was detected, but it is not compatible with this receiver." +msgstr "" +"აღმოჩენილია ახალი მოწყობილობა, მაგრამ ის არ არის თავსებადი ამ მიმღებთან." + +#: lib/solaar/ui/pair_window.py:253 +msgid "More paired devices than receiver can support." +msgstr "უფრო მეტი დაწყვილებული მოწყობილობა, ვიდრე მიმღებს შეუძლია მხარდაჭერა." + +#: lib/solaar/ui/pair_window.py:255 +msgid "No further details are available about the error." +msgstr "შეცდომის შესახებ დამატებითი დეტალები ხელმისაწვდომი არაა." + +#: lib/solaar/ui/rule_actions.py:54 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"აკორდული ღილაკის დაწკაპუნების, დაჭერის ან აშვების სიმულაცია.\n" +"Wayland-ზე სჭირდება ჩაწერის უფლება სპეციალურ ფაილში /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:60 +msgid "Add key" +msgstr "გასაღების დამატება" + +#: lib/solaar/ui/rule_actions.py:63 +msgid "Click" +msgstr "წკაპი" + +#: lib/solaar/ui/rule_actions.py:66 +msgid "Depress" +msgstr "გაჭყლეტა" + +#: lib/solaar/ui/rule_actions.py:69 +msgid "Release" +msgstr "რელიზი" + +#: lib/solaar/ui/rule_actions.py:153 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"თაგუნას გადახვევის სიმულაცია.\n" +"Wayland-ზე სჭირდება ჩაწერის უფლება სპეციალურ ფაილში /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:211 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"თაგუნას დაწკაპუნების სიმულაცია.\n" +"Wayland-ზე სჭირდება ჩაწერის უფლება სპეციალურ ფაილში /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:216 +msgid "Button" +msgstr "ღილაკი" + +#: lib/solaar/ui/rule_actions.py:218 +msgid "Action (and Count, if click)" +msgstr "ქმედება (და რაოდენობა, თუ წკაპია)" + +#: lib/solaar/ui/rule_actions.py:269 +msgid "Execute a command with arguments." +msgstr "ბრძანების შესრულება არგუმენტებით." + +#: lib/solaar/ui/rule_actions.py:273 +msgid "Add argument" +msgstr "არგუმენტის დამატება" + +#: lib/solaar/ui/rule_conditions.py:52 +msgid "X11 active process. For use in X11 only." +msgstr "X11-ის აქტიური პროცესი. გამოიყენება, მხოლოდ, X11-ში." + +#: lib/solaar/ui/rule_conditions.py:82 +msgid "X11 mouse process. For use in X11 only." +msgstr "X11-ის თაგუნას პროცესი. გამოიყენება, მხოლოდ, X11-ში." + +#: lib/solaar/ui/rule_conditions.py:99 +msgid "MouseProcess" +msgstr "თაგუნას ქმედება" + +#: lib/solaar/ui/rule_conditions.py:123 +msgid "Feature name of notification triggering rule processing." +msgstr "შეტყობინების ფუნქციის სახელი, რომელიც იწვევს წესის დამუშავებას." + +#: lib/solaar/ui/rule_conditions.py:169 +msgid "Report number of notification triggering rule processing." +msgstr "წესის დამუშავების დამატრიგერებელი გაფრთხილებების რაოდენობის გადაცემა." + +#: lib/solaar/ui/rule_conditions.py:201 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "" +"აქტიური კლავიატურის მოდიფიკატორების. Wayland-ზე ყოველთვის ხელმისაწვდომი არაა." + +#: lib/solaar/ui/rule_conditions.py:241 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"გადამისამართებულ ღილაკს დააწვნენ, ან აუშვეს.\n" +"ღილაკის გადამისამართების და G ღილაკის პარამეტრების გამოყენება ღილაკების " +"გადამისამართებისთვის." + +#: lib/solaar/ui/rule_conditions.py:250 +msgid "Key down" +msgstr "ღილაკის დაჭერა" + +#: lib/solaar/ui/rule_conditions.py:253 +msgid "Key up" +msgstr "ღილაკის გაშვება" + +#: lib/solaar/ui/rule_conditions.py:293 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"გადამისამართებული ღილაკი ამჟამად დაჭერილია.\n" +"ღილაკის გადამისამართების და G ღილაკის პარამეტრების გამოყენება ღილაკების " +"გადამისამართებისთვის." + +#: lib/solaar/ui/rule_conditions.py:331 +msgid "Test condition on notification triggering rule processing." +msgstr "სატესტო პირობა წესის დამუშავების გაფრთხილებაზე." + +#: lib/solaar/ui/rule_conditions.py:335 +msgid "Parameter" +msgstr "პარამეტრი" + +#: lib/solaar/ui/rule_conditions.py:408 +msgid "begin (inclusive)" +msgstr "დასაწყისი (ჩათვლით)" + +#: lib/solaar/ui/rule_conditions.py:409 +msgid "end (exclusive)" +msgstr "დასასრული (მხოლოდ)" + +#: lib/solaar/ui/rule_conditions.py:417 +msgid "range" +msgstr "შუალედი" + +#: lib/solaar/ui/rule_conditions.py:420 +msgid "minimum" +msgstr "მინიმუმი" + +#: lib/solaar/ui/rule_conditions.py:421 +msgid "maximum" +msgstr "მაქსიმუმი" + +#: lib/solaar/ui/rule_conditions.py:423 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "ბაიტი %(0)d-დან %(1)d-მდე, დაწყებული %(2)d-დან %(3)d-მდე" + +#: lib/solaar/ui/rule_conditions.py:426 lib/solaar/ui/rule_conditions.py:427 +msgid "mask" +msgstr "ნიღაბი" + +#: lib/solaar/ui/rule_conditions.py:428 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "ბაიტი %(0)d-დან %(1)d-მდე, ნიღაბი %(2)d" + +#: lib/solaar/ui/rule_conditions.py:437 +msgid "" +"Bit or range test on bytes in notification message triggering rule " +"processing." +msgstr "" +"ბიტის ან დიაპაზონის ტესტი ბაიტებზე შეტყობინებების შეტყობინების გამომწვევი " +"წესის დამუშავებაში." + +#: lib/solaar/ui/rule_conditions.py:447 +msgid "type" +msgstr "ტიპი" + +#: lib/solaar/ui/rule_conditions.py:535 +msgid "" +"Mouse gesture with optional initiating button followed by zero or more mouse " +"movements." +msgstr "" +"თაგუნას ჟესტი არასავალდებულო გამშვები ღილაკით, რომელსაც 0, ან მეტი თაგუნას " +"მოძრაობა მოჰყვება." + +#: lib/solaar/ui/rule_conditions.py:541 +msgid "Add movement" +msgstr "მოძრაობის დამატება" + +#: lib/solaar/ui/tray.py:55 +msgid "No supported device found" +msgstr "მხარდაჭერილი მოწყობილობა აღმოჩენილი არაა" + +#: lib/solaar/ui/tray.py:60 lib/solaar/ui/window.py:328 +#, python-format +msgid "About %s" +msgstr "%s-ის შესახებ" + +#: lib/solaar/ui/tray.py:61 lib/solaar/ui/window.py:326 +#, python-format +msgid "Quit %s" +msgstr "%s-დან გასვლა" + +#: lib/solaar/ui/tray.py:273 lib/solaar/ui/tray.py:281 +msgid "no receiver" +msgstr "მიმღების გარეშე" + +#: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:299 +#: lib/solaar/ui/window.py:728 +msgid "offline" +msgstr "გათიშული" + +#: lib/solaar/ui/tray.py:297 +msgid "no status" +msgstr "სტატუსის გარეშე" + +#: lib/solaar/ui/window.py:110 +msgid "Scanning" +msgstr "სკანირება" + +#: lib/solaar/ui/window.py:141 +msgid "Battery" +msgstr "ელემენტი" + +#: lib/solaar/ui/window.py:144 +msgid "Wireless Link" +msgstr "უსადენო კავშირი" + +#: lib/solaar/ui/window.py:148 +msgid "Lighting" +msgstr "განათება" + +#: lib/solaar/ui/window.py:182 +msgid "Show Technical Details" +msgstr "ტექნიკური დეტალების ჩვენება" + +#: lib/solaar/ui/window.py:198 +msgid "Pair new device" +msgstr "ახალი მოწყობილობის დაწყვილება" + +#: lib/solaar/ui/window.py:216 +msgid "Select a device" +msgstr "აირჩიეთ მოწყობილობა" + +#: lib/solaar/ui/window.py:331 +msgid "Rule Editor" +msgstr "წესების რედაქტორი" + +#: lib/solaar/ui/window.py:522 +msgid "Path" +msgstr "ბილიკი" + +#: lib/solaar/ui/window.py:524 +msgid "USB ID" +msgstr "USB ID" + +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 +#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 +msgid "Serial" +msgstr "სერიული" + +#: lib/solaar/ui/window.py:533 +msgid "Index" +msgstr "ინდექსი" + +#: lib/solaar/ui/window.py:535 +msgid "Wireless PID" +msgstr "უსადენო PID" + +#: lib/solaar/ui/window.py:537 +msgid "Product ID" +msgstr "პროდუქტის ID" + +#: lib/solaar/ui/window.py:539 +msgid "Protocol" +msgstr "პროტოკოლი" + +#: lib/solaar/ui/window.py:539 +msgid "Unknown" +msgstr "უცნობი" + +#: lib/solaar/ui/window.py:541 +msgid "Polling rate" +msgstr "გამოკითხვის სიხშირე" + +#: lib/solaar/ui/window.py:548 +msgid "Unit ID" +msgstr "ერთეულის ID" + +#: lib/solaar/ui/window.py:559 +msgid "none" +msgstr "არცერთი" + +#: lib/solaar/ui/window.py:560 +msgid "Notifications" +msgstr "შეტყობინებები" + +#: lib/solaar/ui/window.py:604 +msgid "No device paired." +msgstr "დაწყვილებული მოწყობილობების გარეშე." + +#: lib/solaar/ui/window.py:613 +#, python-format +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "" +"ამ მიმღებთან, მაქსიმუმ, %(max_count)s მოწყობილობის დაწყვილება შეიძლება." +msgstr[1] "" +"ამ მიმღებთან, მაქსიმუმ, %(max_count)s მოწყობილობის დაწყვილება შეიძლება." + +#: lib/solaar/ui/window.py:620 +msgid "Only one device can be paired to this receiver." +msgstr "ამ მიმღებთან, მაქს., ერთი მოწყობილობის დაწყვილებაა შესაძლებელი." + +#: lib/solaar/ui/window.py:624 +#, python-format +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "ამ მიმღებს %d დაწყვილება აქვს დარჩენილი." +msgstr[1] "ამ მიმღებს %d დაწყვილება აქვს დარჩენილი." + +#: lib/solaar/ui/window.py:681 +msgid "Battery Voltage" +msgstr "ელემენტის ვოლტაჟი" + +#: lib/solaar/ui/window.py:683 +msgid "Voltage reported by battery" +msgstr "ელემენტის მიერ მოწოდებული ძაბვა" + +#: lib/solaar/ui/window.py:685 +msgid "Battery Level" +msgstr "ელემენტის მუხტის დონე" + +#: lib/solaar/ui/window.py:687 +msgid "Approximate level reported by battery" +msgstr "დონის მიერ მუხტის მოწოდებული დაახლოებითი დონე" + +#: lib/solaar/ui/window.py:694 lib/solaar/ui/window.py:696 +msgid "next reported " +msgstr "შემდეგი ანგარიში " + +#: lib/solaar/ui/window.py:697 +msgid " and next level to be reported." +msgstr " და შემდეგი დონე გადასაცემად." + +#: lib/solaar/ui/window.py:702 +msgid "last known" +msgstr "ბოლო ცნობილი" + +#: lib/solaar/ui/window.py:713 +msgid "encrypted" +msgstr "დაშიფრულია" + +#: lib/solaar/ui/window.py:715 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "უსადენო კავშირი ამ მოწყობილობასა და მის მიმღებს შორის დაშიფრულია." + +#: lib/solaar/ui/window.py:717 +msgid "not encrypted" +msgstr "დაშიფრული არაა" + +#: lib/solaar/ui/window.py:721 +msgid "" +"The wireless link between this device and its receiver is not encrypted.\n" +"This is a security issue for pointing devices, and a major security issue " +"for text-input devices." +msgstr "" +"უსადენო კავშირი ამ მოწყობილობასა და მის მიმღებს შორის დაშიფრული არაა.\n" +"ეს უსაფრთხოების პრობლემაა მაჩვენებელი მოწყობილობებისთვის და უსაფრთხოების " +"დიდი პრობლემაა ტექსტის შეყვანის მოწყობილობებისთვის." + +#: lib/solaar/ui/window.py:737 +#, python-format +msgid "%(light_level)d lux" +msgstr "%(light_level)d ლუქსი" diff --git a/po/nb.po b/po/nb.po index 1380b385..b308ab8b 100644 --- a/po/nb.po +++ b/po/nb.po @@ -2,1895 +2,2356 @@ # Copyright (C) 2020 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # Automatically generated, 2020. -# John Erling Blad , 2022. +# John Erling Blad , 2020-2024. # -msgid "" -msgstr "Project-Id-Version: solaar 1.0.3\n" - "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2022-09-01 00:04+0200\n" - "PO-Revision-Date: 2022-09-01 00:04+0200\n" - "Last-Translator: John Erling Blad \n" - "Language-Team: \n" - "Language: nb\n" - "MIME-Version: 1.0\n" - "Content-Type: text/plain; charset=UTF-8\n" - "Content-Transfer-Encoding: 8bit\n" - "Plural-Forms: nplurals=2; plural=(n!=1)\n" - "X-Generator: Gtranslator 42.0\n" +msgid "" +msgstr "" +"Project-Id-Version: solaar 1.0.3\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-10-24 03:15+0200\n" +"PO-Revision-Date: 2024-10-31 02:21+0100\n" +"Last-Translator: John Erling Blad \n" +"Language-Team: \n" +"Language: nb\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1)\n" +"X-Generator: Gtranslator 47.0\n" -#: lib/logitech_receiver/base_usb.py:47 -msgid "Bolt Receiver" -msgstr "Bolt mottaker" +#: lib/logitech_receiver/base_usb.py:48 +msgid "Bolt Receiver" +msgstr "Bolt mottaker" -#: lib/logitech_receiver/base_usb.py:58 -msgid "Unifying Receiver" -msgstr "Unifying mottaker" +#: lib/logitech_receiver/base_usb.py:60 +msgid "Unifying Receiver" +msgstr "Unifying mottaker" -#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 -#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 -#: lib/logitech_receiver/base_usb.py:114 -msgid "Nano Receiver" -msgstr "Nano mottaker" +#: lib/logitech_receiver/base_usb.py:71 lib/logitech_receiver/base_usb.py:83 +#: lib/logitech_receiver/base_usb.py:96 lib/logitech_receiver/base_usb.py:109 +msgid "Nano Receiver" +msgstr "Nano mottaker" -#: lib/logitech_receiver/base_usb.py:123 -msgid "Lightspeed Receiver" -msgstr "Lightspeed mottaker" +#: lib/logitech_receiver/base_usb.py:121 +msgid "Lightspeed Receiver" +msgstr "Lightspeed mottaker" #: lib/logitech_receiver/base_usb.py:131 -msgid "EX100 Receiver 27 Mhz" -msgstr "EX100 mottaker 27 Mhz" +msgid "EX100 Receiver 27 Mhz" +msgstr "EX100 mottaker 27 Mhz mottaker" + +#: lib/logitech_receiver/common.py:648 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Batteri: %(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:651 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Batteri: %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:962 +#: lib/logitech_receiver/settings_templates.py:294 +msgid "Disabled" +msgstr "Avslått" + +#: lib/logitech_receiver/hidpp20.py:963 +msgid "Static" +msgstr "Statisk" + +#: lib/logitech_receiver/hidpp20.py:964 +msgid "Pulse" +msgstr "Puls" + +#: lib/logitech_receiver/hidpp20.py:965 +msgid "Cycle" +msgstr "Syklus" + +#: lib/logitech_receiver/hidpp20.py:966 +msgid "Boot" +msgstr "Boot" + +#: lib/logitech_receiver/hidpp20.py:967 +msgid "Demo" +msgstr "Demo" + +#: lib/logitech_receiver/hidpp20.py:969 +msgid "Breathe" +msgstr "Pust" + +#: lib/logitech_receiver/hidpp20.py:972 +msgid "Ripple" +msgstr "Krusning" + +#: lib/logitech_receiver/hidpp20.py:973 +msgid "Decomposition" +msgstr "Dekomponering" + +#: lib/logitech_receiver/hidpp20.py:974 +msgid "Signature1" +msgstr "Signatur1" + +#: lib/logitech_receiver/hidpp20.py:975 +msgid "Signature2" +msgstr "Signatur2" + +#: lib/logitech_receiver/hidpp20.py:976 +msgid "CycleS" +msgstr "SyklusS" + +#: lib/logitech_receiver/hidpp20.py:1040 +msgid "Unknown Location" +msgstr "Ukjent lokasjon" + +#: lib/logitech_receiver/hidpp20.py:1041 +msgid "Primary" +msgstr "Primær" + +#: lib/logitech_receiver/hidpp20.py:1042 +msgid "Logo" +msgstr "Logo" + +#: lib/logitech_receiver/hidpp20.py:1043 +msgid "Left Side" +msgstr "Venstre side" + +#: lib/logitech_receiver/hidpp20.py:1044 +msgid "Right Side" +msgstr "Høyre side" + +#: lib/logitech_receiver/hidpp20.py:1045 +msgid "Combined" +msgstr "Kombinert" + +#: lib/logitech_receiver/hidpp20.py:1046 +msgid "Primary 1" +msgstr "Primær 1" + +#: lib/logitech_receiver/hidpp20.py:1047 +msgid "Primary 2" +msgstr "Primær 2" + +#: lib/logitech_receiver/hidpp20.py:1048 +msgid "Primary 3" +msgstr "Primær 3" + +#: lib/logitech_receiver/hidpp20.py:1049 +msgid "Primary 4" +msgstr "Primær 4" + +#: lib/logitech_receiver/hidpp20.py:1050 +msgid "Primary 5" +msgstr "Primær 5" + +#: lib/logitech_receiver/hidpp20.py:1051 +msgid "Primary 6" +msgstr "Primær 6" + +#: lib/logitech_receiver/i18n.py:28 +msgid "empty" +msgstr "tom" + +#: lib/logitech_receiver/i18n.py:29 +msgid "critical" +msgstr "kritisk" #: lib/logitech_receiver/i18n.py:30 -msgid "empty" -msgstr "tom" +msgid "low" +msgstr "lav" #: lib/logitech_receiver/i18n.py:31 -msgid "critical" -msgstr "kritisk" +msgid "average" +msgstr "middels" #: lib/logitech_receiver/i18n.py:32 -msgid "low" -msgstr "lav" +msgid "good" +msgstr "god" #: lib/logitech_receiver/i18n.py:33 -msgid "average" -msgstr "middels" - -#: lib/logitech_receiver/i18n.py:34 -msgid "good" -msgstr "god" +msgid "full" +msgstr "full" #: lib/logitech_receiver/i18n.py:35 -msgid "full" -msgstr "full" +msgid "discharging" +msgstr "utlading" + +#: lib/logitech_receiver/i18n.py:36 +msgid "recharging" +msgstr "opplading" + +#: lib/logitech_receiver/i18n.py:37 +msgid "charging" +msgstr "lader" #: lib/logitech_receiver/i18n.py:38 -msgid "discharging" -msgstr "utlading" +msgid "not charging" +msgstr "lader ikke" #: lib/logitech_receiver/i18n.py:39 -msgid "recharging" -msgstr "opplading" +msgid "almost full" +msgstr "nesten full" -#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:718 -msgid "charging" -msgstr "lader" +#: lib/logitech_receiver/i18n.py:40 +msgid "charged" +msgstr "ladet" #: lib/logitech_receiver/i18n.py:41 -msgid "not charging" -msgstr "lader ikke" +msgid "slow recharge" +msgstr "langsom lading" #: lib/logitech_receiver/i18n.py:42 -msgid "almost full" -msgstr "nesten full" +msgid "invalid battery" +msgstr "batterifeil" #: lib/logitech_receiver/i18n.py:43 -msgid "charged" -msgstr "ladet" +msgid "thermal error" +msgstr "termisk feil" #: lib/logitech_receiver/i18n.py:44 -msgid "slow recharge" -msgstr "langsom lading" +msgid "error" +msgstr "feil" #: lib/logitech_receiver/i18n.py:45 -msgid "invalid battery" -msgstr "batterifeil" +msgid "standard" +msgstr "standard" #: lib/logitech_receiver/i18n.py:46 -msgid "thermal error" -msgstr "termisk feil" +msgid "fast" +msgstr "rask" #: lib/logitech_receiver/i18n.py:47 -msgid "error" -msgstr "feil" - -#: lib/logitech_receiver/i18n.py:48 -msgid "standard" -msgstr "standard" +msgid "slow" +msgstr "langsom" #: lib/logitech_receiver/i18n.py:49 -msgid "fast" -msgstr "rask" +msgid "device timeout" +msgstr "enheten svarte ikke" #: lib/logitech_receiver/i18n.py:50 -msgid "slow" -msgstr "langsom" +msgid "device not supported" +msgstr "enheten er ikke støttet" -#: lib/logitech_receiver/i18n.py:53 -msgid "device timeout" -msgstr "enheten svarte ikke" +#: lib/logitech_receiver/i18n.py:51 +msgid "too many devices" +msgstr "for mange enheter" + +#: lib/logitech_receiver/i18n.py:52 +msgid "sequence timeout" +msgstr "sekvens timeout" #: lib/logitech_receiver/i18n.py:54 -msgid "device not supported" -msgstr "enheten er ikke støttet" +msgid "Firmware" +msgstr "Fastvare" #: lib/logitech_receiver/i18n.py:55 -msgid "too many devices" -msgstr "for mange enheter" +msgid "Bootloader" +msgstr "Oppstartslaster" #: lib/logitech_receiver/i18n.py:56 -msgid "sequence timeout" -msgstr "sekvens timeout" +msgid "Hardware" +msgstr "Maskinvare" -#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:577 -msgid "Firmware" -msgstr "Fastvare" +#: lib/logitech_receiver/i18n.py:57 +msgid "Other" +msgstr "Annet" + +#: lib/logitech_receiver/i18n.py:59 +msgid "Left Button" +msgstr "Venstreknapp" #: lib/logitech_receiver/i18n.py:60 -msgid "Bootloader" -msgstr "Oppstartslaster" +msgid "Right Button" +msgstr "Høyreknapp" #: lib/logitech_receiver/i18n.py:61 -msgid "Hardware" -msgstr "Maskinvare" +msgid "Middle Button" +msgstr "Midtknapp" #: lib/logitech_receiver/i18n.py:62 -msgid "Other" -msgstr "Annet" +msgid "Back Button" +msgstr "Tilbakeknapp" + +#: lib/logitech_receiver/i18n.py:63 +msgid "Forward Button" +msgstr "Foroverknapp" + +#: lib/logitech_receiver/i18n.py:64 +msgid "Mouse Gesture Button" +msgstr "Bevegelsesknapp" #: lib/logitech_receiver/i18n.py:65 -msgid "Left Button" -msgstr "Venstreknapp" +msgid "Smart Shift" +msgstr "Smart skift" #: lib/logitech_receiver/i18n.py:66 -msgid "Right Button" -msgstr "Høyreknapp" +msgid "DPI Switch" +msgstr "DPI-bryter" #: lib/logitech_receiver/i18n.py:67 -msgid "Middle Button" -msgstr "Midtknapp" +msgid "Left Tilt" +msgstr "Venstre-tilt" #: lib/logitech_receiver/i18n.py:68 -msgid "Back Button" -msgstr "Tilbakeknapp" +msgid "Right Tilt" +msgstr "Høyre-tilt" #: lib/logitech_receiver/i18n.py:69 -msgid "Forward Button" -msgstr "Foroverknapp" +msgid "Left Click" +msgstr "Venstre-klikk" #: lib/logitech_receiver/i18n.py:70 -msgid "Mouse Gesture Button" -msgstr "Bevegelsesknapp" +msgid "Right Click" +msgstr "Høyre-klikk" #: lib/logitech_receiver/i18n.py:71 -msgid "Smart Shift" -msgstr "Smart skift" +msgid "Mouse Middle Button" +msgstr "Midtknapp for mus" #: lib/logitech_receiver/i18n.py:72 -msgid "DPI Switch" -msgstr "DPI-bryter" +msgid "Mouse Back Button" +msgstr "Tilbakeknapp for mus" #: lib/logitech_receiver/i18n.py:73 -msgid "Left Tilt" -msgstr "Venstre-tilt" +msgid "Mouse Forward Button" +msgstr "Foroverknapp for mus" #: lib/logitech_receiver/i18n.py:74 -msgid "Right Tilt" -msgstr "Høyre-tilt" +msgid "Gesture Button Navigation" +msgstr "Navigering med bevegelsesknapp" #: lib/logitech_receiver/i18n.py:75 -msgid "Left Click" -msgstr "Venstre-klikk" +msgid "Mouse Scroll Left Button" +msgstr "Venstre rulleknapp for mus" #: lib/logitech_receiver/i18n.py:76 -msgid "Right Click" -msgstr "Høyre-klikk" - -#: lib/logitech_receiver/i18n.py:77 -msgid "Mouse Middle Button" -msgstr "Midtknapp for mus" +msgid "Mouse Scroll Right Button" +msgstr "Høyre rulleknapp for mus" #: lib/logitech_receiver/i18n.py:78 -msgid "Mouse Back Button" -msgstr "Tilbakeknapp for mus" +msgid "pressed" +msgstr "nedtrykt" #: lib/logitech_receiver/i18n.py:79 -msgid "Mouse Forward Button" -msgstr "Foroverknapp for mus" +msgid "released" +msgstr "utløst" -#: lib/logitech_receiver/i18n.py:80 -msgid "Gesture Button Navigation" -msgstr "Navigering med bevegelsesknapp" +#: lib/logitech_receiver/notifications.py:86 +#: lib/logitech_receiver/notifications.py:138 +msgid "pairing lock is closed" +msgstr "parlåsen er lukket" -#: lib/logitech_receiver/i18n.py:81 -msgid "Mouse Scroll Left Button" -msgstr "Rullvenstreknapp for mus" +#: lib/logitech_receiver/notifications.py:86 +#: lib/logitech_receiver/notifications.py:138 +msgid "pairing lock is open" +msgstr "parlåsen er åpen" -#: lib/logitech_receiver/i18n.py:82 -msgid "Mouse Scroll Right Button" -msgstr "Rullhøyreknapp for mus" +#: lib/logitech_receiver/notifications.py:103 +msgid "discovery lock is closed" +msgstr "oppdagelseslåsen er lukket" -#: lib/logitech_receiver/i18n.py:85 -msgid "pressed" -msgstr "nedtrykt" +#: lib/logitech_receiver/notifications.py:103 +msgid "discovery lock is open" +msgstr "oppdagelseslåsen er åpen" -#: lib/logitech_receiver/i18n.py:86 -msgid "released" -msgstr "utløst" +#: lib/logitech_receiver/notifications.py:232 lib/solaar/listener.py:247 +msgid "connected" +msgstr "tilkoblet" -#: lib/logitech_receiver/notifications.py:74 -#: lib/logitech_receiver/notifications.py:125 -msgid "pairing lock is closed" -msgstr "parlåsen er lukket" +#: lib/logitech_receiver/notifications.py:232 lib/solaar/listener.py:247 +msgid "disconnected" +msgstr "frakoblet" -#: lib/logitech_receiver/notifications.py:74 -#: lib/logitech_receiver/notifications.py:125 -msgid "pairing lock is open" -msgstr "parlåsen er åpen" +#: lib/logitech_receiver/notifications.py:258 +msgid "unpaired" +msgstr "uparet" -#: lib/logitech_receiver/notifications.py:91 -msgid "discovery lock is closed" -msgstr "oppdagelseslåsen er lukket" +#: lib/logitech_receiver/notifications.py:305 +msgid "powered on" +msgstr "påslått" -#: lib/logitech_receiver/notifications.py:91 -msgid "discovery lock is open" -msgstr "oppdagelseslåsen er åpen" +#: lib/logitech_receiver/receiver.py:383 +msgid "No paired devices." +msgstr "Ingen paret enhet." -#: lib/logitech_receiver/notifications.py:223 lib/solaar/ui/notify.py:120 -msgid "connected" -msgstr "tilkoblet" +#: lib/logitech_receiver/receiver.py:385 lib/solaar/ui/window.py:590 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s paret enhet." +msgstr[1] "%(count)s parede enheter." -#: lib/logitech_receiver/notifications.py:223 -msgid "disconnected" -msgstr "frakoblet" +#: lib/logitech_receiver/settings.py:614 +msgid "register" +msgstr "register" -#: lib/logitech_receiver/notifications.py:261 lib/solaar/ui/notify.py:118 -msgid "unpaired" -msgstr "uparet" +#: lib/logitech_receiver/settings.py:628 lib/logitech_receiver/settings.py:663 +msgid "feature" +msgstr "karakteristikk" -#: lib/logitech_receiver/notifications.py:303 -msgid "powered on" -msgstr "påslått" +#: lib/logitech_receiver/settings_templates.py:133 +msgid "Swap Fx function" +msgstr "Bytt Fx-funksjon" -#: lib/logitech_receiver/settings.py:734 -msgid "register" -msgstr "register" +#: lib/logitech_receiver/settings_templates.py:136 +msgid "" +"When set, the F1..F12 keys will activate their special function,\n" +"and you must hold the FN key to activate their standard function." +msgstr "" +"Hvis aktivert, vil F1–F12-tastene aktivere sin spesialfunksjon,\n" +"og du må holde FN-tasten nede for å aktivere deres standardfunksjon." -#: lib/logitech_receiver/settings.py:748 lib/logitech_receiver/settings.py:775 -msgid "feature" -msgstr "karakteristikk" - -#: lib/logitech_receiver/settings_templates.py:139 -msgid "Swap Fx function" -msgstr "Bytt Fx-funksjon" - -#: lib/logitech_receiver/settings_templates.py:140 -msgid "When set, the F1..F12 keys will activate their special function,\n" - "and you must hold the FN key to activate their standard function." -msgstr "Hvis aktivert, vil F1–F12-tastene aktivere sin spesialfunksjon,\n" - "og du må holde FN-tasten nede for å aktivere deres standardfunksjon." - -#: lib/logitech_receiver/settings_templates.py:142 -msgid "When unset, the F1..F12 keys will activate their standard function,\n" - "and you must hold the FN key to activate their special function." -msgstr "Hvis deaktivert, vil F1–F12-tastene aktivere sin standardfunksjon,\n" - "og du må holde FN-tasten nede for å aktivere deres spesialfunksjon." +#: lib/logitech_receiver/settings_templates.py:141 +msgid "" +"When unset, the F1..F12 keys will activate their standard function,\n" +"and you must hold the FN key to activate their special function." +msgstr "" +"Hvis deaktivert, vil F1–F12-tastene aktivere sin standardfunksjon,\n" +"og du må holde FN-tasten nede for å aktivere deres spesialfunksjon." #: lib/logitech_receiver/settings_templates.py:149 -msgid "Hand Detection" -msgstr "Håndpåvisning" +msgid "Hand Detection" +msgstr "Håndpåvisning" #: lib/logitech_receiver/settings_templates.py:150 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "Slå på belysning når hendene er over tastaturet." +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Slå på belysning når hendene er over tastaturet." #: lib/logitech_receiver/settings_templates.py:157 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "Myk rulling med rullehjul" +msgid "Scroll Wheel Smooth Scrolling" +msgstr "Myk rulling med rullehjul" #: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:239 -#: lib/logitech_receiver/settings_templates.py:267 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "Høysensitiv modus for vertikal rulling med hjulet." +#: lib/logitech_receiver/settings_templates.py:405 +#: lib/logitech_receiver/settings_templates.py:434 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Høysensitiv modus for vertikal rulling med hjulet." #: lib/logitech_receiver/settings_templates.py:165 -msgid "Side Scrolling" -msgstr "Siderulling" +msgid "Side Scrolling" +msgstr "Siderulling" #: lib/logitech_receiver/settings_templates.py:167 -msgid "When disabled, pushing the wheel sideways sends custom button " - "events\n" - "instead of the standard side-scrolling events." -msgstr "Hvis deaktivert; trykkes hjulet sideveis så sendes en tilpasset " - "knappehendelse\n" - "istedenfor standard siderulling." +msgid "" +"When disabled, pushing the wheel sideways sends custom button events\n" +"instead of the standard side-scrolling events." +msgstr "" +"Hvis avslått; trykkes hjulet sideveis så sendes en tilpasset knappehendelse\n" +"istedenfor standard siderulling." #: lib/logitech_receiver/settings_templates.py:177 -msgid "Sensitivity (DPI - older mice)" -msgstr "Følsomhet (DPI - eldre mus)" +msgid "Sensitivity (DPI - older mice)" +msgstr "Følsomhet (DPI – eldre mus)" #: lib/logitech_receiver/settings_templates.py:178 -#: lib/logitech_receiver/settings_templates.py:695 -msgid "Mouse movement sensitivity" -msgstr "Følsomhet for musbevegelser" +#: lib/logitech_receiver/settings_templates.py:977 +#: lib/logitech_receiver/settings_templates.py:1005 +msgid "Mouse movement sensitivity" +msgstr "Følsomhet for musbevegelser" -#: lib/logitech_receiver/settings_templates.py:208 -#: lib/logitech_receiver/settings_templates.py:218 -#: lib/logitech_receiver/settings_templates.py:225 -msgid "Backlight" -msgstr "Bakbelysning" +#: lib/logitech_receiver/settings_templates.py:251 +msgid "Backlight Timed" +msgstr "Tidsbestemt bakgrunnslys" -#: lib/logitech_receiver/settings_templates.py:209 -#: lib/logitech_receiver/settings_templates.py:226 -msgid "Set illumination time for keyboard." -msgstr "Still inn belysningstid for tastatur." +#: lib/logitech_receiver/settings_templates.py:252 +#: lib/logitech_receiver/settings_templates.py:392 +msgid "Set illumination time for keyboard." +msgstr "Still inn belysningstid for tastatur." -#: lib/logitech_receiver/settings_templates.py:219 -msgid "Turn illumination on or off on keyboard." -msgstr "Slå på eller av belysning for tastaturet." +#: lib/logitech_receiver/settings_templates.py:263 +msgid "Backlight" +msgstr "Bakbelysning" -#: lib/logitech_receiver/settings_templates.py:237 -msgid "Scroll Wheel High Resolution" -msgstr "Høyoppløselig rullehjul" +#: lib/logitech_receiver/settings_templates.py:264 +msgid "" +"Illumination level on keyboard. Changes made are only applied in Manual " +"mode." +msgstr "" +"Lysnivå på tastaturet. Endringer som er gjort, brukes kun i manuell modus." -#: lib/logitech_receiver/settings_templates.py:240 -#: lib/logitech_receiver/settings_templates.py:268 -msgid "Set to ignore if scrolling is abnormally fast or slow" -msgstr "Sett til å ignorere hvis rullingen er unormalt rask eller sakte" +#: lib/logitech_receiver/settings_templates.py:296 +msgid "Automatic" +msgstr "Automatisk" -#: lib/logitech_receiver/settings_templates.py:247 -#: lib/logitech_receiver/settings_templates.py:277 -msgid "Scroll Wheel Diversion" -msgstr "Omdirigering for rullehjul" +#: lib/logitech_receiver/settings_templates.py:298 +msgid "Manual" +msgstr "Manuell" -#: lib/logitech_receiver/settings_templates.py:249 -msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " - "trigger Solaar rules but are otherwise ignored)." -msgstr "Få rullehjulet til å sende LOWRES_WHEEL HID++-varsler (som utløser " - "Solaar-regler, men som ellers ignoreres)." +#: lib/logitech_receiver/settings_templates.py:300 +msgid "Enabled" +msgstr "Påslått" -#: lib/logitech_receiver/settings_templates.py:256 -msgid "Scroll Wheel Direction" -msgstr "Retning på rullehjul" +#: lib/logitech_receiver/settings_templates.py:306 +msgid "Backlight Level" +msgstr "Bakgrunnslysnivå" -#: lib/logitech_receiver/settings_templates.py:257 -msgid "Invert direction for vertical scroll with wheel." -msgstr "Inverter retning for vertikal rulling med hjul." - -#: lib/logitech_receiver/settings_templates.py:265 -msgid "Scroll Wheel Resolution" -msgstr "Oppløsning på rullehjul" - -#: lib/logitech_receiver/settings_templates.py:279 -msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " - "trigger Solaar rules but are otherwise ignored)." -msgstr "Få rullehjulet til å sende HIRES_WHEEL HID++-varsler (som utløser " - "Solaar-regler, men som ellers ignoreres)." - -#: lib/logitech_receiver/settings_templates.py:288 -msgid "Sensitivity (Pointer Speed)" -msgstr "Følsomhet (pekerfart)" - -#: lib/logitech_receiver/settings_templates.py:289 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "Fartsmultiplikator for mus (256 er normal multiplikator)." - -#: lib/logitech_receiver/settings_templates.py:299 -msgid "Thumb Wheel Diversion" -msgstr "Omdirigering for tommelhjul" - -#: lib/logitech_receiver/settings_templates.py:301 -msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." -msgstr "Få tommelhjulet til å sende THUMB_WHEEL HID++-varsler (som utløser " - "Solaar-regler, men som ellers ignoreres)." - -#: lib/logitech_receiver/settings_templates.py:310 -msgid "Thumb Wheel Direction" -msgstr "Retning på tommelhjul" - -#: lib/logitech_receiver/settings_templates.py:311 -msgid "Invert thumb wheel scroll direction." -msgstr "Inverter rulleretning for tommelhjul." - -#: lib/logitech_receiver/settings_templates.py:319 -msgid "Onboard Profiles" -msgstr "Innebygde profiler" - -#: lib/logitech_receiver/settings_templates.py:320 -msgid "Enable onboard profiles, which often control report rate and " - "keyboard lighting" -msgstr "Aktiver innebygde profiler, som ofte kontrollerer rapporthastighet " - "og tastaturbelysning" - -#: lib/logitech_receiver/settings_templates.py:330 -msgid "Polling Rate (ms)" -msgstr "Spørrerate (ms)" - -#: lib/logitech_receiver/settings_templates.py:332 -msgid "Frequency of device polling, in milliseconds" -msgstr "Frekvens på enhetsspørring, i millisekunder" - -#: lib/logitech_receiver/settings_templates.py:333 -#: lib/logitech_receiver/settings_templates.py:1018 -#: lib/logitech_receiver/settings_templates.py:1046 -msgid "May need Onboard Profiles set to Disable to be effective." -msgstr "Kan trenge at innebygde profiler settes til Disable for å være " - "effektive." - -#: lib/logitech_receiver/settings_templates.py:363 -msgid "Divert crown events" -msgstr "Omdiriger krone-hendelser" +#: lib/logitech_receiver/settings_templates.py:307 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "Lysnivå på tastaturet i manuell modus." #: lib/logitech_receiver/settings_templates.py:364 -msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "Få kronen til å sende CROWN HID++-varsler (som utløser Solaar-" - "regler, men som ellers ignoreres)." +msgid "Backlight Delay Hands Out" +msgstr "Forsinkelse på bakgrunnsbelysning når hendene fjernes" -#: lib/logitech_receiver/settings_templates.py:372 -msgid "Crown smooth scroll" -msgstr "Krone glatt rulling" +#: lib/logitech_receiver/settings_templates.py:365 +msgid "" +"Delay in seconds until backlight fades out with hands away from keyboard." +msgstr "" +"Forsinkelse i sekunder til bakgrunnsbelysningen toner ut når hendene er " +"borte fra tastaturet." #: lib/logitech_receiver/settings_templates.py:373 -msgid "Set crown smooth scroll" -msgstr "Still inn krone til glatt rulling" +msgid "Backlight Delay Hands In" +msgstr "Forsinkelse på bakgrunnsbelysning når hendene er nære" -#: lib/logitech_receiver/settings_templates.py:381 -msgid "Divert G Keys" -msgstr "Omdiriger G-taster" +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "" +"Forsinkelse i sekunder til bakgrunnsbelysningen toner ut når hendene er nære " +"tastaturet." + +#: lib/logitech_receiver/settings_templates.py:382 +msgid "Backlight Delay Powered" +msgstr "Forsinkelse på bakgrunnsbelysning når strømforsynt" #: lib/logitech_receiver/settings_templates.py:383 -msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "Få G-taster til å sende GKEY HID++-varsler (som utløser Solaar-" - "regler, men som ellers ignoreres)." +msgid "Delay in seconds until backlight fades out with external power." +msgstr "" +"Forsinkelse i sekunder til bakgrunnsbelysningen toner ut med ekstern " +"strømforsyning." -#: lib/logitech_receiver/settings_templates.py:384 -msgid "May also make M keys and MR key send HID++ notifications" -msgstr "Kan også få M-taster og MR-taster til å sende HID++-varsler" +#: lib/logitech_receiver/settings_templates.py:391 +msgid "Backlight (Seconds)" +msgstr "Bakgrunnslys (sekunder)" -#: lib/logitech_receiver/settings_templates.py:399 -msgid "Scroll Wheel Rachet" -msgstr "Skrallen for rullehjulet" +#: lib/logitech_receiver/settings_templates.py:403 +msgid "Scroll Wheel High Resolution" +msgstr "Høyoppløselig rullehjul" -#: lib/logitech_receiver/settings_templates.py:401 -msgid "Automatically switch the mouse wheel between ratchet and freespin " - "mode.\n" - "The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "Bytt automatisk rullehjulet mellom skralle- og frispinn-modus.\n" - "Rullehjulet er alltid fritt ved 0, og med skralle ved 50" +#: lib/logitech_receiver/settings_templates.py:407 +#: lib/logitech_receiver/settings_templates.py:436 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "Sett til å ignorere hvis rullingen er unormalt rask eller sakte" -#: lib/logitech_receiver/settings_templates.py:450 -msgid "Key/Button Actions" -msgstr "Taste-/knappehandling" +#: lib/logitech_receiver/settings_templates.py:414 +#: lib/logitech_receiver/settings_templates.py:445 +msgid "Scroll Wheel Diversion" +msgstr "Omdirigering for rullehjul" -#: lib/logitech_receiver/settings_templates.py:452 -msgid "Change the action for the key or button." -msgstr "Endre handling for en tast eller knapp." +#: lib/logitech_receiver/settings_templates.py:416 +msgid "" +"Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " +"Solaar rules but are otherwise ignored)." +msgstr "" +"Få rullehjulet til å sende LOWRES_WHEEL HID++-varsler (som utløser Solaar-" +"regler, men som ellers ignoreres)." -#: lib/logitech_receiver/settings_templates.py:452 -msgid "Overridden by diversion." -msgstr "Overstyrt av omdirigering." +#: lib/logitech_receiver/settings_templates.py:423 +msgid "Scroll Wheel Direction" +msgstr "Retning på rullehjul" -#: lib/logitech_receiver/settings_templates.py:453 -msgid "Changing important actions (such as for the left mouse button) can " - "result in an unusable system." -msgstr "Endring av viktige handlinger (for eksempel venstre musknapp) kan " - "resultere i et ubrukbart system." +#: lib/logitech_receiver/settings_templates.py:424 +msgid "Invert direction for vertical scroll with wheel." +msgstr "Inverter retning for vertikal rulling med hjul." -#: lib/logitech_receiver/settings_templates.py:622 -msgid "Key/Button Diversion" -msgstr "Taste-/knappeomdirigering" +#: lib/logitech_receiver/settings_templates.py:432 +msgid "Scroll Wheel Resolution" +msgstr "Oppløsning på rullehjul" -#: lib/logitech_receiver/settings_templates.py:623 -msgid "Make the key or button send HID++ notifications (Diverted) or " - "initiate Mouse Gestures or Sliding DPI" -msgstr "Få nøkkelen eller knappen til å sende HID++-varsler (omdirigert) " - "eller initier musebevegelser eller glidende DPI" +#: lib/logitech_receiver/settings_templates.py:447 +msgid "" +"Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"Få rullehjulet til å sende HIRES_WHEEL HID++-varsler (som utløser Solaar-" +"regler, men som ellers ignoreres)." + +#: lib/logitech_receiver/settings_templates.py:456 +msgid "Sensitivity (Pointer Speed)" +msgstr "Følsomhet (pekerfart)" + +#: lib/logitech_receiver/settings_templates.py:457 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Fartsmultiplikator for mus (256 er normal multiplikator)." + +#: lib/logitech_receiver/settings_templates.py:467 +msgid "Thumb Wheel Diversion" +msgstr "Omdirigering for tommelhjul" + +#: lib/logitech_receiver/settings_templates.py:469 +msgid "" +"Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"Få tommelhjulet til å sende THUMB_WHEEL HID++-varsler (som utløser Solaar-" +"regler, men som ellers ignoreres)." + +#: lib/logitech_receiver/settings_templates.py:478 +msgid "Thumb Wheel Direction" +msgstr "Retning på tommelhjul" + +#: lib/logitech_receiver/settings_templates.py:479 +msgid "Invert thumb wheel scroll direction." +msgstr "Inverter rulleretning for tommelhjul." + +#: lib/logitech_receiver/settings_templates.py:499 +msgid "Onboard Profiles" +msgstr "Innebygde profiler" + +#: lib/logitech_receiver/settings_templates.py:500 +msgid "" +"Enable an onboard profile, which controls report rate, sensitivity, and " +"button actions" +msgstr "" +"Aktiver en innebygd profil, som kontrollerer rapporthyppighet, følsomhet og " +"knappehandlinger" + +#: lib/logitech_receiver/settings_templates.py:544 +#: lib/logitech_receiver/settings_templates.py:577 +msgid "Report Rate" +msgstr "Rapporthyppighet" + +#: lib/logitech_receiver/settings_templates.py:546 +#: lib/logitech_receiver/settings_templates.py:579 +msgid "Frequency of device movement reports" +msgstr "Frekvensen til rapporter om enhetsbevegelser" + +#: lib/logitech_receiver/settings_templates.py:546 +#: lib/logitech_receiver/settings_templates.py:579 +#: lib/logitech_receiver/settings_templates.py:1005 +#: lib/logitech_receiver/settings_templates.py:1379 +#: lib/logitech_receiver/settings_templates.py:1410 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" +"Kan trenge at innebygde profiler settes til avslått for å være effektive." + +#: lib/logitech_receiver/settings_templates.py:607 +msgid "Divert crown events" +msgstr "Omdiriger krone-hendelser" + +#: lib/logitech_receiver/settings_templates.py:608 +msgid "" +"Make crown send CROWN HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"Få kronen til å sende CROWN HID++-varsler (som utløser Solaar-regler, men " +"som ellers ignoreres)." + +#: lib/logitech_receiver/settings_templates.py:616 +msgid "Crown smooth scroll" +msgstr "Glatt krone-rulling" + +#: lib/logitech_receiver/settings_templates.py:617 +msgid "Set crown smooth scroll" +msgstr "Still inn glatt krone-rulling" + +#: lib/logitech_receiver/settings_templates.py:625 +msgid "Divert G and M Keys" +msgstr "Omdiriger G- og M-taster" #: lib/logitech_receiver/settings_templates.py:626 -#: lib/logitech_receiver/settings_templates.py:627 -#: lib/logitech_receiver/settings_templates.py:628 -msgid "Diverted" -msgstr "Omdirigert" +msgid "" +"Make G and M keys send HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"Få G- og M-tastene til å sende HID++-varsler (som utløser Solaar-regler, men " +"som ellers ignoreres)." -#: lib/logitech_receiver/settings_templates.py:626 -#: lib/logitech_receiver/settings_templates.py:627 -msgid "Mouse Gestures" -msgstr "Musebevegelser" +#: lib/logitech_receiver/settings_templates.py:640 +msgid "Scroll Wheel Ratcheted" +msgstr "Rullehjul sperrehaket" -#: lib/logitech_receiver/settings_templates.py:626 -#: lib/logitech_receiver/settings_templates.py:627 -#: lib/logitech_receiver/settings_templates.py:628 -msgid "Regular" -msgstr "Vanlig" +#: lib/logitech_receiver/settings_templates.py:641 +msgid "" +"Switch the mouse wheel between speed-controlled ratcheting and always " +"freespin." +msgstr "" +"Bytt musehjulet mellom hastighetskontrollert sperrehake og alltid " +"frittløpende." -#: lib/logitech_receiver/settings_templates.py:626 -msgid "Sliding DPI" -msgstr "Glidende DPI" +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Freespinning" +msgstr "Frittløpende" -#: lib/logitech_receiver/settings_templates.py:694 -msgid "Sensitivity (DPI)" -msgstr "Følsomhet (DPI)" +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Ratcheted" +msgstr "Sperrehake" -#: lib/logitech_receiver/settings_templates.py:734 -msgid "Sensitivity Switching" -msgstr "Følsomhetsbytte" +#: lib/logitech_receiver/settings_templates.py:650 +msgid "Scroll Wheel Ratchet Speed" +msgstr "Rullehjul sperrehakefart" -#: lib/logitech_receiver/settings_templates.py:736 -msgid "Switch the current sensitivity and the remembered sensitivity when " - "the key or button is pressed.\n" - "If there is no remembered sensitivity, just remember the current " - "sensitivity" -msgstr "Bytt gjeldende følsomhet og tidligere følsomhet når tasten eller " - "knappen trykkes.\n" - "Hvis det ikke er noen tidligere følsomhet, sett kun gjeldende " - "følsomhet" +#: lib/logitech_receiver/settings_templates.py:652 +msgid "" +"Use the mouse wheel speed to switch between ratcheted and freespinning.\n" +"The mouse wheel is always ratcheted at 50." +msgstr "" +"Bruk musehjulfarten til å bytte mellom sperrehake og frittløpende.\n" +"Musehjulet er alltid satt til 50." -#: lib/logitech_receiver/settings_templates.py:740 -msgid "Off" -msgstr "Av" +#: lib/logitech_receiver/settings_templates.py:701 +msgid "Key/Button Actions" +msgstr "Taste-/knappehandling" -#: lib/logitech_receiver/settings_templates.py:771 -msgid "Disable keys" -msgstr "Slå av taster" +#: lib/logitech_receiver/settings_templates.py:703 +msgid "Change the action for the key or button." +msgstr "Endre handling for en tast eller knapp." -#: lib/logitech_receiver/settings_templates.py:772 -msgid "Disable specific keyboard keys." -msgstr "Slå av spesifikke tastaturtaster." +#: lib/logitech_receiver/settings_templates.py:705 +msgid "Overridden by diversion." +msgstr "Overstyrt av omdirigering." -#: lib/logitech_receiver/settings_templates.py:775 -#, python-format -msgid "Disables the %s key." -msgstr "Deaktiverer %s-tasten." - -#: lib/logitech_receiver/settings_templates.py:788 -#: lib/logitech_receiver/settings_templates.py:836 -msgid "Set OS" -msgstr "Still inn OS" - -#: lib/logitech_receiver/settings_templates.py:789 -#: lib/logitech_receiver/settings_templates.py:837 -msgid "Change keys to match OS." -msgstr "Endre taster så de stemmer med OS." - -#: lib/logitech_receiver/settings_templates.py:849 -msgid "Change Host" -msgstr "Endre tjener" - -#: lib/logitech_receiver/settings_templates.py:850 -msgid "Switch connection to a different host" -msgstr "Endre koblingen til en annen tjener" - -#: lib/logitech_receiver/settings_templates.py:875 -msgid "Performs a left click." -msgstr "Utfører et venstreklikk." - -#: lib/logitech_receiver/settings_templates.py:875 -msgid "Single tap" -msgstr "Enkeltklikk" - -#: lib/logitech_receiver/settings_templates.py:876 -msgid "Performs a right click." -msgstr "Utfører et høyreklikk." - -#: lib/logitech_receiver/settings_templates.py:876 -msgid "Single tap with two fingers" -msgstr "Enkelt trykk med to fingre" - -#: lib/logitech_receiver/settings_templates.py:877 -msgid "Single tap with three fingers" -msgstr "Enkelt trykk med tre fingre" - -#: lib/logitech_receiver/settings_templates.py:881 -msgid "Double tap" -msgstr "Dobbeltklikk" - -#: lib/logitech_receiver/settings_templates.py:881 -msgid "Performs a double click." -msgstr "Utfører et dobbeltklikk." +#: lib/logitech_receiver/settings_templates.py:707 +msgid "" +"Changing important actions (such as for the left mouse button) can result in " +"an unusable system." +msgstr "" +"Endring av viktige handlinger (for eksempel venstre musknapp) kan resultere " +"i et ubrukbart system." #: lib/logitech_receiver/settings_templates.py:882 -msgid "Double tap with two fingers" -msgstr "Dobbeltklikk med to fingre" +msgid "Key/Button Diversion" +msgstr "Taste-/knappeomdirigering" #: lib/logitech_receiver/settings_templates.py:883 -msgid "Double tap with three fingers" -msgstr "Dobbeltklikk med tre fingre" +msgid "" +"Make the key or button send HID++ notifications (Diverted) or initiate Mouse " +"Gestures or Sliding DPI" +msgstr "" +"Få nøkkelen eller knappen til å sende HID++-varsler (omdirigert) eller " +"initier musebevegelser eller glidende DPI" #: lib/logitech_receiver/settings_templates.py:886 -msgid "Drags items by dragging the finger after double tapping." -msgstr "Dra elementer med fingeren etter dobbeltklikk." +#: lib/logitech_receiver/settings_templates.py:887 +#: lib/logitech_receiver/settings_templates.py:888 +msgid "Diverted" +msgstr "Omdirigert" #: lib/logitech_receiver/settings_templates.py:886 -msgid "Tap and drag" -msgstr "Klikk og dra" +#: lib/logitech_receiver/settings_templates.py:887 +msgid "Mouse Gestures" +msgstr "Musebevegelser" +#: lib/logitech_receiver/settings_templates.py:886 +#: lib/logitech_receiver/settings_templates.py:887 #: lib/logitech_receiver/settings_templates.py:888 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "Dra elementer med fingrene etter dobbeltklikk." +msgid "Regular" +msgstr "Vanlig" -#: lib/logitech_receiver/settings_templates.py:888 -msgid "Tap and drag with two fingers" -msgstr "Klikk og dra med to fingre" +#: lib/logitech_receiver/settings_templates.py:886 +msgid "Sliding DPI" +msgstr "Glidende DPI" -#: lib/logitech_receiver/settings_templates.py:889 -msgid "Tap and drag with three fingers" -msgstr "Klikk og dra med tre fingre" +#: lib/logitech_receiver/settings_templates.py:976 +#: lib/logitech_receiver/settings_templates.py:1004 +msgid "Sensitivity (DPI)" +msgstr "Følsomhet (DPI)" -#: lib/logitech_receiver/settings_templates.py:892 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "Deaktiverer klikk- og kantbevegelser (tilsvarer å trykke " - "Fn+Venstreklikk)." +#: lib/logitech_receiver/settings_templates.py:1081 +msgid "Sensitivity Switching" +msgstr "Følsomhetsbytte" -#: lib/logitech_receiver/settings_templates.py:892 -msgid "Suppress tap and edge gestures" -msgstr "Undertrykk klikk- og kantbevegelser" +#: lib/logitech_receiver/settings_templates.py:1083 +msgid "" +"Switch the current sensitivity and the remembered sensitivity when the key " +"or button is pressed.\n" +"If there is no remembered sensitivity, just remember the current sensitivity" +msgstr "" +"Bytt gjeldende følsomhet og tidligere følsomhet når tasten eller knappen " +"trykkes.\n" +"Hvis det ikke er noen tidligere følsomhet, sett kun gjeldende følsomhet" -#: lib/logitech_receiver/settings_templates.py:893 -msgid "Scroll with one finger" -msgstr "Rull med en finger" +#: lib/logitech_receiver/settings_templates.py:1087 +msgid "Off" +msgstr "Av" -#: lib/logitech_receiver/settings_templates.py:893 -#: lib/logitech_receiver/settings_templates.py:894 -#: lib/logitech_receiver/settings_templates.py:897 -msgid "Scrolls." -msgstr "Ruller." +#: lib/logitech_receiver/settings_templates.py:1118 +msgid "Disable keys" +msgstr "Slå av taster" -#: lib/logitech_receiver/settings_templates.py:894 -#: lib/logitech_receiver/settings_templates.py:897 -msgid "Scroll with two fingers" -msgstr "Rull med to fingre" +#: lib/logitech_receiver/settings_templates.py:1119 +msgid "Disable specific keyboard keys." +msgstr "Slå av spesifikke tastaturtaster." -#: lib/logitech_receiver/settings_templates.py:895 -msgid "Scroll horizontally with two fingers" -msgstr "Rull horisontalt med to fingre" - -#: lib/logitech_receiver/settings_templates.py:895 -msgid "Scrolls horizontally." -msgstr "Rull horisontalt." - -#: lib/logitech_receiver/settings_templates.py:896 -msgid "Scroll vertically with two fingers" -msgstr "Rull vertikalt med to fingre" - -#: lib/logitech_receiver/settings_templates.py:896 -msgid "Scrolls vertically." -msgstr "Rull vertikalt." - -#: lib/logitech_receiver/settings_templates.py:898 -msgid "Inverts the scrolling direction." -msgstr "Inverterer rulleretningen." - -#: lib/logitech_receiver/settings_templates.py:898 -msgid "Natural scrolling" -msgstr "Naturlig rulling" - -#: lib/logitech_receiver/settings_templates.py:899 -msgid "Enables the thumbwheel." -msgstr "Aktiverer tommelhjulet." - -#: lib/logitech_receiver/settings_templates.py:899 -msgid "Thumbwheel" -msgstr "Tommelhjul" - -#: lib/logitech_receiver/settings_templates.py:910 -#: lib/logitech_receiver/settings_templates.py:914 -msgid "Swipe from the top edge" -msgstr "Sveip fra øvre kant" - -#: lib/logitech_receiver/settings_templates.py:911 -msgid "Swipe from the left edge" -msgstr "Sveip fra venstre kant" - -#: lib/logitech_receiver/settings_templates.py:912 -msgid "Swipe from the right edge" -msgstr "Sveip fra høyre kant" - -#: lib/logitech_receiver/settings_templates.py:913 -msgid "Swipe from the bottom edge" -msgstr "Sveip fra nedre kant" - -#: lib/logitech_receiver/settings_templates.py:915 -msgid "Swipe two fingers from the left edge" -msgstr "Sveip to fingre fra venstre kant" - -#: lib/logitech_receiver/settings_templates.py:916 -msgid "Swipe two fingers from the right edge" -msgstr "Sveip to fingre fra høyre kant" - -#: lib/logitech_receiver/settings_templates.py:917 -msgid "Swipe two fingers from the bottom edge" -msgstr "Sveip to fingre fra nedre kant" - -#: lib/logitech_receiver/settings_templates.py:918 -msgid "Swipe two fingers from the top edge" -msgstr "Sveip to fingre fra øvre kant" - -#: lib/logitech_receiver/settings_templates.py:919 -#: lib/logitech_receiver/settings_templates.py:923 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "Knip for å zoome ut; spre for å zoome inn." - -#: lib/logitech_receiver/settings_templates.py:919 -msgid "Zoom with two fingers." -msgstr "Zoom med to fingre." - -#: lib/logitech_receiver/settings_templates.py:920 -msgid "Pinch to zoom out." -msgstr "Knip for å zoome ut." - -#: lib/logitech_receiver/settings_templates.py:921 -msgid "Spread to zoom in." -msgstr "Spre for å zoome inn." - -#: lib/logitech_receiver/settings_templates.py:922 -msgid "Zoom with three fingers." -msgstr "Zoom med tre fingre" - -#: lib/logitech_receiver/settings_templates.py:923 -msgid "Zoom with two fingers" -msgstr "Zoom med to fingre" - -#: lib/logitech_receiver/settings_templates.py:941 -msgid "Pixel zone" -msgstr "Pikselsone" - -#: lib/logitech_receiver/settings_templates.py:942 -msgid "Ratio zone" -msgstr "Forholdssone" - -#: lib/logitech_receiver/settings_templates.py:943 -msgid "Scale factor" -msgstr "Skalafaktor" - -#: lib/logitech_receiver/settings_templates.py:943 -msgid "Sets the cursor speed." -msgstr "Still inn markørhastigheten." - -#: lib/logitech_receiver/settings_templates.py:947 -msgid "Left" -msgstr "Venstre" - -#: lib/logitech_receiver/settings_templates.py:947 -msgid "Left-most coordinate." -msgstr "Koordinat lengst til venstre." - -#: lib/logitech_receiver/settings_templates.py:948 -msgid "Bottom" -msgstr "Nederst" - -#: lib/logitech_receiver/settings_templates.py:948 -msgid "Bottom coordinate." -msgstr "Nederste koordinat." - -#: lib/logitech_receiver/settings_templates.py:949 -msgid "Width" -msgstr "Bredde" - -#: lib/logitech_receiver/settings_templates.py:949 -msgid "Width." -msgstr "Bredde." - -#: lib/logitech_receiver/settings_templates.py:950 -msgid "Height" -msgstr "Høyde" - -#: lib/logitech_receiver/settings_templates.py:950 -msgid "Height." -msgstr "Høyde." - -#: lib/logitech_receiver/settings_templates.py:951 -msgid "Cursor speed." -msgstr "Markørhastighet." - -#: lib/logitech_receiver/settings_templates.py:951 -msgid "Scale" -msgstr "Skala" - -#: lib/logitech_receiver/settings_templates.py:957 -msgid "Gestures" -msgstr "Bevegelser" - -#: lib/logitech_receiver/settings_templates.py:958 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "Juster musens/berøringsplatens oppførsel." - -#: lib/logitech_receiver/settings_templates.py:974 -msgid "Gestures Diversion" -msgstr "Omdirigering av bevegelser" - -#: lib/logitech_receiver/settings_templates.py:975 -msgid "Divert mouse/touchpad gestures." -msgstr "Omdiriger mus/berøringsflatebevegelser." - -#: lib/logitech_receiver/settings_templates.py:991 -msgid "Gesture params" -msgstr "Parametre for bevegelser" - -#: lib/logitech_receiver/settings_templates.py:992 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "Endre numeriske parametere for en mus/berøringsplate." - -#: lib/logitech_receiver/settings_templates.py:1016 -msgid "M-Key LEDs" -msgstr "M-tast LED-ene" - -#: lib/logitech_receiver/settings_templates.py:1018 -msgid "Control the M-Key LEDs." -msgstr "Kontroller M-tast LED-ene." - -#: lib/logitech_receiver/settings_templates.py:1019 -#: lib/logitech_receiver/settings_templates.py:1047 -msgid "May need G Keys diverted to be effective." -msgstr "Kan trenge omdirigering av G-taster for å være effektivt." - -#: lib/logitech_receiver/settings_templates.py:1025 +#: lib/logitech_receiver/settings_templates.py:1122 #, python-format -msgid "Lights up the %s key." -msgstr "Lyser opp %s-tasten." - -#: lib/logitech_receiver/settings_templates.py:1044 -msgid "MR-Key LED" -msgstr "MR-tast LED" - -#: lib/logitech_receiver/settings_templates.py:1046 -msgid "Control the MR-Key LED." -msgstr "Kontroller MR-tast LED." - -#: lib/logitech_receiver/settings_templates.py:1064 -msgid "Persistent Key/Button Mapping" -msgstr "Vedvarende tast-/knapptilordning" - -#: lib/logitech_receiver/settings_templates.py:1066 -msgid "Permanently change the mapping for the key or button." -msgstr "Endre tilordningen for tasten eller knappen permanent." - -#: lib/logitech_receiver/settings_templates.py:1067 -msgid "Changing important keys or buttons (such as for the left mouse " - "button) can result in an unusable system." -msgstr "Endring av viktige taster eller knapper (som venstre museknapp) kan " - "resultere i et ubrukelig system." - -#: lib/logitech_receiver/settings_templates.py:1124 -msgid "Sidetone" -msgstr "Sidetone" - -#: lib/logitech_receiver/settings_templates.py:1125 -msgid "Set sidetone level." -msgstr "Still inn sidetone nivå." - -#: lib/logitech_receiver/settings_templates.py:1134 -msgid "Equalizer" -msgstr "Equalizer" +msgid "Disables the %s key." +msgstr "Slår av %s-tasten." #: lib/logitech_receiver/settings_templates.py:1135 -msgid "Set equalizer levels." -msgstr "Still inn equalizer nivå." +#: lib/logitech_receiver/settings_templates.py:1192 +msgid "Set OS" +msgstr "Still OS" -#: lib/logitech_receiver/settings_templates.py:1157 -msgid "Hz" -msgstr "Hz" +#: lib/logitech_receiver/settings_templates.py:1136 +#: lib/logitech_receiver/settings_templates.py:1193 +msgid "Change keys to match OS." +msgstr "Endre taster så de stemmer med OS." -#: lib/logitech_receiver/status.py:113 -msgid "No paired devices." -msgstr "Ingen paret enhet." +#: lib/logitech_receiver/settings_templates.py:1205 +msgid "Change Host" +msgstr "Endre tjener" -#: lib/logitech_receiver/status.py:114 lib/solaar/ui/window.py:620 +#: lib/logitech_receiver/settings_templates.py:1206 +msgid "Switch connection to a different host" +msgstr "Endre koblingen til en annen tjener" + +#: lib/logitech_receiver/settings_templates.py:1230 +msgid "Performs a left click." +msgstr "Utfører et venstreklikk." + +#: lib/logitech_receiver/settings_templates.py:1230 +msgid "Single tap" +msgstr "Enkeltklikk" + +#: lib/logitech_receiver/settings_templates.py:1231 +msgid "Performs a right click." +msgstr "Utfører et høyreklikk." + +#: lib/logitech_receiver/settings_templates.py:1231 +msgid "Single tap with two fingers" +msgstr "Enkelt trykk med to fingre" + +#: lib/logitech_receiver/settings_templates.py:1232 +msgid "Single tap with three fingers" +msgstr "Enkelt trykk med tre fingre" + +#: lib/logitech_receiver/settings_templates.py:1236 +msgid "Double tap" +msgstr "Dobbeltklikk" + +#: lib/logitech_receiver/settings_templates.py:1236 +msgid "Performs a double click." +msgstr "Utfører et dobbeltklikk." + +#: lib/logitech_receiver/settings_templates.py:1237 +msgid "Double tap with two fingers" +msgstr "Dobbeltklikk med to fingre" + +#: lib/logitech_receiver/settings_templates.py:1238 +msgid "Double tap with three fingers" +msgstr "Dobbeltklikk med tre fingre" + +#: lib/logitech_receiver/settings_templates.py:1241 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Dra elementer med fingeren etter dobbeltklikk." + +#: lib/logitech_receiver/settings_templates.py:1241 +msgid "Tap and drag" +msgstr "Klikk og dra" + +#: lib/logitech_receiver/settings_templates.py:1243 +msgid "Tap and drag with two fingers" +msgstr "Klikk og dra med to fingre" + +#: lib/logitech_receiver/settings_templates.py:1244 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Dra elementer med fingrene etter dobbeltklikk." + +#: lib/logitech_receiver/settings_templates.py:1246 +msgid "Tap and drag with three fingers" +msgstr "Klikk og dra med tre fingre" + +#: lib/logitech_receiver/settings_templates.py:1249 +msgid "Suppress tap and edge gestures" +msgstr "Undertrykk klikk- og kantbevegelser" + +#: lib/logitech_receiver/settings_templates.py:1250 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "Slår av klikk- og kantbevegelser (tilsvarer å trykke Fn+Venstreklikk)." + +#: lib/logitech_receiver/settings_templates.py:1252 +msgid "Scroll with one finger" +msgstr "Rull med en finger" + +#: lib/logitech_receiver/settings_templates.py:1252 +#: lib/logitech_receiver/settings_templates.py:1253 +#: lib/logitech_receiver/settings_templates.py:1256 +msgid "Scrolls." +msgstr "Ruller." + +#: lib/logitech_receiver/settings_templates.py:1253 +#: lib/logitech_receiver/settings_templates.py:1256 +msgid "Scroll with two fingers" +msgstr "Rull med to fingre" + +#: lib/logitech_receiver/settings_templates.py:1254 +msgid "Scroll horizontally with two fingers" +msgstr "Rull horisontalt med to fingre" + +#: lib/logitech_receiver/settings_templates.py:1254 +msgid "Scrolls horizontally." +msgstr "Rull horisontalt." + +#: lib/logitech_receiver/settings_templates.py:1255 +msgid "Scroll vertically with two fingers" +msgstr "Rull vertikalt med to fingre" + +#: lib/logitech_receiver/settings_templates.py:1255 +msgid "Scrolls vertically." +msgstr "Rull vertikalt." + +#: lib/logitech_receiver/settings_templates.py:1257 +msgid "Inverts the scrolling direction." +msgstr "Inverterer rulleretningen." + +#: lib/logitech_receiver/settings_templates.py:1257 +msgid "Natural scrolling" +msgstr "Naturlig rulling" + +#: lib/logitech_receiver/settings_templates.py:1258 +msgid "Enables the thumbwheel." +msgstr "Aktiverer tommelhjulet." + +#: lib/logitech_receiver/settings_templates.py:1258 +msgid "Thumbwheel" +msgstr "Tommelhjul" + +#: lib/logitech_receiver/settings_templates.py:1269 +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Swipe from the top edge" +msgstr "Sveip fra øvre kant" + +#: lib/logitech_receiver/settings_templates.py:1270 +msgid "Swipe from the left edge" +msgstr "Sveip fra venstre kant" + +#: lib/logitech_receiver/settings_templates.py:1271 +msgid "Swipe from the right edge" +msgstr "Sveip fra høyre kant" + +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Swipe from the bottom edge" +msgstr "Sveip fra nedre kant" + +#: lib/logitech_receiver/settings_templates.py:1274 +msgid "Swipe two fingers from the left edge" +msgstr "Sveip to fingre fra venstre kant" + +#: lib/logitech_receiver/settings_templates.py:1275 +msgid "Swipe two fingers from the right edge" +msgstr "Sveip to fingre fra høyre kant" + +#: lib/logitech_receiver/settings_templates.py:1276 +msgid "Swipe two fingers from the bottom edge" +msgstr "Sveip to fingre fra nedre kant" + +#: lib/logitech_receiver/settings_templates.py:1277 +msgid "Swipe two fingers from the top edge" +msgstr "Sveip to fingre fra øvre kant" + +#: lib/logitech_receiver/settings_templates.py:1278 +#: lib/logitech_receiver/settings_templates.py:1282 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "Knip for å zoome ut; spre for å zoome inn." + +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Zoom with two fingers." +msgstr "Zoom med to fingre." + +#: lib/logitech_receiver/settings_templates.py:1279 +msgid "Pinch to zoom out." +msgstr "Knip for å zoome ut." + +#: lib/logitech_receiver/settings_templates.py:1280 +msgid "Spread to zoom in." +msgstr "Spre for å zoome inn." + +#: lib/logitech_receiver/settings_templates.py:1281 +msgid "Zoom with three fingers." +msgstr "Zoom med tre fingre." + +#: lib/logitech_receiver/settings_templates.py:1282 +msgid "Zoom with two fingers" +msgstr "Zoom med to fingre" + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Pixel zone" +msgstr "Pikselsone" + +#: lib/logitech_receiver/settings_templates.py:1301 +msgid "Ratio zone" +msgstr "Forholdssone" + +#: lib/logitech_receiver/settings_templates.py:1302 +msgid "Scale factor" +msgstr "Skalafaktor" + +#: lib/logitech_receiver/settings_templates.py:1302 +msgid "Sets the cursor speed." +msgstr "Still inn markørhastigheten." + +#: lib/logitech_receiver/settings_templates.py:1306 +msgid "Left" +msgstr "Venstre" + +#: lib/logitech_receiver/settings_templates.py:1306 +msgid "Left-most coordinate." +msgstr "Koordinat lengst til venstre." + +#: lib/logitech_receiver/settings_templates.py:1307 +msgid "Bottom" +msgstr "Nederst" + +#: lib/logitech_receiver/settings_templates.py:1307 +msgid "Bottom coordinate." +msgstr "Nederste koordinat." + +#: lib/logitech_receiver/settings_templates.py:1308 +msgid "Width" +msgstr "Bredde" + +#: lib/logitech_receiver/settings_templates.py:1308 +msgid "Width." +msgstr "Bredde." + +#: lib/logitech_receiver/settings_templates.py:1309 +msgid "Height" +msgstr "Høyde" + +#: lib/logitech_receiver/settings_templates.py:1309 +msgid "Height." +msgstr "Høyde." + +#: lib/logitech_receiver/settings_templates.py:1310 +msgid "Cursor speed." +msgstr "Markørhastighet." + +#: lib/logitech_receiver/settings_templates.py:1310 +msgid "Scale" +msgstr "Skala" + +#: lib/logitech_receiver/settings_templates.py:1316 +msgid "Gestures" +msgstr "Bevegelser" + +#: lib/logitech_receiver/settings_templates.py:1317 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Juster musens/berøringsplatens oppførsel." + +#: lib/logitech_receiver/settings_templates.py:1333 +msgid "Gestures Diversion" +msgstr "Omdirigering av bevegelser" + +#: lib/logitech_receiver/settings_templates.py:1334 +msgid "Divert mouse/touchpad gestures." +msgstr "Omdiriger musens/berøringsflatens bevegelser." + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Gesture params" +msgstr "Parametre for bevegelser" + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Endre numeriske parametere for en mus/berøringsplate." + +#: lib/logitech_receiver/settings_templates.py:1375 +msgid "M-Key LEDs" +msgstr "M-tast LED-ene" + +#: lib/logitech_receiver/settings_templates.py:1377 +msgid "Control the M-Key LEDs." +msgstr "Kontroller M-tast LED-ene." + +#: lib/logitech_receiver/settings_templates.py:1381 +#: lib/logitech_receiver/settings_templates.py:1412 +msgid "May need G Keys diverted to be effective." +msgstr "Kan trenge omdirigering av G-taster for å være effektivt." + +#: lib/logitech_receiver/settings_templates.py:1387 #, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s paret enhet." -msgstr[1] "%(count)s parede enheter." +msgid "Lights up the %s key." +msgstr "Lyser opp %s-tasten." -#: lib/logitech_receiver/status.py:167 +#: lib/logitech_receiver/settings_templates.py:1406 +msgid "MR-Key LED" +msgstr "MR-tast LED" + +#: lib/logitech_receiver/settings_templates.py:1408 +msgid "Control the MR-Key LED." +msgstr "Kontroller MR-tast LED." + +#: lib/logitech_receiver/settings_templates.py:1429 +msgid "Persistent Key/Button Mapping" +msgstr "Vedvarende tast-/knapptilordning" + +#: lib/logitech_receiver/settings_templates.py:1431 +msgid "Permanently change the mapping for the key or button." +msgstr "Endre tilordningen for tasten eller knappen permanent." + +#: lib/logitech_receiver/settings_templates.py:1433 +msgid "" +"Changing important keys or buttons (such as for the left mouse button) can " +"result in an unusable system." +msgstr "" +"Endring av viktige taster eller knapper (som venstre museknapp) kan " +"resultere i et ubrukelig system." + +#: lib/logitech_receiver/settings_templates.py:1490 +msgid "Sidetone" +msgstr "Sidetone" + +#: lib/logitech_receiver/settings_templates.py:1491 +msgid "Set sidetone level." +msgstr "Still inn sidetone nivå." + +#: lib/logitech_receiver/settings_templates.py:1500 +msgid "Equalizer" +msgstr "Equalizer" + +#: lib/logitech_receiver/settings_templates.py:1501 +msgid "Set equalizer levels." +msgstr "Still inn equalizer nivå." + +#: lib/logitech_receiver/settings_templates.py:1523 +msgid "Hz" +msgstr "Hz" + +#: lib/logitech_receiver/settings_templates.py:1529 +msgid "Power Management" +msgstr "Strømstyring" + +#: lib/logitech_receiver/settings_templates.py:1530 +msgid "Power off in minutes (0 for never)." +msgstr "Slå av i minutter (0 for aldri)." + +#: lib/logitech_receiver/settings_templates.py:1541 +msgid "Brightness Control" +msgstr "Lysstyrkekontroll" + +#: lib/logitech_receiver/settings_templates.py:1542 +msgid "Control overall brightness" +msgstr "Kontroller den generelle lysstyrken" + +#: lib/logitech_receiver/settings_templates.py:1585 +#: lib/logitech_receiver/settings_templates.py:1639 +msgid "LED Control" +msgstr "LED-kontroll" + +#: lib/logitech_receiver/settings_templates.py:1586 +#: lib/logitech_receiver/settings_templates.py:1640 +msgid "Switch control of LED zones between device and Solaar" +msgstr "Bytt kontroll av LED-soner mellom enhet og Solaar" + +#: lib/logitech_receiver/settings_templates.py:1601 +#: lib/logitech_receiver/settings_templates.py:1650 +msgid "LED Zone Effects" +msgstr "LED-soneeffekter" + +#: lib/logitech_receiver/settings_templates.py:1602 +#: lib/logitech_receiver/settings_templates.py:1651 +msgid "LED Control needs to be set to Solaar to be effective." +msgstr "LED-kontroll må settes til Solaar for å være effektiv." + +#: lib/logitech_receiver/settings_templates.py:1602 +#: lib/logitech_receiver/settings_templates.py:1651 +msgid "Set effect for LED Zone" +msgstr "Still inn effekt for LED-sone" + +#: lib/logitech_receiver/settings_templates.py:1605 +msgid "Speed" +msgstr "Hastighet" + +#: lib/logitech_receiver/settings_templates.py:1606 +msgid "Period" +msgstr "Periode" + +#: lib/logitech_receiver/settings_templates.py:1607 +msgid "Intensity" +msgstr "Intensitet" + +#: lib/logitech_receiver/settings_templates.py:1608 +msgid "Ramp" +msgstr "Stigning" + +#: lib/logitech_receiver/settings_templates.py:1624 +msgid "LEDs" +msgstr "LEDs" + +#: lib/logitech_receiver/settings_templates.py:1661 +msgid "Per-key Lighting" +msgstr "Lys for hver tast" + +#: lib/logitech_receiver/settings_templates.py:1662 +msgid "Control per-key lighting." +msgstr "Kontroller lys for hver tast." + +#: lib/solaar/ui/__init__.py:117 +msgid "Another Solaar process is already running so just expose its window" +msgstr "En annen Solaar-prosess kjører allerede, så bare vis vinduet" + +#: lib/solaar/ui/about/model.py:36 +msgid "" +"Manages Logitech receivers,\n" +"keyboards, mice, and tablets." +msgstr "" +"Administrerer Logitech-mottakere,\n" +"tastaturer, mus og nettbrett." + +#: lib/solaar/ui/about/model.py:62 +msgid "Additional Programming" +msgstr "Ekstra programmering" + +#: lib/solaar/ui/about/model.py:63 +msgid "GUI design" +msgstr "GUI utseende" + +#: lib/solaar/ui/about/model.py:65 +msgid "Testing" +msgstr "Testing" + +#: lib/solaar/ui/about/model.py:73 +msgid "Logitech documentation" +msgstr "Logitech dokumentasjon" + +#: lib/solaar/ui/action.py:83 lib/solaar/ui/action.py:87 +#: lib/solaar/ui/window.py:188 +msgid "Unpair" +msgstr "Fjern paring" + +#: lib/solaar/ui/action.py:86 lib/solaar/ui/diversion_rules.py:107 +msgid "Cancel" +msgstr "Avbryt" + +#: lib/solaar/ui/common.py:35 +msgid "Permissions error" +msgstr "Rettighetsfeil" + +#: lib/solaar/ui/common.py:37 #, python-format -msgid "Battery: %(level)s" -msgstr "Batteri: %(level)s" +msgid "" +"Found a Logitech receiver or device (%s), but did not have permission to " +"open it." +msgstr "" +"Fant en Logitech-mottaker eller enhet (%s), men hadde ikke tillatelse til å " +"åpne den." -#: lib/logitech_receiver/status.py:169 +#: lib/solaar/ui/common.py:39 +msgid "" +"If you've just installed Solaar, try disconnecting the receiver or device " +"and then reconnecting it." +msgstr "" +"Hvis du nettopp har installert Solaar, kan du prøve å koble fra mottakeren " +"eller enheten og deretter koble den til igjen." + +#: lib/solaar/ui/common.py:42 +msgid "Cannot connect to device error" +msgstr "Kan ikke koble til enhet-feil" + +#: lib/solaar/ui/common.py:44 #, python-format -msgid "Battery: %(percent)d%%" -msgstr "Batteri: %(percent)d%%" +msgid "" +"Found a Logitech receiver or device at %s, but encountered an error " +"connecting to it." +msgstr "" +"Fant en Logitech-mottaker eller -enhet på %s, men oppdaget en feil når den " +"ble tilkoblet." -#: lib/logitech_receiver/status.py:181 -#, python-format -msgid "Lighting: %(level)s lux" -msgstr "Belysning: %(level)s lux" +#: lib/solaar/ui/common.py:46 +msgid "" +"Try disconnecting the device and then reconnecting it or turning it off and " +"then on." +msgstr "" +"Prøv å koble fra enheten og deretter koble den til igjen eller slå den av og " +"på igjen." -#: lib/logitech_receiver/status.py:236 -#, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "Batteri: %(level)s (%(status)s)" +#: lib/solaar/ui/common.py:49 +msgid "Unpairing failed" +msgstr "Kunne ikke bryte paringen" -#: lib/logitech_receiver/status.py:238 -#, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "Batteri: %(percent)d%% (%(status)s)" - -#: lib/solaar/ui/__init__.py:51 -msgid "Permissions error" -msgstr "Rettighetsfeil" - -#: lib/solaar/ui/__init__.py:53 -#, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open " - "it." -msgstr "Fant en Logitech-mottaker (%s), men mangler rettigheter til å åpne " - "den." - -#: lib/solaar/ui/__init__.py:54 -msgid "If you've just installed Solaar, try removing the receiver and " - "plugging it back in." -msgstr "Hvis du nettopp har installert Solaar, forsøk å fjerne mottakeren og " - "plugg den inn igjen." - -#: lib/solaar/ui/__init__.py:57 -msgid "Cannot connect to device error" -msgstr "Kan ikke koble til enhet-feil" - -#: lib/solaar/ui/__init__.py:59 -#, python-format -msgid "Found a Logitech receiver or device at %s, but encountered an error " - "connecting to it." -msgstr "Fant en Logitech-mottaker eller -enhet på %s, men oppdaget en feil " - "når den ble tilkoblet." - -#: lib/solaar/ui/__init__.py:60 -msgid "Try removing the device and plugging it back in or turning it off " - "and then on." -msgstr "Prøv å fjerne enheten og koble den til igjen eller slå den av og på." - -#: lib/solaar/ui/__init__.py:63 -msgid "Unpairing failed" -msgstr "Kunne ikke bryte paringen" - -#: lib/solaar/ui/__init__.py:65 +#: lib/solaar/ui/common.py:51 #, python-brace-format -msgid "Failed to unpair %{device} from %{receiver}." -msgstr "Mislyktes med å bryte paret %{device} og %{receiver}." +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Mislyktes med å bryte paret %{device} og %{receiver}." -#: lib/solaar/ui/__init__.py:66 -msgid "The receiver returned an error, with no further details." -msgstr "Mottakeren rapporterte en feil, uten flere detaljer." +#: lib/solaar/ui/common.py:56 +msgid "The receiver returned an error, with no further details." +msgstr "Mottakeren rapporterte en feil, uten flere detaljer." -#: lib/solaar/ui/__init__.py:176 -msgid "Another Solaar process is already running so just expose its window" -msgstr "En annen Solaar-prosess kjører allerede, så bare vis vinduet" +#: lib/solaar/ui/config_panel.py:228 +msgid "Complete - ENTER to change" +msgstr "Fullfør - ENTER for å endre" -#: lib/solaar/ui/about.py:36 -msgid "Manages Logitech receivers,\n" - "keyboards, mice, and tablets." -msgstr "Administrerer Logitech-mottakere,\n" - "tastaturer, mus og nettbrett." +#: lib/solaar/ui/config_panel.py:228 +msgid "Incomplete" +msgstr "Ufullstendig" -#: lib/solaar/ui/about.py:44 -msgid "Additional Programming" -msgstr "Ekstra programmering" - -#: lib/solaar/ui/about.py:45 -msgid "GUI design" -msgstr "GUI utseende" - -#: lib/solaar/ui/about.py:47 -msgid "Testing" -msgstr "Testing" - -#: lib/solaar/ui/about.py:54 -msgid "Logitech documentation" -msgstr "Logitech dokumentasjon" - -#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 -#: lib/solaar/ui/window.py:202 -msgid "Unpair" -msgstr "Fjern paring" - -#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:148 -msgid "Cancel" -msgstr "Avbryt" - -#: lib/solaar/ui/config_panel.py:205 -msgid "Complete - ENTER to change" -msgstr "Fullfør - ENTER for å endre" - -#: lib/solaar/ui/config_panel.py:205 -msgid "Incomplete" -msgstr "Ufullstendig" - -#: lib/solaar/ui/config_panel.py:444 lib/solaar/ui/config_panel.py:495 +#: lib/solaar/ui/config_panel.py:475 lib/solaar/ui/config_panel.py:527 #, python-format -msgid "%d value" -msgid_plural "%d values" -msgstr[0] "%d verdi" -msgstr[1] "%d verdier" +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d verdi" +msgstr[1] "%d verdier" -#: lib/solaar/ui/config_panel.py:506 -msgid "Changes allowed" -msgstr "Endringer tillatt" +#: lib/solaar/ui/config_panel.py:609 +msgid "Changes allowed" +msgstr "Endringer tillatt" -#: lib/solaar/ui/config_panel.py:507 -msgid "No changes allowed" -msgstr "Ingen endringer tillatt" +#: lib/solaar/ui/config_panel.py:610 +msgid "No changes allowed" +msgstr "Ingen endringer tillatt" -#: lib/solaar/ui/config_panel.py:508 -msgid "Ignore this setting" -msgstr "Ignorer denne innstillingen" +#: lib/solaar/ui/config_panel.py:611 +msgid "Ignore this setting" +msgstr "Ignorer denne innstillingen" -#: lib/solaar/ui/config_panel.py:553 -msgid "Working" -msgstr "Suksess" +#: lib/solaar/ui/config_panel.py:655 +msgid "Working" +msgstr "Suksess" -#: lib/solaar/ui/config_panel.py:556 -msgid "Read/write operation failed." -msgstr "Lese-/skriveoperasjonen mislyktes." +#: lib/solaar/ui/config_panel.py:658 +msgid "Read/write operation failed." +msgstr "Lese-/skriveoperasjonen mislyktes." -#: lib/solaar/ui/diversion_rules.py:64 -msgid "Built-in rules" -msgstr "Faste regler" - -#: lib/solaar/ui/diversion_rules.py:64 -msgid "User-defined rules" -msgstr "Brukerdefinerte regler" - -#: lib/solaar/ui/diversion_rules.py:66 lib/solaar/ui/diversion_rules.py:1074 -msgid "Rule" -msgstr "Regel" - -#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:507 -#: lib/solaar/ui/diversion_rules.py:630 -msgid "Sub-rule" -msgstr "Underregel" +#: lib/solaar/ui/desktop_notifications.py:114 +msgid "unspecified reason" +msgstr "uspesifisert årsak" #: lib/solaar/ui/diversion_rules.py:69 -msgid "[empty]" -msgstr "[tom]" +msgid "Built-in rules" +msgstr "Faste regler" -#: lib/solaar/ui/diversion_rules.py:92 -msgid "Solaar Rule Editor" -msgstr "Solaar regel editor" +#: lib/solaar/ui/diversion_rules.py:69 +msgid "User-defined rules" +msgstr "Brukerdefinerte regler" -#: lib/solaar/ui/diversion_rules.py:139 -msgid "Make changes permanent?" -msgstr "Gjøre endringer permanente?" +#: lib/solaar/ui/diversion_rules.py:71 lib/solaar/ui/diversion_rules.py:1089 +msgid "Rule" +msgstr "Regel" -#: lib/solaar/ui/diversion_rules.py:144 -msgid "Yes" -msgstr "Ja" +#: lib/solaar/ui/diversion_rules.py:72 lib/solaar/ui/diversion_rules.py:348 +#: lib/solaar/ui/diversion_rules.py:475 +msgid "Sub-rule" +msgstr "Underregel" -#: lib/solaar/ui/diversion_rules.py:146 -msgid "No" -msgstr "Nei" +#: lib/solaar/ui/diversion_rules.py:74 +msgid "[empty]" +msgstr "[tom]" -#: lib/solaar/ui/diversion_rules.py:151 -msgid "If you choose No, changes will be lost when Solaar is closed." -msgstr "Hvis du velger Nei, vil endringer gå tapt når Solaar lukkes." +#: lib/solaar/ui/diversion_rules.py:98 +msgid "Make changes permanent?" +msgstr "Gjøre endringer permanente?" -#: lib/solaar/ui/diversion_rules.py:199 -msgid "Save changes" -msgstr "Lagre endringer" +#: lib/solaar/ui/diversion_rules.py:103 +msgid "Yes" +msgstr "Ja" -#: lib/solaar/ui/diversion_rules.py:204 -msgid "Discard changes" -msgstr "Forkast endringer" +#: lib/solaar/ui/diversion_rules.py:105 +msgid "No" +msgstr "Nei" -#: lib/solaar/ui/diversion_rules.py:370 -msgid "Insert here" -msgstr "Sett inn her" +#: lib/solaar/ui/diversion_rules.py:110 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "Hvis du velger Nei, vil endringer gå tapt når Solaar lukkes." -#: lib/solaar/ui/diversion_rules.py:372 -msgid "Insert above" -msgstr "Sett inn ovenfor" +#: lib/solaar/ui/diversion_rules.py:239 +msgid "Paste here" +msgstr "Lim inn her" -#: lib/solaar/ui/diversion_rules.py:374 -msgid "Insert below" -msgstr "Sett inn nedenfor" +#: lib/solaar/ui/diversion_rules.py:241 +msgid "Paste above" +msgstr "Lim inn ovenfor" -#: lib/solaar/ui/diversion_rules.py:380 -msgid "Insert new rule here" -msgstr "Sett inn ny regel her" +#: lib/solaar/ui/diversion_rules.py:243 +msgid "Paste below" +msgstr "Lim inn nedenfor" -#: lib/solaar/ui/diversion_rules.py:382 -msgid "Insert new rule above" -msgstr "Sett inn ny regel ovenfor" +#: lib/solaar/ui/diversion_rules.py:249 +msgid "Paste rule here" +msgstr "Lim inn regel her" -#: lib/solaar/ui/diversion_rules.py:384 -msgid "Insert new rule below" -msgstr "Sett inn ny regel nedenfor" +#: lib/solaar/ui/diversion_rules.py:251 +msgid "Paste rule above" +msgstr "Lim inn regel ovenfor" -#: lib/solaar/ui/diversion_rules.py:425 -msgid "Paste here" -msgstr "Lim inn her" +#: lib/solaar/ui/diversion_rules.py:253 +msgid "Paste rule below" +msgstr "Lim inn regel nedenfor" -#: lib/solaar/ui/diversion_rules.py:427 -msgid "Paste above" -msgstr "Lim inn ovenfor" +#: lib/solaar/ui/diversion_rules.py:257 +msgid "Paste rule" +msgstr "Lim inn regel" -#: lib/solaar/ui/diversion_rules.py:429 -msgid "Paste below" -msgstr "Lim inn nedenfor" +#: lib/solaar/ui/diversion_rules.py:272 +msgid "Insert here" +msgstr "Sett inn her" -#: lib/solaar/ui/diversion_rules.py:435 -msgid "Paste rule here" -msgstr "Lim inn regel her" +#: lib/solaar/ui/diversion_rules.py:274 +msgid "Insert above" +msgstr "Sett inn ovenfor" -#: lib/solaar/ui/diversion_rules.py:437 -msgid "Paste rule above" -msgstr "Lim inn regel ovenfor" +#: lib/solaar/ui/diversion_rules.py:276 +msgid "Insert below" +msgstr "Sett inn nedenfor" -#: lib/solaar/ui/diversion_rules.py:439 -msgid "Paste rule below" -msgstr "Lim inn regel nedenfor" +#: lib/solaar/ui/diversion_rules.py:282 +msgid "Insert new rule here" +msgstr "Sett inn ny regel her" -#: lib/solaar/ui/diversion_rules.py:443 -msgid "Paste rule" -msgstr "Lim inn regel" +#: lib/solaar/ui/diversion_rules.py:284 +msgid "Insert new rule above" +msgstr "Sett inn ny regel ovenfor" -#: lib/solaar/ui/diversion_rules.py:472 -msgid "Flatten" -msgstr "Utflate" +#: lib/solaar/ui/diversion_rules.py:286 +msgid "Insert new rule below" +msgstr "Sett inn ny regel nedenfor" -#: lib/solaar/ui/diversion_rules.py:505 -msgid "Insert" -msgstr "Sett inn" +#: lib/solaar/ui/diversion_rules.py:313 +msgid "Flatten" +msgstr "Utflate" -#: lib/solaar/ui/diversion_rules.py:508 lib/solaar/ui/diversion_rules.py:632 -#: lib/solaar/ui/diversion_rules.py:1117 -msgid "Or" -msgstr "Eller" +#: lib/solaar/ui/diversion_rules.py:346 +msgid "Insert" +msgstr "Sett inn" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:631 -#: lib/solaar/ui/diversion_rules.py:1102 -msgid "And" -msgstr "Og" +#: lib/solaar/ui/diversion_rules.py:349 lib/solaar/ui/diversion_rules.py:477 +#: lib/solaar/ui/diversion_rules.py:1121 +msgid "Or" +msgstr "Eller" -#: lib/solaar/ui/diversion_rules.py:511 -msgid "Condition" -msgstr "Betingelse" +#: lib/solaar/ui/diversion_rules.py:350 lib/solaar/ui/diversion_rules.py:476 +#: lib/solaar/ui/diversion_rules.py:1107 +msgid "And" +msgstr "Og" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1247 -msgid "Feature" -msgstr "Karakteristikk" +#: lib/solaar/ui/diversion_rules.py:352 +msgid "Condition" +msgstr "Betingelse" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1283 -msgid "Report" -msgstr "Rapport" +#: lib/solaar/ui/diversion_rules.py:354 lib/solaar/ui/rule_conditions.py:145 +msgid "Feature" +msgstr "Karakteristikk" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1159 -msgid "Process" -msgstr "Prosess" +#: lib/solaar/ui/diversion_rules.py:355 lib/solaar/ui/rule_conditions.py:179 +msgid "Report" +msgstr "Rapport" -#: lib/solaar/ui/diversion_rules.py:516 -msgid "Mouse process" -msgstr "Musprosess" +#: lib/solaar/ui/diversion_rules.py:356 lib/solaar/ui/rule_conditions.py:60 +msgid "Process" +msgstr "Prosess" -#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1321 -msgid "Modifiers" -msgstr "Modifikatorer" +#: lib/solaar/ui/diversion_rules.py:357 +msgid "Mouse process" +msgstr "Musprosess" -#: lib/solaar/ui/diversion_rules.py:518 lib/solaar/ui/diversion_rules.py:1374 -msgid "Key" -msgstr "Tast" +#: lib/solaar/ui/diversion_rules.py:358 lib/solaar/ui/rule_conditions.py:216 +msgid "Modifiers" +msgstr "Modifikatorer" -#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:2140 -msgid "Active" -msgstr "Aktiv" +#: lib/solaar/ui/diversion_rules.py:359 lib/solaar/ui/rule_conditions.py:268 +msgid "Key" +msgstr "Tast" -#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:2183 -msgid "Setting" -msgstr "Innstilling" +#: lib/solaar/ui/diversion_rules.py:360 lib/solaar/ui/rule_conditions.py:309 +msgid "KeyIsDown" +msgstr "TastErNede" -#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1422 -msgid "Test" -msgstr "Test" +#: lib/solaar/ui/diversion_rules.py:361 lib/solaar/ui/diversion_rules.py:1414 +msgid "Active" +msgstr "Aktiv" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1539 -msgid "Test bytes" -msgstr "Test bytes" +#: lib/solaar/ui/diversion_rules.py:362 lib/solaar/ui/diversion_rules.py:1372 +#: lib/solaar/ui/diversion_rules.py:1423 lib/solaar/ui/diversion_rules.py:1470 +msgid "Device" +msgstr "Enhet" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1632 -msgid "Mouse Gesture" -msgstr "Musebevegelse" +#: lib/solaar/ui/diversion_rules.py:363 lib/solaar/ui/diversion_rules.py:1449 +msgid "Host" +msgstr "Vert" -#: lib/solaar/ui/diversion_rules.py:527 -msgid "Action" -msgstr "Handling" +#: lib/solaar/ui/diversion_rules.py:364 lib/solaar/ui/diversion_rules.py:1489 +msgid "Setting" +msgstr "Innstilling" -#: lib/solaar/ui/diversion_rules.py:529 lib/solaar/ui/diversion_rules.py:1741 -msgid "Key press" -msgstr "Tastetrykk" +#: lib/solaar/ui/diversion_rules.py:365 lib/solaar/ui/rule_conditions.py:324 +#: lib/solaar/ui/rule_conditions.py:373 +msgid "Test" +msgstr "Test" -#: lib/solaar/ui/diversion_rules.py:530 lib/solaar/ui/diversion_rules.py:1794 -msgid "Mouse scroll" -msgstr "Rull med mus" +#: lib/solaar/ui/diversion_rules.py:366 lib/solaar/ui/rule_conditions.py:498 +msgid "Test bytes" +msgstr "Test bytes" -#: lib/solaar/ui/diversion_rules.py:531 lib/solaar/ui/diversion_rules.py:1845 -msgid "Mouse click" -msgstr "Museklikk" +#: lib/solaar/ui/diversion_rules.py:367 lib/solaar/ui/rule_conditions.py:599 +msgid "Mouse Gesture" +msgstr "Musebevegelse" -#: lib/solaar/ui/diversion_rules.py:532 -msgid "Set" -msgstr "Still inn" +#: lib/solaar/ui/diversion_rules.py:371 +msgid "Action" +msgstr "Handling" -#: lib/solaar/ui/diversion_rules.py:533 lib/solaar/ui/diversion_rules.py:1916 -msgid "Execute" -msgstr "Utfør" +#: lib/solaar/ui/diversion_rules.py:373 lib/solaar/ui/rule_actions.py:131 +msgid "Key press" +msgstr "Tastetrykk" -#: lib/solaar/ui/diversion_rules.py:562 -msgid "Insert new rule" -msgstr "Sett inn ny regel" +#: lib/solaar/ui/diversion_rules.py:374 lib/solaar/ui/rule_actions.py:182 +msgid "Mouse scroll" +msgstr "Muserulling" -#: lib/solaar/ui/diversion_rules.py:582 lib/solaar/ui/diversion_rules.py:1582 -#: lib/solaar/ui/diversion_rules.py:1686 lib/solaar/ui/diversion_rules.py:1875 -msgid "Delete" -msgstr "Slett" +#: lib/solaar/ui/diversion_rules.py:375 lib/solaar/ui/rule_actions.py:243 +msgid "Mouse click" +msgstr "Museklikk" -#: lib/solaar/ui/diversion_rules.py:604 -msgid "Negate" -msgstr "Neger" +#: lib/solaar/ui/diversion_rules.py:376 +msgid "Set" +msgstr "Still inn" -#: lib/solaar/ui/diversion_rules.py:628 -msgid "Wrap with" -msgstr "Pakk inn" +#: lib/solaar/ui/diversion_rules.py:377 lib/solaar/ui/rule_actions.py:313 +msgid "Execute" +msgstr "Utfør" -#: lib/solaar/ui/diversion_rules.py:650 -msgid "Cut" -msgstr "Klipp ut" +#: lib/solaar/ui/diversion_rules.py:378 lib/solaar/ui/diversion_rules.py:1152 +msgid "Later" +msgstr "Senere" -#: lib/solaar/ui/diversion_rules.py:665 -msgid "Paste" -msgstr "Lim inn" +#: lib/solaar/ui/diversion_rules.py:407 +msgid "Insert new rule" +msgstr "Sett inn ny regel" -#: lib/solaar/ui/diversion_rules.py:677 -msgid "Copy" -msgstr "Kopier" +#: lib/solaar/ui/diversion_rules.py:427 lib/solaar/ui/rule_actions.py:74 +#: lib/solaar/ui/rule_actions.py:272 lib/solaar/ui/rule_conditions.py:546 +msgid "Delete" +msgstr "Slett" -#: lib/solaar/ui/diversion_rules.py:1054 -msgid "This editor does not support the selected rule component yet." -msgstr "Denne editoren støtter foreløpig ikke den valgte regelkomponenten." +#: lib/solaar/ui/diversion_rules.py:449 +msgid "Negate" +msgstr "Omvendt" + +#: lib/solaar/ui/diversion_rules.py:473 +msgid "Wrap with" +msgstr "Pakk inn med" + +#: lib/solaar/ui/diversion_rules.py:503 +msgid "Cut" +msgstr "Klipp ut" + +#: lib/solaar/ui/diversion_rules.py:519 +msgid "Paste" +msgstr "Lim inn" + +#: lib/solaar/ui/diversion_rules.py:525 +msgid "Copy" +msgstr "Kopier" + +#: lib/solaar/ui/diversion_rules.py:534 +msgid "Solaar Rule Editor" +msgstr "Solaar regel editor" + +#: lib/solaar/ui/diversion_rules.py:634 +msgid "Save changes" +msgstr "Lagre endringer" + +#: lib/solaar/ui/diversion_rules.py:639 +msgid "Discard changes" +msgstr "Forkast endringer" + +#: lib/solaar/ui/diversion_rules.py:1070 +msgid "This editor does not support the selected rule component yet." +msgstr "Denne editoren støtter foreløpig ikke den valgte regelkomponenten." #: lib/solaar/ui/diversion_rules.py:1132 -msgid "Not" -msgstr "Ikke" +msgid "" +"Number of seconds to delay. Delay between 0 and 1 is done with higher " +"precision." +msgstr "" +"Antall sekunder med forsinkelse. Forsinkelse mellom 0 og 1 gjøres med høyere " +"presisjon." -#: lib/solaar/ui/diversion_rules.py:1142 -msgid "X11 active process. For use in X11 only." -msgstr "X11 aktiv prosess. Kun til bruk i X11." +#: lib/solaar/ui/diversion_rules.py:1170 +msgid "Not" +msgstr "Ikke" -#: lib/solaar/ui/diversion_rules.py:1173 -msgid "X11 mouse process. For use in X11 only." -msgstr "X11-museprosess. Kun til bruk i X11." +#: lib/solaar/ui/diversion_rules.py:1201 +msgid "Toggle" +msgstr "Veksle" -#: lib/solaar/ui/diversion_rules.py:1190 -msgid "MouseProcess" -msgstr "MusProsess" +#: lib/solaar/ui/diversion_rules.py:1202 +msgid "True" +msgstr "Sann" -#: lib/solaar/ui/diversion_rules.py:1215 -msgid "Feature name of notification triggering rule processing." -msgstr "Funksjonsnavn på varsel som utløste regelbehandling." +#: lib/solaar/ui/diversion_rules.py:1203 +msgid "False" +msgstr "Falsk" -#: lib/solaar/ui/diversion_rules.py:1263 -msgid "Report number of notification triggering rule processing." -msgstr "Rapportnummer på varsel som utløste regelbehandling." +#: lib/solaar/ui/diversion_rules.py:1216 +msgid "Unsupported setting" +msgstr "Innstillingen støttes ikke" -#: lib/solaar/ui/diversion_rules.py:1297 -msgid "Active keyboard modifiers. Not always available in Wayland." -msgstr "Aktive tastaturmodifikatorer. Ikke alltid tilgjengelig i Wayland." +#: lib/solaar/ui/diversion_rules.py:1378 lib/solaar/ui/diversion_rules.py:1397 +#: lib/solaar/ui/diversion_rules.py:1476 lib/solaar/ui/diversion_rules.py:1731 +#: lib/solaar/ui/diversion_rules.py:1749 +msgid "Originating device" +msgstr "Kildeenhet" -#: lib/solaar/ui/diversion_rules.py:1338 -msgid "Diverted key or button depressed or released.\n" - "Use the Key/Button Diversion setting to divert keys and buttons." -msgstr "Omdirigert tast eller knapp, trykket eller sluppet.\n" - "Bruk innstillingen for omdirigering av tast/knapp for å viderekoble " - "taster og knapper." +#: lib/solaar/ui/diversion_rules.py:1410 +msgid "Device is active and its settings can be changed." +msgstr "Enheten er aktiv og innstillingene kan endres." -#: lib/solaar/ui/diversion_rules.py:1347 -msgid "Key down" -msgstr "Tast ned" +#: lib/solaar/ui/diversion_rules.py:1419 +msgid "Device that originated the current notification." +msgstr "Enheten som ga det gjeldende varselet." -#: lib/solaar/ui/diversion_rules.py:1350 -msgid "Key up" -msgstr "Tast opp" +#: lib/solaar/ui/diversion_rules.py:1432 +msgid "Name of host computer." +msgstr "Navn på vertsmaskin." -#: lib/solaar/ui/diversion_rules.py:1388 -msgid "Test condition on notification triggering rule processing." -msgstr "Testbetingelse på varsel som utløser regelbehandling." +#: lib/solaar/ui/diversion_rules.py:1503 +msgid "Value" +msgstr "Verdi" -#: lib/solaar/ui/diversion_rules.py:1438 -msgid "begin (inclusive)" -msgstr "begynn (inklusiv)" +#: lib/solaar/ui/diversion_rules.py:1512 +msgid "Item" +msgstr "Punkt" -#: lib/solaar/ui/diversion_rules.py:1439 -msgid "end (exclusive)" -msgstr "slutt (eksklusiv)" +#: lib/solaar/ui/diversion_rules.py:1791 +msgid "Change setting on device" +msgstr "Endre innstilling på enhet" -#: lib/solaar/ui/diversion_rules.py:1448 -msgid "range" -msgstr "område" +#: lib/solaar/ui/diversion_rules.py:1807 +msgid "Setting on device" +msgstr "Innstilling på enhet" -#: lib/solaar/ui/diversion_rules.py:1450 -msgid "minimum" -msgstr "minimum" - -#: lib/solaar/ui/diversion_rules.py:1451 -msgid "maximum" -msgstr "maksimum" - -#: lib/solaar/ui/diversion_rules.py:1453 +#: lib/solaar/ui/pair_window.py:37 lib/solaar/ui/pair_window.py:165 #, python-format -msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" -msgstr "byte %(0)d til %(1)d, fra %(2)d til %(3)d" +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: par ny enhet" -#: lib/solaar/ui/diversion_rules.py:1458 -msgid "mask" -msgstr "maske" +#: lib/solaar/ui/pair_window.py:39 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "Bolt-mottakere er kun kompatible med Bolt-enheter." -#: lib/solaar/ui/diversion_rules.py:1459 +#: lib/solaar/ui/pair_window.py:41 +msgid "Press a pairing button or key until the pairing light flashes quickly." +msgstr "" +"Trykk på sammenkoblingsknappen eller -tasten til sammenkoblingslampen " +"blinker raskt." + +#: lib/solaar/ui/pair_window.py:44 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "Unifying-mottakere er bare kompatible med Unifying-enheter." + +#: lib/solaar/ui/pair_window.py:46 +msgid "Other receivers are only compatible with a few devices." +msgstr "Andre mottakere er kun kompatible med noen få enheter." + +#: lib/solaar/ui/pair_window.py:48 +msgid "For most devices, turn on the device you want to pair." +msgstr "For de fleste enheter slår du på enheten du vil pare." + +#: lib/solaar/ui/pair_window.py:49 +msgid "If the device is already turned on, turn it off and on again." +msgstr "Hvis enheten allerede er påslått, slå den av og på igjen." + +#: lib/solaar/ui/pair_window.py:51 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "Enheten må ikke pares med en påslått mottaker i nærheten." + +#: lib/solaar/ui/pair_window.py:54 +msgid "" +"For devices with multiple channels, press, hold, and release the button for " +"the channel you wish to pair\n" +"or use the channel switch button to select a channel and then press, hold, " +"and release the channel switch button." +msgstr "" +"For enheter med flere kanaler, trykk, hold og slipp knappen for kanalen du " +"ønsker å pare\n" +"eller bruk kanalbryterknappen for å velge en kanal og trykk, hold og slipp " +"kanalbryterknappen." + +#: lib/solaar/ui/pair_window.py:61 +msgid "The channel indicator light should be blinking rapidly." +msgstr "Kanalindikatorlampen skal blinke raskt." + +#: lib/solaar/ui/pair_window.py:65 #, python-format -msgid "bytes %(0)d to %(1)d, mask %(2)d" -msgstr "byte %(0)d til %(1)d, maske %(2)d" +msgid "" +"\n" +"\n" +"This receiver has %d pairing remaining." +msgid_plural "" +"\n" +"\n" +"This receiver has %d pairings remaining." +msgstr[0] "" +"\n" +"\n" +"Denne mottakeren har %d gjenstående paring." +msgstr[1] "" +"\n" +"\n" +"Denne mottakeren har %d gjenstående paringer." -#: lib/solaar/ui/diversion_rules.py:1469 -msgid "Bit or range test on bytes in notification message triggering rule " - "processing." -msgstr "Bit- eller områdetest på bytes i varselmelding som utløste " - "regelbehandling." +#: lib/solaar/ui/pair_window.py:71 +msgid "" +"\n" +"Cancelling at this point will not use up a pairing." +msgstr "" +"\n" +"Kansellering nå vil ikke bruke opp en paring." -#: lib/solaar/ui/diversion_rules.py:1479 -msgid "type" -msgstr "type" - -#: lib/solaar/ui/diversion_rules.py:1562 -msgid "Mouse gesture with optional initiating button followed by zero or " - "more mouse movements." -msgstr "Musebevegelse med valgfri startknapp etterfulgt av null eller flere " - "musebevegelser." - -#: lib/solaar/ui/diversion_rules.py:1567 -msgid "Add movement" -msgstr "Legg til bevegelse" - -#: lib/solaar/ui/diversion_rules.py:1660 -msgid "Simulate a chorded key click or depress or release.\n" - "On Wayland requires write access to /dev/uinput." -msgstr "Simuler en chorded (?) tast, klikk eller trykk ned eller slipp.\n" - "På Wayland krever dette skrivetilgang til /dev/uinput." - -#: lib/solaar/ui/diversion_rules.py:1665 -msgid "Add key" -msgstr "Legg til tast" - -#: lib/solaar/ui/diversion_rules.py:1668 -msgid "Click" -msgstr "klikk" - -#: lib/solaar/ui/diversion_rules.py:1671 -msgid "Depress" -msgstr "Nedtrykk" - -#: lib/solaar/ui/diversion_rules.py:1674 -msgid "Release" -msgstr "Utløs" - -#: lib/solaar/ui/diversion_rules.py:1758 -msgid "Simulate a mouse scroll.\n" - "On Wayland requires write access to /dev/uinput." -msgstr "Simuler en muserulling.\n" - "På Wayland krever dette skrivetilgang til /dev/uinput." - -#: lib/solaar/ui/diversion_rules.py:1814 -msgid "Simulate a mouse click.\n" - "On Wayland requires write access to /dev/uinput." -msgstr "Simuler et museklikk.\n" - "På Wayland krever dette skrivetilgang til /dev/uinput." - -#: lib/solaar/ui/diversion_rules.py:1817 -msgid "Button" -msgstr "Knapp" - -#: lib/solaar/ui/diversion_rules.py:1818 -msgid "Count" -msgstr "Antall" - -#: lib/solaar/ui/diversion_rules.py:1858 -msgid "Execute a command with arguments." -msgstr "Utfør en kommando med argumenter." - -#: lib/solaar/ui/diversion_rules.py:1861 -msgid "Add argument" -msgstr "Legg til argument" - -#: lib/solaar/ui/diversion_rules.py:1935 -msgid "Toggle" -msgstr "Veksle" - -#: lib/solaar/ui/diversion_rules.py:1935 -msgid "True" -msgstr "Sann" - -#: lib/solaar/ui/diversion_rules.py:1936 -msgid "False" -msgstr "Falsk" - -#: lib/solaar/ui/diversion_rules.py:1950 -msgid "Unsupported setting" -msgstr "Innstillingen støttes ikke" - -#: lib/solaar/ui/diversion_rules.py:2096 lib/solaar/ui/diversion_rules.py:2167 -msgid "Device" -msgstr "Enhet" - -#: lib/solaar/ui/diversion_rules.py:2101 lib/solaar/ui/diversion_rules.py:2133 -#: lib/solaar/ui/diversion_rules.py:2172 lib/solaar/ui/diversion_rules.py:2414 -#: lib/solaar/ui/diversion_rules.py:2432 -msgid "Originating device" -msgstr "Kildeenhet" - -#: lib/solaar/ui/diversion_rules.py:2122 -msgid "Device is active and its settings can be changed." -msgstr "Enheten er aktiv og innstillingene kan endres." - -#: lib/solaar/ui/diversion_rules.py:2191 -msgid "Value" -msgstr "Verdi" - -#: lib/solaar/ui/diversion_rules.py:2199 -msgid "Item" -msgstr "Punkt" - -#: lib/solaar/ui/diversion_rules.py:2474 -msgid "Change setting on device" -msgstr "Endre innstilling på enhet" - -#: lib/solaar/ui/diversion_rules.py:2491 -msgid "Setting on device" -msgstr "Innstilling på enhet" - -#: lib/solaar/ui/notify.py:122 lib/solaar/ui/tray.py:323 -#: lib/solaar/ui/tray.py:328 lib/solaar/ui/window.py:746 -msgid "offline" -msgstr "avslått" - -#: lib/solaar/ui/pair_window.py:121 lib/solaar/ui/pair_window.py:255 -#: lib/solaar/ui/pair_window.py:277 +#: lib/solaar/ui/pair_window.py:166 #, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s: par ny enhet" +msgid "Enter passcode on %(name)s." +msgstr "Skriv inn passordet på %(name)s." -#: lib/solaar/ui/pair_window.py:122 +#: lib/solaar/ui/pair_window.py:169 #, python-format -msgid "Enter passcode on %(name)s." -msgstr "Skriv inn passordet på %(name)s." +msgid "Type %(passcode)s and then press the enter key." +msgstr "Skriv inn %(passcode)s og trykk deretter på enter-tasten." -#: lib/solaar/ui/pair_window.py:125 +#: lib/solaar/ui/pair_window.py:174 +msgid "left" +msgstr "venstre" + +#: lib/solaar/ui/pair_window.py:174 +msgid "right" +msgstr "høyre" + +#: lib/solaar/ui/pair_window.py:176 #, python-format -msgid "Type %(passcode)s and then press the enter key." -msgstr "Skriv inn %(passcode)s og trykk deretter på enter-tasten." +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"Trykk %(code)s\n" +"og trykk deretter på venstre og høyre knapp samtidig." -#: lib/solaar/ui/pair_window.py:128 -msgid "left" -msgstr "venstre" +#: lib/solaar/ui/pair_window.py:219 +msgid "The wireless link is not encrypted" +msgstr "Den trådløse koblingen er ukryptert" -#: lib/solaar/ui/pair_window.py:128 -msgid "right" -msgstr "høyre" +#: lib/solaar/ui/pair_window.py:224 +msgid "Found a new device:" +msgstr "Fant ny enhet:" -#: lib/solaar/ui/pair_window.py:130 +#: lib/solaar/ui/pair_window.py:245 +msgid "Pairing failed" +msgstr "Paringen feilet" + +#: lib/solaar/ui/pair_window.py:247 +msgid "Make sure your device is within range, and has a decent battery charge." +msgstr "" +"Forsikre deg om at enheten er innen rekkevidde, og at den er tilstrekkelig " +"oppladet." + +#: lib/solaar/ui/pair_window.py:249 +msgid "A new device was detected, but it is not compatible with this receiver." +msgstr "En ny enhet ble påvist, men den er ukompatibel med denne mottakeren." + +#: lib/solaar/ui/pair_window.py:251 +msgid "More paired devices than receiver can support." +msgstr "Fler parede enheter enn mottakeren kan støtte." + +#: lib/solaar/ui/pair_window.py:253 +msgid "No further details are available about the error." +msgstr "Ingen ytterligere detaljer er tilgjengelig om feilen." + +#: lib/solaar/ui/rule_actions.py:48 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simuler en chorded (?) tast, klikk eller trykk ned eller slipp.\n" +"På Wayland krever dette skrivetilgang til /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:53 +msgid "Add key" +msgstr "Legg til tast" + +#: lib/solaar/ui/rule_actions.py:56 +msgid "Click" +msgstr "Klikk" + +#: lib/solaar/ui/rule_actions.py:59 +msgid "Depress" +msgstr "Nedtrykk" + +#: lib/solaar/ui/rule_actions.py:62 +msgid "Release" +msgstr "Utløs" + +#: lib/solaar/ui/rule_actions.py:146 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simuler en muserulling.\n" +"På Wayland krever dette skrivetilgang til /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:202 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simuler et museklikk.\n" +"På Wayland krever dette skrivetilgang til /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:205 +msgid "Button" +msgstr "Knapp" + +#: lib/solaar/ui/rule_actions.py:206 +msgid "Count and Action" +msgstr "Antall og handling" + +#: lib/solaar/ui/rule_actions.py:255 +msgid "Execute a command with arguments." +msgstr "Utfør en kommando med argumenter." + +#: lib/solaar/ui/rule_actions.py:258 +msgid "Add argument" +msgstr "Legg til argument" + +#: lib/solaar/ui/rule_conditions.py:43 +msgid "X11 active process. For use in X11 only." +msgstr "X11 aktiv prosess. Kun til bruk i X11." + +#: lib/solaar/ui/rule_conditions.py:73 +msgid "X11 mouse process. For use in X11 only." +msgstr "X11-museprosess. Kun til bruk i X11." + +#: lib/solaar/ui/rule_conditions.py:90 +msgid "MouseProcess" +msgstr "MusProsess" + +#: lib/solaar/ui/rule_conditions.py:114 +msgid "Feature name of notification triggering rule processing." +msgstr "Funksjonsnavn på varsel som utløste regelbehandling." + +#: lib/solaar/ui/rule_conditions.py:160 +msgid "Report number of notification triggering rule processing." +msgstr "Rapportnummer for varsling som utløser regelbehandling." + +#: lib/solaar/ui/rule_conditions.py:192 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "Aktive tastaturmodifikatorer. Ikke alltid tilgjengelig i Wayland." + +#: lib/solaar/ui/rule_conditions.py:232 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"Omdirigert tast eller knapp som er trykket eller sluppet.\n" +"Bruk innstillingene for omdirigering av tast/knapp og omdiriger G-taster for " +"å videresende taster og knapper." + +#: lib/solaar/ui/rule_conditions.py:241 +msgid "Key down" +msgstr "Tast ned" + +#: lib/solaar/ui/rule_conditions.py:244 +msgid "Key up" +msgstr "Tast opp" + +#: lib/solaar/ui/rule_conditions.py:284 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"Omdirigert tast eller knapp er nede.\n" +"Bruk innstillingene for omdirigering av tast/knapp og omdirigerte G-taster " +"for å videresende taster og knapper." + +#: lib/solaar/ui/rule_conditions.py:322 +msgid "Test condition on notification triggering rule processing." +msgstr "Testbetingelse på varsel som utløser regelbehandling." + +#: lib/solaar/ui/rule_conditions.py:326 +msgid "Parameter" +msgstr "Parameter" + +#: lib/solaar/ui/rule_conditions.py:399 +msgid "begin (inclusive)" +msgstr "begynn (inklusiv)" + +#: lib/solaar/ui/rule_conditions.py:400 +msgid "end (exclusive)" +msgstr "slutt (eksklusiv)" + +#: lib/solaar/ui/rule_conditions.py:408 +msgid "range" +msgstr "område" + +#: lib/solaar/ui/rule_conditions.py:411 +msgid "minimum" +msgstr "minimum" + +#: lib/solaar/ui/rule_conditions.py:412 +msgid "maximum" +msgstr "maksimum" + +#: lib/solaar/ui/rule_conditions.py:414 #, python-format -msgid "Press %(code)s\n" - "and then press left and right buttons simultaneously." -msgstr "Trykk %(code)s\n" - "og trykk deretter på venstre og høyre knapp samtidig." +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "byte %(0)d til %(1)d, fra %(2)d til %(3)d" -#: lib/solaar/ui/pair_window.py:187 -msgid "Pairing failed" -msgstr "Paringen feilet" +#: lib/solaar/ui/rule_conditions.py:417 lib/solaar/ui/rule_conditions.py:418 +msgid "mask" +msgstr "maske" -#: lib/solaar/ui/pair_window.py:189 -msgid "Make sure your device is within range, and has a decent battery " - "charge." -msgstr "Forsikre deg om at enheten er innen rekkevidde, og at den er " - "tilstrekkelig oppladet." - -#: lib/solaar/ui/pair_window.py:191 -msgid "A new device was detected, but it is not compatible with this " - "receiver." -msgstr "En ny enhet ble påvist, men den er ukompatibel med denne mottakeren." - -#: lib/solaar/ui/pair_window.py:193 -msgid "More paired devices than receiver can support." -msgstr "Fler parede enheter enn mottakeren kan støtte." - -#: lib/solaar/ui/pair_window.py:195 -msgid "No further details are available about the error." -msgstr "Ingen ytterligere detaljer er tilgjengelig om feilen." - -#: lib/solaar/ui/pair_window.py:209 -msgid "Found a new device:" -msgstr "Fant ny enhet:" - -#: lib/solaar/ui/pair_window.py:234 -msgid "The wireless link is not encrypted" -msgstr "Den trådløse koblingen er ukryptert" - -#: lib/solaar/ui/pair_window.py:263 -msgid "Press a pairing button or key until the pairing light flashes " - "quickly." -msgstr "Trykk på sammenkoblingsknappen eller -tasten til " - "sammenkoblingslampen blinker raskt." - -#: lib/solaar/ui/pair_window.py:265 -msgid "You may have to first turn the device off and on again." -msgstr "Først må du kanskje slå enheten av og på igjen." - -#: lib/solaar/ui/pair_window.py:267 -msgid "Turn on the device you want to pair." -msgstr "Slå på enheten du ønsker å pare." - -#: lib/solaar/ui/pair_window.py:269 -msgid "If the device is already turned on, turn it off and on again." -msgstr "Hvis enheten allerede er slått på, slå den av og på igjen." - -#: lib/solaar/ui/pair_window.py:272 +#: lib/solaar/ui/rule_conditions.py:419 #, python-format -msgid "\n" - "\n" - "This receiver has %d pairing remaining." -msgid_plural "\n" - "\n" - "This receiver has %d pairings remaining." -msgstr[0] "\n" - "\n" - "Denne mottakeren har %d gjenstående paring." -msgstr[1] "\n" - "\n" - "Denne mottakeren har %d gjenstående paringer." +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "byte %(0)d til %(1)d, maske %(2)d" -#: lib/solaar/ui/pair_window.py:275 -msgid "\n" - "Cancelling at this point will not use up a pairing." -msgstr "\n" - "Kansellering nå vil ikke bruke opp en paring." +#: lib/solaar/ui/rule_conditions.py:428 +msgid "" +"Bit or range test on bytes in notification message triggering rule " +"processing." +msgstr "" +"Bit- eller områdetest på bytes i varselmelding som utløste regelbehandling." -#: lib/solaar/ui/tray.py:58 -msgid "No Logitech device found" -msgstr "Finner ingen Logitech-enhet" +#: lib/solaar/ui/rule_conditions.py:438 +msgid "type" +msgstr "type" -#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:324 +#: lib/solaar/ui/rule_conditions.py:526 +msgid "" +"Mouse gesture with optional initiating button followed by zero or more mouse " +"movements." +msgstr "" +"Musebevegelse med valgfri startknapp etterfulgt av null eller flere " +"musebevegelser." + +#: lib/solaar/ui/rule_conditions.py:531 +msgid "Add movement" +msgstr "Legg til bevegelse" + +#: lib/solaar/ui/tray.py:49 +msgid "No supported device found" +msgstr "Ingen støttede enheter er funnet" + +#: lib/solaar/ui/tray.py:54 lib/solaar/ui/window.py:308 #, python-format -msgid "About %s" -msgstr "Om %s" +msgid "About %s" +msgstr "Om %s" -#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:322 +#: lib/solaar/ui/tray.py:55 lib/solaar/ui/window.py:306 #, python-format -msgid "Quit %s" -msgstr "Avslutt %s" +msgid "Quit %s" +msgstr "Avslutt %s" -#: lib/solaar/ui/tray.py:302 lib/solaar/ui/tray.py:310 -msgid "no receiver" -msgstr "ingen mottaker" +#: lib/solaar/ui/tray.py:270 lib/solaar/ui/tray.py:278 +msgid "no receiver" +msgstr "ingen mottaker" -#: lib/solaar/ui/tray.py:326 -msgid "no status" -msgstr "ingen status" +#: lib/solaar/ui/tray.py:291 lib/solaar/ui/tray.py:296 +msgid "offline" +msgstr "avslått" -#: lib/solaar/ui/window.py:101 -msgid "Scanning" -msgstr "Søker" +#: lib/solaar/ui/tray.py:294 +msgid "no status" +msgstr "ingen status" -#: lib/solaar/ui/window.py:134 lib/solaar/ui/window.py:687 -msgid "Battery" -msgstr "Batteri" +#: lib/solaar/ui/window.py:90 +msgid "Scanning" +msgstr "Søker" -#: lib/solaar/ui/window.py:137 -msgid "Wireless Link" -msgstr "Trådløs kobling" +#: lib/solaar/ui/window.py:121 +msgid "Battery" +msgstr "Batteri" -#: lib/solaar/ui/window.py:141 -msgid "Lighting" -msgstr "Belysning" +#: lib/solaar/ui/window.py:124 +msgid "Wireless Link" +msgstr "Trådløs kobling" -#: lib/solaar/ui/window.py:175 -msgid "Show Technical Details" -msgstr "Vis tekniske detaljer" +#: lib/solaar/ui/window.py:128 +msgid "Lighting" +msgstr "Belysning" -#: lib/solaar/ui/window.py:191 -msgid "Pair new device" -msgstr "Par ny enhet" +#: lib/solaar/ui/window.py:162 +msgid "Show Technical Details" +msgstr "Vis tekniske detaljer" -#: lib/solaar/ui/window.py:210 -msgid "Select a device" -msgstr "Velg en enhet" +#: lib/solaar/ui/window.py:178 +msgid "Pair new device" +msgstr "Par ny enhet" -#: lib/solaar/ui/window.py:327 -msgid "Rule Editor" -msgstr "Regel editor" +#: lib/solaar/ui/window.py:196 +msgid "Select a device" +msgstr "Velg en enhet" -#: lib/solaar/ui/window.py:538 -msgid "Path" -msgstr "Sti" +#: lib/solaar/ui/window.py:311 +msgid "Rule Editor" +msgstr "Regel editor" -#: lib/solaar/ui/window.py:541 -msgid "USB ID" -msgstr "USB-ID" +#: lib/solaar/ui/window.py:504 +msgid "Path" +msgstr "Sti" -#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 -#: lib/solaar/ui/window.py:566 lib/solaar/ui/window.py:568 -msgid "Serial" -msgstr "Seriell" +#: lib/solaar/ui/window.py:506 +msgid "USB ID" +msgstr "USB-ID" -#: lib/solaar/ui/window.py:550 -msgid "Index" -msgstr "Indeks" +#: lib/solaar/ui/window.py:509 lib/solaar/ui/window.py:511 +#: lib/solaar/ui/window.py:526 lib/solaar/ui/window.py:528 +msgid "Serial" +msgstr "Seriell" -#: lib/solaar/ui/window.py:552 -msgid "Wireless PID" -msgstr "Trådløs-PID" +#: lib/solaar/ui/window.py:515 +msgid "Index" +msgstr "Indeks" -#: lib/solaar/ui/window.py:554 -msgid "Product ID" -msgstr "Produkt-ID" +#: lib/solaar/ui/window.py:517 +msgid "Wireless PID" +msgstr "Trådløs-PID" -#: lib/solaar/ui/window.py:556 -msgid "Protocol" -msgstr "Protokoll" +#: lib/solaar/ui/window.py:519 +msgid "Product ID" +msgstr "Produkt-ID" -#: lib/solaar/ui/window.py:556 -msgid "Unknown" -msgstr "Ukjent" +#: lib/solaar/ui/window.py:521 +msgid "Protocol" +msgstr "Protokoll" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:521 +msgid "Unknown" +msgstr "Ukjent" + +#: lib/solaar/ui/window.py:523 +msgid "Polling rate" +msgstr "Spørrerate" + +#: lib/solaar/ui/window.py:530 +msgid "Unit ID" +msgstr "Enhets-ID" + +#: lib/solaar/ui/window.py:544 +msgid "Notifications" +msgstr "Varsler" + +#: lib/solaar/ui/window.py:588 +msgid "No device paired." +msgstr "Ingen enhet paret." + +#: lib/solaar/ui/window.py:597 #, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d ms (%(rate_hz)dHz)" +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "Inntil %(max_count)s enhet kan bli paret med denne mottakeren." +msgstr[1] "Inntil %(max_count)s enheter kan bli paret med denne mottakeren." -#: lib/solaar/ui/window.py:559 -msgid "Polling rate" -msgstr "Spørrerate" - -#: lib/solaar/ui/window.py:570 -msgid "Unit ID" -msgstr "Enhets-ID" - -#: lib/solaar/ui/window.py:581 -msgid "none" -msgstr "ingen" - -#: lib/solaar/ui/window.py:582 -msgid "Notifications" -msgstr "Varsler" - -#: lib/solaar/ui/window.py:619 -msgid "No device paired." -msgstr "Ingen enhet paret." - -#: lib/solaar/ui/window.py:626 +#: lib/solaar/ui/window.py:608 #, python-format -msgid "Up to %(max_count)s device can be paired to this receiver." -msgid_plural "Up to %(max_count)s devices can be paired to this receiver." -msgstr[0] "Inntil %(max_count)s enhet kan bli paret med denne " - "mottakeren." -msgstr[1] "Inntil %(max_count)s enheter kan bli paret med denne " - "mottakeren." +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "Denne mottakeren har %d gjenstående paring." +msgstr[1] "Denne mottakeren har %d gjenstående paringer." -#: lib/solaar/ui/window.py:632 -msgid "Only one device can be paired to this receiver." -msgstr "Kun én enhet kan pares med denne mottakeren." +#: lib/solaar/ui/window.py:665 +msgid "Battery Voltage" +msgstr "Batterispenning" -#: lib/solaar/ui/window.py:636 -#, python-format -msgid "This receiver has %d pairing remaining." -msgid_plural "This receiver has %d pairings remaining." -msgstr[0] "Denne mottakeren har %d gjenstående paring." -msgstr[1] "Denne mottakeren har %d gjenstående paringer." +#: lib/solaar/ui/window.py:667 +msgid "Voltage reported by battery" +msgstr "Spenning rapportert av batteri" -#: lib/solaar/ui/window.py:688 -msgid "unknown" -msgstr "ukjent" +#: lib/solaar/ui/window.py:669 +msgid "Battery Level" +msgstr "Batterinivå" -#: lib/solaar/ui/window.py:689 -msgid "Battery information unknown." -msgstr "Ukjent batteriinformasjon" +#: lib/solaar/ui/window.py:671 +msgid "Approximate level reported by battery" +msgstr "Omtrentlig nivå rapportert fra batteri" + +#: lib/solaar/ui/window.py:678 lib/solaar/ui/window.py:680 +msgid "next reported " +msgstr "neste rapportert " + +#: lib/solaar/ui/window.py:681 +msgid " and next level to be reported." +msgstr " og neste nivå som blir rapportert." + +#: lib/solaar/ui/window.py:697 +msgid "encrypted" +msgstr "kryptert" #: lib/solaar/ui/window.py:699 -msgid "Battery Voltage" -msgstr "Batterispenning" +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "Den trådløse tilkoblingen mellom enheten og mottakeren er kryptert." #: lib/solaar/ui/window.py:701 -msgid "Voltage reported by battery" -msgstr "Spenning rapportert av batteri" - -#: lib/solaar/ui/window.py:703 -msgid "Battery Level" -msgstr "Batterinivå" +msgid "not encrypted" +msgstr "ukryptert" #: lib/solaar/ui/window.py:705 -msgid "Approximate level reported by battery" -msgstr "Omtrentlig nivå rapportert fra batteri" +msgid "" +"The wireless link between this device and its receiver is not encrypted.\n" +"This is a security issue for pointing devices, and a major security issue " +"for text-input devices." +msgstr "" +"Den trådløse koblingen mellom denne enheten og mottakeren er ikke kryptert.\n" +"Dette er et sikkerhetsproblem for pekeenheter, og et stort sikkerhetsproblem " +"for enheter for tekstinndata." -#: lib/solaar/ui/window.py:712 lib/solaar/ui/window.py:714 -msgid "next reported " -msgstr "neste rapportert " - -#: lib/solaar/ui/window.py:715 -msgid " and next level to be reported." -msgstr " og neste nivå som blir rapportert." - -#: lib/solaar/ui/window.py:720 -msgid "last known" -msgstr "sist kjent" - -#: lib/solaar/ui/window.py:728 -msgid "not encrypted" -msgstr "ukryptert" - -#: lib/solaar/ui/window.py:732 -msgid "The wireless link between this device and its receiver is not " - "encrypted.\n" - "\n" - "For pointing devices (mice, trackballs, trackpads), this is a minor " - "security issue.\n" - "\n" - "It is, however, a major security issue for text-input devices " - "(keyboards, numpads),\n" - "because typed text can be sniffed inconspicuously by 3rd parties " - "within range." -msgstr "Den trådløse koblingen mellom denne enheten og mottakeren er " - "ukryptert.\n" - "\n" - "For pekeenheter (mus, styrekuler, styreflater) er dette en mindre " - "sikkerhetsproblem.\n" - "\n" - "Det er imidlertid et viktig sikkerhetsproblem for " - "tekstinndataenheter (tastaturer, numpads),\n" - "fordi maskinskrevet tekst kan bli sniffet ubemerket av tredjeparter " - "innen rekkevidde." - -#: lib/solaar/ui/window.py:741 -msgid "encrypted" -msgstr "kryptert" - -#: lib/solaar/ui/window.py:743 -msgid "The wireless link between this device and its receiver is encrypted." -msgstr "Den trådløse tilkoblingen mellom enheten og mottakeren er kryptert." - -#: lib/solaar/ui/window.py:756 +#: lib/solaar/ui/window.py:721 #, python-format -msgid "%(light_level)d lux" -msgstr "%(light_level)d lux" +msgid "%(light_level)d lux" +msgstr "%(light_level)d lux" -#~ msgid "\n" -#~ "\n" -#~ "This receiver has %d pairing(s) remaining." -#~ msgstr "\n" -#~ "\n" -#~ "Denne mottakeren har %d gjenstående paringer." +#~ msgid "" +#~ "\n" +#~ "\n" +#~ "This receiver has %d pairing(s) remaining." +#~ msgstr "" +#~ "\n" +#~ "\n" +#~ "Denne mottakeren har %d gjenstående paringer." -#~ msgid "Actions" -#~ msgstr "Handlinger" +#, python-format +#~ msgid "%(rate)d ms (%(rate_hz)dHz)" +#~ msgstr "%(rate)d ms (%(rate_hz)dHz)" -#~ msgid "Add action" -#~ msgstr "Legg til handling" +#~ msgid "Actions" +#~ msgstr "Handlinger" -#~ msgid "Adjust the DPI by sliding the mouse horizontally while " -#~ "holding the button down." -#~ msgstr "Juster DPI ved å skyve musen horisontalt mens du holder " -#~ "knappen nede." +#~ msgid "Add action" +#~ msgstr "Legg til handling" -#~ msgid "Automatically switch the mouse wheel between ratchet and " -#~ "freespin mode.\n" -#~ "The mouse wheel is always free at 0, and always locked at 50" -#~ msgstr "Bytt mushjulet automatisk mellom sperre- og frispinnmodus.\n" -#~ "Mushjulet er alltid fritt på 0, og låses alltid på 50" +#~ msgid "" +#~ "Adjust the DPI by sliding the mouse horizontally while holding the button " +#~ "down." +#~ msgstr "" +#~ "Juster DPI ved å skyve musen horisontalt mens du holder knappen nede." -#~ msgid "DPI Sliding Adjustment" -#~ msgstr "DPI-glidejustering" +#~ msgid "" +#~ "Automatically switch the mouse wheel between ratchet and freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always locked at 50" +#~ msgstr "" +#~ "Bytt mushjulet automatisk mellom sperre- og frispinnmodus.\n" +#~ "Mushjulet er alltid fritt på 0, og låses alltid på 50" -#~ msgid "Effectively turns off thumb scrolling in Linux." -#~ msgstr "Slår i praksis av tommelrulling i Linux." +#~ msgid "" +#~ "Automatically switch the mouse wheel between ratchet and freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always ratcheted at 50" +#~ msgstr "" +#~ "Bytt automatisk rullehjulet mellom skralle- og frispinn-modus.\n" +#~ "Rullehjulet er alltid fritt ved 0, og med skralle ved 50" -#~ msgid "Effectively turns off wheel scrolling in Linux." -#~ msgstr "Slår i praksis av hjulrulling i Linux." +#~ msgid "Battery information unknown." +#~ msgstr "Ukjent batteriinformasjon" -#~ msgid "For more information see the Solaar installation directions\n" -#~ "at https://pwr-solaar.github.io/Solaar/installation" -#~ msgstr "For mer informasjon, se Solaar-installasjonsanvisningene\n" -#~ "på https://pwr-solaar.github.io/Solaar/installation" +#, python-format +#~ msgid "Battery: %(level)s" +#~ msgstr "Batteri: %(level)s" -#~ msgid "HID++ Scrolling" -#~ msgstr "HID++ rulling" +#, python-format +#~ msgid "Battery: %(percent)d%%" +#~ msgstr "Batteri: %(percent)d%%" -#~ msgid "HID++ Thumb Scrolling" -#~ msgstr "HID++ tommelrulling" +#~ msgid "Count" +#~ msgstr "Antall" -#~ msgid "HID++ mode for horizontal scroll with the thumb wheel." -#~ msgstr "HID++ modus for horisontal rulling med tommelhjulet." +#~ msgid "DPI Sliding Adjustment" +#~ msgstr "DPI-glidejustering" -#~ msgid "HID++ mode for vertical scroll with the wheel." -#~ msgstr "HID++ modus for vertikal rulling med hjulet." +#~ msgid "Divert G Keys" +#~ msgstr "Omdiriger G-taster" -#~ msgid "High Resolution Scrolling" -#~ msgstr "Høyoppløselig rulling" +#~ msgid "" +#~ "Diverted key or button depressed or released.\n" +#~ "Use the Key/Button Diversion setting to divert keys and buttons." +#~ msgstr "" +#~ "Omdirigert tast eller knapp, trykket eller sluppet.\n" +#~ "Bruk innstillingen for omdirigering av tast/knapp for å viderekoble " +#~ "taster og knapper." -#~ msgid "High Resolution Wheel Invert" -#~ msgstr "Inverter høy rulleoppløsning" +#~ msgid "Effectively turns off thumb scrolling in Linux." +#~ msgstr "Slår i praksis av tommelrulling i Linux." -#~ msgid "High-sensitivity wheel invert direction for vertical scroll." -#~ msgstr "Inverter retning for høy vertikal rulleoppløsning." +#~ msgid "Effectively turns off wheel scrolling in Linux." +#~ msgstr "Slår i praksis av hjulrulling i Linux." -#~ msgid "If the device is already turned on, turn if off and on again." -#~ msgstr "Hvis enheten allerede er slått på, slå den av og på igjen." +#~ msgid "" +#~ "Enable onboard profiles, which often control report rate and keyboard " +#~ "lighting" +#~ msgstr "" +#~ "Aktiver innebygde profiler, som ofte kontrollerer rapporthastighet og " +#~ "tastaturbelysning" -#~ msgid "Invert thumb scroll direction." -#~ msgstr "Inverter retning på tommelrulling." +#~ msgid "" +#~ "For more information see the Solaar installation directions\n" +#~ "at https://pwr-solaar.github.io/Solaar/installation" +#~ msgstr "" +#~ "For mer informasjon, se Solaar-installasjonsanvisningene\n" +#~ "på https://pwr-solaar.github.io/Solaar/installation" -#~ msgid "Make the key or button send HID++ notifications (which " -#~ "trigger Solaar rules but are otherwise ignored)." -#~ msgstr "Få tasten eller knappen til å sende HID++-varsler (som " -#~ "utløser Solaar-regler, men ellers ignoreres)." +#, python-format +#~ msgid "" +#~ "Found a Logitech Receiver (%s), but did not have permission to open it." +#~ msgstr "" +#~ "Fant en Logitech-mottaker (%s), men mangler rettigheter til å åpne den." -#~ msgid "No Logitech receiver found" -#~ msgstr "Ingen Logitech-mottaker funnet" +#~ msgid "Frequency of device polling, in milliseconds" +#~ msgstr "Frekvens på enhetsspørring, i millisekunder" -#~ msgid "Send a gesture by sliding the mouse while holding the button " -#~ "down." -#~ msgstr "Send en bevegelse ved å skyve musen mens du holder knappen " -#~ "nede." +#~ msgid "HID++ Scrolling" +#~ msgstr "HID++ rulling" -#~ msgid "Shows status of devices connected\n" -#~ "through wireless Logitech receivers." -#~ msgstr "Viser status for enheter tilkoblet\n" -#~ "via trådløse Logitech-mottakere." +#~ msgid "HID++ Thumb Scrolling" +#~ msgstr "HID++ tommelrulling" -#~ msgid "Smooth Scrolling" -#~ msgstr "Myk rulling" +#~ msgid "HID++ mode for horizontal scroll with the thumb wheel." +#~ msgstr "HID++ modus for horisontal rulling med tommelhjulet." -#~ msgid "Solaar depends on a udev file that is not present" -#~ msgstr "Solaar er avhengig av en udev-fil som ikke finnes" +#~ msgid "HID++ mode for vertical scroll with the wheel." +#~ msgstr "HID++ modus for vertikal rulling med hjulet." -#~ msgid "The receiver only supports %d paired device(s)." -#~ msgstr "Mottakeren kan kun støtte %d parede enhet(er)." +#~ msgid "High Resolution Scrolling" +#~ msgstr "Høyoppløselig rulling" -#~ msgid "The receiver was unplugged." -#~ msgstr "Mottakeren ble fjernet." +#~ msgid "High Resolution Wheel Invert" +#~ msgstr "Inverter høy rulleoppløsning" -#~ msgid "This receiver has %d pairing(s) remaining." -#~ msgstr "Denne mottakeren har %d gjenstående paring(er)." +#~ msgid "High-sensitivity wheel invert direction for vertical scroll." +#~ msgstr "Inverter retning for høy vertikal rulleoppløsning." -#~ msgid "Thumb Scroll Invert" -#~ msgstr "Inverter tommelrulling" +#~ msgid "If the device is already turned on, turn if off and on again." +#~ msgstr "Hvis enheten allerede er slått på, slå den av og på igjen." -#~ msgid "Top-most coordinate." -#~ msgstr "Koordinat lengst oppe." +#~ msgid "" +#~ "If you've just installed Solaar, try removing the receiver and plugging " +#~ "it back in." +#~ msgstr "" +#~ "Hvis du nettopp har installert Solaar, forsøk å fjerne mottakeren og " +#~ "plugg den inn igjen." -#~ msgid "USB id" -#~ msgstr "USB-id" +#~ msgid "Invert thumb scroll direction." +#~ msgstr "Inverter retning på tommelrulling." -#~ msgid "Unexpected device number (%s) in notification %s." -#~ msgstr "Uventet enhetsnummer (%s) i varsel %s." +#, python-format +#~ msgid "Lighting: %(level)s lux" +#~ msgstr "Belysning: %(level)s lux" -#~ msgid "Wheel Resolution" -#~ msgstr "Hjuloppløsning" +#~ msgid "" +#~ "Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " +#~ "are otherwise ignored)." +#~ msgstr "" +#~ "Få G-taster til å sende GKEY HID++-varsler (som utløser Solaar-regler, " +#~ "men som ellers ignoreres)." -#~ msgid "height" -#~ msgstr "høyde" +#~ msgid "" +#~ "Make the key or button send HID++ notifications (which trigger Solaar " +#~ "rules but are otherwise ignored)." +#~ msgstr "" +#~ "Få tasten eller knappen til å sende HID++-varsler (som utløser Solaar-" +#~ "regler, men ellers ignoreres)." -#~ msgid "next " -#~ msgstr "neste " +#~ msgid "May also make M keys and MR key send HID++ notifications" +#~ msgstr "Kan også få M-taster og MR-taster til å sende HID++-varsler" -#~ msgid "top" -#~ msgstr "top" +#~ msgid "No Logitech device found" +#~ msgstr "Finner ingen Logitech-enhet" -#~ msgid "width" -#~ msgstr "bredde" +#~ msgid "No Logitech receiver found" +#~ msgstr "Ingen Logitech-mottaker funnet" + +#~ msgid "Only one device can be paired to this receiver." +#~ msgstr "Kun én enhet kan pares med denne mottakeren." + +#~ msgid "Polling Rate (ms)" +#~ msgstr "Spørrerate (ms)" + +#~ msgid "Report number of notification triggering rule processing." +#~ msgstr "Rapportnummer på varsel som utløste regelbehandling." + +#~ msgid "Scroll Wheel Rachet" +#~ msgstr "Skrallen for rullehjulet" + +#~ msgid "Send a gesture by sliding the mouse while holding the button down." +#~ msgstr "Send en bevegelse ved å skyve musen mens du holder knappen nede." + +#~ msgid "" +#~ "Shows status of devices connected\n" +#~ "through wireless Logitech receivers." +#~ msgstr "" +#~ "Viser status for enheter tilkoblet\n" +#~ "via trådløse Logitech-mottakere." + +#~ msgid "Smooth Scrolling" +#~ msgstr "Myk rulling" + +#~ msgid "Solaar depends on a udev file that is not present" +#~ msgstr "Solaar er avhengig av en udev-fil som ikke finnes" + +#~ msgid "The receiver only supports %d paired device(s)." +#~ msgstr "Mottakeren kan kun støtte %d parede enhet(er)." + +#~ msgid "The receiver was unplugged." +#~ msgstr "Mottakeren ble fjernet." + +#~ msgid "" +#~ "The wireless link between this device and its receiver is not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices (keyboards, " +#~ "numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties within " +#~ "range." +#~ msgstr "" +#~ "Den trådløse koblingen mellom denne enheten og mottakeren er ukryptert.\n" +#~ "\n" +#~ "For pekeenheter (mus, styrekuler, styreflater) er dette en mindre " +#~ "sikkerhetsproblem.\n" +#~ "\n" +#~ "Det er imidlertid et viktig sikkerhetsproblem for tekstinndataenheter " +#~ "(tastaturer, numpads),\n" +#~ "fordi maskinskrevet tekst kan bli sniffet ubemerket av tredjeparter innen " +#~ "rekkevidde." + +#~ msgid "This receiver has %d pairing(s) remaining." +#~ msgstr "Denne mottakeren har %d gjenstående paring(er)." + +#~ msgid "Thumb Scroll Invert" +#~ msgstr "Inverter tommelrulling" + +#~ msgid "Top-most coordinate." +#~ msgstr "Koordinat lengst oppe." + +#~ msgid "" +#~ "Try removing the device and plugging it back in or turning it off and " +#~ "then on." +#~ msgstr "" +#~ "Prøv å fjerne enheten og koble den til igjen eller slå den av og på." + +#~ msgid "Turn illumination on or off on keyboard." +#~ msgstr "Slå på eller av belysning for tastaturet." + +#~ msgid "Turn on the device you want to pair." +#~ msgstr "Slå på enheten du ønsker å pare." + +#~ msgid "USB id" +#~ msgstr "USB-id" + +#~ msgid "Unexpected device number (%s) in notification %s." +#~ msgstr "Uventet enhetsnummer (%s) i varsel %s." + +#~ msgid "Wheel Resolution" +#~ msgstr "Hjuloppløsning" + +#~ msgid "You may have to first turn the device off and on again." +#~ msgstr "Først må du kanskje slå enheten av og på igjen." + +#~ msgid "height" +#~ msgstr "høyde" + +#~ msgid "last known" +#~ msgstr "sist kjent" + +#~ msgid "next " +#~ msgstr "neste " + +#~ msgid "none" +#~ msgstr "ingen" + +#~ msgid "top" +#~ msgstr "top" + +#~ msgid "unknown" +#~ msgstr "ukjent" + +#~ msgid "width" +#~ msgstr "bredde" diff --git a/po/nl.po b/po/nl.po index 65bf79bf..3fe5ad71 100644 --- a/po/nl.po +++ b/po/nl.po @@ -5,7 +5,7 @@ msgid "" msgstr "Project-Id-Version: solaar 1.0.1\n" "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2021-09-25 13:56-0400\n" + "POT-Creation-Date: 2023-12-28 17:40+0100\n" "PO-Revision-Date: 2021-01-15 22:03+0100\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: \n" @@ -16,272 +16,298 @@ msgstr "Project-Id-Version: solaar 1.0.1\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 2.4.2\n" -#: lib/logitech_receiver/base_usb.py:50 +#: lib/logitech_receiver/base_usb.py:46 +msgid "Bolt Receiver" +msgstr "" + +#: lib/logitech_receiver/base_usb.py:57 msgid "Unifying Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:58 lib/logitech_receiver/base_usb.py:68 -#: lib/logitech_receiver/base_usb.py:79 lib/logitech_receiver/base_usb.py:90 -#: lib/logitech_receiver/base_usb.py:101 +#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 +#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 +#: lib/logitech_receiver/base_usb.py:114 msgid "Nano Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:109 +#: lib/logitech_receiver/base_usb.py:124 msgid "Lightspeed Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:117 +#: lib/logitech_receiver/base_usb.py:133 msgid "EX100 Receiver 27 Mhz" msgstr "" -#: lib/logitech_receiver/device.py:133 lib/solaar/ui/window.py:693 -msgid "unknown" -msgstr "onbekend" - -#: lib/logitech_receiver/i18n.py:38 +#: lib/logitech_receiver/i18n.py:30 msgid "empty" msgstr "leeg" -#: lib/logitech_receiver/i18n.py:39 +#: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "bijna leeg" -#: lib/logitech_receiver/i18n.py:40 +#: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "laag" -#: lib/logitech_receiver/i18n.py:41 +#: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "" -#: lib/logitech_receiver/i18n.py:42 +#: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "goed" -#: lib/logitech_receiver/i18n.py:43 +#: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "opgeladen" -#: lib/logitech_receiver/i18n.py:46 +#: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "bezig met ontladen" -#: lib/logitech_receiver/i18n.py:47 +#: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "bezig met opladen" -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:721 +#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "bezig met opladen" -#: lib/logitech_receiver/i18n.py:49 +#: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "" -#: lib/logitech_receiver/i18n.py:50 +#: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "bijna opgeladen" -#: lib/logitech_receiver/i18n.py:51 +#: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "opgeladen" -#: lib/logitech_receiver/i18n.py:52 +#: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "langzaam opladen" -#: lib/logitech_receiver/i18n.py:53 +#: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "verkeerde batterij" -#: lib/logitech_receiver/i18n.py:54 +#: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "temperatuurfout" -#: lib/logitech_receiver/i18n.py:55 +#: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "" -#: lib/logitech_receiver/i18n.py:56 +#: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "" -#: lib/logitech_receiver/i18n.py:57 +#: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "" -#: lib/logitech_receiver/i18n.py:58 +#: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "" -#: lib/logitech_receiver/i18n.py:61 +#: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "time-out opgetreden" -#: lib/logitech_receiver/i18n.py:62 +#: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "apparaat wordt niet ondersteund" -#: lib/logitech_receiver/i18n.py:63 +#: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "te veel apparaten" -#: lib/logitech_receiver/i18n.py:64 +#: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "meerdere time-outs" -#: lib/logitech_receiver/i18n.py:67 lib/solaar/ui/window.py:580 +#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 msgid "Firmware" msgstr "Firmware" -#: lib/logitech_receiver/i18n.py:68 +#: lib/logitech_receiver/i18n.py:60 msgid "Bootloader" msgstr "Opstartlader" -#: lib/logitech_receiver/i18n.py:69 +#: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardware" -#: lib/logitech_receiver/i18n.py:70 +#: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Overig" -#: lib/logitech_receiver/i18n.py:73 +#: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:74 +#: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:75 +#: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "" -#: lib/logitech_receiver/i18n.py:76 +#: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "" -#: lib/logitech_receiver/i18n.py:77 +#: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "" -#: lib/logitech_receiver/i18n.py:78 +#: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "" -#: lib/logitech_receiver/i18n.py:79 +#: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "Slim schakelen" -#: lib/logitech_receiver/i18n.py:80 +#: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "" -#: lib/logitech_receiver/i18n.py:81 +#: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "" -#: lib/logitech_receiver/i18n.py:82 +#: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "" -#: lib/logitech_receiver/i18n.py:83 +#: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "" -#: lib/logitech_receiver/i18n.py:84 +#: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "" -#: lib/logitech_receiver/i18n.py:85 +#: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "" -#: lib/logitech_receiver/i18n.py:86 +#: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "" -#: lib/logitech_receiver/i18n.py:87 +#: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "" -#: lib/logitech_receiver/i18n.py:88 +#: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "" -#: lib/logitech_receiver/i18n.py:89 +#: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:90 +#: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:93 +#: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "" -#: lib/logitech_receiver/i18n.py:94 +#: lib/logitech_receiver/i18n.py:86 msgid "released" msgstr "" -#: lib/logitech_receiver/notifications.py:76 +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is closed" msgstr "koppelvergendeling ingeschakeld" -#: lib/logitech_receiver/notifications.py:76 +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is open" msgstr "koppelvergendeling uitgeschakeld" -#: lib/logitech_receiver/notifications.py:160 lib/solaar/ui/notify.py:123 +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is closed" +msgstr "" + +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is open" +msgstr "" + +#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 msgid "connected" msgstr "verbonden" -#: lib/logitech_receiver/notifications.py:160 +#: lib/logitech_receiver/notifications.py:224 msgid "disconnected" msgstr "verbinding verbroken" -#: lib/logitech_receiver/notifications.py:198 lib/solaar/ui/notify.py:121 +#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "niet gekoppeld" -#: lib/logitech_receiver/notifications.py:248 +#: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "ingeschakeld" -#: lib/logitech_receiver/settings.py:526 +#: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "" -#: lib/logitech_receiver/settings.py:542 lib/logitech_receiver/settings.py:568 +#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 msgid "feature" msgstr "" -#: lib/logitech_receiver/settings_templates.py:71 +#: lib/logitech_receiver/settings_templates.py:139 +msgid "Swap Fx function" +msgstr "Fx-functies omdraaien" + +#: lib/logitech_receiver/settings_templates.py:140 +msgid "When set, the F1..F12 keys will activate their special function,\n" + "and you must hold the FN key to activate their standard function." +msgstr "Schakel dit in om de speciale functies van F1-F12 te gebruiken -\n" + "de standaard functies kunnen worden geactiveerd met Fn + F1-F12." + +#: lib/logitech_receiver/settings_templates.py:142 +msgid "When unset, the F1..F12 keys will activate their standard function,\n" + "and you must hold the FN key to activate their special function." +msgstr "Schakel dit in om de standaard functies van F1-F12 te gebruiken -\n" + "de speciale functies kunnen worden geactiveerd met Fn + F1-F12." + +#: lib/logitech_receiver/settings_templates.py:149 msgid "Hand Detection" msgstr "Handpalmdetectie" -#: lib/logitech_receiver/settings_templates.py:71 +#: lib/logitech_receiver/settings_templates.py:150 msgid "Turn on illumination when the hands hover over the keyboard." msgstr "Schakelt de verlichting in als u beide handen boven het toetsenbord " "houdt." -#: lib/logitech_receiver/settings_templates.py:72 +#: lib/logitech_receiver/settings_templates.py:157 msgid "Scroll Wheel Smooth Scrolling" msgstr "Vloeiend scrollen" -#: lib/logitech_receiver/settings_templates.py:73 -#: lib/logitech_receiver/settings_templates.py:78 -#: lib/logitech_receiver/settings_templates.py:85 +#: lib/logitech_receiver/settings_templates.py:158 +#: lib/logitech_receiver/settings_templates.py:239 +#: lib/logitech_receiver/settings_templates.py:267 msgid "High-sensitivity mode for vertical scroll with the wheel." msgstr "Hogegevoeligheidsmodus voor verticaal scrollen." -#: lib/logitech_receiver/settings_templates.py:74 +#: lib/logitech_receiver/settings_templates.py:165 msgid "Side Scrolling" msgstr "Zijwaarts scrollen" -#: lib/logitech_receiver/settings_templates.py:75 +#: lib/logitech_receiver/settings_templates.py:167 msgid "When disabled, pushing the wheel sideways sends custom button " "events\n" "instead of the standard side-scrolling events." @@ -289,491 +315,619 @@ msgstr "Schakel dit uit om aangepaste knopgebeurtenissen te versturen als u " "het scrollwiel\n" "opzijduwt in plaats van de standaard gebeurtenissen." -#: lib/logitech_receiver/settings_templates.py:77 -msgid "Scroll Wheel High Resolution" -msgstr "Scrollen met hoge resolutie" - -#: lib/logitech_receiver/settings_templates.py:79 -msgid "Scroll Wheel Diversion" +#: lib/logitech_receiver/settings_templates.py:177 +msgid "Sensitivity (DPI - older mice)" msgstr "" -#: lib/logitech_receiver/settings_templates.py:80 -msgid "HID++ mode for vertical scroll with the wheel." -msgstr "HID++-modus voor verticaal scrollen." - -#: lib/logitech_receiver/settings_templates.py:81 -msgid "Effectively turns off wheel scrolling in Linux." -msgstr "Schakelt scrollen met het scrollwiel op Linux uit." - -#: lib/logitech_receiver/settings_templates.py:82 -msgid "Scroll Wheel Direction" -msgstr "Scrollrichting" - -#: lib/logitech_receiver/settings_templates.py:83 -msgid "Invert direction for vertical scroll with wheel." -msgstr "Keer de modus van verticaal scrollen met het scrollwiel om." - -#: lib/logitech_receiver/settings_templates.py:84 -msgid "Scroll Wheel Resolution" -msgstr "Scrollresolutie" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Frequency of device polling, in milliseconds" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Polling Rate (ms)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:87 -msgid "Swap Fx function" -msgstr "Fx-functies omdraaien" - -#: lib/logitech_receiver/settings_templates.py:88 -msgid "When set, the F1..F12 keys will activate their special function,\n" - "and you must hold the FN key to activate their standard function." -msgstr "Schakel dit in om de speciale functies van F1-F12 te gebruiken -\n" - "de standaard functies kunnen worden geactiveerd met Fn + F1-F12." - -#: lib/logitech_receiver/settings_templates.py:90 -msgid "When unset, the F1..F12 keys will activate their standard function,\n" - "and you must hold the FN key to activate their special function." -msgstr "Schakel dit in om de standaard functies van F1-F12 te gebruiken -\n" - "de speciale functies kunnen worden geactiveerd met Fn + F1-F12." - -#: lib/logitech_receiver/settings_templates.py:92 +#: lib/logitech_receiver/settings_templates.py:178 +#: lib/logitech_receiver/settings_templates.py:712 msgid "Mouse movement sensitivity" msgstr "Muisgevoeligheid" -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Sensitivity (DPI)" -msgstr "Gevoeligheid (DPI)" - -#: lib/logitech_receiver/settings_templates.py:93 -msgid "Sensitivity (Pointer Speed)" -msgstr "Gevoeligheid (cursorsnelheid)" - -#: lib/logitech_receiver/settings_templates.py:94 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "Muisversnelling (256 is de standaard versnelling)." - -#: lib/logitech_receiver/settings_templates.py:95 -msgid "Scroll Wheel Rachet" -msgstr "Vrij scrollen" - -#: lib/logitech_receiver/settings_templates.py:96 -msgid "Automatically switch the mouse wheel between ratchet and freespin " - "mode.\n" - "The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "Schakel het scrollwiel automatisch over van regel-voor-regelmodus " - "naar vrij scrollen.\n" - "Bij een waarde van 0 is vrij scrollen altijd ingeschakeld; bij een " - "waarde van 50 nooit." - -#: lib/logitech_receiver/settings_templates.py:98 +#: lib/logitech_receiver/settings_templates.py:208 +#: lib/logitech_receiver/settings_templates.py:218 +#: lib/logitech_receiver/settings_templates.py:225 msgid "Backlight" msgstr "Achtergrondverlichting" -#: lib/logitech_receiver/settings_templates.py:98 +#: lib/logitech_receiver/settings_templates.py:209 +#: lib/logitech_receiver/settings_templates.py:226 +msgid "Set illumination time for keyboard." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:219 msgid "Turn illumination on or off on keyboard." msgstr "Schakel de toetsenbordverlichting in of uit." -#: lib/logitech_receiver/settings_templates.py:99 -msgid "Key/Button Actions" -msgstr "Toets-/Knopacties" +#: lib/logitech_receiver/settings_templates.py:237 +msgid "Scroll Wheel High Resolution" +msgstr "Scrollen met hoge resolutie" -#: lib/logitech_receiver/settings_templates.py:100 -msgid "Change the action for the key or button." -msgstr "Pas de actie van een toets of knop aan." +#: lib/logitech_receiver/settings_templates.py:240 +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:101 -msgid "Changing important actions (such as for the left mouse button) can " - "result in an unusable system." -msgstr "Let op: het aanpassen van belangrijke acties (zoals die van de " - "linkermuisknop) kan leiden tot een onbruikbaar systeem." +#: lib/logitech_receiver/settings_templates.py:247 +#: lib/logitech_receiver/settings_templates.py:277 +msgid "Scroll Wheel Diversion" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:102 -msgid "Key/Button Diversion" -msgstr "Toets-/Knopomleiding" +#: lib/logitech_receiver/settings_templates.py:249 +msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:103 -msgid "Make the key or button send HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." -msgstr "Laat de toets of knop een HID++-melding versturen (welke Solaar's " - "regels volgen, maar verder genegeerd worden)." +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Scroll Wheel Direction" +msgstr "Scrollrichting" -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable keys" -msgstr "Toetsen uitschakelen" +#: lib/logitech_receiver/settings_templates.py:257 +msgid "Invert direction for vertical scroll with wheel." +msgstr "Keer de modus van verticaal scrollen met het scrollwiel om." -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable specific keyboard keys." -msgstr "Schakel specifieke toetsen op het toetsenbord uit." +#: lib/logitech_receiver/settings_templates.py:265 +msgid "Scroll Wheel Resolution" +msgstr "Scrollresolutie" -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Change keys to match OS." -msgstr "Pas de toetsen aan aan het besturingssysteem." +#: lib/logitech_receiver/settings_templates.py:279 +msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Set OS" -msgstr "Besturingssysteem instellen" +#: lib/logitech_receiver/settings_templates.py:288 +msgid "Sensitivity (Pointer Speed)" +msgstr "Gevoeligheid (cursorsnelheid)" -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Change Host" -msgstr "Host veranderen" +#: lib/logitech_receiver/settings_templates.py:289 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Muisversnelling (256 is de standaard versnelling)." -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Switch connection to a different host" -msgstr "Schakel over naar een andere host." - -#: lib/logitech_receiver/settings_templates.py:107 +#: lib/logitech_receiver/settings_templates.py:299 msgid "Thumb Wheel Diversion" msgstr "" -#: lib/logitech_receiver/settings_templates.py:108 -msgid "HID++ mode for horizontal scroll with the thumb wheel." -msgstr "HID++-modus voor horizontaal scrollen met het duimwiel." +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " + "Solaar rules but are otherwise ignored)." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:109 -msgid "Effectively turns off thumb scrolling in Linux." -msgstr "Schakelt scrollen met het duimwiel op Linux uit." - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Invert thumb wheel scroll direction." -msgstr "Keer de scrollrichting van het duimwiel om." - -#: lib/logitech_receiver/settings_templates.py:110 +#: lib/logitech_receiver/settings_templates.py:310 msgid "Thumb Wheel Direction" msgstr "Duimwielrichting" -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Gestures" -msgstr "Gebaren" +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Invert thumb wheel scroll direction." +msgstr "Keer de scrollrichting van het duimwiel om." -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "Pas het gedrag van het trackpad/de muis aan." - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "Pas de numerieke waarden van het trackpad/de muis aan." - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Gesture params" -msgstr "Gebarenwaarden" - -#: lib/logitech_receiver/settings_templates.py:113 -msgid "DPI Sliding Adjustment" -msgstr "DPI aanpassen middels verplaatsen" - -#: lib/logitech_receiver/settings_templates.py:114 -msgid "Adjust the DPI by sliding the mouse horizontally while holding the " - "button down." +#: lib/logitech_receiver/settings_templates.py:319 +msgid "Onboard Profiles" msgstr "" -#: lib/logitech_receiver/settings_templates.py:115 -msgid "Mouse Gestures" +#: lib/logitech_receiver/settings_templates.py:320 +msgid "Enable onboard profiles, which often control report rate and " + "keyboard lighting" msgstr "" -#: lib/logitech_receiver/settings_templates.py:116 -msgid "Send a gesture by sliding the mouse while holding the button down." +#: lib/logitech_receiver/settings_templates.py:330 +msgid "Polling Rate (ms)" msgstr "" -#: lib/logitech_receiver/settings_templates.py:117 +#: lib/logitech_receiver/settings_templates.py:332 +msgid "Frequency of device polling, in milliseconds" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:333 +#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:365 msgid "Divert crown events" msgstr "Kroongebeurtenissen omleiden" -#: lib/logitech_receiver/settings_templates.py:118 +#: lib/logitech_receiver/settings_templates.py:366 msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "Laat de kroon een HID++-melding versturen (welke Solaar's regels " "volgen, maar verder genegeerd worden)." -#: lib/logitech_receiver/settings_templates.py:119 +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:375 +msgid "Set crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:383 msgid "Divert G Keys" msgstr "G-toetsen omleiden" -#: lib/logitech_receiver/settings_templates.py:120 +#: lib/logitech_receiver/settings_templates.py:385 msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "Laat de G-toetsen een HID++-melding versturen (welke Solaar's regels " "volgen, maar verder genegeerd worden)." -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Performs a left click." -msgstr "Voert een linkermuisklik uit." - -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Single tap" -msgstr "Eenmaal tikken" - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Performs a right click." -msgstr "Voert een rechtermuisklik uit." - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Single tap with two fingers" -msgstr "Eenmaal tikken met twee vingers" - -#: lib/logitech_receiver/settings_templates.py:125 -msgid "Single tap with three fingers" -msgstr "Eenmaal tikken met drie vingers" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Double tap" -msgstr "Dubbeltikken" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Performs a double click." -msgstr "Voert een dubbele klik uit." - -#: lib/logitech_receiver/settings_templates.py:130 -msgid "Double tap with two fingers" -msgstr "Dubbeltikken met twee vingers" - -#: lib/logitech_receiver/settings_templates.py:131 -msgid "Double tap with three fingers" -msgstr "Dubbeltikken met drie vingers" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Drags items by dragging the finger after double tapping." -msgstr "Verplaats items door na het dubbeltikken uw vinger te verplaatsen." - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Tap and drag" -msgstr "Tikken-en-slepen" - -#: lib/logitech_receiver/settings_templates.py:135 -msgid "Tap and drag with two fingers" -msgstr "Tikken-en-slepen met twee vingers" - -#: lib/logitech_receiver/settings_templates.py:136 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "Verplaats items door na het dubbeltikken uw vingers te verplaatsen." - -#: lib/logitech_receiver/settings_templates.py:137 -msgid "Tap and drag with three fingers" -msgstr "Tikken-en-slepen met drie vingers" - -#: lib/logitech_receiver/settings_templates.py:139 -msgid "Suppress tap and edge gestures" -msgstr "Tik- en randgebaren uitschakelen" - -#: lib/logitech_receiver/settings_templates.py:140 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "Schakel tik- en randgebaren uit (gelijk aan Fn+linkermuisklik)." - -#: lib/logitech_receiver/settings_templates.py:141 -msgid "Scroll with one finger" -msgstr "Scrollen met één vinger" - -#: lib/logitech_receiver/settings_templates.py:141 -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scrolls." -msgstr "Scrollt." - -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scroll with two fingers" -msgstr "Scrollen met twee vingers" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scroll horizontally with two fingers" -msgstr "Horizontaal scrollen met twee vingers" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scrolls horizontally." -msgstr "Scrollt horizontaal." - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scroll vertically with two fingers" -msgstr "Verticaal scrollen met twee vingers" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scrolls vertically." -msgstr "Scrollt verticaal." - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Inverts the scrolling direction." -msgstr "Draai de scrollrichting om." - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Natural scrolling" -msgstr "Natuurlijk scrollen" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Enables the thumbwheel." -msgstr "Schakel het duimwiel in." - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Thumbwheel" -msgstr "Duimwiel" - -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:162 -msgid "Swipe from the top edge" -msgstr "Vegen vanaf bovenkant" - -#: lib/logitech_receiver/settings_templates.py:159 -msgid "Swipe from the left edge" -msgstr "Vegen vanaf linkerkant" - -#: lib/logitech_receiver/settings_templates.py:160 -msgid "Swipe from the right edge" -msgstr "Vegen vanaf rechterkant" - -#: lib/logitech_receiver/settings_templates.py:161 -msgid "Swipe from the bottom edge" -msgstr "Vegen vanaf onderkant" - -#: lib/logitech_receiver/settings_templates.py:163 -msgid "Swipe two fingers from the left edge" -msgstr "Vegen vanaf linkerkant (twee vingers)" - -#: lib/logitech_receiver/settings_templates.py:164 -msgid "Swipe two fingers from the right edge" -msgstr "Vegen vanaf rechterkant (twee vingers)" - -#: lib/logitech_receiver/settings_templates.py:165 -msgid "Swipe two fingers from the bottom edge" -msgstr "Vegen vanaf onderkant (twee vingers)" - -#: lib/logitech_receiver/settings_templates.py:166 -msgid "Swipe two fingers from the top edge" -msgstr "Vegen vanaf bovenkant (twee vingers)" - -#: lib/logitech_receiver/settings_templates.py:167 -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "Knijp om uit te zoomen; spreid om in te zoomen." - -#: lib/logitech_receiver/settings_templates.py:167 -msgid "Zoom with two fingers." -msgstr "Zoomen met twee vingers." - -#: lib/logitech_receiver/settings_templates.py:168 -msgid "Pinch to zoom out." -msgstr "Knijp om uit te zoomen." - -#: lib/logitech_receiver/settings_templates.py:169 -msgid "Spread to zoom in." -msgstr "Spreid om in te zoomen." - -#: lib/logitech_receiver/settings_templates.py:170 -msgid "Zoom with three fingers." -msgstr "Zoomen met drie vingers." - -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Zoom with two fingers" -msgstr "Zoomen met twee vingers" - -#: lib/logitech_receiver/settings_templates.py:189 -msgid "Pixel zone" +#: lib/logitech_receiver/settings_templates.py:386 +msgid "May also make M keys and MR key send HID++ notifications" msgstr "" -#: lib/logitech_receiver/settings_templates.py:190 -msgid "Ratio zone" +#: lib/logitech_receiver/settings_templates.py:402 +msgid "Scroll Wheel Ratcheted" msgstr "" -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Scale factor" -msgstr "Schaalfactor" - -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Sets the cursor speed." +#: lib/logitech_receiver/settings_templates.py:403 +msgid "Switch the mouse wheel between speed-controlled ratcheting and " + "always freespin." msgstr "" -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left" -msgstr "Links" +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Freespinning" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left-most coordinate." -msgstr "Het meest linker coördinaat." +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Ratcheted" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:196 -msgid "Top-most coordinate." -msgstr "Het bovenste coördinaat." +#: lib/logitech_receiver/settings_templates.py:412 +msgid "Scroll Wheel Ratchet Speed" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:196 -msgid "top" -msgstr "boven" +#: lib/logitech_receiver/settings_templates.py:414 +msgid "Use the mouse wheel speed to switch between ratcheted and " + "freespinning.\n" + "The mouse wheel is always ratcheted at 50." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:197 -msgid "Width." -msgstr "Breedte." +#: lib/logitech_receiver/settings_templates.py:463 +msgid "Key/Button Actions" +msgstr "Toets-/Knopacties" -#: lib/logitech_receiver/settings_templates.py:197 -msgid "width" -msgstr "breedte" +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Change the action for the key or button." +msgstr "Pas de actie van een toets of knop aan." -#: lib/logitech_receiver/settings_templates.py:198 -msgid "Height." -msgstr "Hoogte." +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Overridden by diversion." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:198 -msgid "height" -msgstr "hoogte" +#: lib/logitech_receiver/settings_templates.py:466 +msgid "Changing important actions (such as for the left mouse button) can " + "result in an unusable system." +msgstr "Let op: het aanpassen van belangrijke acties (zoals die van de " + "linkermuisknop) kan leiden tot een onbruikbaar systeem." -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Cursor speed." -msgstr "Cursorsnelheid." +#: lib/logitech_receiver/settings_templates.py:639 +msgid "Key/Button Diversion" +msgstr "Toets-/Knopomleiding" -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Scale" -msgstr "Schaal" +#: lib/logitech_receiver/settings_templates.py:640 +msgid "Make the key or button send HID++ notifications (Diverted) or " + "initiate Mouse Gestures or Sliding DPI" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:202 +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Diverted" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +msgid "Mouse Gestures" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Regular" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Sliding DPI" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:711 +msgid "Sensitivity (DPI)" +msgstr "Gevoeligheid (DPI)" + +#: lib/logitech_receiver/settings_templates.py:752 +msgid "Sensitivity Switching" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:754 +msgid "Switch the current sensitivity and the remembered sensitivity when " + "the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current " + "sensitivity" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:758 +msgid "Off" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:791 +msgid "Disable keys" +msgstr "Toetsen uitschakelen" + +#: lib/logitech_receiver/settings_templates.py:792 +msgid "Disable specific keyboard keys." +msgstr "Schakel specifieke toetsen op het toetsenbord uit." + +#: lib/logitech_receiver/settings_templates.py:795 #, python-format msgid "Disables the %s key." msgstr "Schakelt de %s-toets uit." -#: lib/logitech_receiver/settings_templates.py:521 -#: lib/logitech_receiver/settings_templates.py:573 -msgid "Off" +#: lib/logitech_receiver/settings_templates.py:809 +#: lib/logitech_receiver/settings_templates.py:860 +msgid "Set OS" +msgstr "Besturingssysteem instellen" + +#: lib/logitech_receiver/settings_templates.py:810 +#: lib/logitech_receiver/settings_templates.py:861 +msgid "Change keys to match OS." +msgstr "Pas de toetsen aan aan het besturingssysteem." + +#: lib/logitech_receiver/settings_templates.py:873 +msgid "Change Host" +msgstr "Host veranderen" + +#: lib/logitech_receiver/settings_templates.py:874 +msgid "Switch connection to a different host" +msgstr "Schakel over naar een andere host." + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Performs a left click." +msgstr "Voert een linkermuisklik uit." + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Single tap" +msgstr "Eenmaal tikken" + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Performs a right click." +msgstr "Voert een rechtermuisklik uit." + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Single tap with two fingers" +msgstr "Eenmaal tikken met twee vingers" + +#: lib/logitech_receiver/settings_templates.py:902 +msgid "Single tap with three fingers" +msgstr "Eenmaal tikken met drie vingers" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Double tap" +msgstr "Dubbeltikken" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Performs a double click." +msgstr "Voert een dubbele klik uit." + +#: lib/logitech_receiver/settings_templates.py:907 +msgid "Double tap with two fingers" +msgstr "Dubbeltikken met twee vingers" + +#: lib/logitech_receiver/settings_templates.py:908 +msgid "Double tap with three fingers" +msgstr "Dubbeltikken met drie vingers" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Verplaats items door na het dubbeltikken uw vinger te verplaatsen." + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Tap and drag" +msgstr "Tikken-en-slepen" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Verplaats items door na het dubbeltikken uw vingers te verplaatsen." + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Tap and drag with two fingers" +msgstr "Tikken-en-slepen met twee vingers" + +#: lib/logitech_receiver/settings_templates.py:914 +msgid "Tap and drag with three fingers" +msgstr "Tikken-en-slepen met drie vingers" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "Schakel tik- en randgebaren uit (gelijk aan Fn+linkermuisklik)." + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Suppress tap and edge gestures" +msgstr "Tik- en randgebaren uitschakelen" + +#: lib/logitech_receiver/settings_templates.py:918 +msgid "Scroll with one finger" +msgstr "Scrollen met één vinger" + +#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scrolls." +msgstr "Scrollt." + +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scroll with two fingers" +msgstr "Scrollen met twee vingers" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scroll horizontally with two fingers" +msgstr "Horizontaal scrollen met twee vingers" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scrolls horizontally." +msgstr "Scrollt horizontaal." + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scroll vertically with two fingers" +msgstr "Verticaal scrollen met twee vingers" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scrolls vertically." +msgstr "Scrollt verticaal." + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Inverts the scrolling direction." +msgstr "Draai de scrollrichting om." + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Natural scrolling" +msgstr "Natuurlijk scrollen" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Enables the thumbwheel." +msgstr "Schakel het duimwiel in." + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Thumbwheel" +msgstr "Duimwiel" + +#: lib/logitech_receiver/settings_templates.py:935 +#: lib/logitech_receiver/settings_templates.py:939 +msgid "Swipe from the top edge" +msgstr "Vegen vanaf bovenkant" + +#: lib/logitech_receiver/settings_templates.py:936 +msgid "Swipe from the left edge" +msgstr "Vegen vanaf linkerkant" + +#: lib/logitech_receiver/settings_templates.py:937 +msgid "Swipe from the right edge" +msgstr "Vegen vanaf rechterkant" + +#: lib/logitech_receiver/settings_templates.py:938 +msgid "Swipe from the bottom edge" +msgstr "Vegen vanaf onderkant" + +#: lib/logitech_receiver/settings_templates.py:940 +msgid "Swipe two fingers from the left edge" +msgstr "Vegen vanaf linkerkant (twee vingers)" + +#: lib/logitech_receiver/settings_templates.py:941 +msgid "Swipe two fingers from the right edge" +msgstr "Vegen vanaf rechterkant (twee vingers)" + +#: lib/logitech_receiver/settings_templates.py:942 +msgid "Swipe two fingers from the bottom edge" +msgstr "Vegen vanaf onderkant (twee vingers)" + +#: lib/logitech_receiver/settings_templates.py:943 +msgid "Swipe two fingers from the top edge" +msgstr "Vegen vanaf bovenkant (twee vingers)" + +#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "Knijp om uit te zoomen; spreid om in te zoomen." + +#: lib/logitech_receiver/settings_templates.py:944 +msgid "Zoom with two fingers." +msgstr "Zoomen met twee vingers." + +#: lib/logitech_receiver/settings_templates.py:945 +msgid "Pinch to zoom out." +msgstr "Knijp om uit te zoomen." + +#: lib/logitech_receiver/settings_templates.py:946 +msgid "Spread to zoom in." +msgstr "Spreid om in te zoomen." + +#: lib/logitech_receiver/settings_templates.py:947 +msgid "Zoom with three fingers." +msgstr "Zoomen met drie vingers." + +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Zoom with two fingers" +msgstr "Zoomen met twee vingers" + +#: lib/logitech_receiver/settings_templates.py:966 +msgid "Pixel zone" msgstr "" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Diverted" +#: lib/logitech_receiver/settings_templates.py:967 +msgid "Ratio zone" msgstr "" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Regular" +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Scale factor" +msgstr "Schaalfactor" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Sets the cursor speed." msgstr "" -#: lib/logitech_receiver/status.py:109 +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left" +msgstr "Links" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left-most coordinate." +msgstr "Het meest linker coördinaat." + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width" +msgstr "Breedte" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width." +msgstr "Breedte." + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height" +msgstr "Hoogte" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height." +msgstr "Hoogte." + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Cursor speed." +msgstr "Cursorsnelheid." + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Scale" +msgstr "Schaal" + +#: lib/logitech_receiver/settings_templates.py:982 +msgid "Gestures" +msgstr "Gebaren" + +#: lib/logitech_receiver/settings_templates.py:983 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Pas het gedrag van het trackpad/de muis aan." + +#: lib/logitech_receiver/settings_templates.py:1000 +msgid "Gestures Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1001 +msgid "Divert mouse/touchpad gestures." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1018 +msgid "Gesture params" +msgstr "Gebarenwaarden" + +#: lib/logitech_receiver/settings_templates.py:1019 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Pas de numerieke waarden van het trackpad/de muis aan." + +#: lib/logitech_receiver/settings_templates.py:1044 +msgid "M-Key LEDs" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Control the M-Key LEDs." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1077 +msgid "May need G Keys diverted to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1053 +#, python-format +msgid "Lights up the %s key." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1074 +msgid "MR-Key LED" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "Control the MR-Key LED." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1095 +msgid "Persistent Key/Button Mapping" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1097 +msgid "Permanently change the mapping for the key or button." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1098 +msgid "Changing important keys or buttons (such as for the left mouse " + "button) can result in an unusable system." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1157 +msgid "Sidetone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1158 +msgid "Set sidetone level." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1167 +msgid "Equalizer" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1168 +msgid "Set equalizer levels." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1191 +msgid "Hz" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1197 +msgid "Power Management" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1198 +msgid "Power off in minutes (0 for never)." +msgstr "" + +#: lib/logitech_receiver/status.py:114 msgid "No paired devices." msgstr "Geen apparaten gekoppeld." -#: lib/logitech_receiver/status.py:110 lib/solaar/ui/window.py:623 +#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 #, python-format msgid "%(count)s paired device." msgid_plural "%(count)s paired devices." msgstr[0] "%(count)s gekoppeld apparaat." msgstr[1] "%(count)s gekoppelde apparaten." -#: lib/logitech_receiver/status.py:165 +#: lib/logitech_receiver/status.py:170 #, python-format msgid "Battery: %(level)s" msgstr "Batterijniveau: %(level)s" -#: lib/logitech_receiver/status.py:167 +#: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "Batterijniveau: %(percent)d%%" -#: lib/logitech_receiver/status.py:179 +#: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "Verlichting: %(level)s lux" -#: lib/logitech_receiver/status.py:234 +#: lib/logitech_receiver/status.py:239 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "Batterijniveau: %(level)s (%(status)s)" -#: lib/logitech_receiver/status.py:236 +#: lib/logitech_receiver/status.py:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Batterijniveau: %(percent)d%% (%(status)s)" @@ -784,16 +938,14 @@ msgstr "Machtigingsfout" #: lib/solaar/ui/__init__.py:54 #, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open " - "it." -msgstr "Er is een Logitech-ontvanger aangetroffen ('%s'), maar Solaar is " - "niet gemachtigd om deze te tonen." +msgid "Found a Logitech receiver or device (%s), but did not have " + "permission to open it." +msgstr "" #: lib/solaar/ui/__init__.py:55 -msgid "If you've just installed Solaar, try removing the receiver and " - "plugging it back in." -msgstr "Als u Solaar nét heeft geïnstalleerd, plug dan de ontvanger opnieuw " - "in." +msgid "If you've just installed Solaar, try disconnecting the receiver or " + "device and then reconnecting it." +msgstr "" #: lib/solaar/ui/__init__.py:58 msgid "Cannot connect to device error" @@ -806,8 +958,8 @@ msgid "Found a Logitech receiver or device at %s, but encountered an error " msgstr "" #: lib/solaar/ui/__init__.py:61 -msgid "Try removing the device and plugging it back in or turning it off " - "and then on." +msgid "Try disconnecting the device and then reconnecting it or turning it " + "off and then on." msgstr "" #: lib/solaar/ui/__init__.py:64 @@ -823,356 +975,629 @@ msgstr "'%{device}' kan niet worden losgekoppeld van '%{receiver}'." msgid "The receiver returned an error, with no further details." msgstr "Er is een fout opgetreden zonder nadere informatie." -#: lib/solaar/ui/about.py:39 +#: lib/solaar/ui/__init__.py:177 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" + +#: lib/solaar/ui/about.py:36 msgid "Manages Logitech receivers,\n" "keyboards, mice, and tablets." msgstr "" -#: lib/solaar/ui/about.py:47 +#: lib/solaar/ui/about.py:44 msgid "Additional Programming" msgstr "" -#: lib/solaar/ui/about.py:48 +#: lib/solaar/ui/about.py:45 msgid "GUI design" msgstr "Toepassingsontwerp" -#: lib/solaar/ui/about.py:50 +#: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "Testen" -#: lib/solaar/ui/about.py:57 +#: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Logitech-documentatie" -#: lib/solaar/ui/action.py:70 lib/solaar/ui/window.py:328 -msgid "About" -msgstr "Over" - -#: lib/solaar/ui/action.py:96 lib/solaar/ui/action.py:100 -#: lib/solaar/ui/window.py:205 +#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 +#: lib/solaar/ui/window.py:197 msgid "Unpair" msgstr "Loskoppelen" -#: lib/solaar/ui/action.py:99 lib/solaar/ui/diversion_rules.py:144 +#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "" -#: lib/solaar/ui/config_panel.py:358 -msgid "Changes allowed" +#: lib/solaar/ui/config_panel.py:212 +msgid "Complete - ENTER to change" msgstr "" -#: lib/solaar/ui/config_panel.py:359 -msgid "No changes allowed" +#: lib/solaar/ui/config_panel.py:212 +msgid "Incomplete" msgstr "" -#: lib/solaar/ui/config_panel.py:360 -msgid "Ignore this setting" -msgstr "" - -#: lib/solaar/ui/config_panel.py:401 -msgid "Working" -msgstr "Aan het werk" - -#: lib/solaar/ui/config_panel.py:404 -msgid "Read/write operation failed." -msgstr "Uitlezen/Wegschrijven mislukt." - -#: lib/solaar/ui/config_panel.py:522 +#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "%d waarde" msgstr[1] "%d waarden" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/config_panel.py:518 +msgid "Changes allowed" +msgstr "" + +#: lib/solaar/ui/config_panel.py:519 +msgid "No changes allowed" +msgstr "" + +#: lib/solaar/ui/config_panel.py:520 +msgid "Ignore this setting" +msgstr "" + +#: lib/solaar/ui/config_panel.py:565 +msgid "Working" +msgstr "Aan het werk" + +#: lib/solaar/ui/config_panel.py:568 +msgid "Read/write operation failed." +msgstr "Uitlezen/Wegschrijven mislukt." + +#: lib/solaar/ui/diversion_rules.py:65 msgid "Built-in rules" msgstr "Meegeleverde regels" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" msgstr "Eigen regels" -#: lib/solaar/ui/diversion_rules.py:59 lib/solaar/ui/diversion_rules.py:792 +#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "Regel" -#: lib/solaar/ui/diversion_rules.py:60 lib/solaar/ui/diversion_rules.py:503 -#: lib/solaar/ui/diversion_rules.py:622 +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 +#: lib/solaar/ui/diversion_rules.py:636 msgid "Sub-rule" msgstr "Aanvullende regel" -#: lib/solaar/ui/diversion_rules.py:62 +#: lib/solaar/ui/diversion_rules.py:70 msgid "[empty]" msgstr "[leeg]" -#: lib/solaar/ui/diversion_rules.py:85 +#: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "Solaar-regelbewerker" -#: lib/solaar/ui/diversion_rules.py:135 +#: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "Wilt u de wijzigingen permanent opslaan?" -#: lib/solaar/ui/diversion_rules.py:140 +#: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Ja" -#: lib/solaar/ui/diversion_rules.py:142 +#: lib/solaar/ui/diversion_rules.py:148 msgid "No" msgstr "" -#: lib/solaar/ui/diversion_rules.py:147 +#: lib/solaar/ui/diversion_rules.py:153 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "Als u 'Nee' kiest, dan worden ze verworpen nadat Solaar is " "afgesloten." -#: lib/solaar/ui/diversion_rules.py:195 +#: lib/solaar/ui/diversion_rules.py:201 msgid "Save changes" msgstr "" -#: lib/solaar/ui/diversion_rules.py:200 +#: lib/solaar/ui/diversion_rules.py:206 msgid "Discard changes" msgstr "" -#: lib/solaar/ui/diversion_rules.py:366 +#: lib/solaar/ui/diversion_rules.py:372 msgid "Insert here" msgstr "Hier invoegen" -#: lib/solaar/ui/diversion_rules.py:368 +#: lib/solaar/ui/diversion_rules.py:374 msgid "Insert above" msgstr "Hierboven invoegen" -#: lib/solaar/ui/diversion_rules.py:370 +#: lib/solaar/ui/diversion_rules.py:376 msgid "Insert below" msgstr "Hieronder invoegen" -#: lib/solaar/ui/diversion_rules.py:376 +#: lib/solaar/ui/diversion_rules.py:382 msgid "Insert new rule here" msgstr "Nieuwe regel hier invoegen" -#: lib/solaar/ui/diversion_rules.py:378 +#: lib/solaar/ui/diversion_rules.py:384 msgid "Insert new rule above" msgstr "Nieuwe regel hierboven invoegen" -#: lib/solaar/ui/diversion_rules.py:380 +#: lib/solaar/ui/diversion_rules.py:386 msgid "Insert new rule below" msgstr "Nieuwe regel hieronder invoegen" -#: lib/solaar/ui/diversion_rules.py:421 +#: lib/solaar/ui/diversion_rules.py:427 msgid "Paste here" msgstr "Hier plakken" -#: lib/solaar/ui/diversion_rules.py:423 +#: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "Hierboven plakken" -#: lib/solaar/ui/diversion_rules.py:425 +#: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "Hieronder plakken" -#: lib/solaar/ui/diversion_rules.py:431 +#: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "Regel hier plakken" -#: lib/solaar/ui/diversion_rules.py:433 +#: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "Regel hierboven plakken" -#: lib/solaar/ui/diversion_rules.py:435 +#: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "Regel hieronder plakken" -#: lib/solaar/ui/diversion_rules.py:439 +#: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "Regel plakken" -#: lib/solaar/ui/diversion_rules.py:468 +#: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "Vlakmaken" -#: lib/solaar/ui/diversion_rules.py:501 +#: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "Invoegen" -#: lib/solaar/ui/diversion_rules.py:504 lib/solaar/ui/diversion_rules.py:624 -#: lib/solaar/ui/diversion_rules.py:835 +#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 +#: lib/solaar/ui/diversion_rules.py:1126 msgid "Or" msgstr "Of" -#: lib/solaar/ui/diversion_rules.py:505 lib/solaar/ui/diversion_rules.py:623 -#: lib/solaar/ui/diversion_rules.py:820 +#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 +#: lib/solaar/ui/diversion_rules.py:1111 msgid "And" msgstr "En" -#: lib/solaar/ui/diversion_rules.py:507 +#: lib/solaar/ui/diversion_rules.py:513 msgid "Condition" msgstr "Omstandigheid" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:951 +#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "Functie" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:874 -msgid "Process" -msgstr "Proces" - -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:902 -msgid "MouseProcess" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:512 lib/solaar/ui/diversion_rules.py:984 +#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "Melden" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1019 +#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 +msgid "Process" +msgstr "Proces" + +#: lib/solaar/ui/diversion_rules.py:518 +msgid "Mouse process" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 msgid "Modifiers" msgstr "Sneltoetsen" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1066 +#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "Toets" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1111 +#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 +msgid "KeyIsDown" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 +msgid "Active" +msgstr "Actief" + +#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2218 +#: lib/solaar/ui/diversion_rules.py:2270 lib/solaar/ui/diversion_rules.py:2323 +msgid "Device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +msgid "Host" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 +msgid "Setting" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 +#: lib/solaar/ui/diversion_rules.py:1526 msgid "Test" msgstr "Testen" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1196 +#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 +msgid "Test bytes" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 msgid "Mouse Gesture" msgstr "" -#: lib/solaar/ui/diversion_rules.py:520 +#: lib/solaar/ui/diversion_rules.py:532 msgid "Action" msgstr "Actie" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1286 +#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 msgid "Key press" msgstr "Toetsaanslag" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1335 +#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "Scrollbeweging" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:1383 +#: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 msgid "Mouse click" msgstr "Muisklik" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1452 +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Set" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 msgid "Execute" msgstr "Uitvoeren" -#: lib/solaar/ui/diversion_rules.py:554 +#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 +msgid "Later" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:568 msgid "Insert new rule" msgstr "Nieuwe regel invoegen" -#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/diversion_rules.py:1146 -#: lib/solaar/ui/diversion_rules.py:1236 lib/solaar/ui/diversion_rules.py:1411 +#: lib/solaar/ui/diversion_rules.py:588 lib/solaar/ui/diversion_rules.py:1686 +#: lib/solaar/ui/diversion_rules.py:1790 lib/solaar/ui/diversion_rules.py:1989 msgid "Delete" msgstr "Verwijderen" -#: lib/solaar/ui/diversion_rules.py:596 +#: lib/solaar/ui/diversion_rules.py:610 msgid "Negate" msgstr "Negeren" -#: lib/solaar/ui/diversion_rules.py:620 +#: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "Omslaan met" -#: lib/solaar/ui/diversion_rules.py:642 +#: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Knippen" -#: lib/solaar/ui/diversion_rules.py:657 +#: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Plakken" -#: lib/solaar/ui/diversion_rules.py:669 +#: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Kopiëren" -#: lib/solaar/ui/diversion_rules.py:772 +#: lib/solaar/ui/diversion_rules.py:1063 msgid "This editor does not support the selected rule component yet." msgstr "Deze bewerker heeft nog geen ondersteuning voor het geselecteerde " "onderdeel." -#: lib/solaar/ui/diversion_rules.py:850 +#: lib/solaar/ui/diversion_rules.py:1138 +msgid "Number of seconds to delay." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1177 msgid "Not" msgstr "Niet" -#: lib/solaar/ui/diversion_rules.py:1039 +#: lib/solaar/ui/diversion_rules.py:1187 +msgid "X11 active process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1218 +msgid "X11 mouse process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1235 +msgid "MouseProcess" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1260 +msgid "Feature name of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1308 +msgid "Report number of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1342 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1383 +msgid "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1392 msgid "Key down" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1042 +#: lib/solaar/ui/diversion_rules.py:1395 msgid "Key up" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1131 -msgid "Add action" +#: lib/solaar/ui/diversion_rules.py:1436 +msgid "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." msgstr "" -#: lib/solaar/ui/diversion_rules.py:1224 +#: lib/solaar/ui/diversion_rules.py:1475 +msgid "Test condition on notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1479 +msgid "Parameter" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1542 +msgid "begin (inclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1543 +msgid "end (exclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1552 +msgid "range" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1554 +msgid "minimum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1555 +msgid "maximum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1557 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1562 +msgid "mask" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1563 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1573 +msgid "Bit or range test on bytes in notification message triggering rule " + "processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1583 +msgid "type" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1666 +msgid "Mouse gesture with optional initiating button followed by zero or " + "more mouse movements." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1671 +msgid "Add movement" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1764 +msgid "Simulate a chorded key click or depress or release.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1769 msgid "Add key" msgstr "Toets toevoegen" -#: lib/solaar/ui/diversion_rules.py:1354 +#: lib/solaar/ui/diversion_rules.py:1772 +msgid "Click" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1775 +msgid "Depress" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1778 +msgid "Release" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1861 +msgid "Simulate a mouse scroll.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1918 +msgid "Simulate a mouse click.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1921 msgid "Button" msgstr "Knop" -#: lib/solaar/ui/diversion_rules.py:1355 -msgid "Count" -msgstr "Teller" +#: lib/solaar/ui/diversion_rules.py:1922 +msgid "Count and Action" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1397 +#: lib/solaar/ui/diversion_rules.py:1972 +msgid "Execute a command with arguments." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1975 msgid "Add argument" msgstr "Aanvullende optie toevoegen" -#: lib/solaar/ui/notify.py:125 lib/solaar/ui/tray.py:319 -#: lib/solaar/ui/tray.py:324 lib/solaar/ui/window.py:749 +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "Toggle" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "True" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2051 +msgid "False" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2065 +msgid "Unsupported setting" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2242 +#: lib/solaar/ui/diversion_rules.py:2328 lib/solaar/ui/diversion_rules.py:2570 +#: lib/solaar/ui/diversion_rules.py:2588 +msgid "Originating device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2256 +msgid "Device is active and its settings can be changed." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2266 +msgid "Device that originated the current notification." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2280 +msgid "Name of host computer." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2347 +msgid "Value" +msgstr "Waarde" + +#: lib/solaar/ui/diversion_rules.py:2355 +msgid "Item" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2630 +msgid "Change setting on device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2647 +msgid "Setting on device" +msgstr "" + +#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:318 +#: lib/solaar/ui/tray.py:323 lib/solaar/ui/window.py:739 msgid "offline" msgstr "offline" -#: lib/solaar/ui/pair_window.py:134 +#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 +#: lib/solaar/ui/pair_window.py:288 +#, python-format +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: nieuw apparaat aankoppelen" + +#: lib/solaar/ui/pair_window.py:123 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "" + +#: lib/solaar/ui/pair_window.py:126 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "left" +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "right" +msgstr "" + +#: lib/solaar/ui/pair_window.py:131 +#, python-format +msgid "Press %(code)s\n" + "and then press left and right buttons simultaneously." +msgstr "" + +#: lib/solaar/ui/pair_window.py:188 msgid "Pairing failed" msgstr "Koppelen mislukt" -#: lib/solaar/ui/pair_window.py:136 +#: lib/solaar/ui/pair_window.py:190 msgid "Make sure your device is within range, and has a decent battery " "charge." msgstr "Zorg er voor dat het apparaat binnen handbereik is en voldoende is " "opgeladen." -#: lib/solaar/ui/pair_window.py:138 +#: lib/solaar/ui/pair_window.py:192 msgid "A new device was detected, but it is not compatible with this " "receiver." msgstr "Er is een nieuw apparaat aangetroffen dat niet compatibel is met " "deze ontvanger." -#: lib/solaar/ui/pair_window.py:140 +#: lib/solaar/ui/pair_window.py:194 msgid "More paired devices than receiver can support." msgstr "Er zijn meer gekoppelde apparaten dan de ontvanger aankan." -#: lib/solaar/ui/pair_window.py:142 +#: lib/solaar/ui/pair_window.py:196 msgid "No further details are available about the error." msgstr "Er is een fout opgetreden zonder nadere informatie." -#: lib/solaar/ui/pair_window.py:156 +#: lib/solaar/ui/pair_window.py:210 msgid "Found a new device:" msgstr "Nieuw apparaat aangetroffen:" -#: lib/solaar/ui/pair_window.py:181 +#: lib/solaar/ui/pair_window.py:235 msgid "The wireless link is not encrypted" msgstr "De draadloze verbinding is onversleuteld" -#: lib/solaar/ui/pair_window.py:199 -#, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s: nieuw apparaat aankoppelen" +#: lib/solaar/ui/pair_window.py:264 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "" -#: lib/solaar/ui/pair_window.py:206 +#: lib/solaar/ui/pair_window.py:266 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:268 +msgid "Other receivers are only compatible with a few devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:270 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" + +#: lib/solaar/ui/pair_window.py:274 +msgid "Press a pairing button or key until the pairing light flashes " + "quickly." +msgstr "" + +#: lib/solaar/ui/pair_window.py:276 +msgid "You may have to first turn the device off and on again." +msgstr "" + +#: lib/solaar/ui/pair_window.py:278 +msgid "Turn on the device you want to pair." +msgstr "Schakel het te koppelen apparaat in." + +#: lib/solaar/ui/pair_window.py:280 msgid "If the device is already turned on, turn it off and on again." msgstr "" -#: lib/solaar/ui/pair_window.py:209 +#: lib/solaar/ui/pair_window.py:283 #, python-format msgid "\n" "\n" @@ -1187,123 +1612,125 @@ msgstr[1] "\n" "\n" "Er kunnen nog %d apparaten worden gekoppeld aan deze ontvanger." -#: lib/solaar/ui/pair_window.py:212 +#: lib/solaar/ui/pair_window.py:286 msgid "\n" "Cancelling at this point will not use up a pairing." msgstr "\n" "Als u nu afbreekt, kunt u nog steeds iets koppelen." -#: lib/solaar/ui/pair_window.py:215 -msgid "Turn on the device you want to pair." -msgstr "Schakel het te koppelen apparaat in." +#: lib/solaar/ui/tray.py:58 +msgid "No supported device found" +msgstr "" -#: lib/solaar/ui/tray.py:61 -msgid "No Logitech receiver found" -msgstr "Geen Logitech-ontvanger aangetroffen" +#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#, python-format +msgid "About %s" +msgstr "Info %s" -#: lib/solaar/ui/tray.py:68 lib/solaar/ui/window.py:325 -msgid "Quit" -msgstr "Afsluiten" +#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 +#, python-format +msgid "Quit %s" +msgstr "%s afsluiten" -#: lib/solaar/ui/tray.py:298 lib/solaar/ui/tray.py:306 +#: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "geen ontvanger" -#: lib/solaar/ui/tray.py:322 +#: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "geen status" -#: lib/solaar/ui/window.py:104 +#: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "Bezig met zoeken..." -#: lib/solaar/ui/window.py:137 lib/solaar/ui/window.py:692 +#: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Batterij" -#: lib/solaar/ui/window.py:140 +#: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Draadloze verbinding" -#: lib/solaar/ui/window.py:144 +#: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Verlichting" -#: lib/solaar/ui/window.py:178 +#: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Technische informatie tonen" -#: lib/solaar/ui/window.py:194 +#: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Nieuw apparaat aankoppelen" -#: lib/solaar/ui/window.py:213 +#: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Kies een apparaat" -#: lib/solaar/ui/window.py:331 +#: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "Regelbewerker" -#: lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Pad" -#: lib/solaar/ui/window.py:544 +#: lib/solaar/ui/window.py:536 msgid "USB ID" msgstr "USB-id" -#: lib/solaar/ui/window.py:547 lib/solaar/ui/window.py:549 -#: lib/solaar/ui/window.py:569 lib/solaar/ui/window.py:571 +#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 msgid "Serial" msgstr "Serie" -#: lib/solaar/ui/window.py:553 +#: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Index" -#: lib/solaar/ui/window.py:555 +#: lib/solaar/ui/window.py:547 msgid "Wireless PID" msgstr "Draadloze pid" -#: lib/solaar/ui/window.py:557 +#: lib/solaar/ui/window.py:549 msgid "Product ID" msgstr "Product-id" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:551 msgid "Protocol" msgstr "Protocol" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:551 msgid "Unknown" msgstr "Onbekend" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:554 #, python-format msgid "%(rate)d ms (%(rate_hz)dHz)" msgstr "%(rate)d ms (%(rate_hz)dHz)" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:554 msgid "Polling rate" msgstr "Controlesnelheid" -#: lib/solaar/ui/window.py:573 +#: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "Apparaat-id" -#: lib/solaar/ui/window.py:584 +#: lib/solaar/ui/window.py:576 msgid "none" msgstr "geen" -#: lib/solaar/ui/window.py:585 +#: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Meldingen" -#: lib/solaar/ui/window.py:622 +#: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "Geen apparaat gekoppeld." -#: lib/solaar/ui/window.py:629 +#: lib/solaar/ui/window.py:628 #, python-format msgid "Up to %(max_count)s device can be paired to this receiver." msgid_plural "Up to %(max_count)s devices can be paired to this receiver." @@ -1312,11 +1739,11 @@ msgstr[0] "Er kan slechts %(max_count)s apparaat worden gekoppeld aan " msgstr[1] "Er kunnen slechts %(max_count)s apparaten worden gekoppeld " "aan deze ontvanger." -#: lib/solaar/ui/window.py:635 +#: lib/solaar/ui/window.py:634 msgid "Only one device can be paired to this receiver." msgstr "Er kan slechts één apparaat worden gekoppeld aan deze ontvanger." -#: lib/solaar/ui/window.py:639 +#: lib/solaar/ui/window.py:638 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." @@ -1324,74 +1751,55 @@ msgstr[0] "Er kan nog %d apparaat worden gekoppeld aan deze ontvanger." msgstr[1] "Er kunnen nog %d apparaten worden gekoppeld aan deze " "ontvanger." -#: lib/solaar/ui/window.py:694 -msgid "Battery information unknown." -msgstr "Geen batterij-informatie beschikbaar." - -#: lib/solaar/ui/window.py:702 +#: lib/solaar/ui/window.py:692 msgid "Battery Voltage" msgstr "Batterijvoltage" -#: lib/solaar/ui/window.py:704 +#: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "Het door de batterij gemelde voltage" -#: lib/solaar/ui/window.py:706 lib/solaar/ui/window.py:710 +#: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "Batterijniveau" -#: lib/solaar/ui/window.py:708 lib/solaar/ui/window.py:712 +#: lib/solaar/ui/window.py:698 msgid "Approximate level reported by battery" msgstr "Het door de batterij geschatte niveau" -#: lib/solaar/ui/window.py:715 lib/solaar/ui/window.py:717 +#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 msgid "next reported " msgstr "volgende melding " -#: lib/solaar/ui/window.py:718 +#: lib/solaar/ui/window.py:708 msgid " and next level to be reported." msgstr " en het volgende te melden niveau." -#: lib/solaar/ui/window.py:723 +#: lib/solaar/ui/window.py:713 msgid "last known" msgstr "laatst bekend" -#: lib/solaar/ui/window.py:731 -msgid "not encrypted" -msgstr "onversleuteld" - -#: lib/solaar/ui/window.py:735 -msgid "The wireless link between this device and its receiver is not " - "encrypted.\n" - "\n" - "For pointing devices (mice, trackballs, trackpads), this is a minor " - "security issue.\n" - "\n" - "It is, however, a major security issue for text-input devices " - "(keyboards, numpads),\n" - "because typed text can be sniffed inconspicuously by 3rd parties " - "within range." -msgstr "De draadloze verbinding tussen dit apparaat en de ontvanger is " - "onversleuteld.\n" - "\n" - "Bij aanwijsapparaten (zoals muizen, trackballs en trackpads) vormt " - "dit een heel klein veiligheidsrisico.\n" - "\n" - "Echter, bij tekstinvoerapparaten (zoals (numerieke) toetsenborden) " - "vormt dit een groot probleem\n" - "omdat ingevoerde tekst stiekem kan worden uitgelezen door derden " - "binnen handbereik." - -#: lib/solaar/ui/window.py:744 +#: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "versleuteld" -#: lib/solaar/ui/window.py:746 +#: lib/solaar/ui/window.py:726 msgid "The wireless link between this device and its receiver is encrypted." msgstr "De draadloze verbinding tussen dit apparaat en de ontvanger is " "versleuteld." -#: lib/solaar/ui/window.py:759 +#: lib/solaar/ui/window.py:728 +msgid "not encrypted" +msgstr "onversleuteld" + +#: lib/solaar/ui/window.py:732 +msgid "The wireless link between this device and its receiver is not " + "encrypted.\n" + "This is a security issue for pointing devices, and a major security " + "issue for text-input devices." +msgstr "" + +#: lib/solaar/ui/window.py:748 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d lux" @@ -1404,22 +1812,60 @@ msgstr "%(light_level)d lux" #~ msgid "%(battery_percent)d%%" #~ msgstr "%(battery_percent)d%%" +#~ msgid "About" +#~ msgstr "Over" + #~ msgid "Adjust the DPI by sliding the mouse horizontally while " #~ "holding the DPI button." #~ msgstr "Pas het aantal dpi aan door de dpi-knop ingedrukt te houden " #~ "en de muis horizontaal te verplaatsen." +#~ msgid "Automatically switch the mouse wheel between ratchet and " +#~ "freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always ratcheted at 50" +#~ msgstr "Schakel het scrollwiel automatisch over van regel-voor-" +#~ "regelmodus naar vrij scrollen.\n" +#~ "Bij een waarde van 0 is vrij scrollen altijd ingeschakeld; bij een " +#~ "waarde van 50 nooit." + +#~ msgid "Battery information unknown." +#~ msgstr "Geen batterij-informatie beschikbaar." + #~ msgid "Click to allow changes." #~ msgstr "Klik om wijzigingen toe te staan." #~ msgid "Click to prevent changes." #~ msgstr "Klik om wijzigingen te voorkomen." +#~ msgid "Count" +#~ msgstr "Teller" + +#~ msgid "DPI Sliding Adjustment" +#~ msgstr "DPI aanpassen middels verplaatsen" + +#~ msgid "Effectively turns off thumb scrolling in Linux." +#~ msgstr "Schakelt scrollen met het duimwiel op Linux uit." + +#~ msgid "Effectively turns off wheel scrolling in Linux." +#~ msgstr "Schakelt scrollen met het scrollwiel op Linux uit." + #~ msgid "For more information see the Solaar installation directions\n" #~ "at https://pwr-solaar.github.io/Solaar/installation" #~ msgstr "Bekijk voor meer informatie de installatiehulp op https://" #~ "pwr-solaar.github.io/Solaar/installation" +#, python-format +#~ msgid "Found a Logitech Receiver (%s), but did not have permission " +#~ "to open it." +#~ msgstr "Er is een Logitech-ontvanger aangetroffen ('%s'), maar " +#~ "Solaar is niet gemachtigd om deze te tonen." + +#~ msgid "HID++ mode for horizontal scroll with the thumb wheel." +#~ msgstr "HID++-modus voor horizontaal scrollen met het duimwiel." + +#~ msgid "HID++ mode for vertical scroll with the wheel." +#~ msgstr "HID++-modus voor verticaal scrollen." + #~ msgid "High Resolution Scrolling" #~ msgstr "Scrollen met hoge resolutie" @@ -1438,9 +1884,28 @@ msgstr "%(light_level)d lux" #~ msgstr "Als het apparaat al is ingeschakeld, schakel het dan uit en " #~ "weer in." +#~ msgid "If you've just installed Solaar, try removing the receiver " +#~ "and plugging it back in." +#~ msgstr "Als u Solaar nét heeft geïnstalleerd, plug dan de ontvanger " +#~ "opnieuw in." + +#~ msgid "Make the key or button send HID++ notifications (which " +#~ "trigger Solaar rules but are otherwise ignored)." +#~ msgstr "Laat de toets of knop een HID++-melding versturen (welke " +#~ "Solaar's regels volgen, maar verder genegeerd worden)." + +#~ msgid "No Logitech receiver found" +#~ msgstr "Geen Logitech-ontvanger aangetroffen" + +#~ msgid "Quit" +#~ msgstr "Afsluiten" + #~ msgid "Scroll Wheel HID++ Scrolling" #~ msgstr "HID++-scrollen" +#~ msgid "Scroll Wheel Rachet" +#~ msgstr "Vrij scrollen" + #~ msgid "Shows status of devices connected\n" #~ "through wireless Logitech receivers." #~ msgstr "Toont de status van apparaten die verbonden\n" @@ -1458,5 +1923,41 @@ msgstr "%(light_level)d lux" #~ msgid "The receiver was unplugged." #~ msgstr "De ontvanger is niet meer aanwezig." +#~ msgid "The wireless link between this device and its receiver is " +#~ "not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices " +#~ "(keyboards, numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties " +#~ "within range." +#~ msgstr "De draadloze verbinding tussen dit apparaat en de ontvanger " +#~ "is onversleuteld.\n" +#~ "\n" +#~ "Bij aanwijsapparaten (zoals muizen, trackballs en trackpads) vormt " +#~ "dit een heel klein veiligheidsrisico.\n" +#~ "\n" +#~ "Echter, bij tekstinvoerapparaten (zoals (numerieke) toetsenborden) " +#~ "vormt dit een groot probleem\n" +#~ "omdat ingevoerde tekst stiekem kan worden uitgelezen door derden " +#~ "binnen handbereik." + #~ msgid "Thumb Wheel HID++ Scrolling" #~ msgstr "HID++-scrollen met duimwiel" + +#~ msgid "Top-most coordinate." +#~ msgstr "Het bovenste coördinaat." + +#~ msgid "height" +#~ msgstr "hoogte" + +#~ msgid "top" +#~ msgstr "boven" + +#~ msgid "unknown" +#~ msgstr "onbekend" + +#~ msgid "width" +#~ msgstr "breedte" diff --git a/po/nn.po b/po/nn.po index 594a4db7..1e8d7c24 100644 --- a/po/nn.po +++ b/po/nn.po @@ -2,1462 +2,2290 @@ # Copyright (C) 2020 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # Automatically generated, 2020. +# John Erling Blad , 2020-2024. # -msgid "" -msgstr "Project-Id-Version: solaar 1.0.3\n" - "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2021-09-25 13:56-0400\n" - "PO-Revision-Date: 2020-08-02 23:35+0200\n" - "Last-Translator: Automatically generated\n" - "Language-Team: none\n" - "Language: nn\n" - "MIME-Version: 1.0\n" - "Content-Type: text/plain; charset=UTF-8\n" - "Content-Transfer-Encoding: 8bit\n" - "Plural-Forms: nplurals=2; plural=(n != 1);\n" - "X-Generator: Poedit 2.3\n" +msgid "" +msgstr "" +"Project-Id-Version: solaar 1.0.3\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-10-31 01:42+0100\n" +"PO-Revision-Date: 2024-10-31 02:20+0100\n" +"Last-Translator: John Erling Blad \n" +"Language-Team: \n" +"Language: nb\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1)\n" +"X-Generator: Gtranslator 47.0\n" -#: lib/logitech_receiver/base_usb.py:50 -msgid "Unifying Receiver" -msgstr "" +#: lib/logitech_receiver/base_usb.py:48 +msgid "Bolt Receiver" +msgstr "Bolt mottakar" -#: lib/logitech_receiver/base_usb.py:58 lib/logitech_receiver/base_usb.py:68 -#: lib/logitech_receiver/base_usb.py:79 lib/logitech_receiver/base_usb.py:90 -#: lib/logitech_receiver/base_usb.py:101 -msgid "Nano Receiver" -msgstr "" +#: lib/logitech_receiver/base_usb.py:60 +msgid "Unifying Receiver" +msgstr "Unifying mottakar" -#: lib/logitech_receiver/base_usb.py:109 -msgid "Lightspeed Receiver" -msgstr "" +#: lib/logitech_receiver/base_usb.py:71 lib/logitech_receiver/base_usb.py:83 +#: lib/logitech_receiver/base_usb.py:96 lib/logitech_receiver/base_usb.py:109 +msgid "Nano Receiver" +msgstr "Nano mottakar" -#: lib/logitech_receiver/base_usb.py:117 -msgid "EX100 Receiver 27 Mhz" -msgstr "" +#: lib/logitech_receiver/base_usb.py:121 +msgid "Lightspeed Receiver" +msgstr "Lightspeed mottakar" -#: lib/logitech_receiver/device.py:133 lib/solaar/ui/window.py:693 -msgid "unknown" -msgstr "ukjend" +#: lib/logitech_receiver/base_usb.py:131 +msgid "EX100 Receiver 27 Mhz" +msgstr "EX100 27 Mhz mottakar" + +#: lib/logitech_receiver/common.py:648 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Batteri: %(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:651 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Batteri: %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:962 +#: lib/logitech_receiver/settings_templates.py:294 +msgid "Disabled" +msgstr "Avslått" + +#: lib/logitech_receiver/hidpp20.py:963 +msgid "Static" +msgstr "Statisk" + +#: lib/logitech_receiver/hidpp20.py:964 +msgid "Pulse" +msgstr "Puls" + +#: lib/logitech_receiver/hidpp20.py:965 +msgid "Cycle" +msgstr "Syklus" + +#: lib/logitech_receiver/hidpp20.py:966 +msgid "Boot" +msgstr "Boot" + +#: lib/logitech_receiver/hidpp20.py:967 +msgid "Demo" +msgstr "Demo" + +#: lib/logitech_receiver/hidpp20.py:969 +msgid "Breathe" +msgstr "Pust" + +#: lib/logitech_receiver/hidpp20.py:972 +msgid "Ripple" +msgstr "Krusning" + +#: lib/logitech_receiver/hidpp20.py:973 +msgid "Decomposition" +msgstr "Dekomponering" + +#: lib/logitech_receiver/hidpp20.py:974 +msgid "Signature1" +msgstr "Signatur1" + +#: lib/logitech_receiver/hidpp20.py:975 +msgid "Signature2" +msgstr "Signatur2" + +#: lib/logitech_receiver/hidpp20.py:976 +msgid "CycleS" +msgstr "SyklusS" + +#: lib/logitech_receiver/hidpp20.py:1040 +msgid "Unknown Location" +msgstr "Ukjend lokasjon" + +#: lib/logitech_receiver/hidpp20.py:1041 +msgid "Primary" +msgstr "Primær" + +#: lib/logitech_receiver/hidpp20.py:1042 +msgid "Logo" +msgstr "Logo" + +#: lib/logitech_receiver/hidpp20.py:1043 +msgid "Left Side" +msgstr "Venstre side" + +#: lib/logitech_receiver/hidpp20.py:1044 +msgid "Right Side" +msgstr "Høgre side" + +#: lib/logitech_receiver/hidpp20.py:1045 +msgid "Combined" +msgstr "Kombinert" + +#: lib/logitech_receiver/hidpp20.py:1046 +msgid "Primary 1" +msgstr "Primær 1" + +#: lib/logitech_receiver/hidpp20.py:1047 +msgid "Primary 2" +msgstr "Primær 2" + +#: lib/logitech_receiver/hidpp20.py:1048 +msgid "Primary 3" +msgstr "Primær 3" + +#: lib/logitech_receiver/hidpp20.py:1049 +msgid "Primary 4" +msgstr "Primær 4" + +#: lib/logitech_receiver/hidpp20.py:1050 +msgid "Primary 5" +msgstr "Primær 5" + +#: lib/logitech_receiver/hidpp20.py:1051 +msgid "Primary 6" +msgstr "Primær 6" + +#: lib/logitech_receiver/i18n.py:28 +msgid "empty" +msgstr "tom" + +#: lib/logitech_receiver/i18n.py:29 +msgid "critical" +msgstr "kritisk" + +#: lib/logitech_receiver/i18n.py:30 +msgid "low" +msgstr "låg" + +#: lib/logitech_receiver/i18n.py:31 +msgid "average" +msgstr "middels" + +#: lib/logitech_receiver/i18n.py:32 +msgid "good" +msgstr "god" + +#: lib/logitech_receiver/i18n.py:33 +msgid "full" +msgstr "full" + +#: lib/logitech_receiver/i18n.py:35 +msgid "discharging" +msgstr "utlading" + +#: lib/logitech_receiver/i18n.py:36 +msgid "recharging" +msgstr "opplading" + +#: lib/logitech_receiver/i18n.py:37 +msgid "charging" +msgstr "lader" #: lib/logitech_receiver/i18n.py:38 -msgid "empty" -msgstr "tom" +msgid "not charging" +msgstr "lader ikkje" #: lib/logitech_receiver/i18n.py:39 -msgid "critical" -msgstr "kritisk" +msgid "almost full" +msgstr "nesten full" #: lib/logitech_receiver/i18n.py:40 -msgid "low" -msgstr "låg" +msgid "charged" +msgstr "ladet" #: lib/logitech_receiver/i18n.py:41 -msgid "average" -msgstr "" +msgid "slow recharge" +msgstr "langsom lading" #: lib/logitech_receiver/i18n.py:42 -msgid "good" -msgstr "god" +msgid "invalid battery" +msgstr "batterifeil" #: lib/logitech_receiver/i18n.py:43 -msgid "full" -msgstr "full" +msgid "thermal error" +msgstr "termisk feil" + +#: lib/logitech_receiver/i18n.py:44 +msgid "error" +msgstr "feil" + +#: lib/logitech_receiver/i18n.py:45 +msgid "standard" +msgstr "standard" #: lib/logitech_receiver/i18n.py:46 -msgid "discharging" -msgstr "utlading" +msgid "fast" +msgstr "rask" #: lib/logitech_receiver/i18n.py:47 -msgid "recharging" -msgstr "opplading" - -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:721 -msgid "charging" -msgstr "lader" +msgid "slow" +msgstr "langsom" #: lib/logitech_receiver/i18n.py:49 -msgid "not charging" -msgstr "" +msgid "device timeout" +msgstr "eininga svarte ikkje" #: lib/logitech_receiver/i18n.py:50 -msgid "almost full" -msgstr "nesten full" +msgid "device not supported" +msgstr "eininga er ikkje støtta" #: lib/logitech_receiver/i18n.py:51 -msgid "charged" -msgstr "ladet" +msgid "too many devices" +msgstr "for mange einingar" #: lib/logitech_receiver/i18n.py:52 -msgid "slow recharge" -msgstr "langsom lading" - -#: lib/logitech_receiver/i18n.py:53 -msgid "invalid battery" -msgstr "batterifeil" +msgid "sequence timeout" +msgstr "sekvens timeout" #: lib/logitech_receiver/i18n.py:54 -msgid "thermal error" -msgstr "termisk feil" +msgid "Firmware" +msgstr "Fastvare" #: lib/logitech_receiver/i18n.py:55 -msgid "error" -msgstr "" +msgid "Bootloader" +msgstr "Oppstartslaster" #: lib/logitech_receiver/i18n.py:56 -msgid "standard" -msgstr "" +msgid "Hardware" +msgstr "Maskinvare" #: lib/logitech_receiver/i18n.py:57 -msgid "fast" -msgstr "" +msgid "Other" +msgstr "Anna" -#: lib/logitech_receiver/i18n.py:58 -msgid "slow" -msgstr "" +#: lib/logitech_receiver/i18n.py:59 +msgid "Left Button" +msgstr "Venstreknapp" + +#: lib/logitech_receiver/i18n.py:60 +msgid "Right Button" +msgstr "Høgreknapp" #: lib/logitech_receiver/i18n.py:61 -msgid "device timeout" -msgstr "eininga svarte ikkje" +msgid "Middle Button" +msgstr "Midtknapp" #: lib/logitech_receiver/i18n.py:62 -msgid "device not supported" -msgstr "eininga er ikkje støtta" +msgid "Back Button" +msgstr "Tlbakeknapp" #: lib/logitech_receiver/i18n.py:63 -msgid "too many devices" -msgstr "for mange einingar" +msgid "Forward Button" +msgstr "Framoverknapp" #: lib/logitech_receiver/i18n.py:64 -msgid "sequence timeout" -msgstr "sekvens timeout" +msgid "Mouse Gesture Button" +msgstr "Rørsleknapp" -#: lib/logitech_receiver/i18n.py:67 lib/solaar/ui/window.py:580 -msgid "Firmware" -msgstr "Fastvare" +#: lib/logitech_receiver/i18n.py:65 +msgid "Smart Shift" +msgstr "Smart skift" + +#: lib/logitech_receiver/i18n.py:66 +msgid "DPI Switch" +msgstr "DPI-bryter" + +#: lib/logitech_receiver/i18n.py:67 +msgid "Left Tilt" +msgstr "Venstre-tilt" #: lib/logitech_receiver/i18n.py:68 -msgid "Bootloader" -msgstr "Oppstartslaster" +msgid "Right Tilt" +msgstr "Høgre-tilt" #: lib/logitech_receiver/i18n.py:69 -msgid "Hardware" -msgstr "Maskinvare" +msgid "Left Click" +msgstr "Venstre-klikk" #: lib/logitech_receiver/i18n.py:70 -msgid "Other" -msgstr "Anna" +msgid "Right Click" +msgstr "Høgre-klikk" + +#: lib/logitech_receiver/i18n.py:71 +msgid "Mouse Middle Button" +msgstr "Midtknapp for mus" + +#: lib/logitech_receiver/i18n.py:72 +msgid "Mouse Back Button" +msgstr "Tilbakeknapp for mus" #: lib/logitech_receiver/i18n.py:73 -msgid "Left Button" -msgstr "" +msgid "Mouse Forward Button" +msgstr "Framoverknapp for mus" #: lib/logitech_receiver/i18n.py:74 -msgid "Right Button" -msgstr "" +msgid "Gesture Button Navigation" +msgstr "Navigering med rørsleknapp" #: lib/logitech_receiver/i18n.py:75 -msgid "Middle Button" -msgstr "" +msgid "Mouse Scroll Left Button" +msgstr "Venstre rulleknapp for mus" #: lib/logitech_receiver/i18n.py:76 -msgid "Back Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:77 -msgid "Forward Button" -msgstr "" +msgid "Mouse Scroll Right Button" +msgstr "Høgre rulleknapp for mus" #: lib/logitech_receiver/i18n.py:78 -msgid "Mouse Gesture Button" -msgstr "" +msgid "pressed" +msgstr "nedtrykt" #: lib/logitech_receiver/i18n.py:79 -msgid "Smart Shift" -msgstr "Smart skift" - -#: lib/logitech_receiver/i18n.py:80 -msgid "DPI Switch" -msgstr "" - -#: lib/logitech_receiver/i18n.py:81 -msgid "Left Tilt" -msgstr "" - -#: lib/logitech_receiver/i18n.py:82 -msgid "Right Tilt" -msgstr "" - -#: lib/logitech_receiver/i18n.py:83 -msgid "Left Click" -msgstr "" - -#: lib/logitech_receiver/i18n.py:84 -msgid "Right Click" -msgstr "" - -#: lib/logitech_receiver/i18n.py:85 -msgid "Mouse Middle Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:86 -msgid "Mouse Back Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:87 -msgid "Mouse Forward Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:88 -msgid "Gesture Button Navigation" -msgstr "" - -#: lib/logitech_receiver/i18n.py:89 -msgid "Mouse Scroll Left Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:90 -msgid "Mouse Scroll Right Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:93 -msgid "pressed" -msgstr "" - -#: lib/logitech_receiver/i18n.py:94 -msgid "released" -msgstr "" - -#: lib/logitech_receiver/notifications.py:76 -msgid "pairing lock is closed" -msgstr "parlåsen er lukka" - -#: lib/logitech_receiver/notifications.py:76 -msgid "pairing lock is open" -msgstr "parlåsen er open" - -#: lib/logitech_receiver/notifications.py:160 lib/solaar/ui/notify.py:123 -msgid "connected" -msgstr "tilkopla" - -#: lib/logitech_receiver/notifications.py:160 -msgid "disconnected" -msgstr "" - -#: lib/logitech_receiver/notifications.py:198 lib/solaar/ui/notify.py:121 -msgid "unpaired" -msgstr "uparet" - -#: lib/logitech_receiver/notifications.py:248 -msgid "powered on" -msgstr "påslått" - -#: lib/logitech_receiver/settings.py:526 -msgid "register" -msgstr "" - -#: lib/logitech_receiver/settings.py:542 lib/logitech_receiver/settings.py:568 -msgid "feature" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Hand Detection" -msgstr "Håndpåvisning" - -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "Slå på belysning når hendene er over tastaturet." - -#: lib/logitech_receiver/settings_templates.py:72 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:73 -#: lib/logitech_receiver/settings_templates.py:78 -#: lib/logitech_receiver/settings_templates.py:85 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "Høgsensitiv modus for vertikal rulling med hjulet." - -#: lib/logitech_receiver/settings_templates.py:74 -msgid "Side Scrolling" -msgstr "Siderulling" - -#: lib/logitech_receiver/settings_templates.py:75 -msgid "When disabled, pushing the wheel sideways sends custom button " - "events\n" - "instead of the standard side-scrolling events." -msgstr "Viss deaktivert; blir trykt hjula sidevegs så sendast ein tilpassa " - "knapphending\n" - "i staden for standard siderulling." - -#: lib/logitech_receiver/settings_templates.py:77 -msgid "Scroll Wheel High Resolution" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:79 -msgid "Scroll Wheel Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:80 -msgid "HID++ mode for vertical scroll with the wheel." -msgstr "HID++ modus for vertikal rulling med hjulet." - -#: lib/logitech_receiver/settings_templates.py:81 -msgid "Effectively turns off wheel scrolling in Linux." -msgstr "Slår i praksis av hjulrulling i Linux." - -#: lib/logitech_receiver/settings_templates.py:82 -msgid "Scroll Wheel Direction" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:83 -msgid "Invert direction for vertical scroll with wheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:84 -msgid "Scroll Wheel Resolution" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Frequency of device polling, in milliseconds" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Polling Rate (ms)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:87 -msgid "Swap Fx function" -msgstr "Bytt Fx-funksjon" - -#: lib/logitech_receiver/settings_templates.py:88 -msgid "When set, the F1..F12 keys will activate their special function,\n" - "and you must hold the FN key to activate their standard function." -msgstr "Viss aktivert, vil F1–F12-tastane aktivera sin spesialfunksjon,\n" - "og du må halda FN-tasten nede for å aktivera deira standardfunksjon." - -#: lib/logitech_receiver/settings_templates.py:90 -msgid "When unset, the F1..F12 keys will activate their standard function,\n" - "and you must hold the FN key to activate their special function." -msgstr "Viss deaktivert, vil F1–F12-tastane aktivera sin standardfunksjon,\n" - "og du må holde FN-tasten nede for å aktivere deira spesialfunksjon." - -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Mouse movement sensitivity" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Sensitivity (DPI)" -msgstr "Følsomhet (DPI)" - -#: lib/logitech_receiver/settings_templates.py:93 -msgid "Sensitivity (Pointer Speed)" -msgstr "Følsomhet (pekerfart)" - -#: lib/logitech_receiver/settings_templates.py:94 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "Fartsmultiplikator for mus (256 er normal multiplikator)." - -#: lib/logitech_receiver/settings_templates.py:95 -msgid "Scroll Wheel Rachet" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:96 -msgid "Automatically switch the mouse wheel between ratchet and freespin " - "mode.\n" - "The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:98 -msgid "Backlight" -msgstr "Bakbelysning" - -#: lib/logitech_receiver/settings_templates.py:98 -msgid "Turn illumination on or off on keyboard." -msgstr "Slå belysning på eller av for tastaturet." - -#: lib/logitech_receiver/settings_templates.py:99 -msgid "Key/Button Actions" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:100 -msgid "Change the action for the key or button." -msgstr "Endre handlinga for ein tast eller knapp." - -#: lib/logitech_receiver/settings_templates.py:101 -msgid "Changing important actions (such as for the left mouse button) can " - "result in an unusable system." -msgstr "Endring av viktige handlingar (til dømes venstre musknapp) kan " - "resultera i eit ubrukande system." - -#: lib/logitech_receiver/settings_templates.py:102 -msgid "Key/Button Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:103 -msgid "Make the key or button send HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable keys" -msgstr "Slå av tastar" - -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable specific keyboard keys." -msgstr "Slå av spesifikke tastaturtastar." - -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Change keys to match OS." -msgstr "Endre tastar så dei stemmer med OS-et." - -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Set OS" -msgstr "Sett OS" - -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Change Host" -msgstr "Endre tjener" - -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Switch connection to a different host" -msgstr "Endre koplinga til ein annan tenar" - -#: lib/logitech_receiver/settings_templates.py:107 -msgid "Thumb Wheel Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:108 -msgid "HID++ mode for horizontal scroll with the thumb wheel." -msgstr "HID++ modus for horisontal rulling med tommelhjulet." - -#: lib/logitech_receiver/settings_templates.py:109 -msgid "Effectively turns off thumb scrolling in Linux." -msgstr "Slår i praksis av tommelrulling i Linux." - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Invert thumb wheel scroll direction." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Thumb Wheel Direction" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Gestures" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Gesture params" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:113 -msgid "DPI Sliding Adjustment" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:114 -msgid "Adjust the DPI by sliding the mouse horizontally while holding the " - "button down." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:115 -msgid "Mouse Gestures" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:116 -msgid "Send a gesture by sliding the mouse while holding the button down." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:117 -msgid "Divert crown events" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:118 -msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:119 -msgid "Divert G Keys" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:120 -msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Performs a left click." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Single tap" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Performs a right click." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Single tap with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:125 -msgid "Single tap with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Double tap" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Performs a double click." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:130 -msgid "Double tap with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:131 -msgid "Double tap with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Drags items by dragging the finger after double tapping." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Tap and drag" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:135 -msgid "Tap and drag with two fingers" -msgstr "" +msgid "released" +msgstr "utløyst" + +#: lib/logitech_receiver/notifications.py:86 +#: lib/logitech_receiver/notifications.py:138 +msgid "pairing lock is closed" +msgstr "parlåsen er lukka" + +#: lib/logitech_receiver/notifications.py:86 +#: lib/logitech_receiver/notifications.py:138 +msgid "pairing lock is open" +msgstr "parlåsen er open" + +#: lib/logitech_receiver/notifications.py:103 +msgid "discovery lock is closed" +msgstr "oppdagelseslåsen er lukka" + +#: lib/logitech_receiver/notifications.py:103 +msgid "discovery lock is open" +msgstr "oppdagelseslåsen er open" + +#: lib/logitech_receiver/notifications.py:232 lib/solaar/listener.py:247 +msgid "connected" +msgstr "tilkopla" + +#: lib/logitech_receiver/notifications.py:232 lib/solaar/listener.py:247 +msgid "disconnected" +msgstr "fråkopla" + +#: lib/logitech_receiver/notifications.py:258 +msgid "unpaired" +msgstr "upara" + +#: lib/logitech_receiver/notifications.py:305 +msgid "powered on" +msgstr "påslått" + +#: lib/logitech_receiver/receiver.py:383 +msgid "No paired devices." +msgstr "Inga para eining." + +#: lib/logitech_receiver/receiver.py:385 lib/solaar/ui/window.py:590 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s para eining." +msgstr[1] "%(count)s para einingar." + +#: lib/logitech_receiver/settings.py:614 +msgid "register" +msgstr "register" + +#: lib/logitech_receiver/settings.py:628 lib/logitech_receiver/settings.py:663 +msgid "feature" +msgstr "karakteristikk" + +#: lib/logitech_receiver/settings_templates.py:133 +msgid "Swap Fx function" +msgstr "Bytt Fx-funksjon" #: lib/logitech_receiver/settings_templates.py:136 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:137 -msgid "Tap and drag with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:139 -msgid "Suppress tap and edge gestures" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:140 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "" +msgid "" +"When set, the F1..F12 keys will activate their special function,\n" +"and you must hold the FN key to activate their standard function." +msgstr "" +"Viss aktivert, vil F1–F12-tastane aktivera sin spesialfunksjon,\n" +"og du må halda FN-tasten nede for å aktivera deira standardfunksjon." #: lib/logitech_receiver/settings_templates.py:141 -msgid "Scroll with one finger" -msgstr "" +msgid "" +"When unset, the F1..F12 keys will activate their standard function,\n" +"and you must hold the FN key to activate their special function." +msgstr "" +"Viss deaktivert, vil F1–F12-tastane aktivera sin standardfunksjon,\n" +"og du må holde FN-tasten nede for å aktivere deira spesialfunksjon." -#: lib/logitech_receiver/settings_templates.py:141 -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scrolls." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:149 +msgid "Hand Detection" +msgstr "Håndpåvisning" -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scroll with two fingers" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:150 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Slå på belysning når hendene er over tastaturet." -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scroll horizontally with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scrolls horizontally." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scroll vertically with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scrolls vertically." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Inverts the scrolling direction." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Natural scrolling" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Enables the thumbwheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Thumbwheel" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:157 +msgid "Scroll Wheel Smooth Scrolling" +msgstr "Mjuk rulling med rullehjul" #: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:162 -msgid "Swipe from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:159 -msgid "Swipe from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:160 -msgid "Swipe from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:161 -msgid "Swipe from the bottom edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:163 -msgid "Swipe two fingers from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:164 -msgid "Swipe two fingers from the right edge" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:405 +#: lib/logitech_receiver/settings_templates.py:434 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Høgsensitiv modus for vertikal rulling med hjulet." #: lib/logitech_receiver/settings_templates.py:165 -msgid "Swipe two fingers from the bottom edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:166 -msgid "Swipe two fingers from the top edge" -msgstr "" +msgid "Side Scrolling" +msgstr "Siderulling" #: lib/logitech_receiver/settings_templates.py:167 -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "" +msgid "" +"When disabled, pushing the wheel sideways sends custom button events\n" +"instead of the standard side-scrolling events." +msgstr "" +"Viss deaktivert; blir trykt hjula sidevegs så sendast ein tilpassa " +"knapphending\n" +"i staden for standard siderulling." -#: lib/logitech_receiver/settings_templates.py:167 -msgid "Zoom with two fingers." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:177 +msgid "Sensitivity (DPI - older mice)" +msgstr "Følsomhet (DPI – eldre mus)" -#: lib/logitech_receiver/settings_templates.py:168 -msgid "Pinch to zoom out." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:178 +#: lib/logitech_receiver/settings_templates.py:977 +#: lib/logitech_receiver/settings_templates.py:1005 +msgid "Mouse movement sensitivity" +msgstr "Sensibilitet for musrørsler" -#: lib/logitech_receiver/settings_templates.py:169 -msgid "Spread to zoom in." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:251 +msgid "Backlight Timed" +msgstr "Tidfesta bakgrunnslys" -#: lib/logitech_receiver/settings_templates.py:170 -msgid "Zoom with three fingers." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:252 +#: lib/logitech_receiver/settings_templates.py:392 +msgid "Set illumination time for keyboard." +msgstr "Still inn belysningstid for tastatur." -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Zoom with two fingers" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:263 +msgid "Backlight" +msgstr "Bakbelysning" -#: lib/logitech_receiver/settings_templates.py:189 -msgid "Pixel zone" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:264 +msgid "" +"Illumination level on keyboard. Changes made are only applied in Manual " +"mode." +msgstr "" +"Lysnivå på tastaturet. Endringar som er gjorde, blir berre brukte i manuell " +"modus." -#: lib/logitech_receiver/settings_templates.py:190 -msgid "Ratio zone" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:296 +msgid "Automatic" +msgstr "Automatisk" -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Scale factor" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:298 +msgid "Manual" +msgstr "Manuell" -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Sets the cursor speed." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:300 +msgid "Enabled" +msgstr "Påslått" -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:306 +msgid "Backlight Level" +msgstr "Bakgrunnslysnivå" -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left-most coordinate." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:307 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "Lysnivå på tastaturet i manuell modus." -#: lib/logitech_receiver/settings_templates.py:196 -msgid "Top-most coordinate." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:364 +msgid "Backlight Delay Hands Out" +msgstr "Forseinking på bakgrunnsbelysning når hendene blir fjerna" -#: lib/logitech_receiver/settings_templates.py:196 -msgid "top" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:365 +msgid "" +"Delay in seconds until backlight fades out with hands away from keyboard." +msgstr "" +"Forseinking i sekund til bakgrunnsbelysninga tonar ut når hendene er borte " +"frå tastaturet." -#: lib/logitech_receiver/settings_templates.py:197 -msgid "Width." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:373 +msgid "Backlight Delay Hands In" +msgstr "Forseinking på bakgrunnsbelysning når hendene er nære" -#: lib/logitech_receiver/settings_templates.py:197 -msgid "width" -msgstr "breidde" +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "" +"Forseinking i sekund til bakgrunnsbelysninga tonar ut når hendene er nære " +"tastaturet." -#: lib/logitech_receiver/settings_templates.py:198 -msgid "Height." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:382 +msgid "Backlight Delay Powered" +msgstr "Forseinking på bakgrunnsbelysning når straumforsynt" -#: lib/logitech_receiver/settings_templates.py:198 -msgid "height" -msgstr "høgde" +#: lib/logitech_receiver/settings_templates.py:383 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "" +"Forseinking i sekund til bakgrunnsbelysninga tonar ut med ekstern " +"straumforsyning." -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Cursor speed." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:391 +msgid "Backlight (Seconds)" +msgstr "Bakgrunnslys (sekund)" -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Scale" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:403 +msgid "Scroll Wheel High Resolution" +msgstr "Høgopplausleg rullehjul" -#: lib/logitech_receiver/settings_templates.py:202 +#: lib/logitech_receiver/settings_templates.py:407 +#: lib/logitech_receiver/settings_templates.py:436 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "Sett til å ignorera viss rullinga er unormalt rask eller sakte" + +#: lib/logitech_receiver/settings_templates.py:414 +#: lib/logitech_receiver/settings_templates.py:445 +msgid "Scroll Wheel Diversion" +msgstr "Omdirigering for rullehjul" + +#: lib/logitech_receiver/settings_templates.py:416 +msgid "" +"Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " +"Solaar rules but are otherwise ignored)." +msgstr "" +"Få rullehjulet til å senda LOWRES_WHEEL HID++-varslar (som utløyser Solaar-" +"reglar, men som elles blir ignorert)." + +#: lib/logitech_receiver/settings_templates.py:423 +msgid "Scroll Wheel Direction" +msgstr "Retning på rullehjul" + +#: lib/logitech_receiver/settings_templates.py:424 +msgid "Invert direction for vertical scroll with wheel." +msgstr "Inverter retning for vertikal rulling med hjul." + +#: lib/logitech_receiver/settings_templates.py:432 +msgid "Scroll Wheel Resolution" +msgstr "Oppløysing på rullehjul" + +#: lib/logitech_receiver/settings_templates.py:447 +msgid "" +"Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"Få rullehjulet til å senda HIRS_WHEEL HID++-varslar (som utløyser Solaar-" +"reglar, men som elles blir ignorert)." + +#: lib/logitech_receiver/settings_templates.py:456 +msgid "Sensitivity (Pointer Speed)" +msgstr "Følsomhet (pekerfart)" + +#: lib/logitech_receiver/settings_templates.py:457 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Fartsmultiplikator for mus (256 er normal multiplikator)." + +#: lib/logitech_receiver/settings_templates.py:467 +msgid "Thumb Wheel Diversion" +msgstr "Omdirigering for tommelhjul" + +#: lib/logitech_receiver/settings_templates.py:469 +msgid "" +"Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"Få tommelhjulet til å senda THUMB_WHEEL HID++-varslar (som utløyser Solaar-" +"reglar, men som elles blir ignorert)." + +#: lib/logitech_receiver/settings_templates.py:478 +msgid "Thumb Wheel Direction" +msgstr "Retning på tommelhjul" + +#: lib/logitech_receiver/settings_templates.py:479 +msgid "Invert thumb wheel scroll direction." +msgstr "Inverter rulleretning for tommelhjul." + +#: lib/logitech_receiver/settings_templates.py:499 +msgid "Onboard Profiles" +msgstr "Innebygde profilar" + +#: lib/logitech_receiver/settings_templates.py:500 +msgid "" +"Enable an onboard profile, which controls report rate, sensitivity, and " +"button actions" +msgstr "" +"Aktiver ein innebygd profil, som kontrollerer rapporthyppigheit, " +"sensibilitet og knappehandlingar" + +#: lib/logitech_receiver/settings_templates.py:544 +#: lib/logitech_receiver/settings_templates.py:577 +msgid "Report Rate" +msgstr "Rapporthyppigheit" + +#: lib/logitech_receiver/settings_templates.py:546 +#: lib/logitech_receiver/settings_templates.py:579 +msgid "Frequency of device movement reports" +msgstr "Frekvensen til rapportar om einingsrørsler" + +#: lib/logitech_receiver/settings_templates.py:546 +#: lib/logitech_receiver/settings_templates.py:579 +#: lib/logitech_receiver/settings_templates.py:1005 +#: lib/logitech_receiver/settings_templates.py:1379 +#: lib/logitech_receiver/settings_templates.py:1410 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" +"Kan trenga at innebygde profilar blir sette til avslått for å vera effektive." + +#: lib/logitech_receiver/settings_templates.py:607 +msgid "Divert crown events" +msgstr "Omdiriger krone-hendingar" + +#: lib/logitech_receiver/settings_templates.py:608 +msgid "" +"Make crown send CROWN HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"Få krona til å senda CROWN HID++-varslar (som utløyser Solaar-reglar, men " +"som elles blir ignorert)." + +#: lib/logitech_receiver/settings_templates.py:616 +msgid "Crown smooth scroll" +msgstr "Glatt krone-rulling" + +#: lib/logitech_receiver/settings_templates.py:617 +msgid "Set crown smooth scroll" +msgstr "Still inn glatt krone-rulling" + +#: lib/logitech_receiver/settings_templates.py:625 +msgid "Divert G and M Keys" +msgstr "Omdiriger G- og M-tastar" + +#: lib/logitech_receiver/settings_templates.py:626 +msgid "" +"Make G and M keys send HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"Få G- og M-tastane til å senda HID++-varslar (som utløyser Solaar-reglar, " +"men som elles blir ignorerte)." + +#: lib/logitech_receiver/settings_templates.py:640 +msgid "Scroll Wheel Ratcheted" +msgstr "Rullehjul sperrehaka" + +#: lib/logitech_receiver/settings_templates.py:641 +msgid "" +"Switch the mouse wheel between speed-controlled ratcheting and always " +"freespin." +msgstr "" +"Byt musehjulet mellom fartskontrollert sperrehake og alltid frittløpende." + +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Freespinning" +msgstr "Frittløpende" + +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Ratcheted" +msgstr "Sperrehake" + +#: lib/logitech_receiver/settings_templates.py:650 +msgid "Scroll Wheel Ratchet Speed" +msgstr "Rullehjul sperrehakefart" + +#: lib/logitech_receiver/settings_templates.py:652 +msgid "" +"Use the mouse wheel speed to switch between ratcheted and freespinning.\n" +"The mouse wheel is always ratcheted at 50." +msgstr "" +"Bruk musehjulfarten til å byta mellom sperrehake og frittløpende.\n" +"Musehjulet er alltid sett til 50." + +#: lib/logitech_receiver/settings_templates.py:701 +msgid "Key/Button Actions" +msgstr "Taste-/knappehandling" + +#: lib/logitech_receiver/settings_templates.py:703 +msgid "Change the action for the key or button." +msgstr "Endre handlinga for ein tast eller knapp." + +#: lib/logitech_receiver/settings_templates.py:705 +msgid "Overridden by diversion." +msgstr "Overstyrt av omdirigering." + +#: lib/logitech_receiver/settings_templates.py:707 +msgid "" +"Changing important actions (such as for the left mouse button) can result in " +"an unusable system." +msgstr "" +"Endring av viktige handlingar (til dømes venstre musknapp) kan resultera i " +"eit ubrukande system." + +#: lib/logitech_receiver/settings_templates.py:882 +msgid "Key/Button Diversion" +msgstr "Taste-/knappeomdirigering" + +#: lib/logitech_receiver/settings_templates.py:883 +msgid "" +"Make the key or button send HID++ notifications (Diverted) or initiate Mouse " +"Gestures or Sliding DPI" +msgstr "" +"Få nøkkelen eller knappen til å senda HID++-varslar (omdirigert) eller " +"initier muserørsler eller glidande Dpi" + +#: lib/logitech_receiver/settings_templates.py:886 +#: lib/logitech_receiver/settings_templates.py:887 +#: lib/logitech_receiver/settings_templates.py:888 +msgid "Diverted" +msgstr "Omdirigert" + +#: lib/logitech_receiver/settings_templates.py:886 +#: lib/logitech_receiver/settings_templates.py:887 +msgid "Mouse Gestures" +msgstr "Muserørsler" + +#: lib/logitech_receiver/settings_templates.py:886 +#: lib/logitech_receiver/settings_templates.py:887 +#: lib/logitech_receiver/settings_templates.py:888 +msgid "Regular" +msgstr "Vanleg" + +#: lib/logitech_receiver/settings_templates.py:886 +msgid "Sliding DPI" +msgstr "Glidande DPI" + +#: lib/logitech_receiver/settings_templates.py:976 +#: lib/logitech_receiver/settings_templates.py:1004 +msgid "Sensitivity (DPI)" +msgstr "Følsomheit (DPI)" + +#: lib/logitech_receiver/settings_templates.py:1081 +msgid "Sensitivity Switching" +msgstr "Følsomheitsbyte" + +#: lib/logitech_receiver/settings_templates.py:1083 +msgid "" +"Switch the current sensitivity and the remembered sensitivity when the key " +"or button is pressed.\n" +"If there is no remembered sensitivity, just remember the current sensitivity" +msgstr "" +"Byt gjeldande følsomheit og tidlegare følsomheit når tasten eller knappen " +"blir trykt.\n" +"Viss det ikkje er nokon tidlegare følsomheit, sett berre gjeldande følsomheit" + +#: lib/logitech_receiver/settings_templates.py:1087 +msgid "Off" +msgstr "Av" + +#: lib/logitech_receiver/settings_templates.py:1118 +msgid "Disable keys" +msgstr "Slå av tastar" + +#: lib/logitech_receiver/settings_templates.py:1119 +msgid "Disable specific keyboard keys." +msgstr "Slå av spesifikke tastaturtastar." + +#: lib/logitech_receiver/settings_templates.py:1122 #, python-format -msgid "Disables the %s key." -msgstr "" +msgid "Disables the %s key." +msgstr "Slår av %s-tasten." -#: lib/logitech_receiver/settings_templates.py:521 -#: lib/logitech_receiver/settings_templates.py:573 -msgid "Off" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1135 +#: lib/logitech_receiver/settings_templates.py:1192 +msgid "Set OS" +msgstr "Sett OS" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Diverted" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1136 +#: lib/logitech_receiver/settings_templates.py:1193 +msgid "Change keys to match OS." +msgstr "Endre tastar så dei stemmer med OS-et." -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Regular" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1205 +msgid "Change Host" +msgstr "Endre tjener" -#: lib/logitech_receiver/status.py:109 -msgid "No paired devices." -msgstr "Inga para eining." +#: lib/logitech_receiver/settings_templates.py:1206 +msgid "Switch connection to a different host" +msgstr "Endre koplinga til ein annan tenar" -#: lib/logitech_receiver/status.py:110 lib/solaar/ui/window.py:623 +#: lib/logitech_receiver/settings_templates.py:1230 +msgid "Performs a left click." +msgstr "Utfører eit venstreklikk." + +#: lib/logitech_receiver/settings_templates.py:1230 +msgid "Single tap" +msgstr "Enkeltklikk" + +#: lib/logitech_receiver/settings_templates.py:1231 +msgid "Performs a right click." +msgstr "Utfører eit høgreklikk." + +#: lib/logitech_receiver/settings_templates.py:1231 +msgid "Single tap with two fingers" +msgstr "Enkelt trykk med to fingrar" + +#: lib/logitech_receiver/settings_templates.py:1232 +msgid "Single tap with three fingers" +msgstr "Enkelt trykk med tre fingrar" + +#: lib/logitech_receiver/settings_templates.py:1236 +msgid "Double tap" +msgstr "Dobbeltklikk" + +#: lib/logitech_receiver/settings_templates.py:1236 +msgid "Performs a double click." +msgstr "Utfører eit dobbeltklikk" + +#: lib/logitech_receiver/settings_templates.py:1237 +msgid "Double tap with two fingers" +msgstr "Dobbeltklikk med to fingrar" + +#: lib/logitech_receiver/settings_templates.py:1238 +msgid "Double tap with three fingers" +msgstr "Dobbeltklikk med to fingrar" + +#: lib/logitech_receiver/settings_templates.py:1241 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Dra element med fingeren etter dobbeltklikk." + +#: lib/logitech_receiver/settings_templates.py:1241 +msgid "Tap and drag" +msgstr "Klikk og dra" + +#: lib/logitech_receiver/settings_templates.py:1243 +msgid "Tap and drag with two fingers" +msgstr "Klikk og dra med to fingrar" + +#: lib/logitech_receiver/settings_templates.py:1244 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Dra element med fingrane etter dobbeltklikk." + +#: lib/logitech_receiver/settings_templates.py:1246 +msgid "Tap and drag with three fingers" +msgstr "Klikk og dra med tre fingrar" + +#: lib/logitech_receiver/settings_templates.py:1249 +msgid "Suppress tap and edge gestures" +msgstr "Undertrykk klikk- og kantrørsler" + +#: lib/logitech_receiver/settings_templates.py:1250 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "Slår av klikk- og kantrørsler (svarer til å trykkja Fn+Venstreklikk)." + +#: lib/logitech_receiver/settings_templates.py:1252 +msgid "Scroll with one finger" +msgstr "Rull med ein finger" + +#: lib/logitech_receiver/settings_templates.py:1252 +#: lib/logitech_receiver/settings_templates.py:1253 +#: lib/logitech_receiver/settings_templates.py:1256 +msgid "Scrolls." +msgstr "Rullar" + +#: lib/logitech_receiver/settings_templates.py:1253 +#: lib/logitech_receiver/settings_templates.py:1256 +msgid "Scroll with two fingers" +msgstr "Rull med to fingre" + +#: lib/logitech_receiver/settings_templates.py:1254 +msgid "Scroll horizontally with two fingers" +msgstr "Rull horisontalt med to fingrar" + +#: lib/logitech_receiver/settings_templates.py:1254 +msgid "Scrolls horizontally." +msgstr "Rull horisontalt." + +#: lib/logitech_receiver/settings_templates.py:1255 +msgid "Scroll vertically with two fingers" +msgstr "Rull vertikalt med to fingrar" + +#: lib/logitech_receiver/settings_templates.py:1255 +msgid "Scrolls vertically." +msgstr "Rull vertikalt." + +#: lib/logitech_receiver/settings_templates.py:1257 +msgid "Inverts the scrolling direction." +msgstr "Inverterer rulleretninga." + +#: lib/logitech_receiver/settings_templates.py:1257 +msgid "Natural scrolling" +msgstr "Naturleg rulling" + +#: lib/logitech_receiver/settings_templates.py:1258 +msgid "Enables the thumbwheel." +msgstr "Aktiverer tommelhjulet." + +#: lib/logitech_receiver/settings_templates.py:1258 +msgid "Thumbwheel" +msgstr "Tommelhjul" + +#: lib/logitech_receiver/settings_templates.py:1269 +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Swipe from the top edge" +msgstr "Sveip frå øvre kant" + +#: lib/logitech_receiver/settings_templates.py:1270 +msgid "Swipe from the left edge" +msgstr "Sveip frå venstre kant" + +#: lib/logitech_receiver/settings_templates.py:1271 +msgid "Swipe from the right edge" +msgstr "Sveip frå høgre kant" + +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Swipe from the bottom edge" +msgstr "Sveip frå nedre kant" + +#: lib/logitech_receiver/settings_templates.py:1274 +msgid "Swipe two fingers from the left edge" +msgstr "Sveip to fingrar frå venstre kant" + +#: lib/logitech_receiver/settings_templates.py:1275 +msgid "Swipe two fingers from the right edge" +msgstr "Sveip to fingrar frå høgre kant" + +#: lib/logitech_receiver/settings_templates.py:1276 +msgid "Swipe two fingers from the bottom edge" +msgstr "Sveip to fingrar frå nedre kant" + +#: lib/logitech_receiver/settings_templates.py:1277 +msgid "Swipe two fingers from the top edge" +msgstr "Sveip to fingrar frå øvre kant" + +#: lib/logitech_receiver/settings_templates.py:1278 +#: lib/logitech_receiver/settings_templates.py:1282 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "Knip for å zooma ut; sprei for å zooma inn." + +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Zoom with two fingers." +msgstr "Zoom med to fingrar." + +#: lib/logitech_receiver/settings_templates.py:1279 +msgid "Pinch to zoom out." +msgstr "Knip for å zooma ut." + +#: lib/logitech_receiver/settings_templates.py:1280 +msgid "Spread to zoom in." +msgstr "Sprei for å zooma inn." + +#: lib/logitech_receiver/settings_templates.py:1281 +msgid "Zoom with three fingers." +msgstr "Zoom med tre fingrar." + +#: lib/logitech_receiver/settings_templates.py:1282 +msgid "Zoom with two fingers" +msgstr "Zoom med to fingrar" + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Pixel zone" +msgstr "Pikselsone" + +#: lib/logitech_receiver/settings_templates.py:1301 +msgid "Ratio zone" +msgstr "Forholdssone" + +#: lib/logitech_receiver/settings_templates.py:1302 +msgid "Scale factor" +msgstr "Skalafaktor" + +#: lib/logitech_receiver/settings_templates.py:1302 +msgid "Sets the cursor speed." +msgstr "Still inn markørfarten." + +#: lib/logitech_receiver/settings_templates.py:1306 +msgid "Left" +msgstr "Venstre" + +#: lib/logitech_receiver/settings_templates.py:1306 +msgid "Left-most coordinate." +msgstr "Koordinat lengst til venstre." + +#: lib/logitech_receiver/settings_templates.py:1307 +msgid "Bottom" +msgstr "Nederst" + +#: lib/logitech_receiver/settings_templates.py:1307 +msgid "Bottom coordinate." +msgstr "Nederste koordinat." + +#: lib/logitech_receiver/settings_templates.py:1308 +msgid "Width" +msgstr "Breidde" + +#: lib/logitech_receiver/settings_templates.py:1308 +msgid "Width." +msgstr "Breidde." + +#: lib/logitech_receiver/settings_templates.py:1309 +msgid "Height" +msgstr "Høgde" + +#: lib/logitech_receiver/settings_templates.py:1309 +msgid "Height." +msgstr "Høgde." + +#: lib/logitech_receiver/settings_templates.py:1310 +msgid "Cursor speed." +msgstr "Markørhastigheit." + +#: lib/logitech_receiver/settings_templates.py:1310 +msgid "Scale" +msgstr "Skala" + +#: lib/logitech_receiver/settings_templates.py:1316 +msgid "Gestures" +msgstr "Rørsler" + +#: lib/logitech_receiver/settings_templates.py:1317 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Juster musas/berøringsplata åtferd." + +#: lib/logitech_receiver/settings_templates.py:1333 +msgid "Gestures Diversion" +msgstr "Omdirigering av rørsler" + +#: lib/logitech_receiver/settings_templates.py:1334 +msgid "Divert mouse/touchpad gestures." +msgstr "Omdiriger musas/berøringsflatas rørsler." + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Gesture params" +msgstr "Parametrar for rørsler" + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Endre numeriske parametrar for ei mus/berøringsplate." + +#: lib/logitech_receiver/settings_templates.py:1375 +msgid "M-Key LEDs" +msgstr "M-tast LED-ene" + +#: lib/logitech_receiver/settings_templates.py:1377 +msgid "Control the M-Key LEDs." +msgstr "Kontroller M-tast LED-ene." + +#: lib/logitech_receiver/settings_templates.py:1381 +#: lib/logitech_receiver/settings_templates.py:1412 +msgid "May need G Keys diverted to be effective." +msgstr "Kan trenga omdirigering av G-tastar for å vera effektivt." + +#: lib/logitech_receiver/settings_templates.py:1387 #, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s para eining." -msgstr[1] "%(count)s para einingar." +msgid "Lights up the %s key." +msgstr "Lyser opp %s-tasten." -#: lib/logitech_receiver/status.py:165 +#: lib/logitech_receiver/settings_templates.py:1406 +msgid "MR-Key LED" +msgstr "MR-tast LED" + +#: lib/logitech_receiver/settings_templates.py:1408 +msgid "Control the MR-Key LED." +msgstr "Kontroller MR-tast LED." + +#: lib/logitech_receiver/settings_templates.py:1429 +msgid "Persistent Key/Button Mapping" +msgstr "Vedvarande tast-/knapptilordning" + +#: lib/logitech_receiver/settings_templates.py:1431 +msgid "Permanently change the mapping for the key or button." +msgstr "Endre tilordninga for tasten eller knappen permanent." + +#: lib/logitech_receiver/settings_templates.py:1433 +msgid "" +"Changing important keys or buttons (such as for the left mouse button) can " +"result in an unusable system." +msgstr "" +"Endring av viktige tastar eller knappar (som venstre museknapp) kan " +"resultera i eit ubrukeleg system." + +#: lib/logitech_receiver/settings_templates.py:1490 +msgid "Sidetone" +msgstr "Sidetone" + +#: lib/logitech_receiver/settings_templates.py:1491 +msgid "Set sidetone level." +msgstr "Still inn sidetone nivå." + +#: lib/logitech_receiver/settings_templates.py:1500 +msgid "Equalizer" +msgstr "Equalizer" + +#: lib/logitech_receiver/settings_templates.py:1501 +msgid "Set equalizer levels." +msgstr "Still inn equalizer nivå." + +#: lib/logitech_receiver/settings_templates.py:1523 +msgid "Hz" +msgstr "Hz" + +#: lib/logitech_receiver/settings_templates.py:1529 +msgid "Power Management" +msgstr "Strømstyring" + +#: lib/logitech_receiver/settings_templates.py:1530 +msgid "Power off in minutes (0 for never)." +msgstr "Slå av i minutt (0 for aldri)." + +#: lib/logitech_receiver/settings_templates.py:1541 +msgid "Brightness Control" +msgstr "Lysstyrkekontroll" + +#: lib/logitech_receiver/settings_templates.py:1542 +msgid "Control overall brightness" +msgstr "Kontroller den generelle lysstyrken" + +#: lib/logitech_receiver/settings_templates.py:1585 +#: lib/logitech_receiver/settings_templates.py:1639 +msgid "LED Control" +msgstr "LED-kontroll" + +#: lib/logitech_receiver/settings_templates.py:1586 +#: lib/logitech_receiver/settings_templates.py:1640 +msgid "Switch control of LED zones between device and Solaar" +msgstr "Byt kontroll av LED-soner mellom eining og Solaar" + +#: lib/logitech_receiver/settings_templates.py:1601 +#: lib/logitech_receiver/settings_templates.py:1650 +msgid "LED Zone Effects" +msgstr "LED-soneeffektar" + +#: lib/logitech_receiver/settings_templates.py:1602 +#: lib/logitech_receiver/settings_templates.py:1651 +msgid "LED Control needs to be set to Solaar to be effective." +msgstr "LED-kontroll må setjast til Solaar for å vera effektiv." + +#: lib/logitech_receiver/settings_templates.py:1602 +#: lib/logitech_receiver/settings_templates.py:1651 +msgid "Set effect for LED Zone" +msgstr "Still inn effekt for LED-sone" + +#: lib/logitech_receiver/settings_templates.py:1605 +msgid "Speed" +msgstr "Fart" + +#: lib/logitech_receiver/settings_templates.py:1606 +msgid "Period" +msgstr "Periode" + +#: lib/logitech_receiver/settings_templates.py:1607 +msgid "Intensity" +msgstr "Intensitet" + +#: lib/logitech_receiver/settings_templates.py:1608 +msgid "Ramp" +msgstr "Stigning" + +#: lib/logitech_receiver/settings_templates.py:1624 +msgid "LEDs" +msgstr "LEDs" + +#: lib/logitech_receiver/settings_templates.py:1661 +msgid "Per-key Lighting" +msgstr "Lys for kvar tast" + +#: lib/logitech_receiver/settings_templates.py:1662 +msgid "Control per-key lighting." +msgstr "Kontrollar lys for kvar tast." + +#: lib/solaar/ui/__init__.py:117 +msgid "Another Solaar process is already running so just expose its window" +msgstr "Ein annan Solaar-prosess køyrer allereie, så berre vis vindauget" + +#: lib/solaar/ui/about/model.py:36 +msgid "" +"Manages Logitech receivers,\n" +"keyboards, mice, and tablets." +msgstr "" +"Administrerer Logitech-mottakarar,\n" +"tastatur, mus og nettbrett." + +#: lib/solaar/ui/about/model.py:62 +msgid "Additional Programming" +msgstr "Ekstra programmering" + +#: lib/solaar/ui/about/model.py:63 +msgid "GUI design" +msgstr "GUI utsjånad" + +#: lib/solaar/ui/about/model.py:65 +msgid "Testing" +msgstr "Testing" + +#: lib/solaar/ui/about/model.py:73 +msgid "Logitech documentation" +msgstr "Logitech dokumentasjon" + +#: lib/solaar/ui/action.py:83 lib/solaar/ui/action.py:87 +#: lib/solaar/ui/window.py:188 +msgid "Unpair" +msgstr "Fjern paring" + +#: lib/solaar/ui/action.py:86 lib/solaar/ui/diversion_rules.py:107 +msgid "Cancel" +msgstr "Avbryt" + +#: lib/solaar/ui/common.py:35 +msgid "Permissions error" +msgstr "Rettighetsfeil" + +#: lib/solaar/ui/common.py:37 #, python-format -msgid "Battery: %(level)s" -msgstr "Batteri: %(level)s" +msgid "" +"Found a Logitech receiver or device (%s), but did not have permission to " +"open it." +msgstr "" +"Fann ein Logitech-mottakar eller eining (%s), men hadde ikkje løyve til å " +"opna han." -#: lib/logitech_receiver/status.py:167 +#: lib/solaar/ui/common.py:39 +msgid "" +"If you've just installed Solaar, try disconnecting the receiver or device " +"and then reconnecting it." +msgstr "" +"Viss du nettopp har installert Solaar, kan du prøva å kopla frå mottakaren " +"eller eininga og deretter kopla ho til igjen." + +#: lib/solaar/ui/common.py:42 +msgid "Cannot connect to device error" +msgstr "Kan ikkje kopla til eining-feil" + +#: lib/solaar/ui/common.py:44 #, python-format -msgid "Battery: %(percent)d%%" -msgstr "Batteri: %(percent)d%%" +msgid "" +"Found a Logitech receiver or device at %s, but encountered an error " +"connecting to it." +msgstr "" +"Fann ein Logitech-mottakar eller -eining på %s, men oppdaga ein feil når han " +"vart tilkopla." -#: lib/logitech_receiver/status.py:179 -#, python-format -msgid "Lighting: %(level)s lux" -msgstr "Belysning: %(level)s lux" +#: lib/solaar/ui/common.py:46 +msgid "" +"Try disconnecting the device and then reconnecting it or turning it off and " +"then on." +msgstr "" +"Prøv å kopla frå eininga og deretter kopla ho til igjen eller slå ho av og " +"på igjen." -#: lib/logitech_receiver/status.py:234 -#, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "Batteri: %(level)s (%(status)s)" +#: lib/solaar/ui/common.py:49 +msgid "Unpairing failed" +msgstr "Kunne ikkje bryta paringa" -#: lib/logitech_receiver/status.py:236 -#, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "Batteri: %(percent)d%% (%(status)s)" - -#: lib/solaar/ui/__init__.py:52 -msgid "Permissions error" -msgstr "Rettighetsfeil" - -#: lib/solaar/ui/__init__.py:54 -#, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open " - "it." -msgstr "Fant ein Logitech-mottakar (%s), men manglar rettar til å opna den." - -#: lib/solaar/ui/__init__.py:55 -msgid "If you've just installed Solaar, try removing the receiver and " - "plugging it back in." -msgstr "Viss du nettopp har installert Solaar, forsøk å fjerna mottakaren og " - "plugg den inn igjen." - -#: lib/solaar/ui/__init__.py:58 -msgid "Cannot connect to device error" -msgstr "" - -#: lib/solaar/ui/__init__.py:60 -#, python-format -msgid "Found a Logitech receiver or device at %s, but encountered an error " - "connecting to it." -msgstr "" - -#: lib/solaar/ui/__init__.py:61 -msgid "Try removing the device and plugging it back in or turning it off " - "and then on." -msgstr "" - -#: lib/solaar/ui/__init__.py:64 -msgid "Unpairing failed" -msgstr "Kunne ikkje bryta paringa" - -#: lib/solaar/ui/__init__.py:66 +#: lib/solaar/ui/common.py:51 #, python-brace-format -msgid "Failed to unpair %{device} from %{receiver}." -msgstr "Mislykkast med å bryta paret %{device} og %{receiver}." +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Mislykkast med å bryta paret %{device} og %{receiver}." -#: lib/solaar/ui/__init__.py:67 -msgid "The receiver returned an error, with no further details." -msgstr "Mottakaren rapporterte ein feil, utan fleire detaljar." +#: lib/solaar/ui/common.py:56 +msgid "The receiver returned an error, with no further details." +msgstr "Mottakaren rapporterte ein feil, utan fleire detaljar." -#: lib/solaar/ui/about.py:39 -msgid "Manages Logitech receivers,\n" - "keyboards, mice, and tablets." -msgstr "" +#: lib/solaar/ui/config_panel.py:228 +msgid "Complete - ENTER to change" +msgstr "Fullfør - ENTER for å endra" -#: lib/solaar/ui/about.py:47 -msgid "Additional Programming" -msgstr "" +#: lib/solaar/ui/config_panel.py:228 +msgid "Incomplete" +msgstr "Ufullstendig" -#: lib/solaar/ui/about.py:48 -msgid "GUI design" -msgstr "GUI utsjånad" - -#: lib/solaar/ui/about.py:50 -msgid "Testing" -msgstr "Testing" - -#: lib/solaar/ui/about.py:57 -msgid "Logitech documentation" -msgstr "Logitech dokumentasjon" - -#: lib/solaar/ui/action.py:70 lib/solaar/ui/window.py:328 -msgid "About %s" -msgstr "Om %s" - -#: lib/solaar/ui/action.py:96 lib/solaar/ui/action.py:100 -#: lib/solaar/ui/window.py:205 -msgid "Unpair" -msgstr "Fjern paring" - -#: lib/solaar/ui/action.py:99 lib/solaar/ui/diversion_rules.py:144 -msgid "Cancel" -msgstr "" - -#: lib/solaar/ui/config_panel.py:358 -msgid "Changes allowed" -msgstr "" - -#: lib/solaar/ui/config_panel.py:359 -msgid "No changes allowed" -msgstr "" - -#: lib/solaar/ui/config_panel.py:360 -msgid "Ignore this setting" -msgstr "" - -#: lib/solaar/ui/config_panel.py:401 -msgid "Working" -msgstr "Suksess" - -#: lib/solaar/ui/config_panel.py:404 -msgid "Read/write operation failed." -msgstr "Lese-/skriveoperasjonen mislykkast." - -#: lib/solaar/ui/config_panel.py:522 +#: lib/solaar/ui/config_panel.py:475 lib/solaar/ui/config_panel.py:527 #, python-format -msgid "%d value" -msgid_plural "%d values" -msgstr[0] "" -msgstr[1] "" +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d verdi" +msgstr[1] "%d verdiar" -#: lib/solaar/ui/diversion_rules.py:57 -msgid "Built-in rules" -msgstr "" +#: lib/solaar/ui/config_panel.py:609 +msgid "Changes allowed" +msgstr "Endringar tillatne" -#: lib/solaar/ui/diversion_rules.py:57 -msgid "User-defined rules" -msgstr "" +#: lib/solaar/ui/config_panel.py:610 +msgid "No changes allowed" +msgstr "Ingen endringar tillatne" -#: lib/solaar/ui/diversion_rules.py:59 lib/solaar/ui/diversion_rules.py:792 -msgid "Rule" -msgstr "" +#: lib/solaar/ui/config_panel.py:611 +msgid "Ignore this setting" +msgstr "Ignorer denne innstillinga" -#: lib/solaar/ui/diversion_rules.py:60 lib/solaar/ui/diversion_rules.py:503 -#: lib/solaar/ui/diversion_rules.py:622 -msgid "Sub-rule" -msgstr "" +#: lib/solaar/ui/config_panel.py:655 +msgid "Working" +msgstr "Suksess" -#: lib/solaar/ui/diversion_rules.py:62 -msgid "[empty]" -msgstr "" +#: lib/solaar/ui/config_panel.py:658 +msgid "Read/write operation failed." +msgstr "Lese-/skriveoperasjonen mislykkast." -#: lib/solaar/ui/diversion_rules.py:85 -msgid "Solaar Rule Editor" -msgstr "" +#: lib/solaar/ui/desktop_notifications.py:114 +msgid "unspecified reason" +msgstr "uspesifisert årsak" -#: lib/solaar/ui/diversion_rules.py:135 -msgid "Make changes permanent?" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:69 +msgid "Built-in rules" +msgstr "Faste reglar" -#: lib/solaar/ui/diversion_rules.py:140 -msgid "Yes" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:69 +msgid "User-defined rules" +msgstr "Brukardefinerte reglar" -#: lib/solaar/ui/diversion_rules.py:142 -msgid "No" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:71 lib/solaar/ui/diversion_rules.py:1089 +msgid "Rule" +msgstr "Regel" -#: lib/solaar/ui/diversion_rules.py:147 -msgid "If you choose No, changes will be lost when Solaar is closed." -msgstr "" +#: lib/solaar/ui/diversion_rules.py:72 lib/solaar/ui/diversion_rules.py:348 +#: lib/solaar/ui/diversion_rules.py:475 +msgid "Sub-rule" +msgstr "Underregel" -#: lib/solaar/ui/diversion_rules.py:195 -msgid "Save changes" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:74 +msgid "[empty]" +msgstr "[tom]" -#: lib/solaar/ui/diversion_rules.py:200 -msgid "Discard changes" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:98 +msgid "Make changes permanent?" +msgstr "Gjera endringar permanente?" -#: lib/solaar/ui/diversion_rules.py:366 -msgid "Insert here" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:103 +msgid "Yes" +msgstr "Ja" -#: lib/solaar/ui/diversion_rules.py:368 -msgid "Insert above" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:105 +msgid "No" +msgstr "Nei" -#: lib/solaar/ui/diversion_rules.py:370 -msgid "Insert below" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:110 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "Viss du vel Nei, vil endringar gå tapt når Solaar blir lukka." + +#: lib/solaar/ui/diversion_rules.py:239 +msgid "Paste here" +msgstr "Lim inn her" + +#: lib/solaar/ui/diversion_rules.py:241 +msgid "Paste above" +msgstr "Lim inn ovanfor" + +#: lib/solaar/ui/diversion_rules.py:243 +msgid "Paste below" +msgstr "Lim inn nedanfor" + +#: lib/solaar/ui/diversion_rules.py:249 +msgid "Paste rule here" +msgstr "Lim inn regel her" + +#: lib/solaar/ui/diversion_rules.py:251 +msgid "Paste rule above" +msgstr "Lim inn regel ovanfor" + +#: lib/solaar/ui/diversion_rules.py:253 +msgid "Paste rule below" +msgstr "Lim inn regel nedanfor" + +#: lib/solaar/ui/diversion_rules.py:257 +msgid "Paste rule" +msgstr "Lim inn regel" + +#: lib/solaar/ui/diversion_rules.py:272 +msgid "Insert here" +msgstr "Set inn her" + +#: lib/solaar/ui/diversion_rules.py:274 +msgid "Insert above" +msgstr "Set inn ovanfor" + +#: lib/solaar/ui/diversion_rules.py:276 +msgid "Insert below" +msgstr "Set inn nedanfor" + +#: lib/solaar/ui/diversion_rules.py:282 +msgid "Insert new rule here" +msgstr "Set inn ny regel her" + +#: lib/solaar/ui/diversion_rules.py:284 +msgid "Insert new rule above" +msgstr "Set inn ny regel ovanfor" + +#: lib/solaar/ui/diversion_rules.py:286 +msgid "Insert new rule below" +msgstr "Set inn ny regel nedanfor" + +#: lib/solaar/ui/diversion_rules.py:313 +msgid "Flatten" +msgstr "Utfalte" + +#: lib/solaar/ui/diversion_rules.py:346 +msgid "Insert" +msgstr "Set inn" + +#: lib/solaar/ui/diversion_rules.py:349 lib/solaar/ui/diversion_rules.py:477 +#: lib/solaar/ui/diversion_rules.py:1121 +msgid "Or" +msgstr "Eller" + +#: lib/solaar/ui/diversion_rules.py:350 lib/solaar/ui/diversion_rules.py:476 +#: lib/solaar/ui/diversion_rules.py:1107 +msgid "And" +msgstr "Og" + +#: lib/solaar/ui/diversion_rules.py:352 +msgid "Condition" +msgstr "Vilkår" + +#: lib/solaar/ui/diversion_rules.py:354 lib/solaar/ui/rule_conditions.py:145 +msgid "Feature" +msgstr "Karakteristikk" + +#: lib/solaar/ui/diversion_rules.py:355 lib/solaar/ui/rule_conditions.py:179 +msgid "Report" +msgstr "Rapport" + +#: lib/solaar/ui/diversion_rules.py:356 lib/solaar/ui/rule_conditions.py:60 +msgid "Process" +msgstr "Prosess" + +#: lib/solaar/ui/diversion_rules.py:357 +msgid "Mouse process" +msgstr "Musprosess" + +#: lib/solaar/ui/diversion_rules.py:358 lib/solaar/ui/rule_conditions.py:216 +msgid "Modifiers" +msgstr "Modifikatorer" + +#: lib/solaar/ui/diversion_rules.py:359 lib/solaar/ui/rule_conditions.py:268 +msgid "Key" +msgstr "Tast" + +#: lib/solaar/ui/diversion_rules.py:360 lib/solaar/ui/rule_conditions.py:309 +msgid "KeyIsDown" +msgstr "TastErNede" + +#: lib/solaar/ui/diversion_rules.py:361 lib/solaar/ui/diversion_rules.py:1414 +msgid "Active" +msgstr "Aktiv" + +#: lib/solaar/ui/diversion_rules.py:362 lib/solaar/ui/diversion_rules.py:1372 +#: lib/solaar/ui/diversion_rules.py:1423 lib/solaar/ui/diversion_rules.py:1470 +msgid "Device" +msgstr "Eining" + +#: lib/solaar/ui/diversion_rules.py:363 lib/solaar/ui/diversion_rules.py:1449 +msgid "Host" +msgstr "Vert" + +#: lib/solaar/ui/diversion_rules.py:364 lib/solaar/ui/diversion_rules.py:1489 +msgid "Setting" +msgstr "Innstilling" + +#: lib/solaar/ui/diversion_rules.py:365 lib/solaar/ui/rule_conditions.py:324 +#: lib/solaar/ui/rule_conditions.py:373 +msgid "Test" +msgstr "Test" + +#: lib/solaar/ui/diversion_rules.py:366 lib/solaar/ui/rule_conditions.py:498 +msgid "Test bytes" +msgstr "Test bytes" + +#: lib/solaar/ui/diversion_rules.py:367 lib/solaar/ui/rule_conditions.py:599 +msgid "Mouse Gesture" +msgstr "Muserørsle" + +#: lib/solaar/ui/diversion_rules.py:371 +msgid "Action" +msgstr "Handling" + +#: lib/solaar/ui/diversion_rules.py:373 lib/solaar/ui/rule_actions.py:131 +msgid "Key press" +msgstr "Tastetrykk" + +#: lib/solaar/ui/diversion_rules.py:374 lib/solaar/ui/rule_actions.py:182 +msgid "Mouse scroll" +msgstr "Muserulling" + +#: lib/solaar/ui/diversion_rules.py:375 lib/solaar/ui/rule_actions.py:243 +msgid "Mouse click" +msgstr "Museklikk" #: lib/solaar/ui/diversion_rules.py:376 -msgid "Insert new rule here" -msgstr "" +msgid "Set" +msgstr "Still inn" -#: lib/solaar/ui/diversion_rules.py:378 -msgid "Insert new rule above" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:377 lib/solaar/ui/rule_actions.py:313 +msgid "Execute" +msgstr "Utfør" -#: lib/solaar/ui/diversion_rules.py:380 -msgid "Insert new rule below" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:378 lib/solaar/ui/diversion_rules.py:1152 +msgid "Later" +msgstr "Seinare" -#: lib/solaar/ui/diversion_rules.py:421 -msgid "Paste here" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:407 +msgid "Insert new rule" +msgstr "Set inn ny regel" -#: lib/solaar/ui/diversion_rules.py:423 -msgid "Paste above" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:427 lib/solaar/ui/rule_actions.py:74 +#: lib/solaar/ui/rule_actions.py:272 lib/solaar/ui/rule_conditions.py:546 +msgid "Delete" +msgstr "Slett" -#: lib/solaar/ui/diversion_rules.py:425 -msgid "Paste below" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:449 +msgid "Negate" +msgstr "Omvendt" -#: lib/solaar/ui/diversion_rules.py:431 -msgid "Paste rule here" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:473 +msgid "Wrap with" +msgstr "Pakk inn med" -#: lib/solaar/ui/diversion_rules.py:433 -msgid "Paste rule above" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:503 +msgid "Cut" +msgstr "KLipp ut" -#: lib/solaar/ui/diversion_rules.py:435 -msgid "Paste rule below" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:519 +msgid "Paste" +msgstr "Lim inn" -#: lib/solaar/ui/diversion_rules.py:439 -msgid "Paste rule" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:525 +msgid "Copy" +msgstr "Kopier" -#: lib/solaar/ui/diversion_rules.py:468 -msgid "Flatten" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:534 +msgid "Solaar Rule Editor" +msgstr "Solaar regel editor" -#: lib/solaar/ui/diversion_rules.py:501 -msgid "Insert" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:634 +msgid "Save changes" +msgstr "Lagre endringar" -#: lib/solaar/ui/diversion_rules.py:504 lib/solaar/ui/diversion_rules.py:624 -#: lib/solaar/ui/diversion_rules.py:835 -msgid "Or" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:639 +msgid "Discard changes" +msgstr "Forkast endringar" -#: lib/solaar/ui/diversion_rules.py:505 lib/solaar/ui/diversion_rules.py:623 -#: lib/solaar/ui/diversion_rules.py:820 -msgid "And" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1070 +msgid "This editor does not support the selected rule component yet." +msgstr "Denne editoren støttar førebels ikkje den valde regelkomponenten." -#: lib/solaar/ui/diversion_rules.py:507 -msgid "Condition" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1132 +msgid "" +"Number of seconds to delay. Delay between 0 and 1 is done with higher " +"precision." +msgstr "" +"Talet på sekund med forseinking. Forseinking mellom 0 og 1 blir gjort med " +"høgare presisjon." -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:951 -msgid "Feature" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1170 +msgid "Not" +msgstr "Ikkje" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:874 -msgid "Process" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1201 +msgid "Toggle" +msgstr "Veksle" -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:902 -msgid "MouseProcess" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1202 +msgid "True" +msgstr "Sann" -#: lib/solaar/ui/diversion_rules.py:512 lib/solaar/ui/diversion_rules.py:984 -msgid "Report" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1203 +msgid "False" +msgstr "Falsk" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1019 -msgid "Modifiers" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1216 +msgid "Unsupported setting" +msgstr "Innstillinga blir ikkje støtta" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1066 -msgid "Key" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1378 lib/solaar/ui/diversion_rules.py:1397 +#: lib/solaar/ui/diversion_rules.py:1476 lib/solaar/ui/diversion_rules.py:1731 +#: lib/solaar/ui/diversion_rules.py:1749 +msgid "Originating device" +msgstr "Kildeeining" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1111 -msgid "Test" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1410 +msgid "Device is active and its settings can be changed." +msgstr "Eininga er aktiv og innstillingane kan endrast." -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1196 -msgid "Mouse Gesture" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1419 +msgid "Device that originated the current notification." +msgstr "Eininga som gav det gjeldande varselet." -#: lib/solaar/ui/diversion_rules.py:520 -msgid "Action" -msgstr "Handling" +#: lib/solaar/ui/diversion_rules.py:1432 +msgid "Name of host computer." +msgstr "Namn på vertsmaskin." -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1286 -msgid "Key press" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1503 +msgid "Value" +msgstr "Verdi" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1335 -msgid "Mouse scroll" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1512 +msgid "Item" +msgstr "Punkt" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:1383 -msgid "Mouse click" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1791 +msgid "Change setting on device" +msgstr "Endre innstilling på eining" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1452 -msgid "Execute" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1807 +msgid "Setting on device" +msgstr "Innstilling på eining" -#: lib/solaar/ui/diversion_rules.py:554 -msgid "Insert new rule" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/diversion_rules.py:1146 -#: lib/solaar/ui/diversion_rules.py:1236 lib/solaar/ui/diversion_rules.py:1411 -msgid "Delete" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:596 -msgid "Negate" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:620 -msgid "Wrap with" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:642 -msgid "Cut" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:657 -msgid "Paste" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:669 -msgid "Copy" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:772 -msgid "This editor does not support the selected rule component yet." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:850 -msgid "Not" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1039 -msgid "Key down" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1042 -msgid "Key up" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1131 -msgid "Add action" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1224 -msgid "Add key" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1354 -msgid "Button" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1355 -msgid "Count" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1397 -msgid "Add argument" -msgstr "" - -#: lib/solaar/ui/notify.py:125 lib/solaar/ui/tray.py:319 -#: lib/solaar/ui/tray.py:324 lib/solaar/ui/window.py:749 -msgid "offline" -msgstr "avslått" - -#: lib/solaar/ui/pair_window.py:134 -msgid "Pairing failed" -msgstr "Paringa feila" - -#: lib/solaar/ui/pair_window.py:136 -msgid "Make sure your device is within range, and has a decent battery " - "charge." -msgstr "Forsikre deg om at eininga er innan rekkevidde, og at den er " - "tilstrekkeleg opplada." - -#: lib/solaar/ui/pair_window.py:138 -msgid "A new device was detected, but it is not compatible with this " - "receiver." -msgstr "Ei ny eining vart påvist, men ho er ikkje kompatibel med denne " - "mottakaren." - -#: lib/solaar/ui/pair_window.py:140 -msgid "More paired devices than receiver can support." -msgstr "Fleir para einingar enn mottakaren kan støtte." - -#: lib/solaar/ui/pair_window.py:142 -msgid "No further details are available about the error." -msgstr "Ingen ytterligare detaljar er tilgjengeleg om feilen." - -#: lib/solaar/ui/pair_window.py:156 -msgid "Found a new device:" -msgstr "Fant ny eining:" - -#: lib/solaar/ui/pair_window.py:181 -msgid "The wireless link is not encrypted" -msgstr "Den trådlause koplinga er ukryptert" - -#: lib/solaar/ui/pair_window.py:199 +#: lib/solaar/ui/pair_window.py:37 lib/solaar/ui/pair_window.py:165 #, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s: par ny eining" +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: par ny eining" -#: lib/solaar/ui/pair_window.py:206 -msgid "If the device is already turned on, turn it off and on again." -msgstr "" +#: lib/solaar/ui/pair_window.py:39 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "Bolt-mottakarar er berre kompatible med Bolt-einingar." -#: lib/solaar/ui/pair_window.py:209 +#: lib/solaar/ui/pair_window.py:41 +msgid "Press a pairing button or key until the pairing light flashes quickly." +msgstr "" +"Trykk på samankoplingsknappen eller -tasten til samankoplingslampa blinkar " +"raskt." + +#: lib/solaar/ui/pair_window.py:44 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "Unifying-mottakarar er berre kompatible med Unifying-einingar." + +#: lib/solaar/ui/pair_window.py:46 +msgid "Other receivers are only compatible with a few devices." +msgstr "Andre mottakarar er berre kompatible med nokre få einingar." + +#: lib/solaar/ui/pair_window.py:48 +msgid "For most devices, turn on the device you want to pair." +msgstr "For dei fleste einingar slår du på eininga du vil para." + +#: lib/solaar/ui/pair_window.py:49 +msgid "If the device is already turned on, turn it off and on again." +msgstr "Viss eininga allereie er pålått, slå ho av og på igjen." + +#: lib/solaar/ui/pair_window.py:51 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "Eininga må ikkje parast med ein pålått mottakar i nærleiken." + +#: lib/solaar/ui/pair_window.py:54 +msgid "" +"For devices with multiple channels, press, hold, and release the button for " +"the channel you wish to pair\n" +"or use the channel switch button to select a channel and then press, hold, " +"and release the channel switch button." +msgstr "" +"For einingar med fleire kanalar, trykk, hald og slipp knappen for kanalen du " +"ønskjer å para\n" +"eller bruk kanalbrytarknappen for å velja ein kanal og trykk, hald og slepp " +"kanalbrytarknappen." + +#: lib/solaar/ui/pair_window.py:61 +msgid "The channel indicator light should be blinking rapidly." +msgstr "Kanalindikatorlampa skal blinka raskt." + +#: lib/solaar/ui/pair_window.py:65 #, python-format -msgid "\n" - "\n" - "This receiver has %d pairing remaining." -msgid_plural "\n" - "\n" - "This receiver has %d pairings remaining." -msgstr[0] "\n" - "\n" - "Denne mottakaren har %d gjenståande paring." -msgstr[1] "\n" - "\n" - "Denne mottakaren har %d gjenståande paringer." +msgid "" +"\n" +"\n" +"This receiver has %d pairing remaining." +msgid_plural "" +"\n" +"\n" +"This receiver has %d pairings remaining." +msgstr[0] "" +"\n" +"\n" +"Denne mottakaren har %d gjenståande paring." +msgstr[1] "" +"\n" +"\n" +"Denne mottakaren har %d gjenståande paringar." -#: lib/solaar/ui/pair_window.py:212 -msgid "\n" - "Cancelling at this point will not use up a pairing." -msgstr "\n" - "Kansellering no vil ikkje bruka opp ein paring." +#: lib/solaar/ui/pair_window.py:71 +msgid "" +"\n" +"Cancelling at this point will not use up a pairing." +msgstr "" +"\n" +"Kansellering no vil ikkje bruka opp ein paring." -#: lib/solaar/ui/pair_window.py:215 -msgid "Turn on the device you want to pair." -msgstr "Slå på eininga du ynsker å pare." +#: lib/solaar/ui/pair_window.py:166 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "Skriv inn passordet på %(name)s." -#: lib/solaar/ui/tray.py:61 -msgid "No Logitech receiver found" -msgstr "Ingen Logitech-mottakar funnet" +#: lib/solaar/ui/pair_window.py:169 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "Skriv inn %(passcode)s og trykk deretter på enter-tasten." -#: lib/solaar/ui/tray.py:68 lib/solaar/ui/window.py:325 -msgid "Quit %s" -msgstr "Avslutt %s" +#: lib/solaar/ui/pair_window.py:174 +msgid "left" +msgstr "venstre" -#: lib/solaar/ui/tray.py:298 lib/solaar/ui/tray.py:306 -msgid "no receiver" -msgstr "ingen mottakar" +#: lib/solaar/ui/pair_window.py:174 +msgid "right" +msgstr "høgre" -#: lib/solaar/ui/tray.py:322 -msgid "no status" -msgstr "ingen status" +#: lib/solaar/ui/pair_window.py:176 +#, python-format +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"Trykk %(code)s\n" +"og trykk deretter på venstre og høgre knapp samtidig." -#: lib/solaar/ui/window.py:104 -msgid "Scanning" -msgstr "Søker" +#: lib/solaar/ui/pair_window.py:219 +msgid "The wireless link is not encrypted" +msgstr "Den trådlause koplinga er ukryptert" -#: lib/solaar/ui/window.py:137 lib/solaar/ui/window.py:692 -msgid "Battery" -msgstr "Batteri" +#: lib/solaar/ui/pair_window.py:224 +msgid "Found a new device:" +msgstr "Fant ny eining:" -#: lib/solaar/ui/window.py:140 -msgid "Wireless Link" -msgstr "Trådlaus kopling" +#: lib/solaar/ui/pair_window.py:245 +msgid "Pairing failed" +msgstr "Paringa feila" -#: lib/solaar/ui/window.py:144 -msgid "Lighting" -msgstr "Belysning" +#: lib/solaar/ui/pair_window.py:247 +msgid "Make sure your device is within range, and has a decent battery charge." +msgstr "" +"Forsikre deg om at eininga er innan rekkevidde, og at den er tilstrekkeleg " +"opplada." + +#: lib/solaar/ui/pair_window.py:249 +msgid "A new device was detected, but it is not compatible with this receiver." +msgstr "" +"Ei ny eining vart påvist, men ho er ikkje kompatibel med denne mottakaren." + +#: lib/solaar/ui/pair_window.py:251 +msgid "More paired devices than receiver can support." +msgstr "Fleir para einingar enn mottakaren kan støtte." + +#: lib/solaar/ui/pair_window.py:253 +msgid "No further details are available about the error." +msgstr "Ingen ytterligare detaljar er tilgjengeleg om feilen." + +#: lib/solaar/ui/rule_actions.py:48 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simuler ein chorded (?) tast, klikk eller trykk ned eller slipp.\n" +"På Wayland krev dette skrivetilgang til /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:53 +msgid "Add key" +msgstr "Legg til tast" + +#: lib/solaar/ui/rule_actions.py:56 +msgid "Click" +msgstr "Klikk" + +#: lib/solaar/ui/rule_actions.py:59 +msgid "Depress" +msgstr "Nedtrykk" + +#: lib/solaar/ui/rule_actions.py:62 +msgid "Release" +msgstr "Utløs" + +#: lib/solaar/ui/rule_actions.py:146 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simuler ei muserulling.\n" +"På Wayland krev dette skrivetilgang til /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:202 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simuler eit museklikk.\n" +"På Wayland krev dette skrivetilgang til /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:205 +msgid "Button" +msgstr "Knapp" + +#: lib/solaar/ui/rule_actions.py:206 +msgid "Count and Action" +msgstr "Antall og handling" + +#: lib/solaar/ui/rule_actions.py:255 +msgid "Execute a command with arguments." +msgstr "Utfør ein kommando med argument." + +#: lib/solaar/ui/rule_actions.py:258 +msgid "Add argument" +msgstr "Legg til argument" + +#: lib/solaar/ui/rule_conditions.py:43 +msgid "X11 active process. For use in X11 only." +msgstr "X11 aktiv prosess. Berre til bruk i X11." + +#: lib/solaar/ui/rule_conditions.py:73 +msgid "X11 mouse process. For use in X11 only." +msgstr "X11-museprosess. Berre til bruk i X11." + +#: lib/solaar/ui/rule_conditions.py:90 +msgid "MouseProcess" +msgstr "MusProsess" + +#: lib/solaar/ui/rule_conditions.py:114 +msgid "Feature name of notification triggering rule processing." +msgstr "Funksjonsnamn på varsel som utløyste regelbehandling." + +#: lib/solaar/ui/rule_conditions.py:160 +msgid "Report number of notification triggering rule processing." +msgstr "Rapportnummer for varsling som utløyser regelbehandling." + +#: lib/solaar/ui/rule_conditions.py:192 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "Aktive tastaturmodifikatorer. Ikkje alltid tilgjengeleg i Wayland." + +#: lib/solaar/ui/rule_conditions.py:232 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"Omdirigert tast eller knapp som er trykt eller sleppt.\n" +"Bruk innstillingane for omdirigering av tast/knapp og omdiriger G-tastar for " +"å vidaresenda tastar og knappar." + +#: lib/solaar/ui/rule_conditions.py:241 +msgid "Key down" +msgstr "Tast ned" + +#: lib/solaar/ui/rule_conditions.py:244 +msgid "Key up" +msgstr "Tast opp" + +#: lib/solaar/ui/rule_conditions.py:284 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"Omdirigert tast eller knapp er nede.\n" +"Bruk innstillingane for omdirigering av tast/knapp og omdirigerte G-tastar " +"for å vidaresenda tastar og knappar." + +#: lib/solaar/ui/rule_conditions.py:322 +msgid "Test condition on notification triggering rule processing." +msgstr "Testvilkår på varsel som utløyser regelbehandling." + +#: lib/solaar/ui/rule_conditions.py:326 +msgid "Parameter" +msgstr "Parameter" + +#: lib/solaar/ui/rule_conditions.py:399 +msgid "begin (inclusive)" +msgstr "byrj (inklusiv)" + +#: lib/solaar/ui/rule_conditions.py:400 +msgid "end (exclusive)" +msgstr "slutt (eksklusiv)" + +#: lib/solaar/ui/rule_conditions.py:408 +msgid "range" +msgstr "område" + +#: lib/solaar/ui/rule_conditions.py:411 +msgid "minimum" +msgstr "minimum" + +#: lib/solaar/ui/rule_conditions.py:412 +msgid "maximum" +msgstr "maksimum" + +#: lib/solaar/ui/rule_conditions.py:414 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "byte %(0)d til %(1)d, fra %(2)d til %(3)d" + +#: lib/solaar/ui/rule_conditions.py:417 lib/solaar/ui/rule_conditions.py:418 +msgid "mask" +msgstr "maske" + +#: lib/solaar/ui/rule_conditions.py:419 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "byte %(0)d til %(1)d, maske %(2)d" + +#: lib/solaar/ui/rule_conditions.py:428 +msgid "" +"Bit or range test on bytes in notification message triggering rule " +"processing." +msgstr "" +"Bit- eller områdetest på bytes i varselmelding som utløyste regelbehandling." + +#: lib/solaar/ui/rule_conditions.py:438 +msgid "type" +msgstr "type" + +#: lib/solaar/ui/rule_conditions.py:526 +msgid "" +"Mouse gesture with optional initiating button followed by zero or more mouse " +"movements." +msgstr "" +"Muserørsle med valfri startknapp etterfølgd av null eller fleire muserørsler." + +#: lib/solaar/ui/rule_conditions.py:531 +msgid "Add movement" +msgstr "Legg til rørsle" + +#: lib/solaar/ui/tray.py:49 +msgid "No supported device found" +msgstr "Ingen støtta einingar er funne" + +#: lib/solaar/ui/tray.py:54 lib/solaar/ui/window.py:308 +#, python-format +msgid "About %s" +msgstr "Om %s" + +#: lib/solaar/ui/tray.py:55 lib/solaar/ui/window.py:306 +#, python-format +msgid "Quit %s" +msgstr "Avslutt %s" + +#: lib/solaar/ui/tray.py:270 lib/solaar/ui/tray.py:278 +msgid "no receiver" +msgstr "ingen mottakar" + +#: lib/solaar/ui/tray.py:291 lib/solaar/ui/tray.py:296 +msgid "offline" +msgstr "avslått" + +#: lib/solaar/ui/tray.py:294 +msgid "no status" +msgstr "ingen status" + +#: lib/solaar/ui/window.py:90 +msgid "Scanning" +msgstr "Søker" + +#: lib/solaar/ui/window.py:121 +msgid "Battery" +msgstr "Batteri" + +#: lib/solaar/ui/window.py:124 +msgid "Wireless Link" +msgstr "Trådlaus kopling" + +#: lib/solaar/ui/window.py:128 +msgid "Lighting" +msgstr "Belysning" + +#: lib/solaar/ui/window.py:162 +msgid "Show Technical Details" +msgstr "Vis tekniske detaljar" #: lib/solaar/ui/window.py:178 -msgid "Show Technical Details" -msgstr "Vis tekniske detaljar" +msgid "Pair new device" +msgstr "Par ny eining" -#: lib/solaar/ui/window.py:194 -msgid "Pair new device" -msgstr "Par ny eining" +#: lib/solaar/ui/window.py:196 +msgid "Select a device" +msgstr "Vel ei eining" -#: lib/solaar/ui/window.py:213 -msgid "Select a device" -msgstr "Vel ei eining" +#: lib/solaar/ui/window.py:311 +msgid "Rule Editor" +msgstr "Regel editor" -#: lib/solaar/ui/window.py:331 -msgid "Rule Editor" -msgstr "" +#: lib/solaar/ui/window.py:504 +msgid "Path" +msgstr "Sti" -#: lib/solaar/ui/window.py:541 -msgid "Path" -msgstr "Sti" +#: lib/solaar/ui/window.py:506 +msgid "USB ID" +msgstr "USB-ID" + +#: lib/solaar/ui/window.py:509 lib/solaar/ui/window.py:511 +#: lib/solaar/ui/window.py:526 lib/solaar/ui/window.py:528 +msgid "Serial" +msgstr "Seriell" + +#: lib/solaar/ui/window.py:515 +msgid "Index" +msgstr "Indeks" + +#: lib/solaar/ui/window.py:517 +msgid "Wireless PID" +msgstr "Trådlaus-PID" + +#: lib/solaar/ui/window.py:519 +msgid "Product ID" +msgstr "Produkt-ID" + +#: lib/solaar/ui/window.py:521 +msgid "Protocol" +msgstr "Protokoll" + +#: lib/solaar/ui/window.py:521 +msgid "Unknown" +msgstr "Ukjend" + +#: lib/solaar/ui/window.py:523 +msgid "Polling rate" +msgstr "Spørjarate" + +#: lib/solaar/ui/window.py:530 +msgid "Unit ID" +msgstr "Einings-ID" #: lib/solaar/ui/window.py:544 -msgid "USB ID" -msgstr "" +msgid "Notifications" +msgstr "Varslar" -#: lib/solaar/ui/window.py:547 lib/solaar/ui/window.py:549 -#: lib/solaar/ui/window.py:569 lib/solaar/ui/window.py:571 -msgid "Serial" -msgstr "Seriell" +#: lib/solaar/ui/window.py:588 +msgid "No device paired." +msgstr "Inga eining para." -#: lib/solaar/ui/window.py:553 -msgid "Index" -msgstr "Indeks" - -#: lib/solaar/ui/window.py:555 -msgid "Wireless PID" -msgstr "Trådlaus-PID" - -#: lib/solaar/ui/window.py:557 -msgid "Product ID" -msgstr "" - -#: lib/solaar/ui/window.py:559 -msgid "Protocol" -msgstr "Protokoll" - -#: lib/solaar/ui/window.py:559 -msgid "Unknown" -msgstr "Ukjend" - -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:597 #, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d ms (%(rate_hz)dHz)" +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "Inntil %(max_count)s eining kan bli para med denne mottakaren." +msgstr[1] "Inntil %(max_count)s einingar kan bli para med denne mottakaren." -#: lib/solaar/ui/window.py:562 -msgid "Polling rate" -msgstr "Spørjarate" - -#: lib/solaar/ui/window.py:573 -msgid "Unit ID" -msgstr "" - -#: lib/solaar/ui/window.py:584 -msgid "none" -msgstr "ingen" - -#: lib/solaar/ui/window.py:585 -msgid "Notifications" -msgstr "Varslar" - -#: lib/solaar/ui/window.py:622 -msgid "No device paired." -msgstr "Inga eining para." - -#: lib/solaar/ui/window.py:629 +#: lib/solaar/ui/window.py:608 #, python-format -msgid "Up to %(max_count)s device can be paired to this receiver." -msgid_plural "Up to %(max_count)s devices can be paired to this receiver." -msgstr[0] "Inntil %(max_count)s eining kan bli para med denne " - "mottakaren." -msgstr[1] "Inntil %(max_count)s einingar kan bli para med denne " - "mottakaren." +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "Denne mottakaren har %d gjenståande paring." +msgstr[1] "Denne mottakaren har %d gjenståande paringer." -#: lib/solaar/ui/window.py:635 -msgid "Only one device can be paired to this receiver." -msgstr "Berre éi eining kan parast med denne mottakaren." +#: lib/solaar/ui/window.py:665 +msgid "Battery Voltage" +msgstr "Batterispenning" -#: lib/solaar/ui/window.py:639 +#: lib/solaar/ui/window.py:667 +msgid "Voltage reported by battery" +msgstr "Spenning rapportert av batteri" + +#: lib/solaar/ui/window.py:669 +msgid "Battery Level" +msgstr "Batterinivå" + +#: lib/solaar/ui/window.py:671 +msgid "Approximate level reported by battery" +msgstr "Omtrentleg nivå rapportert frå batteri" + +#: lib/solaar/ui/window.py:678 lib/solaar/ui/window.py:680 +msgid "next reported " +msgstr "neste rapportert " + +#: lib/solaar/ui/window.py:681 +msgid " and next level to be reported." +msgstr " og neste nivå som blir rapportert." + +#: lib/solaar/ui/window.py:697 +msgid "encrypted" +msgstr "kryptert" + +#: lib/solaar/ui/window.py:699 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "Den trådlause tilkoplinga mellom eininga og mottakaren er kryptert." + +#: lib/solaar/ui/window.py:701 +msgid "not encrypted" +msgstr "ikkje kryptert" + +#: lib/solaar/ui/window.py:705 +msgid "" +"The wireless link between this device and its receiver is not encrypted.\n" +"This is a security issue for pointing devices, and a major security issue " +"for text-input devices." +msgstr "" +"Den trådlause koplinga mellom denne eininga og mottakaren er ikkje " +"kryptert.\n" +"Dette er eit tryggingsproblem for peikeeiningar, og eit stort " +"tryggingsproblem for einingar for tekstinndata." + +#: lib/solaar/ui/window.py:721 #, python-format -msgid "This receiver has %d pairing remaining." -msgid_plural "This receiver has %d pairings remaining." -msgstr[0] "Denne mottakaren har %d gjenståande paring." -msgstr[1] "Denne mottakaren har %d gjenståande paringer." +msgid "%(light_level)d lux" +msgstr "%(light_level)d lux" -#: lib/solaar/ui/window.py:694 -msgid "Battery information unknown." -msgstr "" +#~ msgid "" +#~ "\n" +#~ "\n" +#~ "This receiver has %d pairing(s) remaining." +#~ msgstr "" +#~ "\n" +#~ "\n" +#~ "Denne mottakaren har %d gjenståande paringar." -#: lib/solaar/ui/window.py:702 -msgid "Battery Voltage" -msgstr "" - -#: lib/solaar/ui/window.py:704 -msgid "Voltage reported by battery" -msgstr "" - -#: lib/solaar/ui/window.py:706 lib/solaar/ui/window.py:710 -msgid "Battery Level" -msgstr "" - -#: lib/solaar/ui/window.py:708 lib/solaar/ui/window.py:712 -msgid "Approximate level reported by battery" -msgstr "" - -#: lib/solaar/ui/window.py:715 lib/solaar/ui/window.py:717 -msgid "next reported " -msgstr "" - -#: lib/solaar/ui/window.py:718 -msgid " and next level to be reported." -msgstr "" - -#: lib/solaar/ui/window.py:723 -msgid "last known" -msgstr "sist kjend" - -#: lib/solaar/ui/window.py:731 -msgid "not encrypted" -msgstr "ikkje kryptert" - -#: lib/solaar/ui/window.py:735 -msgid "The wireless link between this device and its receiver is not " - "encrypted.\n" - "\n" - "For pointing devices (mice, trackballs, trackpads), this is a minor " - "security issue.\n" - "\n" - "It is, however, a major security issue for text-input devices " - "(keyboards, numpads),\n" - "because typed text can be sniffed inconspicuously by 3rd parties " - "within range." -msgstr "Den trådlause koplinga mellom denne eininga og mottakaren er " - "ukryptert.\n" - "\n" - "For peikeeeiningar (mus, styrekuler, styreflater) er dette eit " - "mindre tryggingsproblem.\n" - "\n" - "Det er likevel eit viktig tryggingsproblem for tekstinndataeiningar " - "(tastatur, numpads),\n" - "fordi maskinskriven tekst kan bli sniffa umerka av tredjepartar " - "innan rekkevidde." - -#: lib/solaar/ui/window.py:744 -msgid "encrypted" -msgstr "kryptert" - -#: lib/solaar/ui/window.py:746 -msgid "The wireless link between this device and its receiver is encrypted." -msgstr "Den trådlause tilkoplinga mellom eininga og mottakaren er kryptert." - -#: lib/solaar/ui/window.py:759 #, python-format -msgid "%(light_level)d lux" -msgstr "%(light_level)d lux" +#~ msgid "%(rate)d ms (%(rate_hz)dHz)" +#~ msgstr "%(rate)d ms (%(rate_hz)dHz)" -#~ msgid "\n" -#~ "\n" -#~ "This receiver has %d pairing(s) remaining." -#~ msgstr "\n" -#~ "\n" -#~ "Denne mottakaren har %d gjenståande paringar." +#~ msgid "Actions" +#~ msgstr "Handlingar" -#~ msgid "Actions" -#~ msgstr "Handlingar" +#~ msgid "" +#~ "Automatically switch the mouse wheel between ratchet and freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always locked at 50" +#~ msgstr "" +#~ "Byt mushjulet automatisk mellom sperre- og frispinnmodus.\n" +#~ "Mushjulet er alltid fritt på 0, og blir alltid låste på 50" -#~ msgid "Automatically switch the mouse wheel between ratchet and " -#~ "freespin mode.\n" -#~ "The mouse wheel is always free at 0, and always locked at 50" -#~ msgstr "Byt mushjulet automatisk mellom sperre- og frispinnmodus.\n" -#~ "Mushjulet er alltid fritt på 0, og blir alltid låste på 50" +#, python-format +#~ msgid "Battery: %(level)s" +#~ msgstr "Batteri: %(level)s" -#~ msgid "For more information see the Solaar installation directions\n" -#~ "at https://pwr-solaar.github.io/Solaar/installation" -#~ msgstr "For meir informasjon, sjå Solaar-installasjonsanvisningene\n" -#~ "på https://pwr-solaar.github.io/Solaar/installation" +#, python-format +#~ msgid "Battery: %(percent)d%%" +#~ msgstr "Batteri: %(percent)d%%" -#~ msgid "HID++ Scrolling" -#~ msgstr "HID++ rulling" +#~ msgid "Divert G Keys" +#~ msgstr "Omdiriger G tastar" -#~ msgid "HID++ Thumb Scrolling" -#~ msgstr "HID++ tommelrulling" +#~ msgid "Effectively turns off thumb scrolling in Linux." +#~ msgstr "Slår i praksis av tommelrulling i Linux." -#~ msgid "High Resolution Scrolling" -#~ msgstr "Høgoppløyseleg rulling" +#~ msgid "Effectively turns off wheel scrolling in Linux." +#~ msgstr "Slår i praksis av hjulrulling i Linux." -#~ msgid "High Resolution Wheel Invert" -#~ msgstr "Inverter høg rulleoppløysning" +#~ msgid "" +#~ "Enable onboard profiles, which often control report rate and keyboard " +#~ "lighting" +#~ msgstr "" +#~ "Aktiver ein innebygd profil, som kontrollerer rapporthyppigheit, " +#~ "følsomheit, og knappehandlingar" -#~ msgid "High-sensitivity wheel invert direction for vertical scroll." -#~ msgstr "Inverter retning for høg vertikal rulleoppløysning." +#~ msgid "" +#~ "For more information see the Solaar installation directions\n" +#~ "at https://pwr-solaar.github.io/Solaar/installation" +#~ msgstr "" +#~ "For meir informasjon, sjå Solaar-installasjonsanvisningene\n" +#~ "på https://pwr-solaar.github.io/Solaar/installation" -#~ msgid "If the device is already turned on, turn if off and on again." -#~ msgstr "Viss eininga allereie er slått på, slå ho av og på igjen." +#, python-format +#~ msgid "" +#~ "Found a Logitech Receiver (%s), but did not have permission to open it." +#~ msgstr "Fant ein Logitech-mottakar (%s), men manglar rettar til å opna den." -#~ msgid "Invert thumb scroll direction." -#~ msgstr "Inverter retning på tommelrulling." +#~ msgid "Frequency of device polling, in milliseconds" +#~ msgstr "Frekvensen til rapportar om einingsrørsler" -#~ msgid "Shows status of devices connected\n" -#~ "through wireless Logitech receivers." -#~ msgstr "Viser status for einingar tilkopla\n" -#~ "via trådlause Logitech-mottakere." +#~ msgid "HID++ Scrolling" +#~ msgstr "HID++ rulling" -#~ msgid "Smooth Scrolling" -#~ msgstr "Mjuk rulling" +#~ msgid "HID++ Thumb Scrolling" +#~ msgstr "HID++ tommelrulling" -#~ msgid "Solaar depends on a udev file that is not present" -#~ msgstr "Solaar er avhengig av ein udev-fil som ikkje finnast" +#~ msgid "HID++ mode for horizontal scroll with the thumb wheel." +#~ msgstr "HID++ modus for horisontal rulling med tommelhjulet." -#~ msgid "The receiver only supports %d paired device(s)." -#~ msgstr "Mottakaren kan kun støtte %d para eining(er)." +#~ msgid "HID++ mode for vertical scroll with the wheel." +#~ msgstr "HID++ modus for vertikal rulling med hjulet." -#~ msgid "The receiver was unplugged." -#~ msgstr "Mottakaren vart fjerna." +#~ msgid "High Resolution Scrolling" +#~ msgstr "Høgoppløyseleg rulling" -#~ msgid "This receiver has %d pairing(s) remaining." -#~ msgstr "Denne mottakaren har %d gjenståande paring(er)." +#~ msgid "High Resolution Wheel Invert" +#~ msgstr "Inverter høg rulleoppløysning" -#~ msgid "Thumb Scroll Invert" -#~ msgstr "Inverter tommelrulling" +#~ msgid "High-sensitivity wheel invert direction for vertical scroll." +#~ msgstr "Inverter retning for høg vertikal rulleoppløysning." -#~ msgid "USB id" -#~ msgstr "USB-id" +#~ msgid "If the device is already turned on, turn if off and on again." +#~ msgstr "Viss eininga allereie er slått på, slå ho av og på igjen." -#~ msgid "Unexpected device number (%s) in notification %s." -#~ msgstr "Uventa einingsnummer (%s) i varsel %s." +#~ msgid "" +#~ "If you've just installed Solaar, try removing the receiver and plugging " +#~ "it back in." +#~ msgstr "" +#~ "Viss du nettopp har installert Solaar, forsøk å fjerna mottakaren og " +#~ "plugg den inn igjen." -#~ msgid "Wheel Resolution" -#~ msgstr "Hjuloppløysning" +#~ msgid "Invert thumb scroll direction." +#~ msgstr "Inverter retning på tommelrulling." -#~ msgid "next " -#~ msgstr "neste " +#, python-format +#~ msgid "Lighting: %(level)s lux" +#~ msgstr "Belysning: %(level)s lux" + +#~ msgid "" +#~ "Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " +#~ "are otherwise ignored)." +#~ msgstr "" +#~ "Få G-tastane til å senda HID++-varslar (som utløyser Solaar-reglar, men " +#~ "som elles blir ignorerte)." + +#~ msgid "No Logitech receiver found" +#~ msgstr "Ingen Logitech-mottakar funnet" + +#~ msgid "Number of seconds to delay." +#~ msgstr "Talet på sekund med forseinking." + +#~ msgid "Only one device can be paired to this receiver." +#~ msgstr "Berre éi eining kan parast med denne mottakaren." + +#~ msgid "Polling Rate (ms)" +#~ msgstr "Rapporthyppigheit" + +#~ msgid "" +#~ "Shows status of devices connected\n" +#~ "through wireless Logitech receivers." +#~ msgstr "" +#~ "Viser status for einingar tilkopla\n" +#~ "via trådlause Logitech-mottakere." + +#~ msgid "Smooth Scrolling" +#~ msgstr "Mjuk rulling" + +#~ msgid "Solaar depends on a udev file that is not present" +#~ msgstr "Solaar er avhengig av ein udev-fil som ikkje finnast" + +#~ msgid "The receiver only supports %d paired device(s)." +#~ msgstr "Mottakaren kan kun støtte %d para eining(er)." + +#~ msgid "The receiver was unplugged." +#~ msgstr "Mottakaren vart fjerna." + +#~ msgid "" +#~ "The wireless link between this device and its receiver is not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices (keyboards, " +#~ "numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties within " +#~ "range." +#~ msgstr "" +#~ "Den trådlause koplinga mellom denne eininga og mottakaren er ukryptert.\n" +#~ "\n" +#~ "For peikeeeiningar (mus, styrekuler, styreflater) er dette eit mindre " +#~ "tryggingsproblem.\n" +#~ "\n" +#~ "Det er likevel eit viktig tryggingsproblem for tekstinndataeiningar " +#~ "(tastatur, numpads),\n" +#~ "fordi maskinskriven tekst kan bli sniffa umerka av tredjepartar innan " +#~ "rekkevidde." + +#~ msgid "This receiver has %d pairing(s) remaining." +#~ msgstr "Denne mottakaren har %d gjenståande paring(er)." + +#~ msgid "Thumb Scroll Invert" +#~ msgstr "Inverter tommelrulling" + +#~ msgid "Turn illumination on or off on keyboard." +#~ msgstr "Slå belysning på eller av for tastaturet." + +#~ msgid "Turn on the device you want to pair." +#~ msgstr "Slå på eininga du ynsker å pare." + +#~ msgid "USB id" +#~ msgstr "USB-id" + +#~ msgid "Unexpected device number (%s) in notification %s." +#~ msgstr "Uventa einingsnummer (%s) i varsel %s." + +#~ msgid "Wheel Resolution" +#~ msgstr "Hjuloppløysning" + +#~ msgid "height" +#~ msgstr "høgde" + +#~ msgid "last known" +#~ msgstr "sist kjend" + +#~ msgid "next " +#~ msgstr "neste " + +#~ msgid "none" +#~ msgstr "ingen" + +#~ msgid "unknown" +#~ msgstr "ukjend" + +#~ msgid "width" +#~ msgstr "breidde" diff --git a/po/pl.po b/po/pl.po index 37feb342..d542bdc8 100644 --- a/po/pl.po +++ b/po/pl.po @@ -5,10 +5,10 @@ # msgid "" msgstr "" -"Project-Id-Version: solaar 1.1.9\n" +"Project-Id-Version: solaar 1.1.17\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-03-09 13:45+0100\n" -"PO-Revision-Date: 2023-03-09 14:08+0100\n" +"POT-Creation-Date: 2025-12-01 19:29+0100\n" +"PO-Revision-Date: 2025-12-01 19:46+0100\n" "Last-Translator: Matthaiks\n" "Language-Team: none\n" "Language: pl\n" @@ -17,265 +17,387 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 3.2\n" +"X-Generator: Poedit 3.8\n" -#: lib/logitech_receiver/base_usb.py:46 +#: lib/logitech_receiver/base_usb.py:52 msgid "Bolt Receiver" msgstr "Odbiornik Bolt" -#: lib/logitech_receiver/base_usb.py:57 +#: lib/logitech_receiver/base_usb.py:64 msgid "Unifying Receiver" msgstr "Odbiornik Unifying" -#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 -#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 -#: lib/logitech_receiver/base_usb.py:114 +#: lib/logitech_receiver/base_usb.py:75 lib/logitech_receiver/base_usb.py:87 +#: lib/logitech_receiver/base_usb.py:100 lib/logitech_receiver/base_usb.py:113 msgid "Nano Receiver" msgstr "Odbiornik Nano" -#: lib/logitech_receiver/base_usb.py:124 +#: lib/logitech_receiver/base_usb.py:125 msgid "Lightspeed Receiver" msgstr "Odbiornik Lightspeed" -#: lib/logitech_receiver/base_usb.py:133 +#: lib/logitech_receiver/base_usb.py:135 msgid "EX100 Receiver 27 Mhz" msgstr "Odbiornik EX100 27 MHz" -#: lib/logitech_receiver/i18n.py:30 +#: lib/logitech_receiver/common.py:649 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Akumulator: %(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:652 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Akumulator: %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:1048 +#: lib/logitech_receiver/settings_templates.py:299 +msgid "Disabled" +msgstr "Wyłączone" + +#: lib/logitech_receiver/hidpp20.py:1049 +msgid "Static" +msgstr "Statyczny" + +#: lib/logitech_receiver/hidpp20.py:1050 +msgid "Pulse" +msgstr "Puls" + +#: lib/logitech_receiver/hidpp20.py:1051 +msgid "Cycle" +msgstr "Cykl" + +#: lib/logitech_receiver/hidpp20.py:1052 +msgid "Boot" +msgstr "Rozruch" + +#: lib/logitech_receiver/hidpp20.py:1053 +msgid "Demo" +msgstr "Demo" + +#: lib/logitech_receiver/hidpp20.py:1055 +msgid "Breathe" +msgstr "Oddech" + +#: lib/logitech_receiver/hidpp20.py:1058 +msgid "Ripple" +msgstr "Plusk" + +#: lib/logitech_receiver/hidpp20.py:1059 +msgid "Decomposition" +msgstr "Dekompozycja" + +#: lib/logitech_receiver/hidpp20.py:1060 +msgid "Signature1" +msgstr "Podpis1" + +#: lib/logitech_receiver/hidpp20.py:1061 +msgid "Signature2" +msgstr "Podpis2" + +#: lib/logitech_receiver/hidpp20.py:1062 +msgid "CycleS" +msgstr "Cykle" + +#: lib/logitech_receiver/hidpp20.py:1126 +msgid "Unknown Location" +msgstr "Nieznana lokalizacja" + +#: lib/logitech_receiver/hidpp20.py:1127 +msgid "Primary" +msgstr "Podstawowy" + +#: lib/logitech_receiver/hidpp20.py:1128 +msgid "Logo" +msgstr "Logo" + +#: lib/logitech_receiver/hidpp20.py:1129 +msgid "Left Side" +msgstr "Lewa strona" + +#: lib/logitech_receiver/hidpp20.py:1130 +msgid "Right Side" +msgstr "Prawa strona" + +#: lib/logitech_receiver/hidpp20.py:1131 +msgid "Combined" +msgstr "Łączony" + +#: lib/logitech_receiver/hidpp20.py:1132 +msgid "Primary 1" +msgstr "Podstawowy 1" + +#: lib/logitech_receiver/hidpp20.py:1133 +msgid "Primary 2" +msgstr "Podstawowy 2" + +#: lib/logitech_receiver/hidpp20.py:1134 +msgid "Primary 3" +msgstr "Podstawowy 3" + +#: lib/logitech_receiver/hidpp20.py:1135 +msgid "Primary 4" +msgstr "Podstawowy 4" + +#: lib/logitech_receiver/hidpp20.py:1136 +msgid "Primary 5" +msgstr "Podstawowy 5" + +#: lib/logitech_receiver/hidpp20.py:1137 +msgid "Primary 6" +msgstr "Podstawowy 6" + +#: lib/logitech_receiver/i18n.py:28 msgid "empty" msgstr "pusty" -#: lib/logitech_receiver/i18n.py:31 +#: lib/logitech_receiver/i18n.py:29 msgid "critical" msgstr "krytyczny" -#: lib/logitech_receiver/i18n.py:32 +#: lib/logitech_receiver/i18n.py:30 msgid "low" msgstr "niski" -#: lib/logitech_receiver/i18n.py:33 +#: lib/logitech_receiver/i18n.py:31 msgid "average" msgstr "przeciętny" -#: lib/logitech_receiver/i18n.py:34 +#: lib/logitech_receiver/i18n.py:32 msgid "good" msgstr "dobry" -#: lib/logitech_receiver/i18n.py:35 +#: lib/logitech_receiver/i18n.py:33 msgid "full" msgstr "pełny" -#: lib/logitech_receiver/i18n.py:38 +#: lib/logitech_receiver/i18n.py:35 msgid "discharging" msgstr "rozładowywanie" -#: lib/logitech_receiver/i18n.py:39 +#: lib/logitech_receiver/i18n.py:36 msgid "recharging" msgstr "ładowanie" -#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 +#: lib/logitech_receiver/i18n.py:37 msgid "charging" msgstr "ładowanie" -#: lib/logitech_receiver/i18n.py:41 +#: lib/logitech_receiver/i18n.py:38 msgid "not charging" msgstr "brak ładowania" -#: lib/logitech_receiver/i18n.py:42 +#: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "prawie pełny" -#: lib/logitech_receiver/i18n.py:43 +#: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "naładowany" -#: lib/logitech_receiver/i18n.py:44 +#: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "powolne ładowanie" -#: lib/logitech_receiver/i18n.py:45 +#: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "nieodpowiedni akumulator" -#: lib/logitech_receiver/i18n.py:46 +#: lib/logitech_receiver/i18n.py:43 msgid "thermal error" msgstr "błąd termiczny" -#: lib/logitech_receiver/i18n.py:47 +#: lib/logitech_receiver/i18n.py:44 msgid "error" msgstr "błąd" -#: lib/logitech_receiver/i18n.py:48 +#: lib/logitech_receiver/i18n.py:45 msgid "standard" msgstr "standardowe" -#: lib/logitech_receiver/i18n.py:49 +#: lib/logitech_receiver/i18n.py:46 msgid "fast" msgstr "szybkie" -#: lib/logitech_receiver/i18n.py:50 +#: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "powolne" -#: lib/logitech_receiver/i18n.py:53 +#: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "upłynął limit czasu urządzenia" -#: lib/logitech_receiver/i18n.py:54 +#: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "urządzenie nie jest obsługiwane" -#: lib/logitech_receiver/i18n.py:55 +#: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "za dużo urządzeń" -#: lib/logitech_receiver/i18n.py:56 +#: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "upłynął limit czasu sekwencji" -#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 +#: lib/logitech_receiver/i18n.py:54 msgid "Firmware" msgstr "Firmware" -#: lib/logitech_receiver/i18n.py:60 +#: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Bootloader" -#: lib/logitech_receiver/i18n.py:61 +#: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Sprzęt" -#: lib/logitech_receiver/i18n.py:62 +#: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "Inne" -#: lib/logitech_receiver/i18n.py:65 +#: lib/logitech_receiver/i18n.py:59 msgid "Left Button" msgstr "Przycisk lewy" -#: lib/logitech_receiver/i18n.py:66 +#: lib/logitech_receiver/i18n.py:60 msgid "Right Button" msgstr "Przycisk prawy" -#: lib/logitech_receiver/i18n.py:67 +#: lib/logitech_receiver/i18n.py:61 msgid "Middle Button" msgstr "Przycisk środkowy" -#: lib/logitech_receiver/i18n.py:68 +#: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "Przycisk wstecz" -#: lib/logitech_receiver/i18n.py:69 +#: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "Przycisk dalej" -#: lib/logitech_receiver/i18n.py:70 +#: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "Przycisk gestów myszy" -#: lib/logitech_receiver/i18n.py:71 +#: lib/logitech_receiver/i18n.py:65 msgid "Smart Shift" msgstr "SmartShift" -#: lib/logitech_receiver/i18n.py:72 +#: lib/logitech_receiver/i18n.py:66 msgid "DPI Switch" msgstr "Przełącznik DPI" -#: lib/logitech_receiver/i18n.py:73 +#: lib/logitech_receiver/i18n.py:67 msgid "Left Tilt" msgstr "Pochylenie w lewo" -#: lib/logitech_receiver/i18n.py:74 +#: lib/logitech_receiver/i18n.py:68 msgid "Right Tilt" msgstr "Pochylenie w prawo" -#: lib/logitech_receiver/i18n.py:75 +#: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "Kliknięcie LPM" -#: lib/logitech_receiver/i18n.py:76 +#: lib/logitech_receiver/i18n.py:70 msgid "Right Click" msgstr "Kliknięcie PPM" -#: lib/logitech_receiver/i18n.py:77 +#: lib/logitech_receiver/i18n.py:71 msgid "Mouse Middle Button" msgstr "Przycisk środkowy myszy" -#: lib/logitech_receiver/i18n.py:78 +#: lib/logitech_receiver/i18n.py:72 msgid "Mouse Back Button" msgstr "Przycisk wstecz myszy" -#: lib/logitech_receiver/i18n.py:79 +#: lib/logitech_receiver/i18n.py:73 msgid "Mouse Forward Button" msgstr "Przycisk dalej myszy" -#: lib/logitech_receiver/i18n.py:80 +#: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "Nawigacja przyciskiem gestów" -#: lib/logitech_receiver/i18n.py:81 +#: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "Lewy przycisk kółka myszy" -#: lib/logitech_receiver/i18n.py:82 +#: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "Prawy przycisk kółka myszy" -#: lib/logitech_receiver/i18n.py:85 +#: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "wciśnięty" -#: lib/logitech_receiver/i18n.py:86 +#: lib/logitech_receiver/i18n.py:79 msgid "released" msgstr "zwolniony" -#: lib/logitech_receiver/notifications.py:75 -#: lib/logitech_receiver/notifications.py:126 -msgid "pairing lock is closed" -msgstr "blokada sparowania jest zamknięta" - -#: lib/logitech_receiver/notifications.py:75 -#: lib/logitech_receiver/notifications.py:126 -msgid "pairing lock is open" -msgstr "blokada sparowania jest otwarta" - -#: lib/logitech_receiver/notifications.py:92 -msgid "discovery lock is closed" -msgstr "blokada wykrywania jest zamknięta" - -#: lib/logitech_receiver/notifications.py:92 -msgid "discovery lock is open" -msgstr "blokada wykrywania jest otwarta" - -#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 msgid "connected" msgstr "podłączone" -#: lib/logitech_receiver/notifications.py:224 +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 msgid "disconnected" msgstr "rozłączone" -#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 +#: lib/logitech_receiver/notifications.py:182 msgid "unpaired" msgstr "niesparowane" -#: lib/logitech_receiver/notifications.py:304 +#: lib/logitech_receiver/notifications.py:231 msgid "powered on" msgstr "włączone" -#: lib/logitech_receiver/settings.py:750 +#: lib/logitech_receiver/notifications.py:283 +msgid "ADC measurement notification" +msgstr "powiadomienie o pomiarze ADC" + +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:483 +msgid "pairing lock is closed" +msgstr "blokada sparowania jest zamknięta" + +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:483 +msgid "pairing lock is open" +msgstr "blokada sparowania jest otwarta" + +#: lib/logitech_receiver/notifications.py:446 +msgid "discovery lock is closed" +msgstr "blokada wykrywania jest zamknięta" + +#: lib/logitech_receiver/notifications.py:446 +msgid "discovery lock is open" +msgstr "blokada wykrywania jest otwarta" + +#: lib/logitech_receiver/receiver.py:437 +msgid "No paired devices." +msgstr "Brak sparowanych urządzeń." + +#: lib/logitech_receiver/receiver.py:439 lib/solaar/ui/window.py:606 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s sparowane urządzenie." +msgstr[1] "%(count)s sparowane urządzenia." +msgstr[2] "%(count)s sparowanych urządzeń." + +#: lib/logitech_receiver/settings.py:602 msgid "register" msgstr "rejestr" -#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 +#: lib/logitech_receiver/settings.py:616 lib/logitech_receiver/settings.py:654 msgid "feature" msgstr "funkcja" -#: lib/logitech_receiver/settings_templates.py:139 +#: lib/logitech_receiver/settings_templates.py:138 msgid "Swap Fx function" msgstr "Zamień funkcję Fx" -#: lib/logitech_receiver/settings_templates.py:140 +#: lib/logitech_receiver/settings_templates.py:141 msgid "" "When set, the F1..F12 keys will activate their special function,\n" "and you must hold the FN key to activate their standard function." @@ -284,7 +406,7 @@ msgstr "" "ich funkcja pomocnicza. Aby aktywować funkcję standardową, należy\n" "przytrzymać klawisz FN." -#: lib/logitech_receiver/settings_templates.py:142 +#: lib/logitech_receiver/settings_templates.py:146 msgid "" "When unset, the F1..F12 keys will activate their standard function,\n" "and you must hold the FN key to activate their special function." @@ -293,31 +415,31 @@ msgstr "" "ich funkcja standardowa. Aby aktywować funkcję pomocniczą, należy\n" "przytrzymać klawisz FN." -#: lib/logitech_receiver/settings_templates.py:149 +#: lib/logitech_receiver/settings_templates.py:154 msgid "Hand Detection" msgstr "Wykrywanie dłoni" -#: lib/logitech_receiver/settings_templates.py:150 +#: lib/logitech_receiver/settings_templates.py:155 msgid "Turn on illumination when the hands hover over the keyboard." msgstr "Włącz podświetlenie, gdy dłonie znajdą się nad klawiaturą." -#: lib/logitech_receiver/settings_templates.py:157 +#: lib/logitech_receiver/settings_templates.py:162 msgid "Scroll Wheel Smooth Scrolling" msgstr "Płynne przewijanie kółkiem przewijania" -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:239 -#: lib/logitech_receiver/settings_templates.py:267 +#: lib/logitech_receiver/settings_templates.py:163 +#: lib/logitech_receiver/settings_templates.py:410 +#: lib/logitech_receiver/settings_templates.py:439 msgid "High-sensitivity mode for vertical scroll with the wheel." msgstr "" "Tryb wysokiej rozdzielczości do przewijania pionowego\n" "przy użyciu kółka myszy." -#: lib/logitech_receiver/settings_templates.py:165 +#: lib/logitech_receiver/settings_templates.py:170 msgid "Side Scrolling" msgstr "Przewijanie na boki" -#: lib/logitech_receiver/settings_templates.py:167 +#: lib/logitech_receiver/settings_templates.py:172 msgid "" "When disabled, pushing the wheel sideways sends custom button events\n" "instead of the standard side-scrolling events." @@ -326,46 +448,107 @@ msgstr "" "niestandardowych\n" "zdarzeń przycisków zamiast standardowych zdarzeń przewijania na boki." -#: lib/logitech_receiver/settings_templates.py:177 +#: lib/logitech_receiver/settings_templates.py:182 msgid "Sensitivity (DPI - older mice)" msgstr "Czułość (DPI - starsze myszy)" -#: lib/logitech_receiver/settings_templates.py:178 -#: lib/logitech_receiver/settings_templates.py:712 +#: lib/logitech_receiver/settings_templates.py:183 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 msgid "Mouse movement sensitivity" msgstr "Czułość ruchu myszy" -#: lib/logitech_receiver/settings_templates.py:208 -#: lib/logitech_receiver/settings_templates.py:218 -#: lib/logitech_receiver/settings_templates.py:225 -msgid "Backlight" -msgstr "Podświetlenie" +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Backlight Timed" +msgstr "Podświetlenie czasowe" -#: lib/logitech_receiver/settings_templates.py:209 -#: lib/logitech_receiver/settings_templates.py:226 +#: lib/logitech_receiver/settings_templates.py:257 +#: lib/logitech_receiver/settings_templates.py:397 msgid "Set illumination time for keyboard." msgstr "Ustaw czas podświetlenia klawiatury." -#: lib/logitech_receiver/settings_templates.py:219 -msgid "Turn illumination on or off on keyboard." -msgstr "Włącz lub wyłącz podświetlenie klawiatury." +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Backlight" +msgstr "Podświetlenie" -#: lib/logitech_receiver/settings_templates.py:237 +#: lib/logitech_receiver/settings_templates.py:269 +msgid "" +"Illumination level on keyboard. Changes made are only applied in Manual " +"mode." +msgstr "" +"Poziom podświetlenia klawiatury. Wprowadzone zmiany zostaną zastosowane " +"wyłącznie w trybie ręcznym." + +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Automatic" +msgstr "Automatyczne" + +#: lib/logitech_receiver/settings_templates.py:303 +msgid "Manual" +msgstr "Ręczne" + +#: lib/logitech_receiver/settings_templates.py:305 +msgid "Enabled" +msgstr "Włączone" + +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Backlight Level" +msgstr "Poziom podświetlenia" + +#: lib/logitech_receiver/settings_templates.py:312 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "Poziom podświetlenia klawiatury w trybie ręcznym." + +#: lib/logitech_receiver/settings_templates.py:369 +msgid "Backlight Delay Hands Out" +msgstr "Opóźnienie podświetlenia bez rąk" + +#: lib/logitech_receiver/settings_templates.py:370 +msgid "" +"Delay in seconds until backlight fades out with hands away from keyboard." +msgstr "" +"Opóźnienie w sekundach, aż podświetlenie zniknie, gdy ręce będą oddalone od " +"klawiatury." + +#: lib/logitech_receiver/settings_templates.py:378 +msgid "Backlight Delay Hands In" +msgstr "Opóźnienie podświetlenia z rękoma" + +#: lib/logitech_receiver/settings_templates.py:379 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "" +"Opóźnienie w sekundach, aż podświetlenie zniknie, gdy ręce będą blisko " +"klawiatury." + +#: lib/logitech_receiver/settings_templates.py:387 +msgid "Backlight Delay Powered" +msgstr "Opóźnienie podświetlenia z zasilanem" + +#: lib/logitech_receiver/settings_templates.py:388 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "" +"Opóźnienie w sekundach, aż podświetlenie zaniknie przy zasilaniu zewnętrznym." + +#: lib/logitech_receiver/settings_templates.py:396 +msgid "Backlight (Seconds)" +msgstr "Podświetlenie (sekundy)" + +#: lib/logitech_receiver/settings_templates.py:408 msgid "Scroll Wheel High Resolution" msgstr "Wysoka rozdzielczość kółka przewijania" -#: lib/logitech_receiver/settings_templates.py:240 -#: lib/logitech_receiver/settings_templates.py:268 +#: lib/logitech_receiver/settings_templates.py:412 +#: lib/logitech_receiver/settings_templates.py:441 msgid "Set to ignore if scrolling is abnormally fast or slow" msgstr "" "Ustaw, aby ignorować, jeśli przewijanie jest nienormalnie szybkie lub wolne" -#: lib/logitech_receiver/settings_templates.py:247 -#: lib/logitech_receiver/settings_templates.py:277 +#: lib/logitech_receiver/settings_templates.py:419 +#: lib/logitech_receiver/settings_templates.py:450 msgid "Scroll Wheel Diversion" msgstr "Przekierowanie kółka przewijania" -#: lib/logitech_receiver/settings_templates.py:249 +#: lib/logitech_receiver/settings_templates.py:421 msgid "" "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " "Solaar rules but are otherwise ignored)." @@ -373,19 +556,19 @@ msgstr "" "Spraw, aby kółko przewijania wysyłało powiadomienia LOWRES_WHEEL HID++ " "(które wyzwalają reguły Solaar, ale w przeciwnym razie są ignorowane)." -#: lib/logitech_receiver/settings_templates.py:256 +#: lib/logitech_receiver/settings_templates.py:428 msgid "Scroll Wheel Direction" msgstr "Kierunek kółka przewijania" -#: lib/logitech_receiver/settings_templates.py:257 +#: lib/logitech_receiver/settings_templates.py:429 msgid "Invert direction for vertical scroll with wheel." msgstr "Odwróć kierunek przewijania w pionie za pomocą kółka." -#: lib/logitech_receiver/settings_templates.py:265 +#: lib/logitech_receiver/settings_templates.py:437 msgid "Scroll Wheel Resolution" msgstr "Rozdzielczość kółka przewijania" -#: lib/logitech_receiver/settings_templates.py:279 +#: lib/logitech_receiver/settings_templates.py:452 msgid "" "Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." @@ -393,19 +576,19 @@ msgstr "" "Spraw, aby kółko przewijania wysyłało powiadomienia HIRES_WHEEL HID++ (które " "wyzwalają reguły Solaar, ale w przeciwnym razie są ignorowane)." -#: lib/logitech_receiver/settings_templates.py:288 +#: lib/logitech_receiver/settings_templates.py:461 msgid "Sensitivity (Pointer Speed)" msgstr "Czułość (prędkość wskaźnika)" -#: lib/logitech_receiver/settings_templates.py:289 +#: lib/logitech_receiver/settings_templates.py:462 msgid "Speed multiplier for mouse (256 is normal multiplier)." msgstr "Mnożnik prędkości myszy (256 to normalny mnożnik)." -#: lib/logitech_receiver/settings_templates.py:299 +#: lib/logitech_receiver/settings_templates.py:472 msgid "Thumb Wheel Diversion" msgstr "Przekierowanie kółka obsługiwanego kciukiem" -#: lib/logitech_receiver/settings_templates.py:301 +#: lib/logitech_receiver/settings_templates.py:474 msgid "" "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." @@ -413,45 +596,50 @@ msgstr "" "Spraw, aby kółko obsługiwane kciukiem wysyłało powiadomienia THUMB_WHEEL HID+" "+ (które wyzwalają reguły Solaar, ale w przeciwnym razie są ignorowane)." -#: lib/logitech_receiver/settings_templates.py:310 +#: lib/logitech_receiver/settings_templates.py:483 msgid "Thumb Wheel Direction" msgstr "Kierunek kółka obsługiwanego kciukiem" -#: lib/logitech_receiver/settings_templates.py:311 +#: lib/logitech_receiver/settings_templates.py:484 msgid "Invert thumb wheel scroll direction." msgstr "Odwróć kierunek przewijania kółkiem obsługiwanym kciukiem." -#: lib/logitech_receiver/settings_templates.py:319 +#: lib/logitech_receiver/settings_templates.py:504 msgid "Onboard Profiles" msgstr "Profile wbudowane" -#: lib/logitech_receiver/settings_templates.py:320 +#: lib/logitech_receiver/settings_templates.py:505 msgid "" -"Enable onboard profiles, which often control report rate and keyboard " -"lighting" +"Enable an onboard profile, which controls report rate, sensitivity, and " +"button actions" msgstr "" -"Włącz profile wbudowane, które często kontrolują szybkość raportowania i " -"oświetlenie klawiatury" +"Włącz wbudowany profil, który kontroluje częstotliwość raportowania, czułość " +"i czynności przycisków" -#: lib/logitech_receiver/settings_templates.py:330 -msgid "Polling Rate (ms)" -msgstr "Odpytywanie (ms)" +#: lib/logitech_receiver/settings_templates.py:549 +#: lib/logitech_receiver/settings_templates.py:582 +msgid "Report Rate" +msgstr "Szybkość raportów" -#: lib/logitech_receiver/settings_templates.py:332 -msgid "Frequency of device polling, in milliseconds" -msgstr "Częstotliwość odpytywania urządzenia w milisekundach" +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +msgid "Frequency of device movement reports" +msgstr "Częstotliwość raportów o ruchu urządzenia" -#: lib/logitech_receiver/settings_templates.py:333 -#: lib/logitech_receiver/settings_templates.py:1046 -#: lib/logitech_receiver/settings_templates.py:1076 +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1421 +#: lib/logitech_receiver/settings_templates.py:1452 msgid "May need Onboard Profiles set to Disable to be effective." msgstr "Aby działało, może być konieczne wyłączenie profili wbudowanych." -#: lib/logitech_receiver/settings_templates.py:365 +#: lib/logitech_receiver/settings_templates.py:612 msgid "Divert crown events" msgstr "Przekieruj zdarzenia korony" -#: lib/logitech_receiver/settings_templates.py:366 +#: lib/logitech_receiver/settings_templates.py:613 msgid "" "Make crown send CROWN HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." @@ -459,36 +647,31 @@ msgstr "" "Spraw, aby korona wysyłała powiadomienia CROWN HID++ (które wyzwalają reguły " "Solaar, ale w przeciwnym razie są ignorowane)." -#: lib/logitech_receiver/settings_templates.py:374 +#: lib/logitech_receiver/settings_templates.py:621 msgid "Crown smooth scroll" msgstr "Płynne przewijanie koroną" -#: lib/logitech_receiver/settings_templates.py:375 +#: lib/logitech_receiver/settings_templates.py:622 msgid "Set crown smooth scroll" msgstr "Ustaw płynne przewijanie koroną" -#: lib/logitech_receiver/settings_templates.py:383 -msgid "Divert G Keys" -msgstr "Przekieruj klawisze G" +#: lib/logitech_receiver/settings_templates.py:630 +msgid "Divert G and M Keys" +msgstr "Przekieruj klawisze G i M" -#: lib/logitech_receiver/settings_templates.py:385 +#: lib/logitech_receiver/settings_templates.py:631 msgid "" -"Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " +"Make G and M keys send HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." msgstr "" -"Spraw, aby klawisze G wysyłały powiadomienia GKEY HID++ (które wyzwalają " +"Spraw, aby klawisze G i M wysyłały powiadomienia HID++ (które wyzwalają " "reguły Solaar, ale w przeciwnym razie są ignorowane)." -#: lib/logitech_receiver/settings_templates.py:386 -msgid "May also make M keys and MR key send HID++ notifications" -msgstr "" -"Również spraw, aby klawisze M i klawisz MR wysyłały powiadomienia HID++" - -#: lib/logitech_receiver/settings_templates.py:402 +#: lib/logitech_receiver/settings_templates.py:645 msgid "Scroll Wheel Ratcheted" msgstr "Zapadkowe kółko przewijania" -#: lib/logitech_receiver/settings_templates.py:403 +#: lib/logitech_receiver/settings_templates.py:646 msgid "" "Switch the mouse wheel between speed-controlled ratcheting and always " "freespin." @@ -496,19 +679,19 @@ msgstr "" "Przełącz kółko myszy między zapadką z kontrolowaną prędkością a zawsze " "swobodnym obrotem." -#: lib/logitech_receiver/settings_templates.py:405 +#: lib/logitech_receiver/settings_templates.py:648 msgid "Freespinning" msgstr "Obrót swobodny" -#: lib/logitech_receiver/settings_templates.py:405 +#: lib/logitech_receiver/settings_templates.py:648 msgid "Ratcheted" msgstr "Obrót zapadkowy" -#: lib/logitech_receiver/settings_templates.py:412 +#: lib/logitech_receiver/settings_templates.py:655 msgid "Scroll Wheel Ratchet Speed" msgstr "Prędkość zapadki kółka przewijania" -#: lib/logitech_receiver/settings_templates.py:414 +#: lib/logitech_receiver/settings_templates.py:657 msgid "" "Use the mouse wheel speed to switch between ratcheted and freespinning.\n" "The mouse wheel is always ratcheted at 50." @@ -517,19 +700,27 @@ msgstr "" "swobodnym.\n" "Kółko myszy jest zawsze ustawione na 50." -#: lib/logitech_receiver/settings_templates.py:463 +#: lib/logitech_receiver/settings_templates.py:707 +msgid "Scroll Wheel Ratchet Torque" +msgstr "Moment obrotowy zapadki kółka przewijania" + +#: lib/logitech_receiver/settings_templates.py:708 +msgid "Change the torque needed to overcome the ratchet." +msgstr "Zmień moment obrotowy potrzebny do przejścia zapadki." + +#: lib/logitech_receiver/settings_templates.py:743 msgid "Key/Button Actions" msgstr "Czynności klawiszy/przycisków" -#: lib/logitech_receiver/settings_templates.py:465 +#: lib/logitech_receiver/settings_templates.py:745 msgid "Change the action for the key or button." msgstr "Zmień czynność klawisza lub przycisku." -#: lib/logitech_receiver/settings_templates.py:465 +#: lib/logitech_receiver/settings_templates.py:747 msgid "Overridden by diversion." msgstr "Omijane przez przekierowanie." -#: lib/logitech_receiver/settings_templates.py:466 +#: lib/logitech_receiver/settings_templates.py:749 msgid "" "Changing important actions (such as for the left mouse button) can result in " "an unusable system." @@ -537,11 +728,11 @@ msgstr "" "Zmiana ważnych czynności (np. lewego przycisku myszy) może spowodować, że " "system będzie nieużywalny." -#: lib/logitech_receiver/settings_templates.py:639 +#: lib/logitech_receiver/settings_templates.py:924 msgid "Key/Button Diversion" msgstr "Przekierowanie klawiszy/przycisków" -#: lib/logitech_receiver/settings_templates.py:640 +#: lib/logitech_receiver/settings_templates.py:925 msgid "" "Make the key or button send HID++ notifications (Diverted) or initiate Mouse " "Gestures or Sliding DPI" @@ -549,36 +740,37 @@ msgstr "" "Spraw, aby klawisz lub przycisk wysyłał powiadomienia HID++ (przekierowany) " "lub inicjował gesty myszy lub przesuwnie DPI" -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 -#: lib/logitech_receiver/settings_templates.py:645 +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 msgid "Diverted" msgstr "Przekierowany" -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 msgid "Mouse Gestures" msgstr "Gesty myszy" -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 -#: lib/logitech_receiver/settings_templates.py:645 +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 msgid "Regular" msgstr "Zwykły" -#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:928 msgid "Sliding DPI" msgstr "Przesuwnie DPI" -#: lib/logitech_receiver/settings_templates.py:711 +#: lib/logitech_receiver/settings_templates.py:1018 +#: lib/logitech_receiver/settings_templates.py:1046 msgid "Sensitivity (DPI)" msgstr "Czułość (DPI)" -#: lib/logitech_receiver/settings_templates.py:752 +#: lib/logitech_receiver/settings_templates.py:1123 msgid "Sensitivity Switching" msgstr "Przełączanie czułości" -#: lib/logitech_receiver/settings_templates.py:754 +#: lib/logitech_receiver/settings_templates.py:1125 msgid "" "Switch the current sensitivity and the remembered sensitivity when the key " "or button is pressed.\n" @@ -588,329 +780,329 @@ msgstr "" "przycisku.\n" "Jeśli nie ma zapamiętanej czułości, po prostu zapamiętaj bieżącą czułość" -#: lib/logitech_receiver/settings_templates.py:758 +#: lib/logitech_receiver/settings_templates.py:1129 msgid "Off" msgstr "Wyłączone" -#: lib/logitech_receiver/settings_templates.py:791 +#: lib/logitech_receiver/settings_templates.py:1160 msgid "Disable keys" msgstr "Wyłącz klawisze" -#: lib/logitech_receiver/settings_templates.py:792 +#: lib/logitech_receiver/settings_templates.py:1161 msgid "Disable specific keyboard keys." msgstr "Wyłącz określone klawisze klawiatury." -#: lib/logitech_receiver/settings_templates.py:795 +#: lib/logitech_receiver/settings_templates.py:1164 #, python-format msgid "Disables the %s key." msgstr "Wyłącza klawisz %s." -#: lib/logitech_receiver/settings_templates.py:809 -#: lib/logitech_receiver/settings_templates.py:860 +#: lib/logitech_receiver/settings_templates.py:1177 +#: lib/logitech_receiver/settings_templates.py:1234 msgid "Set OS" msgstr "Ustaw system operacyjny" -#: lib/logitech_receiver/settings_templates.py:810 -#: lib/logitech_receiver/settings_templates.py:861 +#: lib/logitech_receiver/settings_templates.py:1178 +#: lib/logitech_receiver/settings_templates.py:1235 msgid "Change keys to match OS." msgstr "Zmień klawisze, aby pasowały do systemu operacyjnego." -#: lib/logitech_receiver/settings_templates.py:873 +#: lib/logitech_receiver/settings_templates.py:1247 msgid "Change Host" msgstr "Zmiana hosta" -#: lib/logitech_receiver/settings_templates.py:874 +#: lib/logitech_receiver/settings_templates.py:1248 msgid "Switch connection to a different host" msgstr "Przełącz połączenie na innego hosta" -#: lib/logitech_receiver/settings_templates.py:900 +#: lib/logitech_receiver/settings_templates.py:1272 msgid "Performs a left click." msgstr "Wykonuje kliknięcie lewym przyciskiem myszy." -#: lib/logitech_receiver/settings_templates.py:900 +#: lib/logitech_receiver/settings_templates.py:1272 msgid "Single tap" msgstr "Pojedyncze stuknięcie" -#: lib/logitech_receiver/settings_templates.py:901 +#: lib/logitech_receiver/settings_templates.py:1273 msgid "Performs a right click." msgstr "Wykonuje kliknięcie prawym przyciskiem myszy." -#: lib/logitech_receiver/settings_templates.py:901 +#: lib/logitech_receiver/settings_templates.py:1273 msgid "Single tap with two fingers" msgstr "Pojedyncze stuknięcie dwoma palcami" -#: lib/logitech_receiver/settings_templates.py:902 +#: lib/logitech_receiver/settings_templates.py:1274 msgid "Single tap with three fingers" msgstr "Pojedyncze stuknięcie trzema palcami" -#: lib/logitech_receiver/settings_templates.py:906 +#: lib/logitech_receiver/settings_templates.py:1278 msgid "Double tap" msgstr "Podwójne stuknięcie" -#: lib/logitech_receiver/settings_templates.py:906 +#: lib/logitech_receiver/settings_templates.py:1278 msgid "Performs a double click." msgstr "Wykonuje podwójne kliknięcie." -#: lib/logitech_receiver/settings_templates.py:907 +#: lib/logitech_receiver/settings_templates.py:1279 msgid "Double tap with two fingers" msgstr "Podwójne stuknięcie dwoma palcami" -#: lib/logitech_receiver/settings_templates.py:908 +#: lib/logitech_receiver/settings_templates.py:1280 msgid "Double tap with three fingers" msgstr "Podwójne stuknięcie trzema palcami" -#: lib/logitech_receiver/settings_templates.py:911 +#: lib/logitech_receiver/settings_templates.py:1283 msgid "Drags items by dragging the finger after double tapping." msgstr "" "Przeciąga elementy poprzez przeciąganie palcem po dwukrotnym stuknięciu." -#: lib/logitech_receiver/settings_templates.py:911 +#: lib/logitech_receiver/settings_templates.py:1283 msgid "Tap and drag" msgstr "Stuknięcie i przeciągnięcie" -#: lib/logitech_receiver/settings_templates.py:913 +#: lib/logitech_receiver/settings_templates.py:1285 +msgid "Tap and drag with two fingers" +msgstr "Stuknięcie i przeciągnięcie dwoma palcami" + +#: lib/logitech_receiver/settings_templates.py:1286 msgid "Drags items by dragging the fingers after double tapping." msgstr "" "Przeciąga elementy poprzez przeciąganie palcami po dwukrotnym stuknięciu." -#: lib/logitech_receiver/settings_templates.py:913 -msgid "Tap and drag with two fingers" -msgstr "Stuknięcie i przeciągnięcie dwoma palcami" - -#: lib/logitech_receiver/settings_templates.py:914 +#: lib/logitech_receiver/settings_templates.py:1288 msgid "Tap and drag with three fingers" msgstr "Stuknięcie i przeciągnięcie trzema palcami" -#: lib/logitech_receiver/settings_templates.py:917 +#: lib/logitech_receiver/settings_templates.py:1291 +msgid "Suppress tap and edge gestures" +msgstr "Stłumienie gestów stukania i krawędziowych" + +#: lib/logitech_receiver/settings_templates.py:1292 msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." msgstr "" "Wyłącza gesty stukania i krawędziowe (odpowiednik naciśnięcia Fn + lewy " "przycisk myszy)." -#: lib/logitech_receiver/settings_templates.py:917 -msgid "Suppress tap and edge gestures" -msgstr "Stłumienie gestów stukania i krawędziowych" - -#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:1294 msgid "Scroll with one finger" msgstr "Przewijanie jednym palcem" -#: lib/logitech_receiver/settings_templates.py:918 -#: lib/logitech_receiver/settings_templates.py:919 -#: lib/logitech_receiver/settings_templates.py:922 +#: lib/logitech_receiver/settings_templates.py:1294 +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 msgid "Scrolls." msgstr "Przewija." -#: lib/logitech_receiver/settings_templates.py:919 -#: lib/logitech_receiver/settings_templates.py:922 +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 msgid "Scroll with two fingers" msgstr "Przewijanie dwoma palcami" -#: lib/logitech_receiver/settings_templates.py:920 +#: lib/logitech_receiver/settings_templates.py:1296 msgid "Scroll horizontally with two fingers" msgstr "Przewijanie w poziomie dwoma palcami" -#: lib/logitech_receiver/settings_templates.py:920 +#: lib/logitech_receiver/settings_templates.py:1296 msgid "Scrolls horizontally." msgstr "Przewija w poziomie." -#: lib/logitech_receiver/settings_templates.py:921 +#: lib/logitech_receiver/settings_templates.py:1297 msgid "Scroll vertically with two fingers" msgstr "Przewijanie w pionie dwoma palcami" -#: lib/logitech_receiver/settings_templates.py:921 +#: lib/logitech_receiver/settings_templates.py:1297 msgid "Scrolls vertically." msgstr "Przewija w pionie." -#: lib/logitech_receiver/settings_templates.py:923 +#: lib/logitech_receiver/settings_templates.py:1299 msgid "Inverts the scrolling direction." msgstr "Odwraca kierunek przewijania." -#: lib/logitech_receiver/settings_templates.py:923 +#: lib/logitech_receiver/settings_templates.py:1299 msgid "Natural scrolling" msgstr "Przewijanie naturalne" -#: lib/logitech_receiver/settings_templates.py:924 +#: lib/logitech_receiver/settings_templates.py:1300 msgid "Enables the thumbwheel." msgstr "Włącza kółko obsługiwane kciukiem." -#: lib/logitech_receiver/settings_templates.py:924 +#: lib/logitech_receiver/settings_templates.py:1300 msgid "Thumbwheel" msgstr "Kółko obsługiwane kciukiem" -#: lib/logitech_receiver/settings_templates.py:935 -#: lib/logitech_receiver/settings_templates.py:939 +#: lib/logitech_receiver/settings_templates.py:1311 +#: lib/logitech_receiver/settings_templates.py:1315 msgid "Swipe from the top edge" msgstr "Przesuwanie od górnej krawędzi" -#: lib/logitech_receiver/settings_templates.py:936 +#: lib/logitech_receiver/settings_templates.py:1312 msgid "Swipe from the left edge" msgstr "Przesuwanie od lewej krawędzi" -#: lib/logitech_receiver/settings_templates.py:937 +#: lib/logitech_receiver/settings_templates.py:1313 msgid "Swipe from the right edge" msgstr "Przesuwanie od prawej krawędzi" -#: lib/logitech_receiver/settings_templates.py:938 +#: lib/logitech_receiver/settings_templates.py:1314 msgid "Swipe from the bottom edge" msgstr "Przesuwanie od dolnej krawędzi" -#: lib/logitech_receiver/settings_templates.py:940 +#: lib/logitech_receiver/settings_templates.py:1316 msgid "Swipe two fingers from the left edge" msgstr "Przesuwanie dwoma palcami od lewej krawędzi" -#: lib/logitech_receiver/settings_templates.py:941 +#: lib/logitech_receiver/settings_templates.py:1317 msgid "Swipe two fingers from the right edge" msgstr "Przesuwanie dwoma palcami od prawej krawędzi" -#: lib/logitech_receiver/settings_templates.py:942 +#: lib/logitech_receiver/settings_templates.py:1318 msgid "Swipe two fingers from the bottom edge" msgstr "Przesuwanie dwoma palcami od dolnej krawędzi" -#: lib/logitech_receiver/settings_templates.py:943 +#: lib/logitech_receiver/settings_templates.py:1319 msgid "Swipe two fingers from the top edge" msgstr "Przesuwanie dwoma palcami od górnej krawędzi" -#: lib/logitech_receiver/settings_templates.py:944 -#: lib/logitech_receiver/settings_templates.py:948 +#: lib/logitech_receiver/settings_templates.py:1320 +#: lib/logitech_receiver/settings_templates.py:1324 msgid "Pinch to zoom out; spread to zoom in." msgstr "Ściśnięcie, aby pomniejszyć; rozłożenie, aby powiększyć." -#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:1320 msgid "Zoom with two fingers." msgstr "Zoom dwoma palcami." -#: lib/logitech_receiver/settings_templates.py:945 +#: lib/logitech_receiver/settings_templates.py:1321 msgid "Pinch to zoom out." msgstr "Ściśnięcie, aby pomniejszyć." -#: lib/logitech_receiver/settings_templates.py:946 +#: lib/logitech_receiver/settings_templates.py:1322 msgid "Spread to zoom in." msgstr "Rozłożenie, aby powiększyć." -#: lib/logitech_receiver/settings_templates.py:947 +#: lib/logitech_receiver/settings_templates.py:1323 msgid "Zoom with three fingers." msgstr "Zoom trzema palcami." -#: lib/logitech_receiver/settings_templates.py:948 +#: lib/logitech_receiver/settings_templates.py:1324 msgid "Zoom with two fingers" msgstr "Zoom dwoma palcami" -#: lib/logitech_receiver/settings_templates.py:966 +#: lib/logitech_receiver/settings_templates.py:1342 msgid "Pixel zone" msgstr "Strefa pikseli" -#: lib/logitech_receiver/settings_templates.py:967 +#: lib/logitech_receiver/settings_templates.py:1343 msgid "Ratio zone" msgstr "Strefa współczynnika" -#: lib/logitech_receiver/settings_templates.py:968 +#: lib/logitech_receiver/settings_templates.py:1344 msgid "Scale factor" msgstr "Współczynnik skalowania" -#: lib/logitech_receiver/settings_templates.py:968 +#: lib/logitech_receiver/settings_templates.py:1344 msgid "Sets the cursor speed." msgstr "Ustawia szybkość kursora." -#: lib/logitech_receiver/settings_templates.py:972 +#: lib/logitech_receiver/settings_templates.py:1348 msgid "Left" msgstr "Lewo" -#: lib/logitech_receiver/settings_templates.py:972 +#: lib/logitech_receiver/settings_templates.py:1348 msgid "Left-most coordinate." msgstr "Współrzędna najbardziej z lewej." -#: lib/logitech_receiver/settings_templates.py:973 +#: lib/logitech_receiver/settings_templates.py:1349 msgid "Bottom" msgstr "Dół" -#: lib/logitech_receiver/settings_templates.py:973 +#: lib/logitech_receiver/settings_templates.py:1349 msgid "Bottom coordinate." msgstr "Współrzędna dolna." -#: lib/logitech_receiver/settings_templates.py:974 +#: lib/logitech_receiver/settings_templates.py:1350 msgid "Width" msgstr "Szerokość" -#: lib/logitech_receiver/settings_templates.py:974 +#: lib/logitech_receiver/settings_templates.py:1350 msgid "Width." msgstr "Szerokość." -#: lib/logitech_receiver/settings_templates.py:975 +#: lib/logitech_receiver/settings_templates.py:1351 msgid "Height" msgstr "Wysokość" -#: lib/logitech_receiver/settings_templates.py:975 +#: lib/logitech_receiver/settings_templates.py:1351 msgid "Height." msgstr "Wysokość." -#: lib/logitech_receiver/settings_templates.py:976 +#: lib/logitech_receiver/settings_templates.py:1352 msgid "Cursor speed." msgstr "Prędkość kursora." -#: lib/logitech_receiver/settings_templates.py:976 +#: lib/logitech_receiver/settings_templates.py:1352 msgid "Scale" msgstr "Skala" -#: lib/logitech_receiver/settings_templates.py:982 +#: lib/logitech_receiver/settings_templates.py:1358 msgid "Gestures" msgstr "Gesty" -#: lib/logitech_receiver/settings_templates.py:983 +#: lib/logitech_receiver/settings_templates.py:1359 msgid "Tweak the mouse/touchpad behaviour." msgstr "Dostosuj zachowania myszy/panelu dotykowego." -#: lib/logitech_receiver/settings_templates.py:1000 +#: lib/logitech_receiver/settings_templates.py:1375 msgid "Gestures Diversion" msgstr "Przekierowanie gestów" -#: lib/logitech_receiver/settings_templates.py:1001 +#: lib/logitech_receiver/settings_templates.py:1376 msgid "Divert mouse/touchpad gestures." msgstr "Przekieruj gesty myszy/panelu dotykowego." -#: lib/logitech_receiver/settings_templates.py:1018 +#: lib/logitech_receiver/settings_templates.py:1392 msgid "Gesture params" msgstr "Parametry gestu" -#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1393 msgid "Change numerical parameters of a mouse/touchpad." msgstr "Zmień parametry numeryczne myszy/panelu dotykowego." -#: lib/logitech_receiver/settings_templates.py:1044 +#: lib/logitech_receiver/settings_templates.py:1417 msgid "M-Key LEDs" msgstr "Diody LED klawiszy M" -#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1419 msgid "Control the M-Key LEDs." msgstr "Kontroluj diody LED klawiszy M." -#: lib/logitech_receiver/settings_templates.py:1047 -#: lib/logitech_receiver/settings_templates.py:1077 +#: lib/logitech_receiver/settings_templates.py:1423 +#: lib/logitech_receiver/settings_templates.py:1454 msgid "May need G Keys diverted to be effective." msgstr "Aby działało, może być konieczne włączenie przekierowania klawiszy G." -#: lib/logitech_receiver/settings_templates.py:1053 +#: lib/logitech_receiver/settings_templates.py:1429 #, python-format msgid "Lights up the %s key." msgstr "Zapala klawisz %s." -#: lib/logitech_receiver/settings_templates.py:1074 +#: lib/logitech_receiver/settings_templates.py:1448 msgid "MR-Key LED" msgstr "Dioda LED klawisza MR" -#: lib/logitech_receiver/settings_templates.py:1076 +#: lib/logitech_receiver/settings_templates.py:1450 msgid "Control the MR-Key LED." msgstr "Kontroluj diody LED klawiszy MR." -#: lib/logitech_receiver/settings_templates.py:1095 +#: lib/logitech_receiver/settings_templates.py:1471 msgid "Persistent Key/Button Mapping" msgstr "Trwałe mapowanie klawiszy/przycisków" -#: lib/logitech_receiver/settings_templates.py:1097 +#: lib/logitech_receiver/settings_templates.py:1473 msgid "Permanently change the mapping for the key or button." msgstr "Trwale zmień mapowania klawisza lub przycisku." -#: lib/logitech_receiver/settings_templates.py:1098 +#: lib/logitech_receiver/settings_templates.py:1475 msgid "" "Changing important keys or buttons (such as for the left mouse button) can " "result in an unusable system." @@ -918,76 +1110,171 @@ msgstr "" "Zmiana ważnych klawiszy lub przycisków (takich jak lewy przycisk myszy) może " "spowodować, że system stanie się bezużyteczny." -#: lib/logitech_receiver/settings_templates.py:1157 +#: lib/logitech_receiver/settings_templates.py:1532 msgid "Sidetone" msgstr "Efekt lokalny" -#: lib/logitech_receiver/settings_templates.py:1158 +#: lib/logitech_receiver/settings_templates.py:1533 msgid "Set sidetone level." msgstr "Ustaw poziom efektu lokalnego." -#: lib/logitech_receiver/settings_templates.py:1167 +#: lib/logitech_receiver/settings_templates.py:1542 msgid "Equalizer" msgstr "Korektor" -#: lib/logitech_receiver/settings_templates.py:1168 +#: lib/logitech_receiver/settings_templates.py:1543 msgid "Set equalizer levels." msgstr "Ustaw poziomy korektora." -#: lib/logitech_receiver/settings_templates.py:1191 +#: lib/logitech_receiver/settings_templates.py:1565 msgid "Hz" msgstr "Hz" -#: lib/logitech_receiver/settings_templates.py:1197 +#: lib/logitech_receiver/settings_templates.py:1571 msgid "Power Management" msgstr "Zarządzanie energią" -#: lib/logitech_receiver/settings_templates.py:1198 +#: lib/logitech_receiver/settings_templates.py:1572 msgid "Power off in minutes (0 for never)." msgstr "Wyłączenie zasilania w minutach (0 oznacza nigdy)." -#: lib/logitech_receiver/status.py:114 -msgid "No paired devices." -msgstr "Brak sparowanych urządzeń." +#: lib/logitech_receiver/settings_templates.py:1584 +msgid "Brightness Control" +msgstr "Sterowanie jasnością" -#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 -#, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s sparowane urządzenie." -msgstr[1] "%(count)s sparowane urządzenia." -msgstr[2] "%(count)s sparowanych urządzeń." +#: lib/logitech_receiver/settings_templates.py:1585 +msgid "Control overall brightness" +msgstr "Steruj ogólną jasnością" -#: lib/logitech_receiver/status.py:170 -#, python-format -msgid "Battery: %(level)s" -msgstr "Akumulator: %(level)s" +#: lib/logitech_receiver/settings_templates.py:1628 +#: lib/logitech_receiver/settings_templates.py:1681 +msgid "LED Control" +msgstr "Sterowanie diodami LED" -#: lib/logitech_receiver/status.py:172 -#, python-format -msgid "Battery: %(percent)d%%" -msgstr "Akumulator: %(percent)d%%" +#: lib/logitech_receiver/settings_templates.py:1629 +#: lib/logitech_receiver/settings_templates.py:1682 +msgid "Switch control of LED zones between device and Solaar" +msgstr "Przełącz sterowanie strefami LED pomiędzy urządzeniem a Solaar" -#: lib/logitech_receiver/status.py:184 -#, python-format -msgid "Lighting: %(level)s lux" -msgstr "Oświetlenie: %(level)s lx" +#: lib/logitech_receiver/settings_templates.py:1644 +#: lib/logitech_receiver/settings_templates.py:1692 +msgid "LED Zone Effects" +msgstr "Efekty strefowe diod LED" -#: lib/logitech_receiver/status.py:239 -#, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "Akumulator: %(level)s (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "LED Control needs to be set to Solaar to be effective." +msgstr "" +"Sterowanie diodami LED musi być ustawione na Solaar, aby było skuteczne." -#: lib/logitech_receiver/status.py:241 -#, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "Akumulator: %(percent)d%% (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "Set effect for LED Zone" +msgstr "Ustaw efekt dla strefy diod LED" -#: lib/solaar/ui/__init__.py:52 +#: lib/logitech_receiver/settings_templates.py:1647 +msgid "Color" +msgstr "Kolor" + +#: lib/logitech_receiver/settings_templates.py:1648 +msgid "Speed" +msgstr "Szybkość" + +#: lib/logitech_receiver/settings_templates.py:1649 +msgid "Period" +msgstr "Okres" + +#: lib/logitech_receiver/settings_templates.py:1650 +msgid "Intensity" +msgstr "Intensywność" + +#: lib/logitech_receiver/settings_templates.py:1651 +msgid "Ramp" +msgstr "Narastanie" + +#: lib/logitech_receiver/settings_templates.py:1666 +msgid "LEDs" +msgstr "Diody LED" + +#: lib/logitech_receiver/settings_templates.py:1703 +msgid "Per-key Lighting" +msgstr "Podświetlenie poszczególnych klawiszy" + +#: lib/logitech_receiver/settings_templates.py:1704 +msgid "Control per-key lighting." +msgstr "Steruj podświetleniem poszczególnych klawiszy." + +#: lib/logitech_receiver/settings_templates.py:1786 +msgid "Force Sensing Buttons" +msgstr "Przyciski wykrywające siłę" + +#: lib/logitech_receiver/settings_templates.py:1787 +msgid "Change the force required to activate button." +msgstr "Zmień siłę potrzebną do aktywowania przycisku." + +#: lib/logitech_receiver/settings_templates.py:1804 +msgid "Force Sensing Button" +msgstr "Przycisk wykrywający siłę" + +#: lib/logitech_receiver/settings_templates.py:1814 +msgid "Haptic Feeback Level" +msgstr "Poziom haptycznego sprzężenia zwrotnego" + +#: lib/logitech_receiver/settings_templates.py:1815 +msgid "Change power of haptic feedback. (Zero to turn off.)" +msgstr "" +"Zmień moc haptycznego sprzężenia zwrotnego. (Zero powoduje wyłączenie.)" + +#: lib/logitech_receiver/settings_templates.py:1857 +msgid "Play Haptic Waveform" +msgstr "Odtwórz falę haptyczną" + +#: lib/logitech_receiver/settings_templates.py:1858 +msgid "Tell device to play a haptic waveform." +msgstr "Poleć urządzeniu odtworzenie fali haptycznej." + +#: lib/solaar/ui/__init__.py:120 +msgid "Another Solaar process is already running so just expose its window" +msgstr "Inny proces programu Solaar już działa, więc po prostu pokaż jego okno" + +#: lib/solaar/ui/about/model.py:36 +msgid "" +"Manages Logitech receivers,\n" +"keyboards, mice, and tablets." +msgstr "" +"Zarządza odbiornikami, klawiaturami,\n" +"myszami i tabletami firmy Logitech." + +#: lib/solaar/ui/about/model.py:63 +msgid "Additional Programming" +msgstr "Dodatkowe programowanie" + +#: lib/solaar/ui/about/model.py:64 +msgid "GUI design" +msgstr "Projekt GUI" + +#: lib/solaar/ui/about/model.py:66 +msgid "Testing" +msgstr "Testowanie" + +#: lib/solaar/ui/about/model.py:74 +msgid "Logitech documentation" +msgstr "Dokumentacja firmy Logitech" + +#: lib/solaar/ui/action.py:88 lib/solaar/ui/action.py:92 +#: lib/solaar/ui/window.py:208 +msgid "Unpair" +msgstr "Usuń sparowanie" + +#: lib/solaar/ui/action.py:91 lib/solaar/ui/diversion_rules.py:141 +msgid "Cancel" +msgstr "Anuluj" + +#: lib/solaar/ui/common.py:42 msgid "Permissions error" msgstr "Błąd uprawnień" -#: lib/solaar/ui/__init__.py:54 +#: lib/solaar/ui/common.py:44 #, python-format msgid "" "Found a Logitech receiver or device (%s), but did not have permission to " @@ -996,7 +1283,7 @@ msgstr "" "Znaleziono odbiornik lub urządzenie firmy Logitech (%s), ale nie ma " "uprawnień do jego otwarcia." -#: lib/solaar/ui/__init__.py:55 +#: lib/solaar/ui/common.py:46 msgid "" "If you've just installed Solaar, try disconnecting the receiver or device " "and then reconnecting it." @@ -1004,11 +1291,11 @@ msgstr "" "Jeśli właśnie zainstalowano Solaar, spróbuj odłączyć odbiornik lub " "urządzenie, a następnie podłączyć je ponownie." -#: lib/solaar/ui/__init__.py:58 +#: lib/solaar/ui/common.py:49 msgid "Cannot connect to device error" msgstr "Błąd łączenia się z urządzeniem" -#: lib/solaar/ui/__init__.py:60 +#: lib/solaar/ui/common.py:51 #, python-format msgid "" "Found a Logitech receiver or device at %s, but encountered an error " @@ -1017,7 +1304,7 @@ msgstr "" "Znaleziono odbiornik lub urządzenie firmy Logitech w %s, ale wystąpił błąd " "podczas łączenia się z nim." -#: lib/solaar/ui/__init__.py:61 +#: lib/solaar/ui/common.py:53 msgid "" "Try disconnecting the device and then reconnecting it or turning it off and " "then on." @@ -1025,65 +1312,28 @@ msgstr "" "Spróbuj odłączyć urządzenie, a następnie podłączyć je ponownie lub wyłączyć " "i ponownie włączyć." -#: lib/solaar/ui/__init__.py:64 +#: lib/solaar/ui/common.py:56 msgid "Unpairing failed" msgstr "Usunięcie sparowania nie powiodło się" -#: lib/solaar/ui/__init__.py:66 +#: lib/solaar/ui/common.py:58 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Nie udało się sparować %{device} z %{receiver}." -#: lib/solaar/ui/__init__.py:67 +#: lib/solaar/ui/common.py:63 msgid "The receiver returned an error, with no further details." msgstr "Odbiornik zwrócił błąd bez dodatkowych informacji." -#: lib/solaar/ui/__init__.py:177 -msgid "Another Solaar process is already running so just expose its window" -msgstr "Inny proces programu Solaar już działa, więc po prostu pokaż jego okno" - -#: lib/solaar/ui/about.py:36 -msgid "" -"Manages Logitech receivers,\n" -"keyboards, mice, and tablets." -msgstr "" -"Zarządza odbiornikami, klawiaturami,\n" -"myszami i tabletami firmy Logitech." - -#: lib/solaar/ui/about.py:44 -msgid "Additional Programming" -msgstr "Dodatkowe programowanie" - -#: lib/solaar/ui/about.py:45 -msgid "GUI design" -msgstr "Projekt GUI" - -#: lib/solaar/ui/about.py:47 -msgid "Testing" -msgstr "Testowanie" - -#: lib/solaar/ui/about.py:54 -msgid "Logitech documentation" -msgstr "Dokumentacja firmy Logitech" - -#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 -#: lib/solaar/ui/window.py:197 -msgid "Unpair" -msgstr "Usuń sparowanie" - -#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 -msgid "Cancel" -msgstr "Anuluj" - -#: lib/solaar/ui/config_panel.py:212 +#: lib/solaar/ui/config_panel.py:245 msgid "Complete - ENTER to change" msgstr "Kompletne - ENTER, aby zmienić" -#: lib/solaar/ui/config_panel.py:212 +#: lib/solaar/ui/config_panel.py:245 msgid "Incomplete" msgstr "Niekompletne" -#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 +#: lib/solaar/ui/config_panel.py:491 lib/solaar/ui/config_panel.py:543 #, python-format msgid "%d value" msgid_plural "%d values" @@ -1091,597 +1341,388 @@ msgstr[0] "%d wartość" msgstr[1] "%d wartości" msgstr[2] "%d wartości" -#: lib/solaar/ui/config_panel.py:518 +#: lib/solaar/ui/config_panel.py:642 msgid "Changes allowed" msgstr "Zmiany dozwolone" -#: lib/solaar/ui/config_panel.py:519 +#: lib/solaar/ui/config_panel.py:643 msgid "No changes allowed" msgstr "Zmiany niedozwolone" -#: lib/solaar/ui/config_panel.py:520 +#: lib/solaar/ui/config_panel.py:644 msgid "Ignore this setting" msgstr "Ignoruj to ustawienie" -#: lib/solaar/ui/config_panel.py:565 +#: lib/solaar/ui/config_panel.py:690 msgid "Working" msgstr "Pracuję" -#: lib/solaar/ui/config_panel.py:568 +#: lib/solaar/ui/config_panel.py:693 msgid "Read/write operation failed." msgstr "Operacja odczytu/zapisu nie powiodła się." -#: lib/solaar/ui/diversion_rules.py:65 +#: lib/solaar/ui/desktop_notifications.py:112 +msgid "unspecified reason" +msgstr "nieokreślony powód" + +#: lib/solaar/ui/diversion_rules.py:103 msgid "Built-in rules" msgstr "Wbudowane reguły" -#: lib/solaar/ui/diversion_rules.py:65 +#: lib/solaar/ui/diversion_rules.py:103 msgid "User-defined rules" msgstr "Reguły zdefiniowane przez użytkownika" -#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1082 +#: lib/solaar/ui/diversion_rules.py:105 lib/solaar/ui/diversion_rules.py:1126 msgid "Rule" msgstr "Reguła" -#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 -#: lib/solaar/ui/diversion_rules.py:635 +#: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:382 +#: lib/solaar/ui/diversion_rules.py:509 msgid "Sub-rule" msgstr "Reguła podrzędna" -#: lib/solaar/ui/diversion_rules.py:70 +#: lib/solaar/ui/diversion_rules.py:108 msgid "[empty]" msgstr "[pusta]" -#: lib/solaar/ui/diversion_rules.py:94 -msgid "Solaar Rule Editor" -msgstr "Edytor reguł Solaar" - -#: lib/solaar/ui/diversion_rules.py:141 +#: lib/solaar/ui/diversion_rules.py:132 msgid "Make changes permanent?" msgstr "Wprowadzić zmiany na stałe?" -#: lib/solaar/ui/diversion_rules.py:146 +#: lib/solaar/ui/diversion_rules.py:137 msgid "Yes" msgstr "Tak" -#: lib/solaar/ui/diversion_rules.py:148 +#: lib/solaar/ui/diversion_rules.py:139 msgid "No" msgstr "Nie" -#: lib/solaar/ui/diversion_rules.py:153 +#: lib/solaar/ui/diversion_rules.py:144 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "Jeśli wybierzesz Nie, zmiany zostaną utracone po zamknięciu Solaar." -#: lib/solaar/ui/diversion_rules.py:201 -msgid "Save changes" -msgstr "Zapisz zmiany" - -#: lib/solaar/ui/diversion_rules.py:206 -msgid "Discard changes" -msgstr "Odrzuć zmiany" - -#: lib/solaar/ui/diversion_rules.py:372 -msgid "Insert here" -msgstr "Wstaw tutaj" - -#: lib/solaar/ui/diversion_rules.py:374 -msgid "Insert above" -msgstr "Wstaw powyżej" - -#: lib/solaar/ui/diversion_rules.py:376 -msgid "Insert below" -msgstr "Wstaw poniżej" - -#: lib/solaar/ui/diversion_rules.py:382 -msgid "Insert new rule here" -msgstr "Wstaw nową regułę tutaj" - -#: lib/solaar/ui/diversion_rules.py:384 -msgid "Insert new rule above" -msgstr "Wstaw nową regułę powyżej" - -#: lib/solaar/ui/diversion_rules.py:386 -msgid "Insert new rule below" -msgstr "Wstaw nową regułę poniżej" - -#: lib/solaar/ui/diversion_rules.py:427 +#: lib/solaar/ui/diversion_rules.py:273 msgid "Paste here" msgstr "Wklej tutaj" -#: lib/solaar/ui/diversion_rules.py:429 +#: lib/solaar/ui/diversion_rules.py:275 msgid "Paste above" msgstr "Wklej powyżej" -#: lib/solaar/ui/diversion_rules.py:431 +#: lib/solaar/ui/diversion_rules.py:277 msgid "Paste below" msgstr "Wklej poniżej" -#: lib/solaar/ui/diversion_rules.py:437 +#: lib/solaar/ui/diversion_rules.py:283 msgid "Paste rule here" msgstr "Wklej regułę tutaj" -#: lib/solaar/ui/diversion_rules.py:439 +#: lib/solaar/ui/diversion_rules.py:285 msgid "Paste rule above" msgstr "Wklej regułę powyżej" -#: lib/solaar/ui/diversion_rules.py:441 +#: lib/solaar/ui/diversion_rules.py:287 msgid "Paste rule below" msgstr "Wklej regułę poniżej" -#: lib/solaar/ui/diversion_rules.py:445 +#: lib/solaar/ui/diversion_rules.py:291 msgid "Paste rule" msgstr "Wklej regułę" -#: lib/solaar/ui/diversion_rules.py:474 +#: lib/solaar/ui/diversion_rules.py:306 +msgid "Insert here" +msgstr "Wstaw tutaj" + +#: lib/solaar/ui/diversion_rules.py:308 +msgid "Insert above" +msgstr "Wstaw powyżej" + +#: lib/solaar/ui/diversion_rules.py:310 +msgid "Insert below" +msgstr "Wstaw poniżej" + +#: lib/solaar/ui/diversion_rules.py:316 +msgid "Insert new rule here" +msgstr "Wstaw nową regułę tutaj" + +#: lib/solaar/ui/diversion_rules.py:318 +msgid "Insert new rule above" +msgstr "Wstaw nową regułę powyżej" + +#: lib/solaar/ui/diversion_rules.py:320 +msgid "Insert new rule below" +msgstr "Wstaw nową regułę poniżej" + +#: lib/solaar/ui/diversion_rules.py:347 msgid "Flatten" msgstr "Spłaszcz" -#: lib/solaar/ui/diversion_rules.py:507 +#: lib/solaar/ui/diversion_rules.py:380 msgid "Insert" msgstr "Wstaw" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:637 -#: lib/solaar/ui/diversion_rules.py:1125 +#: lib/solaar/ui/diversion_rules.py:383 lib/solaar/ui/diversion_rules.py:511 +#: lib/solaar/ui/diversion_rules.py:1158 msgid "Or" msgstr "Lub" -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:636 -#: lib/solaar/ui/diversion_rules.py:1110 +#: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:510 +#: lib/solaar/ui/diversion_rules.py:1144 msgid "And" msgstr "Oraz" -#: lib/solaar/ui/diversion_rules.py:513 +#: lib/solaar/ui/diversion_rules.py:386 msgid "Condition" msgstr "Warunek" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1291 +#: lib/solaar/ui/diversion_rules.py:388 lib/solaar/ui/rule_conditions.py:154 msgid "Feature" msgstr "Funkcja" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1327 +#: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:188 msgid "Report" msgstr "Zgłoszenie" -#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1203 +#: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:69 msgid "Process" msgstr "Proces" -#: lib/solaar/ui/diversion_rules.py:518 +#: lib/solaar/ui/diversion_rules.py:391 msgid "Mouse process" msgstr "Proces myszy" -#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1365 +#: lib/solaar/ui/diversion_rules.py:392 lib/solaar/ui/rule_conditions.py:225 msgid "Modifiers" msgstr "Modyfikatory" -#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1418 +#: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:277 msgid "Key" msgstr "Klawisz" -#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1460 +#: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:318 msgid "KeyIsDown" msgstr "KlawiszJestWciśnięty" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2249 +#: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/diversion_rules.py:1432 msgid "Active" msgstr "Aktywny" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2207 -#: lib/solaar/ui/diversion_rules.py:2259 lib/solaar/ui/diversion_rules.py:2281 +#: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1390 +#: lib/solaar/ui/diversion_rules.py:1441 lib/solaar/ui/diversion_rules.py:1487 msgid "Device" msgstr "Urządzenie" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +#: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1467 +msgid "Host" +msgstr "Host" + +#: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1506 msgid "Setting" msgstr "Ustawienie" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1476 -#: lib/solaar/ui/diversion_rules.py:1525 +#: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/rule_conditions.py:333 +#: lib/solaar/ui/rule_conditions.py:382 msgid "Test" msgstr "Test" -#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1642 +#: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:507 msgid "Test bytes" msgstr "Bajty testowe" -#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1735 +#: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:608 msgid "Mouse Gesture" msgstr "Gest myszy" -#: lib/solaar/ui/diversion_rules.py:531 +#: lib/solaar/ui/diversion_rules.py:405 msgid "Action" msgstr "Czynność" -#: lib/solaar/ui/diversion_rules.py:533 lib/solaar/ui/diversion_rules.py:1844 +#: lib/solaar/ui/diversion_rules.py:407 lib/solaar/ui/rule_actions.py:138 msgid "Key press" msgstr "Naciśnięcie klawisza" -#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1897 +#: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:191 msgid "Mouse scroll" msgstr "Przewijanie myszą" -#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1948 +#: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:256 msgid "Mouse click" msgstr "Kliknięcie myszą" -#: lib/solaar/ui/diversion_rules.py:536 +#: lib/solaar/ui/diversion_rules.py:410 msgid "Set" msgstr "Ustaw" -#: lib/solaar/ui/diversion_rules.py:537 lib/solaar/ui/diversion_rules.py:2019 +#: lib/solaar/ui/diversion_rules.py:411 lib/solaar/ui/rule_actions.py:328 msgid "Execute" msgstr "Wykonaj" -#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:1157 +#: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/diversion_rules.py:1189 msgid "Later" msgstr "Później" -#: lib/solaar/ui/diversion_rules.py:567 +#: lib/solaar/ui/diversion_rules.py:441 msgid "Insert new rule" msgstr "Wstaw nową regułę" -#: lib/solaar/ui/diversion_rules.py:587 lib/solaar/ui/diversion_rules.py:1685 -#: lib/solaar/ui/diversion_rules.py:1789 lib/solaar/ui/diversion_rules.py:1978 +#: lib/solaar/ui/diversion_rules.py:461 lib/solaar/ui/rule_actions.py:82 +#: lib/solaar/ui/rule_actions.py:287 lib/solaar/ui/rule_conditions.py:556 msgid "Delete" msgstr "Usuń" -#: lib/solaar/ui/diversion_rules.py:609 +#: lib/solaar/ui/diversion_rules.py:483 msgid "Negate" msgstr "Neguj" -#: lib/solaar/ui/diversion_rules.py:633 +#: lib/solaar/ui/diversion_rules.py:507 msgid "Wrap with" msgstr "Obwiąż" -#: lib/solaar/ui/diversion_rules.py:655 +#: lib/solaar/ui/diversion_rules.py:537 msgid "Cut" msgstr "Wytnij" -#: lib/solaar/ui/diversion_rules.py:670 +#: lib/solaar/ui/diversion_rules.py:553 msgid "Paste" msgstr "Wklej" -#: lib/solaar/ui/diversion_rules.py:682 +#: lib/solaar/ui/diversion_rules.py:559 msgid "Copy" msgstr "Skopiuj" -#: lib/solaar/ui/diversion_rules.py:1062 +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Solaar Rule Editor" +msgstr "Edytor reguł Solaar" + +#: lib/solaar/ui/diversion_rules.py:668 +msgid "Save changes" +msgstr "Zapisz zmiany" + +#: lib/solaar/ui/diversion_rules.py:673 +msgid "Discard changes" +msgstr "Odrzuć zmiany" + +#: lib/solaar/ui/diversion_rules.py:1104 msgid "This editor does not support the selected rule component yet." msgstr "Ten edytor nie obsługuje jeszcze wybranego składnika reguły." -#: lib/solaar/ui/diversion_rules.py:1137 -msgid "Number of seconds to delay." -msgstr "Liczba sekund do opóźnienia." +#: lib/solaar/ui/diversion_rules.py:1169 +msgid "" +"Number of seconds to delay. Delay between 0 and 1 is done with higher " +"precision." +msgstr "" +"Liczba sekund opóźnienia. Opóźnienie pomiędzy 0 i 1 odbywa się z większą " +"precyzją." -#: lib/solaar/ui/diversion_rules.py:1176 +#: lib/solaar/ui/diversion_rules.py:1207 msgid "Not" msgstr "Nie" -#: lib/solaar/ui/diversion_rules.py:1186 -msgid "X11 active process. For use in X11 only." -msgstr "Aktywny proces X11. Do użytku tylko w X11." - -#: lib/solaar/ui/diversion_rules.py:1217 -msgid "X11 mouse process. For use in X11 only." -msgstr "Proces myszy X11. Do użytku tylko w X11." - -#: lib/solaar/ui/diversion_rules.py:1234 -msgid "MouseProcess" -msgstr "ProcesMyszy" - -#: lib/solaar/ui/diversion_rules.py:1259 -msgid "Feature name of notification triggering rule processing." -msgstr "Nazwa funkcji powiadomienia wyzwalająca przetwarzanie reguły." - -#: lib/solaar/ui/diversion_rules.py:1307 -msgid "Report number of notification triggering rule processing." -msgstr "Numer zgłoszenia powiadomienia wyzwalającego przetwarzanie reguły." - -#: lib/solaar/ui/diversion_rules.py:1341 -msgid "Active keyboard modifiers. Not always available in Wayland." -msgstr "Aktywne modyfikatory klawiatury. Nie zawsze dostępne w Wayland." - -#: lib/solaar/ui/diversion_rules.py:1382 -msgid "" -"Diverted key or button depressed or released.\n" -"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " -"buttons." -msgstr "" -"Przekierowany klawisz lub przycisk wciśnięto lub zwolniono.\n" -"Użyj ustawień przekierowania klawiszy/przycisków oraz przekierowania " -"klawiszy G, aby przekierować klawisze i przyciski." - -#: lib/solaar/ui/diversion_rules.py:1391 -msgid "Key down" -msgstr "Klawisz w dół" - -#: lib/solaar/ui/diversion_rules.py:1394 -msgid "Key up" -msgstr "Klawisz w górę" - -#: lib/solaar/ui/diversion_rules.py:1435 -msgid "" -"Diverted key or button is currently down.\n" -"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " -"buttons." -msgstr "" -"Przekierowany klawisz lub przycisk jest obecnie wciśnięty.\n" -"Użyj ustawień przekierowania klawiszy/przycisków oraz przekierowania " -"klawiszy G, aby przekierować klawisze i przyciski." - -#: lib/solaar/ui/diversion_rules.py:1474 -msgid "Test condition on notification triggering rule processing." -msgstr "Warunek testowy przy powiadomieniu wyzwalający przetwarzanie reguły." - -#: lib/solaar/ui/diversion_rules.py:1478 -msgid "Parameter" -msgstr "Parametr" - -#: lib/solaar/ui/diversion_rules.py:1541 -msgid "begin (inclusive)" -msgstr "rozpocznij (włącznie)" - -#: lib/solaar/ui/diversion_rules.py:1542 -msgid "end (exclusive)" -msgstr "zakończ (wyłącznie)" - -#: lib/solaar/ui/diversion_rules.py:1551 -msgid "range" -msgstr "zakres" - -#: lib/solaar/ui/diversion_rules.py:1553 -msgid "minimum" -msgstr "minimum" - -#: lib/solaar/ui/diversion_rules.py:1554 -msgid "maximum" -msgstr "maksimum" - -#: lib/solaar/ui/diversion_rules.py:1556 -#, python-format -msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" -msgstr "bajty %(0)d do %(1)d, począwszy od %(2)d do %(3)d" - -#: lib/solaar/ui/diversion_rules.py:1561 -msgid "mask" -msgstr "maska" - -#: lib/solaar/ui/diversion_rules.py:1562 -#, python-format -msgid "bytes %(0)d to %(1)d, mask %(2)d" -msgstr "bajty %(0)d do %(1)d, maska %(2)d" - -#: lib/solaar/ui/diversion_rules.py:1572 -msgid "" -"Bit or range test on bytes in notification message triggering rule " -"processing." -msgstr "" -"Test bitowy lub zakresowy na bajtach w komunikacie powiadomienia " -"uruchamiający przetwarzanie reguł." - -#: lib/solaar/ui/diversion_rules.py:1582 -msgid "type" -msgstr "typ" - -#: lib/solaar/ui/diversion_rules.py:1665 -msgid "" -"Mouse gesture with optional initiating button followed by zero or more mouse " -"movements." -msgstr "" -"Gest myszy z opcjonalnym przyciskiem inicjującym, po którym następuje zero " -"lub więcej ruchów myszy." - -#: lib/solaar/ui/diversion_rules.py:1670 -msgid "Add movement" -msgstr "Dodaj ruch" - -#: lib/solaar/ui/diversion_rules.py:1763 -msgid "" -"Simulate a chorded key click or depress or release.\n" -"On Wayland requires write access to /dev/uinput." -msgstr "" -"Symuluj akordowe kliknięcie klawisza, wciśnięcie lub zwolnenie.\n" -"W Wayland wymaga dostępu do zapisu w /dev/uinput." - -#: lib/solaar/ui/diversion_rules.py:1768 -msgid "Add key" -msgstr "Dodaj klawisz" - -#: lib/solaar/ui/diversion_rules.py:1771 -msgid "Click" -msgstr "Kliknij" - -#: lib/solaar/ui/diversion_rules.py:1774 -msgid "Depress" -msgstr "Wciśnij" - -#: lib/solaar/ui/diversion_rules.py:1777 -msgid "Release" -msgstr "Zwolnij" - -#: lib/solaar/ui/diversion_rules.py:1861 -msgid "" -"Simulate a mouse scroll.\n" -"On Wayland requires write access to /dev/uinput." -msgstr "" -"Symuluj przewijanie myszy.\n" -"W Wayland wymaga dostępu do zapisu w /dev/uinput." - -#: lib/solaar/ui/diversion_rules.py:1917 -msgid "" -"Simulate a mouse click.\n" -"On Wayland requires write access to /dev/uinput." -msgstr "" -"Symuluj kliknięcie myszą.\n" -"W Wayland wymaga dostępu do zapisu w /dev/uinput." - -#: lib/solaar/ui/diversion_rules.py:1920 -msgid "Button" -msgstr "Przycisk" - -#: lib/solaar/ui/diversion_rules.py:1921 -msgid "Count" -msgstr "Liczba" - -#: lib/solaar/ui/diversion_rules.py:1961 -msgid "Execute a command with arguments." -msgstr "Wykonaj polecenie z argumentami." - -#: lib/solaar/ui/diversion_rules.py:1964 -msgid "Add argument" -msgstr "Dodaj argument" - -#: lib/solaar/ui/diversion_rules.py:2039 +#: lib/solaar/ui/diversion_rules.py:1238 msgid "Toggle" msgstr "Przełącz" -#: lib/solaar/ui/diversion_rules.py:2039 +#: lib/solaar/ui/diversion_rules.py:1239 msgid "True" msgstr "Prawda" -#: lib/solaar/ui/diversion_rules.py:2040 +#: lib/solaar/ui/diversion_rules.py:1240 msgid "False" msgstr "Fałsz" -#: lib/solaar/ui/diversion_rules.py:2054 +#: lib/solaar/ui/diversion_rules.py:1253 msgid "Unsupported setting" msgstr "Nieobsługiwane ustawienie" -#: lib/solaar/ui/diversion_rules.py:2212 lib/solaar/ui/diversion_rules.py:2231 -#: lib/solaar/ui/diversion_rules.py:2286 lib/solaar/ui/diversion_rules.py:2528 -#: lib/solaar/ui/diversion_rules.py:2546 +#: lib/solaar/ui/diversion_rules.py:1396 lib/solaar/ui/diversion_rules.py:1415 +#: lib/solaar/ui/diversion_rules.py:1493 lib/solaar/ui/diversion_rules.py:1748 +#: lib/solaar/ui/diversion_rules.py:1766 msgid "Originating device" msgstr "Urządzenie inicjujące" -#: lib/solaar/ui/diversion_rules.py:2245 +#: lib/solaar/ui/diversion_rules.py:1428 msgid "Device is active and its settings can be changed." msgstr "Urządzenie jest aktywne i można zmienić jego ustawienia." -#: lib/solaar/ui/diversion_rules.py:2255 -msgid "Device originated the current notification." -msgstr "Urządzenie wysłało bieżące powiadomienie." +#: lib/solaar/ui/diversion_rules.py:1437 +msgid "Device that originated the current notification." +msgstr "Urządzenie, z którego pochodzi bieżące powiadomienie." -#: lib/solaar/ui/diversion_rules.py:2305 +#: lib/solaar/ui/diversion_rules.py:1450 +msgid "Name of host computer." +msgstr "Nazwa komputera hosta." + +#: lib/solaar/ui/diversion_rules.py:1520 msgid "Value" msgstr "Wartość" -#: lib/solaar/ui/diversion_rules.py:2313 +#: lib/solaar/ui/diversion_rules.py:1529 msgid "Item" msgstr "Pozycja" -#: lib/solaar/ui/diversion_rules.py:2588 +#: lib/solaar/ui/diversion_rules.py:1808 msgid "Change setting on device" msgstr "Zmień ustawienie w urządzeniu" -#: lib/solaar/ui/diversion_rules.py:2605 +#: lib/solaar/ui/diversion_rules.py:1824 msgid "Setting on device" msgstr "Ustawienie w urządzeniu" -#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:323 -#: lib/solaar/ui/tray.py:328 lib/solaar/ui/window.py:739 -msgid "offline" -msgstr "wyłączone" - -#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 -#: lib/solaar/ui/pair_window.py:288 +#: lib/solaar/ui/pair_window.py:44 lib/solaar/ui/pair_window.py:167 #, python-format msgid "%(receiver_name)s: pair new device" msgstr "%(receiver_name)s: sparuj nowe urządzenie" -#: lib/solaar/ui/pair_window.py:123 -#, python-format -msgid "Enter passcode on %(name)s." -msgstr "Wprowadź kod dostępu do %(name)s." - -#: lib/solaar/ui/pair_window.py:126 -#, python-format -msgid "Type %(passcode)s and then press the enter key." -msgstr "Wpisz %(passcode)s i naciśnij klawisz Enter." - -#: lib/solaar/ui/pair_window.py:129 -msgid "left" -msgstr "lewo" - -#: lib/solaar/ui/pair_window.py:129 -msgid "right" -msgstr "prawo" - -#: lib/solaar/ui/pair_window.py:131 -#, python-format -msgid "" -"Press %(code)s\n" -"and then press left and right buttons simultaneously." -msgstr "" -"Naciśnij %(code)s\n" -"i przyciśnij jednocześnie lewy i prawy przycisk." - -#: lib/solaar/ui/pair_window.py:188 -msgid "Pairing failed" -msgstr "Sparowanie nie powiodło się" - -#: lib/solaar/ui/pair_window.py:190 -msgid "Make sure your device is within range, and has a decent battery charge." -msgstr "Upewnij się, że urządzenie jest w zasięgu i ma naładowany akumulator." - -#: lib/solaar/ui/pair_window.py:192 -msgid "A new device was detected, but it is not compatible with this receiver." -msgstr "" -"Wykryto nowe urządzenie, jednak nie jest ono kompatybilne z tym odbiornikiem." - -#: lib/solaar/ui/pair_window.py:194 -msgid "More paired devices than receiver can support." -msgstr "Więcej sparowanych urządzeń niż obsługuje odbiornik." - -#: lib/solaar/ui/pair_window.py:196 -msgid "No further details are available about the error." -msgstr "Brak dodatkowych informacji na temat błędu." - -#: lib/solaar/ui/pair_window.py:210 -msgid "Found a new device:" -msgstr "Znaleziono nowe urządzenie:" - -#: lib/solaar/ui/pair_window.py:235 -msgid "The wireless link is not encrypted" -msgstr "Połączenie nie jest szyfrowane" - -#: lib/solaar/ui/pair_window.py:264 -msgid "Unifying receivers are only compatible with Unifying devices." -msgstr "Odbiorniki Unifying są kompatybilne tylko z urządzeniami Unifying." - -#: lib/solaar/ui/pair_window.py:266 +#: lib/solaar/ui/pair_window.py:46 msgid "Bolt receivers are only compatible with Bolt devices." msgstr "Odbiorniki Bolt są kompatybilne tylko z urządzeniami Bolt." -#: lib/solaar/ui/pair_window.py:268 -msgid "Other receivers are only compatible with a few devices." -msgstr "Inne odbiorniki są kompatybilne tylko z kilkoma urządzeniami." - -#: lib/solaar/ui/pair_window.py:270 -msgid "The device must not be paired with a nearby powered-on receiver." -msgstr "" -"Urządzenia nie można parować ze znajdującym się w pobliżu włączonym " -"odbiornikiem." - -#: lib/solaar/ui/pair_window.py:274 +#: lib/solaar/ui/pair_window.py:48 msgid "Press a pairing button or key until the pairing light flashes quickly." msgstr "" "Naciśnij przycisk lub klawisz parowania, aż kontrolka parowania zacznie " "szybko migać." -#: lib/solaar/ui/pair_window.py:276 -msgid "You may have to first turn the device off and on again." -msgstr "" -"Być może trzeba będzie najpierw wyłączyć i ponownie włączyć urządzenie." +#: lib/solaar/ui/pair_window.py:51 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "Odbiorniki Unifying są kompatybilne tylko z urządzeniami Unifying." -#: lib/solaar/ui/pair_window.py:278 -msgid "Turn on the device you want to pair." -msgstr "Włącz urządzenie, które chcesz sparować." +#: lib/solaar/ui/pair_window.py:53 +msgid "Other receivers are only compatible with a few devices." +msgstr "Inne odbiorniki są kompatybilne tylko z kilkoma urządzeniami." -#: lib/solaar/ui/pair_window.py:280 +#: lib/solaar/ui/pair_window.py:55 +msgid "For most devices, turn on the device you want to pair." +msgstr "W przypadku większości urządzeń należy je włączyć do parowania." + +#: lib/solaar/ui/pair_window.py:56 msgid "If the device is already turned on, turn it off and on again." msgstr "Jeśli urządzenie jest już włączone, wyłącz je i włącz ponownie." -#: lib/solaar/ui/pair_window.py:283 +#: lib/solaar/ui/pair_window.py:58 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" +"Urządzenia nie można parować ze znajdującym się w pobliżu włączonym " +"odbiornikiem." + +#: lib/solaar/ui/pair_window.py:61 +msgid "" +"For devices with multiple channels, press, hold, and release the button for " +"the channel you wish to pair\n" +"or use the channel switch button to select a channel and then press, hold, " +"and release the channel switch button." +msgstr "" +"W przypadku urządzeń z wieloma kanałami naciśnij, przytrzymaj i zwolnij " +"przycisk kanału do parowania\n" +"lub użyj przycisku przełączania kanałów, aby wybrać kanał, a następnie " +"naciśnij, przytrzymaj i zwolnij przycisk przełączania kanałów." + +#: lib/solaar/ui/pair_window.py:68 +msgid "The channel indicator light should be blinking rapidly." +msgstr "Kontrolka kanału powinna szybko migać." + +#: lib/solaar/ui/pair_window.py:72 #, python-format msgid "" "\n" @@ -1704,7 +1745,7 @@ msgstr[2] "" "\n" "Ten odbiornik ma jeszcze %d sparowań." -#: lib/solaar/ui/pair_window.py:286 +#: lib/solaar/ui/pair_window.py:78 msgid "" "\n" "Cancelling at this point will not use up a pairing." @@ -1712,119 +1753,344 @@ msgstr "" "\n" "Anulowanie w tym momencie nie spowoduje zużycia sparowania." -#: lib/solaar/ui/tray.py:58 -msgid "No Logitech device found" -msgstr "Nie znaleziono urządzenia Logitech" +#: lib/solaar/ui/pair_window.py:168 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "Wprowadź kod dostępu do %(name)s." -#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#: lib/solaar/ui/pair_window.py:171 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "Wpisz %(passcode)s i naciśnij klawisz Enter." + +#: lib/solaar/ui/pair_window.py:176 +msgid "left" +msgstr "lewo" + +#: lib/solaar/ui/pair_window.py:176 +msgid "right" +msgstr "prawo" + +#: lib/solaar/ui/pair_window.py:178 +#, python-format +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"Naciśnij %(code)s\n" +"i przyciśnij jednocześnie lewy i prawy przycisk." + +#: lib/solaar/ui/pair_window.py:221 +msgid "The wireless link is not encrypted" +msgstr "Połączenie nie jest szyfrowane" + +#: lib/solaar/ui/pair_window.py:226 +msgid "Found a new device:" +msgstr "Znaleziono nowe urządzenie:" + +#: lib/solaar/ui/pair_window.py:247 +msgid "Pairing failed" +msgstr "Sparowanie nie powiodło się" + +#: lib/solaar/ui/pair_window.py:249 +msgid "Make sure your device is within range, and has a decent battery charge." +msgstr "Upewnij się, że urządzenie jest w zasięgu i ma naładowany akumulator." + +#: lib/solaar/ui/pair_window.py:251 +msgid "A new device was detected, but it is not compatible with this receiver." +msgstr "" +"Wykryto nowe urządzenie, jednak nie jest ono kompatybilne z tym odbiornikiem." + +#: lib/solaar/ui/pair_window.py:253 +msgid "More paired devices than receiver can support." +msgstr "Więcej sparowanych urządzeń niż obsługuje odbiornik." + +#: lib/solaar/ui/pair_window.py:255 +msgid "No further details are available about the error." +msgstr "Brak dodatkowych informacji na temat błędu." + +#: lib/solaar/ui/rule_actions.py:54 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Symuluj akordowe kliknięcie klawisza, wciśnięcie lub zwolnenie.\n" +"W Wayland wymaga dostępu do zapisu w /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:60 +msgid "Add key" +msgstr "Dodaj klawisz" + +#: lib/solaar/ui/rule_actions.py:63 +msgid "Click" +msgstr "Kliknij" + +#: lib/solaar/ui/rule_actions.py:66 +msgid "Depress" +msgstr "Wciśnij" + +#: lib/solaar/ui/rule_actions.py:69 +msgid "Release" +msgstr "Zwolnij" + +#: lib/solaar/ui/rule_actions.py:153 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Symuluj przewijanie myszy.\n" +"W Wayland wymaga dostępu do zapisu w /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:211 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Symuluj kliknięcie myszą.\n" +"W Wayland wymaga dostępu do zapisu w /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:216 +msgid "Button" +msgstr "Przycisk" + +#: lib/solaar/ui/rule_actions.py:218 +msgid "Action (and Count, if click)" +msgstr "Czynność (i liczba, jeśli kliknięcie)" + +#: lib/solaar/ui/rule_actions.py:269 +msgid "Execute a command with arguments." +msgstr "Wykonaj polecenie z argumentami." + +#: lib/solaar/ui/rule_actions.py:273 +msgid "Add argument" +msgstr "Dodaj argument" + +#: lib/solaar/ui/rule_conditions.py:52 +msgid "X11 active process. For use in X11 only." +msgstr "Aktywny proces X11. Do użytku tylko w X11." + +#: lib/solaar/ui/rule_conditions.py:82 +msgid "X11 mouse process. For use in X11 only." +msgstr "Proces myszy X11. Do użytku tylko w X11." + +#: lib/solaar/ui/rule_conditions.py:99 +msgid "MouseProcess" +msgstr "ProcesMyszy" + +#: lib/solaar/ui/rule_conditions.py:123 +msgid "Feature name of notification triggering rule processing." +msgstr "Nazwa funkcji powiadomienia wyzwalająca przetwarzanie reguły." + +#: lib/solaar/ui/rule_conditions.py:169 +msgid "Report number of notification triggering rule processing." +msgstr "Numer zgłoszenia powiadomienia wyzwalającego przetwarzanie reguły." + +#: lib/solaar/ui/rule_conditions.py:201 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "Aktywne modyfikatory klawiatury. Nie zawsze dostępne w Wayland." + +#: lib/solaar/ui/rule_conditions.py:241 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"Przekierowany klawisz lub przycisk wciśnięto lub zwolniono.\n" +"Użyj ustawień przekierowania klawiszy/przycisków oraz przekierowania " +"klawiszy G, aby przekierować klawisze i przyciski." + +#: lib/solaar/ui/rule_conditions.py:250 +msgid "Key down" +msgstr "Klawisz w dół" + +#: lib/solaar/ui/rule_conditions.py:253 +msgid "Key up" +msgstr "Klawisz w górę" + +#: lib/solaar/ui/rule_conditions.py:293 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"Przekierowany klawisz lub przycisk jest obecnie wciśnięty.\n" +"Użyj ustawień przekierowania klawiszy/przycisków oraz przekierowania " +"klawiszy G, aby przekierować klawisze i przyciski." + +#: lib/solaar/ui/rule_conditions.py:331 +msgid "Test condition on notification triggering rule processing." +msgstr "Warunek testowy przy powiadomieniu wyzwalający przetwarzanie reguły." + +#: lib/solaar/ui/rule_conditions.py:335 +msgid "Parameter" +msgstr "Parametr" + +#: lib/solaar/ui/rule_conditions.py:408 +msgid "begin (inclusive)" +msgstr "rozpocznij (włącznie)" + +#: lib/solaar/ui/rule_conditions.py:409 +msgid "end (exclusive)" +msgstr "zakończ (wyłącznie)" + +#: lib/solaar/ui/rule_conditions.py:417 +msgid "range" +msgstr "zakres" + +#: lib/solaar/ui/rule_conditions.py:420 +msgid "minimum" +msgstr "minimum" + +#: lib/solaar/ui/rule_conditions.py:421 +msgid "maximum" +msgstr "maksimum" + +#: lib/solaar/ui/rule_conditions.py:423 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "bajty %(0)d do %(1)d, począwszy od %(2)d do %(3)d" + +#: lib/solaar/ui/rule_conditions.py:426 lib/solaar/ui/rule_conditions.py:427 +msgid "mask" +msgstr "maska" + +#: lib/solaar/ui/rule_conditions.py:428 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "bajty %(0)d do %(1)d, maska %(2)d" + +#: lib/solaar/ui/rule_conditions.py:437 +msgid "" +"Bit or range test on bytes in notification message triggering rule " +"processing." +msgstr "" +"Test bitowy lub zakresowy na bajtach w komunikacie powiadomienia " +"uruchamiający przetwarzanie reguł." + +#: lib/solaar/ui/rule_conditions.py:447 +msgid "type" +msgstr "typ" + +#: lib/solaar/ui/rule_conditions.py:535 +msgid "" +"Mouse gesture with optional initiating button followed by zero or more mouse " +"movements." +msgstr "" +"Gest myszy z opcjonalnym przyciskiem inicjującym, po którym następuje zero " +"lub więcej ruchów myszy." + +#: lib/solaar/ui/rule_conditions.py:541 +msgid "Add movement" +msgstr "Dodaj ruch" + +#: lib/solaar/ui/tray.py:55 +msgid "No supported device found" +msgstr "Nie znaleziono obsługiwanego urządzenia" + +#: lib/solaar/ui/tray.py:60 lib/solaar/ui/window.py:328 #, python-format msgid "About %s" msgstr "O programie %s" -#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 +#: lib/solaar/ui/tray.py:61 lib/solaar/ui/window.py:326 #, python-format msgid "Quit %s" msgstr "Zakończ %s" -#: lib/solaar/ui/tray.py:302 lib/solaar/ui/tray.py:310 +#: lib/solaar/ui/tray.py:273 lib/solaar/ui/tray.py:281 msgid "no receiver" msgstr "brak odbiornika" -#: lib/solaar/ui/tray.py:326 +#: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:299 +msgid "offline" +msgstr "wyłączone" + +#: lib/solaar/ui/tray.py:297 msgid "no status" msgstr "brak statusu" -#: lib/solaar/ui/window.py:96 +#: lib/solaar/ui/window.py:110 msgid "Scanning" msgstr "Skanowanie" -#: lib/solaar/ui/window.py:129 +#: lib/solaar/ui/window.py:141 msgid "Battery" msgstr "Akumulator" -#: lib/solaar/ui/window.py:132 +#: lib/solaar/ui/window.py:144 msgid "Wireless Link" msgstr "Połączenie bezprzewodowe" -#: lib/solaar/ui/window.py:136 +#: lib/solaar/ui/window.py:148 msgid "Lighting" msgstr "Podświetlenie" -#: lib/solaar/ui/window.py:170 +#: lib/solaar/ui/window.py:182 msgid "Show Technical Details" msgstr "Wyświetl szczegóły techniczne" -#: lib/solaar/ui/window.py:186 +#: lib/solaar/ui/window.py:198 msgid "Pair new device" msgstr "Sparuj nowe urządzenie" -#: lib/solaar/ui/window.py:205 +#: lib/solaar/ui/window.py:216 msgid "Select a device" msgstr "Wybierz urządzenie" -#: lib/solaar/ui/window.py:322 +#: lib/solaar/ui/window.py:331 msgid "Rule Editor" msgstr "Edytor reguł" -#: lib/solaar/ui/window.py:533 +#: lib/solaar/ui/window.py:522 msgid "Path" msgstr "Ścieżka" -#: lib/solaar/ui/window.py:536 +#: lib/solaar/ui/window.py:524 msgid "USB ID" msgstr "ID USB" -#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 -#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 +#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 msgid "Serial" msgstr "Nr seryjny" -#: lib/solaar/ui/window.py:545 +#: lib/solaar/ui/window.py:533 msgid "Index" msgstr "Indeks" -#: lib/solaar/ui/window.py:547 +#: lib/solaar/ui/window.py:535 msgid "Wireless PID" msgstr "PID bezprz." -#: lib/solaar/ui/window.py:549 +#: lib/solaar/ui/window.py:537 msgid "Product ID" msgstr "ID produktu" -#: lib/solaar/ui/window.py:551 +#: lib/solaar/ui/window.py:539 msgid "Protocol" msgstr "Protokół" -#: lib/solaar/ui/window.py:551 +#: lib/solaar/ui/window.py:539 msgid "Unknown" msgstr "Nieznany" -#: lib/solaar/ui/window.py:554 -#, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d ms (%(rate_hz)dHz)" - -#: lib/solaar/ui/window.py:554 +#: lib/solaar/ui/window.py:541 msgid "Polling rate" msgstr "Odpytywanie" -#: lib/solaar/ui/window.py:565 +#: lib/solaar/ui/window.py:548 msgid "Unit ID" msgstr "ID jednostki" -#: lib/solaar/ui/window.py:576 -msgid "none" -msgstr "brak" - -#: lib/solaar/ui/window.py:577 +#: lib/solaar/ui/window.py:560 msgid "Notifications" msgstr "Powiadomienia" -#: lib/solaar/ui/window.py:621 +#: lib/solaar/ui/window.py:604 msgid "No device paired." msgstr "Brak sparowanych urządzeń." -#: lib/solaar/ui/window.py:628 +#: lib/solaar/ui/window.py:613 #, python-format msgid "Up to %(max_count)s device can be paired to this receiver." msgid_plural "Up to %(max_count)s devices can be paired to this receiver." @@ -1835,11 +2101,7 @@ msgstr[1] "" msgstr[2] "" "Można sparować maksymalnie %(max_count)s urządzeń z tym odbiornikiem." -#: lib/solaar/ui/window.py:634 -msgid "Only one device can be paired to this receiver." -msgstr "Tylko jedno urządzenie może być sparowane z tym odbiornikiem." - -#: lib/solaar/ui/window.py:638 +#: lib/solaar/ui/window.py:624 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." @@ -1847,49 +2109,45 @@ msgstr[0] "Ten odbiornik ma jeszcze %d sparowanie." msgstr[1] "Ten odbiornik ma jeszcze %d sparowania." msgstr[2] "Ten odbiornik ma jeszcze %d sparowań." -#: lib/solaar/ui/window.py:692 +#: lib/solaar/ui/window.py:681 msgid "Battery Voltage" msgstr "Napięcie akumulatora" -#: lib/solaar/ui/window.py:694 +#: lib/solaar/ui/window.py:683 msgid "Voltage reported by battery" msgstr "Napięcie zgłoszone przez akumulator" -#: lib/solaar/ui/window.py:696 +#: lib/solaar/ui/window.py:685 msgid "Battery Level" msgstr "Poziom akumulatora" -#: lib/solaar/ui/window.py:698 +#: lib/solaar/ui/window.py:687 msgid "Approximate level reported by battery" msgstr "Przybliżony poziom zgłoszony przez akumulator" -#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 +#: lib/solaar/ui/window.py:694 lib/solaar/ui/window.py:696 msgid "next reported " msgstr "następny zgłoszony " -#: lib/solaar/ui/window.py:708 +#: lib/solaar/ui/window.py:697 msgid " and next level to be reported." msgstr " oraz następny poziom do zgłoszenia." #: lib/solaar/ui/window.py:713 -msgid "last known" -msgstr "ostatni znany" - -#: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "szyfrowane" -#: lib/solaar/ui/window.py:726 +#: lib/solaar/ui/window.py:715 msgid "The wireless link between this device and its receiver is encrypted." msgstr "" "Połączenie bezprzewodowe pomiędzy tym urządzeniem i odbiornikiem jest " "szyfrowane." -#: lib/solaar/ui/window.py:728 +#: lib/solaar/ui/window.py:717 msgid "not encrypted" msgstr "nieszyfrowane" -#: lib/solaar/ui/window.py:732 +#: lib/solaar/ui/window.py:721 msgid "" "The wireless link between this device and its receiver is not encrypted.\n" "This is a security issue for pointing devices, and a major security issue " @@ -1900,7 +2158,7 @@ msgstr "" "Jest to problem dotyczący zabezpieczeń urządzeń wskazujących i poważny " "problem dotyczący zabezpieczeń urządzeń do wprowadzania tekstu." -#: lib/solaar/ui/window.py:748 +#: lib/solaar/ui/window.py:737 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d lx" diff --git a/po/pt.po b/po/pt.po index a080679b..3a5e1813 100644 --- a/po/pt.po +++ b/po/pt.po @@ -6,7 +6,7 @@ msgid "" msgstr "Project-Id-Version: solaar 0.9.2\n" "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2021-09-25 13:56-0400\n" + "POT-Creation-Date: 2023-12-28 17:40+0100\n" "PO-Revision-Date: 2014-09-14 20:57+0100\n" "Last-Translator: Américo Monteiro \n" "Language-Team: Portuguese \n" @@ -17,319 +17,263 @@ msgstr "Project-Id-Version: solaar 0.9.2\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Lokalize 1.4\n" -#: lib/logitech_receiver/base_usb.py:50 +#: lib/logitech_receiver/base_usb.py:46 +msgid "Bolt Receiver" +msgstr "" + +#: lib/logitech_receiver/base_usb.py:57 msgid "Unifying Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:58 lib/logitech_receiver/base_usb.py:68 -#: lib/logitech_receiver/base_usb.py:79 lib/logitech_receiver/base_usb.py:90 -#: lib/logitech_receiver/base_usb.py:101 +#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 +#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 +#: lib/logitech_receiver/base_usb.py:114 msgid "Nano Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:109 +#: lib/logitech_receiver/base_usb.py:124 msgid "Lightspeed Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:117 +#: lib/logitech_receiver/base_usb.py:133 msgid "EX100 Receiver 27 Mhz" msgstr "" -#: lib/logitech_receiver/device.py:133 lib/solaar/ui/window.py:693 -msgid "unknown" -msgstr "desconhecido" - -#: lib/logitech_receiver/i18n.py:38 +#: lib/logitech_receiver/i18n.py:30 msgid "empty" msgstr "vazio" -#: lib/logitech_receiver/i18n.py:39 +#: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "crítico" -#: lib/logitech_receiver/i18n.py:40 +#: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "baixo" -#: lib/logitech_receiver/i18n.py:41 +#: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "" -#: lib/logitech_receiver/i18n.py:42 +#: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "bom" -#: lib/logitech_receiver/i18n.py:43 +#: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "cheio" -#: lib/logitech_receiver/i18n.py:46 +#: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "a descarregar" -#: lib/logitech_receiver/i18n.py:47 +#: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "a carregar" -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:721 +#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "a carregar" -#: lib/logitech_receiver/i18n.py:49 +#: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "" -#: lib/logitech_receiver/i18n.py:50 +#: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "quase cheiro" -#: lib/logitech_receiver/i18n.py:51 +#: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "" -#: lib/logitech_receiver/i18n.py:52 +#: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "carga lenta" -#: lib/logitech_receiver/i18n.py:53 +#: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "bateria inválida" -#: lib/logitech_receiver/i18n.py:54 +#: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "erro térmico" -#: lib/logitech_receiver/i18n.py:55 +#: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "" -#: lib/logitech_receiver/i18n.py:56 +#: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "" -#: lib/logitech_receiver/i18n.py:57 +#: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "" -#: lib/logitech_receiver/i18n.py:58 +#: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "" -#: lib/logitech_receiver/i18n.py:61 +#: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "tempo limite do dispositivo" -#: lib/logitech_receiver/i18n.py:62 +#: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "dispositivo não suportado" -#: lib/logitech_receiver/i18n.py:63 +#: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "demasiados dispositivos" -#: lib/logitech_receiver/i18n.py:64 +#: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "tempo limite da sequência" -#: lib/logitech_receiver/i18n.py:67 lib/solaar/ui/window.py:580 +#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 msgid "Firmware" msgstr "Firmware" -#: lib/logitech_receiver/i18n.py:68 +#: lib/logitech_receiver/i18n.py:60 msgid "Bootloader" msgstr "Gestor de arranque" -#: lib/logitech_receiver/i18n.py:69 +#: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardware" -#: lib/logitech_receiver/i18n.py:70 +#: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Outro" -#: lib/logitech_receiver/i18n.py:73 +#: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:74 +#: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:75 +#: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "" -#: lib/logitech_receiver/i18n.py:76 +#: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "" -#: lib/logitech_receiver/i18n.py:77 +#: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "" -#: lib/logitech_receiver/i18n.py:78 +#: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "" -#: lib/logitech_receiver/i18n.py:79 +#: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "" -#: lib/logitech_receiver/i18n.py:80 +#: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "" -#: lib/logitech_receiver/i18n.py:81 +#: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "" -#: lib/logitech_receiver/i18n.py:82 +#: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "" -#: lib/logitech_receiver/i18n.py:83 +#: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "" -#: lib/logitech_receiver/i18n.py:84 +#: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "" -#: lib/logitech_receiver/i18n.py:85 +#: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "" -#: lib/logitech_receiver/i18n.py:86 +#: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "" -#: lib/logitech_receiver/i18n.py:87 +#: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "" -#: lib/logitech_receiver/i18n.py:88 +#: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "" -#: lib/logitech_receiver/i18n.py:89 +#: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:90 +#: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:93 +#: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "" -#: lib/logitech_receiver/i18n.py:94 +#: lib/logitech_receiver/i18n.py:86 msgid "released" msgstr "" -#: lib/logitech_receiver/notifications.py:76 +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is closed" msgstr "" -#: lib/logitech_receiver/notifications.py:76 +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is open" msgstr "" -#: lib/logitech_receiver/notifications.py:160 lib/solaar/ui/notify.py:123 +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is closed" +msgstr "" + +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is open" +msgstr "" + +#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 msgid "connected" msgstr "ligado" -#: lib/logitech_receiver/notifications.py:160 +#: lib/logitech_receiver/notifications.py:224 msgid "disconnected" msgstr "" -#: lib/logitech_receiver/notifications.py:198 lib/solaar/ui/notify.py:121 +#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "desemparelhado" -#: lib/logitech_receiver/notifications.py:248 +#: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "com energia ligada" -#: lib/logitech_receiver/settings.py:526 +#: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "" -#: lib/logitech_receiver/settings.py:542 lib/logitech_receiver/settings.py:568 +#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 msgid "feature" msgstr "" -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Hand Detection" -msgstr "Detecção de Mão" - -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "Liga a iluminação quando as mãos estão sobre o teclado." - -#: lib/logitech_receiver/settings_templates.py:72 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:73 -#: lib/logitech_receiver/settings_templates.py:78 -#: lib/logitech_receiver/settings_templates.py:85 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "Modo de alta-sensibilidade para deslocamento vertical com a roda." - -#: lib/logitech_receiver/settings_templates.py:74 -msgid "Side Scrolling" -msgstr "Deslocamento Lateral" - -#: lib/logitech_receiver/settings_templates.py:75 -msgid "When disabled, pushing the wheel sideways sends custom button " - "events\n" - "instead of the standard side-scrolling events." -msgstr "Quando desactivado, premir a roda para os lados envia eventos de " - "botões personalizados\n" - "em vez dos eventos de deslocamento lateral standard." - -#: lib/logitech_receiver/settings_templates.py:77 -msgid "Scroll Wheel High Resolution" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:79 -msgid "Scroll Wheel Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:80 -msgid "HID++ mode for vertical scroll with the wheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:81 -msgid "Effectively turns off wheel scrolling in Linux." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:82 -msgid "Scroll Wheel Direction" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:83 -msgid "Invert direction for vertical scroll with wheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:84 -msgid "Scroll Wheel Resolution" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Frequency of device polling, in milliseconds" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Polling Rate (ms)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:87 +#: lib/logitech_receiver/settings_templates.py:139 msgid "Swap Fx function" msgstr "Trocar função Fx" -#: lib/logitech_receiver/settings_templates.py:88 +#: lib/logitech_receiver/settings_templates.py:140 msgid "When set, the F1..F12 keys will activate their special function,\n" "and you must hold the FN key to activate their standard function." msgstr "Quando definido, as teclas F1..F12 irão activar as suas funções " @@ -337,7 +281,7 @@ msgstr "Quando definido, as teclas F1..F12 irão activar as suas funções " "e você tem manter a tecla FN carregada para activar as suas funções " "standard." -#: lib/logitech_receiver/settings_templates.py:90 +#: lib/logitech_receiver/settings_templates.py:142 msgid "When unset, the F1..F12 keys will activate their standard function,\n" "and you must hold the FN key to activate their special function." msgstr "Quando não definido, as teclas F1..F12 irão activar as suas funções " @@ -345,432 +289,646 @@ msgstr "Quando não definido, as teclas F1..F12 irão activar as suas funções "e você tem manter a tecla FN carregada para activar as suas funções " "especiais." -#: lib/logitech_receiver/settings_templates.py:92 +#: lib/logitech_receiver/settings_templates.py:149 +msgid "Hand Detection" +msgstr "Detecção de Mão" + +#: lib/logitech_receiver/settings_templates.py:150 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Liga a iluminação quando as mãos estão sobre o teclado." + +#: lib/logitech_receiver/settings_templates.py:157 +msgid "Scroll Wheel Smooth Scrolling" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:158 +#: lib/logitech_receiver/settings_templates.py:239 +#: lib/logitech_receiver/settings_templates.py:267 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Modo de alta-sensibilidade para deslocamento vertical com a roda." + +#: lib/logitech_receiver/settings_templates.py:165 +msgid "Side Scrolling" +msgstr "Deslocamento Lateral" + +#: lib/logitech_receiver/settings_templates.py:167 +msgid "When disabled, pushing the wheel sideways sends custom button " + "events\n" + "instead of the standard side-scrolling events." +msgstr "Quando desactivado, premir a roda para os lados envia eventos de " + "botões personalizados\n" + "em vez dos eventos de deslocamento lateral standard." + +#: lib/logitech_receiver/settings_templates.py:177 +msgid "Sensitivity (DPI - older mice)" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:178 +#: lib/logitech_receiver/settings_templates.py:712 msgid "Mouse movement sensitivity" msgstr "" -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Sensitivity (DPI)" -msgstr "Sensibilidade (DPI)" - -#: lib/logitech_receiver/settings_templates.py:93 -msgid "Sensitivity (Pointer Speed)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:94 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:95 -msgid "Scroll Wheel Rachet" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:96 -msgid "Automatically switch the mouse wheel between ratchet and freespin " - "mode.\n" - "The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:98 +#: lib/logitech_receiver/settings_templates.py:208 +#: lib/logitech_receiver/settings_templates.py:218 +#: lib/logitech_receiver/settings_templates.py:225 msgid "Backlight" msgstr "" -#: lib/logitech_receiver/settings_templates.py:98 +#: lib/logitech_receiver/settings_templates.py:209 +#: lib/logitech_receiver/settings_templates.py:226 +msgid "Set illumination time for keyboard." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:219 msgid "Turn illumination on or off on keyboard." msgstr "" -#: lib/logitech_receiver/settings_templates.py:99 -msgid "Key/Button Actions" +#: lib/logitech_receiver/settings_templates.py:237 +msgid "Scroll Wheel High Resolution" msgstr "" -#: lib/logitech_receiver/settings_templates.py:100 -msgid "Change the action for the key or button." +#: lib/logitech_receiver/settings_templates.py:240 +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Set to ignore if scrolling is abnormally fast or slow" msgstr "" -#: lib/logitech_receiver/settings_templates.py:101 -msgid "Changing important actions (such as for the left mouse button) can " - "result in an unusable system." +#: lib/logitech_receiver/settings_templates.py:247 +#: lib/logitech_receiver/settings_templates.py:277 +msgid "Scroll Wheel Diversion" msgstr "" -#: lib/logitech_receiver/settings_templates.py:102 -msgid "Key/Button Diversion" +#: lib/logitech_receiver/settings_templates.py:249 +msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:103 -msgid "Make the key or button send HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Scroll Wheel Direction" msgstr "" -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable keys" +#: lib/logitech_receiver/settings_templates.py:257 +msgid "Invert direction for vertical scroll with wheel." msgstr "" -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable specific keyboard keys." +#: lib/logitech_receiver/settings_templates.py:265 +msgid "Scroll Wheel Resolution" msgstr "" -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Change keys to match OS." +#: lib/logitech_receiver/settings_templates.py:279 +msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Set OS" +#: lib/logitech_receiver/settings_templates.py:288 +msgid "Sensitivity (Pointer Speed)" msgstr "" -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Change Host" +#: lib/logitech_receiver/settings_templates.py:289 +msgid "Speed multiplier for mouse (256 is normal multiplier)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Switch connection to a different host" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:107 +#: lib/logitech_receiver/settings_templates.py:299 msgid "Thumb Wheel Diversion" msgstr "" -#: lib/logitech_receiver/settings_templates.py:108 -msgid "HID++ mode for horizontal scroll with the thumb wheel." +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " + "Solaar rules but are otherwise ignored)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:109 -msgid "Effectively turns off thumb scrolling in Linux." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Invert thumb wheel scroll direction." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:110 +#: lib/logitech_receiver/settings_templates.py:310 msgid "Thumb Wheel Direction" msgstr "" -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Gestures" -msgstr "Gestos" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Tweak the mouse/touchpad behaviour." +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Invert thumb wheel scroll direction." msgstr "" -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Change numerical parameters of a mouse/touchpad." +#: lib/logitech_receiver/settings_templates.py:319 +msgid "Onboard Profiles" msgstr "" -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Gesture params" +#: lib/logitech_receiver/settings_templates.py:320 +msgid "Enable onboard profiles, which often control report rate and " + "keyboard lighting" msgstr "" -#: lib/logitech_receiver/settings_templates.py:113 -msgid "DPI Sliding Adjustment" +#: lib/logitech_receiver/settings_templates.py:330 +msgid "Polling Rate (ms)" msgstr "" -#: lib/logitech_receiver/settings_templates.py:114 -msgid "Adjust the DPI by sliding the mouse horizontally while holding the " - "button down." +#: lib/logitech_receiver/settings_templates.py:332 +msgid "Frequency of device polling, in milliseconds" msgstr "" -#: lib/logitech_receiver/settings_templates.py:115 -msgid "Mouse Gestures" +#: lib/logitech_receiver/settings_templates.py:333 +#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "May need Onboard Profiles set to Disable to be effective." msgstr "" -#: lib/logitech_receiver/settings_templates.py:116 -msgid "Send a gesture by sliding the mouse while holding the button down." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:117 +#: lib/logitech_receiver/settings_templates.py:365 msgid "Divert crown events" msgstr "" -#: lib/logitech_receiver/settings_templates.py:118 +#: lib/logitech_receiver/settings_templates.py:366 msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:119 +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:375 +msgid "Set crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:383 msgid "Divert G Keys" msgstr "" -#: lib/logitech_receiver/settings_templates.py:120 +#: lib/logitech_receiver/settings_templates.py:385 msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Performs a left click." +#: lib/logitech_receiver/settings_templates.py:386 +msgid "May also make M keys and MR key send HID++ notifications" msgstr "" -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Single tap" +#: lib/logitech_receiver/settings_templates.py:402 +msgid "Scroll Wheel Ratcheted" msgstr "" -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Performs a right click." +#: lib/logitech_receiver/settings_templates.py:403 +msgid "Switch the mouse wheel between speed-controlled ratcheting and " + "always freespin." msgstr "" -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Single tap with two fingers" +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Freespinning" msgstr "" -#: lib/logitech_receiver/settings_templates.py:125 -msgid "Single tap with three fingers" +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Ratcheted" msgstr "" -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Double tap" +#: lib/logitech_receiver/settings_templates.py:412 +msgid "Scroll Wheel Ratchet Speed" msgstr "" -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Performs a double click." +#: lib/logitech_receiver/settings_templates.py:414 +msgid "Use the mouse wheel speed to switch between ratcheted and " + "freespinning.\n" + "The mouse wheel is always ratcheted at 50." msgstr "" -#: lib/logitech_receiver/settings_templates.py:130 -msgid "Double tap with two fingers" +#: lib/logitech_receiver/settings_templates.py:463 +msgid "Key/Button Actions" msgstr "" -#: lib/logitech_receiver/settings_templates.py:131 -msgid "Double tap with three fingers" +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Change the action for the key or button." msgstr "" -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Drags items by dragging the finger after double tapping." +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Overridden by diversion." msgstr "" -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Tap and drag" +#: lib/logitech_receiver/settings_templates.py:466 +msgid "Changing important actions (such as for the left mouse button) can " + "result in an unusable system." msgstr "" -#: lib/logitech_receiver/settings_templates.py:135 -msgid "Tap and drag with two fingers" +#: lib/logitech_receiver/settings_templates.py:639 +msgid "Key/Button Diversion" msgstr "" -#: lib/logitech_receiver/settings_templates.py:136 -msgid "Drags items by dragging the fingers after double tapping." +#: lib/logitech_receiver/settings_templates.py:640 +msgid "Make the key or button send HID++ notifications (Diverted) or " + "initiate Mouse Gestures or Sliding DPI" msgstr "" -#: lib/logitech_receiver/settings_templates.py:137 -msgid "Tap and drag with three fingers" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Diverted" msgstr "" -#: lib/logitech_receiver/settings_templates.py:139 -msgid "Suppress tap and edge gestures" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +msgid "Mouse Gestures" msgstr "" -#: lib/logitech_receiver/settings_templates.py:140 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Regular" msgstr "" -#: lib/logitech_receiver/settings_templates.py:141 -msgid "Scroll with one finger" +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Sliding DPI" msgstr "" -#: lib/logitech_receiver/settings_templates.py:141 -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scrolls." +#: lib/logitech_receiver/settings_templates.py:711 +msgid "Sensitivity (DPI)" +msgstr "Sensibilidade (DPI)" + +#: lib/logitech_receiver/settings_templates.py:752 +msgid "Sensitivity Switching" msgstr "" -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scroll with two fingers" +#: lib/logitech_receiver/settings_templates.py:754 +msgid "Switch the current sensitivity and the remembered sensitivity when " + "the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current " + "sensitivity" msgstr "" -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scroll horizontally with two fingers" +#: lib/logitech_receiver/settings_templates.py:758 +msgid "Off" msgstr "" -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scrolls horizontally." +#: lib/logitech_receiver/settings_templates.py:791 +msgid "Disable keys" msgstr "" -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scroll vertically with two fingers" +#: lib/logitech_receiver/settings_templates.py:792 +msgid "Disable specific keyboard keys." msgstr "" -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scrolls vertically." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Inverts the scrolling direction." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Natural scrolling" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Enables the thumbwheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Thumbwheel" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:162 -msgid "Swipe from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:159 -msgid "Swipe from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:160 -msgid "Swipe from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:161 -msgid "Swipe from the bottom edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:163 -msgid "Swipe two fingers from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:164 -msgid "Swipe two fingers from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:165 -msgid "Swipe two fingers from the bottom edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:166 -msgid "Swipe two fingers from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:167 -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:167 -msgid "Zoom with two fingers." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:168 -msgid "Pinch to zoom out." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:169 -msgid "Spread to zoom in." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:170 -msgid "Zoom with three fingers." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Zoom with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:189 -msgid "Pixel zone" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:190 -msgid "Ratio zone" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Scale factor" -msgstr "Fator de escala" - -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Sets the cursor speed." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left-most coordinate." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:196 -msgid "Top-most coordinate." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:196 -msgid "top" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:197 -msgid "Width." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:197 -msgid "width" -msgstr "largura" - -#: lib/logitech_receiver/settings_templates.py:198 -msgid "Height." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:198 -msgid "height" -msgstr "altura" - -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Cursor speed." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Scale" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:202 +#: lib/logitech_receiver/settings_templates.py:795 #, python-format msgid "Disables the %s key." msgstr "" -#: lib/logitech_receiver/settings_templates.py:521 -#: lib/logitech_receiver/settings_templates.py:573 -msgid "Off" +#: lib/logitech_receiver/settings_templates.py:809 +#: lib/logitech_receiver/settings_templates.py:860 +msgid "Set OS" msgstr "" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Diverted" +#: lib/logitech_receiver/settings_templates.py:810 +#: lib/logitech_receiver/settings_templates.py:861 +msgid "Change keys to match OS." msgstr "" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Regular" +#: lib/logitech_receiver/settings_templates.py:873 +msgid "Change Host" msgstr "" -#: lib/logitech_receiver/status.py:109 +#: lib/logitech_receiver/settings_templates.py:874 +msgid "Switch connection to a different host" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Performs a left click." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Single tap" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Performs a right click." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Single tap with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:902 +msgid "Single tap with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Double tap" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Performs a double click." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:907 +msgid "Double tap with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:908 +msgid "Double tap with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Drags items by dragging the finger after double tapping." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Tap and drag" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Tap and drag with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:914 +msgid "Tap and drag with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Suppress tap and edge gestures" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:918 +msgid "Scroll with one finger" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scrolls." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scroll with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scroll horizontally with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scrolls horizontally." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scroll vertically with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scrolls vertically." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Inverts the scrolling direction." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Natural scrolling" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Enables the thumbwheel." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Thumbwheel" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:935 +#: lib/logitech_receiver/settings_templates.py:939 +msgid "Swipe from the top edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:936 +msgid "Swipe from the left edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:937 +msgid "Swipe from the right edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:938 +msgid "Swipe from the bottom edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:940 +msgid "Swipe two fingers from the left edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:941 +msgid "Swipe two fingers from the right edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:942 +msgid "Swipe two fingers from the bottom edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:943 +msgid "Swipe two fingers from the top edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:944 +msgid "Zoom with two fingers." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:945 +msgid "Pinch to zoom out." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:946 +msgid "Spread to zoom in." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:947 +msgid "Zoom with three fingers." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Zoom with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:966 +msgid "Pixel zone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:967 +msgid "Ratio zone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Scale factor" +msgstr "Fator de escala" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Sets the cursor speed." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left-most coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width" +msgstr "Largura" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height" +msgstr "Altura" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Cursor speed." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Scale" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:982 +msgid "Gestures" +msgstr "Gestos" + +#: lib/logitech_receiver/settings_templates.py:983 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1000 +msgid "Gestures Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1001 +msgid "Divert mouse/touchpad gestures." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1018 +msgid "Gesture params" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1019 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1044 +msgid "M-Key LEDs" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Control the M-Key LEDs." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1077 +msgid "May need G Keys diverted to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1053 +#, python-format +msgid "Lights up the %s key." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1074 +msgid "MR-Key LED" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "Control the MR-Key LED." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1095 +msgid "Persistent Key/Button Mapping" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1097 +msgid "Permanently change the mapping for the key or button." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1098 +msgid "Changing important keys or buttons (such as for the left mouse " + "button) can result in an unusable system." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1157 +msgid "Sidetone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1158 +msgid "Set sidetone level." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1167 +msgid "Equalizer" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1168 +msgid "Set equalizer levels." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1191 +msgid "Hz" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1197 +msgid "Power Management" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1198 +msgid "Power off in minutes (0 for never)." +msgstr "" + +#: lib/logitech_receiver/status.py:114 msgid "No paired devices." msgstr "Nenhum dispositivo emparelhado." -#: lib/logitech_receiver/status.py:110 lib/solaar/ui/window.py:623 +#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 #, python-format msgid "%(count)s paired device." msgid_plural "%(count)s paired devices." msgstr[0] "" msgstr[1] "" -#: lib/logitech_receiver/status.py:165 +#: lib/logitech_receiver/status.py:170 #, python-format msgid "Battery: %(level)s" msgstr "" -#: lib/logitech_receiver/status.py:167 +#: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "" -#: lib/logitech_receiver/status.py:179 +#: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "" -#: lib/logitech_receiver/status.py:234 +#: lib/logitech_receiver/status.py:239 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "" -#: lib/logitech_receiver/status.py:236 +#: lib/logitech_receiver/status.py:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "" @@ -781,16 +939,14 @@ msgstr "Erro de permissões" #: lib/solaar/ui/__init__.py:54 #, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open " - "it." -msgstr "Encontrado um Receptor Logitech (%s), mas não teve permissões para o " - "abrir." +msgid "Found a Logitech receiver or device (%s), but did not have " + "permission to open it." +msgstr "" #: lib/solaar/ui/__init__.py:55 -msgid "If you've just installed Solaar, try removing the receiver and " - "plugging it back in." -msgstr "Se você acabou de instalar o Solaar, tente remover o receptor e ligá-" - "lo de novo." +msgid "If you've just installed Solaar, try disconnecting the receiver or " + "device and then reconnecting it." +msgstr "" #: lib/solaar/ui/__init__.py:58 msgid "Cannot connect to device error" @@ -803,8 +959,8 @@ msgid "Found a Logitech receiver or device at %s, but encountered an error " msgstr "" #: lib/solaar/ui/__init__.py:61 -msgid "Try removing the device and plugging it back in or turning it off " - "and then on." +msgid "Try disconnecting the device and then reconnecting it or turning it " + "off and then on." msgstr "" #: lib/solaar/ui/__init__.py:64 @@ -820,354 +976,627 @@ msgstr "" msgid "The receiver returned an error, with no further details." msgstr "O receptor devolveu um erro, sem mais detalhes." -#: lib/solaar/ui/about.py:39 +#: lib/solaar/ui/__init__.py:177 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" + +#: lib/solaar/ui/about.py:36 msgid "Manages Logitech receivers,\n" "keyboards, mice, and tablets." msgstr "" -#: lib/solaar/ui/about.py:47 +#: lib/solaar/ui/about.py:44 msgid "Additional Programming" msgstr "" -#: lib/solaar/ui/about.py:48 +#: lib/solaar/ui/about.py:45 msgid "GUI design" msgstr "Desenho da GUI" -#: lib/solaar/ui/about.py:50 +#: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "A testar" -#: lib/solaar/ui/about.py:57 +#: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Documentação da Logitech" -#: lib/solaar/ui/action.py:70 lib/solaar/ui/window.py:328 -msgid "About %s" -msgstr "Acerca de %s" - -#: lib/solaar/ui/action.py:96 lib/solaar/ui/action.py:100 -#: lib/solaar/ui/window.py:205 +#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 +#: lib/solaar/ui/window.py:197 msgid "Unpair" msgstr "Desemparelhar" -#: lib/solaar/ui/action.py:99 lib/solaar/ui/diversion_rules.py:144 +#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "" -#: lib/solaar/ui/config_panel.py:358 -msgid "Changes allowed" +#: lib/solaar/ui/config_panel.py:212 +msgid "Complete - ENTER to change" msgstr "" -#: lib/solaar/ui/config_panel.py:359 -msgid "No changes allowed" +#: lib/solaar/ui/config_panel.py:212 +msgid "Incomplete" msgstr "" -#: lib/solaar/ui/config_panel.py:360 -msgid "Ignore this setting" -msgstr "" - -#: lib/solaar/ui/config_panel.py:401 -msgid "Working" -msgstr "A funcionar" - -#: lib/solaar/ui/config_panel.py:404 -msgid "Read/write operation failed." -msgstr "Operação de leitura/escrita falhada." - -#: lib/solaar/ui/config_panel.py:522 +#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "" msgstr[1] "" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/config_panel.py:518 +msgid "Changes allowed" +msgstr "" + +#: lib/solaar/ui/config_panel.py:519 +msgid "No changes allowed" +msgstr "" + +#: lib/solaar/ui/config_panel.py:520 +msgid "Ignore this setting" +msgstr "" + +#: lib/solaar/ui/config_panel.py:565 +msgid "Working" +msgstr "A funcionar" + +#: lib/solaar/ui/config_panel.py:568 +msgid "Read/write operation failed." +msgstr "Operação de leitura/escrita falhada." + +#: lib/solaar/ui/diversion_rules.py:65 msgid "Built-in rules" msgstr "" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" msgstr "" -#: lib/solaar/ui/diversion_rules.py:59 lib/solaar/ui/diversion_rules.py:792 +#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:60 lib/solaar/ui/diversion_rules.py:503 -#: lib/solaar/ui/diversion_rules.py:622 +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 +#: lib/solaar/ui/diversion_rules.py:636 msgid "Sub-rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:62 +#: lib/solaar/ui/diversion_rules.py:70 msgid "[empty]" msgstr "" -#: lib/solaar/ui/diversion_rules.py:85 +#: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "" -#: lib/solaar/ui/diversion_rules.py:135 +#: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "" -#: lib/solaar/ui/diversion_rules.py:140 +#: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Sim" -#: lib/solaar/ui/diversion_rules.py:142 +#: lib/solaar/ui/diversion_rules.py:148 msgid "No" msgstr "" -#: lib/solaar/ui/diversion_rules.py:147 +#: lib/solaar/ui/diversion_rules.py:153 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "" -#: lib/solaar/ui/diversion_rules.py:195 +#: lib/solaar/ui/diversion_rules.py:201 msgid "Save changes" msgstr "" -#: lib/solaar/ui/diversion_rules.py:200 +#: lib/solaar/ui/diversion_rules.py:206 msgid "Discard changes" msgstr "" -#: lib/solaar/ui/diversion_rules.py:366 +#: lib/solaar/ui/diversion_rules.py:372 msgid "Insert here" msgstr "" -#: lib/solaar/ui/diversion_rules.py:368 +#: lib/solaar/ui/diversion_rules.py:374 msgid "Insert above" msgstr "" -#: lib/solaar/ui/diversion_rules.py:370 +#: lib/solaar/ui/diversion_rules.py:376 msgid "Insert below" msgstr "" -#: lib/solaar/ui/diversion_rules.py:376 +#: lib/solaar/ui/diversion_rules.py:382 msgid "Insert new rule here" msgstr "" -#: lib/solaar/ui/diversion_rules.py:378 +#: lib/solaar/ui/diversion_rules.py:384 msgid "Insert new rule above" msgstr "" -#: lib/solaar/ui/diversion_rules.py:380 +#: lib/solaar/ui/diversion_rules.py:386 msgid "Insert new rule below" msgstr "" -#: lib/solaar/ui/diversion_rules.py:421 +#: lib/solaar/ui/diversion_rules.py:427 msgid "Paste here" msgstr "" -#: lib/solaar/ui/diversion_rules.py:423 +#: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "" -#: lib/solaar/ui/diversion_rules.py:425 +#: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "" -#: lib/solaar/ui/diversion_rules.py:431 +#: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "" -#: lib/solaar/ui/diversion_rules.py:433 +#: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "" -#: lib/solaar/ui/diversion_rules.py:435 +#: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "" -#: lib/solaar/ui/diversion_rules.py:439 +#: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:468 +#: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "" -#: lib/solaar/ui/diversion_rules.py:501 +#: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "" -#: lib/solaar/ui/diversion_rules.py:504 lib/solaar/ui/diversion_rules.py:624 -#: lib/solaar/ui/diversion_rules.py:835 +#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 +#: lib/solaar/ui/diversion_rules.py:1126 msgid "Or" msgstr "" -#: lib/solaar/ui/diversion_rules.py:505 lib/solaar/ui/diversion_rules.py:623 -#: lib/solaar/ui/diversion_rules.py:820 +#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 +#: lib/solaar/ui/diversion_rules.py:1111 msgid "And" msgstr "" -#: lib/solaar/ui/diversion_rules.py:507 +#: lib/solaar/ui/diversion_rules.py:513 msgid "Condition" msgstr "" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:951 +#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:874 -msgid "Process" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:902 -msgid "MouseProcess" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:512 lib/solaar/ui/diversion_rules.py:984 +#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1019 +#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 +msgid "Process" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:518 +msgid "Mouse process" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 msgid "Modifiers" msgstr "" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1066 +#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1111 +#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 +msgid "KeyIsDown" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 +msgid "Active" +msgstr "Ativo" + +#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2218 +#: lib/solaar/ui/diversion_rules.py:2270 lib/solaar/ui/diversion_rules.py:2323 +msgid "Device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +msgid "Host" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 +msgid "Setting" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 +#: lib/solaar/ui/diversion_rules.py:1526 msgid "Test" msgstr "" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1196 +#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 +msgid "Test bytes" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 msgid "Mouse Gesture" msgstr "" -#: lib/solaar/ui/diversion_rules.py:520 +#: lib/solaar/ui/diversion_rules.py:532 msgid "Action" msgstr "Ação" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1286 +#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 msgid "Key press" msgstr "" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1335 +#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:1383 +#: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 msgid "Mouse click" msgstr "" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1452 +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Set" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 msgid "Execute" msgstr "" -#: lib/solaar/ui/diversion_rules.py:554 +#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 +msgid "Later" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:568 msgid "Insert new rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/diversion_rules.py:1146 -#: lib/solaar/ui/diversion_rules.py:1236 lib/solaar/ui/diversion_rules.py:1411 +#: lib/solaar/ui/diversion_rules.py:588 lib/solaar/ui/diversion_rules.py:1686 +#: lib/solaar/ui/diversion_rules.py:1790 lib/solaar/ui/diversion_rules.py:1989 msgid "Delete" msgstr "" -#: lib/solaar/ui/diversion_rules.py:596 +#: lib/solaar/ui/diversion_rules.py:610 msgid "Negate" msgstr "" -#: lib/solaar/ui/diversion_rules.py:620 +#: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "" -#: lib/solaar/ui/diversion_rules.py:642 +#: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Cortar" -#: lib/solaar/ui/diversion_rules.py:657 +#: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Colar" -#: lib/solaar/ui/diversion_rules.py:669 +#: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Copiar" -#: lib/solaar/ui/diversion_rules.py:772 +#: lib/solaar/ui/diversion_rules.py:1063 msgid "This editor does not support the selected rule component yet." msgstr "" -#: lib/solaar/ui/diversion_rules.py:850 +#: lib/solaar/ui/diversion_rules.py:1138 +msgid "Number of seconds to delay." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1177 msgid "Not" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1039 +#: lib/solaar/ui/diversion_rules.py:1187 +msgid "X11 active process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1218 +msgid "X11 mouse process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1235 +msgid "MouseProcess" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1260 +msgid "Feature name of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1308 +msgid "Report number of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1342 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1383 +msgid "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1392 msgid "Key down" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1042 +#: lib/solaar/ui/diversion_rules.py:1395 msgid "Key up" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1131 -msgid "Add action" +#: lib/solaar/ui/diversion_rules.py:1436 +msgid "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." msgstr "" -#: lib/solaar/ui/diversion_rules.py:1224 +#: lib/solaar/ui/diversion_rules.py:1475 +msgid "Test condition on notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1479 +msgid "Parameter" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1542 +msgid "begin (inclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1543 +msgid "end (exclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1552 +msgid "range" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1554 +msgid "minimum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1555 +msgid "maximum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1557 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1562 +msgid "mask" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1563 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1573 +msgid "Bit or range test on bytes in notification message triggering rule " + "processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1583 +msgid "type" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1666 +msgid "Mouse gesture with optional initiating button followed by zero or " + "more mouse movements." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1671 +msgid "Add movement" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1764 +msgid "Simulate a chorded key click or depress or release.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1769 msgid "Add key" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1354 +#: lib/solaar/ui/diversion_rules.py:1772 +msgid "Click" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1775 +msgid "Depress" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1778 +msgid "Release" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1861 +msgid "Simulate a mouse scroll.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1918 +msgid "Simulate a mouse click.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1921 msgid "Button" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1355 -msgid "Count" -msgstr "Contagem" +#: lib/solaar/ui/diversion_rules.py:1922 +msgid "Count and Action" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1397 +#: lib/solaar/ui/diversion_rules.py:1972 +msgid "Execute a command with arguments." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1975 msgid "Add argument" msgstr "" -#: lib/solaar/ui/notify.py:125 lib/solaar/ui/tray.py:319 -#: lib/solaar/ui/tray.py:324 lib/solaar/ui/window.py:749 +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "Toggle" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "True" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2051 +msgid "False" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2065 +msgid "Unsupported setting" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2242 +#: lib/solaar/ui/diversion_rules.py:2328 lib/solaar/ui/diversion_rules.py:2570 +#: lib/solaar/ui/diversion_rules.py:2588 +msgid "Originating device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2256 +msgid "Device is active and its settings can be changed." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2266 +msgid "Device that originated the current notification." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2280 +msgid "Name of host computer." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2347 +msgid "Value" +msgstr "Valor" + +#: lib/solaar/ui/diversion_rules.py:2355 +msgid "Item" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2630 +msgid "Change setting on device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2647 +msgid "Setting on device" +msgstr "" + +#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:318 +#: lib/solaar/ui/tray.py:323 lib/solaar/ui/window.py:739 msgid "offline" msgstr "offline" -#: lib/solaar/ui/pair_window.py:134 +#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 +#: lib/solaar/ui/pair_window.py:288 +#, python-format +msgid "%(receiver_name)s: pair new device" +msgstr "" + +#: lib/solaar/ui/pair_window.py:123 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "" + +#: lib/solaar/ui/pair_window.py:126 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "left" +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "right" +msgstr "" + +#: lib/solaar/ui/pair_window.py:131 +#, python-format +msgid "Press %(code)s\n" + "and then press left and right buttons simultaneously." +msgstr "" + +#: lib/solaar/ui/pair_window.py:188 msgid "Pairing failed" msgstr "Emparelhamento falhado" -#: lib/solaar/ui/pair_window.py:136 +#: lib/solaar/ui/pair_window.py:190 msgid "Make sure your device is within range, and has a decent battery " "charge." msgstr "Certifique-se que o seu dispositivo está dentro do alcance, e tem " "uma carga de bateria decente." -#: lib/solaar/ui/pair_window.py:138 +#: lib/solaar/ui/pair_window.py:192 msgid "A new device was detected, but it is not compatible with this " "receiver." msgstr "Foi detectado um novo dispositivo, mas não é compatível com este " "receptor." -#: lib/solaar/ui/pair_window.py:140 +#: lib/solaar/ui/pair_window.py:194 msgid "More paired devices than receiver can support." msgstr "" -#: lib/solaar/ui/pair_window.py:142 +#: lib/solaar/ui/pair_window.py:196 msgid "No further details are available about the error." msgstr "Não há mais detalhes disponíveis acerca do erro." -#: lib/solaar/ui/pair_window.py:156 +#: lib/solaar/ui/pair_window.py:210 msgid "Found a new device:" msgstr "" -#: lib/solaar/ui/pair_window.py:181 +#: lib/solaar/ui/pair_window.py:235 msgid "The wireless link is not encrypted" msgstr "A ligação sem fios não está encriptada" -#: lib/solaar/ui/pair_window.py:199 -#, python-format -msgid "%(receiver_name)s: pair new device" +#: lib/solaar/ui/pair_window.py:264 +msgid "Unifying receivers are only compatible with Unifying devices." msgstr "" -#: lib/solaar/ui/pair_window.py:206 +#: lib/solaar/ui/pair_window.py:266 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:268 +msgid "Other receivers are only compatible with a few devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:270 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" + +#: lib/solaar/ui/pair_window.py:274 +msgid "Press a pairing button or key until the pairing light flashes " + "quickly." +msgstr "" + +#: lib/solaar/ui/pair_window.py:276 +msgid "You may have to first turn the device off and on again." +msgstr "" + +#: lib/solaar/ui/pair_window.py:278 +msgid "Turn on the device you want to pair." +msgstr "Ligue o dispositivo que deseja emparelhar." + +#: lib/solaar/ui/pair_window.py:280 msgid "If the device is already turned on, turn it off and on again." msgstr "" -#: lib/solaar/ui/pair_window.py:209 +#: lib/solaar/ui/pair_window.py:283 #, python-format msgid "\n" "\n" @@ -1178,207 +1607,190 @@ msgid_plural "\n" msgstr[0] "" msgstr[1] "" -#: lib/solaar/ui/pair_window.py:212 +#: lib/solaar/ui/pair_window.py:286 msgid "\n" "Cancelling at this point will not use up a pairing." msgstr "" -#: lib/solaar/ui/pair_window.py:215 -msgid "Turn on the device you want to pair." -msgstr "Ligue o dispositivo que deseja emparelhar." +#: lib/solaar/ui/tray.py:58 +msgid "No supported device found" +msgstr "" -#: lib/solaar/ui/tray.py:61 -msgid "No Logitech receiver found" -msgstr "Nenhum receptor Logitech encontrado" +#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#, python-format +msgid "About %s" +msgstr "Acerca de %s" -#: lib/solaar/ui/tray.py:68 lib/solaar/ui/window.py:325 +#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 +#, python-format msgid "Quit %s" msgstr "Terminar %s" -#: lib/solaar/ui/tray.py:298 lib/solaar/ui/tray.py:306 +#: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "nenhum receptor" -#: lib/solaar/ui/tray.py:322 +#: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "nenhum estado" -#: lib/solaar/ui/window.py:104 +#: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "A sondar" -#: lib/solaar/ui/window.py:137 lib/solaar/ui/window.py:692 +#: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Bateria" -#: lib/solaar/ui/window.py:140 +#: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Ligação Sem Fios" -#: lib/solaar/ui/window.py:144 +#: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Iluminação" -#: lib/solaar/ui/window.py:178 +#: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Mostrar Detalhes Técnicos" -#: lib/solaar/ui/window.py:194 +#: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Emparelhar novo dispositivo" -#: lib/solaar/ui/window.py:213 +#: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Seleccionar um dispositivo" -#: lib/solaar/ui/window.py:331 +#: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "" -#: lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Caminho" -#: lib/solaar/ui/window.py:544 +#: lib/solaar/ui/window.py:536 msgid "USB ID" msgstr "" -#: lib/solaar/ui/window.py:547 lib/solaar/ui/window.py:549 -#: lib/solaar/ui/window.py:569 lib/solaar/ui/window.py:571 +#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 msgid "Serial" msgstr "Série" -#: lib/solaar/ui/window.py:553 +#: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Índice" -#: lib/solaar/ui/window.py:555 +#: lib/solaar/ui/window.py:547 msgid "Wireless PID" msgstr "PID de ligação sem fios" -#: lib/solaar/ui/window.py:557 +#: lib/solaar/ui/window.py:549 msgid "Product ID" msgstr "ID do produto" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:551 msgid "Protocol" msgstr "Protocolo" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:551 msgid "Unknown" msgstr "Desconhecido" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:554 #, python-format msgid "%(rate)d ms (%(rate_hz)dHz)" msgstr "" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:554 msgid "Polling rate" msgstr "Rácio de votações" -#: lib/solaar/ui/window.py:573 +#: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "" -#: lib/solaar/ui/window.py:584 +#: lib/solaar/ui/window.py:576 msgid "none" msgstr "nenhum" -#: lib/solaar/ui/window.py:585 +#: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Notificações" -#: lib/solaar/ui/window.py:622 +#: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "" -#: lib/solaar/ui/window.py:629 +#: lib/solaar/ui/window.py:628 #, python-format msgid "Up to %(max_count)s device can be paired to this receiver." msgid_plural "Up to %(max_count)s devices can be paired to this receiver." msgstr[0] "" msgstr[1] "" -#: lib/solaar/ui/window.py:635 +#: lib/solaar/ui/window.py:634 msgid "Only one device can be paired to this receiver." msgstr "" -#: lib/solaar/ui/window.py:639 +#: lib/solaar/ui/window.py:638 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." msgstr[0] "" msgstr[1] "" -#: lib/solaar/ui/window.py:694 -msgid "Battery information unknown." -msgstr "" - -#: lib/solaar/ui/window.py:702 +#: lib/solaar/ui/window.py:692 msgid "Battery Voltage" msgstr "" -#: lib/solaar/ui/window.py:704 +#: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "" -#: lib/solaar/ui/window.py:706 lib/solaar/ui/window.py:710 +#: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "" -#: lib/solaar/ui/window.py:708 lib/solaar/ui/window.py:712 +#: lib/solaar/ui/window.py:698 msgid "Approximate level reported by battery" msgstr "" -#: lib/solaar/ui/window.py:715 lib/solaar/ui/window.py:717 +#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 msgid "next reported " msgstr "" -#: lib/solaar/ui/window.py:718 +#: lib/solaar/ui/window.py:708 msgid " and next level to be reported." msgstr "" -#: lib/solaar/ui/window.py:723 +#: lib/solaar/ui/window.py:713 msgid "last known" msgstr "último conhecido" -#: lib/solaar/ui/window.py:731 -msgid "not encrypted" -msgstr "não encriptado" - -#: lib/solaar/ui/window.py:735 -msgid "The wireless link between this device and its receiver is not " - "encrypted.\n" - "\n" - "For pointing devices (mice, trackballs, trackpads), this is a minor " - "security issue.\n" - "\n" - "It is, however, a major security issue for text-input devices " - "(keyboards, numpads),\n" - "because typed text can be sniffed inconspicuously by 3rd parties " - "within range." -msgstr "A ligação sem fios entre este dispositivo e o seu receptor não está " - "encriptada.\n" - "\n" - "Para dispositivos apontadores (ratos, trackballs, trackpads), este é " - "um problema de segurança menor.\n" - "\n" - "No entanto, é um problema de segurança maior para dispositivos de " - "entrada de texto (teclados, teclados numéricos).\n" - "porque o texto teclado pode ser \"cheirado\" de modo imperceptível " - "por terceiros que estejam dentro do alcance da ligação." - -#: lib/solaar/ui/window.py:744 +#: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "encriptado" -#: lib/solaar/ui/window.py:746 +#: lib/solaar/ui/window.py:726 msgid "The wireless link between this device and its receiver is encrypted." msgstr "A ligação sem fios entre este dispositivo e o seu receptor está " "encriptada." -#: lib/solaar/ui/window.py:759 +#: lib/solaar/ui/window.py:728 +msgid "not encrypted" +msgstr "não encriptado" + +#: lib/solaar/ui/window.py:732 +msgid "The wireless link between this device and its receiver is not " + "encrypted.\n" + "This is a security issue for pointing devices, and a major security " + "issue for text-input devices." +msgstr "" + +#: lib/solaar/ui/window.py:748 #, python-format msgid "%(light_level)d lux" msgstr "" @@ -1389,10 +1801,19 @@ msgstr "" #~ msgid "1 paired device." #~ msgstr "1 dispositivo emparelhado." +#~ msgid "Count" +#~ msgstr "Contagem" + #, python-format #~ msgid "Failed to unpair %s from %s." #~ msgstr "Falha ao desemparelhar %s de %s." +#, python-format +#~ msgid "Found a Logitech Receiver (%s), but did not have permission " +#~ "to open it." +#~ msgstr "Encontrado um Receptor Logitech (%s), mas não teve " +#~ "permissões para o abrir." + #~ msgid "Found a new device" #~ msgstr "Encontrado um novo dispositivo" @@ -1401,6 +1822,14 @@ msgstr "" #~ msgstr "Se o dispositivo já está ligado,\n" #~ "desligue-o e volte a ligá-lo." +#~ msgid "If you've just installed Solaar, try removing the receiver " +#~ "and plugging it back in." +#~ msgstr "Se você acabou de instalar o Solaar, tente remover o " +#~ "receptor e ligá-lo de novo." + +#~ msgid "No Logitech receiver found" +#~ msgstr "Nenhum receptor Logitech encontrado" + #~ msgid "No device paired" #~ msgstr "Nenhum dispositivo emparelhado" @@ -1422,6 +1851,27 @@ msgstr "" #~ msgid "The receiver was unplugged." #~ msgstr "O receptor foi desligado." +#~ msgid "The wireless link between this device and its receiver is " +#~ "not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices " +#~ "(keyboards, numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties " +#~ "within range." +#~ msgstr "A ligação sem fios entre este dispositivo e o seu receptor " +#~ "não está encriptada.\n" +#~ "\n" +#~ "Para dispositivos apontadores (ratos, trackballs, trackpads), este é " +#~ "um problema de segurança menor.\n" +#~ "\n" +#~ "No entanto, é um problema de segurança maior para dispositivos de " +#~ "entrada de texto (teclados, teclados numéricos).\n" +#~ "porque o texto teclado pode ser \"cheirado\" de modo imperceptível " +#~ "por terceiros que estejam dentro do alcance da ligação." + #~ msgid "USB id" #~ msgstr "id de USB" @@ -1432,6 +1882,9 @@ msgstr "" #~ msgid "closed" #~ msgstr "fechado" +#~ msgid "height" +#~ msgstr "altura" + #~ msgid "lux" #~ msgstr "lux" @@ -1446,3 +1899,9 @@ msgstr "" #~ msgid "pairing lock is " #~ msgstr "tranca de emparelhamento é " + +#~ msgid "unknown" +#~ msgstr "desconhecido" + +#~ msgid "width" +#~ msgstr "largura" diff --git a/po/pt_BR.po b/po/pt_BR.po index 7625fd90..45a328b2 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -7,9 +7,9 @@ msgid "" msgstr "" "Project-Id-Version: solaar 0.9.2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-08-20 18:21-0300\n" -"PO-Revision-Date: 2021-08-20 18:29-0300\n" -"Last-Translator: Vinícius \n" +"POT-Creation-Date: 2026-01-08 19:37-0300\n" +"PO-Revision-Date: 2026-01-08 22:37-0300\n" +"Last-Translator: Mário Victor Ribeiro Silva \n" "Language-Team: Sídnei A. Drovetto Jr.\n" "Language: pt_BR\n" "MIME-Version: 1.0\n" @@ -17,273 +17,424 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Launchpad-Export-Date: 2020-10-21 16:52+0000\n" -"X-Generator: Poedit 3.0\n" +"X-Generator: Poedit 3.8\n" -#: lib/logitech_receiver/base_usb.py:50 +#: lib/logitech_receiver/base_usb.py:52 +msgid "Bolt Receiver" +msgstr "Receptor Bolt" + +#: lib/logitech_receiver/base_usb.py:64 msgid "Unifying Receiver" msgstr "Receptor Unifying" -#: lib/logitech_receiver/base_usb.py:58 lib/logitech_receiver/base_usb.py:68 -#: lib/logitech_receiver/base_usb.py:79 lib/logitech_receiver/base_usb.py:90 -#: lib/logitech_receiver/base_usb.py:101 +#: lib/logitech_receiver/base_usb.py:75 lib/logitech_receiver/base_usb.py:87 +#: lib/logitech_receiver/base_usb.py:100 lib/logitech_receiver/base_usb.py:113 msgid "Nano Receiver" msgstr "Receptor Nano" -#: lib/logitech_receiver/base_usb.py:109 +#: lib/logitech_receiver/base_usb.py:125 msgid "Lightspeed Receiver" msgstr "Receptor Lightspeed" -#: lib/logitech_receiver/base_usb.py:117 +#: lib/logitech_receiver/base_usb.py:136 msgid "EX100 Receiver 27 Mhz" msgstr "Receptor EX100 27 Mhz" -#: lib/logitech_receiver/device.py:134 lib/solaar/ui/window.py:693 -msgid "unknown" -msgstr "desconhecida" +#: lib/logitech_receiver/common.py:649 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Bateria: %(level)s (%(status)s)" -#: lib/logitech_receiver/i18n.py:38 +#: lib/logitech_receiver/common.py:652 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Bateria: %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:1048 +#: lib/logitech_receiver/settings_templates.py:299 +msgid "Disabled" +msgstr "Desativado" + +#: lib/logitech_receiver/hidpp20.py:1049 +msgid "Static" +msgstr "Estático" + +#: lib/logitech_receiver/hidpp20.py:1050 +msgid "Pulse" +msgstr "Pulso" + +#: lib/logitech_receiver/hidpp20.py:1051 +msgid "Cycle" +msgstr "Ciclo" + +#: lib/logitech_receiver/hidpp20.py:1052 +msgid "Boot" +msgstr "Inicialização" + +#: lib/logitech_receiver/hidpp20.py:1053 +msgid "Demo" +msgstr "Demonstração" + +#: lib/logitech_receiver/hidpp20.py:1055 +msgid "Breathe" +msgstr "Respirar" + +#: lib/logitech_receiver/hidpp20.py:1058 +msgid "Ripple" +msgstr "Ondulação" + +#: lib/logitech_receiver/hidpp20.py:1059 +msgid "Decomposition" +msgstr "Decomposição" + +#: lib/logitech_receiver/hidpp20.py:1060 +msgid "Signature1" +msgstr "Assinatura1" + +#: lib/logitech_receiver/hidpp20.py:1061 +msgid "Signature2" +msgstr "Assinatura2" + +#: lib/logitech_receiver/hidpp20.py:1062 +msgid "CycleS" +msgstr "CicloS" + +#: lib/logitech_receiver/hidpp20.py:1126 +msgid "Unknown Location" +msgstr "Localização Desconhecida" + +#: lib/logitech_receiver/hidpp20.py:1127 +msgid "Primary" +msgstr "Primário" + +#: lib/logitech_receiver/hidpp20.py:1128 +msgid "Logo" +msgstr "Logotipo" + +#: lib/logitech_receiver/hidpp20.py:1129 +msgid "Left Side" +msgstr "Lado esquerdo" + +#: lib/logitech_receiver/hidpp20.py:1130 +msgid "Right Side" +msgstr "Lado direito" + +#: lib/logitech_receiver/hidpp20.py:1131 +msgid "Combined" +msgstr "Combinado" + +#: lib/logitech_receiver/hidpp20.py:1132 +msgid "Primary 1" +msgstr "Primário 1" + +#: lib/logitech_receiver/hidpp20.py:1133 +msgid "Primary 2" +msgstr "Primário 2" + +#: lib/logitech_receiver/hidpp20.py:1134 +msgid "Primary 3" +msgstr "Primário 3" + +#: lib/logitech_receiver/hidpp20.py:1135 +msgid "Primary 4" +msgstr "Primário 4" + +#: lib/logitech_receiver/hidpp20.py:1136 +msgid "Primary 5" +msgstr "Primário 5" + +#: lib/logitech_receiver/hidpp20.py:1137 +msgid "Primary 6" +msgstr "Primário 6" + +#: lib/logitech_receiver/i18n.py:28 msgid "empty" msgstr "vazia" -#: lib/logitech_receiver/i18n.py:39 +#: lib/logitech_receiver/i18n.py:29 msgid "critical" msgstr "crítica" -#: lib/logitech_receiver/i18n.py:40 +#: lib/logitech_receiver/i18n.py:30 msgid "low" msgstr "fraca" -#: lib/logitech_receiver/i18n.py:41 +#: lib/logitech_receiver/i18n.py:31 msgid "average" msgstr "média" -#: lib/logitech_receiver/i18n.py:42 +#: lib/logitech_receiver/i18n.py:32 msgid "good" msgstr "boa" -#: lib/logitech_receiver/i18n.py:43 +#: lib/logitech_receiver/i18n.py:33 msgid "full" msgstr "cheia" -#: lib/logitech_receiver/i18n.py:46 +#: lib/logitech_receiver/i18n.py:35 msgid "discharging" msgstr "descarregando" -#: lib/logitech_receiver/i18n.py:47 +#: lib/logitech_receiver/i18n.py:36 msgid "recharging" msgstr "recarregando" -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:721 +#: lib/logitech_receiver/i18n.py:37 lib/solaar/ui/window.py:700 msgid "charging" msgstr "carregando" -#: lib/logitech_receiver/i18n.py:49 +#: lib/logitech_receiver/i18n.py:38 msgid "not charging" msgstr "não carregando" -#: lib/logitech_receiver/i18n.py:50 +#: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "quase cheia" -#: lib/logitech_receiver/i18n.py:51 +#: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "carregada" -#: lib/logitech_receiver/i18n.py:52 +#: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "recarga lenta" -#: lib/logitech_receiver/i18n.py:53 +#: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "bateria inválida" -#: lib/logitech_receiver/i18n.py:54 +#: lib/logitech_receiver/i18n.py:43 msgid "thermal error" msgstr "erro térmico" -#: lib/logitech_receiver/i18n.py:55 +#: lib/logitech_receiver/i18n.py:44 msgid "error" msgstr "erro" -#: lib/logitech_receiver/i18n.py:56 +#: lib/logitech_receiver/i18n.py:45 msgid "standard" msgstr "padrão" -#: lib/logitech_receiver/i18n.py:57 +#: lib/logitech_receiver/i18n.py:46 msgid "fast" msgstr "rápido" -#: lib/logitech_receiver/i18n.py:58 +#: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "lento" -#: lib/logitech_receiver/i18n.py:61 +#: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "tempo esgotado do dispositivo" -#: lib/logitech_receiver/i18n.py:62 +#: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "dispositivo não suportado" -#: lib/logitech_receiver/i18n.py:63 +#: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "número excessivo de dispositivos" -#: lib/logitech_receiver/i18n.py:64 +#: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "tempo esgotado da sequência" -#: lib/logitech_receiver/i18n.py:67 lib/solaar/ui/window.py:580 +#: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:555 msgid "Firmware" msgstr "Firmware" -#: lib/logitech_receiver/i18n.py:68 +#: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Bootloader" -#: lib/logitech_receiver/i18n.py:69 +#: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Hardware" -#: lib/logitech_receiver/i18n.py:70 +#: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "Outro" -#: lib/logitech_receiver/i18n.py:73 +#: lib/logitech_receiver/i18n.py:59 msgid "Left Button" msgstr "Botão da esquerda" -#: lib/logitech_receiver/i18n.py:74 +#: lib/logitech_receiver/i18n.py:60 msgid "Right Button" msgstr "Botão da direita" -#: lib/logitech_receiver/i18n.py:75 +#: lib/logitech_receiver/i18n.py:61 msgid "Middle Button" msgstr "Botão do meio" -#: lib/logitech_receiver/i18n.py:76 +#: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "Botão Voltar" -#: lib/logitech_receiver/i18n.py:77 +#: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "Botão Avançar" -#: lib/logitech_receiver/i18n.py:78 +#: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "Botão de gestos do mouse" -#: lib/logitech_receiver/i18n.py:79 +#: lib/logitech_receiver/i18n.py:65 msgid "Smart Shift" msgstr "Smart Shift" -#: lib/logitech_receiver/i18n.py:80 +#: lib/logitech_receiver/i18n.py:66 msgid "DPI Switch" msgstr "Mudança de DPI" -#: lib/logitech_receiver/i18n.py:81 +#: lib/logitech_receiver/i18n.py:67 msgid "Left Tilt" msgstr "Inclinação para a esquerda" -#: lib/logitech_receiver/i18n.py:82 +#: lib/logitech_receiver/i18n.py:68 msgid "Right Tilt" msgstr "Inclinação para a direita" -#: lib/logitech_receiver/i18n.py:83 +#: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "Clique com o botão esquerdo" -#: lib/logitech_receiver/i18n.py:84 +#: lib/logitech_receiver/i18n.py:70 msgid "Right Click" msgstr "Clique com o botão direito" -#: lib/logitech_receiver/i18n.py:85 +#: lib/logitech_receiver/i18n.py:71 msgid "Mouse Middle Button" msgstr "Botão do meio do mouse" -#: lib/logitech_receiver/i18n.py:86 +#: lib/logitech_receiver/i18n.py:72 msgid "Mouse Back Button" msgstr "Botão de voltar do mouse" -#: lib/logitech_receiver/i18n.py:87 +#: lib/logitech_receiver/i18n.py:73 msgid "Mouse Forward Button" msgstr "Botão de avançar do mouse" -#: lib/logitech_receiver/i18n.py:88 +#: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "Botão de navegação por gestos" -#: lib/logitech_receiver/i18n.py:89 +#: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "Botão de rolagem esquerda do mouse" -#: lib/logitech_receiver/i18n.py:90 +#: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "Botão de rolagem direita do mouse" -#: lib/logitech_receiver/i18n.py:93 +#: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "pressionada" -#: lib/logitech_receiver/i18n.py:94 +#: lib/logitech_receiver/i18n.py:79 msgid "released" msgstr "liberada" -#: lib/logitech_receiver/notifications.py:76 -msgid "pairing lock is closed" -msgstr "trava de emparelhamento está fechada" - -#: lib/logitech_receiver/notifications.py:76 -msgid "pairing lock is open" -msgstr "trava de emparelhamento está aberta" - -#: lib/logitech_receiver/notifications.py:160 lib/solaar/ui/notify.py:123 +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 msgid "connected" msgstr "conectado" -#: lib/logitech_receiver/notifications.py:160 +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 msgid "disconnected" msgstr "desconectado" -#: lib/logitech_receiver/notifications.py:198 lib/solaar/ui/notify.py:121 +#: lib/logitech_receiver/notifications.py:182 msgid "unpaired" msgstr "desemparelhado" -#: lib/logitech_receiver/notifications.py:248 +#: lib/logitech_receiver/notifications.py:231 msgid "powered on" msgstr "ligado" -#: lib/logitech_receiver/settings.py:524 +#: lib/logitech_receiver/notifications.py:283 +msgid "ADC measurement notification" +msgstr "" + +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:484 +msgid "pairing lock is closed" +msgstr "trava de emparelhamento está fechada" + +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:484 +msgid "pairing lock is open" +msgstr "trava de emparelhamento está aberta" + +#: lib/logitech_receiver/notifications.py:447 +msgid "discovery lock is closed" +msgstr "bloqueio de descoberta está fechado" + +#: lib/logitech_receiver/notifications.py:447 +msgid "discovery lock is open" +msgstr "bloqueio de descoberta está aberto" + +#: lib/logitech_receiver/receiver.py:437 +msgid "No paired devices." +msgstr "Nenhum dispositivo emparelhado." + +#: lib/logitech_receiver/receiver.py:439 lib/solaar/ui/window.py:606 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s dispositivo emparelhado." +msgstr[1] "%(count)s dispositivos emparelhados." + +#: lib/logitech_receiver/settings.py:602 msgid "register" msgstr "register" -#: lib/logitech_receiver/settings.py:540 lib/logitech_receiver/settings.py:566 +#: lib/logitech_receiver/settings.py:616 lib/logitech_receiver/settings.py:654 msgid "feature" msgstr "feature" -#: lib/logitech_receiver/settings_templates.py:71 +#: lib/logitech_receiver/settings_templates.py:138 +msgid "Swap Fx function" +msgstr "Trocar função de teclas Fx" + +#: lib/logitech_receiver/settings_templates.py:141 +msgid "" +"When set, the F1..F12 keys will activate their special function,\n" +"and you must hold the FN key to activate their standard function." +msgstr "" +"Quando marcada, as teclas F1..F12 vão acionar sua função especial,\n" +"e você precisa manter pressionada a tecla FN para acionar sua função padrão." + +#: lib/logitech_receiver/settings_templates.py:146 +msgid "" +"When unset, the F1..F12 keys will activate their standard function,\n" +"and you must hold the FN key to activate their special function." +msgstr "" +"Quando desmarcada, as teclas F1..F12 vão acionar sua função padrão,\n" +"e você precisa pressionar a tecla FN para acionar sua função especial." + +#: lib/logitech_receiver/settings_templates.py:154 msgid "Hand Detection" msgstr "Detecção de Mão" -#: lib/logitech_receiver/settings_templates.py:71 +#: lib/logitech_receiver/settings_templates.py:155 msgid "Turn on illumination when the hands hover over the keyboard." msgstr "Ligar a iluminação quando a mão estiver sobre o telcado." -#: lib/logitech_receiver/settings_templates.py:72 +#: lib/logitech_receiver/settings_templates.py:162 msgid "Scroll Wheel Smooth Scrolling" msgstr "Rolagem suave com a roda" -#: lib/logitech_receiver/settings_templates.py:73 -#: lib/logitech_receiver/settings_templates.py:78 -#: lib/logitech_receiver/settings_templates.py:85 +#: lib/logitech_receiver/settings_templates.py:163 +#: lib/logitech_receiver/settings_templates.py:410 +#: lib/logitech_receiver/settings_templates.py:439 msgid "High-sensitivity mode for vertical scroll with the wheel." msgstr "Modo de alta-sensibilidade para rolagem vertical com a roda." -#: lib/logitech_receiver/settings_templates.py:74 +#: lib/logitech_receiver/settings_templates.py:170 msgid "Side Scrolling" msgstr "Rolagem Lateral" -#: lib/logitech_receiver/settings_templates.py:75 +#: lib/logitech_receiver/settings_templates.py:172 msgid "" "When disabled, pushing the wheel sideways sends custom button events\n" "instead of the standard side-scrolling events." @@ -292,211 +443,190 @@ msgstr "" "customizados\n" "ao invés dos eventos tradicionais de rolagem lateral." -#: lib/logitech_receiver/settings_templates.py:77 -msgid "Scroll Wheel High Resolution" -msgstr "Alta resolução de rolagem com a roda" +#: lib/logitech_receiver/settings_templates.py:182 +msgid "Sensitivity (DPI - older mice)" +msgstr "Sensibilidade (DPI - mouses antigos)" -#: lib/logitech_receiver/settings_templates.py:79 -msgid "Scroll Wheel HID++ Scrolling" -msgstr "Rolagem com a roda usando HID++" - -#: lib/logitech_receiver/settings_templates.py:80 -msgid "HID++ mode for vertical scroll with the wheel." -msgstr "Modo HID++ para a rolagem vertical com a roda." - -#: lib/logitech_receiver/settings_templates.py:81 -msgid "Effectively turns off wheel scrolling in Linux." -msgstr "Na prática, desativa a rolagem com a roda no Linux." - -#: lib/logitech_receiver/settings_templates.py:82 -msgid "Scroll Wheel Direction" -msgstr "Direção de rolagem" - -#: lib/logitech_receiver/settings_templates.py:83 -msgid "Invert direction for vertical scroll with wheel." -msgstr "Inverte a direção da rolagem vertical." - -#: lib/logitech_receiver/settings_templates.py:84 -msgid "Scroll Wheel Resolution" -msgstr "Resolução da roda de rolagem" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Frequency of device polling, in milliseconds" -msgstr "Frequência de sondagem, em milissegundos" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Polling Rate (ms)" -msgstr "Taxa de Sondagem (ms)" - -#: lib/logitech_receiver/settings_templates.py:87 -msgid "Swap Fx function" -msgstr "Trocar função de teclas Fx" - -#: lib/logitech_receiver/settings_templates.py:88 -msgid "" -"When set, the F1..F12 keys will activate their special function,\n" -"and you must hold the FN key to activate their standard function." -msgstr "" -"Quando marcada, as teclas F1..F12 vão acionar sua função especial,\n" -"e você precisa manter pressionada a tecla FN para acionar sua função padrão." - -#: lib/logitech_receiver/settings_templates.py:90 -msgid "" -"When unset, the F1..F12 keys will activate their standard function,\n" -"and you must hold the FN key to activate their special function." -msgstr "" -"Quando desmarcada, as teclas F1..F12 vão acionar sua função padrão,\n" -"e você precisa pressionar a tecla FN para acionar sua função especial." - -#: lib/logitech_receiver/settings_templates.py:92 +#: lib/logitech_receiver/settings_templates.py:183 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 msgid "Mouse movement sensitivity" msgstr "Sensibilidade do movimento do mouse" -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Sensitivity (DPI)" -msgstr "Sensibilidade (DPI)" +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Backlight Timed" +msgstr "Temporizador de luz de fundo" -#: lib/logitech_receiver/settings_templates.py:93 -msgid "Sensitivity (Pointer Speed)" -msgstr "Sensibilidade (Velocidade do Ponteiro)" +#: lib/logitech_receiver/settings_templates.py:257 +#: lib/logitech_receiver/settings_templates.py:397 +msgid "Set illumination time for keyboard." +msgstr "Defina o tempo de iluminação do teclado." -#: lib/logitech_receiver/settings_templates.py:94 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "Fator de multiplicação da velocidade do mouse (256 é o fator normal)." - -#: lib/logitech_receiver/settings_templates.py:95 -msgid "Scroll Wheel Rachet" -msgstr "Rolagem no modo catraca" - -#: lib/logitech_receiver/settings_templates.py:96 -msgid "" -"Automatically switch the mouse wheel between ratchet and freespin mode.\n" -"The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "" -"Alternar automaticamente a roda do mouse entre o modo de catraca e o modo de " -"rotação livre.\n" -"A roda do mouse fica sempre livre no valor 0, e sempre no modo catraca no " -"valor 50" - -#: lib/logitech_receiver/settings_templates.py:98 +#: lib/logitech_receiver/settings_templates.py:268 msgid "Backlight" msgstr "Luz de fundo" -#: lib/logitech_receiver/settings_templates.py:98 -msgid "Turn illumination on or off on keyboard." -msgstr "Ativar ou desativar a iluminação do teclado." - -#: lib/logitech_receiver/settings_templates.py:99 -msgid "Key/Button Actions" -msgstr "Ações das teclas/botões" - -#: lib/logitech_receiver/settings_templates.py:100 -msgid "Change the action for the key or button." -msgstr "Trocar a ação da tecla/botão." - -#: lib/logitech_receiver/settings_templates.py:101 +#: lib/logitech_receiver/settings_templates.py:269 msgid "" -"Changing important actions (such as for the left mouse button) can result in " -"an unusable system." +"Illumination level on keyboard. Changes made are only applied in Manual " +"mode." msgstr "" -"Trocar ações importantes (como a do botão esquerdo do mouse) pode tornar o " -"sistema inutilizável." +"Nível de iluminação do teclado. As alterações feitas só se aplicam no modo " +"Manual." -#: lib/logitech_receiver/settings_templates.py:102 -msgid "Key/Button Diversion" -msgstr "Desvio de Tecla/Botão" +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Automatic" +msgstr "Automático" -#: lib/logitech_receiver/settings_templates.py:103 +#: lib/logitech_receiver/settings_templates.py:303 +msgid "Manual" +msgstr "Manual" + +#: lib/logitech_receiver/settings_templates.py:305 +msgid "Enabled" +msgstr "Habilitado" + +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Backlight Level" +msgstr "Nível de luz de fundo" + +#: lib/logitech_receiver/settings_templates.py:312 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "Nível de iluminação do teclado no modo Manual." + +#: lib/logitech_receiver/settings_templates.py:369 +msgid "Backlight Delay Hands Out" +msgstr "Atraso da luz de fundo ao retirar as mãos" + +#: lib/logitech_receiver/settings_templates.py:370 msgid "" -"Make the key or button send HID++ notifications (which trigger Solaar rules " -"but are otherwise ignored)." +"Delay in seconds until backlight fades out with hands away from keyboard." msgstr "" -"Fazer as teclas ou botões enviarem notificações HID++ (que desencadeiam " -"regras do Solaar, mas são ignoradas fora disso)." +"Tempo de atraso, em segundos, até que a luz de fundo se apague gradualmente " +"quando as mãos forem retiradas do teclado." -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable keys" -msgstr "Desativar teclas" +#: lib/logitech_receiver/settings_templates.py:378 +msgid "Backlight Delay Hands In" +msgstr "Atraso da luz de fundo ao colocar as mãos" -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable specific keyboard keys." -msgstr "Desativar teclas específicas do teclado." +#: lib/logitech_receiver/settings_templates.py:379 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "" +"Tempo de atraso, em segundos, até que a luz de fundo se apague com as mãos " +"próximas ao teclado." -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Change keys to match OS." -msgstr "Trocar as teclas para corresponder ao SO." +#: lib/logitech_receiver/settings_templates.py:387 +msgid "Backlight Delay Powered" +msgstr "Atraso da luz de fundo Alimentado" -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Set OS" -msgstr "Definir SO" +#: lib/logitech_receiver/settings_templates.py:388 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "" +"Tempo de atraso, em segundos, até que a luz de fundo se apague com a " +"alimentação externa." -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Change Host" -msgstr "Trocar Hospedeiro" +#: lib/logitech_receiver/settings_templates.py:396 +msgid "Backlight (Seconds)" +msgstr "Luz de fundo (segundos)" -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Switch connection to a different host" -msgstr "Trocar a conexão para um hospedeiro diferente" +#: lib/logitech_receiver/settings_templates.py:408 +msgid "Scroll Wheel High Resolution" +msgstr "Alta resolução de rolagem com a roda" -#: lib/logitech_receiver/settings_templates.py:107 -msgid "Thumb Wheel HID++ Scrolling" -msgstr "Rolagem com o polegar usando HID++" +#: lib/logitech_receiver/settings_templates.py:412 +#: lib/logitech_receiver/settings_templates.py:441 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "Configure para ignorar se a rolagem for anormalmente rápida ou lenta" -#: lib/logitech_receiver/settings_templates.py:108 -msgid "HID++ mode for horizontal scroll with the thumb wheel." -msgstr "Modo HID++ para a rolagem horizontal com o polegar." +#: lib/logitech_receiver/settings_templates.py:419 +#: lib/logitech_receiver/settings_templates.py:450 +msgid "Scroll Wheel Diversion" +msgstr "Desvio da roda de rolagem" -#: lib/logitech_receiver/settings_templates.py:109 -msgid "Effectively turns off thumb scrolling in Linux." -msgstr "Na prática, desativa a rolagem com o polegar no Linux." +#: lib/logitech_receiver/settings_templates.py:421 +msgid "" +"Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " +"Solaar rules but are otherwise ignored)." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Invert thumb wheel scroll direction." -msgstr "Inverter a direção da rolagem com o polegar." +#: lib/logitech_receiver/settings_templates.py:428 +msgid "Scroll Wheel Direction" +msgstr "Direção de rolagem" -#: lib/logitech_receiver/settings_templates.py:110 +#: lib/logitech_receiver/settings_templates.py:429 +msgid "Invert direction for vertical scroll with wheel." +msgstr "Inverte a direção da rolagem vertical." + +#: lib/logitech_receiver/settings_templates.py:437 +msgid "Scroll Wheel Resolution" +msgstr "Resolução da roda de rolagem" + +#: lib/logitech_receiver/settings_templates.py:452 +msgid "" +"Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:461 +msgid "Sensitivity (Pointer Speed)" +msgstr "Sensibilidade (Velocidade do Ponteiro)" + +#: lib/logitech_receiver/settings_templates.py:462 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Fator de multiplicação da velocidade do mouse (256 é o fator normal)." + +#: lib/logitech_receiver/settings_templates.py:472 +msgid "Thumb Wheel Diversion" +msgstr "Desvio da roda de polegar" + +#: lib/logitech_receiver/settings_templates.py:474 +msgid "" +"Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:483 msgid "Thumb Wheel Direction" msgstr "Direção da rolagem com a roda" -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Gestures" -msgstr "Gestos" +#: lib/logitech_receiver/settings_templates.py:484 +msgid "Invert thumb wheel scroll direction." +msgstr "Inverter a direção da rolagem com o polegar." -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "Ajustar o comportamento do mouse/touchpad." +#: lib/logitech_receiver/settings_templates.py:504 +msgid "Onboard Profiles" +msgstr "Perfis Embarcados" -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "Alterar parâmetros numéricos de um mouse/touchpad." - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Gesture params" -msgstr "Parâmetros de gestos" - -#: lib/logitech_receiver/settings_templates.py:113 -msgid "DPI Sliding Adjustment" -msgstr "Ajuste de DPI por deslizamento" - -#: lib/logitech_receiver/settings_templates.py:114 +#: lib/logitech_receiver/settings_templates.py:505 msgid "" -"Adjust the DPI by sliding the mouse horizontally while holding the button " -"down." -msgstr "Ajustar DPI deslizando o mouse horizontalmente ao segurar o botão." +"Enable an onboard profile, which controls report rate, sensitivity, and " +"button actions" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:115 -msgid "Mouse Gestures" -msgstr "Gestos do mouse" +#: lib/logitech_receiver/settings_templates.py:549 +#: lib/logitech_receiver/settings_templates.py:582 +msgid "Report Rate" +msgstr "Taxa de relatório" -#: lib/logitech_receiver/settings_templates.py:116 -msgid "Send a gesture by sliding the mouse while holding the button down." -msgstr "Enviar um gesto deslizando o mouse ao segurar o botão." +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +msgid "Frequency of device movement reports" +msgstr "Frequência de relatórios de movimentação de dispositivos" -#: lib/logitech_receiver/settings_templates.py:117 +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1421 +#: lib/logitech_receiver/settings_templates.py:1452 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:612 msgid "Divert crown events" msgstr "Desviar eventos da coroa" -#: lib/logitech_receiver/settings_templates.py:118 +#: lib/logitech_receiver/settings_templates.py:613 msgid "" "Make crown send CROWN HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." @@ -504,322 +634,639 @@ msgstr "" "Faz a coroa enviar notificações HID++ CROWN (que desencadeiam regras do " "Solaar, mas são ignoradas fora disso)." -#: lib/logitech_receiver/settings_templates.py:119 -msgid "Divert G Keys" -msgstr "Desviar teclas G" +#: lib/logitech_receiver/settings_templates.py:621 +msgid "Crown smooth scroll" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:120 +#: lib/logitech_receiver/settings_templates.py:622 +msgid "Set crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:630 +msgid "Divert G and M Keys" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:631 msgid "" -"Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " +"Make G and M keys send HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." msgstr "" -"Fazer as teclas G enviarem notificações HID++ GKEY (que desencadeiam regras " -"do Solaar, mas são ignoradas fora disso)." -#: lib/logitech_receiver/settings_templates.py:123 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Scroll Wheel Ratcheted" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:646 +msgid "" +"Switch the mouse wheel between speed-controlled ratcheting and always " +"freespin." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Freespinning" +msgstr "Giro livre" + +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Ratcheted" +msgstr "Ratcheted" + +#: lib/logitech_receiver/settings_templates.py:655 +msgid "Scroll Wheel Ratchet Speed" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:657 +msgid "" +"Use the mouse wheel speed to switch between ratcheted and freespinning.\n" +"The mouse wheel is always ratcheted at 50." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:707 +msgid "Scroll Wheel Ratchet Torque" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:708 +msgid "Change the torque needed to overcome the ratchet." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:743 +msgid "Key/Button Actions" +msgstr "Ações das teclas/botões" + +#: lib/logitech_receiver/settings_templates.py:745 +msgid "Change the action for the key or button." +msgstr "Trocar a ação da tecla/botão." + +#: lib/logitech_receiver/settings_templates.py:747 +msgid "Overridden by diversion." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:749 +msgid "" +"Changing important actions (such as for the left mouse button) can result in " +"an unusable system." +msgstr "" +"Trocar ações importantes (como a do botão esquerdo do mouse) pode tornar o " +"sistema inutilizável." + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Key/Button Diversion" +msgstr "Desvio de Tecla/Botão" + +#: lib/logitech_receiver/settings_templates.py:925 +msgid "" +"Make the key or button send HID++ notifications (Diverted) or initiate Mouse " +"Gestures or Sliding DPI" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Diverted" +msgstr "Desviado" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +msgid "Mouse Gestures" +msgstr "Gestos do mouse" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Regular" +msgstr "Regular" + +#: lib/logitech_receiver/settings_templates.py:928 +msgid "Sliding DPI" +msgstr "DPI deslizante" + +#: lib/logitech_receiver/settings_templates.py:1018 +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Sensitivity (DPI)" +msgstr "Sensibilidade (DPI)" + +#: lib/logitech_receiver/settings_templates.py:1123 +msgid "Sensitivity Switching" +msgstr "Alternar sensibilidade" + +#: lib/logitech_receiver/settings_templates.py:1125 +msgid "" +"Switch the current sensitivity and the remembered sensitivity when the key " +"or button is pressed.\n" +"If there is no remembered sensitivity, just remember the current sensitivity" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1129 +msgid "Off" +msgstr "Desativado" + +#: lib/logitech_receiver/settings_templates.py:1160 +msgid "Disable keys" +msgstr "Desativar teclas" + +#: lib/logitech_receiver/settings_templates.py:1161 +msgid "Disable specific keyboard keys." +msgstr "Desativar teclas específicas do teclado." + +#: lib/logitech_receiver/settings_templates.py:1164 +#, python-format +msgid "Disables the %s key." +msgstr "Desativa a tecla %s." + +#: lib/logitech_receiver/settings_templates.py:1177 +#: lib/logitech_receiver/settings_templates.py:1234 +msgid "Set OS" +msgstr "Definir SO" + +#: lib/logitech_receiver/settings_templates.py:1178 +#: lib/logitech_receiver/settings_templates.py:1235 +msgid "Change keys to match OS." +msgstr "Trocar as teclas para corresponder ao SO." + +#: lib/logitech_receiver/settings_templates.py:1247 +msgid "Change Host" +msgstr "Trocar Hospedeiro" + +#: lib/logitech_receiver/settings_templates.py:1248 +msgid "Switch connection to a different host" +msgstr "Trocar a conexão para um hospedeiro diferente" + +#: lib/logitech_receiver/settings_templates.py:1272 msgid "Performs a left click." msgstr "Executa um clique com botão esquerdo." -#: lib/logitech_receiver/settings_templates.py:123 +#: lib/logitech_receiver/settings_templates.py:1272 msgid "Single tap" msgstr "Toque único" -#: lib/logitech_receiver/settings_templates.py:124 +#: lib/logitech_receiver/settings_templates.py:1273 msgid "Performs a right click." msgstr "Executa um clique com botão direito." -#: lib/logitech_receiver/settings_templates.py:124 +#: lib/logitech_receiver/settings_templates.py:1273 msgid "Single tap with two fingers" msgstr "Toque único com dois dedos" -#: lib/logitech_receiver/settings_templates.py:125 +#: lib/logitech_receiver/settings_templates.py:1274 msgid "Single tap with three fingers" msgstr "Toque único com três dedos" -#: lib/logitech_receiver/settings_templates.py:129 +#: lib/logitech_receiver/settings_templates.py:1278 msgid "Double tap" msgstr "Toque duplo" -#: lib/logitech_receiver/settings_templates.py:129 +#: lib/logitech_receiver/settings_templates.py:1278 msgid "Performs a double click." msgstr "Executa um clique duplo." -#: lib/logitech_receiver/settings_templates.py:130 +#: lib/logitech_receiver/settings_templates.py:1279 msgid "Double tap with two fingers" msgstr "Toque duplo com dois dedos" -#: lib/logitech_receiver/settings_templates.py:131 +#: lib/logitech_receiver/settings_templates.py:1280 msgid "Double tap with three fingers" msgstr "Toque duplo com três dedos" -#: lib/logitech_receiver/settings_templates.py:134 +#: lib/logitech_receiver/settings_templates.py:1283 msgid "Drags items by dragging the finger after double tapping." msgstr "Deslizar itens arrastando o dedo após um toque duplo." -#: lib/logitech_receiver/settings_templates.py:134 +#: lib/logitech_receiver/settings_templates.py:1283 msgid "Tap and drag" msgstr "Tocar e arrastar" -#: lib/logitech_receiver/settings_templates.py:135 +#: lib/logitech_receiver/settings_templates.py:1285 msgid "Tap and drag with two fingers" msgstr "Tocar e arrastar com dois dedos" -#: lib/logitech_receiver/settings_templates.py:136 +#: lib/logitech_receiver/settings_templates.py:1286 msgid "Drags items by dragging the fingers after double tapping." msgstr "Arrasta itens arrastando os dedos após um toque duplo." -#: lib/logitech_receiver/settings_templates.py:137 +#: lib/logitech_receiver/settings_templates.py:1288 msgid "Tap and drag with three fingers" msgstr "Tocar e arrastar com três dedos" -#: lib/logitech_receiver/settings_templates.py:139 +#: lib/logitech_receiver/settings_templates.py:1291 msgid "Suppress tap and edge gestures" msgstr "Impedir gestos de toque e borda" -#: lib/logitech_receiver/settings_templates.py:140 +#: lib/logitech_receiver/settings_templates.py:1292 msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." msgstr "" "Desativa os gestos de toque e borda (equivalente a apertar Fn + clique " "esquerdo)." -#: lib/logitech_receiver/settings_templates.py:141 +#: lib/logitech_receiver/settings_templates.py:1294 msgid "Scroll with one finger" msgstr "Rolar com um dedo" -#: lib/logitech_receiver/settings_templates.py:141 -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 +#: lib/logitech_receiver/settings_templates.py:1294 +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 msgid "Scrolls." msgstr "Executa a rolagem." -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 msgid "Scroll with two fingers" msgstr "Rolar com dois dedos" -#: lib/logitech_receiver/settings_templates.py:143 +#: lib/logitech_receiver/settings_templates.py:1296 msgid "Scroll horizontally with two fingers" msgstr "Rolar horizontalmente com dois dedos" -#: lib/logitech_receiver/settings_templates.py:143 +#: lib/logitech_receiver/settings_templates.py:1296 msgid "Scrolls horizontally." msgstr "Faz rolagem horizontal." -#: lib/logitech_receiver/settings_templates.py:144 +#: lib/logitech_receiver/settings_templates.py:1297 msgid "Scroll vertically with two fingers" msgstr "Rolar verticalmente com dois dedos" -#: lib/logitech_receiver/settings_templates.py:144 +#: lib/logitech_receiver/settings_templates.py:1297 msgid "Scrolls vertically." msgstr "Faz rolagem vertical." -#: lib/logitech_receiver/settings_templates.py:146 +#: lib/logitech_receiver/settings_templates.py:1299 msgid "Inverts the scrolling direction." msgstr "Inverte a direção de rolagem." -#: lib/logitech_receiver/settings_templates.py:146 +#: lib/logitech_receiver/settings_templates.py:1299 msgid "Natural scrolling" msgstr "Rolagem natural" -#: lib/logitech_receiver/settings_templates.py:147 +#: lib/logitech_receiver/settings_templates.py:1300 msgid "Enables the thumbwheel." msgstr "Ativa a roda do polegar." -#: lib/logitech_receiver/settings_templates.py:147 +#: lib/logitech_receiver/settings_templates.py:1300 msgid "Thumbwheel" msgstr "Roda do polegar" -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:162 +#: lib/logitech_receiver/settings_templates.py:1311 +#: lib/logitech_receiver/settings_templates.py:1315 msgid "Swipe from the top edge" msgstr "Deslizar da borda superior" -#: lib/logitech_receiver/settings_templates.py:159 +#: lib/logitech_receiver/settings_templates.py:1312 msgid "Swipe from the left edge" msgstr "Deslizar da borda esquerda" -#: lib/logitech_receiver/settings_templates.py:160 +#: lib/logitech_receiver/settings_templates.py:1313 msgid "Swipe from the right edge" msgstr "Deslizar da borda direita" -#: lib/logitech_receiver/settings_templates.py:161 +#: lib/logitech_receiver/settings_templates.py:1314 msgid "Swipe from the bottom edge" msgstr "Deslizar da borda inferior" -#: lib/logitech_receiver/settings_templates.py:163 +#: lib/logitech_receiver/settings_templates.py:1316 msgid "Swipe two fingers from the left edge" msgstr "Deslizar dois dedos da borda esquerda" -#: lib/logitech_receiver/settings_templates.py:164 +#: lib/logitech_receiver/settings_templates.py:1317 msgid "Swipe two fingers from the right edge" msgstr "Deslizar dois dedos da borda direita" -#: lib/logitech_receiver/settings_templates.py:165 +#: lib/logitech_receiver/settings_templates.py:1318 msgid "Swipe two fingers from the bottom edge" msgstr "Deslizar dois dedos da borda inferior" -#: lib/logitech_receiver/settings_templates.py:166 +#: lib/logitech_receiver/settings_templates.py:1319 msgid "Swipe two fingers from the top edge" msgstr "Deslizar dois dedos da borda superior" -#: lib/logitech_receiver/settings_templates.py:167 -#: lib/logitech_receiver/settings_templates.py:171 +#: lib/logitech_receiver/settings_templates.py:1320 +#: lib/logitech_receiver/settings_templates.py:1324 msgid "Pinch to zoom out; spread to zoom in." msgstr "Aproximar os dedos para diminuir o zoom; afastá-los para aumentar." -#: lib/logitech_receiver/settings_templates.py:167 +#: lib/logitech_receiver/settings_templates.py:1320 msgid "Zoom with two fingers." msgstr "Fazer zoom com dois dedos." -#: lib/logitech_receiver/settings_templates.py:168 +#: lib/logitech_receiver/settings_templates.py:1321 msgid "Pinch to zoom out." msgstr "Aproximar os dedos para diminuir o zoom." -#: lib/logitech_receiver/settings_templates.py:169 +#: lib/logitech_receiver/settings_templates.py:1322 msgid "Spread to zoom in." msgstr "Afastar os dedos para aumentar o zoom." -#: lib/logitech_receiver/settings_templates.py:170 +#: lib/logitech_receiver/settings_templates.py:1323 msgid "Zoom with three fingers." msgstr "Fazer zoom com três dedos." -#: lib/logitech_receiver/settings_templates.py:171 +#: lib/logitech_receiver/settings_templates.py:1324 msgid "Zoom with two fingers" msgstr "Fazer zoom com dois dedos" -#: lib/logitech_receiver/settings_templates.py:189 +#: lib/logitech_receiver/settings_templates.py:1342 msgid "Pixel zone" msgstr "Região de pixels" -#: lib/logitech_receiver/settings_templates.py:190 +#: lib/logitech_receiver/settings_templates.py:1343 msgid "Ratio zone" msgstr "Região de proporção" -#: lib/logitech_receiver/settings_templates.py:191 +#: lib/logitech_receiver/settings_templates.py:1344 msgid "Scale factor" msgstr "Fator de escala" -#: lib/logitech_receiver/settings_templates.py:191 +#: lib/logitech_receiver/settings_templates.py:1344 msgid "Sets the cursor speed." msgstr "Define a velocidade do cursor." -#: lib/logitech_receiver/settings_templates.py:195 +#: lib/logitech_receiver/settings_templates.py:1348 msgid "Left" msgstr "Esquerda" -#: lib/logitech_receiver/settings_templates.py:195 +#: lib/logitech_receiver/settings_templates.py:1348 msgid "Left-most coordinate." msgstr "Coordenada do ponto mais à esquerda." -#: lib/logitech_receiver/settings_templates.py:196 -msgid "Top-most coordinate." -msgstr "Coordenada do ponto mais acima." +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom" +msgstr "Fundo" -#: lib/logitech_receiver/settings_templates.py:196 -msgid "top" -msgstr "topo" +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom coordinate." +msgstr "Coordenada inferior." -#: lib/logitech_receiver/settings_templates.py:197 +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width" +msgstr "Largura" + +#: lib/logitech_receiver/settings_templates.py:1350 msgid "Width." msgstr "Largura." -#: lib/logitech_receiver/settings_templates.py:197 -msgid "width" -msgstr "largura" +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height" +msgstr "Altura" -#: lib/logitech_receiver/settings_templates.py:198 +#: lib/logitech_receiver/settings_templates.py:1351 msgid "Height." msgstr "Altura." -#: lib/logitech_receiver/settings_templates.py:198 -msgid "height" -msgstr "altura" - -#: lib/logitech_receiver/settings_templates.py:199 +#: lib/logitech_receiver/settings_templates.py:1352 msgid "Cursor speed." msgstr "Velocidade do cursor." -#: lib/logitech_receiver/settings_templates.py:199 +#: lib/logitech_receiver/settings_templates.py:1352 msgid "Scale" msgstr "Escala" -#: lib/logitech_receiver/settings_templates.py:202 +#: lib/logitech_receiver/settings_templates.py:1358 +msgid "Gestures" +msgstr "Gestos" + +#: lib/logitech_receiver/settings_templates.py:1359 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Ajustar o comportamento do mouse/touchpad." + +#: lib/logitech_receiver/settings_templates.py:1375 +msgid "Gestures Diversion" +msgstr "Desvio de Gesto" + +#: lib/logitech_receiver/settings_templates.py:1376 +msgid "Divert mouse/touchpad gestures." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1392 +msgid "Gesture params" +msgstr "Parâmetros de gestos" + +#: lib/logitech_receiver/settings_templates.py:1393 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Alterar parâmetros numéricos de um mouse/touchpad." + +#: lib/logitech_receiver/settings_templates.py:1417 +msgid "M-Key LEDs" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1419 +msgid "Control the M-Key LEDs." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1423 +#: lib/logitech_receiver/settings_templates.py:1454 +msgid "May need G Keys diverted to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1429 #, python-format -msgid "Disables the %s key." -msgstr "Desativa a tecla %s." +msgid "Lights up the %s key." +msgstr "Ilumina a tecla %s." -#: lib/logitech_receiver/settings_templates.py:521 -#: lib/logitech_receiver/settings_templates.py:573 -msgid "Off" -msgstr "Desativado" +#: lib/logitech_receiver/settings_templates.py:1448 +msgid "MR-Key LED" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Diverted" -msgstr "Desviado" +#: lib/logitech_receiver/settings_templates.py:1450 +msgid "Control the MR-Key LED." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Regular" -msgstr "Regular" +#: lib/logitech_receiver/settings_templates.py:1471 +msgid "Persistent Key/Button Mapping" +msgstr "" -#: lib/logitech_receiver/status.py:109 -msgid "No paired devices." -msgstr "Nenhum dispositivo emparelhado." +#: lib/logitech_receiver/settings_templates.py:1473 +msgid "Permanently change the mapping for the key or button." +msgstr "Alterar permanentemente o mapeamento da tecla ou do botão." -#: lib/logitech_receiver/status.py:110 lib/solaar/ui/window.py:623 -#, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s dispositivo emparelhado." -msgstr[1] "%(count)s dispositivos emparelhados." +#: lib/logitech_receiver/settings_templates.py:1475 +msgid "" +"Changing important keys or buttons (such as for the left mouse button) can " +"result in an unusable system." +msgstr "" +"Alterar teclas ou botões importantes (como o botão esquerdo do mouse) pode " +"tornar o sistema inutilizável." -#: lib/logitech_receiver/status.py:165 -#, python-format -msgid "Battery: %(level)s" -msgstr "Bateria: %(level)s" +#: lib/logitech_receiver/settings_templates.py:1532 +msgid "Sidetone" +msgstr "" -#: lib/logitech_receiver/status.py:167 -#, python-format -msgid "Battery: %(percent)d%%" -msgstr "Bateria: %(percent)d%%" +#: lib/logitech_receiver/settings_templates.py:1533 +msgid "Set sidetone level." +msgstr "" -#: lib/logitech_receiver/status.py:179 -#, python-format -msgid "Lighting: %(level)s lux" -msgstr "Iluminação: %(level)s lux" +#: lib/logitech_receiver/settings_templates.py:1542 +msgid "Equalizer" +msgstr "Equalizador" -#: lib/logitech_receiver/status.py:234 -#, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "Bateria: %(level)s (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:1543 +msgid "Set equalizer levels." +msgstr "" -#: lib/logitech_receiver/status.py:236 -#, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "Bateria: %(percent)d%% (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:1565 +msgid "Hz" +msgstr "Hz" -#: lib/solaar/ui/__init__.py:52 +#: lib/logitech_receiver/settings_templates.py:1571 +msgid "Power Management" +msgstr "Gerenciamento de energia" + +#: lib/logitech_receiver/settings_templates.py:1572 +msgid "Power off in minutes (0 for never)." +msgstr "Desligamento em minutos (0 para nunca)." + +#: lib/logitech_receiver/settings_templates.py:1584 +msgid "Brightness Control" +msgstr "Controle de brilho" + +#: lib/logitech_receiver/settings_templates.py:1585 +msgid "Control overall brightness" +msgstr "Controle o brilho geral" + +#: lib/logitech_receiver/settings_templates.py:1628 +#: lib/logitech_receiver/settings_templates.py:1681 +msgid "LED Control" +msgstr "LED Control" + +#: lib/logitech_receiver/settings_templates.py:1629 +#: lib/logitech_receiver/settings_templates.py:1682 +msgid "Switch control of LED zones between device and Solaar" +msgstr "Controle as zonas de LED alternando entre o dispositivo e o Solaar" + +#: lib/logitech_receiver/settings_templates.py:1644 +#: lib/logitech_receiver/settings_templates.py:1692 +msgid "LED Zone Effects" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "LED Control needs to be set to Solaar to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "Set effect for LED Zone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1647 +msgid "Color" +msgstr "Cor" + +#: lib/logitech_receiver/settings_templates.py:1648 +msgid "Speed" +msgstr "Velocidade" + +#: lib/logitech_receiver/settings_templates.py:1649 +msgid "Period" +msgstr "Período" + +#: lib/logitech_receiver/settings_templates.py:1650 +msgid "Intensity" +msgstr "Intensity" + +#: lib/logitech_receiver/settings_templates.py:1651 +msgid "Ramp" +msgstr "Rampa" + +#: lib/logitech_receiver/settings_templates.py:1666 +msgid "LEDs" +msgstr "LEDs" + +#: lib/logitech_receiver/settings_templates.py:1703 +msgid "Per-key Lighting" +msgstr "Iluminação por tecla" + +#: lib/logitech_receiver/settings_templates.py:1704 +msgid "Control per-key lighting." +msgstr "Control per-key lighting." + +#: lib/logitech_receiver/settings_templates.py:1786 +msgid "Force Sensing Buttons" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1787 +msgid "Change the force required to activate button." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1804 +msgid "Force Sensing Button" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1814 +msgid "Haptic Feeback Level" +msgstr "Nível de feedback háptico" + +#: lib/logitech_receiver/settings_templates.py:1815 +msgid "Change power of haptic feedback. (Zero to turn off.)" +msgstr "Alterar a intensidade do feedback tátil. (Zero para desligar.)" + +#: lib/logitech_receiver/settings_templates.py:1857 +msgid "Play Haptic Waveform" +msgstr "Reproduzir forma de onda háptica" + +#: lib/logitech_receiver/settings_templates.py:1858 +msgid "Tell device to play a haptic waveform." +msgstr "Diga ao dispositivo para reproduzir uma forma de onda háptica." + +#: lib/solaar/ui/__init__.py:120 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" + +#: lib/solaar/ui/about/model.py:36 +msgid "" +"Manages Logitech receivers,\n" +"keyboards, mice, and tablets." +msgstr "" +"Gerencia receptores, teclados,\n" +"mouses e tablets da Logitech." + +#: lib/solaar/ui/about/model.py:63 +msgid "Additional Programming" +msgstr "Programação adicional" + +#: lib/solaar/ui/about/model.py:64 +msgid "GUI design" +msgstr "Design da GUI" + +#: lib/solaar/ui/about/model.py:66 +msgid "Testing" +msgstr "Teste" + +#: lib/solaar/ui/about/model.py:74 +msgid "Logitech documentation" +msgstr "Documentação da Logitech" + +#: lib/solaar/ui/action.py:88 lib/solaar/ui/action.py:92 +#: lib/solaar/ui/window.py:208 +msgid "Unpair" +msgstr "Desemparelhar" + +#: lib/solaar/ui/action.py:91 lib/solaar/ui/diversion_rules.py:141 +msgid "Cancel" +msgstr "Cancelar" + +#: lib/solaar/ui/common.py:42 msgid "Permissions error" msgstr "Erro de permissão" -#: lib/solaar/ui/__init__.py:54 +#: lib/solaar/ui/common.py:44 #, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open it." -msgstr "" -"Foi encontrado um Receptor da Logitech (%s), mas sem permissão para abri-lo." - -#: lib/solaar/ui/__init__.py:55 msgid "" -"If you've just installed Solaar, try removing the receiver and plugging it " -"back in." +"Found a Logitech receiver or device (%s), but did not have permission to " +"open it." msgstr "" -"Se você acabou de instalar o Solaar, tente retirar o receptor e conectá-lo " -"novamente." +"Foi encontrado um receptor ou dispositivo Logitech (%s), mas não tinha " +"permissão para abri-lo." -#: lib/solaar/ui/__init__.py:58 +#: lib/solaar/ui/common.py:46 +msgid "" +"If you've just installed Solaar, try disconnecting the receiver or device " +"and then reconnecting it." +msgstr "" +"Se você acabou de instalar o Solaar, tente desconectar o receptor ou " +"dispositivo e, em seguida, reconectá-lo." + +#: lib/solaar/ui/common.py:49 msgid "Cannot connect to device error" msgstr "Erro - não foi possível conectar-se ao dispositivo" -#: lib/solaar/ui/__init__.py:60 +#: lib/solaar/ui/common.py:51 #, python-format msgid "" "Found a Logitech receiver or device at %s, but encountered an error " @@ -828,378 +1275,424 @@ msgstr "" "Foi encontrado um receptor ou dispositivo Logitech em %s, mas houve um erro " "ao conectar-se a ele." -#: lib/solaar/ui/__init__.py:61 +#: lib/solaar/ui/common.py:53 msgid "" -"Try removing the device and plugging it back in or turning it off and then " -"on." +"Try disconnecting the device and then reconnecting it or turning it off and " +"then on." msgstr "" -"Tente remover o dispositivo e conectá-lo novamente, ou desligá-lo e ligá-lo." +"Tente desconectar o dispositivo e reconectá-lo, ou desligá-lo e ligá-lo " +"novamente." -#: lib/solaar/ui/__init__.py:64 +#: lib/solaar/ui/common.py:56 msgid "Unpairing failed" msgstr "Desemparelhamento falhou" -#: lib/solaar/ui/__init__.py:66 +#: lib/solaar/ui/common.py:58 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Falha ao desemparelhar %{device} do %{receiver}." -#: lib/solaar/ui/__init__.py:67 +#: lib/solaar/ui/common.py:63 msgid "The receiver returned an error, with no further details." msgstr "O receptor retornou um erro, sem maiores detalhes." -#: lib/solaar/ui/about.py:39 -msgid "" -"Manages Logitech receivers,\n" -"keyboards, mice, and tablets." -msgstr "" -"Gerencia receptores, teclados,\n" -"mouses e tablets da Logitech." +#: lib/solaar/ui/config_panel.py:245 +msgid "Complete - ENTER to change" +msgstr "Completo - pressione ENTER para alterar" -#: lib/solaar/ui/about.py:47 -msgid "Additional Programming" -msgstr "Programação adicional" +#: lib/solaar/ui/config_panel.py:245 +msgid "Incomplete" +msgstr "Incompleto" -#: lib/solaar/ui/about.py:48 -msgid "GUI design" -msgstr "Design da GUI" - -#: lib/solaar/ui/about.py:50 -msgid "Testing" -msgstr "Teste" - -#: lib/solaar/ui/about.py:57 -msgid "Logitech documentation" -msgstr "Documentação da Logitech" - -#: lib/solaar/ui/action.py:70 lib/solaar/ui/window.py:328 -msgid "About %s" -msgstr "Sobre o %s" - -#: lib/solaar/ui/action.py:96 lib/solaar/ui/action.py:100 -#: lib/solaar/ui/window.py:205 -msgid "Unpair" -msgstr "Desemparelhar" - -#: lib/solaar/ui/action.py:99 lib/solaar/ui/diversion_rules.py:144 -msgid "Cancel" -msgstr "Cancelar" - -#: lib/solaar/ui/config_panel.py:358 -msgid "Changes allowed" -msgstr "Mudanças permitidas" - -#: lib/solaar/ui/config_panel.py:359 -msgid "No changes allowed" -msgstr "Mudanças não permitidas" - -#: lib/solaar/ui/config_panel.py:360 -msgid "Ignore this setting" -msgstr "Ignorar esta configuração" - -#: lib/solaar/ui/config_panel.py:401 -msgid "Working" -msgstr "Funcionando" - -#: lib/solaar/ui/config_panel.py:404 -msgid "Read/write operation failed." -msgstr "Operação de leitura/escrita falhou." - -#: lib/solaar/ui/config_panel.py:522 +#: lib/solaar/ui/config_panel.py:491 lib/solaar/ui/config_panel.py:543 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "%d valor" msgstr[1] "%d valores" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/config_panel.py:642 +msgid "Changes allowed" +msgstr "Mudanças permitidas" + +#: lib/solaar/ui/config_panel.py:643 +msgid "No changes allowed" +msgstr "Mudanças não permitidas" + +#: lib/solaar/ui/config_panel.py:644 +msgid "Ignore this setting" +msgstr "Ignorar esta configuração" + +#: lib/solaar/ui/config_panel.py:690 +msgid "Working" +msgstr "Funcionando" + +#: lib/solaar/ui/config_panel.py:693 +msgid "Read/write operation failed." +msgstr "Operação de leitura/escrita falhou." + +#: lib/solaar/ui/desktop_notifications.py:112 +msgid "unspecified reason" +msgstr "razão não especificada" + +#: lib/solaar/ui/diversion_rules.py:103 msgid "Built-in rules" msgstr "Regras predefinidas" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/diversion_rules.py:103 msgid "User-defined rules" msgstr "Regras do usuário" -#: lib/solaar/ui/diversion_rules.py:59 lib/solaar/ui/diversion_rules.py:792 +#: lib/solaar/ui/diversion_rules.py:105 lib/solaar/ui/diversion_rules.py:1126 msgid "Rule" msgstr "Regra" -#: lib/solaar/ui/diversion_rules.py:60 lib/solaar/ui/diversion_rules.py:503 -#: lib/solaar/ui/diversion_rules.py:622 +#: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:382 +#: lib/solaar/ui/diversion_rules.py:509 msgid "Sub-rule" msgstr "Sub-regra" -#: lib/solaar/ui/diversion_rules.py:62 +#: lib/solaar/ui/diversion_rules.py:108 msgid "[empty]" msgstr "[vazio]" -#: lib/solaar/ui/diversion_rules.py:85 -msgid "Solaar Rule Editor" -msgstr "Editor de Regras do Solaar" - -#: lib/solaar/ui/diversion_rules.py:135 +#: lib/solaar/ui/diversion_rules.py:132 msgid "Make changes permanent?" msgstr "Tornar as alterações permanentes?" -#: lib/solaar/ui/diversion_rules.py:140 +#: lib/solaar/ui/diversion_rules.py:137 msgid "Yes" msgstr "Sim" -#: lib/solaar/ui/diversion_rules.py:142 +#: lib/solaar/ui/diversion_rules.py:139 msgid "No" msgstr "Não" -#: lib/solaar/ui/diversion_rules.py:147 +#: lib/solaar/ui/diversion_rules.py:144 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "" "Se você escolher Não, as alterações serão perdidas quando o Solaar for " "fechado." -#: lib/solaar/ui/diversion_rules.py:195 -msgid "Save changes" -msgstr "Salvar alterações" - -#: lib/solaar/ui/diversion_rules.py:200 -msgid "Discard changes" -msgstr "Descartar alterações" - -#: lib/solaar/ui/diversion_rules.py:366 -msgid "Insert here" -msgstr "Inserir aqui" - -#: lib/solaar/ui/diversion_rules.py:368 -msgid "Insert above" -msgstr "Inserir acima" - -#: lib/solaar/ui/diversion_rules.py:370 -msgid "Insert below" -msgstr "Inserir abaixo" - -#: lib/solaar/ui/diversion_rules.py:376 -msgid "Insert new rule here" -msgstr "Inserir nova regra aqui" - -#: lib/solaar/ui/diversion_rules.py:378 -msgid "Insert new rule above" -msgstr "Inserir nova regra acima" - -#: lib/solaar/ui/diversion_rules.py:380 -msgid "Insert new rule below" -msgstr "Inserir nova regra abaixo" - -#: lib/solaar/ui/diversion_rules.py:421 +#: lib/solaar/ui/diversion_rules.py:273 msgid "Paste here" msgstr "Colar aqui" -#: lib/solaar/ui/diversion_rules.py:423 +#: lib/solaar/ui/diversion_rules.py:275 msgid "Paste above" msgstr "Colar acima" -#: lib/solaar/ui/diversion_rules.py:425 +#: lib/solaar/ui/diversion_rules.py:277 msgid "Paste below" msgstr "Colar abaixo" -#: lib/solaar/ui/diversion_rules.py:431 +#: lib/solaar/ui/diversion_rules.py:283 msgid "Paste rule here" msgstr "Colar regra aqui" -#: lib/solaar/ui/diversion_rules.py:433 +#: lib/solaar/ui/diversion_rules.py:285 msgid "Paste rule above" msgstr "Colar regra acima" -#: lib/solaar/ui/diversion_rules.py:435 +#: lib/solaar/ui/diversion_rules.py:287 msgid "Paste rule below" msgstr "Colar regra abaixo" -#: lib/solaar/ui/diversion_rules.py:439 +#: lib/solaar/ui/diversion_rules.py:291 msgid "Paste rule" msgstr "Colar regra" -#: lib/solaar/ui/diversion_rules.py:468 +#: lib/solaar/ui/diversion_rules.py:306 +msgid "Insert here" +msgstr "Inserir aqui" + +#: lib/solaar/ui/diversion_rules.py:308 +msgid "Insert above" +msgstr "Inserir acima" + +#: lib/solaar/ui/diversion_rules.py:310 +msgid "Insert below" +msgstr "Inserir abaixo" + +#: lib/solaar/ui/diversion_rules.py:316 +msgid "Insert new rule here" +msgstr "Inserir nova regra aqui" + +#: lib/solaar/ui/diversion_rules.py:318 +msgid "Insert new rule above" +msgstr "Inserir nova regra acima" + +#: lib/solaar/ui/diversion_rules.py:320 +msgid "Insert new rule below" +msgstr "Inserir nova regra abaixo" + +#: lib/solaar/ui/diversion_rules.py:347 msgid "Flatten" msgstr "Desaninhar" -#: lib/solaar/ui/diversion_rules.py:501 +#: lib/solaar/ui/diversion_rules.py:380 msgid "Insert" msgstr "Inserir" -#: lib/solaar/ui/diversion_rules.py:504 lib/solaar/ui/diversion_rules.py:624 -#: lib/solaar/ui/diversion_rules.py:835 +#: lib/solaar/ui/diversion_rules.py:383 lib/solaar/ui/diversion_rules.py:511 +#: lib/solaar/ui/diversion_rules.py:1158 msgid "Or" msgstr "Ou" -#: lib/solaar/ui/diversion_rules.py:505 lib/solaar/ui/diversion_rules.py:623 -#: lib/solaar/ui/diversion_rules.py:820 +#: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:510 +#: lib/solaar/ui/diversion_rules.py:1144 msgid "And" msgstr "E" -#: lib/solaar/ui/diversion_rules.py:507 +#: lib/solaar/ui/diversion_rules.py:386 msgid "Condition" msgstr "Condição" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:951 +#: lib/solaar/ui/diversion_rules.py:388 lib/solaar/ui/rule_conditions.py:154 msgid "Feature" msgstr "Feature" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:874 -msgid "Process" -msgstr "Processo" - -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:902 -msgid "MouseProcess" -msgstr "Processo (mouse)" - -#: lib/solaar/ui/diversion_rules.py:512 lib/solaar/ui/diversion_rules.py:984 +#: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:188 msgid "Report" msgstr "Report" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1019 +#: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:69 +msgid "Process" +msgstr "Processo" + +#: lib/solaar/ui/diversion_rules.py:391 +msgid "Mouse process" +msgstr "Processo do mouse" + +#: lib/solaar/ui/diversion_rules.py:392 lib/solaar/ui/rule_conditions.py:225 msgid "Modifiers" msgstr "Teclas modificadoras" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1066 +#: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:277 msgid "Key" msgstr "Tecla" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1111 +#: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:318 +msgid "KeyIsDown" +msgstr "Tecla Pressionada" + +#: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/diversion_rules.py:1432 +msgid "Active" +msgstr "Ativo" + +#: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1390 +#: lib/solaar/ui/diversion_rules.py:1441 lib/solaar/ui/diversion_rules.py:1487 +msgid "Device" +msgstr "Dispositivo" + +#: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1467 +msgid "Host" +msgstr "Host" + +#: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1506 +msgid "Setting" +msgstr "Configuração" + +#: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/rule_conditions.py:333 +#: lib/solaar/ui/rule_conditions.py:382 msgid "Test" msgstr "Teste" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1196 +#: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:507 +msgid "Test bytes" +msgstr "Bytes de teste" + +#: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:608 msgid "Mouse Gesture" msgstr "Gesto do mouse" -#: lib/solaar/ui/diversion_rules.py:520 +#: lib/solaar/ui/diversion_rules.py:405 msgid "Action" msgstr "Ação" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1286 +#: lib/solaar/ui/diversion_rules.py:407 lib/solaar/ui/rule_actions.py:138 msgid "Key press" msgstr "Pressionamento de tecla" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1335 +#: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:191 msgid "Mouse scroll" msgstr "Rolagem do mouse" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:1383 +#: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:256 msgid "Mouse click" msgstr "Clique do mouse" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1452 +#: lib/solaar/ui/diversion_rules.py:410 +msgid "Set" +msgstr "Definir" + +#: lib/solaar/ui/diversion_rules.py:411 lib/solaar/ui/rule_actions.py:328 msgid "Execute" msgstr "Executar" -#: lib/solaar/ui/diversion_rules.py:554 +#: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/diversion_rules.py:1189 +msgid "Later" +msgstr "Mais tarde" + +#: lib/solaar/ui/diversion_rules.py:441 msgid "Insert new rule" msgstr "Inserir nova regra" -#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/diversion_rules.py:1146 -#: lib/solaar/ui/diversion_rules.py:1236 lib/solaar/ui/diversion_rules.py:1411 +#: lib/solaar/ui/diversion_rules.py:461 lib/solaar/ui/rule_actions.py:82 +#: lib/solaar/ui/rule_actions.py:287 lib/solaar/ui/rule_conditions.py:556 msgid "Delete" msgstr "Apagar" -#: lib/solaar/ui/diversion_rules.py:596 +#: lib/solaar/ui/diversion_rules.py:483 msgid "Negate" msgstr "Negar" -#: lib/solaar/ui/diversion_rules.py:620 +#: lib/solaar/ui/diversion_rules.py:507 msgid "Wrap with" msgstr "Envolver com" -#: lib/solaar/ui/diversion_rules.py:642 +#: lib/solaar/ui/diversion_rules.py:537 msgid "Cut" msgstr "Recortar" -#: lib/solaar/ui/diversion_rules.py:657 +#: lib/solaar/ui/diversion_rules.py:553 msgid "Paste" msgstr "Colar" -#: lib/solaar/ui/diversion_rules.py:669 +#: lib/solaar/ui/diversion_rules.py:559 msgid "Copy" msgstr "Copiar" -#: lib/solaar/ui/diversion_rules.py:772 +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Solaar Rule Editor" +msgstr "Editor de Regras do Solaar" + +#: lib/solaar/ui/diversion_rules.py:668 +msgid "Save changes" +msgstr "Salvar alterações" + +#: lib/solaar/ui/diversion_rules.py:673 +msgid "Discard changes" +msgstr "Descartar alterações" + +#: lib/solaar/ui/diversion_rules.py:1104 msgid "This editor does not support the selected rule component yet." msgstr "Este editor ainda não suporta o componente de regra selecionado." -#: lib/solaar/ui/diversion_rules.py:850 +#: lib/solaar/ui/diversion_rules.py:1169 +msgid "" +"Number of seconds to delay. Delay between 0 and 1 is done with higher " +"precision." +msgstr "" +"Número de segundos de atraso. Atrasos entre 0 e 1 são calculados com maior " +"precisão." + +#: lib/solaar/ui/diversion_rules.py:1207 msgid "Not" msgstr "Não" -#: lib/solaar/ui/diversion_rules.py:1039 -msgid "Key down" -msgstr "Tecla pressionada" +#: lib/solaar/ui/diversion_rules.py:1238 +msgid "Toggle" +msgstr "Alternar" -#: lib/solaar/ui/diversion_rules.py:1042 -msgid "Key up" -msgstr "Tecla liberada" +#: lib/solaar/ui/diversion_rules.py:1239 +msgid "True" +msgstr "Verdadeiro" -#: lib/solaar/ui/diversion_rules.py:1131 -msgid "Add action" -msgstr "Adicionar ação" +#: lib/solaar/ui/diversion_rules.py:1240 +msgid "False" +msgstr "Falso" -#: lib/solaar/ui/diversion_rules.py:1224 -msgid "Add key" -msgstr "Adicionar tecla" +#: lib/solaar/ui/diversion_rules.py:1253 +msgid "Unsupported setting" +msgstr "Configuração não suportada" -#: lib/solaar/ui/diversion_rules.py:1354 -msgid "Button" -msgstr "Botão" +#: lib/solaar/ui/diversion_rules.py:1396 lib/solaar/ui/diversion_rules.py:1415 +#: lib/solaar/ui/diversion_rules.py:1493 lib/solaar/ui/diversion_rules.py:1748 +#: lib/solaar/ui/diversion_rules.py:1766 +msgid "Originating device" +msgstr "Dispositivo de origem" -#: lib/solaar/ui/diversion_rules.py:1355 -msgid "Count" -msgstr "Número" +#: lib/solaar/ui/diversion_rules.py:1428 +msgid "Device is active and its settings can be changed." +msgstr "O dispositivo está ativo e suas configurações podem ser alteradas." -#: lib/solaar/ui/diversion_rules.py:1397 -msgid "Add argument" -msgstr "Adicionar argumento" +#: lib/solaar/ui/diversion_rules.py:1437 +msgid "Device that originated the current notification." +msgstr "Dispositivo que originou a notificação atual." -#: lib/solaar/ui/notify.py:125 lib/solaar/ui/tray.py:319 -#: lib/solaar/ui/tray.py:324 lib/solaar/ui/window.py:749 -msgid "offline" -msgstr "offline" +#: lib/solaar/ui/diversion_rules.py:1450 +msgid "Name of host computer." +msgstr "Name of host computer." -#: lib/solaar/ui/pair_window.py:134 -msgid "Pairing failed" -msgstr "Emparelhamento falhou" +#: lib/solaar/ui/diversion_rules.py:1520 +msgid "Value" +msgstr "Valor" -#: lib/solaar/ui/pair_window.py:136 -msgid "Make sure your device is within range, and has a decent battery charge." -msgstr "" -"Certifique-se de que o seu dispositivo esteja dentro do alcance e, que " -"possui bateria com carga aceitável." +#: lib/solaar/ui/diversion_rules.py:1529 +msgid "Item" +msgstr "Item" -#: lib/solaar/ui/pair_window.py:138 -msgid "A new device was detected, but it is not compatible with this receiver." -msgstr "" -"Um novo dispositivo foi detectado, mas não é compatível com este receptor." +#: lib/solaar/ui/diversion_rules.py:1808 +msgid "Change setting on device" +msgstr "Alterar configuração no dispositivo" -#: lib/solaar/ui/pair_window.py:140 -msgid "More paired devices than receiver can support." -msgstr "Há mais dispositivos pareados do que o receptor suporta." +#: lib/solaar/ui/diversion_rules.py:1824 +msgid "Setting on device" +msgstr "Configuração no dispositivo" -#: lib/solaar/ui/pair_window.py:142 -msgid "No further details are available about the error." -msgstr "Sem maiores detalhes sobre o erro." - -#: lib/solaar/ui/pair_window.py:156 -msgid "Found a new device:" -msgstr "Encontrado um novo dispositivo:" - -#: lib/solaar/ui/pair_window.py:181 -msgid "The wireless link is not encrypted" -msgstr "A conexão sem fio não é criptografada" - -#: lib/solaar/ui/pair_window.py:199 +#: lib/solaar/ui/pair_window.py:44 lib/solaar/ui/pair_window.py:167 #, python-format msgid "%(receiver_name)s: pair new device" msgstr "%(receiver_name)s: new dispositivo emparelhado" -#: lib/solaar/ui/pair_window.py:206 +#: lib/solaar/ui/pair_window.py:46 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "Os receptores Bolt são compatíveis apenas com dispositivos Bolt." + +#: lib/solaar/ui/pair_window.py:48 +msgid "Press a pairing button or key until the pairing light flashes quickly." +msgstr "" +"Pressione o botão ou tecla de emparelhamento até que a luz de emparelhamento " +"pisque rapidamente." + +#: lib/solaar/ui/pair_window.py:51 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "" +"Os receptores Unifying são compatíveis apenas com dispositivos Unifying." + +#: lib/solaar/ui/pair_window.py:53 +msgid "Other receivers are only compatible with a few devices." +msgstr "Outros receptores são compatíveis apenas com alguns dispositivos." + +#: lib/solaar/ui/pair_window.py:55 +msgid "For most devices, turn on the device you want to pair." +msgstr "" +"Para a maioria dos dispositivos, ligue o dispositivo que deseja emparelhar." + +#: lib/solaar/ui/pair_window.py:56 msgid "If the device is already turned on, turn it off and on again." msgstr "Se o dispositivo já estiver ligado, desligue-o e ligue-o novamente." -#: lib/solaar/ui/pair_window.py:209 +#: lib/solaar/ui/pair_window.py:58 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" +"O dispositivo não deve estar emparelhado com um receptor ligado nas " +"proximidades." + +#: lib/solaar/ui/pair_window.py:61 +msgid "" +"For devices with multiple channels, press, hold, and release the button for " +"the channel you wish to pair\n" +"or use the channel switch button to select a channel and then press, hold, " +"and release the channel switch button." +msgstr "" + +#: lib/solaar/ui/pair_window.py:68 +msgid "The channel indicator light should be blinking rapidly." +msgstr "A luz indicadora do canal deve estar piscando rapidamente." + +#: lib/solaar/ui/pair_window.py:72 #, python-format msgid "" "\n" @@ -1218,7 +1711,7 @@ msgstr[1] "" "\n" "Este dispositivo tem %d pareamentos disponíveis." -#: lib/solaar/ui/pair_window.py:212 +#: lib/solaar/ui/pair_window.py:78 msgid "" "\n" "Cancelling at this point will not use up a pairing." @@ -1226,51 +1719,282 @@ msgstr "" "\n" "Cancelar neste ponto não usará um pareamento." -#: lib/solaar/ui/pair_window.py:215 -msgid "Turn on the device you want to pair." -msgstr "Ligue o dispositivo que você quer emparelhar." +#: lib/solaar/ui/pair_window.py:168 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "Digite a senha em %(name)s." -#: lib/solaar/ui/tray.py:61 -msgid "No Logitech receiver found" -msgstr "Nenhum receptor da Logitech encontrado" +#: lib/solaar/ui/pair_window.py:171 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "Digite %(passcode)s e então pressione a tecla Enter." -#: lib/solaar/ui/tray.py:68 lib/solaar/ui/window.py:325 +#: lib/solaar/ui/pair_window.py:176 +msgid "left" +msgstr "esquerda" + +#: lib/solaar/ui/pair_window.py:176 +msgid "right" +msgstr "direita" + +#: lib/solaar/ui/pair_window.py:178 +#, python-format +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"Pressione %(code)s\n" +"e em seguida, pressione os botões esquerdo e direito simultaneamente." + +#: lib/solaar/ui/pair_window.py:221 +msgid "The wireless link is not encrypted" +msgstr "A conexão sem fio não é criptografada" + +#: lib/solaar/ui/pair_window.py:226 +msgid "Found a new device:" +msgstr "Encontrado um novo dispositivo:" + +#: lib/solaar/ui/pair_window.py:247 +msgid "Pairing failed" +msgstr "Emparelhamento falhou" + +#: lib/solaar/ui/pair_window.py:249 +msgid "Make sure your device is within range, and has a decent battery charge." +msgstr "" +"Certifique-se de que o seu dispositivo esteja dentro do alcance e, que " +"possui bateria com carga aceitável." + +#: lib/solaar/ui/pair_window.py:251 +msgid "A new device was detected, but it is not compatible with this receiver." +msgstr "" +"Um novo dispositivo foi detectado, mas não é compatível com este receptor." + +#: lib/solaar/ui/pair_window.py:253 +msgid "More paired devices than receiver can support." +msgstr "Há mais dispositivos pareados do que o receptor suporta." + +#: lib/solaar/ui/pair_window.py:255 +msgid "No further details are available about the error." +msgstr "Sem maiores detalhes sobre o erro." + +#: lib/solaar/ui/rule_actions.py:54 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simule um clique, pressionamento ou liberação de tecla em conjunto.\n" +"No Wayland, requer acesso de gravação a /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:60 +msgid "Add key" +msgstr "Adicionar tecla" + +#: lib/solaar/ui/rule_actions.py:63 +msgid "Click" +msgstr "Clique" + +#: lib/solaar/ui/rule_actions.py:66 +msgid "Depress" +msgstr "Pressionar" + +#: lib/solaar/ui/rule_actions.py:69 +msgid "Release" +msgstr "Liberar" + +#: lib/solaar/ui/rule_actions.py:153 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simule a rolagem do mouse.\n" +"Em Wayland, é necessário ter acesso de escrita a /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:211 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simule o clique do mouse.\r\n" +"Em Wayland, é necessário ter acesso de escrita a /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:216 +msgid "Button" +msgstr "Botão" + +#: lib/solaar/ui/rule_actions.py:218 +msgid "Action (and Count, if click)" +msgstr "Ação (e contagem, se houver clique)" + +#: lib/solaar/ui/rule_actions.py:269 +msgid "Execute a command with arguments." +msgstr "Executar um comando com argumentos." + +#: lib/solaar/ui/rule_actions.py:273 +msgid "Add argument" +msgstr "Adicionar argumento" + +#: lib/solaar/ui/rule_conditions.py:52 +msgid "X11 active process. For use in X11 only." +msgstr "Processo ativo do X11. Para uso exclusivo no X11." + +#: lib/solaar/ui/rule_conditions.py:82 +msgid "X11 mouse process. For use in X11 only." +msgstr "Processo de mouse X11. Para uso exclusivo no X11." + +#: lib/solaar/ui/rule_conditions.py:99 +msgid "MouseProcess" +msgstr "Processo (mouse)" + +#: lib/solaar/ui/rule_conditions.py:123 +msgid "Feature name of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:169 +msgid "Report number of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:201 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:241 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:250 +msgid "Key down" +msgstr "Tecla pressionada" + +#: lib/solaar/ui/rule_conditions.py:253 +msgid "Key up" +msgstr "Tecla liberada" + +#: lib/solaar/ui/rule_conditions.py:293 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:331 +msgid "Test condition on notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:335 +msgid "Parameter" +msgstr "Parâmetro" + +#: lib/solaar/ui/rule_conditions.py:408 +msgid "begin (inclusive)" +msgstr "início (inclusivo)" + +#: lib/solaar/ui/rule_conditions.py:409 +msgid "end (exclusive)" +msgstr "fim (exclusivo)" + +#: lib/solaar/ui/rule_conditions.py:417 +msgid "range" +msgstr "faixa" + +#: lib/solaar/ui/rule_conditions.py:420 +msgid "minimum" +msgstr "mínimo" + +#: lib/solaar/ui/rule_conditions.py:421 +msgid "maximum" +msgstr "máximo" + +#: lib/solaar/ui/rule_conditions.py:423 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:426 lib/solaar/ui/rule_conditions.py:427 +msgid "mask" +msgstr "máscara" + +#: lib/solaar/ui/rule_conditions.py:428 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:437 +msgid "" +"Bit or range test on bytes in notification message triggering rule " +"processing." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:447 +msgid "type" +msgstr "tipo" + +#: lib/solaar/ui/rule_conditions.py:535 +msgid "" +"Mouse gesture with optional initiating button followed by zero or more mouse " +"movements." +msgstr "" +"Gesto do mouse com botão iniciador opcional, seguido por zero ou mais " +"movimentos do mouse." + +#: lib/solaar/ui/rule_conditions.py:541 +msgid "Add movement" +msgstr "Adicionar movimento" + +#: lib/solaar/ui/tray.py:55 +msgid "No supported device found" +msgstr "Nenhum dispositivo suportado encontrado" + +#: lib/solaar/ui/tray.py:60 lib/solaar/ui/window.py:328 +#, python-format +msgid "About %s" +msgstr "Sobre o %s" + +#: lib/solaar/ui/tray.py:61 lib/solaar/ui/window.py:326 +#, python-format msgid "Quit %s" msgstr "Sair do %s" -#: lib/solaar/ui/tray.py:298 lib/solaar/ui/tray.py:306 +#: lib/solaar/ui/tray.py:273 lib/solaar/ui/tray.py:281 msgid "no receiver" msgstr "nenhum receptor" -#: lib/solaar/ui/tray.py:322 +#: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:299 +#: lib/solaar/ui/window.py:728 +msgid "offline" +msgstr "offline" + +#: lib/solaar/ui/tray.py:297 msgid "no status" msgstr "sem status" -#: lib/solaar/ui/window.py:104 +#: lib/solaar/ui/window.py:110 msgid "Scanning" msgstr "Procurando" -#: lib/solaar/ui/window.py:137 lib/solaar/ui/window.py:692 +#: lib/solaar/ui/window.py:141 msgid "Battery" msgstr "Bateria" -#: lib/solaar/ui/window.py:140 +#: lib/solaar/ui/window.py:144 msgid "Wireless Link" msgstr "Conexão sem fio" -#: lib/solaar/ui/window.py:144 +#: lib/solaar/ui/window.py:148 msgid "Lighting" msgstr "Iluminação" -#: lib/solaar/ui/window.py:178 +#: lib/solaar/ui/window.py:182 msgid "Show Technical Details" msgstr "Mostrar detalhes técnicos" -#: lib/solaar/ui/window.py:194 +#: lib/solaar/ui/window.py:198 msgid "Pair new device" msgstr "Emparelhar novo dispositivo" -#: lib/solaar/ui/window.py:213 +#: lib/solaar/ui/window.py:216 msgid "Select a device" msgstr "Selecione um dispositivo" @@ -1278,65 +2002,60 @@ msgstr "Selecione um dispositivo" msgid "Rule Editor" msgstr "Editor de regras" -#: lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:522 msgid "Path" msgstr "Caminho" -#: lib/solaar/ui/window.py:544 +#: lib/solaar/ui/window.py:524 msgid "USB ID" msgstr "ID USB" -#: lib/solaar/ui/window.py:547 lib/solaar/ui/window.py:549 -#: lib/solaar/ui/window.py:569 lib/solaar/ui/window.py:571 +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 +#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 msgid "Serial" msgstr "Número de série" -#: lib/solaar/ui/window.py:553 +#: lib/solaar/ui/window.py:533 msgid "Index" msgstr "Índice" -#: lib/solaar/ui/window.py:555 +#: lib/solaar/ui/window.py:535 msgid "Wireless PID" msgstr "PID sem fio" -#: lib/solaar/ui/window.py:557 +#: lib/solaar/ui/window.py:537 msgid "Product ID" msgstr "ID do produto" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:539 msgid "Protocol" msgstr "Protocolo" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:539 msgid "Unknown" msgstr "Desconhecido" -#: lib/solaar/ui/window.py:562 -#, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d ms (%(rate_hz)dHz)" - -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:541 msgid "Polling rate" msgstr "Taxa de sondagem" -#: lib/solaar/ui/window.py:573 +#: lib/solaar/ui/window.py:548 msgid "Unit ID" msgstr "Unit ID" -#: lib/solaar/ui/window.py:584 +#: lib/solaar/ui/window.py:559 msgid "none" msgstr "nenhum" -#: lib/solaar/ui/window.py:585 +#: lib/solaar/ui/window.py:560 msgid "Notifications" msgstr "Notificações" -#: lib/solaar/ui/window.py:622 +#: lib/solaar/ui/window.py:604 msgid "No device paired." msgstr "Nenhum dispositivo emparelhado." -#: lib/solaar/ui/window.py:629 +#: lib/solaar/ui/window.py:613 #, python-format msgid "Up to %(max_count)s device can be paired to this receiver." msgid_plural "Up to %(max_count)s devices can be paired to this receiver." @@ -1344,90 +2063,91 @@ msgstr[0] "Até %(max_count)s dispositivo pode ser emparelhado neste receptor." msgstr[1] "" "Até %(max_count)s dispositivos podem ser emparelhados neste receptor." -#: lib/solaar/ui/window.py:635 +#: lib/solaar/ui/window.py:620 msgid "Only one device can be paired to this receiver." msgstr "Apenas um dispositivo pode ser emparelhado neste receptor." -#: lib/solaar/ui/window.py:639 +#: lib/solaar/ui/window.py:624 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." msgstr[0] "Este dispositivo tem %d pareamento disponível." msgstr[1] "Este dispositivo tem %d pareamentos disponíveis." -#: lib/solaar/ui/window.py:694 -msgid "Battery information unknown." -msgstr "A informação sobre a bateria é desconhecida." - -#: lib/solaar/ui/window.py:702 +#: lib/solaar/ui/window.py:681 msgid "Battery Voltage" msgstr "Tensão da Bateria" -#: lib/solaar/ui/window.py:704 +#: lib/solaar/ui/window.py:683 msgid "Voltage reported by battery" msgstr "Tensão informada pela bateria" -#: lib/solaar/ui/window.py:706 lib/solaar/ui/window.py:710 +#: lib/solaar/ui/window.py:685 msgid "Battery Level" msgstr "Nível de bateria" -#: lib/solaar/ui/window.py:708 lib/solaar/ui/window.py:712 +#: lib/solaar/ui/window.py:687 msgid "Approximate level reported by battery" msgstr "Nível aproximado informado pela bateria" -#: lib/solaar/ui/window.py:715 lib/solaar/ui/window.py:717 +#: lib/solaar/ui/window.py:694 lib/solaar/ui/window.py:696 msgid "next reported " msgstr "próximo valor informado: " -#: lib/solaar/ui/window.py:718 +#: lib/solaar/ui/window.py:697 msgid " and next level to be reported." msgstr " e o próximo nível que será informado." -#: lib/solaar/ui/window.py:723 +#: lib/solaar/ui/window.py:702 msgid "last known" msgstr "último conhecido" -#: lib/solaar/ui/window.py:731 -msgid "not encrypted" -msgstr "não criptografada" - -#: lib/solaar/ui/window.py:735 -msgid "" -"The wireless link between this device and its receiver is not encrypted.\n" -"\n" -"For pointing devices (mice, trackballs, trackpads), this is a minor security " -"issue.\n" -"\n" -"It is, however, a major security issue for text-input devices (keyboards, " -"numpads),\n" -"because typed text can be sniffed inconspicuously by 3rd parties within " -"range." -msgstr "" -"A conexão sem fio entre o dispositivo e seu receptor não é criptografada.\n" -"\n" -"Para dispositivos de apontamento (mouses, trackballs, trackpads), isto não " -"representa um grande problema de segurança.\n" -"\n" -"No entanto, é um grande problema de segurança para dispositivos de entrada " -"de texto (teclados, teclados numéricos), pois\n" -"o texto digitado pode ser capturado, de forma indetectável, por terceiros " -"que estejam dentro do alcance." - -#: lib/solaar/ui/window.py:744 +#: lib/solaar/ui/window.py:713 msgid "encrypted" msgstr "criptografada" -#: lib/solaar/ui/window.py:746 +#: lib/solaar/ui/window.py:715 msgid "The wireless link between this device and its receiver is encrypted." msgstr "A conexão sem fio entre o dispositivo e seu receptor é criptografada." -#: lib/solaar/ui/window.py:759 +#: lib/solaar/ui/window.py:717 +msgid "not encrypted" +msgstr "não criptografada" + +#: lib/solaar/ui/window.py:721 +msgid "" +"The wireless link between this device and its receiver is not encrypted.\n" +"This is a security issue for pointing devices, and a major security issue " +"for text-input devices." +msgstr "" +"A conexão sem fio entre este dispositivo e o seu receptor não está " +"criptografada.\n" +"Isso representa um problema de segurança para dispositivos apontadores e um " +"problema de segurança grave para dispositivos de entrada de texto." + +#: lib/solaar/ui/window.py:737 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d lux" -#~ msgid "DPI Sliding Ajustment" -#~ msgstr "Ajuste de DPI por deslizamento" +#~ msgid " paired devices." +#~ msgstr " dispositivos emparelhados." + +#~ msgid "%(battery_level)s" +#~ msgstr "%(battery_level)s" + +#~ msgid "%(battery_percent)d%%" +#~ msgstr "%(battery_percent)d%%" + +#, python-format +#~ msgid "%(rate)d ms (%(rate_hz)dHz)" +#~ msgstr "%(rate)d ms (%(rate_hz)dHz)" + +#~ msgid "1 paired device." +#~ msgstr "1 dispositivo emparelhado." + +#~ msgid "Add action" +#~ msgstr "Adicionar ação" #~ msgid "" #~ "Adjust the DPI by sliding the mouse horizontally while holding the DPI " @@ -1435,40 +2155,10 @@ msgstr "%(light_level)d lux" #~ msgstr "" #~ "Ajustar o DPI deslizando o mouse horizontalmente ao segurar o botão DPI." -#~ msgid "Triple tap" -#~ msgstr "Toque triplo" - -#~ msgid "Solaar depends on a udev file that is not present" -#~ msgstr "Solaar depende de um arquivo udev que não está presente." - #~ msgid "" -#~ "For more information see the Solaar installation directions\n" -#~ "at https://pwr-solaar.github.io/Solaar/installation" -#~ msgstr "" -#~ "Para mais informações, consulte as instruções de instalação do Solaar\n" -#~ "em https://pwr-solaar.github.io/Solaar/installation" - -#~ msgid "" -#~ "Shows status of devices connected\n" -#~ "through wireless Logitech receivers." -#~ msgstr "" -#~ "Mostra o status dos dispositivos conectados\n" -#~ "através de receptores sem fio da Logitech." - -#~ msgid "If the device is already turned on, turn if off and on again." -#~ msgstr "Se o dispositivo já estiver ligado, desligue-o e ligue-o novamente." - -#~ msgid "Smooth Scrolling" -#~ msgstr "Rolagem suave" - -#~ msgid "High Resolution Wheel Invert" -#~ msgstr "Roda Invertida de Alta Resolução" - -#~ msgid "High-sensitivity wheel invert mode for vertical scroll." -#~ msgstr "Roda invertida de alta sensibilidade para rolagem vertical." - -#~ msgid "Wheel Resolution" -#~ msgstr "Resolução da Roda" +#~ "Adjust the DPI by sliding the mouse horizontally while holding the button " +#~ "down." +#~ msgstr "Ajustar DPI deslizando o mouse horizontalmente ao segurar o botão." #~ msgid "" #~ "Automatically switch the mouse wheel between ratchet and freespin mode.\n" @@ -1477,11 +2167,75 @@ msgstr "%(light_level)d lux" #~ "Automaticamente alterna a roda do mouse entre modo catraca e modo livre.\n" #~ "A roda do mouse está sempre live em 0, e sempre em modo catraca em 50" -#~ msgid "The receiver was unplugged." -#~ msgstr "O receptor foi desplugado." +#~ msgid "" +#~ "Automatically switch the mouse wheel between ratchet and freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always ratcheted at 50" +#~ msgstr "" +#~ "Alternar automaticamente a roda do mouse entre o modo de catraca e o modo " +#~ "de rotação livre.\n" +#~ "A roda do mouse fica sempre livre no valor 0, e sempre no modo catraca no " +#~ "valor 50" -#~ msgid "The receiver only supports %d paired device(s)." -#~ msgstr "O receptor só suporta %d dispositivo(s) emparelhado(s)" +#~ msgid "Battery information unknown." +#~ msgstr "A informação sobre a bateria é desconhecida." + +#, python-format +#~ msgid "Battery: %(level)s" +#~ msgstr "Bateria: %(level)s" + +#, python-format +#~ msgid "Battery: %(percent)d%%" +#~ msgstr "Bateria: %(percent)d%%" + +#~ msgid "Count" +#~ msgstr "Número" + +#~ msgid "DPI Sliding Adjustment" +#~ msgstr "Ajuste de DPI por deslizamento" + +#~ msgid "DPI Sliding Ajustment" +#~ msgstr "Ajuste de DPI por deslizamento" + +#~ msgid "Divert G Keys" +#~ msgstr "Desviar teclas G" + +#~ msgid "Effectively turns off thumb scrolling in Linux." +#~ msgstr "Na prática, desativa a rolagem com o polegar no Linux." + +#~ msgid "Effectively turns off wheel scrolling in Linux." +#~ msgstr "Na prática, desativa a rolagem com a roda no Linux." + +#~ msgid "" +#~ "For more information see the Solaar installation directions\n" +#~ "at https://pwr-solaar.github.io/Solaar/installation" +#~ msgstr "" +#~ "Para mais informações, consulte as instruções de instalação do Solaar\n" +#~ "em https://pwr-solaar.github.io/Solaar/installation" + +#, python-format +#~ msgid "" +#~ "Found a Logitech Receiver (%s), but did not have permission to open it." +#~ msgstr "" +#~ "Foi encontrado um Receptor da Logitech (%s), mas sem permissão para abri-" +#~ "lo." + +#~ msgid "Found a new device" +#~ msgstr "Novo dispositivo encontrado" + +#~ msgid "Frequency of device polling, in milliseconds" +#~ msgstr "Frequência de sondagem, em milissegundos" + +#~ msgid "HID++ mode for horizontal scroll with the thumb wheel." +#~ msgstr "Modo HID++ para a rolagem horizontal com o polegar." + +#~ msgid "HID++ mode for vertical scroll with the wheel." +#~ msgstr "Modo HID++ para a rolagem vertical com a roda." + +#~ msgid "High Resolution Wheel Invert" +#~ msgstr "Roda Invertida de Alta Resolução" + +#~ msgid "High-sensitivity wheel invert mode for vertical scroll." +#~ msgstr "Roda invertida de alta sensibilidade para rolagem vertical." #~ msgid "" #~ "If the device is already turned on,\n" @@ -1490,47 +2244,153 @@ msgstr "%(light_level)d lux" #~ "Se o dispositivo já encontra-se ligado,\n" #~ "desligue-o e ligue-o novamente." -#~ msgid "USB id" -#~ msgstr "ID do USB" +#~ msgid "If the device is already turned on, turn if off and on again." +#~ msgstr "Se o dispositivo já estiver ligado, desligue-o e ligue-o novamente." -#~ msgid "%(battery_percent)d%%" -#~ msgstr "%(battery_percent)d%%" +#~ msgid "" +#~ "If you've just installed Solaar, try removing the receiver and plugging " +#~ "it back in." +#~ msgstr "" +#~ "Se você acabou de instalar o Solaar, tente retirar o receptor e conectá-" +#~ "lo novamente." -#~ msgid "%(battery_level)s" -#~ msgstr "%(battery_level)s" +#, python-format +#~ msgid "Lighting: %(level)s lux" +#~ msgstr "Iluminação: %(level)s lux" -#~ msgid "Only one device can be paired to this receiver" -#~ msgstr "Apenas um dispositivo pode ser emparelhado com este receptor" +#~ msgid "" +#~ "Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " +#~ "are otherwise ignored)." +#~ msgstr "" +#~ "Fazer as teclas G enviarem notificações HID++ GKEY (que desencadeiam " +#~ "regras do Solaar, mas são ignoradas fora disso)." -#~ msgid "paired devices" -#~ msgstr "dispositivos emparelhados" +#~ msgid "" +#~ "Make the key or button send HID++ notifications (which trigger Solaar " +#~ "rules but are otherwise ignored)." +#~ msgstr "" +#~ "Fazer as teclas ou botões enviarem notificações HID++ (que desencadeiam " +#~ "regras do Solaar, mas são ignoradas fora disso)." -#~ msgid "Up to %d devices can be paired to this receiver" -#~ msgstr "Até %d dispositivos podem ser emparelhados com este receptor" +#~ msgid "No Logitech receiver found" +#~ msgstr "Nenhum receptor da Logitech encontrado" #~ msgid "No device paired" #~ msgstr "Nenhum dispositivo emparelhado" -#~ msgid "pair new device" -#~ msgstr "emparelhar novo dispositivo" +#~ msgid "Only one device can be paired to this receiver" +#~ msgstr "Apenas um dispositivo pode ser emparelhado com este receptor" -#~ msgid "Found a new device" -#~ msgstr "Novo dispositivo encontrado" +#~ msgid "Polling Rate (ms)" +#~ msgstr "Taxa de Sondagem (ms)" + +#~ msgid "Scroll Wheel HID++ Scrolling" +#~ msgstr "Rolagem com a roda usando HID++" + +#~ msgid "Scroll Wheel Rachet" +#~ msgstr "Rolagem no modo catraca" + +#~ msgid "Send a gesture by sliding the mouse while holding the button down." +#~ msgstr "Enviar um gesto deslizando o mouse ao segurar o botão." + +#~ msgid "" +#~ "Shows status of devices connected\n" +#~ "through wireless Logitech receivers." +#~ msgstr "" +#~ "Mostra o status dos dispositivos conectados\n" +#~ "através de receptores sem fio da Logitech." + +#~ msgid "Smooth Scrolling" +#~ msgstr "Rolagem suave" + +#~ msgid "Solaar depends on a udev file that is not present" +#~ msgstr "Solaar depende de um arquivo udev que não está presente." + +#~ msgid "The receiver only supports %d paired device(s)." +#~ msgstr "O receptor só suporta %d dispositivo(s) emparelhado(s)" + +#~ msgid "The receiver was unplugged." +#~ msgstr "O receptor foi desplugado." + +#~ msgid "" +#~ "The wireless link between this device and its receiver is not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices (keyboards, " +#~ "numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties within " +#~ "range." +#~ msgstr "" +#~ "A conexão sem fio entre o dispositivo e seu receptor não é " +#~ "criptografada.\n" +#~ "\n" +#~ "Para dispositivos de apontamento (mouses, trackballs, trackpads), isto " +#~ "não representa um grande problema de segurança.\n" +#~ "\n" +#~ "No entanto, é um grande problema de segurança para dispositivos de " +#~ "entrada de texto (teclados, teclados numéricos), pois\n" +#~ "o texto digitado pode ser capturado, de forma indetectável, por terceiros " +#~ "que estejam dentro do alcance." + +#~ msgid "Thumb Wheel HID++ Scrolling" +#~ msgstr "Rolagem com o polegar usando HID++" + +#~ msgid "Top-most coordinate." +#~ msgstr "Coordenada do ponto mais acima." + +#~ msgid "Triple tap" +#~ msgstr "Toque triplo" + +#~ msgid "" +#~ "Try removing the device and plugging it back in or turning it off and " +#~ "then on." +#~ msgstr "" +#~ "Tente remover o dispositivo e conectá-lo novamente, ou desligá-lo e ligá-" +#~ "lo." + +#~ msgid "Turn illumination on or off on keyboard." +#~ msgstr "Ativar ou desativar a iluminação do teclado." + +#~ msgid "Turn on the device you want to pair." +#~ msgstr "Ligue o dispositivo que você quer emparelhar." + +#~ msgid "USB id" +#~ msgstr "ID do USB" + +#~ msgid "Up to %d devices can be paired to this receiver" +#~ msgstr "Até %d dispositivos podem ser emparelhados com este receptor" + +#~ msgid "Wheel Resolution" +#~ msgstr "Resolução da Roda" + +#~ msgid "closed" +#~ msgstr "fechado" + +#~ msgid "height" +#~ msgstr "altura" #~ msgid "lux" #~ msgstr "lux" -#~ msgid " paired devices." -#~ msgstr " dispositivos emparelhados." +#~ msgid "open" +#~ msgstr "aberto" -#~ msgid "1 paired device." -#~ msgstr "1 dispositivo emparelhado." +#~ msgid "pair new device" +#~ msgstr "emparelhar novo dispositivo" + +#~ msgid "paired devices" +#~ msgstr "dispositivos emparelhados" #~ msgid "pairing lock is " #~ msgstr "trava de emparelhamento está " -#~ msgid "open" -#~ msgstr "aberto" +#~ msgid "top" +#~ msgstr "topo" -#~ msgid "closed" -#~ msgstr "fechado" +#~ msgid "unknown" +#~ msgstr "desconhecida" + +#~ msgid "width" +#~ msgstr "largura" diff --git a/po/ro.po b/po/ro.po index b98a4291..9851512b 100644 --- a/po/ro.po +++ b/po/ro.po @@ -6,7 +6,7 @@ msgid "" msgstr "Project-Id-Version: solaar 0.9.1\n" "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2021-09-25 13:56-0400\n" + "POT-Creation-Date: 2023-12-28 17:40+0100\n" "PO-Revision-Date: 2013-07-17 20:27+0100\n" "Last-Translator: Daniel Pavel \n" "Language-Team: none\n" @@ -14,733 +14,891 @@ msgstr "Project-Id-Version: solaar 0.9.1\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" - "Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n" - "%100 < 20)) ? 1 : 2;\n" + "Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && " + "n%100 < 20)) ? 1 : 2;\n" "X-Generator: Poedit 1.5.4\n" -#: lib/logitech_receiver/base_usb.py:50 +#: lib/logitech_receiver/base_usb.py:46 +msgid "Bolt Receiver" +msgstr "" + +#: lib/logitech_receiver/base_usb.py:57 msgid "Unifying Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:58 lib/logitech_receiver/base_usb.py:68 -#: lib/logitech_receiver/base_usb.py:79 lib/logitech_receiver/base_usb.py:90 -#: lib/logitech_receiver/base_usb.py:101 +#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 +#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 +#: lib/logitech_receiver/base_usb.py:114 msgid "Nano Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:109 +#: lib/logitech_receiver/base_usb.py:124 msgid "Lightspeed Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:117 +#: lib/logitech_receiver/base_usb.py:133 msgid "EX100 Receiver 27 Mhz" msgstr "" -#: lib/logitech_receiver/device.py:133 lib/solaar/ui/window.py:693 -msgid "unknown" -msgstr "necunoscută" - -#: lib/logitech_receiver/i18n.py:38 +#: lib/logitech_receiver/i18n.py:30 msgid "empty" msgstr "descărcată" -#: lib/logitech_receiver/i18n.py:39 +#: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "aproape descărcată" -#: lib/logitech_receiver/i18n.py:40 +#: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "joasă" -#: lib/logitech_receiver/i18n.py:41 +#: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "" -#: lib/logitech_receiver/i18n.py:42 +#: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "bună" -#: lib/logitech_receiver/i18n.py:43 +#: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "plină" -#: lib/logitech_receiver/i18n.py:46 +#: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "în descarcare" -#: lib/logitech_receiver/i18n.py:47 +#: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "re-încărcare" -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:721 +#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "se încarcă" -#: lib/logitech_receiver/i18n.py:49 +#: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "" -#: lib/logitech_receiver/i18n.py:50 +#: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "aproape plină" -#: lib/logitech_receiver/i18n.py:51 +#: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "" -#: lib/logitech_receiver/i18n.py:52 +#: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "încarcare inceată" -#: lib/logitech_receiver/i18n.py:53 +#: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "baterie necorespunzătoare" -#: lib/logitech_receiver/i18n.py:54 +#: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "eroare termică" -#: lib/logitech_receiver/i18n.py:55 +#: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "" -#: lib/logitech_receiver/i18n.py:56 +#: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "" -#: lib/logitech_receiver/i18n.py:57 +#: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "" -#: lib/logitech_receiver/i18n.py:58 +#: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "" -#: lib/logitech_receiver/i18n.py:61 +#: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "" -#: lib/logitech_receiver/i18n.py:62 +#: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "periferic incompatibil" -#: lib/logitech_receiver/i18n.py:63 +#: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "prea multe periferice" -#: lib/logitech_receiver/i18n.py:64 +#: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "" -#: lib/logitech_receiver/i18n.py:67 lib/solaar/ui/window.py:580 +#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 msgid "Firmware" msgstr "" -#: lib/logitech_receiver/i18n.py:68 +#: lib/logitech_receiver/i18n.py:60 msgid "Bootloader" msgstr "" -#: lib/logitech_receiver/i18n.py:69 +#: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "" -#: lib/logitech_receiver/i18n.py:70 +#: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "" -#: lib/logitech_receiver/i18n.py:73 +#: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:74 +#: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:75 +#: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "" -#: lib/logitech_receiver/i18n.py:76 +#: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "" -#: lib/logitech_receiver/i18n.py:77 +#: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "" -#: lib/logitech_receiver/i18n.py:78 +#: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "" -#: lib/logitech_receiver/i18n.py:79 +#: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "" -#: lib/logitech_receiver/i18n.py:80 +#: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "" -#: lib/logitech_receiver/i18n.py:81 +#: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "" -#: lib/logitech_receiver/i18n.py:82 +#: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "" -#: lib/logitech_receiver/i18n.py:83 +#: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "" -#: lib/logitech_receiver/i18n.py:84 +#: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "" -#: lib/logitech_receiver/i18n.py:85 +#: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "" -#: lib/logitech_receiver/i18n.py:86 +#: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "" -#: lib/logitech_receiver/i18n.py:87 +#: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "" -#: lib/logitech_receiver/i18n.py:88 +#: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "" -#: lib/logitech_receiver/i18n.py:89 +#: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:90 +#: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:93 +#: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "" -#: lib/logitech_receiver/i18n.py:94 +#: lib/logitech_receiver/i18n.py:86 msgid "released" msgstr "" -#: lib/logitech_receiver/notifications.py:76 +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is closed" msgstr "" -#: lib/logitech_receiver/notifications.py:76 +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is open" msgstr "" -#: lib/logitech_receiver/notifications.py:160 lib/solaar/ui/notify.py:123 +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is closed" +msgstr "" + +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is open" +msgstr "" + +#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 msgid "connected" msgstr "conectat(ă)" -#: lib/logitech_receiver/notifications.py:160 +#: lib/logitech_receiver/notifications.py:224 msgid "disconnected" msgstr "" -#: lib/logitech_receiver/notifications.py:198 lib/solaar/ui/notify.py:121 +#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "deconectat(ă)" -#: lib/logitech_receiver/notifications.py:248 +#: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "a pornit" -#: lib/logitech_receiver/settings.py:526 +#: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "" -#: lib/logitech_receiver/settings.py:542 lib/logitech_receiver/settings.py:568 +#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 msgid "feature" msgstr "" -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Hand Detection" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:72 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:73 -#: lib/logitech_receiver/settings_templates.py:78 -#: lib/logitech_receiver/settings_templates.py:85 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "Senzitivitate crescută la derularea verticală cu rotița." - -#: lib/logitech_receiver/settings_templates.py:74 -msgid "Side Scrolling" -msgstr "Derulare orizontală" - -#: lib/logitech_receiver/settings_templates.py:75 -msgid "When disabled, pushing the wheel sideways sends custom button " - "events\n" - "instead of the standard side-scrolling events." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:77 -msgid "Scroll Wheel High Resolution" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:79 -msgid "Scroll Wheel Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:80 -msgid "HID++ mode for vertical scroll with the wheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:81 -msgid "Effectively turns off wheel scrolling in Linux." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:82 -msgid "Scroll Wheel Direction" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:83 -msgid "Invert direction for vertical scroll with wheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:84 -msgid "Scroll Wheel Resolution" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Frequency of device polling, in milliseconds" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Polling Rate (ms)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:87 +#: lib/logitech_receiver/settings_templates.py:139 msgid "Swap Fx function" msgstr "Inversează funcțiile Fx" -#: lib/logitech_receiver/settings_templates.py:88 +#: lib/logitech_receiver/settings_templates.py:140 msgid "When set, the F1..F12 keys will activate their special function,\n" "and you must hold the FN key to activate their standard function." msgstr "Când este activ, tastele F1..F12 vor opera funcțiile speciale,\n" "și trebuie să țineți apăsată tasta FN pentru a folosi funcțiile lor " "standard." -#: lib/logitech_receiver/settings_templates.py:90 +#: lib/logitech_receiver/settings_templates.py:142 msgid "When unset, the F1..F12 keys will activate their standard function,\n" "and you must hold the FN key to activate their special function." msgstr "Când nu este activ, tastele F1..F12 vor opera functiile standard,\n" "și trebuie să țineți apăsată tasta FN pentru a folosi funcțiile lor " "speciale." -#: lib/logitech_receiver/settings_templates.py:92 +#: lib/logitech_receiver/settings_templates.py:149 +msgid "Hand Detection" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:150 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:157 +msgid "Scroll Wheel Smooth Scrolling" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:158 +#: lib/logitech_receiver/settings_templates.py:239 +#: lib/logitech_receiver/settings_templates.py:267 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Senzitivitate crescută la derularea verticală cu rotița." + +#: lib/logitech_receiver/settings_templates.py:165 +msgid "Side Scrolling" +msgstr "Derulare orizontală" + +#: lib/logitech_receiver/settings_templates.py:167 +msgid "When disabled, pushing the wheel sideways sends custom button " + "events\n" + "instead of the standard side-scrolling events." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:177 +msgid "Sensitivity (DPI - older mice)" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:178 +#: lib/logitech_receiver/settings_templates.py:712 msgid "Mouse movement sensitivity" msgstr "" -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Sensitivity (DPI)" -msgstr "Sentivitivate (PPI)" - -#: lib/logitech_receiver/settings_templates.py:93 -msgid "Sensitivity (Pointer Speed)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:94 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:95 -msgid "Scroll Wheel Rachet" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:96 -msgid "Automatically switch the mouse wheel between ratchet and freespin " - "mode.\n" - "The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:98 +#: lib/logitech_receiver/settings_templates.py:208 +#: lib/logitech_receiver/settings_templates.py:218 +#: lib/logitech_receiver/settings_templates.py:225 msgid "Backlight" msgstr "" -#: lib/logitech_receiver/settings_templates.py:98 +#: lib/logitech_receiver/settings_templates.py:209 +#: lib/logitech_receiver/settings_templates.py:226 +msgid "Set illumination time for keyboard." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:219 msgid "Turn illumination on or off on keyboard." msgstr "" -#: lib/logitech_receiver/settings_templates.py:99 -msgid "Key/Button Actions" +#: lib/logitech_receiver/settings_templates.py:237 +msgid "Scroll Wheel High Resolution" msgstr "" -#: lib/logitech_receiver/settings_templates.py:100 -msgid "Change the action for the key or button." +#: lib/logitech_receiver/settings_templates.py:240 +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Set to ignore if scrolling is abnormally fast or slow" msgstr "" -#: lib/logitech_receiver/settings_templates.py:101 -msgid "Changing important actions (such as for the left mouse button) can " - "result in an unusable system." +#: lib/logitech_receiver/settings_templates.py:247 +#: lib/logitech_receiver/settings_templates.py:277 +msgid "Scroll Wheel Diversion" msgstr "" -#: lib/logitech_receiver/settings_templates.py:102 -msgid "Key/Button Diversion" +#: lib/logitech_receiver/settings_templates.py:249 +msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:103 -msgid "Make the key or button send HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Scroll Wheel Direction" msgstr "" -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable keys" +#: lib/logitech_receiver/settings_templates.py:257 +msgid "Invert direction for vertical scroll with wheel." msgstr "" -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable specific keyboard keys." +#: lib/logitech_receiver/settings_templates.py:265 +msgid "Scroll Wheel Resolution" msgstr "" -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Change keys to match OS." +#: lib/logitech_receiver/settings_templates.py:279 +msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Set OS" +#: lib/logitech_receiver/settings_templates.py:288 +msgid "Sensitivity (Pointer Speed)" msgstr "" -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Change Host" +#: lib/logitech_receiver/settings_templates.py:289 +msgid "Speed multiplier for mouse (256 is normal multiplier)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Switch connection to a different host" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:107 +#: lib/logitech_receiver/settings_templates.py:299 msgid "Thumb Wheel Diversion" msgstr "" -#: lib/logitech_receiver/settings_templates.py:108 -msgid "HID++ mode for horizontal scroll with the thumb wheel." +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " + "Solaar rules but are otherwise ignored)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:109 -msgid "Effectively turns off thumb scrolling in Linux." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Invert thumb wheel scroll direction." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:110 +#: lib/logitech_receiver/settings_templates.py:310 msgid "Thumb Wheel Direction" msgstr "" -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Gestures" -msgstr "Gesturi" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Tweak the mouse/touchpad behaviour." +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Invert thumb wheel scroll direction." msgstr "" -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Change numerical parameters of a mouse/touchpad." +#: lib/logitech_receiver/settings_templates.py:319 +msgid "Onboard Profiles" msgstr "" -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Gesture params" +#: lib/logitech_receiver/settings_templates.py:320 +msgid "Enable onboard profiles, which often control report rate and " + "keyboard lighting" msgstr "" -#: lib/logitech_receiver/settings_templates.py:113 -msgid "DPI Sliding Adjustment" +#: lib/logitech_receiver/settings_templates.py:330 +msgid "Polling Rate (ms)" msgstr "" -#: lib/logitech_receiver/settings_templates.py:114 -msgid "Adjust the DPI by sliding the mouse horizontally while holding the " - "button down." +#: lib/logitech_receiver/settings_templates.py:332 +msgid "Frequency of device polling, in milliseconds" msgstr "" -#: lib/logitech_receiver/settings_templates.py:115 -msgid "Mouse Gestures" +#: lib/logitech_receiver/settings_templates.py:333 +#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "May need Onboard Profiles set to Disable to be effective." msgstr "" -#: lib/logitech_receiver/settings_templates.py:116 -msgid "Send a gesture by sliding the mouse while holding the button down." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:117 +#: lib/logitech_receiver/settings_templates.py:365 msgid "Divert crown events" msgstr "" -#: lib/logitech_receiver/settings_templates.py:118 +#: lib/logitech_receiver/settings_templates.py:366 msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:119 +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:375 +msgid "Set crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:383 msgid "Divert G Keys" msgstr "" -#: lib/logitech_receiver/settings_templates.py:120 +#: lib/logitech_receiver/settings_templates.py:385 msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "" -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Performs a left click." +#: lib/logitech_receiver/settings_templates.py:386 +msgid "May also make M keys and MR key send HID++ notifications" msgstr "" -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Single tap" +#: lib/logitech_receiver/settings_templates.py:402 +msgid "Scroll Wheel Ratcheted" msgstr "" -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Performs a right click." +#: lib/logitech_receiver/settings_templates.py:403 +msgid "Switch the mouse wheel between speed-controlled ratcheting and " + "always freespin." msgstr "" -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Single tap with two fingers" +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Freespinning" msgstr "" -#: lib/logitech_receiver/settings_templates.py:125 -msgid "Single tap with three fingers" +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Ratcheted" msgstr "" -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Double tap" +#: lib/logitech_receiver/settings_templates.py:412 +msgid "Scroll Wheel Ratchet Speed" msgstr "" -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Performs a double click." +#: lib/logitech_receiver/settings_templates.py:414 +msgid "Use the mouse wheel speed to switch between ratcheted and " + "freespinning.\n" + "The mouse wheel is always ratcheted at 50." msgstr "" -#: lib/logitech_receiver/settings_templates.py:130 -msgid "Double tap with two fingers" +#: lib/logitech_receiver/settings_templates.py:463 +msgid "Key/Button Actions" msgstr "" -#: lib/logitech_receiver/settings_templates.py:131 -msgid "Double tap with three fingers" +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Change the action for the key or button." msgstr "" -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Drags items by dragging the finger after double tapping." +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Overridden by diversion." msgstr "" -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Tap and drag" +#: lib/logitech_receiver/settings_templates.py:466 +msgid "Changing important actions (such as for the left mouse button) can " + "result in an unusable system." msgstr "" -#: lib/logitech_receiver/settings_templates.py:135 -msgid "Tap and drag with two fingers" +#: lib/logitech_receiver/settings_templates.py:639 +msgid "Key/Button Diversion" msgstr "" -#: lib/logitech_receiver/settings_templates.py:136 -msgid "Drags items by dragging the fingers after double tapping." +#: lib/logitech_receiver/settings_templates.py:640 +msgid "Make the key or button send HID++ notifications (Diverted) or " + "initiate Mouse Gestures or Sliding DPI" msgstr "" -#: lib/logitech_receiver/settings_templates.py:137 -msgid "Tap and drag with three fingers" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Diverted" msgstr "" -#: lib/logitech_receiver/settings_templates.py:139 -msgid "Suppress tap and edge gestures" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +msgid "Mouse Gestures" msgstr "" -#: lib/logitech_receiver/settings_templates.py:140 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Regular" msgstr "" -#: lib/logitech_receiver/settings_templates.py:141 -msgid "Scroll with one finger" +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Sliding DPI" msgstr "" -#: lib/logitech_receiver/settings_templates.py:141 -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scrolls." +#: lib/logitech_receiver/settings_templates.py:711 +msgid "Sensitivity (DPI)" +msgstr "Sentivitivate (PPI)" + +#: lib/logitech_receiver/settings_templates.py:752 +msgid "Sensitivity Switching" msgstr "" -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scroll with two fingers" +#: lib/logitech_receiver/settings_templates.py:754 +msgid "Switch the current sensitivity and the remembered sensitivity when " + "the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current " + "sensitivity" msgstr "" -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scroll horizontally with two fingers" +#: lib/logitech_receiver/settings_templates.py:758 +msgid "Off" msgstr "" -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scrolls horizontally." +#: lib/logitech_receiver/settings_templates.py:791 +msgid "Disable keys" msgstr "" -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scroll vertically with two fingers" +#: lib/logitech_receiver/settings_templates.py:792 +msgid "Disable specific keyboard keys." msgstr "" -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scrolls vertically." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Inverts the scrolling direction." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Natural scrolling" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Enables the thumbwheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Thumbwheel" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:162 -msgid "Swipe from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:159 -msgid "Swipe from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:160 -msgid "Swipe from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:161 -msgid "Swipe from the bottom edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:163 -msgid "Swipe two fingers from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:164 -msgid "Swipe two fingers from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:165 -msgid "Swipe two fingers from the bottom edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:166 -msgid "Swipe two fingers from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:167 -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:167 -msgid "Zoom with two fingers." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:168 -msgid "Pinch to zoom out." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:169 -msgid "Spread to zoom in." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:170 -msgid "Zoom with three fingers." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Zoom with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:189 -msgid "Pixel zone" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:190 -msgid "Ratio zone" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Scale factor" -msgstr "Factor de scalare" - -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Sets the cursor speed." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left-most coordinate." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:196 -msgid "Top-most coordinate." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:196 -msgid "top" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:197 -msgid "Width." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:197 -msgid "width" -msgstr "lățime" - -#: lib/logitech_receiver/settings_templates.py:198 -msgid "Height." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:198 -msgid "height" -msgstr "înălțime" - -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Cursor speed." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Scale" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:202 +#: lib/logitech_receiver/settings_templates.py:795 #, python-format msgid "Disables the %s key." msgstr "" -#: lib/logitech_receiver/settings_templates.py:521 -#: lib/logitech_receiver/settings_templates.py:573 -msgid "Off" +#: lib/logitech_receiver/settings_templates.py:809 +#: lib/logitech_receiver/settings_templates.py:860 +msgid "Set OS" msgstr "" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Diverted" +#: lib/logitech_receiver/settings_templates.py:810 +#: lib/logitech_receiver/settings_templates.py:861 +msgid "Change keys to match OS." msgstr "" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Regular" +#: lib/logitech_receiver/settings_templates.py:873 +msgid "Change Host" msgstr "" -#: lib/logitech_receiver/status.py:109 +#: lib/logitech_receiver/settings_templates.py:874 +msgid "Switch connection to a different host" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Performs a left click." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Single tap" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Performs a right click." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Single tap with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:902 +msgid "Single tap with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Double tap" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Performs a double click." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:907 +msgid "Double tap with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:908 +msgid "Double tap with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Drags items by dragging the finger after double tapping." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Tap and drag" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Tap and drag with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:914 +msgid "Tap and drag with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Suppress tap and edge gestures" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:918 +msgid "Scroll with one finger" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scrolls." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scroll with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scroll horizontally with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scrolls horizontally." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scroll vertically with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scrolls vertically." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Inverts the scrolling direction." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Natural scrolling" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Enables the thumbwheel." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Thumbwheel" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:935 +#: lib/logitech_receiver/settings_templates.py:939 +msgid "Swipe from the top edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:936 +msgid "Swipe from the left edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:937 +msgid "Swipe from the right edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:938 +msgid "Swipe from the bottom edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:940 +msgid "Swipe two fingers from the left edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:941 +msgid "Swipe two fingers from the right edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:942 +msgid "Swipe two fingers from the bottom edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:943 +msgid "Swipe two fingers from the top edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:944 +msgid "Zoom with two fingers." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:945 +msgid "Pinch to zoom out." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:946 +msgid "Spread to zoom in." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:947 +msgid "Zoom with three fingers." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Zoom with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:966 +msgid "Pixel zone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:967 +msgid "Ratio zone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Scale factor" +msgstr "Factor de scalare" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Sets the cursor speed." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left-most coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width" +msgstr "Lățime" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height" +msgstr "Înălțime" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Cursor speed." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Scale" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:982 +msgid "Gestures" +msgstr "Gesturi" + +#: lib/logitech_receiver/settings_templates.py:983 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1000 +msgid "Gestures Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1001 +msgid "Divert mouse/touchpad gestures." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1018 +msgid "Gesture params" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1019 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1044 +msgid "M-Key LEDs" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Control the M-Key LEDs." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1077 +msgid "May need G Keys diverted to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1053 +#, python-format +msgid "Lights up the %s key." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1074 +msgid "MR-Key LED" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "Control the MR-Key LED." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1095 +msgid "Persistent Key/Button Mapping" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1097 +msgid "Permanently change the mapping for the key or button." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1098 +msgid "Changing important keys or buttons (such as for the left mouse " + "button) can result in an unusable system." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1157 +msgid "Sidetone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1158 +msgid "Set sidetone level." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1167 +msgid "Equalizer" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1168 +msgid "Set equalizer levels." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1191 +msgid "Hz" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1197 +msgid "Power Management" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1198 +msgid "Power off in minutes (0 for never)." +msgstr "" + +#: lib/logitech_receiver/status.py:114 msgid "No paired devices." msgstr "Nici un periferic contectat." -#: lib/logitech_receiver/status.py:110 lib/solaar/ui/window.py:623 +#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 #, python-format msgid "%(count)s paired device." msgid_plural "%(count)s paired devices." @@ -748,27 +906,27 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" -#: lib/logitech_receiver/status.py:165 +#: lib/logitech_receiver/status.py:170 #, python-format msgid "Battery: %(level)s" msgstr "" -#: lib/logitech_receiver/status.py:167 +#: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "" -#: lib/logitech_receiver/status.py:179 +#: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "" -#: lib/logitech_receiver/status.py:234 +#: lib/logitech_receiver/status.py:239 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "" -#: lib/logitech_receiver/status.py:236 +#: lib/logitech_receiver/status.py:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "" @@ -779,15 +937,14 @@ msgstr "Eroare de permisiuni" #: lib/solaar/ui/__init__.py:54 #, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open " - "it." -msgstr "Receptor Logitech detectat (%s), dar nu am permisiunea să-l deschid." +msgid "Found a Logitech receiver or device (%s), but did not have " + "permission to open it." +msgstr "" #: lib/solaar/ui/__init__.py:55 -msgid "If you've just installed Solaar, try removing the receiver and " - "plugging it back in." -msgstr "Dacă tocmai ați instalat Solaar, scoateți receptorul și re-" - "introduceți-l." +msgid "If you've just installed Solaar, try disconnecting the receiver or " + "device and then reconnecting it." +msgstr "" #: lib/solaar/ui/__init__.py:58 msgid "Cannot connect to device error" @@ -800,8 +957,8 @@ msgid "Found a Logitech receiver or device at %s, but encountered an error " msgstr "" #: lib/solaar/ui/__init__.py:61 -msgid "Try removing the device and plugging it back in or turning it off " - "and then on." +msgid "Try disconnecting the device and then reconnecting it or turning it " + "off and then on." msgstr "" #: lib/solaar/ui/__init__.py:64 @@ -817,61 +974,49 @@ msgstr "Deconectarea %{device} de la %{receiver} a eșuat." msgid "The receiver returned an error, with no further details." msgstr "Receptorul a semnalat o eroare, fără alte detalii." -#: lib/solaar/ui/about.py:39 +#: lib/solaar/ui/__init__.py:177 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" + +#: lib/solaar/ui/about.py:36 msgid "Manages Logitech receivers,\n" "keyboards, mice, and tablets." msgstr "" -#: lib/solaar/ui/about.py:47 +#: lib/solaar/ui/about.py:44 msgid "Additional Programming" msgstr "" -#: lib/solaar/ui/about.py:48 +#: lib/solaar/ui/about.py:45 msgid "GUI design" msgstr "Interfață grafica" -#: lib/solaar/ui/about.py:50 +#: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "Testare" -#: lib/solaar/ui/about.py:57 +#: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Documentație Logitech" -#: lib/solaar/ui/action.py:70 lib/solaar/ui/window.py:328 -msgid "About %s" -msgstr "Despre %s" - -#: lib/solaar/ui/action.py:96 lib/solaar/ui/action.py:100 -#: lib/solaar/ui/window.py:205 +#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 +#: lib/solaar/ui/window.py:197 msgid "Unpair" msgstr "Deconectează" -#: lib/solaar/ui/action.py:99 lib/solaar/ui/diversion_rules.py:144 +#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "" -#: lib/solaar/ui/config_panel.py:358 -msgid "Changes allowed" +#: lib/solaar/ui/config_panel.py:212 +msgid "Complete - ENTER to change" msgstr "" -#: lib/solaar/ui/config_panel.py:359 -msgid "No changes allowed" +#: lib/solaar/ui/config_panel.py:212 +msgid "Incomplete" msgstr "" -#: lib/solaar/ui/config_panel.py:360 -msgid "Ignore this setting" -msgstr "" - -#: lib/solaar/ui/config_panel.py:401 -msgid "Working" -msgstr "Prelucrez" - -#: lib/solaar/ui/config_panel.py:404 -msgid "Read/write operation failed." -msgstr "Operațiunea a eșuat." - -#: lib/solaar/ui/config_panel.py:522 +#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 #, python-format msgid "%d value" msgid_plural "%d values" @@ -879,293 +1024,578 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/config_panel.py:518 +msgid "Changes allowed" +msgstr "" + +#: lib/solaar/ui/config_panel.py:519 +msgid "No changes allowed" +msgstr "" + +#: lib/solaar/ui/config_panel.py:520 +msgid "Ignore this setting" +msgstr "" + +#: lib/solaar/ui/config_panel.py:565 +msgid "Working" +msgstr "Prelucrez" + +#: lib/solaar/ui/config_panel.py:568 +msgid "Read/write operation failed." +msgstr "Operațiunea a eșuat." + +#: lib/solaar/ui/diversion_rules.py:65 msgid "Built-in rules" msgstr "" -#: lib/solaar/ui/diversion_rules.py:57 +#: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" msgstr "" -#: lib/solaar/ui/diversion_rules.py:59 lib/solaar/ui/diversion_rules.py:792 +#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:60 lib/solaar/ui/diversion_rules.py:503 -#: lib/solaar/ui/diversion_rules.py:622 +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 +#: lib/solaar/ui/diversion_rules.py:636 msgid "Sub-rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:62 +#: lib/solaar/ui/diversion_rules.py:70 msgid "[empty]" msgstr "" -#: lib/solaar/ui/diversion_rules.py:85 +#: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "" -#: lib/solaar/ui/diversion_rules.py:135 +#: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "" -#: lib/solaar/ui/diversion_rules.py:140 +#: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Da" -#: lib/solaar/ui/diversion_rules.py:142 +#: lib/solaar/ui/diversion_rules.py:148 msgid "No" msgstr "" -#: lib/solaar/ui/diversion_rules.py:147 +#: lib/solaar/ui/diversion_rules.py:153 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "" -#: lib/solaar/ui/diversion_rules.py:195 +#: lib/solaar/ui/diversion_rules.py:201 msgid "Save changes" msgstr "" -#: lib/solaar/ui/diversion_rules.py:200 +#: lib/solaar/ui/diversion_rules.py:206 msgid "Discard changes" msgstr "" -#: lib/solaar/ui/diversion_rules.py:366 +#: lib/solaar/ui/diversion_rules.py:372 msgid "Insert here" msgstr "" -#: lib/solaar/ui/diversion_rules.py:368 +#: lib/solaar/ui/diversion_rules.py:374 msgid "Insert above" msgstr "" -#: lib/solaar/ui/diversion_rules.py:370 +#: lib/solaar/ui/diversion_rules.py:376 msgid "Insert below" msgstr "" -#: lib/solaar/ui/diversion_rules.py:376 +#: lib/solaar/ui/diversion_rules.py:382 msgid "Insert new rule here" msgstr "" -#: lib/solaar/ui/diversion_rules.py:378 +#: lib/solaar/ui/diversion_rules.py:384 msgid "Insert new rule above" msgstr "" -#: lib/solaar/ui/diversion_rules.py:380 +#: lib/solaar/ui/diversion_rules.py:386 msgid "Insert new rule below" msgstr "" -#: lib/solaar/ui/diversion_rules.py:421 +#: lib/solaar/ui/diversion_rules.py:427 msgid "Paste here" msgstr "" -#: lib/solaar/ui/diversion_rules.py:423 +#: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "" -#: lib/solaar/ui/diversion_rules.py:425 +#: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "" -#: lib/solaar/ui/diversion_rules.py:431 +#: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "" -#: lib/solaar/ui/diversion_rules.py:433 +#: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "" -#: lib/solaar/ui/diversion_rules.py:435 +#: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "" -#: lib/solaar/ui/diversion_rules.py:439 +#: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:468 +#: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "" -#: lib/solaar/ui/diversion_rules.py:501 +#: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "" -#: lib/solaar/ui/diversion_rules.py:504 lib/solaar/ui/diversion_rules.py:624 -#: lib/solaar/ui/diversion_rules.py:835 +#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 +#: lib/solaar/ui/diversion_rules.py:1126 msgid "Or" msgstr "" -#: lib/solaar/ui/diversion_rules.py:505 lib/solaar/ui/diversion_rules.py:623 -#: lib/solaar/ui/diversion_rules.py:820 +#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 +#: lib/solaar/ui/diversion_rules.py:1111 msgid "And" msgstr "" -#: lib/solaar/ui/diversion_rules.py:507 +#: lib/solaar/ui/diversion_rules.py:513 msgid "Condition" msgstr "" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:951 +#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:874 -msgid "Process" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:902 -msgid "MouseProcess" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:512 lib/solaar/ui/diversion_rules.py:984 +#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1019 +#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 +msgid "Process" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:518 +msgid "Mouse process" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 msgid "Modifiers" msgstr "" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1066 +#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1111 +#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 +msgid "KeyIsDown" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 +msgid "Active" +msgstr "Activ" + +#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2218 +#: lib/solaar/ui/diversion_rules.py:2270 lib/solaar/ui/diversion_rules.py:2323 +msgid "Device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +msgid "Host" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 +msgid "Setting" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 +#: lib/solaar/ui/diversion_rules.py:1526 msgid "Test" msgstr "" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1196 +#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 +msgid "Test bytes" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 msgid "Mouse Gesture" msgstr "" -#: lib/solaar/ui/diversion_rules.py:520 +#: lib/solaar/ui/diversion_rules.py:532 msgid "Action" msgstr "Acțiune" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1286 +#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 msgid "Key press" msgstr "" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1335 +#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:1383 +#: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 msgid "Mouse click" msgstr "" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1452 +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Set" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 msgid "Execute" msgstr "" -#: lib/solaar/ui/diversion_rules.py:554 +#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 +msgid "Later" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:568 msgid "Insert new rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/diversion_rules.py:1146 -#: lib/solaar/ui/diversion_rules.py:1236 lib/solaar/ui/diversion_rules.py:1411 +#: lib/solaar/ui/diversion_rules.py:588 lib/solaar/ui/diversion_rules.py:1686 +#: lib/solaar/ui/diversion_rules.py:1790 lib/solaar/ui/diversion_rules.py:1989 msgid "Delete" msgstr "" -#: lib/solaar/ui/diversion_rules.py:596 +#: lib/solaar/ui/diversion_rules.py:610 msgid "Negate" msgstr "" -#: lib/solaar/ui/diversion_rules.py:620 +#: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "" -#: lib/solaar/ui/diversion_rules.py:642 +#: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Taie" -#: lib/solaar/ui/diversion_rules.py:657 +#: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Lipește" -#: lib/solaar/ui/diversion_rules.py:669 +#: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Copiază" -#: lib/solaar/ui/diversion_rules.py:772 +#: lib/solaar/ui/diversion_rules.py:1063 msgid "This editor does not support the selected rule component yet." msgstr "" -#: lib/solaar/ui/diversion_rules.py:850 +#: lib/solaar/ui/diversion_rules.py:1138 +msgid "Number of seconds to delay." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1177 msgid "Not" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1039 +#: lib/solaar/ui/diversion_rules.py:1187 +msgid "X11 active process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1218 +msgid "X11 mouse process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1235 +msgid "MouseProcess" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1260 +msgid "Feature name of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1308 +msgid "Report number of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1342 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1383 +msgid "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1392 msgid "Key down" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1042 +#: lib/solaar/ui/diversion_rules.py:1395 msgid "Key up" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1131 -msgid "Add action" +#: lib/solaar/ui/diversion_rules.py:1436 +msgid "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." msgstr "" -#: lib/solaar/ui/diversion_rules.py:1224 +#: lib/solaar/ui/diversion_rules.py:1475 +msgid "Test condition on notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1479 +msgid "Parameter" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1542 +msgid "begin (inclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1543 +msgid "end (exclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1552 +msgid "range" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1554 +msgid "minimum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1555 +msgid "maximum" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1557 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1562 +msgid "mask" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1563 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1573 +msgid "Bit or range test on bytes in notification message triggering rule " + "processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1583 +msgid "type" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1666 +msgid "Mouse gesture with optional initiating button followed by zero or " + "more mouse movements." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1671 +msgid "Add movement" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1764 +msgid "Simulate a chorded key click or depress or release.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1769 msgid "Add key" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1354 +#: lib/solaar/ui/diversion_rules.py:1772 +msgid "Click" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1775 +msgid "Depress" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1778 +msgid "Release" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1861 +msgid "Simulate a mouse scroll.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1918 +msgid "Simulate a mouse click.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1921 msgid "Button" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1355 -msgid "Count" -msgstr "Contor" +#: lib/solaar/ui/diversion_rules.py:1922 +msgid "Count and Action" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1397 +#: lib/solaar/ui/diversion_rules.py:1972 +msgid "Execute a command with arguments." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1975 msgid "Add argument" msgstr "" -#: lib/solaar/ui/notify.py:125 lib/solaar/ui/tray.py:319 -#: lib/solaar/ui/tray.py:324 lib/solaar/ui/window.py:749 +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "Toggle" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "True" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2051 +msgid "False" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2065 +msgid "Unsupported setting" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2242 +#: lib/solaar/ui/diversion_rules.py:2328 lib/solaar/ui/diversion_rules.py:2570 +#: lib/solaar/ui/diversion_rules.py:2588 +msgid "Originating device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2256 +msgid "Device is active and its settings can be changed." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2266 +msgid "Device that originated the current notification." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2280 +msgid "Name of host computer." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2347 +msgid "Value" +msgstr "Valoare" + +#: lib/solaar/ui/diversion_rules.py:2355 +msgid "Item" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2630 +msgid "Change setting on device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2647 +msgid "Setting on device" +msgstr "" + +#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:318 +#: lib/solaar/ui/tray.py:323 lib/solaar/ui/window.py:739 msgid "offline" msgstr "inactivă" -#: lib/solaar/ui/pair_window.py:134 +#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 +#: lib/solaar/ui/pair_window.py:288 +#, python-format +msgid "%(receiver_name)s: pair new device" +msgstr "" + +#: lib/solaar/ui/pair_window.py:123 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "" + +#: lib/solaar/ui/pair_window.py:126 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "left" +msgstr "" + +#: lib/solaar/ui/pair_window.py:129 +msgid "right" +msgstr "" + +#: lib/solaar/ui/pair_window.py:131 +#, python-format +msgid "Press %(code)s\n" + "and then press left and right buttons simultaneously." +msgstr "" + +#: lib/solaar/ui/pair_window.py:188 msgid "Pairing failed" msgstr "Conectare eșuată" -#: lib/solaar/ui/pair_window.py:136 +#: lib/solaar/ui/pair_window.py:190 msgid "Make sure your device is within range, and has a decent battery " "charge." msgstr "Asigurați-vă că dispozitivul este în apropiere, iar bateria este " "încarcată." -#: lib/solaar/ui/pair_window.py:138 +#: lib/solaar/ui/pair_window.py:192 msgid "A new device was detected, but it is not compatible with this " "receiver." msgstr "A fost detectat un nou periferic, dar nu este compatibil cu acest " "receptor." -#: lib/solaar/ui/pair_window.py:140 +#: lib/solaar/ui/pair_window.py:194 msgid "More paired devices than receiver can support." msgstr "" -#: lib/solaar/ui/pair_window.py:142 +#: lib/solaar/ui/pair_window.py:196 msgid "No further details are available about the error." msgstr "Alte detalii despre eroare nu sunt disponibile." -#: lib/solaar/ui/pair_window.py:156 +#: lib/solaar/ui/pair_window.py:210 msgid "Found a new device:" msgstr "" -#: lib/solaar/ui/pair_window.py:181 +#: lib/solaar/ui/pair_window.py:235 msgid "The wireless link is not encrypted" msgstr "Legătura fără fir nu este criptată" -#: lib/solaar/ui/pair_window.py:199 -#, python-format -msgid "%(receiver_name)s: pair new device" +#: lib/solaar/ui/pair_window.py:264 +msgid "Unifying receivers are only compatible with Unifying devices." msgstr "" -#: lib/solaar/ui/pair_window.py:206 +#: lib/solaar/ui/pair_window.py:266 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:268 +msgid "Other receivers are only compatible with a few devices." +msgstr "" + +#: lib/solaar/ui/pair_window.py:270 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" + +#: lib/solaar/ui/pair_window.py:274 +msgid "Press a pairing button or key until the pairing light flashes " + "quickly." +msgstr "" + +#: lib/solaar/ui/pair_window.py:276 +msgid "You may have to first turn the device off and on again." +msgstr "" + +#: lib/solaar/ui/pair_window.py:278 +msgid "Turn on the device you want to pair." +msgstr "Porniți dispozitivul pe care doriți să-l conectați." + +#: lib/solaar/ui/pair_window.py:280 msgid "If the device is already turned on, turn it off and on again." msgstr "" -#: lib/solaar/ui/pair_window.py:209 +#: lib/solaar/ui/pair_window.py:283 #, python-format msgid "\n" "\n" @@ -1177,122 +1607,124 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" -#: lib/solaar/ui/pair_window.py:212 +#: lib/solaar/ui/pair_window.py:286 msgid "\n" "Cancelling at this point will not use up a pairing." msgstr "" -#: lib/solaar/ui/pair_window.py:215 -msgid "Turn on the device you want to pair." -msgstr "Porniți dispozitivul pe care doriți să-l conectați." +#: lib/solaar/ui/tray.py:58 +msgid "No supported device found" +msgstr "" -#: lib/solaar/ui/tray.py:61 -msgid "No Logitech receiver found" -msgstr "Nu am găsit nici un receptor Logitech" +#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#, python-format +msgid "About %s" +msgstr "Despre %s" -#: lib/solaar/ui/tray.py:68 lib/solaar/ui/window.py:325 +#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 +#, python-format msgid "Quit %s" msgstr "Ieșire %s" -#: lib/solaar/ui/tray.py:298 lib/solaar/ui/tray.py:306 +#: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "nici un receptor" -#: lib/solaar/ui/tray.py:322 +#: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "stare necunoscută" -#: lib/solaar/ui/window.py:104 +#: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "Caut" -#: lib/solaar/ui/window.py:137 lib/solaar/ui/window.py:692 +#: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Baterie" -#: lib/solaar/ui/window.py:140 +#: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Legatură fără fir" -#: lib/solaar/ui/window.py:144 +#: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Lumină" -#: lib/solaar/ui/window.py:178 +#: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Detalii tehnice" -#: lib/solaar/ui/window.py:194 +#: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Conectează periferic" -#: lib/solaar/ui/window.py:213 +#: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Selectați un dispozitiv" -#: lib/solaar/ui/window.py:331 +#: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "" -#: lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Cale" -#: lib/solaar/ui/window.py:544 +#: lib/solaar/ui/window.py:536 msgid "USB ID" msgstr "" -#: lib/solaar/ui/window.py:547 lib/solaar/ui/window.py:549 -#: lib/solaar/ui/window.py:569 lib/solaar/ui/window.py:571 +#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 msgid "Serial" msgstr "Serial" -#: lib/solaar/ui/window.py:553 +#: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Index" -#: lib/solaar/ui/window.py:555 +#: lib/solaar/ui/window.py:547 msgid "Wireless PID" msgstr "Cod WPID" -#: lib/solaar/ui/window.py:557 +#: lib/solaar/ui/window.py:549 msgid "Product ID" msgstr "ID produs" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:551 msgid "Protocol" msgstr "Protocol" -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:551 msgid "Unknown" msgstr "Necunoscut" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:554 #, python-format msgid "%(rate)d ms (%(rate_hz)dHz)" msgstr "" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:554 msgid "Polling rate" msgstr "Rată acces" -#: lib/solaar/ui/window.py:573 +#: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "" -#: lib/solaar/ui/window.py:584 +#: lib/solaar/ui/window.py:576 msgid "none" msgstr "nici una" -#: lib/solaar/ui/window.py:585 +#: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Notificări" -#: lib/solaar/ui/window.py:622 +#: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "" -#: lib/solaar/ui/window.py:629 +#: lib/solaar/ui/window.py:628 #, python-format msgid "Up to %(max_count)s device can be paired to this receiver." msgid_plural "Up to %(max_count)s devices can be paired to this receiver." @@ -1300,11 +1732,11 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" -#: lib/solaar/ui/window.py:635 +#: lib/solaar/ui/window.py:634 msgid "Only one device can be paired to this receiver." msgstr "" -#: lib/solaar/ui/window.py:639 +#: lib/solaar/ui/window.py:638 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." @@ -1312,64 +1744,54 @@ msgstr[0] "" msgstr[1] "" msgstr[2] "" -#: lib/solaar/ui/window.py:694 -msgid "Battery information unknown." -msgstr "" - -#: lib/solaar/ui/window.py:702 +#: lib/solaar/ui/window.py:692 msgid "Battery Voltage" msgstr "" -#: lib/solaar/ui/window.py:704 +#: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "" -#: lib/solaar/ui/window.py:706 lib/solaar/ui/window.py:710 +#: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "" -#: lib/solaar/ui/window.py:708 lib/solaar/ui/window.py:712 +#: lib/solaar/ui/window.py:698 msgid "Approximate level reported by battery" msgstr "" -#: lib/solaar/ui/window.py:715 lib/solaar/ui/window.py:717 +#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 msgid "next reported " msgstr "" -#: lib/solaar/ui/window.py:718 +#: lib/solaar/ui/window.py:708 msgid " and next level to be reported." msgstr "" -#: lib/solaar/ui/window.py:723 +#: lib/solaar/ui/window.py:713 msgid "last known" msgstr "ultima valoare" -#: lib/solaar/ui/window.py:731 -msgid "not encrypted" -msgstr "ne-criptată" - -#: lib/solaar/ui/window.py:735 -msgid "The wireless link between this device and its receiver is not " - "encrypted.\n" - "\n" - "For pointing devices (mice, trackballs, trackpads), this is a minor " - "security issue.\n" - "\n" - "It is, however, a major security issue for text-input devices " - "(keyboards, numpads),\n" - "because typed text can be sniffed inconspicuously by 3rd parties " - "within range." -msgstr "Legătura fără fir nu este criptată." - -#: lib/solaar/ui/window.py:744 +#: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "criptată" -#: lib/solaar/ui/window.py:746 +#: lib/solaar/ui/window.py:726 msgid "The wireless link between this device and its receiver is encrypted." msgstr "Legătura fără fir este criptată." -#: lib/solaar/ui/window.py:759 +#: lib/solaar/ui/window.py:728 +msgid "not encrypted" +msgstr "ne-criptată" + +#: lib/solaar/ui/window.py:732 +msgid "The wireless link between this device and its receiver is not " + "encrypted.\n" + "This is a security issue for pointing devices, and a major security " + "issue for text-input devices." +msgstr "" + +#: lib/solaar/ui/window.py:748 #, python-format msgid "%(light_level)d lux" msgstr "" @@ -1380,6 +1802,15 @@ msgstr "" #~ msgid "1 paired device." #~ msgstr "Un periferic contectat." +#~ msgid "Count" +#~ msgstr "Contor" + +#, python-format +#~ msgid "Found a Logitech Receiver (%s), but did not have permission " +#~ "to open it." +#~ msgstr "Receptor Logitech detectat (%s), dar nu am permisiunea să-l " +#~ "deschid." + #~ msgid "Found a new device" #~ msgstr "Periferic nou detectat" @@ -1388,6 +1819,14 @@ msgstr "" #~ msgstr "Dacă dispozitivul este deja pornit,\n" #~ "opriți-l și porniți-l din nou." +#~ msgid "If you've just installed Solaar, try removing the receiver " +#~ "and plugging it back in." +#~ msgstr "Dacă tocmai ați instalat Solaar, scoateți receptorul și re-" +#~ "introduceți-l." + +#~ msgid "No Logitech receiver found" +#~ msgstr "Nu am găsit nici un receptor Logitech" + #~ msgid "No device paired" #~ msgstr "Nici un periferic conectat" @@ -1409,6 +1848,18 @@ msgstr "" #~ msgid "The receiver was unplugged." #~ msgstr "Receptor deconectat." +#~ msgid "The wireless link between this device and its receiver is " +#~ "not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices " +#~ "(keyboards, numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties " +#~ "within range." +#~ msgstr "Legătura fără fir nu este criptată." + #~ msgid "USB id" #~ msgstr "USB" @@ -1418,6 +1869,9 @@ msgstr "" #~ msgid "closed" #~ msgstr "închis" +#~ msgid "height" +#~ msgstr "înălțime" + #~ msgid "lux" #~ msgstr "lucși" @@ -1432,3 +1886,9 @@ msgstr "" #~ msgid "pairing lock is " #~ msgstr "lacătul de contectare este " + +#~ msgid "unknown" +#~ msgstr "necunoscută" + +#~ msgid "width" +#~ msgstr "lățime" diff --git a/po/ru.po b/po/ru.po index 105679b7..5f027121 100644 --- a/po/ru.po +++ b/po/ru.po @@ -6,1721 +6,2100 @@ # Dmitry Ryazantcev , 2013-2016. # Vadim Illarionov , 2021. # Olesya Gerasimenko , 2021, 2022. -msgid "" -msgstr "" -"Project-Id-Version: solaar 1.1.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-03-25 02:04+0800\n" -"PO-Revision-Date: 2022-04-12 13:06+0300\n" -"Last-Translator: Olesya Gerasimenko \n" -"Language-Team: Basealt Translation Team\n" -"Language: ru\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<" -"=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Lokalize 21.12.3\n" +# MistificaT0r <0mistificator0@gmail.com>, 2024. +msgid "" +msgstr "Project-Id-Version: solaar 1.1.13\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2025-12-01 21:43+0300\n" + "PO-Revision-Date: 2024-04-21 01:38+0300\n" + "Last-Translator: MistificaT0r <0mistificator0@gmail.com>\n" + "Language-Team: ru\n" + "Language: ru\n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 " + "&& n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + "X-Generator: Lokalize 24.02.2\n" -#: lib/logitech_receiver/base_usb.py:47 -msgid "Bolt Receiver" -msgstr "Приёмник «Bolt»" +#: lib/logitech_receiver/base_usb.py:52 +msgid "Bolt Receiver" +msgstr "Приёмник «Bolt»" -#: lib/logitech_receiver/base_usb.py:58 -msgid "Unifying Receiver" -msgstr "Приёмник «Unifying»" +#: lib/logitech_receiver/base_usb.py:64 +msgid "Unifying Receiver" +msgstr "Приёмник «Unifying»" -#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 -#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 -#: lib/logitech_receiver/base_usb.py:114 -msgid "Nano Receiver" -msgstr "Приёмник «Nano»" +#: lib/logitech_receiver/base_usb.py:75 lib/logitech_receiver/base_usb.py:87 +#: lib/logitech_receiver/base_usb.py:100 lib/logitech_receiver/base_usb.py:113 +msgid "Nano Receiver" +msgstr "Приёмник «Nano»" -#: lib/logitech_receiver/base_usb.py:123 -msgid "Lightspeed Receiver" -msgstr "Приёмник «Lightspeed»" +#: lib/logitech_receiver/base_usb.py:125 +msgid "Lightspeed Receiver" +msgstr "Приёмник «Lightspeed»" -#: lib/logitech_receiver/base_usb.py:131 -msgid "EX100 Receiver 27 Mhz" -msgstr "Приёмник «EX100» 27 МГц" +#: lib/logitech_receiver/base_usb.py:135 +msgid "EX100 Receiver 27 Mhz" +msgstr "Приёмник «EX100» 27 МГц" + +#: lib/logitech_receiver/common.py:649 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Батарея: %(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:652 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Батарея: %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:1048 +#: lib/logitech_receiver/settings_templates.py:299 +msgid "Disabled" +msgstr "Отключено" + +#: lib/logitech_receiver/hidpp20.py:1049 +msgid "Static" +msgstr "Статичный" + +#: lib/logitech_receiver/hidpp20.py:1050 +msgid "Pulse" +msgstr "Пульс" + +#: lib/logitech_receiver/hidpp20.py:1051 +msgid "Cycle" +msgstr "Цикл" + +#: lib/logitech_receiver/hidpp20.py:1052 +msgid "Boot" +msgstr "Запуск" + +#: lib/logitech_receiver/hidpp20.py:1053 +msgid "Demo" +msgstr "Демонстрация" + +#: lib/logitech_receiver/hidpp20.py:1055 +msgid "Breathe" +msgstr "Дышать" + +#: lib/logitech_receiver/hidpp20.py:1058 +msgid "Ripple" +msgstr "Пульсация" + +#: lib/logitech_receiver/hidpp20.py:1059 +msgid "Decomposition" +msgstr "Разложение" + +#: lib/logitech_receiver/hidpp20.py:1060 +msgid "Signature1" +msgstr "Подпись1" + +#: lib/logitech_receiver/hidpp20.py:1061 +msgid "Signature2" +msgstr "Подпись2" + +#: lib/logitech_receiver/hidpp20.py:1062 +msgid "CycleS" +msgstr "Циклы" + +#: lib/logitech_receiver/hidpp20.py:1126 +msgid "Unknown Location" +msgstr "Неизвестное местоположение" + +#: lib/logitech_receiver/hidpp20.py:1127 +msgid "Primary" +msgstr "Первичный" + +#: lib/logitech_receiver/hidpp20.py:1128 +msgid "Logo" +msgstr "Логотип" + +#: lib/logitech_receiver/hidpp20.py:1129 +msgid "Left Side" +msgstr "Левая сторона" + +#: lib/logitech_receiver/hidpp20.py:1130 +msgid "Right Side" +msgstr "Правая сторона" + +#: lib/logitech_receiver/hidpp20.py:1131 +msgid "Combined" +msgstr "Комбинированный" + +#: lib/logitech_receiver/hidpp20.py:1132 +msgid "Primary 1" +msgstr "Первичный 1" + +#: lib/logitech_receiver/hidpp20.py:1133 +msgid "Primary 2" +msgstr "Первичный 2" + +#: lib/logitech_receiver/hidpp20.py:1134 +msgid "Primary 3" +msgstr "Первичный 3" + +#: lib/logitech_receiver/hidpp20.py:1135 +msgid "Primary 4" +msgstr "Первичный 4" + +#: lib/logitech_receiver/hidpp20.py:1136 +msgid "Primary 5" +msgstr "Первичный 5" + +#: lib/logitech_receiver/hidpp20.py:1137 +msgid "Primary 6" +msgstr "Первичный 6" + +#: lib/logitech_receiver/i18n.py:28 +msgid "empty" +msgstr "пусто" + +#: lib/logitech_receiver/i18n.py:29 +msgid "critical" +msgstr "критический" + +#: lib/logitech_receiver/i18n.py:30 +msgid "low" +msgstr "низкий" + +#: lib/logitech_receiver/i18n.py:31 +msgid "average" +msgstr "средний" + +#: lib/logitech_receiver/i18n.py:32 +msgid "good" +msgstr "нормальный" + +#: lib/logitech_receiver/i18n.py:33 +msgid "full" +msgstr "полный" #: lib/logitech_receiver/i18n.py:35 -msgid "empty" -msgstr "пусто" +msgid "discharging" +msgstr "разряжается" #: lib/logitech_receiver/i18n.py:36 -msgid "critical" -msgstr "критический" +msgid "recharging" +msgstr "перезаряжается" -#: lib/logitech_receiver/i18n.py:37 -msgid "low" -msgstr "низкий" +#: lib/logitech_receiver/i18n.py:37 lib/solaar/ui/window.py:700 +msgid "charging" +msgstr "заряжается" #: lib/logitech_receiver/i18n.py:38 -msgid "average" -msgstr "средний" +msgid "not charging" +msgstr "не заряжается" #: lib/logitech_receiver/i18n.py:39 -msgid "good" -msgstr "нормальный" +msgid "almost full" +msgstr "почти полная" #: lib/logitech_receiver/i18n.py:40 -msgid "full" -msgstr "полный" +msgid "charged" +msgstr "заряжено" + +#: lib/logitech_receiver/i18n.py:41 +msgid "slow recharge" +msgstr "медленная разрядка" + +#: lib/logitech_receiver/i18n.py:42 +msgid "invalid battery" +msgstr "недопустимая батарея" #: lib/logitech_receiver/i18n.py:43 -msgid "discharging" -msgstr "разряжается" +msgid "thermal error" +msgstr "тепловая ошибка" #: lib/logitech_receiver/i18n.py:44 -msgid "recharging" -msgstr "перезаряжается" +msgid "error" +msgstr "ошибка" -#: lib/logitech_receiver/i18n.py:45 lib/solaar/ui/window.py:718 -msgid "charging" -msgstr "заряжается" +#: lib/logitech_receiver/i18n.py:45 +msgid "standard" +msgstr "стандартно" #: lib/logitech_receiver/i18n.py:46 -msgid "not charging" -msgstr "не заряжается" +msgid "fast" +msgstr "быстро" #: lib/logitech_receiver/i18n.py:47 -msgid "almost full" -msgstr "почти полная" - -#: lib/logitech_receiver/i18n.py:48 -msgid "charged" -msgstr "заряжено" +msgid "slow" +msgstr "медленно" #: lib/logitech_receiver/i18n.py:49 -msgid "slow recharge" -msgstr "медленная разрядка" +msgid "device timeout" +msgstr "тайм-аут устройства" #: lib/logitech_receiver/i18n.py:50 -msgid "invalid battery" -msgstr "недопустимая батарея" +msgid "device not supported" +msgstr "устройство не поддерживается" #: lib/logitech_receiver/i18n.py:51 -msgid "thermal error" -msgstr "тепловая ошибка" +msgid "too many devices" +msgstr "слишком много устройств" #: lib/logitech_receiver/i18n.py:52 -msgid "error" -msgstr "ошибка" +msgid "sequence timeout" +msgstr "тайм-аут последовательности" -#: lib/logitech_receiver/i18n.py:53 -msgid "standard" -msgstr "стандартно" - -#: lib/logitech_receiver/i18n.py:54 -msgid "fast" -msgstr "быстро" +#: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:555 +msgid "Firmware" +msgstr "Прошивка" #: lib/logitech_receiver/i18n.py:55 -msgid "slow" -msgstr "медленно" +msgid "Bootloader" +msgstr "Загрузчик" -#: lib/logitech_receiver/i18n.py:58 -msgid "device timeout" -msgstr "тайм-аут устройства" +#: lib/logitech_receiver/i18n.py:56 +msgid "Hardware" +msgstr "Оборудование" + +#: lib/logitech_receiver/i18n.py:57 +msgid "Other" +msgstr "Другое" #: lib/logitech_receiver/i18n.py:59 -msgid "device not supported" -msgstr "устройство не поддерживается" +msgid "Left Button" +msgstr "Левая кнопка" #: lib/logitech_receiver/i18n.py:60 -msgid "too many devices" -msgstr "слишком много устройств" +msgid "Right Button" +msgstr "Правая кнопка" #: lib/logitech_receiver/i18n.py:61 -msgid "sequence timeout" -msgstr "тайм-аут последовательности" +msgid "Middle Button" +msgstr "Средняя кнопка" -#: lib/logitech_receiver/i18n.py:64 lib/solaar/ui/window.py:577 -msgid "Firmware" -msgstr "Прошивка" +#: lib/logitech_receiver/i18n.py:62 +msgid "Back Button" +msgstr "Кнопка назад" + +#: lib/logitech_receiver/i18n.py:63 +msgid "Forward Button" +msgstr "Кнопка вперёд" + +#: lib/logitech_receiver/i18n.py:64 +msgid "Mouse Gesture Button" +msgstr "Кнопка жестов" #: lib/logitech_receiver/i18n.py:65 -msgid "Bootloader" -msgstr "Загрузчик" +msgid "Smart Shift" +msgstr "Умный Переход" #: lib/logitech_receiver/i18n.py:66 -msgid "Hardware" -msgstr "Оборудование" +msgid "DPI Switch" +msgstr "Переключатель чувствительности" #: lib/logitech_receiver/i18n.py:67 -msgid "Other" -msgstr "Другое" +msgid "Left Tilt" +msgstr "Наклон влево" + +#: lib/logitech_receiver/i18n.py:68 +msgid "Right Tilt" +msgstr "Наклон вправо" + +#: lib/logitech_receiver/i18n.py:69 +msgid "Left Click" +msgstr "Щелчок левой кнопкой" #: lib/logitech_receiver/i18n.py:70 -msgid "Left Button" -msgstr "Левая кнопка" +msgid "Right Click" +msgstr "Щелчок правой кнопкой" #: lib/logitech_receiver/i18n.py:71 -msgid "Right Button" -msgstr "Правая кнопка" +msgid "Mouse Middle Button" +msgstr "Средняя кнопка мыши" #: lib/logitech_receiver/i18n.py:72 -msgid "Middle Button" -msgstr "Средняя кнопка" +msgid "Mouse Back Button" +msgstr "Кнопка мыши Назад" #: lib/logitech_receiver/i18n.py:73 -msgid "Back Button" -msgstr "Колесо назад" +msgid "Mouse Forward Button" +msgstr "Кнопка мыши Вперед" #: lib/logitech_receiver/i18n.py:74 -msgid "Forward Button" -msgstr "Колесо вперёд" +msgid "Gesture Button Navigation" +msgstr "Навигация кнопкой жестов" #: lib/logitech_receiver/i18n.py:75 -msgid "Mouse Gesture Button" -msgstr "Кнопка жестов" +msgid "Mouse Scroll Left Button" +msgstr "Кнопка прокрутки влево" #: lib/logitech_receiver/i18n.py:76 -msgid "Smart Shift" -msgstr "Smart Shift" - -#: lib/logitech_receiver/i18n.py:77 -msgid "DPI Switch" -msgstr "Переключатель чувствительности" +msgid "Mouse Scroll Right Button" +msgstr "Кнопка прокрутки вправо" #: lib/logitech_receiver/i18n.py:78 -msgid "Left Tilt" -msgstr "Наклон влево" +msgid "pressed" +msgstr "нажата" #: lib/logitech_receiver/i18n.py:79 -msgid "Right Tilt" -msgstr "Наклон вправо" +msgid "released" +msgstr "отпущена" -#: lib/logitech_receiver/i18n.py:80 -msgid "Left Click" -msgstr "Щелчок левой кнопкой" +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 +msgid "connected" +msgstr "соединено" -#: lib/logitech_receiver/i18n.py:81 -msgid "Right Click" -msgstr "Щелчок правой кнопкой" +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 +msgid "disconnected" +msgstr "разъединено" -#: lib/logitech_receiver/i18n.py:82 -msgid "Mouse Middle Button" -msgstr "Щелчок средней кнопкой" +#: lib/logitech_receiver/notifications.py:182 +msgid "unpaired" +msgstr "не сопряжено" -#: lib/logitech_receiver/i18n.py:83 -msgid "Mouse Back Button" -msgstr "Прокрутка назад" +#: lib/logitech_receiver/notifications.py:231 +msgid "powered on" +msgstr "включено" -#: lib/logitech_receiver/i18n.py:84 -msgid "Mouse Forward Button" -msgstr "Прокрутка вперёд" +#: lib/logitech_receiver/notifications.py:283 +msgid "ADC measurement notification" +msgstr "Уведомление об измерении ADC" -#: lib/logitech_receiver/i18n.py:85 -msgid "Gesture Button Navigation" -msgstr "Навигация кнопкой жестов" +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:483 +msgid "pairing lock is closed" +msgstr "сопряжение заблокировано" -#: lib/logitech_receiver/i18n.py:86 -msgid "Mouse Scroll Left Button" -msgstr "Кнопка прокрутки влево" +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:483 +msgid "pairing lock is open" +msgstr "сопряжение разблокировано" -#: lib/logitech_receiver/i18n.py:87 -msgid "Mouse Scroll Right Button" -msgstr "Кнопка прокрутки вправо" +#: lib/logitech_receiver/notifications.py:446 +msgid "discovery lock is closed" +msgstr "обнаружение заблокировано" -#: lib/logitech_receiver/i18n.py:90 -msgid "pressed" -msgstr "нажата" +#: lib/logitech_receiver/notifications.py:446 +msgid "discovery lock is open" +msgstr "обнаружение разблокировано" -#: lib/logitech_receiver/i18n.py:91 -msgid "released" -msgstr "отпущена" +#: lib/logitech_receiver/receiver.py:437 +msgid "No paired devices." +msgstr "Нет сопряжённых устройств." -#: lib/logitech_receiver/notifications.py:74 -#: lib/logitech_receiver/notifications.py:125 -msgid "pairing lock is closed" -msgstr "сопряжение заблокировано" +#: lib/logitech_receiver/receiver.py:439 lib/solaar/ui/window.py:606 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s сопряжённое устройство." +msgstr[1] "%(count)s сопряжённых устройства." +msgstr[2] "%(count)s сопряжённых устройств." -#: lib/logitech_receiver/notifications.py:74 -#: lib/logitech_receiver/notifications.py:125 -msgid "pairing lock is open" -msgstr "сопряжение разблокировано" +#: lib/logitech_receiver/settings.py:602 +msgid "register" +msgstr "зарегистрировано" -#: lib/logitech_receiver/notifications.py:91 -msgid "discovery lock is closed" -msgstr "обнаружение заблокировано" +#: lib/logitech_receiver/settings.py:616 lib/logitech_receiver/settings.py:654 +msgid "feature" +msgstr "особенность" -#: lib/logitech_receiver/notifications.py:91 -msgid "discovery lock is open" -msgstr "обнаружение разблокировано" +#: lib/logitech_receiver/settings_templates.py:138 +msgid "Swap Fx function" +msgstr "Сменить поведение Fx" -#: lib/logitech_receiver/notifications.py:223 lib/solaar/ui/notify.py:120 -msgid "connected" -msgstr "соединено" +#: lib/logitech_receiver/settings_templates.py:141 +msgid "When set, the F1..F12 keys will activate their special function,\n" + "and you must hold the FN key to activate their standard function." +msgstr "Когда включено, кнопки F1..F12 работают по дополнительным функциям,\n" + "а выбор основных производится с зажатой кнопкой «FN»." -#: lib/logitech_receiver/notifications.py:223 -msgid "disconnected" -msgstr "разъединено" +#: lib/logitech_receiver/settings_templates.py:146 +msgid "When unset, the F1..F12 keys will activate their standard function,\n" + "and you must hold the FN key to activate their special function." +msgstr "Когда выключено, кнопки F1..F12 работают по основным функциям,\n" + "а выбор дополнительных производится с зажатой кнопкой «FN»." -#: lib/logitech_receiver/notifications.py:261 lib/solaar/ui/notify.py:118 -msgid "unpaired" -msgstr "не сопряжено" +#: lib/logitech_receiver/settings_templates.py:154 +msgid "Hand Detection" +msgstr "Обнаружение рук" -#: lib/logitech_receiver/notifications.py:303 -msgid "powered on" -msgstr "включено" - -#: lib/logitech_receiver/settings.py:675 -msgid "register" -msgstr "зарегистрировано" - -#: lib/logitech_receiver/settings.py:689 lib/logitech_receiver/settings.py:717 -msgid "feature" -msgstr "особенность" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Swap Fx function" -msgstr "Сменить поведение Fx" - -#: lib/logitech_receiver/settings_templates.py:135 -msgid "" -"When set, the F1..F12 keys will activate their special function,\n" -"and you must hold the FN key to activate their standard function." -msgstr "" -"Когда включено, кнопки F1..F12 работают по дополнительным функциям,\n" -"а выбор основных производится с зажатой кнопкой «FN»." - -#: lib/logitech_receiver/settings_templates.py:137 -msgid "" -"When unset, the F1..F12 keys will activate their standard function,\n" -"and you must hold the FN key to activate their special function." -msgstr "" -"Когда выключено, кнопки F1..F12 работают по основным функциям,\n" -"а выбор дополнительных производится с зажатой кнопкой «FN»." - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Hand Detection" -msgstr "Обнаружение рук" - -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "Включать подсветку, когда руки над клавиатурой." - -#: lib/logitech_receiver/settings_templates.py:152 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "Плавная прокрутка колесом мыши" - -#: lib/logitech_receiver/settings_templates.py:153 -#: lib/logitech_receiver/settings_templates.py:234 -#: lib/logitech_receiver/settings_templates.py:262 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "Режим высокой чувствительности для вертикальной прокрутки колесом." - -#: lib/logitech_receiver/settings_templates.py:160 -msgid "Side Scrolling" -msgstr "Горизонтальная прокрутка" +#: lib/logitech_receiver/settings_templates.py:155 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Включать подсветку, когда руки над клавиатурой." #: lib/logitech_receiver/settings_templates.py:162 -msgid "" -"When disabled, pushing the wheel sideways sends custom button events\n" -"instead of the standard side-scrolling events." -msgstr "" -"Когда отключено, нажатие на колесо сбоку\n" -"вместо стандартных событий горизонтальной прокрутки\n" -"будет отправлять пользовательские события нажатия." +msgid "Scroll Wheel Smooth Scrolling" +msgstr "Плавная прокрутка колесом мыши" + +#: lib/logitech_receiver/settings_templates.py:163 +#: lib/logitech_receiver/settings_templates.py:410 +#: lib/logitech_receiver/settings_templates.py:439 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Режим высокой чувствительности для вертикальной прокрутки колесом." + +#: lib/logitech_receiver/settings_templates.py:170 +msgid "Side Scrolling" +msgstr "Горизонтальная прокрутка" #: lib/logitech_receiver/settings_templates.py:172 -msgid "Sensitivity (DPI - older mice)" -msgstr "Чувствительность (тчк/дюйм — старые мыши)" +msgid "When disabled, pushing the wheel sideways sends custom button " + "events\n" + "instead of the standard side-scrolling events." +msgstr "Когда отключено, нажатие на колесо сбоку\n" + "вместо стандартных событий горизонтальной прокрутки\n" + "будет отправлять пользовательские события нажатия." -#: lib/logitech_receiver/settings_templates.py:173 -#: lib/logitech_receiver/settings_templates.py:514 -msgid "Mouse movement sensitivity" -msgstr "Чувствительность перемещений мыши." +#: lib/logitech_receiver/settings_templates.py:182 +msgid "Sensitivity (DPI - older mice)" +msgstr "Чувствительность (тчк/дюйм — старые мыши)" -#: lib/logitech_receiver/settings_templates.py:203 -#: lib/logitech_receiver/settings_templates.py:213 -#: lib/logitech_receiver/settings_templates.py:220 -msgid "Backlight" -msgstr "Подсветка" +#: lib/logitech_receiver/settings_templates.py:183 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +msgid "Mouse movement sensitivity" +msgstr "Чувствительность перемещения мыши" -#: lib/logitech_receiver/settings_templates.py:204 -#: lib/logitech_receiver/settings_templates.py:221 -msgid "Set illumination time for keyboard." -msgstr "Настройка времени подсветки клавиатуры." +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Backlight Timed" +msgstr "Подсветка по времени" -#: lib/logitech_receiver/settings_templates.py:214 -msgid "Turn illumination on or off on keyboard." -msgstr "Переключает подсветку клавиатуры." +#: lib/logitech_receiver/settings_templates.py:257 +#: lib/logitech_receiver/settings_templates.py:397 +msgid "Set illumination time for keyboard." +msgstr "Настройка времени подсветки клавиатуры." -#: lib/logitech_receiver/settings_templates.py:232 -msgid "Scroll Wheel High Resolution" -msgstr "Высокое разрешение колеса прокрутки" +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Backlight" +msgstr "Подсветка" -#: lib/logitech_receiver/settings_templates.py:235 -#: lib/logitech_receiver/settings_templates.py:263 -msgid "Set to ignore if scrolling is abnormally fast or slow" -msgstr "Настройка игнорирования слишком быстрой или медленной прокрутки" +#: lib/logitech_receiver/settings_templates.py:269 +msgid "Illumination level on keyboard. Changes made are only applied in " + "Manual mode." +msgstr "Уровень подсветки клавиатуры. Внесенные изменения применяются только " + "в Ручном режиме." -#: lib/logitech_receiver/settings_templates.py:242 -msgid "Scroll Wheel Diversion" -msgstr "Поведение колеса прокрутки" - -#: lib/logitech_receiver/settings_templates.py:244 -msgid "Effectively turns off wheel scrolling in Linux." -msgstr "Полное отключение прокрутки колесом." - -#: lib/logitech_receiver/settings_templates.py:244 -msgid "HID++ mode for vertical scroll with the wheel." -msgstr "Режим HID++ вертикальной прокрутки колеса." - -#: lib/logitech_receiver/settings_templates.py:251 -msgid "Scroll Wheel Direction" -msgstr "Направление прокрутки" - -#: lib/logitech_receiver/settings_templates.py:252 -msgid "Invert direction for vertical scroll with wheel." -msgstr "Обратная прокрутка колесом." - -#: lib/logitech_receiver/settings_templates.py:260 -msgid "Scroll Wheel Resolution" -msgstr "Разрешение прокрутки колесом" - -#: lib/logitech_receiver/settings_templates.py:272 -msgid "Sensitivity (Pointer Speed)" -msgstr "Чувствительность (скорость указателя)" - -#: lib/logitech_receiver/settings_templates.py:273 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "Множитель скорости мыши (обычно — 256)." - -#: lib/logitech_receiver/settings_templates.py:283 -msgid "Thumb Wheel Diversion" -msgstr "Поведение бокового колеса" - -#: lib/logitech_receiver/settings_templates.py:284 -msgid "HID++ mode for horizontal scroll with the thumb wheel." -msgstr "Режим HID++ для горизонтальной прокрутки боковым колесом." - -#: lib/logitech_receiver/settings_templates.py:285 -msgid "Effectively turns off thumb scrolling in Linux." -msgstr "Полностью отключает прокрутку боковым колесом." - -#: lib/logitech_receiver/settings_templates.py:293 -msgid "Thumb Wheel Direction" -msgstr "Направление вращения бокового колеса" - -#: lib/logitech_receiver/settings_templates.py:294 -msgid "Invert thumb wheel scroll direction." -msgstr "Изменяет направление вращения бокового колеса." - -#: lib/logitech_receiver/settings_templates.py:302 -msgid "Onboard Profiles" -msgstr "Встроенные профили" +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Automatic" +msgstr "Автоматический" #: lib/logitech_receiver/settings_templates.py:303 -msgid "" -"Enable onboard profiles, which often control report rate and keyboard " -"lighting" -msgstr "" -"Включение встроенных профилей, что часто управляет частотой отчётов и" -" подсветкой клавиатуры" +msgid "Manual" +msgstr "Ручной" -#: lib/logitech_receiver/settings_templates.py:313 -msgid "Polling Rate (ms)" -msgstr "Частота опроса (мс)" +#: lib/logitech_receiver/settings_templates.py:305 +msgid "Enabled" +msgstr "Включенный" -#: lib/logitech_receiver/settings_templates.py:315 -msgid "Frequency of device polling, in milliseconds" -msgstr "Частота опроса устройства (мс)" +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Backlight Level" +msgstr "Уровень подсветки" -#: lib/logitech_receiver/settings_templates.py:316 -#: lib/logitech_receiver/settings_templates.py:1043 -msgid "May need Onboard Profiles set to Disable to be effective." -msgstr "" -"Для работы может потребоваться отключить параметр «Встроенные профили»." +#: lib/logitech_receiver/settings_templates.py:312 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "Уровень освещенности клавиатуры в ручном режиме." -#: lib/logitech_receiver/settings_templates.py:346 -msgid "Divert crown events" -msgstr "Поведение верньера" +#: lib/logitech_receiver/settings_templates.py:369 +msgid "Backlight Delay Hands Out" +msgstr "Задержка подсветки" -#: lib/logitech_receiver/settings_templates.py:347 -msgid "" -"Make crown send CROWN HID++ notifications (which trigger Solaar rules but " -"are otherwise ignored)." -msgstr "" -"Включает отправку верньером оповещения CROWN HID++\n" -"(применимо только к правилам Solaar, остальными игнорируется)." +#: lib/logitech_receiver/settings_templates.py:370 +msgid "Delay in seconds until backlight fades out with hands away from " + "keyboard." +msgstr "Задержка в секундах, пока подсветка не погаснет, если руки не будут " + "лежать на клавиатуре." -#: lib/logitech_receiver/settings_templates.py:355 -msgid "Crown smooth scroll" -msgstr "Плавная прокрутка верньера" +#: lib/logitech_receiver/settings_templates.py:378 +msgid "Backlight Delay Hands In" +msgstr "Задержка подсветки стрелок в" -#: lib/logitech_receiver/settings_templates.py:356 -msgid "Set crown smooth scroll" -msgstr "Настройка плавной прокрутки верньера" +#: lib/logitech_receiver/settings_templates.py:379 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "Задержка в секундах, пока подсветка не погаснет, если руки находятся " + "рядом с клавиатурой." -#: lib/logitech_receiver/settings_templates.py:364 -msgid "Divert G Keys" -msgstr "Отклонять G-клавиши" +#: lib/logitech_receiver/settings_templates.py:387 +msgid "Backlight Delay Powered" +msgstr "Включение подсветки с задержкой" -#: lib/logitech_receiver/settings_templates.py:366 -msgid "" -"Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " -"are otherwise ignored)." -msgstr "" -"Включает отправку G-клавишами оповещения GKEY HID++\n" -"(применимо только к правилам Solaar, остальными игнорируется)." +#: lib/logitech_receiver/settings_templates.py:388 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "Задержка в секундах до тех пор, пока подсветка не погаснет при " + "включении внешнего питания." -#: lib/logitech_receiver/settings_templates.py:367 -msgid "May also make M keys and MR key send HID++ notifications" -msgstr "" -"Также можно настроить клавиши M и клавишу MR на отправку оповещений HID++" +#: lib/logitech_receiver/settings_templates.py:396 +msgid "Backlight (Seconds)" +msgstr "Подсветка (секунды)" -#: lib/logitech_receiver/settings_templates.py:382 -msgid "Scroll Wheel Rachet" -msgstr "Трещотка колеса прокрутки" +#: lib/logitech_receiver/settings_templates.py:408 +msgid "Scroll Wheel High Resolution" +msgstr "Высокое разрешение колеса прокрутки" -#: lib/logitech_receiver/settings_templates.py:384 -msgid "" -"Automatically switch the mouse wheel between ratchet and freespin mode.\n" -"The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "" -"Баланс между трещоткой и свободным вращением колеса\n" -"(0 — полностью свободное вращение, 50 — трещотка)." +#: lib/logitech_receiver/settings_templates.py:412 +#: lib/logitech_receiver/settings_templates.py:441 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "Настройка игнорирования слишком быстрой или медленной прокрутки" -#: lib/logitech_receiver/settings_templates.py:433 -msgid "Key/Button Actions" -msgstr "Действие кнопки/клавиши" +#: lib/logitech_receiver/settings_templates.py:419 +#: lib/logitech_receiver/settings_templates.py:450 +msgid "Scroll Wheel Diversion" +msgstr "Поведение колеса прокрутки" -#: lib/logitech_receiver/settings_templates.py:435 -msgid "Change the action for the key or button." -msgstr "Изменить действие кнопки или клавиши" +#: lib/logitech_receiver/settings_templates.py:421 +msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "Заставить колесо прокрутки отправлять уведомления LOWRES_WHEEL HID++ " + "(которые запускают правила Solaar, но в остальном игнорируются)." -#: lib/logitech_receiver/settings_templates.py:436 -msgid "" -"Changing important actions (such as for the left mouse button) can result in " -"an unusable system." -msgstr "" -"(изменение важных действий — например, левой кнопки мыши —\n" -"может вывести систему из строя)." +#: lib/logitech_receiver/settings_templates.py:428 +msgid "Scroll Wheel Direction" +msgstr "Направление прокрутки" + +#: lib/logitech_receiver/settings_templates.py:429 +msgid "Invert direction for vertical scroll with wheel." +msgstr "Обратная прокрутка колесом." + +#: lib/logitech_receiver/settings_templates.py:437 +msgid "Scroll Wheel Resolution" +msgstr "Разрешение прокрутки колесом" + +#: lib/logitech_receiver/settings_templates.py:452 +msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "Заставить колесо прокрутки отправлять уведомления HIRES_WHEEL HID++ " + "(которые запускают правила Solaar, но в остальном игнорируются)." + +#: lib/logitech_receiver/settings_templates.py:461 +msgid "Sensitivity (Pointer Speed)" +msgstr "Чувствительность (скорость указателя)" + +#: lib/logitech_receiver/settings_templates.py:462 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Множитель скорости мыши (обычно - 256)." #: lib/logitech_receiver/settings_templates.py:472 -msgid "Key/Button Diversion" -msgstr "Поведение клавиш/кнопок" +msgid "Thumb Wheel Diversion" +msgstr "Поведение бокового колеса" -#: lib/logitech_receiver/settings_templates.py:473 -msgid "" -"Make the key or button send HID++ notifications (which trigger Solaar rules " -"but are otherwise ignored)." -msgstr "" -"Включает отправку кнопкой или клавишей оповещения HID++\n" -"(применимо только к правилам Solaar, остальными игнорируется)." +#: lib/logitech_receiver/settings_templates.py:474 +msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " + "Solaar rules but are otherwise ignored)." +msgstr "Заставить колесо большого пальца отправлять уведомления THUMB_WHEEL " + "HID++ (которые запускают Правила Solaar, но в остальном " + "игнорируются)." -#: lib/logitech_receiver/settings_templates.py:476 -msgid "Diverted" -msgstr "Перенаправлено" +#: lib/logitech_receiver/settings_templates.py:483 +msgid "Thumb Wheel Direction" +msgstr "Направление вращения бокового колеса" -#: lib/logitech_receiver/settings_templates.py:476 -msgid "Regular" -msgstr "Обычное" +#: lib/logitech_receiver/settings_templates.py:484 +msgid "Invert thumb wheel scroll direction." +msgstr "Изменяет направление вращения бокового колеса." -#: lib/logitech_receiver/settings_templates.py:513 -msgid "Sensitivity (DPI)" -msgstr "Чувствительность (тчк/дюйм)" +#: lib/logitech_receiver/settings_templates.py:504 +msgid "Onboard Profiles" +msgstr "Встроенные профили" -#: lib/logitech_receiver/settings_templates.py:553 -msgid "Sensitivity Switching" -msgstr "Переключение чувствительности" +#: lib/logitech_receiver/settings_templates.py:505 +msgid "Enable an onboard profile, which controls report rate, sensitivity, " + "and button actions" +msgstr "Включите встроенный профиль, который контролирует частоту отчетов, " + "чувствительность, и действия кнопок" -#: lib/logitech_receiver/settings_templates.py:555 -msgid "" -"Switch the current sensitivity and the remembered sensitivity when the key " -"or button is pressed.\n" -"If there is no remembered sensitivity, just remember the current sensitivity" -msgstr "" -"Переключение текущей чувствительности и сохранённой чувствительности при" -" нажатии клавиши или кнопки.\n" -"Если сохранённой чувствительности нет, сохраняется текущая чувствительность" +#: lib/logitech_receiver/settings_templates.py:549 +#: lib/logitech_receiver/settings_templates.py:582 +msgid "Report Rate" +msgstr "Частота отчетов" -#: lib/logitech_receiver/settings_templates.py:559 -#: lib/logitech_receiver/settings_templates.py:600 -#: lib/logitech_receiver/settings_templates.py:719 -msgid "Off" -msgstr "Откл." +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +msgid "Frequency of device movement reports" +msgstr "Частота сообщений о перемещении устройства" -#: lib/logitech_receiver/settings_templates.py:597 -msgid "DPI Sliding Adjustment" -msgstr "Регулировка чувствительности смещением" +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1421 +#: lib/logitech_receiver/settings_templates.py:1452 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "Для работы может потребоваться отключить параметр «Встроенные " + "профили»." -#: lib/logitech_receiver/settings_templates.py:598 -msgid "" -"Adjust the DPI by sliding the mouse horizontally while holding the button " -"down." -msgstr "Регулировка чувствительности смещением мыши с нажатой кнопкой." +#: lib/logitech_receiver/settings_templates.py:612 +msgid "Divert crown events" +msgstr "Поведение верньера" -#: lib/logitech_receiver/settings_templates.py:695 -msgid "Disable keys" -msgstr "Отключить клавиши" +#: lib/logitech_receiver/settings_templates.py:613 +msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "Включает отправку верньером оповещения CROWN HID++ (применимо только " + "к правилам Solaar, остальными игнорируется)." -#: lib/logitech_receiver/settings_templates.py:696 -msgid "Disable specific keyboard keys." -msgstr "Отключение определённых клавиш клавиатуры." +#: lib/logitech_receiver/settings_templates.py:621 +msgid "Crown smooth scroll" +msgstr "Плавная прокрутка верньера" -#: lib/logitech_receiver/settings_templates.py:699 -#, python-format -msgid "Disables the %s key." -msgstr "Отключение клавиши %s." +#: lib/logitech_receiver/settings_templates.py:622 +msgid "Set crown smooth scroll" +msgstr "Настройка плавной прокрутки верньера" -#: lib/logitech_receiver/settings_templates.py:714 -msgid "Mouse Gestures" -msgstr "Жесты мышью" +#: lib/logitech_receiver/settings_templates.py:630 +msgid "Divert G and M Keys" +msgstr "Перенаправление клавиш G и M" -#: lib/logitech_receiver/settings_templates.py:715 -msgid "Send a gesture by sliding the mouse while holding the button down." -msgstr "Отправлять жесты, двигая мышью с нажатой кнопкой." +#: lib/logitech_receiver/settings_templates.py:631 +msgid "Make G and M keys send HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "Заставить клавиши G и M отправлять уведомления HID++ (которые " + "запускают Solaar правила, но в остальном игнорируются)." -#: lib/logitech_receiver/settings_templates.py:814 -#: lib/logitech_receiver/settings_templates.py:862 -msgid "Set OS" -msgstr "Операционная система" +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Scroll Wheel Ratcheted" +msgstr "Колесо прокрутки с храповым механизмом" -#: lib/logitech_receiver/settings_templates.py:815 -#: lib/logitech_receiver/settings_templates.py:863 -msgid "Change keys to match OS." -msgstr "Изменяет назначение клавиш соответственно ОС." +#: lib/logitech_receiver/settings_templates.py:646 +msgid "Switch the mouse wheel between speed-controlled ratcheting and " + "always freespin." +msgstr "Переключение колеса мыши между вращением с храповым механизмом и " + "свободным вращением." -#: lib/logitech_receiver/settings_templates.py:875 -msgid "Change Host" -msgstr "Подключено к" +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Freespinning" +msgstr "Свободное вращение" -#: lib/logitech_receiver/settings_templates.py:876 -msgid "Switch connection to a different host" -msgstr "Переключает соединение на выбранный хост." +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Ratcheted" +msgstr "Вращение с храповым механизмом" -#: lib/logitech_receiver/settings_templates.py:901 -msgid "Performs a left click." -msgstr "Осуществляет щелчок левой кнопкой." +#: lib/logitech_receiver/settings_templates.py:655 +msgid "Scroll Wheel Ratchet Speed" +msgstr "Скорость вращения колеса прокрутки" -#: lib/logitech_receiver/settings_templates.py:901 -msgid "Single tap" -msgstr "Одиночное касание" +#: lib/logitech_receiver/settings_templates.py:657 +msgid "Use the mouse wheel speed to switch between ratcheted and " + "freespinning.\n" + "The mouse wheel is always ratcheted at 50." +msgstr "Используйте скорость вращения колесика мыши для переключения между " + "вращением с храповым механизмом и свободным вращением.\n" + "Колесо мыши всегда вращается с храповым механизмом на 50 градусов." -#: lib/logitech_receiver/settings_templates.py:902 -msgid "Performs a right click." -msgstr "Осуществляет щелчок правой кнопкой." +#: lib/logitech_receiver/settings_templates.py:707 +msgid "Scroll Wheel Ratchet Torque" +msgstr "Крутящий момент храпового механизма прокручивающего колеса" -#: lib/logitech_receiver/settings_templates.py:902 -msgid "Single tap with two fingers" -msgstr "Одиночное касание двумя пальцами" +#: lib/logitech_receiver/settings_templates.py:708 +msgid "Change the torque needed to overcome the ratchet." +msgstr "Измените крутящий момент, необходимый для преодоления храпового механизма." -#: lib/logitech_receiver/settings_templates.py:903 -msgid "Single tap with three fingers" -msgstr "Одиночное касание тремя пальцами" +#: lib/logitech_receiver/settings_templates.py:743 +msgid "Key/Button Actions" +msgstr "Действия с клавишами/кнопками" -#: lib/logitech_receiver/settings_templates.py:907 -msgid "Double tap" -msgstr "Двойное касание" +#: lib/logitech_receiver/settings_templates.py:745 +msgid "Change the action for the key or button." +msgstr "Изменить действие кнопки или клавиши." -#: lib/logitech_receiver/settings_templates.py:907 -msgid "Performs a double click." -msgstr "Осуществление двойного щелчка." +#: lib/logitech_receiver/settings_templates.py:747 +msgid "Overridden by diversion." +msgstr "Отменено перенаправлением." -#: lib/logitech_receiver/settings_templates.py:908 -msgid "Double tap with two fingers" -msgstr "Двойное касание двумя пальцами" - -#: lib/logitech_receiver/settings_templates.py:909 -msgid "Double tap with three fingers" -msgstr "Двойное касание тремя пальцами" - -#: lib/logitech_receiver/settings_templates.py:912 -msgid "Drags items by dragging the finger after double tapping." -msgstr "Перетаскивать объекты перемещением пальца после двойного касания." - -#: lib/logitech_receiver/settings_templates.py:912 -msgid "Tap and drag" -msgstr "Касание и перетаскивание" - -#: lib/logitech_receiver/settings_templates.py:914 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "Перетаскивать объекты перемещением пальцев после двойного касания." - -#: lib/logitech_receiver/settings_templates.py:914 -msgid "Tap and drag with two fingers" -msgstr "Касание и перетаскивание двумя пальцами" - -#: lib/logitech_receiver/settings_templates.py:915 -msgid "Tap and drag with three fingers" -msgstr "Касание и перетаскивание тремя пальцами" - -#: lib/logitech_receiver/settings_templates.py:918 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "Отключает касания и жесты по краям (то же что FN+ЩелчокЛевойКнопкой)" - -#: lib/logitech_receiver/settings_templates.py:918 -msgid "Suppress tap and edge gestures" -msgstr "Подавление касаний и жестов по краям" - -#: lib/logitech_receiver/settings_templates.py:919 -msgid "Scroll with one finger" -msgstr "Вертикальная прокрутка одним пальцем" - -#: lib/logitech_receiver/settings_templates.py:919 -#: lib/logitech_receiver/settings_templates.py:920 -#: lib/logitech_receiver/settings_templates.py:923 -msgid "Scrolls." -msgstr "Прокручивать вертикально." - -#: lib/logitech_receiver/settings_templates.py:920 -#: lib/logitech_receiver/settings_templates.py:923 -msgid "Scroll with two fingers" -msgstr "Вертикальная прокрутка двумя пальцами" - -#: lib/logitech_receiver/settings_templates.py:921 -msgid "Scroll horizontally with two fingers" -msgstr "Горизонтальная прокрутка двумя пальцами" - -#: lib/logitech_receiver/settings_templates.py:921 -msgid "Scrolls horizontally." -msgstr "Прокручивать горизонтально." - -#: lib/logitech_receiver/settings_templates.py:922 -msgid "Scroll vertically with two fingers" -msgstr "Вертикальная прокрутка двумя пальцами" - -#: lib/logitech_receiver/settings_templates.py:922 -msgid "Scrolls vertically." -msgstr "Прокручивать вертикально." +#: lib/logitech_receiver/settings_templates.py:749 +msgid "Changing important actions (such as for the left mouse button) can " + "result in an unusable system." +msgstr "(изменение важных действий — например, левой кнопки мыши - может " + "вывести систему из строя)." #: lib/logitech_receiver/settings_templates.py:924 -msgid "Inverts the scrolling direction." -msgstr "Инвертирует направления прокрутки." - -#: lib/logitech_receiver/settings_templates.py:924 -msgid "Natural scrolling" -msgstr "Естественная прокрутка" +msgid "Key/Button Diversion" +msgstr "Поведение клавиш/кнопок" #: lib/logitech_receiver/settings_templates.py:925 -msgid "Enables the thumbwheel." -msgstr "Включение бокового колеса прокрутки." +msgid "Make the key or button send HID++ notifications (Diverted) or " + "initiate Mouse Gestures or Sliding DPI" +msgstr "Заставьте клавишу или кнопку отправлять уведомления HID++ " + "(перенаправленные) или инициировать жесты мыши или скользящее " + "значение DPI" -#: lib/logitech_receiver/settings_templates.py:925 -msgid "Thumbwheel" -msgstr "Боковое колесо прокрутки" +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Diverted" +msgstr "Перенаправлено" -#: lib/logitech_receiver/settings_templates.py:936 -#: lib/logitech_receiver/settings_templates.py:940 -msgid "Swipe from the top edge" -msgstr "Листание от верхнего края" +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +msgid "Mouse Gestures" +msgstr "Жесты мышью" -#: lib/logitech_receiver/settings_templates.py:937 -msgid "Swipe from the left edge" -msgstr "Листание от левого края" +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Regular" +msgstr "Обычное" -#: lib/logitech_receiver/settings_templates.py:938 -msgid "Swipe from the right edge" -msgstr "Листание от правого края" - -#: lib/logitech_receiver/settings_templates.py:939 -msgid "Swipe from the bottom edge" -msgstr "Листание от нижнего края" - -#: lib/logitech_receiver/settings_templates.py:941 -msgid "Swipe two fingers from the left edge" -msgstr "Листание двумя пальцами от левого края" - -#: lib/logitech_receiver/settings_templates.py:942 -msgid "Swipe two fingers from the right edge" -msgstr "Листание двумя пальцами от правого края" - -#: lib/logitech_receiver/settings_templates.py:943 -msgid "Swipe two fingers from the bottom edge" -msgstr "Листание двумя пальцами от нижнего края" - -#: lib/logitech_receiver/settings_templates.py:944 -msgid "Swipe two fingers from the top edge" -msgstr "Листание двумя пальцами от верхнего края" - -#: lib/logitech_receiver/settings_templates.py:945 -#: lib/logitech_receiver/settings_templates.py:949 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "Щипок — уменьшить, раздвинуть — увеличить." - -#: lib/logitech_receiver/settings_templates.py:945 -msgid "Zoom with two fingers." -msgstr "Масштабирование двумя пальцами." - -#: lib/logitech_receiver/settings_templates.py:946 -msgid "Pinch to zoom out." -msgstr "Щипок — уменьшить." - -#: lib/logitech_receiver/settings_templates.py:947 -msgid "Spread to zoom in." -msgstr "Раздвинуть — увеличить." - -#: lib/logitech_receiver/settings_templates.py:948 -msgid "Zoom with three fingers." -msgstr "Масштабирование тремя пальцами." - -#: lib/logitech_receiver/settings_templates.py:949 -msgid "Zoom with two fingers" -msgstr "Масштабирование двумя пальцами" - -#: lib/logitech_receiver/settings_templates.py:967 -msgid "Pixel zone" -msgstr "Зона пикселей" - -#: lib/logitech_receiver/settings_templates.py:968 -msgid "Ratio zone" -msgstr "Зона масштаба" - -#: lib/logitech_receiver/settings_templates.py:969 -msgid "Scale factor" -msgstr "Коэффициент масштабирования" - -#: lib/logitech_receiver/settings_templates.py:969 -msgid "Sets the cursor speed." -msgstr "Установка скорости курсора." - -#: lib/logitech_receiver/settings_templates.py:973 -msgid "Left" -msgstr "Лево" - -#: lib/logitech_receiver/settings_templates.py:973 -msgid "Left-most coordinate." -msgstr "Самая левая позиция." - -#: lib/logitech_receiver/settings_templates.py:974 -msgid "Bottom" -msgstr "Низ" - -#: lib/logitech_receiver/settings_templates.py:974 -msgid "Bottom coordinate." -msgstr "Нижняя позиция." - -#: lib/logitech_receiver/settings_templates.py:975 -msgid "Width" -msgstr "Ширина" - -#: lib/logitech_receiver/settings_templates.py:975 -msgid "Width." -msgstr "Ширина." - -#: lib/logitech_receiver/settings_templates.py:976 -msgid "Height" -msgstr "Высота" - -#: lib/logitech_receiver/settings_templates.py:976 -msgid "Height." -msgstr "Высота." - -#: lib/logitech_receiver/settings_templates.py:977 -msgid "Cursor speed." -msgstr "Скорость курсора." - -#: lib/logitech_receiver/settings_templates.py:977 -msgid "Scale" -msgstr "Масштаб" - -#: lib/logitech_receiver/settings_templates.py:983 -msgid "Gestures" -msgstr "Жесты" - -#: lib/logitech_receiver/settings_templates.py:984 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "Регулировка поведения мыши/сенсорной панели" - -#: lib/logitech_receiver/settings_templates.py:1000 -msgid "Gestures Diversion" -msgstr "Поведение жестов" - -#: lib/logitech_receiver/settings_templates.py:1001 -msgid "Divert mouse/touchpad gestures." -msgstr "Регулировка поведения жестов мыши/сенсорной панели." - -#: lib/logitech_receiver/settings_templates.py:1017 -msgid "Gesture params" -msgstr "Параметры жестов" +#: lib/logitech_receiver/settings_templates.py:928 +msgid "Sliding DPI" +msgstr "Скользящий тчк/дюйм" #: lib/logitech_receiver/settings_templates.py:1018 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "Изменение числовых параметров мыши/сенсорной панели" +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Sensitivity (DPI)" +msgstr "Чувствительность (тчк/дюйм)" -#: lib/logitech_receiver/settings_templates.py:1042 -msgid "M-Key LEDs" -msgstr "Светодиоды клавиш M" +#: lib/logitech_receiver/settings_templates.py:1123 +msgid "Sensitivity Switching" +msgstr "Переключение чувствительности" -#: lib/logitech_receiver/settings_templates.py:1043 -msgid "Control the M-Key LEDs." -msgstr "Управление светодиодами клавиш M." +#: lib/logitech_receiver/settings_templates.py:1125 +msgid "Switch the current sensitivity and the remembered sensitivity when " + "the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current " + "sensitivity" +msgstr "Переключение текущей чувствительности и сохранённой чувствительности " + "при нажатии клавиши или кнопки.\n" + "Если сохранённой чувствительности нет, сохраняется текущая " + "чувствительность" -#: lib/logitech_receiver/settings_templates.py:1048 +#: lib/logitech_receiver/settings_templates.py:1129 +msgid "Off" +msgstr "Откл" + +#: lib/logitech_receiver/settings_templates.py:1160 +msgid "Disable keys" +msgstr "Отключить клавиши" + +#: lib/logitech_receiver/settings_templates.py:1161 +msgid "Disable specific keyboard keys." +msgstr "Отключение определённых клавиш клавиатуры." + +#: lib/logitech_receiver/settings_templates.py:1164 #, python-format -msgid "Lights up the %s key." -msgstr "Подсветка клавиши %s." +msgid "Disables the %s key." +msgstr "Отключение клавиши %s." -#: lib/logitech_receiver/settings_templates.py:1067 -msgid "MR-Key LED" -msgstr "Светодиод клавиши MR" +#: lib/logitech_receiver/settings_templates.py:1177 +#: lib/logitech_receiver/settings_templates.py:1234 +msgid "Set OS" +msgstr "Операционная система" -#: lib/logitech_receiver/settings_templates.py:1068 -msgid "Control the MR-Key LED." -msgstr "Управление светодиодом клавиши MR." +#: lib/logitech_receiver/settings_templates.py:1178 +#: lib/logitech_receiver/settings_templates.py:1235 +msgid "Change keys to match OS." +msgstr "Изменяет назначение клавиш соответственно ОС." -#: lib/logitech_receiver/settings_templates.py:1084 -msgid "Persistent Key/Button Mapping" -msgstr "Постоянное сопоставление клавиши или кнопки" +#: lib/logitech_receiver/settings_templates.py:1247 +msgid "Change Host" +msgstr "Подключено к" -#: lib/logitech_receiver/settings_templates.py:1086 -msgid "Permanently change the mapping for the key or button." -msgstr "Навсегда изменить сопоставление для клавиши или кнопки." +#: lib/logitech_receiver/settings_templates.py:1248 +msgid "Switch connection to a different host" +msgstr "Переключает соединение на выбранный хост." -#: lib/logitech_receiver/settings_templates.py:1087 -msgid "" -"Changing important keys or buttons (such as for the left mouse button) can " -"result in an unusable system." -msgstr "" -"Изменение важных клавиш или кнопок (например, левой кнопки мыши) может" -" вывести систему из строя." +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Performs a left click." +msgstr "Осуществляет щелчок левой кнопкой." -#: lib/logitech_receiver/status.py:113 -msgid "No paired devices." -msgstr "Нет сопряжённых устройств." +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Single tap" +msgstr "Одиночное касание" -#: lib/logitech_receiver/status.py:114 lib/solaar/ui/window.py:620 +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Performs a right click." +msgstr "Осуществляет щелчок правой кнопкой." + +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Single tap with two fingers" +msgstr "Одиночное касание двумя пальцами" + +#: lib/logitech_receiver/settings_templates.py:1274 +msgid "Single tap with three fingers" +msgstr "Одиночное касание тремя пальцами" + +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Double tap" +msgstr "Двойное касание" + +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Performs a double click." +msgstr "Осуществление двойного щелчка." + +#: lib/logitech_receiver/settings_templates.py:1279 +msgid "Double tap with two fingers" +msgstr "Двойное касание двумя пальцами" + +#: lib/logitech_receiver/settings_templates.py:1280 +msgid "Double tap with three fingers" +msgstr "Двойное касание тремя пальцами" + +#: lib/logitech_receiver/settings_templates.py:1283 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Перетаскивать объекты перемещением пальца после двойного касания." + +#: lib/logitech_receiver/settings_templates.py:1283 +msgid "Tap and drag" +msgstr "Касание и перетаскивание" + +#: lib/logitech_receiver/settings_templates.py:1285 +msgid "Tap and drag with two fingers" +msgstr "Касание и перетаскивание двумя пальцами" + +#: lib/logitech_receiver/settings_templates.py:1286 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Перетаскивать объекты перемещением пальцев после двойного касания." + +#: lib/logitech_receiver/settings_templates.py:1288 +msgid "Tap and drag with three fingers" +msgstr "Касание и перетаскивание тремя пальцами" + +#: lib/logitech_receiver/settings_templates.py:1291 +msgid "Suppress tap and edge gestures" +msgstr "Подавление касаний и жестов по краям" + +#: lib/logitech_receiver/settings_templates.py:1292 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "Отключает касания и жесты по краям (то же что FN+ЩелчокЛевойКнопкой)." + +#: lib/logitech_receiver/settings_templates.py:1294 +msgid "Scroll with one finger" +msgstr "Вертикальная прокрутка одним пальцем" + +#: lib/logitech_receiver/settings_templates.py:1294 +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 +msgid "Scrolls." +msgstr "Прокручивать вертикально." + +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 +msgid "Scroll with two fingers" +msgstr "Вертикальная прокрутка двумя пальцами" + +#: lib/logitech_receiver/settings_templates.py:1296 +msgid "Scroll horizontally with two fingers" +msgstr "Горизонтальная прокрутка двумя пальцами" + +#: lib/logitech_receiver/settings_templates.py:1296 +msgid "Scrolls horizontally." +msgstr "Прокручивать горизонтально." + +#: lib/logitech_receiver/settings_templates.py:1297 +msgid "Scroll vertically with two fingers" +msgstr "Вертикальная прокрутка двумя пальцами" + +#: lib/logitech_receiver/settings_templates.py:1297 +msgid "Scrolls vertically." +msgstr "Прокручивать вертикально." + +#: lib/logitech_receiver/settings_templates.py:1299 +msgid "Inverts the scrolling direction." +msgstr "Инвертирует направления прокрутки." + +#: lib/logitech_receiver/settings_templates.py:1299 +msgid "Natural scrolling" +msgstr "Естественная прокрутка" + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Enables the thumbwheel." +msgstr "Включение бокового колеса прокрутки." + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Thumbwheel" +msgstr "Боковое колесо прокрутки" + +#: lib/logitech_receiver/settings_templates.py:1311 +#: lib/logitech_receiver/settings_templates.py:1315 +msgid "Swipe from the top edge" +msgstr "Листание от верхнего края" + +#: lib/logitech_receiver/settings_templates.py:1312 +msgid "Swipe from the left edge" +msgstr "Листание от левого края" + +#: lib/logitech_receiver/settings_templates.py:1313 +msgid "Swipe from the right edge" +msgstr "Листание от правого края" + +#: lib/logitech_receiver/settings_templates.py:1314 +msgid "Swipe from the bottom edge" +msgstr "Листание от нижнего края" + +#: lib/logitech_receiver/settings_templates.py:1316 +msgid "Swipe two fingers from the left edge" +msgstr "Листание двумя пальцами от левого края" + +#: lib/logitech_receiver/settings_templates.py:1317 +msgid "Swipe two fingers from the right edge" +msgstr "Листание двумя пальцами от правого края" + +#: lib/logitech_receiver/settings_templates.py:1318 +msgid "Swipe two fingers from the bottom edge" +msgstr "Листание двумя пальцами от нижнего края" + +#: lib/logitech_receiver/settings_templates.py:1319 +msgid "Swipe two fingers from the top edge" +msgstr "Листание двумя пальцами от верхнего края" + +#: lib/logitech_receiver/settings_templates.py:1320 +#: lib/logitech_receiver/settings_templates.py:1324 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "Щипок - уменьшить, раздвинуть - увеличить." + +#: lib/logitech_receiver/settings_templates.py:1320 +msgid "Zoom with two fingers." +msgstr "Масштабирование двумя пальцами." + +#: lib/logitech_receiver/settings_templates.py:1321 +msgid "Pinch to zoom out." +msgstr "Щипок - уменьшить." + +#: lib/logitech_receiver/settings_templates.py:1322 +msgid "Spread to zoom in." +msgstr "Раздвинуть - увеличить." + +#: lib/logitech_receiver/settings_templates.py:1323 +msgid "Zoom with three fingers." +msgstr "Масштабирование тремя пальцами." + +#: lib/logitech_receiver/settings_templates.py:1324 +msgid "Zoom with two fingers" +msgstr "Масштабирование двумя пальцами" + +#: lib/logitech_receiver/settings_templates.py:1342 +msgid "Pixel zone" +msgstr "Зона пикселей" + +#: lib/logitech_receiver/settings_templates.py:1343 +msgid "Ratio zone" +msgstr "Зона масштаба" + +#: lib/logitech_receiver/settings_templates.py:1344 +msgid "Scale factor" +msgstr "Коэффициент масштабирования" + +#: lib/logitech_receiver/settings_templates.py:1344 +msgid "Sets the cursor speed." +msgstr "Установка скорости курсора." + +#: lib/logitech_receiver/settings_templates.py:1348 +msgid "Left" +msgstr "Лево" + +#: lib/logitech_receiver/settings_templates.py:1348 +msgid "Left-most coordinate." +msgstr "Самая левая позиция." + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom" +msgstr "Низ" + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom coordinate." +msgstr "Нижняя позиция." + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width" +msgstr "Ширина" + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width." +msgstr "Ширина." + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height" +msgstr "Высота" + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height." +msgstr "Высота." + +#: lib/logitech_receiver/settings_templates.py:1352 +msgid "Cursor speed." +msgstr "Скорость курсора." + +#: lib/logitech_receiver/settings_templates.py:1352 +msgid "Scale" +msgstr "Масштаб" + +#: lib/logitech_receiver/settings_templates.py:1358 +msgid "Gestures" +msgstr "Жесты" + +#: lib/logitech_receiver/settings_templates.py:1359 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Регулировка поведения мыши/сенсорной панели." + +#: lib/logitech_receiver/settings_templates.py:1375 +msgid "Gestures Diversion" +msgstr "Поведение жестов" + +#: lib/logitech_receiver/settings_templates.py:1376 +msgid "Divert mouse/touchpad gestures." +msgstr "Регулировка поведения жестов мыши/сенсорной панели." + +#: lib/logitech_receiver/settings_templates.py:1392 +msgid "Gesture params" +msgstr "Параметры жестов" + +#: lib/logitech_receiver/settings_templates.py:1393 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Изменение числовых параметров мыши/сенсорной панели." + +#: lib/logitech_receiver/settings_templates.py:1417 +msgid "M-Key LEDs" +msgstr "Светодиоды клавиш M" + +#: lib/logitech_receiver/settings_templates.py:1419 +msgid "Control the M-Key LEDs." +msgstr "Управление светодиодами клавиш M." + +#: lib/logitech_receiver/settings_templates.py:1423 +#: lib/logitech_receiver/settings_templates.py:1454 +msgid "May need G Keys diverted to be effective." +msgstr "Возможно, для эффективности потребуется перенаправить G-клавиши." + +#: lib/logitech_receiver/settings_templates.py:1429 #, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s сопряжённое устройство." -msgstr[1] "%(count)s сопряжённых устройства." -msgstr[2] "%(count)s сопряжённых устройств." +msgid "Lights up the %s key." +msgstr "Подсветка клавиши %s." -#: lib/logitech_receiver/status.py:169 +#: lib/logitech_receiver/settings_templates.py:1448 +msgid "MR-Key LED" +msgstr "Светодиод клавиши MR" + +#: lib/logitech_receiver/settings_templates.py:1450 +msgid "Control the MR-Key LED." +msgstr "Управление светодиодом клавиши MR." + +#: lib/logitech_receiver/settings_templates.py:1471 +msgid "Persistent Key/Button Mapping" +msgstr "Постоянное сопоставление клавиши или кнопки" + +#: lib/logitech_receiver/settings_templates.py:1473 +msgid "Permanently change the mapping for the key or button." +msgstr "Навсегда изменить сопоставление для клавиши или кнопки." + +#: lib/logitech_receiver/settings_templates.py:1475 +msgid "Changing important keys or buttons (such as for the left mouse " + "button) can result in an unusable system." +msgstr "Изменение важных клавиш или кнопок (например, левой мыши кнопку) " + "может привести к выводу системы из строя." + +#: lib/logitech_receiver/settings_templates.py:1532 +msgid "Sidetone" +msgstr "Самопрослушивание" + +#: lib/logitech_receiver/settings_templates.py:1533 +msgid "Set sidetone level." +msgstr "Установите уровень самопрослушивания." + +#: lib/logitech_receiver/settings_templates.py:1542 +msgid "Equalizer" +msgstr "Эквалайзер" + +#: lib/logitech_receiver/settings_templates.py:1543 +msgid "Set equalizer levels." +msgstr "Установите уровни эквалайзера." + +#: lib/logitech_receiver/settings_templates.py:1565 +msgid "Hz" +msgstr "Гц" + +#: lib/logitech_receiver/settings_templates.py:1571 +msgid "Power Management" +msgstr "Управление питанием" + +#: lib/logitech_receiver/settings_templates.py:1572 +msgid "Power off in minutes (0 for never)." +msgstr "Выключите питание через несколько минут (0 означает \"никогда\")." + +#: lib/logitech_receiver/settings_templates.py:1584 +msgid "Brightness Control" +msgstr "Регулировка яркости" + +#: lib/logitech_receiver/settings_templates.py:1585 +msgid "Control overall brightness" +msgstr "Контролируйте общую яркость" + +#: lib/logitech_receiver/settings_templates.py:1628 +#: lib/logitech_receiver/settings_templates.py:1681 +msgid "LED Control" +msgstr "Светодиодное управление" + +#: lib/logitech_receiver/settings_templates.py:1629 +#: lib/logitech_receiver/settings_templates.py:1682 +msgid "Switch control of LED zones between device and Solaar" +msgstr "Переключайте управление светодиодными зонами между устройством и " + "Solaar" + +#: lib/logitech_receiver/settings_templates.py:1644 +#: lib/logitech_receiver/settings_templates.py:1692 +msgid "LED Zone Effects" +msgstr "Эффекты светодиодной зоны" + +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "LED Control needs to be set to Solaar to be effective." +msgstr "Для эффективного управления светодиодом необходимо установить режим " + "Solaar." + +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "Set effect for LED Zone" +msgstr "Установите эффект для светодиодной зоны" + +#: lib/logitech_receiver/settings_templates.py:1647 +msgid "Color" +msgstr "Цвет" + +#: lib/logitech_receiver/settings_templates.py:1648 +msgid "Speed" +msgstr "Скорость" + +#: lib/logitech_receiver/settings_templates.py:1649 +msgid "Period" +msgstr "Период" + +#: lib/logitech_receiver/settings_templates.py:1650 +msgid "Intensity" +msgstr "Интенсивность" + +#: lib/logitech_receiver/settings_templates.py:1651 +msgid "Ramp" +msgstr "Рампа" + +#: lib/logitech_receiver/settings_templates.py:1666 +msgid "LEDs" +msgstr "Светодиоды" + +#: lib/logitech_receiver/settings_templates.py:1703 +msgid "Per-key Lighting" +msgstr "Индивидуальное освещение" + +#: lib/logitech_receiver/settings_templates.py:1704 +msgid "Control per-key lighting." +msgstr "Управление подсветкой каждой клавиши." + +#: lib/logitech_receiver/settings_templates.py:1786 +msgid "Force Sensing Buttons" +msgstr "Кнопки с датчиками силы нажатия" + +#: lib/logitech_receiver/settings_templates.py:1787 +msgid "Change the force required to activate button." +msgstr "Измените усилие, необходимое для активации кнопки." + +#: lib/logitech_receiver/settings_templates.py:1804 +msgid "Force Sensing Button" +msgstr "Кнопка измерения усилия" + +#: lib/logitech_receiver/settings_templates.py:1814 +msgid "Haptic Feeback Level" +msgstr "Уровень тактильной обратной связи" + +#: lib/logitech_receiver/settings_templates.py:1815 +msgid "Change power of haptic feedback. (Zero to turn off.)" +msgstr "Измените мощность тактильной обратной связи. (Ноль - отключение.)" + +#: lib/logitech_receiver/settings_templates.py:1857 +msgid "Play Haptic Waveform" +msgstr "Воспроизведение тактильной формы сигнала" + +#: lib/logitech_receiver/settings_templates.py:1858 +msgid "Tell device to play a haptic waveform." +msgstr "Дайте команду устройству воспроизвести тактильный сигнал." + +#: lib/solaar/ui/__init__.py:120 +msgid "Another Solaar process is already running so just expose its window" +msgstr "Другой Solaar процесс уже запущен, так что просто откройте его окно" + +#: lib/solaar/ui/about/model.py:36 +msgid "Manages Logitech receivers,\n" + "keyboards, mice, and tablets." +msgstr "Управление приёмниками, клавиатурами,\n" + "мышами и другими устройствами Logitech." + +#: lib/solaar/ui/about/model.py:63 +msgid "Additional Programming" +msgstr "Дополнительные параметры" + +#: lib/solaar/ui/about/model.py:64 +msgid "GUI design" +msgstr "Дизайн интерфейса" + +#: lib/solaar/ui/about/model.py:66 +msgid "Testing" +msgstr "Тестирование" + +#: lib/solaar/ui/about/model.py:74 +msgid "Logitech documentation" +msgstr "Документация Logitech" + +#: lib/solaar/ui/action.py:88 lib/solaar/ui/action.py:92 +#: lib/solaar/ui/window.py:208 +msgid "Unpair" +msgstr "Отмена сопряжения" + +#: lib/solaar/ui/action.py:91 lib/solaar/ui/diversion_rules.py:141 +msgid "Cancel" +msgstr "Отмена" + +#: lib/solaar/ui/common.py:42 +msgid "Permissions error" +msgstr "Ошибка доступа" + +#: lib/solaar/ui/common.py:44 #, python-format -msgid "Battery: %(level)s" -msgstr "Батарея: %(level)s" +msgid "Found a Logitech receiver or device (%s), but did not have " + "permission to open it." +msgstr "Найден приёмник Logitech (%s), но нет прав доступ открыть его." -#: lib/logitech_receiver/status.py:171 +#: lib/solaar/ui/common.py:46 +msgid "If you've just installed Solaar, try disconnecting the receiver or " + "device and then reconnecting it." +msgstr "Если вы только что установили Solaar, попробуйте отключить и снова " + "подключить приёмник или устройство." + +#: lib/solaar/ui/common.py:49 +msgid "Cannot connect to device error" +msgstr "Невозможно подключиться к устройству" + +#: lib/solaar/ui/common.py:51 #, python-format -msgid "Battery: %(percent)d%%" -msgstr "Батарея: %(percent)d%%" +msgid "Found a Logitech receiver or device at %s, but encountered an error " + "connecting to it." +msgstr "Найден приёмник или устройство Logitech на %s, но не удалось к нему " + "подключиться." -#: lib/logitech_receiver/status.py:183 -#, python-format -msgid "Lighting: %(level)s lux" -msgstr "Освещенность: %(level)s люкс" +#: lib/solaar/ui/common.py:53 +msgid "Try disconnecting the device and then reconnecting it or turning it " + "off and then on." +msgstr "Попробуйте отключить и снова подключить устройство или выключить и " + "затем снова включить его." -#: lib/logitech_receiver/status.py:238 -#, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "Батарея: %(level)s (%(status)s)" +#: lib/solaar/ui/common.py:56 +msgid "Unpairing failed" +msgstr "Отмена сопряжения не удалась" -#: lib/logitech_receiver/status.py:240 -#, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "Батарея: %(percent)d%% (%(status)s)" - -#: lib/solaar/ui/__init__.py:51 -msgid "Permissions error" -msgstr "Ошибка доступа" - -#: lib/solaar/ui/__init__.py:53 -#, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open it." -msgstr "Найден приёмник Logitech «%s», но нет прав доступа к нему." - -#: lib/solaar/ui/__init__.py:54 -msgid "" -"If you've just installed Solaar, try removing the receiver and plugging it " -"back in." -msgstr "" -"Если это первый запуск Solaar, попробуйте извлечь и снова вставить приёмник." - -#: lib/solaar/ui/__init__.py:57 -msgid "Cannot connect to device error" -msgstr "Невозможно подключиться к устройству" - -#: lib/solaar/ui/__init__.py:59 -#, python-format -msgid "" -"Found a Logitech receiver or device at %s, but encountered an error " -"connecting to it." -msgstr "" -"Найден приёмник или устройство Logitech на %s, но не удалось к нему " -"подключиться." - -#: lib/solaar/ui/__init__.py:60 -msgid "" -"Try removing the device and plugging it back in or turning it off and then " -"on." -msgstr "" -"Попробуйте извлечь и снова вставить устройство или отключить и затем снова " -"включить его." - -#: lib/solaar/ui/__init__.py:63 -msgid "Unpairing failed" -msgstr "Отмена сопряжения не удалась" - -#: lib/solaar/ui/__init__.py:65 +#: lib/solaar/ui/common.py:58 #, python-brace-format -msgid "Failed to unpair %{device} from %{receiver}." -msgstr "Отмена сопряжения %{device} с %{receiver} не удалась." +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Отмена сопряжения %{device} с %{receiver} не удалась." -#: lib/solaar/ui/__init__.py:66 -msgid "The receiver returned an error, with no further details." -msgstr "Приёмник вернул ошибку без дополнительных сведений." +#: lib/solaar/ui/common.py:63 +msgid "The receiver returned an error, with no further details." +msgstr "Приёмник вернул ошибку без дополнительных сведений." -#: lib/solaar/ui/about.py:36 -msgid "" -"Manages Logitech receivers,\n" -"keyboards, mice, and tablets." -msgstr "" -"Управление приёмниками, клавиатурами,\n" -"мышами и другими устройствами Logitech." +#: lib/solaar/ui/config_panel.py:245 +msgid "Complete - ENTER to change" +msgstr "Завершено - «ВВОД» для изменения" -#: lib/solaar/ui/about.py:44 -msgid "Additional Programming" -msgstr "Дополнительные параметры" +#: lib/solaar/ui/config_panel.py:245 +msgid "Incomplete" +msgstr "Незавершено" -#: lib/solaar/ui/about.py:45 -msgid "GUI design" -msgstr "Дизайн интерфейса" - -#: lib/solaar/ui/about.py:47 -msgid "Testing" -msgstr "Тестирование" - -#: lib/solaar/ui/about.py:54 -msgid "Logitech documentation" -msgstr "Документация Logitech" - -#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 -#: lib/solaar/ui/window.py:202 -msgid "Unpair" -msgstr "Отмена сопряжения" - -#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 -msgid "Cancel" -msgstr "Отмена" - -#: lib/solaar/ui/config_panel.py:199 -msgid "Complete - ENTER to change" -msgstr "Завершено — «ВВОД» для изменения" - -#: lib/solaar/ui/config_panel.py:199 -msgid "Incomplete" -msgstr "Незавершено" - -#: lib/solaar/ui/config_panel.py:454 +#: lib/solaar/ui/config_panel.py:491 lib/solaar/ui/config_panel.py:543 #, python-format -msgid "%d value" -msgid_plural "%d values" -msgstr[0] "%d значение" -msgstr[1] "%d значения" -msgstr[2] "%d значений" +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d значение" +msgstr[1] "%d значения" +msgstr[2] "%d значений" -#: lib/solaar/ui/config_panel.py:465 -msgid "Changes allowed" -msgstr "Изменения разрешены" +#: lib/solaar/ui/config_panel.py:642 +msgid "Changes allowed" +msgstr "Изменения разрешены" -#: lib/solaar/ui/config_panel.py:466 -msgid "No changes allowed" -msgstr "Изменения запрещены" +#: lib/solaar/ui/config_panel.py:643 +msgid "No changes allowed" +msgstr "Изменения запрещены" -#: lib/solaar/ui/config_panel.py:467 -msgid "Ignore this setting" -msgstr "Игнорировать данный параметр" +#: lib/solaar/ui/config_panel.py:644 +msgid "Ignore this setting" +msgstr "Игнорировать данный параметр" -#: lib/solaar/ui/config_panel.py:512 -msgid "Working" -msgstr "Работает" +#: lib/solaar/ui/config_panel.py:690 +msgid "Working" +msgstr "Работает" -#: lib/solaar/ui/config_panel.py:515 -msgid "Read/write operation failed." -msgstr "Операция чтения/записи не удалась." +#: lib/solaar/ui/config_panel.py:693 +msgid "Read/write operation failed." +msgstr "Операция чтения/записи не удалась." -#: lib/solaar/ui/diversion_rules.py:63 -msgid "Built-in rules" -msgstr "Встроенные правила" +#: lib/solaar/ui/desktop_notifications.py:112 +msgid "unspecified reason" +msgstr "неустановленная причина" -#: lib/solaar/ui/diversion_rules.py:63 -msgid "User-defined rules" -msgstr "Пользовательские правила" +#: lib/solaar/ui/diversion_rules.py:103 +msgid "Built-in rules" +msgstr "Встроенные правила" -#: lib/solaar/ui/diversion_rules.py:65 lib/solaar/ui/diversion_rules.py:1073 -msgid "Rule" -msgstr "Правило" +#: lib/solaar/ui/diversion_rules.py:103 +msgid "User-defined rules" +msgstr "Пользовательские правила" -#: lib/solaar/ui/diversion_rules.py:66 lib/solaar/ui/diversion_rules.py:507 -#: lib/solaar/ui/diversion_rules.py:629 -msgid "Sub-rule" -msgstr "Подправило" +#: lib/solaar/ui/diversion_rules.py:105 lib/solaar/ui/diversion_rules.py:1126 +msgid "Rule" +msgstr "Правило" -#: lib/solaar/ui/diversion_rules.py:68 -msgid "[empty]" -msgstr "[пусто]" +#: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:382 +#: lib/solaar/ui/diversion_rules.py:509 +msgid "Sub-rule" +msgstr "Подправило" -#: lib/solaar/ui/diversion_rules.py:91 -msgid "Solaar Rule Editor" -msgstr "Редактор правил" +#: lib/solaar/ui/diversion_rules.py:108 +msgid "[empty]" +msgstr "[пусто]" -#: lib/solaar/ui/diversion_rules.py:141 -msgid "Make changes permanent?" -msgstr "Сохранить изменения навсегда?" +#: lib/solaar/ui/diversion_rules.py:132 +msgid "Make changes permanent?" +msgstr "Сохранить изменения навсегда?" -#: lib/solaar/ui/diversion_rules.py:146 -msgid "Yes" -msgstr "Да" +#: lib/solaar/ui/diversion_rules.py:137 +msgid "Yes" +msgstr "Да" -#: lib/solaar/ui/diversion_rules.py:148 -msgid "No" -msgstr "Нет" +#: lib/solaar/ui/diversion_rules.py:139 +msgid "No" +msgstr "Нет" -#: lib/solaar/ui/diversion_rules.py:153 -msgid "If you choose No, changes will be lost when Solaar is closed." -msgstr "Если выбрать «Нет», при закрытии программы изменения пропадут." +#: lib/solaar/ui/diversion_rules.py:144 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "Если выбрать «Нет», при закрытии программы изменения пропадут." -#: lib/solaar/ui/diversion_rules.py:201 -msgid "Save changes" -msgstr "Сохранить изменения" +#: lib/solaar/ui/diversion_rules.py:273 +msgid "Paste here" +msgstr "Скопировать сюда" -#: lib/solaar/ui/diversion_rules.py:206 -msgid "Discard changes" -msgstr "Отменить изменения" +#: lib/solaar/ui/diversion_rules.py:275 +msgid "Paste above" +msgstr "Скопировать выше" -#: lib/solaar/ui/diversion_rules.py:370 -msgid "Insert here" -msgstr "Вставить сюда" +#: lib/solaar/ui/diversion_rules.py:277 +msgid "Paste below" +msgstr "Скопировать ниже" -#: lib/solaar/ui/diversion_rules.py:372 -msgid "Insert above" -msgstr "Вставить выше" +#: lib/solaar/ui/diversion_rules.py:283 +msgid "Paste rule here" +msgstr "Скопировать правило сюда" -#: lib/solaar/ui/diversion_rules.py:374 -msgid "Insert below" -msgstr "Вставить ниже" +#: lib/solaar/ui/diversion_rules.py:285 +msgid "Paste rule above" +msgstr "Скопировать правило выше" + +#: lib/solaar/ui/diversion_rules.py:287 +msgid "Paste rule below" +msgstr "Скопировать правило ниже" + +#: lib/solaar/ui/diversion_rules.py:291 +msgid "Paste rule" +msgstr "Скопировать правило" + +#: lib/solaar/ui/diversion_rules.py:306 +msgid "Insert here" +msgstr "Вставить сюда" + +#: lib/solaar/ui/diversion_rules.py:308 +msgid "Insert above" +msgstr "Вставить выше" + +#: lib/solaar/ui/diversion_rules.py:310 +msgid "Insert below" +msgstr "Вставить ниже" + +#: lib/solaar/ui/diversion_rules.py:316 +msgid "Insert new rule here" +msgstr "Вставить новое правило сюда" + +#: lib/solaar/ui/diversion_rules.py:318 +msgid "Insert new rule above" +msgstr "Вставить новое правило выше" + +#: lib/solaar/ui/diversion_rules.py:320 +msgid "Insert new rule below" +msgstr "Вставить новое правило ниже" + +#: lib/solaar/ui/diversion_rules.py:347 +msgid "Flatten" +msgstr "Снять обёртку" #: lib/solaar/ui/diversion_rules.py:380 -msgid "Insert new rule here" -msgstr "Вставить новое правило сюда" +msgid "Insert" +msgstr "Вставка" -#: lib/solaar/ui/diversion_rules.py:382 -msgid "Insert new rule above" -msgstr "Вставить новое правило выше" +#: lib/solaar/ui/diversion_rules.py:383 lib/solaar/ui/diversion_rules.py:511 +#: lib/solaar/ui/diversion_rules.py:1158 +msgid "Or" +msgstr "Или" -#: lib/solaar/ui/diversion_rules.py:384 -msgid "Insert new rule below" -msgstr "Вставить новое правило ниже" +#: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:510 +#: lib/solaar/ui/diversion_rules.py:1144 +msgid "And" +msgstr "И" -#: lib/solaar/ui/diversion_rules.py:425 -msgid "Paste here" -msgstr "Скопировать сюда" +#: lib/solaar/ui/diversion_rules.py:386 +msgid "Condition" +msgstr "Компонент" -#: lib/solaar/ui/diversion_rules.py:427 -msgid "Paste above" -msgstr "Скопировать выше" +#: lib/solaar/ui/diversion_rules.py:388 lib/solaar/ui/rule_conditions.py:154 +msgid "Feature" +msgstr "Feature" -#: lib/solaar/ui/diversion_rules.py:429 -msgid "Paste below" -msgstr "Скопировать ниже" +#: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:188 +msgid "Report" +msgstr "Отчёт" -#: lib/solaar/ui/diversion_rules.py:435 -msgid "Paste rule here" -msgstr "Скопировать правило сюда" +#: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:69 +msgid "Process" +msgstr "Процесс" -#: lib/solaar/ui/diversion_rules.py:437 -msgid "Paste rule above" -msgstr "Скопировать правило выше" +#: lib/solaar/ui/diversion_rules.py:391 +msgid "Mouse process" +msgstr "Действие мыши" -#: lib/solaar/ui/diversion_rules.py:439 -msgid "Paste rule below" -msgstr "Скопировать правило ниже" +#: lib/solaar/ui/diversion_rules.py:392 lib/solaar/ui/rule_conditions.py:225 +msgid "Modifiers" +msgstr "Модификаторы" -#: lib/solaar/ui/diversion_rules.py:443 -msgid "Paste rule" -msgstr "Скопировать правило" +#: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:277 +msgid "Key" +msgstr "Клавиша" -#: lib/solaar/ui/diversion_rules.py:472 -msgid "Flatten" -msgstr "Снять обёртку" +#: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:318 +msgid "KeyIsDown" +msgstr "Клавиша нажата" -#: lib/solaar/ui/diversion_rules.py:505 -msgid "Insert" -msgstr "Вставка" +#: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/diversion_rules.py:1432 +msgid "Active" +msgstr "Активный" -#: lib/solaar/ui/diversion_rules.py:508 lib/solaar/ui/diversion_rules.py:631 -#: lib/solaar/ui/diversion_rules.py:1116 -msgid "Or" -msgstr "Или" +#: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1390 +#: lib/solaar/ui/diversion_rules.py:1441 lib/solaar/ui/diversion_rules.py:1487 +msgid "Device" +msgstr "Устройство" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:630 -#: lib/solaar/ui/diversion_rules.py:1101 -msgid "And" -msgstr "И" +#: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1467 +msgid "Host" +msgstr "Хост" -#: lib/solaar/ui/diversion_rules.py:511 -msgid "Condition" -msgstr "Компонент" +#: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1506 +msgid "Setting" +msgstr "Установка" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1232 -msgid "Feature" -msgstr "Элемент" +#: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/rule_conditions.py:333 +#: lib/solaar/ui/rule_conditions.py:382 +msgid "Test" +msgstr "Условие" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1155 -msgid "Process" -msgstr "Процесс" +#: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:507 +msgid "Test bytes" +msgstr "Тестовые байты" -#: lib/solaar/ui/diversion_rules.py:515 -msgid "Mouse process" -msgstr "Действие мыши" +#: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:608 +msgid "Mouse Gesture" +msgstr "Жест мышью" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1265 -msgid "Report" -msgstr "Отчёт" +#: lib/solaar/ui/diversion_rules.py:405 +msgid "Action" +msgstr "Действие" -#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1300 -msgid "Modifiers" -msgstr "Модификаторы" +#: lib/solaar/ui/diversion_rules.py:407 lib/solaar/ui/rule_actions.py:138 +msgid "Key press" +msgstr "Нажатие клавиши" -#: lib/solaar/ui/diversion_rules.py:518 lib/solaar/ui/diversion_rules.py:1347 -msgid "Key" -msgstr "Клавиша" +#: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:191 +msgid "Mouse scroll" +msgstr "Прокрутка мышью" -#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1392 -msgid "Test" -msgstr "Условие" +#: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:256 +msgid "Mouse click" +msgstr "Щелчок мышью" -#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1506 -msgid "Test bytes" -msgstr "Тестовые байты" +#: lib/solaar/ui/diversion_rules.py:410 +msgid "Set" +msgstr "Установить" -#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:2053 -msgid "Setting" -msgstr "Установка" +#: lib/solaar/ui/diversion_rules.py:411 lib/solaar/ui/rule_actions.py:328 +msgid "Execute" +msgstr "Исполнить" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1594 -msgid "Mouse Gesture" -msgstr "Жест мышью" +#: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/diversion_rules.py:1189 +msgid "Later" +msgstr "Задержка" -#: lib/solaar/ui/diversion_rules.py:526 -msgid "Action" -msgstr "Действие" +#: lib/solaar/ui/diversion_rules.py:441 +msgid "Insert new rule" +msgstr "Добавить правило" -#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1684 -msgid "Key press" -msgstr "Нажатие клавиши" +#: lib/solaar/ui/diversion_rules.py:461 lib/solaar/ui/rule_actions.py:82 +#: lib/solaar/ui/rule_actions.py:287 lib/solaar/ui/rule_conditions.py:556 +msgid "Delete" +msgstr "Удалить" -#: lib/solaar/ui/diversion_rules.py:529 lib/solaar/ui/diversion_rules.py:1733 -msgid "Mouse scroll" -msgstr "Прокрутка мышью" +#: lib/solaar/ui/diversion_rules.py:483 +msgid "Negate" +msgstr "Инвертировать" -#: lib/solaar/ui/diversion_rules.py:530 lib/solaar/ui/diversion_rules.py:1781 -msgid "Mouse click" -msgstr "Щелчок мышью" +#: lib/solaar/ui/diversion_rules.py:507 +msgid "Wrap with" +msgstr "Завернуть в" -#: lib/solaar/ui/diversion_rules.py:531 lib/solaar/ui/diversion_rules.py:1850 -msgid "Execute" -msgstr "Исполнить" +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Cut" +msgstr "Вырезать" -#: lib/solaar/ui/diversion_rules.py:532 -msgid "Set" -msgstr "Установить" +#: lib/solaar/ui/diversion_rules.py:553 +msgid "Paste" +msgstr "Вставить" -#: lib/solaar/ui/diversion_rules.py:561 -msgid "Insert new rule" -msgstr "Добавить правило" +#: lib/solaar/ui/diversion_rules.py:559 +msgid "Copy" +msgstr "Копировать" -#: lib/solaar/ui/diversion_rules.py:581 lib/solaar/ui/diversion_rules.py:1544 -#: lib/solaar/ui/diversion_rules.py:1634 lib/solaar/ui/diversion_rules.py:1809 -msgid "Delete" -msgstr "Удалить" +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Solaar Rule Editor" +msgstr "Редактор правил" -#: lib/solaar/ui/diversion_rules.py:603 -msgid "Negate" -msgstr "Инвертировать" +#: lib/solaar/ui/diversion_rules.py:668 +msgid "Save changes" +msgstr "Сохранить изменения" -#: lib/solaar/ui/diversion_rules.py:627 -msgid "Wrap with" -msgstr "Завернуть в" +#: lib/solaar/ui/diversion_rules.py:673 +msgid "Discard changes" +msgstr "Отменить изменения" -#: lib/solaar/ui/diversion_rules.py:649 -msgid "Cut" -msgstr "Вырезать" +#: lib/solaar/ui/diversion_rules.py:1104 +msgid "This editor does not support the selected rule component yet." +msgstr "Редактор пока не поддерживает выбранную компоненту правила." -#: lib/solaar/ui/diversion_rules.py:664 -msgid "Paste" -msgstr "Вставить" +#: lib/solaar/ui/diversion_rules.py:1169 +msgid "Number of seconds to delay. Delay between 0 and 1 is done with " + "higher precision." +msgstr "Количество секунд задержки. Задержка между 0 и 1 выполняется с " + "помощью более высокая точность." -#: lib/solaar/ui/diversion_rules.py:676 -msgid "Copy" -msgstr "Копировать" +#: lib/solaar/ui/diversion_rules.py:1207 +msgid "Not" +msgstr "Не" -#: lib/solaar/ui/diversion_rules.py:1053 -msgid "This editor does not support the selected rule component yet." -msgstr "Редактор пока не поддерживает выбранную компоненту правила." +#: lib/solaar/ui/diversion_rules.py:1238 +msgid "Toggle" +msgstr "Переключение" -#: lib/solaar/ui/diversion_rules.py:1131 -msgid "Not" -msgstr "Не" +#: lib/solaar/ui/diversion_rules.py:1239 +msgid "True" +msgstr "Истина" -#: lib/solaar/ui/diversion_rules.py:1183 -msgid "MouseProcess" -msgstr "Действие мыши" +#: lib/solaar/ui/diversion_rules.py:1240 +msgid "False" +msgstr "Ложь" -#: lib/solaar/ui/diversion_rules.py:1320 -msgid "Key down" -msgstr "Нажать клавишу" +#: lib/solaar/ui/diversion_rules.py:1253 +msgid "Unsupported setting" +msgstr "Неподдерживаемый параметр" -#: lib/solaar/ui/diversion_rules.py:1323 -msgid "Key up" -msgstr "Отпустить клавишу" - -#: lib/solaar/ui/diversion_rules.py:1408 -msgid "begin (inclusive)" -msgstr "начало (включительно)" - -#: lib/solaar/ui/diversion_rules.py:1409 -msgid "end (exclusive)" -msgstr "конец (исключительно)" - -#: lib/solaar/ui/diversion_rules.py:1418 -msgid "range" -msgstr "диапазон" - -#: lib/solaar/ui/diversion_rules.py:1420 -msgid "minimum" -msgstr "минимум" - -#: lib/solaar/ui/diversion_rules.py:1421 -msgid "maximum" -msgstr "максимум" - -#: lib/solaar/ui/diversion_rules.py:1423 -#, python-format -msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" -msgstr "байты с %(0)d по %(1)d, в диапазоне от %(2)d до %(3)d" +#: lib/solaar/ui/diversion_rules.py:1396 lib/solaar/ui/diversion_rules.py:1415 +#: lib/solaar/ui/diversion_rules.py:1493 lib/solaar/ui/diversion_rules.py:1748 +#: lib/solaar/ui/diversion_rules.py:1766 +msgid "Originating device" +msgstr "Исходное устройство" #: lib/solaar/ui/diversion_rules.py:1428 -msgid "mask" -msgstr "маска" +msgid "Device is active and its settings can be changed." +msgstr "Устройство активно, и его настройки могут быть изменены." -#: lib/solaar/ui/diversion_rules.py:1429 -#, python-format -msgid "bytes %(0)d to %(1)d, mask %(2)d" -msgstr "байты с %(0)d по %(1)d, маска %(2)d" +#: lib/solaar/ui/diversion_rules.py:1437 +msgid "Device that originated the current notification." +msgstr "Устройство, отправившее текущее уведомление." -#: lib/solaar/ui/diversion_rules.py:1446 -msgid "type" -msgstr "тип" +#: lib/solaar/ui/diversion_rules.py:1450 +msgid "Name of host computer." +msgstr "Имя главного компьютера." + +#: lib/solaar/ui/diversion_rules.py:1520 +msgid "Value" +msgstr "Значение" #: lib/solaar/ui/diversion_rules.py:1529 -msgid "Add action" -msgstr "Добавить действие" +msgid "Item" +msgstr "Элемент" -#: lib/solaar/ui/diversion_rules.py:1622 -msgid "Add key" -msgstr "Добавить клавишу" +#: lib/solaar/ui/diversion_rules.py:1808 +msgid "Change setting on device" +msgstr "Изменить настройку на устройстве" -#: lib/solaar/ui/diversion_rules.py:1752 -msgid "Button" -msgstr "Кнопка" +#: lib/solaar/ui/diversion_rules.py:1824 +msgid "Setting on device" +msgstr "Настройка на устройстве" -#: lib/solaar/ui/diversion_rules.py:1753 -msgid "Count" -msgstr "Счётчик" - -#: lib/solaar/ui/diversion_rules.py:1795 -msgid "Add argument" -msgstr "Добавить аргумент" - -#: lib/solaar/ui/diversion_rules.py:1869 -msgid "Toggle" -msgstr "Переключение" - -#: lib/solaar/ui/diversion_rules.py:1869 -msgid "True" -msgstr "Истина" - -#: lib/solaar/ui/diversion_rules.py:1870 -msgid "False" -msgstr "Ложь" - -#: lib/solaar/ui/diversion_rules.py:1884 -msgid "Unsupported setting" -msgstr "Неподдерживаемый параметр" - -#: lib/solaar/ui/diversion_rules.py:2036 -msgid "Device" -msgstr "Устройство" - -#: lib/solaar/ui/diversion_rules.py:2042 lib/solaar/ui/diversion_rules.py:2289 -#: lib/solaar/ui/diversion_rules.py:2307 -msgid "Originating device" -msgstr "Исходное устройство" - -#: lib/solaar/ui/diversion_rules.py:2061 -msgid "Value" -msgstr "Значение" - -#: lib/solaar/ui/notify.py:122 lib/solaar/ui/tray.py:323 -#: lib/solaar/ui/tray.py:328 lib/solaar/ui/window.py:746 -msgid "offline" -msgstr "нет соединения" - -#: lib/solaar/ui/pair_window.py:121 lib/solaar/ui/pair_window.py:255 -#: lib/solaar/ui/pair_window.py:277 +#: lib/solaar/ui/pair_window.py:44 lib/solaar/ui/pair_window.py:167 #, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s: новое сопряжённое устройство" +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: новое сопряжённое устройство" -#: lib/solaar/ui/pair_window.py:122 +#: lib/solaar/ui/pair_window.py:46 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "Приемники Bolt совместимы только с устройствами Bolt." + +#: lib/solaar/ui/pair_window.py:48 +msgid "Press a pairing button or key until the pairing light flashes " + "quickly." +msgstr "Нажимайте кнопку или клавишу сопряжения, пока индикатор сопряжения " + "не начнёт быстро мигать." + +#: lib/solaar/ui/pair_window.py:51 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "Приемники Unifying совместимы только с устройствами Unifying." + +#: lib/solaar/ui/pair_window.py:53 +msgid "Other receivers are only compatible with a few devices." +msgstr "Другие ресиверы совместимы только с некоторыми устройствами." + +#: lib/solaar/ui/pair_window.py:55 +msgid "For most devices, turn on the device you want to pair." +msgstr "Обычно достаточно включить устройство, с которым вы хотите " + "установить соединение." + +#: lib/solaar/ui/pair_window.py:56 +msgid "If the device is already turned on, turn it off and on again." +msgstr "Если устройство включено, выключите его и включите снова." + +#: lib/solaar/ui/pair_window.py:58 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "Устройство не должно быть сопряжено с находящимся поблизости " + "включенным приемником." + +#: lib/solaar/ui/pair_window.py:61 +msgid "For devices with multiple channels, press, hold, and release the " + "button for the channel you wish to pair\n" + "or use the channel switch button to select a channel and then press, " + "hold, and release the channel switch button." +msgstr "Для устройств с несколькими каналами нажмите, удерживайте и " + "отпустите кнопку соответствующего канала, который требуется " + "подключить, или используйте кнопку переключения каналов, чтобы " + "выбрать нужный канал, затем нажмите, удерживайте и отпустите кнопку " + "переключения каналов." + +#: lib/solaar/ui/pair_window.py:68 +msgid "The channel indicator light should be blinking rapidly." +msgstr "Индикатор канала должен быстро мигать." + +#: lib/solaar/ui/pair_window.py:72 #, python-format -msgid "Enter passcode on %(name)s." -msgstr "Введите код доступа на %(name)s." +msgid "\n" + "\n" + "This receiver has %d pairing remaining." +msgid_plural "\n" + "\n" + "This receiver has %d pairings remaining." +msgstr[0] "\n" + "Данному приёмнику доступно %d сопряжение." +msgstr[1] "\n" + "Данному приёмнику доступно %d сопряжения." +msgstr[2] "\n" + "Данному приёмнику доступно %d сопряжений." -#: lib/solaar/ui/pair_window.py:125 +#: lib/solaar/ui/pair_window.py:78 +msgid "\n" + "Cancelling at this point will not use up a pairing." +msgstr "\n" + "Прерывание в данный момент отменит сопряжение." + +#: lib/solaar/ui/pair_window.py:168 #, python-format -msgid "Type %(passcode)s and then press the enter key." -msgstr "Введите %(passcode)s и затем нажмите клавишу «Ввод»." +msgid "Enter passcode on %(name)s." +msgstr "Введите код доступа на %(name)s." -#: lib/solaar/ui/pair_window.py:128 -msgid "left" -msgstr "слева" - -#: lib/solaar/ui/pair_window.py:128 -msgid "right" -msgstr "справа" - -#: lib/solaar/ui/pair_window.py:130 +#: lib/solaar/ui/pair_window.py:171 #, python-format -msgid "" -"Press %(code)s\n" -"and then press left and right buttons simultaneously." -msgstr "" -"Нажмите %(code)s\n" -"и затем нажмите одновременно левую и правую кнопки." +msgid "Type %(passcode)s and then press the enter key." +msgstr "Введите %(passcode)s и затем нажмите клавишу «Ввод»." -#: lib/solaar/ui/pair_window.py:187 -msgid "Pairing failed" -msgstr "Сопряжение не удалось" +#: lib/solaar/ui/pair_window.py:176 +msgid "left" +msgstr "слева" -#: lib/solaar/ui/pair_window.py:189 -msgid "Make sure your device is within range, and has a decent battery charge." -msgstr "" -"Удостоверьтесь, что ваше устройство в радиусе приёма и батарея заряжена." +#: lib/solaar/ui/pair_window.py:176 +msgid "right" +msgstr "справа" -#: lib/solaar/ui/pair_window.py:191 -msgid "A new device was detected, but it is not compatible with this receiver." -msgstr "Обнаружено новое устройство, несовместимое с данным приёмником." - -#: lib/solaar/ui/pair_window.py:193 -msgid "More paired devices than receiver can support." -msgstr "Превышено число сопрягаемых устройств, поддерживаемое приёмником." - -#: lib/solaar/ui/pair_window.py:195 -msgid "No further details are available about the error." -msgstr "Дополнительные сведения об этой ошибке отсутствуют." - -#: lib/solaar/ui/pair_window.py:209 -msgid "Found a new device:" -msgstr "Найдено новое устройство:" - -#: lib/solaar/ui/pair_window.py:234 -msgid "The wireless link is not encrypted" -msgstr "Беспроводное соединение не зашифровано" - -#: lib/solaar/ui/pair_window.py:263 -msgid "Press a pairing button or key until the pairing light flashes quickly." -msgstr "" -"Нажимайте кнопку или клавишу сопряжения, пока индикатор сопряжения не начнёт" -" быстро мигать." - -#: lib/solaar/ui/pair_window.py:265 -msgid "You may have to first turn the device off and on again." -msgstr "Возможно, сначала придётся выключить и снова включить устройство." - -#: lib/solaar/ui/pair_window.py:267 -msgid "Turn on the device you want to pair." -msgstr "Включите устройство, с которым нужно установить сопряжение." - -#: lib/solaar/ui/pair_window.py:269 -msgid "If the device is already turned on, turn it off and on again." -msgstr "Если устройство включено, выключите его и включите снова." - -#: lib/solaar/ui/pair_window.py:272 +#: lib/solaar/ui/pair_window.py:178 #, python-format -msgid "" -"\n" -"\n" -"This receiver has %d pairing remaining." -msgid_plural "" -"\n" -"\n" -"This receiver has %d pairings remaining." -msgstr[0] "" -"\n" -"Данному приёмнику доступно %d сопряжение." -msgstr[1] "" -"\n" -"Данному приёмнику доступно %d сопряжения." -msgstr[2] "" -"\n" -"Данному приёмнику доступно %d сопряжений." +msgid "Press %(code)s\n" + "and then press left and right buttons simultaneously." +msgstr "Нажмите %(code)s\n" + "и затем нажмите одновременно левую и правую кнопки." -#: lib/solaar/ui/pair_window.py:275 -msgid "" -"\n" -"Cancelling at this point will not use up a pairing." -msgstr "" -"\n" -"Прерывание в данный момент отменит сопряжение." +#: lib/solaar/ui/pair_window.py:221 +msgid "The wireless link is not encrypted" +msgstr "Беспроводное соединение не зашифровано" -#: lib/solaar/ui/tray.py:58 -msgid "No Logitech device found" -msgstr "Устройство Logitech не найдено" +#: lib/solaar/ui/pair_window.py:226 +msgid "Found a new device:" +msgstr "Найдено новое устройство:" -#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:324 +#: lib/solaar/ui/pair_window.py:247 +msgid "Pairing failed" +msgstr "Сопряжение не удалось" + +#: lib/solaar/ui/pair_window.py:249 +msgid "Make sure your device is within range, and has a decent battery " + "charge." +msgstr "Удостоверьтесь, что ваше устройство в радиусе приёма и батарея " + "заряжена." + +#: lib/solaar/ui/pair_window.py:251 +msgid "A new device was detected, but it is not compatible with this " + "receiver." +msgstr "Обнаружено новое устройство, несовместимое с данным приёмником." + +#: lib/solaar/ui/pair_window.py:253 +msgid "More paired devices than receiver can support." +msgstr "Превышено число сопрягаемых устройств, поддерживаемое приёмником." + +#: lib/solaar/ui/pair_window.py:255 +msgid "No further details are available about the error." +msgstr "Дополнительные сведения об этой ошибке отсутствуют." + +#: lib/solaar/ui/rule_actions.py:54 +msgid "Simulate a chorded key click or depress or release.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "Имитируйте щелчок аккордовой клавиши, нажмите или отпустите.\n" + "В Wayland требуется доступ на запись в /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:60 +msgid "Add key" +msgstr "Добавить клавишу" + +#: lib/solaar/ui/rule_actions.py:63 +msgid "Click" +msgstr "Щелчок" + +#: lib/solaar/ui/rule_actions.py:66 +msgid "Depress" +msgstr "Подавить" + +#: lib/solaar/ui/rule_actions.py:69 +msgid "Release" +msgstr "Выпускать" + +#: lib/solaar/ui/rule_actions.py:153 +msgid "Simulate a mouse scroll.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "Имитация прокрутки мыши.\n" + "В Wayland требуется доступ на запись в /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:211 +msgid "Simulate a mouse click.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "Имитация щелчка мыши.В Wayland требуется доступ на запись в /dev/" + "uinput." + +#: lib/solaar/ui/rule_actions.py:216 +msgid "Button" +msgstr "Кнопка" + +#: lib/solaar/ui/rule_actions.py:218 +msgid "Action (and Count, if click)" +msgstr "Действие (и количество, если кликнуть)" + +#: lib/solaar/ui/rule_actions.py:269 +msgid "Execute a command with arguments." +msgstr "Выполните команду с аргументами." + +#: lib/solaar/ui/rule_actions.py:273 +msgid "Add argument" +msgstr "Добавить аргумент" + +#: lib/solaar/ui/rule_conditions.py:52 +msgid "X11 active process. For use in X11 only." +msgstr "Активный процесс X11. Только для использования в X11." + +#: lib/solaar/ui/rule_conditions.py:82 +msgid "X11 mouse process. For use in X11 only." +msgstr "Процесс работы с мышью X11. Только для использования в X11." + +#: lib/solaar/ui/rule_conditions.py:99 +msgid "MouseProcess" +msgstr "Действие мыши" + +#: lib/solaar/ui/rule_conditions.py:123 +msgid "Feature name of notification triggering rule processing." +msgstr "Название функции обработки правила запуска уведомления." + +#: lib/solaar/ui/rule_conditions.py:169 +msgid "Report number of notification triggering rule processing." +msgstr "Сообщите номер уведомления, запускающего обработку правила." + +#: lib/solaar/ui/rule_conditions.py:201 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "Активные модификаторы клавиатуры. Не всегда доступны в Wayland." + +#: lib/solaar/ui/rule_conditions.py:241 +msgid "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "Перенаправленная клавиша или кнопка нажата или отпущена.\n" + "Для переадресации используйте настройки «Переадресация клавиш/" + "кнопок» и «Переадресация G-клавиш» ключи и кнопки." + +#: lib/solaar/ui/rule_conditions.py:250 +msgid "Key down" +msgstr "Нажать клавишу" + +#: lib/solaar/ui/rule_conditions.py:253 +msgid "Key up" +msgstr "Отпустить клавишу" + +#: lib/solaar/ui/rule_conditions.py:293 +msgid "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "Переадресованная клавиша или кнопка в данный момент нажата.\n" + "Используйте настройки Переадресации клавиш/кнопок и Переадресации " + "клавиш G для переадресации клавиш и кнопок." + +#: lib/solaar/ui/rule_conditions.py:331 +msgid "Test condition on notification triggering rule processing." +msgstr "Тестовое условие для обработки правила запуска уведомления." + +#: lib/solaar/ui/rule_conditions.py:335 +msgid "Parameter" +msgstr "Параметр" + +#: lib/solaar/ui/rule_conditions.py:408 +msgid "begin (inclusive)" +msgstr "начало (включительно)" + +#: lib/solaar/ui/rule_conditions.py:409 +msgid "end (exclusive)" +msgstr "конец (исключительно)" + +#: lib/solaar/ui/rule_conditions.py:417 +msgid "range" +msgstr "диапазон" + +#: lib/solaar/ui/rule_conditions.py:420 +msgid "minimum" +msgstr "минимум" + +#: lib/solaar/ui/rule_conditions.py:421 +msgid "maximum" +msgstr "максимум" + +#: lib/solaar/ui/rule_conditions.py:423 #, python-format -msgid "About %s" -msgstr "О программе %s" +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "байты с %(0)d по %(1)d, в диапазоне от %(2)d до %(3)d" -#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:322 +#: lib/solaar/ui/rule_conditions.py:426 lib/solaar/ui/rule_conditions.py:427 +msgid "mask" +msgstr "маска" + +#: lib/solaar/ui/rule_conditions.py:428 #, python-format -msgid "Quit %s" -msgstr "Выйти из %s" +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "байты с %(0)d по %(1)d, маска %(2)d" -#: lib/solaar/ui/tray.py:302 lib/solaar/ui/tray.py:310 -msgid "no receiver" -msgstr "отсутствует приёмник" +#: lib/solaar/ui/rule_conditions.py:437 +msgid "Bit or range test on bytes in notification message triggering rule " + "processing." +msgstr "Проверка битов или диапазона на байтах в уведомительном сообщении, " + "запускающем обработку правила." -#: lib/solaar/ui/tray.py:326 -msgid "no status" -msgstr "отсутствует статус" +#: lib/solaar/ui/rule_conditions.py:447 +msgid "type" +msgstr "тип" -#: lib/solaar/ui/window.py:101 -msgid "Scanning" -msgstr "Сканирование" +#: lib/solaar/ui/rule_conditions.py:535 +msgid "Mouse gesture with optional initiating button followed by zero or " + "more mouse movements." +msgstr "Жест мыши с дополнительной кнопкой запуска, за которым следует ноль " + "или более движений мыши." -#: lib/solaar/ui/window.py:134 lib/solaar/ui/window.py:689 -msgid "Battery" -msgstr "Батарея" +#: lib/solaar/ui/rule_conditions.py:541 +msgid "Add movement" +msgstr "Добавить движение" -#: lib/solaar/ui/window.py:137 -msgid "Wireless Link" -msgstr "Беспроводное соединение" +#: lib/solaar/ui/tray.py:55 +msgid "No supported device found" +msgstr "Поддерживаемое устройство не найдено" + +#: lib/solaar/ui/tray.py:60 lib/solaar/ui/window.py:328 +#, python-format +msgid "About %s" +msgstr "О программе %s" + +#: lib/solaar/ui/tray.py:61 lib/solaar/ui/window.py:326 +#, python-format +msgid "Quit %s" +msgstr "Выйти из %s" + +#: lib/solaar/ui/tray.py:273 lib/solaar/ui/tray.py:281 +msgid "no receiver" +msgstr "отсутствует приёмник" + +#: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:299 +#: lib/solaar/ui/window.py:728 +msgid "offline" +msgstr "нет соединения" + +#: lib/solaar/ui/tray.py:297 +msgid "no status" +msgstr "отсутствует статус" + +#: lib/solaar/ui/window.py:110 +msgid "Scanning" +msgstr "Сканирование" #: lib/solaar/ui/window.py:141 -msgid "Lighting" -msgstr "Освещённость" +msgid "Battery" +msgstr "Батарея" -#: lib/solaar/ui/window.py:175 -msgid "Show Technical Details" -msgstr "Технические сведения об устройстве" +#: lib/solaar/ui/window.py:144 +msgid "Wireless Link" +msgstr "Беспроводное соединение" -#: lib/solaar/ui/window.py:191 -msgid "Pair new device" -msgstr "Новое сопряженное устройство" +#: lib/solaar/ui/window.py:148 +msgid "Lighting" +msgstr "Освещённость" -#: lib/solaar/ui/window.py:210 -msgid "Select a device" -msgstr "Выбор устройства" +#: lib/solaar/ui/window.py:182 +msgid "Show Technical Details" +msgstr "Технические сведения об устройстве" -#: lib/solaar/ui/window.py:327 -msgid "Rule Editor" -msgstr "Редактор правил" +#: lib/solaar/ui/window.py:198 +msgid "Pair new device" +msgstr "Новое сопряженное устройство" -#: lib/solaar/ui/window.py:538 -msgid "Path" -msgstr "Расположение" +#: lib/solaar/ui/window.py:216 +msgid "Select a device" +msgstr "Выбор устройства" + +#: lib/solaar/ui/window.py:331 +msgid "Rule Editor" +msgstr "Редактор правил" + +#: lib/solaar/ui/window.py:522 +msgid "Path" +msgstr "Расположение" + +#: lib/solaar/ui/window.py:524 +msgid "USB ID" +msgstr "Идент. USB" + +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 +#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 +msgid "Serial" +msgstr "Серийный №" + +#: lib/solaar/ui/window.py:533 +msgid "Index" +msgstr "Индекс" + +#: lib/solaar/ui/window.py:535 +msgid "Wireless PID" +msgstr "Б/проводн.PID" + +#: lib/solaar/ui/window.py:537 +msgid "Product ID" +msgstr "ID изделия" + +#: lib/solaar/ui/window.py:539 +msgid "Protocol" +msgstr "Протокол" + +#: lib/solaar/ui/window.py:539 +msgid "Unknown" +msgstr "Неизвестен" #: lib/solaar/ui/window.py:541 -msgid "USB ID" -msgstr "ID USB" +msgid "Polling rate" +msgstr "Част. опроса" -#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 -#: lib/solaar/ui/window.py:566 lib/solaar/ui/window.py:568 -msgid "Serial" -msgstr "Серийный №" - -#: lib/solaar/ui/window.py:550 -msgid "Index" -msgstr "Индекс" - -#: lib/solaar/ui/window.py:552 -msgid "Wireless PID" -msgstr "Б/проводн.PID" - -#: lib/solaar/ui/window.py:554 -msgid "Product ID" -msgstr "ID изделия" - -#: lib/solaar/ui/window.py:556 -msgid "Protocol" -msgstr "Протокол" - -#: lib/solaar/ui/window.py:556 -msgid "Unknown" -msgstr "Неизвестен" +#: lib/solaar/ui/window.py:548 +msgid "Unit ID" +msgstr "ID устройства" #: lib/solaar/ui/window.py:559 +msgid "none" +msgstr "никто" + +#: lib/solaar/ui/window.py:560 +msgid "Notifications" +msgstr "Уведомления" + +#: lib/solaar/ui/window.py:604 +msgid "No device paired." +msgstr "Нет сопряжённых устройств." + +#: lib/solaar/ui/window.py:613 #, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d мс (%(rate_hz)d Гц)" +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "С этим приёмником может быть сопряжено до %(max_count)s " + "устройства." +msgstr[1] "С этим приёмником может быть сопряжено до %(max_count)s " + "устройств." +msgstr[2] "С этим приёмником может быть сопряжено до %(max_count)s " + "устройств." -#: lib/solaar/ui/window.py:559 -msgid "Polling rate" -msgstr "Част. опроса" +#: lib/solaar/ui/window.py:620 +msgid "Only one device can be paired to this receiver." +msgstr "К этому приемнику может быть подключено только одно устройство." -#: lib/solaar/ui/window.py:570 -msgid "Unit ID" -msgstr "ID устройства" - -#: lib/solaar/ui/window.py:581 -msgid "none" -msgstr "нет" - -#: lib/solaar/ui/window.py:582 -msgid "Notifications" -msgstr "Уведомления" - -#: lib/solaar/ui/window.py:619 -msgid "No device paired." -msgstr "Нет сопряжённых устройств." - -#: lib/solaar/ui/window.py:626 +#: lib/solaar/ui/window.py:624 #, python-format -msgid "Up to %(max_count)s device can be paired to this receiver." -msgid_plural "Up to %(max_count)s devices can be paired to this receiver." -msgstr[0] "С этим приёмником может быть сопряжено до %(max_count)s устройства." -msgstr[1] "С этим приёмником может быть сопряжено до %(max_count)s устройств." -msgstr[2] "С этим приёмником может быть сопряжено до %(max_count)s устройств." +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "Данному приёмнику доступно ещё %d сопряжение." +msgstr[1] "Данному приёмнику доступно ещё %d сопряжения." +msgstr[2] "Данному приёмнику доступно ещё %d сопряжений." -#: lib/solaar/ui/window.py:632 -msgid "Only one device can be paired to this receiver." -msgstr "С этим приёмником может быть сопряжено только одно устройство." +#: lib/solaar/ui/window.py:681 +msgid "Battery Voltage" +msgstr "Напряжение батареи" -#: lib/solaar/ui/window.py:636 -#, python-format -msgid "This receiver has %d pairing remaining." -msgid_plural "This receiver has %d pairings remaining." -msgstr[0] "Данному приёмнику доступно ещё %d сопряжение." -msgstr[1] "Данному приёмнику доступно ещё %d сопряжения." -msgstr[2] "Данному приёмнику доступно ещё %d сопряжений." +#: lib/solaar/ui/window.py:683 +msgid "Voltage reported by battery" +msgstr "Напряжение батареи" -#: lib/solaar/ui/window.py:690 -msgid "unknown" -msgstr "неизвестно" +#: lib/solaar/ui/window.py:685 +msgid "Battery Level" +msgstr "Уровень заряда" -#: lib/solaar/ui/window.py:691 -msgid "Battery information unknown." -msgstr "Сведения о батарее отсутствуют." +#: lib/solaar/ui/window.py:687 +msgid "Approximate level reported by battery" +msgstr "Приблизительный уровень заряда, cообщаемый батареей" -#: lib/solaar/ui/window.py:699 -msgid "Battery Voltage" -msgstr "Напряжение батареи" +#: lib/solaar/ui/window.py:694 lib/solaar/ui/window.py:696 +msgid "next reported " +msgstr "следующий доклад на " -#: lib/solaar/ui/window.py:701 -msgid "Voltage reported by battery" -msgstr "Напряжение батареи" +#: lib/solaar/ui/window.py:697 +msgid " and next level to be reported." +msgstr " \n" + "(следующий уровень для отчёта)." -#: lib/solaar/ui/window.py:703 lib/solaar/ui/window.py:707 -msgid "Battery Level" -msgstr "Уровень заряда" +#: lib/solaar/ui/window.py:702 +msgid "last known" +msgstr "последний известный" -#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:709 -msgid "Approximate level reported by battery" -msgstr "Приблизительный уровень заряда, cообщаемый батареей" - -#: lib/solaar/ui/window.py:712 lib/solaar/ui/window.py:714 -msgid "next reported " -msgstr "следующий доклад на " +#: lib/solaar/ui/window.py:713 +msgid "encrypted" +msgstr "зашифровано" #: lib/solaar/ui/window.py:715 -msgid " and next level to be reported." -msgstr "" -" \n" -"(следующий уровень для отчёта)." +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "Беспроводное соединение между этим устройством и его приёмником " + "зашифровано." -#: lib/solaar/ui/window.py:720 -msgid "last known" -msgstr "последнее известное" +#: lib/solaar/ui/window.py:717 +msgid "not encrypted" +msgstr "не зашифровано" -#: lib/solaar/ui/window.py:728 -msgid "not encrypted" -msgstr "не зашифровано" +#: lib/solaar/ui/window.py:721 +msgid "The wireless link between this device and its receiver is not " + "encrypted.\n" + "This is a security issue for pointing devices, and a major security " + "issue for text-input devices." +msgstr "Беспроводное соединение между этим устройством и его приёмником не " + "зашифровано.\n" + "Это проблема безопасности для указывающих устройств и серьезная " + "проблема безопасности для устройств ввода текста." -#: lib/solaar/ui/window.py:732 -msgid "" -"The wireless link between this device and its receiver is not encrypted.\n" -"\n" -"For pointing devices (mice, trackballs, trackpads), this is a minor security " -"issue.\n" -"\n" -"It is, however, a major security issue for text-input devices (keyboards, " -"numpads),\n" -"because typed text can be sniffed inconspicuously by 3rd parties within " -"range." -msgstr "" -"Беспроводное соединение между этим устройством и приёмником не зашифровано.\n" -"\n" -"Для указывающих устройств (мышь, трекбол, трекпад) не является серьёзной " -"уязвимостью в безопасности, в отличие от устройств ввода текста (клавиатура, " -"цифровая клавиатура), ввод с которых может быть перехвачен кем-либо в " -"радиусе передачи." - -#: lib/solaar/ui/window.py:741 -msgid "encrypted" -msgstr "зашифровано" - -#: lib/solaar/ui/window.py:743 -msgid "The wireless link between this device and its receiver is encrypted." -msgstr "" -"Беспроводное соединение между этим устройством и приёмником зашифровано." - -#: lib/solaar/ui/window.py:756 +#: lib/solaar/ui/window.py:737 #, python-format -msgid "%(light_level)d lux" -msgstr "%(light_level)d люкс" - -#~ msgid "top" -#~ msgstr "верх" - -#~ msgid "width" -#~ msgstr "ширина" - -#, python-format -#~ msgid "%(battery_level)s" -#~ msgstr "%(battery_level)s" - -#, python-format -#~ msgid "%(battery_percent)d%%" -#~ msgstr "%(battery_percent)d%%" - -#~ msgid "" -#~ "Automatically switch the mouse wheel between ratchet and freespin mode.\n" -#~ "The mouse wheel is always free at 0, and always locked at 50" -#~ msgstr "" -#~ "Автоматически переключать режим колесика между пошаговой и сверхбыстрой " -#~ "прокруткой.\n" -#~ "Колесико мыши всегда свободно при значении 0 и заблокировано при 50" - -#~ msgid "" -#~ "If the device is already turned on,\n" -#~ "turn if off and on again." -#~ msgstr "" -#~ "Если устройство уже включено,\n" -#~ "выключите и включите его снова." - -#~ msgid "" -#~ "Shows status of devices connected\n" -#~ "through wireless Logitech receivers." -#~ msgstr "" -#~ "Показывает статус устройств, подсоединенных\n" -#~ "через беспроводные приёмники Logitech." - -#, python-format -#~ msgid "The receiver only supports %d paired device(s)." -#~ msgstr "Приёмник поддерживает только %d сопряженных устройств(а)." - -#~ msgid "The receiver was unplugged." -#~ msgstr "Приёмник извлечён." - -#~ msgid "USB id" -#~ msgstr "Идент. USB" +msgid "%(light_level)d lux" +msgstr "%(light_level)d люкс" diff --git a/po/sk.po b/po/sk.po index 667b78d1..e28d9513 100644 --- a/po/sk.po +++ b/po/sk.po @@ -3,1434 +3,1996 @@ # This file is distributed under the same license as the solaar package. # Automatically generated, 2021. # -msgid "" -msgstr "Project-Id-Version: solaar 1.0.6\n" - "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2021-09-25 13:56-0400\n" - "PO-Revision-Date: 2021-08-24 09:07+0200\n" - "Last-Translator: Jose Riha \n" - "Language-Team: none\n" - "Language: sk\n" - "MIME-Version: 1.0\n" - "Content-Type: text/plain; charset=UTF-8\n" - "Content-Transfer-Encoding: 8bit\n" - "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : " - "2;\n" - "X-Generator: Poedit 3.0\n" +# SPDX-FileCopyrightText: 2026 Miroslav Ďurian +msgid "" +msgstr "" +"Project-Id-Version: solaar 1.0.6\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-12-28 17:40+0100\n" +"PO-Revision-Date: 2026-01-15 13:17+0100\n" +"Last-Translator: Miroslav Ďurian \n" +"Language-Team: Slovak \n" +"Language: sk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"X-Generator: Lokalize 25.12.0\n" -#: lib/logitech_receiver/base_usb.py:50 -msgid "Unifying Receiver" -msgstr "Prijímač Unifying" +#: lib/logitech_receiver/base_usb.py:46 +msgid "Bolt Receiver" +msgstr "Prijímač Bolt" -#: lib/logitech_receiver/base_usb.py:58 lib/logitech_receiver/base_usb.py:68 -#: lib/logitech_receiver/base_usb.py:79 lib/logitech_receiver/base_usb.py:90 -#: lib/logitech_receiver/base_usb.py:101 -msgid "Nano Receiver" -msgstr "Prijímač Nano" +#: lib/logitech_receiver/base_usb.py:57 +msgid "Unifying Receiver" +msgstr "Prijímač Unifying" -#: lib/logitech_receiver/base_usb.py:109 -msgid "Lightspeed Receiver" -msgstr "Prijímač Lightspeed" +#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 +#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 +#: lib/logitech_receiver/base_usb.py:114 +msgid "Nano Receiver" +msgstr "Prijímač Nano" -#: lib/logitech_receiver/base_usb.py:117 -msgid "EX100 Receiver 27 Mhz" -msgstr "Prijímač EX100 27 Mhz" +#: lib/logitech_receiver/base_usb.py:124 +msgid "Lightspeed Receiver" +msgstr "Prijímač Lightspeed" -#: lib/logitech_receiver/device.py:133 lib/solaar/ui/window.py:693 -msgid "unknown" -msgstr "neznáme" +#: lib/logitech_receiver/base_usb.py:133 +msgid "EX100 Receiver 27 Mhz" +msgstr "Prijímač EX100 27 Mhz" + +#: lib/logitech_receiver/i18n.py:30 +msgid "empty" +msgstr "prázdna" + +#: lib/logitech_receiver/i18n.py:31 +msgid "critical" +msgstr "kritická" + +#: lib/logitech_receiver/i18n.py:32 +msgid "low" +msgstr "nízka" + +#: lib/logitech_receiver/i18n.py:33 +msgid "average" +msgstr "priemerná" + +#: lib/logitech_receiver/i18n.py:34 +msgid "good" +msgstr "dobrá" + +#: lib/logitech_receiver/i18n.py:35 +msgid "full" +msgstr "plná" #: lib/logitech_receiver/i18n.py:38 -msgid "empty" -msgstr "prázdna" +msgid "discharging" +msgstr "vybíja sa" #: lib/logitech_receiver/i18n.py:39 -msgid "critical" -msgstr "kritická" +msgid "recharging" +msgstr "nabíja sa" -#: lib/logitech_receiver/i18n.py:40 -msgid "low" -msgstr "nízka" +#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 +msgid "charging" +msgstr "nabíja sa" #: lib/logitech_receiver/i18n.py:41 -msgid "average" -msgstr "priemerná" +msgid "not charging" +msgstr "nenabíja sa" #: lib/logitech_receiver/i18n.py:42 -msgid "good" -msgstr "dobrá" +msgid "almost full" +msgstr "takmer nabitá" #: lib/logitech_receiver/i18n.py:43 -msgid "full" -msgstr "plná" +msgid "charged" +msgstr "nabitá" + +#: lib/logitech_receiver/i18n.py:44 +msgid "slow recharge" +msgstr "pomalé nabíjanie" + +#: lib/logitech_receiver/i18n.py:45 +msgid "invalid battery" +msgstr "chybná batéria" #: lib/logitech_receiver/i18n.py:46 -msgid "discharging" -msgstr "vybíja sa" +msgid "thermal error" +msgstr "teplotná chyba" #: lib/logitech_receiver/i18n.py:47 -msgid "recharging" -msgstr "nabíja sa" +msgid "error" +msgstr "chyba" -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:721 -msgid "charging" -msgstr "nabíja sa" +#: lib/logitech_receiver/i18n.py:48 +msgid "standard" +msgstr "štandardné" #: lib/logitech_receiver/i18n.py:49 -msgid "not charging" -msgstr "nenabíja sa" +msgid "fast" +msgstr "rýchle" #: lib/logitech_receiver/i18n.py:50 -msgid "almost full" -msgstr "takmer nabitá" - -#: lib/logitech_receiver/i18n.py:51 -msgid "charged" -msgstr "nabitá" - -#: lib/logitech_receiver/i18n.py:52 -msgid "slow recharge" -msgstr "pomalé nabíjanie" +msgid "slow" +msgstr "pomalé" #: lib/logitech_receiver/i18n.py:53 -msgid "invalid battery" -msgstr "chybná batéria" +msgid "device timeout" +msgstr "vypršal časový limit zariadenia" #: lib/logitech_receiver/i18n.py:54 -msgid "thermal error" -msgstr "teplotná chyba" +msgid "device not supported" +msgstr "nepodporované zariadenie" #: lib/logitech_receiver/i18n.py:55 -msgid "error" -msgstr "chyba" +msgid "too many devices" +msgstr "príliš veľa zariadení" #: lib/logitech_receiver/i18n.py:56 -msgid "standard" -msgstr "štandardné" +msgid "sequence timeout" +msgstr "vypršal časový limit sekvencie" -#: lib/logitech_receiver/i18n.py:57 -msgid "fast" -msgstr "rýchle" +#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 +msgid "Firmware" +msgstr "Firmvér" -#: lib/logitech_receiver/i18n.py:58 -msgid "slow" -msgstr "pomalé" +#: lib/logitech_receiver/i18n.py:60 +msgid "Bootloader" +msgstr "Bootloader" #: lib/logitech_receiver/i18n.py:61 -msgid "device timeout" -msgstr "vypršal časový limit zariadenia" +msgid "Hardware" +msgstr "Hardvér" #: lib/logitech_receiver/i18n.py:62 -msgid "device not supported" -msgstr "nepodporované zariadenie" +msgid "Other" +msgstr "Ďalší" -#: lib/logitech_receiver/i18n.py:63 -msgid "too many devices" -msgstr "príliš veľa zariadení" +#: lib/logitech_receiver/i18n.py:65 +msgid "Left Button" +msgstr "Ľavé tlačidlo" -#: lib/logitech_receiver/i18n.py:64 -msgid "sequence timeout" -msgstr "vypršal časový limit sekvencie" +#: lib/logitech_receiver/i18n.py:66 +msgid "Right Button" +msgstr "Pravé tlačidlo" -#: lib/logitech_receiver/i18n.py:67 lib/solaar/ui/window.py:580 -msgid "Firmware" -msgstr "Firmvér" +#: lib/logitech_receiver/i18n.py:67 +msgid "Middle Button" +msgstr "Stredné tlačidlo" #: lib/logitech_receiver/i18n.py:68 -msgid "Bootloader" -msgstr "Bootloader" +msgid "Back Button" +msgstr "Tlačidlo Späť" #: lib/logitech_receiver/i18n.py:69 -msgid "Hardware" -msgstr "Hardvér" +msgid "Forward Button" +msgstr "Tlačidlo Dopredu" #: lib/logitech_receiver/i18n.py:70 -msgid "Other" -msgstr "Ďalší" +msgid "Mouse Gesture Button" +msgstr "Tlačidlo gesta myši" + +#: lib/logitech_receiver/i18n.py:71 +msgid "Smart Shift" +msgstr "Inteligentné Prepínanie" + +#: lib/logitech_receiver/i18n.py:72 +msgid "DPI Switch" +msgstr "Prepínač DPI" #: lib/logitech_receiver/i18n.py:73 -msgid "Left Button" -msgstr "" +msgid "Left Tilt" +msgstr "Náklon Vľavo" #: lib/logitech_receiver/i18n.py:74 -msgid "Right Button" -msgstr "" +msgid "Right Tilt" +msgstr "Náklon Vpravo" #: lib/logitech_receiver/i18n.py:75 -msgid "Middle Button" -msgstr "" +msgid "Left Click" +msgstr "Ľavý klik" #: lib/logitech_receiver/i18n.py:76 -msgid "Back Button" -msgstr "" +msgid "Right Click" +msgstr "Pravý klik" #: lib/logitech_receiver/i18n.py:77 -msgid "Forward Button" -msgstr "" +msgid "Mouse Middle Button" +msgstr "Stredné tlačidlo myši" #: lib/logitech_receiver/i18n.py:78 -msgid "Mouse Gesture Button" -msgstr "Tlačidlo gesta myši" +msgid "Mouse Back Button" +msgstr "Tlačidlo Späť na myši" #: lib/logitech_receiver/i18n.py:79 -msgid "Smart Shift" -msgstr "" +msgid "Mouse Forward Button" +msgstr "Tlačidlo Dopredu na myši" #: lib/logitech_receiver/i18n.py:80 -msgid "DPI Switch" -msgstr "" +msgid "Gesture Button Navigation" +msgstr "Tlačidlo navigácie gestami" #: lib/logitech_receiver/i18n.py:81 -msgid "Left Tilt" -msgstr "" +msgid "Mouse Scroll Left Button" +msgstr "Ľavé tlačidlo rolovania na myši" #: lib/logitech_receiver/i18n.py:82 -msgid "Right Tilt" -msgstr "" - -#: lib/logitech_receiver/i18n.py:83 -msgid "Left Click" -msgstr "" - -#: lib/logitech_receiver/i18n.py:84 -msgid "Right Click" -msgstr "" +msgid "Mouse Scroll Right Button" +msgstr "Pravé tlačidlo rolovania na myši" #: lib/logitech_receiver/i18n.py:85 -msgid "Mouse Middle Button" -msgstr "" +msgid "pressed" +msgstr "stlačnené" #: lib/logitech_receiver/i18n.py:86 -msgid "Mouse Back Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:87 -msgid "Mouse Forward Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:88 -msgid "Gesture Button Navigation" -msgstr "" - -#: lib/logitech_receiver/i18n.py:89 -msgid "Mouse Scroll Left Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:90 -msgid "Mouse Scroll Right Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:93 -msgid "pressed" -msgstr "" - -#: lib/logitech_receiver/i18n.py:94 -msgid "released" -msgstr "" - -#: lib/logitech_receiver/notifications.py:76 -msgid "pairing lock is closed" -msgstr "zámok párovania bol uzatvorený" - -#: lib/logitech_receiver/notifications.py:76 -msgid "pairing lock is open" -msgstr "zámok párovania bol otvorený" - -#: lib/logitech_receiver/notifications.py:160 lib/solaar/ui/notify.py:123 -msgid "connected" -msgstr "pripojené" - -#: lib/logitech_receiver/notifications.py:160 -msgid "disconnected" -msgstr "odpojené" - -#: lib/logitech_receiver/notifications.py:198 lib/solaar/ui/notify.py:121 -msgid "unpaired" -msgstr "nespárované" - -#: lib/logitech_receiver/notifications.py:248 -msgid "powered on" -msgstr "zapnuté" - -#: lib/logitech_receiver/settings.py:526 -msgid "register" -msgstr "prihlásiť" - -#: lib/logitech_receiver/settings.py:542 lib/logitech_receiver/settings.py:568 -msgid "feature" -msgstr "vlastnosť" - -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Hand Detection" -msgstr "Detekcia rúk" - -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "Zapne podsvietenie, keď sa nad klávesnicou objavia ruky." - -#: lib/logitech_receiver/settings_templates.py:72 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "Plynulé skrolovanie" - -#: lib/logitech_receiver/settings_templates.py:73 -#: lib/logitech_receiver/settings_templates.py:78 -#: lib/logitech_receiver/settings_templates.py:85 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "Vysokocitlivý režim zvislého posunu kolieska myši." - -#: lib/logitech_receiver/settings_templates.py:74 -msgid "Side Scrolling" -msgstr "Bočné skrolovanie" - -#: lib/logitech_receiver/settings_templates.py:75 -msgid "When disabled, pushing the wheel sideways sends custom button " - "events\n" - "instead of the standard side-scrolling events." -msgstr "Ak je vypnuté, bočné stlačenie kolieska vygeneruje vlastné udalosti " - "tlačidiel\n" - "namiesto štandardných udalostí skrolovania do strán." - -#: lib/logitech_receiver/settings_templates.py:77 -msgid "Scroll Wheel High Resolution" -msgstr "Vysoké rozlíšenie skrolovania myši" - -#: lib/logitech_receiver/settings_templates.py:79 -msgid "Scroll Wheel Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:80 -msgid "HID++ mode for vertical scroll with the wheel." -msgstr "HID++ režim pre vertikálne skrolovanie myši." - -#: lib/logitech_receiver/settings_templates.py:81 -msgid "Effectively turns off wheel scrolling in Linux." -msgstr "Vypne skrolovanie kolieskom myši v Linuxe." - -#: lib/logitech_receiver/settings_templates.py:82 -msgid "Scroll Wheel Direction" -msgstr "Smer skrolovania" - -#: lib/logitech_receiver/settings_templates.py:83 -msgid "Invert direction for vertical scroll with wheel." -msgstr "Zmeniť zmer zvislého skrolovania myši." - -#: lib/logitech_receiver/settings_templates.py:84 -msgid "Scroll Wheel Resolution" -msgstr "Rozlíšenie skrolovania myši" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Frequency of device polling, in milliseconds" -msgstr "Frekvencia dopytov na zariadenie, v ms" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Polling Rate (ms)" -msgstr "Intenzita dopytovania (ms)" - -#: lib/logitech_receiver/settings_templates.py:87 -msgid "Swap Fx function" -msgstr "Prepnúť funkciu Fx" - -#: lib/logitech_receiver/settings_templates.py:88 -msgid "When set, the F1..F12 keys will activate their special function,\n" - "and you must hold the FN key to activate their standard function." -msgstr "Ak je nastavené, budú klávesy F1..F12 zapínať ich špeciálne " - "funkcie.\n" - "Pre aktiváciu ich štandardnej funkcie budete musieť súčasne stlačiť " - "Fn." - -#: lib/logitech_receiver/settings_templates.py:90 -msgid "When unset, the F1..F12 keys will activate their standard function,\n" - "and you must hold the FN key to activate their special function." -msgstr "Ak nie je nastavené, budú klávesy F1..F12 zapínať ich štandardné " - "funkcie.\n" - "Pre aktiváciu špeciálnych funkcií budete musieť súčasne stlačiť Fn." - -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Mouse movement sensitivity" -msgstr "Citlivosť kurzora myši" - -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Sensitivity (DPI)" -msgstr "Citlivosť (DPI)" - -#: lib/logitech_receiver/settings_templates.py:93 -msgid "Sensitivity (Pointer Speed)" -msgstr "Citlivosť (rýchlosť kurzora)" - -#: lib/logitech_receiver/settings_templates.py:94 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "Násobič rýchlosti myši (256 je bežný násobič)." - -#: lib/logitech_receiver/settings_templates.py:95 -msgid "Scroll Wheel Rachet" -msgstr "Krokovanie kolieska" - -#: lib/logitech_receiver/settings_templates.py:96 -msgid "Automatically switch the mouse wheel between ratchet and freespin " - "mode.\n" - "The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "Automaticky prepnúť režim kolieska myši medzi krokovaním a voľným " - "otáčaním.\n" - "Koliesko je pri hodnote 0 vždy voľné a pri hodnote 50 vždy krokuje" - -#: lib/logitech_receiver/settings_templates.py:98 -msgid "Backlight" -msgstr "Podsvietenie" - -#: lib/logitech_receiver/settings_templates.py:98 -msgid "Turn illumination on or off on keyboard." -msgstr "Vypne/zapne podsvietenie klávesnice." - -#: lib/logitech_receiver/settings_templates.py:99 -msgid "Key/Button Actions" -msgstr "Akcie klávesu/tlačidla" - -#: lib/logitech_receiver/settings_templates.py:100 -msgid "Change the action for the key or button." -msgstr "Zmeniť akciu klávesy alebo tlačidla." - -#: lib/logitech_receiver/settings_templates.py:101 -msgid "Changing important actions (such as for the left mouse button) can " - "result in an unusable system." -msgstr "Zmenou dôležitej akcie (napríklad pre ľavé tlačidlo myši) môžete " - "systém uviesť do stavu, kedy sa nebude dať používať." - -#: lib/logitech_receiver/settings_templates.py:102 -msgid "Key/Button Diversion" -msgstr "Odklonenie klávesu/tlačidla" - -#: lib/logitech_receiver/settings_templates.py:103 -msgid "Make the key or button send HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." -msgstr "Kláves alebo tlačidlo budú zasielať HID++ notifikácie (tie aktivujú " - "Solaar pravidlá, ale inak budú ignorované)." - -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable keys" -msgstr "Zakázať klávesy" - -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable specific keyboard keys." -msgstr "Zakázať špecifické klávesy." - -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Change keys to match OS." -msgstr "Zmeniť klávesy podľa operačného systému." - -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Set OS" -msgstr "Nastaviť OS" - -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Change Host" -msgstr "Zmeniť hostiteľa" - -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Switch connection to a different host" -msgstr "Prepnúť spojenie na iného hostiteľa" - -#: lib/logitech_receiver/settings_templates.py:107 -msgid "Thumb Wheel Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:108 -msgid "HID++ mode for horizontal scroll with the thumb wheel." -msgstr "HID++ režim pre vodorovný posun pomocou kolieska." - -#: lib/logitech_receiver/settings_templates.py:109 -msgid "Effectively turns off thumb scrolling in Linux." -msgstr "Vypne skrolovanie palcom v Linuxe." - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Invert thumb wheel scroll direction." -msgstr "Zmeniť smer otáčania kolieska." - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Thumb Wheel Direction" -msgstr "Smer posuvného kolieska" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Gestures" -msgstr "Gestá" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "Upraviť správanie myši/touchpadu." - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "Zmeňte číselné parametre myši/touchpadu." - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Gesture params" -msgstr "Parametre gesta" - -#: lib/logitech_receiver/settings_templates.py:113 -msgid "DPI Sliding Adjustment" -msgstr "Úprava DPI pomocou myši" - -#: lib/logitech_receiver/settings_templates.py:114 -msgid "Adjust the DPI by sliding the mouse horizontally while holding the " - "button down." -msgstr "Úprava DPI pohybom myši vo vodorovnom smere pri súčasnom držaní " - "tlačidla." - -#: lib/logitech_receiver/settings_templates.py:115 -msgid "Mouse Gestures" -msgstr "Gestá myši" - -#: lib/logitech_receiver/settings_templates.py:116 -msgid "Send a gesture by sliding the mouse while holding the button down." -msgstr "Pošlite gesto pohybom myši pri súčasnom držaní tlačidla." - -#: lib/logitech_receiver/settings_templates.py:117 -msgid "Divert crown events" -msgstr "Odklonenie udalostí korunky" - -#: lib/logitech_receiver/settings_templates.py:118 -msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "Korunka bude zasielať CROWN HID++ notifikácie (tie aktivujú Solaar " - "pravidlá, ale inak budú ignorované)." - -#: lib/logitech_receiver/settings_templates.py:119 -msgid "Divert G Keys" -msgstr "Odkloniť G klávesy" - -#: lib/logitech_receiver/settings_templates.py:120 -msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "G klávesy budú zasielať GKEY HID++ notifikácie (tie aktivujú Solaar " - "pravidlá, ale inak budú ignorované)." - -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Performs a left click." -msgstr "Vykoná kliknutie ľavým tlačidlom." - -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Single tap" -msgstr "Dotyk" - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Performs a right click." -msgstr "Vykoná kliknutie pravým tlačidlom." - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Single tap with two fingers" -msgstr "Dotyk dvoma prstami" - -#: lib/logitech_receiver/settings_templates.py:125 -msgid "Single tap with three fingers" -msgstr "Dotyk tromi prstami" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Double tap" -msgstr "Dvojdotyk" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Performs a double click." -msgstr "Vykoná dvojité kliknutie." - -#: lib/logitech_receiver/settings_templates.py:130 -msgid "Double tap with two fingers" -msgstr "Dvojdotyk dvoma prstami" - -#: lib/logitech_receiver/settings_templates.py:131 -msgid "Double tap with three fingers" -msgstr "Dvojdotyk troma prstami" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Drags items by dragging the finger after double tapping." -msgstr "Presúvanie položiek ťahaním prstu po dvojdotyku." - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Tap and drag" -msgstr "Ťahaj a pusti" - -#: lib/logitech_receiver/settings_templates.py:135 -msgid "Tap and drag with two fingers" -msgstr "Dotyk a ťahanie dvomi prstami" - -#: lib/logitech_receiver/settings_templates.py:136 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "Presúvanie položiek ťahaním prstov po dvojdotyku." - -#: lib/logitech_receiver/settings_templates.py:137 -msgid "Tap and drag with three fingers" -msgstr "Dotyk a ťahanie tromi prstami" +msgid "released" +msgstr "uvoľnené" + +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 +msgid "pairing lock is closed" +msgstr "zámok párovania bol uzatvorený" + +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 +msgid "pairing lock is open" +msgstr "zámok párovania bol otvorený" + +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is closed" +msgstr "zámok objavovania je uzatvorený" + +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is open" +msgstr "zámok objavovania je otvorený" + +#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 +msgid "connected" +msgstr "pripojené" + +#: lib/logitech_receiver/notifications.py:224 +msgid "disconnected" +msgstr "odpojené" + +#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 +msgid "unpaired" +msgstr "nespárované" + +#: lib/logitech_receiver/notifications.py:304 +msgid "powered on" +msgstr "zapnuté" + +#: lib/logitech_receiver/settings.py:750 +msgid "register" +msgstr "prihlásiť" + +#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 +msgid "feature" +msgstr "vlastnosť" #: lib/logitech_receiver/settings_templates.py:139 -msgid "Suppress tap and edge gestures" -msgstr "Potlačiť gestá dotyku a hrany" +msgid "Swap Fx function" +msgstr "Prepnúť funkciu Fx" #: lib/logitech_receiver/settings_templates.py:140 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "Vypnúť gestá dotyku a hrany (ekvivalent stlačenia Fn + ľavý klik)." - -#: lib/logitech_receiver/settings_templates.py:141 -msgid "Scroll with one finger" -msgstr "Skrolovanie jedným prstom" - -#: lib/logitech_receiver/settings_templates.py:141 -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scrolls." -msgstr "Skrolovania." +msgid "" +"When set, the F1..F12 keys will activate their special function,\n" +"and you must hold the FN key to activate their standard function." +msgstr "" +"Ak je nastavené, budú klávesy F1..F12 zapínať ich špeciálne " +"funkcie.\n" +"Pre aktiváciu ich štandardnej funkcie budete musieť súčasne stlačiť " +"Fn." #: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scroll with two fingers" -msgstr "Skrolovanie dvoma prstami" +msgid "" +"When unset, the F1..F12 keys will activate their standard function,\n" +"and you must hold the FN key to activate their special function." +msgstr "" +"Ak nie je nastavené, budú klávesy F1..F12 zapínať ich štandardné " +"funkcie.\n" +"Pre aktiváciu špeciálnych funkcií budete musieť súčasne stlačiť Fn." -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scroll horizontally with two fingers" -msgstr "Vodorovné skrolovanie dvoma prstami" +#: lib/logitech_receiver/settings_templates.py:149 +msgid "Hand Detection" +msgstr "Detekcia rúk" -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scrolls horizontally." -msgstr "Vodorovné skrolovanie." +#: lib/logitech_receiver/settings_templates.py:150 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Zapne podsvietenie, keď sa nad klávesnicou objavia ruky." -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scroll vertically with two fingers" -msgstr "Zvislé skrolovanie dvoma prstami" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scrolls vertically." -msgstr "Zvislé skrolovanie." - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Inverts the scrolling direction." -msgstr "Zmeniť smer otáčania." - -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Natural scrolling" -msgstr "Prirodzené skrolovanie" - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Enables the thumbwheel." -msgstr "Povoliť koliesko." - -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Thumbwheel" -msgstr "Koliesko" +#: lib/logitech_receiver/settings_templates.py:157 +msgid "Scroll Wheel Smooth Scrolling" +msgstr "Plynulé skrolovanie" #: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:162 -msgid "Swipe from the top edge" -msgstr "Ťahanie z hornej hrany" - -#: lib/logitech_receiver/settings_templates.py:159 -msgid "Swipe from the left edge" -msgstr "Ťahanie z ľavej hrany" - -#: lib/logitech_receiver/settings_templates.py:160 -msgid "Swipe from the right edge" -msgstr "Ťahanie z pravej hrany" - -#: lib/logitech_receiver/settings_templates.py:161 -msgid "Swipe from the bottom edge" -msgstr "Ťahanie zo spodnej hrany" - -#: lib/logitech_receiver/settings_templates.py:163 -msgid "Swipe two fingers from the left edge" -msgstr "Ťahanie dvoma prstami z ľavej hrany" - -#: lib/logitech_receiver/settings_templates.py:164 -msgid "Swipe two fingers from the right edge" -msgstr "Ťahanie dvoma prstami z pravej hrany" +#: lib/logitech_receiver/settings_templates.py:239 +#: lib/logitech_receiver/settings_templates.py:267 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Vysokocitlivý režim zvislého posunu kolieska myši." #: lib/logitech_receiver/settings_templates.py:165 -msgid "Swipe two fingers from the bottom edge" -msgstr "Ťahanie dvoma prstami zo spodnej hrany" - -#: lib/logitech_receiver/settings_templates.py:166 -msgid "Swipe two fingers from the top edge" -msgstr "Ťahanie dvoma prstami z hornej hrany" +msgid "Side Scrolling" +msgstr "Bočné skrolovanie" #: lib/logitech_receiver/settings_templates.py:167 -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "Zovrieť pre priblíženie; rozovrieť pre oddialenie." +msgid "" +"When disabled, pushing the wheel sideways sends custom button " +"events\n" +"instead of the standard side-scrolling events." +msgstr "" +"Ak je vypnuté, bočné stlačenie kolieska vygeneruje vlastné udalosti " +"tlačidiel\n" +"namiesto štandardných udalostí skrolovania do strán." -#: lib/logitech_receiver/settings_templates.py:167 -msgid "Zoom with two fingers." -msgstr "Priblíženie dvoma prstami." +#: lib/logitech_receiver/settings_templates.py:177 +msgid "Sensitivity (DPI - older mice)" +msgstr "Citlivosť (DPI - staršie myši)" -#: lib/logitech_receiver/settings_templates.py:168 -msgid "Pinch to zoom out." -msgstr "Zovrieť pre priblíženie." +#: lib/logitech_receiver/settings_templates.py:178 +#: lib/logitech_receiver/settings_templates.py:712 +msgid "Mouse movement sensitivity" +msgstr "Citlivosť kurzora myši" -#: lib/logitech_receiver/settings_templates.py:169 -msgid "Spread to zoom in." -msgstr "Rozovrieť pre oddialenie." +#: lib/logitech_receiver/settings_templates.py:208 +#: lib/logitech_receiver/settings_templates.py:218 +#: lib/logitech_receiver/settings_templates.py:225 +msgid "Backlight" +msgstr "Podsvietenie" -#: lib/logitech_receiver/settings_templates.py:170 -msgid "Zoom with three fingers." -msgstr "Priblíženie troma prstami." +#: lib/logitech_receiver/settings_templates.py:209 +#: lib/logitech_receiver/settings_templates.py:226 +msgid "Set illumination time for keyboard." +msgstr "Nastaviť čas podsvietenia klávesnice" -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Zoom with two fingers" -msgstr "Priblíženie dvoma pstami" +#: lib/logitech_receiver/settings_templates.py:219 +msgid "Turn illumination on or off on keyboard." +msgstr "Vypne/zapne podsvietenie klávesnice." -#: lib/logitech_receiver/settings_templates.py:189 -msgid "Pixel zone" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:237 +msgid "Scroll Wheel High Resolution" +msgstr "Vysoké rozlíšenie skrolovania myši" -#: lib/logitech_receiver/settings_templates.py:190 -msgid "Ratio zone" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:240 +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "" +"Nastavte na ignorovanie ak je rolovanie neprimerane rýchle alebo pomalé" -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Scale factor" -msgstr "Faktor mierky" +#: lib/logitech_receiver/settings_templates.py:247 +#: lib/logitech_receiver/settings_templates.py:277 +msgid "Scroll Wheel Diversion" +msgstr "Odklonenie rolovacieho kolieska" -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Sets the cursor speed." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:249 +msgid "" +"Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " +"trigger Solaar rules but are otherwise ignored)." +msgstr "" +"Nech rolovacie koliesko posiela LOWRES_WHEEL HID++ notifikácie (ktoré spúšťajú" +" pravidlá v Solaare ale inak sú ignorované)." -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left" -msgstr "Vľavo" +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Scroll Wheel Direction" +msgstr "Smer skrolovania" -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left-most coordinate." -msgstr "Súradnica najviac vľavo." +#: lib/logitech_receiver/settings_templates.py:257 +msgid "Invert direction for vertical scroll with wheel." +msgstr "Zmeniť zmer zvislého skrolovania myši." -#: lib/logitech_receiver/settings_templates.py:196 -msgid "Top-most coordinate." -msgstr "Najvyššia súradnica." +#: lib/logitech_receiver/settings_templates.py:265 +msgid "Scroll Wheel Resolution" +msgstr "Rozlíšenie skrolovania myši" -#: lib/logitech_receiver/settings_templates.py:196 -msgid "top" -msgstr "hore" +#: lib/logitech_receiver/settings_templates.py:279 +msgid "" +"Make scroll wheel send HIRES_WHEEL HID++ notifications (which " +"trigger Solaar rules but are otherwise ignored)." +msgstr "" +"Nech rolovacie koliesko posiela HIRES_WHEEL HID++ notifikácie (ktoré spúšťajú " +"pravidlá v Solaare ale inak sú ignorované)." -#: lib/logitech_receiver/settings_templates.py:197 -msgid "Width." -msgstr "Šírka." +#: lib/logitech_receiver/settings_templates.py:288 +msgid "Sensitivity (Pointer Speed)" +msgstr "Citlivosť (rýchlosť kurzora)" -#: lib/logitech_receiver/settings_templates.py:197 -msgid "width" -msgstr "šírka" +#: lib/logitech_receiver/settings_templates.py:289 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Násobič rýchlosti myši (256 je bežný násobič)." -#: lib/logitech_receiver/settings_templates.py:198 -msgid "Height." -msgstr "Výška." +#: lib/logitech_receiver/settings_templates.py:299 +msgid "Thumb Wheel Diversion" +msgstr "Odchylka palcového kolieska" -#: lib/logitech_receiver/settings_templates.py:198 -msgid "height" -msgstr "výška" +#: lib/logitech_receiver/settings_templates.py:301 +msgid "" +"Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " +"Solaar rules but are otherwise ignored)." +msgstr "" +"Nech rolovacie koliesko posiela THUMB_WHEEL HID++ notifikácie (ktoré spúšťajú " +"pravidlá v Solaare ale inak sú ignorované)." -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Cursor speed." -msgstr "Rýchlosť kurzora." +#: lib/logitech_receiver/settings_templates.py:310 +msgid "Thumb Wheel Direction" +msgstr "Smer posuvného kolieska" -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Scale" -msgstr "Mierka" +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Invert thumb wheel scroll direction." +msgstr "Zmeniť smer otáčania kolieska." -#: lib/logitech_receiver/settings_templates.py:202 +#: lib/logitech_receiver/settings_templates.py:319 +msgid "Onboard Profiles" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:320 +msgid "" +"Enable onboard profiles, which often control report rate and " +"keyboard lighting" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:330 +msgid "Polling Rate (ms)" +msgstr "Intenzita dopytovania (ms)" + +#: lib/logitech_receiver/settings_templates.py:332 +msgid "Frequency of device polling, in milliseconds" +msgstr "Frekvencia dopytov na zariadenie, v ms" + +#: lib/logitech_receiver/settings_templates.py:333 +#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:365 +msgid "Divert crown events" +msgstr "Odklonenie udalostí korunky" + +#: lib/logitech_receiver/settings_templates.py:366 +msgid "" +"Make crown send CROWN HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"Korunka bude zasielať CROWN HID++ notifikácie (tie aktivujú Solaar " +"pravidlá, ale inak budú ignorované)." + +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Crown smooth scroll" +msgstr "Hladké rolovanie korunky" + +#: lib/logitech_receiver/settings_templates.py:375 +msgid "Set crown smooth scroll" +msgstr "Nastavuje hladké rolovanie korunky" + +#: lib/logitech_receiver/settings_templates.py:383 +msgid "Divert G Keys" +msgstr "Odkloniť G klávesy" + +#: lib/logitech_receiver/settings_templates.py:385 +msgid "" +"Make G keys send GKEY HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"G klávesy budú zasielať GKEY HID++ notifikácie (tie aktivujú Solaar " +"pravidlá, ale inak budú ignorované)." + +#: lib/logitech_receiver/settings_templates.py:386 +msgid "May also make M keys and MR key send HID++ notifications" +msgstr "Umožniť aj klávesom M a MR posielať HID++ upozornenia" + +#: lib/logitech_receiver/settings_templates.py:402 +msgid "Scroll Wheel Ratcheted" +msgstr "Rapkavé rolovacie koliesko" + +#: lib/logitech_receiver/settings_templates.py:403 +msgid "" +"Switch the mouse wheel between speed-controlled ratcheting and " +"always freespin." +msgstr "" +"Prepína koliesko myši medzi rapkaním v závislosti od rýchlosti a voľnobehom." + +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Freespinning" +msgstr "Voľnobeh" + +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Ratcheted" +msgstr "Rapkanie" + +#: lib/logitech_receiver/settings_templates.py:412 +msgid "Scroll Wheel Ratchet Speed" +msgstr "Rýchlosť rapkania rolovacieho kolieska" + +#: lib/logitech_receiver/settings_templates.py:414 +msgid "" +"Use the mouse wheel speed to switch between ratcheted and " +"freespinning.\n" +"The mouse wheel is always ratcheted at 50." +msgstr "" +"Použiť rýchlosť kolieska myši na prepínanie medzi rapkaním a voľnobehom." + +#: lib/logitech_receiver/settings_templates.py:463 +msgid "Key/Button Actions" +msgstr "Akcie klávesu/tlačidla" + +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Change the action for the key or button." +msgstr "Zmeniť akciu klávesy alebo tlačidla." + +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Overridden by diversion." +msgstr "Obídené nastavením odklonenia" + +#: lib/logitech_receiver/settings_templates.py:466 +msgid "" +"Changing important actions (such as for the left mouse button) can " +"result in an unusable system." +msgstr "" +"Zmenou dôležitej akcie (napríklad pre ľavé tlačidlo myši) môžete " +"systém uviesť do stavu, kedy sa nebude dať používať." + +#: lib/logitech_receiver/settings_templates.py:639 +msgid "Key/Button Diversion" +msgstr "Odklonenie klávesu/tlačidla" + +#: lib/logitech_receiver/settings_templates.py:640 +msgid "" +"Make the key or button send HID++ notifications (Diverted) or " +"initiate Mouse Gestures or Sliding DPI" +msgstr "" +"Nech kláves alebo tlačidlo posiela HID++ upozornenia (Odklonené) alebo spustí " +"gestá myši alebo posunie DPI" + +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Diverted" +msgstr "Odklonené" + +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +msgid "Mouse Gestures" +msgstr "Gestá myši" + +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Regular" +msgstr "Normálne" + +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Sliding DPI" +msgstr "Posun DPI" + +#: lib/logitech_receiver/settings_templates.py:711 +msgid "Sensitivity (DPI)" +msgstr "Citlivosť (DPI)" + +#: lib/logitech_receiver/settings_templates.py:752 +msgid "Sensitivity Switching" +msgstr "Prepínanie citlivosti" + +#: lib/logitech_receiver/settings_templates.py:754 +msgid "" +"Switch the current sensitivity and the remembered sensitivity when " +"the key or button is pressed.\n" +"If there is no remembered sensitivity, just remember the current " +"sensitivity" +msgstr "" +"Stlačením klávesu alebo tlačidla, prepínať súčasnú citlivosť a zapamätanú citl" +"ivosť.\n" +"Ak nie je zapamätaná citlivosť, tak si len zapamätá súčasnú citlivosť." + +#: lib/logitech_receiver/settings_templates.py:758 +msgid "Off" +msgstr "Vyp" + +#: lib/logitech_receiver/settings_templates.py:791 +msgid "Disable keys" +msgstr "Zakázať klávesy" + +#: lib/logitech_receiver/settings_templates.py:792 +msgid "Disable specific keyboard keys." +msgstr "Zakázať špecifické klávesy." + +#: lib/logitech_receiver/settings_templates.py:795 #, python-format -msgid "Disables the %s key." -msgstr "Zakáže %s kláves." +msgid "Disables the %s key." +msgstr "Zakáže %s kláves." -#: lib/logitech_receiver/settings_templates.py:521 -#: lib/logitech_receiver/settings_templates.py:573 -msgid "Off" -msgstr "Vyp" +#: lib/logitech_receiver/settings_templates.py:809 +#: lib/logitech_receiver/settings_templates.py:860 +msgid "Set OS" +msgstr "Nastaviť OS" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Diverted" -msgstr "Odklonené" +#: lib/logitech_receiver/settings_templates.py:810 +#: lib/logitech_receiver/settings_templates.py:861 +msgid "Change keys to match OS." +msgstr "Zmeniť klávesy podľa operačného systému." -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Regular" -msgstr "Normálne" +#: lib/logitech_receiver/settings_templates.py:873 +msgid "Change Host" +msgstr "Zmeniť hostiteľa" -#: lib/logitech_receiver/status.py:109 -msgid "No paired devices." -msgstr "Žiadne spárované zariadenia." +#: lib/logitech_receiver/settings_templates.py:874 +msgid "Switch connection to a different host" +msgstr "Prepnúť spojenie na iného hostiteľa" -#: lib/logitech_receiver/status.py:110 lib/solaar/ui/window.py:623 +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Performs a left click." +msgstr "Vykoná kliknutie ľavým tlačidlom." + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Single tap" +msgstr "Dotyk" + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Performs a right click." +msgstr "Vykoná kliknutie pravým tlačidlom." + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Single tap with two fingers" +msgstr "Dotyk dvoma prstami" + +#: lib/logitech_receiver/settings_templates.py:902 +msgid "Single tap with three fingers" +msgstr "Dotyk tromi prstami" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Double tap" +msgstr "Dvojdotyk" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Performs a double click." +msgstr "Vykoná dvojité kliknutie." + +#: lib/logitech_receiver/settings_templates.py:907 +msgid "Double tap with two fingers" +msgstr "Dvojdotyk dvoma prstami" + +#: lib/logitech_receiver/settings_templates.py:908 +msgid "Double tap with three fingers" +msgstr "Dvojdotyk troma prstami" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Presúvanie položiek ťahaním prstu po dvojdotyku." + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Tap and drag" +msgstr "Ťahaj a pusti" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Presúvanie položiek ťahaním prstov po dvojdotyku." + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Tap and drag with two fingers" +msgstr "Dotyk a ťahanie dvomi prstami" + +#: lib/logitech_receiver/settings_templates.py:914 +msgid "Tap and drag with three fingers" +msgstr "Dotyk a ťahanie tromi prstami" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "Vypnúť gestá dotyku a hrany (ekvivalent stlačenia Fn + ľavý klik)." + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Suppress tap and edge gestures" +msgstr "Potlačiť gestá dotyku a hrany" + +#: lib/logitech_receiver/settings_templates.py:918 +msgid "Scroll with one finger" +msgstr "Skrolovanie jedným prstom" + +#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scrolls." +msgstr "Skrolovania." + +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scroll with two fingers" +msgstr "Skrolovanie dvoma prstami" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scroll horizontally with two fingers" +msgstr "Vodorovné skrolovanie dvoma prstami" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scrolls horizontally." +msgstr "Vodorovné skrolovanie." + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scroll vertically with two fingers" +msgstr "Zvislé skrolovanie dvoma prstami" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scrolls vertically." +msgstr "Zvislé skrolovanie." + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Inverts the scrolling direction." +msgstr "Zmeniť smer otáčania." + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Natural scrolling" +msgstr "Prirodzené skrolovanie" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Enables the thumbwheel." +msgstr "Povoliť koliesko." + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Thumbwheel" +msgstr "Koliesko" + +#: lib/logitech_receiver/settings_templates.py:935 +#: lib/logitech_receiver/settings_templates.py:939 +msgid "Swipe from the top edge" +msgstr "Ťahanie z hornej hrany" + +#: lib/logitech_receiver/settings_templates.py:936 +msgid "Swipe from the left edge" +msgstr "Ťahanie z ľavej hrany" + +#: lib/logitech_receiver/settings_templates.py:937 +msgid "Swipe from the right edge" +msgstr "Ťahanie z pravej hrany" + +#: lib/logitech_receiver/settings_templates.py:938 +msgid "Swipe from the bottom edge" +msgstr "Ťahanie zo spodnej hrany" + +#: lib/logitech_receiver/settings_templates.py:940 +msgid "Swipe two fingers from the left edge" +msgstr "Ťahanie dvoma prstami z ľavej hrany" + +#: lib/logitech_receiver/settings_templates.py:941 +msgid "Swipe two fingers from the right edge" +msgstr "Ťahanie dvoma prstami z pravej hrany" + +#: lib/logitech_receiver/settings_templates.py:942 +msgid "Swipe two fingers from the bottom edge" +msgstr "Ťahanie dvoma prstami zo spodnej hrany" + +#: lib/logitech_receiver/settings_templates.py:943 +msgid "Swipe two fingers from the top edge" +msgstr "Ťahanie dvoma prstami z hornej hrany" + +#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "Zovrieť pre priblíženie; rozovrieť pre oddialenie." + +#: lib/logitech_receiver/settings_templates.py:944 +msgid "Zoom with two fingers." +msgstr "Priblíženie dvoma prstami." + +#: lib/logitech_receiver/settings_templates.py:945 +msgid "Pinch to zoom out." +msgstr "Zovrieť pre priblíženie." + +#: lib/logitech_receiver/settings_templates.py:946 +msgid "Spread to zoom in." +msgstr "Rozovrieť pre oddialenie." + +#: lib/logitech_receiver/settings_templates.py:947 +msgid "Zoom with three fingers." +msgstr "Priblíženie troma prstami." + +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Zoom with two fingers" +msgstr "Priblíženie dvoma pstami" + +#: lib/logitech_receiver/settings_templates.py:966 +msgid "Pixel zone" +msgstr "Pixlová oblasť" + +#: lib/logitech_receiver/settings_templates.py:967 +msgid "Ratio zone" +msgstr "Pomerná oblasť" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Scale factor" +msgstr "Faktor mierky" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Sets the cursor speed." +msgstr "Nastavuje rýchlosť kurzora." + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left" +msgstr "Vľavo" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left-most coordinate." +msgstr "Súradnica najviac vľavo." + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom" +msgstr "Spodok" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom coordinate." +msgstr "Úplne spodná súradnica." + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width" +msgstr "Šírka" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width." +msgstr "Šírka." + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height" +msgstr "Výška" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height." +msgstr "Výška." + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Cursor speed." +msgstr "Rýchlosť kurzora." + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Scale" +msgstr "Mierka" + +#: lib/logitech_receiver/settings_templates.py:982 +msgid "Gestures" +msgstr "Gestá" + +#: lib/logitech_receiver/settings_templates.py:983 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Upraviť správanie myši/touchpadu." + +#: lib/logitech_receiver/settings_templates.py:1000 +msgid "Gestures Diversion" +msgstr "Odklon gest" + +#: lib/logitech_receiver/settings_templates.py:1001 +msgid "Divert mouse/touchpad gestures." +msgstr "Odkloniť gestá myši/touchpadu." + +#: lib/logitech_receiver/settings_templates.py:1018 +msgid "Gesture params" +msgstr "Parametre gesta" + +#: lib/logitech_receiver/settings_templates.py:1019 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Zmeňte číselné parametre myši/touchpadu." + +#: lib/logitech_receiver/settings_templates.py:1044 +msgid "M-Key LEDs" +msgstr "LEDky M-Key" + +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Control the M-Key LEDs." +msgstr "Ovládanie LED svetla klávesu M-Key" + +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1077 +msgid "May need G Keys diverted to be effective." +msgstr "Môže vyžadovať odklon G Klávesov aby to fungovalo." + +#: lib/logitech_receiver/settings_templates.py:1053 #, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s spárované zariadenie." -msgstr[1] "%(count)s spárované zariadenia." -msgstr[2] "%(count)s spárovaných zariadení." +msgid "Lights up the %s key." +msgstr "Rozsvieti kláves %s." -#: lib/logitech_receiver/status.py:165 -#, python-format -msgid "Battery: %(level)s" -msgstr "Batéria: %(level)s" +#: lib/logitech_receiver/settings_templates.py:1074 +msgid "MR-Key LED" +msgstr "MR-Key LED" -#: lib/logitech_receiver/status.py:167 -#, python-format -msgid "Battery: %(percent)d%%" -msgstr "Batéria: %(percent)d%%" +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "Control the MR-Key LED." +msgstr "Ovláda LEDky klávesu MR-Key." -#: lib/logitech_receiver/status.py:179 -#, python-format -msgid "Lighting: %(level)s lux" -msgstr "Osvetlenie: %(level)s lux" +#: lib/logitech_receiver/settings_templates.py:1095 +msgid "Persistent Key/Button Mapping" +msgstr "Trvalé mapovanie Klávesov/Tlačidiel" -#: lib/logitech_receiver/status.py:234 -#, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "Batéria: %(level)s (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:1097 +msgid "Permanently change the mapping for the key or button." +msgstr "Natrvalo zmeniť mapovanie pre kláves alebo tlačidlo." -#: lib/logitech_receiver/status.py:236 +#: lib/logitech_receiver/settings_templates.py:1098 +msgid "" +"Changing important keys or buttons (such as for the left mouse " +"button) can result in an unusable system." +msgstr "" +"Zmena dôležitých klávesov alebo tlačidiel (ako je Ľavé tlačidlo myši) môže sko" +"nčiť nepoužiteľným systémom." + +#: lib/logitech_receiver/settings_templates.py:1157 +msgid "Sidetone" +msgstr "Bočný odtieň" + +#: lib/logitech_receiver/settings_templates.py:1158 +msgid "Set sidetone level." +msgstr "Nastavenie bočného odtieňa." + +#: lib/logitech_receiver/settings_templates.py:1167 +msgid "Equalizer" +msgstr "Ekvalizér" + +#: lib/logitech_receiver/settings_templates.py:1168 +msgid "Set equalizer levels." +msgstr "Nastavenie úrovne ekvalizéru." + +#: lib/logitech_receiver/settings_templates.py:1191 +msgid "Hz" +msgstr "Hz" + +#: lib/logitech_receiver/settings_templates.py:1197 +msgid "Power Management" +msgstr "Správa napájania" + +#: lib/logitech_receiver/settings_templates.py:1198 +msgid "Power off in minutes (0 for never)." +msgstr "Minúty do vypnutia (0 znamená nikdy)" + +#: lib/logitech_receiver/status.py:114 +msgid "No paired devices." +msgstr "Žiadne spárované zariadenia." + +#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 #, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "Batéria: %(percent)d%% (%(status)s)" +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s spárované zariadenie." +msgstr[1] "%(count)s spárované zariadenia." +msgstr[2] "%(count)s spárovaných zariadení." + +#: lib/logitech_receiver/status.py:170 +#, python-format +msgid "Battery: %(level)s" +msgstr "Batéria: %(level)s" + +#: lib/logitech_receiver/status.py:172 +#, python-format +msgid "Battery: %(percent)d%%" +msgstr "Batéria: %(percent)d%%" + +#: lib/logitech_receiver/status.py:184 +#, python-format +msgid "Lighting: %(level)s lux" +msgstr "Osvetlenie: %(level)s lux" + +#: lib/logitech_receiver/status.py:239 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Batéria: %(level)s (%(status)s)" + +#: lib/logitech_receiver/status.py:241 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Batéria: %(percent)d%% (%(status)s)" #: lib/solaar/ui/__init__.py:52 -msgid "Permissions error" -msgstr "Chyba oprávnenia" +msgid "Permissions error" +msgstr "Chyba oprávnenia" #: lib/solaar/ui/__init__.py:54 #, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open " - "it." -msgstr "Nájdený prijímač Logitech (%s), ale chýbajú práva na jeho otvorenie." +msgid "" +"Found a Logitech receiver or device (%s), but did not have " +"permission to open it." +msgstr "" +"Našiel sa prijímač alebo zariadenie Logitech (%s), ale nemal oprávnenie na jeh" +"o otvorenie." #: lib/solaar/ui/__init__.py:55 -msgid "If you've just installed Solaar, try removing the receiver and " - "plugging it back in." -msgstr "Ak ste Solaar práve nainštalovali, skúste prijímač odpojiť a znova " - "pripojiť." +msgid "" +"If you've just installed Solaar, try disconnecting the receiver or " +"device and then reconnecting it." +msgstr "" +"Ak ste práve nainštalovali Solaar, skúste odpojiť prijímač alebo zariadenie a " +"znova ho pripojiť." #: lib/solaar/ui/__init__.py:58 -msgid "Cannot connect to device error" -msgstr "Chyba počas pripájania k zariadeniu" +msgid "Cannot connect to device error" +msgstr "Chyba počas pripájania k zariadeniu" #: lib/solaar/ui/__init__.py:60 #, python-format -msgid "Found a Logitech receiver or device at %s, but encountered an error " - "connecting to it." -msgstr "Našiel sa Logitech prijímač alebo zariadenie na %s, ale pri pokuse o " - "pripojenie nastala chyba." +msgid "" +"Found a Logitech receiver or device at %s, but encountered an error " +"connecting to it." +msgstr "" +"Našiel sa Logitech prijímač alebo zariadenie na %s, ale pri pokuse o " +"pripojenie nastala chyba." #: lib/solaar/ui/__init__.py:61 -msgid "Try removing the device and plugging it back in or turning it off " - "and then on." -msgstr "Skúste zariadenie odpojiť a znovu ho pripojiť alebo ho skúste vypnúť " - "a opätovne zapnúť." +msgid "" +"Try disconnecting the device and then reconnecting it or turning it " +"off and then on." +msgstr "" +"Skúste odpojiť zariadenie a potom ho opätovne pripojiť alebo ho skúste vypnúť " +"a zase zapnúť." #: lib/solaar/ui/__init__.py:64 -msgid "Unpairing failed" -msgstr "Rušenie spárovania skončilo s chybou" +msgid "Unpairing failed" +msgstr "Rušenie spárovania skončilo s chybou" #: lib/solaar/ui/__init__.py:66 #, python-brace-format -msgid "Failed to unpair %{device} from %{receiver}." -msgstr "Zlyhalo zrušenie spárovania zariadenia %{device} k %{receiver}." +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Zlyhalo zrušenie spárovania zariadenia %{device} k %{receiver}." #: lib/solaar/ui/__init__.py:67 -msgid "The receiver returned an error, with no further details." -msgstr "Prijímač vrátil chybu, ale nie sú dostupné žiadne podrobnosti o nej." +msgid "The receiver returned an error, with no further details." +msgstr "Prijímač vrátil chybu, ale nie sú dostupné žiadne podrobnosti o nej." -#: lib/solaar/ui/about.py:39 -msgid "Manages Logitech receivers,\n" - "keyboards, mice, and tablets." -msgstr "Správa prijímačov Logitech,\n" - "klávesníc, myší a tabletov." +#: lib/solaar/ui/__init__.py:177 +msgid "Another Solaar process is already running so just expose its window" +msgstr "Iný proces Solaaru uz beží, takže len odkryte jeho okno" + +#: lib/solaar/ui/about.py:36 +msgid "" +"Manages Logitech receivers,\n" +"keyboards, mice, and tablets." +msgstr "" +"Správa prijímačov Logitech,\n" +"klávesníc, myší a tabletov." + +#: lib/solaar/ui/about.py:44 +msgid "Additional Programming" +msgstr "Dodatočné programovanie" + +#: lib/solaar/ui/about.py:45 +msgid "GUI design" +msgstr "Návrh GUI" #: lib/solaar/ui/about.py:47 -msgid "Additional Programming" -msgstr "Dodatočné programovanie" +msgid "Testing" +msgstr "Testovanie" -#: lib/solaar/ui/about.py:48 -msgid "GUI design" -msgstr "Návrh GUI" +#: lib/solaar/ui/about.py:54 +msgid "Logitech documentation" +msgstr "Dokumentácia Logitechu" -#: lib/solaar/ui/about.py:50 -msgid "Testing" -msgstr "Testovanie" +#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 +#: lib/solaar/ui/window.py:197 +msgid "Unpair" +msgstr "Zrušiť párovanie" -#: lib/solaar/ui/about.py:57 -msgid "Logitech documentation" -msgstr "Dokumentácia Logitechu" +#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 +msgid "Cancel" +msgstr "Zrušiť" -#: lib/solaar/ui/action.py:70 lib/solaar/ui/window.py:328 -msgid "About" -msgstr "O programe" +#: lib/solaar/ui/config_panel.py:212 +msgid "Complete - ENTER to change" +msgstr "" -#: lib/solaar/ui/action.py:96 lib/solaar/ui/action.py:100 -#: lib/solaar/ui/window.py:205 -msgid "Unpair" -msgstr "Zrušiť párovanie" +#: lib/solaar/ui/config_panel.py:212 +msgid "Incomplete" +msgstr "" -#: lib/solaar/ui/action.py:99 lib/solaar/ui/diversion_rules.py:144 -msgid "Cancel" -msgstr "Zrušiť" - -#: lib/solaar/ui/config_panel.py:358 -msgid "Changes allowed" -msgstr "Zmeny sú povolené" - -#: lib/solaar/ui/config_panel.py:359 -msgid "No changes allowed" -msgstr "Zmeny nie sú povolené" - -#: lib/solaar/ui/config_panel.py:360 -msgid "Ignore this setting" -msgstr "Ignorovať toto nastavenie" - -#: lib/solaar/ui/config_panel.py:401 -msgid "Working" -msgstr "Pracujem" - -#: lib/solaar/ui/config_panel.py:404 -msgid "Read/write operation failed." -msgstr "Operácia čítania/zápisu zlyhala." - -#: lib/solaar/ui/config_panel.py:522 +#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 #, python-format -msgid "%d value" -msgid_plural "%d values" -msgstr[0] "%d hodnota" -msgstr[1] "%d hodnoty" -msgstr[2] "%d hodnôt" +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d hodnota" +msgstr[1] "%d hodnoty" +msgstr[2] "%d hodnôt" -#: lib/solaar/ui/diversion_rules.py:57 -msgid "Built-in rules" -msgstr "Zabudované pravidlá" +#: lib/solaar/ui/config_panel.py:518 +msgid "Changes allowed" +msgstr "Zmeny sú povolené" -#: lib/solaar/ui/diversion_rules.py:57 -msgid "User-defined rules" -msgstr "Používateľom definované pravidlá" +#: lib/solaar/ui/config_panel.py:519 +msgid "No changes allowed" +msgstr "Zmeny nie sú povolené" -#: lib/solaar/ui/diversion_rules.py:59 lib/solaar/ui/diversion_rules.py:792 -msgid "Rule" -msgstr "Pravidlo" +#: lib/solaar/ui/config_panel.py:520 +msgid "Ignore this setting" +msgstr "Ignorovať toto nastavenie" -#: lib/solaar/ui/diversion_rules.py:60 lib/solaar/ui/diversion_rules.py:503 -#: lib/solaar/ui/diversion_rules.py:622 -msgid "Sub-rule" -msgstr "Podpravidlo" +#: lib/solaar/ui/config_panel.py:565 +msgid "Working" +msgstr "Pracujem" -#: lib/solaar/ui/diversion_rules.py:62 -msgid "[empty]" -msgstr "[prázdne]" +#: lib/solaar/ui/config_panel.py:568 +msgid "Read/write operation failed." +msgstr "Operácia čítania/zápisu zlyhala." -#: lib/solaar/ui/diversion_rules.py:85 -msgid "Solaar Rule Editor" -msgstr "Editor pravidiel Solaar" +#: lib/solaar/ui/diversion_rules.py:65 +msgid "Built-in rules" +msgstr "Zabudované pravidlá" -#: lib/solaar/ui/diversion_rules.py:135 -msgid "Make changes permanent?" -msgstr "Označiť zmeny ako trvalé?" +#: lib/solaar/ui/diversion_rules.py:65 +msgid "User-defined rules" +msgstr "Používateľom definované pravidlá" -#: lib/solaar/ui/diversion_rules.py:140 -msgid "Yes" -msgstr "Áno" +#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 +msgid "Rule" +msgstr "Pravidlo" -#: lib/solaar/ui/diversion_rules.py:142 -msgid "No" -msgstr "Nie" +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 +#: lib/solaar/ui/diversion_rules.py:636 +msgid "Sub-rule" +msgstr "Podpravidlo" -#: lib/solaar/ui/diversion_rules.py:147 -msgid "If you choose No, changes will be lost when Solaar is closed." -msgstr "Ak vyberiete Nie, zmeny, ktoré ste vykonali sa po ukončení " - "aplikácie, stratia." +#: lib/solaar/ui/diversion_rules.py:70 +msgid "[empty]" +msgstr "[prázdne]" -#: lib/solaar/ui/diversion_rules.py:195 -msgid "Save changes" -msgstr "Uložiť zmeny" +#: lib/solaar/ui/diversion_rules.py:94 +msgid "Solaar Rule Editor" +msgstr "Editor pravidiel Solaar" -#: lib/solaar/ui/diversion_rules.py:200 -msgid "Discard changes" -msgstr "Zahodiť zmeny" +#: lib/solaar/ui/diversion_rules.py:141 +msgid "Make changes permanent?" +msgstr "Označiť zmeny ako trvalé?" -#: lib/solaar/ui/diversion_rules.py:366 -msgid "Insert here" -msgstr "Vložiť sem" +#: lib/solaar/ui/diversion_rules.py:146 +msgid "Yes" +msgstr "Áno" -#: lib/solaar/ui/diversion_rules.py:368 -msgid "Insert above" -msgstr "Vložiť nad" +#: lib/solaar/ui/diversion_rules.py:148 +msgid "No" +msgstr "Nie" -#: lib/solaar/ui/diversion_rules.py:370 -msgid "Insert below" -msgstr "Vložiť pod" +#: lib/solaar/ui/diversion_rules.py:153 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "" +"Ak vyberiete Nie, zmeny, ktoré ste vykonali sa po ukončení " +"aplikácie, stratia." + +#: lib/solaar/ui/diversion_rules.py:201 +msgid "Save changes" +msgstr "Uložiť zmeny" + +#: lib/solaar/ui/diversion_rules.py:206 +msgid "Discard changes" +msgstr "Zahodiť zmeny" + +#: lib/solaar/ui/diversion_rules.py:372 +msgid "Insert here" +msgstr "Vložiť sem" + +#: lib/solaar/ui/diversion_rules.py:374 +msgid "Insert above" +msgstr "Vložiť nad" #: lib/solaar/ui/diversion_rules.py:376 -msgid "Insert new rule here" -msgstr "Vložiť nové pravidlo sem" +msgid "Insert below" +msgstr "Vložiť pod" -#: lib/solaar/ui/diversion_rules.py:378 -msgid "Insert new rule above" -msgstr "Vložiť nové pravidlo nad" +#: lib/solaar/ui/diversion_rules.py:382 +msgid "Insert new rule here" +msgstr "Vložiť nové pravidlo sem" -#: lib/solaar/ui/diversion_rules.py:380 -msgid "Insert new rule below" -msgstr "Vložiť nové pravidlo pod" +#: lib/solaar/ui/diversion_rules.py:384 +msgid "Insert new rule above" +msgstr "Vložiť nové pravidlo nad" -#: lib/solaar/ui/diversion_rules.py:421 -msgid "Paste here" -msgstr "Vložiť sem" +#: lib/solaar/ui/diversion_rules.py:386 +msgid "Insert new rule below" +msgstr "Vložiť nové pravidlo pod" -#: lib/solaar/ui/diversion_rules.py:423 -msgid "Paste above" -msgstr "Vložiť nad" +#: lib/solaar/ui/diversion_rules.py:427 +msgid "Paste here" +msgstr "Vložiť sem" -#: lib/solaar/ui/diversion_rules.py:425 -msgid "Paste below" -msgstr "Vložiť pod" +#: lib/solaar/ui/diversion_rules.py:429 +msgid "Paste above" +msgstr "Vložiť nad" #: lib/solaar/ui/diversion_rules.py:431 -msgid "Paste rule here" -msgstr "Vložiť pravidlo sem" +msgid "Paste below" +msgstr "Vložiť pod" -#: lib/solaar/ui/diversion_rules.py:433 -msgid "Paste rule above" -msgstr "Vložiť pravidlo nad" - -#: lib/solaar/ui/diversion_rules.py:435 -msgid "Paste rule below" -msgstr "Vložiť pravidlo pod" +#: lib/solaar/ui/diversion_rules.py:437 +msgid "Paste rule here" +msgstr "Vložiť pravidlo sem" #: lib/solaar/ui/diversion_rules.py:439 -msgid "Paste rule" -msgstr "Vložiť pravidlo" +msgid "Paste rule above" +msgstr "Vložiť pravidlo nad" -#: lib/solaar/ui/diversion_rules.py:468 -msgid "Flatten" -msgstr "Sploštiť" +#: lib/solaar/ui/diversion_rules.py:441 +msgid "Paste rule below" +msgstr "Vložiť pravidlo pod" -#: lib/solaar/ui/diversion_rules.py:501 -msgid "Insert" -msgstr "Vložiť" +#: lib/solaar/ui/diversion_rules.py:445 +msgid "Paste rule" +msgstr "Vložiť pravidlo" -#: lib/solaar/ui/diversion_rules.py:504 lib/solaar/ui/diversion_rules.py:624 -#: lib/solaar/ui/diversion_rules.py:835 -msgid "Or" -msgstr "Alebo" - -#: lib/solaar/ui/diversion_rules.py:505 lib/solaar/ui/diversion_rules.py:623 -#: lib/solaar/ui/diversion_rules.py:820 -msgid "And" -msgstr "A" +#: lib/solaar/ui/diversion_rules.py:474 +msgid "Flatten" +msgstr "Sploštiť" #: lib/solaar/ui/diversion_rules.py:507 -msgid "Condition" -msgstr "Podmienka" +msgid "Insert" +msgstr "Vložiť" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:951 -msgid "Feature" -msgstr "Vlastnosť" +#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 +#: lib/solaar/ui/diversion_rules.py:1126 +msgid "Or" +msgstr "Alebo" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:874 -msgid "Process" -msgstr "Proces" +#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 +#: lib/solaar/ui/diversion_rules.py:1111 +msgid "And" +msgstr "A" -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:902 -msgid "MouseProcess" -msgstr "ProcesMyši" +#: lib/solaar/ui/diversion_rules.py:513 +msgid "Condition" +msgstr "Podmienka" -#: lib/solaar/ui/diversion_rules.py:512 lib/solaar/ui/diversion_rules.py:984 -msgid "Report" -msgstr "Hlásenie" +#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 +msgid "Feature" +msgstr "Vlastnosť" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1019 -msgid "Modifiers" -msgstr "Modifikátory" +#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 +msgid "Report" +msgstr "Hlásenie" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1066 -msgid "Key" -msgstr "Kláves" +#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 +msgid "Process" +msgstr "Proces" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1111 -msgid "Test" -msgstr "Test" +#: lib/solaar/ui/diversion_rules.py:518 +msgid "Mouse process" +msgstr "Proces myši" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1196 -msgid "Mouse Gesture" -msgstr "Gesto myši" +#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 +msgid "Modifiers" +msgstr "Modifikátory" -#: lib/solaar/ui/diversion_rules.py:520 -msgid "Action" -msgstr "Akcia" +#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 +msgid "Key" +msgstr "Kláves" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1286 -msgid "Key press" -msgstr "Stlačenie klávesu" +#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 +msgid "KeyIsDown" +msgstr "KlávesJeStlačený" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1335 -msgid "Mouse scroll" -msgstr "Skrolovanie kolieskom" +#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 +msgid "Active" +msgstr "Aktívne" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:1383 -msgid "Mouse click" -msgstr "Kliknutie myšou" +#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2218 +#: lib/solaar/ui/diversion_rules.py:2270 lib/solaar/ui/diversion_rules.py:2323 +msgid "Device" +msgstr "Zariadenie" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1452 -msgid "Execute" -msgstr "Spustiť program" +#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +msgid "Host" +msgstr "Hostiteľ" -#: lib/solaar/ui/diversion_rules.py:554 -msgid "Insert new rule" -msgstr "Vložiť nové pravidlo" +#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 +msgid "Setting" +msgstr "Nastavenie" -#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/diversion_rules.py:1146 -#: lib/solaar/ui/diversion_rules.py:1236 lib/solaar/ui/diversion_rules.py:1411 -msgid "Delete" -msgstr "Odstrániť" +#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 +#: lib/solaar/ui/diversion_rules.py:1526 +msgid "Test" +msgstr "Test" -#: lib/solaar/ui/diversion_rules.py:596 -msgid "Negate" -msgstr "Negovať" +#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 +msgid "Test bytes" +msgstr "Test bajtov" -#: lib/solaar/ui/diversion_rules.py:620 -msgid "Wrap with" -msgstr "Obaliť čím" +#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 +msgid "Mouse Gesture" +msgstr "Gesto myši" -#: lib/solaar/ui/diversion_rules.py:642 -msgid "Cut" -msgstr "Vystrihnúť" +#: lib/solaar/ui/diversion_rules.py:532 +msgid "Action" +msgstr "Akcia" -#: lib/solaar/ui/diversion_rules.py:657 -msgid "Paste" -msgstr "Vložiť" +#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 +msgid "Key press" +msgstr "Stlačenie klávesu" -#: lib/solaar/ui/diversion_rules.py:669 -msgid "Copy" -msgstr "Kopírovať" +#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 +msgid "Mouse scroll" +msgstr "Skrolovanie kolieskom" -#: lib/solaar/ui/diversion_rules.py:772 -msgid "This editor does not support the selected rule component yet." -msgstr "Vybraný komponent nie je editorom ešte podporovaný." +#: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 +msgid "Mouse click" +msgstr "Kliknutie myšou" -#: lib/solaar/ui/diversion_rules.py:850 -msgid "Not" -msgstr "Opak" +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Set" +msgstr "Nastaviť" -#: lib/solaar/ui/diversion_rules.py:1039 -msgid "Key down" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 +msgid "Execute" +msgstr "Spustiť program" -#: lib/solaar/ui/diversion_rules.py:1042 -msgid "Key up" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 +msgid "Later" +msgstr "Neskôr" -#: lib/solaar/ui/diversion_rules.py:1131 -msgid "Add action" -msgstr "Pridať akciu" +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Insert new rule" +msgstr "Vložiť nové pravidlo" -#: lib/solaar/ui/diversion_rules.py:1224 -msgid "Add key" -msgstr "Pridať kláves" +#: lib/solaar/ui/diversion_rules.py:588 lib/solaar/ui/diversion_rules.py:1686 +#: lib/solaar/ui/diversion_rules.py:1790 lib/solaar/ui/diversion_rules.py:1989 +msgid "Delete" +msgstr "Odstrániť" -#: lib/solaar/ui/diversion_rules.py:1354 -msgid "Button" -msgstr "Tlačidlo" +#: lib/solaar/ui/diversion_rules.py:610 +msgid "Negate" +msgstr "Negovať" -#: lib/solaar/ui/diversion_rules.py:1355 -msgid "Count" -msgstr "Počet" +#: lib/solaar/ui/diversion_rules.py:634 +msgid "Wrap with" +msgstr "Obaliť čím" -#: lib/solaar/ui/diversion_rules.py:1397 -msgid "Add argument" -msgstr "Pridať argument" +#: lib/solaar/ui/diversion_rules.py:656 +msgid "Cut" +msgstr "Vystrihnúť" -#: lib/solaar/ui/notify.py:125 lib/solaar/ui/tray.py:319 -#: lib/solaar/ui/tray.py:324 lib/solaar/ui/window.py:749 -msgid "offline" -msgstr "offline" +#: lib/solaar/ui/diversion_rules.py:671 +msgid "Paste" +msgstr "Vložiť" -#: lib/solaar/ui/pair_window.py:134 -msgid "Pairing failed" -msgstr "Párovanie skončilo s chybou" +#: lib/solaar/ui/diversion_rules.py:683 +msgid "Copy" +msgstr "Kopírovať" -#: lib/solaar/ui/pair_window.py:136 -msgid "Make sure your device is within range, and has a decent battery " - "charge." -msgstr "Uistite sa, že je zariadenie v dosahu a je dostatočne nabité." +#: lib/solaar/ui/diversion_rules.py:1063 +msgid "This editor does not support the selected rule component yet." +msgstr "Vybraný komponent nie je editorom ešte podporovaný." -#: lib/solaar/ui/pair_window.py:138 -msgid "A new device was detected, but it is not compatible with this " - "receiver." -msgstr "Bolo zistené nové zariadenie, ale nie je kompatibilné s týmto " - "prijímačom." +#: lib/solaar/ui/diversion_rules.py:1138 +msgid "Number of seconds to delay." +msgstr "Počet sekúnd oneskorenia." -#: lib/solaar/ui/pair_window.py:140 -msgid "More paired devices than receiver can support." -msgstr "Viac pripojených zariadení ako dokáže prijímač zvládnuť." +#: lib/solaar/ui/diversion_rules.py:1177 +msgid "Not" +msgstr "Opak" -#: lib/solaar/ui/pair_window.py:142 -msgid "No further details are available about the error." -msgstr "Ďalšie podrobnosti o chybe nie sú dostupné." +#: lib/solaar/ui/diversion_rules.py:1187 +msgid "X11 active process. For use in X11 only." +msgstr "Aktývny proces X11. Len pre použitie v X11." -#: lib/solaar/ui/pair_window.py:156 -msgid "Found a new device:" -msgstr "Nájdené nové zariadenie:" +#: lib/solaar/ui/diversion_rules.py:1218 +msgid "X11 mouse process. For use in X11 only." +msgstr "Proces myši X11. Len pre použitie v X11." -#: lib/solaar/ui/pair_window.py:181 -msgid "The wireless link is not encrypted" -msgstr "Bezdrôtové pripojenie nepoužíva šifrovanie" +#: lib/solaar/ui/diversion_rules.py:1235 +msgid "MouseProcess" +msgstr "ProcesMyši" -#: lib/solaar/ui/pair_window.py:199 +#: lib/solaar/ui/diversion_rules.py:1260 +msgid "Feature name of notification triggering rule processing." +msgstr "Názov funkcie upozornenia, ktorá spúšťa spracovanie pravidla." + +#: lib/solaar/ui/diversion_rules.py:1308 +msgid "Report number of notification triggering rule processing." +msgstr "Počet upozornení, ktoré spúšťajú spracovanie pravidla." + +#: lib/solaar/ui/diversion_rules.py:1342 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "Aktívne modifikátory klávesnice. Vo Waylande nie sú vždy dostupné." + +#: lib/solaar/ui/diversion_rules.py:1383 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert " +"keys and buttons." +msgstr "" +"Odklonený kláves alebo tlačidlo stlačené alebo uvoľnené.\n" +"Použiť Odklonenie Klávesu/Tlačidla a Odkloniť nastavenie G Klávesov pre odklon" +"enie klávesov a tlačidiel." + +#: lib/solaar/ui/diversion_rules.py:1392 +msgid "Key down" +msgstr "Kláves stlačený" + +#: lib/solaar/ui/diversion_rules.py:1395 +msgid "Key up" +msgstr "Kláves uvoľnený" + +#: lib/solaar/ui/diversion_rules.py:1436 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert " +"keys and buttons." +msgstr "" +"Odklonený kláves alebo tlačidlo je práve stlačené.\n" +"Použiť Odklon Klávesov/Tlačidiel a Odkloniť nastavenie G Klávesov na odkloneni" +"e klávesov a tlačidiel." + +#: lib/solaar/ui/diversion_rules.py:1475 +msgid "Test condition on notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1479 +msgid "Parameter" +msgstr "Parameter" + +#: lib/solaar/ui/diversion_rules.py:1542 +msgid "begin (inclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1543 +msgid "end (exclusive)" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1552 +msgid "range" +msgstr "rozsah" + +#: lib/solaar/ui/diversion_rules.py:1554 +msgid "minimum" +msgstr "minimum" + +#: lib/solaar/ui/diversion_rules.py:1555 +msgid "maximum" +msgstr "maximum" + +#: lib/solaar/ui/diversion_rules.py:1557 #, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s: spárovať nové zariadenie" +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "" -#: lib/solaar/ui/pair_window.py:206 -msgid "If the device is already turned on, turn it off and on again." -msgstr "Ak je zariadenie už zapnuté, vypnite ho a opäť zapnite." +#: lib/solaar/ui/diversion_rules.py:1562 +msgid "mask" +msgstr "maska" -#: lib/solaar/ui/pair_window.py:209 +#: lib/solaar/ui/diversion_rules.py:1563 #, python-format -msgid "\n" - "\n" - "This receiver has %d pairing remaining." -msgid_plural "\n" - "\n" - "This receiver has %d pairings remaining." -msgstr[0] "\n" - "\n" - "Tomuto prijímaču ostáva ešte %d párovanie." -msgstr[1] "\n" - "\n" - "Tomuto prijímaču ostáva ešte %d párovania." -msgstr[2] "\n" - "\n" - "Tomuto prijímaču ostáva ešte %d párovaní." +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "" -#: lib/solaar/ui/pair_window.py:212 -msgid "\n" - "Cancelling at this point will not use up a pairing." -msgstr "\n" - "Ak proces teraz zrušíte, spárovanie sa nespotrebuje." +#: lib/solaar/ui/diversion_rules.py:1573 +msgid "" +"Bit or range test on bytes in notification message triggering rule " +"processing." +msgstr "" -#: lib/solaar/ui/pair_window.py:215 -msgid "Turn on the device you want to pair." -msgstr "Zapnite zariadenie, ktoré chcete spárovať." +#: lib/solaar/ui/diversion_rules.py:1583 +msgid "type" +msgstr "typ" -#: lib/solaar/ui/tray.py:61 -msgid "No Logitech receiver found" -msgstr "Nebol nájdený žiaden prijímač Logitech" +#: lib/solaar/ui/diversion_rules.py:1666 +msgid "" +"Mouse gesture with optional initiating button followed by zero or " +"more mouse movements." +msgstr "" -#: lib/solaar/ui/tray.py:68 lib/solaar/ui/window.py:325 -msgid "Quit %s" -msgstr "Ukončiť %s" +#: lib/solaar/ui/diversion_rules.py:1671 +msgid "Add movement" +msgstr "Pridať pohyb" -#: lib/solaar/ui/tray.py:298 lib/solaar/ui/tray.py:306 -msgid "no receiver" -msgstr "bez prijímača" +#: lib/solaar/ui/diversion_rules.py:1764 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" -#: lib/solaar/ui/tray.py:322 -msgid "no status" -msgstr "žiaden stav" +#: lib/solaar/ui/diversion_rules.py:1769 +msgid "Add key" +msgstr "Pridať kláves" -#: lib/solaar/ui/window.py:104 -msgid "Scanning" -msgstr "Hľadá sa" +#: lib/solaar/ui/diversion_rules.py:1772 +msgid "Click" +msgstr "Kliknutie" -#: lib/solaar/ui/window.py:137 lib/solaar/ui/window.py:692 -msgid "Battery" -msgstr "Batéria" +#: lib/solaar/ui/diversion_rules.py:1775 +msgid "Depress" +msgstr "Stlačiť" -#: lib/solaar/ui/window.py:140 -msgid "Wireless Link" -msgstr "Bezdrôtové prepojenie" +#: lib/solaar/ui/diversion_rules.py:1778 +msgid "Release" +msgstr "Uvoľniť" -#: lib/solaar/ui/window.py:144 -msgid "Lighting" -msgstr "Osvetlenie" +#: lib/solaar/ui/diversion_rules.py:1861 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simulovať rolovanie myši.\n" +"Vo Waylande vyžaduje právo na zápis do /dev/uinput." -#: lib/solaar/ui/window.py:178 -msgid "Show Technical Details" -msgstr "Zobraziť technické podrobnosti" +#: lib/solaar/ui/diversion_rules.py:1918 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simulovať kliknutie myši.\n" +"Vo Waylande vyžaduje právo na zápis do /dev/uinput." -#: lib/solaar/ui/window.py:194 -msgid "Pair new device" -msgstr "Spárovať nové zariadenie" +#: lib/solaar/ui/diversion_rules.py:1921 +msgid "Button" +msgstr "Tlačidlo" -#: lib/solaar/ui/window.py:213 -msgid "Select a device" -msgstr "Vyberte zariadenie" +#: lib/solaar/ui/diversion_rules.py:1922 +msgid "Count and Action" +msgstr "Počet a Akcia" -#: lib/solaar/ui/window.py:331 -msgid "Rule Editor" -msgstr "Editor pravidiel" +#: lib/solaar/ui/diversion_rules.py:1972 +msgid "Execute a command with arguments." +msgstr "Vykonať príkaz s argumentami." -#: lib/solaar/ui/window.py:541 -msgid "Path" -msgstr "Cesta" +#: lib/solaar/ui/diversion_rules.py:1975 +msgid "Add argument" +msgstr "Pridať argument" -#: lib/solaar/ui/window.py:544 -msgid "USB ID" -msgstr "USB ID" +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "Toggle" +msgstr "Prepínač" -#: lib/solaar/ui/window.py:547 lib/solaar/ui/window.py:549 -#: lib/solaar/ui/window.py:569 lib/solaar/ui/window.py:571 -msgid "Serial" -msgstr "Sériové číslo" +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "True" +msgstr "Pravda" -#: lib/solaar/ui/window.py:553 -msgid "Index" -msgstr "Index" +#: lib/solaar/ui/diversion_rules.py:2051 +msgid "False" +msgstr "Nepravda" -#: lib/solaar/ui/window.py:555 -msgid "Wireless PID" -msgstr "Bezdrôt. PID" +#: lib/solaar/ui/diversion_rules.py:2065 +msgid "Unsupported setting" +msgstr "Nepodporované nastavenie" -#: lib/solaar/ui/window.py:557 -msgid "Product ID" -msgstr "Identifikátor produktu" +#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2242 +#: lib/solaar/ui/diversion_rules.py:2328 lib/solaar/ui/diversion_rules.py:2570 +#: lib/solaar/ui/diversion_rules.py:2588 +msgid "Originating device" +msgstr "" -#: lib/solaar/ui/window.py:559 -msgid "Protocol" -msgstr "Protokol" +#: lib/solaar/ui/diversion_rules.py:2256 +msgid "Device is active and its settings can be changed." +msgstr "Zariadenie je aktívne a jeho nastavenia možno meniť." -#: lib/solaar/ui/window.py:559 -msgid "Unknown" -msgstr "Neznámy" +#: lib/solaar/ui/diversion_rules.py:2266 +msgid "Device that originated the current notification." +msgstr "" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/diversion_rules.py:2280 +msgid "Name of host computer." +msgstr "Názov hostiteľského počítača." + +#: lib/solaar/ui/diversion_rules.py:2347 +msgid "Value" +msgstr "Hodnota" + +#: lib/solaar/ui/diversion_rules.py:2355 +msgid "Item" +msgstr "Položka" + +#: lib/solaar/ui/diversion_rules.py:2630 +msgid "Change setting on device" +msgstr "Zmeniť nastavenie na zariadení" + +#: lib/solaar/ui/diversion_rules.py:2647 +msgid "Setting on device" +msgstr "Nastavenie na zariadení" + +#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:318 +#: lib/solaar/ui/tray.py:323 lib/solaar/ui/window.py:739 +msgid "offline" +msgstr "offline" + +#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 +#: lib/solaar/ui/pair_window.py:288 #, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d ms (%(rate_hz)dHz)" +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: spárovať nové zariadenie" -#: lib/solaar/ui/window.py:562 -msgid "Polling rate" -msgstr "Intenzita dopytovania" - -#: lib/solaar/ui/window.py:573 -msgid "Unit ID" -msgstr "Číslo (ID) jednotky" - -#: lib/solaar/ui/window.py:584 -msgid "none" -msgstr "žiadne" - -#: lib/solaar/ui/window.py:585 -msgid "Notifications" -msgstr "Notifikácie" - -#: lib/solaar/ui/window.py:622 -msgid "No device paired." -msgstr "Žiadne spárované zariadenie." - -#: lib/solaar/ui/window.py:629 +#: lib/solaar/ui/pair_window.py:123 #, python-format -msgid "Up to %(max_count)s device can be paired to this receiver." -msgid_plural "Up to %(max_count)s devices can be paired to this receiver." -msgstr[0] "K tomuto prijímaču môžete pripojiť %(max_count)s zariadenie." -msgstr[1] "K tomuto prijímaču môžete pripojiť až %(max_count)s " - "zariadenia." -msgstr[2] "K tomuto prijímaču môžete pripojiť až %(max_count)s " - "zariadení." +msgid "Enter passcode on %(name)s." +msgstr "Vložte tajný kód na %(name)s." -#: lib/solaar/ui/window.py:635 -msgid "Only one device can be paired to this receiver." -msgstr "K tomuto prijímaču sa dá pripojiť len jedno zariadenie." - -#: lib/solaar/ui/window.py:639 +#: lib/solaar/ui/pair_window.py:126 #, python-format -msgid "This receiver has %d pairing remaining." -msgid_plural "This receiver has %d pairings remaining." -msgstr[0] "Tomuto prijímaču ostáva %d párovanie." -msgstr[1] "Tomuto prijímaču ostávajú %d párovania." -msgstr[2] "Tomuto prijímaču ostáva %d párovaní." +msgid "Type %(passcode)s and then press the enter key." +msgstr "Napíšte %(passcode)s a potom stlačte kláves Enter." + +#: lib/solaar/ui/pair_window.py:129 +msgid "left" +msgstr "vľavo" + +#: lib/solaar/ui/pair_window.py:129 +msgid "right" +msgstr "vpravo" + +#: lib/solaar/ui/pair_window.py:131 +#, python-format +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"Stlačte %(code)s\n" +"a potom stlačte ľavé a pravé tlačidlo súčasne." + +#: lib/solaar/ui/pair_window.py:188 +msgid "Pairing failed" +msgstr "Párovanie skončilo s chybou" + +#: lib/solaar/ui/pair_window.py:190 +msgid "" +"Make sure your device is within range, and has a decent battery " +"charge." +msgstr "Uistite sa, že je zariadenie v dosahu a je dostatočne nabité." + +#: lib/solaar/ui/pair_window.py:192 +msgid "" +"A new device was detected, but it is not compatible with this " +"receiver." +msgstr "" +"Bolo zistené nové zariadenie, ale nie je kompatibilné s týmto " +"prijímačom." + +#: lib/solaar/ui/pair_window.py:194 +msgid "More paired devices than receiver can support." +msgstr "Viac pripojených zariadení ako dokáže prijímač zvládnuť." + +#: lib/solaar/ui/pair_window.py:196 +msgid "No further details are available about the error." +msgstr "Ďalšie podrobnosti o chybe nie sú dostupné." + +#: lib/solaar/ui/pair_window.py:210 +msgid "Found a new device:" +msgstr "Nájdené nové zariadenie:" + +#: lib/solaar/ui/pair_window.py:235 +msgid "The wireless link is not encrypted" +msgstr "Bezdrôtové pripojenie nepoužíva šifrovanie" + +#: lib/solaar/ui/pair_window.py:264 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "Unifying prijímače sú kompatibilné len s Unifying zariadeniami." + +#: lib/solaar/ui/pair_window.py:266 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "Bolt prijímače sú kompatibilné len s Bolt zariadeniami." + +#: lib/solaar/ui/pair_window.py:268 +msgid "Other receivers are only compatible with a few devices." +msgstr "Ostatné prijímače sú kompatibilné len s máloktorými zariadeniami." + +#: lib/solaar/ui/pair_window.py:270 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "Zariadenie nesmie byť spárované s nedaľekým zapnutým prijímačom." + +#: lib/solaar/ui/pair_window.py:274 +msgid "" +"Press a pairing button or key until the pairing light flashes " +"quickly." +msgstr "" +"Stlačte párovacie tlačidlo alebo kláves kým rýchlo nezabliká párovacie svetlo." + +#: lib/solaar/ui/pair_window.py:276 +msgid "You may have to first turn the device off and on again." +msgstr "Možno bude treba najprv zariadenie vypnuť a znova zapnúť." + +#: lib/solaar/ui/pair_window.py:278 +msgid "Turn on the device you want to pair." +msgstr "Zapnite zariadenie, ktoré chcete spárovať." + +#: lib/solaar/ui/pair_window.py:280 +msgid "If the device is already turned on, turn it off and on again." +msgstr "Ak je zariadenie už zapnuté, vypnite ho a opäť zapnite." + +#: lib/solaar/ui/pair_window.py:283 +#, python-format +msgid "" +"\n" +"\n" +"This receiver has %d pairing remaining." +msgid_plural "" +"\n" +"\n" +"This receiver has %d pairings remaining." +msgstr[0] "" +"\n" +"\n" +"Tomuto prijímaču ostáva ešte %d párovanie." +msgstr[1] "" +"\n" +"\n" +"Tomuto prijímaču ostáva ešte %d párovania." +msgstr[2] "" +"\n" +"\n" +"Tomuto prijímaču ostáva ešte %d párovaní." + +#: lib/solaar/ui/pair_window.py:286 +msgid "" +"\n" +"Cancelling at this point will not use up a pairing." +msgstr "" +"\n" +"Ak proces teraz zrušíte, spárovanie sa nespotrebuje." + +#: lib/solaar/ui/tray.py:58 +msgid "No supported device found" +msgstr "Nenašlo sa podporované zariadenie" + +#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#, python-format +msgid "About %s" +msgstr "O programe %s" + +#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 +#, python-format +msgid "Quit %s" +msgstr "Ukončiť %s" + +#: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 +msgid "no receiver" +msgstr "bez prijímača" + +#: lib/solaar/ui/tray.py:321 +msgid "no status" +msgstr "žiaden stav" + +#: lib/solaar/ui/window.py:96 +msgid "Scanning" +msgstr "Hľadá sa" + +#: lib/solaar/ui/window.py:129 +msgid "Battery" +msgstr "Batéria" + +#: lib/solaar/ui/window.py:132 +msgid "Wireless Link" +msgstr "Bezdrôtové prepojenie" + +#: lib/solaar/ui/window.py:136 +msgid "Lighting" +msgstr "Osvetlenie" + +#: lib/solaar/ui/window.py:170 +msgid "Show Technical Details" +msgstr "Zobraziť technické podrobnosti" + +#: lib/solaar/ui/window.py:186 +msgid "Pair new device" +msgstr "Spárovať nové zariadenie" + +#: lib/solaar/ui/window.py:205 +msgid "Select a device" +msgstr "Vyberte zariadenie" + +#: lib/solaar/ui/window.py:322 +msgid "Rule Editor" +msgstr "Editor pravidiel" + +#: lib/solaar/ui/window.py:533 +msgid "Path" +msgstr "Cesta" + +#: lib/solaar/ui/window.py:536 +msgid "USB ID" +msgstr "USB ID" + +#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 +msgid "Serial" +msgstr "Sériové číslo" + +#: lib/solaar/ui/window.py:545 +msgid "Index" +msgstr "Index" + +#: lib/solaar/ui/window.py:547 +msgid "Wireless PID" +msgstr "Bezdrôt. PID" + +#: lib/solaar/ui/window.py:549 +msgid "Product ID" +msgstr "Identifikátor produktu" + +#: lib/solaar/ui/window.py:551 +msgid "Protocol" +msgstr "Protokol" + +#: lib/solaar/ui/window.py:551 +msgid "Unknown" +msgstr "Neznámy" + +#: lib/solaar/ui/window.py:554 +#, python-format +msgid "%(rate)d ms (%(rate_hz)dHz)" +msgstr "%(rate)d ms (%(rate_hz)dHz)" + +#: lib/solaar/ui/window.py:554 +msgid "Polling rate" +msgstr "Intenzita dopytovania" + +#: lib/solaar/ui/window.py:565 +msgid "Unit ID" +msgstr "Číslo (ID) jednotky" + +#: lib/solaar/ui/window.py:576 +msgid "none" +msgstr "žiadne" + +#: lib/solaar/ui/window.py:577 +msgid "Notifications" +msgstr "Notifikácie" + +#: lib/solaar/ui/window.py:621 +msgid "No device paired." +msgstr "Žiadne spárované zariadenie." + +#: lib/solaar/ui/window.py:628 +#, python-format +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "K tomuto prijímaču môžete pripojiť %(max_count)s zariadenie." +msgstr[1] "" +"K tomuto prijímaču môžete pripojiť až %(max_count)s " +"zariadenia." +msgstr[2] "" +"K tomuto prijímaču môžete pripojiť až %(max_count)s " +"zariadení." + +#: lib/solaar/ui/window.py:634 +msgid "Only one device can be paired to this receiver." +msgstr "K tomuto prijímaču sa dá pripojiť len jedno zariadenie." + +#: lib/solaar/ui/window.py:638 +#, python-format +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "Tomuto prijímaču ostáva %d párovanie." +msgstr[1] "Tomuto prijímaču ostávajú %d párovania." +msgstr[2] "Tomuto prijímaču ostáva %d párovaní." + +#: lib/solaar/ui/window.py:692 +msgid "Battery Voltage" +msgstr "Napätie batérie" #: lib/solaar/ui/window.py:694 -msgid "Battery information unknown." -msgstr "Informácie o batérii nie sú známe." +msgid "Voltage reported by battery" +msgstr "Napätie hlásené batériou" -#: lib/solaar/ui/window.py:702 -msgid "Battery Voltage" -msgstr "Napätie batérie" +#: lib/solaar/ui/window.py:696 +msgid "Battery Level" +msgstr "Úroveň batérie" -#: lib/solaar/ui/window.py:704 -msgid "Voltage reported by battery" -msgstr "Napätie hlásené batériou" +#: lib/solaar/ui/window.py:698 +msgid "Approximate level reported by battery" +msgstr "Približná úroveň hlásená batériou" -#: lib/solaar/ui/window.py:706 lib/solaar/ui/window.py:710 -msgid "Battery Level" -msgstr "Úroveň batérie" +#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 +msgid "next reported " +msgstr "ďalšie hlásené " -#: lib/solaar/ui/window.py:708 lib/solaar/ui/window.py:712 -msgid "Approximate level reported by battery" -msgstr "Približná úroveň hlásená batériou" +#: lib/solaar/ui/window.py:708 +msgid " and next level to be reported." +msgstr " a ďalšia úroveň, ktorá bude hlásená." -#: lib/solaar/ui/window.py:715 lib/solaar/ui/window.py:717 -msgid "next reported " -msgstr "ďalšie hlásené " +#: lib/solaar/ui/window.py:713 +msgid "last known" +msgstr "naposledy známa" -#: lib/solaar/ui/window.py:718 -msgid " and next level to be reported." -msgstr " a ďalšia úroveň, ktorá bude hlásená." +#: lib/solaar/ui/window.py:724 +msgid "encrypted" +msgstr "šifrované" -#: lib/solaar/ui/window.py:723 -msgid "last known" -msgstr "naposledy známa" +#: lib/solaar/ui/window.py:726 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "Bezdrôtové spojenie medzi zariadením a prijímačom je šifrované." -#: lib/solaar/ui/window.py:731 -msgid "not encrypted" -msgstr "bez šifrovania" +#: lib/solaar/ui/window.py:728 +msgid "not encrypted" +msgstr "bez šifrovania" -#: lib/solaar/ui/window.py:735 -msgid "The wireless link between this device and its receiver is not " - "encrypted.\n" - "\n" - "For pointing devices (mice, trackballs, trackpads), this is a minor " - "security issue.\n" - "\n" - "It is, however, a major security issue for text-input devices " - "(keyboards, numpads),\n" - "because typed text can be sniffed inconspicuously by 3rd parties " - "within range." -msgstr "Bezdrôtové spojenie medzi zariadením a prijímačom nie je šifrované.\n" - "\n" - "Pri ukazovacích zariadeniach (myši, trackbally, trackpady) to " - "nepredstavuje veľký bezpečnostný problém.\n" - "\n" - "Avšak pri vstupných zariadeniach zadávajúcich text (klávesnica, " - "numerická \n" - "klávesnica) to predstavuje veľké riziko z hľadiska bezpečnosti, " - "pretože \n" - "písaný text môže byť nepozorovane zachytený treťou stranou, ak je " - "signál v jej dosahu." +#: lib/solaar/ui/window.py:732 +msgid "" +"The wireless link between this device and its receiver is not " +"encrypted.\n" +"This is a security issue for pointing devices, and a major security " +"issue for text-input devices." +msgstr "" +"Bezdrôtové prepojenie tohto zariadenia s jeho prijímačom nie je šifrované.\n" +"To je bezpečnostný problém pre ukazovacie zariadenia a závažný bezpečnostný pr" +"oblém pre textové vstupné zariadenia." -#: lib/solaar/ui/window.py:744 -msgid "encrypted" -msgstr "šifrované" - -#: lib/solaar/ui/window.py:746 -msgid "The wireless link between this device and its receiver is encrypted." -msgstr "Bezdrôtové spojenie medzi zariadením a prijímačom je šifrované." - -#: lib/solaar/ui/window.py:759 +#: lib/solaar/ui/window.py:748 #, python-format -msgid "%(light_level)d lux" -msgstr "%(light_level)d lux" +msgid "%(light_level)d lux" +msgstr "%(light_level)d lux" + +#~ msgid "About" +#~ msgstr "O programe" + +#~ msgid "Add action" +#~ msgstr "Pridať akciu" #~ msgid "Adjust the DPI by sliding the mouse horizontally while " #~ "holding the DPI button." #~ msgstr "Upraviť DPI pohybom myši vo vodorovnom smere pri súčasnom " #~ "držaní tlačidla DPI." +#~ msgid "Adjust the DPI by sliding the mouse horizontally while " +#~ "holding the button down." +#~ msgstr "Úprava DPI pohybom myši vo vodorovnom smere pri súčasnom " +#~ "držaní tlačidla." + +#~ msgid "Automatically switch the mouse wheel between ratchet and " +#~ "freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always ratcheted at 50" +#~ msgstr "Automaticky prepnúť režim kolieska myši medzi krokovaním a " +#~ "voľným otáčaním.\n" +#~ "Koliesko je pri hodnote 0 vždy voľné a pri hodnote 50 vždy krokuje" + +#~ msgid "Battery information unknown." +#~ msgstr "Informácie o batérii nie sú známe." + #~ msgid "Click to allow changes." #~ msgstr "Kliknite pre povolenie zmien." #~ msgid "Click to prevent changes." #~ msgstr "Kliknite pre zakázanie zmien." +#~ msgid "Count" +#~ msgstr "Počet" + +#~ msgid "DPI Sliding Adjustment" +#~ msgstr "Úprava DPI pomocou myši" + #~ msgid "ERROR: " #~ msgstr "CHYBA: " +#~ msgid "Effectively turns off thumb scrolling in Linux." +#~ msgstr "Vypne skrolovanie palcom v Linuxe." + +#~ msgid "Effectively turns off wheel scrolling in Linux." +#~ msgstr "Vypne skrolovanie kolieskom myši v Linuxe." + #~ msgid "For more information see the Solaar installation directions\n" #~ "at https://pwr-solaar.github.io/Solaar/installation" #~ msgstr "Ďalšie informácie o inštalácii Solaaru nájdete na\n" #~ "https://pwr-solaar.github.io/Solaar/installation" +#, python-format +#~ msgid "Found a Logitech Receiver (%s), but did not have permission " +#~ "to open it." +#~ msgstr "Nájdený prijímač Logitech (%s), ale chýbajú práva na jeho " +#~ "otvorenie." + +#~ msgid "HID++ mode for horizontal scroll with the thumb wheel." +#~ msgstr "HID++ režim pre vodorovný posun pomocou kolieska." + +#~ msgid "HID++ mode for vertical scroll with the wheel." +#~ msgstr "HID++ režim pre vertikálne skrolovanie myši." + #~ msgid "If the device is already turned on, turn if off and on again." #~ msgstr "Ak je zariadenie už zapnuté, vypnite ho a znovu zapnite." +#~ msgid "If you've just installed Solaar, try removing the receiver " +#~ "and plugging it back in." +#~ msgstr "Ak ste Solaar práve nainštalovali, skúste prijímač odpojiť a " +#~ "znova pripojiť." + +#~ msgid "Make the key or button send HID++ notifications (which " +#~ "trigger Solaar rules but are otherwise ignored)." +#~ msgstr "Kláves alebo tlačidlo budú zasielať HID++ notifikácie (tie " +#~ "aktivujú Solaar pravidlá, ale inak budú ignorované)." + +#~ msgid "No Logitech receiver found" +#~ msgstr "Nebol nájdený žiaden prijímač Logitech" + #~ msgid "Scroll Wheel HID++ Scrolling" #~ msgstr "HID++ skrolovanie" +#~ msgid "Scroll Wheel Rachet" +#~ msgstr "Krokovanie kolieska" + +#~ msgid "Send a gesture by sliding the mouse while holding the button " +#~ "down." +#~ msgstr "Pošlite gesto pohybom myši pri súčasnom držaní tlačidla." + #~ msgid "Shows status of devices connected\n" #~ "through wireless Logitech receivers." #~ msgstr "Zobrazí stav zariadení pripojených\n" @@ -1439,5 +2001,48 @@ msgstr "%(light_level)d lux" #~ msgid "Solaar depends on a udev file that is not present" #~ msgstr "Solaar závisí na udev súbore, ktorý chýba" +#~ msgid "The wireless link between this device and its receiver is " +#~ "not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices " +#~ "(keyboards, numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties " +#~ "within range." +#~ msgstr "Bezdrôtové spojenie medzi zariadením a prijímačom nie je " +#~ "šifrované.\n" +#~ "\n" +#~ "Pri ukazovacích zariadeniach (myši, trackbally, trackpady) to " +#~ "nepredstavuje veľký bezpečnostný problém.\n" +#~ "\n" +#~ "Avšak pri vstupných zariadeniach zadávajúcich text (klávesnica, " +#~ "numerická \n" +#~ "klávesnica) to predstavuje veľké riziko z hľadiska bezpečnosti, " +#~ "pretože \n" +#~ "písaný text môže byť nepozorovane zachytený treťou stranou, ak je " +#~ "signál v jej dosahu." + #~ msgid "Thumb Wheel HID++ Scrolling" #~ msgstr "HID++ posun kolieskom" + +#~ msgid "Top-most coordinate." +#~ msgstr "Najvyššia súradnica." + +#~ msgid "Try removing the device and plugging it back in or turning " +#~ "it off and then on." +#~ msgstr "Skúste zariadenie odpojiť a znovu ho pripojiť alebo ho " +#~ "skúste vypnúť a opätovne zapnúť." + +#~ msgid "height" +#~ msgstr "výška" + +#~ msgid "top" +#~ msgstr "hore" + +#~ msgid "unknown" +#~ msgstr "neznáme" + +#~ msgid "width" +#~ msgstr "šírka" diff --git a/po/solaar.pot b/po/solaar.pot index be900d8f..6b081ec7 100644 --- a/po/solaar.pot +++ b/po/solaar.pot @@ -5,9 +5,9 @@ # #, fuzzy msgid "" -msgstr "Project-Id-Version: solaar 1.1.10\n" +msgstr "Project-Id-Version: solaar 1.1.19\n" "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2023-09-25 06:09+0200\n" + "POT-Creation-Date: 2026-03-05 14:00+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,1576 +17,1618 @@ msgstr "Project-Id-Version: solaar 1.1.10\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -#: lib/logitech_receiver/base_usb.py:46 +#: lib/logitech_receiver/base_usb.py:52 msgid "Bolt Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:57 +#: lib/logitech_receiver/base_usb.py:64 msgid "Unifying Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 -#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 -#: lib/logitech_receiver/base_usb.py:114 +#: lib/logitech_receiver/base_usb.py:75 lib/logitech_receiver/base_usb.py:87 +#: lib/logitech_receiver/base_usb.py:100 lib/logitech_receiver/base_usb.py:113 msgid "Nano Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:124 +#: lib/logitech_receiver/base_usb.py:125 msgid "Lightspeed Receiver" msgstr "" -#: lib/logitech_receiver/base_usb.py:133 +#: lib/logitech_receiver/base_usb.py:136 msgid "EX100 Receiver 27 Mhz" msgstr "" -#: lib/logitech_receiver/i18n.py:30 +#: lib/logitech_receiver/common.py:649 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "" + +#: lib/logitech_receiver/common.py:652 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1048 +#: lib/logitech_receiver/settings_templates.py:299 +msgid "Disabled" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1049 +msgid "Static" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1050 +msgid "Pulse" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1051 +msgid "Cycle" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1052 +msgid "Boot" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1053 +msgid "Demo" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1055 +msgid "Breathe" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1058 +msgid "Ripple" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1059 +msgid "Decomposition" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1060 +msgid "Signature1" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1061 +msgid "Signature2" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1062 +msgid "CycleS" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1126 +msgid "Unknown Location" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1127 +msgid "Primary" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1128 +msgid "Logo" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1129 +msgid "Left Side" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1130 +msgid "Right Side" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1131 +msgid "Combined" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1132 +msgid "Primary 1" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1133 +msgid "Primary 2" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1134 +msgid "Primary 3" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1135 +msgid "Primary 4" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1136 +msgid "Primary 5" +msgstr "" + +#: lib/logitech_receiver/hidpp20.py:1137 +msgid "Primary 6" +msgstr "" + +#: lib/logitech_receiver/i18n.py:28 msgid "empty" msgstr "" -#: lib/logitech_receiver/i18n.py:31 +#: lib/logitech_receiver/i18n.py:29 msgid "critical" msgstr "" -#: lib/logitech_receiver/i18n.py:32 +#: lib/logitech_receiver/i18n.py:30 msgid "low" msgstr "" -#: lib/logitech_receiver/i18n.py:33 +#: lib/logitech_receiver/i18n.py:31 msgid "average" msgstr "" -#: lib/logitech_receiver/i18n.py:34 +#: lib/logitech_receiver/i18n.py:32 msgid "good" msgstr "" -#: lib/logitech_receiver/i18n.py:35 +#: lib/logitech_receiver/i18n.py:33 msgid "full" msgstr "" -#: lib/logitech_receiver/i18n.py:38 +#: lib/logitech_receiver/i18n.py:35 msgid "discharging" msgstr "" -#: lib/logitech_receiver/i18n.py:39 +#: lib/logitech_receiver/i18n.py:36 msgid "recharging" msgstr "" -#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 +#: lib/logitech_receiver/i18n.py:37 lib/solaar/ui/window.py:700 msgid "charging" msgstr "" -#: lib/logitech_receiver/i18n.py:41 +#: lib/logitech_receiver/i18n.py:38 msgid "not charging" msgstr "" -#: lib/logitech_receiver/i18n.py:42 +#: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "" -#: lib/logitech_receiver/i18n.py:43 +#: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "" -#: lib/logitech_receiver/i18n.py:44 +#: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "" -#: lib/logitech_receiver/i18n.py:45 +#: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "" -#: lib/logitech_receiver/i18n.py:46 +#: lib/logitech_receiver/i18n.py:43 msgid "thermal error" msgstr "" -#: lib/logitech_receiver/i18n.py:47 +#: lib/logitech_receiver/i18n.py:44 msgid "error" msgstr "" -#: lib/logitech_receiver/i18n.py:48 +#: lib/logitech_receiver/i18n.py:45 msgid "standard" msgstr "" -#: lib/logitech_receiver/i18n.py:49 +#: lib/logitech_receiver/i18n.py:46 msgid "fast" msgstr "" -#: lib/logitech_receiver/i18n.py:50 +#: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "" -#: lib/logitech_receiver/i18n.py:53 +#: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "" -#: lib/logitech_receiver/i18n.py:54 +#: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "" -#: lib/logitech_receiver/i18n.py:55 +#: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "" -#: lib/logitech_receiver/i18n.py:56 +#: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "" -#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 +#: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:555 msgid "Firmware" msgstr "" -#: lib/logitech_receiver/i18n.py:60 +#: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "" -#: lib/logitech_receiver/i18n.py:61 +#: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "" -#: lib/logitech_receiver/i18n.py:62 +#: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "" -#: lib/logitech_receiver/i18n.py:65 +#: lib/logitech_receiver/i18n.py:59 msgid "Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:66 +#: lib/logitech_receiver/i18n.py:60 msgid "Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:67 +#: lib/logitech_receiver/i18n.py:61 msgid "Middle Button" msgstr "" -#: lib/logitech_receiver/i18n.py:68 +#: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "" -#: lib/logitech_receiver/i18n.py:69 +#: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "" -#: lib/logitech_receiver/i18n.py:70 +#: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "" -#: lib/logitech_receiver/i18n.py:71 +#: lib/logitech_receiver/i18n.py:65 msgid "Smart Shift" msgstr "" -#: lib/logitech_receiver/i18n.py:72 +#: lib/logitech_receiver/i18n.py:66 msgid "DPI Switch" msgstr "" -#: lib/logitech_receiver/i18n.py:73 +#: lib/logitech_receiver/i18n.py:67 msgid "Left Tilt" msgstr "" -#: lib/logitech_receiver/i18n.py:74 +#: lib/logitech_receiver/i18n.py:68 msgid "Right Tilt" msgstr "" -#: lib/logitech_receiver/i18n.py:75 +#: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "" -#: lib/logitech_receiver/i18n.py:76 +#: lib/logitech_receiver/i18n.py:70 msgid "Right Click" msgstr "" -#: lib/logitech_receiver/i18n.py:77 +#: lib/logitech_receiver/i18n.py:71 msgid "Mouse Middle Button" msgstr "" -#: lib/logitech_receiver/i18n.py:78 +#: lib/logitech_receiver/i18n.py:72 msgid "Mouse Back Button" msgstr "" -#: lib/logitech_receiver/i18n.py:79 +#: lib/logitech_receiver/i18n.py:73 msgid "Mouse Forward Button" msgstr "" -#: lib/logitech_receiver/i18n.py:80 +#: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "" -#: lib/logitech_receiver/i18n.py:81 +#: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "" -#: lib/logitech_receiver/i18n.py:82 +#: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "" -#: lib/logitech_receiver/i18n.py:85 +#: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "" -#: lib/logitech_receiver/i18n.py:86 +#: lib/logitech_receiver/i18n.py:79 msgid "released" msgstr "" -#: lib/logitech_receiver/notifications.py:75 -#: lib/logitech_receiver/notifications.py:126 -msgid "pairing lock is closed" -msgstr "" - -#: lib/logitech_receiver/notifications.py:75 -#: lib/logitech_receiver/notifications.py:126 -msgid "pairing lock is open" -msgstr "" - -#: lib/logitech_receiver/notifications.py:92 -msgid "discovery lock is closed" -msgstr "" - -#: lib/logitech_receiver/notifications.py:92 -msgid "discovery lock is open" -msgstr "" - -#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 msgid "connected" msgstr "" -#: lib/logitech_receiver/notifications.py:224 +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 msgid "disconnected" msgstr "" -#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 +#: lib/logitech_receiver/notifications.py:182 msgid "unpaired" msgstr "" -#: lib/logitech_receiver/notifications.py:304 +#: lib/logitech_receiver/notifications.py:231 msgid "powered on" msgstr "" -#: lib/logitech_receiver/settings.py:750 -msgid "register" +#: lib/logitech_receiver/notifications.py:283 +msgid "ADC measurement notification" msgstr "" -#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 -msgid "feature" +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:484 +msgid "pairing lock is closed" msgstr "" -#: lib/logitech_receiver/settings_templates.py:139 -msgid "Swap Fx function" +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:484 +msgid "pairing lock is open" msgstr "" -#: lib/logitech_receiver/settings_templates.py:140 -msgid "When set, the F1..F12 keys will activate their special function,\n" - "and you must hold the FN key to activate their standard function." +#: lib/logitech_receiver/notifications.py:447 +msgid "discovery lock is closed" msgstr "" -#: lib/logitech_receiver/settings_templates.py:142 -msgid "When unset, the F1..F12 keys will activate their standard function,\n" - "and you must hold the FN key to activate their special function." +#: lib/logitech_receiver/notifications.py:447 +msgid "discovery lock is open" msgstr "" -#: lib/logitech_receiver/settings_templates.py:149 -msgid "Hand Detection" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:150 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:157 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:239 -#: lib/logitech_receiver/settings_templates.py:267 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:165 -msgid "Side Scrolling" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:167 -msgid "When disabled, pushing the wheel sideways sends custom button " - "events\n" - "instead of the standard side-scrolling events." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:177 -msgid "Sensitivity (DPI - older mice)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:178 -#: lib/logitech_receiver/settings_templates.py:712 -msgid "Mouse movement sensitivity" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:208 -#: lib/logitech_receiver/settings_templates.py:218 -#: lib/logitech_receiver/settings_templates.py:225 -msgid "Backlight" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:209 -#: lib/logitech_receiver/settings_templates.py:226 -msgid "Set illumination time for keyboard." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:219 -msgid "Turn illumination on or off on keyboard." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:237 -msgid "Scroll Wheel High Resolution" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:240 -#: lib/logitech_receiver/settings_templates.py:268 -msgid "Set to ignore if scrolling is abnormally fast or slow" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:247 -#: lib/logitech_receiver/settings_templates.py:277 -msgid "Scroll Wheel Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:249 -msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " - "trigger Solaar rules but are otherwise ignored)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:256 -msgid "Scroll Wheel Direction" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:257 -msgid "Invert direction for vertical scroll with wheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:265 -msgid "Scroll Wheel Resolution" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:279 -msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " - "trigger Solaar rules but are otherwise ignored)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:288 -msgid "Sensitivity (Pointer Speed)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:289 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:299 -msgid "Thumb Wheel Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:301 -msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:310 -msgid "Thumb Wheel Direction" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:311 -msgid "Invert thumb wheel scroll direction." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:319 -msgid "Onboard Profiles" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:320 -msgid "Enable onboard profiles, which often control report rate and " - "keyboard lighting" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:330 -msgid "Polling Rate (ms)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:332 -msgid "Frequency of device polling, in milliseconds" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:333 -#: lib/logitech_receiver/settings_templates.py:1046 -#: lib/logitech_receiver/settings_templates.py:1076 -msgid "May need Onboard Profiles set to Disable to be effective." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:365 -msgid "Divert crown events" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:366 -msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:374 -msgid "Crown smooth scroll" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:375 -msgid "Set crown smooth scroll" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:383 -msgid "Divert G Keys" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:385 -msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:386 -msgid "May also make M keys and MR key send HID++ notifications" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:402 -msgid "Scroll Wheel Ratcheted" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:403 -msgid "Switch the mouse wheel between speed-controlled ratcheting and " - "always freespin." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:405 -msgid "Freespinning" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:405 -msgid "Ratcheted" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:412 -msgid "Scroll Wheel Ratchet Speed" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:414 -msgid "Use the mouse wheel speed to switch between ratcheted and " - "freespinning.\n" - "The mouse wheel is always ratcheted at 50." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:463 -msgid "Key/Button Actions" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:465 -msgid "Change the action for the key or button." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:465 -msgid "Overridden by diversion." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:466 -msgid "Changing important actions (such as for the left mouse button) can " - "result in an unusable system." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:639 -msgid "Key/Button Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:640 -msgid "Make the key or button send HID++ notifications (Diverted) or " - "initiate Mouse Gestures or Sliding DPI" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 -#: lib/logitech_receiver/settings_templates.py:645 -msgid "Diverted" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 -msgid "Mouse Gestures" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 -#: lib/logitech_receiver/settings_templates.py:645 -msgid "Regular" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:643 -msgid "Sliding DPI" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:711 -msgid "Sensitivity (DPI)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:752 -msgid "Sensitivity Switching" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:754 -msgid "Switch the current sensitivity and the remembered sensitivity when " - "the key or button is pressed.\n" - "If there is no remembered sensitivity, just remember the current " - "sensitivity" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:758 -msgid "Off" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:791 -msgid "Disable keys" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:792 -msgid "Disable specific keyboard keys." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:795 -#, python-format -msgid "Disables the %s key." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:809 -#: lib/logitech_receiver/settings_templates.py:860 -msgid "Set OS" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:810 -#: lib/logitech_receiver/settings_templates.py:861 -msgid "Change keys to match OS." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:873 -msgid "Change Host" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:874 -msgid "Switch connection to a different host" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:900 -msgid "Performs a left click." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:900 -msgid "Single tap" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:901 -msgid "Performs a right click." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:901 -msgid "Single tap with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:902 -msgid "Single tap with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:906 -msgid "Double tap" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:906 -msgid "Performs a double click." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:907 -msgid "Double tap with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:908 -msgid "Double tap with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:911 -msgid "Drags items by dragging the finger after double tapping." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:911 -msgid "Tap and drag" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:913 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:913 -msgid "Tap and drag with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:914 -msgid "Tap and drag with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:917 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:917 -msgid "Suppress tap and edge gestures" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:918 -msgid "Scroll with one finger" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:918 -#: lib/logitech_receiver/settings_templates.py:919 -#: lib/logitech_receiver/settings_templates.py:922 -msgid "Scrolls." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:919 -#: lib/logitech_receiver/settings_templates.py:922 -msgid "Scroll with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:920 -msgid "Scroll horizontally with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:920 -msgid "Scrolls horizontally." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:921 -msgid "Scroll vertically with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:921 -msgid "Scrolls vertically." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:923 -msgid "Inverts the scrolling direction." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:923 -msgid "Natural scrolling" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:924 -msgid "Enables the thumbwheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:924 -msgid "Thumbwheel" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:935 -#: lib/logitech_receiver/settings_templates.py:939 -msgid "Swipe from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:936 -msgid "Swipe from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:937 -msgid "Swipe from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:938 -msgid "Swipe from the bottom edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:940 -msgid "Swipe two fingers from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:941 -msgid "Swipe two fingers from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:942 -msgid "Swipe two fingers from the bottom edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:943 -msgid "Swipe two fingers from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:944 -#: lib/logitech_receiver/settings_templates.py:948 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:944 -msgid "Zoom with two fingers." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:945 -msgid "Pinch to zoom out." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:946 -msgid "Spread to zoom in." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:947 -msgid "Zoom with three fingers." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:948 -msgid "Zoom with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:966 -msgid "Pixel zone" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:967 -msgid "Ratio zone" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:968 -msgid "Scale factor" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:968 -msgid "Sets the cursor speed." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:972 -msgid "Left" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:972 -msgid "Left-most coordinate." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:973 -msgid "Bottom" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:973 -msgid "Bottom coordinate." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:974 -msgid "Width" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:974 -msgid "Width." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:975 -msgid "Height" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:975 -msgid "Height." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:976 -msgid "Cursor speed." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:976 -msgid "Scale" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:982 -msgid "Gestures" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:983 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1000 -msgid "Gestures Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1001 -msgid "Divert mouse/touchpad gestures." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1018 -msgid "Gesture params" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1019 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1044 -msgid "M-Key LEDs" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1046 -msgid "Control the M-Key LEDs." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1047 -#: lib/logitech_receiver/settings_templates.py:1077 -msgid "May need G Keys diverted to be effective." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1053 -#, python-format -msgid "Lights up the %s key." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1074 -msgid "MR-Key LED" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1076 -msgid "Control the MR-Key LED." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1095 -msgid "Persistent Key/Button Mapping" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1097 -msgid "Permanently change the mapping for the key or button." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1098 -msgid "Changing important keys or buttons (such as for the left mouse " - "button) can result in an unusable system." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1157 -msgid "Sidetone" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1158 -msgid "Set sidetone level." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1167 -msgid "Equalizer" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1168 -msgid "Set equalizer levels." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1191 -msgid "Hz" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1197 -msgid "Power Management" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:1198 -msgid "Power off in minutes (0 for never)." -msgstr "" - -#: lib/logitech_receiver/status.py:114 +#: lib/logitech_receiver/receiver.py:437 msgid "No paired devices." msgstr "" -#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 +#: lib/logitech_receiver/receiver.py:439 lib/solaar/ui/window.py:606 #, python-format msgid "%(count)s paired device." msgid_plural "%(count)s paired devices." msgstr[0] "" msgstr[1] "" -#: lib/logitech_receiver/status.py:170 -#, python-format -msgid "Battery: %(level)s" +#: lib/logitech_receiver/settings.py:602 +msgid "register" msgstr "" -#: lib/logitech_receiver/status.py:172 -#, python-format -msgid "Battery: %(percent)d%%" +#: lib/logitech_receiver/settings.py:616 lib/logitech_receiver/settings.py:654 +msgid "feature" msgstr "" -#: lib/logitech_receiver/status.py:184 -#, python-format -msgid "Lighting: %(level)s lux" +#: lib/logitech_receiver/settings_templates.py:138 +msgid "Swap Fx function" msgstr "" -#: lib/logitech_receiver/status.py:239 -#, python-format -msgid "Battery: %(level)s (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:141 +msgid "When set, the F1..F12 keys will activate their special function,\n" + "and you must hold the FN key to activate their standard function." msgstr "" -#: lib/logitech_receiver/status.py:241 -#, python-format -msgid "Battery: %(percent)d%% (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:146 +msgid "When unset, the F1..F12 keys will activate their standard function,\n" + "and you must hold the FN key to activate their special function." msgstr "" -#: lib/solaar/ui/__init__.py:52 +#: lib/logitech_receiver/settings_templates.py:154 +msgid "Hand Detection" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:155 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:162 +msgid "Scroll Wheel Smooth Scrolling" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:163 +#: lib/logitech_receiver/settings_templates.py:410 +#: lib/logitech_receiver/settings_templates.py:439 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:170 +msgid "Side Scrolling" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:172 +msgid "When disabled, pushing the wheel sideways sends custom button " + "events\n" + "instead of the standard side-scrolling events." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:182 +msgid "Sensitivity (DPI - older mice)" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:183 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +msgid "Mouse movement sensitivity" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Backlight Timed" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:257 +#: lib/logitech_receiver/settings_templates.py:397 +msgid "Set illumination time for keyboard." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Backlight" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:269 +msgid "Illumination level on keyboard. Changes made are only applied in " + "Manual mode." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Automatic" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:303 +msgid "Manual" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:305 +msgid "Enabled" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Backlight Level" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:312 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:369 +msgid "Backlight Delay Hands Out" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:370 +msgid "Delay in seconds until backlight fades out with hands away from " + "keyboard." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:378 +msgid "Backlight Delay Hands In" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:379 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:387 +msgid "Backlight Delay Powered" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:388 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:396 +msgid "Backlight (Seconds)" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:408 +msgid "Scroll Wheel High Resolution" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:412 +#: lib/logitech_receiver/settings_templates.py:441 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:419 +#: lib/logitech_receiver/settings_templates.py:450 +msgid "Scroll Wheel Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:421 +msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:428 +msgid "Scroll Wheel Direction" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:429 +msgid "Invert direction for vertical scroll with wheel." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:437 +msgid "Scroll Wheel Resolution" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:452 +msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:461 +msgid "Sensitivity (Pointer Speed)" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:462 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:472 +msgid "Thumb Wheel Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:474 +msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " + "Solaar rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:483 +msgid "Thumb Wheel Direction" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:484 +msgid "Invert thumb wheel scroll direction." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:504 +msgid "Onboard Profiles" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:505 +msgid "Enable an onboard profile, which controls report rate, sensitivity, " + "and button actions" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:549 +#: lib/logitech_receiver/settings_templates.py:582 +msgid "Report Rate" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +msgid "Frequency of device movement reports" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1421 +#: lib/logitech_receiver/settings_templates.py:1452 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:612 +msgid "Divert crown events" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:613 +msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:621 +msgid "Crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:622 +msgid "Set crown smooth scroll" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:630 +msgid "Divert G and M Keys" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:631 +msgid "Make G and M keys send HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Scroll Wheel Ratcheted" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:646 +msgid "Switch the mouse wheel between speed-controlled ratcheting and " + "always freespin." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Freespinning" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Ratcheted" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:655 +msgid "Scroll Wheel Ratchet Speed" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:657 +msgid "Use the mouse wheel speed to switch between ratcheted and " + "freespinning.\n" + "The mouse wheel is always ratcheted at 50." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:707 +msgid "Scroll Wheel Ratchet Torque" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:708 +msgid "Change the torque needed to overcome the ratchet." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:743 +msgid "Key/Button Actions" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:745 +msgid "Change the action for the key or button." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:747 +msgid "Overridden by diversion." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:749 +msgid "Changing important actions (such as for the left mouse button) can " + "result in an unusable system." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Key/Button Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:925 +msgid "Make the key or button send HID++ notifications (Diverted) or " + "initiate Mouse Gestures or Sliding DPI" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Diverted" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +msgid "Mouse Gestures" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Regular" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:928 +msgid "Sliding DPI" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1018 +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Sensitivity (DPI)" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1123 +msgid "Sensitivity Switching" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1125 +msgid "Switch the current sensitivity and the remembered sensitivity when " + "the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current " + "sensitivity" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1129 +msgid "Off" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1160 +msgid "Disable keys" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1161 +msgid "Disable specific keyboard keys." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1164 +#, python-format +msgid "Disables the %s key." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1177 +#: lib/logitech_receiver/settings_templates.py:1234 +msgid "Set OS" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1178 +#: lib/logitech_receiver/settings_templates.py:1235 +msgid "Change keys to match OS." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1247 +msgid "Change Host" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1248 +msgid "Switch connection to a different host" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Performs a left click." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Single tap" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Performs a right click." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Single tap with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1274 +msgid "Single tap with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Double tap" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Performs a double click." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1279 +msgid "Double tap with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1280 +msgid "Double tap with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1283 +msgid "Drags items by dragging the finger after double tapping." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1283 +msgid "Tap and drag" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1285 +msgid "Tap and drag with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1286 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1288 +msgid "Tap and drag with three fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1291 +msgid "Suppress tap and edge gestures" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1292 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1294 +msgid "Scroll with one finger" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1294 +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 +msgid "Scrolls." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 +msgid "Scroll with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1296 +msgid "Scroll horizontally with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1296 +msgid "Scrolls horizontally." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1297 +msgid "Scroll vertically with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1297 +msgid "Scrolls vertically." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1299 +msgid "Inverts the scrolling direction." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1299 +msgid "Natural scrolling" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Enables the thumbwheel." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Thumbwheel" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1311 +#: lib/logitech_receiver/settings_templates.py:1315 +msgid "Swipe from the top edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1312 +msgid "Swipe from the left edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1313 +msgid "Swipe from the right edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1314 +msgid "Swipe from the bottom edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1316 +msgid "Swipe two fingers from the left edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1317 +msgid "Swipe two fingers from the right edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1318 +msgid "Swipe two fingers from the bottom edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1319 +msgid "Swipe two fingers from the top edge" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1320 +#: lib/logitech_receiver/settings_templates.py:1324 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1320 +msgid "Zoom with two fingers." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1321 +msgid "Pinch to zoom out." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1322 +msgid "Spread to zoom in." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1323 +msgid "Zoom with three fingers." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1324 +msgid "Zoom with two fingers" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1342 +msgid "Pixel zone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1343 +msgid "Ratio zone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1344 +msgid "Scale factor" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1344 +msgid "Sets the cursor speed." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1348 +msgid "Left" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1348 +msgid "Left-most coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom coordinate." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1352 +msgid "Cursor speed." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1352 +msgid "Scale" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1358 +msgid "Gestures" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1359 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1375 +msgid "Gestures Diversion" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1376 +msgid "Divert mouse/touchpad gestures." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1392 +msgid "Gesture params" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1393 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1417 +msgid "M-Key LEDs" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1419 +msgid "Control the M-Key LEDs." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1423 +#: lib/logitech_receiver/settings_templates.py:1454 +msgid "May need G Keys diverted to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1429 +#, python-format +msgid "Lights up the %s key." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1448 +msgid "MR-Key LED" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1450 +msgid "Control the MR-Key LED." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1471 +msgid "Persistent Key/Button Mapping" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1473 +msgid "Permanently change the mapping for the key or button." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1475 +msgid "Changing important keys or buttons (such as for the left mouse " + "button) can result in an unusable system." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1532 +msgid "Sidetone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1533 +msgid "Set sidetone level." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1542 +msgid "Equalizer" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1543 +msgid "Set equalizer levels." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1565 +msgid "Hz" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1571 +msgid "Power Management" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1572 +msgid "Power off in minutes (0 for never)." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1584 +msgid "Brightness Control" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1585 +msgid "Control overall brightness" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1628 +#: lib/logitech_receiver/settings_templates.py:1681 +msgid "LED Control" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1629 +#: lib/logitech_receiver/settings_templates.py:1682 +msgid "Switch control of LED zones between device and Solaar" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1644 +#: lib/logitech_receiver/settings_templates.py:1692 +msgid "LED Zone Effects" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "LED Control needs to be set to Solaar to be effective." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "Set effect for LED Zone" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1647 +msgid "Color" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1648 +msgid "Speed" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1649 +msgid "Period" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1650 +msgid "Intensity" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1651 +msgid "Ramp" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1666 +msgid "LEDs" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1703 +msgid "Per-key Lighting" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1704 +msgid "Control per-key lighting." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1786 +msgid "Force Sensing Buttons" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1787 +msgid "Change the force required to activate button." +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1804 +msgid "Force Sensing Button" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1814 +msgid "Haptic Feedback Level" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1815 +msgid "Change power of haptic feedback. (Zero to turn off.)" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1857 +msgid "Play Haptic Waveform" +msgstr "" + +#: lib/logitech_receiver/settings_templates.py:1858 +msgid "Tell device to play a haptic waveform." +msgstr "" + +#: lib/solaar/ui/__init__.py:120 +msgid "Another Solaar process is already running so just expose its window" +msgstr "" + +#: lib/solaar/ui/about/model.py:36 +msgid "Manages Logitech receivers,\n" + "keyboards, mice, and tablets." +msgstr "" + +#: lib/solaar/ui/about/model.py:64 +msgid "Additional Programming" +msgstr "" + +#: lib/solaar/ui/about/model.py:65 +msgid "GUI design" +msgstr "" + +#: lib/solaar/ui/about/model.py:67 +msgid "Testing" +msgstr "" + +#: lib/solaar/ui/about/model.py:75 +msgid "Logitech documentation" +msgstr "" + +#: lib/solaar/ui/action.py:88 lib/solaar/ui/action.py:92 +#: lib/solaar/ui/window.py:208 +msgid "Unpair" +msgstr "" + +#: lib/solaar/ui/action.py:91 lib/solaar/ui/diversion_rules.py:141 +msgid "Cancel" +msgstr "" + +#: lib/solaar/ui/common.py:42 msgid "Permissions error" msgstr "" -#: lib/solaar/ui/__init__.py:54 +#: lib/solaar/ui/common.py:44 #, python-format msgid "Found a Logitech receiver or device (%s), but did not have " "permission to open it." msgstr "" -#: lib/solaar/ui/__init__.py:55 +#: lib/solaar/ui/common.py:46 msgid "If you've just installed Solaar, try disconnecting the receiver or " "device and then reconnecting it." msgstr "" -#: lib/solaar/ui/__init__.py:58 +#: lib/solaar/ui/common.py:49 msgid "Cannot connect to device error" msgstr "" -#: lib/solaar/ui/__init__.py:60 +#: lib/solaar/ui/common.py:51 #, python-format msgid "Found a Logitech receiver or device at %s, but encountered an error " "connecting to it." msgstr "" -#: lib/solaar/ui/__init__.py:61 +#: lib/solaar/ui/common.py:53 msgid "Try disconnecting the device and then reconnecting it or turning it " "off and then on." msgstr "" -#: lib/solaar/ui/__init__.py:64 +#: lib/solaar/ui/common.py:56 msgid "Unpairing failed" msgstr "" -#: lib/solaar/ui/__init__.py:66 +#: lib/solaar/ui/common.py:58 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "" -#: lib/solaar/ui/__init__.py:67 +#: lib/solaar/ui/common.py:63 msgid "The receiver returned an error, with no further details." msgstr "" -#: lib/solaar/ui/__init__.py:177 -msgid "Another Solaar process is already running so just expose its window" -msgstr "" - -#: lib/solaar/ui/about.py:36 -msgid "Manages Logitech receivers,\n" - "keyboards, mice, and tablets." -msgstr "" - -#: lib/solaar/ui/about.py:44 -msgid "Additional Programming" -msgstr "" - -#: lib/solaar/ui/about.py:45 -msgid "GUI design" -msgstr "" - -#: lib/solaar/ui/about.py:47 -msgid "Testing" -msgstr "" - -#: lib/solaar/ui/about.py:54 -msgid "Logitech documentation" -msgstr "" - -#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 -#: lib/solaar/ui/window.py:197 -msgid "Unpair" -msgstr "" - -#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 -msgid "Cancel" -msgstr "" - -#: lib/solaar/ui/config_panel.py:212 +#: lib/solaar/ui/config_panel.py:245 msgid "Complete - ENTER to change" msgstr "" -#: lib/solaar/ui/config_panel.py:212 +#: lib/solaar/ui/config_panel.py:245 msgid "Incomplete" msgstr "" -#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 +#: lib/solaar/ui/config_panel.py:491 lib/solaar/ui/config_panel.py:543 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "" msgstr[1] "" -#: lib/solaar/ui/config_panel.py:518 +#: lib/solaar/ui/config_panel.py:642 msgid "Changes allowed" msgstr "" -#: lib/solaar/ui/config_panel.py:519 +#: lib/solaar/ui/config_panel.py:643 msgid "No changes allowed" msgstr "" -#: lib/solaar/ui/config_panel.py:520 +#: lib/solaar/ui/config_panel.py:644 msgid "Ignore this setting" msgstr "" -#: lib/solaar/ui/config_panel.py:565 +#: lib/solaar/ui/config_panel.py:690 msgid "Working" msgstr "" -#: lib/solaar/ui/config_panel.py:568 +#: lib/solaar/ui/config_panel.py:693 msgid "Read/write operation failed." msgstr "" -#: lib/solaar/ui/diversion_rules.py:65 +#: lib/solaar/ui/desktop_notifications.py:112 +msgid "unspecified reason" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:103 msgid "Built-in rules" msgstr "" -#: lib/solaar/ui/diversion_rules.py:65 +#: lib/solaar/ui/diversion_rules.py:103 msgid "User-defined rules" msgstr "" -#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 +#: lib/solaar/ui/diversion_rules.py:105 lib/solaar/ui/diversion_rules.py:1126 msgid "Rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 -#: lib/solaar/ui/diversion_rules.py:636 +#: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:382 +#: lib/solaar/ui/diversion_rules.py:509 msgid "Sub-rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:70 +#: lib/solaar/ui/diversion_rules.py:108 msgid "[empty]" msgstr "" -#: lib/solaar/ui/diversion_rules.py:94 -msgid "Solaar Rule Editor" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:141 +#: lib/solaar/ui/diversion_rules.py:132 msgid "Make changes permanent?" msgstr "" -#: lib/solaar/ui/diversion_rules.py:146 +#: lib/solaar/ui/diversion_rules.py:137 msgid "Yes" msgstr "" -#: lib/solaar/ui/diversion_rules.py:148 +#: lib/solaar/ui/diversion_rules.py:139 msgid "No" msgstr "" -#: lib/solaar/ui/diversion_rules.py:153 +#: lib/solaar/ui/diversion_rules.py:144 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "" -#: lib/solaar/ui/diversion_rules.py:201 -msgid "Save changes" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:206 -msgid "Discard changes" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:372 -msgid "Insert here" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:374 -msgid "Insert above" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:376 -msgid "Insert below" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:382 -msgid "Insert new rule here" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:384 -msgid "Insert new rule above" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:386 -msgid "Insert new rule below" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:427 +#: lib/solaar/ui/diversion_rules.py:273 msgid "Paste here" msgstr "" -#: lib/solaar/ui/diversion_rules.py:429 +#: lib/solaar/ui/diversion_rules.py:275 msgid "Paste above" msgstr "" -#: lib/solaar/ui/diversion_rules.py:431 +#: lib/solaar/ui/diversion_rules.py:277 msgid "Paste below" msgstr "" -#: lib/solaar/ui/diversion_rules.py:437 +#: lib/solaar/ui/diversion_rules.py:283 msgid "Paste rule here" msgstr "" -#: lib/solaar/ui/diversion_rules.py:439 +#: lib/solaar/ui/diversion_rules.py:285 msgid "Paste rule above" msgstr "" -#: lib/solaar/ui/diversion_rules.py:441 +#: lib/solaar/ui/diversion_rules.py:287 msgid "Paste rule below" msgstr "" -#: lib/solaar/ui/diversion_rules.py:445 +#: lib/solaar/ui/diversion_rules.py:291 msgid "Paste rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:474 +#: lib/solaar/ui/diversion_rules.py:306 +msgid "Insert here" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:308 +msgid "Insert above" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:310 +msgid "Insert below" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:316 +msgid "Insert new rule here" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:318 +msgid "Insert new rule above" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:320 +msgid "Insert new rule below" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:347 msgid "Flatten" msgstr "" -#: lib/solaar/ui/diversion_rules.py:507 +#: lib/solaar/ui/diversion_rules.py:380 msgid "Insert" msgstr "" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 -#: lib/solaar/ui/diversion_rules.py:1126 +#: lib/solaar/ui/diversion_rules.py:383 lib/solaar/ui/diversion_rules.py:511 +#: lib/solaar/ui/diversion_rules.py:1158 msgid "Or" msgstr "" -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 -#: lib/solaar/ui/diversion_rules.py:1111 +#: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:510 +#: lib/solaar/ui/diversion_rules.py:1144 msgid "And" msgstr "" -#: lib/solaar/ui/diversion_rules.py:513 +#: lib/solaar/ui/diversion_rules.py:386 msgid "Condition" msgstr "" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 +#: lib/solaar/ui/diversion_rules.py:388 lib/solaar/ui/rule_conditions.py:154 msgid "Feature" msgstr "" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 +#: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:188 msgid "Report" msgstr "" -#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 +#: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:69 msgid "Process" msgstr "" -#: lib/solaar/ui/diversion_rules.py:518 +#: lib/solaar/ui/diversion_rules.py:391 msgid "Mouse process" msgstr "" -#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 +#: lib/solaar/ui/diversion_rules.py:392 lib/solaar/ui/rule_conditions.py:225 msgid "Modifiers" msgstr "" -#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 +#: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:277 msgid "Key" msgstr "" -#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 +#: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:318 msgid "KeyIsDown" msgstr "" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 +#: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/diversion_rules.py:1432 msgid "Active" msgstr "" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2218 -#: lib/solaar/ui/diversion_rules.py:2270 lib/solaar/ui/diversion_rules.py:2323 +#: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1390 +#: lib/solaar/ui/diversion_rules.py:1441 lib/solaar/ui/diversion_rules.py:1487 msgid "Device" msgstr "" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +#: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1467 msgid "Host" msgstr "" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 +#: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1506 msgid "Setting" msgstr "" -#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 -#: lib/solaar/ui/diversion_rules.py:1526 +#: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/rule_conditions.py:333 +#: lib/solaar/ui/rule_conditions.py:382 msgid "Test" msgstr "" -#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 +#: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:507 msgid "Test bytes" msgstr "" -#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 +#: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:608 msgid "Mouse Gesture" msgstr "" -#: lib/solaar/ui/diversion_rules.py:532 +#: lib/solaar/ui/diversion_rules.py:405 msgid "Action" msgstr "" -#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 +#: lib/solaar/ui/diversion_rules.py:407 lib/solaar/ui/rule_actions.py:138 msgid "Key press" msgstr "" -#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 +#: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:191 msgid "Mouse scroll" msgstr "" -#: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 +#: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:256 msgid "Mouse click" msgstr "" -#: lib/solaar/ui/diversion_rules.py:537 +#: lib/solaar/ui/diversion_rules.py:410 msgid "Set" msgstr "" -#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 +#: lib/solaar/ui/diversion_rules.py:411 lib/solaar/ui/rule_actions.py:328 msgid "Execute" msgstr "" -#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 +#: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/diversion_rules.py:1189 msgid "Later" msgstr "" -#: lib/solaar/ui/diversion_rules.py:568 +#: lib/solaar/ui/diversion_rules.py:441 msgid "Insert new rule" msgstr "" -#: lib/solaar/ui/diversion_rules.py:588 lib/solaar/ui/diversion_rules.py:1686 -#: lib/solaar/ui/diversion_rules.py:1790 lib/solaar/ui/diversion_rules.py:1989 +#: lib/solaar/ui/diversion_rules.py:461 lib/solaar/ui/rule_actions.py:82 +#: lib/solaar/ui/rule_actions.py:287 lib/solaar/ui/rule_conditions.py:556 msgid "Delete" msgstr "" -#: lib/solaar/ui/diversion_rules.py:610 +#: lib/solaar/ui/diversion_rules.py:483 msgid "Negate" msgstr "" -#: lib/solaar/ui/diversion_rules.py:634 +#: lib/solaar/ui/diversion_rules.py:507 msgid "Wrap with" msgstr "" -#: lib/solaar/ui/diversion_rules.py:656 +#: lib/solaar/ui/diversion_rules.py:537 msgid "Cut" msgstr "" -#: lib/solaar/ui/diversion_rules.py:671 +#: lib/solaar/ui/diversion_rules.py:553 msgid "Paste" msgstr "" -#: lib/solaar/ui/diversion_rules.py:683 +#: lib/solaar/ui/diversion_rules.py:559 msgid "Copy" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1063 +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Solaar Rule Editor" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:668 +msgid "Save changes" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:673 +msgid "Discard changes" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1104 msgid "This editor does not support the selected rule component yet." msgstr "" -#: lib/solaar/ui/diversion_rules.py:1138 -msgid "Number of seconds to delay." +#: lib/solaar/ui/diversion_rules.py:1169 +msgid "Number of seconds to delay. Delay between 0 and 1 is done with " + "higher precision." msgstr "" -#: lib/solaar/ui/diversion_rules.py:1177 +#: lib/solaar/ui/diversion_rules.py:1207 msgid "Not" msgstr "" -#: lib/solaar/ui/diversion_rules.py:1187 -msgid "X11 active process. For use in X11 only." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1218 -msgid "X11 mouse process. For use in X11 only." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1235 -msgid "MouseProcess" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1260 -msgid "Feature name of notification triggering rule processing." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1308 -msgid "Report number of notification triggering rule processing." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1342 -msgid "Active keyboard modifiers. Not always available in Wayland." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1383 -msgid "Diverted key or button depressed or released.\n" - "Use the Key/Button Diversion and Divert G Keys settings to divert " - "keys and buttons." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1392 -msgid "Key down" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1395 -msgid "Key up" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1436 -msgid "Diverted key or button is currently down.\n" - "Use the Key/Button Diversion and Divert G Keys settings to divert " - "keys and buttons." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1475 -msgid "Test condition on notification triggering rule processing." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1479 -msgid "Parameter" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1542 -msgid "begin (inclusive)" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1543 -msgid "end (exclusive)" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1552 -msgid "range" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1554 -msgid "minimum" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1555 -msgid "maximum" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1557 -#, python-format -msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1562 -msgid "mask" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1563 -#, python-format -msgid "bytes %(0)d to %(1)d, mask %(2)d" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1573 -msgid "Bit or range test on bytes in notification message triggering rule " - "processing." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1583 -msgid "type" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1666 -msgid "Mouse gesture with optional initiating button followed by zero or " - "more mouse movements." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1671 -msgid "Add movement" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1764 -msgid "Simulate a chorded key click or depress or release.\n" - "On Wayland requires write access to /dev/uinput." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1769 -msgid "Add key" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1772 -msgid "Click" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1775 -msgid "Depress" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1778 -msgid "Release" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1861 -msgid "Simulate a mouse scroll.\n" - "On Wayland requires write access to /dev/uinput." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1918 -msgid "Simulate a mouse click.\n" - "On Wayland requires write access to /dev/uinput." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1921 -msgid "Button" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1922 -msgid "Count and Action" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1972 -msgid "Execute a command with arguments." -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1975 -msgid "Add argument" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:2050 +#: lib/solaar/ui/diversion_rules.py:1238 msgid "Toggle" msgstr "" -#: lib/solaar/ui/diversion_rules.py:2050 +#: lib/solaar/ui/diversion_rules.py:1239 msgid "True" msgstr "" -#: lib/solaar/ui/diversion_rules.py:2051 +#: lib/solaar/ui/diversion_rules.py:1240 msgid "False" msgstr "" -#: lib/solaar/ui/diversion_rules.py:2065 +#: lib/solaar/ui/diversion_rules.py:1253 msgid "Unsupported setting" msgstr "" -#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2242 -#: lib/solaar/ui/diversion_rules.py:2328 lib/solaar/ui/diversion_rules.py:2570 -#: lib/solaar/ui/diversion_rules.py:2588 +#: lib/solaar/ui/diversion_rules.py:1396 lib/solaar/ui/diversion_rules.py:1415 +#: lib/solaar/ui/diversion_rules.py:1493 lib/solaar/ui/diversion_rules.py:1748 +#: lib/solaar/ui/diversion_rules.py:1766 msgid "Originating device" msgstr "" -#: lib/solaar/ui/diversion_rules.py:2256 +#: lib/solaar/ui/diversion_rules.py:1428 msgid "Device is active and its settings can be changed." msgstr "" -#: lib/solaar/ui/diversion_rules.py:2266 +#: lib/solaar/ui/diversion_rules.py:1437 msgid "Device that originated the current notification." msgstr "" -#: lib/solaar/ui/diversion_rules.py:2280 +#: lib/solaar/ui/diversion_rules.py:1450 msgid "Name of host computer." msgstr "" -#: lib/solaar/ui/diversion_rules.py:2347 +#: lib/solaar/ui/diversion_rules.py:1520 msgid "Value" msgstr "" -#: lib/solaar/ui/diversion_rules.py:2355 +#: lib/solaar/ui/diversion_rules.py:1529 msgid "Item" msgstr "" -#: lib/solaar/ui/diversion_rules.py:2630 +#: lib/solaar/ui/diversion_rules.py:1808 msgid "Change setting on device" msgstr "" -#: lib/solaar/ui/diversion_rules.py:2647 +#: lib/solaar/ui/diversion_rules.py:1824 msgid "Setting on device" msgstr "" -#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:320 -#: lib/solaar/ui/tray.py:325 lib/solaar/ui/window.py:739 -msgid "offline" -msgstr "" - -#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 -#: lib/solaar/ui/pair_window.py:288 +#: lib/solaar/ui/pair_window.py:44 lib/solaar/ui/pair_window.py:167 #, python-format msgid "%(receiver_name)s: pair new device" msgstr "" -#: lib/solaar/ui/pair_window.py:123 -#, python-format -msgid "Enter passcode on %(name)s." -msgstr "" - -#: lib/solaar/ui/pair_window.py:126 -#, python-format -msgid "Type %(passcode)s and then press the enter key." -msgstr "" - -#: lib/solaar/ui/pair_window.py:129 -msgid "left" -msgstr "" - -#: lib/solaar/ui/pair_window.py:129 -msgid "right" -msgstr "" - -#: lib/solaar/ui/pair_window.py:131 -#, python-format -msgid "Press %(code)s\n" - "and then press left and right buttons simultaneously." -msgstr "" - -#: lib/solaar/ui/pair_window.py:188 -msgid "Pairing failed" -msgstr "" - -#: lib/solaar/ui/pair_window.py:190 -msgid "Make sure your device is within range, and has a decent battery " - "charge." -msgstr "" - -#: lib/solaar/ui/pair_window.py:192 -msgid "A new device was detected, but it is not compatible with this " - "receiver." -msgstr "" - -#: lib/solaar/ui/pair_window.py:194 -msgid "More paired devices than receiver can support." -msgstr "" - -#: lib/solaar/ui/pair_window.py:196 -msgid "No further details are available about the error." -msgstr "" - -#: lib/solaar/ui/pair_window.py:210 -msgid "Found a new device:" -msgstr "" - -#: lib/solaar/ui/pair_window.py:235 -msgid "The wireless link is not encrypted" -msgstr "" - -#: lib/solaar/ui/pair_window.py:264 -msgid "Unifying receivers are only compatible with Unifying devices." -msgstr "" - -#: lib/solaar/ui/pair_window.py:266 +#: lib/solaar/ui/pair_window.py:46 msgid "Bolt receivers are only compatible with Bolt devices." msgstr "" -#: lib/solaar/ui/pair_window.py:268 -msgid "Other receivers are only compatible with a few devices." -msgstr "" - -#: lib/solaar/ui/pair_window.py:270 -msgid "The device must not be paired with a nearby powered-on receiver." -msgstr "" - -#: lib/solaar/ui/pair_window.py:274 +#: lib/solaar/ui/pair_window.py:48 msgid "Press a pairing button or key until the pairing light flashes " "quickly." msgstr "" -#: lib/solaar/ui/pair_window.py:276 -msgid "You may have to first turn the device off and on again." +#: lib/solaar/ui/pair_window.py:51 +msgid "Unifying receivers are only compatible with Unifying devices." msgstr "" -#: lib/solaar/ui/pair_window.py:278 -msgid "Turn on the device you want to pair." +#: lib/solaar/ui/pair_window.py:53 +msgid "Other receivers are only compatible with a few devices." msgstr "" -#: lib/solaar/ui/pair_window.py:280 +#: lib/solaar/ui/pair_window.py:55 +msgid "For most devices, turn on the device you want to pair." +msgstr "" + +#: lib/solaar/ui/pair_window.py:56 msgid "If the device is already turned on, turn it off and on again." msgstr "" -#: lib/solaar/ui/pair_window.py:283 +#: lib/solaar/ui/pair_window.py:58 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" + +#: lib/solaar/ui/pair_window.py:61 +msgid "For devices with multiple channels, press, hold, and release the " + "button for the channel you wish to pair\n" + "or use the channel switch button to select a channel and then press, " + "hold, and release the channel switch button." +msgstr "" + +#: lib/solaar/ui/pair_window.py:68 +msgid "The channel indicator light should be blinking rapidly." +msgstr "" + +#: lib/solaar/ui/pair_window.py:72 #, python-format msgid "\n" "\n" @@ -1597,189 +1639,394 @@ msgid_plural "\n" msgstr[0] "" msgstr[1] "" -#: lib/solaar/ui/pair_window.py:286 +#: lib/solaar/ui/pair_window.py:78 msgid "\n" "Cancelling at this point will not use up a pairing." msgstr "" -#: lib/solaar/ui/tray.py:58 +#: lib/solaar/ui/pair_window.py:168 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "" + +#: lib/solaar/ui/pair_window.py:171 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "" + +#: lib/solaar/ui/pair_window.py:176 +msgid "left" +msgstr "" + +#: lib/solaar/ui/pair_window.py:176 +msgid "right" +msgstr "" + +#: lib/solaar/ui/pair_window.py:178 +#, python-format +msgid "Press %(code)s\n" + "and then press left and right buttons simultaneously." +msgstr "" + +#: lib/solaar/ui/pair_window.py:221 +msgid "The wireless link is not encrypted" +msgstr "" + +#: lib/solaar/ui/pair_window.py:226 +msgid "Found a new device:" +msgstr "" + +#: lib/solaar/ui/pair_window.py:247 +msgid "Pairing failed" +msgstr "" + +#: lib/solaar/ui/pair_window.py:249 +msgid "Make sure your device is within range, and has a decent battery " + "charge." +msgstr "" + +#: lib/solaar/ui/pair_window.py:251 +msgid "A new device was detected, but it is not compatible with this " + "receiver." +msgstr "" + +#: lib/solaar/ui/pair_window.py:253 +msgid "More paired devices than receiver can support." +msgstr "" + +#: lib/solaar/ui/pair_window.py:255 +msgid "No further details are available about the error." +msgstr "" + +#: lib/solaar/ui/rule_actions.py:54 +msgid "Simulate a chorded key click or depress or release.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/rule_actions.py:60 +msgid "Add key" +msgstr "" + +#: lib/solaar/ui/rule_actions.py:63 +msgid "Click" +msgstr "" + +#: lib/solaar/ui/rule_actions.py:66 +msgid "Depress" +msgstr "" + +#: lib/solaar/ui/rule_actions.py:69 +msgid "Release" +msgstr "" + +#: lib/solaar/ui/rule_actions.py:153 +msgid "Simulate a mouse scroll.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/rule_actions.py:211 +msgid "Simulate a mouse click.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" + +#: lib/solaar/ui/rule_actions.py:216 +msgid "Button" +msgstr "" + +#: lib/solaar/ui/rule_actions.py:218 +msgid "Action (and Count, if click)" +msgstr "" + +#: lib/solaar/ui/rule_actions.py:269 +msgid "Execute a command with arguments." +msgstr "" + +#: lib/solaar/ui/rule_actions.py:273 +msgid "Add argument" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:52 +msgid "X11 active process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:82 +msgid "X11 mouse process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:99 +msgid "MouseProcess" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:123 +msgid "Feature name of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:169 +msgid "Report number of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:201 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:241 +msgid "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:250 +msgid "Key down" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:253 +msgid "Key up" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:293 +msgid "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:331 +msgid "Test condition on notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:335 +msgid "Parameter" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:408 +msgid "begin (inclusive)" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:409 +msgid "end (exclusive)" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:417 +msgid "range" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:420 +msgid "minimum" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:421 +msgid "maximum" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:423 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:426 lib/solaar/ui/rule_conditions.py:427 +msgid "mask" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:428 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:437 +msgid "Bit or range test on bytes in notification message triggering rule " + "processing." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:447 +msgid "type" +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:535 +msgid "Mouse gesture with optional initiating button followed by zero or " + "more mouse movements." +msgstr "" + +#: lib/solaar/ui/rule_conditions.py:541 +msgid "Add movement" +msgstr "" + +#: lib/solaar/ui/tray.py:55 msgid "No supported device found" msgstr "" -#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#: lib/solaar/ui/tray.py:60 lib/solaar/ui/window.py:328 #, python-format msgid "About %s" msgstr "" -#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 +#: lib/solaar/ui/tray.py:61 lib/solaar/ui/window.py:326 #, python-format msgid "Quit %s" msgstr "" -#: lib/solaar/ui/tray.py:299 lib/solaar/ui/tray.py:307 +#: lib/solaar/ui/tray.py:273 lib/solaar/ui/tray.py:281 msgid "no receiver" msgstr "" -#: lib/solaar/ui/tray.py:323 +#: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:299 +#: lib/solaar/ui/window.py:728 +msgid "offline" +msgstr "" + +#: lib/solaar/ui/tray.py:297 msgid "no status" msgstr "" -#: lib/solaar/ui/window.py:96 +#: lib/solaar/ui/window.py:110 msgid "Scanning" msgstr "" -#: lib/solaar/ui/window.py:129 +#: lib/solaar/ui/window.py:141 msgid "Battery" msgstr "" -#: lib/solaar/ui/window.py:132 +#: lib/solaar/ui/window.py:144 msgid "Wireless Link" msgstr "" -#: lib/solaar/ui/window.py:136 +#: lib/solaar/ui/window.py:148 msgid "Lighting" msgstr "" -#: lib/solaar/ui/window.py:170 +#: lib/solaar/ui/window.py:182 msgid "Show Technical Details" msgstr "" -#: lib/solaar/ui/window.py:186 +#: lib/solaar/ui/window.py:198 msgid "Pair new device" msgstr "" -#: lib/solaar/ui/window.py:205 +#: lib/solaar/ui/window.py:216 msgid "Select a device" msgstr "" -#: lib/solaar/ui/window.py:322 +#: lib/solaar/ui/window.py:331 msgid "Rule Editor" msgstr "" -#: lib/solaar/ui/window.py:533 +#: lib/solaar/ui/window.py:522 msgid "Path" msgstr "" -#: lib/solaar/ui/window.py:536 +#: lib/solaar/ui/window.py:524 msgid "USB ID" msgstr "" -#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 -#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 +#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 msgid "Serial" msgstr "" -#: lib/solaar/ui/window.py:545 +#: lib/solaar/ui/window.py:533 msgid "Index" msgstr "" -#: lib/solaar/ui/window.py:547 +#: lib/solaar/ui/window.py:535 msgid "Wireless PID" msgstr "" -#: lib/solaar/ui/window.py:549 +#: lib/solaar/ui/window.py:537 msgid "Product ID" msgstr "" -#: lib/solaar/ui/window.py:551 +#: lib/solaar/ui/window.py:539 msgid "Protocol" msgstr "" -#: lib/solaar/ui/window.py:551 +#: lib/solaar/ui/window.py:539 msgid "Unknown" msgstr "" -#: lib/solaar/ui/window.py:554 -#, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "" - -#: lib/solaar/ui/window.py:554 +#: lib/solaar/ui/window.py:541 msgid "Polling rate" msgstr "" -#: lib/solaar/ui/window.py:565 +#: lib/solaar/ui/window.py:548 msgid "Unit ID" msgstr "" -#: lib/solaar/ui/window.py:576 +#: lib/solaar/ui/window.py:559 msgid "none" msgstr "" -#: lib/solaar/ui/window.py:577 +#: lib/solaar/ui/window.py:560 msgid "Notifications" msgstr "" -#: lib/solaar/ui/window.py:621 +#: lib/solaar/ui/window.py:604 msgid "No device paired." msgstr "" -#: lib/solaar/ui/window.py:628 +#: lib/solaar/ui/window.py:613 #, python-format msgid "Up to %(max_count)s device can be paired to this receiver." msgid_plural "Up to %(max_count)s devices can be paired to this receiver." msgstr[0] "" msgstr[1] "" -#: lib/solaar/ui/window.py:634 +#: lib/solaar/ui/window.py:620 msgid "Only one device can be paired to this receiver." msgstr "" -#: lib/solaar/ui/window.py:638 +#: lib/solaar/ui/window.py:624 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." msgstr[0] "" msgstr[1] "" -#: lib/solaar/ui/window.py:692 +#: lib/solaar/ui/window.py:681 msgid "Battery Voltage" msgstr "" -#: lib/solaar/ui/window.py:694 +#: lib/solaar/ui/window.py:683 msgid "Voltage reported by battery" msgstr "" -#: lib/solaar/ui/window.py:696 +#: lib/solaar/ui/window.py:685 msgid "Battery Level" msgstr "" -#: lib/solaar/ui/window.py:698 +#: lib/solaar/ui/window.py:687 msgid "Approximate level reported by battery" msgstr "" -#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 +#: lib/solaar/ui/window.py:694 lib/solaar/ui/window.py:696 msgid "next reported " msgstr "" -#: lib/solaar/ui/window.py:708 +#: lib/solaar/ui/window.py:697 msgid " and next level to be reported." msgstr "" -#: lib/solaar/ui/window.py:713 +#: lib/solaar/ui/window.py:702 msgid "last known" msgstr "" -#: lib/solaar/ui/window.py:724 +#: lib/solaar/ui/window.py:713 msgid "encrypted" msgstr "" -#: lib/solaar/ui/window.py:726 +#: lib/solaar/ui/window.py:715 msgid "The wireless link between this device and its receiver is encrypted." msgstr "" -#: lib/solaar/ui/window.py:728 +#: lib/solaar/ui/window.py:717 msgid "not encrypted" msgstr "" -#: lib/solaar/ui/window.py:732 +#: lib/solaar/ui/window.py:721 msgid "The wireless link between this device and its receiver is not " "encrypted.\n" "This is a security issue for pointing devices, and a major security " "issue for text-input devices." msgstr "" -#: lib/solaar/ui/window.py:748 +#: lib/solaar/ui/window.py:737 #, python-format msgid "%(light_level)d lux" msgstr "" diff --git a/po/sr.po b/po/sr.po index 188c5962..35001700 100644 --- a/po/sr.po +++ b/po/sr.po @@ -3,1716 +3,1889 @@ # This file is distributed under the same license as the solaar package. # Automatically generated, 2022. # -msgid "" -msgstr "" -"Project-Id-Version: solaar 1.1.4\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-08-09 17:35+0200\n" -"PO-Revision-Date: 2022-08-09 17:35+0200\n" -"Last-Translator: Automatically generated\n" -"Language-Team: github.com/renatoka\n" -"Language: rs\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=n != 1;\n" +msgid "" +msgstr "Project-Id-Version: solaar 1.1.4\n" + "Report-Msgid-Bugs-To: \n" + "POT-Creation-Date: 2023-12-28 17:40+0100\n" + "PO-Revision-Date: 2022-08-09 17:35+0200\n" + "Last-Translator: Automatically generated\n" + "Language-Team: github.com/renatoka\n" + "Language: sr\n" + "MIME-Version: 1.0\n" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=2; plural=n != 1;\n" -#: lib/logitech_receiver/base_usb.py:47 -msgid "Bolt Receiver" -msgstr "Bolt prijemnik" +#: lib/logitech_receiver/base_usb.py:46 +msgid "Bolt Receiver" +msgstr "Bolt prijemnik" -#: lib/logitech_receiver/base_usb.py:58 -msgid "Unifying Receiver" -msgstr "Unifying prijemnik" +#: lib/logitech_receiver/base_usb.py:57 +msgid "Unifying Receiver" +msgstr "Unifying prijemnik" #: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 #: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 #: lib/logitech_receiver/base_usb.py:114 -msgid "Nano Receiver" -msgstr "Nano prijemnik" +msgid "Nano Receiver" +msgstr "Nano prijemnik" -#: lib/logitech_receiver/base_usb.py:123 -msgid "Lightspeed Receiver" -msgstr "Lightspeed prijemnik" +#: lib/logitech_receiver/base_usb.py:124 +msgid "Lightspeed Receiver" +msgstr "Lightspeed prijemnik" -#: lib/logitech_receiver/base_usb.py:131 -msgid "EX100 Receiver 27 Mhz" -msgstr "EX100 prijemnik 27 MHZ" +#: lib/logitech_receiver/base_usb.py:133 +msgid "EX100 Receiver 27 Mhz" +msgstr "EX100 prijemnik 27 MHZ" #: lib/logitech_receiver/i18n.py:30 -msgid "empty" -msgstr "prazna" +msgid "empty" +msgstr "prazna" #: lib/logitech_receiver/i18n.py:31 -msgid "critical" -msgstr "kritično" +msgid "critical" +msgstr "kritično" #: lib/logitech_receiver/i18n.py:32 -msgid "low" -msgstr "slabo" +msgid "low" +msgstr "slabo" #: lib/logitech_receiver/i18n.py:33 -msgid "average" -msgstr "prosečna" +msgid "average" +msgstr "prosečna" #: lib/logitech_receiver/i18n.py:34 -msgid "good" -msgstr "dobra" +msgid "good" +msgstr "dobra" #: lib/logitech_receiver/i18n.py:35 -msgid "full" -msgstr "puna" +msgid "full" +msgstr "puna" #: lib/logitech_receiver/i18n.py:38 -msgid "discharging" -msgstr "pražnjenje" +msgid "discharging" +msgstr "pražnjenje" #: lib/logitech_receiver/i18n.py:39 -msgid "recharging" -msgstr "punjenje" +msgid "recharging" +msgstr "punjenje" -#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:718 -msgid "charging" -msgstr "punjenje" +#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 +msgid "charging" +msgstr "punjenje" #: lib/logitech_receiver/i18n.py:41 -msgid "not charging" -msgstr "ne puni se" +msgid "not charging" +msgstr "ne puni se" #: lib/logitech_receiver/i18n.py:42 -msgid "almost full" -msgstr "skoro puna" +msgid "almost full" +msgstr "skoro puna" #: lib/logitech_receiver/i18n.py:43 -msgid "charged" -msgstr "napunjeno" +msgid "charged" +msgstr "napunjeno" #: lib/logitech_receiver/i18n.py:44 -msgid "slow recharge" -msgstr "sporo punjenje" +msgid "slow recharge" +msgstr "sporo punjenje" #: lib/logitech_receiver/i18n.py:45 -msgid "invalid battery" -msgstr "neispravna baterija" +msgid "invalid battery" +msgstr "neispravna baterija" #: lib/logitech_receiver/i18n.py:46 -msgid "thermal error" -msgstr "toplotna greška" +msgid "thermal error" +msgstr "toplotna greška" #: lib/logitech_receiver/i18n.py:47 -msgid "error" -msgstr "greška" +msgid "error" +msgstr "greška" #: lib/logitech_receiver/i18n.py:48 -msgid "standard" -msgstr "standardno" +msgid "standard" +msgstr "standardno" #: lib/logitech_receiver/i18n.py:49 -msgid "fast" -msgstr "brzo" +msgid "fast" +msgstr "brzo" #: lib/logitech_receiver/i18n.py:50 -msgid "slow" -msgstr "sporo" +msgid "slow" +msgstr "sporo" #: lib/logitech_receiver/i18n.py:53 -msgid "device timeout" -msgstr "istek čekanja uređaja" +msgid "device timeout" +msgstr "istek čekanja uređaja" #: lib/logitech_receiver/i18n.py:54 -msgid "device not supported" -msgstr "uređaj nije podržan" +msgid "device not supported" +msgstr "uređaj nije podržan" #: lib/logitech_receiver/i18n.py:55 -msgid "too many devices" -msgstr "previše uređaja" +msgid "too many devices" +msgstr "previše uređaja" #: lib/logitech_receiver/i18n.py:56 -msgid "sequence timeout" -msgstr "istek vremenske sekvence" +msgid "sequence timeout" +msgstr "istek vremenske sekvence" -#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:577 -msgid "Firmware" -msgstr "Firmver" +#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 +msgid "Firmware" +msgstr "Firmver" #: lib/logitech_receiver/i18n.py:60 -msgid "Bootloader" -msgstr "Bootloader" +msgid "Bootloader" +msgstr "Bootloader" #: lib/logitech_receiver/i18n.py:61 -msgid "Hardware" -msgstr "Hardver" +msgid "Hardware" +msgstr "Hardver" #: lib/logitech_receiver/i18n.py:62 -msgid "Other" -msgstr "Ostalo" +msgid "Other" +msgstr "Ostalo" #: lib/logitech_receiver/i18n.py:65 -msgid "Left Button" -msgstr "Levo dugme" +msgid "Left Button" +msgstr "Levo dugme" #: lib/logitech_receiver/i18n.py:66 -msgid "Right Button" -msgstr "Desno dugme" +msgid "Right Button" +msgstr "Desno dugme" #: lib/logitech_receiver/i18n.py:67 -msgid "Middle Button" -msgstr "Srednje dugme" +msgid "Middle Button" +msgstr "Srednje dugme" #: lib/logitech_receiver/i18n.py:68 -msgid "Back Button" -msgstr "Dugme za unazad" +msgid "Back Button" +msgstr "Dugme za unazad" #: lib/logitech_receiver/i18n.py:69 -msgid "Forward Button" -msgstr "Dugme za unapred" +msgid "Forward Button" +msgstr "Dugme za unapred" #: lib/logitech_receiver/i18n.py:70 -msgid "Mouse Gesture Button" -msgstr "Dugme za pokrete miša" +msgid "Mouse Gesture Button" +msgstr "Dugme za pokrete miša" #: lib/logitech_receiver/i18n.py:71 -msgid "Smart Shift" -msgstr "Pametan pomak" +msgid "Smart Shift" +msgstr "Pametan pomak" #: lib/logitech_receiver/i18n.py:72 -msgid "DPI Switch" -msgstr "DPI svitch" +msgid "DPI Switch" +msgstr "DPI svitch" #: lib/logitech_receiver/i18n.py:73 -msgid "Left Tilt" -msgstr "Levi nagib" +msgid "Left Tilt" +msgstr "Levi nagib" #: lib/logitech_receiver/i18n.py:74 -msgid "Right Tilt" -msgstr "Desni nagib" +msgid "Right Tilt" +msgstr "Desni nagib" #: lib/logitech_receiver/i18n.py:75 -msgid "Left Click" -msgstr "Levi lik" +msgid "Left Click" +msgstr "Levi lik" #: lib/logitech_receiver/i18n.py:76 -msgid "Right Click" -msgstr "Desni klik" +msgid "Right Click" +msgstr "Desni klik" #: lib/logitech_receiver/i18n.py:77 -msgid "Mouse Middle Button" -msgstr "Srednje dugme miša" +msgid "Mouse Middle Button" +msgstr "Srednje dugme miša" #: lib/logitech_receiver/i18n.py:78 -msgid "Mouse Back Button" -msgstr "Dugme za povratak na mišu" +msgid "Mouse Back Button" +msgstr "Dugme za povratak na mišu" #: lib/logitech_receiver/i18n.py:79 -msgid "Mouse Forward Button" -msgstr "Dugme za unapred na mišu" +msgid "Mouse Forward Button" +msgstr "Dugme za unapred na mišu" #: lib/logitech_receiver/i18n.py:80 -msgid "Gesture Button Navigation" -msgstr "Navigacija pomoću dugmadi" +msgid "Gesture Button Navigation" +msgstr "Navigacija pomoću dugmadi" #: lib/logitech_receiver/i18n.py:81 -msgid "Mouse Scroll Left Button" -msgstr "Levo dugme za pomeranje miša" +msgid "Mouse Scroll Left Button" +msgstr "Levo dugme za pomeranje miša" #: lib/logitech_receiver/i18n.py:82 -msgid "Mouse Scroll Right Button" -msgstr "Desno dugme za pomeranje miša" +msgid "Mouse Scroll Right Button" +msgstr "Desno dugme za pomeranje miša" #: lib/logitech_receiver/i18n.py:85 -msgid "pressed" -msgstr "pritisnuto" +msgid "pressed" +msgstr "pritisnuto" #: lib/logitech_receiver/i18n.py:86 -msgid "released" -msgstr "otpušteno" +msgid "released" +msgstr "otpušteno" -#: lib/logitech_receiver/notifications.py:74 -#: lib/logitech_receiver/notifications.py:125 -msgid "pairing lock is closed" -msgstr "uparivanje je zatvoreno" +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 +msgid "pairing lock is closed" +msgstr "uparivanje je zatvoreno" -#: lib/logitech_receiver/notifications.py:74 -#: lib/logitech_receiver/notifications.py:125 -msgid "pairing lock is open" -msgstr "uparivanje je otvoreno" +#: lib/logitech_receiver/notifications.py:75 +#: lib/logitech_receiver/notifications.py:126 +msgid "pairing lock is open" +msgstr "uparivanje je otvoreno" -#: lib/logitech_receiver/notifications.py:91 -msgid "discovery lock is closed" -msgstr "otkrivanje je otvoreno" +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is closed" +msgstr "otkrivanje je otvoreno" -#: lib/logitech_receiver/notifications.py:91 -msgid "discovery lock is open" -msgstr "otkrivanje je zatvoreno" +#: lib/logitech_receiver/notifications.py:92 +msgid "discovery lock is open" +msgstr "otkrivanje je zatvoreno" -#: lib/logitech_receiver/notifications.py:223 lib/solaar/ui/notify.py:120 -msgid "connected" -msgstr "povezano" +#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 +msgid "connected" +msgstr "povezano" -#: lib/logitech_receiver/notifications.py:223 -msgid "disconnected" -msgstr "odspojeno" +#: lib/logitech_receiver/notifications.py:224 +msgid "disconnected" +msgstr "odspojeno" -#: lib/logitech_receiver/notifications.py:261 lib/solaar/ui/notify.py:118 -msgid "unpaired" -msgstr "nije upareno" +#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 +msgid "unpaired" +msgstr "nije upareno" -#: lib/logitech_receiver/notifications.py:303 -msgid "powered on" -msgstr "uključen" +#: lib/logitech_receiver/notifications.py:304 +msgid "powered on" +msgstr "uključen" -#: lib/logitech_receiver/settings.py:734 -msgid "register" -msgstr "registriraj se" +#: lib/logitech_receiver/settings.py:750 +msgid "register" +msgstr "registriraj se" -#: lib/logitech_receiver/settings.py:748 lib/logitech_receiver/settings.py:775 -msgid "feature" -msgstr "značajka" +#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 +msgid "feature" +msgstr "značajka" #: lib/logitech_receiver/settings_templates.py:139 -msgid "Swap Fx function" -msgstr "Zameni Fx funkciju" +msgid "Swap Fx function" +msgstr "Zameni Fx funkciju" #: lib/logitech_receiver/settings_templates.py:140 -msgid "" -"When set, the F1..F12 keys will activate their special function,\n" -"and you must hold the FN key to activate their standard function." -msgstr "" -"Kada su podešeni, tasteri F1..F12 će aktivirati svoju posebnu funkciju,\n" -"i morate držati FN taster da biste aktivirali njihovu standardnu funkciju." +msgid "When set, the F1..F12 keys will activate their special function,\n" + "and you must hold the FN key to activate their standard function." +msgstr "Kada su podešeni, tasteri F1..F12 će aktivirati svoju posebnu " + "funkciju,\n" + "i morate držati FN taster da biste aktivirali njihovu standardnu " + "funkciju." #: lib/logitech_receiver/settings_templates.py:142 -msgid "" -"When unset, the F1..F12 keys will activate their standard function,\n" -"and you must hold the FN key to activate their special function." -msgstr "Kada nisu omogućeni, tasteri F1..F12 će aktivirati svoju standardnu funkciju, \n" -"i morate držati FN taster da biste aktivirali njihovu posebnu funkciju." +msgid "When unset, the F1..F12 keys will activate their standard function,\n" + "and you must hold the FN key to activate their special function." +msgstr "Kada nisu omogućeni, tasteri F1..F12 će aktivirati svoju standardnu " + "funkciju, \n" + "i morate držati FN taster da biste aktivirali njihovu posebnu " + "funkciju." #: lib/logitech_receiver/settings_templates.py:149 -msgid "Hand Detection" -msgstr "Detekcija ruku" +msgid "Hand Detection" +msgstr "Detekcija ruku" #: lib/logitech_receiver/settings_templates.py:150 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "Uključite osvetljenje kada ruke lebde preko tastature." +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Uključite osvetljenje kada ruke lebde preko tastature." #: lib/logitech_receiver/settings_templates.py:157 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "Točak za pomeranje, glatko pomeranje" +msgid "Scroll Wheel Smooth Scrolling" +msgstr "Točak za pomeranje, glatko pomeranje" #: lib/logitech_receiver/settings_templates.py:158 #: lib/logitech_receiver/settings_templates.py:239 #: lib/logitech_receiver/settings_templates.py:267 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "Režim visoke osetljivosti za vertikalno pomeranje pomoću točkića." +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Režim visoke osetljivosti za vertikalno pomeranje pomoću točkića." #: lib/logitech_receiver/settings_templates.py:165 -msgid "Side Scrolling" -msgstr "Bočno pomeranje" +msgid "Side Scrolling" +msgstr "Bočno pomeranje" #: lib/logitech_receiver/settings_templates.py:167 -msgid "" -"When disabled, pushing the wheel sideways sends custom button events\n" -"instead of the standard side-scrolling events." -msgstr "Kada je onemogućeno, pomeranje točkića u stranu šalje događaje prilagođenog dugmeta \n" -"umesto standardnih događaja bočnog pomeranja." +msgid "When disabled, pushing the wheel sideways sends custom button " + "events\n" + "instead of the standard side-scrolling events." +msgstr "Kada je onemogućeno, pomeranje točkića u stranu šalje događaje " + "prilagođenog dugmeta \n" + "umesto standardnih događaja bočnog pomeranja." #: lib/logitech_receiver/settings_templates.py:177 -msgid "Sensitivity (DPI - older mice)" -msgstr "Osetljivost (DPI - stariji miševi)" +msgid "Sensitivity (DPI - older mice)" +msgstr "Osetljivost (DPI - stariji miševi)" #: lib/logitech_receiver/settings_templates.py:178 -#: lib/logitech_receiver/settings_templates.py:687 -msgid "Mouse movement sensitivity" -msgstr "Osetljivost pokreta miša" +#: lib/logitech_receiver/settings_templates.py:712 +msgid "Mouse movement sensitivity" +msgstr "Osetljivost pokreta miša" #: lib/logitech_receiver/settings_templates.py:208 #: lib/logitech_receiver/settings_templates.py:218 #: lib/logitech_receiver/settings_templates.py:225 -msgid "Backlight" -msgstr "Pozadinsko osvetljenje" +msgid "Backlight" +msgstr "Pozadinsko osvetljenje" #: lib/logitech_receiver/settings_templates.py:209 #: lib/logitech_receiver/settings_templates.py:226 -msgid "Set illumination time for keyboard." -msgstr "Podesite vreme osvetljenja za tastaturu." +msgid "Set illumination time for keyboard." +msgstr "Podesite vreme osvetljenja za tastaturu." #: lib/logitech_receiver/settings_templates.py:219 -msgid "Turn illumination on or off on keyboard." -msgstr "Uključite ili isključite osvetljenje na tastaturi." +msgid "Turn illumination on or off on keyboard." +msgstr "Uključite ili isključite osvetljenje na tastaturi." #: lib/logitech_receiver/settings_templates.py:237 -msgid "Scroll Wheel High Resolution" -msgstr "Visoka rezolucija točkića za pomeranje." +msgid "Scroll Wheel High Resolution" +msgstr "Visoka rezolucija točkića za pomeranje." #: lib/logitech_receiver/settings_templates.py:240 #: lib/logitech_receiver/settings_templates.py:268 -msgid "Set to ignore if scrolling is abnormally fast or slow" -msgstr "Podesite da ignorišete ako je pomeranje nenormalno brzo ili sporo" +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "Podesite da ignorišete ako je pomeranje nenormalno brzo ili sporo" #: lib/logitech_receiver/settings_templates.py:247 #: lib/logitech_receiver/settings_templates.py:277 -msgid "Scroll Wheel Diversion" -msgstr "Preusmeravanje točkića za pomeranje" +msgid "Scroll Wheel Diversion" +msgstr "Preusmeravanje točkića za pomeranje" #: lib/logitech_receiver/settings_templates.py:249 -msgid "" -"Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " -"Solaar rules but are otherwise ignored)." -msgstr "Neka točkić za pomeranje šalje LOWRES_WHEEL HID++ obaveštenja (koja pokreću" -"Solaar pravila, ali se inače ignorišu." +msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "Neka točkić za pomeranje šalje LOWRES_WHEEL HID++ obaveštenja (koja " + "pokrećuSolaar pravila, ali se inače ignorišu." #: lib/logitech_receiver/settings_templates.py:256 -msgid "Scroll Wheel Direction" -msgstr "Smer točkića za pomeranje" +msgid "Scroll Wheel Direction" +msgstr "Smer točkića za pomeranje" #: lib/logitech_receiver/settings_templates.py:257 -msgid "Invert direction for vertical scroll with wheel." -msgstr "Obrni smer za vertikalno pomeranje sa točkom." +msgid "Invert direction for vertical scroll with wheel." +msgstr "Obrni smer za vertikalno pomeranje sa točkom." #: lib/logitech_receiver/settings_templates.py:265 -msgid "Scroll Wheel Resolution" -msgstr "Rezolucija točkića za pomeranje" +msgid "Scroll Wheel Resolution" +msgstr "Rezolucija točkića za pomeranje" #: lib/logitech_receiver/settings_templates.py:279 -msgid "" -"Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " -"rules but are otherwise ignored)." -msgstr "Neka točkić za pomeranje šalje HIRES_WHEEL HID++ obaveštenja (koja pokreću" -"Solaar pravila, ali se inače ignorišu." +msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " + "trigger Solaar rules but are otherwise ignored)." +msgstr "Neka točkić za pomeranje šalje HIRES_WHEEL HID++ obaveštenja (koja " + "pokrećuSolaar pravila, ali se inače ignorišu." #: lib/logitech_receiver/settings_templates.py:288 -msgid "Sensitivity (Pointer Speed)" -msgstr "Osetljivost (brzina pokazivača)" +msgid "Sensitivity (Pointer Speed)" +msgstr "Osetljivost (brzina pokazivača)" #: lib/logitech_receiver/settings_templates.py:289 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "Množilac brzine za miš (256 je normalan množilac)." +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Množilac brzine za miš (256 je normalan množilac)." #: lib/logitech_receiver/settings_templates.py:299 -msgid "Thumb Wheel Diversion" -msgstr "Diverzija sa palcem" +msgid "Thumb Wheel Diversion" +msgstr "Diverzija sa palcem" #: lib/logitech_receiver/settings_templates.py:301 -msgid "" -"Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " -"rules but are otherwise ignored)." -msgstr "" -"Neka kotačić palca šalje THUMB_WHEEL HID++ obavijesti (što pokreće Solaar " -"pravila, u suprotnome ih zanemaruje)." +msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " + "Solaar rules but are otherwise ignored)." +msgstr "Neka kotačić palca šalje THUMB_WHEEL HID++ obavijesti (što pokreće " + "Solaar pravila, u suprotnome ih zanemaruje)." #: lib/logitech_receiver/settings_templates.py:310 -msgid "Thumb Wheel Direction" -msgstr "Preusmeravanje pomicanja kotačića palca" +msgid "Thumb Wheel Direction" +msgstr "Preusmeravanje pomicanja kotačića palca" #: lib/logitech_receiver/settings_templates.py:311 -msgid "Invert thumb wheel scroll direction." -msgstr "Obrnite smer pomeranja točkića" +msgid "Invert thumb wheel scroll direction." +msgstr "Obrnite smer pomeranja točkića" #: lib/logitech_receiver/settings_templates.py:319 -msgid "Onboard Profiles" -msgstr "Onboard profili" +msgid "Onboard Profiles" +msgstr "Onboard profili" #: lib/logitech_receiver/settings_templates.py:320 -msgid "" -"Enable onboard profiles, which often control report rate and keyboard " -"lighting" -msgstr "" -"Omogućite onboard profile, koji najčešće kontrolišu brzinom pozivanja i " -"osvetljenje tipkovnice" +msgid "Enable onboard profiles, which often control report rate and " + "keyboard lighting" +msgstr "Omogućite onboard profile, koji najčešće kontrolišu brzinom " + "pozivanja i osvetljenje tipkovnice" #: lib/logitech_receiver/settings_templates.py:330 -msgid "Polling Rate (ms)" -msgstr "Brzina pozivanja (ms)" +msgid "Polling Rate (ms)" +msgstr "Brzina pozivanja (ms)" #: lib/logitech_receiver/settings_templates.py:332 -msgid "Frequency of device polling, in milliseconds" -msgstr "Frekvencija brzine pozivanja uređaja, u milisekundama." +msgid "Frequency of device polling, in milliseconds" +msgstr "Frekvencija brzine pozivanja uređaja, u milisekundama." #: lib/logitech_receiver/settings_templates.py:333 -#: lib/logitech_receiver/settings_templates.py:1216 -#: lib/logitech_receiver/settings_templates.py:1244 -msgid "May need Onboard Profiles set to Disable to be effective." -msgstr "Možda će biti potrebni ugrađeni profili podešeni na 'Onemogućeno' da bi bili efikasni." +#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "Možda će biti potrebni ugrađeni profili podešeni na 'Onemogućeno' da " + "bi bili efikasni." -#: lib/logitech_receiver/settings_templates.py:363 -msgid "Divert crown events" -msgstr "Preusmerite crown događaje" +#: lib/logitech_receiver/settings_templates.py:365 +msgid "Divert crown events" +msgstr "Preusmerite crown događaje" -#: lib/logitech_receiver/settings_templates.py:364 -msgid "" -"Make crown send CROWN HID++ notifications (which trigger Solaar rules but " -"are otherwise ignored)." -msgstr "Neka crown šalje CROWN HID++ obaveštenja (koja pokreću Solaar pravila, ali" -"neka se inače ignorišu." +#: lib/logitech_receiver/settings_templates.py:366 +msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "Neka crown šalje CROWN HID++ obaveštenja (koja pokreću Solaar " + "pravila, alineka se inače ignorišu." -#: lib/logitech_receiver/settings_templates.py:372 -msgid "Crown smooth scroll" -msgstr "Crown glatko pomicanje" +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Crown smooth scroll" +msgstr "Crown glatko pomicanje" -#: lib/logitech_receiver/settings_templates.py:373 -msgid "Set crown smooth scroll" -msgstr "Podesi Crown glatko pomicanje" - -#: lib/logitech_receiver/settings_templates.py:381 -msgid "Divert G Keys" -msgstr "Preusmeri G tastere" +#: lib/logitech_receiver/settings_templates.py:375 +msgid "Set crown smooth scroll" +msgstr "Podesi Crown glatko pomicanje" #: lib/logitech_receiver/settings_templates.py:383 -msgid "" -"Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " -"are otherwise ignored)." -msgstr "Neka G tasteri šalju GKEY HID++ obaveštenja (koja pokreću Solaar pravila, ali" -"neka se inače ignorišu." +msgid "Divert G Keys" +msgstr "Preusmeri G tastere" -#: lib/logitech_receiver/settings_templates.py:384 -msgid "May also make M keys and MR key send HID++ notifications" -msgstr "Takođe može da učini da M i MR tasteri šalju HID++ obaveštenja" +#: lib/logitech_receiver/settings_templates.py:385 +msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " + "rules but are otherwise ignored)." +msgstr "Neka G tasteri šalju GKEY HID++ obaveštenja (koja pokreću Solaar " + "pravila, alineka se inače ignorišu." -#: lib/logitech_receiver/settings_templates.py:399 -msgid "Scroll Wheel Rachet" -msgstr "Ustavljanje pomicanja kotačića" +#: lib/logitech_receiver/settings_templates.py:386 +msgid "May also make M keys and MR key send HID++ notifications" +msgstr "Takođe može da učini da M i MR tasteri šalju HID++ obaveštenja" -#: lib/logitech_receiver/settings_templates.py:401 -msgid "" -"Automatically switch the mouse wheel between ratchet and freespin mode.\n" -"The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "Automatski prebacite točkić miša između režima ratchet i slobodnog načina. \n" -"Točak miša je uvek slobodan na 0, i uvek zaključan na 50" +#: lib/logitech_receiver/settings_templates.py:402 +msgid "Scroll Wheel Ratcheted" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:450 -msgid "Key/Button Actions" -msgstr "Radnje tastera" +#: lib/logitech_receiver/settings_templates.py:403 +msgid "Switch the mouse wheel between speed-controlled ratcheting and " + "always freespin." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:452 -msgid "Change the action for the key or button." -msgstr "Promenite radnju za taster ili dugme." +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Freespinning" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:452 -msgid "Overridden by diversion." -msgstr "Preusmjeravanje tipki." +#: lib/logitech_receiver/settings_templates.py:405 +msgid "Ratcheted" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:453 -msgid "" -"Changing important actions (such as for the left mouse button) can result in " -"an unusable system." -msgstr "Promena važnih radnji (kao što je levi taster miša) može dovesti do " -"neupotrebljivosti sistem." +#: lib/logitech_receiver/settings_templates.py:412 +msgid "Scroll Wheel Ratchet Speed" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:614 -msgid "Key/Button Diversion" -msgstr "Preusmeravanje tipki" +#: lib/logitech_receiver/settings_templates.py:414 +msgid "Use the mouse wheel speed to switch between ratcheted and " + "freespinning.\n" + "The mouse wheel is always ratcheted at 50." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:615 -msgid "" -"Make the key or button send HID++ notifications (Diverted) or initiate Mouse " -"Gestures or Sliding DPI" -msgstr "Neka taster ili dugme šalje HID++ obaveštenja (preusmereno) ili pokrenite miš " -"pokretom" +#: lib/logitech_receiver/settings_templates.py:463 +msgid "Key/Button Actions" +msgstr "Radnje tastera" -#: lib/logitech_receiver/settings_templates.py:618 -#: lib/logitech_receiver/settings_templates.py:619 -#: lib/logitech_receiver/settings_templates.py:620 -msgid "Diverted" -msgstr "Preusmereno" +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Change the action for the key or button." +msgstr "Promenite radnju za taster ili dugme." -#: lib/logitech_receiver/settings_templates.py:618 -#: lib/logitech_receiver/settings_templates.py:619 -#: lib/logitech_receiver/settings_templates.py:887 -msgid "Mouse Gestures" -msgstr "Pokreti miša" +#: lib/logitech_receiver/settings_templates.py:465 +msgid "Overridden by diversion." +msgstr "Preusmjeravanje tipki." -#: lib/logitech_receiver/settings_templates.py:618 -#: lib/logitech_receiver/settings_templates.py:619 -#: lib/logitech_receiver/settings_templates.py:620 -msgid "Regular" -msgstr "Redovno" +#: lib/logitech_receiver/settings_templates.py:466 +msgid "Changing important actions (such as for the left mouse button) can " + "result in an unusable system." +msgstr "Promena važnih radnji (kao što je levi taster miša) može dovesti do " + "neupotrebljivosti sistem." -#: lib/logitech_receiver/settings_templates.py:618 -msgid "Sliding DPI" -msgstr "Klizni DPI" +#: lib/logitech_receiver/settings_templates.py:639 +msgid "Key/Button Diversion" +msgstr "Preusmeravanje tipki" -#: lib/logitech_receiver/settings_templates.py:686 -msgid "Sensitivity (DPI)" -msgstr "Osetljivost (DPI)" +#: lib/logitech_receiver/settings_templates.py:640 +msgid "Make the key or button send HID++ notifications (Diverted) or " + "initiate Mouse Gestures or Sliding DPI" +msgstr "Neka taster ili dugme šalje HID++ obaveštenja (preusmereno) ili " + "pokrenite miš pokretom" -#: lib/logitech_receiver/settings_templates.py:726 -msgid "Sensitivity Switching" -msgstr "Promena osetljivosti" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Diverted" +msgstr "Preusmereno" -#: lib/logitech_receiver/settings_templates.py:728 -msgid "" -"Switch the current sensitivity and the remembered sensitivity when the key " -"or button is pressed.\n" -"If there is no remembered sensitivity, just remember the current sensitivity" -msgstr "Promenite trenutnu osetljivost i zapamćenu osetljivost kada pritisnete taster " -"ili kada je dugme pritisnuto. \n" -"Ako nema zapamćene osetljivosti, samo zapamtite trenutnu osetljivost" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +msgid "Mouse Gestures" +msgstr "Pokreti miša" -#: lib/logitech_receiver/settings_templates.py:732 -#: lib/logitech_receiver/settings_templates.py:773 -#: lib/logitech_receiver/settings_templates.py:892 -msgid "Off" -msgstr "Ugašen" +#: lib/logitech_receiver/settings_templates.py:643 +#: lib/logitech_receiver/settings_templates.py:644 +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Regular" +msgstr "Redovno" -#: lib/logitech_receiver/settings_templates.py:770 -msgid "DPI Sliding Adjustment" -msgstr "DPI klizno podešavanje" +#: lib/logitech_receiver/settings_templates.py:643 +msgid "Sliding DPI" +msgstr "Klizni DPI" -#: lib/logitech_receiver/settings_templates.py:771 -msgid "" -"Adjust the DPI by sliding the mouse horizontally while holding the button " -"down." -msgstr "Podesite DPI pomeranjem miša horizontalno dok držite taster" -"za dolje." +#: lib/logitech_receiver/settings_templates.py:711 +msgid "Sensitivity (DPI)" +msgstr "Osetljivost (DPI)" -#: lib/logitech_receiver/settings_templates.py:868 -msgid "Disable keys" -msgstr "Onemogući tipke" +#: lib/logitech_receiver/settings_templates.py:752 +msgid "Sensitivity Switching" +msgstr "Promena osetljivosti" -#: lib/logitech_receiver/settings_templates.py:869 -msgid "Disable specific keyboard keys." -msgstr "Onemogući određene tipke na tipkovnici." +#: lib/logitech_receiver/settings_templates.py:754 +msgid "Switch the current sensitivity and the remembered sensitivity when " + "the key or button is pressed.\n" + "If there is no remembered sensitivity, just remember the current " + "sensitivity" +msgstr "Promenite trenutnu osetljivost i zapamćenu osetljivost kada " + "pritisnete taster ili kada je dugme pritisnuto. \n" + "Ako nema zapamćene osetljivosti, samo zapamtite trenutnu osetljivost" -#: lib/logitech_receiver/settings_templates.py:872 +#: lib/logitech_receiver/settings_templates.py:758 +msgid "Off" +msgstr "Ugašen" + +#: lib/logitech_receiver/settings_templates.py:791 +msgid "Disable keys" +msgstr "Onemogući tipke" + +#: lib/logitech_receiver/settings_templates.py:792 +msgid "Disable specific keyboard keys." +msgstr "Onemogući određene tipke na tipkovnici." + +#: lib/logitech_receiver/settings_templates.py:795 #, python-format -msgid "Disables the %s key." -msgstr "Onemogućuje %s tipku." +msgid "Disables the %s key." +msgstr "Onemogućuje %s tipku." -#: lib/logitech_receiver/settings_templates.py:888 -msgid "Send a gesture by sliding the mouse while holding the button down." -msgstr "Pošaljite pokret prevlačenjem miša dok držite dugme za dole." +#: lib/logitech_receiver/settings_templates.py:809 +#: lib/logitech_receiver/settings_templates.py:860 +msgid "Set OS" +msgstr "Postavi OS" -#: lib/logitech_receiver/settings_templates.py:986 -#: lib/logitech_receiver/settings_templates.py:1034 -msgid "Set OS" -msgstr "Postavi OS" +#: lib/logitech_receiver/settings_templates.py:810 +#: lib/logitech_receiver/settings_templates.py:861 +msgid "Change keys to match OS." +msgstr "Promeni tastere da se poklapaju sa OS." -#: lib/logitech_receiver/settings_templates.py:987 -#: lib/logitech_receiver/settings_templates.py:1035 -msgid "Change keys to match OS." -msgstr "Promeni tastere da se poklapaju sa OS." +#: lib/logitech_receiver/settings_templates.py:873 +msgid "Change Host" +msgstr "Promeni računalo" + +#: lib/logitech_receiver/settings_templates.py:874 +msgid "Switch connection to a different host" +msgstr "Prebaci vezu na drugo računalo" + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Performs a left click." +msgstr "Izvodi levi klik." + +#: lib/logitech_receiver/settings_templates.py:900 +msgid "Single tap" +msgstr "Jedan dodir" + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Performs a right click." +msgstr "Izvodi desni klik." + +#: lib/logitech_receiver/settings_templates.py:901 +msgid "Single tap with two fingers" +msgstr "Jedan dodir sa dva prsta" + +#: lib/logitech_receiver/settings_templates.py:902 +msgid "Single tap with three fingers" +msgstr "Jedan dodir sa tri prsta" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Double tap" +msgstr "Dvostruki dodir" + +#: lib/logitech_receiver/settings_templates.py:906 +msgid "Performs a double click." +msgstr "Izvodi dvostruki klik." + +#: lib/logitech_receiver/settings_templates.py:907 +msgid "Double tap with two fingers" +msgstr "Dvoklik sa dva prsta" + +#: lib/logitech_receiver/settings_templates.py:908 +msgid "Double tap with three fingers" +msgstr "Dvoklik sa tri prsta" + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Prevlači stavke prevlačenjem prsta nakon dvostrukog dodira." + +#: lib/logitech_receiver/settings_templates.py:911 +msgid "Tap and drag" +msgstr "Dodirnite i prevucite" + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Prevlači stavke prevlačenjem prstiju nakon dvostrukog dodira." + +#: lib/logitech_receiver/settings_templates.py:913 +msgid "Tap and drag with two fingers" +msgstr "Dodirnite i prevucite sa dva prsta" + +#: lib/logitech_receiver/settings_templates.py:914 +msgid "Tap and drag with three fingers" +msgstr "Dodirnite i prevucite sa tri prsta" + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "Onemogućava pokrete dodira i rubnih gesta (ekvivalentno pritiskanju " + "Fn+LeftClick)." + +#: lib/logitech_receiver/settings_templates.py:917 +msgid "Suppress tap and edge gestures" +msgstr "Zaustavite pokrete dodira i ivica" + +#: lib/logitech_receiver/settings_templates.py:918 +msgid "Scroll with one finger" +msgstr "Pomerite se jednim prstom" + +#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scrolls." +msgstr "Pomeranje." + +#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:922 +msgid "Scroll with two fingers" +msgstr "Pomeranje sa dva prsta" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scroll horizontally with two fingers" +msgstr "Pomeranje horizontalno sa dva prsta" + +#: lib/logitech_receiver/settings_templates.py:920 +msgid "Scrolls horizontally." +msgstr "Pomeranje horizontalno." + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scroll vertically with two fingers" +msgstr "Pomeranje vertikalno sa dva prsta" + +#: lib/logitech_receiver/settings_templates.py:921 +msgid "Scrolls vertically." +msgstr "Pomeranje vertikalno." + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Inverts the scrolling direction." +msgstr "Obrče smer pomeranja." + +#: lib/logitech_receiver/settings_templates.py:923 +msgid "Natural scrolling" +msgstr "Prirodno pomeranje" + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Enables the thumbwheel." +msgstr "Omogućava točkić." + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Thumbwheel" +msgstr "Kotačić palca." + +#: lib/logitech_receiver/settings_templates.py:935 +#: lib/logitech_receiver/settings_templates.py:939 +msgid "Swipe from the top edge" +msgstr "Povucite od gornjeg ruba" + +#: lib/logitech_receiver/settings_templates.py:936 +msgid "Swipe from the left edge" +msgstr "Povucite od levog ruba" + +#: lib/logitech_receiver/settings_templates.py:937 +msgid "Swipe from the right edge" +msgstr "Povucite od desnog ruba" + +#: lib/logitech_receiver/settings_templates.py:938 +msgid "Swipe from the bottom edge" +msgstr "Povucite od donjeg ruba" + +#: lib/logitech_receiver/settings_templates.py:940 +msgid "Swipe two fingers from the left edge" +msgstr "Povucite sa dva prsta od levog ruba" + +#: lib/logitech_receiver/settings_templates.py:941 +msgid "Swipe two fingers from the right edge" +msgstr "Povucite sa dva prsta od desnog ruba" + +#: lib/logitech_receiver/settings_templates.py:942 +msgid "Swipe two fingers from the bottom edge" +msgstr "Povucite sa dva prsta od donjeg ruba" + +#: lib/logitech_receiver/settings_templates.py:943 +msgid "Swipe two fingers from the top edge" +msgstr "Povucite sa dva prsta od gornjeg ruba" + +#: lib/logitech_receiver/settings_templates.py:944 +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "Stisnite da biste umanjili; proširite da biste uvećali." + +#: lib/logitech_receiver/settings_templates.py:944 +msgid "Zoom with two fingers." +msgstr "Zumirajte sa dva prsta." + +#: lib/logitech_receiver/settings_templates.py:945 +msgid "Pinch to zoom out." +msgstr "Stisnite prste da biste umanjili prikaz." + +#: lib/logitech_receiver/settings_templates.py:946 +msgid "Spread to zoom in." +msgstr "Raširite prste da biste uvećali." + +#: lib/logitech_receiver/settings_templates.py:947 +msgid "Zoom with three fingers." +msgstr "Zumirajte sa tri prsta." + +#: lib/logitech_receiver/settings_templates.py:948 +msgid "Zoom with two fingers" +msgstr "Zumirajte sa dva prsta." + +#: lib/logitech_receiver/settings_templates.py:966 +msgid "Pixel zone" +msgstr "Zona piksela" + +#: lib/logitech_receiver/settings_templates.py:967 +msgid "Ratio zone" +msgstr "Zona omera" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Scale factor" +msgstr "Faktor veličine" + +#: lib/logitech_receiver/settings_templates.py:968 +msgid "Sets the cursor speed." +msgstr "Podešava brzinu pokazivača." + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left" +msgstr "Levo" + +#: lib/logitech_receiver/settings_templates.py:972 +msgid "Left-most coordinate." +msgstr "Najučestalija leva koordinata." + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom" +msgstr "Dno" + +#: lib/logitech_receiver/settings_templates.py:973 +msgid "Bottom coordinate." +msgstr "Donja koordinata." + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width" +msgstr "Širina" + +#: lib/logitech_receiver/settings_templates.py:974 +msgid "Width." +msgstr "Širina" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height" +msgstr "Visina" + +#: lib/logitech_receiver/settings_templates.py:975 +msgid "Height." +msgstr "Visina." + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Cursor speed." +msgstr "Brzina kursora." + +#: lib/logitech_receiver/settings_templates.py:976 +msgid "Scale" +msgstr "Skala" + +#: lib/logitech_receiver/settings_templates.py:982 +msgid "Gestures" +msgstr "Geste" + +#: lib/logitech_receiver/settings_templates.py:983 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Podesite ponašanje miša/tačpeda." + +#: lib/logitech_receiver/settings_templates.py:1000 +msgid "Gestures Diversion" +msgstr "Preusmjeravanje gesta" + +#: lib/logitech_receiver/settings_templates.py:1001 +msgid "Divert mouse/touchpad gestures." +msgstr "Preusmeri ponašanje miša/tačpeda." + +#: lib/logitech_receiver/settings_templates.py:1018 +msgid "Gesture params" +msgstr "Parametri pokreta" + +#: lib/logitech_receiver/settings_templates.py:1019 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Promenite numeričke parametre miša/tačpeda." + +#: lib/logitech_receiver/settings_templates.py:1044 +msgid "M-Key LEDs" +msgstr "LED osvetljenje M-tastera" + +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Control the M-Key LEDs." +msgstr "Kontrolišite LED osvetljenje M-tastera." #: lib/logitech_receiver/settings_templates.py:1047 -msgid "Change Host" -msgstr "Promeni računalo" +#: lib/logitech_receiver/settings_templates.py:1077 +msgid "May need G Keys diverted to be effective." +msgstr "Možda će biti potrebno ponovo preusmeriti taster G da bi bili " + "efikasni." -#: lib/logitech_receiver/settings_templates.py:1048 -msgid "Switch connection to a different host" -msgstr "Prebaci vezu na drugo računalo" - -#: lib/logitech_receiver/settings_templates.py:1073 -msgid "Performs a left click." -msgstr "Izvodi levi klik." - -#: lib/logitech_receiver/settings_templates.py:1073 -msgid "Single tap" -msgstr "Jedan dodir" +#: lib/logitech_receiver/settings_templates.py:1053 +#, python-format +msgid "Lights up the %s key." +msgstr "Osvetli %s taster." #: lib/logitech_receiver/settings_templates.py:1074 -msgid "Performs a right click." -msgstr "Izvodi desni klik." +msgid "MR-Key LED" +msgstr "LED MR-tastera" -#: lib/logitech_receiver/settings_templates.py:1074 -msgid "Single tap with two fingers" -msgstr "Jedan dodir sa dva prsta" +#: lib/logitech_receiver/settings_templates.py:1076 +msgid "Control the MR-Key LED." +msgstr "Kontroliše LED osvetljenje MR-tastera." -#: lib/logitech_receiver/settings_templates.py:1075 -msgid "Single tap with three fingers" -msgstr "Jedan dodir sa tri prsta" - -#: lib/logitech_receiver/settings_templates.py:1079 -msgid "Double tap" -msgstr "Dvostruki dodir" - -#: lib/logitech_receiver/settings_templates.py:1079 -msgid "Performs a double click." -msgstr "Izvodi dvostruki klik." - -#: lib/logitech_receiver/settings_templates.py:1080 -msgid "Double tap with two fingers" -msgstr "Dvoklik sa dva prsta" - -#: lib/logitech_receiver/settings_templates.py:1081 -msgid "Double tap with three fingers" -msgstr "Dvoklik sa tri prsta" - -#: lib/logitech_receiver/settings_templates.py:1084 -msgid "Drags items by dragging the finger after double tapping." -msgstr "Prevlači stavke prevlačenjem prsta nakon dvostrukog dodira." - -#: lib/logitech_receiver/settings_templates.py:1084 -msgid "Tap and drag" -msgstr "Dodirnite i prevucite" - -#: lib/logitech_receiver/settings_templates.py:1086 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "Prevlači stavke prevlačenjem prstiju nakon dvostrukog dodira." - -#: lib/logitech_receiver/settings_templates.py:1086 -msgid "Tap and drag with two fingers" -msgstr "Dodirnite i prevucite sa dva prsta" - -#: lib/logitech_receiver/settings_templates.py:1087 -msgid "Tap and drag with three fingers" -msgstr "Dodirnite i prevucite sa tri prsta" - -#: lib/logitech_receiver/settings_templates.py:1090 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "Onemogućava pokrete dodira i rubnih gesta (ekvivalentno pritiskanju Fn+LeftClick)." - -#: lib/logitech_receiver/settings_templates.py:1090 -msgid "Suppress tap and edge gestures" -msgstr "Zaustavite pokrete dodira i ivica" - -#: lib/logitech_receiver/settings_templates.py:1091 -msgid "Scroll with one finger" -msgstr "Pomerite se jednim prstom" - -#: lib/logitech_receiver/settings_templates.py:1091 -#: lib/logitech_receiver/settings_templates.py:1092 #: lib/logitech_receiver/settings_templates.py:1095 -msgid "Scrolls." -msgstr "Pomeranje." - -#: lib/logitech_receiver/settings_templates.py:1092 -#: lib/logitech_receiver/settings_templates.py:1095 -msgid "Scroll with two fingers" -msgstr "Pomeranje sa dva prsta" - -#: lib/logitech_receiver/settings_templates.py:1093 -msgid "Scroll horizontally with two fingers" -msgstr "Pomeranje horizontalno sa dva prsta" - -#: lib/logitech_receiver/settings_templates.py:1093 -msgid "Scrolls horizontally." -msgstr "Pomeranje horizontalno." - -#: lib/logitech_receiver/settings_templates.py:1094 -msgid "Scroll vertically with two fingers" -msgstr "Pomeranje vertikalno sa dva prsta" - -#: lib/logitech_receiver/settings_templates.py:1094 -msgid "Scrolls vertically." -msgstr "Pomeranje vertikalno." - -#: lib/logitech_receiver/settings_templates.py:1096 -msgid "Inverts the scrolling direction." -msgstr "Obrče smer pomeranja." - -#: lib/logitech_receiver/settings_templates.py:1096 -msgid "Natural scrolling" -msgstr "Prirodno pomeranje" +msgid "Persistent Key/Button Mapping" +msgstr "Trajno mapiranje tastera/dugmada" #: lib/logitech_receiver/settings_templates.py:1097 -msgid "Enables the thumbwheel." -msgstr "Omogućava točkić." +msgid "Permanently change the mapping for the key or button." +msgstr "Trajno promenite mapiranje za taster ili dugme." -#: lib/logitech_receiver/settings_templates.py:1097 -msgid "Thumbwheel" -msgstr "Kotačić palca." +#: lib/logitech_receiver/settings_templates.py:1098 +msgid "Changing important keys or buttons (such as for the left mouse " + "button) can result in an unusable system." +msgstr "Promena važnih tastera ili dugmadi (kao što je levi taster miša) " + "može rezultiraju nestabilnim sistemom." -#: lib/logitech_receiver/settings_templates.py:1108 -#: lib/logitech_receiver/settings_templates.py:1112 -msgid "Swipe from the top edge" -msgstr "Povucite od gornjeg ruba" +#: lib/logitech_receiver/settings_templates.py:1157 +msgid "Sidetone" +msgstr "Bočni ton" -#: lib/logitech_receiver/settings_templates.py:1109 -msgid "Swipe from the left edge" -msgstr "Povucite od levog ruba" +#: lib/logitech_receiver/settings_templates.py:1158 +msgid "Set sidetone level." +msgstr "Podesite nivo bočnih tonova." -#: lib/logitech_receiver/settings_templates.py:1110 -msgid "Swipe from the right edge" -msgstr "Povucite od desnog ruba" +#: lib/logitech_receiver/settings_templates.py:1167 +msgid "Equalizer" +msgstr "Ekvilajzer" -#: lib/logitech_receiver/settings_templates.py:1111 -msgid "Swipe from the bottom edge" -msgstr "Povucite od donjeg ruba" +#: lib/logitech_receiver/settings_templates.py:1168 +msgid "Set equalizer levels." +msgstr "Podesite nivo ekvilajzera." -#: lib/logitech_receiver/settings_templates.py:1113 -msgid "Swipe two fingers from the left edge" -msgstr "Povucite sa dva prsta od levog ruba" +#: lib/logitech_receiver/settings_templates.py:1191 +msgid "Hz" +msgstr "Hz" -#: lib/logitech_receiver/settings_templates.py:1114 -msgid "Swipe two fingers from the right edge" -msgstr "Povucite sa dva prsta od desnog ruba" +#: lib/logitech_receiver/settings_templates.py:1197 +msgid "Power Management" +msgstr "" -#: lib/logitech_receiver/settings_templates.py:1115 -msgid "Swipe two fingers from the bottom edge" -msgstr "Povucite sa dva prsta od donjeg ruba" +#: lib/logitech_receiver/settings_templates.py:1198 +msgid "Power off in minutes (0 for never)." +msgstr "" -#: lib/logitech_receiver/settings_templates.py:1116 -msgid "Swipe two fingers from the top edge" -msgstr "Povucite sa dva prsta od gornjeg ruba" +#: lib/logitech_receiver/status.py:114 +msgid "No paired devices." +msgstr "Nema uparenih uređaja" -#: lib/logitech_receiver/settings_templates.py:1117 -#: lib/logitech_receiver/settings_templates.py:1121 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "Stisnite da biste umanjili; proširite da biste uvećali." - -#: lib/logitech_receiver/settings_templates.py:1117 -msgid "Zoom with two fingers." -msgstr "Zumirajte sa dva prsta." - -#: lib/logitech_receiver/settings_templates.py:1118 -msgid "Pinch to zoom out." -msgstr "Stisnite prste da biste umanjili prikaz." - -#: lib/logitech_receiver/settings_templates.py:1119 -msgid "Spread to zoom in." -msgstr "Raširite prste da biste uvećali." - -#: lib/logitech_receiver/settings_templates.py:1120 -msgid "Zoom with three fingers." -msgstr "Zumirajte sa tri prsta." - -#: lib/logitech_receiver/settings_templates.py:1121 -msgid "Zoom with two fingers" -msgstr "Zumirajte sa dva prsta." - -#: lib/logitech_receiver/settings_templates.py:1139 -msgid "Pixel zone" -msgstr "Zona piksela" - -#: lib/logitech_receiver/settings_templates.py:1140 -msgid "Ratio zone" -msgstr "Zona omera" - -#: lib/logitech_receiver/settings_templates.py:1141 -msgid "Scale factor" -msgstr "Faktor veličine" - -#: lib/logitech_receiver/settings_templates.py:1141 -msgid "Sets the cursor speed." -msgstr "Podešava brzinu pokazivača." - -#: lib/logitech_receiver/settings_templates.py:1145 -msgid "Left" -msgstr "Levo" - -#: lib/logitech_receiver/settings_templates.py:1145 -msgid "Left-most coordinate." -msgstr "Najučestalija leva koordinata." - -#: lib/logitech_receiver/settings_templates.py:1146 -msgid "Bottom" -msgstr "Dno" - -#: lib/logitech_receiver/settings_templates.py:1146 -msgid "Bottom coordinate." -msgstr "Donja koordinata." - -#: lib/logitech_receiver/settings_templates.py:1147 -msgid "Width" -msgstr "Širina" - -#: lib/logitech_receiver/settings_templates.py:1147 -msgid "Width." -msgstr "Širina" - -#: lib/logitech_receiver/settings_templates.py:1148 -msgid "Height" -msgstr "Visina" - -#: lib/logitech_receiver/settings_templates.py:1148 -msgid "Height." -msgstr "Visina." - -#: lib/logitech_receiver/settings_templates.py:1149 -msgid "Cursor speed." -msgstr "Brzina kursora." - -#: lib/logitech_receiver/settings_templates.py:1149 -msgid "Scale" -msgstr "Skala" - -#: lib/logitech_receiver/settings_templates.py:1155 -msgid "Gestures" -msgstr "Geste" - -#: lib/logitech_receiver/settings_templates.py:1156 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "Podesite ponašanje miša/tačpeda." - -#: lib/logitech_receiver/settings_templates.py:1172 -msgid "Gestures Diversion" -msgstr "Preusmjeravanje gesta" - -#: lib/logitech_receiver/settings_templates.py:1173 -msgid "Divert mouse/touchpad gestures." -msgstr "Preusmeri ponašanje miša/tačpeda." - -#: lib/logitech_receiver/settings_templates.py:1189 -msgid "Gesture params" -msgstr "Parametri pokreta" - -#: lib/logitech_receiver/settings_templates.py:1190 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "Promenite numeričke parametre miša/tačpeda." - -#: lib/logitech_receiver/settings_templates.py:1214 -msgid "M-Key LEDs" -msgstr "LED osvetljenje M-tastera" - -#: lib/logitech_receiver/settings_templates.py:1216 -msgid "Control the M-Key LEDs." -msgstr "Kontrolišite LED osvetljenje M-tastera." - -#: lib/logitech_receiver/settings_templates.py:1217 -#: lib/logitech_receiver/settings_templates.py:1245 -msgid "May need G Keys diverted to be effective." -msgstr "Možda će biti potrebno ponovo preusmeriti taster G da bi bili efikasni." - -#: lib/logitech_receiver/settings_templates.py:1223 +#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 #, python-format -msgid "Lights up the %s key." -msgstr "Osvetli %s taster." +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s uparen uređaj." +msgstr[1] "%(count)s uparena uređaja." -#: lib/logitech_receiver/settings_templates.py:1242 -msgid "MR-Key LED" -msgstr "LED MR-tastera" - -#: lib/logitech_receiver/settings_templates.py:1244 -msgid "Control the MR-Key LED." -msgstr "Kontroliše LED osvetljenje MR-tastera." - -#: lib/logitech_receiver/settings_templates.py:1262 -msgid "Persistent Key/Button Mapping" -msgstr "Trajno mapiranje tastera/dugmada" - -#: lib/logitech_receiver/settings_templates.py:1264 -msgid "Permanently change the mapping for the key or button." -msgstr "Trajno promenite mapiranje za taster ili dugme." - -#: lib/logitech_receiver/settings_templates.py:1265 -msgid "" -"Changing important keys or buttons (such as for the left mouse button) can " -"result in an unusable system." -msgstr "Promena važnih tastera ili dugmadi (kao što je levi taster miša) može " -"rezultiraju nestabilnim sistemom." - -#: lib/logitech_receiver/settings_templates.py:1322 -msgid "Sidetone" -msgstr "Bočni ton" - -#: lib/logitech_receiver/settings_templates.py:1323 -msgid "Set sidetone level." -msgstr "Podesite nivo bočnih tonova." - -#: lib/logitech_receiver/settings_templates.py:1332 -msgid "Equalizer" -msgstr "Ekvilajzer" - -#: lib/logitech_receiver/settings_templates.py:1333 -msgid "Set equalizer levels." -msgstr "Podesite nivo ekvilajzera." - -#: lib/logitech_receiver/settings_templates.py:1355 -msgid "Hz" -msgstr "Hz" - -#: lib/logitech_receiver/status.py:113 -msgid "No paired devices." -msgstr "Nema uparenih uređaja" - -#: lib/logitech_receiver/status.py:114 lib/solaar/ui/window.py:620 +#: lib/logitech_receiver/status.py:170 #, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s uparen uređaj." -msgstr[1] "%(count)s uparena uređaja." +msgid "Battery: %(level)s" +msgstr "Baterija: %(level)s" -#: lib/logitech_receiver/status.py:167 +#: lib/logitech_receiver/status.py:172 #, python-format -msgid "Battery: %(level)s" -msgstr "Baterija: %(level)s" +msgid "Battery: %(percent)d%%" +msgstr "Baterija: %(percent)d%%" -#: lib/logitech_receiver/status.py:169 +#: lib/logitech_receiver/status.py:184 #, python-format -msgid "Battery: %(percent)d%%" -msgstr "Baterija: %(percent)d%%" +msgid "Lighting: %(level)s lux" +msgstr "Osvetljenje %(level)s lux" -#: lib/logitech_receiver/status.py:181 +#: lib/logitech_receiver/status.py:239 #, python-format -msgid "Lighting: %(level)s lux" -msgstr "Osvetljenje %(level)s lux" +msgid "Battery: %(level)s (%(status)s)" +msgstr "Baterija: %(level)s (%(status)s)" -#: lib/logitech_receiver/status.py:236 +#: lib/logitech_receiver/status.py:241 #, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "Baterija: %(level)s (%(status)s)" +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Baterija: %(percent)d%% (%(status)s)" -#: lib/logitech_receiver/status.py:238 -#, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "Baterija: %(percent)d%% (%(status)s)" - -#: lib/solaar/ui/__init__.py:51 -msgid "Permissions error" -msgstr "Greška u dozvolama" - -#: lib/solaar/ui/__init__.py:53 -#, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open it." -msgstr "Pronašao sam Logitech prijemnik (%s), ali nisam imao dozvolu da ga otvorim." +#: lib/solaar/ui/__init__.py:52 +msgid "Permissions error" +msgstr "Greška u dozvolama" #: lib/solaar/ui/__init__.py:54 -msgid "" -"If you've just installed Solaar, try removing the receiver and plugging it " -"back in." -msgstr "Ako ste upravo instalirali Solaar, pokušajte da odspojite prijemnik i priključite ga" -"ponovo." - -#: lib/solaar/ui/__init__.py:57 -msgid "Cannot connect to device error" -msgstr "Ne mogu da se povežem sa uređajem, greška" - -#: lib/solaar/ui/__init__.py:59 #, python-format -msgid "" -"Found a Logitech receiver or device at %s, but encountered an error " -"connecting to it." -msgstr "Pronašli smo Logitech prijemnik ili uređaj na %s, ali smo naišli na grešku" -"prilikom povezivanja." +msgid "Found a Logitech receiver or device (%s), but did not have " + "permission to open it." +msgstr "" + +#: lib/solaar/ui/__init__.py:55 +msgid "If you've just installed Solaar, try disconnecting the receiver or " + "device and then reconnecting it." +msgstr "" + +#: lib/solaar/ui/__init__.py:58 +msgid "Cannot connect to device error" +msgstr "Ne mogu da se povežem sa uređajem, greška" #: lib/solaar/ui/__init__.py:60 -msgid "" -"Try removing the device and plugging it back in or turning it off and then " -"on." -msgstr "Pokušajte da odspojite uređaj i ponovo ga uključite ili isključite, a zatim" -"upalite." +#, python-format +msgid "Found a Logitech receiver or device at %s, but encountered an error " + "connecting to it." +msgstr "Pronašli smo Logitech prijemnik ili uređaj na %s, ali smo naišli na " + "greškuprilikom povezivanja." -#: lib/solaar/ui/__init__.py:63 -msgid "Unpairing failed" -msgstr "Uparivanje neuspelo" +#: lib/solaar/ui/__init__.py:61 +msgid "Try disconnecting the device and then reconnecting it or turning it " + "off and then on." +msgstr "" -#: lib/solaar/ui/__init__.py:65 -#, python-brace-format -msgid "Failed to unpair %{device} from %{receiver}." -msgstr "Odparivanje %{device} sa %{receiver} nije uspelo." +#: lib/solaar/ui/__init__.py:64 +msgid "Unpairing failed" +msgstr "Uparivanje neuspelo" #: lib/solaar/ui/__init__.py:66 -msgid "The receiver returned an error, with no further details." -msgstr "Prijemnik je vratio grešku, bez dodatnih detalja." +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Odparivanje %{device} sa %{receiver} nije uspelo." -#: lib/solaar/ui/__init__.py:176 -msgid "Another Solaar process is already running so just expose its window" -msgstr "Još jedan Solaar proces je već pokrenut, pa samo otvorite njegov prozor" +#: lib/solaar/ui/__init__.py:67 +msgid "The receiver returned an error, with no further details." +msgstr "Prijemnik je vratio grešku, bez dodatnih detalja." + +#: lib/solaar/ui/__init__.py:177 +msgid "Another Solaar process is already running so just expose its window" +msgstr "Još jedan Solaar proces je već pokrenut, pa samo otvorite njegov " + "prozor" #: lib/solaar/ui/about.py:36 -msgid "" -"Manages Logitech receivers,\n" -"keyboards, mice, and tablets." -msgstr "Upravlja Logitech prijemnicima,\n" -"tastature, miševi i tableti." +msgid "Manages Logitech receivers,\n" + "keyboards, mice, and tablets." +msgstr "Upravlja Logitech prijemnicima,\n" + "tastature, miševi i tableti." #: lib/solaar/ui/about.py:44 -msgid "Additional Programming" -msgstr "Dodatno programiranje" +msgid "Additional Programming" +msgstr "Dodatno programiranje" #: lib/solaar/ui/about.py:45 -msgid "GUI design" -msgstr "GUI dizajn" +msgid "GUI design" +msgstr "GUI dizajn" #: lib/solaar/ui/about.py:47 -msgid "Testing" -msgstr "Testiranje" +msgid "Testing" +msgstr "Testiranje" #: lib/solaar/ui/about.py:54 -msgid "Logitech documentation" -msgstr "Logitech dokumentacija" +msgid "Logitech documentation" +msgstr "Logitech dokumentacija" #: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 -#: lib/solaar/ui/window.py:202 -msgid "Unpair" -msgstr "Odspoji" +#: lib/solaar/ui/window.py:197 +msgid "Unpair" +msgstr "Odspoji" -#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:151 -msgid "Cancel" -msgstr "Poništi" +#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 +msgid "Cancel" +msgstr "Poništi" -#: lib/solaar/ui/config_panel.py:205 -msgid "Complete - ENTER to change" -msgstr "Završeno - pritisnite ENTER da biste promenili" +#: lib/solaar/ui/config_panel.py:212 +msgid "Complete - ENTER to change" +msgstr "Završeno - pritisnite ENTER da biste promenili" -#: lib/solaar/ui/config_panel.py:205 -msgid "Incomplete" -msgstr "Nepotpuno" +#: lib/solaar/ui/config_panel.py:212 +msgid "Incomplete" +msgstr "Nepotpuno" -#: lib/solaar/ui/config_panel.py:444 lib/solaar/ui/config_panel.py:495 +#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 #, python-format -msgid "%d value" -msgid_plural "%d values" -msgstr[0] "%d vrednost" -msgstr[1] "%d vrednosti" - -#: lib/solaar/ui/config_panel.py:506 -msgid "Changes allowed" -msgstr "Promene dozvoljene" - -#: lib/solaar/ui/config_panel.py:507 -msgid "No changes allowed" -msgstr "Promene zabranjene" - -#: lib/solaar/ui/config_panel.py:508 -msgid "Ignore this setting" -msgstr "Ignoriši ovu postavku" - -#: lib/solaar/ui/config_panel.py:553 -msgid "Working" -msgstr "Radim" - -#: lib/solaar/ui/config_panel.py:556 -msgid "Read/write operation failed." -msgstr "Operacija čitanja/pisanja nije uspela." - -#: lib/solaar/ui/diversion_rules.py:64 -msgid "Built-in rules" -msgstr "Ugrađena pravila" - -#: lib/solaar/ui/diversion_rules.py:64 -msgid "User-defined rules" -msgstr "Korisnički definisana pravila" - -#: lib/solaar/ui/diversion_rules.py:66 lib/solaar/ui/diversion_rules.py:1074 -msgid "Rule" -msgstr "Pravilo" - -#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:508 -#: lib/solaar/ui/diversion_rules.py:630 -msgid "Sub-rule" -msgstr "Podpravilo" - -#: lib/solaar/ui/diversion_rules.py:69 -msgid "[empty]" -msgstr "[prazno]" - -#: lib/solaar/ui/diversion_rules.py:92 -msgid "Solaar Rule Editor" -msgstr "Solaar uređivač pravila" - -#: lib/solaar/ui/diversion_rules.py:142 -msgid "Make changes permanent?" -msgstr "Želite li da promene budu trajne?" - -#: lib/solaar/ui/diversion_rules.py:147 -msgid "Yes" -msgstr "Da" - -#: lib/solaar/ui/diversion_rules.py:149 -msgid "No" -msgstr "Ne" - -#: lib/solaar/ui/diversion_rules.py:154 -msgid "If you choose No, changes will be lost when Solaar is closed." -msgstr "Ako izaberete Ne, promene će biti izgubljene kada se Solaar zatvori." - -#: lib/solaar/ui/diversion_rules.py:202 -msgid "Save changes" -msgstr "Spremi promene" - -#: lib/solaar/ui/diversion_rules.py:207 -msgid "Discard changes" -msgstr "Odbaci promene" - -#: lib/solaar/ui/diversion_rules.py:371 -msgid "Insert here" -msgstr "Unesi ovde" - -#: lib/solaar/ui/diversion_rules.py:373 -msgid "Insert above" -msgstr "Unesi iznad" - -#: lib/solaar/ui/diversion_rules.py:375 -msgid "Insert below" -msgstr "Unesi ispod" - -#: lib/solaar/ui/diversion_rules.py:381 -msgid "Insert new rule here" -msgstr "Unesi novo pravilo ovde" - -#: lib/solaar/ui/diversion_rules.py:383 -msgid "Insert new rule above" -msgstr "Unesi novo pravilo iznad" - -#: lib/solaar/ui/diversion_rules.py:385 -msgid "Insert new rule below" -msgstr "Unesi novo pravilo ispod" - -#: lib/solaar/ui/diversion_rules.py:426 -msgid "Paste here" -msgstr "Zalepi ovde" - -#: lib/solaar/ui/diversion_rules.py:428 -msgid "Paste above" -msgstr "Zalepi iznad" - -#: lib/solaar/ui/diversion_rules.py:430 -msgid "Paste below" -msgstr "Zalepi ispod" - -#: lib/solaar/ui/diversion_rules.py:436 -msgid "Paste rule here" -msgstr "Zalepi pravilo ovde" - -#: lib/solaar/ui/diversion_rules.py:438 -msgid "Paste rule above" -msgstr "Zalepi pravilo iznad" - -#: lib/solaar/ui/diversion_rules.py:440 -msgid "Paste rule below" -msgstr "Zalepi pravilo ispod" - -#: lib/solaar/ui/diversion_rules.py:444 -msgid "Paste rule" -msgstr "Zalepi pravilo" - -#: lib/solaar/ui/diversion_rules.py:473 -msgid "Flatten" -msgstr "Poravnaj" - -#: lib/solaar/ui/diversion_rules.py:506 -msgid "Insert" -msgstr "Unesi" - -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:632 -#: lib/solaar/ui/diversion_rules.py:1117 -msgid "Or" -msgstr "Ili" - -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:631 -#: lib/solaar/ui/diversion_rules.py:1102 -msgid "And" -msgstr "I" - -#: lib/solaar/ui/diversion_rules.py:512 -msgid "Condition" -msgstr "Stanje" - -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1238 -msgid "Feature" -msgstr "Značajka" - -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1156 -msgid "Process" -msgstr "Proces" - -#: lib/solaar/ui/diversion_rules.py:516 -msgid "Mouse process" -msgstr "Proces miša" - -#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1271 -msgid "Report" -msgstr "Izveštaj" - -#: lib/solaar/ui/diversion_rules.py:518 lib/solaar/ui/diversion_rules.py:1306 -msgid "Modifiers" -msgstr "Modifikatori" - -#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1353 -msgid "Key" -msgstr "Taster" - -#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1398 -msgid "Test" -msgstr "Test" - -#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1512 -msgid "Test bytes" -msgstr "Testiraj bajtove" - -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2072 -msgid "Setting" -msgstr "Postavka" - -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1600 -msgid "Mouse Gesture" -msgstr "Gesta miša" - -#: lib/solaar/ui/diversion_rules.py:527 -msgid "Action" -msgstr "Akcija" - -#: lib/solaar/ui/diversion_rules.py:529 lib/solaar/ui/diversion_rules.py:1702 -msgid "Key press" -msgstr "Pritisak tipke" - -#: lib/solaar/ui/diversion_rules.py:530 lib/solaar/ui/diversion_rules.py:1752 -msgid "Mouse scroll" -msgstr "Pomeranje miša" - -#: lib/solaar/ui/diversion_rules.py:531 lib/solaar/ui/diversion_rules.py:1800 -msgid "Mouse click" -msgstr "Klik mišem" - -#: lib/solaar/ui/diversion_rules.py:532 lib/solaar/ui/diversion_rules.py:1869 -msgid "Execute" -msgstr "Izvrši" - -#: lib/solaar/ui/diversion_rules.py:533 -msgid "Set" -msgstr "Postavi" - -#: lib/solaar/ui/diversion_rules.py:562 -msgid "Insert new rule" -msgstr "Unesi novo pravilo" - -#: lib/solaar/ui/diversion_rules.py:582 lib/solaar/ui/diversion_rules.py:1550 -#: lib/solaar/ui/diversion_rules.py:1649 lib/solaar/ui/diversion_rules.py:1828 -msgid "Delete" -msgstr "Obriši" - -#: lib/solaar/ui/diversion_rules.py:604 -msgid "Negate" -msgstr "Negiraj" - -#: lib/solaar/ui/diversion_rules.py:628 -msgid "Wrap with" -msgstr "Zamotajte sa" - -#: lib/solaar/ui/diversion_rules.py:650 -msgid "Cut" -msgstr "Izreži" - -#: lib/solaar/ui/diversion_rules.py:665 -msgid "Paste" -msgstr "Zalepi" - -#: lib/solaar/ui/diversion_rules.py:677 -msgid "Copy" -msgstr "Kopiraj" - -#: lib/solaar/ui/diversion_rules.py:1054 -msgid "This editor does not support the selected rule component yet." -msgstr "Ovaj tekstualni uređivač još uvek ne podržava izabranu komponentu pravila." - -#: lib/solaar/ui/diversion_rules.py:1132 -msgid "Not" -msgstr "Ne" - -#: lib/solaar/ui/diversion_rules.py:1184 -msgid "MouseProcess" -msgstr "Proces miša" - -#: lib/solaar/ui/diversion_rules.py:1326 -msgid "Key down" -msgstr "Tipka za dolje" - -#: lib/solaar/ui/diversion_rules.py:1329 -msgid "Key up" -msgstr "Tipka za gore" - -#: lib/solaar/ui/diversion_rules.py:1414 -msgid "begin (inclusive)" -msgstr "početak (uključiv)" - -#: lib/solaar/ui/diversion_rules.py:1415 -msgid "end (exclusive)" -msgstr "završetak (isključiv)" - -#: lib/solaar/ui/diversion_rules.py:1424 -msgid "range" -msgstr "domet" - -#: lib/solaar/ui/diversion_rules.py:1426 -msgid "minimum" -msgstr "minimum" - -#: lib/solaar/ui/diversion_rules.py:1427 -msgid "maximum" -msgstr "maximum" - -#: lib/solaar/ui/diversion_rules.py:1429 +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d vrednost" +msgstr[1] "%d vrednosti" + +#: lib/solaar/ui/config_panel.py:518 +msgid "Changes allowed" +msgstr "Promene dozvoljene" + +#: lib/solaar/ui/config_panel.py:519 +msgid "No changes allowed" +msgstr "Promene zabranjene" + +#: lib/solaar/ui/config_panel.py:520 +msgid "Ignore this setting" +msgstr "Ignoriši ovu postavku" + +#: lib/solaar/ui/config_panel.py:565 +msgid "Working" +msgstr "Radim" + +#: lib/solaar/ui/config_panel.py:568 +msgid "Read/write operation failed." +msgstr "Operacija čitanja/pisanja nije uspela." + +#: lib/solaar/ui/diversion_rules.py:65 +msgid "Built-in rules" +msgstr "Ugrađena pravila" + +#: lib/solaar/ui/diversion_rules.py:65 +msgid "User-defined rules" +msgstr "Korisnički definisana pravila" + +#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 +msgid "Rule" +msgstr "Pravilo" + +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 +#: lib/solaar/ui/diversion_rules.py:636 +msgid "Sub-rule" +msgstr "Podpravilo" + +#: lib/solaar/ui/diversion_rules.py:70 +msgid "[empty]" +msgstr "[prazno]" + +#: lib/solaar/ui/diversion_rules.py:94 +msgid "Solaar Rule Editor" +msgstr "Solaar uređivač pravila" + +#: lib/solaar/ui/diversion_rules.py:141 +msgid "Make changes permanent?" +msgstr "Želite li da promene budu trajne?" + +#: lib/solaar/ui/diversion_rules.py:146 +msgid "Yes" +msgstr "Da" + +#: lib/solaar/ui/diversion_rules.py:148 +msgid "No" +msgstr "Ne" + +#: lib/solaar/ui/diversion_rules.py:153 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "Ako izaberete Ne, promene će biti izgubljene kada se Solaar zatvori." + +#: lib/solaar/ui/diversion_rules.py:201 +msgid "Save changes" +msgstr "Spremi promene" + +#: lib/solaar/ui/diversion_rules.py:206 +msgid "Discard changes" +msgstr "Odbaci promene" + +#: lib/solaar/ui/diversion_rules.py:372 +msgid "Insert here" +msgstr "Unesi ovde" + +#: lib/solaar/ui/diversion_rules.py:374 +msgid "Insert above" +msgstr "Unesi iznad" + +#: lib/solaar/ui/diversion_rules.py:376 +msgid "Insert below" +msgstr "Unesi ispod" + +#: lib/solaar/ui/diversion_rules.py:382 +msgid "Insert new rule here" +msgstr "Unesi novo pravilo ovde" + +#: lib/solaar/ui/diversion_rules.py:384 +msgid "Insert new rule above" +msgstr "Unesi novo pravilo iznad" + +#: lib/solaar/ui/diversion_rules.py:386 +msgid "Insert new rule below" +msgstr "Unesi novo pravilo ispod" + +#: lib/solaar/ui/diversion_rules.py:427 +msgid "Paste here" +msgstr "Zalepi ovde" + +#: lib/solaar/ui/diversion_rules.py:429 +msgid "Paste above" +msgstr "Zalepi iznad" + +#: lib/solaar/ui/diversion_rules.py:431 +msgid "Paste below" +msgstr "Zalepi ispod" + +#: lib/solaar/ui/diversion_rules.py:437 +msgid "Paste rule here" +msgstr "Zalepi pravilo ovde" + +#: lib/solaar/ui/diversion_rules.py:439 +msgid "Paste rule above" +msgstr "Zalepi pravilo iznad" + +#: lib/solaar/ui/diversion_rules.py:441 +msgid "Paste rule below" +msgstr "Zalepi pravilo ispod" + +#: lib/solaar/ui/diversion_rules.py:445 +msgid "Paste rule" +msgstr "Zalepi pravilo" + +#: lib/solaar/ui/diversion_rules.py:474 +msgid "Flatten" +msgstr "Poravnaj" + +#: lib/solaar/ui/diversion_rules.py:507 +msgid "Insert" +msgstr "Unesi" + +#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 +#: lib/solaar/ui/diversion_rules.py:1126 +msgid "Or" +msgstr "Ili" + +#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 +#: lib/solaar/ui/diversion_rules.py:1111 +msgid "And" +msgstr "I" + +#: lib/solaar/ui/diversion_rules.py:513 +msgid "Condition" +msgstr "Stanje" + +#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 +msgid "Feature" +msgstr "Značajka" + +#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 +msgid "Report" +msgstr "Izveštaj" + +#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 +msgid "Process" +msgstr "Proces" + +#: lib/solaar/ui/diversion_rules.py:518 +msgid "Mouse process" +msgstr "Proces miša" + +#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 +msgid "Modifiers" +msgstr "Modifikatori" + +#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 +msgid "Key" +msgstr "Taster" + +#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 +msgid "KeyIsDown" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 +msgid "Active" +msgstr "Радан" + +#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2218 +#: lib/solaar/ui/diversion_rules.py:2270 lib/solaar/ui/diversion_rules.py:2323 +msgid "Device" +msgstr "Uređaj" + +#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 +msgid "Host" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 +msgid "Setting" +msgstr "Postavka" + +#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 +#: lib/solaar/ui/diversion_rules.py:1526 +msgid "Test" +msgstr "Test" + +#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 +msgid "Test bytes" +msgstr "Testiraj bajtove" + +#: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 +msgid "Mouse Gesture" +msgstr "Gesta miša" + +#: lib/solaar/ui/diversion_rules.py:532 +msgid "Action" +msgstr "Akcija" + +#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 +msgid "Key press" +msgstr "Pritisak tipke" + +#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 +msgid "Mouse scroll" +msgstr "Pomeranje miša" + +#: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 +msgid "Mouse click" +msgstr "Klik mišem" + +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Set" +msgstr "Postavi" + +#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 +msgid "Execute" +msgstr "Izvrši" + +#: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 +msgid "Later" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Insert new rule" +msgstr "Unesi novo pravilo" + +#: lib/solaar/ui/diversion_rules.py:588 lib/solaar/ui/diversion_rules.py:1686 +#: lib/solaar/ui/diversion_rules.py:1790 lib/solaar/ui/diversion_rules.py:1989 +msgid "Delete" +msgstr "Obriši" + +#: lib/solaar/ui/diversion_rules.py:610 +msgid "Negate" +msgstr "Negiraj" + +#: lib/solaar/ui/diversion_rules.py:634 +msgid "Wrap with" +msgstr "Zamotajte sa" + +#: lib/solaar/ui/diversion_rules.py:656 +msgid "Cut" +msgstr "Izreži" + +#: lib/solaar/ui/diversion_rules.py:671 +msgid "Paste" +msgstr "Zalepi" + +#: lib/solaar/ui/diversion_rules.py:683 +msgid "Copy" +msgstr "Kopiraj" + +#: lib/solaar/ui/diversion_rules.py:1063 +msgid "This editor does not support the selected rule component yet." +msgstr "Ovaj tekstualni uređivač još uvek ne podržava izabranu komponentu " + "pravila." + +#: lib/solaar/ui/diversion_rules.py:1138 +msgid "Number of seconds to delay." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1177 +msgid "Not" +msgstr "Ne" + +#: lib/solaar/ui/diversion_rules.py:1187 +msgid "X11 active process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1218 +msgid "X11 mouse process. For use in X11 only." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1235 +msgid "MouseProcess" +msgstr "Proces miša" + +#: lib/solaar/ui/diversion_rules.py:1260 +msgid "Feature name of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1308 +msgid "Report number of notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1342 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1383 +msgid "Diverted key or button depressed or released.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1392 +msgid "Key down" +msgstr "Tipka za dolje" + +#: lib/solaar/ui/diversion_rules.py:1395 +msgid "Key up" +msgstr "Tipka za gore" + +#: lib/solaar/ui/diversion_rules.py:1436 +msgid "Diverted key or button is currently down.\n" + "Use the Key/Button Diversion and Divert G Keys settings to divert " + "keys and buttons." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1475 +msgid "Test condition on notification triggering rule processing." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1479 +msgid "Parameter" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:1542 +msgid "begin (inclusive)" +msgstr "početak (uključiv)" + +#: lib/solaar/ui/diversion_rules.py:1543 +msgid "end (exclusive)" +msgstr "završetak (isključiv)" + +#: lib/solaar/ui/diversion_rules.py:1552 +msgid "range" +msgstr "domet" + +#: lib/solaar/ui/diversion_rules.py:1554 +msgid "minimum" +msgstr "minimum" + +#: lib/solaar/ui/diversion_rules.py:1555 +msgid "maximum" +msgstr "maximum" + +#: lib/solaar/ui/diversion_rules.py:1557 #, python-format -msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" -msgstr "bajtovi %(0)d do %(1)d, u dometu od %(2)d do %(3)d" +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "bajtovi %(0)d do %(1)d, u dometu od %(2)d do %(3)d" -#: lib/solaar/ui/diversion_rules.py:1434 -msgid "mask" -msgstr "mask" +#: lib/solaar/ui/diversion_rules.py:1562 +msgid "mask" +msgstr "mask" -#: lib/solaar/ui/diversion_rules.py:1435 +#: lib/solaar/ui/diversion_rules.py:1563 #, python-format -msgid "bytes %(0)d to %(1)d, mask %(2)d" -msgstr "bajtovi %(0)d do %(1)d, maska %(2)d" +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "bajtovi %(0)d do %(1)d, maska %(2)d" -#: lib/solaar/ui/diversion_rules.py:1452 -msgid "type" -msgstr "tip" +#: lib/solaar/ui/diversion_rules.py:1573 +msgid "Bit or range test on bytes in notification message triggering rule " + "processing." +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1535 -msgid "Add action" -msgstr "Dodaj akciju" +#: lib/solaar/ui/diversion_rules.py:1583 +msgid "type" +msgstr "tip" -#: lib/solaar/ui/diversion_rules.py:1628 -msgid "Add key" -msgstr "Dodaj tipku" +#: lib/solaar/ui/diversion_rules.py:1666 +msgid "Mouse gesture with optional initiating button followed by zero or " + "more mouse movements." +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1631 -msgid "Click" -msgstr "Kliknite" +#: lib/solaar/ui/diversion_rules.py:1671 +msgid "Add movement" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1634 -msgid "Depress" -msgstr "Odpustite" +#: lib/solaar/ui/diversion_rules.py:1764 +msgid "Simulate a chorded key click or depress or release.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1637 -msgid "Release" -msgstr "Odpustite" - -#: lib/solaar/ui/diversion_rules.py:1771 -msgid "Button" -msgstr "Taster" +#: lib/solaar/ui/diversion_rules.py:1769 +msgid "Add key" +msgstr "Dodaj tipku" #: lib/solaar/ui/diversion_rules.py:1772 -msgid "Count" -msgstr "Brojilo" +msgid "Click" +msgstr "Kliknite" -#: lib/solaar/ui/diversion_rules.py:1814 -msgid "Add argument" -msgstr "Dodajte argument" +#: lib/solaar/ui/diversion_rules.py:1775 +msgid "Depress" +msgstr "Odpustite" -#: lib/solaar/ui/diversion_rules.py:1888 -msgid "Toggle" -msgstr "Uključi/Isključi" +#: lib/solaar/ui/diversion_rules.py:1778 +msgid "Release" +msgstr "Odpustite" -#: lib/solaar/ui/diversion_rules.py:1888 -msgid "True" -msgstr "Istina" +#: lib/solaar/ui/diversion_rules.py:1861 +msgid "Simulate a mouse scroll.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1889 -msgid "False" -msgstr "Laž" +#: lib/solaar/ui/diversion_rules.py:1918 +msgid "Simulate a mouse click.\n" + "On Wayland requires write access to /dev/uinput." +msgstr "" -#: lib/solaar/ui/diversion_rules.py:1903 -msgid "Unsupported setting" -msgstr "Nepodržana postavka" +#: lib/solaar/ui/diversion_rules.py:1921 +msgid "Button" +msgstr "Taster" -#: lib/solaar/ui/diversion_rules.py:2055 -msgid "Device" -msgstr "Uređaj" +#: lib/solaar/ui/diversion_rules.py:1922 +msgid "Count and Action" +msgstr "" -#: lib/solaar/ui/diversion_rules.py:2061 lib/solaar/ui/diversion_rules.py:2303 -#: lib/solaar/ui/diversion_rules.py:2321 -msgid "Originating device" -msgstr "Izvorni uređaj" +#: lib/solaar/ui/diversion_rules.py:1972 +msgid "Execute a command with arguments." +msgstr "" -#: lib/solaar/ui/diversion_rules.py:2080 -msgid "Value" -msgstr "Vrednost" +#: lib/solaar/ui/diversion_rules.py:1975 +msgid "Add argument" +msgstr "Dodajte argument" -#: lib/solaar/ui/diversion_rules.py:2088 -msgid "Item" -msgstr "Stavka" +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "Toggle" +msgstr "Uključi/Isključi" -#: lib/solaar/ui/notify.py:122 lib/solaar/ui/tray.py:323 -#: lib/solaar/ui/tray.py:328 lib/solaar/ui/window.py:746 -msgid "offline" -msgstr "ugašeno" +#: lib/solaar/ui/diversion_rules.py:2050 +msgid "True" +msgstr "Istina" -#: lib/solaar/ui/pair_window.py:121 lib/solaar/ui/pair_window.py:255 -#: lib/solaar/ui/pair_window.py:277 +#: lib/solaar/ui/diversion_rules.py:2051 +msgid "False" +msgstr "Laž" + +#: lib/solaar/ui/diversion_rules.py:2065 +msgid "Unsupported setting" +msgstr "Nepodržana postavka" + +#: lib/solaar/ui/diversion_rules.py:2223 lib/solaar/ui/diversion_rules.py:2242 +#: lib/solaar/ui/diversion_rules.py:2328 lib/solaar/ui/diversion_rules.py:2570 +#: lib/solaar/ui/diversion_rules.py:2588 +msgid "Originating device" +msgstr "Izvorni uređaj" + +#: lib/solaar/ui/diversion_rules.py:2256 +msgid "Device is active and its settings can be changed." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2266 +msgid "Device that originated the current notification." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2280 +msgid "Name of host computer." +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2347 +msgid "Value" +msgstr "Vrednost" + +#: lib/solaar/ui/diversion_rules.py:2355 +msgid "Item" +msgstr "Stavka" + +#: lib/solaar/ui/diversion_rules.py:2630 +msgid "Change setting on device" +msgstr "" + +#: lib/solaar/ui/diversion_rules.py:2647 +msgid "Setting on device" +msgstr "" + +#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:318 +#: lib/solaar/ui/tray.py:323 lib/solaar/ui/window.py:739 +msgid "offline" +msgstr "ugašeno" + +#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 +#: lib/solaar/ui/pair_window.py:288 #, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s: uparite novi uređaj" +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: uparite novi uređaj" -#: lib/solaar/ui/pair_window.py:122 +#: lib/solaar/ui/pair_window.py:123 #, python-format -msgid "Enter passcode on %(name)s." -msgstr "Unesite lozinku na %(name)s." +msgid "Enter passcode on %(name)s." +msgstr "Unesite lozinku na %(name)s." -#: lib/solaar/ui/pair_window.py:125 +#: lib/solaar/ui/pair_window.py:126 #, python-format -msgid "Type %(passcode)s and then press the enter key." -msgstr "Unesite %(passcode)s, a zatim pritisnite tipku enter." +msgid "Type %(passcode)s and then press the enter key." +msgstr "Unesite %(passcode)s, a zatim pritisnite tipku enter." -#: lib/solaar/ui/pair_window.py:128 -msgid "left" -msgstr "levo" +#: lib/solaar/ui/pair_window.py:129 +msgid "left" +msgstr "levo" -#: lib/solaar/ui/pair_window.py:128 -msgid "right" -msgstr "desno" +#: lib/solaar/ui/pair_window.py:129 +msgid "right" +msgstr "desno" -#: lib/solaar/ui/pair_window.py:130 +#: lib/solaar/ui/pair_window.py:131 #, python-format -msgid "" -"Press %(code)s\n" -"and then press left and right buttons simultaneously." -msgstr "Pritisnite %(code)s\n" -"a zatim istovremeno pritisnite levo i desno dugme." +msgid "Press %(code)s\n" + "and then press left and right buttons simultaneously." +msgstr "Pritisnite %(code)s\n" + "a zatim istovremeno pritisnite levo i desno dugme." -#: lib/solaar/ui/pair_window.py:187 -msgid "Pairing failed" -msgstr "Uparivanje neuspelo" +#: lib/solaar/ui/pair_window.py:188 +msgid "Pairing failed" +msgstr "Uparivanje neuspelo" -#: lib/solaar/ui/pair_window.py:189 -msgid "Make sure your device is within range, and has a decent battery charge." -msgstr "Uverite se da je vaš uređaj u dometu i da ima pristojno punjenje baterije." +#: lib/solaar/ui/pair_window.py:190 +msgid "Make sure your device is within range, and has a decent battery " + "charge." +msgstr "Uverite se da je vaš uređaj u dometu i da ima pristojno punjenje " + "baterije." -#: lib/solaar/ui/pair_window.py:191 -msgid "A new device was detected, but it is not compatible with this receiver." -msgstr "Otkriven je novi uređaj, ali nije kompatibilan sa ovim prijemnikom." +#: lib/solaar/ui/pair_window.py:192 +msgid "A new device was detected, but it is not compatible with this " + "receiver." +msgstr "Otkriven je novi uređaj, ali nije kompatibilan sa ovim prijemnikom." -#: lib/solaar/ui/pair_window.py:193 -msgid "More paired devices than receiver can support." -msgstr "Više uparenih uređaja nego što prijemnik može da podrži." +#: lib/solaar/ui/pair_window.py:194 +msgid "More paired devices than receiver can support." +msgstr "Više uparenih uređaja nego što prijemnik može da podrži." -#: lib/solaar/ui/pair_window.py:195 -msgid "No further details are available about the error." -msgstr "Nisu dostupni dodatni detalji o grešci." +#: lib/solaar/ui/pair_window.py:196 +msgid "No further details are available about the error." +msgstr "Nisu dostupni dodatni detalji o grešci." -#: lib/solaar/ui/pair_window.py:209 -msgid "Found a new device:" -msgstr "Pronađen novi uređaj:" +#: lib/solaar/ui/pair_window.py:210 +msgid "Found a new device:" +msgstr "Pronađen novi uređaj:" -#: lib/solaar/ui/pair_window.py:234 -msgid "The wireless link is not encrypted" -msgstr "Bežična veza nije šifrovana" +#: lib/solaar/ui/pair_window.py:235 +msgid "The wireless link is not encrypted" +msgstr "Bežična veza nije šifrovana" -#: lib/solaar/ui/pair_window.py:263 -msgid "Press a pairing button or key until the pairing light flashes quickly." -msgstr "Pritisnite dugme ili taster za uparivanje dok lampica za uparivanje ne počne brzo da treperi." +#: lib/solaar/ui/pair_window.py:264 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "" -#: lib/solaar/ui/pair_window.py:265 -msgid "You may have to first turn the device off and on again." -msgstr "Možda ćete morati prvo da isključite i ponovo uključite uređaj." +#: lib/solaar/ui/pair_window.py:266 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "" -#: lib/solaar/ui/pair_window.py:267 -msgid "Turn on the device you want to pair." -msgstr "Uključite uređaj koji želite da uparite." +#: lib/solaar/ui/pair_window.py:268 +msgid "Other receivers are only compatible with a few devices." +msgstr "" -#: lib/solaar/ui/pair_window.py:269 -msgid "If the device is already turned on, turn it off and on again." -msgstr "Ako je uređaj već uključen, isključite ga i ponovo uključite." +#: lib/solaar/ui/pair_window.py:270 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" -#: lib/solaar/ui/pair_window.py:272 +#: lib/solaar/ui/pair_window.py:274 +msgid "Press a pairing button or key until the pairing light flashes " + "quickly." +msgstr "Pritisnite dugme ili taster za uparivanje dok lampica za uparivanje " + "ne počne brzo da treperi." + +#: lib/solaar/ui/pair_window.py:276 +msgid "You may have to first turn the device off and on again." +msgstr "Možda ćete morati prvo da isključite i ponovo uključite uređaj." + +#: lib/solaar/ui/pair_window.py:278 +msgid "Turn on the device you want to pair." +msgstr "Uključite uređaj koji želite da uparite." + +#: lib/solaar/ui/pair_window.py:280 +msgid "If the device is already turned on, turn it off and on again." +msgstr "Ako je uređaj već uključen, isključite ga i ponovo uključite." + +#: lib/solaar/ui/pair_window.py:283 #, python-format -msgid "" -"\n" -"\n" -"This receiver has %d pairing remaining." -msgid_plural "" -"\n" -"\n" -"This receiver has %d pairings remaining." -msgstr[0] "" -"\n" -"\n" -"Ovaj prijemnik ima još %d uparivanje." -msgstr[1] "" -"\n" -"\n" -"Ovaj prijemnik ima još %d uparivanja." +msgid "\n" + "\n" + "This receiver has %d pairing remaining." +msgid_plural "\n" + "\n" + "This receiver has %d pairings remaining." +msgstr[0] "\n" + "\n" + "Ovaj prijemnik ima još %d uparivanje." +msgstr[1] "\n" + "\n" + "Ovaj prijemnik ima još %d uparivanja." -#: lib/solaar/ui/pair_window.py:275 -msgid "" -"\n" -"Cancelling at this point will not use up a pairing." -msgstr "" -"\n" -"Otkazivanje u ovom trenutku neće potrošiti uparivanje." +#: lib/solaar/ui/pair_window.py:286 +msgid "\n" + "Cancelling at this point will not use up a pairing." +msgstr "\n" + "Otkazivanje u ovom trenutku neće potrošiti uparivanje." #: lib/solaar/ui/tray.py:58 -msgid "No Logitech device found" -msgstr "Nije pronađen nijedan Logitech uređaj" +msgid "No supported device found" +msgstr "" -#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:324 +#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 #, python-format -msgid "About %s" -msgstr "O %s" +msgid "About %s" +msgstr "O %s" -#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:322 +#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 #, python-format -msgid "Quit %s" -msgstr "Zatvori %s" +msgid "Quit %s" +msgstr "Zatvori %s" -#: lib/solaar/ui/tray.py:302 lib/solaar/ui/tray.py:310 -msgid "no receiver" -msgstr "nema prijemnika" +#: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 +msgid "no receiver" +msgstr "nema prijemnika" -#: lib/solaar/ui/tray.py:326 -msgid "no status" -msgstr "nema statusa" +#: lib/solaar/ui/tray.py:321 +msgid "no status" +msgstr "nema statusa" -#: lib/solaar/ui/window.py:101 -msgid "Scanning" -msgstr "Skeniranje" +#: lib/solaar/ui/window.py:96 +msgid "Scanning" +msgstr "Skeniranje" -#: lib/solaar/ui/window.py:134 lib/solaar/ui/window.py:687 -msgid "Battery" -msgstr "Baterija" +#: lib/solaar/ui/window.py:129 +msgid "Battery" +msgstr "Baterija" -#: lib/solaar/ui/window.py:137 -msgid "Wireless Link" -msgstr "Bežična veza" +#: lib/solaar/ui/window.py:132 +msgid "Wireless Link" +msgstr "Bežična veza" -#: lib/solaar/ui/window.py:141 -msgid "Lighting" -msgstr "Osvetljenje" +#: lib/solaar/ui/window.py:136 +msgid "Lighting" +msgstr "Osvetljenje" -#: lib/solaar/ui/window.py:175 -msgid "Show Technical Details" -msgstr "Prikaži tehničke detalje" +#: lib/solaar/ui/window.py:170 +msgid "Show Technical Details" +msgstr "Prikaži tehničke detalje" -#: lib/solaar/ui/window.py:191 -msgid "Pair new device" -msgstr "Uparite novi uređaj" +#: lib/solaar/ui/window.py:186 +msgid "Pair new device" +msgstr "Uparite novi uređaj" -#: lib/solaar/ui/window.py:210 -msgid "Select a device" -msgstr "Odaberite uređaj" +#: lib/solaar/ui/window.py:205 +msgid "Select a device" +msgstr "Odaberite uređaj" -#: lib/solaar/ui/window.py:327 -msgid "Rule Editor" -msgstr "Tekstualni uređivač prava" +#: lib/solaar/ui/window.py:322 +msgid "Rule Editor" +msgstr "Tekstualni uređivač prava" -#: lib/solaar/ui/window.py:538 -msgid "Path" -msgstr "Put" +#: lib/solaar/ui/window.py:533 +msgid "Path" +msgstr "Put" -#: lib/solaar/ui/window.py:541 -msgid "USB ID" -msgstr "USB ID" +#: lib/solaar/ui/window.py:536 +msgid "USB ID" +msgstr "USB ID" -#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 -#: lib/solaar/ui/window.py:566 lib/solaar/ui/window.py:568 -msgid "Serial" -msgstr "Serijski" +#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 +msgid "Serial" +msgstr "Serijski" -#: lib/solaar/ui/window.py:550 -msgid "Index" -msgstr "Indeks" +#: lib/solaar/ui/window.py:545 +msgid "Index" +msgstr "Indeks" -#: lib/solaar/ui/window.py:552 -msgid "Wireless PID" -msgstr "Wireless PID" +#: lib/solaar/ui/window.py:547 +msgid "Wireless PID" +msgstr "Wireless PID" + +#: lib/solaar/ui/window.py:549 +msgid "Product ID" +msgstr "Product ID" + +#: lib/solaar/ui/window.py:551 +msgid "Protocol" +msgstr "Protokol" + +#: lib/solaar/ui/window.py:551 +msgid "Unknown" +msgstr "Nepoznato" #: lib/solaar/ui/window.py:554 -msgid "Product ID" -msgstr "Product ID" - -#: lib/solaar/ui/window.py:556 -msgid "Protocol" -msgstr "Protokol" - -#: lib/solaar/ui/window.py:556 -msgid "Unknown" -msgstr "Nepoznato" - -#: lib/solaar/ui/window.py:559 #, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d ms (%(rate_hz)dHz)" +msgid "%(rate)d ms (%(rate_hz)dHz)" +msgstr "%(rate)d ms (%(rate_hz)dHz)" -#: lib/solaar/ui/window.py:559 -msgid "Polling rate" -msgstr "Brzina pozivanja" +#: lib/solaar/ui/window.py:554 +msgid "Polling rate" +msgstr "Brzina pozivanja" -#: lib/solaar/ui/window.py:570 -msgid "Unit ID" -msgstr "Unit ID" +#: lib/solaar/ui/window.py:565 +msgid "Unit ID" +msgstr "Unit ID" -#: lib/solaar/ui/window.py:581 -msgid "none" -msgstr "nema" +#: lib/solaar/ui/window.py:576 +msgid "none" +msgstr "nema" -#: lib/solaar/ui/window.py:582 -msgid "Notifications" -msgstr "Obaveštenja" +#: lib/solaar/ui/window.py:577 +msgid "Notifications" +msgstr "Obaveštenja" -#: lib/solaar/ui/window.py:619 -msgid "No device paired." -msgstr "Nijedan uređaj nije uparen." +#: lib/solaar/ui/window.py:621 +msgid "No device paired." +msgstr "Nijedan uređaj nije uparen." -#: lib/solaar/ui/window.py:626 +#: lib/solaar/ui/window.py:628 #, python-format -msgid "Up to %(max_count)s device can be paired to this receiver." -msgid_plural "Up to %(max_count)s devices can be paired to this receiver." -msgstr[0] "Do %(max_count)s uređaj može da se upari sa ovim prijemnikom." -msgstr[1] "Do %(max_count)s uređaja može da se upari sa ovim prijemnikom." +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "Do %(max_count)s uređaj može da se upari sa ovim prijemnikom." +msgstr[1] "Do %(max_count)s uređaja može da se upari sa ovim " + "prijemnikom." -#: lib/solaar/ui/window.py:632 -msgid "Only one device can be paired to this receiver." -msgstr "Samo jedan uređaj može biti uparen sa ovim prijemnikom." +#: lib/solaar/ui/window.py:634 +msgid "Only one device can be paired to this receiver." +msgstr "Samo jedan uređaj može biti uparen sa ovim prijemnikom." -#: lib/solaar/ui/window.py:636 +#: lib/solaar/ui/window.py:638 #, python-format -msgid "This receiver has %d pairing remaining." -msgid_plural "This receiver has %d pairings remaining." -msgstr[0] "Ovaj prijemnik ima još %d uparivanje." -msgstr[1] "Ovaj prijemnik ima još %d uparivanja." +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "Ovaj prijemnik ima još %d uparivanje." +msgstr[1] "Ovaj prijemnik ima još %d uparivanja." -#: lib/solaar/ui/window.py:688 -msgid "unknown" -msgstr "nepoznato" +#: lib/solaar/ui/window.py:692 +msgid "Battery Voltage" +msgstr "Voltaža baterije" -#: lib/solaar/ui/window.py:689 -msgid "Battery information unknown." -msgstr "Informacije o bateriji nepoznate." +#: lib/solaar/ui/window.py:694 +msgid "Voltage reported by battery" +msgstr "Napon koji javlja baterija" -#: lib/solaar/ui/window.py:699 -msgid "Battery Voltage" -msgstr "Voltaža baterije" +#: lib/solaar/ui/window.py:696 +msgid "Battery Level" +msgstr "Nivo baterije" -#: lib/solaar/ui/window.py:701 -msgid "Voltage reported by battery" -msgstr "Napon koji javlja baterija" +#: lib/solaar/ui/window.py:698 +msgid "Approximate level reported by battery" +msgstr "Baterija prijavljuje približan nivo" -#: lib/solaar/ui/window.py:703 -msgid "Battery Level" -msgstr "Nivo baterije" +#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 +msgid "next reported " +msgstr "sledeći izveštaj " -#: lib/solaar/ui/window.py:705 -msgid "Approximate level reported by battery" -msgstr "Baterija prijavljuje približan nivo" +#: lib/solaar/ui/window.py:708 +msgid " and next level to be reported." +msgstr " i sledeći nivo koji treba izvesti." -#: lib/solaar/ui/window.py:712 lib/solaar/ui/window.py:714 -msgid "next reported " -msgstr "sledeći izveštaj " +#: lib/solaar/ui/window.py:713 +msgid "last known" +msgstr "poslednje poznato" -#: lib/solaar/ui/window.py:715 -msgid " and next level to be reported." -msgstr " i sledeći nivo koji treba izvesti." +#: lib/solaar/ui/window.py:724 +msgid "encrypted" +msgstr "šifrovano" -#: lib/solaar/ui/window.py:720 -msgid "last known" -msgstr "poslednje poznato" +#: lib/solaar/ui/window.py:726 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "Bežična veza između ovog uređaja i njegovog prijemnika je šifrovana." #: lib/solaar/ui/window.py:728 -msgid "not encrypted" -msgstr "nije šifrovano" +msgid "not encrypted" +msgstr "nije šifrovano" #: lib/solaar/ui/window.py:732 -msgid "" -"The wireless link between this device and its receiver is not encrypted.\n" -"\n" -"For pointing devices (mice, trackballs, trackpads), this is a minor security " -"issue.\n" -"\n" -"It is, however, a major security issue for text-input devices (keyboards, " -"numpads),\n" -"because typed text can be sniffed inconspicuously by 3rd parties within " -"range." -msgstr "" -"Bežična veza između ovog uređaja i njegovog prijemnika nije šifrovana.\n" -"\n" -"Za pokazivačke uređaje (miševi, kuglice za praćenje, dodirne table) ovo je manji bezbednosni " -"problem.\n" -"Međutim, to je glavni bezbednosni problem za uređaje za unos teksta (tastature, " -"numeričke podloge),\n" -"jer otkucani tekst mogu neprimetno da nanjuše treće strane " -"u dometu" +msgid "The wireless link between this device and its receiver is not " + "encrypted.\n" + "This is a security issue for pointing devices, and a major security " + "issue for text-input devices." +msgstr "" -#: lib/solaar/ui/window.py:741 -msgid "encrypted" -msgstr "šifrovano" - -#: lib/solaar/ui/window.py:743 -msgid "The wireless link between this device and its receiver is encrypted." -msgstr "Bežična veza između ovog uređaja i njegovog prijemnika je šifrovana." - -#: lib/solaar/ui/window.py:756 +#: lib/solaar/ui/window.py:748 #, python-format -msgid "%(light_level)d lux" -msgstr "%(light_level)d lux" +msgid "%(light_level)d lux" +msgstr "%(light_level)d lux" + +#~ msgid "Add action" +#~ msgstr "Dodaj akciju" + +#~ msgid "Adjust the DPI by sliding the mouse horizontally while " +#~ "holding the button down." +#~ msgstr "Podesite DPI pomeranjem miša horizontalno dok držite " +#~ "tasterza dolje." + +#~ msgid "Automatically switch the mouse wheel between ratchet and " +#~ "freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always ratcheted at 50" +#~ msgstr "Automatski prebacite točkić miša između režima ratchet i " +#~ "slobodnog načina. \n" +#~ "Točak miša je uvek slobodan na 0, i uvek zaključan na 50" + +#~ msgid "Battery information unknown." +#~ msgstr "Informacije o bateriji nepoznate." + +#~ msgid "Count" +#~ msgstr "Brojilo" + +#~ msgid "DPI Sliding Adjustment" +#~ msgstr "DPI klizno podešavanje" + +#, python-format +#~ msgid "Found a Logitech Receiver (%s), but did not have permission " +#~ "to open it." +#~ msgstr "Pronašao sam Logitech prijemnik (%s), ali nisam imao dozvolu " +#~ "da ga otvorim." + +#~ msgid "If you've just installed Solaar, try removing the receiver " +#~ "and plugging it back in." +#~ msgstr "Ako ste upravo instalirali Solaar, pokušajte da odspojite " +#~ "prijemnik i priključite gaponovo." + +#~ msgid "No Logitech device found" +#~ msgstr "Nije pronađen nijedan Logitech uređaj" + +#~ msgid "Scroll Wheel Rachet" +#~ msgstr "Ustavljanje pomicanja kotačića" + +#~ msgid "Send a gesture by sliding the mouse while holding the button " +#~ "down." +#~ msgstr "Pošaljite pokret prevlačenjem miša dok držite dugme za dole." + +#~ msgid "The wireless link between this device and its receiver is " +#~ "not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices " +#~ "(keyboards, numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties " +#~ "within range." +#~ msgstr "Bežična veza između ovog uređaja i njegovog prijemnika nije " +#~ "šifrovana.\n" +#~ "\n" +#~ "Za pokazivačke uređaje (miševi, kuglice za praćenje, dodirne table) " +#~ "ovo je manji bezbednosni problem.\n" +#~ "Međutim, to je glavni bezbednosni problem za uređaje za unos teksta " +#~ "(tastature, numeričke podloge),\n" +#~ "jer otkucani tekst mogu neprimetno da nanjuše treće strane u dometu" + +#~ msgid "Try removing the device and plugging it back in or turning " +#~ "it off and then on." +#~ msgstr "Pokušajte da odspojite uređaj i ponovo ga uključite ili " +#~ "isključite, a zatimupalite." + +#~ msgid "unknown" +#~ msgstr "nepoznato" diff --git a/po/sv.po b/po/sv.po index 2497e0fe..ca5fc9fb 100644 --- a/po/sv.po +++ b/po/sv.po @@ -3,1519 +3,2415 @@ # This file is distributed under the same license as the solaar package. # Automatically generated, 2013. # -msgid "" -msgstr "Project-Id-Version: solaar 1.0.3\n" - "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2021-09-25 13:56-0400\n" - "PO-Revision-Date: 2020-08-03 03:10+0200\n" - "Last-Translator: John Erling Blad \n" - "Language-Team: none\n" - "Language: sv\n" - "MIME-Version: 1.0\n" - "Content-Type: text/plain; charset=UTF-8\n" - "Content-Transfer-Encoding: 8bit\n" - "Plural-Forms: nplurals=2; plural=(n != 1);\n" - "X-Generator: Poedit 2.3\n" - "X-Poedit-Basepath: /pwr/Solaar/po\n" - "X-Poedit-SearchPath-0: /pwr/Solaar/po\n" +msgid "" +msgstr "" +"Project-Id-Version: solaar 1.0.3\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-01 21:43+0300\n" +"PO-Revision-Date: 2026-04-21 21:46+0300\n" +"Last-Translator: Karl Jonatan Nyberg\n" +"Language-Team: none\n" +"Language: sv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-Basepath: /pwr/Solaar/po\n" +"X-Generator: Poedit 3.6\n" +"X-Poedit-SearchPath-0: /pwr/Solaar/po\n" -#: lib/logitech_receiver/base_usb.py:50 -msgid "Unifying Receiver" -msgstr "" +#: lib/logitech_receiver/base_usb.py:52 +msgid "Bolt Receiver" +msgstr "Bolt-mottagare" -#: lib/logitech_receiver/base_usb.py:58 lib/logitech_receiver/base_usb.py:68 -#: lib/logitech_receiver/base_usb.py:79 lib/logitech_receiver/base_usb.py:90 -#: lib/logitech_receiver/base_usb.py:101 -msgid "Nano Receiver" -msgstr "" +#: lib/logitech_receiver/base_usb.py:64 +msgid "Unifying Receiver" +msgstr "Unifying-mottagare" -#: lib/logitech_receiver/base_usb.py:109 -msgid "Lightspeed Receiver" -msgstr "" +#: lib/logitech_receiver/base_usb.py:75 lib/logitech_receiver/base_usb.py:87 +#: lib/logitech_receiver/base_usb.py:100 lib/logitech_receiver/base_usb.py:113 +msgid "Nano Receiver" +msgstr "Nano-mottagare" -#: lib/logitech_receiver/base_usb.py:117 -msgid "EX100 Receiver 27 Mhz" -msgstr "" +#: lib/logitech_receiver/base_usb.py:125 +msgid "Lightspeed Receiver" +msgstr "Lightspeed-mottagare" -#: lib/logitech_receiver/device.py:133 lib/solaar/ui/window.py:693 -msgid "unknown" -msgstr "okänd" +#: lib/logitech_receiver/base_usb.py:135 +msgid "EX100 Receiver 27 Mhz" +msgstr "EX100-mottagare 27 MHz" + +#: lib/logitech_receiver/common.py:649 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Batteri: %(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:652 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Batteri: %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:1048 +#: lib/logitech_receiver/settings_templates.py:299 +msgid "Disabled" +msgstr "Inaktiverad" + +#: lib/logitech_receiver/hidpp20.py:1049 +msgid "Static" +msgstr "Statisk" + +#: lib/logitech_receiver/hidpp20.py:1050 +msgid "Pulse" +msgstr "Pulsera" + +#: lib/logitech_receiver/hidpp20.py:1051 +msgid "Cycle" +msgstr "Växla" + +#: lib/logitech_receiver/hidpp20.py:1052 +msgid "Boot" +msgstr "Boot" + +#: lib/logitech_receiver/hidpp20.py:1053 +msgid "Demo" +msgstr "Demo" + +#: lib/logitech_receiver/hidpp20.py:1055 +msgid "Breathe" +msgstr "Andas" + +#: lib/logitech_receiver/hidpp20.py:1058 +msgid "Ripple" +msgstr "Krusning" + +#: lib/logitech_receiver/hidpp20.py:1059 +msgid "Decomposition" +msgstr "Nedbrytning" + +#: lib/logitech_receiver/hidpp20.py:1060 +msgid "Signature1" +msgstr "Signatur1" + +#: lib/logitech_receiver/hidpp20.py:1061 +msgid "Signature2" +msgstr "Signatur2" + +#: lib/logitech_receiver/hidpp20.py:1062 +msgid "CycleS" +msgstr "CycleS" + +#: lib/logitech_receiver/hidpp20.py:1126 +msgid "Unknown Location" +msgstr "Okänd plats" + +#: lib/logitech_receiver/hidpp20.py:1127 +msgid "Primary" +msgstr "Primär" + +#: lib/logitech_receiver/hidpp20.py:1128 +msgid "Logo" +msgstr "Logotyp" + +#: lib/logitech_receiver/hidpp20.py:1129 +msgid "Left Side" +msgstr "Vänster sida" + +#: lib/logitech_receiver/hidpp20.py:1130 +msgid "Right Side" +msgstr "Höger sida" + +#: lib/logitech_receiver/hidpp20.py:1131 +msgid "Combined" +msgstr "Kombinerad" + +#: lib/logitech_receiver/hidpp20.py:1132 +msgid "Primary 1" +msgstr "Primär 1" + +#: lib/logitech_receiver/hidpp20.py:1133 +msgid "Primary 2" +msgstr "Primär 2" + +#: lib/logitech_receiver/hidpp20.py:1134 +msgid "Primary 3" +msgstr "Primär 3" + +#: lib/logitech_receiver/hidpp20.py:1135 +msgid "Primary 4" +msgstr "Primär 4" + +#: lib/logitech_receiver/hidpp20.py:1136 +msgid "Primary 5" +msgstr "Primär 5" + +#: lib/logitech_receiver/hidpp20.py:1137 +msgid "Primary 6" +msgstr "Primär 6" + +#: lib/logitech_receiver/i18n.py:28 +msgid "empty" +msgstr "tom" + +#: lib/logitech_receiver/i18n.py:29 +msgid "critical" +msgstr "kritisk" + +#: lib/logitech_receiver/i18n.py:30 +msgid "low" +msgstr "låg" + +#: lib/logitech_receiver/i18n.py:31 +msgid "average" +msgstr "genomsnitt" + +#: lib/logitech_receiver/i18n.py:32 +msgid "good" +msgstr "god" + +#: lib/logitech_receiver/i18n.py:33 +msgid "full" +msgstr "full" + +#: lib/logitech_receiver/i18n.py:35 +msgid "discharging" +msgstr "urladdning" + +#: lib/logitech_receiver/i18n.py:36 +msgid "recharging" +msgstr "uppladdning" + +#: lib/logitech_receiver/i18n.py:37 lib/solaar/ui/window.py:700 +msgid "charging" +msgstr "laddar" #: lib/logitech_receiver/i18n.py:38 -msgid "empty" -msgstr "tom" +msgid "not charging" +msgstr "laddar inte" #: lib/logitech_receiver/i18n.py:39 -msgid "critical" -msgstr "kritisk" +msgid "almost full" +msgstr "nästan full" #: lib/logitech_receiver/i18n.py:40 -msgid "low" -msgstr "låg" +msgid "charged" +msgstr "laddad" #: lib/logitech_receiver/i18n.py:41 -msgid "average" -msgstr "" +msgid "slow recharge" +msgstr "långsam laddning" #: lib/logitech_receiver/i18n.py:42 -msgid "good" -msgstr "normal" +msgid "invalid battery" +msgstr "batterifel" #: lib/logitech_receiver/i18n.py:43 -msgid "full" -msgstr "full" +msgid "thermal error" +msgstr "termiskt fel" + +#: lib/logitech_receiver/i18n.py:44 +msgid "error" +msgstr "fel" + +#: lib/logitech_receiver/i18n.py:45 +msgid "standard" +msgstr "standard" #: lib/logitech_receiver/i18n.py:46 -msgid "discharging" -msgstr "urladdning" +msgid "fast" +msgstr "snabb" #: lib/logitech_receiver/i18n.py:47 -msgid "recharging" -msgstr "uppladdning" - -#: lib/logitech_receiver/i18n.py:48 lib/solaar/ui/window.py:721 -msgid "charging" -msgstr "laddar" +msgid "slow" +msgstr "långsam" #: lib/logitech_receiver/i18n.py:49 -msgid "not charging" -msgstr "" +msgid "device timeout" +msgstr "enheten svarade inte" #: lib/logitech_receiver/i18n.py:50 -msgid "almost full" -msgstr "nästan full" +msgid "device not supported" +msgstr "enheten stöds inte" #: lib/logitech_receiver/i18n.py:51 -msgid "charged" -msgstr "laddad" +msgid "too many devices" +msgstr "för många enheter" #: lib/logitech_receiver/i18n.py:52 -msgid "slow recharge" -msgstr "långsam laddning" +msgid "sequence timeout" +msgstr "tidsgräns för sekvens överskreds" -#: lib/logitech_receiver/i18n.py:53 -msgid "invalid battery" -msgstr "batterifel" - -#: lib/logitech_receiver/i18n.py:54 -msgid "thermal error" -msgstr "termiskt fel" +#: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:555 +msgid "Firmware" +msgstr "Fast programvara" #: lib/logitech_receiver/i18n.py:55 -msgid "error" -msgstr "" +msgid "Bootloader" +msgstr "Startladdare" #: lib/logitech_receiver/i18n.py:56 -msgid "standard" -msgstr "" +msgid "Hardware" +msgstr "Hårdvara" #: lib/logitech_receiver/i18n.py:57 -msgid "fast" -msgstr "" +msgid "Other" +msgstr "Annan" -#: lib/logitech_receiver/i18n.py:58 -msgid "slow" -msgstr "" +#: lib/logitech_receiver/i18n.py:59 +msgid "Left Button" +msgstr "Vänster knapp" + +#: lib/logitech_receiver/i18n.py:60 +msgid "Right Button" +msgstr "Höger knapp" #: lib/logitech_receiver/i18n.py:61 -msgid "device timeout" -msgstr "enheten svarade inte" +msgid "Middle Button" +msgstr "Mittenknapp" #: lib/logitech_receiver/i18n.py:62 -msgid "device not supported" -msgstr "enheten stöds inte" +msgid "Back Button" +msgstr "Tillbakaknapp" #: lib/logitech_receiver/i18n.py:63 -msgid "too many devices" -msgstr "för många enheter" +msgid "Forward Button" +msgstr "Framåtknapp" #: lib/logitech_receiver/i18n.py:64 -msgid "sequence timeout" -msgstr "sekvens timout" +msgid "Mouse Gesture Button" +msgstr "Mus gestknapp" -#: lib/logitech_receiver/i18n.py:67 lib/solaar/ui/window.py:580 -msgid "Firmware" -msgstr "Fastvara" +#: lib/logitech_receiver/i18n.py:65 +msgid "Smart Shift" +msgstr "Smart skift" + +#: lib/logitech_receiver/i18n.py:66 +msgid "DPI Switch" +msgstr "DPI-växel" + +#: lib/logitech_receiver/i18n.py:67 +msgid "Left Tilt" +msgstr "Vänster lutning" #: lib/logitech_receiver/i18n.py:68 -msgid "Bootloader" -msgstr "Starthanterar" +msgid "Right Tilt" +msgstr "Höger lutning" #: lib/logitech_receiver/i18n.py:69 -msgid "Hardware" -msgstr "Hårdvara" +msgid "Left Click" +msgstr "Vänsterklick" #: lib/logitech_receiver/i18n.py:70 -msgid "Other" -msgstr "Annan" +msgid "Right Click" +msgstr "Högerklick" + +#: lib/logitech_receiver/i18n.py:71 +msgid "Mouse Middle Button" +msgstr "Mus mittenknapp" + +#: lib/logitech_receiver/i18n.py:72 +msgid "Mouse Back Button" +msgstr "Mus tillbakaknapp" #: lib/logitech_receiver/i18n.py:73 -msgid "Left Button" -msgstr "" +msgid "Mouse Forward Button" +msgstr "Mus framåtknapp" #: lib/logitech_receiver/i18n.py:74 -msgid "Right Button" -msgstr "" +msgid "Gesture Button Navigation" +msgstr "Gestknapp navigering" #: lib/logitech_receiver/i18n.py:75 -msgid "Middle Button" -msgstr "" +msgid "Mouse Scroll Left Button" +msgstr "Vänster musrullningsknapp" #: lib/logitech_receiver/i18n.py:76 -msgid "Back Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:77 -msgid "Forward Button" -msgstr "" +msgid "Mouse Scroll Right Button" +msgstr "Höger musrullningsknapp" #: lib/logitech_receiver/i18n.py:78 -msgid "Mouse Gesture Button" -msgstr "" +msgid "pressed" +msgstr "nedtryckt" #: lib/logitech_receiver/i18n.py:79 -msgid "Smart Shift" -msgstr "Smart skift" - -#: lib/logitech_receiver/i18n.py:80 -msgid "DPI Switch" -msgstr "" - -#: lib/logitech_receiver/i18n.py:81 -msgid "Left Tilt" -msgstr "" - -#: lib/logitech_receiver/i18n.py:82 -msgid "Right Tilt" -msgstr "" - -#: lib/logitech_receiver/i18n.py:83 -msgid "Left Click" -msgstr "" - -#: lib/logitech_receiver/i18n.py:84 -msgid "Right Click" -msgstr "" - -#: lib/logitech_receiver/i18n.py:85 -msgid "Mouse Middle Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:86 -msgid "Mouse Back Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:87 -msgid "Mouse Forward Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:88 -msgid "Gesture Button Navigation" -msgstr "" - -#: lib/logitech_receiver/i18n.py:89 -msgid "Mouse Scroll Left Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:90 -msgid "Mouse Scroll Right Button" -msgstr "" - -#: lib/logitech_receiver/i18n.py:93 -msgid "pressed" -msgstr "" - -#: lib/logitech_receiver/i18n.py:94 -msgid "released" -msgstr "" - -#: lib/logitech_receiver/notifications.py:76 -msgid "pairing lock is closed" -msgstr "kopplingslåset är stängt" - -#: lib/logitech_receiver/notifications.py:76 -msgid "pairing lock is open" -msgstr "kopplingslåset är öppet" - -#: lib/logitech_receiver/notifications.py:160 lib/solaar/ui/notify.py:123 -msgid "connected" -msgstr "ansluten" - -#: lib/logitech_receiver/notifications.py:160 -msgid "disconnected" -msgstr "" - -#: lib/logitech_receiver/notifications.py:198 lib/solaar/ui/notify.py:121 -msgid "unpaired" -msgstr "inte parkopplad" - -#: lib/logitech_receiver/notifications.py:248 -msgid "powered on" -msgstr "påslagen" - -#: lib/logitech_receiver/settings.py:526 -msgid "register" -msgstr "" - -#: lib/logitech_receiver/settings.py:542 lib/logitech_receiver/settings.py:568 -msgid "feature" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Hand Detection" -msgstr "Handavkänning" - -#: lib/logitech_receiver/settings_templates.py:71 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "Tänd belysning, om en hand hålls över tangentbordet." - -#: lib/logitech_receiver/settings_templates.py:72 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:73 -#: lib/logitech_receiver/settings_templates.py:78 -#: lib/logitech_receiver/settings_templates.py:85 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "Hög känslighet för vertikal rullning med hjulet." - -#: lib/logitech_receiver/settings_templates.py:74 -msgid "Side Scrolling" -msgstr "Siderullning" - -#: lib/logitech_receiver/settings_templates.py:75 -msgid "When disabled, pushing the wheel sideways sends custom button " - "events\n" - "instead of the standard side-scrolling events." -msgstr "Vid avaktivering, kommer tryckningar sidledes på rullhjulet\n" - "fungera som specialknappar istället för standard sidrulling." - -#: lib/logitech_receiver/settings_templates.py:77 -msgid "Scroll Wheel High Resolution" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:79 -msgid "Scroll Wheel Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:80 -msgid "HID++ mode for vertical scroll with the wheel." -msgstr "HID++ läge för vertikal rullning med hjulet." - -#: lib/logitech_receiver/settings_templates.py:81 -msgid "Effectively turns off wheel scrolling in Linux." -msgstr "Stänger effektivt av rullning i Linux." - -#: lib/logitech_receiver/settings_templates.py:82 -msgid "Scroll Wheel Direction" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:83 -msgid "Invert direction for vertical scroll with wheel." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:84 -msgid "Scroll Wheel Resolution" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Frequency of device polling, in milliseconds" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:86 -msgid "Polling Rate (ms)" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:87 -msgid "Swap Fx function" -msgstr "Skifta Fx-funktion" - -#: lib/logitech_receiver/settings_templates.py:88 -msgid "When set, the F1..F12 keys will activate their special function,\n" - "and you must hold the FN key to activate their standard function." -msgstr "Vid aktivering, kommer F1–12-tangenterna få sina specialfunktioner,\n" - "och du behöver trycka ned FN-tangenten för att komma åt dess normala " - "funktioner." - -#: lib/logitech_receiver/settings_templates.py:90 -msgid "When unset, the F1..F12 keys will activate their standard function,\n" - "and you must hold the FN key to activate their special function." -msgstr "Vid avaktivering, kommer F1–12-tangenterna få sina " - "standardfunktioner,\n" - "och du behöver trycka ned FN-tangenten för att komma åt dess " - "spesicalfunktioner." - -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Mouse movement sensitivity" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:92 -msgid "Sensitivity (DPI)" -msgstr "Känslighet (DPI)" - -#: lib/logitech_receiver/settings_templates.py:93 -msgid "Sensitivity (Pointer Speed)" -msgstr "Känslighet (pekarhastighet)" - -#: lib/logitech_receiver/settings_templates.py:94 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "Hastighetsmultiplikator för mus (256 är normal multiplikator)." - -#: lib/logitech_receiver/settings_templates.py:95 -msgid "Scroll Wheel Rachet" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:96 -msgid "Automatically switch the mouse wheel between ratchet and freespin " - "mode.\n" - "The mouse wheel is always free at 0, and always ratcheted at 50" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:98 -msgid "Backlight" -msgstr "Bakbelysning" - -#: lib/logitech_receiver/settings_templates.py:98 -msgid "Turn illumination on or off on keyboard." -msgstr "Slå på eller stäng av belysningen på tangentbordet." - -#: lib/logitech_receiver/settings_templates.py:99 -msgid "Key/Button Actions" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:100 -msgid "Change the action for the key or button." -msgstr "Ändra åtgärden för tangenten eller knappen." - -#: lib/logitech_receiver/settings_templates.py:101 -msgid "Changing important actions (such as for the left mouse button) can " - "result in an unusable system." -msgstr "Ändring av viktiga åtgärder (till exempel för vänster musknapp) kan " - "resultera i ett oanvändbart system." - -#: lib/logitech_receiver/settings_templates.py:102 -msgid "Key/Button Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:103 -msgid "Make the key or button send HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable keys" -msgstr "Inaktivera tangenter" - -#: lib/logitech_receiver/settings_templates.py:104 -msgid "Disable specific keyboard keys." -msgstr "Inaktivera specifika tangentbordstangenter." - -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Change keys to match OS." -msgstr "Byt tangenter för att matcha OS." - -#: lib/logitech_receiver/settings_templates.py:105 -msgid "Set OS" -msgstr "Ställa in OS" - -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Change Host" -msgstr "Byt värd" - -#: lib/logitech_receiver/settings_templates.py:106 -msgid "Switch connection to a different host" -msgstr "Byt ko till en annan värd" - -#: lib/logitech_receiver/settings_templates.py:107 -msgid "Thumb Wheel Diversion" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:108 -msgid "HID++ mode for horizontal scroll with the thumb wheel." -msgstr "HID ++ -läge för horisontell rullning med tumhjulet." - -#: lib/logitech_receiver/settings_templates.py:109 -msgid "Effectively turns off thumb scrolling in Linux." -msgstr "Stänger effektivt av tumrullning i Linux." - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Invert thumb wheel scroll direction." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:110 -msgid "Thumb Wheel Direction" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Gestures" -msgstr "Gester" - -#: lib/logitech_receiver/settings_templates.py:111 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:112 -msgid "Gesture params" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:113 -msgid "DPI Sliding Adjustment" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:114 -msgid "Adjust the DPI by sliding the mouse horizontally while holding the " - "button down." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:115 -msgid "Mouse Gestures" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:116 -msgid "Send a gesture by sliding the mouse while holding the button down." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:117 -msgid "Divert crown events" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:118 -msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:119 -msgid "Divert G Keys" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:120 -msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Performs a left click." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:123 -msgid "Single tap" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Performs a right click." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:124 -msgid "Single tap with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:125 -msgid "Single tap with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Double tap" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:129 -msgid "Performs a double click." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:130 -msgid "Double tap with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:131 -msgid "Double tap with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Drags items by dragging the finger after double tapping." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:134 -msgid "Tap and drag" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:135 -msgid "Tap and drag with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:136 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:137 -msgid "Tap and drag with three fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:139 -msgid "Suppress tap and edge gestures" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:140 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "" +msgid "released" +msgstr "släpptes" + +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 +msgid "connected" +msgstr "ansluten" + +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 +msgid "disconnected" +msgstr "frånkopplad" + +#: lib/logitech_receiver/notifications.py:182 +msgid "unpaired" +msgstr "inte parkopplad" + +#: lib/logitech_receiver/notifications.py:231 +msgid "powered on" +msgstr "påslagen" + +#: lib/logitech_receiver/notifications.py:283 +msgid "ADC measurement notification" +msgstr "ADC-mätningsmeddelande" + +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:483 +msgid "pairing lock is closed" +msgstr "parkopplingslåset är stängt" + +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:483 +msgid "pairing lock is open" +msgstr "parkopplingslåset är öppet" + +#: lib/logitech_receiver/notifications.py:446 +msgid "discovery lock is closed" +msgstr "upptäcktslåset är stängt" + +#: lib/logitech_receiver/notifications.py:446 +msgid "discovery lock is open" +msgstr "upptäcktslåset är öppet" + +#: lib/logitech_receiver/receiver.py:437 +msgid "No paired devices." +msgstr "Inga parkopplade enheter." + +#: lib/logitech_receiver/receiver.py:439 lib/solaar/ui/window.py:606 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s parkopplad enhet." +msgstr[1] "%(count)s parkopplade enheter." + +#: lib/logitech_receiver/settings.py:602 +msgid "register" +msgstr "registrera" + +#: lib/logitech_receiver/settings.py:616 lib/logitech_receiver/settings.py:654 +msgid "feature" +msgstr "funktion" + +#: lib/logitech_receiver/settings_templates.py:138 +msgid "Swap Fx function" +msgstr "Byt Fx-funktion" #: lib/logitech_receiver/settings_templates.py:141 -msgid "Scroll with one finger" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:141 -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scrolls." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:142 -#: lib/logitech_receiver/settings_templates.py:145 -msgid "Scroll with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scroll horizontally with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:143 -msgid "Scrolls horizontally." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scroll vertically with two fingers" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:144 -msgid "Scrolls vertically." -msgstr "" +msgid "" +"When set, the F1..F12 keys will activate their special function,\n" +"and you must hold the FN key to activate their standard function." +msgstr "" +"När de är inställda kommer F1..F12-tangenterna att aktivera sin " +"specialfunktion,\n" +"och du måste hålla ner FN-tangenten för att aktivera deras standardfunktion." #: lib/logitech_receiver/settings_templates.py:146 -msgid "Inverts the scrolling direction." -msgstr "" +msgid "" +"When unset, the F1..F12 keys will activate their standard function,\n" +"and you must hold the FN key to activate their special function." +msgstr "" +"När de inte är inställda kommer F1..F12-tangenterna att aktivera sin " +"standardfunktion,\n" +"och du måste hålla ner FN-tangenten för att aktivera deras specialfunktion." -#: lib/logitech_receiver/settings_templates.py:146 -msgid "Natural scrolling" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:154 +msgid "Hand Detection" +msgstr "Handavkänning" -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Enables the thumbwheel." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:155 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Tänd belysning när händerna hålls över tangentbordet." -#: lib/logitech_receiver/settings_templates.py:147 -msgid "Thumbwheel" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:158 #: lib/logitech_receiver/settings_templates.py:162 -msgid "Swipe from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:159 -msgid "Swipe from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:160 -msgid "Swipe from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:161 -msgid "Swipe from the bottom edge" -msgstr "" +msgid "Scroll Wheel Smooth Scrolling" +msgstr "Rullningshjul mjuk rullning" #: lib/logitech_receiver/settings_templates.py:163 -msgid "Swipe two fingers from the left edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:164 -msgid "Swipe two fingers from the right edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:165 -msgid "Swipe two fingers from the bottom edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:166 -msgid "Swipe two fingers from the top edge" -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:167 -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:167 -msgid "Zoom with two fingers." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:168 -msgid "Pinch to zoom out." -msgstr "" - -#: lib/logitech_receiver/settings_templates.py:169 -msgid "Spread to zoom in." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:410 +#: lib/logitech_receiver/settings_templates.py:439 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Högkänsligt läge för vertikal rullning med hjulet." #: lib/logitech_receiver/settings_templates.py:170 -msgid "Zoom with three fingers." -msgstr "" +msgid "Side Scrolling" +msgstr "Sidrullning" -#: lib/logitech_receiver/settings_templates.py:171 -msgid "Zoom with two fingers" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:172 +msgid "" +"When disabled, pushing the wheel sideways sends custom button events\n" +"instead of the standard side-scrolling events." +msgstr "" +"Vid inaktivering skickas anpassade knapphändelser om du trycker på hjulet i " +"sidled\n" +"istället för standardhändelserna för sidrullning." -#: lib/logitech_receiver/settings_templates.py:189 -msgid "Pixel zone" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:182 +msgid "Sensitivity (DPI - older mice)" +msgstr "Känslighet (DPI - äldre möss)" -#: lib/logitech_receiver/settings_templates.py:190 -msgid "Ratio zone" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:183 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +msgid "Mouse movement sensitivity" +msgstr "Musrörelsekänslighet" -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Scale factor" -msgstr "Skalfaktor" +#: lib/logitech_receiver/settings_templates.py:256 +msgid "Backlight Timed" +msgstr "Bakgrundsbelysning tidsinställd" -#: lib/logitech_receiver/settings_templates.py:191 -msgid "Sets the cursor speed." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:257 +#: lib/logitech_receiver/settings_templates.py:397 +msgid "Set illumination time for keyboard." +msgstr "Ställ in belysningstid för tangentbordet." -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Backlight" +msgstr "Bakbelysning" -#: lib/logitech_receiver/settings_templates.py:195 -msgid "Left-most coordinate." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:269 +msgid "" +"Illumination level on keyboard. Changes made are only applied in Manual " +"mode." +msgstr "" +"Belysningsnivå på tangentbordet. Ändringar som görs tillämpas endast i " +"manuellt läge." -#: lib/logitech_receiver/settings_templates.py:196 -msgid "Top-most coordinate." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Automatic" +msgstr "Automatiskt" -#: lib/logitech_receiver/settings_templates.py:196 -msgid "top" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:303 +msgid "Manual" +msgstr "Manuellt" -#: lib/logitech_receiver/settings_templates.py:197 -msgid "Width." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:305 +msgid "Enabled" +msgstr "Aktiverad" -#: lib/logitech_receiver/settings_templates.py:197 -msgid "width" -msgstr "bredd" +#: lib/logitech_receiver/settings_templates.py:311 +msgid "Backlight Level" +msgstr "Bakgrundsbelysningsnivå" -#: lib/logitech_receiver/settings_templates.py:198 -msgid "Height." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:312 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "Belysningsnivå på tangentbordet i manuellt läge." -#: lib/logitech_receiver/settings_templates.py:198 -msgid "height" -msgstr "höjd" +#: lib/logitech_receiver/settings_templates.py:369 +msgid "Backlight Delay Hands Out" +msgstr "Fördröjning av bakgrundsbelysning händer ute" -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Cursor speed." -msgstr "" +#: lib/logitech_receiver/settings_templates.py:370 +msgid "" +"Delay in seconds until backlight fades out with hands away from keyboard." +msgstr "" +"Fördröjning i sekunder tills bakgrundsbelysningen tonar ut när händerna tas " +"bort från tangentbordet." -#: lib/logitech_receiver/settings_templates.py:199 -msgid "Scale" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:378 +msgid "Backlight Delay Hands In" +msgstr "Fördröjning av bakgrundsbelysning händer inne" -#: lib/logitech_receiver/settings_templates.py:202 +#: lib/logitech_receiver/settings_templates.py:379 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "" +"Fördröjning i sekunder tills bakgrundsbelysningen tonar ut när händerna är " +"nära tangentbordet." + +#: lib/logitech_receiver/settings_templates.py:387 +msgid "Backlight Delay Powered" +msgstr "Fördröjning av bakgrundsbelysning extern ström" + +#: lib/logitech_receiver/settings_templates.py:388 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "" +"Fördröjning i sekunder tills bakgrundsbelysningen tonar ut med extern " +"strömförsörjning." + +#: lib/logitech_receiver/settings_templates.py:396 +msgid "Backlight (Seconds)" +msgstr "Bakgrundsbelysning (sekunder)" + +#: lib/logitech_receiver/settings_templates.py:408 +msgid "Scroll Wheel High Resolution" +msgstr "Rullningshjul hög upplösning" + +#: lib/logitech_receiver/settings_templates.py:412 +#: lib/logitech_receiver/settings_templates.py:441 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "Ställ in att ignorera om rullningen är onormalt snabb eller långsam" + +#: lib/logitech_receiver/settings_templates.py:419 +#: lib/logitech_receiver/settings_templates.py:450 +msgid "Scroll Wheel Diversion" +msgstr "Rullhjulsavledning" + +#: lib/logitech_receiver/settings_templates.py:421 +msgid "" +"Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " +"Solaar rules but are otherwise ignored)." +msgstr "" +"Få rullhjulet att skicka LOWRES_WHEEL HID++-aviseringar (som utlöser Solaar-" +"regler men som annars ignoreras)." + +#: lib/logitech_receiver/settings_templates.py:428 +msgid "Scroll Wheel Direction" +msgstr "Rullhjulets riktning" + +#: lib/logitech_receiver/settings_templates.py:429 +msgid "Invert direction for vertical scroll with wheel." +msgstr "Invertera riktning för vertikal rullning med hjul." + +#: lib/logitech_receiver/settings_templates.py:437 +msgid "Scroll Wheel Resolution" +msgstr "Rullhjulsupplösning" + +#: lib/logitech_receiver/settings_templates.py:452 +msgid "" +"Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"Få rullhjulet att skicka HIRES_WHEEL HID++-aviseringar (som utlöser Solaar-" +"regler men som annars ignoreras)." + +#: lib/logitech_receiver/settings_templates.py:461 +msgid "Sensitivity (Pointer Speed)" +msgstr "Känslighet (pekarhastighet)" + +#: lib/logitech_receiver/settings_templates.py:462 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Hastighetsmultiplikator för mus (256 är normal multiplikator)." + +#: lib/logitech_receiver/settings_templates.py:472 +msgid "Thumb Wheel Diversion" +msgstr "Tumhjulsavledning" + +#: lib/logitech_receiver/settings_templates.py:474 +msgid "" +"Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"Få tumhjulet att skicka THUMB_WHEEL HID++-aviseringar (som utlöser Solaar-" +"regler men som annars ignoreras)." + +#: lib/logitech_receiver/settings_templates.py:483 +msgid "Thumb Wheel Direction" +msgstr "Tumhjulets riktning" + +#: lib/logitech_receiver/settings_templates.py:484 +msgid "Invert thumb wheel scroll direction." +msgstr "Invertera tumhjulets rullningsriktning." + +#: lib/logitech_receiver/settings_templates.py:504 +msgid "Onboard Profiles" +msgstr "Inbyggda profiler" + +#: lib/logitech_receiver/settings_templates.py:505 +msgid "" +"Enable an onboard profile, which controls report rate, sensitivity, and " +"button actions" +msgstr "" +"Aktivera en inbyggd profil som styr rapporteringsfrekvens, känslighet och " +"knappfunktioner" + +#: lib/logitech_receiver/settings_templates.py:549 +#: lib/logitech_receiver/settings_templates.py:582 +msgid "Report Rate" +msgstr "Rapporteringsfrekvens" + +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +msgid "Frequency of device movement reports" +msgstr "Frekvens för rapporter om enhetsrörelser" + +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1421 +#: lib/logitech_receiver/settings_templates.py:1452 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "" +"Kan kräva att inbyggda profiler är inställda på inaktiverade för att fungera." + +#: lib/logitech_receiver/settings_templates.py:612 +msgid "Divert crown events" +msgstr "Avled kronhändelser" + +#: lib/logitech_receiver/settings_templates.py:613 +msgid "" +"Make crown send CROWN HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"Få kronan att skicka CROWN HID++-meddelanden (som utlöser Solaar-regler men " +"annars ignoreras)." + +#: lib/logitech_receiver/settings_templates.py:621 +msgid "Crown smooth scroll" +msgstr "Kronans mjuka rullning" + +#: lib/logitech_receiver/settings_templates.py:622 +msgid "Set crown smooth scroll" +msgstr "Ställ in kronans mjuka rullning" + +#: lib/logitech_receiver/settings_templates.py:630 +msgid "Divert G and M Keys" +msgstr "Omdirigera G- och M-tangenterna" + +#: lib/logitech_receiver/settings_templates.py:631 +msgid "" +"Make G and M keys send HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"Få tangenterna G och M att skicka HID++-meddelanden (som aktiverar Solaar-" +"regler men annars ignoreras)." + +#: lib/logitech_receiver/settings_templates.py:645 +msgid "Scroll Wheel Ratcheted" +msgstr "Rullningshjul spärrat" + +#: lib/logitech_receiver/settings_templates.py:646 +msgid "" +"Switch the mouse wheel between speed-controlled ratcheting and always " +"freespin." +msgstr "" +"Växla mushjulet mellan hastighetskontrollerad spärrning och alltid fri snurr." + +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Freespinning" +msgstr "Fri snurrning" + +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Ratcheted" +msgstr "Spärrad" + +#: lib/logitech_receiver/settings_templates.py:655 +msgid "Scroll Wheel Ratchet Speed" +msgstr "Rullningshjulets spärrhastighet" + +#: lib/logitech_receiver/settings_templates.py:657 +msgid "" +"Use the mouse wheel speed to switch between ratcheted and freespinning.\n" +"The mouse wheel is always ratcheted at 50." +msgstr "" +"Använd mushjulets hastighet för att växla mellan spärrad och fri snurrning.\n" +"Mushjulet är alltid spärrat till 50." + +#: lib/logitech_receiver/settings_templates.py:707 +msgid "Scroll Wheel Ratchet Torque" +msgstr "Vridmoment för rullhjulsspärr" + +#: lib/logitech_receiver/settings_templates.py:708 +msgid "Change the torque needed to overcome the ratchet." +msgstr "Ändra vridmomentet som behövs för att övervinna spärren." + +#: lib/logitech_receiver/settings_templates.py:743 +msgid "Key/Button Actions" +msgstr "Tangent-/knappåtgärder" + +#: lib/logitech_receiver/settings_templates.py:745 +msgid "Change the action for the key or button." +msgstr "Ändra åtgärden för tangenten eller knappen." + +#: lib/logitech_receiver/settings_templates.py:747 +msgid "Overridden by diversion." +msgstr "Åsidosatt av avledning." + +#: lib/logitech_receiver/settings_templates.py:749 +msgid "" +"Changing important actions (such as for the left mouse button) can result in " +"an unusable system." +msgstr "" +"Ändring av viktiga åtgärder (som för vänster musknapp) kan resultera i ett " +"oanvändbart system." + +#: lib/logitech_receiver/settings_templates.py:924 +msgid "Key/Button Diversion" +msgstr "Tangent/knapp avledning" + +#: lib/logitech_receiver/settings_templates.py:925 +msgid "" +"Make the key or button send HID++ notifications (Diverted) or initiate Mouse " +"Gestures or Sliding DPI" +msgstr "" +"Få tangenten eller knappen att skicka HID++-aviseringar (vidarekopplade) " +"eller initiera musgester eller glidande DPI" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Diverted" +msgstr "Avledd" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +msgid "Mouse Gestures" +msgstr "Musgester" + +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Regular" +msgstr "Regelbunden" + +#: lib/logitech_receiver/settings_templates.py:928 +msgid "Sliding DPI" +msgstr "Glidande DPI" + +#: lib/logitech_receiver/settings_templates.py:1018 +#: lib/logitech_receiver/settings_templates.py:1046 +msgid "Sensitivity (DPI)" +msgstr "Känslighet (DPI)" + +#: lib/logitech_receiver/settings_templates.py:1123 +msgid "Sensitivity Switching" +msgstr "Känslighetsväxling" + +#: lib/logitech_receiver/settings_templates.py:1125 +msgid "" +"Switch the current sensitivity and the remembered sensitivity when the key " +"or button is pressed.\n" +"If there is no remembered sensitivity, just remember the current sensitivity" +msgstr "" +"Växla den aktuella känsligheten och den ihågkomna känsligheten när knappen " +"eller knappen trycks ned.\n" +"Om det inte finns någon ihågkommen känslighet, kom ihåg bara den aktuella " +"känsligheten" + +#: lib/logitech_receiver/settings_templates.py:1129 +msgid "Off" +msgstr "Av" + +#: lib/logitech_receiver/settings_templates.py:1160 +msgid "Disable keys" +msgstr "Inaktivera tangenter" + +#: lib/logitech_receiver/settings_templates.py:1161 +msgid "Disable specific keyboard keys." +msgstr "Inaktivera specifika tangentbordstangenter." + +#: lib/logitech_receiver/settings_templates.py:1164 #, python-format -msgid "Disables the %s key." -msgstr "" +msgid "Disables the %s key." +msgstr "Inaktiverar tangenten %s." -#: lib/logitech_receiver/settings_templates.py:521 -#: lib/logitech_receiver/settings_templates.py:573 -msgid "Off" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1177 +#: lib/logitech_receiver/settings_templates.py:1234 +msgid "Set OS" +msgstr "Ställ in OS" -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Diverted" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1178 +#: lib/logitech_receiver/settings_templates.py:1235 +msgid "Change keys to match OS." +msgstr "Ändra tangenter för att matcha OS." -#: lib/logitech_receiver/settings_templates.py:667 -msgid "Regular" -msgstr "" +#: lib/logitech_receiver/settings_templates.py:1247 +msgid "Change Host" +msgstr "Byt värd" -#: lib/logitech_receiver/status.py:109 -msgid "No paired devices." -msgstr "Inga enheter är parkopplade." +#: lib/logitech_receiver/settings_templates.py:1248 +msgid "Switch connection to a different host" +msgstr "Byt anslutning till en annan värd" -#: lib/logitech_receiver/status.py:110 lib/solaar/ui/window.py:623 +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Performs a left click." +msgstr "Utför ett vänsterklick." + +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Single tap" +msgstr "Enkelt tryck" + +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Performs a right click." +msgstr "Utför ett högerklick." + +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Single tap with two fingers" +msgstr "Enkelt tryck med två fingrar" + +#: lib/logitech_receiver/settings_templates.py:1274 +msgid "Single tap with three fingers" +msgstr "Enkelt tryck med tre fingrar" + +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Double tap" +msgstr "Dubbeltryck" + +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Performs a double click." +msgstr "Utför ett dubbelklick." + +#: lib/logitech_receiver/settings_templates.py:1279 +msgid "Double tap with two fingers" +msgstr "Dubbeltryck med två fingrar" + +#: lib/logitech_receiver/settings_templates.py:1280 +msgid "Double tap with three fingers" +msgstr "Dubbeltryck med tre fingrar" + +#: lib/logitech_receiver/settings_templates.py:1283 +msgid "Drags items by dragging the finger after double tapping." +msgstr "Dra objekt genom att dra fingret efter att ha dubbelklickat." + +#: lib/logitech_receiver/settings_templates.py:1283 +msgid "Tap and drag" +msgstr "Tryck och dra" + +#: lib/logitech_receiver/settings_templates.py:1285 +msgid "Tap and drag with two fingers" +msgstr "Tryck och dra med två fingrar" + +#: lib/logitech_receiver/settings_templates.py:1286 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Dra objekt genom att dra fingrarna efter att du dubbelklickat." + +#: lib/logitech_receiver/settings_templates.py:1288 +msgid "Tap and drag with three fingers" +msgstr "Tryck och dra med tre fingrar" + +#: lib/logitech_receiver/settings_templates.py:1291 +msgid "Suppress tap and edge gestures" +msgstr "Inaktivera tryck- och kantgester" + +#: lib/logitech_receiver/settings_templates.py:1292 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "" +"Inaktiverade tryck- och kantgester (motsvarar att trycka på Fn+vänsterklick)." + +#: lib/logitech_receiver/settings_templates.py:1294 +msgid "Scroll with one finger" +msgstr "Bläddra med ett finger" + +#: lib/logitech_receiver/settings_templates.py:1294 +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 +msgid "Scrolls." +msgstr "Rullningar." + +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 +msgid "Scroll with two fingers" +msgstr "Rulla med två fingrar" + +#: lib/logitech_receiver/settings_templates.py:1296 +msgid "Scroll horizontally with two fingers" +msgstr "Rulla horisontellt med två fingrar" + +#: lib/logitech_receiver/settings_templates.py:1296 +msgid "Scrolls horizontally." +msgstr "Rullar horisontellt." + +#: lib/logitech_receiver/settings_templates.py:1297 +msgid "Scroll vertically with two fingers" +msgstr "Rulla vertikalt med två fingrar" + +#: lib/logitech_receiver/settings_templates.py:1297 +msgid "Scrolls vertically." +msgstr "Rullar vertikalt." + +#: lib/logitech_receiver/settings_templates.py:1299 +msgid "Inverts the scrolling direction." +msgstr "Inverterar rullningsriktningen." + +#: lib/logitech_receiver/settings_templates.py:1299 +msgid "Natural scrolling" +msgstr "Naturlig rullning" + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Enables the thumbwheel." +msgstr "Aktiverar tumhjulet." + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Thumbwheel" +msgstr "Tumhjul" + +#: lib/logitech_receiver/settings_templates.py:1311 +#: lib/logitech_receiver/settings_templates.py:1315 +msgid "Swipe from the top edge" +msgstr "Svep från den övre kanten" + +#: lib/logitech_receiver/settings_templates.py:1312 +msgid "Swipe from the left edge" +msgstr "Svep från vänstra kanten" + +#: lib/logitech_receiver/settings_templates.py:1313 +msgid "Swipe from the right edge" +msgstr "Svep från den högra kanten" + +#: lib/logitech_receiver/settings_templates.py:1314 +msgid "Swipe from the bottom edge" +msgstr "Svep från den nedre kanten" + +#: lib/logitech_receiver/settings_templates.py:1316 +msgid "Swipe two fingers from the left edge" +msgstr "Svep två fingrar från den vänstra kanten" + +#: lib/logitech_receiver/settings_templates.py:1317 +msgid "Swipe two fingers from the right edge" +msgstr "Svep två fingrar från den högra kanten" + +#: lib/logitech_receiver/settings_templates.py:1318 +msgid "Swipe two fingers from the bottom edge" +msgstr "Svep två fingrar från den nedre kanten" + +#: lib/logitech_receiver/settings_templates.py:1319 +msgid "Swipe two fingers from the top edge" +msgstr "Svep två fingrar från den övre kanten" + +#: lib/logitech_receiver/settings_templates.py:1320 +#: lib/logitech_receiver/settings_templates.py:1324 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "Nyp för att zooma ut; sprid för att zooma in." + +#: lib/logitech_receiver/settings_templates.py:1320 +msgid "Zoom with two fingers." +msgstr "Zooma med två fingrar." + +#: lib/logitech_receiver/settings_templates.py:1321 +msgid "Pinch to zoom out." +msgstr "Nyp för att zooma ut." + +#: lib/logitech_receiver/settings_templates.py:1322 +msgid "Spread to zoom in." +msgstr "Sprid för att zooma in." + +#: lib/logitech_receiver/settings_templates.py:1323 +msgid "Zoom with three fingers." +msgstr "Zooma med tre fingrar." + +#: lib/logitech_receiver/settings_templates.py:1324 +msgid "Zoom with two fingers" +msgstr "Zooma med två fingrar" + +#: lib/logitech_receiver/settings_templates.py:1342 +msgid "Pixel zone" +msgstr "Pixelzon" + +#: lib/logitech_receiver/settings_templates.py:1343 +msgid "Ratio zone" +msgstr "Ratiozon" + +#: lib/logitech_receiver/settings_templates.py:1344 +msgid "Scale factor" +msgstr "Skalfaktor" + +#: lib/logitech_receiver/settings_templates.py:1344 +msgid "Sets the cursor speed." +msgstr "Ställer in markörhastigheten." + +#: lib/logitech_receiver/settings_templates.py:1348 +msgid "Left" +msgstr "Vänster" + +#: lib/logitech_receiver/settings_templates.py:1348 +msgid "Left-most coordinate." +msgstr "Koordinaten längst till vänster." + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom" +msgstr "Nedre" + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom coordinate." +msgstr "Nedre koordinat." + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width" +msgstr "Bredd" + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width." +msgstr "Bredd." + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height" +msgstr "Höjd" + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height." +msgstr "Höjd." + +#: lib/logitech_receiver/settings_templates.py:1352 +msgid "Cursor speed." +msgstr "Markörhastighet." + +#: lib/logitech_receiver/settings_templates.py:1352 +msgid "Scale" +msgstr "Skala" + +#: lib/logitech_receiver/settings_templates.py:1358 +msgid "Gestures" +msgstr "Gester" + +#: lib/logitech_receiver/settings_templates.py:1359 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "Justera musens/pekplattans beteende." + +#: lib/logitech_receiver/settings_templates.py:1375 +msgid "Gestures Diversion" +msgstr "Avledning av gester" + +#: lib/logitech_receiver/settings_templates.py:1376 +msgid "Divert mouse/touchpad gestures." +msgstr "Avleda mus-/pekplattagester." + +#: lib/logitech_receiver/settings_templates.py:1392 +msgid "Gesture params" +msgstr "Gestparametrar" + +#: lib/logitech_receiver/settings_templates.py:1393 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "Ändra numeriska parametrar för en mus/pekplatta." + +#: lib/logitech_receiver/settings_templates.py:1417 +msgid "M-Key LEDs" +msgstr "M-Key LED:ar" + +#: lib/logitech_receiver/settings_templates.py:1419 +msgid "Control the M-Key LEDs." +msgstr "Styr M-Key LED:ar." + +#: lib/logitech_receiver/settings_templates.py:1423 +#: lib/logitech_receiver/settings_templates.py:1454 +msgid "May need G Keys diverted to be effective." +msgstr "Kan behöva omdirigera G-tangenter för att vara effektiva." + +#: lib/logitech_receiver/settings_templates.py:1429 #, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s parade enhet." -msgstr[1] "%(count)s parade enheter." +msgid "Lights up the %s key." +msgstr "Lyser upp tangenten %s." -#: lib/logitech_receiver/status.py:165 +#: lib/logitech_receiver/settings_templates.py:1448 +msgid "MR-Key LED" +msgstr "MR-Key LED" + +#: lib/logitech_receiver/settings_templates.py:1450 +msgid "Control the MR-Key LED." +msgstr "Styr MR-Key LED." + +#: lib/logitech_receiver/settings_templates.py:1471 +msgid "Persistent Key/Button Mapping" +msgstr "Ihållande nyckel/knappkartläggning" + +#: lib/logitech_receiver/settings_templates.py:1473 +msgid "Permanently change the mapping for the key or button." +msgstr "Ändra kartläggningen för nyckeln eller knappen permanent." + +#: lib/logitech_receiver/settings_templates.py:1475 +msgid "" +"Changing important keys or buttons (such as for the left mouse button) can " +"result in an unusable system." +msgstr "" +"Att ändra viktiga tangenter eller knappar (som för vänster musknapp) kan " +"resultera i ett oanvändbart system." + +#: lib/logitech_receiver/settings_templates.py:1532 +msgid "Sidetone" +msgstr "Sidoton" + +#: lib/logitech_receiver/settings_templates.py:1533 +msgid "Set sidetone level." +msgstr "Ställ in sidotonsnivå." + +#: lib/logitech_receiver/settings_templates.py:1542 +msgid "Equalizer" +msgstr "Utjämnare" + +#: lib/logitech_receiver/settings_templates.py:1543 +msgid "Set equalizer levels." +msgstr "Ställ in utjämningsnivåer." + +#: lib/logitech_receiver/settings_templates.py:1565 +msgid "Hz" +msgstr "Hz" + +#: lib/logitech_receiver/settings_templates.py:1571 +msgid "Power Management" +msgstr "Strömhantering" + +#: lib/logitech_receiver/settings_templates.py:1572 +msgid "Power off in minutes (0 for never)." +msgstr "Stäng av på några minuter (0 för aldrig)." + +#: lib/logitech_receiver/settings_templates.py:1584 +msgid "Brightness Control" +msgstr "Ljusstyrkekontroll" + +#: lib/logitech_receiver/settings_templates.py:1585 +msgid "Control overall brightness" +msgstr "Kontrollera allmän ljusstyrka" + +#: lib/logitech_receiver/settings_templates.py:1628 +#: lib/logitech_receiver/settings_templates.py:1681 +msgid "LED Control" +msgstr "LED-kontroll" + +#: lib/logitech_receiver/settings_templates.py:1629 +#: lib/logitech_receiver/settings_templates.py:1682 +msgid "Switch control of LED zones between device and Solaar" +msgstr "Växla styrning av LED-zoner mellan enhet och Solaar" + +#: lib/logitech_receiver/settings_templates.py:1644 +#: lib/logitech_receiver/settings_templates.py:1692 +msgid "LED Zone Effects" +msgstr "LED-zoneffekter" + +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "LED Control needs to be set to Solaar to be effective." +msgstr "LED-kontrollen måste ställas in på Solaar för att fungera." + +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "Set effect for LED Zone" +msgstr "Ställ in effekt för LED-zon" + +#: lib/logitech_receiver/settings_templates.py:1647 +msgid "Color" +msgstr "Färg" + +#: lib/logitech_receiver/settings_templates.py:1648 +msgid "Speed" +msgstr "Hastighet" + +#: lib/logitech_receiver/settings_templates.py:1649 +msgid "Period" +msgstr "Period" + +#: lib/logitech_receiver/settings_templates.py:1650 +msgid "Intensity" +msgstr "Intensitet" + +#: lib/logitech_receiver/settings_templates.py:1651 +msgid "Ramp" +msgstr "Ramp" + +#: lib/logitech_receiver/settings_templates.py:1666 +msgid "LEDs" +msgstr "LED" + +#: lib/logitech_receiver/settings_templates.py:1703 +msgid "Per-key Lighting" +msgstr "Belysning per tangent" + +#: lib/logitech_receiver/settings_templates.py:1704 +msgid "Control per-key lighting." +msgstr "Kontrollera belysning per tangent." + +#: lib/logitech_receiver/settings_templates.py:1786 +msgid "Force Sensing Buttons" +msgstr "Kraftavkännande knappar" + +#: lib/logitech_receiver/settings_templates.py:1787 +msgid "Change the force required to activate button." +msgstr "Ändra den kraft som krävs för att aktivera knappen." + +#: lib/logitech_receiver/settings_templates.py:1804 +msgid "Force Sensing Button" +msgstr "Kraftavkännande knapp" + +#: lib/logitech_receiver/settings_templates.py:1814 +msgid "Haptic Feeback Level" +msgstr "Haptisk återkopplingsnivå" + +#: lib/logitech_receiver/settings_templates.py:1815 +msgid "Change power of haptic feedback. (Zero to turn off.)" +msgstr "" +"Ändra styrkan på den haptiska återkopplingen. (Noll för att stänga av.)" + +#: lib/logitech_receiver/settings_templates.py:1857 +msgid "Play Haptic Waveform" +msgstr "Spela upp haptisk vågform" + +#: lib/logitech_receiver/settings_templates.py:1858 +msgid "Tell device to play a haptic waveform." +msgstr "Be enheten att spela upp en haptisk vågform." + +#: lib/solaar/ui/__init__.py:120 +msgid "Another Solaar process is already running so just expose its window" +msgstr "En annan Solaar-process körs redan så bara exponera dess fönster" + +#: lib/solaar/ui/about/model.py:36 +msgid "" +"Manages Logitech receivers,\n" +"keyboards, mice, and tablets." +msgstr "" +"Hanterar Logitech-mottagare,\n" +"tangentbord, möss och surfplattor." + +#: lib/solaar/ui/about/model.py:63 +msgid "Additional Programming" +msgstr "Ytterligare programmering" + +#: lib/solaar/ui/about/model.py:64 +msgid "GUI design" +msgstr "Grafisk design av användargränssnitt" + +#: lib/solaar/ui/about/model.py:66 +msgid "Testing" +msgstr "Testning" + +#: lib/solaar/ui/about/model.py:74 +msgid "Logitech documentation" +msgstr "Logitech-dokumentation" + +#: lib/solaar/ui/action.py:88 lib/solaar/ui/action.py:92 +#: lib/solaar/ui/window.py:208 +msgid "Unpair" +msgstr "Bryt parkoppling" + +#: lib/solaar/ui/action.py:91 lib/solaar/ui/diversion_rules.py:141 +msgid "Cancel" +msgstr "Avbryt" + +#: lib/solaar/ui/common.py:42 +msgid "Permissions error" +msgstr "Behörighetsfel" + +#: lib/solaar/ui/common.py:44 #, python-format -msgid "Battery: %(level)s" -msgstr "Batteri: %(level)s" +msgid "" +"Found a Logitech receiver or device (%s), but did not have permission to " +"open it." +msgstr "" +"Hittade en Logitech-mottagare eller enhet (%s), men hade inte behörighet att " +"öppna den." -#: lib/logitech_receiver/status.py:167 +#: lib/solaar/ui/common.py:46 +msgid "" +"If you've just installed Solaar, try disconnecting the receiver or device " +"and then reconnecting it." +msgstr "" +"Om du precis har installerat Solaar, försök att koppla bort mottagaren eller " +"enheten och sedan återansluta den." + +#: lib/solaar/ui/common.py:49 +msgid "Cannot connect to device error" +msgstr "Kan inte ansluta till enhet-fel" + +#: lib/solaar/ui/common.py:51 #, python-format -msgid "Battery: %(percent)d%%" -msgstr "Batteri: %(percent)d%%" +msgid "" +"Found a Logitech receiver or device at %s, but encountered an error " +"connecting to it." +msgstr "" +"Hittade en Logitech-mottagare eller enhet på %s, men stötte på ett fel vid " +"anslutning till den." -#: lib/logitech_receiver/status.py:179 -#, python-format -msgid "Lighting: %(level)s lux" -msgstr "Belysning: %(level)s lux" +#: lib/solaar/ui/common.py:53 +msgid "" +"Try disconnecting the device and then reconnecting it or turning it off and " +"then on." +msgstr "" +"Försök att koppla bort enheten och sedan återansluta den eller stänga av och " +"sedan på den." -#: lib/logitech_receiver/status.py:234 -#, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "Batteri: %(level)s (%(status)s)" +#: lib/solaar/ui/common.py:56 +msgid "Unpairing failed" +msgstr "Kunde inte bryta parkoppling" -#: lib/logitech_receiver/status.py:236 -#, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "Batteri: %(percent)d%% (%(status)s)" - -#: lib/solaar/ui/__init__.py:52 -msgid "Permissions error" -msgstr "Behörighetsfel" - -#: lib/solaar/ui/__init__.py:54 -#, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open " - "it." -msgstr "En Logitech mottagare hittades (%s), fast saknar behörighet till att " - "öppna den." - -#: lib/solaar/ui/__init__.py:55 -msgid "If you've just installed Solaar, try removing the receiver and " - "plugging it back in." -msgstr "Om du just installerade solaar, prova koppla ur mottagaren och " - "anslut den sedan igen." - -#: lib/solaar/ui/__init__.py:58 -msgid "Cannot connect to device error" -msgstr "" - -#: lib/solaar/ui/__init__.py:60 -#, python-format -msgid "Found a Logitech receiver or device at %s, but encountered an error " - "connecting to it." -msgstr "" - -#: lib/solaar/ui/__init__.py:61 -msgid "Try removing the device and plugging it back in or turning it off " - "and then on." -msgstr "" - -#: lib/solaar/ui/__init__.py:64 -msgid "Unpairing failed" -msgstr "Kunde inte bryta parkoppling" - -#: lib/solaar/ui/__init__.py:66 +#: lib/solaar/ui/common.py:58 #, python-brace-format -msgid "Failed to unpair %{device} from %{receiver}." -msgstr "Misslyckades att bryta parkoppling mellan %{device} och %{receiver}." +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Misslyckades att bryta parkoppling mellan %{device} och %{receiver}." -#: lib/solaar/ui/__init__.py:67 -msgid "The receiver returned an error, with no further details." -msgstr "Mottagaren rapporterade ett fel, utan specifika detaljer." +#: lib/solaar/ui/common.py:63 +msgid "The receiver returned an error, with no further details." +msgstr "Mottagaren rapporterade ett fel, utan ytterligare detaljer." -#: lib/solaar/ui/about.py:39 -msgid "Manages Logitech receivers,\n" - "keyboards, mice, and tablets." -msgstr "" +#: lib/solaar/ui/config_panel.py:245 +msgid "Complete - ENTER to change" +msgstr "Komplett - ENTER för att ändra" -#: lib/solaar/ui/about.py:47 -msgid "Additional Programming" -msgstr "" +#: lib/solaar/ui/config_panel.py:245 +msgid "Incomplete" +msgstr "Ofullständig" -#: lib/solaar/ui/about.py:48 -msgid "GUI design" -msgstr "GUI utseende" - -#: lib/solaar/ui/about.py:50 -msgid "Testing" -msgstr "Prövar" - -#: lib/solaar/ui/about.py:57 -msgid "Logitech documentation" -msgstr "Logitech dokumentation" - -#: lib/solaar/ui/action.py:70 lib/solaar/ui/window.py:328 -msgid "About %s" -msgstr "Om %s" - -#: lib/solaar/ui/action.py:96 lib/solaar/ui/action.py:100 -#: lib/solaar/ui/window.py:205 -msgid "Unpair" -msgstr "Ta bort parkoppling" - -#: lib/solaar/ui/action.py:99 lib/solaar/ui/diversion_rules.py:144 -msgid "Cancel" -msgstr "" - -#: lib/solaar/ui/config_panel.py:358 -msgid "Changes allowed" -msgstr "" - -#: lib/solaar/ui/config_panel.py:359 -msgid "No changes allowed" -msgstr "" - -#: lib/solaar/ui/config_panel.py:360 -msgid "Ignore this setting" -msgstr "" - -#: lib/solaar/ui/config_panel.py:401 -msgid "Working" -msgstr "Lyckades" - -#: lib/solaar/ui/config_panel.py:404 -msgid "Read/write operation failed." -msgstr "Läsning/Skrivning misslyckades." - -#: lib/solaar/ui/config_panel.py:522 +#: lib/solaar/ui/config_panel.py:491 lib/solaar/ui/config_panel.py:543 #, python-format -msgid "%d value" -msgid_plural "%d values" -msgstr[0] "" -msgstr[1] "" +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d värde" +msgstr[1] "%d värden" -#: lib/solaar/ui/diversion_rules.py:57 -msgid "Built-in rules" -msgstr "" +#: lib/solaar/ui/config_panel.py:642 +msgid "Changes allowed" +msgstr "Ändringar tillåtna" -#: lib/solaar/ui/diversion_rules.py:57 -msgid "User-defined rules" -msgstr "" +#: lib/solaar/ui/config_panel.py:643 +msgid "No changes allowed" +msgstr "Inga ändringar tillåtna" -#: lib/solaar/ui/diversion_rules.py:59 lib/solaar/ui/diversion_rules.py:792 -msgid "Rule" -msgstr "" +#: lib/solaar/ui/config_panel.py:644 +msgid "Ignore this setting" +msgstr "Ignorera denna inställning" -#: lib/solaar/ui/diversion_rules.py:60 lib/solaar/ui/diversion_rules.py:503 -#: lib/solaar/ui/diversion_rules.py:622 -msgid "Sub-rule" -msgstr "" +#: lib/solaar/ui/config_panel.py:690 +msgid "Working" +msgstr "Fungerar" -#: lib/solaar/ui/diversion_rules.py:62 -msgid "[empty]" -msgstr "" +#: lib/solaar/ui/config_panel.py:693 +msgid "Read/write operation failed." +msgstr "Läs-/skrivoperationen misslyckades." -#: lib/solaar/ui/diversion_rules.py:85 -msgid "Solaar Rule Editor" -msgstr "" +#: lib/solaar/ui/desktop_notifications.py:112 +msgid "unspecified reason" +msgstr "ospecificerad anledning" -#: lib/solaar/ui/diversion_rules.py:135 -msgid "Make changes permanent?" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:103 +msgid "Built-in rules" +msgstr "Inbyggda regler" -#: lib/solaar/ui/diversion_rules.py:140 -msgid "Yes" -msgstr "Ja" +#: lib/solaar/ui/diversion_rules.py:103 +msgid "User-defined rules" +msgstr "Användardefinierade regler" -#: lib/solaar/ui/diversion_rules.py:142 -msgid "No" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:105 lib/solaar/ui/diversion_rules.py:1126 +msgid "Rule" +msgstr "Regel" -#: lib/solaar/ui/diversion_rules.py:147 -msgid "If you choose No, changes will be lost when Solaar is closed." -msgstr "" +#: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:382 +#: lib/solaar/ui/diversion_rules.py:509 +msgid "Sub-rule" +msgstr "Underregel" -#: lib/solaar/ui/diversion_rules.py:195 -msgid "Save changes" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:108 +msgid "[empty]" +msgstr "[tom]" -#: lib/solaar/ui/diversion_rules.py:200 -msgid "Discard changes" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:132 +msgid "Make changes permanent?" +msgstr "Göra ändringarna permanenta?" -#: lib/solaar/ui/diversion_rules.py:366 -msgid "Insert here" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:137 +msgid "Yes" +msgstr "Ja" -#: lib/solaar/ui/diversion_rules.py:368 -msgid "Insert above" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:139 +msgid "No" +msgstr "Nej" -#: lib/solaar/ui/diversion_rules.py:370 -msgid "Insert below" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:144 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "Om du väljer Nej kommer ändringar att gå förlorade när Solaar stängs." -#: lib/solaar/ui/diversion_rules.py:376 -msgid "Insert new rule here" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:273 +msgid "Paste here" +msgstr "Klistra in här" -#: lib/solaar/ui/diversion_rules.py:378 -msgid "Insert new rule above" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:275 +msgid "Paste above" +msgstr "Klistra in ovan" + +#: lib/solaar/ui/diversion_rules.py:277 +msgid "Paste below" +msgstr "Klistra in nedan" + +#: lib/solaar/ui/diversion_rules.py:283 +msgid "Paste rule here" +msgstr "Klistra in regeln här" + +#: lib/solaar/ui/diversion_rules.py:285 +msgid "Paste rule above" +msgstr "Klistra in regeln ovan" + +#: lib/solaar/ui/diversion_rules.py:287 +msgid "Paste rule below" +msgstr "Klistra in regeln nedan" + +#: lib/solaar/ui/diversion_rules.py:291 +msgid "Paste rule" +msgstr "Klistra in regel" + +#: lib/solaar/ui/diversion_rules.py:306 +msgid "Insert here" +msgstr "Infoga här" + +#: lib/solaar/ui/diversion_rules.py:308 +msgid "Insert above" +msgstr "Infoga ovan" + +#: lib/solaar/ui/diversion_rules.py:310 +msgid "Insert below" +msgstr "Infoga nedan" + +#: lib/solaar/ui/diversion_rules.py:316 +msgid "Insert new rule here" +msgstr "Infoga ny regel här" + +#: lib/solaar/ui/diversion_rules.py:318 +msgid "Insert new rule above" +msgstr "Infoga ny regel ovan" + +#: lib/solaar/ui/diversion_rules.py:320 +msgid "Insert new rule below" +msgstr "Infoga ny regel nedan" + +#: lib/solaar/ui/diversion_rules.py:347 +msgid "Flatten" +msgstr "Platta till" #: lib/solaar/ui/diversion_rules.py:380 -msgid "Insert new rule below" -msgstr "" +msgid "Insert" +msgstr "Infoga" -#: lib/solaar/ui/diversion_rules.py:421 -msgid "Paste here" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:383 lib/solaar/ui/diversion_rules.py:511 +#: lib/solaar/ui/diversion_rules.py:1158 +msgid "Or" +msgstr "Eller" -#: lib/solaar/ui/diversion_rules.py:423 -msgid "Paste above" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:510 +#: lib/solaar/ui/diversion_rules.py:1144 +msgid "And" +msgstr "Och" -#: lib/solaar/ui/diversion_rules.py:425 -msgid "Paste below" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:386 +msgid "Condition" +msgstr "Villkor" -#: lib/solaar/ui/diversion_rules.py:431 -msgid "Paste rule here" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:388 lib/solaar/ui/rule_conditions.py:154 +msgid "Feature" +msgstr "Funktion" -#: lib/solaar/ui/diversion_rules.py:433 -msgid "Paste rule above" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:188 +msgid "Report" +msgstr "Rapport" -#: lib/solaar/ui/diversion_rules.py:435 -msgid "Paste rule below" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:69 +msgid "Process" +msgstr "Process" -#: lib/solaar/ui/diversion_rules.py:439 -msgid "Paste rule" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:391 +msgid "Mouse process" +msgstr "Musprocess" -#: lib/solaar/ui/diversion_rules.py:468 -msgid "Flatten" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:392 lib/solaar/ui/rule_conditions.py:225 +msgid "Modifiers" +msgstr "Modifierare" -#: lib/solaar/ui/diversion_rules.py:501 -msgid "Insert" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:277 +msgid "Key" +msgstr "Tangent" -#: lib/solaar/ui/diversion_rules.py:504 lib/solaar/ui/diversion_rules.py:624 -#: lib/solaar/ui/diversion_rules.py:835 -msgid "Or" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:318 +msgid "KeyIsDown" +msgstr "KeyIsDown" -#: lib/solaar/ui/diversion_rules.py:505 lib/solaar/ui/diversion_rules.py:623 -#: lib/solaar/ui/diversion_rules.py:820 -msgid "And" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/diversion_rules.py:1432 +msgid "Active" +msgstr "Aktiv" + +#: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1390 +#: lib/solaar/ui/diversion_rules.py:1441 lib/solaar/ui/diversion_rules.py:1487 +msgid "Device" +msgstr "Enhet" + +#: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1467 +msgid "Host" +msgstr "Värd" + +#: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1506 +msgid "Setting" +msgstr "Inställning" + +#: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/rule_conditions.py:333 +#: lib/solaar/ui/rule_conditions.py:382 +msgid "Test" +msgstr "Testa" + +#: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:507 +msgid "Test bytes" +msgstr "Testa byte" + +#: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:608 +msgid "Mouse Gesture" +msgstr "Musgest" + +#: lib/solaar/ui/diversion_rules.py:405 +msgid "Action" +msgstr "Åtgärd" + +#: lib/solaar/ui/diversion_rules.py:407 lib/solaar/ui/rule_actions.py:138 +msgid "Key press" +msgstr "Knapptryckning" + +#: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:191 +msgid "Mouse scroll" +msgstr "Musrullning" + +#: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:256 +msgid "Mouse click" +msgstr "Musklick" + +#: lib/solaar/ui/diversion_rules.py:410 +msgid "Set" +msgstr "Ställ in" + +#: lib/solaar/ui/diversion_rules.py:411 lib/solaar/ui/rule_actions.py:328 +msgid "Execute" +msgstr "Utför" + +#: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/diversion_rules.py:1189 +msgid "Later" +msgstr "Senare" + +#: lib/solaar/ui/diversion_rules.py:441 +msgid "Insert new rule" +msgstr "Infoga ny regel" + +#: lib/solaar/ui/diversion_rules.py:461 lib/solaar/ui/rule_actions.py:82 +#: lib/solaar/ui/rule_actions.py:287 lib/solaar/ui/rule_conditions.py:556 +msgid "Delete" +msgstr "Ta bort" + +#: lib/solaar/ui/diversion_rules.py:483 +msgid "Negate" +msgstr "Negera" #: lib/solaar/ui/diversion_rules.py:507 -msgid "Condition" -msgstr "" +msgid "Wrap with" +msgstr "Omslut med" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:951 -msgid "Feature" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Cut" +msgstr "Klipp ut" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:874 -msgid "Process" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:553 +msgid "Paste" +msgstr "Klistra in" -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:902 -msgid "MouseProcess" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:559 +msgid "Copy" +msgstr "Kopiera" -#: lib/solaar/ui/diversion_rules.py:512 lib/solaar/ui/diversion_rules.py:984 -msgid "Report" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Solaar Rule Editor" +msgstr "Solaar regelredigerare" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1019 -msgid "Modifiers" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:668 +msgid "Save changes" +msgstr "Spara ändringar" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1066 -msgid "Key" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:673 +msgid "Discard changes" +msgstr "Släng ändringar" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1111 -msgid "Test" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1104 +msgid "This editor does not support the selected rule component yet." +msgstr "Denna redigerare stöder inte den valda regelkomponenten ännu." -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1196 -msgid "Mouse Gesture" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1169 +msgid "" +"Number of seconds to delay. Delay between 0 and 1 is done with higher " +"precision." +msgstr "" +"Antal sekunder att fördröja. Fördröjning mellan 0 och 1 görs med högre " +"precision." -#: lib/solaar/ui/diversion_rules.py:520 -msgid "Action" -msgstr "Åtgärd" +#: lib/solaar/ui/diversion_rules.py:1207 +msgid "Not" +msgstr "Inte" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1286 -msgid "Key press" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1238 +msgid "Toggle" +msgstr "Växla" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1335 -msgid "Mouse scroll" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1239 +msgid "True" +msgstr "Sant" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:1383 -msgid "Mouse click" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1240 +msgid "False" +msgstr "Falskt" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1452 -msgid "Execute" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1253 +msgid "Unsupported setting" +msgstr "Inställning som inte stöds" -#: lib/solaar/ui/diversion_rules.py:554 -msgid "Insert new rule" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1396 lib/solaar/ui/diversion_rules.py:1415 +#: lib/solaar/ui/diversion_rules.py:1493 lib/solaar/ui/diversion_rules.py:1748 +#: lib/solaar/ui/diversion_rules.py:1766 +msgid "Originating device" +msgstr "Ursprungsenhet" -#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/diversion_rules.py:1146 -#: lib/solaar/ui/diversion_rules.py:1236 lib/solaar/ui/diversion_rules.py:1411 -msgid "Delete" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1428 +msgid "Device is active and its settings can be changed." +msgstr "Enheten är aktiv och dess inställningar kan ändras." -#: lib/solaar/ui/diversion_rules.py:596 -msgid "Negate" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1437 +msgid "Device that originated the current notification." +msgstr "Enhet som skapade det aktuella meddelandet." -#: lib/solaar/ui/diversion_rules.py:620 -msgid "Wrap with" -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1450 +msgid "Name of host computer." +msgstr "Namn på värddator." -#: lib/solaar/ui/diversion_rules.py:642 -msgid "Cut" -msgstr "Klipp ut" +#: lib/solaar/ui/diversion_rules.py:1520 +msgid "Value" +msgstr "Värde" -#: lib/solaar/ui/diversion_rules.py:657 -msgid "Paste" -msgstr "Klistra in" +#: lib/solaar/ui/diversion_rules.py:1529 +msgid "Item" +msgstr "Objekt" -#: lib/solaar/ui/diversion_rules.py:669 -msgid "Copy" -msgstr "Kopiera" +#: lib/solaar/ui/diversion_rules.py:1808 +msgid "Change setting on device" +msgstr "Ändra inställning på enheten" -#: lib/solaar/ui/diversion_rules.py:772 -msgid "This editor does not support the selected rule component yet." -msgstr "" +#: lib/solaar/ui/diversion_rules.py:1824 +msgid "Setting on device" +msgstr "Inställning på enheten" -#: lib/solaar/ui/diversion_rules.py:850 -msgid "Not" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1039 -msgid "Key down" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1042 -msgid "Key up" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1131 -msgid "Add action" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1224 -msgid "Add key" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1354 -msgid "Button" -msgstr "" - -#: lib/solaar/ui/diversion_rules.py:1355 -msgid "Count" -msgstr "Antal" - -#: lib/solaar/ui/diversion_rules.py:1397 -msgid "Add argument" -msgstr "" - -#: lib/solaar/ui/notify.py:125 lib/solaar/ui/tray.py:319 -#: lib/solaar/ui/tray.py:324 lib/solaar/ui/window.py:749 -msgid "offline" -msgstr "avstängd" - -#: lib/solaar/ui/pair_window.py:134 -msgid "Pairing failed" -msgstr "Parkoppling misslyckades" - -#: lib/solaar/ui/pair_window.py:136 -msgid "Make sure your device is within range, and has a decent battery " - "charge." -msgstr "Se till så att enheten är inom räckhåll, och har tillräckligt laddat " - "batteri." - -#: lib/solaar/ui/pair_window.py:138 -msgid "A new device was detected, but it is not compatible with this " - "receiver." -msgstr "En ny enhet upptäcktes, fast den är ukompatibel med denna mottakeren." - -#: lib/solaar/ui/pair_window.py:140 -msgid "More paired devices than receiver can support." -msgstr "Fler parade enheter än mottagaren kan stödja." - -#: lib/solaar/ui/pair_window.py:142 -msgid "No further details are available about the error." -msgstr "Inga ytterligare information är tillgänglig om felet." - -#: lib/solaar/ui/pair_window.py:156 -msgid "Found a new device:" -msgstr "Hittade en ny enhet:" - -#: lib/solaar/ui/pair_window.py:181 -msgid "The wireless link is not encrypted" -msgstr "Den trådlösa anslutningen är okrypterad" - -#: lib/solaar/ui/pair_window.py:199 +#: lib/solaar/ui/pair_window.py:44 lib/solaar/ui/pair_window.py:167 #, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s: koppla ihop enhet" +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: parkoppla ny enhet" -#: lib/solaar/ui/pair_window.py:206 -msgid "If the device is already turned on, turn it off and on again." -msgstr "" +#: lib/solaar/ui/pair_window.py:46 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "Bolt-mottagare är endast kompatibla med Bolt-enheter." -#: lib/solaar/ui/pair_window.py:209 +#: lib/solaar/ui/pair_window.py:48 +msgid "Press a pairing button or key until the pairing light flashes quickly." +msgstr "" +"Tryck på en parkopplingstangent eller -knapp tills parkopplingslampan " +"blinkar snabbt." + +#: lib/solaar/ui/pair_window.py:51 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "Unifying-mottagare är endast kompatibla med Unifying-enheter." + +#: lib/solaar/ui/pair_window.py:53 +msgid "Other receivers are only compatible with a few devices." +msgstr "Andra mottagare är endast kompatibla med ett fåtal enheter." + +#: lib/solaar/ui/pair_window.py:55 +msgid "For most devices, turn on the device you want to pair." +msgstr "För de flesta enheter, slå på den enhet du vill para ihop." + +#: lib/solaar/ui/pair_window.py:56 +msgid "If the device is already turned on, turn it off and on again." +msgstr "Om enheten redan är påslagen, stäng av och sätt på den igen." + +#: lib/solaar/ui/pair_window.py:58 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "Enheten får inte parkopplas med en närliggande påslagen mottagare." + +#: lib/solaar/ui/pair_window.py:61 +msgid "" +"For devices with multiple channels, press, hold, and release the button for " +"the channel you wish to pair\n" +"or use the channel switch button to select a channel and then press, hold, " +"and release the channel switch button." +msgstr "" +"För enheter med flera kanaler, tryck, håll ned och släpp knappen för den " +"kanal du vill para ihop eller använd\n" +"kanalväxlingsknappen för att välja en kanal och tryck sedan, håll ned och " +"släpp kanalväxlingsknappen." + +#: lib/solaar/ui/pair_window.py:68 +msgid "The channel indicator light should be blinking rapidly." +msgstr "Lampan för kanalindikator ska blinka snabbt." + +#: lib/solaar/ui/pair_window.py:72 #, python-format -msgid "\n" - "\n" - "This receiver has %d pairing remaining." -msgid_plural "\n" - "\n" - "This receiver has %d pairings remaining." -msgstr[0] "\n" - "\n" - "Denna mottagare har %d parning kvar." -msgstr[1] "\n" - "\n" - "Denna mottagare har %d parningar kvar." +msgid "" +"\n" +"\n" +"This receiver has %d pairing remaining." +msgid_plural "" +"\n" +"\n" +"This receiver has %d pairings remaining." +msgstr[0] "" +"\n" +"\n" +"Denna mottagare har %d parkoppling kvar." +msgstr[1] "" +"\n" +"\n" +"Denna mottagare har %d parkopplingar kvar." -#: lib/solaar/ui/pair_window.py:212 -msgid "\n" - "Cancelling at this point will not use up a pairing." -msgstr "\n" - "Om du avbryter vid detta tillfälle kommer inte en parning att " - "användas." +#: lib/solaar/ui/pair_window.py:78 +msgid "" +"\n" +"Cancelling at this point will not use up a pairing." +msgstr "" +"\n" +"Avbrytning vid detta tillfälle kommer inte att förbruka en parkoppling." -#: lib/solaar/ui/pair_window.py:215 -msgid "Turn on the device you want to pair." -msgstr "Sätt på enheten du vill parkoppla." +#: lib/solaar/ui/pair_window.py:168 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "Ange lösenkoden på %(name)s." -#: lib/solaar/ui/tray.py:61 -msgid "No Logitech receiver found" -msgstr "Ingen Logitech mottagare hittades" +#: lib/solaar/ui/pair_window.py:171 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "Skriv %(passcode)s och tryck sedan på enter-tangenten." -#: lib/solaar/ui/tray.py:68 lib/solaar/ui/window.py:325 -msgid "Quit %s" -msgstr "Stäng %s" +#: lib/solaar/ui/pair_window.py:176 +msgid "left" +msgstr "vänster" -#: lib/solaar/ui/tray.py:298 lib/solaar/ui/tray.py:306 -msgid "no receiver" -msgstr "ingen mottagare" +#: lib/solaar/ui/pair_window.py:176 +msgid "right" +msgstr "höger" -#: lib/solaar/ui/tray.py:322 -msgid "no status" -msgstr "ingen status" +#: lib/solaar/ui/pair_window.py:178 +#, python-format +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"Tryck på %(code)s\n" +"och tryck sedan på vänster och höger knappar samtidigt." -#: lib/solaar/ui/window.py:104 -msgid "Scanning" -msgstr "Söker" +#: lib/solaar/ui/pair_window.py:221 +msgid "The wireless link is not encrypted" +msgstr "Den trådlösa anslutningen är inte krypterad" -#: lib/solaar/ui/window.py:137 lib/solaar/ui/window.py:692 -msgid "Battery" -msgstr "Batteri" +#: lib/solaar/ui/pair_window.py:226 +msgid "Found a new device:" +msgstr "Hittade en ny enhet:" -#: lib/solaar/ui/window.py:140 -msgid "Wireless Link" -msgstr "Trådlös anslutning" +#: lib/solaar/ui/pair_window.py:247 +msgid "Pairing failed" +msgstr "Parkoppling misslyckades" + +#: lib/solaar/ui/pair_window.py:249 +msgid "Make sure your device is within range, and has a decent battery charge." +msgstr "" +"Se till att din enhet är inom räckhåll och har tillräckligt laddat batteri." + +#: lib/solaar/ui/pair_window.py:251 +msgid "A new device was detected, but it is not compatible with this receiver." +msgstr "" +"En ny enhet upptäcktes, men den är inte kompatibel med denna mottagare." + +#: lib/solaar/ui/pair_window.py:253 +msgid "More paired devices than receiver can support." +msgstr "Fler parkopplade enheter än vad mottagaren kan stöda." + +#: lib/solaar/ui/pair_window.py:255 +msgid "No further details are available about the error." +msgstr "Inga ytterligare detaljer finns tillgängliga om felet." + +#: lib/solaar/ui/rule_actions.py:54 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simulera ett ackorderat tangentklick eller tryck ner eller släpp.\n" +"I Wayland krävs skrivåtkomst till /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:60 +msgid "Add key" +msgstr "Lägg till tangent" + +#: lib/solaar/ui/rule_actions.py:63 +msgid "Click" +msgstr "Klick" + +#: lib/solaar/ui/rule_actions.py:66 +msgid "Depress" +msgstr "Tryck ned" + +#: lib/solaar/ui/rule_actions.py:69 +msgid "Release" +msgstr "Släppa" + +#: lib/solaar/ui/rule_actions.py:153 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simulera en musrullning.\n" +"I Wayland krävs skrivåtkomst till /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:211 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Simulera ett musklick.\n" +"I Wayland krävs skrivåtkomst till /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:216 +msgid "Button" +msgstr "Knapp" + +#: lib/solaar/ui/rule_actions.py:218 +msgid "Action (and Count, if click)" +msgstr "Åtgärd (och räkna, om klick)" + +#: lib/solaar/ui/rule_actions.py:269 +msgid "Execute a command with arguments." +msgstr "Utför ett kommando med argument." + +#: lib/solaar/ui/rule_actions.py:273 +msgid "Add argument" +msgstr "Lägg till argument" + +#: lib/solaar/ui/rule_conditions.py:52 +msgid "X11 active process. For use in X11 only." +msgstr "X11 aktiv process. Endast för användning i X11." + +#: lib/solaar/ui/rule_conditions.py:82 +msgid "X11 mouse process. For use in X11 only." +msgstr "X11 musprocess. Endast för användning i X11." + +#: lib/solaar/ui/rule_conditions.py:99 +msgid "MouseProcess" +msgstr "MouseProcess" + +#: lib/solaar/ui/rule_conditions.py:123 +msgid "Feature name of notification triggering rule processing." +msgstr "Funktionsnamn för avisering som utlöser regelbearbetning." + +#: lib/solaar/ui/rule_conditions.py:169 +msgid "Report number of notification triggering rule processing." +msgstr "Rapportnummer för avisering som utlöser regelbearbetning." + +#: lib/solaar/ui/rule_conditions.py:201 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "Aktiva tangentbordsmodifierare. Inte alltid tillgängligt i Wayland." + +#: lib/solaar/ui/rule_conditions.py:241 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"Omdirigerad knapp eller knapp nedtryckt eller släppt.\n" +"Använd inställningarna för vidarekoppling av tangent/knapp och " +"vidarekoppling av G-tangenter för att vidarekoppla tangenter och knappar." + +#: lib/solaar/ui/rule_conditions.py:250 +msgid "Key down" +msgstr "Tangent ner" + +#: lib/solaar/ui/rule_conditions.py:253 +msgid "Key up" +msgstr "Tangent upp" + +#: lib/solaar/ui/rule_conditions.py:293 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"Vidarekopplad nyckel eller knapp är för närvarande nere.\n" +"Använd inställningarna för vidarekoppling av tangent/knapp och " +"vidarekoppling av G-tangenter för att vidarekoppla tangenter och knappar." + +#: lib/solaar/ui/rule_conditions.py:331 +msgid "Test condition on notification triggering rule processing." +msgstr "Testvillkor för meddelande som utlöser regelbearbetning." + +#: lib/solaar/ui/rule_conditions.py:335 +msgid "Parameter" +msgstr "Parameter" + +#: lib/solaar/ui/rule_conditions.py:408 +msgid "begin (inclusive)" +msgstr "börja (inklusive)" + +#: lib/solaar/ui/rule_conditions.py:409 +msgid "end (exclusive)" +msgstr "slut (exklusivt)" + +#: lib/solaar/ui/rule_conditions.py:417 +msgid "range" +msgstr "omfång" + +#: lib/solaar/ui/rule_conditions.py:420 +msgid "minimum" +msgstr "minimum" + +#: lib/solaar/ui/rule_conditions.py:421 +msgid "maximum" +msgstr "maximum" + +#: lib/solaar/ui/rule_conditions.py:423 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "byte %(0)d till %(1)d, från %(2)d till %(3)d" + +#: lib/solaar/ui/rule_conditions.py:426 lib/solaar/ui/rule_conditions.py:427 +msgid "mask" +msgstr "mask" + +#: lib/solaar/ui/rule_conditions.py:428 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "byte %(0)d till %(1)d, mask %(2)d" + +#: lib/solaar/ui/rule_conditions.py:437 +msgid "" +"Bit or range test on bytes in notification message triggering rule " +"processing." +msgstr "" +"Bit- eller omfångstest på byte i aviseringsmeddelande som utlöser " +"regelbearbetning." + +#: lib/solaar/ui/rule_conditions.py:447 +msgid "type" +msgstr "typ" + +#: lib/solaar/ui/rule_conditions.py:535 +msgid "" +"Mouse gesture with optional initiating button followed by zero or more mouse " +"movements." +msgstr "" +"Musgest med valfri initieringsknapp följt av noll eller fler musrörelser." + +#: lib/solaar/ui/rule_conditions.py:541 +msgid "Add movement" +msgstr "Lägg till rörelse" + +#: lib/solaar/ui/tray.py:55 +msgid "No supported device found" +msgstr "Ingen enhet som stöds hittades" + +#: lib/solaar/ui/tray.py:60 lib/solaar/ui/window.py:328 +#, python-format +msgid "About %s" +msgstr "Om %s" + +#: lib/solaar/ui/tray.py:61 lib/solaar/ui/window.py:326 +#, python-format +msgid "Quit %s" +msgstr "Avsluta %s" + +#: lib/solaar/ui/tray.py:273 lib/solaar/ui/tray.py:281 +msgid "no receiver" +msgstr "ingen mottagare" + +#: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:299 +#: lib/solaar/ui/window.py:728 +msgid "offline" +msgstr "frånkopplad" + +#: lib/solaar/ui/tray.py:297 +msgid "no status" +msgstr "ingen status" + +#: lib/solaar/ui/window.py:110 +msgid "Scanning" +msgstr "Skannar" + +#: lib/solaar/ui/window.py:141 +msgid "Battery" +msgstr "Batteri" #: lib/solaar/ui/window.py:144 -msgid "Lighting" -msgstr "Belysning" +msgid "Wireless Link" +msgstr "Trådlös anslutning" -#: lib/solaar/ui/window.py:178 -msgid "Show Technical Details" -msgstr "Visa tekniska detaljer" +#: lib/solaar/ui/window.py:148 +msgid "Lighting" +msgstr "Belysning" -#: lib/solaar/ui/window.py:194 -msgid "Pair new device" -msgstr "Parkoppla en ny enhet" +#: lib/solaar/ui/window.py:182 +msgid "Show Technical Details" +msgstr "Visa tekniska detaljer" -#: lib/solaar/ui/window.py:213 -msgid "Select a device" -msgstr "Välj en enhet" +#: lib/solaar/ui/window.py:198 +msgid "Pair new device" +msgstr "Parkoppla ny enhet" + +#: lib/solaar/ui/window.py:216 +msgid "Select a device" +msgstr "Välj en enhet" #: lib/solaar/ui/window.py:331 -msgid "Rule Editor" -msgstr "" +msgid "Rule Editor" +msgstr "Regelredigerare" + +#: lib/solaar/ui/window.py:522 +msgid "Path" +msgstr "Sökväg" + +#: lib/solaar/ui/window.py:524 +msgid "USB ID" +msgstr "USB-ID" + +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 +#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 +msgid "Serial" +msgstr "Seriell" + +#: lib/solaar/ui/window.py:533 +msgid "Index" +msgstr "Index" + +#: lib/solaar/ui/window.py:535 +msgid "Wireless PID" +msgstr "Trådlös-PID" + +#: lib/solaar/ui/window.py:537 +msgid "Product ID" +msgstr "Produkt-ID" + +#: lib/solaar/ui/window.py:539 +msgid "Protocol" +msgstr "Protokoll" + +#: lib/solaar/ui/window.py:539 +msgid "Unknown" +msgstr "Okänd" #: lib/solaar/ui/window.py:541 -msgid "Path" -msgstr "Sökväg" +msgid "Polling rate" +msgstr "Uppdateringshastighet" -#: lib/solaar/ui/window.py:544 -msgid "USB ID" -msgstr "" - -#: lib/solaar/ui/window.py:547 lib/solaar/ui/window.py:549 -#: lib/solaar/ui/window.py:569 lib/solaar/ui/window.py:571 -msgid "Serial" -msgstr "Seriell" - -#: lib/solaar/ui/window.py:553 -msgid "Index" -msgstr "Index" - -#: lib/solaar/ui/window.py:555 -msgid "Wireless PID" -msgstr "Trådlös-PID" - -#: lib/solaar/ui/window.py:557 -msgid "Product ID" -msgstr "Produkt-ID" +#: lib/solaar/ui/window.py:548 +msgid "Unit ID" +msgstr "Enhets-ID" #: lib/solaar/ui/window.py:559 -msgid "Protocol" -msgstr "Protokoll" +msgid "none" +msgstr "ingen" -#: lib/solaar/ui/window.py:559 -msgid "Unknown" -msgstr "Okänd" +#: lib/solaar/ui/window.py:560 +msgid "Notifications" +msgstr "Aviseringar" -#: lib/solaar/ui/window.py:562 +#: lib/solaar/ui/window.py:604 +msgid "No device paired." +msgstr "Ingen enhet parkopplad." + +#: lib/solaar/ui/window.py:613 #, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d ms (%(rate_hz)dHz)" +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "Upp till %(max_count)s enhet kan parkopplas med denna mottagare." +msgstr[1] "Upp till %(max_count)s enheter kan parkopplas med denna mottagare." -#: lib/solaar/ui/window.py:562 -msgid "Polling rate" -msgstr "Uppdateringshastighet" +#: lib/solaar/ui/window.py:620 +msgid "Only one device can be paired to this receiver." +msgstr "Endast en enhet kan parkopplas ihop med denna mottagare." -#: lib/solaar/ui/window.py:573 -msgid "Unit ID" -msgstr "" - -#: lib/solaar/ui/window.py:584 -msgid "none" -msgstr "ingen" - -#: lib/solaar/ui/window.py:585 -msgid "Notifications" -msgstr "Notifikation" - -#: lib/solaar/ui/window.py:622 -msgid "No device paired." -msgstr "Ingen enhet kopplad." - -#: lib/solaar/ui/window.py:629 +#: lib/solaar/ui/window.py:624 #, python-format -msgid "Up to %(max_count)s device can be paired to this receiver." -msgid_plural "Up to %(max_count)s devices can be paired to this receiver." -msgstr[0] "Upp till %(max_count)s enhet kan kopplas till denna " - "mottagare." -msgstr[1] "Upp till %(max_count)s enheter kan kopplas till denna " - "mottagare." +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "Denna mottagare har %d parkoppling kvar." +msgstr[1] "Denna mottagare har %d parkopplingar kvar." -#: lib/solaar/ui/window.py:635 -msgid "Only one device can be paired to this receiver." -msgstr "Endast en enhet kan kopplas ihop med denna mottagare." +#: lib/solaar/ui/window.py:681 +msgid "Battery Voltage" +msgstr "Batterispänning" -#: lib/solaar/ui/window.py:639 -#, python-format -msgid "This receiver has %d pairing remaining." -msgid_plural "This receiver has %d pairings remaining." -msgstr[0] "Denna mottagare har %d parning kvar." -msgstr[1] "Denna mottagare har %d parningar kvar." +#: lib/solaar/ui/window.py:683 +msgid "Voltage reported by battery" +msgstr "Spänning rapporterad av batteri" -#: lib/solaar/ui/window.py:694 -msgid "Battery information unknown." -msgstr "" +#: lib/solaar/ui/window.py:685 +msgid "Battery Level" +msgstr "Batterinivå" + +#: lib/solaar/ui/window.py:687 +msgid "Approximate level reported by battery" +msgstr "Ungefärlig nivå rapporterad av batteriet" + +#: lib/solaar/ui/window.py:694 lib/solaar/ui/window.py:696 +msgid "next reported " +msgstr "nästa rapporterade " + +#: lib/solaar/ui/window.py:697 +msgid " and next level to be reported." +msgstr " och nästa nivå som ska rapporteras." #: lib/solaar/ui/window.py:702 -msgid "Battery Voltage" -msgstr "" +msgid "last known" +msgstr "senast kända" -#: lib/solaar/ui/window.py:704 -msgid "Voltage reported by battery" -msgstr "" +#: lib/solaar/ui/window.py:713 +msgid "encrypted" +msgstr "krypterad" -#: lib/solaar/ui/window.py:706 lib/solaar/ui/window.py:710 -msgid "Battery Level" -msgstr "" +#: lib/solaar/ui/window.py:715 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "" +"Den trådlösa anslutningen mellan denna enheten och dess mottagare är " +"krypterad." -#: lib/solaar/ui/window.py:708 lib/solaar/ui/window.py:712 -msgid "Approximate level reported by battery" -msgstr "" +#: lib/solaar/ui/window.py:717 +msgid "not encrypted" +msgstr "inte krypterad" -#: lib/solaar/ui/window.py:715 lib/solaar/ui/window.py:717 -msgid "next reported " -msgstr "" +#: lib/solaar/ui/window.py:721 +msgid "" +"The wireless link between this device and its receiver is not encrypted.\n" +"This is a security issue for pointing devices, and a major security issue " +"for text-input devices." +msgstr "" +"Den trådlösa anslutningen mellan den här enheten och dess mottagare är inte " +"krypterad.\n" +"Detta är ett säkerhetsproblem för pekdon och ett stort säkerhetsproblem för " +"enheter för textinmatning." -#: lib/solaar/ui/window.py:718 -msgid " and next level to be reported." -msgstr "" - -#: lib/solaar/ui/window.py:723 -msgid "last known" -msgstr "senast kända" - -#: lib/solaar/ui/window.py:731 -msgid "not encrypted" -msgstr "okrypterad" - -#: lib/solaar/ui/window.py:735 -msgid "The wireless link between this device and its receiver is not " - "encrypted.\n" - "\n" - "For pointing devices (mice, trackballs, trackpads), this is a minor " - "security issue.\n" - "\n" - "It is, however, a major security issue for text-input devices " - "(keyboards, numpads),\n" - "because typed text can be sniffed inconspicuously by 3rd parties " - "within range." -msgstr "Den trådlösa anslutningen mellan den här enheten och mottagaren är " - "okrypterad\n" - "\n" - "För pekdon (möss, styrkulor, pekplattor), är detta inget stort " - "säkerhetsproblem.\n" - "\n" - "Men för textinmatande enheter (tangentbord, numpads) är denna " - "säkerhetsbrist\n" - "allvarlig, eftersom skriven text obemärkt kan fångas upp av tredje " - "part som\n" - "befinner sig inom enhetens räckhåll." - -#: lib/solaar/ui/window.py:744 -msgid "encrypted" -msgstr "krypterad" - -#: lib/solaar/ui/window.py:746 -msgid "The wireless link between this device and its receiver is encrypted." -msgstr "Den trådlösa anslutningen mellan den här enheten och dess mottagare " - "är krypterad." - -#: lib/solaar/ui/window.py:759 +#: lib/solaar/ui/window.py:737 #, python-format -msgid "%(light_level)d lux" -msgstr "%(light_level)d lux" +msgid "%(light_level)d lux" +msgstr "%(light_level)d lux" -#~ msgid "\n" -#~ "\n" -#~ "This receiver has %d pairing(s) remaining." -#~ msgstr "\n" -#~ "\n" -#~ "Denna mottagare har %d parning(er) kvar." +#~ msgid "Turn illumination on or off on keyboard." +#~ msgstr "Slå på eller stäng av belysningen på tangentbordet." -#~ msgid " paired devices." -#~ msgstr "parkopplade enheter." +#~ msgid "" +#~ "Enable onboard profiles, which often control report rate and keyboard " +#~ "lighting" +#~ msgstr "" +#~ "Aktivera inbyggda profiler, som ofta styr rapporthastighet och " +#~ "tangentbordsbelysning" -#~ msgid "%(battery_level)s" -#~ msgstr "%(battery_level)s" +#~ msgid "Polling Rate (ms)" +#~ msgstr "Uppdateringsfrekvens (ms)" -#~ msgid "%(battery_percent)d%%" -#~ msgstr "%(battery_percent)d%%" +#~ msgid "Frequency of device polling, in milliseconds" +#~ msgstr "Frekvens för enhetsavfrågning, i millisekunder" -#~ msgid "1 paired device." -#~ msgstr "1 parkopplad enhet." +#~ msgid "Divert G Keys" +#~ msgstr "Avleda G-tangenter" -#~ msgid "Actions" -#~ msgstr "Åtgärder" +#~ msgid "" +#~ "Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " +#~ "are otherwise ignored)." +#~ msgstr "" +#~ "Få G-tangenter att skicka GKEY HID++-aviseringar (som utlöser Solaar-" +#~ "regler men som annars ignoreras)." -#~ msgid "Automatically switch the mouse wheel between ratchet and " -#~ "freespin mode.\n" -#~ "The mouse wheel is always free at 0, and always locked at 50" -#~ msgstr "Växla automatiskt mushjulet mellan spärr- och frispinn-" -#~ "läge.\n" -#~ "Mushjulet är alltid fritt vid 0 och låses alltid vid 50" +#~ msgid "May also make M keys and MR key send HID++ notifications" +#~ msgstr "" +#~ "Kan också få M-tangenter och MR-tangenten att skicka HID++-aviseringar" -#~ msgid "For more information see the Solaar installation directions\n" -#~ "at https://pwr-solaar.github.io/Solaar/installation" -#~ msgstr "Mer information finns i Solaar-installationsanvisningarna\n" -#~ "på https://pwr-solaar.github.io/Solaar/installation" +#, python-format +#~ msgid "Battery: %(level)s" +#~ msgstr "Batteri: %(level)s" -#~ msgid "Found a new device" -#~ msgstr "Ny enhet har hittats" +#, python-format +#~ msgid "Battery: %(percent)d%%" +#~ msgstr "Batteri: %(percent)d%%" -#~ msgid "HID++ Scrolling" -#~ msgstr "HID++ rullning" +#, python-format +#~ msgid "Lighting: %(level)s lux" +#~ msgstr "Belysning: %(level)s lux" -#~ msgid "HID++ Thumb Scrolling" -#~ msgstr "HID++ thumrullning" +#~ msgid "Number of seconds to delay." +#~ msgstr "Antal sekunder att fördröja." -#~ msgid "High Resolution Scrolling" -#~ msgstr "Rullning med hög upplösning" +#~ msgid "Count and Action" +#~ msgstr "Antal och åtgärd" -#~ msgid "High Resolution Wheel Invert" -#~ msgstr "Invertera hög hjulupplösning" +#~ msgid "You may have to first turn the device off and on again." +#~ msgstr "Du kanske först måste stänga av enheten och slå på den igen." -#~ msgid "High-sensitivity wheel invert direction for vertical scroll." -#~ msgstr "Invertera riktning för vertikal rullning med hög känslighet." +#~ msgid "Turn on the device you want to pair." +#~ msgstr "Slå på enheten du vill parkoppla." -#~ msgid "High-sensitivity wheel invert mode for vertical scroll." -#~ msgstr "Inverteringsläge med hög känslighet för vertikal rullning." +#, python-format +#~ msgid "%(rate)d ms (%(rate_hz)dHz)" +#~ msgstr "%(rate)d ms (%(rate_hz)dHz)" -#~ msgid "If the device is already turned on,\n" -#~ "turn if off and on again." -#~ msgstr "Om enheten redan är igång,\n" -#~ "stäng av den och sätt på den igen." +#~ msgid "" +#~ "\n" +#~ "\n" +#~ "This receiver has %d pairing(s) remaining." +#~ msgstr "" +#~ "\n" +#~ "\n" +#~ "Denna mottagare har %d parning(er) kvar." -#~ msgid "If the device is already turned on, turn if off and on again." -#~ msgstr "Om enheten redan är påslagen, stäng av og slå den på igen." +#~ msgid " paired devices." +#~ msgstr "parkopplade enheter." -#~ msgid "Invert thumb scroll direction." -#~ msgstr "Invertera tumrulleriktningen." +#~ msgid "%(battery_level)s" +#~ msgstr "%(battery_level)s" -#~ msgid "No device paired" -#~ msgstr "Inga enheter parkopplade" +#~ msgid "%(battery_percent)d%%" +#~ msgstr "%(battery_percent)d%%" -#~ msgid "Only one device can be paired to this receiver" -#~ msgstr "Bara en enhet kan parkopplas till den här mottagaren" +#~ msgid "1 paired device." +#~ msgstr "1 parkopplad enhet." -#~ msgid "Shows status of devices connected\n" -#~ "through wireless Logitech receivers." -#~ msgstr "Visar status för enheter kopplade till\n" -#~ "din trådlösa Logitech mottagare." +#~ msgid "Actions" +#~ msgstr "Åtgärder" -#~ msgid "Smooth Scrolling" -#~ msgstr "Mjuk rullning" +#~ msgid "" +#~ "Automatically switch the mouse wheel between ratchet and freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always locked at 50" +#~ msgstr "" +#~ "Växla automatiskt mushjulet mellan spärr- och frispinn-läge.\n" +#~ "Mushjulet är alltid fritt vid 0 och låses alltid vid 50" -#~ msgid "Solaar depends on a udev file that is not present" -#~ msgstr "Solaar beror på en udev-fil som inte finns" +#~ msgid "Count" +#~ msgstr "Antal" -#~ msgid "The receiver only supports %d paired device(s)." -#~ msgstr "Mottagaren klarar bara %d parkopplad(e) enhet(er)." +#~ msgid "Effectively turns off thumb scrolling in Linux." +#~ msgstr "Stänger effektivt av tumrullning i Linux." -#~ msgid "The receiver was unplugged." -#~ msgstr "Mottagaren kopplades ur." +#~ msgid "Effectively turns off wheel scrolling in Linux." +#~ msgstr "Stänger effektivt av rullning i Linux." -#~ msgid "This receiver has %d pairing(s) remaining." -#~ msgstr "Denna mottagare har %d parning(er) kvar." +#~ msgid "" +#~ "For more information see the Solaar installation directions\n" +#~ "at https://pwr-solaar.github.io/Solaar/installation" +#~ msgstr "" +#~ "Mer information finns i Solaar-installationsanvisningarna\n" +#~ "på https://pwr-solaar.github.io/Solaar/installation" -#~ msgid "Thumb Scroll Invert" -#~ msgstr "Invertera tumrullningen" +#, python-format +#~ msgid "" +#~ "Found a Logitech Receiver (%s), but did not have permission to open it." +#~ msgstr "" +#~ "En Logitech mottagare hittades (%s), fast saknar behörighet till att " +#~ "öppna den." -#~ msgid "USB id" -#~ msgstr "USB-id" +#~ msgid "Found a new device" +#~ msgstr "Ny enhet har hittats" -#~ msgid "Unexpected device number (%s) in notification %s." -#~ msgstr "Oväntat enhetsnummer (%s) i meddelandet %s." +#~ msgid "HID++ Scrolling" +#~ msgstr "HID++ rullning" -#~ msgid "Up to %d devices can be paired to this receiver" -#~ msgstr "Upp till %d enheter kan parkopplas till mottagaren" +#~ msgid "HID++ Thumb Scrolling" +#~ msgstr "HID++ thumrullning" -#~ msgid "Wheel Resolution" -#~ msgstr "Hjulupplösning" +#~ msgid "HID++ mode for horizontal scroll with the thumb wheel." +#~ msgstr "HID ++ -läge för horisontell rullning med tumhjulet." -#~ msgid "closed" -#~ msgstr "inaktiverat" +#~ msgid "HID++ mode for vertical scroll with the wheel." +#~ msgstr "HID++ läge för vertikal rullning med hjulet." -#~ msgid "lux" -#~ msgstr "lux" +#~ msgid "High Resolution Scrolling" +#~ msgstr "Rullning med hög upplösning" -#~ msgid "next " -#~ msgstr "nästa " +#~ msgid "High Resolution Wheel Invert" +#~ msgstr "Invertera hög hjulupplösning" -#~ msgid "open" -#~ msgstr "aktiverat" +#~ msgid "High-sensitivity wheel invert direction for vertical scroll." +#~ msgstr "Invertera riktning för vertikal rullning med hög känslighet." -#~ msgid "pair new device" -#~ msgstr "parkoppla ny enhet" +#~ msgid "High-sensitivity wheel invert mode for vertical scroll." +#~ msgstr "Inverteringsläge med hög känslighet för vertikal rullning." -#~ msgid "paired devices" -#~ msgstr "parkopplade enheter" +#~ msgid "" +#~ "If the device is already turned on,\n" +#~ "turn if off and on again." +#~ msgstr "" +#~ "Om enheten redan är igång,\n" +#~ "stäng av den och sätt på den igen." -#~ msgid "pairing lock is " -#~ msgstr "parkopplingsläget är " +#~ msgid "If the device is already turned on, turn if off and on again." +#~ msgstr "Om enheten redan är påslagen, stäng av og slå den på igen." + +#~ msgid "" +#~ "If you've just installed Solaar, try removing the receiver and plugging " +#~ "it back in." +#~ msgstr "" +#~ "Om du just installerade solaar, prova koppla ur mottagaren och anslut den " +#~ "sedan igen." + +#~ msgid "Invert thumb scroll direction." +#~ msgstr "Invertera tumrulleriktningen." + +#~ msgid "No Logitech receiver found" +#~ msgstr "Ingen Logitech mottagare hittades" + +#~ msgid "No device paired" +#~ msgstr "Inga enheter parkopplade" + +#~ msgid "Only one device can be paired to this receiver" +#~ msgstr "Bara en enhet kan parkopplas till den här mottagaren" + +#~ msgid "" +#~ "Shows status of devices connected\n" +#~ "through wireless Logitech receivers." +#~ msgstr "" +#~ "Visar status för enheter kopplade till\n" +#~ "din trådlösa Logitech mottagare." + +#~ msgid "Smooth Scrolling" +#~ msgstr "Mjuk rullning" + +#~ msgid "Solaar depends on a udev file that is not present" +#~ msgstr "Solaar beror på en udev-fil som inte finns" + +#~ msgid "The receiver only supports %d paired device(s)." +#~ msgstr "Mottagaren klarar bara %d parkopplad(e) enhet(er)." + +#~ msgid "The receiver was unplugged." +#~ msgstr "Mottagaren kopplades ur." + +#~ msgid "" +#~ "The wireless link between this device and its receiver is not encrypted.\n" +#~ "\n" +#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " +#~ "security issue.\n" +#~ "\n" +#~ "It is, however, a major security issue for text-input devices (keyboards, " +#~ "numpads),\n" +#~ "because typed text can be sniffed inconspicuously by 3rd parties within " +#~ "range." +#~ msgstr "" +#~ "Den trådlösa anslutningen mellan den här enheten och mottagaren är " +#~ "okrypterad\n" +#~ "\n" +#~ "För pekdon (möss, styrkulor, pekplattor), är detta inget stort " +#~ "säkerhetsproblem.\n" +#~ "\n" +#~ "Men för textinmatande enheter (tangentbord, numpads) är denna " +#~ "säkerhetsbrist\n" +#~ "allvarlig, eftersom skriven text obemärkt kan fångas upp av tredje part " +#~ "som\n" +#~ "befinner sig inom enhetens räckhåll." + +#~ msgid "This receiver has %d pairing(s) remaining." +#~ msgstr "Denna mottagare har %d parning(er) kvar." + +#~ msgid "Thumb Scroll Invert" +#~ msgstr "Invertera tumrullningen" + +#~ msgid "USB id" +#~ msgstr "USB-id" + +#~ msgid "Unexpected device number (%s) in notification %s." +#~ msgstr "Oväntat enhetsnummer (%s) i meddelandet %s." + +#~ msgid "Up to %d devices can be paired to this receiver" +#~ msgstr "Upp till %d enheter kan parkopplas till mottagaren" + +#~ msgid "Wheel Resolution" +#~ msgstr "Hjulupplösning" + +#~ msgid "closed" +#~ msgstr "inaktiverat" + +#~ msgid "height" +#~ msgstr "höjd" + +#~ msgid "lux" +#~ msgstr "lux" + +#~ msgid "next " +#~ msgstr "nästa " + +#~ msgid "open" +#~ msgstr "aktiverat" + +#~ msgid "pair new device" +#~ msgstr "parkoppla ny enhet" + +#~ msgid "paired devices" +#~ msgstr "parkopplade enheter" + +#~ msgid "pairing lock is " +#~ msgstr "parkopplingslåset är " + +#~ msgid "unknown" +#~ msgstr "okänd" + +#~ msgid "width" +#~ msgstr "bredd" diff --git a/po/tr.po b/po/tr.po index 4008c65e..0bce566b 100644 --- a/po/tr.po +++ b/po/tr.po @@ -4,14 +4,14 @@ # Automatically generated, 2015. # # Behzat Erte, , 2015. -# Osman Karagöz, , 2022. +# Osman Karagöz, , 2024. # msgid "" msgstr "" "Project-Id-Version: solaar 0.9.2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-08-10 16:48+0200\n" -"PO-Revision-Date: 2022-09-09 13:26+0300\n" +"POT-Creation-Date: 2024-07-01 13:49+0800\n" +"PO-Revision-Date: 2024-11-17 16:01+0300\n" "Last-Translator: Osman Karagöz \n" "Language-Team: \n" "Language: tr\n" @@ -19,266 +19,384 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.1\n" +"X-Generator: Poedit 3.4.2\n" "X-Poedit-SourceCharset: UTF-8\n" -#: lib/logitech_receiver/base_usb.py:47 +#: lib/logitech_receiver/base_usb.py:45 msgid "Bolt Receiver" msgstr "Bolt Alıcı" -#: lib/logitech_receiver/base_usb.py:58 +#: lib/logitech_receiver/base_usb.py:57 msgid "Unifying Receiver" msgstr "Unifying Alıcı" -#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 -#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 -#: lib/logitech_receiver/base_usb.py:114 +#: lib/logitech_receiver/base_usb.py:68 lib/logitech_receiver/base_usb.py:80 +#: lib/logitech_receiver/base_usb.py:93 lib/logitech_receiver/base_usb.py:106 +#: lib/logitech_receiver/base_usb.py:119 msgid "Nano Receiver" msgstr "Nano Alıcı" -#: lib/logitech_receiver/base_usb.py:123 +#: lib/logitech_receiver/base_usb.py:131 msgid "Lightspeed Receiver" msgstr "Lightspeed Alıcı" -#: lib/logitech_receiver/base_usb.py:131 +#: lib/logitech_receiver/base_usb.py:141 msgid "EX100 Receiver 27 Mhz" msgstr "EX100 27 Mhz Alıcı" -#: lib/logitech_receiver/i18n.py:30 +#: lib/logitech_receiver/common.py:610 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Pil: %(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:613 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Pil: %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:921 +#: lib/logitech_receiver/settings_templates.py:283 +msgid "Disabled" +msgstr "Devredışı" + +#: lib/logitech_receiver/hidpp20.py:922 +msgid "Static" +msgstr "Sabit" + +#: lib/logitech_receiver/hidpp20.py:923 +msgid "Pulse" +msgstr "Nabız" + +#: lib/logitech_receiver/hidpp20.py:924 +msgid "Cycle" +msgstr "Döngü" + +#: lib/logitech_receiver/hidpp20.py:925 +msgid "Boot" +msgstr "Önyükleme" + +#: lib/logitech_receiver/hidpp20.py:926 +msgid "Demo" +msgstr "Demo" + +#: lib/logitech_receiver/hidpp20.py:927 +msgid "Breathe" +msgstr "Nefes" + +#: lib/logitech_receiver/hidpp20.py:928 +msgid "Ripple" +msgstr "Dalgalanma" + +#: lib/logitech_receiver/hidpp20.py:929 +msgid "Decomposition" +msgstr "Ayrışma" + +#: lib/logitech_receiver/hidpp20.py:930 +msgid "Signature1" +msgstr "İmza1" + +#: lib/logitech_receiver/hidpp20.py:931 +msgid "Signature2" +msgstr "İmza2" + +#: lib/logitech_receiver/hidpp20.py:932 +msgid "CycleS" +msgstr "DöngüS" + +#: lib/logitech_receiver/hidpp20.py:996 +msgid "Unknown Location" +msgstr "Bilinmeyen Konum" + +#: lib/logitech_receiver/hidpp20.py:997 +msgid "Primary" +msgstr "Birincil" + +#: lib/logitech_receiver/hidpp20.py:998 +msgid "Logo" +msgstr "Logo" + +#: lib/logitech_receiver/hidpp20.py:999 +msgid "Left Side" +msgstr "Sola Taraf" + +#: lib/logitech_receiver/hidpp20.py:1000 +msgid "Right Side" +msgstr "Sağ Taraf" + +#: lib/logitech_receiver/hidpp20.py:1001 +msgid "Combined" +msgstr "Birleşik" + +#: lib/logitech_receiver/hidpp20.py:1002 +msgid "Primary 1" +msgstr "Birincil 1" + +#: lib/logitech_receiver/hidpp20.py:1003 +msgid "Primary 2" +msgstr "Birincil 2" + +#: lib/logitech_receiver/hidpp20.py:1004 +msgid "Primary 3" +msgstr "Birincil 3" + +#: lib/logitech_receiver/hidpp20.py:1005 +msgid "Primary 4" +msgstr "Birincil 4" + +#: lib/logitech_receiver/hidpp20.py:1006 +msgid "Primary 5" +msgstr "Birincil 5" + +#: lib/logitech_receiver/hidpp20.py:1007 +msgid "Primary 6" +msgstr "Birincil 6" + +#: lib/logitech_receiver/i18n.py:28 msgid "empty" msgstr "boş" -#: lib/logitech_receiver/i18n.py:31 +#: lib/logitech_receiver/i18n.py:29 msgid "critical" msgstr "kritik" -#: lib/logitech_receiver/i18n.py:32 +#: lib/logitech_receiver/i18n.py:30 msgid "low" msgstr "az" -#: lib/logitech_receiver/i18n.py:33 +#: lib/logitech_receiver/i18n.py:31 msgid "average" msgstr "ortalama" -#: lib/logitech_receiver/i18n.py:34 +#: lib/logitech_receiver/i18n.py:32 msgid "good" msgstr "iyi" -#: lib/logitech_receiver/i18n.py:35 +#: lib/logitech_receiver/i18n.py:33 msgid "full" msgstr "dolu" -#: lib/logitech_receiver/i18n.py:38 +#: lib/logitech_receiver/i18n.py:35 msgid "discharging" msgstr "boşalıyor" -#: lib/logitech_receiver/i18n.py:39 +#: lib/logitech_receiver/i18n.py:36 msgid "recharging" msgstr "yeniden şarj ediliyor" -#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:718 +#: lib/logitech_receiver/i18n.py:37 msgid "charging" msgstr "şarj ediliyor" -#: lib/logitech_receiver/i18n.py:41 +#: lib/logitech_receiver/i18n.py:38 msgid "not charging" msgstr "şarj edilmiyor" -#: lib/logitech_receiver/i18n.py:42 +#: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "nerdeyse dolu" -#: lib/logitech_receiver/i18n.py:43 +#: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "şarj edildi" -#: lib/logitech_receiver/i18n.py:44 +#: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "yavaş yeniden şarj edilme" -#: lib/logitech_receiver/i18n.py:45 +#: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "geçersiz pil" -#: lib/logitech_receiver/i18n.py:46 +#: lib/logitech_receiver/i18n.py:43 msgid "thermal error" -msgstr "ısı hatası" +msgstr "sıcaklık hatası" -#: lib/logitech_receiver/i18n.py:47 +#: lib/logitech_receiver/i18n.py:44 msgid "error" msgstr "hata" -#: lib/logitech_receiver/i18n.py:48 +#: lib/logitech_receiver/i18n.py:45 msgid "standard" msgstr "standart" -#: lib/logitech_receiver/i18n.py:49 +#: lib/logitech_receiver/i18n.py:46 msgid "fast" msgstr "hızlı" -#: lib/logitech_receiver/i18n.py:50 +#: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "yavaş" -#: lib/logitech_receiver/i18n.py:53 +#: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "aygıt zaman aşımı" -#: lib/logitech_receiver/i18n.py:54 +#: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "aygıt desteklenmiyor" -#: lib/logitech_receiver/i18n.py:55 +#: lib/logitech_receiver/i18n.py:51 msgid "too many devices" -msgstr "aygıt sayısı cok fazla" +msgstr "çok fazla aygıt" -#: lib/logitech_receiver/i18n.py:56 +#: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" -msgstr "sıralı zaman aşımı" +msgstr "sıralama zaman aşımı" -#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:577 +#: lib/logitech_receiver/i18n.py:54 msgid "Firmware" msgstr "Donanım Yazılımı" -#: lib/logitech_receiver/i18n.py:60 +#: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Önyükleme Yazılımı" -#: lib/logitech_receiver/i18n.py:61 +#: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Donanım" -#: lib/logitech_receiver/i18n.py:62 +#: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "Diğer" -#: lib/logitech_receiver/i18n.py:65 +#: lib/logitech_receiver/i18n.py:59 msgid "Left Button" msgstr "Sol Düğme" -#: lib/logitech_receiver/i18n.py:66 +#: lib/logitech_receiver/i18n.py:60 msgid "Right Button" msgstr "Sag Düğme" -#: lib/logitech_receiver/i18n.py:67 +#: lib/logitech_receiver/i18n.py:61 msgid "Middle Button" msgstr "Orta Düğme" -#: lib/logitech_receiver/i18n.py:68 +#: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "Geri Düğmesi" -#: lib/logitech_receiver/i18n.py:69 +#: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "İleri Düğmesi" -#: lib/logitech_receiver/i18n.py:70 +#: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "Fare Hareketi Düğmesi" -#: lib/logitech_receiver/i18n.py:71 +#: lib/logitech_receiver/i18n.py:65 msgid "Smart Shift" msgstr "Akıllı Kaydırma" -#: lib/logitech_receiver/i18n.py:72 +#: lib/logitech_receiver/i18n.py:66 msgid "DPI Switch" -msgstr "Çözünürlük düğmesi" +msgstr "DPI Anahtarı" -#: lib/logitech_receiver/i18n.py:73 +#: lib/logitech_receiver/i18n.py:67 msgid "Left Tilt" -msgstr "Sol Eğim" +msgstr "Sola Eğme" -#: lib/logitech_receiver/i18n.py:74 +#: lib/logitech_receiver/i18n.py:68 msgid "Right Tilt" -msgstr "Sağ Eğim" +msgstr "Sağa Eğme" -#: lib/logitech_receiver/i18n.py:75 +#: lib/logitech_receiver/i18n.py:69 msgid "Left Click" -msgstr "Sol Tık" +msgstr "Sol Tıklama" -#: lib/logitech_receiver/i18n.py:76 +#: lib/logitech_receiver/i18n.py:70 msgid "Right Click" -msgstr "Sağ Tık" +msgstr "Sağ Tıklama" -#: lib/logitech_receiver/i18n.py:77 +#: lib/logitech_receiver/i18n.py:71 msgid "Mouse Middle Button" msgstr "Fare Orta Düğmesi" -#: lib/logitech_receiver/i18n.py:78 +#: lib/logitech_receiver/i18n.py:72 msgid "Mouse Back Button" msgstr "Fare Geri Düğmesi" -#: lib/logitech_receiver/i18n.py:79 +#: lib/logitech_receiver/i18n.py:73 msgid "Mouse Forward Button" msgstr "Fare İleri Düğmesi" -#: lib/logitech_receiver/i18n.py:80 +#: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" -msgstr "Hareket Tuşu Navigasyonu" +msgstr "Hareket Düğmesi Navigasyonu" -#: lib/logitech_receiver/i18n.py:81 +#: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "Fare Tekerleği Sol Düğmesi" -#: lib/logitech_receiver/i18n.py:82 +#: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "Fare Tekerleği Sağ Düğmesi" -#: lib/logitech_receiver/i18n.py:85 +#: lib/logitech_receiver/i18n.py:78 msgid "pressed" -msgstr "basılı" +msgstr "basıldı" -#: lib/logitech_receiver/i18n.py:86 +#: lib/logitech_receiver/i18n.py:79 msgid "released" msgstr "bırakıldı" -#: lib/logitech_receiver/notifications.py:74 -#: lib/logitech_receiver/notifications.py:125 +#: lib/logitech_receiver/notifications.py:64 +#: lib/logitech_receiver/notifications.py:116 msgid "pairing lock is closed" msgstr "eşleştirme kilidi kapalı" -#: lib/logitech_receiver/notifications.py:74 -#: lib/logitech_receiver/notifications.py:125 +#: lib/logitech_receiver/notifications.py:64 +#: lib/logitech_receiver/notifications.py:116 msgid "pairing lock is open" msgstr "eşleştirme kilidi açık" -#: lib/logitech_receiver/notifications.py:91 +#: lib/logitech_receiver/notifications.py:81 msgid "discovery lock is closed" msgstr "keşif kilidi kapalı" -#: lib/logitech_receiver/notifications.py:91 +#: lib/logitech_receiver/notifications.py:81 msgid "discovery lock is open" msgstr "keşif kilidi açık" -#: lib/logitech_receiver/notifications.py:223 lib/solaar/ui/notify.py:120 +#: lib/logitech_receiver/notifications.py:210 lib/solaar/listener.py:238 msgid "connected" msgstr "bağlı" -#: lib/logitech_receiver/notifications.py:223 +#: lib/logitech_receiver/notifications.py:210 lib/solaar/listener.py:238 msgid "disconnected" msgstr "bağlantı kesildi" -#: lib/logitech_receiver/notifications.py:261 lib/solaar/ui/notify.py:118 +#: lib/logitech_receiver/notifications.py:236 msgid "unpaired" msgstr "eşleşmemiş" -#: lib/logitech_receiver/notifications.py:303 +#: lib/logitech_receiver/notifications.py:283 msgid "powered on" msgstr "açık" -#: lib/logitech_receiver/settings.py:734 +#: lib/logitech_receiver/receiver.py:371 +msgid "No paired devices." +msgstr "Eşleşmiş aygıt yok." + +#: lib/logitech_receiver/receiver.py:373 lib/solaar/ui/window.py:604 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s eşleşmiş aygıt." +msgstr[1] "%(count)s eşleşmiş aygıt." + +#: lib/logitech_receiver/settings.py:611 msgid "register" msgstr "kaydet" -#: lib/logitech_receiver/settings.py:748 lib/logitech_receiver/settings.py:775 +#: lib/logitech_receiver/settings.py:625 lib/logitech_receiver/settings.py:651 msgid "feature" msgstr "özellik" -#: lib/logitech_receiver/settings_templates.py:139 +#: lib/logitech_receiver/settings_templates.py:122 msgid "Swap Fx function" -msgstr "Fx işlevlerini ters cevir" +msgstr "Fx işlevlerini ters çevir" -#: lib/logitech_receiver/settings_templates.py:140 +#: lib/logitech_receiver/settings_templates.py:125 msgid "" "When set, the F1..F12 keys will activate their special function,\n" "and you must hold the FN key to activate their standard function." @@ -286,7 +404,7 @@ msgstr "" "F1..F12 tuşları özel işlevler için ayarlandığında,\n" "FN tuşuna basılı tutarak standart işlevlerini aktif edebilirsiniz." -#: lib/logitech_receiver/settings_templates.py:142 +#: lib/logitech_receiver/settings_templates.py:130 msgid "" "When unset, the F1..F12 keys will activate their standard function,\n" "and you must hold the FN key to activate their special function." @@ -294,221 +412,300 @@ msgstr "" "F1..F12 tuşları standart işlevler için ayarlandığında,\n" "FN tuşuna basılı tutarak özel işlevlerini aktif edebilirsiniz." -#: lib/logitech_receiver/settings_templates.py:149 +#: lib/logitech_receiver/settings_templates.py:138 msgid "Hand Detection" msgstr "El Algılama" -#: lib/logitech_receiver/settings_templates.py:150 +#: lib/logitech_receiver/settings_templates.py:139 msgid "Turn on illumination when the hands hover over the keyboard." msgstr "Eller klavyenin üzerindeyken aydınlanmayı aç." -#: lib/logitech_receiver/settings_templates.py:157 +#: lib/logitech_receiver/settings_templates.py:146 msgid "Scroll Wheel Smooth Scrolling" msgstr "Kaydırma Tekerleği Yumuşak Kaydırma" -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:239 -#: lib/logitech_receiver/settings_templates.py:267 +#: lib/logitech_receiver/settings_templates.py:147 +#: lib/logitech_receiver/settings_templates.py:394 +#: lib/logitech_receiver/settings_templates.py:423 msgid "High-sensitivity mode for vertical scroll with the wheel." msgstr "Tekerleğin dikey kaydırılması için yüksek hasasiyet modu." -#: lib/logitech_receiver/settings_templates.py:165 +#: lib/logitech_receiver/settings_templates.py:154 msgid "Side Scrolling" -msgstr "Yana Kaydırma" +msgstr "Yatay Kaydırma" -#: lib/logitech_receiver/settings_templates.py:167 +#: lib/logitech_receiver/settings_templates.py:156 msgid "" "When disabled, pushing the wheel sideways sends custom button events\n" "instead of the standard side-scrolling events." msgstr "" -"Devredışı olduğunda, tekerleğin yanına basmak standart yana kaydırma komutu " +"Devredışı olduğunda, tekerleğin yanına basmak standart yatay kaydırma komutu " "yerine\n" "özel tuş komutunu gönderir." -#: lib/logitech_receiver/settings_templates.py:177 +#: lib/logitech_receiver/settings_templates.py:166 msgid "Sensitivity (DPI - older mice)" msgstr "Hassasiyet (DPI - eski fare)" -#: lib/logitech_receiver/settings_templates.py:178 -#: lib/logitech_receiver/settings_templates.py:687 +#: lib/logitech_receiver/settings_templates.py:167 +#: lib/logitech_receiver/settings_templates.py:955 +#: lib/logitech_receiver/settings_templates.py:983 msgid "Mouse movement sensitivity" msgstr "Fare hareketi hassasiyeti" -#: lib/logitech_receiver/settings_templates.py:208 -#: lib/logitech_receiver/settings_templates.py:218 -#: lib/logitech_receiver/settings_templates.py:225 +#: lib/logitech_receiver/settings_templates.py:240 +msgid "Backlight Timed" +msgstr "Zamanlanmış Aydınlatma" + +#: lib/logitech_receiver/settings_templates.py:241 +#: lib/logitech_receiver/settings_templates.py:381 +msgid "Set illumination time for keyboard." +msgstr "Klavye için aydınlatma zaman aşımı." + +#: lib/logitech_receiver/settings_templates.py:252 msgid "Backlight" msgstr "Aydınlatma" -#: lib/logitech_receiver/settings_templates.py:209 -#: lib/logitech_receiver/settings_templates.py:226 -msgid "Set illumination time for keyboard." -msgstr "Klavye için aydınlanma zaman aşımı." +#: lib/logitech_receiver/settings_templates.py:253 +msgid "" +"Illumination level on keyboard. Changes made are only applied in Manual " +"mode." +msgstr "" +"Klavyedeki aydınlatma düzeyi. Değişiklikler sadece Elle modunda uygulanır." -#: lib/logitech_receiver/settings_templates.py:219 -msgid "Turn illumination on or off on keyboard." -msgstr "Klavyede aydınlatmayı açma kapama." +#: lib/logitech_receiver/settings_templates.py:285 +msgid "Automatic" +msgstr "Otomatik" -#: lib/logitech_receiver/settings_templates.py:237 +#: lib/logitech_receiver/settings_templates.py:287 +msgid "Manual" +msgstr "Elle" + +#: lib/logitech_receiver/settings_templates.py:289 +msgid "Enabled" +msgstr "Etkin" + +#: lib/logitech_receiver/settings_templates.py:295 +msgid "Backlight Level" +msgstr "Aydınlatma Düzeyi" + +#: lib/logitech_receiver/settings_templates.py:296 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "Elle modundaki klavye aydınlatma düzeyi." + +#: lib/logitech_receiver/settings_templates.py:353 +msgid "Backlight Delay Hands Out" +msgstr "Eller Çekildiğinde Aydınlatma Gecikmesi" + +#: lib/logitech_receiver/settings_templates.py:354 +msgid "" +"Delay in seconds until backlight fades out with hands away from keyboard." +msgstr "" +"Eller klavyeden uzaklaştırıldığında aydınlatmanın solması için gecikme." + +#: lib/logitech_receiver/settings_templates.py:362 +msgid "Backlight Delay Hands In" +msgstr "Eller Yaklaştırıldığında Aydıtlatma Gecikmesi" + +#: lib/logitech_receiver/settings_templates.py:363 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "Eller klavyeye yaklaştırıldığında aydınlatmanın solması için gecikme." + +#: lib/logitech_receiver/settings_templates.py:371 +msgid "Backlight Delay Powered" +msgstr "Güce Bağlandığında Aydınlatma Gecikmesi" + +#: lib/logitech_receiver/settings_templates.py:372 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "Güce bağlandığında aydınlatmanın solması için gecikme." + +#: lib/logitech_receiver/settings_templates.py:380 +msgid "Backlight (Seconds)" +msgstr "Aydınlatma (Saniye)" + +#: lib/logitech_receiver/settings_templates.py:392 msgid "Scroll Wheel High Resolution" -msgstr "Yüksek Çözünürlüklü Kaydırma Tekerleği" +msgstr "Kaydırma Tekerleği Yüksek Çözünürlük" -#: lib/logitech_receiver/settings_templates.py:240 -#: lib/logitech_receiver/settings_templates.py:268 +#: lib/logitech_receiver/settings_templates.py:396 +#: lib/logitech_receiver/settings_templates.py:425 msgid "Set to ignore if scrolling is abnormally fast or slow" msgstr "Kaydırma aşırı yavaş veya hızlıysa yoksaymak için ayarla" -#: lib/logitech_receiver/settings_templates.py:247 -#: lib/logitech_receiver/settings_templates.py:277 +#: lib/logitech_receiver/settings_templates.py:403 +#: lib/logitech_receiver/settings_templates.py:434 msgid "Scroll Wheel Diversion" msgstr "Kaydırma Tekerleği Yönlendirme" -#: lib/logitech_receiver/settings_templates.py:249 +#: lib/logitech_receiver/settings_templates.py:405 msgid "" "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " "Solaar rules but are otherwise ignored)." msgstr "" -"Kaydırma tekerleğini LOWRES_WHEEL HID++ bildirimi gönderecek şekilde ayarla " -"(Solaar rölü tetikler veya yoksayar)" +"Kaydırma tekerleğinin LOWRES_WHEEL HID++ bildirimler göndermesini sağlar (bu " +"bildirimler Solaar kurallarını tetikler, ancak diğer durumlarda görmezden " +"gelinir)." -#: lib/logitech_receiver/settings_templates.py:256 +#: lib/logitech_receiver/settings_templates.py:412 msgid "Scroll Wheel Direction" msgstr "Kaydırma Tekerleği Yönü" -#: lib/logitech_receiver/settings_templates.py:257 +#: lib/logitech_receiver/settings_templates.py:413 msgid "Invert direction for vertical scroll with wheel." -msgstr "Tekerlekle kaydırmada dikey yönü tersine çevirir." +msgstr "Kaydırma tekerleği ile dikey kaydırma yönünü tersine çevirir." -#: lib/logitech_receiver/settings_templates.py:265 +#: lib/logitech_receiver/settings_templates.py:421 msgid "Scroll Wheel Resolution" msgstr "Kaydırma Tekerleği Çözünürlüğü" -#: lib/logitech_receiver/settings_templates.py:279 +#: lib/logitech_receiver/settings_templates.py:436 msgid "" "Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "" -"Kaydırma tekerleğini HIRES_WHEEL HID++ bildirimi gönderecek şekilde ayarla " -"(Solaar rölü tetikler veya yoksayar)" +"Kaydırma tekerleğinin HIRES_WHEEL HID++ bildirimler göndermesini sağlar (bu " +"bildirimler Solaar kurallarını tetikler, ancak diğer durumlarda görmezden " +"gelinir)." -#: lib/logitech_receiver/settings_templates.py:288 +#: lib/logitech_receiver/settings_templates.py:445 msgid "Sensitivity (Pointer Speed)" msgstr "Hassasiyet (İşaretçi Hızı)" -#: lib/logitech_receiver/settings_templates.py:289 +#: lib/logitech_receiver/settings_templates.py:446 msgid "Speed multiplier for mouse (256 is normal multiplier)." msgstr "Fare için hız çarpanı (256 normal çarpandır)." -#: lib/logitech_receiver/settings_templates.py:299 +#: lib/logitech_receiver/settings_templates.py:456 msgid "Thumb Wheel Diversion" msgstr "Başparmak Tekerleği Yönlendirme" -#: lib/logitech_receiver/settings_templates.py:301 +#: lib/logitech_receiver/settings_templates.py:458 msgid "" "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "" -"Başparmak tekerleğini THUMB_WHEEL HID++ bildirimi gönderecek şekilde ayarla " -"(Solaar rölü tetikler veya yoksayar)" +"Başparmak tekerleğinin THUMB_WHEEL HID++ bildirimler göndermesini sağlar (bu " +"bildirimler Solaar kurallarını tetikler, ancak diğer durumlarda görmezden " +"gelinir)." -#: lib/logitech_receiver/settings_templates.py:310 +#: lib/logitech_receiver/settings_templates.py:467 msgid "Thumb Wheel Direction" msgstr "Başparmak Tekerleği Yönü" -#: lib/logitech_receiver/settings_templates.py:311 +#: lib/logitech_receiver/settings_templates.py:468 msgid "Invert thumb wheel scroll direction." -msgstr "Tekerlekle kaydırmada başparmak yönü tersine çevirir." +msgstr "Kaydırma tekerleği ile başparmak kaydırma yönünü tersine çevirir." -#: lib/logitech_receiver/settings_templates.py:319 +#: lib/logitech_receiver/settings_templates.py:488 msgid "Onboard Profiles" msgstr "Yerleşik Profiller" -#: lib/logitech_receiver/settings_templates.py:320 +#: lib/logitech_receiver/settings_templates.py:489 msgid "" -"Enable onboard profiles, which often control report rate and keyboard " -"lighting" +"Enable an onboard profile, which controls report rate, sensitivity, and " +"button actions" msgstr "" -"Yerleşik profilleri etkinleştir, bu klavye aydınlatması ve rapor hızını " -"kontrol eder" +"Yerleşik profilleri etkinleştir, bu rapor hızını, hassasiyeti ve düğme " +"etkinliğini kontrol eder" -#: lib/logitech_receiver/settings_templates.py:330 -msgid "Polling Rate (ms)" -msgstr "Yoklama Oranı (ms)" +#: lib/logitech_receiver/settings_templates.py:533 +#: lib/logitech_receiver/settings_templates.py:566 +msgid "Report Rate" +msgstr "Rapor Hızı" -#: lib/logitech_receiver/settings_templates.py:332 -msgid "Frequency of device polling, in milliseconds" -msgstr "Aygıt yoklamasının milisaniye olarak frekansı" +#: lib/logitech_receiver/settings_templates.py:535 +#: lib/logitech_receiver/settings_templates.py:568 +msgid "Frequency of device movement reports" +msgstr "Aygıt hareketlerinin rapor sıklığı" -#: lib/logitech_receiver/settings_templates.py:333 -#: lib/logitech_receiver/settings_templates.py:1216 -#: lib/logitech_receiver/settings_templates.py:1244 +#: lib/logitech_receiver/settings_templates.py:535 +#: lib/logitech_receiver/settings_templates.py:568 +#: lib/logitech_receiver/settings_templates.py:983 +#: lib/logitech_receiver/settings_templates.py:1357 +#: lib/logitech_receiver/settings_templates.py:1388 msgid "May need Onboard Profiles set to Disable to be effective." msgstr "" "Etkili olabilmesi için Yerleşik Profillerin devredışı bırakılması " "gerekebilir." -#: lib/logitech_receiver/settings_templates.py:363 +#: lib/logitech_receiver/settings_templates.py:596 msgid "Divert crown events" msgstr "Kadran etkinliklerini yönlendir" -#: lib/logitech_receiver/settings_templates.py:364 +#: lib/logitech_receiver/settings_templates.py:597 msgid "" "Make crown send CROWN HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." msgstr "" -"Kadranı CROWN HID++ bildirimi gönderecek şekilde ayarla (Solaar rölü " -"tetikler veya yoksayar)" +"Kadranın CROWN HID++ bildirimler göndermesini sağlar (bu bildirimler Solaar " +"kurallarını tetikler, ancak diğer durumlarda görmezden gelinir)." -#: lib/logitech_receiver/settings_templates.py:372 +#: lib/logitech_receiver/settings_templates.py:605 msgid "Crown smooth scroll" msgstr "Kadran yumuşak kaydırma" -#: lib/logitech_receiver/settings_templates.py:373 +#: lib/logitech_receiver/settings_templates.py:606 msgid "Set crown smooth scroll" msgstr "Kadranın yumuşak kaydırmasını ayarla" -#: lib/logitech_receiver/settings_templates.py:381 -msgid "Divert G Keys" -msgstr "G Tuşlarını Yönlendir" +#: lib/logitech_receiver/settings_templates.py:614 +msgid "Divert G and M Keys" +msgstr "G ve M Tuşlarını Yönlendir" -#: lib/logitech_receiver/settings_templates.py:383 +#: lib/logitech_receiver/settings_templates.py:615 msgid "" -"Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " +"Make G and M keys send HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." msgstr "" -"G Tuşlarını GKEY HID++ bildirimi gönderecek şekilde ayarla (Solaar rölü " -"tetikler veya yoksayar)" +"G ve M Tuşlarının HID++ bildirimler göndermesini sağlar (bu bildirimler " +"Solaar kurallarını tetikler, ancak diğer durumlarda görmezden gelinir)." -#: lib/logitech_receiver/settings_templates.py:384 -msgid "May also make M keys and MR key send HID++ notifications" -msgstr "M tuşları ve MR tuşu da HID++ bildirimleri gönderebilir" +#: lib/logitech_receiver/settings_templates.py:629 +msgid "Scroll Wheel Ratcheted" +msgstr "Kaydırma Tekerleği Dönüşü" -#: lib/logitech_receiver/settings_templates.py:399 -msgid "Scroll Wheel Rachet" -msgstr "Kaydırma Tekerleği Mandalı" - -#: lib/logitech_receiver/settings_templates.py:401 +#: lib/logitech_receiver/settings_templates.py:630 msgid "" -"Automatically switch the mouse wheel between ratchet and freespin mode.\n" -"The mouse wheel is always free at 0, and always ratcheted at 50" +"Switch the mouse wheel between speed-controlled ratcheting and always " +"freespin." msgstr "" -"Fare tekerleğinin serbest dönüş veya kademeli dönüş arasında otomatik " -"geçişini sağlar.\n" -"Fare tekerleği 0'da daima serbest dönüşte ve 50'de daima kademeli dönüştedir" +"Fare Tekerleğini, hız kontrollü dişli kaydırma ile sürekli serbest dönüş " +"arasında değiştir." -#: lib/logitech_receiver/settings_templates.py:450 +#: lib/logitech_receiver/settings_templates.py:632 +msgid "Freespinning" +msgstr "Serbest Dönüş" + +#: lib/logitech_receiver/settings_templates.py:632 +msgid "Ratcheted" +msgstr "Dişli" + +#: lib/logitech_receiver/settings_templates.py:639 +msgid "Scroll Wheel Ratchet Speed" +msgstr "Kaydırma Tekerleği Dişli Hızı" + +#: lib/logitech_receiver/settings_templates.py:641 +msgid "" +"Use the mouse wheel speed to switch between ratcheted and freespinning.\n" +"The mouse wheel is always ratcheted at 50." +msgstr "" +"Fare tekerleği hızını kullanarak dişli kaydırma ile serbest dönüş arasında " +"geçiş yap.\n" +"Fare tekerleği, 50 hızda her zaman dişli kaydırmalı olur." + +#: lib/logitech_receiver/settings_templates.py:690 msgid "Key/Button Actions" msgstr "Tuş/Düğme Eylemleri" -#: lib/logitech_receiver/settings_templates.py:452 +#: lib/logitech_receiver/settings_templates.py:692 msgid "Change the action for the key or button." msgstr "Tuş veya düğmenin eylemini değiştirir." -#: lib/logitech_receiver/settings_templates.py:452 +#: lib/logitech_receiver/settings_templates.py:694 msgid "Overridden by diversion." -msgstr "Yönlendirmeyle geçersiz kılındı" +msgstr "Yönlendirmeyle geçersiz kılındı." -#: lib/logitech_receiver/settings_templates.py:453 +#: lib/logitech_receiver/settings_templates.py:696 msgid "" "Changing important actions (such as for the left mouse button) can result in " "an unusable system." @@ -516,11 +713,11 @@ msgstr "" "Önemli eylemleri değiştirmek (farenin sol tuşu gibi) sistemde " "kararsızlıklara yol açabilir." -#: lib/logitech_receiver/settings_templates.py:614 +#: lib/logitech_receiver/settings_templates.py:862 msgid "Key/Button Diversion" msgstr "Tuş/Düğme Yönlendirme" -#: lib/logitech_receiver/settings_templates.py:615 +#: lib/logitech_receiver/settings_templates.py:863 msgid "" "Make the key or button send HID++ notifications (Diverted) or initiate Mouse " "Gestures or Sliding DPI" @@ -528,37 +725,37 @@ msgstr "" "Tuş veya düğmenin HID++ bildirimleri (yönlendirmeler) veya Fare Hareketleri " "başlatmalar veya Kaydırma DPI'si" -#: lib/logitech_receiver/settings_templates.py:618 -#: lib/logitech_receiver/settings_templates.py:619 -#: lib/logitech_receiver/settings_templates.py:620 +#: lib/logitech_receiver/settings_templates.py:866 +#: lib/logitech_receiver/settings_templates.py:867 +#: lib/logitech_receiver/settings_templates.py:868 msgid "Diverted" msgstr "Yönlendirilmiş" -#: lib/logitech_receiver/settings_templates.py:618 -#: lib/logitech_receiver/settings_templates.py:619 -#: lib/logitech_receiver/settings_templates.py:887 +#: lib/logitech_receiver/settings_templates.py:866 +#: lib/logitech_receiver/settings_templates.py:867 msgid "Mouse Gestures" msgstr "Fare Hareketleri" -#: lib/logitech_receiver/settings_templates.py:618 -#: lib/logitech_receiver/settings_templates.py:619 -#: lib/logitech_receiver/settings_templates.py:620 +#: lib/logitech_receiver/settings_templates.py:866 +#: lib/logitech_receiver/settings_templates.py:867 +#: lib/logitech_receiver/settings_templates.py:868 msgid "Regular" msgstr "Normal" -#: lib/logitech_receiver/settings_templates.py:618 +#: lib/logitech_receiver/settings_templates.py:866 msgid "Sliding DPI" msgstr "Kaydırma DPI" -#: lib/logitech_receiver/settings_templates.py:686 +#: lib/logitech_receiver/settings_templates.py:954 +#: lib/logitech_receiver/settings_templates.py:982 msgid "Sensitivity (DPI)" msgstr "Hassasiyet (DPI)" -#: lib/logitech_receiver/settings_templates.py:726 +#: lib/logitech_receiver/settings_templates.py:1059 msgid "Sensitivity Switching" msgstr "Hassasiyet Değiştirme" -#: lib/logitech_receiver/settings_templates.py:728 +#: lib/logitech_receiver/settings_templates.py:1061 msgid "" "Switch the current sensitivity and the remembered sensitivity when the key " "or button is pressed.\n" @@ -568,342 +765,327 @@ msgstr "" "değiştir.\n" "Eğer hatırlanan hassasiyet yoksa sadece şuanki hassasiyeti hatırla" -#: lib/logitech_receiver/settings_templates.py:732 -#: lib/logitech_receiver/settings_templates.py:773 -#: lib/logitech_receiver/settings_templates.py:892 +#: lib/logitech_receiver/settings_templates.py:1065 msgid "Off" msgstr "Kapalı" -#: lib/logitech_receiver/settings_templates.py:770 -msgid "DPI Sliding Adjustment" -msgstr "DPI Kaydırma Ayarı" - -#: lib/logitech_receiver/settings_templates.py:771 -msgid "" -"Adjust the DPI by sliding the mouse horizontally while holding the button " -"down." -msgstr "Düğmeye basarken farenin dikey kaydırma DPI ayarı." - -#: lib/logitech_receiver/settings_templates.py:868 +#: lib/logitech_receiver/settings_templates.py:1096 msgid "Disable keys" msgstr "Tuşları devredışı bırak" -#: lib/logitech_receiver/settings_templates.py:869 +#: lib/logitech_receiver/settings_templates.py:1097 msgid "Disable specific keyboard keys." msgstr "Özel klavye tuşlarını devredışı bırak." -#: lib/logitech_receiver/settings_templates.py:872 +#: lib/logitech_receiver/settings_templates.py:1100 #, python-format msgid "Disables the %s key." msgstr "%s tuşunu devredışı bırakır." -#: lib/logitech_receiver/settings_templates.py:888 -msgid "Send a gesture by sliding the mouse while holding the button down." -msgstr "Düğmeye basıldığında fareyi kaydırma hareketi gönder." - -#: lib/logitech_receiver/settings_templates.py:986 -#: lib/logitech_receiver/settings_templates.py:1034 +#: lib/logitech_receiver/settings_templates.py:1113 +#: lib/logitech_receiver/settings_templates.py:1170 msgid "Set OS" msgstr "İşletim Sistemi Ayarla" -#: lib/logitech_receiver/settings_templates.py:987 -#: lib/logitech_receiver/settings_templates.py:1035 +#: lib/logitech_receiver/settings_templates.py:1114 +#: lib/logitech_receiver/settings_templates.py:1171 msgid "Change keys to match OS." msgstr "Tuşları eşleşen işletim sistemine göre değiştir." -#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1183 msgid "Change Host" -msgstr "Bağlanılan Makineyi Değiştir" +msgstr "Bağlanılan Bilgisayarı Değiştir" -#: lib/logitech_receiver/settings_templates.py:1048 +#: lib/logitech_receiver/settings_templates.py:1184 msgid "Switch connection to a different host" -msgstr "Bağlantıyı başka makineye değiştir" +msgstr "Bağlantıyı başka bilgisayara değiştir" -#: lib/logitech_receiver/settings_templates.py:1073 +#: lib/logitech_receiver/settings_templates.py:1208 msgid "Performs a left click." msgstr "Sol tıklama eylemi gerçekleştirir." -#: lib/logitech_receiver/settings_templates.py:1073 +#: lib/logitech_receiver/settings_templates.py:1208 msgid "Single tap" msgstr "Tek dokunma" -#: lib/logitech_receiver/settings_templates.py:1074 +#: lib/logitech_receiver/settings_templates.py:1209 msgid "Performs a right click." msgstr "Sağ tıklama eylemi gerçekleştirir." -#: lib/logitech_receiver/settings_templates.py:1074 +#: lib/logitech_receiver/settings_templates.py:1209 msgid "Single tap with two fingers" msgstr "İki parmakla tek dokunma" -#: lib/logitech_receiver/settings_templates.py:1075 +#: lib/logitech_receiver/settings_templates.py:1210 msgid "Single tap with three fingers" msgstr "Üç parmakla tek dokunma" -#: lib/logitech_receiver/settings_templates.py:1079 +#: lib/logitech_receiver/settings_templates.py:1214 msgid "Double tap" msgstr "Çift dokunma" -#: lib/logitech_receiver/settings_templates.py:1079 +#: lib/logitech_receiver/settings_templates.py:1214 msgid "Performs a double click." msgstr "Çift tıklama eylemi gerçekleştirir." -#: lib/logitech_receiver/settings_templates.py:1080 +#: lib/logitech_receiver/settings_templates.py:1215 msgid "Double tap with two fingers" msgstr "İki parmakla çift dokunma" -#: lib/logitech_receiver/settings_templates.py:1081 +#: lib/logitech_receiver/settings_templates.py:1216 msgid "Double tap with three fingers" msgstr "Üç parmakla çift dokunma" -#: lib/logitech_receiver/settings_templates.py:1084 +#: lib/logitech_receiver/settings_templates.py:1219 msgid "Drags items by dragging the finger after double tapping." msgstr "Çift tıklama eylemi ile nesneleri taşı." -#: lib/logitech_receiver/settings_templates.py:1084 +#: lib/logitech_receiver/settings_templates.py:1219 msgid "Tap and drag" msgstr "Dokun ve taşı" -#: lib/logitech_receiver/settings_templates.py:1086 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "Çift tıklama eylemi ile nesneleri taşı." - -#: lib/logitech_receiver/settings_templates.py:1086 +#: lib/logitech_receiver/settings_templates.py:1221 msgid "Tap and drag with two fingers" msgstr "İki parmakla dokun ve taşı" -#: lib/logitech_receiver/settings_templates.py:1087 +#: lib/logitech_receiver/settings_templates.py:1222 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "Çift tıklama eylemi ile nesneleri taşı." + +#: lib/logitech_receiver/settings_templates.py:1224 msgid "Tap and drag with three fingers" msgstr "Üç parmakla dokun ve taşı" -#: lib/logitech_receiver/settings_templates.py:1090 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "" -"Dokunma ve kenar hareketlerini devredışı bırakır (Fn + SolTık ile aynıdır)." - -#: lib/logitech_receiver/settings_templates.py:1090 +#: lib/logitech_receiver/settings_templates.py:1227 msgid "Suppress tap and edge gestures" msgstr "Dokunma ve kenar hareketlerini baskıla" -#: lib/logitech_receiver/settings_templates.py:1091 +#: lib/logitech_receiver/settings_templates.py:1228 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "" +"Dokunma ve kenar hareketlerini devredışı bırakır (Fn + SolTıklama ile " +"aynıdır)." + +#: lib/logitech_receiver/settings_templates.py:1230 msgid "Scroll with one finger" msgstr "Bir parmakla kaydır" -#: lib/logitech_receiver/settings_templates.py:1091 -#: lib/logitech_receiver/settings_templates.py:1092 -#: lib/logitech_receiver/settings_templates.py:1095 +#: lib/logitech_receiver/settings_templates.py:1230 +#: lib/logitech_receiver/settings_templates.py:1231 +#: lib/logitech_receiver/settings_templates.py:1234 msgid "Scrolls." msgstr "Kaydırır." -#: lib/logitech_receiver/settings_templates.py:1092 -#: lib/logitech_receiver/settings_templates.py:1095 +#: lib/logitech_receiver/settings_templates.py:1231 +#: lib/logitech_receiver/settings_templates.py:1234 msgid "Scroll with two fingers" msgstr "İki parmakla kaydır" -#: lib/logitech_receiver/settings_templates.py:1093 +#: lib/logitech_receiver/settings_templates.py:1232 msgid "Scroll horizontally with two fingers" msgstr "İki parmakla yatay kaydır" -#: lib/logitech_receiver/settings_templates.py:1093 +#: lib/logitech_receiver/settings_templates.py:1232 msgid "Scrolls horizontally." msgstr "Yatay kaydırır." -#: lib/logitech_receiver/settings_templates.py:1094 +#: lib/logitech_receiver/settings_templates.py:1233 msgid "Scroll vertically with two fingers" msgstr "İki parmakla dikey kaydır" -#: lib/logitech_receiver/settings_templates.py:1094 +#: lib/logitech_receiver/settings_templates.py:1233 msgid "Scrolls vertically." msgstr "Dikey kaydırır." -#: lib/logitech_receiver/settings_templates.py:1096 +#: lib/logitech_receiver/settings_templates.py:1235 msgid "Inverts the scrolling direction." msgstr "Kaydırma yönünü ters çevir." -#: lib/logitech_receiver/settings_templates.py:1096 +#: lib/logitech_receiver/settings_templates.py:1235 msgid "Natural scrolling" msgstr "Doğal kaydırma" -#: lib/logitech_receiver/settings_templates.py:1097 +#: lib/logitech_receiver/settings_templates.py:1236 msgid "Enables the thumbwheel." msgstr "Başparmak tekerleğini etkinleştirir." -#: lib/logitech_receiver/settings_templates.py:1097 +#: lib/logitech_receiver/settings_templates.py:1236 msgid "Thumbwheel" msgstr "Başparmak Tekerleği" -#: lib/logitech_receiver/settings_templates.py:1108 -#: lib/logitech_receiver/settings_templates.py:1112 +#: lib/logitech_receiver/settings_templates.py:1247 +#: lib/logitech_receiver/settings_templates.py:1251 msgid "Swipe from the top edge" msgstr "Üst kenardan vuruş" -#: lib/logitech_receiver/settings_templates.py:1109 +#: lib/logitech_receiver/settings_templates.py:1248 msgid "Swipe from the left edge" msgstr "Sol kenardan vuruş" -#: lib/logitech_receiver/settings_templates.py:1110 +#: lib/logitech_receiver/settings_templates.py:1249 msgid "Swipe from the right edge" msgstr "Sağ kenardan vuruş" -#: lib/logitech_receiver/settings_templates.py:1111 +#: lib/logitech_receiver/settings_templates.py:1250 msgid "Swipe from the bottom edge" msgstr "Alt kenardan vuruş" -#: lib/logitech_receiver/settings_templates.py:1113 +#: lib/logitech_receiver/settings_templates.py:1252 msgid "Swipe two fingers from the left edge" msgstr "Sol kenardan iki parmak vuruş" -#: lib/logitech_receiver/settings_templates.py:1114 +#: lib/logitech_receiver/settings_templates.py:1253 msgid "Swipe two fingers from the right edge" msgstr "Sağ kenardan iki parmak vuruş" -#: lib/logitech_receiver/settings_templates.py:1115 +#: lib/logitech_receiver/settings_templates.py:1254 msgid "Swipe two fingers from the bottom edge" msgstr "Alt kenardan iki parmak vuruş" -#: lib/logitech_receiver/settings_templates.py:1116 +#: lib/logitech_receiver/settings_templates.py:1255 msgid "Swipe two fingers from the top edge" msgstr "Üst kenardan iki parmak vuruş" -#: lib/logitech_receiver/settings_templates.py:1117 -#: lib/logitech_receiver/settings_templates.py:1121 +#: lib/logitech_receiver/settings_templates.py:1256 +#: lib/logitech_receiver/settings_templates.py:1260 msgid "Pinch to zoom out; spread to zoom in." msgstr "Uzaklaşmak için sıkıştırın, yakınlaştırmak için yayın." -#: lib/logitech_receiver/settings_templates.py:1117 +#: lib/logitech_receiver/settings_templates.py:1256 msgid "Zoom with two fingers." msgstr "İki parmakla yakınlaştırma." -#: lib/logitech_receiver/settings_templates.py:1118 +#: lib/logitech_receiver/settings_templates.py:1257 msgid "Pinch to zoom out." msgstr "Uzaklaştırmak için sıkıştırın." -#: lib/logitech_receiver/settings_templates.py:1119 +#: lib/logitech_receiver/settings_templates.py:1258 msgid "Spread to zoom in." msgstr "Yakınlaştırmak için yayın." -#: lib/logitech_receiver/settings_templates.py:1120 +#: lib/logitech_receiver/settings_templates.py:1259 msgid "Zoom with three fingers." msgstr "Üç parmakla yakınlaştırma." -#: lib/logitech_receiver/settings_templates.py:1121 +#: lib/logitech_receiver/settings_templates.py:1260 msgid "Zoom with two fingers" msgstr "İki parmakla yakınlaştır" -#: lib/logitech_receiver/settings_templates.py:1139 +#: lib/logitech_receiver/settings_templates.py:1278 msgid "Pixel zone" msgstr "Piksel alanı" -#: lib/logitech_receiver/settings_templates.py:1140 +#: lib/logitech_receiver/settings_templates.py:1279 msgid "Ratio zone" msgstr "Oran alanı" -#: lib/logitech_receiver/settings_templates.py:1141 +#: lib/logitech_receiver/settings_templates.py:1280 msgid "Scale factor" msgstr "Ölçek katsayısı" -#: lib/logitech_receiver/settings_templates.py:1141 +#: lib/logitech_receiver/settings_templates.py:1280 msgid "Sets the cursor speed." msgstr "İmleç hızını ayarla." -#: lib/logitech_receiver/settings_templates.py:1145 +#: lib/logitech_receiver/settings_templates.py:1284 msgid "Left" msgstr "Sol" -#: lib/logitech_receiver/settings_templates.py:1145 +#: lib/logitech_receiver/settings_templates.py:1284 msgid "Left-most coordinate." msgstr "En soldaki koordinat." -#: lib/logitech_receiver/settings_templates.py:1146 +#: lib/logitech_receiver/settings_templates.py:1285 msgid "Bottom" msgstr "Alt" -#: lib/logitech_receiver/settings_templates.py:1146 +#: lib/logitech_receiver/settings_templates.py:1285 msgid "Bottom coordinate." msgstr "Alt koordinat." -#: lib/logitech_receiver/settings_templates.py:1147 +#: lib/logitech_receiver/settings_templates.py:1286 msgid "Width" msgstr "Genişlik" -#: lib/logitech_receiver/settings_templates.py:1147 +#: lib/logitech_receiver/settings_templates.py:1286 msgid "Width." msgstr "Genişlik." -#: lib/logitech_receiver/settings_templates.py:1148 +#: lib/logitech_receiver/settings_templates.py:1287 msgid "Height" msgstr "Yükseklik" -#: lib/logitech_receiver/settings_templates.py:1148 +#: lib/logitech_receiver/settings_templates.py:1287 msgid "Height." msgstr "Yükseklik." -#: lib/logitech_receiver/settings_templates.py:1149 +#: lib/logitech_receiver/settings_templates.py:1288 msgid "Cursor speed." msgstr "İmleç hızı." -#: lib/logitech_receiver/settings_templates.py:1149 +#: lib/logitech_receiver/settings_templates.py:1288 msgid "Scale" msgstr "Ölçek" -#: lib/logitech_receiver/settings_templates.py:1155 +#: lib/logitech_receiver/settings_templates.py:1294 msgid "Gestures" msgstr "Hareketler" -#: lib/logitech_receiver/settings_templates.py:1156 +#: lib/logitech_receiver/settings_templates.py:1295 msgid "Tweak the mouse/touchpad behaviour." msgstr "Fare/dokunmatik yüzey davranışında ince ayar yapın." -#: lib/logitech_receiver/settings_templates.py:1172 +#: lib/logitech_receiver/settings_templates.py:1311 msgid "Gestures Diversion" msgstr "Hareketleri Yönlendir" -#: lib/logitech_receiver/settings_templates.py:1173 +#: lib/logitech_receiver/settings_templates.py:1312 msgid "Divert mouse/touchpad gestures." msgstr "Fare/dokunmatik yüzey hareketlerini yönlendirir." -#: lib/logitech_receiver/settings_templates.py:1189 +#: lib/logitech_receiver/settings_templates.py:1328 msgid "Gesture params" msgstr "Hareket parametreleri" -#: lib/logitech_receiver/settings_templates.py:1190 +#: lib/logitech_receiver/settings_templates.py:1329 msgid "Change numerical parameters of a mouse/touchpad." msgstr "Fare/dokunmatik yüzey sayısal parametrelerini değiştir." -#: lib/logitech_receiver/settings_templates.py:1214 +#: lib/logitech_receiver/settings_templates.py:1353 msgid "M-Key LEDs" msgstr "M-Tuşu LED'leri" -#: lib/logitech_receiver/settings_templates.py:1216 +#: lib/logitech_receiver/settings_templates.py:1355 msgid "Control the M-Key LEDs." msgstr "M-Tuşu LED'lerini kontrol eder." -#: lib/logitech_receiver/settings_templates.py:1217 -#: lib/logitech_receiver/settings_templates.py:1245 +#: lib/logitech_receiver/settings_templates.py:1359 +#: lib/logitech_receiver/settings_templates.py:1390 msgid "May need G Keys diverted to be effective." msgstr "G Tuşlarını yönlendirmek etkili olabilir." -#: lib/logitech_receiver/settings_templates.py:1223 +#: lib/logitech_receiver/settings_templates.py:1365 #, python-format msgid "Lights up the %s key." -msgstr "%s tuşunu aydınlat" +msgstr "%s tuşunu aydınlat." -#: lib/logitech_receiver/settings_templates.py:1242 +#: lib/logitech_receiver/settings_templates.py:1384 msgid "MR-Key LED" msgstr "MR-Tuşu LED'i" -#: lib/logitech_receiver/settings_templates.py:1244 +#: lib/logitech_receiver/settings_templates.py:1386 msgid "Control the MR-Key LED." msgstr "MR-Tuşu LED'inin kontrol eder." -#: lib/logitech_receiver/settings_templates.py:1262 +#: lib/logitech_receiver/settings_templates.py:1407 msgid "Persistent Key/Button Mapping" msgstr "Kalıcı Tuş/Düğme Haritalama" -#: lib/logitech_receiver/settings_templates.py:1264 +#: lib/logitech_receiver/settings_templates.py:1409 msgid "Permanently change the mapping for the key or button." msgstr "Tuş veya düğmenin haritalamasını kalıcı olarak değiştir." -#: lib/logitech_receiver/settings_templates.py:1265 +#: lib/logitech_receiver/settings_templates.py:1411 msgid "" "Changing important keys or buttons (such as for the left mouse button) can " "result in an unusable system." @@ -911,114 +1093,100 @@ msgstr "" "Önemli tuşları veya düğmeleri değiştirmek (farenin sol tuşu gibi) sistemde " "kararsızlıklara yol açabilir." -#: lib/logitech_receiver/settings_templates.py:1322 +#: lib/logitech_receiver/settings_templates.py:1468 msgid "Sidetone" msgstr "Yan ton" -#: lib/logitech_receiver/settings_templates.py:1323 +#: lib/logitech_receiver/settings_templates.py:1469 msgid "Set sidetone level." msgstr "Yan ton seviyesini ayarla." -#: lib/logitech_receiver/settings_templates.py:1332 +#: lib/logitech_receiver/settings_templates.py:1478 msgid "Equalizer" msgstr "Ekolayzer" -#: lib/logitech_receiver/settings_templates.py:1333 +#: lib/logitech_receiver/settings_templates.py:1479 msgid "Set equalizer levels." msgstr "Ekolayzer seviyesini ayarla." -#: lib/logitech_receiver/settings_templates.py:1355 +#: lib/logitech_receiver/settings_templates.py:1501 msgid "Hz" msgstr "Hz" -#: lib/logitech_receiver/status.py:113 -msgid "No paired devices." -msgstr "Eşleşmiş aygıt yok." +#: lib/logitech_receiver/settings_templates.py:1507 +msgid "Power Management" +msgstr "Güç Yönetimi" -#: lib/logitech_receiver/status.py:114 lib/solaar/ui/window.py:620 -#, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s eşleşmiş aygıt." -msgstr[1] "%(count)s eşleşmiş aygıt." +#: lib/logitech_receiver/settings_templates.py:1508 +msgid "Power off in minutes (0 for never)." +msgstr "Dakika cinsinden kapanma süresi (asla kapanmaması için 0)." -#: lib/logitech_receiver/status.py:167 -#, python-format -msgid "Battery: %(level)s" -msgstr "Pil: %(level)s" +#: lib/logitech_receiver/settings_templates.py:1519 +msgid "Brightness Control" +msgstr "Parlaklık Kontrolü" -#: lib/logitech_receiver/status.py:169 -#, python-format -msgid "Battery: %(percent)d%%" -msgstr "Pil: %(percent)d%%" +#: lib/logitech_receiver/settings_templates.py:1520 +msgid "Control overall brightness" +msgstr "Genel parlaklığı kontrol et" -#: lib/logitech_receiver/status.py:181 -#, python-format -msgid "Lighting: %(level)s lux" -msgstr "Aydınlatma: %(level)s lux" +#: lib/logitech_receiver/settings_templates.py:1563 +#: lib/logitech_receiver/settings_templates.py:1617 +msgid "LED Control" +msgstr "LED Kontrolü" -#: lib/logitech_receiver/status.py:236 -#, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "Pil: %(level)s (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:1564 +#: lib/logitech_receiver/settings_templates.py:1618 +msgid "Switch control of LED zones between device and Solaar" +msgstr "LED bölgelerinin kontrolünü aygıt ile Solaar arasında değiştir" -#: lib/logitech_receiver/status.py:238 -#, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "Pil: %(percent)d%% (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:1579 +#: lib/logitech_receiver/settings_templates.py:1628 +msgid "LED Zone Effects" +msgstr "LED Bölge Efektleri" -#: lib/solaar/ui/__init__.py:51 -msgid "Permissions error" -msgstr "Yetki hatası" +#: lib/logitech_receiver/settings_templates.py:1580 +#: lib/logitech_receiver/settings_templates.py:1629 +msgid "LED Control needs to be set to Solaar to be effective." +msgstr "LED kontrolünün etkili olabilmesi için Solaar seçilmelidir." -#: lib/solaar/ui/__init__.py:53 -#, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open it." -msgstr "Logitech Alıcısı bulundu (%s), fakat açmak için yetki yok." +#: lib/logitech_receiver/settings_templates.py:1580 +#: lib/logitech_receiver/settings_templates.py:1629 +msgid "Set effect for LED Zone" +msgstr "LED Bölgesi için efekt ayarla" -#: lib/solaar/ui/__init__.py:54 -msgid "" -"If you've just installed Solaar, try removing the receiver and plugging it " -"back in." -msgstr "Eğer Solaar'ı yeni yüklediyseniz, alıcıyı çıkartıp tekrar takın." +#: lib/logitech_receiver/settings_templates.py:1583 +msgid "Speed" +msgstr "Hız" -#: lib/solaar/ui/__init__.py:57 -msgid "Cannot connect to device error" -msgstr "Hata. Aygıta bağlanılamıyor" +#: lib/logitech_receiver/settings_templates.py:1584 +msgid "Period" +msgstr "Sıklık" -#: lib/solaar/ui/__init__.py:59 -#, python-format -msgid "" -"Found a Logitech receiver or device at %s, but encountered an error " -"connecting to it." -msgstr "" -"%s konumunda bir Logitech alıcısı veya aygıtı bulunduancak buna bağlanırken " -"bir hatayla karşılaşıldı." +#: lib/logitech_receiver/settings_templates.py:1585 +msgid "Intensity" +msgstr "Yoğunluk" -#: lib/solaar/ui/__init__.py:60 -msgid "" -"Try removing the device and plugging it back in or turning it off and then " -"on." -msgstr "Aygıtı çıkarmayı ve yeniden takmayı veya kapatıp açmayı deneyin." +#: lib/logitech_receiver/settings_templates.py:1586 +msgid "Ramp" +msgstr "Eğim" -#: lib/solaar/ui/__init__.py:63 -msgid "Unpairing failed" -msgstr "Eşlestirmeyi kaldirma hatalı" +#: lib/logitech_receiver/settings_templates.py:1602 +msgid "LEDs" +msgstr "LEDler" -#: lib/solaar/ui/__init__.py:65 -#, python-brace-format -msgid "Failed to unpair %{device} from %{receiver}." -msgstr "%{device} ve %{receiver} eşleştirmesini kaldırma başarısız." +#: lib/logitech_receiver/settings_templates.py:1639 +msgid "Per-key Lighting" +msgstr "Tuş başına Aydınlatma" -#: lib/solaar/ui/__init__.py:66 -msgid "The receiver returned an error, with no further details." -msgstr "Alıcı hata verdi, bu hata hakkında ayrıntı mevcut değil." +#: lib/logitech_receiver/settings_templates.py:1640 +msgid "Control per-key lighting." +msgstr "Tuş başına aydınlatmayı kontrol et." -#: lib/solaar/ui/__init__.py:176 +#: lib/solaar/ui/__init__.py:103 msgid "Another Solaar process is already running so just expose its window" msgstr "Başka bir Solaar işlemi çalışıyor, sadece onun penceresini görünür yap" -#: lib/solaar/ui/about.py:36 +#: lib/solaar/ui/about.py:34 msgid "" "Manages Logitech receivers,\n" "keyboards, mice, and tablets." @@ -1026,15 +1194,15 @@ msgstr "" "Logitech alıcılarını, klavyelerini,\n" "farelerini ve tabletlerini yönetir." -#: lib/solaar/ui/about.py:44 +#: lib/solaar/ui/about.py:43 msgid "Additional Programming" msgstr "Ek Programlama" -#: lib/solaar/ui/about.py:45 +#: lib/solaar/ui/about.py:44 msgid "GUI design" msgstr "GUI tasarımı" -#: lib/solaar/ui/about.py:47 +#: lib/solaar/ui/about.py:46 msgid "Testing" msgstr "Sınama" @@ -1042,475 +1210,443 @@ msgstr "Sınama" msgid "Logitech documentation" msgstr "Logitech belgeleri" -#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 -#: lib/solaar/ui/window.py:202 +#: lib/solaar/ui/action.py:83 lib/solaar/ui/action.py:87 +#: lib/solaar/ui/window.py:189 msgid "Unpair" msgstr "Eşleşmeyi Kaldır" -#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:151 +#: lib/solaar/ui/action.py:86 lib/solaar/ui/diversion_rules.py:103 msgid "Cancel" msgstr "Vazgeç" -#: lib/solaar/ui/config_panel.py:205 +#: lib/solaar/ui/common.py:35 +msgid "Permissions error" +msgstr "Yetki hatası" + +#: lib/solaar/ui/common.py:37 +#, python-format +msgid "" +"Found a Logitech receiver or device (%s), but did not have permission to " +"open it." +msgstr "Bir Logitech aygıtı (%s) bulundu ama açmak için yetki yok." + +#: lib/solaar/ui/common.py:39 +msgid "" +"If you've just installed Solaar, try disconnecting the receiver or device " +"and then reconnecting it." +msgstr "" +"Eğer Solaar'ı yeni kurmuşsanız, aygıtı veya alıcıyı tekrar bağlamayı deneyin." + +#: lib/solaar/ui/common.py:42 +msgid "Cannot connect to device error" +msgstr "Aygıta bağlanılamıyor" + +#: lib/solaar/ui/common.py:44 +#, python-format +msgid "" +"Found a Logitech receiver or device at %s, but encountered an error " +"connecting to it." +msgstr "" +"%s konumunda bir Logitech alıcısı veya aygıtı bulundu, ancak buna " +"bağlanırken bir hatayla karşılaşıldı." + +#: lib/solaar/ui/common.py:46 +msgid "" +"Try disconnecting the device and then reconnecting it or turning it off and " +"then on." +msgstr "Aygıtı tekrar bağlamayı deneyin veya kapatıp açın." + +#: lib/solaar/ui/common.py:49 +msgid "Unpairing failed" +msgstr "Eşleştirme kaldırılamadı" + +#: lib/solaar/ui/common.py:51 +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "%{device} ve %{receiver} eşleştirmesi kaldırılamadı." + +#: lib/solaar/ui/common.py:53 +msgid "The receiver returned an error, with no further details." +msgstr "Alıcı hata verdi, bu hata hakkında ayrıntı mevcut değil." + +#: lib/solaar/ui/config_panel.py:228 msgid "Complete - ENTER to change" msgstr "Tamamlanmış - Değiştirmek için ENTER'a basın" -#: lib/solaar/ui/config_panel.py:205 +#: lib/solaar/ui/config_panel.py:228 msgid "Incomplete" msgstr "Tamamlanmamış" -#: lib/solaar/ui/config_panel.py:444 lib/solaar/ui/config_panel.py:495 +#: lib/solaar/ui/config_panel.py:470 lib/solaar/ui/config_panel.py:522 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "%d değer" -msgstr[1] "%d değerler" +msgstr[1] "%d değer" -#: lib/solaar/ui/config_panel.py:506 +#: lib/solaar/ui/config_panel.py:603 msgid "Changes allowed" msgstr "Değişiklikler kabul ediliyor" -#: lib/solaar/ui/config_panel.py:507 +#: lib/solaar/ui/config_panel.py:604 msgid "No changes allowed" msgstr "Değişiklik kabul edilmiyor" -#: lib/solaar/ui/config_panel.py:508 +#: lib/solaar/ui/config_panel.py:605 msgid "Ignore this setting" msgstr "Bu ayarı görmezden gel" -#: lib/solaar/ui/config_panel.py:553 +#: lib/solaar/ui/config_panel.py:649 msgid "Working" msgstr "Çalışıyor" -#: lib/solaar/ui/config_panel.py:556 +#: lib/solaar/ui/config_panel.py:652 msgid "Read/write operation failed." -msgstr "Okuma/yazma işlemi hatası." +msgstr "Okuma/yazma işlemi yapılamadı." -#: lib/solaar/ui/diversion_rules.py:64 +#: lib/solaar/ui/diversion_rules.py:65 msgid "Built-in rules" msgstr "Varolan kurallar" -#: lib/solaar/ui/diversion_rules.py:64 +#: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" msgstr "Kullanıcı tanımlı kurallar" -#: lib/solaar/ui/diversion_rules.py:66 lib/solaar/ui/diversion_rules.py:1074 +#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1033 msgid "Rule" msgstr "Kural" -#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:508 -#: lib/solaar/ui/diversion_rules.py:630 +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:545 +#: lib/solaar/ui/diversion_rules.py:672 msgid "Sub-rule" msgstr "Alt Kural" -#: lib/solaar/ui/diversion_rules.py:69 +#: lib/solaar/ui/diversion_rules.py:70 msgid "[empty]" msgstr "[boş]" -#: lib/solaar/ui/diversion_rules.py:92 -msgid "Solaar Rule Editor" -msgstr "Solar Kural Editörü" - -#: lib/solaar/ui/diversion_rules.py:142 +#: lib/solaar/ui/diversion_rules.py:94 msgid "Make changes permanent?" msgstr "Değişiklikleri kalıcı yap?" -#: lib/solaar/ui/diversion_rules.py:147 +#: lib/solaar/ui/diversion_rules.py:99 msgid "Yes" msgstr "Evet" -#: lib/solaar/ui/diversion_rules.py:149 +#: lib/solaar/ui/diversion_rules.py:101 msgid "No" msgstr "Hayır" -#: lib/solaar/ui/diversion_rules.py:154 +#: lib/solaar/ui/diversion_rules.py:106 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "Hayır'ı seçerseniz, Solaar kapatıldığında değişiklikler kaybolacaktır." -#: lib/solaar/ui/diversion_rules.py:202 +#: lib/solaar/ui/diversion_rules.py:167 +msgid "Solaar Rule Editor" +msgstr "Solar Kural Editörü" + +#: lib/solaar/ui/diversion_rules.py:260 msgid "Save changes" msgstr "Değişiklikleri kaydet" -#: lib/solaar/ui/diversion_rules.py:207 +#: lib/solaar/ui/diversion_rules.py:265 msgid "Discard changes" msgstr "Değişiklikleri gözardı et" -#: lib/solaar/ui/diversion_rules.py:371 +#: lib/solaar/ui/diversion_rules.py:404 msgid "Insert here" msgstr "Buraya ekle" -#: lib/solaar/ui/diversion_rules.py:373 +#: lib/solaar/ui/diversion_rules.py:406 msgid "Insert above" msgstr "Yukarıya ekle" -#: lib/solaar/ui/diversion_rules.py:375 +#: lib/solaar/ui/diversion_rules.py:408 msgid "Insert below" msgstr "Aşağıya ekle" -#: lib/solaar/ui/diversion_rules.py:381 +#: lib/solaar/ui/diversion_rules.py:414 msgid "Insert new rule here" msgstr "Buraya yeni kural ekle" -#: lib/solaar/ui/diversion_rules.py:383 +#: lib/solaar/ui/diversion_rules.py:416 msgid "Insert new rule above" msgstr "Yukarıya yeni kural ekle" -#: lib/solaar/ui/diversion_rules.py:385 +#: lib/solaar/ui/diversion_rules.py:418 msgid "Insert new rule below" msgstr "Aşağıya yeni kural ekle" -#: lib/solaar/ui/diversion_rules.py:426 +#: lib/solaar/ui/diversion_rules.py:463 msgid "Paste here" msgstr "Buraya yapıştır" -#: lib/solaar/ui/diversion_rules.py:428 +#: lib/solaar/ui/diversion_rules.py:465 msgid "Paste above" msgstr "Yukarı yapıştır" -#: lib/solaar/ui/diversion_rules.py:430 +#: lib/solaar/ui/diversion_rules.py:467 msgid "Paste below" msgstr "Aşağıya yapıştır" -#: lib/solaar/ui/diversion_rules.py:436 +#: lib/solaar/ui/diversion_rules.py:473 msgid "Paste rule here" msgstr "Kuralı buraya yapıştır" -#: lib/solaar/ui/diversion_rules.py:438 +#: lib/solaar/ui/diversion_rules.py:475 msgid "Paste rule above" msgstr "Kuralı yukarı yapıştır" -#: lib/solaar/ui/diversion_rules.py:440 +#: lib/solaar/ui/diversion_rules.py:477 msgid "Paste rule below" msgstr "Kuralı aşağıya yapıştır" -#: lib/solaar/ui/diversion_rules.py:444 +#: lib/solaar/ui/diversion_rules.py:481 msgid "Paste rule" msgstr "Kuralı yapıştır" -#: lib/solaar/ui/diversion_rules.py:473 +#: lib/solaar/ui/diversion_rules.py:510 msgid "Flatten" msgstr "Kapsamdan çıkar" -#: lib/solaar/ui/diversion_rules.py:506 +#: lib/solaar/ui/diversion_rules.py:543 msgid "Insert" msgstr "Ekle" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:632 -#: lib/solaar/ui/diversion_rules.py:1117 +#: lib/solaar/ui/diversion_rules.py:546 lib/solaar/ui/diversion_rules.py:674 +#: lib/solaar/ui/diversion_rules.py:1065 msgid "Or" msgstr "Veya" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:631 -#: lib/solaar/ui/diversion_rules.py:1102 +#: lib/solaar/ui/diversion_rules.py:547 lib/solaar/ui/diversion_rules.py:673 +#: lib/solaar/ui/diversion_rules.py:1051 msgid "And" msgstr "Ve" -#: lib/solaar/ui/diversion_rules.py:512 +#: lib/solaar/ui/diversion_rules.py:549 msgid "Condition" msgstr "Durum" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1238 +#: lib/solaar/ui/diversion_rules.py:551 lib/solaar/ui/rule_conditions.py:146 msgid "Feature" msgstr "Özellik" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1156 -msgid "Process" -msgstr "İşlem" - -#: lib/solaar/ui/diversion_rules.py:516 -msgid "Mouse process" -msgstr "Fare İşlemi" - -#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1271 +#: lib/solaar/ui/diversion_rules.py:552 lib/solaar/ui/rule_conditions.py:181 msgid "Report" msgstr "Rapor" -#: lib/solaar/ui/diversion_rules.py:518 lib/solaar/ui/diversion_rules.py:1306 +#: lib/solaar/ui/diversion_rules.py:553 lib/solaar/ui/rule_conditions.py:60 +msgid "Process" +msgstr "İşlem" + +#: lib/solaar/ui/diversion_rules.py:554 +msgid "Mouse process" +msgstr "Fare İşlemi" + +#: lib/solaar/ui/diversion_rules.py:555 lib/solaar/ui/rule_conditions.py:218 msgid "Modifiers" msgstr "Değiştiriciler" -#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1353 +#: lib/solaar/ui/diversion_rules.py:556 lib/solaar/ui/rule_conditions.py:270 msgid "Key" msgstr "Tuş" -#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1398 -msgid "Test" -msgstr "Sınama" +#: lib/solaar/ui/diversion_rules.py:557 lib/solaar/ui/rule_conditions.py:311 +msgid "KeyIsDown" +msgstr "TuşBasılı" -#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1512 -msgid "Test bytes" -msgstr "Sınama baytları" +#: lib/solaar/ui/diversion_rules.py:558 lib/solaar/ui/diversion_rules.py:1359 +msgid "Active" +msgstr "Etkin" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2072 -msgid "Setting" -msgstr "Ayar" - -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1600 -msgid "Mouse Gesture" -msgstr "Fare Hareketi" - -#: lib/solaar/ui/diversion_rules.py:527 -msgid "Action" -msgstr "Eylem" - -#: lib/solaar/ui/diversion_rules.py:529 lib/solaar/ui/diversion_rules.py:1702 -msgid "Key press" -msgstr "Tuş basımı" - -#: lib/solaar/ui/diversion_rules.py:530 lib/solaar/ui/diversion_rules.py:1752 -msgid "Mouse scroll" -msgstr "Fare kaydırma" - -#: lib/solaar/ui/diversion_rules.py:531 lib/solaar/ui/diversion_rules.py:1800 -msgid "Mouse click" -msgstr "Fare tıklaması" - -#: lib/solaar/ui/diversion_rules.py:532 lib/solaar/ui/diversion_rules.py:1869 -msgid "Execute" -msgstr "Uygula" - -#: lib/solaar/ui/diversion_rules.py:533 -msgid "Set" -msgstr "Belirle" - -#: lib/solaar/ui/diversion_rules.py:562 -msgid "Insert new rule" -msgstr "Yeni kural ekle" - -#: lib/solaar/ui/diversion_rules.py:582 lib/solaar/ui/diversion_rules.py:1550 -#: lib/solaar/ui/diversion_rules.py:1649 lib/solaar/ui/diversion_rules.py:1828 -msgid "Delete" -msgstr "Sil" - -#: lib/solaar/ui/diversion_rules.py:604 -msgid "Negate" -msgstr "Olumsuzla" - -#: lib/solaar/ui/diversion_rules.py:628 -msgid "Wrap with" -msgstr "Kapsa" - -#: lib/solaar/ui/diversion_rules.py:650 -msgid "Cut" -msgstr "Kes" - -#: lib/solaar/ui/diversion_rules.py:665 -msgid "Paste" -msgstr "Yapıştır" - -#: lib/solaar/ui/diversion_rules.py:677 -msgid "Copy" -msgstr "Kopyala" - -#: lib/solaar/ui/diversion_rules.py:1054 -msgid "This editor does not support the selected rule component yet." -msgstr "Seçilen kural bileşeni henüz desteklenmiyor." - -#: lib/solaar/ui/diversion_rules.py:1132 -msgid "Not" -msgstr "Değil" - -#: lib/solaar/ui/diversion_rules.py:1184 -msgid "MouseProcess" -msgstr "Fareİşlemi" - -#: lib/solaar/ui/diversion_rules.py:1326 -msgid "Key down" -msgstr "Aşağı Tuşu" - -#: lib/solaar/ui/diversion_rules.py:1329 -msgid "Key up" -msgstr "Yukarı Tuşu" - -#: lib/solaar/ui/diversion_rules.py:1414 -msgid "begin (inclusive)" -msgstr "baş (dahil edilen)" - -#: lib/solaar/ui/diversion_rules.py:1415 -msgid "end (exclusive)" -msgstr "son (hariç tutulan)" - -#: lib/solaar/ui/diversion_rules.py:1424 -msgid "range" -msgstr "aralık" - -#: lib/solaar/ui/diversion_rules.py:1426 -msgid "minimum" -msgstr "en az" - -#: lib/solaar/ui/diversion_rules.py:1427 -msgid "maximum" -msgstr "en çok" - -#: lib/solaar/ui/diversion_rules.py:1429 -#, python-format -msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" -msgstr "%(0)d bayttan %(1)d bayta, %(2)d ile %(3)d arasında" - -#: lib/solaar/ui/diversion_rules.py:1434 -msgid "mask" -msgstr "maske" - -#: lib/solaar/ui/diversion_rules.py:1435 -#, python-format -msgid "bytes %(0)d to %(1)d, mask %(2)d" -msgstr "%(0)d bayttan %(1)d bayta, %(2)d maskeleme" - -#: lib/solaar/ui/diversion_rules.py:1452 -msgid "type" -msgstr "tür" - -#: lib/solaar/ui/diversion_rules.py:1535 -msgid "Add action" -msgstr "Eylem ekle" - -#: lib/solaar/ui/diversion_rules.py:1628 -msgid "Add key" -msgstr "Tuş ekle" - -#: lib/solaar/ui/diversion_rules.py:1631 -msgid "Click" -msgstr "Tıklama" - -#: lib/solaar/ui/diversion_rules.py:1634 -msgid "Depress" -msgstr "Basma" - -#: lib/solaar/ui/diversion_rules.py:1637 -msgid "Release" -msgstr "Bırakma" - -#: lib/solaar/ui/diversion_rules.py:1771 -msgid "Button" -msgstr "Düğme" - -#: lib/solaar/ui/diversion_rules.py:1772 -msgid "Count" -msgstr "Say" - -#: lib/solaar/ui/diversion_rules.py:1814 -msgid "Add argument" -msgstr "Argüman ekle" - -#: lib/solaar/ui/diversion_rules.py:1888 -msgid "Toggle" -msgstr "Değiştir" - -#: lib/solaar/ui/diversion_rules.py:1888 -msgid "True" -msgstr "Doğru" - -#: lib/solaar/ui/diversion_rules.py:1889 -msgid "False" -msgstr "Yanlış" - -#: lib/solaar/ui/diversion_rules.py:1903 -msgid "Unsupported setting" -msgstr "Desteklenmeyen ayar" - -#: lib/solaar/ui/diversion_rules.py:2055 +#: lib/solaar/ui/diversion_rules.py:559 lib/solaar/ui/diversion_rules.py:1316 +#: lib/solaar/ui/diversion_rules.py:1368 lib/solaar/ui/diversion_rules.py:1418 msgid "Device" msgstr "Aygıt" -#: lib/solaar/ui/diversion_rules.py:2061 lib/solaar/ui/diversion_rules.py:2303 -#: lib/solaar/ui/diversion_rules.py:2321 +#: lib/solaar/ui/diversion_rules.py:560 lib/solaar/ui/diversion_rules.py:1394 +msgid "Host" +msgstr "Bilgisayar" + +#: lib/solaar/ui/diversion_rules.py:561 lib/solaar/ui/diversion_rules.py:1436 +msgid "Setting" +msgstr "Ayar" + +#: lib/solaar/ui/diversion_rules.py:562 lib/solaar/ui/rule_conditions.py:326 +#: lib/solaar/ui/rule_conditions.py:375 +msgid "Test" +msgstr "Sınama" + +#: lib/solaar/ui/diversion_rules.py:563 lib/solaar/ui/rule_conditions.py:500 +msgid "Test bytes" +msgstr "Sınama baytları" + +#: lib/solaar/ui/diversion_rules.py:564 lib/solaar/ui/rule_conditions.py:601 +msgid "Mouse Gesture" +msgstr "Fare Hareketi" + +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Action" +msgstr "Eylem" + +#: lib/solaar/ui/diversion_rules.py:570 lib/solaar/ui/rule_actions.py:132 +msgid "Key press" +msgstr "Tuş basımı" + +#: lib/solaar/ui/diversion_rules.py:571 lib/solaar/ui/rule_actions.py:183 +msgid "Mouse scroll" +msgstr "Fare kaydırma" + +#: lib/solaar/ui/diversion_rules.py:572 lib/solaar/ui/rule_actions.py:244 +msgid "Mouse click" +msgstr "Fare tıklaması" + +#: lib/solaar/ui/diversion_rules.py:573 +msgid "Set" +msgstr "Belirle" + +#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/rule_actions.py:314 +msgid "Execute" +msgstr "Uygula" + +#: lib/solaar/ui/diversion_rules.py:575 lib/solaar/ui/diversion_rules.py:1096 +msgid "Later" +msgstr "Sonra" + +#: lib/solaar/ui/diversion_rules.py:604 +msgid "Insert new rule" +msgstr "Yeni kural ekle" + +#: lib/solaar/ui/diversion_rules.py:624 lib/solaar/ui/rule_actions.py:74 +#: lib/solaar/ui/rule_actions.py:273 lib/solaar/ui/rule_conditions.py:548 +msgid "Delete" +msgstr "Sil" + +#: lib/solaar/ui/diversion_rules.py:646 +msgid "Negate" +msgstr "Olumsuzla" + +#: lib/solaar/ui/diversion_rules.py:670 +msgid "Wrap with" +msgstr "Kapsa" + +#: lib/solaar/ui/diversion_rules.py:692 +msgid "Cut" +msgstr "Kes" + +#: lib/solaar/ui/diversion_rules.py:707 +msgid "Paste" +msgstr "Yapıştır" + +#: lib/solaar/ui/diversion_rules.py:713 +msgid "Copy" +msgstr "Kopyala" + +#: lib/solaar/ui/diversion_rules.py:1014 +msgid "This editor does not support the selected rule component yet." +msgstr "Seçilen kural bileşeni henüz desteklenmiyor." + +#: lib/solaar/ui/diversion_rules.py:1076 +msgid "" +"Number of seconds to delay. Delay between 0 and 1 is done with higher " +"precision." +msgstr "" +"Saniye cinsinden gecikme süresi. 0 ile 1 arasındaki gecikmeler daha yüksek " +"hassasiyetle yapılır." + +#: lib/solaar/ui/diversion_rules.py:1114 +msgid "Not" +msgstr "Değil" + +#: lib/solaar/ui/diversion_rules.py:1145 +msgid "Toggle" +msgstr "Değiştir" + +#: lib/solaar/ui/diversion_rules.py:1146 +msgid "True" +msgstr "Doğru" + +#: lib/solaar/ui/diversion_rules.py:1147 +msgid "False" +msgstr "Yanlış" + +#: lib/solaar/ui/diversion_rules.py:1160 +msgid "Unsupported setting" +msgstr "Desteklenmeyen ayar" + +#: lib/solaar/ui/diversion_rules.py:1322 lib/solaar/ui/diversion_rules.py:1342 +#: lib/solaar/ui/diversion_rules.py:1424 lib/solaar/ui/diversion_rules.py:1669 +#: lib/solaar/ui/diversion_rules.py:1687 msgid "Originating device" msgstr "Kaynak aygıt" -#: lib/solaar/ui/diversion_rules.py:2080 +#: lib/solaar/ui/diversion_rules.py:1355 +msgid "Device is active and its settings can be changed." +msgstr "Aygıt etkin ve ayarları değiştirilebilir." + +#: lib/solaar/ui/diversion_rules.py:1364 +msgid "Device that originated the current notification." +msgstr "Mevcut bildirimin kaynağı olan aygıt." + +#: lib/solaar/ui/diversion_rules.py:1377 +msgid "Name of host computer." +msgstr "Bilgisayarın adı." + +#: lib/solaar/ui/diversion_rules.py:1445 msgid "Value" msgstr "Değer" -#: lib/solaar/ui/diversion_rules.py:2088 +#: lib/solaar/ui/diversion_rules.py:1454 msgid "Item" msgstr "Öge" -#: lib/solaar/ui/notify.py:122 lib/solaar/ui/tray.py:323 -#: lib/solaar/ui/tray.py:328 lib/solaar/ui/window.py:746 -msgid "offline" -msgstr "çevrimdışı" +#: lib/solaar/ui/diversion_rules.py:1728 +msgid "Change setting on device" +msgstr "Aygıtın ayarını değiştir" -#: lib/solaar/ui/pair_window.py:121 lib/solaar/ui/pair_window.py:255 -#: lib/solaar/ui/pair_window.py:277 +#: lib/solaar/ui/diversion_rules.py:1744 +msgid "Setting on device" +msgstr "Aygıtta ayar" + +#: lib/solaar/ui/notify.py:115 +msgid "unspecified reason" +msgstr "tanımlanmamış sebep" + +#: lib/solaar/ui/pair_window.py:37 lib/solaar/ui/pair_window.py:155 #, python-format msgid "%(receiver_name)s: pair new device" msgstr "%(receiver_name)s: yeni aygıtı eşleştir" -#: lib/solaar/ui/pair_window.py:122 -#, python-format -msgid "Enter passcode on %(name)s." -msgstr "%(name)s üzerinde geçiş kodunu girin." +#: lib/solaar/ui/pair_window.py:39 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "Bolt alıcılar sadece Bolt aygıtlarla uyumludur." -#: lib/solaar/ui/pair_window.py:125 -#, python-format -msgid "Type %(passcode)s and then press the enter key." -msgstr "%(passcode)s yazın ve Enter tuşuna basın." - -#: lib/solaar/ui/pair_window.py:128 -msgid "left" -msgstr "sol" - -#: lib/solaar/ui/pair_window.py:128 -msgid "right" -msgstr "sağ" - -#: lib/solaar/ui/pair_window.py:130 -#, python-format -msgid "" -"Press %(code)s\n" -"and then press left and right buttons simultaneously." -msgstr "" -"%(code)s basın\n" -"ve sonra sağ ve sol tuşlarına aynı anda basın." - -#: lib/solaar/ui/pair_window.py:187 -msgid "Pairing failed" -msgstr "Eşleşme başarısız" - -#: lib/solaar/ui/pair_window.py:189 -msgid "Make sure your device is within range, and has a decent battery charge." -msgstr "" -"Aygıtınızın kapsama alanı içerisinde olduğundan, pilin çalıştığından veya " -"şarjının tam olduğundan emin olun." - -#: lib/solaar/ui/pair_window.py:191 -msgid "A new device was detected, but it is not compatible with this receiver." -msgstr "Aygıt algılandı fakat bu alıcı ile uyumlu değil." - -#: lib/solaar/ui/pair_window.py:193 -msgid "More paired devices than receiver can support." -msgstr "Alıcının destekleyebileceğinden daha fazla eşleştirilmiş aygıt." - -#: lib/solaar/ui/pair_window.py:195 -msgid "No further details are available about the error." -msgstr "Hata hakkında daha fazla ayrıntı mevcut değil." - -#: lib/solaar/ui/pair_window.py:209 -msgid "Found a new device:" -msgstr "Yeni bir aygıt bulundu:" - -#: lib/solaar/ui/pair_window.py:234 -msgid "The wireless link is not encrypted" -msgstr "Kablosuz bağlantı şifrelenmemiş" - -#: lib/solaar/ui/pair_window.py:263 +#: lib/solaar/ui/pair_window.py:41 msgid "Press a pairing button or key until the pairing light flashes quickly." msgstr "" "Eşleşme ışığı hızlıca yanıp sönene kadar eşleşme düğmesi ya da tuşuna basın." -#: lib/solaar/ui/pair_window.py:265 -msgid "You may have to first turn the device off and on again." -msgstr "Öncelikle aygıtınızı kapatıp açmalısınız." +#: lib/solaar/ui/pair_window.py:44 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "Unifying alıcılar sadece Unifying aygıtlarla uyumludur." -#: lib/solaar/ui/pair_window.py:267 +#: lib/solaar/ui/pair_window.py:46 +msgid "Other receivers are only compatible with a few devices." +msgstr "Diğer alıcılar sadece az miktarda aygıtla uyumludur." + +#: lib/solaar/ui/pair_window.py:48 msgid "Turn on the device you want to pair." msgstr "Eşleştirmek istediğiniz aygıtı açın." -#: lib/solaar/ui/pair_window.py:269 +#: lib/solaar/ui/pair_window.py:49 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "Aygıt yakınlarda açık olan bir alıcıyla eşleşmemiş olmalıdır." + +#: lib/solaar/ui/pair_window.py:51 msgid "If the device is already turned on, turn it off and on again." msgstr "Aygıt zaten açıksa, kapatıp tekrar açın." -#: lib/solaar/ui/pair_window.py:272 +#: lib/solaar/ui/pair_window.py:55 #, python-format msgid "" "\n" @@ -1529,7 +1665,7 @@ msgstr[1] "" "\n" "Bu alıcı %d aygıtla daha eşleşebilir." -#: lib/solaar/ui/pair_window.py:275 +#: lib/solaar/ui/pair_window.py:61 msgid "" "\n" "Cancelling at this point will not use up a pairing." @@ -1537,207 +1673,405 @@ msgstr "" "\n" "Bu adımda vezgeçerseniz eşleşme kullanılamayacak." -#: lib/solaar/ui/tray.py:58 -msgid "No Logitech device found" -msgstr "Hiçbir Logitech aygıtı bulunamadı" +#: lib/solaar/ui/pair_window.py:156 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "%(name)s üzerinde geçiş kodunu girin." -#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:324 +#: lib/solaar/ui/pair_window.py:159 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "%(passcode)s yazın ve Enter tuşuna basın." + +#: lib/solaar/ui/pair_window.py:162 +msgid "left" +msgstr "sol" + +#: lib/solaar/ui/pair_window.py:162 +msgid "right" +msgstr "sağ" + +#: lib/solaar/ui/pair_window.py:164 +#, python-format +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"%(code)s basın\n" +"ve sonra sağ ve sol tuşlarına aynı anda basın." + +#: lib/solaar/ui/pair_window.py:195 +msgid "The wireless link is not encrypted" +msgstr "Kablosuz bağlantı şifrelenmemiş" + +#: lib/solaar/ui/pair_window.py:200 +msgid "Found a new device:" +msgstr "Yeni bir aygıt bulundu:" + +#: lib/solaar/ui/pair_window.py:221 +msgid "Pairing failed" +msgstr "Eşleşme başarısız" + +#: lib/solaar/ui/pair_window.py:223 +msgid "Make sure your device is within range, and has a decent battery charge." +msgstr "" +"Aygıtınızın kapsama alanı içerisinde olduğundan, pilin çalıştığından veya " +"şarjının tam olduğundan emin olun." + +#: lib/solaar/ui/pair_window.py:225 +msgid "A new device was detected, but it is not compatible with this receiver." +msgstr "Aygıt algılandı fakat bu alıcı ile uyumlu değil." + +#: lib/solaar/ui/pair_window.py:227 +msgid "More paired devices than receiver can support." +msgstr "Alıcının destekleyebileceğinden daha fazla eşleştirilmiş aygıt." + +#: lib/solaar/ui/pair_window.py:229 +msgid "No further details are available about the error." +msgstr "Hata hakkında daha fazla ayrıntı mevcut değil." + +#: lib/solaar/ui/rule_actions.py:48 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Akorlu tuş tıklaması, basması veya bırakması öykün.\n" +"Wayland'da /dev/uinput'a yazma yetkisi gerektirir." + +#: lib/solaar/ui/rule_actions.py:53 +msgid "Add key" +msgstr "Tuş ekle" + +#: lib/solaar/ui/rule_actions.py:56 +msgid "Click" +msgstr "Tıklama" + +#: lib/solaar/ui/rule_actions.py:59 +msgid "Depress" +msgstr "Basma" + +#: lib/solaar/ui/rule_actions.py:62 +msgid "Release" +msgstr "Bırakma" + +#: lib/solaar/ui/rule_actions.py:147 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Fare kayrıdması öykün.\n" +"Wayland'da /dev/uinput'a yazma yetkisi gerektirir." + +#: lib/solaar/ui/rule_actions.py:203 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Fare tıklaması öykün.\n" +"Wayland'da /dev/uinput'a yazma yetkisi gerektirir." + +#: lib/solaar/ui/rule_actions.py:206 +msgid "Button" +msgstr "Düğme" + +#: lib/solaar/ui/rule_actions.py:207 +msgid "Count and Action" +msgstr "Say ve İşle" + +#: lib/solaar/ui/rule_actions.py:256 +msgid "Execute a command with arguments." +msgstr "Argümanlarla bir komut çalıştır." + +#: lib/solaar/ui/rule_actions.py:259 +msgid "Add argument" +msgstr "Argüman ekle" + +#: lib/solaar/ui/rule_conditions.py:43 +msgid "X11 active process. For use in X11 only." +msgstr "X11 etkin işlem. Sadece X11'de kullanım için." + +#: lib/solaar/ui/rule_conditions.py:73 +msgid "X11 mouse process. For use in X11 only." +msgstr "X11 fare işlemi. Sadece X11'de kullanım için." + +#: lib/solaar/ui/rule_conditions.py:90 +msgid "MouseProcess" +msgstr "Fareİşlemi" + +#: lib/solaar/ui/rule_conditions.py:114 +msgid "Feature name of notification triggering rule processing." +msgstr "Bildirim tetikleme işlemi özelliği adı." + +#: lib/solaar/ui/rule_conditions.py:161 +msgid "Report number of notification triggering rule processing." +msgstr "Bildirim tetikleme işlemi rapor numarası." + +#: lib/solaar/ui/rule_conditions.py:194 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "Etkin klavye değiştiriciler. Wayland'da her zaman çalışmaz." + +#: lib/solaar/ui/rule_conditions.py:234 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"Yönlendirilmiş tuş veya düğme basıldı veya bırakıldı.\n" +"Tuşları ve düğmeleri yönlendirmek için Tuş/Düğme Yönlendirme ve " +"Yönlendirilen G Tuşları ayarlarını kullanın." + +#: lib/solaar/ui/rule_conditions.py:243 +msgid "Key down" +msgstr "Tuş basılı" + +#: lib/solaar/ui/rule_conditions.py:246 +msgid "Key up" +msgstr "Tuş serbest" + +#: lib/solaar/ui/rule_conditions.py:286 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"Yönlendirilmiş tuş veya düğme şu anda basılı.\n" +"Tuşları ve düğmeleri yönlendirmek için Tuş/Düğme Yönlendirme ve " +"Yönlendirilen G Tuşları ayarlarını kullanın." + +#: lib/solaar/ui/rule_conditions.py:324 +msgid "Test condition on notification triggering rule processing." +msgstr "Bildirim tetikleme işlemi test durumu." + +#: lib/solaar/ui/rule_conditions.py:328 +msgid "Parameter" +msgstr "Parametre" + +#: lib/solaar/ui/rule_conditions.py:401 +msgid "begin (inclusive)" +msgstr "baş (dahil edilen)" + +#: lib/solaar/ui/rule_conditions.py:402 +msgid "end (exclusive)" +msgstr "son (hariç tutulan)" + +#: lib/solaar/ui/rule_conditions.py:410 +msgid "range" +msgstr "aralık" + +#: lib/solaar/ui/rule_conditions.py:413 +msgid "minimum" +msgstr "en az" + +#: lib/solaar/ui/rule_conditions.py:414 +msgid "maximum" +msgstr "en çok" + +#: lib/solaar/ui/rule_conditions.py:416 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "%(0)d bayttan %(1)d bayta, %(2)d ile %(3)d arasında" + +#: lib/solaar/ui/rule_conditions.py:419 lib/solaar/ui/rule_conditions.py:420 +msgid "mask" +msgstr "maske" + +#: lib/solaar/ui/rule_conditions.py:421 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "%(0)d bayttan %(1)d bayta, %(2)d maskeleme" + +#: lib/solaar/ui/rule_conditions.py:430 +msgid "" +"Bit or range test on bytes in notification message triggering rule " +"processing." +msgstr "" +"Bildirim mesajı tetikleyici kural işlemesinde baytlarda bit veya aralık " +"testi." + +#: lib/solaar/ui/rule_conditions.py:440 +msgid "type" +msgstr "tür" + +#: lib/solaar/ui/rule_conditions.py:528 +msgid "" +"Mouse gesture with optional initiating button followed by zero or more mouse " +"movements." +msgstr "" +"Sıfır veya daha fazla fare hareketinin ardından isteğe bağlı başlatma " +"düğmesi ile fare hareketi." + +#: lib/solaar/ui/rule_conditions.py:533 +msgid "Add movement" +msgstr "Hareket ekle" + +#: lib/solaar/ui/tray.py:50 +msgid "No supported device found" +msgstr "Desteklenen aygıt bulunamadı" + +#: lib/solaar/ui/tray.py:55 lib/solaar/ui/window.py:310 #, python-format msgid "About %s" msgstr "%s Hakkında" -#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:322 +#: lib/solaar/ui/tray.py:56 lib/solaar/ui/window.py:308 #, python-format msgid "Quit %s" msgstr "%s Çıkış" -#: lib/solaar/ui/tray.py:302 lib/solaar/ui/tray.py:310 +#: lib/solaar/ui/tray.py:267 lib/solaar/ui/tray.py:275 msgid "no receiver" msgstr "alıcı yok" -#: lib/solaar/ui/tray.py:326 +#: lib/solaar/ui/tray.py:288 lib/solaar/ui/tray.py:293 +msgid "offline" +msgstr "çevrimdışı" + +#: lib/solaar/ui/tray.py:291 msgid "no status" msgstr "durum yok" -#: lib/solaar/ui/window.py:101 +#: lib/solaar/ui/window.py:91 msgid "Scanning" msgstr "Taranıyor" -#: lib/solaar/ui/window.py:134 lib/solaar/ui/window.py:687 +#: lib/solaar/ui/window.py:122 msgid "Battery" msgstr "Pil" -#: lib/solaar/ui/window.py:137 +#: lib/solaar/ui/window.py:125 msgid "Wireless Link" msgstr "Kablosuz Bağlantı" -#: lib/solaar/ui/window.py:141 +#: lib/solaar/ui/window.py:129 msgid "Lighting" msgstr "Aydınlatma" -#: lib/solaar/ui/window.py:175 +#: lib/solaar/ui/window.py:163 msgid "Show Technical Details" msgstr "Teknik Ayrıntıları Göster" -#: lib/solaar/ui/window.py:191 +#: lib/solaar/ui/window.py:179 msgid "Pair new device" msgstr "Yeni aygıt eşle" -#: lib/solaar/ui/window.py:210 +#: lib/solaar/ui/window.py:197 msgid "Select a device" msgstr "Aygıtı seç" -#: lib/solaar/ui/window.py:327 +#: lib/solaar/ui/window.py:313 msgid "Rule Editor" msgstr "Kural Düzenleyici" -#: lib/solaar/ui/window.py:538 +#: lib/solaar/ui/window.py:517 msgid "Path" msgstr "Yol" -#: lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:520 msgid "USB ID" msgstr "USB Kimliği" -#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 -#: lib/solaar/ui/window.py:566 lib/solaar/ui/window.py:568 +#: lib/solaar/ui/window.py:523 lib/solaar/ui/window.py:525 +#: lib/solaar/ui/window.py:540 lib/solaar/ui/window.py:542 msgid "Serial" msgstr "Seri" -#: lib/solaar/ui/window.py:550 +#: lib/solaar/ui/window.py:529 msgid "Index" msgstr "Dizin" -#: lib/solaar/ui/window.py:552 +#: lib/solaar/ui/window.py:531 msgid "Wireless PID" msgstr "Kablosuz bağlantı kimliği" -#: lib/solaar/ui/window.py:554 +#: lib/solaar/ui/window.py:533 msgid "Product ID" msgstr "Ürün Kimliği" -#: lib/solaar/ui/window.py:556 +#: lib/solaar/ui/window.py:535 msgid "Protocol" msgstr "İletişim kuralı" -#: lib/solaar/ui/window.py:556 +#: lib/solaar/ui/window.py:535 msgid "Unknown" msgstr "Bilinmeyen" -#: lib/solaar/ui/window.py:559 -#, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d ms (%(rate_hz)dHz)" - -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:537 msgid "Polling rate" -msgstr "Yoklama oranı" +msgstr "Bağlantı sıklığı" -#: lib/solaar/ui/window.py:570 +#: lib/solaar/ui/window.py:544 msgid "Unit ID" msgstr "Birim Kimliği" -#: lib/solaar/ui/window.py:581 -msgid "none" -msgstr "yok" - -#: lib/solaar/ui/window.py:582 +#: lib/solaar/ui/window.py:558 msgid "Notifications" msgstr "Bildirimler" -#: lib/solaar/ui/window.py:619 +#: lib/solaar/ui/window.py:602 msgid "No device paired." msgstr "Eşleştirilmiş aygıt yok." -#: lib/solaar/ui/window.py:626 +#: lib/solaar/ui/window.py:611 #, python-format msgid "Up to %(max_count)s device can be paired to this receiver." msgid_plural "Up to %(max_count)s devices can be paired to this receiver." msgstr[0] "En fazla %(max_count)s aygıt bu alıcıyla eşleşebilir." msgstr[1] "En fazla %(max_count)s aygıt bu alıcıyla eşleşebilir." -#: lib/solaar/ui/window.py:632 -msgid "Only one device can be paired to this receiver." -msgstr "Sadece bir aygıt bu alıcıya eşlenebilir." - -#: lib/solaar/ui/window.py:636 +#: lib/solaar/ui/window.py:622 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." msgstr[0] "Bu alıcı %d aygıtla daha eşleşebilir." msgstr[1] "Bu alıcı %d aygıtla daha eşleşebilir." -#: lib/solaar/ui/window.py:688 -msgid "unknown" -msgstr "bilinmeyen" - -#: lib/solaar/ui/window.py:689 -msgid "Battery information unknown." -msgstr "Bilinmeyen pil bilgisi." - -#: lib/solaar/ui/window.py:699 +#: lib/solaar/ui/window.py:679 msgid "Battery Voltage" msgstr "Pil Gerilimi" -#: lib/solaar/ui/window.py:701 +#: lib/solaar/ui/window.py:681 msgid "Voltage reported by battery" msgstr "Pil tarafından bildirilen gerilim" -#: lib/solaar/ui/window.py:703 +#: lib/solaar/ui/window.py:683 msgid "Battery Level" -msgstr "Pil Durumu" +msgstr "Pil Düzeyi" -#: lib/solaar/ui/window.py:705 +#: lib/solaar/ui/window.py:685 msgid "Approximate level reported by battery" -msgstr "Pil tarafından bildirilen yaklaşık durum" +msgstr "Pil tarafından bildirilen yaklaşık düzeyi" -#: lib/solaar/ui/window.py:712 lib/solaar/ui/window.py:714 +#: lib/solaar/ui/window.py:692 lib/solaar/ui/window.py:694 msgid "next reported " msgstr "sonraki rapor " -#: lib/solaar/ui/window.py:715 +#: lib/solaar/ui/window.py:695 msgid " and next level to be reported." msgstr " ve sonraki seviye raporlanması." -#: lib/solaar/ui/window.py:720 -msgid "last known" -msgstr "son bilinen" - -#: lib/solaar/ui/window.py:728 -msgid "not encrypted" -msgstr "şifresiz" - -#: lib/solaar/ui/window.py:732 -msgid "" -"The wireless link between this device and its receiver is not encrypted.\n" -"\n" -"For pointing devices (mice, trackballs, trackpads), this is a minor security " -"issue.\n" -"\n" -"It is, however, a major security issue for text-input devices (keyboards, " -"numpads),\n" -"because typed text can be sniffed inconspicuously by 3rd parties within " -"range." -msgstr "" -"Bu aygıt ile alıcı arasındaki kablosuz bağlantı şirelenmemiştir.\n" -"\n" -"İşaretçi aygıtlar için (fare, iztopu, dokunmatik fare), bu durum ufak bir " -"günvenlik sorunudur.\n" -"\n" -"Fakat yazı giriş aygıtları için bu durum büyük bir sorundur(klavyerler, tuş " -"takımları),\n" -"çünkü şifrelenmemiş yazı girişleri kapsama alanı içerisindeki 3. kişiler " -"tarafından dinlenebilir." - -#: lib/solaar/ui/window.py:741 +#: lib/solaar/ui/window.py:711 msgid "encrypted" msgstr "şifreli" -#: lib/solaar/ui/window.py:743 +#: lib/solaar/ui/window.py:713 msgid "The wireless link between this device and its receiver is encrypted." msgstr "Bu aygıt ile alıcı arasındaki kablosuz bağlantı şirelenmiştir." -#: lib/solaar/ui/window.py:756 +#: lib/solaar/ui/window.py:715 +msgid "not encrypted" +msgstr "şifresiz" + +#: lib/solaar/ui/window.py:719 +msgid "" +"The wireless link between this device and its receiver is not encrypted.\n" +"This is a security issue for pointing devices, and a major security issue " +"for text-input devices." +msgstr "" +"Bu aygıt ile alıcı arasındaki kablosuz bağlantı şirelenmemiştir.\n" +"Bu işaretçi cihazlar için güvenlik açığı ve metin-giriş aygıtları için büyük " +"bir güvenlik açığı." + +#: lib/solaar/ui/window.py:735 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d lüks" diff --git a/po/uk.po b/po/uk.po new file mode 100644 index 00000000..32828011 --- /dev/null +++ b/po/uk.po @@ -0,0 +1,1660 @@ +# Ukrainian translation for solaar +# This file is distributed under the same license as the solaar package. +# Kira, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: solaar 1.1.14\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-10-16 18:53+0300\n" +"PO-Revision-Date: 2025-10-16 20:11+0300\n" +"Last-Translator: Олександр Афанасьєв \n" +"Language-Team: Ukrainian \n" +"Language: uk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: lib/logitech_receiver/base_usb.py:52 +msgid "Bolt Receiver" +msgstr "Приймач Bolt" + +#: lib/logitech_receiver/base_usb.py:64 +msgid "Unifying Receiver" +msgstr "Приймач Unifying" + +#: lib/logitech_receiver/base_usb.py:75 lib/logitech_receiver/base_usb.py:87 +#: lib/logitech_receiver/base_usb.py:100 lib/logitech_receiver/base_usb.py:113 +msgid "Nano Receiver" +msgstr "Наноприймач" + +#: lib/logitech_receiver/base_usb.py:125 +msgid "Lightspeed Receiver" +msgstr "Приймач Lightspeed" + +#: lib/logitech_receiver/base_usb.py:135 +msgid "EX100 Receiver 27 Mhz" +msgstr "Приймач EX100 27 МГц" + +#: lib/logitech_receiver/common.py:649 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "Батарея: %(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:652 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "Батарея: %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:1023 +#: lib/logitech_receiver/settings_templates.py:295 +msgid "Disabled" +msgstr "Вимкнено" + +#: lib/logitech_receiver/hidpp20.py:1024 +msgid "Static" +msgstr "Статичний" + +#: lib/logitech_receiver/hidpp20.py:1025 +msgid "Pulse" +msgstr "Пульсація" + +#: lib/logitech_receiver/hidpp20.py:1026 +msgid "Cycle" +msgstr "Цикл" + +#: lib/logitech_receiver/hidpp20.py:1027 +msgid "Boot" +msgstr "Завантаження" + +#: lib/logitech_receiver/hidpp20.py:1028 +msgid "Demo" +msgstr "Демо" + +#: lib/logitech_receiver/hidpp20.py:1030 +msgid "Breathe" +msgstr "Дихання" + +#: lib/logitech_receiver/hidpp20.py:1033 +msgid "Ripple" +msgstr "Брижі" + +#: lib/logitech_receiver/hidpp20.py:1034 +msgid "Decomposition" +msgstr "Розкладання" + +#: lib/logitech_receiver/hidpp20.py:1035 +msgid "Signature1" +msgstr "Підпис1" + +#: lib/logitech_receiver/hidpp20.py:1036 +msgid "Signature2" +msgstr "Підпис2" + +#: lib/logitech_receiver/hidpp20.py:1037 +msgid "CycleS" +msgstr "ЦиклS" + +#: lib/logitech_receiver/hidpp20.py:1101 +msgid "Unknown Location" +msgstr "Невідоме місце" + +#: lib/logitech_receiver/hidpp20.py:1102 +msgid "Primary" +msgstr "Основний" + +#: lib/logitech_receiver/hidpp20.py:1103 +msgid "Logo" +msgstr "Логотип" + +#: lib/logitech_receiver/hidpp20.py:1104 +msgid "Left Side" +msgstr "Ліва сторона" + +#: lib/logitech_receiver/hidpp20.py:1105 +msgid "Right Side" +msgstr "Права сторона" + +#: lib/logitech_receiver/hidpp20.py:1106 +msgid "Combined" +msgstr "Комбінований" + +#: lib/logitech_receiver/hidpp20.py:1107 +msgid "Primary 1" +msgstr "Основний 1" + +#: lib/logitech_receiver/hidpp20.py:1108 +msgid "Primary 2" +msgstr "Основний 2" + +#: lib/logitech_receiver/hidpp20.py:1109 +msgid "Primary 3" +msgstr "Основний 3" + +#: lib/logitech_receiver/hidpp20.py:1110 +msgid "Primary 4" +msgstr "Основний 4" + +#: lib/logitech_receiver/hidpp20.py:1111 +msgid "Primary 5" +msgstr "Основний 5" + +#: lib/logitech_receiver/hidpp20.py:1112 +msgid "Primary 6" +msgstr "Основний 6" + +#: lib/logitech_receiver/i18n.py:28 +msgid "empty" +msgstr "порожній" + +#: lib/logitech_receiver/i18n.py:29 +msgid "critical" +msgstr "критичний" + +#: lib/logitech_receiver/i18n.py:30 +msgid "low" +msgstr "низький" + +#: lib/logitech_receiver/i18n.py:31 +msgid "average" +msgstr "середній" + +#: lib/logitech_receiver/i18n.py:32 +msgid "good" +msgstr "добрий" + +#: lib/logitech_receiver/i18n.py:33 +msgid "full" +msgstr "повний" + +#: lib/logitech_receiver/i18n.py:35 +msgid "discharging" +msgstr "розряджається" + +#: lib/logitech_receiver/i18n.py:36 +msgid "recharging" +msgstr "заряджається" + +#: lib/logitech_receiver/i18n.py:37 lib/solaar/ui/window.py:700 +msgid "charging" +msgstr "заряджається" + +#: lib/logitech_receiver/i18n.py:38 +msgid "not charging" +msgstr "не заряджається" + +#: lib/logitech_receiver/i18n.py:39 +msgid "almost full" +msgstr "майже повний" + +#: lib/logitech_receiver/i18n.py:40 +msgid "charged" +msgstr "заряджено" + +#: lib/logitech_receiver/i18n.py:41 +msgid "slow recharge" +msgstr "повільне заряджання" + +#: lib/logitech_receiver/i18n.py:42 +msgid "invalid battery" +msgstr "несправна батарея" + +#: lib/logitech_receiver/i18n.py:43 +msgid "thermal error" +msgstr "термічна помилка" + +#: lib/logitech_receiver/i18n.py:44 +msgid "error" +msgstr "помилка" + +#: lib/logitech_receiver/i18n.py:45 +msgid "standard" +msgstr "стандартний" + +#: lib/logitech_receiver/i18n.py:46 +msgid "fast" +msgstr "швидкий" + +#: lib/logitech_receiver/i18n.py:47 +msgid "slow" +msgstr "повільний" + +#: lib/logitech_receiver/i18n.py:49 +msgid "device timeout" +msgstr "тайм-аут пристрою" + +#: lib/logitech_receiver/i18n.py:50 +msgid "device not supported" +msgstr "пристрій не підтримується" + +#: lib/logitech_receiver/i18n.py:51 +msgid "too many devices" +msgstr "забагато пристроїв" + +#: lib/logitech_receiver/i18n.py:52 +msgid "sequence timeout" +msgstr "тайм-аут послідовності" + +#: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:555 +msgid "Firmware" +msgstr "Мікропрограма" + +#: lib/logitech_receiver/i18n.py:55 +msgid "Bootloader" +msgstr "Завантажувач" + +#: lib/logitech_receiver/i18n.py:56 +msgid "Hardware" +msgstr "Обладнання" + +#: lib/logitech_receiver/i18n.py:57 +msgid "Other" +msgstr "Інше" + +#: lib/logitech_receiver/i18n.py:59 +msgid "Left Button" +msgstr "Ліва кнопка" + +#: lib/logitech_receiver/i18n.py:60 +msgid "Right Button" +msgstr "Права кнопка" + +#: lib/logitech_receiver/i18n.py:61 +msgid "Middle Button" +msgstr "Середня кнопка" + +#: lib/logitech_receiver/i18n.py:62 +msgid "Back Button" +msgstr "Кнопка 'Назад'" + +#: lib/logitech_receiver/i18n.py:63 +msgid "Forward Button" +msgstr "Кнопка 'Вперед'" + +#: lib/logitech_receiver/i18n.py:64 +msgid "Mouse Gesture Button" +msgstr "Кнопка жестів миші" + +#: lib/logitech_receiver/i18n.py:65 +msgid "Smart Shift" +msgstr "Розумне перемикання" + +#: lib/logitech_receiver/i18n.py:66 +msgid "DPI Switch" +msgstr "Перемикач DPI" + +#: lib/logitech_receiver/i18n.py:67 +msgid "Left Tilt" +msgstr "Нахил вліво" + +#: lib/logitech_receiver/i18n.py:68 +msgid "Right Tilt" +msgstr "Нахил вправо" + +#: lib/logitech_receiver/i18n.py:69 +msgid "Left Click" +msgstr "Лівий клік" + +#: lib/logitech_receiver/i18n.py:70 +msgid "Right Click" +msgstr "Правий клік" + +#: lib/logitech_receiver/i18n.py:71 +msgid "Mouse Middle Button" +msgstr "Середня кнопка миші" + +#: lib/logitech_receiver/i18n.py:72 +msgid "Mouse Back Button" +msgstr "Кнопка миші 'Назад'" + +#: lib/logitech_receiver/i18n.py:73 +msgid "Mouse Forward Button" +msgstr "Кнопка миші 'Вперед'" + +#: lib/logitech_receiver/i18n.py:74 +msgid "Gesture Button Navigation" +msgstr "Навігація кнопкою жестів" + +#: lib/logitech_receiver/i18n.py:75 +msgid "Mouse Scroll Left Button" +msgstr "Кнопка прокрутки миші вліво" + +#: lib/logitech_receiver/i18n.py:76 +msgid "Mouse Scroll Right Button" +msgstr "Кнопка прокрутки миші вправо" + +#: lib/logitech_receiver/i18n.py:78 +msgid "pressed" +msgstr "натиснуто" + +#: lib/logitech_receiver/i18n.py:79 +msgid "released" +msgstr "відпущено" + +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 +msgid "connected" +msgstr "під'єднано" + +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 +msgid "disconnected" +msgstr "від'єднано" + +#: lib/logitech_receiver/notifications.py:182 +msgid "unpaired" +msgstr "пару розірвано" + +#: lib/logitech_receiver/notifications.py:231 +msgid "powered on" +msgstr "увімкнено" + +#: lib/logitech_receiver/notifications.py:283 +msgid "ADC measurement notification" +msgstr "сповіщення про вимірювання АЦП" + +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:483 +msgid "pairing lock is closed" +msgstr "блокування створення пари закрито" + +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:483 +msgid "pairing lock is open" +msgstr "блокування створення пари відкрито" + +#: lib/logitech_receiver/notifications.py:446 +msgid "discovery lock is closed" +msgstr "блокування виявлення закрито" + +#: lib/logitech_receiver/notifications.py:446 +msgid "discovery lock is open" +msgstr "блокування виявлення відкрито" + +#: lib/logitech_receiver/receiver.py:437 +msgid "No paired devices." +msgstr "Немає під'єднаних пристроїв." + +#: lib/logitech_receiver/receiver.py:439 lib/solaar/ui/window.py:606 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s під'єднаний пристрій." +msgstr[1] "%(count)s під'єднаних пристроїв." +msgstr[2] "%(count)s під'єднаних пристроїв." + +#: lib/logitech_receiver/settings.py:598 +msgid "register" +msgstr "реєстр" + +#: lib/logitech_receiver/settings.py:612 lib/logitech_receiver/settings.py:650 +msgid "feature" +msgstr "функція" + +#: lib/logitech_receiver/settings_templates.py:134 +msgid "Swap Fx function" +msgstr "Поміняти функцію Fx" + +#: lib/logitech_receiver/settings_templates.py:137 +msgid "" +"When set, the F1..F12 keys will activate their special function,\n" +"and you must hold the FN key to activate their standard function." +msgstr "" +"Якщо встановлено, клавіші F1-F12 активуватимуть свою спеціальну функцію,\n" +"і вам потрібно буде утримувати клавішу FN для активації стандартної функції." + +#: lib/logitech_receiver/settings_templates.py:142 +msgid "" +"When unset, the F1..F12 keys will activate their standard function,\n" +"and you must hold the FN key to activate their special function." +msgstr "" +"Якщо не встановлено, клавіші F1-F12 активуватимуть свою стандартну функцію,\n" +"і вам потрібно буде утримувати клавішу FN для активації спеціальної функції." + +#: lib/logitech_receiver/settings_templates.py:150 +msgid "Hand Detection" +msgstr "Виявлення рук" + +#: lib/logitech_receiver/settings_templates.py:151 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "Вмикати підсвічування, коли руки знаходяться над клавіатурою." + +#: lib/logitech_receiver/settings_templates.py:158 +msgid "Scroll Wheel Smooth Scrolling" +msgstr "Плавне прокручування коліщатка" + +#: lib/logitech_receiver/settings_templates.py:159 +#: lib/logitech_receiver/settings_templates.py:406 +#: lib/logitech_receiver/settings_templates.py:435 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "Режим високої чутливості для вертикального прокручування коліщатком." + +#: lib/logitech_receiver/settings_templates.py:166 +msgid "Side Scrolling" +msgstr "Бічне прокручування" + +#: lib/logitech_receiver/settings_templates.py:168 +msgid "" +"When disabled, pushing the wheel sideways sends custom button events\n" +"instead of the standard side-scrolling events." +msgstr "" +"Якщо вимкнено, натискання коліщатка вбік надсилає спеціальні події кнопок\n" +"замість стандартних подій бічного прокручування." + +#: lib/logitech_receiver/settings_templates.py:178 +msgid "Sensitivity (DPI - older mice)" +msgstr "Чутливість (DPI - для старих мишей)" + +#: lib/logitech_receiver/settings_templates.py:179 +#: lib/logitech_receiver/settings_templates.py:983 +#: lib/logitech_receiver/settings_templates.py:1011 +msgid "Mouse movement sensitivity" +msgstr "Чутливість руху миші" + +#: lib/logitech_receiver/settings_templates.py:252 +msgid "Backlight Timed" +msgstr "Підсвічування за таймером" + +#: lib/logitech_receiver/settings_templates.py:253 +#: lib/logitech_receiver/settings_templates.py:393 +msgid "Set illumination time for keyboard." +msgstr "Встановити час підсвічування клавіатури." + +#: lib/logitech_receiver/settings_templates.py:264 +msgid "Backlight" +msgstr "Підсвічування" + +#: lib/logitech_receiver/settings_templates.py:265 +msgid "" +"Illumination level on keyboard. Changes made are only applied in Manual " +"mode." +msgstr "" +"Рівень підсвічування клавіатури. Зміни застосовуються лише в ручному режимі." + +#: lib/logitech_receiver/settings_templates.py:297 +msgid "Automatic" +msgstr "Автоматично" + +#: lib/logitech_receiver/settings_templates.py:299 +msgid "Manual" +msgstr "Вручну" + +#: lib/logitech_receiver/settings_templates.py:301 +msgid "Enabled" +msgstr "Увімкнено" + +#: lib/logitech_receiver/settings_templates.py:307 +msgid "Backlight Level" +msgstr "Рівень підсвічування" + +#: lib/logitech_receiver/settings_templates.py:308 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "Рівень підсвічування клавіатури в ручному режимі." + +#: lib/logitech_receiver/settings_templates.py:365 +msgid "Backlight Delay Hands Out" +msgstr "Затримка підсвічування (руки прибрано)" + +#: lib/logitech_receiver/settings_templates.py:366 +msgid "" +"Delay in seconds until backlight fades out with hands away from keyboard." +msgstr "" +"Затримка в секундах до згасання підсвічування, коли руки прибрані з " +"клавіатури." + +#: lib/logitech_receiver/settings_templates.py:374 +msgid "Backlight Delay Hands In" +msgstr "Затримка підсвічування (руки на клавіатурі)" + +#: lib/logitech_receiver/settings_templates.py:375 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "" +"Затримка в секундах до згасання підсвічування, коли руки біля клавіатури." + +#: lib/logitech_receiver/settings_templates.py:383 +msgid "Backlight Delay Powered" +msgstr "Затримка підсвічування (від живлення)" + +#: lib/logitech_receiver/settings_templates.py:384 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "" +"Затримка в секундах до згасання підсвічування при зовнішньому живленні." + +#: lib/logitech_receiver/settings_templates.py:392 +msgid "Backlight (Seconds)" +msgstr "Підсвічування (секунди)" + +#: lib/logitech_receiver/settings_templates.py:404 +msgid "Scroll Wheel High Resolution" +msgstr "Висока роздільна здатність коліщатка" + +#: lib/logitech_receiver/settings_templates.py:408 +#: lib/logitech_receiver/settings_templates.py:437 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "Ігнорувати, якщо прокручування ненормально швидке або повільне" + +#: lib/logitech_receiver/settings_templates.py:415 +#: lib/logitech_receiver/settings_templates.py:446 +msgid "Scroll Wheel Diversion" +msgstr "Перенаправлення коліщатка прокрутки" + +#: lib/logitech_receiver/settings_templates.py:417 +msgid "" +"Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " +"Solaar rules but are otherwise ignored)." +msgstr "" +"Примусити коліщатко прокрутки надсилати сповіщення LOWRES_WHEEL HID++ (які " +"запускають правила Solaar, але в іншому випадку ігноруються)." + +#: lib/logitech_receiver/settings_templates.py:424 +msgid "Scroll Wheel Direction" +msgstr "Напрямок прокручування коліщатка" + +#: lib/logitech_receiver/settings_templates.py:425 +msgid "Invert direction for vertical scroll with wheel." +msgstr "Інвертувати напрямок вертикального прокручування коліщатком." + +#: lib/logitech_receiver/settings_templates.py:433 +msgid "Scroll Wheel Resolution" +msgstr "Роздільна здатність коліщатка прокрутки" + +#: lib/logitech_receiver/settings_templates.py:448 +msgid "" +"Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"Примусити коліщатко прокрутки надсилати сповіщення HIRES_WHEEL HID++ (які " +"запускають правила Solaar, але в іншому випадку ігноруються)." + +#: lib/logitech_receiver/settings_templates.py:457 +msgid "Sensitivity (Pointer Speed)" +msgstr "Чутливість (швидкість вказівника)" + +#: lib/logitech_receiver/settings_templates.py:458 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "Множник швидкості для миші (256 — звичайний множник)." + +#: lib/logitech_receiver/settings_templates.py:468 +msgid "Thumb Wheel Diversion" +msgstr "Перенаправлення коліщатка для великого пальця" + +#: lib/logitech_receiver/settings_templates.py:470 +msgid "" +"Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"Примусити коліщатко для великого пальця надсилати сповіщення THUMB_WHEEL HID+" +"+ (які запускають правила Solaar, але в іншому випадку ігноруються)." + +#: lib/logitech_receiver/settings_templates.py:479 +msgid "Thumb Wheel Direction" +msgstr "Напрямок коліщатка для великого пальця" + +#: lib/logitech_receiver/settings_templates.py:480 +msgid "Invert thumb wheel scroll direction." +msgstr "Інвертувати напрямок прокручування коліщатка для великого пальця." + +#: lib/logitech_receiver/settings_templates.py:500 +msgid "Onboard Profiles" +msgstr "Вбудовані профілі" + +#: lib/logitech_receiver/settings_templates.py:501 +msgid "" +"Enable an onboard profile, which controls report rate, sensitivity, and " +"button actions" +msgstr "" +"Увімкнути вбудований профіль, який контролює частоту опитування, чутливість " +"та дії кнопок" + +#: lib/logitech_receiver/settings_templates.py:545 +#: lib/logitech_receiver/settings_templates.py:578 +msgid "Report Rate" +msgstr "Частота опитування" + +#: lib/logitech_receiver/settings_templates.py:547 +#: lib/logitech_receiver/settings_templates.py:580 +msgid "Frequency of device movement reports" +msgstr "Частота звітів про рух пристрою" + +#: lib/logitech_receiver/settings_templates.py:547 +#: lib/logitech_receiver/settings_templates.py:580 +#: lib/logitech_receiver/settings_templates.py:1011 +#: lib/logitech_receiver/settings_templates.py:1385 +#: lib/logitech_receiver/settings_templates.py:1416 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "Для ефективності може знадобитися вимкнути вбудовані профілі." + +#: lib/logitech_receiver/settings_templates.py:608 +msgid "Divert crown events" +msgstr "Перенаправляти події коронки" + +#: lib/logitech_receiver/settings_templates.py:609 +msgid "" +"Make crown send CROWN HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"Примусити коронку надсилати сповіщення CROWN HID++ (які запускають правила " +"Solaar, але в іншому випадку ігноруються)." + +#: lib/logitech_receiver/settings_templates.py:617 +msgid "Crown smooth scroll" +msgstr "Плавне прокручування коронкою" + +#: lib/logitech_receiver/settings_templates.py:618 +msgid "Set crown smooth scroll" +msgstr "Встановити плавне прокручування коронкою" + +#: lib/logitech_receiver/settings_templates.py:626 +msgid "Divert G and M Keys" +msgstr "Перенаправити клавіші G та M" + +#: lib/logitech_receiver/settings_templates.py:627 +msgid "" +"Make G and M keys send HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"Примусити клавіші G та M надсилати сповіщення HID++ (які запускають правила " +"Solaar, але в іншому випадку ігноруються)." + +#: lib/logitech_receiver/settings_templates.py:641 +msgid "Scroll Wheel Ratcheted" +msgstr "Тріскачка коліщатка прокрутки" + +#: lib/logitech_receiver/settings_templates.py:642 +msgid "" +"Switch the mouse wheel between speed-controlled ratcheting and always " +"freespin." +msgstr "" +"Перемикання коліщатка миші між тріскачкою з контролем швидкості та вільним " +"обертанням." + +#: lib/logitech_receiver/settings_templates.py:644 +msgid "Freespinning" +msgstr "Вільне обертання" + +#: lib/logitech_receiver/settings_templates.py:644 +msgid "Ratcheted" +msgstr "Тріскачка" + +#: lib/logitech_receiver/settings_templates.py:651 +msgid "Scroll Wheel Ratchet Speed" +msgstr "Швидкість тріскачки коліщатка прокрутки" + +#: lib/logitech_receiver/settings_templates.py:653 +msgid "" +"Use the mouse wheel speed to switch between ratcheted and freespinning.\n" +"The mouse wheel is always ratcheted at 50." +msgstr "" +"Використовуйте швидкість коліщатка миші для перемикання між тріскачкою та " +"вільним обертанням.\n" +"Коліщатко миші завжди має тріскачку на 50." + +#: lib/logitech_receiver/settings_templates.py:707 +msgid "Key/Button Actions" +msgstr "Дії клавіш/кнопок" + +#: lib/logitech_receiver/settings_templates.py:709 +msgid "Change the action for the key or button." +msgstr "Змінити дію для клавіші або кнопки." + +#: lib/logitech_receiver/settings_templates.py:711 +msgid "Overridden by diversion." +msgstr "Перевизначено перенаправленням." + +#: lib/logitech_receiver/settings_templates.py:713 +msgid "" +"Changing important actions (such as for the left mouse button) can result in " +"an unusable system." +msgstr "" +"Зміна важливих дій (наприклад, для лівої кнопки миші) може призвести до " +"непрацездатності системи." + +#: lib/solaar/ui/__init__.py:120 +msgid "Another Solaar process is already running so just expose its window" +msgstr "Інший процес Solaar вже запущено, тому просто показуємо його вікно" + +#: lib/solaar/ui/about/model.py:36 +msgid "" +"Manages Logitech receivers,\n" +"keyboards, mice, and tablets." +msgstr "" +"Керує приймачами, клавіатурами,\n" +"мишами та планшетами Logitech." + +#: lib/solaar/ui/about/model.py:63 +msgid "Additional Programming" +msgstr "Додаткове програмування" + +#: lib/solaar/ui/about/model.py:64 +msgid "GUI design" +msgstr "Дизайн GUI" + +#: lib/solaar/ui/about/model.py:66 +msgid "Testing" +msgstr "Тестування" + +#: lib/solaar/ui/about/model.py:74 +msgid "Logitech documentation" +msgstr "Документація Logitech" + +#: lib/solaar/ui/action.py:88 lib/solaar/ui/action.py:92 +#: lib/solaar/ui/window.py:208 +msgid "Unpair" +msgstr "Розірвати пару" + +#: lib/solaar/ui/action.py:91 lib/solaar/ui/diversion_rules.py:141 +msgid "Cancel" +msgstr "Скасувати" + +#: lib/solaar/ui/common.py:42 +msgid "Permissions error" +msgstr "Помилка дозволів" + +#: lib/solaar/ui/common.py:44 +#, python-format +msgid "" +"Found a Logitech receiver or device (%s), but did not have permission to " +"open it." +msgstr "" +"Знайдено приймач або пристрій Logitech (%s), але немає дозволу на його " +"відкриття." + +#: lib/solaar/ui/common.py:46 +msgid "" +"If you've just installed Solaar, try disconnecting the receiver or device " +"and then reconnecting it." +msgstr "" +"Якщо ви щойно встановили Solaar, спробуйте від'єднати та знову під'єднати " +"приймач або пристрій." + +#: lib/solaar/ui/common.py:49 +msgid "Cannot connect to device error" +msgstr "Помилка підключення до пристрою" + +#: lib/solaar/ui/common.py:51 +#, python-format +msgid "" +"Found a Logitech receiver or device at %s, but encountered an error " +"connecting to it." +msgstr "" +"Знайдено приймач або пристрій Logitech за адресою %s, але виникла помилка " +"при підключенні до нього." + +#: lib/solaar/ui/common.py:53 +msgid "" +"Try disconnecting the device and then reconnecting it or turning it off and " +"then on." +msgstr "" +"Спробуйте від'єднати та знову під'єднати пристрій або вимкнути та увімкнути " +"його." + +#: lib/solaar/ui/common.py:56 +msgid "Unpairing failed" +msgstr "Не вдалося розірвати пару" + +#: lib/solaar/ui/common.py:58 +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "Не вдалося розірвати пару між %{device} та %{receiver}." + +#: lib/solaar/ui/common.py:63 +msgid "The receiver returned an error, with no further details." +msgstr "Приймач повернув помилку без додаткових деталей." + +#: lib/solaar/ui/config_panel.py:241 +msgid "Complete - ENTER to change" +msgstr "Завершено - ENTER для зміни" + +#: lib/solaar/ui/config_panel.py:241 +msgid "Incomplete" +msgstr "Не завершено" + +#: lib/solaar/ui/config_panel.py:487 lib/solaar/ui/config_panel.py:539 +#, python-format +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d значення" +msgstr[1] "%d значення" +msgstr[2] "%d значень" + +#: lib/solaar/ui/config_panel.py:621 +msgid "Changes allowed" +msgstr "Зміни дозволено" + +#: lib/solaar/ui/config_panel.py:622 +msgid "No changes allowed" +msgstr "Зміни не дозволено" + +#: lib/solaar/ui/config_panel.py:623 +msgid "Ignore this setting" +msgstr "Ігнорувати це налаштування" + +#: lib/solaar/ui/config_panel.py:667 +msgid "Working" +msgstr "Працюю" + +#: lib/solaar/ui/config_panel.py:670 +msgid "Read/write operation failed." +msgstr "Операція читання/запису не вдалася." + +#: lib/solaar/ui/desktop_notifications.py:112 +msgid "unspecified reason" +msgstr "невідома причина" + +#: lib/solaar/ui/diversion_rules.py:103 +msgid "Built-in rules" +msgstr "Вбудовані правила" + +#: lib/solaar/ui/diversion_rules.py:103 +msgid "User-defined rules" +msgstr "Правила користувача" + +#: lib/solaar/ui/diversion_rules.py:105 lib/solaar/ui/diversion_rules.py:1126 +msgid "Rule" +msgstr "Правило" + +#: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:382 +#: lib/solaar/ui/diversion_rules.py:509 +msgid "Sub-rule" +msgstr "Підправило" + +#: lib/solaar/ui/diversion_rules.py:108 +msgid "[empty]" +msgstr "[порожньо]" + +#: lib/solaar/ui/diversion_rules.py:132 +msgid "Make changes permanent?" +msgstr "Зробити зміни постійними?" + +#: lib/solaar/ui/diversion_rules.py:137 +msgid "Yes" +msgstr "Так" + +#: lib/solaar/ui/diversion_rules.py:139 +msgid "No" +msgstr "Ні" + +#: lib/solaar/ui/diversion_rules.py:144 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "Якщо ви оберете 'Ні', зміни буде втрачено при закритті Solaar." + +#: lib/solaar/ui/diversion_rules.py:273 +msgid "Paste here" +msgstr "Вставити сюди" + +#: lib/solaar/ui/diversion_rules.py:275 +msgid "Paste above" +msgstr "Вставити вище" + +#: lib/solaar/ui/diversion_rules.py:277 +msgid "Paste below" +msgstr "Вставити нижче" + +#: lib/solaar/ui/diversion_rules.py:283 +msgid "Paste rule here" +msgstr "Вставити правило сюди" + +#: lib/solaar/ui/diversion_rules.py:285 +msgid "Paste rule above" +msgstr "Вставити правило вище" + +#: lib/solaar/ui/diversion_rules.py:287 +msgid "Paste rule below" +msgstr "Вставити правило нижче" + +#: lib/solaar/ui/diversion_rules.py:291 +msgid "Paste rule" +msgstr "Вставити правило" + +#: lib/solaar/ui/diversion_rules.py:306 +msgid "Insert here" +msgstr "Вставити сюди" + +#: lib/solaar/ui/diversion_rules.py:308 +msgid "Insert above" +msgstr "Вставити вище" + +#: lib/solaar/ui/diversion_rules.py:310 +msgid "Insert below" +msgstr "Вставити нижче" + +#: lib/solaar/ui/diversion_rules.py:316 +msgid "Insert new rule here" +msgstr "Вставити нове правило сюди" + +#: lib/solaar/ui/diversion_rules.py:318 +msgid "Insert new rule above" +msgstr "Вставити нове правило вище" + +#: lib/solaar/ui/diversion_rules.py:320 +msgid "Insert new rule below" +msgstr "Вставити нове правило нижче" + +#: lib/solaar/ui/diversion_rules.py:347 +msgid "Flatten" +msgstr "Сплощити" + +#: lib/solaar/ui/diversion_rules.py:380 +msgid "Insert" +msgstr "Вставити" + +#: lib/solaar/ui/diversion_rules.py:383 lib/solaar/ui/diversion_rules.py:511 +#: lib/solaar/ui/diversion_rules.py:1158 +msgid "Or" +msgstr "Або" + +#: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:510 +#: lib/solaar/ui/diversion_rules.py:1144 +msgid "And" +msgstr "І" + +#: lib/solaar/ui/diversion_rules.py:386 +msgid "Condition" +msgstr "Умова" + +#: lib/solaar/ui/diversion_rules.py:388 lib/solaar/ui/rule_conditions.py:154 +msgid "Feature" +msgstr "Функція" + +#: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:188 +msgid "Report" +msgstr "Звіт" + +#: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:69 +msgid "Process" +msgstr "Процес" + +#: lib/solaar/ui/diversion_rules.py:391 +msgid "Mouse process" +msgstr "Процес миші" + +#: lib/solaar/ui/diversion_rules.py:392 lib/solaar/ui/rule_conditions.py:225 +msgid "Modifiers" +msgstr "Модифікатори" + +#: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:277 +msgid "Key" +msgstr "Клавіша" + +#: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:318 +msgid "KeyIsDown" +msgstr "КлавішаНатиснута" + +#: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/diversion_rules.py:1432 +msgid "Active" +msgstr "Активний" + +#: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1390 +#: lib/solaar/ui/diversion_rules.py:1441 lib/solaar/ui/diversion_rules.py:1487 +msgid "Device" +msgstr "Пристрій" + +#: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1467 +msgid "Host" +msgstr "Хост" + +#: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1506 +msgid "Setting" +msgstr "Налаштування" + +#: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/rule_conditions.py:333 +#: lib/solaar/ui/rule_conditions.py:382 +msgid "Test" +msgstr "Тест" + +#: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:507 +msgid "Test bytes" +msgstr "Тестові байти" + +#: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:608 +msgid "Mouse Gesture" +msgstr "Жест миші" + +#: lib/solaar/ui/diversion_rules.py:405 +msgid "Action" +msgstr "Дія" + +#: lib/solaar/ui/diversion_rules.py:407 lib/solaar/ui/rule_actions.py:138 +msgid "Key press" +msgstr "Натискання клавіші" + +#: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:191 +msgid "Mouse scroll" +msgstr "Прокручування миші" + +#: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:254 +msgid "Mouse click" +msgstr "Клік миші" + +#: lib/solaar/ui/diversion_rules.py:410 +msgid "Set" +msgstr "Встановити" + +#: lib/solaar/ui/diversion_rules.py:411 lib/solaar/ui/rule_actions.py:326 +msgid "Execute" +msgstr "Виконати" + +#: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/diversion_rules.py:1189 +msgid "Later" +msgstr "Пізніше" + +#: lib/solaar/ui/diversion_rules.py:441 +msgid "Insert new rule" +msgstr "Вставити нове правило" + +#: lib/solaar/ui/diversion_rules.py:461 lib/solaar/ui/rule_actions.py:82 +#: lib/solaar/ui/rule_actions.py:285 lib/solaar/ui/rule_conditions.py:556 +msgid "Delete" +msgstr "Видалити" + +#: lib/solaar/ui/diversion_rules.py:483 +msgid "Negate" +msgstr "Заперечити" + +#: lib/solaar/ui/diversion_rules.py:507 +msgid "Wrap with" +msgstr "Огорнути в" + +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Cut" +msgstr "Вирізати" + +#: lib/solaar/ui/diversion_rules.py:553 +msgid "Paste" +msgstr "Вставити" + +#: lib/solaar/ui/diversion_rules.py:559 +msgid "Copy" +msgstr "Копіювати" + +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Solaar Rule Editor" +msgstr "Редактор правил Solaar" + +#: lib/solaar/ui/diversion_rules.py:668 +msgid "Save changes" +msgstr "Зберегти зміни" + +#: lib/solaar/ui/diversion_rules.py:673 +msgid "Discard changes" +msgstr "Скасувати зміни" + +#: lib/solaar/ui/diversion_rules.py:1104 +msgid "This editor does not support the selected rule component yet." +msgstr "Цей редактор ще не підтримує вибраний компонент правила." + +#: lib/solaar/ui/diversion_rules.py:1169 +msgid "" +"Number of seconds to delay.\n" +"Delay between 0 and 1 is done with higher precision." +msgstr "" +"Кількість секунд для затримки.\n" +"Затримка від 0 до 1 виконується з вищою точністю." + +#: lib/solaar/ui/diversion_rules.py:1207 +msgid "Not" +msgstr "Не" + +#: lib/solaar/ui/diversion_rules.py:1238 +msgid "Toggle" +msgstr "Перемкнути" + +#: lib/solaar/ui/diversion_rules.py:1239 +msgid "True" +msgstr "Істина" + +#: lib/solaar/ui/diversion_rules.py:1240 +msgid "False" +msgstr "Хиба" + +#: lib/solaar/ui/diversion_rules.py:1253 +msgid "Unsupported setting" +msgstr "Налаштування не підтримується" + +#: lib/solaar/ui/diversion_rules.py:1396 lib/solaar/ui/diversion_rules.py:1415 +#: lib/solaar/ui/diversion_rules.py:1493 lib/solaar/ui/diversion_rules.py:1748 +#: lib/solaar/ui/diversion_rules.py:1766 +msgid "Originating device" +msgstr "Пристрій-джерело" + +#: lib/solaar/ui/diversion_rules.py:1428 +msgid "Device is active and its settings can be changed." +msgstr "Пристрій активний, і його налаштування можна змінювати." + +#: lib/solaar/ui/diversion_rules.py:1437 +msgid "Device that originated the current notification." +msgstr "Пристрій, який надіслав поточне сповіщення." + +#: lib/solaar/ui/diversion_rules.py:1450 +msgid "Name of host computer." +msgstr "Ім'я комп'ютера-хоста." + +#: lib/solaar/ui/diversion_rules.py:1520 +msgid "Value" +msgstr "Значення" + +#: lib/solaar/ui/diversion_rules.py:1529 +msgid "Item" +msgstr "Елемент" + +#: lib/solaar/ui/diversion_rules.py:1808 +msgid "Change setting on device" +msgstr "Змінити налаштування на пристрої" + +#: lib/solaar/ui/diversion_rules.py:1824 +msgid "Setting on device" +msgstr "Налаштування на пристрої" + +#: lib/solaar/ui/pair_window.py:44 lib/solaar/ui/pair_window.py:167 +#, python-format +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s: створити пару з новим пристроєм" + +#: lib/solaar/ui/pair_window.py:46 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "Приймачі Bolt сумісні лише з пристроями Bolt." + +#: lib/solaar/ui/pair_window.py:48 +msgid "Press a pairing button or key until the pairing light flashes quickly." +msgstr "" +"Натисніть кнопку або клавішу створення пари, доки індикатор не почне швидко " +"блимати." + +#: lib/solaar/ui/pair_window.py:51 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "Приймачі Unifying сумісні лише з пристроями Unifying." + +#: lib/solaar/ui/pair_window.py:53 +msgid "Other receivers are only compatible with a few devices." +msgstr "Інші приймачі сумісні лише з кількома пристроями." + +#: lib/solaar/ui/pair_window.py:55 +msgid "For most devices, turn on the device you want to pair." +msgstr "" +"Для більшості пристроїв увімкніть пристрій, з яким хочете створити пару." + +#: lib/solaar/ui/pair_window.py:56 +msgid "If the device is already turned on, turn it off and on again." +msgstr "Якщо пристрій уже увімкнено, вимкніть його та увімкніть знову." + +#: lib/solaar/ui/pair_window.py:58 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "" +"Пристрій не повинен бути з'єднаний з іншим увімкненим приймачем поблизу." + +#: lib/solaar/ui/pair_window.py:61 +msgid "" +"For devices with multiple channels, press, hold, and release the button for " +"the channel you wish to pair\n" +"or use the channel switch button to select a channel and then press, hold, " +"and release the channel switch button." +msgstr "" +"Для пристроїв з кількома каналами натисніть, утримуйте та відпустіть кнопку " +"потрібного каналу\n" +"або скористайтеся кнопкою перемикання каналів, щоб вибрати канал, а потім " +"натисніть, утримуйте та відпустіть її." + +#: lib/solaar/ui/pair_window.py:68 +msgid "The channel indicator light should be blinking rapidly." +msgstr "Індикатор каналу повинен швидко блимати." + +#: lib/solaar/ui/pair_window.py:72 +#, python-format +msgid "" +"\n" +"\n" +"This receiver has %d pairing remaining." +msgid_plural "" +"\n" +"\n" +"This receiver has %d pairings remaining." +msgstr[0] "" +"\n" +"\n" +"Цей приймач має ще %d спробу створення пари." +msgstr[1] "" +"\n" +"\n" +"Цей приймач має ще %d спроби створення пари." +msgstr[2] "" +"\n" +"\n" +"Цей приймач має ще %d спроб створення пари." + +#: lib/solaar/ui/pair_window.py:78 +msgid "" +"\n" +"Cancelling at this point will not use up a pairing." +msgstr "" +"\n" +"Скасування на цьому етапі не використає спробу створення пари." + +#: lib/solaar/ui/pair_window.py:168 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "Введіть пароль на %(name)s." + +#: lib/solaar/ui/pair_window.py:171 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "Введіть %(passcode)s, а потім натисніть клавішу Enter." + +#: lib/solaar/ui/pair_window.py:176 +msgid "left" +msgstr "ліва" + +#: lib/solaar/ui/pair_window.py:176 +msgid "right" +msgstr "права" + +#: lib/solaar/ui/pair_window.py:178 +#, python-format +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"Натисніть %(code)s\n" +", а потім одночасно натисніть ліву та праву кнопки." + +#: lib/solaar/ui/pair_window.py:221 +msgid "The wireless link is not encrypted" +msgstr "Бездротове з'єднання не зашифроване" + +#: lib/solaar/ui/pair_window.py:226 +msgid "Found a new device:" +msgstr "Знайдено новий пристрій:" + +#: lib/solaar/ui/pair_window.py:247 +msgid "Pairing failed" +msgstr "Не вдалося створити пару" + +#: lib/solaar/ui/pair_window.py:249 +msgid "Make sure your device is within range, and has a decent battery charge." +msgstr "" +"Переконайтеся, що ваш пристрій знаходиться в зоні досяжності та має " +"достатній заряд батареї." + +#: lib/solaar/ui/pair_window.py:251 +msgid "A new device was detected, but it is not compatible with this receiver." +msgstr "Було виявлено новий пристрій, але він не сумісний з цим приймачем." + +#: lib/solaar/ui/pair_window.py:253 +msgid "More paired devices than receiver can support." +msgstr "Більше під'єднаних пристроїв, ніж може підтримувати приймач." + +#: lib/solaar/ui/pair_window.py:255 +msgid "No further details are available about the error." +msgstr "Додаткові відомості про помилку відсутні." + +#: lib/solaar/ui/rule_actions.py:54 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Імітувати натискання, утримання або відпускання комбінації клавіш.\n" +"У Wayland потрібен дозвіл на запис до /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:60 +msgid "Add key" +msgstr "Додати клавішу" + +#: lib/solaar/ui/rule_actions.py:63 +msgid "Click" +msgstr "Клік" + +#: lib/solaar/ui/rule_actions.py:66 +msgid "Depress" +msgstr "Натиснути" + +#: lib/solaar/ui/rule_actions.py:69 +msgid "Release" +msgstr "Відпустити" + +#: lib/solaar/ui/rule_actions.py:153 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Імітувати прокручування миші.\n" +"У Wayland потрібен дозвіл на запис до /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:211 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"Імітувати клік миші.\n" +"У Wayland потрібен дозвіл на запис до /dev/uinput." + +#: lib/solaar/ui/rule_actions.py:216 +msgid "Button" +msgstr "Кнопка" + +#: lib/solaar/ui/rule_actions.py:217 +msgid "Count and Action" +msgstr "Кількість та дія" + +#: lib/solaar/ui/rule_actions.py:267 +msgid "Execute a command with arguments." +msgstr "Виконати команду з аргументами." + +#: lib/solaar/ui/rule_actions.py:271 +msgid "Add argument" +msgstr "Додати аргумент" + +#: lib/solaar/ui/rule_conditions.py:52 +msgid "" +"X11 active process.\n" +"For use in X11 only." +msgstr "" +"Активний процес X11.\n" +"Використовувати лише в X11." + +#: lib/solaar/ui/rule_conditions.py:82 +msgid "" +"X11 mouse process.\n" +"For use in X11 only." +msgstr "" +"Процес миші X11.\n" +"Використовувати лише в X11." + +#: lib/solaar/ui/rule_conditions.py:99 +msgid "MouseProcess" +msgstr "ПроцесМиші" + +#: lib/solaar/ui/rule_conditions.py:123 +msgid "Feature name of notification triggering rule processing." +msgstr "Назва функції сповіщення, що запускає обробку правила." + +#: lib/solaar/ui/rule_conditions.py:169 +msgid "Report number of notification triggering rule processing." +msgstr "Номер звіту сповіщення, що запускає обробку правила." + +#: lib/solaar/ui/rule_conditions.py:201 +msgid "" +"Active keyboard modifiers.\n" +"Not always available in Wayland." +msgstr "" +"Активні модифікатори клавіатури.\n" +"Не завжди доступні у Wayland." + +#: lib/solaar/ui/rule_conditions.py:241 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"Перенаправлена клавіша або кнопка натиснута чи відпущена.\n" +"Використовуйте налаштування 'Перенаправлення клавіш/кнопок' та " +"'Перенаправити G-клавіші' для перенаправлення." + +#: lib/solaar/ui/rule_conditions.py:250 +msgid "Key down" +msgstr "Клавішу натиснуто" + +#: lib/solaar/ui/rule_conditions.py:253 +msgid "Key up" +msgstr "Клавішу відпущено" + +#: lib/solaar/ui/rule_conditions.py:293 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"Перенаправлена клавіша або кнопка зараз натиснута.\n" +"Використовуйте налаштування 'Перенаправлення клавіш/кнопок' та " +"'Перенаправити G-клавіші' для перенаправлення." + +#: lib/solaar/ui/rule_conditions.py:331 +msgid "Test condition on notification triggering rule processing." +msgstr "Перевірка умови для сповіщення, що запускає обробку правила." + +#: lib/solaar/ui/rule_conditions.py:335 +msgid "Parameter" +msgstr "Параметр" + +#: lib/solaar/ui/rule_conditions.py:408 +msgid "begin (inclusive)" +msgstr "початок (включно)" + +#: lib/solaar/ui/rule_conditions.py:409 +msgid "end (exclusive)" +msgstr "кінець (виключно)" + +#: lib/solaar/ui/rule_conditions.py:417 +msgid "range" +msgstr "діапазон" + +#: lib/solaar/ui/rule_conditions.py:420 +msgid "minimum" +msgstr "мінімум" + +#: lib/solaar/ui/rule_conditions.py:421 +msgid "maximum" +msgstr "максимум" + +#: lib/solaar/ui/rule_conditions.py:423 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "байти від %(0)d до %(1)d, в діапазоні від %(2)d до %(3)d" + +#: lib/solaar/ui/rule_conditions.py:426 lib/solaar/ui/rule_conditions.py:427 +msgid "mask" +msgstr "маска" + +#: lib/solaar/ui/rule_conditions.py:428 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "байти від %(0)d до %(1)d, маска %(2)d" + +#: lib/solaar/ui/rule_conditions.py:437 +msgid "" +"Bit or range test on bytes in notification message triggering rule " +"processing." +msgstr "" +"Бітовий або діапазонний тест байтів у повідомленні-сповіщенні, що запускає " +"обробку правила." + +#: lib/solaar/ui/rule_conditions.py:447 +msgid "type" +msgstr "тип" + +#: lib/solaar/ui/rule_conditions.py:535 +msgid "" +"Mouse gesture with optional initiating button followed by zero or more mouse " +"movements." +msgstr "" +"Жест миші з необов'язковою кнопкою-ініціатором, за якою слідують нуль або " +"більше рухів миші." + +#: lib/solaar/ui/rule_conditions.py:541 +msgid "Add movement" +msgstr "Додати рух" + +#: lib/solaar/ui/tray.py:55 +msgid "No supported device found" +msgstr "Не знайдено підтримуваних пристроїв" + +#: lib/solaar/ui/tray.py:60 lib/solaar/ui/window.py:328 +#, python-format +msgid "About %s" +msgstr "Про %s" + +#: lib/solaar/ui/tray.py:61 lib/solaar/ui/window.py:326 +#, python-format +msgid "Quit %s" +msgstr "Вийти з %s" + +#: lib/solaar/ui/tray.py:273 lib/solaar/ui/tray.py:281 +msgid "no receiver" +msgstr "немає приймача" + +#: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:299 +#: lib/solaar/ui/window.py:728 +msgid "offline" +msgstr "офлайн" + +#: lib/solaar/ui/tray.py:297 +msgid "no status" +msgstr "немає статусу" + +#: lib/solaar/ui/window.py:110 +msgid "Scanning" +msgstr "Сканування" + +#: lib/solaar/ui/window.py:141 +msgid "Battery" +msgstr "Батарея" + +#: lib/solaar/ui/window.py:144 +msgid "Wireless Link" +msgstr "Бездротове з'єднання" + +#: lib/solaar/ui/window.py:148 +msgid "Lighting" +msgstr "Підсвічування" + +#: lib/solaar/ui/window.py:182 +msgid "Show Technical Details" +msgstr "Показати технічні деталі" + +#: lib/solaar/ui/window.py:198 +msgid "Pair new device" +msgstr "Створити пару з новим пристроєм" + +#: lib/solaar/ui/window.py:216 +msgid "Select a device" +msgstr "Виберіть пристрій" + +#: lib/solaar/ui/window.py:331 +msgid "Rule Editor" +msgstr "Редактор правил" + +#: lib/solaar/ui/window.py:522 +msgid "Path" +msgstr "Шлях" + +#: lib/solaar/ui/window.py:524 +msgid "USB ID" +msgstr "USB ID" + +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 +#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 +msgid "Serial" +msgstr "Серійний номер" + +#: lib/solaar/ui/window.py:533 +msgid "Index" +msgstr "Індекс" + +#: lib/solaar/ui/window.py:535 +msgid "Wireless PID" +msgstr "Бездротовий PID" + +#: lib/solaar/ui/window.py:537 +msgid "Product ID" +msgstr "ID продукту" + +#: lib/solaar/ui/window.py:539 +msgid "Protocol" +msgstr "Протокол" + +#: lib/solaar/ui/window.py:539 +msgid "Unknown" +msgstr "Невідомо" + +#: lib/solaar/ui/window.py:541 +msgid "Polling rate" +msgstr "Частота опитування" + +#: lib/solaar/ui/window.py:548 +msgid "Unit ID" +msgstr "ID одиниці" + +#: lib/solaar/ui/window.py:559 +msgid "none" +msgstr "немає" + +#: lib/solaar/ui/window.py:560 +msgid "Notifications" +msgstr "Сповіщення" + +#: lib/solaar/ui/window.py:604 +msgid "No device paired." +msgstr "Немає під'єднаних пристроїв." + +#: lib/solaar/ui/window.py:613 +#, python-format +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "До цього приймача можна під'єднати до %(max_count)s пристрою." +msgstr[1] "До цього приймача можна під'єднати до %(max_count)s пристроїв." +msgstr[2] "До цього приймача можна під'єднати до %(max_count)s пристроїв." + +#: lib/solaar/ui/window.py:620 +msgid "Only one device can be paired to this receiver." +msgstr "До цього приймача можна під'єднати лише один пристрій." + +#: lib/solaar/ui/window.py:624 +#, python-format +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "Цей приймач має ще %d спробу створення пари." +msgstr[1] "Цей приймач має ще %d спроби створення пари." +msgstr[2] "Цей приймач має ще %d спроб створення пари." + +#: lib/solaar/ui/window.py:681 +msgid "Battery Voltage" +msgstr "Напруга батареї" + +#: lib/solaar/ui/window.py:683 +msgid "Voltage reported by battery" +msgstr "Напруга, що повідомляється батареєю" + +#: lib/solaar/ui/window.py:685 +msgid "Battery Level" +msgstr "Рівень заряду батареї" + +#: lib/solaar/ui/window.py:687 +msgid "Approximate level reported by battery" +msgstr "Приблизний рівень, що повідомляється батареєю" + +#: lib/solaar/ui/window.py:694 lib/solaar/ui/window.py:696 +msgid "next reported " +msgstr "наступний повідомлений " + +#: lib/solaar/ui/window.py:697 +msgid " and next level to be reported." +msgstr " та наступний рівень, що буде повідомлено." + +#: lib/solaar/ui/window.py:702 +msgid "last known" +msgstr "останній відомий" + +#: lib/solaar/ui/window.py:713 +msgid "encrypted" +msgstr "зашифровано" + +#: lib/solaar/ui/window.py:715 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "Бездротове з'єднання між цим пристроєм та його приймачем зашифроване." + +#: lib/solaar/ui/window.py:717 +msgid "not encrypted" +msgstr "не зашифровано" + +#: lib/solaar/ui/window.py:721 +msgid "" +"The wireless link between this device and its receiver is not encrypted.\n" +"This is a security issue for pointing devices, and a major security issue " +"for text-input devices." +msgstr "" +"Бездротове з'єднання між цим пристроєм та його приймачем не зашифроване.\n" +"Це проблема безпеки для вказівних пристроїв та серйозна проблема безпеки для " +"пристроїв введення тексту." + +#: lib/solaar/ui/window.py:737 +#, python-format +msgid "%(light_level)d lux" +msgstr "%(light_level)d люкс" diff --git a/po/zh_CN.po b/po/zh_CN.po index 66575686..209c5c10 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -6,11 +6,11 @@ # Rongrong , 2022. msgid "" msgstr "" -"Project-Id-Version: solaar 1.1.5rc1\n" +"Project-Id-Version: solaar 1.1.13\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-09-10 23:24+0800\n" -"PO-Revision-Date: 2022-09-11 00:22+0800\n" -"Last-Translator: Rongrong \n" +"POT-Creation-Date: 2024-07-01 13:49+0800\n" +"PO-Revision-Date: 2024-07-01 16:23+0800\n" +"Last-Translator: iskandarma \n" "Language-Team: none\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" @@ -19,265 +19,382 @@ msgstr "" "Plural-Forms: nplurals=1; plural=0;\n" "X-Language: zh_CN\n" "X-Source-Language: C\n" -"X-Generator: Poedit 3.1.1\n" +"X-Generator: Poedit 3.4.3\n" -#: lib/logitech_receiver/base_usb.py:47 +#: lib/logitech_receiver/base_usb.py:45 msgid "Bolt Receiver" msgstr "Bolt 接收器" -#: lib/logitech_receiver/base_usb.py:58 +#: lib/logitech_receiver/base_usb.py:57 msgid "Unifying Receiver" msgstr "优联接收器" -#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 -#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 -#: lib/logitech_receiver/base_usb.py:114 +#: lib/logitech_receiver/base_usb.py:68 lib/logitech_receiver/base_usb.py:80 +#: lib/logitech_receiver/base_usb.py:93 lib/logitech_receiver/base_usb.py:106 +#: lib/logitech_receiver/base_usb.py:119 msgid "Nano Receiver" msgstr "微型接收器" -#: lib/logitech_receiver/base_usb.py:123 +#: lib/logitech_receiver/base_usb.py:131 msgid "Lightspeed Receiver" msgstr "Lightspeed 接收器" -#: lib/logitech_receiver/base_usb.py:131 +#: lib/logitech_receiver/base_usb.py:141 msgid "EX100 Receiver 27 Mhz" msgstr "EX100 接收器 (27MHz)" -#: lib/logitech_receiver/i18n.py:30 +#: lib/logitech_receiver/common.py:610 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "电池: %(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:613 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "电池: %(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:921 +#: lib/logitech_receiver/settings_templates.py:283 +msgid "Disabled" +msgstr "禁用" + +#: lib/logitech_receiver/hidpp20.py:922 +msgid "Static" +msgstr "静态" + +#: lib/logitech_receiver/hidpp20.py:923 +msgid "Pulse" +msgstr "脉冲" + +#: lib/logitech_receiver/hidpp20.py:924 +msgid "Cycle" +msgstr "循环" + +#: lib/logitech_receiver/hidpp20.py:925 +msgid "Boot" +msgstr "启动" + +#: lib/logitech_receiver/hidpp20.py:926 +msgid "Demo" +msgstr "演示" + +#: lib/logitech_receiver/hidpp20.py:927 +msgid "Breathe" +msgstr "呼吸" + +#: lib/logitech_receiver/hidpp20.py:928 +msgid "Ripple" +msgstr "涟漪" + +#: lib/logitech_receiver/hidpp20.py:929 +msgid "Decomposition" +msgstr "分解" + +#: lib/logitech_receiver/hidpp20.py:930 +msgid "Signature1" +msgstr "签名1" + +#: lib/logitech_receiver/hidpp20.py:931 +msgid "Signature2" +msgstr "签名2" + +#: lib/logitech_receiver/hidpp20.py:932 +msgid "CycleS" +msgstr "饱和度循环" + +#: lib/logitech_receiver/hidpp20.py:996 +msgid "Unknown Location" +msgstr "未知位置" + +#: lib/logitech_receiver/hidpp20.py:997 +msgid "Primary" +msgstr "主要" + +#: lib/logitech_receiver/hidpp20.py:998 +msgid "Logo" +msgstr "标志" + +#: lib/logitech_receiver/hidpp20.py:999 +msgid "Left Side" +msgstr "左侧" + +#: lib/logitech_receiver/hidpp20.py:1000 +msgid "Right Side" +msgstr "右侧" + +#: lib/logitech_receiver/hidpp20.py:1001 +msgid "Combined" +msgstr "合并" + +#: lib/logitech_receiver/hidpp20.py:1002 +msgid "Primary 1" +msgstr "主要 1" + +#: lib/logitech_receiver/hidpp20.py:1003 +msgid "Primary 2" +msgstr "主要 2" + +#: lib/logitech_receiver/hidpp20.py:1004 +msgid "Primary 3" +msgstr "主要 3" + +#: lib/logitech_receiver/hidpp20.py:1005 +msgid "Primary 4" +msgstr "主要 4" + +#: lib/logitech_receiver/hidpp20.py:1006 +msgid "Primary 5" +msgstr "主要 5" + +#: lib/logitech_receiver/hidpp20.py:1007 +msgid "Primary 6" +msgstr "主要6" + +#: lib/logitech_receiver/i18n.py:28 msgid "empty" msgstr "空" -#: lib/logitech_receiver/i18n.py:31 +#: lib/logitech_receiver/i18n.py:29 msgid "critical" msgstr "极低" -#: lib/logitech_receiver/i18n.py:32 +#: lib/logitech_receiver/i18n.py:30 msgid "low" msgstr "低" -#: lib/logitech_receiver/i18n.py:33 +#: lib/logitech_receiver/i18n.py:31 msgid "average" msgstr "平均" -#: lib/logitech_receiver/i18n.py:34 +#: lib/logitech_receiver/i18n.py:32 msgid "good" msgstr "良好" -#: lib/logitech_receiver/i18n.py:35 +#: lib/logitech_receiver/i18n.py:33 msgid "full" msgstr "全满" -#: lib/logitech_receiver/i18n.py:38 +#: lib/logitech_receiver/i18n.py:35 msgid "discharging" msgstr "放电中" -#: lib/logitech_receiver/i18n.py:39 +#: lib/logitech_receiver/i18n.py:36 msgid "recharging" msgstr "充电中" -#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:718 +#: lib/logitech_receiver/i18n.py:37 msgid "charging" msgstr "正充电" -#: lib/logitech_receiver/i18n.py:41 +#: lib/logitech_receiver/i18n.py:38 msgid "not charging" msgstr "未在充电" -#: lib/logitech_receiver/i18n.py:42 +#: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "接近全满" -#: lib/logitech_receiver/i18n.py:43 +#: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "已充满" -#: lib/logitech_receiver/i18n.py:44 +#: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "慢速充电" -#: lib/logitech_receiver/i18n.py:45 +#: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "电池无效" -#: lib/logitech_receiver/i18n.py:46 +#: lib/logitech_receiver/i18n.py:43 msgid "thermal error" msgstr "温度异常" -#: lib/logitech_receiver/i18n.py:47 +#: lib/logitech_receiver/i18n.py:44 msgid "error" msgstr "异常" -#: lib/logitech_receiver/i18n.py:48 +#: lib/logitech_receiver/i18n.py:45 msgid "standard" msgstr "标准" -#: lib/logitech_receiver/i18n.py:49 +#: lib/logitech_receiver/i18n.py:46 msgid "fast" msgstr "快速" -#: lib/logitech_receiver/i18n.py:50 +#: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "慢速" -#: lib/logitech_receiver/i18n.py:53 +#: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "设备超时" -#: lib/logitech_receiver/i18n.py:54 +#: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "不支持的设备" -#: lib/logitech_receiver/i18n.py:55 +#: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "设备数量太多" -#: lib/logitech_receiver/i18n.py:56 +#: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "序列超时" -#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:577 +#: lib/logitech_receiver/i18n.py:54 msgid "Firmware" msgstr "固件" -#: lib/logitech_receiver/i18n.py:60 +#: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "启动器" -#: lib/logitech_receiver/i18n.py:61 +#: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "硬件" -#: lib/logitech_receiver/i18n.py:62 +#: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "其他" -#: lib/logitech_receiver/i18n.py:65 +#: lib/logitech_receiver/i18n.py:59 msgid "Left Button" msgstr "左键" -#: lib/logitech_receiver/i18n.py:66 +#: lib/logitech_receiver/i18n.py:60 msgid "Right Button" msgstr "右键" -#: lib/logitech_receiver/i18n.py:67 +#: lib/logitech_receiver/i18n.py:61 msgid "Middle Button" msgstr "中键" -#: lib/logitech_receiver/i18n.py:68 +#: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "返回键" -#: lib/logitech_receiver/i18n.py:69 +#: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "前进键" -#: lib/logitech_receiver/i18n.py:70 +#: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "鼠标手势按钮" -#: lib/logitech_receiver/i18n.py:71 +#: lib/logitech_receiver/i18n.py:65 msgid "Smart Shift" msgstr "SmartShift" -#: lib/logitech_receiver/i18n.py:72 +#: lib/logitech_receiver/i18n.py:66 msgid "DPI Switch" msgstr "DPI 切换" -#: lib/logitech_receiver/i18n.py:73 +#: lib/logitech_receiver/i18n.py:67 msgid "Left Tilt" msgstr "滚轮向左倾斜" -#: lib/logitech_receiver/i18n.py:74 +#: lib/logitech_receiver/i18n.py:68 msgid "Right Tilt" msgstr "滚轮向右倾斜" -#: lib/logitech_receiver/i18n.py:75 +#: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "左击" -#: lib/logitech_receiver/i18n.py:76 +#: lib/logitech_receiver/i18n.py:70 msgid "Right Click" msgstr "右击" -#: lib/logitech_receiver/i18n.py:77 +#: lib/logitech_receiver/i18n.py:71 msgid "Mouse Middle Button" msgstr "鼠标中键" -#: lib/logitech_receiver/i18n.py:78 +#: lib/logitech_receiver/i18n.py:72 msgid "Mouse Back Button" msgstr "鼠标返回键" -#: lib/logitech_receiver/i18n.py:79 +#: lib/logitech_receiver/i18n.py:73 msgid "Mouse Forward Button" msgstr "鼠标前进键" -#: lib/logitech_receiver/i18n.py:80 +#: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "手势按钮导航" -#: lib/logitech_receiver/i18n.py:81 +#: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "鼠标向左滚动键" -#: lib/logitech_receiver/i18n.py:82 +#: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "鼠标向右滚动键" -#: lib/logitech_receiver/i18n.py:85 +#: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "按下" -#: lib/logitech_receiver/i18n.py:86 +#: lib/logitech_receiver/i18n.py:79 msgid "released" msgstr "释放" -#: lib/logitech_receiver/notifications.py:74 -#: lib/logitech_receiver/notifications.py:125 +#: lib/logitech_receiver/notifications.py:64 +#: lib/logitech_receiver/notifications.py:116 msgid "pairing lock is closed" msgstr "配对锁已关闭" -#: lib/logitech_receiver/notifications.py:74 -#: lib/logitech_receiver/notifications.py:125 +#: lib/logitech_receiver/notifications.py:64 +#: lib/logitech_receiver/notifications.py:116 msgid "pairing lock is open" msgstr "配对锁已打开" -#: lib/logitech_receiver/notifications.py:91 +#: lib/logitech_receiver/notifications.py:81 msgid "discovery lock is closed" msgstr "发现锁已关闭" -#: lib/logitech_receiver/notifications.py:91 +#: lib/logitech_receiver/notifications.py:81 msgid "discovery lock is open" msgstr "发现锁已打开" -#: lib/logitech_receiver/notifications.py:223 lib/solaar/ui/notify.py:120 +#: lib/logitech_receiver/notifications.py:210 lib/solaar/listener.py:238 msgid "connected" msgstr "已连接" -#: lib/logitech_receiver/notifications.py:223 +#: lib/logitech_receiver/notifications.py:210 lib/solaar/listener.py:238 msgid "disconnected" msgstr "已断开" -#: lib/logitech_receiver/notifications.py:261 lib/solaar/ui/notify.py:118 +#: lib/logitech_receiver/notifications.py:236 msgid "unpaired" msgstr "未配对" -#: lib/logitech_receiver/notifications.py:303 +#: lib/logitech_receiver/notifications.py:283 msgid "powered on" msgstr "已上电" -#: lib/logitech_receiver/settings.py:734 +#: lib/logitech_receiver/receiver.py:371 +msgid "No paired devices." +msgstr "无已配对设备。" + +#: lib/logitech_receiver/receiver.py:373 lib/solaar/ui/window.py:604 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s 个已配对设备。" + +#: lib/logitech_receiver/settings.py:611 msgid "register" msgstr "注册" -#: lib/logitech_receiver/settings.py:748 lib/logitech_receiver/settings.py:775 +#: lib/logitech_receiver/settings.py:625 lib/logitech_receiver/settings.py:651 msgid "feature" msgstr "特性" -#: lib/logitech_receiver/settings_templates.py:139 +#: lib/logitech_receiver/settings_templates.py:122 msgid "Swap Fx function" msgstr "互换 Fx 键功能" -#: lib/logitech_receiver/settings_templates.py:140 +#: lib/logitech_receiver/settings_templates.py:125 msgid "" "When set, the F1..F12 keys will activate their special function,\n" "and you must hold the FN key to activate their standard function." @@ -285,7 +402,7 @@ msgstr "" "若开启,F1 到 F12 键会激活特殊功能,\n" "使用标准功能请同时按住 FN 键。" -#: lib/logitech_receiver/settings_templates.py:142 +#: lib/logitech_receiver/settings_templates.py:130 msgid "" "When unset, the F1..F12 keys will activate their standard function,\n" "and you must hold the FN key to activate their special function." @@ -293,29 +410,29 @@ msgstr "" "若关闭,F1 到 F12 键会激活标准功能,\n" "使用特殊功能请同时按住 FN 键。" -#: lib/logitech_receiver/settings_templates.py:149 +#: lib/logitech_receiver/settings_templates.py:138 msgid "Hand Detection" msgstr "手掌识别" -#: lib/logitech_receiver/settings_templates.py:150 +#: lib/logitech_receiver/settings_templates.py:139 msgid "Turn on illumination when the hands hover over the keyboard." msgstr "手掌置于键盘上方时开启照明。" -#: lib/logitech_receiver/settings_templates.py:157 +#: lib/logitech_receiver/settings_templates.py:146 msgid "Scroll Wheel Smooth Scrolling" msgstr "滚轮平滑滚动" -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:239 -#: lib/logitech_receiver/settings_templates.py:267 +#: lib/logitech_receiver/settings_templates.py:147 +#: lib/logitech_receiver/settings_templates.py:394 +#: lib/logitech_receiver/settings_templates.py:423 msgid "High-sensitivity mode for vertical scroll with the wheel." msgstr "滚轮高分辨率滚动模式。" -#: lib/logitech_receiver/settings_templates.py:165 +#: lib/logitech_receiver/settings_templates.py:154 msgid "Side Scrolling" msgstr "侧向滚轮" -#: lib/logitech_receiver/settings_templates.py:167 +#: lib/logitech_receiver/settings_templates.py:156 msgid "" "When disabled, pushing the wheel sideways sends custom button events\n" "instead of the standard side-scrolling events." @@ -323,230 +440,296 @@ msgstr "" "若禁用,按下滚轮侧边将发送自定义按钮事件\n" "而不是标准侧向滚动事件。" -#: lib/logitech_receiver/settings_templates.py:177 +#: lib/logitech_receiver/settings_templates.py:166 msgid "Sensitivity (DPI - older mice)" msgstr "灵敏度 (DPI - 旧款鼠标)" -#: lib/logitech_receiver/settings_templates.py:178 -#: lib/logitech_receiver/settings_templates.py:695 +#: lib/logitech_receiver/settings_templates.py:167 +#: lib/logitech_receiver/settings_templates.py:955 +#: lib/logitech_receiver/settings_templates.py:983 msgid "Mouse movement sensitivity" msgstr "鼠标移动灵敏度" -#: lib/logitech_receiver/settings_templates.py:208 -#: lib/logitech_receiver/settings_templates.py:218 -#: lib/logitech_receiver/settings_templates.py:225 -msgid "Backlight" -msgstr "背光" +#: lib/logitech_receiver/settings_templates.py:240 +msgid "Backlight Timed" +msgstr "定时背光" -#: lib/logitech_receiver/settings_templates.py:209 -#: lib/logitech_receiver/settings_templates.py:226 +#: lib/logitech_receiver/settings_templates.py:241 +#: lib/logitech_receiver/settings_templates.py:381 msgid "Set illumination time for keyboard." msgstr "为键盘设置背光时间。" -#: lib/logitech_receiver/settings_templates.py:219 -msgid "Turn illumination on or off on keyboard." -msgstr "打开或关闭键盘背光。" +#: lib/logitech_receiver/settings_templates.py:252 +msgid "Backlight" +msgstr "背光" -#: lib/logitech_receiver/settings_templates.py:237 +#: lib/logitech_receiver/settings_templates.py:253 +msgid "" +"Illumination level on keyboard. Changes made are only applied in Manual " +"mode." +msgstr "键盘亮度等级。仅在手动模式下可用" + +#: lib/logitech_receiver/settings_templates.py:285 +msgid "Automatic" +msgstr "自动" + +#: lib/logitech_receiver/settings_templates.py:287 +msgid "Manual" +msgstr "手动" + +#: lib/logitech_receiver/settings_templates.py:289 +msgid "Enabled" +msgstr "已启用" + +#: lib/logitech_receiver/settings_templates.py:295 +msgid "Backlight Level" +msgstr "背光等级" + +#: lib/logitech_receiver/settings_templates.py:296 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "手动模式下的键盘亮度等级" + +#: lib/logitech_receiver/settings_templates.py:353 +msgid "Backlight Delay Hands Out" +msgstr "手离开键盘后背光关闭的延迟" + +#: lib/logitech_receiver/settings_templates.py:354 +msgid "" +"Delay in seconds until backlight fades out with hands away from keyboard." +msgstr "手离开键盘后,背光渐灭的延迟秒数" + +#: lib/logitech_receiver/settings_templates.py:362 +msgid "Backlight Delay Hands In" +msgstr "手靠近键盘后背光亮起的延迟" + +#: lib/logitech_receiver/settings_templates.py:363 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "手靠近键盘后,背光亮起的延迟秒数" + +#: lib/logitech_receiver/settings_templates.py:371 +msgid "Backlight Delay Powered" +msgstr "外接电源时的背光延迟" + +#: lib/logitech_receiver/settings_templates.py:372 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "外接电源时背光渐灭的延迟秒数" + +#: lib/logitech_receiver/settings_templates.py:380 +msgid "Backlight (Seconds)" +msgstr "背光(秒)" + +#: lib/logitech_receiver/settings_templates.py:392 msgid "Scroll Wheel High Resolution" msgstr "滚轮高分辨率" -#: lib/logitech_receiver/settings_templates.py:240 -#: lib/logitech_receiver/settings_templates.py:268 +#: lib/logitech_receiver/settings_templates.py:396 +#: lib/logitech_receiver/settings_templates.py:425 msgid "Set to ignore if scrolling is abnormally fast or slow" -msgstr "如果滚动超乎寻常地迅速或缓慢,则设为忽略" +msgstr "如果滚动异常地快或慢,请设置为忽略" -#: lib/logitech_receiver/settings_templates.py:247 -#: lib/logitech_receiver/settings_templates.py:277 +#: lib/logitech_receiver/settings_templates.py:403 +#: lib/logitech_receiver/settings_templates.py:434 msgid "Scroll Wheel Diversion" -msgstr "滚轮改道" +msgstr "滚轮重定义" -#: lib/logitech_receiver/settings_templates.py:249 +#: lib/logitech_receiver/settings_templates.py:405 msgid "" "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " "Solaar rules but are otherwise ignored)." msgstr "" -"使滚轮发送 LOWRES_WHEEL (低精度滚轮) HID++ 通告 (这会触发 Solaar 规则,但如果" -"没有,就会被忽略)" +"使滚轮发送 LOWRES_WHEEL (低精度滚轮) HID++ 报告(如有Solaar规则,则被触发)" -#: lib/logitech_receiver/settings_templates.py:256 +#: lib/logitech_receiver/settings_templates.py:412 msgid "Scroll Wheel Direction" msgstr "滚轮方向" -#: lib/logitech_receiver/settings_templates.py:257 +#: lib/logitech_receiver/settings_templates.py:413 msgid "Invert direction for vertical scroll with wheel." msgstr "反转滚轮的垂直滚动方向。" -#: lib/logitech_receiver/settings_templates.py:265 +#: lib/logitech_receiver/settings_templates.py:421 msgid "Scroll Wheel Resolution" msgstr "滚轮分辨率" -#: lib/logitech_receiver/settings_templates.py:279 +#: lib/logitech_receiver/settings_templates.py:436 msgid "" "Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "" -"使滚轮发送 HIRES_WHEEL (高精度滚轮) HID++ 通告 (这会触发 Solaar 规则,但如果" -"没有,就会被忽略)" +"使滚轮发送 HIRES_WHEEL (高精度滚轮) HID++ 报告(如有Solaar规则,则被触发)" -#: lib/logitech_receiver/settings_templates.py:288 +#: lib/logitech_receiver/settings_templates.py:445 msgid "Sensitivity (Pointer Speed)" msgstr "灵敏度 (指针速度)" -#: lib/logitech_receiver/settings_templates.py:289 +#: lib/logitech_receiver/settings_templates.py:446 msgid "Speed multiplier for mouse (256 is normal multiplier)." msgstr "鼠标速度倍率 (256 为常规倍率)。" -#: lib/logitech_receiver/settings_templates.py:299 +#: lib/logitech_receiver/settings_templates.py:456 msgid "Thumb Wheel Diversion" -msgstr "拇指滚轮改道" +msgstr "拇指滚轮重定义" -#: lib/logitech_receiver/settings_templates.py:301 +#: lib/logitech_receiver/settings_templates.py:458 msgid "" "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "" -"使拇指滚轮发送 THUMB_WHEEL (拇指滚轮) HID++ 通告 (这会触发 Solaar 规则,但如" -"果没有,就会被忽略)" +"使拇指滚轮发送 THUMB_WHEEL (拇指滚轮) HID++ 报告(如有Solaar规则,则被触发)" -#: lib/logitech_receiver/settings_templates.py:310 +#: lib/logitech_receiver/settings_templates.py:467 msgid "Thumb Wheel Direction" msgstr "拇指滚轮方向" -#: lib/logitech_receiver/settings_templates.py:311 +#: lib/logitech_receiver/settings_templates.py:468 msgid "Invert thumb wheel scroll direction." msgstr "反转拇指滚轮的滚动方向。" -#: lib/logitech_receiver/settings_templates.py:319 +#: lib/logitech_receiver/settings_templates.py:488 msgid "Onboard Profiles" msgstr "板载预设" -#: lib/logitech_receiver/settings_templates.py:320 +#: lib/logitech_receiver/settings_templates.py:489 msgid "" -"Enable onboard profiles, which often control report rate and keyboard " -"lighting" -msgstr "启用板载预设,通常会接管回报率和键盘灯光" +"Enable an onboard profile, which controls report rate, sensitivity, and " +"button actions" +msgstr "启用板载预设,用于控制回报率、灵敏度及按键功能" -#: lib/logitech_receiver/settings_templates.py:330 -msgid "Polling Rate (ms)" -msgstr "轮询速率 (ms)" +#: lib/logitech_receiver/settings_templates.py:533 +#: lib/logitech_receiver/settings_templates.py:566 +msgid "Report Rate" +msgstr "回报率" -#: lib/logitech_receiver/settings_templates.py:332 -msgid "Frequency of device polling, in milliseconds" -msgstr "设备轮询频率,单位为毫秒" +#: lib/logitech_receiver/settings_templates.py:535 +#: lib/logitech_receiver/settings_templates.py:568 +msgid "Frequency of device movement reports" +msgstr "设备移动事件的回报频率" -#: lib/logitech_receiver/settings_templates.py:333 -#: lib/logitech_receiver/settings_templates.py:1018 -#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:535 +#: lib/logitech_receiver/settings_templates.py:568 +#: lib/logitech_receiver/settings_templates.py:983 +#: lib/logitech_receiver/settings_templates.py:1357 +#: lib/logitech_receiver/settings_templates.py:1388 msgid "May need Onboard Profiles set to Disable to be effective." msgstr "可能需要禁用板载预设才能生效。" -#: lib/logitech_receiver/settings_templates.py:363 +#: lib/logitech_receiver/settings_templates.py:596 msgid "Divert crown events" -msgstr "旋钮事件改道" +msgstr "重定义旋钮事件" -#: lib/logitech_receiver/settings_templates.py:364 +#: lib/logitech_receiver/settings_templates.py:597 msgid "" "Make crown send CROWN HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." -msgstr "" -"使旋钮发送 CROWN (旋钮) HID++ 通告 (这会触发 Solaar 规则,但如果没有,就会被" -"忽略)" +msgstr "使旋钮发送 CROWN (旋钮) HID++ 报告(如有Solaar规则,则被触发)" -#: lib/logitech_receiver/settings_templates.py:372 +#: lib/logitech_receiver/settings_templates.py:605 msgid "Crown smooth scroll" msgstr "旋钮平滑旋动" -#: lib/logitech_receiver/settings_templates.py:373 +#: lib/logitech_receiver/settings_templates.py:606 msgid "Set crown smooth scroll" msgstr "设置旋钮的平滑旋动" -#: lib/logitech_receiver/settings_templates.py:381 -msgid "Divert G Keys" -msgstr "G 键改道" +#: lib/logitech_receiver/settings_templates.py:614 +msgid "Divert G and M Keys" +msgstr "转换G键及M键" -#: lib/logitech_receiver/settings_templates.py:383 +#: lib/logitech_receiver/settings_templates.py:615 msgid "" -"Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " +"Make G and M keys send HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." -msgstr "" -"使 G 键发送 GKEY (G 键) HID++ 通告 (这会触发 Solaar 规则,但如果没有,就会被" -"忽略)" +msgstr "G键及M键将发送 HID++ 报告(如有Solaar规则,则被触发)" -#: lib/logitech_receiver/settings_templates.py:384 -msgid "May also make M keys and MR key send HID++ notifications" -msgstr "同样也使 M 键和 MR 键发送 HID++ 通告" +#: lib/logitech_receiver/settings_templates.py:629 +msgid "Scroll Wheel Ratcheted" +msgstr "滚轮棘轮" -#: lib/logitech_receiver/settings_templates.py:399 -msgid "Scroll Wheel Rachet" -msgstr "滚轮段落模式" - -#: lib/logitech_receiver/settings_templates.py:401 +#: lib/logitech_receiver/settings_templates.py:630 msgid "" -"Automatically switch the mouse wheel between ratchet and freespin mode.\n" -"The mouse wheel is always free at 0, and always ratcheted at 50" +"Switch the mouse wheel between speed-controlled ratcheting and always " +"freespin." +msgstr "在滚速响应式棘轮及平滑滚动模式间切换" + +#: lib/logitech_receiver/settings_templates.py:632 +msgid "Freespinning" +msgstr "平滑滚动" + +#: lib/logitech_receiver/settings_templates.py:632 +msgid "Ratcheted" +msgstr "棘轮式" + +#: lib/logitech_receiver/settings_templates.py:639 +msgid "Scroll Wheel Ratchet Speed" +msgstr "滚轮棘轮速度" + +#: lib/logitech_receiver/settings_templates.py:641 +msgid "" +"Use the mouse wheel speed to switch between ratcheted and freespinning.\n" +"The mouse wheel is always ratcheted at 50." msgstr "" -"自动切换滚轮的段落和自由滚动模式。\n" -"在 0 时,滚轮永远为自由滚动模式;在 50 时,滚轮永远为段落模式" +"根据鼠标滚轮速度,在棘轮模式和平滑滚动模式间切换。\n" +"50时将始终处于棘轮模式。" -#: lib/logitech_receiver/settings_templates.py:450 +#: lib/logitech_receiver/settings_templates.py:690 msgid "Key/Button Actions" -msgstr "按键/按钮操作" +msgstr "按键/按钮行为" -#: lib/logitech_receiver/settings_templates.py:452 +#: lib/logitech_receiver/settings_templates.py:692 msgid "Change the action for the key or button." -msgstr "更改按键或按钮触发的操作。" +msgstr "更改按键或按钮的行为。" -#: lib/logitech_receiver/settings_templates.py:452 +#: lib/logitech_receiver/settings_templates.py:694 msgid "Overridden by diversion." -msgstr "会被改道覆盖。" +msgstr "会被重定义配置覆盖。" -#: lib/logitech_receiver/settings_templates.py:453 +#: lib/logitech_receiver/settings_templates.py:696 msgid "" "Changing important actions (such as for the left mouse button) can result in " "an unusable system." -msgstr "更改重要的操作 (如鼠标左键的操作) 可以导致系统无法使用。" +msgstr "更改关键行为 (如鼠标左键) 可能会导致无法操作。" -#: lib/logitech_receiver/settings_templates.py:622 +#: lib/logitech_receiver/settings_templates.py:862 msgid "Key/Button Diversion" -msgstr "按键/按钮改道" +msgstr "按键/按钮重定义" -#: lib/logitech_receiver/settings_templates.py:623 +#: lib/logitech_receiver/settings_templates.py:863 msgid "" "Make the key or button send HID++ notifications (Diverted) or initiate Mouse " "Gestures or Sliding DPI" -msgstr "使按键或按钮发送 HID++ 通告 (改道) 、触发鼠标手势或滑动调节 DPI" +msgstr "使按键或按钮发送 HID++ (重定义)报告 、触发鼠标手势或滑动调节 DPI" -#: lib/logitech_receiver/settings_templates.py:626 -#: lib/logitech_receiver/settings_templates.py:627 -#: lib/logitech_receiver/settings_templates.py:628 +#: lib/logitech_receiver/settings_templates.py:866 +#: lib/logitech_receiver/settings_templates.py:867 +#: lib/logitech_receiver/settings_templates.py:868 msgid "Diverted" -msgstr "改道" +msgstr "重定义" -#: lib/logitech_receiver/settings_templates.py:626 -#: lib/logitech_receiver/settings_templates.py:627 +#: lib/logitech_receiver/settings_templates.py:866 +#: lib/logitech_receiver/settings_templates.py:867 msgid "Mouse Gestures" msgstr "鼠标手势" -#: lib/logitech_receiver/settings_templates.py:626 -#: lib/logitech_receiver/settings_templates.py:627 -#: lib/logitech_receiver/settings_templates.py:628 +#: lib/logitech_receiver/settings_templates.py:866 +#: lib/logitech_receiver/settings_templates.py:867 +#: lib/logitech_receiver/settings_templates.py:868 msgid "Regular" msgstr "常规" -#: lib/logitech_receiver/settings_templates.py:626 +#: lib/logitech_receiver/settings_templates.py:866 msgid "Sliding DPI" msgstr "滑动调节 DPI" -#: lib/logitech_receiver/settings_templates.py:694 +#: lib/logitech_receiver/settings_templates.py:954 +#: lib/logitech_receiver/settings_templates.py:982 msgid "Sensitivity (DPI)" msgstr "灵敏度 (DPI)" -#: lib/logitech_receiver/settings_templates.py:734 +#: lib/logitech_receiver/settings_templates.py:1059 msgid "Sensitivity Switching" msgstr "灵敏度切换" -#: lib/logitech_receiver/settings_templates.py:736 +#: lib/logitech_receiver/settings_templates.py:1061 msgid "" "Switch the current sensitivity and the remembered sensitivity when the key " "or button is pressed.\n" @@ -555,435 +738,424 @@ msgstr "" "切换当前灵敏度,并记忆按键或按钮按下时的灵敏度。\n" "如果没有已记忆的灵敏度,就只会记忆当前灵敏度" -#: lib/logitech_receiver/settings_templates.py:740 +#: lib/logitech_receiver/settings_templates.py:1065 msgid "Off" msgstr "关" -#: lib/logitech_receiver/settings_templates.py:771 +#: lib/logitech_receiver/settings_templates.py:1096 msgid "Disable keys" msgstr "禁用按键" -#: lib/logitech_receiver/settings_templates.py:772 +#: lib/logitech_receiver/settings_templates.py:1097 msgid "Disable specific keyboard keys." msgstr "禁用特定的键盘按键。" -#: lib/logitech_receiver/settings_templates.py:775 +#: lib/logitech_receiver/settings_templates.py:1100 #, python-format msgid "Disables the %s key." msgstr "禁用 %s 按键。" -#: lib/logitech_receiver/settings_templates.py:788 -#: lib/logitech_receiver/settings_templates.py:836 +#: lib/logitech_receiver/settings_templates.py:1113 +#: lib/logitech_receiver/settings_templates.py:1170 msgid "Set OS" msgstr "设置操作系统" -#: lib/logitech_receiver/settings_templates.py:789 -#: lib/logitech_receiver/settings_templates.py:837 +#: lib/logitech_receiver/settings_templates.py:1114 +#: lib/logitech_receiver/settings_templates.py:1171 msgid "Change keys to match OS." msgstr "改变键位以匹配操作系统。" -#: lib/logitech_receiver/settings_templates.py:849 +#: lib/logitech_receiver/settings_templates.py:1183 msgid "Change Host" -msgstr "改变主机" +msgstr "切换主机" -#: lib/logitech_receiver/settings_templates.py:850 +#: lib/logitech_receiver/settings_templates.py:1184 msgid "Switch connection to a different host" -msgstr "切换链接到不同的主机" +msgstr "切换连接的主机" -#: lib/logitech_receiver/settings_templates.py:875 +#: lib/logitech_receiver/settings_templates.py:1208 msgid "Performs a left click." msgstr "单击鼠标左键。" -#: lib/logitech_receiver/settings_templates.py:875 +#: lib/logitech_receiver/settings_templates.py:1208 msgid "Single tap" msgstr "单指点击" -#: lib/logitech_receiver/settings_templates.py:876 +#: lib/logitech_receiver/settings_templates.py:1209 msgid "Performs a right click." msgstr "单击鼠标右键。" -#: lib/logitech_receiver/settings_templates.py:876 +#: lib/logitech_receiver/settings_templates.py:1209 msgid "Single tap with two fingers" msgstr "双指点击" -#: lib/logitech_receiver/settings_templates.py:877 +#: lib/logitech_receiver/settings_templates.py:1210 msgid "Single tap with three fingers" msgstr "三指点击" -#: lib/logitech_receiver/settings_templates.py:881 +#: lib/logitech_receiver/settings_templates.py:1214 msgid "Double tap" msgstr "单指双击" -#: lib/logitech_receiver/settings_templates.py:881 +#: lib/logitech_receiver/settings_templates.py:1214 msgid "Performs a double click." msgstr "双击。" -#: lib/logitech_receiver/settings_templates.py:882 +#: lib/logitech_receiver/settings_templates.py:1215 msgid "Double tap with two fingers" msgstr "双指双击" -#: lib/logitech_receiver/settings_templates.py:883 +#: lib/logitech_receiver/settings_templates.py:1216 msgid "Double tap with three fingers" msgstr "三指双击" -#: lib/logitech_receiver/settings_templates.py:886 +#: lib/logitech_receiver/settings_templates.py:1219 msgid "Drags items by dragging the finger after double tapping." msgstr "单指双击后移动手指来拖动项目。" -#: lib/logitech_receiver/settings_templates.py:886 +#: lib/logitech_receiver/settings_templates.py:1219 msgid "Tap and drag" msgstr "触摸并拖动" -#: lib/logitech_receiver/settings_templates.py:888 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "多指双击后移动手指来拖动项目。" - -#: lib/logitech_receiver/settings_templates.py:888 +#: lib/logitech_receiver/settings_templates.py:1221 msgid "Tap and drag with two fingers" msgstr "双指触摸并拖动" -#: lib/logitech_receiver/settings_templates.py:889 +#: lib/logitech_receiver/settings_templates.py:1222 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "多指双击后移动手指来拖动项目。" + +#: lib/logitech_receiver/settings_templates.py:1224 msgid "Tap and drag with three fingers" msgstr "三指触摸并拖动" -#: lib/logitech_receiver/settings_templates.py:892 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "禁用触摸和边缘手势 (等价于按下 Fn+左键单击)。" - -#: lib/logitech_receiver/settings_templates.py:892 +#: lib/logitech_receiver/settings_templates.py:1227 msgid "Suppress tap and edge gestures" msgstr "禁用触摸和边缘手势" -#: lib/logitech_receiver/settings_templates.py:893 +#: lib/logitech_receiver/settings_templates.py:1228 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "禁用触摸和边缘手势 (等价于按下 Fn+左键单击)。" + +#: lib/logitech_receiver/settings_templates.py:1230 msgid "Scroll with one finger" msgstr "单指滚动" -#: lib/logitech_receiver/settings_templates.py:893 -#: lib/logitech_receiver/settings_templates.py:894 -#: lib/logitech_receiver/settings_templates.py:897 +#: lib/logitech_receiver/settings_templates.py:1230 +#: lib/logitech_receiver/settings_templates.py:1231 +#: lib/logitech_receiver/settings_templates.py:1234 msgid "Scrolls." msgstr "滚动。" -#: lib/logitech_receiver/settings_templates.py:894 -#: lib/logitech_receiver/settings_templates.py:897 +#: lib/logitech_receiver/settings_templates.py:1231 +#: lib/logitech_receiver/settings_templates.py:1234 msgid "Scroll with two fingers" msgstr "双指滚动" -#: lib/logitech_receiver/settings_templates.py:895 +#: lib/logitech_receiver/settings_templates.py:1232 msgid "Scroll horizontally with two fingers" msgstr "双指水平滚动" -#: lib/logitech_receiver/settings_templates.py:895 +#: lib/logitech_receiver/settings_templates.py:1232 msgid "Scrolls horizontally." msgstr "水平滚动。" -#: lib/logitech_receiver/settings_templates.py:896 +#: lib/logitech_receiver/settings_templates.py:1233 msgid "Scroll vertically with two fingers" msgstr "双指垂直滚动" -#: lib/logitech_receiver/settings_templates.py:896 +#: lib/logitech_receiver/settings_templates.py:1233 msgid "Scrolls vertically." msgstr "垂直滚动。" -#: lib/logitech_receiver/settings_templates.py:898 +#: lib/logitech_receiver/settings_templates.py:1235 msgid "Inverts the scrolling direction." msgstr "反转滚动方向。" -#: lib/logitech_receiver/settings_templates.py:898 +#: lib/logitech_receiver/settings_templates.py:1235 msgid "Natural scrolling" msgstr "自然滚动" -#: lib/logitech_receiver/settings_templates.py:899 +#: lib/logitech_receiver/settings_templates.py:1236 msgid "Enables the thumbwheel." msgstr "启用拇指滚轮。" -#: lib/logitech_receiver/settings_templates.py:899 +#: lib/logitech_receiver/settings_templates.py:1236 msgid "Thumbwheel" msgstr "拇指滚轮" -#: lib/logitech_receiver/settings_templates.py:910 -#: lib/logitech_receiver/settings_templates.py:914 +#: lib/logitech_receiver/settings_templates.py:1247 +#: lib/logitech_receiver/settings_templates.py:1251 msgid "Swipe from the top edge" msgstr "从顶部边缘滑动" -#: lib/logitech_receiver/settings_templates.py:911 +#: lib/logitech_receiver/settings_templates.py:1248 msgid "Swipe from the left edge" msgstr "从左侧边缘滑动" -#: lib/logitech_receiver/settings_templates.py:912 +#: lib/logitech_receiver/settings_templates.py:1249 msgid "Swipe from the right edge" msgstr "从右侧边缘滑动" -#: lib/logitech_receiver/settings_templates.py:913 +#: lib/logitech_receiver/settings_templates.py:1250 msgid "Swipe from the bottom edge" msgstr "从底部边缘滑动" -#: lib/logitech_receiver/settings_templates.py:915 +#: lib/logitech_receiver/settings_templates.py:1252 msgid "Swipe two fingers from the left edge" msgstr "从左侧边缘双指滑动" -#: lib/logitech_receiver/settings_templates.py:916 +#: lib/logitech_receiver/settings_templates.py:1253 msgid "Swipe two fingers from the right edge" msgstr "从右侧边缘双指滑动" -#: lib/logitech_receiver/settings_templates.py:917 +#: lib/logitech_receiver/settings_templates.py:1254 msgid "Swipe two fingers from the bottom edge" msgstr "从底部边缘双指滑动" -#: lib/logitech_receiver/settings_templates.py:918 +#: lib/logitech_receiver/settings_templates.py:1255 msgid "Swipe two fingers from the top edge" msgstr "从顶部边缘双指滑动" -#: lib/logitech_receiver/settings_templates.py:919 -#: lib/logitech_receiver/settings_templates.py:923 +#: lib/logitech_receiver/settings_templates.py:1256 +#: lib/logitech_receiver/settings_templates.py:1260 msgid "Pinch to zoom out; spread to zoom in." msgstr "通过捏合手指缩小;伸展手指以放大。" -#: lib/logitech_receiver/settings_templates.py:919 +#: lib/logitech_receiver/settings_templates.py:1256 msgid "Zoom with two fingers." msgstr "双指缩放。" -#: lib/logitech_receiver/settings_templates.py:920 +#: lib/logitech_receiver/settings_templates.py:1257 msgid "Pinch to zoom out." msgstr "捏合手指以缩小。" -#: lib/logitech_receiver/settings_templates.py:921 +#: lib/logitech_receiver/settings_templates.py:1258 msgid "Spread to zoom in." msgstr "伸展手指以放大。" -#: lib/logitech_receiver/settings_templates.py:922 +#: lib/logitech_receiver/settings_templates.py:1259 msgid "Zoom with three fingers." msgstr "三指缩放。" -#: lib/logitech_receiver/settings_templates.py:923 +#: lib/logitech_receiver/settings_templates.py:1260 msgid "Zoom with two fingers" msgstr "双指缩放" -#: lib/logitech_receiver/settings_templates.py:941 +#: lib/logitech_receiver/settings_templates.py:1278 msgid "Pixel zone" msgstr "像素区" -#: lib/logitech_receiver/settings_templates.py:942 +#: lib/logitech_receiver/settings_templates.py:1279 msgid "Ratio zone" msgstr "比例区" -#: lib/logitech_receiver/settings_templates.py:943 +#: lib/logitech_receiver/settings_templates.py:1280 msgid "Scale factor" msgstr "缩放比例" -#: lib/logitech_receiver/settings_templates.py:943 +#: lib/logitech_receiver/settings_templates.py:1280 msgid "Sets the cursor speed." msgstr "设置指针速度。" -#: lib/logitech_receiver/settings_templates.py:947 +#: lib/logitech_receiver/settings_templates.py:1284 msgid "Left" msgstr "左侧" -#: lib/logitech_receiver/settings_templates.py:947 +#: lib/logitech_receiver/settings_templates.py:1284 msgid "Left-most coordinate." msgstr "最左坐标。" -#: lib/logitech_receiver/settings_templates.py:948 +#: lib/logitech_receiver/settings_templates.py:1285 msgid "Bottom" msgstr "底部" -#: lib/logitech_receiver/settings_templates.py:948 +#: lib/logitech_receiver/settings_templates.py:1285 msgid "Bottom coordinate." msgstr "底部坐标。" -#: lib/logitech_receiver/settings_templates.py:949 +#: lib/logitech_receiver/settings_templates.py:1286 msgid "Width" msgstr "宽度" -#: lib/logitech_receiver/settings_templates.py:949 +#: lib/logitech_receiver/settings_templates.py:1286 msgid "Width." msgstr "宽度。" -#: lib/logitech_receiver/settings_templates.py:950 +#: lib/logitech_receiver/settings_templates.py:1287 msgid "Height" msgstr "高度" -#: lib/logitech_receiver/settings_templates.py:950 +#: lib/logitech_receiver/settings_templates.py:1287 msgid "Height." msgstr "高度。" -#: lib/logitech_receiver/settings_templates.py:951 +#: lib/logitech_receiver/settings_templates.py:1288 msgid "Cursor speed." msgstr "指针速度。" -#: lib/logitech_receiver/settings_templates.py:951 +#: lib/logitech_receiver/settings_templates.py:1288 msgid "Scale" msgstr "比例" -#: lib/logitech_receiver/settings_templates.py:957 +#: lib/logitech_receiver/settings_templates.py:1294 msgid "Gestures" msgstr "手势" -#: lib/logitech_receiver/settings_templates.py:958 +#: lib/logitech_receiver/settings_templates.py:1295 msgid "Tweak the mouse/touchpad behaviour." msgstr "调整鼠标或触摸板的行为。" -#: lib/logitech_receiver/settings_templates.py:974 +#: lib/logitech_receiver/settings_templates.py:1311 msgid "Gestures Diversion" -msgstr "手势改道" +msgstr "手势重定义" -#: lib/logitech_receiver/settings_templates.py:975 +#: lib/logitech_receiver/settings_templates.py:1312 msgid "Divert mouse/touchpad gestures." -msgstr "为鼠标或触摸板手势改道。" +msgstr "重定义鼠标或触摸板手势。" -#: lib/logitech_receiver/settings_templates.py:991 +#: lib/logitech_receiver/settings_templates.py:1328 msgid "Gesture params" msgstr "手势参数" -#: lib/logitech_receiver/settings_templates.py:992 +#: lib/logitech_receiver/settings_templates.py:1329 msgid "Change numerical parameters of a mouse/touchpad." msgstr "更改鼠标或触摸板的数值参数。" -#: lib/logitech_receiver/settings_templates.py:1016 +#: lib/logitech_receiver/settings_templates.py:1353 msgid "M-Key LEDs" msgstr "M 键 LED" -#: lib/logitech_receiver/settings_templates.py:1018 +#: lib/logitech_receiver/settings_templates.py:1355 msgid "Control the M-Key LEDs." msgstr "控制 M 键的 LED。" -#: lib/logitech_receiver/settings_templates.py:1019 -#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1359 +#: lib/logitech_receiver/settings_templates.py:1390 msgid "May need G Keys diverted to be effective." -msgstr "可能需要开启 G 键改道。" +msgstr "可能需要开启 G 键重定义。" -#: lib/logitech_receiver/settings_templates.py:1025 +#: lib/logitech_receiver/settings_templates.py:1365 #, python-format msgid "Lights up the %s key." msgstr "点亮 %s 按键。" -#: lib/logitech_receiver/settings_templates.py:1044 +#: lib/logitech_receiver/settings_templates.py:1384 msgid "MR-Key LED" msgstr "MR 键 LED" -#: lib/logitech_receiver/settings_templates.py:1046 +#: lib/logitech_receiver/settings_templates.py:1386 msgid "Control the MR-Key LED." msgstr "控制 MR 键的 LED。" -#: lib/logitech_receiver/settings_templates.py:1064 +#: lib/logitech_receiver/settings_templates.py:1407 msgid "Persistent Key/Button Mapping" msgstr "持久化按键/按钮映射" -#: lib/logitech_receiver/settings_templates.py:1066 +#: lib/logitech_receiver/settings_templates.py:1409 msgid "Permanently change the mapping for the key or button." msgstr "永久更改按键或按钮的映射。" -#: lib/logitech_receiver/settings_templates.py:1067 +#: lib/logitech_receiver/settings_templates.py:1411 msgid "" "Changing important keys or buttons (such as for the left mouse button) can " "result in an unusable system." msgstr "更改重要的按键或按钮 (如鼠标左键) 可以导致系统无法使用。" -#: lib/logitech_receiver/settings_templates.py:1124 +#: lib/logitech_receiver/settings_templates.py:1468 msgid "Sidetone" msgstr "侧音 (麦克风回馈)" -#: lib/logitech_receiver/settings_templates.py:1125 +#: lib/logitech_receiver/settings_templates.py:1469 msgid "Set sidetone level." msgstr "设置侧音水平。" -#: lib/logitech_receiver/settings_templates.py:1134 +#: lib/logitech_receiver/settings_templates.py:1478 msgid "Equalizer" msgstr "均衡器" -#: lib/logitech_receiver/settings_templates.py:1135 +#: lib/logitech_receiver/settings_templates.py:1479 msgid "Set equalizer levels." msgstr "设置均衡器水平。" -#: lib/logitech_receiver/settings_templates.py:1157 +#: lib/logitech_receiver/settings_templates.py:1501 msgid "Hz" msgstr "" -#: lib/logitech_receiver/status.py:113 -msgid "No paired devices." -msgstr "无已配对设备。" +#: lib/logitech_receiver/settings_templates.py:1507 +msgid "Power Management" +msgstr "电源管理" -#: lib/logitech_receiver/status.py:114 lib/solaar/ui/window.py:620 -#, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s 个已配对设备。" +#: lib/logitech_receiver/settings_templates.py:1508 +msgid "Power off in minutes (0 for never)." +msgstr "分钟后关机(0为不关机)" -#: lib/logitech_receiver/status.py:167 -#, python-format -msgid "Battery: %(level)s" -msgstr "电池: %(level)s" +#: lib/logitech_receiver/settings_templates.py:1519 +msgid "Brightness Control" +msgstr "亮度控制" -#: lib/logitech_receiver/status.py:169 -#, python-format -msgid "Battery: %(percent)d%%" -msgstr "电池: %(percent)d%%" +#: lib/logitech_receiver/settings_templates.py:1520 +msgid "Control overall brightness" +msgstr "全局亮度控制" -#: lib/logitech_receiver/status.py:181 -#, python-format -msgid "Lighting: %(level)s lux" -msgstr "照明: %(level)s lux" +#: lib/logitech_receiver/settings_templates.py:1563 +#: lib/logitech_receiver/settings_templates.py:1617 +msgid "LED Control" +msgstr "LED控制" -#: lib/logitech_receiver/status.py:236 -#, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "电池: %(level)s (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:1564 +#: lib/logitech_receiver/settings_templates.py:1618 +msgid "Switch control of LED zones between device and Solaar" +msgstr "在设备和Solaar间切换LED区域控制" -#: lib/logitech_receiver/status.py:238 -#, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "电池: %(percent)d%% (%(status)s)" +#: lib/logitech_receiver/settings_templates.py:1579 +#: lib/logitech_receiver/settings_templates.py:1628 +msgid "LED Zone Effects" +msgstr "LED区域效果" -#: lib/solaar/ui/__init__.py:51 -msgid "Permissions error" -msgstr "权限错误" +#: lib/logitech_receiver/settings_templates.py:1580 +#: lib/logitech_receiver/settings_templates.py:1629 +msgid "LED Control needs to be set to Solaar to be effective." +msgstr "设置并启用LED控制到Solaar" -#: lib/solaar/ui/__init__.py:53 -#, python-format -msgid "Found a Logitech Receiver (%s), but did not have permission to open it." -msgstr "已发现 1 个罗技接收器 (%s),但您没有访问权限。" +#: lib/logitech_receiver/settings_templates.py:1580 +#: lib/logitech_receiver/settings_templates.py:1629 +msgid "Set effect for LED Zone" +msgstr "设置LED区域效果" -#: lib/solaar/ui/__init__.py:54 -msgid "" -"If you've just installed Solaar, try removing the receiver and plugging it " -"back in." -msgstr "若您刚刚安装 Solaar,请尝试将接收器拔下再重新插上。" +#: lib/logitech_receiver/settings_templates.py:1583 +msgid "Speed" +msgstr "速度" -#: lib/solaar/ui/__init__.py:57 -msgid "Cannot connect to device error" -msgstr "错误: 无法连接到设备" +#: lib/logitech_receiver/settings_templates.py:1584 +msgid "Period" +msgstr "周期" -#: lib/solaar/ui/__init__.py:59 -#, python-format -msgid "" -"Found a Logitech receiver or device at %s, but encountered an error " -"connecting to it." -msgstr "已在 %s 上发现 1 个罗技接收器或设备,但在连接它时遇到错误。" +#: lib/logitech_receiver/settings_templates.py:1585 +msgid "Intensity" +msgstr "强度" -#: lib/solaar/ui/__init__.py:60 -msgid "" -"Try removing the device and plugging it back in or turning it off and then " -"on." -msgstr "请尝试将设备拔下再重新插上,或关闭电源后重新打开。" +#: lib/logitech_receiver/settings_templates.py:1586 +msgid "Ramp" +msgstr "渐变" -#: lib/solaar/ui/__init__.py:63 -msgid "Unpairing failed" -msgstr "取消配对失败" +#: lib/logitech_receiver/settings_templates.py:1602 +msgid "LEDs" +msgstr "LED" -#: lib/solaar/ui/__init__.py:65 -#, python-brace-format -msgid "Failed to unpair %{device} from %{receiver}." -msgstr "取消 %{device} 到 %{receiver} 的配对失败。" +#: lib/logitech_receiver/settings_templates.py:1639 +msgid "Per-key Lighting" +msgstr "按键独立灯光" -#: lib/solaar/ui/__init__.py:66 -msgid "The receiver returned an error, with no further details." -msgstr "接收器出现错误,未提供详细信息。" +#: lib/logitech_receiver/settings_templates.py:1640 +msgid "Control per-key lighting." +msgstr "设置按键独立灯光" -#: lib/solaar/ui/__init__.py:176 +#: lib/solaar/ui/__init__.py:103 msgid "Another Solaar process is already running so just expose its window" -msgstr "另一个 Solaar 进程已经在运行,因此只会将它的窗口重新显示" +msgstr "另一个 Solaar 进程正在运行,已唤醒窗口" -#: lib/solaar/ui/about.py:36 +#: lib/solaar/ui/about.py:34 msgid "" "Manages Logitech receivers,\n" "keyboards, mice, and tablets." @@ -991,15 +1163,15 @@ msgstr "" "管理罗技接收器、\n" "键盘、鼠标和平板电脑。" -#: lib/solaar/ui/about.py:44 +#: lib/solaar/ui/about.py:43 msgid "Additional Programming" -msgstr "额外编程" +msgstr "协作研发" -#: lib/solaar/ui/about.py:45 +#: lib/solaar/ui/about.py:44 msgid "GUI design" msgstr "界面设计" -#: lib/solaar/ui/about.py:47 +#: lib/solaar/ui/about.py:46 msgid "Testing" msgstr "软件测试" @@ -1007,560 +1179,436 @@ msgstr "软件测试" msgid "Logitech documentation" msgstr "罗技文档" -#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 -#: lib/solaar/ui/window.py:202 +#: lib/solaar/ui/action.py:83 lib/solaar/ui/action.py:87 +#: lib/solaar/ui/window.py:189 msgid "Unpair" msgstr "取消配对" -#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:148 +#: lib/solaar/ui/action.py:86 lib/solaar/ui/diversion_rules.py:103 msgid "Cancel" msgstr "取消" -#: lib/solaar/ui/config_panel.py:205 +#: lib/solaar/ui/common.py:35 +msgid "Permissions error" +msgstr "权限错误" + +#: lib/solaar/ui/common.py:37 +#, python-format +msgid "" +"Found a Logitech receiver or device (%s), but did not have permission to " +"open it." +msgstr "发现罗技接收器或设备(%s),但无访问权限" + +#: lib/solaar/ui/common.py:39 +msgid "" +"If you've just installed Solaar, try disconnecting the receiver or device " +"and then reconnecting it." +msgstr "若您是刚安装Solaar,请插拔一下罗技接收器或设备" + +#: lib/solaar/ui/common.py:42 +msgid "Cannot connect to device error" +msgstr "错误: 无法连接到设备" + +#: lib/solaar/ui/common.py:44 +#, python-format +msgid "" +"Found a Logitech receiver or device at %s, but encountered an error " +"connecting to it." +msgstr "已在 %s 上发现 1 个罗技接收器或设备,但连接时发生错误。" + +#: lib/solaar/ui/common.py:46 +msgid "" +"Try disconnecting the device and then reconnecting it or turning it off and " +"then on." +msgstr "请尝试插拔设备,或关闭后重新打开设备" + +#: lib/solaar/ui/common.py:49 +msgid "Unpairing failed" +msgstr "取消配对失败" + +#: lib/solaar/ui/common.py:51 +#, python-brace-format +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "取消 %{device} 到 %{receiver} 的配对失败。" + +#: lib/solaar/ui/common.py:53 +msgid "The receiver returned an error, with no further details." +msgstr "接收器出现错误,无详细信息。" + +#: lib/solaar/ui/config_panel.py:228 msgid "Complete - ENTER to change" msgstr "完成 - 按下 ENTER 键以进行更改" -#: lib/solaar/ui/config_panel.py:205 +#: lib/solaar/ui/config_panel.py:228 msgid "Incomplete" msgstr "未完成" -#: lib/solaar/ui/config_panel.py:444 lib/solaar/ui/config_panel.py:495 +#: lib/solaar/ui/config_panel.py:470 lib/solaar/ui/config_panel.py:522 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "%d 个值" -#: lib/solaar/ui/config_panel.py:506 +#: lib/solaar/ui/config_panel.py:603 msgid "Changes allowed" msgstr "允许更改" -#: lib/solaar/ui/config_panel.py:507 +#: lib/solaar/ui/config_panel.py:604 msgid "No changes allowed" msgstr "不允许更改" -#: lib/solaar/ui/config_panel.py:508 +#: lib/solaar/ui/config_panel.py:605 msgid "Ignore this setting" msgstr "忽略此设置" -#: lib/solaar/ui/config_panel.py:553 +#: lib/solaar/ui/config_panel.py:649 msgid "Working" msgstr "工作中" -#: lib/solaar/ui/config_panel.py:556 +#: lib/solaar/ui/config_panel.py:652 msgid "Read/write operation failed." msgstr "读写操作失败。" -#: lib/solaar/ui/diversion_rules.py:64 +#: lib/solaar/ui/diversion_rules.py:65 msgid "Built-in rules" msgstr "内置规则" -#: lib/solaar/ui/diversion_rules.py:64 +#: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" -msgstr "用户定义规则" +msgstr "自定义规则" -#: lib/solaar/ui/diversion_rules.py:66 lib/solaar/ui/diversion_rules.py:1074 +#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1033 msgid "Rule" msgstr "规则" -#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:507 -#: lib/solaar/ui/diversion_rules.py:630 +#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:545 +#: lib/solaar/ui/diversion_rules.py:672 msgid "Sub-rule" msgstr "子规则" -#: lib/solaar/ui/diversion_rules.py:69 +#: lib/solaar/ui/diversion_rules.py:70 msgid "[empty]" msgstr "[空]" -#: lib/solaar/ui/diversion_rules.py:92 -msgid "Solaar Rule Editor" -msgstr "Solaar 规则编辑器" - -#: lib/solaar/ui/diversion_rules.py:139 +#: lib/solaar/ui/diversion_rules.py:94 msgid "Make changes permanent?" -msgstr "永久更改?" +msgstr "永久应用更改?" -#: lib/solaar/ui/diversion_rules.py:144 +#: lib/solaar/ui/diversion_rules.py:99 msgid "Yes" msgstr "是" -#: lib/solaar/ui/diversion_rules.py:146 +#: lib/solaar/ui/diversion_rules.py:101 msgid "No" msgstr "否" -#: lib/solaar/ui/diversion_rules.py:151 +#: lib/solaar/ui/diversion_rules.py:106 msgid "If you choose No, changes will be lost when Solaar is closed." -msgstr "如果选择否,更改会在 Solaar 关闭时重设。" +msgstr "如果选择否,改动会在 Solaar 关闭后丢失。" -#: lib/solaar/ui/diversion_rules.py:199 +#: lib/solaar/ui/diversion_rules.py:167 +msgid "Solaar Rule Editor" +msgstr "Solaar 规则编辑器" + +#: lib/solaar/ui/diversion_rules.py:260 msgid "Save changes" msgstr "保存更改" -#: lib/solaar/ui/diversion_rules.py:204 +#: lib/solaar/ui/diversion_rules.py:265 msgid "Discard changes" msgstr "放弃更改" -#: lib/solaar/ui/diversion_rules.py:370 +#: lib/solaar/ui/diversion_rules.py:404 msgid "Insert here" -msgstr "在这里插入" +msgstr "插入" -#: lib/solaar/ui/diversion_rules.py:372 +#: lib/solaar/ui/diversion_rules.py:406 msgid "Insert above" msgstr "在上方插入" -#: lib/solaar/ui/diversion_rules.py:374 +#: lib/solaar/ui/diversion_rules.py:408 msgid "Insert below" msgstr "在下方插入" -#: lib/solaar/ui/diversion_rules.py:380 +#: lib/solaar/ui/diversion_rules.py:414 msgid "Insert new rule here" -msgstr "在这里插入新规则" +msgstr "新建规则" -#: lib/solaar/ui/diversion_rules.py:382 +#: lib/solaar/ui/diversion_rules.py:416 msgid "Insert new rule above" -msgstr "在上方插入新规则" +msgstr "在上方添加规则" -#: lib/solaar/ui/diversion_rules.py:384 +#: lib/solaar/ui/diversion_rules.py:418 msgid "Insert new rule below" -msgstr "在下方插入新规则" +msgstr "在下方添加规则" -#: lib/solaar/ui/diversion_rules.py:425 +#: lib/solaar/ui/diversion_rules.py:463 msgid "Paste here" -msgstr "在这里粘贴" +msgstr "在此处粘贴" -#: lib/solaar/ui/diversion_rules.py:427 +#: lib/solaar/ui/diversion_rules.py:465 msgid "Paste above" msgstr "在上方粘贴" -#: lib/solaar/ui/diversion_rules.py:429 +#: lib/solaar/ui/diversion_rules.py:467 msgid "Paste below" msgstr "在下方粘贴" -#: lib/solaar/ui/diversion_rules.py:435 +#: lib/solaar/ui/diversion_rules.py:473 msgid "Paste rule here" -msgstr "在这里粘贴规则" +msgstr "在此处粘贴规则" -#: lib/solaar/ui/diversion_rules.py:437 +#: lib/solaar/ui/diversion_rules.py:475 msgid "Paste rule above" msgstr "在上方粘贴规则" -#: lib/solaar/ui/diversion_rules.py:439 +#: lib/solaar/ui/diversion_rules.py:477 msgid "Paste rule below" msgstr "在下方粘贴规则" -#: lib/solaar/ui/diversion_rules.py:443 +#: lib/solaar/ui/diversion_rules.py:481 msgid "Paste rule" msgstr "粘贴规则" -#: lib/solaar/ui/diversion_rules.py:472 +#: lib/solaar/ui/diversion_rules.py:510 msgid "Flatten" msgstr "展平" -#: lib/solaar/ui/diversion_rules.py:505 +#: lib/solaar/ui/diversion_rules.py:543 msgid "Insert" msgstr "插入" -#: lib/solaar/ui/diversion_rules.py:508 lib/solaar/ui/diversion_rules.py:632 -#: lib/solaar/ui/diversion_rules.py:1117 +#: lib/solaar/ui/diversion_rules.py:546 lib/solaar/ui/diversion_rules.py:674 +#: lib/solaar/ui/diversion_rules.py:1065 msgid "Or" msgstr "或" -#: lib/solaar/ui/diversion_rules.py:509 lib/solaar/ui/diversion_rules.py:631 -#: lib/solaar/ui/diversion_rules.py:1102 +#: lib/solaar/ui/diversion_rules.py:547 lib/solaar/ui/diversion_rules.py:673 +#: lib/solaar/ui/diversion_rules.py:1051 msgid "And" -msgstr "与" +msgstr "且" -#: lib/solaar/ui/diversion_rules.py:511 +#: lib/solaar/ui/diversion_rules.py:549 msgid "Condition" msgstr "条件" -#: lib/solaar/ui/diversion_rules.py:513 lib/solaar/ui/diversion_rules.py:1247 +#: lib/solaar/ui/diversion_rules.py:551 lib/solaar/ui/rule_conditions.py:146 msgid "Feature" msgstr "特性" -#: lib/solaar/ui/diversion_rules.py:514 lib/solaar/ui/diversion_rules.py:1283 +#: lib/solaar/ui/diversion_rules.py:552 lib/solaar/ui/rule_conditions.py:181 msgid "Report" msgstr "回报" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1159 +#: lib/solaar/ui/diversion_rules.py:553 lib/solaar/ui/rule_conditions.py:60 msgid "Process" msgstr "焦点所在进程" -#: lib/solaar/ui/diversion_rules.py:516 +#: lib/solaar/ui/diversion_rules.py:554 msgid "Mouse process" msgstr "光标所在进程" -#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1321 +#: lib/solaar/ui/diversion_rules.py:555 lib/solaar/ui/rule_conditions.py:218 msgid "Modifiers" msgstr "修饰键" -#: lib/solaar/ui/diversion_rules.py:518 lib/solaar/ui/diversion_rules.py:1374 +#: lib/solaar/ui/diversion_rules.py:556 lib/solaar/ui/rule_conditions.py:270 msgid "Key" msgstr "按键" -#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:2140 +#: lib/solaar/ui/diversion_rules.py:557 lib/solaar/ui/rule_conditions.py:311 +msgid "KeyIsDown" +msgstr "按键已按下" + +#: lib/solaar/ui/diversion_rules.py:558 lib/solaar/ui/diversion_rules.py:1359 msgid "Active" -msgstr "活动" +msgstr "活跃设备" -#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:2183 -msgid "Setting" -msgstr "设置" - -#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1422 -msgid "Test" -msgstr "测试" - -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:1539 -msgid "Test bytes" -msgstr "测试字节" - -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:1632 -msgid "Mouse Gesture" -msgstr "鼠标手势" - -#: lib/solaar/ui/diversion_rules.py:527 -msgid "Action" -msgstr "操作" - -#: lib/solaar/ui/diversion_rules.py:529 lib/solaar/ui/diversion_rules.py:1741 -msgid "Key press" -msgstr "按下按键" - -#: lib/solaar/ui/diversion_rules.py:530 lib/solaar/ui/diversion_rules.py:1794 -msgid "Mouse scroll" -msgstr "移动鼠标" - -#: lib/solaar/ui/diversion_rules.py:531 lib/solaar/ui/diversion_rules.py:1845 -msgid "Mouse click" -msgstr "鼠标点击" - -#: lib/solaar/ui/diversion_rules.py:532 -msgid "Set" -msgstr "设置" - -#: lib/solaar/ui/diversion_rules.py:533 lib/solaar/ui/diversion_rules.py:1916 -msgid "Execute" -msgstr "执行程序" - -#: lib/solaar/ui/diversion_rules.py:562 -msgid "Insert new rule" -msgstr "插入新规则" - -#: lib/solaar/ui/diversion_rules.py:582 lib/solaar/ui/diversion_rules.py:1582 -#: lib/solaar/ui/diversion_rules.py:1686 lib/solaar/ui/diversion_rules.py:1875 -msgid "Delete" -msgstr "删除" - -#: lib/solaar/ui/diversion_rules.py:604 -msgid "Negate" -msgstr "取反" - -#: lib/solaar/ui/diversion_rules.py:628 -msgid "Wrap with" -msgstr "以…包裹" - -#: lib/solaar/ui/diversion_rules.py:650 -msgid "Cut" -msgstr "剪切" - -#: lib/solaar/ui/diversion_rules.py:665 -msgid "Paste" -msgstr "粘贴" - -#: lib/solaar/ui/diversion_rules.py:677 -msgid "Copy" -msgstr "复制" - -#: lib/solaar/ui/diversion_rules.py:1054 -msgid "This editor does not support the selected rule component yet." -msgstr "此编辑器尚未支持所选的规则组件。" - -#: lib/solaar/ui/diversion_rules.py:1132 -msgid "Not" -msgstr "非" - -#: lib/solaar/ui/diversion_rules.py:1142 -msgid "X11 active process. For use in X11 only." -msgstr "X11 活动进程。仅在 X11 中可用。" - -#: lib/solaar/ui/diversion_rules.py:1173 -msgid "X11 mouse process. For use in X11 only." -msgstr "X11 光标所在进程。仅在 X11 中可用。" - -#: lib/solaar/ui/diversion_rules.py:1190 -msgid "MouseProcess" -msgstr "光标所在进程" - -#: lib/solaar/ui/diversion_rules.py:1215 -msgid "Feature name of notification triggering rule processing." -msgstr "处理通告触发规则时的特性名称。" - -#: lib/solaar/ui/diversion_rules.py:1263 -msgid "Report number of notification triggering rule processing." -msgstr "处理通告触发规则时的回报数字。" - -#: lib/solaar/ui/diversion_rules.py:1297 -msgid "Active keyboard modifiers. Not always available in Wayland." -msgstr "激活的键盘修饰键。在 Wayland 中不总是可用。" - -#: lib/solaar/ui/diversion_rules.py:1338 -msgid "" -"Diverted key or button depressed or released.\n" -"Use the Key/Button Diversion setting to divert keys and buttons." -msgstr "" -"已改道的按键或按钮被按住或释放。\n" -"要改道按键和按钮,请使用按键/按钮改道设置。" - -#: lib/solaar/ui/diversion_rules.py:1347 -msgid "Key down" -msgstr "按键按下" - -#: lib/solaar/ui/diversion_rules.py:1350 -msgid "Key up" -msgstr "按键弹起" - -#: lib/solaar/ui/diversion_rules.py:1388 -msgid "Test condition on notification triggering rule processing." -msgstr "处理通告触发规则时需满足的测试条件。" - -#: lib/solaar/ui/diversion_rules.py:1438 -msgid "begin (inclusive)" -msgstr "起始 (含)" - -#: lib/solaar/ui/diversion_rules.py:1439 -msgid "end (exclusive)" -msgstr "结束 (不含)" - -#: lib/solaar/ui/diversion_rules.py:1448 -msgid "range" -msgstr "范围" - -#: lib/solaar/ui/diversion_rules.py:1450 -msgid "minimum" -msgstr "最小值" - -#: lib/solaar/ui/diversion_rules.py:1451 -msgid "maximum" -msgstr "最大值" - -#: lib/solaar/ui/diversion_rules.py:1453 -#, python-format -msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" -msgstr "从%(0)d 到 %(1)d 字节,范围从 %(2)d 到 %(3)d" - -#: lib/solaar/ui/diversion_rules.py:1458 -msgid "mask" -msgstr "掩码" - -#: lib/solaar/ui/diversion_rules.py:1459 -#, python-format -msgid "bytes %(0)d to %(1)d, mask %(2)d" -msgstr "从%(0)d 到 %(1)d 字节,掩码为 %(2)d" - -#: lib/solaar/ui/diversion_rules.py:1469 -msgid "" -"Bit or range test on bytes in notification message triggering rule " -"processing." -msgstr "处理通告触发规则时需满足的比特位或范围测试条件。" - -#: lib/solaar/ui/diversion_rules.py:1479 -msgid "type" -msgstr "类型" - -#: lib/solaar/ui/diversion_rules.py:1562 -msgid "" -"Mouse gesture with optional initiating button followed by zero or more mouse " -"movements." -msgstr "带有可选触发键并跟随有零个或多个鼠标移动轨迹的鼠标手势。" - -#: lib/solaar/ui/diversion_rules.py:1567 -msgid "Add movement" -msgstr "增加移动轨迹" - -#: lib/solaar/ui/diversion_rules.py:1660 -msgid "" -"Simulate a chorded key click or depress or release.\n" -"On Wayland requires write access to /dev/uinput." -msgstr "" -"模拟同时点击、按住或释放一个按键。\n" -"在 Wayland 上需要具备对 /dev/input 的写权限。" - -#: lib/solaar/ui/diversion_rules.py:1665 -msgid "Add key" -msgstr "添加按键" - -#: lib/solaar/ui/diversion_rules.py:1668 -msgid "Click" -msgstr "点击" - -#: lib/solaar/ui/diversion_rules.py:1671 -msgid "Depress" -msgstr "按住" - -#: lib/solaar/ui/diversion_rules.py:1674 -msgid "Release" -msgstr "释放" - -#: lib/solaar/ui/diversion_rules.py:1758 -msgid "" -"Simulate a mouse scroll.\n" -"On Wayland requires write access to /dev/uinput." -msgstr "" -"模拟一次鼠标滚轮滚动。\n" -"在 Wayland 上需要具备对 /dev/input 的写权限。" - -#: lib/solaar/ui/diversion_rules.py:1814 -msgid "" -"Simulate a mouse click.\n" -"On Wayland requires write access to /dev/uinput." -msgstr "" -"模拟一次鼠标点击。\n" -"在 Wayland 上需要具备对 /dev/input 的写权限。" - -#: lib/solaar/ui/diversion_rules.py:1817 -msgid "Button" -msgstr "按钮" - -#: lib/solaar/ui/diversion_rules.py:1818 -msgid "Count" -msgstr "数量" - -#: lib/solaar/ui/diversion_rules.py:1858 -msgid "Execute a command with arguments." -msgstr "运行一条带有参数的命令。" - -#: lib/solaar/ui/diversion_rules.py:1861 -msgid "Add argument" -msgstr "添加参数" - -#: lib/solaar/ui/diversion_rules.py:1935 -msgid "Toggle" -msgstr "切换" - -#: lib/solaar/ui/diversion_rules.py:1935 -msgid "True" -msgstr "真" - -#: lib/solaar/ui/diversion_rules.py:1936 -msgid "False" -msgstr "假" - -#: lib/solaar/ui/diversion_rules.py:1950 -msgid "Unsupported setting" -msgstr "不支持的设置" - -#: lib/solaar/ui/diversion_rules.py:2096 lib/solaar/ui/diversion_rules.py:2167 +#: lib/solaar/ui/diversion_rules.py:559 lib/solaar/ui/diversion_rules.py:1316 +#: lib/solaar/ui/diversion_rules.py:1368 lib/solaar/ui/diversion_rules.py:1418 msgid "Device" msgstr "设备" -#: lib/solaar/ui/diversion_rules.py:2101 lib/solaar/ui/diversion_rules.py:2133 -#: lib/solaar/ui/diversion_rules.py:2172 lib/solaar/ui/diversion_rules.py:2414 -#: lib/solaar/ui/diversion_rules.py:2432 +#: lib/solaar/ui/diversion_rules.py:560 lib/solaar/ui/diversion_rules.py:1394 +msgid "Host" +msgstr "主机" + +#: lib/solaar/ui/diversion_rules.py:561 lib/solaar/ui/diversion_rules.py:1436 +msgid "Setting" +msgstr "设置" + +#: lib/solaar/ui/diversion_rules.py:562 lib/solaar/ui/rule_conditions.py:326 +#: lib/solaar/ui/rule_conditions.py:375 +msgid "Test" +msgstr "测试" + +#: lib/solaar/ui/diversion_rules.py:563 lib/solaar/ui/rule_conditions.py:500 +msgid "Test bytes" +msgstr "字节检测" + +#: lib/solaar/ui/diversion_rules.py:564 lib/solaar/ui/rule_conditions.py:601 +msgid "Mouse Gesture" +msgstr "鼠标手势" + +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Action" +msgstr "操作" + +#: lib/solaar/ui/diversion_rules.py:570 lib/solaar/ui/rule_actions.py:132 +msgid "Key press" +msgstr "按下按键" + +#: lib/solaar/ui/diversion_rules.py:571 lib/solaar/ui/rule_actions.py:183 +msgid "Mouse scroll" +msgstr "移动鼠标" + +#: lib/solaar/ui/diversion_rules.py:572 lib/solaar/ui/rule_actions.py:244 +msgid "Mouse click" +msgstr "鼠标点击" + +#: lib/solaar/ui/diversion_rules.py:573 +msgid "Set" +msgstr "更改设置" + +#: lib/solaar/ui/diversion_rules.py:574 lib/solaar/ui/rule_actions.py:314 +msgid "Execute" +msgstr "执行程序" + +#: lib/solaar/ui/diversion_rules.py:575 lib/solaar/ui/diversion_rules.py:1096 +msgid "Later" +msgstr "延迟" + +#: lib/solaar/ui/diversion_rules.py:604 +msgid "Insert new rule" +msgstr "插入新规则" + +#: lib/solaar/ui/diversion_rules.py:624 lib/solaar/ui/rule_actions.py:74 +#: lib/solaar/ui/rule_actions.py:273 lib/solaar/ui/rule_conditions.py:548 +msgid "Delete" +msgstr "删除" + +#: lib/solaar/ui/diversion_rules.py:646 +msgid "Negate" +msgstr "取反" + +#: lib/solaar/ui/diversion_rules.py:670 +msgid "Wrap with" +msgstr "以…包裹" + +#: lib/solaar/ui/diversion_rules.py:692 +msgid "Cut" +msgstr "剪切" + +#: lib/solaar/ui/diversion_rules.py:707 +msgid "Paste" +msgstr "粘贴" + +#: lib/solaar/ui/diversion_rules.py:713 +msgid "Copy" +msgstr "复制" + +#: lib/solaar/ui/diversion_rules.py:1014 +msgid "This editor does not support the selected rule component yet." +msgstr "此编辑器尚未支持所选的规则组件。" + +#: lib/solaar/ui/diversion_rules.py:1076 +msgid "" +"Number of seconds to delay. Delay between 0 and 1 is done with higher " +"precision." +msgstr "延迟秒数。0到1之间的延迟将通过高精度实现" + +#: lib/solaar/ui/diversion_rules.py:1114 +msgid "Not" +msgstr "非" + +#: lib/solaar/ui/diversion_rules.py:1145 +msgid "Toggle" +msgstr "切换" + +#: lib/solaar/ui/diversion_rules.py:1146 +msgid "True" +msgstr "真" + +#: lib/solaar/ui/diversion_rules.py:1147 +msgid "False" +msgstr "假" + +#: lib/solaar/ui/diversion_rules.py:1160 +msgid "Unsupported setting" +msgstr "不支持的设置" + +#: lib/solaar/ui/diversion_rules.py:1322 lib/solaar/ui/diversion_rules.py:1342 +#: lib/solaar/ui/diversion_rules.py:1424 lib/solaar/ui/diversion_rules.py:1669 +#: lib/solaar/ui/diversion_rules.py:1687 msgid "Originating device" msgstr "源设备" -#: lib/solaar/ui/diversion_rules.py:2122 +#: lib/solaar/ui/diversion_rules.py:1355 msgid "Device is active and its settings can be changed." -msgstr "设备已激活,可更改其设置。" +msgstr "设备已激活,且可更改其设置。" -#: lib/solaar/ui/diversion_rules.py:2191 +#: lib/solaar/ui/diversion_rules.py:1364 +msgid "Device that originated the current notification." +msgstr "触发当前事件的设备" + +#: lib/solaar/ui/diversion_rules.py:1377 +msgid "Name of host computer." +msgstr "主机名" + +#: lib/solaar/ui/diversion_rules.py:1445 msgid "Value" msgstr "值" -#: lib/solaar/ui/diversion_rules.py:2199 +#: lib/solaar/ui/diversion_rules.py:1454 msgid "Item" msgstr "项目" -#: lib/solaar/ui/diversion_rules.py:2474 +#: lib/solaar/ui/diversion_rules.py:1728 msgid "Change setting on device" msgstr "更改设备上保存的设置" -#: lib/solaar/ui/diversion_rules.py:2491 +#: lib/solaar/ui/diversion_rules.py:1744 msgid "Setting on device" msgstr "设备上保存的设置" -#: lib/solaar/ui/notify.py:122 lib/solaar/ui/tray.py:323 -#: lib/solaar/ui/tray.py:328 lib/solaar/ui/window.py:742 -msgid "offline" -msgstr "离线" +#: lib/solaar/ui/notify.py:115 +msgid "unspecified reason" +msgstr "未定原因" -#: lib/solaar/ui/pair_window.py:121 lib/solaar/ui/pair_window.py:255 -#: lib/solaar/ui/pair_window.py:277 +#: lib/solaar/ui/pair_window.py:37 lib/solaar/ui/pair_window.py:155 #, python-format msgid "%(receiver_name)s: pair new device" msgstr "%(receiver_name)s: 配对新设备" -#: lib/solaar/ui/pair_window.py:122 -#, python-format -msgid "Enter passcode on %(name)s." -msgstr "在 %(name)s 上输入密码。" +#: lib/solaar/ui/pair_window.py:39 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "Bolt接收器仅适用于Bolt设备" -#: lib/solaar/ui/pair_window.py:125 -#, python-format -msgid "Type %(passcode)s and then press the enter key." -msgstr "输入 %(passcode)s 后按下回车键。" - -#: lib/solaar/ui/pair_window.py:128 -msgid "left" -msgstr "左键" - -#: lib/solaar/ui/pair_window.py:128 -msgid "right" -msgstr "右键" - -#: lib/solaar/ui/pair_window.py:130 -#, python-format -msgid "" -"Press %(code)s\n" -"and then press left and right buttons simultaneously." -msgstr "" -"按下 %(code)s\n" -"后同时按下左键和右键。" - -#: lib/solaar/ui/pair_window.py:187 -msgid "Pairing failed" -msgstr "配对失败" - -#: lib/solaar/ui/pair_window.py:189 -msgid "Make sure your device is within range, and has a decent battery charge." -msgstr "请确认您的设备已在范围内,并保证其电量充足。" - -#: lib/solaar/ui/pair_window.py:191 -msgid "A new device was detected, but it is not compatible with this receiver." -msgstr "检测到 1 个新设备,但与此接收器不兼容。" - -#: lib/solaar/ui/pair_window.py:193 -msgid "More paired devices than receiver can support." -msgstr "已配对设备超出接收器所支持的数量。" - -#: lib/solaar/ui/pair_window.py:195 -msgid "No further details are available about the error." -msgstr "无此错误的详细信息。" - -#: lib/solaar/ui/pair_window.py:209 -msgid "Found a new device:" -msgstr "发现新设备:" - -#: lib/solaar/ui/pair_window.py:234 -msgid "The wireless link is not encrypted" -msgstr "无线连接未加密" - -#: lib/solaar/ui/pair_window.py:263 +#: lib/solaar/ui/pair_window.py:41 msgid "Press a pairing button or key until the pairing light flashes quickly." msgstr "按住配对按钮或按键直到配对指示灯快速闪烁。" -#: lib/solaar/ui/pair_window.py:265 -msgid "You may have to first turn the device off and on again." -msgstr "您可能需要先关闭设备的电源再重新打开。" +#: lib/solaar/ui/pair_window.py:44 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "优联接收器仅适用于优联设备" -#: lib/solaar/ui/pair_window.py:267 +#: lib/solaar/ui/pair_window.py:46 +msgid "Other receivers are only compatible with a few devices." +msgstr "其他接收器仅能用于少量设备" + +#: lib/solaar/ui/pair_window.py:48 msgid "Turn on the device you want to pair." msgstr "打开需配对设备的电源。" -#: lib/solaar/ui/pair_window.py:269 +#: lib/solaar/ui/pair_window.py:49 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "设备不可与附近已上电的接收器处于配对状态" + +#: lib/solaar/ui/pair_window.py:51 msgid "If the device is already turned on, turn it off and on again." msgstr "如果设备电源已经打开,请先关闭再重新打开。" -#: lib/solaar/ui/pair_window.py:272 +#: lib/solaar/ui/pair_window.py:55 #, python-format msgid "" "\n" @@ -1575,7 +1623,7 @@ msgstr[0] "" "\n" "此接收器还可再与 %d 个设备配对。" -#: lib/solaar/ui/pair_window.py:275 +#: lib/solaar/ui/pair_window.py:61 msgid "" "\n" "Cancelling at this point will not use up a pairing." @@ -1583,193 +1631,394 @@ msgstr "" "\n" "此时取消将不会使配对生效。" -#: lib/solaar/ui/tray.py:58 -msgid "No Logitech device found" -msgstr "未发现罗技设备" +#: lib/solaar/ui/pair_window.py:156 +#, python-format +msgid "Enter passcode on %(name)s." +msgstr "在 %(name)s 上输入密码。" -#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:324 +#: lib/solaar/ui/pair_window.py:159 +#, python-format +msgid "Type %(passcode)s and then press the enter key." +msgstr "输入 %(passcode)s 后按下回车键。" + +#: lib/solaar/ui/pair_window.py:162 +msgid "left" +msgstr "左键" + +#: lib/solaar/ui/pair_window.py:162 +msgid "right" +msgstr "右键" + +#: lib/solaar/ui/pair_window.py:164 +#, python-format +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"按下 %(code)s\n" +"后同时按下左键和右键。" + +#: lib/solaar/ui/pair_window.py:195 +msgid "The wireless link is not encrypted" +msgstr "无线连接未加密" + +#: lib/solaar/ui/pair_window.py:200 +msgid "Found a new device:" +msgstr "发现新设备:" + +#: lib/solaar/ui/pair_window.py:221 +msgid "Pairing failed" +msgstr "配对失败" + +#: lib/solaar/ui/pair_window.py:223 +msgid "Make sure your device is within range, and has a decent battery charge." +msgstr "请确认您的设备已在范围内,并保证其电量充足。" + +#: lib/solaar/ui/pair_window.py:225 +msgid "A new device was detected, but it is not compatible with this receiver." +msgstr "检测到 1 个新设备,但与此接收器不兼容。" + +#: lib/solaar/ui/pair_window.py:227 +msgid "More paired devices than receiver can support." +msgstr "已配对设备超出接收器所支持的数量。" + +#: lib/solaar/ui/pair_window.py:229 +msgid "No further details are available about the error." +msgstr "无此错误的详细信息。" + +#: lib/solaar/ui/rule_actions.py:48 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"模拟同时点击、按住或释放一个按键。\n" +"在 Wayland 上需要具备对 /dev/input 的写权限。" + +#: lib/solaar/ui/rule_actions.py:53 +msgid "Add key" +msgstr "添加按键" + +#: lib/solaar/ui/rule_actions.py:56 +msgid "Click" +msgstr "点击" + +#: lib/solaar/ui/rule_actions.py:59 +msgid "Depress" +msgstr "按住" + +#: lib/solaar/ui/rule_actions.py:62 +msgid "Release" +msgstr "释放" + +#: lib/solaar/ui/rule_actions.py:147 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"模拟一次鼠标滚轮滚动。\n" +"在 Wayland 上需要具备对 /dev/input 的写权限。" + +#: lib/solaar/ui/rule_actions.py:203 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"模拟一次鼠标点击。\n" +"在 Wayland 上需要具备对 /dev/input 的写权限。" + +#: lib/solaar/ui/rule_actions.py:206 +msgid "Button" +msgstr "按钮" + +#: lib/solaar/ui/rule_actions.py:207 +msgid "Count and Action" +msgstr "计数及行为" + +#: lib/solaar/ui/rule_actions.py:256 +msgid "Execute a command with arguments." +msgstr "运行一条带有参数的命令。" + +#: lib/solaar/ui/rule_actions.py:259 +msgid "Add argument" +msgstr "添加参数" + +#: lib/solaar/ui/rule_conditions.py:43 +msgid "X11 active process. For use in X11 only." +msgstr "X11 活动进程。仅在 X11 中可用。" + +#: lib/solaar/ui/rule_conditions.py:73 +msgid "X11 mouse process. For use in X11 only." +msgstr "X11 光标所在进程。仅在 X11 中可用。" + +#: lib/solaar/ui/rule_conditions.py:90 +msgid "MouseProcess" +msgstr "光标所在进程" + +#: lib/solaar/ui/rule_conditions.py:114 +msgid "Feature name of notification triggering rule processing." +msgstr "处理报告触发规则时的特性名称。" + +#: lib/solaar/ui/rule_conditions.py:161 +msgid "Report number of notification triggering rule processing." +msgstr "处理报告触发规则时的回报数字。" + +#: lib/solaar/ui/rule_conditions.py:194 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "激活的键盘修饰键。在 Wayland 中不总是可用。" + +#: lib/solaar/ui/rule_conditions.py:234 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"转换的键未按下或已松开。\n" +"请通过“转换按键”或“转换G键”设置来配置按键转换。" + +#: lib/solaar/ui/rule_conditions.py:243 +msgid "Key down" +msgstr "按键按下" + +#: lib/solaar/ui/rule_conditions.py:246 +msgid "Key up" +msgstr "按键弹起" + +#: lib/solaar/ui/rule_conditions.py:286 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"转换的键已按下。\n" +"请通过“转换按键”或“转换G键”设置来配置按键转换。" + +#: lib/solaar/ui/rule_conditions.py:324 +msgid "Test condition on notification triggering rule processing." +msgstr "在规则触发时测试条件。" + +#: lib/solaar/ui/rule_conditions.py:328 +msgid "Parameter" +msgstr "参数" + +#: lib/solaar/ui/rule_conditions.py:401 +msgid "begin (inclusive)" +msgstr "起始 (含)" + +#: lib/solaar/ui/rule_conditions.py:402 +msgid "end (exclusive)" +msgstr "结束 (不含)" + +#: lib/solaar/ui/rule_conditions.py:410 +msgid "range" +msgstr "范围" + +#: lib/solaar/ui/rule_conditions.py:413 +msgid "minimum" +msgstr "最小值" + +#: lib/solaar/ui/rule_conditions.py:414 +msgid "maximum" +msgstr "最大值" + +#: lib/solaar/ui/rule_conditions.py:416 +#, python-format +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "从%(0)d 到 %(1)d 字节,范围从 %(2)d 到 %(3)d" + +#: lib/solaar/ui/rule_conditions.py:419 lib/solaar/ui/rule_conditions.py:420 +msgid "mask" +msgstr "掩码" + +#: lib/solaar/ui/rule_conditions.py:421 +#, python-format +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "从%(0)d 到 %(1)d 字节,掩码为 %(2)d" + +#: lib/solaar/ui/rule_conditions.py:430 +msgid "" +"Bit or range test on bytes in notification message triggering rule " +"processing." +msgstr "规则触发时对指定的比特位进行测试或值域检测。" + +#: lib/solaar/ui/rule_conditions.py:440 +msgid "type" +msgstr "类型" + +#: lib/solaar/ui/rule_conditions.py:528 +msgid "" +"Mouse gesture with optional initiating button followed by zero or more mouse " +"movements." +msgstr "带有可选触发键并跟随有零个或多个鼠标移动轨迹的鼠标手势。" + +#: lib/solaar/ui/rule_conditions.py:533 +msgid "Add movement" +msgstr "增加移动轨迹" + +#: lib/solaar/ui/tray.py:50 +msgid "No supported device found" +msgstr "未发现支持的设备" + +#: lib/solaar/ui/tray.py:55 lib/solaar/ui/window.py:310 #, python-format msgid "About %s" msgstr "关于 %s" -#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:322 +#: lib/solaar/ui/tray.py:56 lib/solaar/ui/window.py:308 #, python-format msgid "Quit %s" msgstr "退出 %s" -#: lib/solaar/ui/tray.py:302 lib/solaar/ui/tray.py:310 +#: lib/solaar/ui/tray.py:267 lib/solaar/ui/tray.py:275 msgid "no receiver" msgstr "无接收器" -#: lib/solaar/ui/tray.py:326 +#: lib/solaar/ui/tray.py:288 lib/solaar/ui/tray.py:293 +msgid "offline" +msgstr "离线" + +#: lib/solaar/ui/tray.py:291 msgid "no status" msgstr "无状态" -#: lib/solaar/ui/window.py:101 +#: lib/solaar/ui/window.py:91 msgid "Scanning" msgstr "扫描中" -#: lib/solaar/ui/window.py:134 lib/solaar/ui/window.py:687 +#: lib/solaar/ui/window.py:122 msgid "Battery" msgstr "电池" -#: lib/solaar/ui/window.py:137 +#: lib/solaar/ui/window.py:125 msgid "Wireless Link" msgstr "无线连接" -#: lib/solaar/ui/window.py:141 +#: lib/solaar/ui/window.py:129 msgid "Lighting" msgstr "照明" -#: lib/solaar/ui/window.py:175 +#: lib/solaar/ui/window.py:163 msgid "Show Technical Details" msgstr "显示技术细节" -#: lib/solaar/ui/window.py:191 +#: lib/solaar/ui/window.py:179 msgid "Pair new device" msgstr "配对新设备" -#: lib/solaar/ui/window.py:210 +#: lib/solaar/ui/window.py:197 msgid "Select a device" msgstr "选择 1 个设备" -#: lib/solaar/ui/window.py:327 +#: lib/solaar/ui/window.py:313 msgid "Rule Editor" msgstr "规则编辑器" -#: lib/solaar/ui/window.py:538 +#: lib/solaar/ui/window.py:517 msgid "Path" msgstr "路径" -#: lib/solaar/ui/window.py:541 +#: lib/solaar/ui/window.py:520 msgid "USB ID" msgstr "" -#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 -#: lib/solaar/ui/window.py:566 lib/solaar/ui/window.py:568 +#: lib/solaar/ui/window.py:523 lib/solaar/ui/window.py:525 +#: lib/solaar/ui/window.py:540 lib/solaar/ui/window.py:542 msgid "Serial" msgstr "序列号" -#: lib/solaar/ui/window.py:550 +#: lib/solaar/ui/window.py:529 msgid "Index" msgstr "索引号" -#: lib/solaar/ui/window.py:552 +#: lib/solaar/ui/window.py:531 msgid "Wireless PID" msgstr "无线标示符" -#: lib/solaar/ui/window.py:554 +#: lib/solaar/ui/window.py:533 msgid "Product ID" msgstr "产品 ID" -#: lib/solaar/ui/window.py:556 +#: lib/solaar/ui/window.py:535 msgid "Protocol" msgstr "协议" -#: lib/solaar/ui/window.py:556 +#: lib/solaar/ui/window.py:535 msgid "Unknown" msgstr "未知" -#: lib/solaar/ui/window.py:559 -#, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "" - -#: lib/solaar/ui/window.py:559 +#: lib/solaar/ui/window.py:537 msgid "Polling rate" msgstr "轮询速率" -#: lib/solaar/ui/window.py:570 +#: lib/solaar/ui/window.py:544 msgid "Unit ID" msgstr "单元 ID" -#: lib/solaar/ui/window.py:581 -msgid "none" -msgstr "无" - -#: lib/solaar/ui/window.py:582 +#: lib/solaar/ui/window.py:558 msgid "Notifications" msgstr "提示" -#: lib/solaar/ui/window.py:619 +#: lib/solaar/ui/window.py:602 msgid "No device paired." msgstr "没有已配对设备。" -#: lib/solaar/ui/window.py:626 +#: lib/solaar/ui/window.py:611 #, python-format msgid "Up to %(max_count)s device can be paired to this receiver." msgid_plural "Up to %(max_count)s devices can be paired to this receiver." msgstr[0] "此接收器最多可与 %(max_count)s 个设备配对。" -#: lib/solaar/ui/window.py:632 -msgid "Only one device can be paired to this receiver." -msgstr "此接收器只能与一个设备配对。" - -#: lib/solaar/ui/window.py:636 +#: lib/solaar/ui/window.py:622 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." msgstr[0] "此接收器还可再与 %d 个设备配对。" -#: lib/solaar/ui/window.py:688 -msgid "unknown" -msgstr "未知的" - -#: lib/solaar/ui/window.py:689 -msgid "Battery information unknown." -msgstr "电池信息未知。" - -#: lib/solaar/ui/window.py:699 +#: lib/solaar/ui/window.py:679 msgid "Battery Voltage" msgstr "电池电压" -#: lib/solaar/ui/window.py:701 +#: lib/solaar/ui/window.py:681 msgid "Voltage reported by battery" -msgstr "电池回报的电压" +msgstr "电池报告的电压" -#: lib/solaar/ui/window.py:703 +#: lib/solaar/ui/window.py:683 msgid "Battery Level" msgstr "电量" -#: lib/solaar/ui/window.py:705 +#: lib/solaar/ui/window.py:685 msgid "Approximate level reported by battery" -msgstr "电池回报的大致电量" +msgstr "电池报告的大致电量" -#: lib/solaar/ui/window.py:712 lib/solaar/ui/window.py:714 +#: lib/solaar/ui/window.py:692 lib/solaar/ui/window.py:694 msgid "next reported " -msgstr "下次回报 " +msgstr "下次报告于 " + +#: lib/solaar/ui/window.py:695 +msgid " and next level to be reported." +msgstr " 以及下一个报告电量。" + +#: lib/solaar/ui/window.py:711 +msgid "encrypted" +msgstr "已加密" + +#: lib/solaar/ui/window.py:713 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "设备与接收器间的无线连接已加密。" #: lib/solaar/ui/window.py:715 -msgid " and next level to be reported." -msgstr " 以及下一个回报电量。" - -#: lib/solaar/ui/window.py:720 -msgid "last known" -msgstr "最后已知" - -#: lib/solaar/ui/window.py:728 msgid "not encrypted" msgstr "未加密" -#: lib/solaar/ui/window.py:732 +#: lib/solaar/ui/window.py:719 msgid "" "The wireless link between this device and its receiver is not encrypted.\n" "This is a security issue for pointing devices, and a major security issue " "for text-input devices." msgstr "" "设备和接收器间的无线连接未加密。\n" -"对于定位设备而言,这是一个安全问题;对于文本输入设备而言,这是一个严重的安全" -"问题。" +"对于指针设备,这是一个安全问题;对于文本输入设备,这是一个严重的安全问题。" -#: lib/solaar/ui/window.py:737 -msgid "encrypted" -msgstr "已加密" - -#: lib/solaar/ui/window.py:739 -msgid "The wireless link between this device and its receiver is encrypted." -msgstr "设备与接收器间的无线连接已加密。" - -#: lib/solaar/ui/window.py:752 +#: lib/solaar/ui/window.py:735 #, python-format msgid "%(light_level)d lux" msgstr "" @@ -1791,18 +2040,59 @@ msgstr "" #~ "down." #~ msgstr "通过在按住按钮的同时滑动鼠标来调节 DPI。" +#~ msgid "" +#~ "Automatically switch the mouse wheel between ratchet and freespin mode.\n" +#~ "The mouse wheel is always free at 0, and always ratcheted at 50" +#~ msgstr "" +#~ "自动切换滚轮的段落和自由滚动模式。\n" +#~ "在 0 时,滚轮永远为自由滚动模式;在 50 时,滚轮永远为段落模式" + +#~ msgid "Battery information unknown." +#~ msgstr "电池信息未知。" + +#~ msgid "Battery: %(level)s" +#~ msgstr "电池: %(level)s" + +#~ msgid "Battery: %(percent)d%%" +#~ msgstr "电池: %(percent)d%%" + +#~ msgid "Count" +#~ msgstr "数量" + #~ msgid "DPI Sliding Adjustment" #~ msgstr "DPI 滑动调节" +#~ msgid "Divert G Keys" +#~ msgstr "G 键重定义" + +#~ msgid "" +#~ "Diverted key or button depressed or released.\n" +#~ "Use the Key/Button Diversion setting to divert keys and buttons." +#~ msgstr "" +#~ "重定义的按键或按钮未按下或已释放。\n" +#~ "要重定义按键/按钮,请使用按键/按钮重定义设置。" + #~ msgid "Effectively turns off thumb scrolling in Linux." #~ msgstr "在 Linux 中有效关闭拇指滚轮滚动。" #~ msgid "Effectively turns off wheel scrolling in Linux." #~ msgstr "在 Linux 中有效关闭滚轮滚动。" +#~ msgid "" +#~ "Enable onboard profiles, which often control report rate and keyboard " +#~ "lighting" +#~ msgstr "启用板载预设,通常会接管回报率和键盘灯光" + +#~ msgid "" +#~ "Found a Logitech Receiver (%s), but did not have permission to open it." +#~ msgstr "已发现 1 个罗技接收器 (%s),但您没有访问权限。" + #~ msgid "Found a new device" #~ msgstr "发现 1 个新设备" +#~ msgid "Frequency of device polling, in milliseconds" +#~ msgstr "设备轮询频率,单位为毫秒" + #~ msgid "HID++ mode for horizontal scroll with the thumb wheel." #~ msgstr "为滚轮的水平滚动启用 HID++ 模式。" @@ -1816,12 +2106,33 @@ msgstr "" #~ "若设备的电源已经打开,\n" #~ "请先关闭再开启。" +#~ msgid "" +#~ "If you've just installed Solaar, try removing the receiver and plugging " +#~ "it back in." +#~ msgstr "若您刚刚安装 Solaar,请尝试将接收器拔下再重新插上。" + +#~ msgid "Lighting: %(level)s lux" +#~ msgstr "照明: %(level)s lux" + +#~ msgid "" +#~ "Make G keys send GKEY HID++ notifications (which trigger Solaar rules but " +#~ "are otherwise ignored)." +#~ msgstr "" +#~ "使 G 键发送 GKEY (G 键) HID++ 通告 (这会触发 Solaar 规则,但如果没有,就会" +#~ "被忽略)" + #~ msgid "" #~ "Make the key or button send HID++ notifications (which trigger Solaar " #~ "rules but are otherwise ignored)." #~ msgstr "" #~ "使按键或按钮发送 HID++ 通告 (这会触发 Solaar 规则,但如果没有,就会被忽略)" +#~ msgid "May also make M keys and MR key send HID++ notifications" +#~ msgstr "同样也使 M 键和 MR 键发送 HID++ 通告" + +#~ msgid "No Logitech device found" +#~ msgstr "未发现罗技设备" + #~ msgid "No Logitech receiver found" #~ msgstr "未发现罗技接收器" @@ -1831,9 +2142,18 @@ msgstr "" #~ msgid "Only one device can be paired to this receiver" #~ msgstr "此接收器仅支持配对 1 个设备" +#~ msgid "Only one device can be paired to this receiver." +#~ msgstr "此接收器只能与一个设备配对。" + +#~ msgid "Polling Rate (ms)" +#~ msgstr "轮询速率 (ms)" + #~ msgid "Quit" #~ msgstr "退出" +#~ msgid "Scroll Wheel Rachet" +#~ msgstr "滚轮段落模式" + #~ msgid "Send a gesture by sliding the mouse while holding the button down." #~ msgstr "通过在按住按钮的同时滑动鼠标来发送手势。" @@ -1847,7 +2167,6 @@ msgstr "" #~ msgid "Smooth Scrolling" #~ msgstr "平滑滚动" -#, python-format #~ msgid "The receiver only supports %d paired device(s)." #~ msgstr "此接收器仅支持 %d 个配对设备。" @@ -1872,19 +2191,35 @@ msgstr "" #~ "然而这对于内容输入设备(如键盘、数字键盘)却是一个重大的安全问题,\n" #~ "您输入的内容有可能被第三方偷偷监听到。" +#~ msgid "" +#~ "Try removing the device and plugging it back in or turning it off and " +#~ "then on." +#~ msgstr "请尝试将设备拔下再重新插上,或关闭电源后重新打开。" + +#~ msgid "Turn illumination on or off on keyboard." +#~ msgstr "打开或关闭键盘背光。" + #~ msgid "USB id" #~ msgstr "USB 序号" -#, python-format #~ msgid "Up to %d devices can be paired to this receiver" #~ msgstr "此接收器可配对至多 %d 个设备" +#~ msgid "You may have to first turn the device off and on again." +#~ msgstr "您可能需要先关闭设备的电源再重新打开。" + #~ msgid "closed" #~ msgstr "关闭" +#~ msgid "last known" +#~ msgstr "最后已知" + #~ msgid "lux" #~ msgstr "lux" +#~ msgid "none" +#~ msgstr "无" + #~ msgid "open" #~ msgstr "开启" @@ -1896,3 +2231,6 @@ msgstr "" #~ msgid "pairing lock is " #~ msgstr "配对锁已" + +#~ msgid "unknown" +#~ msgstr "未知的" diff --git a/po/zh_TW.po b/po/zh_TW.po index 3e78eff0..394ef393 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -3,1910 +3,2098 @@ # This file is distributed under the same license as the solaar package. # Automatically generated, 2014. # -msgid "" -msgstr "Project-Id-Version: solaar 0.9.2\n" - "Report-Msgid-Bugs-To: \n" - "POT-Creation-Date: 2023-06-22 01:47+0800\n" - "PO-Revision-Date: 2023-06-22 01:47+0800\n" - "Last-Translator: Peter Dave Hello \n" - "Language-Team: Peter Dave Hello \n" - "Language: zh_TW\n" - "MIME-Version: 1.0\n" - "Content-Type: text/plain; charset=UTF-8\n" - "Content-Transfer-Encoding: 8bit\n" - "Plural-Forms: nplurals=1; plural=0;\n" - "X-Language: zh_TW\n" - "X-Source-Language: C\n" - "X-Generator: Poedit 3.0\n" +msgid "" +msgstr "" +"Project-Id-Version: solaar 0.9.2\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-12-01 21:43+0300\n" +"PO-Revision-Date: 2025-12-07 07:17+0800\n" +"Last-Translator: Peter Dave Hello \n" +"Language-Team: Peter Dave Hello \n" +"Language: zh_TW\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Language: zh_TW\n" +"X-Source-Language: C\n" +"X-Generator: Poedit 3.0\n" -#: lib/logitech_receiver/base_usb.py:46 -msgid "Bolt Receiver" -msgstr "Bolt 接收器" +#: lib/logitech_receiver/base_usb.py:52 +msgid "Bolt Receiver" +msgstr "Bolt 接收器" -#: lib/logitech_receiver/base_usb.py:57 -msgid "Unifying Receiver" -msgstr "Unifying 接收器" +#: lib/logitech_receiver/base_usb.py:64 +msgid "Unifying Receiver" +msgstr "Unifying 接收器" -#: lib/logitech_receiver/base_usb.py:67 lib/logitech_receiver/base_usb.py:78 -#: lib/logitech_receiver/base_usb.py:90 lib/logitech_receiver/base_usb.py:102 -#: lib/logitech_receiver/base_usb.py:114 -msgid "Nano Receiver" -msgstr "Nano 接收器" +#: lib/logitech_receiver/base_usb.py:75 lib/logitech_receiver/base_usb.py:87 +#: lib/logitech_receiver/base_usb.py:100 lib/logitech_receiver/base_usb.py:113 +msgid "Nano Receiver" +msgstr "Nano 接收器" -#: lib/logitech_receiver/base_usb.py:124 -msgid "Lightspeed Receiver" -msgstr "Lightspeed 接收器" +#: lib/logitech_receiver/base_usb.py:125 +msgid "Lightspeed Receiver" +msgstr "Lightspeed 接收器" -#: lib/logitech_receiver/base_usb.py:133 -msgid "EX100 Receiver 27 Mhz" -msgstr "EX100 27 Mhz 接收器" +#: lib/logitech_receiver/base_usb.py:135 +msgid "EX100 Receiver 27 Mhz" +msgstr "EX100 27 Mhz 接收器" + +#: lib/logitech_receiver/common.py:649 +#, python-format +msgid "Battery: %(level)s (%(status)s)" +msgstr "電量:%(level)s (%(status)s)" + +#: lib/logitech_receiver/common.py:652 +#, python-format +msgid "Battery: %(percent)d%% (%(status)s)" +msgstr "電量:%(percent)d%% (%(status)s)" + +#: lib/logitech_receiver/hidpp20.py:1048 +#: lib/logitech_receiver/settings_templates.py:299 +msgid "Disabled" +msgstr "已停用" + +#: lib/logitech_receiver/hidpp20.py:1049 +msgid "Static" +msgstr "靜態" + +#: lib/logitech_receiver/hidpp20.py:1050 +msgid "Pulse" +msgstr "脈衝" + +#: lib/logitech_receiver/hidpp20.py:1051 +msgid "Cycle" +msgstr "循環" + +#: lib/logitech_receiver/hidpp20.py:1052 +msgid "Boot" +msgstr "開機" + +#: lib/logitech_receiver/hidpp20.py:1053 +msgid "Demo" +msgstr "示範" + +#: lib/logitech_receiver/hidpp20.py:1055 +msgid "Breathe" +msgstr "呼吸" + +#: lib/logitech_receiver/hidpp20.py:1058 +msgid "Ripple" +msgstr "漣漪" + +#: lib/logitech_receiver/hidpp20.py:1059 +msgid "Decomposition" +msgstr "分解" + +#: lib/logitech_receiver/hidpp20.py:1060 +msgid "Signature1" +msgstr "簽名樣式 1" + +#: lib/logitech_receiver/hidpp20.py:1061 +msgid "Signature2" +msgstr "簽名樣式 2" + +#: lib/logitech_receiver/hidpp20.py:1062 +msgid "CycleS" +msgstr "循環式" + +#: lib/logitech_receiver/hidpp20.py:1126 +msgid "Unknown Location" +msgstr "未知位置" + +#: lib/logitech_receiver/hidpp20.py:1127 +msgid "Primary" +msgstr "主要" + +#: lib/logitech_receiver/hidpp20.py:1128 +msgid "Logo" +msgstr "Logo" + +#: lib/logitech_receiver/hidpp20.py:1129 +msgid "Left Side" +msgstr "左側" + +#: lib/logitech_receiver/hidpp20.py:1130 +msgid "Right Side" +msgstr "右側" + +#: lib/logitech_receiver/hidpp20.py:1131 +msgid "Combined" +msgstr "結合" + +#: lib/logitech_receiver/hidpp20.py:1132 +msgid "Primary 1" +msgstr "主要 1" + +#: lib/logitech_receiver/hidpp20.py:1133 +msgid "Primary 2" +msgstr "主要 2" + +#: lib/logitech_receiver/hidpp20.py:1134 +msgid "Primary 3" +msgstr "主要 3" + +#: lib/logitech_receiver/hidpp20.py:1135 +msgid "Primary 4" +msgstr "主要 4" + +#: lib/logitech_receiver/hidpp20.py:1136 +msgid "Primary 5" +msgstr "主要 5" + +#: lib/logitech_receiver/hidpp20.py:1137 +msgid "Primary 6" +msgstr "主要 6" + +#: lib/logitech_receiver/i18n.py:28 +msgid "empty" +msgstr "電池已耗盡" + +#: lib/logitech_receiver/i18n.py:29 +msgid "critical" +msgstr "極低" #: lib/logitech_receiver/i18n.py:30 -msgid "empty" -msgstr "電池已耗盡" +msgid "low" +msgstr "低" #: lib/logitech_receiver/i18n.py:31 -msgid "critical" -msgstr "極低" +msgid "average" +msgstr "中等" #: lib/logitech_receiver/i18n.py:32 -msgid "low" -msgstr "低" +msgid "good" +msgstr "良好" #: lib/logitech_receiver/i18n.py:33 -msgid "average" -msgstr "平均" - -#: lib/logitech_receiver/i18n.py:34 -msgid "good" -msgstr "良好" +msgid "full" +msgstr "滿格" #: lib/logitech_receiver/i18n.py:35 -msgid "full" -msgstr "滿格" +msgid "discharging" +msgstr "放電中" + +#: lib/logitech_receiver/i18n.py:36 +msgid "recharging" +msgstr "充電中" + +#: lib/logitech_receiver/i18n.py:37 lib/solaar/ui/window.py:700 +msgid "charging" +msgstr "充電中" #: lib/logitech_receiver/i18n.py:38 -msgid "discharging" -msgstr "放電中" +msgid "not charging" +msgstr "未充電" #: lib/logitech_receiver/i18n.py:39 -msgid "recharging" -msgstr "充電中" +msgid "almost full" +msgstr "即將充滿" -#: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 -msgid "charging" -msgstr "充電中" +#: lib/logitech_receiver/i18n.py:40 +msgid "charged" +msgstr "已充滿" #: lib/logitech_receiver/i18n.py:41 -msgid "not charging" -msgstr "未充電" +msgid "slow recharge" +msgstr "慢速充電" #: lib/logitech_receiver/i18n.py:42 -msgid "almost full" -msgstr "即將充滿" +msgid "invalid battery" +msgstr "電池無效" #: lib/logitech_receiver/i18n.py:43 -msgid "charged" -msgstr "已充滿" +msgid "thermal error" +msgstr "溫度異常" #: lib/logitech_receiver/i18n.py:44 -msgid "slow recharge" -msgstr "慢速充電" +msgid "error" +msgstr "錯誤" #: lib/logitech_receiver/i18n.py:45 -msgid "invalid battery" -msgstr "電池無效" +msgid "standard" +msgstr "標準" #: lib/logitech_receiver/i18n.py:46 -msgid "thermal error" -msgstr "溫度異常" +msgid "fast" +msgstr "快速" #: lib/logitech_receiver/i18n.py:47 -msgid "error" -msgstr "錯誤" - -#: lib/logitech_receiver/i18n.py:48 -msgid "standard" -msgstr "標準" +msgid "slow" +msgstr "慢" #: lib/logitech_receiver/i18n.py:49 -msgid "fast" -msgstr "快速" +msgid "device timeout" +msgstr "裝置逾時" #: lib/logitech_receiver/i18n.py:50 -msgid "slow" -msgstr "慢" +msgid "device not supported" +msgstr "不支援此裝置" -#: lib/logitech_receiver/i18n.py:53 -msgid "device timeout" -msgstr "裝置逾時" +#: lib/logitech_receiver/i18n.py:51 +msgid "too many devices" +msgstr "裝置數量太多" -#: lib/logitech_receiver/i18n.py:54 -msgid "device not supported" -msgstr "不支援的裝置" +#: lib/logitech_receiver/i18n.py:52 +msgid "sequence timeout" +msgstr "序列逾時" + +#: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:555 +msgid "Firmware" +msgstr "韌體" #: lib/logitech_receiver/i18n.py:55 -msgid "too many devices" -msgstr "裝置數量太多" +msgid "Bootloader" +msgstr "開機載入程式" #: lib/logitech_receiver/i18n.py:56 -msgid "sequence timeout" -msgstr "序列超時" +msgid "Hardware" +msgstr "硬體" -#: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 -msgid "Firmware" -msgstr "韌體" +#: lib/logitech_receiver/i18n.py:57 +msgid "Other" +msgstr "其他" + +#: lib/logitech_receiver/i18n.py:59 +msgid "Left Button" +msgstr "左鍵" #: lib/logitech_receiver/i18n.py:60 -msgid "Bootloader" -msgstr "啟動器" +msgid "Right Button" +msgstr "右鍵" #: lib/logitech_receiver/i18n.py:61 -msgid "Hardware" -msgstr "硬體" +msgid "Middle Button" +msgstr "中鍵" #: lib/logitech_receiver/i18n.py:62 -msgid "Other" -msgstr "其他" +msgid "Back Button" +msgstr "返回鍵" + +#: lib/logitech_receiver/i18n.py:63 +msgid "Forward Button" +msgstr "前進鍵" + +#: lib/logitech_receiver/i18n.py:64 +msgid "Mouse Gesture Button" +msgstr "滑鼠手勢鍵" #: lib/logitech_receiver/i18n.py:65 -msgid "Left Button" -msgstr "左鍵" +msgid "Smart Shift" +msgstr "智慧切換" #: lib/logitech_receiver/i18n.py:66 -msgid "Right Button" -msgstr "右鍵" +msgid "DPI Switch" +msgstr "DPI 切換" #: lib/logitech_receiver/i18n.py:67 -msgid "Middle Button" -msgstr "中鍵" +msgid "Left Tilt" +msgstr "向左傾斜" #: lib/logitech_receiver/i18n.py:68 -msgid "Back Button" -msgstr "返回鍵" +msgid "Right Tilt" +msgstr "向右傾斜" #: lib/logitech_receiver/i18n.py:69 -msgid "Forward Button" -msgstr "前進鍵" +msgid "Left Click" +msgstr "左鍵" #: lib/logitech_receiver/i18n.py:70 -msgid "Mouse Gesture Button" -msgstr "滑鼠手勢鍵" +msgid "Right Click" +msgstr "右鍵" #: lib/logitech_receiver/i18n.py:71 -msgid "Smart Shift" -msgstr "智慧切換" +msgid "Mouse Middle Button" +msgstr "滑鼠中鍵" #: lib/logitech_receiver/i18n.py:72 -msgid "DPI Switch" -msgstr "DPI 切換" +msgid "Mouse Back Button" +msgstr "滑鼠返回鍵" #: lib/logitech_receiver/i18n.py:73 -msgid "Left Tilt" -msgstr "向左傾斜" +msgid "Mouse Forward Button" +msgstr "滑鼠前進鍵" #: lib/logitech_receiver/i18n.py:74 -msgid "Right Tilt" -msgstr "向右傾斜" +msgid "Gesture Button Navigation" +msgstr "手勢按鈕導覽" #: lib/logitech_receiver/i18n.py:75 -msgid "Left Click" -msgstr "左鍵" +msgid "Mouse Scroll Left Button" +msgstr "滑鼠滾輪向左按鈕" #: lib/logitech_receiver/i18n.py:76 -msgid "Right Click" -msgstr "右鍵" - -#: lib/logitech_receiver/i18n.py:77 -msgid "Mouse Middle Button" -msgstr "滑鼠中鍵" +msgid "Mouse Scroll Right Button" +msgstr "滑鼠滾輪向右按鈕" #: lib/logitech_receiver/i18n.py:78 -msgid "Mouse Back Button" -msgstr "滑鼠返回鍵" +msgid "pressed" +msgstr "按下" #: lib/logitech_receiver/i18n.py:79 -msgid "Mouse Forward Button" -msgstr "滑鼠前進鍵" +msgid "released" +msgstr "放開" -#: lib/logitech_receiver/i18n.py:80 -msgid "Gesture Button Navigation" -msgstr "手勢按鈕導航" +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 +msgid "connected" +msgstr "已連線" -#: lib/logitech_receiver/i18n.py:81 -msgid "Mouse Scroll Left Button" -msgstr "滑鼠滾輪向左按鈕" +#: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 +msgid "disconnected" +msgstr "已斷線" -#: lib/logitech_receiver/i18n.py:82 -msgid "Mouse Scroll Right Button" -msgstr "滑鼠滾輪向右按鈕" +#: lib/logitech_receiver/notifications.py:182 +msgid "unpaired" +msgstr "未配對" -#: lib/logitech_receiver/i18n.py:85 -msgid "pressed" -msgstr "按下" +#: lib/logitech_receiver/notifications.py:231 +msgid "powered on" +msgstr "已啟動" -#: lib/logitech_receiver/i18n.py:86 -msgid "released" -msgstr "放開" +#: lib/logitech_receiver/notifications.py:283 +msgid "ADC measurement notification" +msgstr "ADC 量測通知" -#: lib/logitech_receiver/notifications.py:75 -#: lib/logitech_receiver/notifications.py:126 -msgid "pairing lock is closed" -msgstr "配對鎖已關閉" +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:483 +msgid "pairing lock is closed" +msgstr "配對鎖已關閉" -#: lib/logitech_receiver/notifications.py:75 -#: lib/logitech_receiver/notifications.py:126 -msgid "pairing lock is open" -msgstr "配對鎖開啟" +#: lib/logitech_receiver/notifications.py:428 +#: lib/logitech_receiver/notifications.py:483 +msgid "pairing lock is open" +msgstr "配對鎖已開啟" -#: lib/logitech_receiver/notifications.py:92 -msgid "discovery lock is closed" -msgstr "發現鎖已關閉" +#: lib/logitech_receiver/notifications.py:446 +msgid "discovery lock is closed" +msgstr "探索鎖已關閉" -#: lib/logitech_receiver/notifications.py:92 -msgid "discovery lock is open" -msgstr "發現鎖已開啟" +#: lib/logitech_receiver/notifications.py:446 +msgid "discovery lock is open" +msgstr "探索鎖已開啟" -#: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 -msgid "connected" -msgstr "已連線" +#: lib/logitech_receiver/receiver.py:437 +msgid "No paired devices." +msgstr "沒有已配對的裝置。" -#: lib/logitech_receiver/notifications.py:224 -msgid "disconnected" -msgstr "斷線" +#: lib/logitech_receiver/receiver.py:439 lib/solaar/ui/window.py:606 +#, python-format +msgid "%(count)s paired device." +msgid_plural "%(count)s paired devices." +msgstr[0] "%(count)s 個已配對裝置。" -#: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 -msgid "unpaired" -msgstr "未配對" +#: lib/logitech_receiver/settings.py:602 +msgid "register" +msgstr "暫存器" -#: lib/logitech_receiver/notifications.py:304 -msgid "powered on" -msgstr "已啟動" +#: lib/logitech_receiver/settings.py:616 lib/logitech_receiver/settings.py:654 +msgid "feature" +msgstr "功能" -#: lib/logitech_receiver/settings.py:750 -msgid "register" -msgstr "註冊" +#: lib/logitech_receiver/settings_templates.py:138 +msgid "Swap Fx function" +msgstr "互換 Fx 鍵功能" -#: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 -msgid "feature" -msgstr "功能" +#: lib/logitech_receiver/settings_templates.py:141 +msgid "" +"When set, the F1..F12 keys will activate their special function,\n" +"and you must hold the FN key to activate their standard function." +msgstr "" +"啟用後,F1 到 F12 鍵將會啟動特殊功能,\n" +"若要使用標準功能請同時按住 FN 鍵。" -#: lib/logitech_receiver/settings_templates.py:139 -msgid "Swap Fx function" -msgstr "互換 Fx 鍵功能" +#: lib/logitech_receiver/settings_templates.py:146 +msgid "" +"When unset, the F1..F12 keys will activate their standard function,\n" +"and you must hold the FN key to activate their special function." +msgstr "" +"停用後,F1 到 F12 鍵將會啟動標準功能,\n" +"若要使用特殊功能請同時按住 FN 鍵。" -#: lib/logitech_receiver/settings_templates.py:140 -msgid "When set, the F1..F12 keys will activate their special function,\n" - "and you must hold the FN key to activate their standard function." -msgstr "若開啟,F1 到 F12 鍵將啟用特殊功能,\n" - "使用標準功能請同時按住 FN 鍵。" +#: lib/logitech_receiver/settings_templates.py:154 +msgid "Hand Detection" +msgstr "手掌偵測" -#: lib/logitech_receiver/settings_templates.py:142 -msgid "When unset, the F1..F12 keys will activate their standard function,\n" - "and you must hold the FN key to activate their special function." -msgstr "若關閉,F1 到 F12 鍵將啟用標準功能,\n" - "使用特殊功能請同時按住 FN 鍵。" +#: lib/logitech_receiver/settings_templates.py:155 +msgid "Turn on illumination when the hands hover over the keyboard." +msgstr "當手掌置於在鍵盤上方時開啟照明。" -#: lib/logitech_receiver/settings_templates.py:149 -msgid "Hand Detection" -msgstr "手掌識別" +#: lib/logitech_receiver/settings_templates.py:162 +msgid "Scroll Wheel Smooth Scrolling" +msgstr "滾輪平滑捲動" -#: lib/logitech_receiver/settings_templates.py:150 -msgid "Turn on illumination when the hands hover over the keyboard." -msgstr "手掌置於鍵盤上方時開啟照明。" +#: lib/logitech_receiver/settings_templates.py:163 +#: lib/logitech_receiver/settings_templates.py:410 +#: lib/logitech_receiver/settings_templates.py:439 +msgid "High-sensitivity mode for vertical scroll with the wheel." +msgstr "滾輪垂直捲動的高靈敏度模式。" -#: lib/logitech_receiver/settings_templates.py:157 -msgid "Scroll Wheel Smooth Scrolling" -msgstr "滾輪平滑捲動" +#: lib/logitech_receiver/settings_templates.py:170 +msgid "Side Scrolling" +msgstr "側邊捲動" -#: lib/logitech_receiver/settings_templates.py:158 -#: lib/logitech_receiver/settings_templates.py:239 -#: lib/logitech_receiver/settings_templates.py:267 -msgid "High-sensitivity mode for vertical scroll with the wheel." -msgstr "滾輪高解析度滾動模式。" +#: lib/logitech_receiver/settings_templates.py:172 +msgid "" +"When disabled, pushing the wheel sideways sends custom button events\n" +"instead of the standard side-scrolling events." +msgstr "" +"停用後,向側邊推動滾輪將會傳送自訂按鈕事件,\n" +"而非標準的側邊捲動事件。" -#: lib/logitech_receiver/settings_templates.py:165 -msgid "Side Scrolling" -msgstr "側邊滾動" +#: lib/logitech_receiver/settings_templates.py:182 +msgid "Sensitivity (DPI - older mice)" +msgstr "靈敏度(DPI - 較舊的滑鼠)" -#: lib/logitech_receiver/settings_templates.py:167 -msgid "When disabled, pushing the wheel sideways sends custom button " - "events\n" - "instead of the standard side-scrolling events." -msgstr "若停用,按下滾輪側邊將傳送自定義按鈕事件\n" - "而不是標準側邊滾動事件。" - -#: lib/logitech_receiver/settings_templates.py:177 -msgid "Sensitivity (DPI - older mice)" -msgstr "靈敏度(DPI - 較舊的滑鼠)" - -#: lib/logitech_receiver/settings_templates.py:178 -#: lib/logitech_receiver/settings_templates.py:712 -msgid "Mouse movement sensitivity" -msgstr "滑鼠移動靈敏度" - -#: lib/logitech_receiver/settings_templates.py:208 -#: lib/logitech_receiver/settings_templates.py:218 -#: lib/logitech_receiver/settings_templates.py:225 -msgid "Backlight" -msgstr "背光" - -#: lib/logitech_receiver/settings_templates.py:209 -#: lib/logitech_receiver/settings_templates.py:226 -msgid "Set illumination time for keyboard." -msgstr "設定鍵盤的照明時間。" - -#: lib/logitech_receiver/settings_templates.py:219 -msgid "Turn illumination on or off on keyboard." -msgstr "開啟或關閉鍵盤照明。" - -#: lib/logitech_receiver/settings_templates.py:237 -msgid "Scroll Wheel High Resolution" -msgstr "滾輪高解析度" - -#: lib/logitech_receiver/settings_templates.py:240 -#: lib/logitech_receiver/settings_templates.py:268 -msgid "Set to ignore if scrolling is abnormally fast or slow" -msgstr "如果滾動速度異常快或慢,設定為忽略" - -#: lib/logitech_receiver/settings_templates.py:247 -#: lib/logitech_receiver/settings_templates.py:277 -msgid "Scroll Wheel Diversion" -msgstr "滾輪轉向" - -#: lib/logitech_receiver/settings_templates.py:249 -msgid "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " - "trigger Solaar rules but are otherwise ignored)." -msgstr "使滾輪發送 LOWRES_WHEEL HID++ 通知(觸發 Solaar 規則,但其他情況下將" - "被忽略)。" +#: lib/logitech_receiver/settings_templates.py:183 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +msgid "Mouse movement sensitivity" +msgstr "滑鼠移動靈敏度" #: lib/logitech_receiver/settings_templates.py:256 -msgid "Scroll Wheel Direction" -msgstr "滾輪方向" +msgid "Backlight Timed" +msgstr "背光計時" #: lib/logitech_receiver/settings_templates.py:257 -msgid "Invert direction for vertical scroll with wheel." -msgstr "反轉滾輪的垂直滾動方向。" +#: lib/logitech_receiver/settings_templates.py:397 +msgid "Set illumination time for keyboard." +msgstr "設定鍵盤的照明時間。" -#: lib/logitech_receiver/settings_templates.py:265 -msgid "Scroll Wheel Resolution" -msgstr "滾輪解析度" +#: lib/logitech_receiver/settings_templates.py:268 +msgid "Backlight" +msgstr "背光" -#: lib/logitech_receiver/settings_templates.py:279 -msgid "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " - "trigger Solaar rules but are otherwise ignored)." -msgstr "使滾輪發送 HIRES_WHEEL HID++ 通知(觸發 Solaar 規則,但其他情況下將被" - "忽略)。" - -#: lib/logitech_receiver/settings_templates.py:288 -msgid "Sensitivity (Pointer Speed)" -msgstr "靈敏度(指標速度)" - -#: lib/logitech_receiver/settings_templates.py:289 -msgid "Speed multiplier for mouse (256 is normal multiplier)." -msgstr "滑鼠速度乘數(256 為正常乘數)。" - -#: lib/logitech_receiver/settings_templates.py:299 -msgid "Thumb Wheel Diversion" -msgstr "拇指滾輪轉向" +#: lib/logitech_receiver/settings_templates.py:269 +msgid "" +"Illumination level on keyboard. Changes made are only applied in Manual " +"mode." +msgstr "鍵盤上的亮度等級。僅在手動模式下適用。" #: lib/logitech_receiver/settings_templates.py:301 -msgid "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " - "Solaar rules but are otherwise ignored)." -msgstr "使拇指滾輪發送 THUMB_WHEEL HID++ 通知(觸發 Solaar 規則,但其他情況下" - "將被忽略)。" +msgid "Automatic" +msgstr "自動" -#: lib/logitech_receiver/settings_templates.py:310 -msgid "Thumb Wheel Direction" -msgstr "拇指滾輪方向" +#: lib/logitech_receiver/settings_templates.py:303 +msgid "Manual" +msgstr "手動" + +#: lib/logitech_receiver/settings_templates.py:305 +msgid "Enabled" +msgstr "已啟用" #: lib/logitech_receiver/settings_templates.py:311 -msgid "Invert thumb wheel scroll direction." -msgstr "反轉拇指滾輪滾動方向。" +msgid "Backlight Level" +msgstr "背光等級" -#: lib/logitech_receiver/settings_templates.py:319 -msgid "Onboard Profiles" -msgstr "內建設定檔" +#: lib/logitech_receiver/settings_templates.py:312 +msgid "Illumination level on keyboard when in Manual mode." +msgstr "手動模式下的鍵盤照明等級。" -#: lib/logitech_receiver/settings_templates.py:320 -msgid "Enable onboard profiles, which often control report rate and " - "keyboard lighting" -msgstr "啟用內建設定檔,通常用於控制報告速率和鍵盤亮度" +#: lib/logitech_receiver/settings_templates.py:369 +msgid "Backlight Delay Hands Out" +msgstr "手離開後背光延遲" -#: lib/logitech_receiver/settings_templates.py:330 -msgid "Polling Rate (ms)" -msgstr "輪詢速率(毫秒)" +#: lib/logitech_receiver/settings_templates.py:370 +msgid "" +"Delay in seconds until backlight fades out with hands away from keyboard." +msgstr "手離開鍵盤後,背光淡出的延遲秒數。" -#: lib/logitech_receiver/settings_templates.py:332 -msgid "Frequency of device polling, in milliseconds" -msgstr "裝置輪詢的頻率,以毫秒為單位" +#: lib/logitech_receiver/settings_templates.py:378 +msgid "Backlight Delay Hands In" +msgstr "手靠近後背光延遲" -#: lib/logitech_receiver/settings_templates.py:333 -#: lib/logitech_receiver/settings_templates.py:1046 -#: lib/logitech_receiver/settings_templates.py:1076 -msgid "May need Onboard Profiles set to Disable to be effective." -msgstr "可能需要將內建設定檔設定為停用才能生效。" +#: lib/logitech_receiver/settings_templates.py:379 +msgid "Delay in seconds until backlight fades out with hands near keyboard." +msgstr "手靠近鍵盤後,背光淡出的延遲秒數。" -#: lib/logitech_receiver/settings_templates.py:365 -msgid "Divert crown events" -msgstr "轉移多功能轉鈕事件" +#: lib/logitech_receiver/settings_templates.py:387 +msgid "Backlight Delay Powered" +msgstr "供電後背光延遲" -#: lib/logitech_receiver/settings_templates.py:366 -msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "使多功能轉鈕發送 CROWN HID++ 通知(觸發 Solaar 規則,但其他情況下將被" - "忽略)。" +#: lib/logitech_receiver/settings_templates.py:388 +msgid "Delay in seconds until backlight fades out with external power." +msgstr "外部供電時,背光淡出的延遲秒數。" -#: lib/logitech_receiver/settings_templates.py:374 -msgid "Crown smooth scroll" -msgstr "Crown 平滑滾動" +#: lib/logitech_receiver/settings_templates.py:396 +msgid "Backlight (Seconds)" +msgstr "背光(秒)" -#: lib/logitech_receiver/settings_templates.py:375 -msgid "Set crown smooth scroll" -msgstr "設定 Crown 平滑滾動" - -#: lib/logitech_receiver/settings_templates.py:383 -msgid "Divert G Keys" -msgstr "轉換 G 鍵" - -#: lib/logitech_receiver/settings_templates.py:385 -msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " - "rules but are otherwise ignored)." -msgstr "讓 G 鍵發送 GKEY HID++ 通知(將觸發 Solaar 規則,但其他情況下會被忽" - "略)。" - -#: lib/logitech_receiver/settings_templates.py:386 -msgid "May also make M keys and MR key send HID++ notifications" -msgstr "也可能讓 M 鍵和 MR 鍵發送 HID++ 通知" - -#: lib/logitech_receiver/settings_templates.py:402 -msgid "Scroll Wheel Ratcheted" -msgstr "滾輪棘輪" - -#: lib/logitech_receiver/settings_templates.py:403 -msgid "Switch the mouse wheel between speed-controlled ratcheting and " - "always freespin." -msgstr "在速度控制的滾輪棘輪和始終自由滾動之間切換滑鼠滾輪。" - -#: lib/logitech_receiver/settings_templates.py:405 -msgid "Freespinning" -msgstr "自由滾動" - -#: lib/logitech_receiver/settings_templates.py:405 -msgid "Ratcheted" -msgstr "棘輪式" +#: lib/logitech_receiver/settings_templates.py:408 +msgid "Scroll Wheel High Resolution" +msgstr "滾輪高解析度" #: lib/logitech_receiver/settings_templates.py:412 -msgid "Scroll Wheel Ratchet Speed" -msgstr "滾輪棘輪速度" +#: lib/logitech_receiver/settings_templates.py:441 +msgid "Set to ignore if scrolling is abnormally fast or slow" +msgstr "若捲動速度異常快或慢,設定為忽略" -#: lib/logitech_receiver/settings_templates.py:414 -msgid "Use the mouse wheel speed to switch between ratcheted and " - "freespinning.\n" - "The mouse wheel is always ratcheted at 50." -msgstr "使用滑鼠滾輪速度在滾輪棘輪和自由滾動之間切換。\n" - "滾輪在 50 時始終處於棘輪狀態。" +#: lib/logitech_receiver/settings_templates.py:419 +#: lib/logitech_receiver/settings_templates.py:450 +msgid "Scroll Wheel Diversion" +msgstr "滾輪轉向" -#: lib/logitech_receiver/settings_templates.py:463 -msgid "Key/Button Actions" -msgstr "鍵/按鈕動作" +#: lib/logitech_receiver/settings_templates.py:421 +msgid "" +"Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " +"Solaar rules but are otherwise ignored)." +msgstr "" +"使輪傳送 LOWRES_WHEEL HID++ 通知(會觸發 Solaar 規則,但其他情況下會被忽" +"略)。" -#: lib/logitech_receiver/settings_templates.py:465 -msgid "Change the action for the key or button." -msgstr "更改鍵或按鈕的動作。" +#: lib/logitech_receiver/settings_templates.py:428 +msgid "Scroll Wheel Direction" +msgstr "滾輪方向" -#: lib/logitech_receiver/settings_templates.py:465 -msgid "Overridden by diversion." -msgstr "由轉換覆蓋。" +#: lib/logitech_receiver/settings_templates.py:429 +msgid "Invert direction for vertical scroll with wheel." +msgstr "反轉滾輪的垂直捲動方向。" -#: lib/logitech_receiver/settings_templates.py:466 -msgid "Changing important actions (such as for the left mouse button) can " - "result in an unusable system." -msgstr "更改重要動作(例如左鍵)可能導致系統無法使用。" +#: lib/logitech_receiver/settings_templates.py:437 +msgid "Scroll Wheel Resolution" +msgstr "滾輪解析度" -#: lib/logitech_receiver/settings_templates.py:639 -msgid "Key/Button Diversion" -msgstr "鍵/按鈕轉換" +#: lib/logitech_receiver/settings_templates.py:452 +msgid "" +"Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"讓滾輪傳送 HIRES_WHEEL HID++ 通知(會觸發 Solaar 規則,但其他情況下會被忽" +"略)。" -#: lib/logitech_receiver/settings_templates.py:640 -msgid "Make the key or button send HID++ notifications (Diverted) or " - "initiate Mouse Gestures or Sliding DPI" -msgstr "讓鍵或按鈕發送 HID++ 通知(已轉換)或啟動滑鼠手勢或滑動 DPI" +#: lib/logitech_receiver/settings_templates.py:461 +msgid "Sensitivity (Pointer Speed)" +msgstr "靈敏度(指標速度)" + +#: lib/logitech_receiver/settings_templates.py:462 +msgid "Speed multiplier for mouse (256 is normal multiplier)." +msgstr "滑鼠速度倍數(256 為正常倍數)。" + +#: lib/logitech_receiver/settings_templates.py:472 +msgid "Thumb Wheel Diversion" +msgstr "拇指滾輪轉向" + +#: lib/logitech_receiver/settings_templates.py:474 +msgid "" +"Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " +"rules but are otherwise ignored)." +msgstr "" +"讓拇指滾輪傳送 THUMB_WHEEL HID++ 通知(會觸發 Solaar 規則,但其他情況下會被忽" +"略)。" + +#: lib/logitech_receiver/settings_templates.py:483 +msgid "Thumb Wheel Direction" +msgstr "拇指滾輪方向" + +#: lib/logitech_receiver/settings_templates.py:484 +msgid "Invert thumb wheel scroll direction." +msgstr "反轉拇指滾輪捲動方向。" + +#: lib/logitech_receiver/settings_templates.py:504 +msgid "Onboard Profiles" +msgstr "內建設定檔" + +#: lib/logitech_receiver/settings_templates.py:505 +msgid "" +"Enable an onboard profile, which controls report rate, sensitivity, and " +"button actions" +msgstr "啟用內建設定檔,可控制回報速率、靈敏度和按鈕動作" + +#: lib/logitech_receiver/settings_templates.py:549 +#: lib/logitech_receiver/settings_templates.py:582 +msgid "Report Rate" +msgstr "回報速率" + +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +msgid "Frequency of device movement reports" +msgstr "裝置移動回報的頻率" + +#: lib/logitech_receiver/settings_templates.py:551 +#: lib/logitech_receiver/settings_templates.py:584 +#: lib/logitech_receiver/settings_templates.py:1019 +#: lib/logitech_receiver/settings_templates.py:1047 +#: lib/logitech_receiver/settings_templates.py:1421 +#: lib/logitech_receiver/settings_templates.py:1452 +msgid "May need Onboard Profiles set to Disable to be effective." +msgstr "可能需要將內建設定檔設為停用才能生效。" + +#: lib/logitech_receiver/settings_templates.py:612 +msgid "Divert crown events" +msgstr "轉向多功能旋鈕事件" + +#: lib/logitech_receiver/settings_templates.py:613 +msgid "" +"Make crown send CROWN HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"讓多功能旋鈕傳送 CROWN HID++ 通知(會觸發 Solaar 規則,但其他情況下會被忽" +"略)。" + +#: lib/logitech_receiver/settings_templates.py:621 +msgid "Crown smooth scroll" +msgstr "多功能旋鈕平滑捲動" + +#: lib/logitech_receiver/settings_templates.py:622 +msgid "Set crown smooth scroll" +msgstr "設定多功能旋鈕平滑捲動" + +#: lib/logitech_receiver/settings_templates.py:630 +msgid "Divert G and M Keys" +msgstr "轉向 G 鍵和 M 鍵" + +#: lib/logitech_receiver/settings_templates.py:631 +msgid "" +"Make G and M keys send HID++ notifications (which trigger Solaar rules but " +"are otherwise ignored)." +msgstr "" +"讓 G 鍵和 M 鍵傳送 HID++ 通知(會觸發 Solaar 規則,但其他情況下會被忽略)。" -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 #: lib/logitech_receiver/settings_templates.py:645 -msgid "Diverted" -msgstr "已轉換" +msgid "Scroll Wheel Ratcheted" +msgstr "滾輪棘輪" -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 -msgid "Mouse Gestures" -msgstr "滑鼠手勢" +#: lib/logitech_receiver/settings_templates.py:646 +msgid "" +"Switch the mouse wheel between speed-controlled ratcheting and always " +"freespin." +msgstr "在速度控制的棘輪模式和永遠自由捲動之間切換滑鼠滾輪。" -#: lib/logitech_receiver/settings_templates.py:643 -#: lib/logitech_receiver/settings_templates.py:644 -#: lib/logitech_receiver/settings_templates.py:645 -msgid "Regular" -msgstr "常規" +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Freespinning" +msgstr "自由捲動" -#: lib/logitech_receiver/settings_templates.py:643 -msgid "Sliding DPI" -msgstr "滑動 DPI" +#: lib/logitech_receiver/settings_templates.py:648 +msgid "Ratcheted" +msgstr "棘輪式" -#: lib/logitech_receiver/settings_templates.py:711 -msgid "Sensitivity (DPI)" -msgstr "靈敏度 (DPI)" +#: lib/logitech_receiver/settings_templates.py:655 +msgid "Scroll Wheel Ratchet Speed" +msgstr "滾輪棘輪速度" -#: lib/logitech_receiver/settings_templates.py:752 -msgid "Sensitivity Switching" -msgstr "靈敏度切換" +#: lib/logitech_receiver/settings_templates.py:657 +msgid "" +"Use the mouse wheel speed to switch between ratcheted and freespinning.\n" +"The mouse wheel is always ratcheted at 50." +msgstr "" +"使用滑鼠滾輪速度在棘輪和自由捲動之間切換。\n" +"滾輪在 50 時永遠處於棘輪狀態。" -#: lib/logitech_receiver/settings_templates.py:754 -msgid "Switch the current sensitivity and the remembered sensitivity when " - "the key or button is pressed.\n" - "If there is no remembered sensitivity, just remember the current " - "sensitivity" -msgstr "在按下鍵或按鈕時切換目前靈敏度和已記憶的靈敏度。\n" - "如果沒有已記憶的靈敏度,只需記住目前的靈敏度" +#: lib/logitech_receiver/settings_templates.py:707 +msgid "Scroll Wheel Ratchet Torque" +msgstr "滾輪棘輪扭矩" -#: lib/logitech_receiver/settings_templates.py:758 -msgid "Off" -msgstr "關" +#: lib/logitech_receiver/settings_templates.py:708 +msgid "Change the torque needed to overcome the ratchet." +msgstr "變更克服棘輪所需的扭矩。" -#: lib/logitech_receiver/settings_templates.py:791 -msgid "Disable keys" -msgstr "停用鍵" +#: lib/logitech_receiver/settings_templates.py:743 +msgid "Key/Button Actions" +msgstr "鍵/按鈕動作" -#: lib/logitech_receiver/settings_templates.py:792 -msgid "Disable specific keyboard keys." -msgstr "停用特定鍵盤按鍵。" +#: lib/logitech_receiver/settings_templates.py:745 +msgid "Change the action for the key or button." +msgstr "變更按鍵或按鈕的動作。" -#: lib/logitech_receiver/settings_templates.py:795 -#, python-format -msgid "Disables the %s key." -msgstr "停用 %s 鍵。" +#: lib/logitech_receiver/settings_templates.py:747 +msgid "Overridden by diversion." +msgstr "已被轉向覆寫。" -#: lib/logitech_receiver/settings_templates.py:809 -#: lib/logitech_receiver/settings_templates.py:860 -msgid "Set OS" -msgstr "設定作業系統" - -#: lib/logitech_receiver/settings_templates.py:810 -#: lib/logitech_receiver/settings_templates.py:861 -msgid "Change keys to match OS." -msgstr "更改按鍵以符合作業系統。" - -#: lib/logitech_receiver/settings_templates.py:873 -msgid "Change Host" -msgstr "更改主機號碼" - -#: lib/logitech_receiver/settings_templates.py:874 -msgid "Switch connection to a different host" -msgstr "切換到其他主機" - -#: lib/logitech_receiver/settings_templates.py:900 -msgid "Performs a left click." -msgstr "執行左鍵點選。" - -#: lib/logitech_receiver/settings_templates.py:900 -msgid "Single tap" -msgstr "點選" - -#: lib/logitech_receiver/settings_templates.py:901 -msgid "Performs a right click." -msgstr "執行右鍵點選。" - -#: lib/logitech_receiver/settings_templates.py:901 -msgid "Single tap with two fingers" -msgstr "用兩根手指點選" - -#: lib/logitech_receiver/settings_templates.py:902 -msgid "Single tap with three fingers" -msgstr "用三根手指點選" - -#: lib/logitech_receiver/settings_templates.py:906 -msgid "Double tap" -msgstr "點兩下" - -#: lib/logitech_receiver/settings_templates.py:906 -msgid "Performs a double click." -msgstr "執行點兩下。" - -#: lib/logitech_receiver/settings_templates.py:907 -msgid "Double tap with two fingers" -msgstr "用兩根手指點兩下" - -#: lib/logitech_receiver/settings_templates.py:908 -msgid "Double tap with three fingers" -msgstr "用三根手指點兩下" - -#: lib/logitech_receiver/settings_templates.py:911 -msgid "Drags items by dragging the finger after double tapping." -msgstr "點兩下後拖動手指來拖動物件。" - -#: lib/logitech_receiver/settings_templates.py:911 -msgid "Tap and drag" -msgstr "點選並拖曳" - -#: lib/logitech_receiver/settings_templates.py:913 -msgid "Drags items by dragging the fingers after double tapping." -msgstr "點兩下後拖動手指來拖動物件。" - -#: lib/logitech_receiver/settings_templates.py:913 -msgid "Tap and drag with two fingers" -msgstr "用兩根手指點兩下並拖曳" - -#: lib/logitech_receiver/settings_templates.py:914 -msgid "Tap and drag with three fingers" -msgstr "用三根手指點選並拖曳" - -#: lib/logitech_receiver/settings_templates.py:917 -msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." -msgstr "停用輕觸和邊緣手勢(相當於按 Fn+左鍵點選)。" - -#: lib/logitech_receiver/settings_templates.py:917 -msgid "Suppress tap and edge gestures" -msgstr "停用輕觸和邊緣手勢" - -#: lib/logitech_receiver/settings_templates.py:918 -msgid "Scroll with one finger" -msgstr "用一根手指滾動" - -#: lib/logitech_receiver/settings_templates.py:918 -#: lib/logitech_receiver/settings_templates.py:919 -#: lib/logitech_receiver/settings_templates.py:922 -msgid "Scrolls." -msgstr "滾動。" - -#: lib/logitech_receiver/settings_templates.py:919 -#: lib/logitech_receiver/settings_templates.py:922 -msgid "Scroll with two fingers" -msgstr "用兩根手指滾動" - -#: lib/logitech_receiver/settings_templates.py:920 -msgid "Scroll horizontally with two fingers" -msgstr "用兩根手指水平滾動" - -#: lib/logitech_receiver/settings_templates.py:920 -msgid "Scrolls horizontally." -msgstr "水平滾動。" - -#: lib/logitech_receiver/settings_templates.py:921 -msgid "Scroll vertically with two fingers" -msgstr "用兩根手指垂直滾動" - -#: lib/logitech_receiver/settings_templates.py:921 -msgid "Scrolls vertically." -msgstr "垂直滾動。" - -#: lib/logitech_receiver/settings_templates.py:923 -msgid "Inverts the scrolling direction." -msgstr "反轉滾動方向。" - -#: lib/logitech_receiver/settings_templates.py:923 -msgid "Natural scrolling" -msgstr "自然滾動" +#: lib/logitech_receiver/settings_templates.py:749 +msgid "" +"Changing important actions (such as for the left mouse button) can result in " +"an unusable system." +msgstr "變更重要動作(例如滑鼠左鍵)可能會導致系統無法使用。" #: lib/logitech_receiver/settings_templates.py:924 -msgid "Enables the thumbwheel." -msgstr "啟用拇指滾輪。" +msgid "Key/Button Diversion" +msgstr "鍵/按鈕轉換" -#: lib/logitech_receiver/settings_templates.py:924 -msgid "Thumbwheel" -msgstr "拇指滾輪" +#: lib/logitech_receiver/settings_templates.py:925 +msgid "" +"Make the key or button send HID++ notifications (Diverted) or initiate Mouse " +"Gestures or Sliding DPI" +msgstr "讓按鍵或按鈕傳送 HID++ 通知(已轉向)或啟動滑鼠手勢或滑動 DPI。" -#: lib/logitech_receiver/settings_templates.py:935 -#: lib/logitech_receiver/settings_templates.py:939 -msgid "Swipe from the top edge" -msgstr "從上緣滑動" +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Diverted" +msgstr "已轉向" -#: lib/logitech_receiver/settings_templates.py:936 -msgid "Swipe from the left edge" -msgstr "從左緣滑動" +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +msgid "Mouse Gestures" +msgstr "滑鼠手勢" -#: lib/logitech_receiver/settings_templates.py:937 -msgid "Swipe from the right edge" -msgstr "從右緣滑動" +#: lib/logitech_receiver/settings_templates.py:928 +#: lib/logitech_receiver/settings_templates.py:929 +#: lib/logitech_receiver/settings_templates.py:930 +msgid "Regular" +msgstr "一般" -#: lib/logitech_receiver/settings_templates.py:938 -msgid "Swipe from the bottom edge" -msgstr "從下緣滑動" - -#: lib/logitech_receiver/settings_templates.py:940 -msgid "Swipe two fingers from the left edge" -msgstr "從左緣滑動兩根手指" - -#: lib/logitech_receiver/settings_templates.py:941 -msgid "Swipe two fingers from the right edge" -msgstr "從右緣滑動兩根手指" - -#: lib/logitech_receiver/settings_templates.py:942 -msgid "Swipe two fingers from the bottom edge" -msgstr "從下緣滑動兩根手指" - -#: lib/logitech_receiver/settings_templates.py:943 -msgid "Swipe two fingers from the top edge" -msgstr "從上緣滑動兩根手指" - -#: lib/logitech_receiver/settings_templates.py:944 -#: lib/logitech_receiver/settings_templates.py:948 -msgid "Pinch to zoom out; spread to zoom in." -msgstr "捏合以縮小;張開以放大。" - -#: lib/logitech_receiver/settings_templates.py:944 -msgid "Zoom with two fingers." -msgstr "用兩根手指縮放。" - -#: lib/logitech_receiver/settings_templates.py:945 -msgid "Pinch to zoom out." -msgstr "捏合以縮小。" - -#: lib/logitech_receiver/settings_templates.py:946 -msgid "Spread to zoom in." -msgstr "張開以放大。" - -#: lib/logitech_receiver/settings_templates.py:947 -msgid "Zoom with three fingers." -msgstr "用三根手指縮放。" - -#: lib/logitech_receiver/settings_templates.py:948 -msgid "Zoom with two fingers" -msgstr "用兩根手指縮放" - -#: lib/logitech_receiver/settings_templates.py:966 -msgid "Pixel zone" -msgstr "像素區域" - -#: lib/logitech_receiver/settings_templates.py:967 -msgid "Ratio zone" -msgstr "比例區域" - -#: lib/logitech_receiver/settings_templates.py:968 -msgid "Scale factor" -msgstr "縮放係數" - -#: lib/logitech_receiver/settings_templates.py:968 -msgid "Sets the cursor speed." -msgstr "設定游標速度。" - -#: lib/logitech_receiver/settings_templates.py:972 -msgid "Left" -msgstr "左" - -#: lib/logitech_receiver/settings_templates.py:972 -msgid "Left-most coordinate." -msgstr "最左邊的座標。" - -#: lib/logitech_receiver/settings_templates.py:973 -msgid "Bottom" -msgstr "底部" - -#: lib/logitech_receiver/settings_templates.py:973 -msgid "Bottom coordinate." -msgstr "底部座標。" - -#: lib/logitech_receiver/settings_templates.py:974 -msgid "Width" -msgstr "寬度" - -#: lib/logitech_receiver/settings_templates.py:974 -msgid "Width." -msgstr "寬度。" - -#: lib/logitech_receiver/settings_templates.py:975 -msgid "Height" -msgstr "高度" - -#: lib/logitech_receiver/settings_templates.py:975 -msgid "Height." -msgstr "高度。" - -#: lib/logitech_receiver/settings_templates.py:976 -msgid "Cursor speed." -msgstr "游標速度。" - -#: lib/logitech_receiver/settings_templates.py:976 -msgid "Scale" -msgstr "縮放" - -#: lib/logitech_receiver/settings_templates.py:982 -msgid "Gestures" -msgstr "手勢" - -#: lib/logitech_receiver/settings_templates.py:983 -msgid "Tweak the mouse/touchpad behaviour." -msgstr "調整滑鼠/觸控板行為。" - -#: lib/logitech_receiver/settings_templates.py:1000 -msgid "Gestures Diversion" -msgstr "手勢轉換" - -#: lib/logitech_receiver/settings_templates.py:1001 -msgid "Divert mouse/touchpad gestures." -msgstr "轉換滑鼠/觸控板手勢。" +#: lib/logitech_receiver/settings_templates.py:928 +msgid "Sliding DPI" +msgstr "滑動 DPI" #: lib/logitech_receiver/settings_templates.py:1018 -msgid "Gesture params" -msgstr "手勢參數" - -#: lib/logitech_receiver/settings_templates.py:1019 -msgid "Change numerical parameters of a mouse/touchpad." -msgstr "更改滑鼠/觸控板的數值參數。" - -#: lib/logitech_receiver/settings_templates.py:1044 -msgid "M-Key LEDs" -msgstr "M 鍵 LED" - #: lib/logitech_receiver/settings_templates.py:1046 -msgid "Control the M-Key LEDs." -msgstr "控制 M 鍵 LED。" +msgid "Sensitivity (DPI)" +msgstr "靈敏度 (DPI)" -#: lib/logitech_receiver/settings_templates.py:1047 -#: lib/logitech_receiver/settings_templates.py:1077 -msgid "May need G Keys diverted to be effective." -msgstr "可能需要轉換 G 鍵才能生效。" +#: lib/logitech_receiver/settings_templates.py:1123 +msgid "Sensitivity Switching" +msgstr "靈敏度切換" -#: lib/logitech_receiver/settings_templates.py:1053 +#: lib/logitech_receiver/settings_templates.py:1125 +msgid "" +"Switch the current sensitivity and the remembered sensitivity when the key " +"or button is pressed.\n" +"If there is no remembered sensitivity, just remember the current sensitivity" +msgstr "" +"按下按鍵或按鈕時,切換目前靈敏度和已記憶的靈敏度。\n" +"若沒有已記憶的靈敏度,則僅記住目前的靈敏度" + +#: lib/logitech_receiver/settings_templates.py:1129 +msgid "Off" +msgstr "關閉" + +#: lib/logitech_receiver/settings_templates.py:1160 +msgid "Disable keys" +msgstr "停用按鍵" + +#: lib/logitech_receiver/settings_templates.py:1161 +msgid "Disable specific keyboard keys." +msgstr "停用特定鍵盤按鍵。" + +#: lib/logitech_receiver/settings_templates.py:1164 #, python-format -msgid "Lights up the %s key." -msgstr "點亮 %s 鍵。" +msgid "Disables the %s key." +msgstr "停用 %s 鍵。" -#: lib/logitech_receiver/settings_templates.py:1074 -msgid "MR-Key LED" -msgstr "MR 鍵 LED" +#: lib/logitech_receiver/settings_templates.py:1177 +#: lib/logitech_receiver/settings_templates.py:1234 +msgid "Set OS" +msgstr "設定作業系統" -#: lib/logitech_receiver/settings_templates.py:1076 -msgid "Control the MR-Key LED." -msgstr "控制 MR 鍵 LED。" +#: lib/logitech_receiver/settings_templates.py:1178 +#: lib/logitech_receiver/settings_templates.py:1235 +msgid "Change keys to match OS." +msgstr "變更按鍵以符合作業系統。" -#: lib/logitech_receiver/settings_templates.py:1095 -msgid "Persistent Key/Button Mapping" -msgstr "持久按鍵/按鈕對應" +#: lib/logitech_receiver/settings_templates.py:1247 +msgid "Change Host" +msgstr "變更主機" -#: lib/logitech_receiver/settings_templates.py:1097 -msgid "Permanently change the mapping for the key or button." -msgstr "永久更改按鍵或按鈕的對應。" +#: lib/logitech_receiver/settings_templates.py:1248 +msgid "Switch connection to a different host" +msgstr "切換連線到不同的主機" -#: lib/logitech_receiver/settings_templates.py:1098 -msgid "Changing important keys or buttons (such as for the left mouse " - "button) can result in an unusable system." -msgstr "更改重要的按鍵或按鈕(例如左鍵)可能導致系統無法使用。" +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Performs a left click." +msgstr "執行左鍵點選。" -#: lib/logitech_receiver/settings_templates.py:1157 -msgid "Sidetone" -msgstr "側音" +#: lib/logitech_receiver/settings_templates.py:1272 +msgid "Single tap" +msgstr "單次點選" -#: lib/logitech_receiver/settings_templates.py:1158 -msgid "Set sidetone level." -msgstr "設定側音等級。" +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Performs a right click." +msgstr "執行右鍵點選。" -#: lib/logitech_receiver/settings_templates.py:1167 -msgid "Equalizer" -msgstr "均衡器" +#: lib/logitech_receiver/settings_templates.py:1273 +msgid "Single tap with two fingers" +msgstr "用兩根手指單次點選" -#: lib/logitech_receiver/settings_templates.py:1168 -msgid "Set equalizer levels." -msgstr "設定均衡器等級。" +#: lib/logitech_receiver/settings_templates.py:1274 +msgid "Single tap with three fingers" +msgstr "用三根手指單次點選" -#: lib/logitech_receiver/settings_templates.py:1191 -msgid "Hz" -msgstr "赫茲" +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Double tap" +msgstr "點兩下" -#: lib/logitech_receiver/settings_templates.py:1197 -msgid "Power Management" -msgstr "電源管理" +#: lib/logitech_receiver/settings_templates.py:1278 +msgid "Performs a double click." +msgstr "執行點兩下。" -#: lib/logitech_receiver/settings_templates.py:1198 -msgid "Power off in minutes (0 for never)." -msgstr "幾分鐘後關機(0 表示永不)。" +#: lib/logitech_receiver/settings_templates.py:1279 +msgid "Double tap with two fingers" +msgstr "用兩根手指點兩下" -#: lib/logitech_receiver/status.py:114 -msgid "No paired devices." -msgstr "沒有已配對的裝置。" +#: lib/logitech_receiver/settings_templates.py:1280 +msgid "Double tap with three fingers" +msgstr "用三根手指點兩下" -#: lib/logitech_receiver/status.py:115 lib/solaar/ui/window.py:622 -#, fuzzy, python-format -msgid "%(count)s paired device." -msgid_plural "%(count)s paired devices." -msgstr[0] "%(count)s 個已配對裝置" +#: lib/logitech_receiver/settings_templates.py:1283 +msgid "Drags items by dragging the finger after double tapping." +msgstr "點兩下後拖曳手指來拖曳項目。" -#: lib/logitech_receiver/status.py:170 +#: lib/logitech_receiver/settings_templates.py:1283 +msgid "Tap and drag" +msgstr "點選並拖曳" + +#: lib/logitech_receiver/settings_templates.py:1285 +msgid "Tap and drag with two fingers" +msgstr "用兩根手指點兩下並拖曳" + +#: lib/logitech_receiver/settings_templates.py:1286 +msgid "Drags items by dragging the fingers after double tapping." +msgstr "點兩下後拖曳手指來拖曳項目。" + +#: lib/logitech_receiver/settings_templates.py:1288 +msgid "Tap and drag with three fingers" +msgstr "用三根手指點選並拖曳" + +#: lib/logitech_receiver/settings_templates.py:1291 +msgid "Suppress tap and edge gestures" +msgstr "停用輕觸和邊緣手勢" + +#: lib/logitech_receiver/settings_templates.py:1292 +msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." +msgstr "停用輕觸和邊緣手勢(相當於按下 Fn+左鍵點選)。" + +#: lib/logitech_receiver/settings_templates.py:1294 +msgid "Scroll with one finger" +msgstr "用一根手指捲動" + +#: lib/logitech_receiver/settings_templates.py:1294 +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 +msgid "Scrolls." +msgstr "捲動。" + +#: lib/logitech_receiver/settings_templates.py:1295 +#: lib/logitech_receiver/settings_templates.py:1298 +msgid "Scroll with two fingers" +msgstr "用兩根手指捲動" + +#: lib/logitech_receiver/settings_templates.py:1296 +msgid "Scroll horizontally with two fingers" +msgstr "用兩根手指水平捲動" + +#: lib/logitech_receiver/settings_templates.py:1296 +msgid "Scrolls horizontally." +msgstr "水平捲動。" + +#: lib/logitech_receiver/settings_templates.py:1297 +msgid "Scroll vertically with two fingers" +msgstr "用兩根手指垂直捲動" + +#: lib/logitech_receiver/settings_templates.py:1297 +msgid "Scrolls vertically." +msgstr "垂直捲動。" + +#: lib/logitech_receiver/settings_templates.py:1299 +msgid "Inverts the scrolling direction." +msgstr "反轉捲動方向。" + +#: lib/logitech_receiver/settings_templates.py:1299 +msgid "Natural scrolling" +msgstr "自然捲動" + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Enables the thumbwheel." +msgstr "啟用拇指滾輪。" + +#: lib/logitech_receiver/settings_templates.py:1300 +msgid "Thumbwheel" +msgstr "拇指滾輪" + +#: lib/logitech_receiver/settings_templates.py:1311 +#: lib/logitech_receiver/settings_templates.py:1315 +msgid "Swipe from the top edge" +msgstr "從上緣滑動" + +#: lib/logitech_receiver/settings_templates.py:1312 +msgid "Swipe from the left edge" +msgstr "從左緣滑動" + +#: lib/logitech_receiver/settings_templates.py:1313 +msgid "Swipe from the right edge" +msgstr "從右緣滑動" + +#: lib/logitech_receiver/settings_templates.py:1314 +msgid "Swipe from the bottom edge" +msgstr "從下緣滑動" + +#: lib/logitech_receiver/settings_templates.py:1316 +msgid "Swipe two fingers from the left edge" +msgstr "從左緣滑動兩根手指" + +#: lib/logitech_receiver/settings_templates.py:1317 +msgid "Swipe two fingers from the right edge" +msgstr "從右緣滑動兩根手指" + +#: lib/logitech_receiver/settings_templates.py:1318 +msgid "Swipe two fingers from the bottom edge" +msgstr "從下緣滑動兩根手指" + +#: lib/logitech_receiver/settings_templates.py:1319 +msgid "Swipe two fingers from the top edge" +msgstr "從上緣滑動兩根手指" + +#: lib/logitech_receiver/settings_templates.py:1320 +#: lib/logitech_receiver/settings_templates.py:1324 +msgid "Pinch to zoom out; spread to zoom in." +msgstr "捏合以縮小;張開以放大。" + +#: lib/logitech_receiver/settings_templates.py:1320 +msgid "Zoom with two fingers." +msgstr "用兩根手指縮放。" + +#: lib/logitech_receiver/settings_templates.py:1321 +msgid "Pinch to zoom out." +msgstr "捏合以縮小。" + +#: lib/logitech_receiver/settings_templates.py:1322 +msgid "Spread to zoom in." +msgstr "張開以放大。" + +#: lib/logitech_receiver/settings_templates.py:1323 +msgid "Zoom with three fingers." +msgstr "用三根手指縮放。" + +#: lib/logitech_receiver/settings_templates.py:1324 +msgid "Zoom with two fingers" +msgstr "用兩根手指縮放" + +#: lib/logitech_receiver/settings_templates.py:1342 +msgid "Pixel zone" +msgstr "像素區域" + +#: lib/logitech_receiver/settings_templates.py:1343 +msgid "Ratio zone" +msgstr "比例區域" + +#: lib/logitech_receiver/settings_templates.py:1344 +msgid "Scale factor" +msgstr "縮放係數" + +#: lib/logitech_receiver/settings_templates.py:1344 +msgid "Sets the cursor speed." +msgstr "設定游標速度。" + +#: lib/logitech_receiver/settings_templates.py:1348 +msgid "Left" +msgstr "左" + +#: lib/logitech_receiver/settings_templates.py:1348 +msgid "Left-most coordinate." +msgstr "最左邊的座標。" + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom" +msgstr "底部" + +#: lib/logitech_receiver/settings_templates.py:1349 +msgid "Bottom coordinate." +msgstr "底部座標。" + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width" +msgstr "寬度" + +#: lib/logitech_receiver/settings_templates.py:1350 +msgid "Width." +msgstr "寬度。" + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height" +msgstr "高度" + +#: lib/logitech_receiver/settings_templates.py:1351 +msgid "Height." +msgstr "高度。" + +#: lib/logitech_receiver/settings_templates.py:1352 +msgid "Cursor speed." +msgstr "游標速度。" + +#: lib/logitech_receiver/settings_templates.py:1352 +msgid "Scale" +msgstr "縮放" + +#: lib/logitech_receiver/settings_templates.py:1358 +msgid "Gestures" +msgstr "手勢" + +#: lib/logitech_receiver/settings_templates.py:1359 +msgid "Tweak the mouse/touchpad behaviour." +msgstr "調整滑鼠/觸控板行為。" + +#: lib/logitech_receiver/settings_templates.py:1375 +msgid "Gestures Diversion" +msgstr "手勢轉換" + +#: lib/logitech_receiver/settings_templates.py:1376 +msgid "Divert mouse/touchpad gestures." +msgstr "轉換滑鼠/觸控板手勢。" + +#: lib/logitech_receiver/settings_templates.py:1392 +msgid "Gesture params" +msgstr "手勢參數" + +#: lib/logitech_receiver/settings_templates.py:1393 +msgid "Change numerical parameters of a mouse/touchpad." +msgstr "變更滑鼠/觸控板的數值參數。" + +#: lib/logitech_receiver/settings_templates.py:1417 +msgid "M-Key LEDs" +msgstr "M 鍵 LED" + +#: lib/logitech_receiver/settings_templates.py:1419 +msgid "Control the M-Key LEDs." +msgstr "控制 M 鍵 LED。" + +#: lib/logitech_receiver/settings_templates.py:1423 +#: lib/logitech_receiver/settings_templates.py:1454 +msgid "May need G Keys diverted to be effective." +msgstr "可能需要轉向 G 鍵才能生效。" + +#: lib/logitech_receiver/settings_templates.py:1429 #, python-format -msgid "Battery: %(level)s" -msgstr "電量:%(level)s " +msgid "Lights up the %s key." +msgstr "點亮 %s 鍵。" -#: lib/logitech_receiver/status.py:172 +#: lib/logitech_receiver/settings_templates.py:1448 +msgid "MR-Key LED" +msgstr "MR 鍵 LED" + +#: lib/logitech_receiver/settings_templates.py:1450 +msgid "Control the MR-Key LED." +msgstr "控制 MR 鍵 LED。" + +#: lib/logitech_receiver/settings_templates.py:1471 +msgid "Persistent Key/Button Mapping" +msgstr "持久按鍵/按鈕對應" + +#: lib/logitech_receiver/settings_templates.py:1473 +msgid "Permanently change the mapping for the key or button." +msgstr "永久變更按鍵或按鈕的對應。" + +#: lib/logitech_receiver/settings_templates.py:1475 +msgid "" +"Changing important keys or buttons (such as for the left mouse button) can " +"result in an unusable system." +msgstr "變更重要的按鍵或按鈕(例如滑鼠左鍵)可能會導致系統無法使用。" + +#: lib/logitech_receiver/settings_templates.py:1532 +msgid "Sidetone" +msgstr "側音" + +#: lib/logitech_receiver/settings_templates.py:1533 +msgid "Set sidetone level." +msgstr "設定側音等級。" + +#: lib/logitech_receiver/settings_templates.py:1542 +msgid "Equalizer" +msgstr "均衡器" + +#: lib/logitech_receiver/settings_templates.py:1543 +msgid "Set equalizer levels." +msgstr "設定等化器等級。" + +#: lib/logitech_receiver/settings_templates.py:1565 +msgid "Hz" +msgstr "Hz" + +#: lib/logitech_receiver/settings_templates.py:1571 +msgid "Power Management" +msgstr "電源管理" + +#: lib/logitech_receiver/settings_templates.py:1572 +msgid "Power off in minutes (0 for never)." +msgstr "幾分鐘後關機(0 表示永不關機)。" + +#: lib/logitech_receiver/settings_templates.py:1584 +msgid "Brightness Control" +msgstr "亮度控制" + +#: lib/logitech_receiver/settings_templates.py:1585 +msgid "Control overall brightness" +msgstr "控制整體亮度" + +#: lib/logitech_receiver/settings_templates.py:1628 +#: lib/logitech_receiver/settings_templates.py:1681 +msgid "LED Control" +msgstr "LED 控制" + +#: lib/logitech_receiver/settings_templates.py:1629 +#: lib/logitech_receiver/settings_templates.py:1682 +msgid "Switch control of LED zones between device and Solaar" +msgstr "在裝置與 Solaar 之間切換 LED 區域控制" + +#: lib/logitech_receiver/settings_templates.py:1644 +#: lib/logitech_receiver/settings_templates.py:1692 +msgid "LED Zone Effects" +msgstr "LED 區域效果" + +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "LED Control needs to be set to Solaar to be effective." +msgstr "需要將 LED 控制設為 Solaar 才能生效。" + +#: lib/logitech_receiver/settings_templates.py:1645 +#: lib/logitech_receiver/settings_templates.py:1693 +msgid "Set effect for LED Zone" +msgstr "設定 LED 區域的效果" + +#: lib/logitech_receiver/settings_templates.py:1647 +msgid "Color" +msgstr "顏色" + +#: lib/logitech_receiver/settings_templates.py:1648 +msgid "Speed" +msgstr "速度" + +#: lib/logitech_receiver/settings_templates.py:1649 +msgid "Period" +msgstr "週期" + +#: lib/logitech_receiver/settings_templates.py:1650 +msgid "Intensity" +msgstr "強度" + +#: lib/logitech_receiver/settings_templates.py:1651 +msgid "Ramp" +msgstr "漸變" + +#: lib/logitech_receiver/settings_templates.py:1666 +msgid "LEDs" +msgstr "LED 燈" + +#: lib/logitech_receiver/settings_templates.py:1703 +msgid "Per-key Lighting" +msgstr "單鍵照明" + +#: lib/logitech_receiver/settings_templates.py:1704 +msgid "Control per-key lighting." +msgstr "控制單鍵照明。" + +#: lib/logitech_receiver/settings_templates.py:1786 +msgid "Force Sensing Buttons" +msgstr "力道感測按鈕" + +#: lib/logitech_receiver/settings_templates.py:1787 +msgid "Change the force required to activate button." +msgstr "調整觸發按鈕所需的力道。" + +#: lib/logitech_receiver/settings_templates.py:1804 +msgid "Force Sensing Button" +msgstr "力道感測按鈕" + +#: lib/logitech_receiver/settings_templates.py:1814 +msgid "Haptic Feeback Level" +msgstr "觸覺回饋等級" + +#: lib/logitech_receiver/settings_templates.py:1815 +msgid "Change power of haptic feedback. (Zero to turn off.)" +msgstr "變更觸覺回饋的力度,設為 0 可關閉。" + +#: lib/logitech_receiver/settings_templates.py:1857 +msgid "Play Haptic Waveform" +msgstr "播放觸覺波形" + +#: lib/logitech_receiver/settings_templates.py:1858 +msgid "Tell device to play a haptic waveform." +msgstr "指示裝置播放觸覺波形。" + +#: lib/solaar/ui/__init__.py:120 +msgid "Another Solaar process is already running so just expose its window" +msgstr "已有另一個 Solaar 處理程序正在執行,因此將會顯示其視窗" + +#: lib/solaar/ui/about/model.py:36 +msgid "" +"Manages Logitech receivers,\n" +"keyboards, mice, and tablets." +msgstr "" +"管理羅技接收器、\n" +"鍵盤、滑鼠和平板電腦。" + +#: lib/solaar/ui/about/model.py:63 +msgid "Additional Programming" +msgstr "額外程式設計" + +#: lib/solaar/ui/about/model.py:64 +msgid "GUI design" +msgstr "圖形介面設計" + +#: lib/solaar/ui/about/model.py:66 +msgid "Testing" +msgstr "測試" + +#: lib/solaar/ui/about/model.py:74 +msgid "Logitech documentation" +msgstr "羅技說明文件" + +#: lib/solaar/ui/action.py:88 lib/solaar/ui/action.py:92 +#: lib/solaar/ui/window.py:208 +msgid "Unpair" +msgstr "取消配對" + +#: lib/solaar/ui/action.py:91 lib/solaar/ui/diversion_rules.py:141 +msgid "Cancel" +msgstr "取消" + +#: lib/solaar/ui/common.py:42 +msgid "Permissions error" +msgstr "權限錯誤" + +#: lib/solaar/ui/common.py:44 #, python-format -msgid "Battery: %(percent)d%%" -msgstr "電量:%(percent)d%%" +msgid "" +"Found a Logitech receiver or device (%s), but did not have permission to " +"open it." +msgstr "找到羅技接收器或裝置 (%s),但沒有權限開啟。" -#: lib/logitech_receiver/status.py:184 +#: lib/solaar/ui/common.py:46 +msgid "" +"If you've just installed Solaar, try disconnecting the receiver or device " +"and then reconnecting it." +msgstr "若剛安裝 Solaar,請嘗試中斷接收器或裝置的連線,然後重新連接。" + +#: lib/solaar/ui/common.py:49 +msgid "Cannot connect to device error" +msgstr "無法連接到裝置錯誤" + +#: lib/solaar/ui/common.py:51 #, python-format -msgid "Lighting: %(level)s lux" -msgstr "亮度: %(level)s lux" +msgid "" +"Found a Logitech receiver or device at %s, but encountered an error " +"connecting to it." +msgstr "在 %s 找到羅技接收器或裝置,但連接時發生錯誤。" -#: lib/logitech_receiver/status.py:239 -#, python-format -msgid "Battery: %(level)s (%(status)s)" -msgstr "電量: %(level)s (%(status)s)" +#: lib/solaar/ui/common.py:53 +msgid "" +"Try disconnecting the device and then reconnecting it or turning it off and " +"then on." +msgstr "請嘗試中斷裝置連線然後重新連接,或將其關閉後再開啟。" -#: lib/logitech_receiver/status.py:241 -#, python-format -msgid "Battery: %(percent)d%% (%(status)s)" -msgstr "電量: %(percent)d%% (%(status)s)" +#: lib/solaar/ui/common.py:56 +msgid "Unpairing failed" +msgstr "取消配對失敗" -#: lib/solaar/ui/__init__.py:52 -msgid "Permissions error" -msgstr "權限錯誤" - -#: lib/solaar/ui/__init__.py:54 -#, python-format -msgid "Found a Logitech receiver or device (%s), but did not have " - "permission to open it." -msgstr "找到了羅技接收器或裝置(%s),但沒有權限開啟。" - -#: lib/solaar/ui/__init__.py:55 -msgid "If you've just installed Solaar, try disconnecting the receiver or " - "device and then reconnecting it." -msgstr "如果您剛剛安裝了 Solaar,請嘗試斷開接收器或裝置,然後重新連接。" - -#: lib/solaar/ui/__init__.py:58 -msgid "Cannot connect to device error" -msgstr "無法連接到裝置錯誤" - -#: lib/solaar/ui/__init__.py:60 -#, python-format -msgid "Found a Logitech receiver or device at %s, but encountered an error " - "connecting to it." -msgstr "在 %s 找到了一個羅技接收器或裝置,但連接時遇到錯誤。" - -#: lib/solaar/ui/__init__.py:61 -msgid "Try disconnecting the device and then reconnecting it or turning it " - "off and then on." -msgstr "嘗試斷開裝置然後重新連接,或者關閉裝置然後再開啟。" - -#: lib/solaar/ui/__init__.py:64 -msgid "Unpairing failed" -msgstr "取消配對失敗" - -#: lib/solaar/ui/__init__.py:66 +#: lib/solaar/ui/common.py:58 #, python-brace-format -msgid "Failed to unpair %{device} from %{receiver}." -msgstr "取消 %{device} 到 %{receiver} 的配對失敗。" +msgid "Failed to unpair %{device} from %{receiver}." +msgstr "無法取消 %{device} 與 %{receiver} 的配對。" -#: lib/solaar/ui/__init__.py:67 -msgid "The receiver returned an error, with no further details." -msgstr "接收器出現錯誤,未提供詳細資訊。" +#: lib/solaar/ui/common.py:63 +msgid "The receiver returned an error, with no further details." +msgstr "接收器傳回錯誤,未提供進一步詳細資訊。" -#: lib/solaar/ui/__init__.py:177 -msgid "Another Solaar process is already running so just expose its window" -msgstr "其他的Solaar執行序已經運行了,所以顯示她的視窗。" +#: lib/solaar/ui/config_panel.py:245 +msgid "Complete - ENTER to change" +msgstr "完成 - 按 ENTER 鍵變更" -#: lib/solaar/ui/about.py:36 -msgid "Manages Logitech receivers,\n" - "keyboards, mice, and tablets." -msgstr "管理羅技接收器、鍵盤、滑鼠和平板。" +#: lib/solaar/ui/config_panel.py:245 +msgid "Incomplete" +msgstr "未完成" -#: lib/solaar/ui/about.py:44 -msgid "Additional Programming" -msgstr "額外程式化" - -#: lib/solaar/ui/about.py:45 -msgid "GUI design" -msgstr "介面設計" - -#: lib/solaar/ui/about.py:47 -msgid "Testing" -msgstr "軟體測試" - -#: lib/solaar/ui/about.py:54 -msgid "Logitech documentation" -msgstr "羅技文件" - -#: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 -#: lib/solaar/ui/window.py:197 -msgid "Unpair" -msgstr "取消配對" - -#: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 -msgid "Cancel" -msgstr "取消" - -#: lib/solaar/ui/config_panel.py:212 -msgid "Complete - ENTER to change" -msgstr "完成 - 按 ENTER 來修改" - -#: lib/solaar/ui/config_panel.py:212 -msgid "Incomplete" -msgstr "未完成" - -#: lib/solaar/ui/config_panel.py:455 lib/solaar/ui/config_panel.py:507 +#: lib/solaar/ui/config_panel.py:491 lib/solaar/ui/config_panel.py:543 #, python-format -msgid "%d value" -msgid_plural "%d values" -msgstr[0] "%d 值" +msgid "%d value" +msgid_plural "%d values" +msgstr[0] "%d 個值" -#: lib/solaar/ui/config_panel.py:518 -msgid "Changes allowed" -msgstr "允許更改" +#: lib/solaar/ui/config_panel.py:642 +msgid "Changes allowed" +msgstr "允許變更" -#: lib/solaar/ui/config_panel.py:519 -msgid "No changes allowed" -msgstr "不允許更改" +#: lib/solaar/ui/config_panel.py:643 +msgid "No changes allowed" +msgstr "不允許變更" -#: lib/solaar/ui/config_panel.py:520 -msgid "Ignore this setting" -msgstr "忽略此設定" +#: lib/solaar/ui/config_panel.py:644 +msgid "Ignore this setting" +msgstr "忽略此設定" -#: lib/solaar/ui/config_panel.py:565 -msgid "Working" -msgstr "工作中" +#: lib/solaar/ui/config_panel.py:690 +msgid "Working" +msgstr "工作中" -#: lib/solaar/ui/config_panel.py:568 -msgid "Read/write operation failed." -msgstr "讀/寫操作失敗。" +#: lib/solaar/ui/config_panel.py:693 +msgid "Read/write operation failed." +msgstr "讀取/寫入操作失敗。" -#: lib/solaar/ui/diversion_rules.py:65 -msgid "Built-in rules" -msgstr "內建規則" +#: lib/solaar/ui/desktop_notifications.py:112 +msgid "unspecified reason" +msgstr "未指定原因" -#: lib/solaar/ui/diversion_rules.py:65 -msgid "User-defined rules" -msgstr "使用者自定義規則" +#: lib/solaar/ui/diversion_rules.py:103 +msgid "Built-in rules" +msgstr "內建規則" -#: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1082 -msgid "Rule" -msgstr "規則" +#: lib/solaar/ui/diversion_rules.py:103 +msgid "User-defined rules" +msgstr "使用者定義規則" -#: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:509 -#: lib/solaar/ui/diversion_rules.py:635 -msgid "Sub-rule" -msgstr "子規則" +#: lib/solaar/ui/diversion_rules.py:105 lib/solaar/ui/diversion_rules.py:1126 +msgid "Rule" +msgstr "規則" -#: lib/solaar/ui/diversion_rules.py:70 -msgid "[empty]" -msgstr "[空]" +#: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:382 +#: lib/solaar/ui/diversion_rules.py:509 +msgid "Sub-rule" +msgstr "子規則" -#: lib/solaar/ui/diversion_rules.py:94 -msgid "Solaar Rule Editor" -msgstr "Solaar 規則編輯器" +#: lib/solaar/ui/diversion_rules.py:108 +msgid "[empty]" +msgstr "[空白]" -#: lib/solaar/ui/diversion_rules.py:141 -msgid "Make changes permanent?" -msgstr "使更改永久生效?" +#: lib/solaar/ui/diversion_rules.py:132 +msgid "Make changes permanent?" +msgstr "要永久套用變更嗎?" -#: lib/solaar/ui/diversion_rules.py:146 -msgid "Yes" -msgstr "是" +#: lib/solaar/ui/diversion_rules.py:137 +msgid "Yes" +msgstr "是" -#: lib/solaar/ui/diversion_rules.py:148 -msgid "No" -msgstr "否" +#: lib/solaar/ui/diversion_rules.py:139 +msgid "No" +msgstr "否" -#: lib/solaar/ui/diversion_rules.py:153 -msgid "If you choose No, changes will be lost when Solaar is closed." -msgstr "如果您選擇否,當 Solaar 關閉時,更改將會遺失。" +#: lib/solaar/ui/diversion_rules.py:144 +msgid "If you choose No, changes will be lost when Solaar is closed." +msgstr "若選擇否,關閉 Solaar 時將會遺失變更。" -#: lib/solaar/ui/diversion_rules.py:201 -msgid "Save changes" -msgstr "儲存修改" +#: lib/solaar/ui/diversion_rules.py:273 +msgid "Paste here" +msgstr "在此貼上" -#: lib/solaar/ui/diversion_rules.py:206 -msgid "Discard changes" -msgstr "不儲存修改" +#: lib/solaar/ui/diversion_rules.py:275 +msgid "Paste above" +msgstr "在上方貼上" -#: lib/solaar/ui/diversion_rules.py:372 -msgid "Insert here" -msgstr "在此插入" +#: lib/solaar/ui/diversion_rules.py:277 +msgid "Paste below" +msgstr "在下方貼上" -#: lib/solaar/ui/diversion_rules.py:374 -msgid "Insert above" -msgstr "在上方插入" +#: lib/solaar/ui/diversion_rules.py:283 +msgid "Paste rule here" +msgstr "在此貼上規則" -#: lib/solaar/ui/diversion_rules.py:376 -msgid "Insert below" -msgstr "在下方插入" +#: lib/solaar/ui/diversion_rules.py:285 +msgid "Paste rule above" +msgstr "在上方貼上規則" -#: lib/solaar/ui/diversion_rules.py:382 -msgid "Insert new rule here" -msgstr "在此插入新規則" +#: lib/solaar/ui/diversion_rules.py:287 +msgid "Paste rule below" +msgstr "在下方貼上規則" -#: lib/solaar/ui/diversion_rules.py:384 -msgid "Insert new rule above" -msgstr "在上方插入新規則" +#: lib/solaar/ui/diversion_rules.py:291 +msgid "Paste rule" +msgstr "貼上規則" + +#: lib/solaar/ui/diversion_rules.py:306 +msgid "Insert here" +msgstr "在此插入" + +#: lib/solaar/ui/diversion_rules.py:308 +msgid "Insert above" +msgstr "在上方插入" + +#: lib/solaar/ui/diversion_rules.py:310 +msgid "Insert below" +msgstr "在下方插入" + +#: lib/solaar/ui/diversion_rules.py:316 +msgid "Insert new rule here" +msgstr "在此插入新規則" + +#: lib/solaar/ui/diversion_rules.py:318 +msgid "Insert new rule above" +msgstr "在上方插入新規則" + +#: lib/solaar/ui/diversion_rules.py:320 +msgid "Insert new rule below" +msgstr "在下方插入新規則" + +#: lib/solaar/ui/diversion_rules.py:347 +msgid "Flatten" +msgstr "平面化" + +#: lib/solaar/ui/diversion_rules.py:380 +msgid "Insert" +msgstr "插入" + +#: lib/solaar/ui/diversion_rules.py:383 lib/solaar/ui/diversion_rules.py:511 +#: lib/solaar/ui/diversion_rules.py:1158 +msgid "Or" +msgstr "或" + +#: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:510 +#: lib/solaar/ui/diversion_rules.py:1144 +msgid "And" +msgstr "且" #: lib/solaar/ui/diversion_rules.py:386 -msgid "Insert new rule below" -msgstr "在下方插入新規則" +msgid "Condition" +msgstr "條件" -#: lib/solaar/ui/diversion_rules.py:427 -msgid "Paste here" -msgstr "在此貼上" +#: lib/solaar/ui/diversion_rules.py:388 lib/solaar/ui/rule_conditions.py:154 +msgid "Feature" +msgstr "功能" -#: lib/solaar/ui/diversion_rules.py:429 -msgid "Paste above" -msgstr "在上方貼上" +#: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:188 +msgid "Report" +msgstr "回報" -#: lib/solaar/ui/diversion_rules.py:431 -msgid "Paste below" -msgstr "在下方貼上" +#: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:69 +msgid "Process" +msgstr "處理程序" -#: lib/solaar/ui/diversion_rules.py:437 -msgid "Paste rule here" -msgstr "在此貼上規則" +#: lib/solaar/ui/diversion_rules.py:391 +msgid "Mouse process" +msgstr "滑鼠處理程序" -#: lib/solaar/ui/diversion_rules.py:439 -msgid "Paste rule above" -msgstr "在上方貼上規則" +#: lib/solaar/ui/diversion_rules.py:392 lib/solaar/ui/rule_conditions.py:225 +msgid "Modifiers" +msgstr "修飾鍵" + +#: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:277 +msgid "Key" +msgstr "按鍵" + +#: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:318 +msgid "KeyIsDown" +msgstr "按鍵處於按下狀態" + +#: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/diversion_rules.py:1432 +msgid "Active" +msgstr "使用中" + +#: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1390 +#: lib/solaar/ui/diversion_rules.py:1441 lib/solaar/ui/diversion_rules.py:1487 +msgid "Device" +msgstr "裝置" + +#: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1467 +msgid "Host" +msgstr "主機" + +#: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1506 +msgid "Setting" +msgstr "設定" + +#: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/rule_conditions.py:333 +#: lib/solaar/ui/rule_conditions.py:382 +msgid "Test" +msgstr "測試" + +#: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:507 +msgid "Test bytes" +msgstr "測試位元組" + +#: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:608 +msgid "Mouse Gesture" +msgstr "滑鼠手勢" + +#: lib/solaar/ui/diversion_rules.py:405 +msgid "Action" +msgstr "動作" + +#: lib/solaar/ui/diversion_rules.py:407 lib/solaar/ui/rule_actions.py:138 +msgid "Key press" +msgstr "按鍵按下" + +#: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:191 +msgid "Mouse scroll" +msgstr "滑鼠捲動" + +#: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:256 +msgid "Mouse click" +msgstr "滑鼠點選" + +#: lib/solaar/ui/diversion_rules.py:410 +msgid "Set" +msgstr "設定" + +#: lib/solaar/ui/diversion_rules.py:411 lib/solaar/ui/rule_actions.py:328 +msgid "Execute" +msgstr "執行" + +#: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/diversion_rules.py:1189 +msgid "Later" +msgstr "稍後" #: lib/solaar/ui/diversion_rules.py:441 -msgid "Paste rule below" -msgstr "在下方貼上規則" +msgid "Insert new rule" +msgstr "插入新規則" -#: lib/solaar/ui/diversion_rules.py:445 -msgid "Paste rule" -msgstr "貼上規則" +#: lib/solaar/ui/diversion_rules.py:461 lib/solaar/ui/rule_actions.py:82 +#: lib/solaar/ui/rule_actions.py:287 lib/solaar/ui/rule_conditions.py:556 +msgid "Delete" +msgstr "刪除" -#: lib/solaar/ui/diversion_rules.py:474 -msgid "Flatten" -msgstr "平坦化" +#: lib/solaar/ui/diversion_rules.py:483 +msgid "Negate" +msgstr "否定" #: lib/solaar/ui/diversion_rules.py:507 -msgid "Insert" -msgstr "插入" +msgid "Wrap with" +msgstr "包裝為" -#: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:637 -#: lib/solaar/ui/diversion_rules.py:1125 -msgid "Or" -msgstr "或" +#: lib/solaar/ui/diversion_rules.py:537 +msgid "Cut" +msgstr "剪下" -#: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:636 -#: lib/solaar/ui/diversion_rules.py:1110 -msgid "And" -msgstr "和" +#: lib/solaar/ui/diversion_rules.py:553 +msgid "Paste" +msgstr "貼上" -#: lib/solaar/ui/diversion_rules.py:513 -msgid "Condition" -msgstr "條件" +#: lib/solaar/ui/diversion_rules.py:559 +msgid "Copy" +msgstr "複製" -#: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1291 -msgid "Feature" -msgstr "功能" +#: lib/solaar/ui/diversion_rules.py:568 +msgid "Solaar Rule Editor" +msgstr "Solaar 規則編輯器" -#: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1327 -msgid "Report" -msgstr "報告" +#: lib/solaar/ui/diversion_rules.py:668 +msgid "Save changes" +msgstr "儲存變更" -#: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1203 -msgid "Process" -msgstr "處理" +#: lib/solaar/ui/diversion_rules.py:673 +msgid "Discard changes" +msgstr "捨棄變更" -#: lib/solaar/ui/diversion_rules.py:518 -msgid "Mouse process" -msgstr "滑鼠處理" +#: lib/solaar/ui/diversion_rules.py:1104 +msgid "This editor does not support the selected rule component yet." +msgstr "此編輯器尚未支援所選的規則元件。" -#: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1365 -msgid "Modifiers" -msgstr "修飾鍵" +#: lib/solaar/ui/diversion_rules.py:1169 +msgid "" +"Number of seconds to delay. Delay between 0 and 1 is done with higher " +"precision." +msgstr "延遲的秒數。0 到 1 秒之間的延遲會以較高的精確度執行。" -#: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1418 -msgid "Key" -msgstr "鍵" +#: lib/solaar/ui/diversion_rules.py:1207 +msgid "Not" +msgstr "非" -#: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1460 -msgid "KeyIsDown" -msgstr "按鍵處於按下狀態" +#: lib/solaar/ui/diversion_rules.py:1238 +msgid "Toggle" +msgstr "切換" -#: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2249 -msgid "Active" -msgstr "使用中" +#: lib/solaar/ui/diversion_rules.py:1239 +msgid "True" +msgstr "真" -#: lib/solaar/ui/diversion_rules.py:523 lib/solaar/ui/diversion_rules.py:2207 -#: lib/solaar/ui/diversion_rules.py:2259 lib/solaar/ui/diversion_rules.py:2281 -msgid "Device" -msgstr "裝置" +#: lib/solaar/ui/diversion_rules.py:1240 +msgid "False" +msgstr "假" -#: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 -msgid "Setting" -msgstr "設定" +#: lib/solaar/ui/diversion_rules.py:1253 +msgid "Unsupported setting" +msgstr "不支援的設定" -#: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:1476 -#: lib/solaar/ui/diversion_rules.py:1525 -msgid "Test" -msgstr "測試" +#: lib/solaar/ui/diversion_rules.py:1396 lib/solaar/ui/diversion_rules.py:1415 +#: lib/solaar/ui/diversion_rules.py:1493 lib/solaar/ui/diversion_rules.py:1748 +#: lib/solaar/ui/diversion_rules.py:1766 +msgid "Originating device" +msgstr "來源裝置" -#: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1642 -msgid "Test bytes" -msgstr "測試位元組" +#: lib/solaar/ui/diversion_rules.py:1428 +msgid "Device is active and its settings can be changed." +msgstr "裝置處於使用中狀態,可以變更其設定。" -#: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1735 -msgid "Mouse Gesture" -msgstr "滑鼠手勢" +#: lib/solaar/ui/diversion_rules.py:1437 +msgid "Device that originated the current notification." +msgstr "產生目前通知的裝置。" -#: lib/solaar/ui/diversion_rules.py:531 -msgid "Action" -msgstr "動作" +#: lib/solaar/ui/diversion_rules.py:1450 +msgid "Name of host computer." +msgstr "主機電腦名稱。" -#: lib/solaar/ui/diversion_rules.py:533 lib/solaar/ui/diversion_rules.py:1844 -msgid "Key press" -msgstr "按鍵按下" +#: lib/solaar/ui/diversion_rules.py:1520 +msgid "Value" +msgstr "值" -#: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1897 -msgid "Mouse scroll" -msgstr "滑鼠滾動" +#: lib/solaar/ui/diversion_rules.py:1529 +msgid "Item" +msgstr "項目" -#: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1948 -msgid "Mouse click" -msgstr "滑鼠點選" +#: lib/solaar/ui/diversion_rules.py:1808 +msgid "Change setting on device" +msgstr "變更裝置上的設定" -#: lib/solaar/ui/diversion_rules.py:536 -msgid "Set" -msgstr "設定" +#: lib/solaar/ui/diversion_rules.py:1824 +msgid "Setting on device" +msgstr "裝置上的設定" -#: lib/solaar/ui/diversion_rules.py:537 lib/solaar/ui/diversion_rules.py:2019 -msgid "Execute" -msgstr "執行" - -#: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:1157 -msgid "Later" -msgstr "稍後" - -#: lib/solaar/ui/diversion_rules.py:567 -msgid "Insert new rule" -msgstr "插入新規則" - -#: lib/solaar/ui/diversion_rules.py:587 lib/solaar/ui/diversion_rules.py:1685 -#: lib/solaar/ui/diversion_rules.py:1789 lib/solaar/ui/diversion_rules.py:1978 -msgid "Delete" -msgstr "刪除" - -#: lib/solaar/ui/diversion_rules.py:609 -msgid "Negate" -msgstr "否" - -#: lib/solaar/ui/diversion_rules.py:633 -msgid "Wrap with" -msgstr "包在一起" - -#: lib/solaar/ui/diversion_rules.py:655 -msgid "Cut" -msgstr "剪下" - -#: lib/solaar/ui/diversion_rules.py:670 -msgid "Paste" -msgstr "貼上" - -#: lib/solaar/ui/diversion_rules.py:682 -msgid "Copy" -msgstr "複製" - -#: lib/solaar/ui/diversion_rules.py:1062 -msgid "This editor does not support the selected rule component yet." -msgstr "此編輯器尚不支援所選規則元件。" - -#: lib/solaar/ui/diversion_rules.py:1137 -msgid "Number of seconds to delay." -msgstr "延遲的秒數。" - -#: lib/solaar/ui/diversion_rules.py:1176 -msgid "Not" -msgstr "非" - -#: lib/solaar/ui/diversion_rules.py:1186 -msgid "X11 active process. For use in X11 only." -msgstr "X11 活躍程序。僅適用於 X11。" - -#: lib/solaar/ui/diversion_rules.py:1217 -msgid "X11 mouse process. For use in X11 only." -msgstr "X11 滑鼠程序。僅適用於 X11。" - -#: lib/solaar/ui/diversion_rules.py:1234 -msgid "MouseProcess" -msgstr "滑鼠程序" - -#: lib/solaar/ui/diversion_rules.py:1259 -msgid "Feature name of notification triggering rule processing." -msgstr "觸發規則處理的通知功能名稱。" - -#: lib/solaar/ui/diversion_rules.py:1307 -msgid "Report number of notification triggering rule processing." -msgstr "報告觸發規則處理的通知數量。" - -#: lib/solaar/ui/diversion_rules.py:1341 -msgid "Active keyboard modifiers. Not always available in Wayland." -msgstr "活躍的鍵盤修飾鍵。在 Wayland 中並非總是可用。" - -#: lib/solaar/ui/diversion_rules.py:1382 -msgid "Diverted key or button depressed or released.\n" - "Use the Key/Button Diversion and Divert G Keys settings to divert " - "keys and buttons." -msgstr "轉向鍵或按鈕被按下或放開。\n" - "使用按鍵 / 按鈕轉向和轉向 G 鍵設定來轉向鍵和按鈕。" - -#: lib/solaar/ui/diversion_rules.py:1391 -msgid "Key down" -msgstr "按鍵按下" - -#: lib/solaar/ui/diversion_rules.py:1394 -msgid "Key up" -msgstr "按鍵鬆開" - -#: lib/solaar/ui/diversion_rules.py:1435 -msgid "Diverted key or button is currently down.\n" - "Use the Key/Button Diversion and Divert G Keys settings to divert " - "keys and buttons." -msgstr "轉向鍵或按鈕目前處於按下狀態。\n" - "使用鍵 / 按鈕轉向和轉向 G 鍵設定來轉向鍵和按鈕。" - -#: lib/solaar/ui/diversion_rules.py:1474 -msgid "Test condition on notification triggering rule processing." -msgstr "在通知觸發規則處理上測試條件。" - -#: lib/solaar/ui/diversion_rules.py:1478 -msgid "Parameter" -msgstr "參數" - -#: lib/solaar/ui/diversion_rules.py:1541 -msgid "begin (inclusive)" -msgstr "開始(包含)" - -#: lib/solaar/ui/diversion_rules.py:1542 -msgid "end (exclusive)" -msgstr "結束(不包含)" - -#: lib/solaar/ui/diversion_rules.py:1551 -msgid "range" -msgstr "範圍" - -#: lib/solaar/ui/diversion_rules.py:1553 -msgid "minimum" -msgstr "最小值" - -#: lib/solaar/ui/diversion_rules.py:1554 -msgid "maximum" -msgstr "最大值" - -#: lib/solaar/ui/diversion_rules.py:1556 +#: lib/solaar/ui/pair_window.py:44 lib/solaar/ui/pair_window.py:167 #, python-format -msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" -msgstr "位元組 %(0)d 到 %(1)d,範圍從 %(2)d 到 %(3)d" +msgid "%(receiver_name)s: pair new device" +msgstr "%(receiver_name)s:配對新的裝置" -#: lib/solaar/ui/diversion_rules.py:1561 -msgid "mask" -msgstr "遮罩" +#: lib/solaar/ui/pair_window.py:46 +msgid "Bolt receivers are only compatible with Bolt devices." +msgstr "Bolt 接收器僅相容於 Bolt 裝置。" -#: lib/solaar/ui/diversion_rules.py:1562 +#: lib/solaar/ui/pair_window.py:48 +msgid "Press a pairing button or key until the pairing light flashes quickly." +msgstr "請按住配對按鈕或按鍵,直到配對指示燈快速閃爍。" + +#: lib/solaar/ui/pair_window.py:51 +msgid "Unifying receivers are only compatible with Unifying devices." +msgstr "Unifying 接收器僅相容於 Unifying 裝置。" + +#: lib/solaar/ui/pair_window.py:53 +msgid "Other receivers are only compatible with a few devices." +msgstr "其他接收器僅支援少數裝置。" + +#: lib/solaar/ui/pair_window.py:55 +msgid "For most devices, turn on the device you want to pair." +msgstr "多數裝置只要開啟想要配對的裝置即可。" + +#: lib/solaar/ui/pair_window.py:56 +msgid "If the device is already turned on, turn it off and on again." +msgstr "若裝置已開啟,請將其關閉後再重新開啟。" + +#: lib/solaar/ui/pair_window.py:58 +msgid "The device must not be paired with a nearby powered-on receiver." +msgstr "裝置不得與附近已開啟的接收器配對。" + +#: lib/solaar/ui/pair_window.py:61 +msgid "" +"For devices with multiple channels, press, hold, and release the button for " +"the channel you wish to pair\n" +"or use the channel switch button to select a channel and then press, hold, " +"and release the channel switch button." +msgstr "" +"對於有多個通道的裝置,請按住並放開想要配對通道的按鈕,\n" +"或使用通道切換按鈕選擇通道後,再按住並放開通道切換按鈕。" + +#: lib/solaar/ui/pair_window.py:68 +msgid "The channel indicator light should be blinking rapidly." +msgstr "通道指示燈應會快速閃爍。" + +#: lib/solaar/ui/pair_window.py:72 #, python-format -msgid "bytes %(0)d to %(1)d, mask %(2)d" -msgstr "位元組 %(0)d 到 %(1)d,遮罩 %(2)d" +msgid "" +"\n" +"\n" +"This receiver has %d pairing remaining." +msgid_plural "" +"\n" +"\n" +"This receiver has %d pairings remaining." +msgstr[0] "" +"\n" +"\n" +"此接收器剩餘 %d 次配對。" -#: lib/solaar/ui/diversion_rules.py:1572 -msgid "Bit or range test on bytes in notification message triggering rule " - "processing." -msgstr "在通知訊息觸發規則處理中對位元組進行位元或範圍測試。" +#: lib/solaar/ui/pair_window.py:78 +msgid "" +"\n" +"Cancelling at this point will not use up a pairing." +msgstr "" +"\n" +"在此階段取消不會消耗配對次數。" -#: lib/solaar/ui/diversion_rules.py:1582 -msgid "type" -msgstr "類型" - -#: lib/solaar/ui/diversion_rules.py:1665 -msgid "Mouse gesture with optional initiating button followed by zero or " - "more mouse movements." -msgstr "滑鼠手勢,可選擇帶有初始按鈕,後跟零個或多個滑鼠移動。" - -#: lib/solaar/ui/diversion_rules.py:1670 -msgid "Add movement" -msgstr "新增移動" - -#: lib/solaar/ui/diversion_rules.py:1763 -msgid "Simulate a chorded key click or depress or release.\n" - "On Wayland requires write access to /dev/uinput." -msgstr "模擬成組按鍵點選或按下或放開。\n" - "在 Wayland 上需要對 /dev/uinput 進行寫入。" - -#: lib/solaar/ui/diversion_rules.py:1768 -msgid "Add key" -msgstr "新增按鍵" - -#: lib/solaar/ui/diversion_rules.py:1771 -msgid "Click" -msgstr "點選" - -#: lib/solaar/ui/diversion_rules.py:1774 -msgid "Depress" -msgstr "按下" - -#: lib/solaar/ui/diversion_rules.py:1777 -msgid "Release" -msgstr "放開" - -#: lib/solaar/ui/diversion_rules.py:1861 -msgid "Simulate a mouse scroll.\n" - "On Wayland requires write access to /dev/uinput." -msgstr "模擬滑鼠滾動。\n" - "在 Wayland 上需要對 /dev/uinput 進行寫入。" - -#: lib/solaar/ui/diversion_rules.py:1917 -msgid "Simulate a mouse click.\n" - "On Wayland requires write access to /dev/uinput." -msgstr "模擬滑鼠點選。\n" - "在 Wayland 上需要對 /dev/uinput 進行寫入。" - -#: lib/solaar/ui/diversion_rules.py:1920 -msgid "Button" -msgstr "按鈕" - -#: lib/solaar/ui/diversion_rules.py:1921 -msgid "Count" -msgstr "計數" - -#: lib/solaar/ui/diversion_rules.py:1961 -msgid "Execute a command with arguments." -msgstr "執行帶有參數的命令。" - -#: lib/solaar/ui/diversion_rules.py:1964 -msgid "Add argument" -msgstr "新增參數" - -#: lib/solaar/ui/diversion_rules.py:2039 -msgid "Toggle" -msgstr "切換" - -#: lib/solaar/ui/diversion_rules.py:2039 -msgid "True" -msgstr "真" - -#: lib/solaar/ui/diversion_rules.py:2040 -msgid "False" -msgstr "假" - -#: lib/solaar/ui/diversion_rules.py:2054 -msgid "Unsupported setting" -msgstr "不支援的設定" - -#: lib/solaar/ui/diversion_rules.py:2212 lib/solaar/ui/diversion_rules.py:2231 -#: lib/solaar/ui/diversion_rules.py:2286 lib/solaar/ui/diversion_rules.py:2528 -#: lib/solaar/ui/diversion_rules.py:2546 -msgid "Originating device" -msgstr "發起裝置" - -#: lib/solaar/ui/diversion_rules.py:2245 -msgid "Device is active and its settings can be changed." -msgstr "裝置處於活躍狀態,其設定可以更改。" - -#: lib/solaar/ui/diversion_rules.py:2255 -msgid "Device originated the current notification." -msgstr "裝置發起了當前通知。" - -#: lib/solaar/ui/diversion_rules.py:2305 -msgid "Value" -msgstr "數值" - -#: lib/solaar/ui/diversion_rules.py:2313 -msgid "Item" -msgstr "項目" - -#: lib/solaar/ui/diversion_rules.py:2588 -msgid "Change setting on device" -msgstr "更改裝置上的設定" - -#: lib/solaar/ui/diversion_rules.py:2605 -msgid "Setting on device" -msgstr "裝置上的設定" - -#: lib/solaar/ui/notify.py:124 lib/solaar/ui/tray.py:320 -#: lib/solaar/ui/tray.py:325 lib/solaar/ui/window.py:739 -msgid "offline" -msgstr "離線" - -#: lib/solaar/ui/pair_window.py:122 lib/solaar/ui/pair_window.py:256 -#: lib/solaar/ui/pair_window.py:288 +#: lib/solaar/ui/pair_window.py:168 #, python-format -msgid "%(receiver_name)s: pair new device" -msgstr "%(receiver_name)s:配對新的裝置" +msgid "Enter passcode on %(name)s." +msgstr "請在 %(name)s 上輸入密碼。" -#: lib/solaar/ui/pair_window.py:123 +#: lib/solaar/ui/pair_window.py:171 #, python-format -msgid "Enter passcode on %(name)s." -msgstr "在 %(name)s 上輸入密碼。" +msgid "Type %(passcode)s and then press the enter key." +msgstr "請輸入 %(passcode)s,然後按 Enter 鍵。" -#: lib/solaar/ui/pair_window.py:126 +#: lib/solaar/ui/pair_window.py:176 +msgid "left" +msgstr "左" + +#: lib/solaar/ui/pair_window.py:176 +msgid "right" +msgstr "右" + +#: lib/solaar/ui/pair_window.py:178 #, python-format -msgid "Type %(passcode)s and then press the enter key." -msgstr "輸入 %(passcode)s,然後按 Enter 鍵。" +msgid "" +"Press %(code)s\n" +"and then press left and right buttons simultaneously." +msgstr "" +"請按下 %(code)s\n" +"然後同時按下左鍵和右鍵。" -#: lib/solaar/ui/pair_window.py:129 -msgid "left" -msgstr "左" +#: lib/solaar/ui/pair_window.py:221 +msgid "The wireless link is not encrypted" +msgstr "無線連線未加密" -#: lib/solaar/ui/pair_window.py:129 -msgid "right" -msgstr "右" +#: lib/solaar/ui/pair_window.py:226 +msgid "Found a new device:" +msgstr "發現一個新裝置:" -#: lib/solaar/ui/pair_window.py:131 +#: lib/solaar/ui/pair_window.py:247 +msgid "Pairing failed" +msgstr "配對失敗" + +#: lib/solaar/ui/pair_window.py:249 +msgid "Make sure your device is within range, and has a decent battery charge." +msgstr "請確認裝置在範圍內,且電池電量充足。" + +#: lib/solaar/ui/pair_window.py:251 +msgid "A new device was detected, but it is not compatible with this receiver." +msgstr "偵測到新裝置,但與此接收器不相容。" + +#: lib/solaar/ui/pair_window.py:253 +msgid "More paired devices than receiver can support." +msgstr "已配對的裝置數量超過接收器可支援的數量。" + +#: lib/solaar/ui/pair_window.py:255 +msgid "No further details are available about the error." +msgstr "沒有關於此錯誤的進一步詳細資訊。" + +#: lib/solaar/ui/rule_actions.py:54 +msgid "" +"Simulate a chorded key click or depress or release.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"模擬組合鍵點選或按下或放開。\n" +"在 Wayland 上需要對 /dev/uinput 的寫入權限。" + +#: lib/solaar/ui/rule_actions.py:60 +msgid "Add key" +msgstr "新增按鍵" + +#: lib/solaar/ui/rule_actions.py:63 +msgid "Click" +msgstr "點選" + +#: lib/solaar/ui/rule_actions.py:66 +msgid "Depress" +msgstr "按下" + +#: lib/solaar/ui/rule_actions.py:69 +msgid "Release" +msgstr "放開" + +#: lib/solaar/ui/rule_actions.py:153 +msgid "" +"Simulate a mouse scroll.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"模擬滑鼠捲動。\n" +"在 Wayland 上需要對 /dev/uinput 的寫入權限。" + +#: lib/solaar/ui/rule_actions.py:211 +msgid "" +"Simulate a mouse click.\n" +"On Wayland requires write access to /dev/uinput." +msgstr "" +"模擬滑鼠點選。\n" +"在 Wayland 上需要對 /dev/uinput 的寫入權限。" + +#: lib/solaar/ui/rule_actions.py:216 +msgid "Button" +msgstr "按鈕" + +#: lib/solaar/ui/rule_actions.py:218 +msgid "Action (and Count, if click)" +msgstr "動作(若為點選則含次數)" + +#: lib/solaar/ui/rule_actions.py:269 +msgid "Execute a command with arguments." +msgstr "執行帶有參數的命令。" + +#: lib/solaar/ui/rule_actions.py:273 +msgid "Add argument" +msgstr "新增參數" + +#: lib/solaar/ui/rule_conditions.py:52 +msgid "X11 active process. For use in X11 only." +msgstr "X11 使用中處理程序。僅適用於 X11。" + +#: lib/solaar/ui/rule_conditions.py:82 +msgid "X11 mouse process. For use in X11 only." +msgstr "X11 滑鼠處理程序。僅適用於 X11。" + +#: lib/solaar/ui/rule_conditions.py:99 +msgid "MouseProcess" +msgstr "滑鼠處理程序" + +#: lib/solaar/ui/rule_conditions.py:123 +msgid "Feature name of notification triggering rule processing." +msgstr "觸發規則處理的通知功能名稱。" + +#: lib/solaar/ui/rule_conditions.py:169 +msgid "Report number of notification triggering rule processing." +msgstr "回報觸發規則處理的通知數量。" + +#: lib/solaar/ui/rule_conditions.py:201 +msgid "Active keyboard modifiers. Not always available in Wayland." +msgstr "使用中的鍵盤修飾鍵。在 Wayland 中並非總是可用。" + +#: lib/solaar/ui/rule_conditions.py:241 +msgid "" +"Diverted key or button depressed or released.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"轉向按鍵或按鈕被按下或放開。\n" +"使用按鍵/按鈕轉向和轉向 G 鍵設定來轉向按鍵和按鈕。" + +#: lib/solaar/ui/rule_conditions.py:250 +msgid "Key down" +msgstr "按鍵按下" + +#: lib/solaar/ui/rule_conditions.py:253 +msgid "Key up" +msgstr "按鍵鬆開" + +#: lib/solaar/ui/rule_conditions.py:293 +msgid "" +"Diverted key or button is currently down.\n" +"Use the Key/Button Diversion and Divert G Keys settings to divert keys and " +"buttons." +msgstr "" +"轉向按鍵或按鈕目前處於按下狀態。\n" +"使用按鍵/按鈕轉向和轉向 G 鍵設定來轉向按鍵和按鈕。" + +#: lib/solaar/ui/rule_conditions.py:331 +msgid "Test condition on notification triggering rule processing." +msgstr "在通知觸發規則處理上測試條件。" + +#: lib/solaar/ui/rule_conditions.py:335 +msgid "Parameter" +msgstr "參數" + +#: lib/solaar/ui/rule_conditions.py:408 +msgid "begin (inclusive)" +msgstr "開始(包含)" + +#: lib/solaar/ui/rule_conditions.py:409 +msgid "end (exclusive)" +msgstr "結束(不包含)" + +#: lib/solaar/ui/rule_conditions.py:417 +msgid "range" +msgstr "範圍" + +#: lib/solaar/ui/rule_conditions.py:420 +msgid "minimum" +msgstr "最小值" + +#: lib/solaar/ui/rule_conditions.py:421 +msgid "maximum" +msgstr "最大值" + +#: lib/solaar/ui/rule_conditions.py:423 #, python-format -msgid "Press %(code)s\n" - "and then press left and right buttons simultaneously." -msgstr "按下 %(code)s\n" - "然後同時按下左鍵和右鍵。" +msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" +msgstr "位元組 %(0)d 到 %(1)d,範圍從 %(2)d 到 %(3)d" -#: lib/solaar/ui/pair_window.py:188 -msgid "Pairing failed" -msgstr "配對失敗" +#: lib/solaar/ui/rule_conditions.py:426 lib/solaar/ui/rule_conditions.py:427 +msgid "mask" +msgstr "遮罩" -#: lib/solaar/ui/pair_window.py:190 -msgid "Make sure your device is within range, and has a decent battery " - "charge." -msgstr "請確認您的裝置已在範圍內,並保證其電量充足。" - -#: lib/solaar/ui/pair_window.py:192 -msgid "A new device was detected, but it is not compatible with this " - "receiver." -msgstr "偵測到 1 個新裝置,但與此接收器不相容。" - -#: lib/solaar/ui/pair_window.py:194 -msgid "More paired devices than receiver can support." -msgstr "配對的裝置數量超過接收器的支援範圍。" - -#: lib/solaar/ui/pair_window.py:196 -msgid "No further details are available about the error." -msgstr "無此錯誤的詳細資訊。" - -#: lib/solaar/ui/pair_window.py:210 -msgid "Found a new device:" -msgstr "發現一個新裝置:" - -#: lib/solaar/ui/pair_window.py:235 -msgid "The wireless link is not encrypted" -msgstr "無線連線未加密" - -#: lib/solaar/ui/pair_window.py:264 -msgid "Unifying receivers are only compatible with Unifying devices." -msgstr "Unifying 接收器只支援 Unifying 裝置。" - -#: lib/solaar/ui/pair_window.py:266 -msgid "Bolt receivers are only compatible with Bolt devices." -msgstr "Bolt 接收器只支援 Bolt 裝置。" - -#: lib/solaar/ui/pair_window.py:268 -msgid "Other receivers are only compatible with a few devices." -msgstr "其他接收器僅支援少數裝置。" - -#: lib/solaar/ui/pair_window.py:270 -msgid "The device must not be paired with a nearby powered-on receiver." -msgstr "裝置不應與附近已開啟的接收器配對。" - -#: lib/solaar/ui/pair_window.py:274 -msgid "Press a pairing button or key until the pairing light flashes " - "quickly." -msgstr "按下配對按鈕或按鍵,直到配對燈快速閃爍。" - -#: lib/solaar/ui/pair_window.py:276 -msgid "You may have to first turn the device off and on again." -msgstr "您可能需要先關閉裝置,然後再開啟。" - -#: lib/solaar/ui/pair_window.py:278 -msgid "Turn on the device you want to pair." -msgstr "開啟您要配對的裝置。" - -#: lib/solaar/ui/pair_window.py:280 -msgid "If the device is already turned on, turn it off and on again." -msgstr "如果裝置已開啟,請將其關閉並重新開啟。" - -#: lib/solaar/ui/pair_window.py:283 +#: lib/solaar/ui/rule_conditions.py:428 #, python-format -msgid "\n" - "\n" - "This receiver has %d pairing remaining." -msgid_plural "\n" - "\n" - "This receiver has %d pairings remaining." -msgstr[0] "\n" - "\n" - "此接收器剩餘 %d 次配對。" +msgid "bytes %(0)d to %(1)d, mask %(2)d" +msgstr "位元組 %(0)d 到 %(1)d,遮罩 %(2)d" -#: lib/solaar/ui/pair_window.py:286 -msgid "\n" - "Cancelling at this point will not use up a pairing." -msgstr "\n" - "在此階段取消不會消耗配對次數。" +#: lib/solaar/ui/rule_conditions.py:437 +msgid "" +"Bit or range test on bytes in notification message triggering rule " +"processing." +msgstr "在通知訊息觸發規則處理中對位元組進行位元或範圍測試。" -#: lib/solaar/ui/tray.py:58 -msgid "No Logitech device found" -msgstr "沒有找到羅技的裝置" +#: lib/solaar/ui/rule_conditions.py:447 +msgid "type" +msgstr "類型" -#: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 +#: lib/solaar/ui/rule_conditions.py:535 +msgid "" +"Mouse gesture with optional initiating button followed by zero or more mouse " +"movements." +msgstr "滑鼠手勢,可選擇性地帶有起始按鈕,後接零個或多個滑鼠移動。" + +#: lib/solaar/ui/rule_conditions.py:541 +msgid "Add movement" +msgstr "新增移動" + +#: lib/solaar/ui/tray.py:55 +msgid "No supported device found" +msgstr "找不到支援的裝置" + +#: lib/solaar/ui/tray.py:60 lib/solaar/ui/window.py:328 #, python-format -msgid "About %s" -msgstr "關於 %s" +msgid "About %s" +msgstr "關於 %s" -#: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 +#: lib/solaar/ui/tray.py:61 lib/solaar/ui/window.py:326 #, python-format -msgid "Quit %s" -msgstr "結束 %s" +msgid "Quit %s" +msgstr "結束 %s" -#: lib/solaar/ui/tray.py:299 lib/solaar/ui/tray.py:307 -msgid "no receiver" -msgstr "無接收器" +#: lib/solaar/ui/tray.py:273 lib/solaar/ui/tray.py:281 +msgid "no receiver" +msgstr "無接收器" -#: lib/solaar/ui/tray.py:323 -msgid "no status" -msgstr "無狀態" +#: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:299 +#: lib/solaar/ui/window.py:728 +msgid "offline" +msgstr "離線" -#: lib/solaar/ui/window.py:96 -msgid "Scanning" -msgstr "掃描中" +#: lib/solaar/ui/tray.py:297 +msgid "no status" +msgstr "無狀態" -#: lib/solaar/ui/window.py:129 -msgid "Battery" -msgstr "電池" +#: lib/solaar/ui/window.py:110 +msgid "Scanning" +msgstr "掃描中" -#: lib/solaar/ui/window.py:132 -msgid "Wireless Link" -msgstr "無線連線" +#: lib/solaar/ui/window.py:141 +msgid "Battery" +msgstr "電池" -#: lib/solaar/ui/window.py:136 -msgid "Lighting" -msgstr "亮度" +#: lib/solaar/ui/window.py:144 +msgid "Wireless Link" +msgstr "無線連線" -#: lib/solaar/ui/window.py:170 -msgid "Show Technical Details" -msgstr "顯示細節" +#: lib/solaar/ui/window.py:148 +msgid "Lighting" +msgstr "亮度" -#: lib/solaar/ui/window.py:186 -msgid "Pair new device" -msgstr "配對新裝置" +#: lib/solaar/ui/window.py:182 +msgid "Show Technical Details" +msgstr "顯示技術細節" -#: lib/solaar/ui/window.py:205 -msgid "Select a device" -msgstr "選擇 1 個裝置" +#: lib/solaar/ui/window.py:198 +msgid "Pair new device" +msgstr "配對新裝置" -#: lib/solaar/ui/window.py:322 -msgid "Rule Editor" -msgstr "規則編輯器" +#: lib/solaar/ui/window.py:216 +msgid "Select a device" +msgstr "請選擇裝置" + +#: lib/solaar/ui/window.py:331 +msgid "Rule Editor" +msgstr "規則編輯器" + +#: lib/solaar/ui/window.py:522 +msgid "Path" +msgstr "路徑" + +#: lib/solaar/ui/window.py:524 +msgid "USB ID" +msgstr "USB ID" + +#: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 +#: lib/solaar/ui/window.py:544 lib/solaar/ui/window.py:546 +msgid "Serial" +msgstr "序號" #: lib/solaar/ui/window.py:533 -msgid "Path" -msgstr "路徑" +msgid "Index" +msgstr "索引" -#: lib/solaar/ui/window.py:536 -msgid "USB ID" -msgstr "USB ID" +#: lib/solaar/ui/window.py:535 +msgid "Wireless PID" +msgstr "無線 PID" -#: lib/solaar/ui/window.py:539 lib/solaar/ui/window.py:541 -#: lib/solaar/ui/window.py:561 lib/solaar/ui/window.py:563 -msgid "Serial" -msgstr "序號" +#: lib/solaar/ui/window.py:537 +msgid "Product ID" +msgstr "產品 ID" -#: lib/solaar/ui/window.py:545 -msgid "Index" -msgstr "索引" +#: lib/solaar/ui/window.py:539 +msgid "Protocol" +msgstr "通訊協定" -#: lib/solaar/ui/window.py:547 -msgid "Wireless PID" -msgstr "無線 PID" +#: lib/solaar/ui/window.py:539 +msgid "Unknown" +msgstr "不明" -#: lib/solaar/ui/window.py:549 -msgid "Product ID" -msgstr "產品 ID" +#: lib/solaar/ui/window.py:541 +msgid "Polling rate" +msgstr "輪詢速率" -#: lib/solaar/ui/window.py:551 -msgid "Protocol" -msgstr "通訊協定" +#: lib/solaar/ui/window.py:548 +msgid "Unit ID" +msgstr "單位 ID" -#: lib/solaar/ui/window.py:551 -msgid "Unknown" -msgstr "不明" +#: lib/solaar/ui/window.py:559 +msgid "none" +msgstr "無" -#: lib/solaar/ui/window.py:554 +#: lib/solaar/ui/window.py:560 +msgid "Notifications" +msgstr "通知" + +#: lib/solaar/ui/window.py:604 +msgid "No device paired." +msgstr "沒有已配對的裝置。" + +#: lib/solaar/ui/window.py:613 #, python-format -msgid "%(rate)d ms (%(rate_hz)dHz)" -msgstr "%(rate)d ms (%(rate_hz)dHz)" +msgid "Up to %(max_count)s device can be paired to this receiver." +msgid_plural "Up to %(max_count)s devices can be paired to this receiver." +msgstr[0] "此接收器最多可配對 %(max_count)s 個裝置。" -#: lib/solaar/ui/window.py:554 -msgid "Polling rate" -msgstr "查詢速率" +#: lib/solaar/ui/window.py:620 +msgid "Only one device can be paired to this receiver." +msgstr "此接收器只能配對一個裝置" -#: lib/solaar/ui/window.py:565 -msgid "Unit ID" -msgstr "單位 ID" - -#: lib/solaar/ui/window.py:576 -msgid "none" -msgstr "無" - -#: lib/solaar/ui/window.py:577 -msgid "Notifications" -msgstr "通知" - -#: lib/solaar/ui/window.py:621 -msgid "No device paired." -msgstr "沒有已經配對的裝置" - -#: lib/solaar/ui/window.py:628 -#, fuzzy, python-format -msgid "Up to %(max_count)s device can be paired to this receiver." -msgid_plural "Up to %(max_count)s devices can be paired to this receiver." -msgstr[0] "此接收器最多可以配對到 %(max_count)s 個裝置" - -#: lib/solaar/ui/window.py:634 -msgid "Only one device can be paired to this receiver." -msgstr "此接收器只能配對一個裝置" - -#: lib/solaar/ui/window.py:638 +#: lib/solaar/ui/window.py:624 #, python-format -msgid "This receiver has %d pairing remaining." -msgid_plural "This receiver has %d pairings remaining." -msgstr[0] "此接收器剩餘 %d 次配對。" +msgid "This receiver has %d pairing remaining." +msgid_plural "This receiver has %d pairings remaining." +msgstr[0] "此接收器剩餘 %d 次配對。" -#: lib/solaar/ui/window.py:692 -msgid "Battery Voltage" -msgstr "電池電壓" +#: lib/solaar/ui/window.py:681 +msgid "Battery Voltage" +msgstr "電池電壓" -#: lib/solaar/ui/window.py:694 -msgid "Voltage reported by battery" -msgstr "電池報告的電壓" +#: lib/solaar/ui/window.py:683 +msgid "Voltage reported by battery" +msgstr "電池回報的電壓" -#: lib/solaar/ui/window.py:696 -msgid "Battery Level" -msgstr "電池電量" +#: lib/solaar/ui/window.py:685 +msgid "Battery Level" +msgstr "電池電量" -#: lib/solaar/ui/window.py:698 -msgid "Approximate level reported by battery" -msgstr "電池報告的大約電量" +#: lib/solaar/ui/window.py:687 +msgid "Approximate level reported by battery" +msgstr "電池回報的大約電量" -#: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 -msgid "next reported " -msgstr "下一次報告的 " +#: lib/solaar/ui/window.py:694 lib/solaar/ui/window.py:696 +msgid "next reported " +msgstr "下一次回報的 " -#: lib/solaar/ui/window.py:708 -msgid " and next level to be reported." -msgstr " 以及下一個將被報告的等級。" +#: lib/solaar/ui/window.py:697 +msgid " and next level to be reported." +msgstr " 以及下一個將回報的等級。" + +#: lib/solaar/ui/window.py:702 +msgid "last known" +msgstr "最後已知" #: lib/solaar/ui/window.py:713 -msgid "last known" -msgstr "最後已知" +msgid "encrypted" +msgstr "已加密" -#: lib/solaar/ui/window.py:724 -msgid "encrypted" -msgstr "已加密" +#: lib/solaar/ui/window.py:715 +msgid "The wireless link between this device and its receiver is encrypted." +msgstr "此裝置與其接收器之間的無線連線已加密。" -#: lib/solaar/ui/window.py:726 -msgid "The wireless link between this device and its receiver is encrypted." -msgstr "裝置與接收器間的無線連線已加密。" +#: lib/solaar/ui/window.py:717 +msgid "not encrypted" +msgstr "未加密" -#: lib/solaar/ui/window.py:728 -msgid "not encrypted" -msgstr "未加密" +#: lib/solaar/ui/window.py:721 +msgid "" +"The wireless link between this device and its receiver is not encrypted.\n" +"This is a security issue for pointing devices, and a major security issue " +"for text-input devices." +msgstr "" +"此裝置與其接收器之間的無線連線未加密。\n" +"這對指向型裝置僅是安全性問題,對文字輸入裝置則是重大的安全性問題。" -#: lib/solaar/ui/window.py:732 -msgid "The wireless link between this device and its receiver is not " - "encrypted.\n" - "This is a security issue for pointing devices, and a major security " - "issue for text-input devices." -msgstr "此裝置與其接收器之間的無線連接未加密。\n" - "這對指向性裝置是一個安全問題,對文字輸入裝置則是一個重大的安全問題。" - -#: lib/solaar/ui/window.py:748 +#: lib/solaar/ui/window.py:737 #, python-format -msgid "%(light_level)d lux" -msgstr "%(light_level)d lux" - -#~ msgid "top" -#~ msgstr "上" - -#~ msgid "width" -#~ msgstr "寬度" - -#~ msgid "height" -#~ msgstr "高度" - -#~ msgid "About" -#~ msgstr "關於" - -#~ msgid "No Logitech receiver found" -#~ msgstr "未發現羅技接收器" - -#~ msgid "Quit" -#~ msgstr "退出" - -#~ msgid "Smooth Scrolling" -#~ msgstr "平滑滾動" - -#~ msgid "High Resolution Wheel Invert" -#~ msgstr "高解析度滾輪反轉" - -#~ msgid "High-sensitivity wheel invert mode for vertical scroll." -#~ msgstr "垂直捲動的高解析度反轉模式" - -#~ msgid "Wheel Resolution" -#~ msgstr "滾輪解析度" - -#~ msgid "Automatically switch the mouse wheel between ratchet and " -#~ "freespin mode.\n" -#~ "The mouse wheel is always free at 0, and always locked at 50" -#~ msgstr "自動切換滑鼠滾輪的段落模式" - -#~ msgid "The receiver was unplugged." -#~ msgstr "接收器已移除。" - -#~ msgid "Shows status of devices connected\n" -#~ "through wireless Logitech receivers." -#~ msgstr "顯示羅技接收器\n" -#~ "已連線的裝置狀態。" - -#~ msgid "The receiver only supports %d paired device(s)." -#~ msgstr "此接收器僅支援 %d 個配對裝置。" - -#~ msgid "If the device is already turned on,\n" -#~ "turn if off and on again." -#~ msgstr "若裝置的電源已經開啟,\n" -#~ "請先關閉再開啟。" - -#~ msgid "USB id" -#~ msgstr "USB ID" - -#~ msgid "%(battery_percent)d%%" -#~ msgstr "%(battery_percent)d%%" - -#~ msgid "%(battery_level)s" -#~ msgstr "%(battery_level)s" - -#~ msgid "The wireless link between this device and its receiver is " -#~ "not encrypted.\n" -#~ "\n" -#~ "For pointing devices (mice, trackballs, trackpads), this is a minor " -#~ "security issue.\n" -#~ "\n" -#~ "It is, however, a major security issue for text-input devices " -#~ "(keyboards, numpads),\n" -#~ "because typed text can be sniffed inconspicuously by 3rd parties " -#~ "within range." -#~ msgstr "裝置與接收器間的無線連線未加密。\n" -#~ "\n" -#~ "對定位裝置(如滑鼠、軌跡球、觸控板)來說這不是什麼問題。\n" -#~ "\n" -#~ "然而這對於內容輸入裝置(如鍵盤、數字鍵盤)卻是一個重大的安全問題,\n" -#~ "您輸入的內容有可能被第三方偷偷監聽到。" - -#~ msgid "Battery information unknown." -#~ msgstr "電池狀態是未知的" - -#~ msgid "unknown" -#~ msgstr "未知的" - -#~ msgid "Add action" -#~ msgstr "增加動作" - -#~ msgid "Try removing the device and plugging it back in or turning " -#~ "it off and then on." -#~ msgstr "嘗試移除裝置並將其插回,或是關閉然後開啟。" - -#~ msgid "If you've just installed Solaar, try removing the receiver " -#~ "and plugging it back in." -#~ msgstr "若您剛剛安裝 Solaar,請嘗試將接收器拔下再重新插上。" - -#~ msgid "Found a Logitech Receiver (%s), but did not have permission " -#~ "to open it." -#~ msgstr "已發現 1 個羅技接收器 (%s),但您沒有權限存取。" - -#~ msgid "Send a gesture by sliding the mouse while holding the button " -#~ "down." -#~ msgstr "按住按鈕時,透過滑動滑鼠來發送手勢。" - -#~ msgid "Adjust the DPI by sliding the mouse horizontally while " -#~ "holding the button down." -#~ msgstr "按住按鈕時,透過水平滑動滑鼠來調整 DPI。" - -#~ msgid "DPI Sliding Adjustment" -#~ msgstr "DPI 滑動調整" - -#~ msgid "Automatically switch the mouse wheel between ratchet and " -#~ "freespin mode.\n" -#~ "The mouse wheel is always free at 0, and always ratcheted at 50" -#~ msgstr "自動在滾輪棘輪模式和自由滾動模式之間切換滑鼠滾輪。\n" -#~ "滾輪在 0 時始終處於自由狀態,而在 50 時始終處於棘輪狀態" - -#~ msgid "Scroll Wheel Rachet" -#~ msgstr "滾輪棘輪" +msgid "%(light_level)d lux" +msgstr "%(light_level)d lux" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..b1cfdfad --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.ruff] +line-length = 127 +target-version = "py37" + +[tool.ruff.lint] +select = [ + "F", # Pyflakes + "E", # pycodestyle + "W", # pycodestyle + "B", # flake8-bugbear + "I", # isort +] + +[tool.ruff.lint.isort] +force-single-line = true +lines-between-types = 1 diff --git a/release.sh b/release.sh index bcd7806f..02c48ae5 100755 --- a/release.sh +++ b/release.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash repo=pwr-Solaar/Solaar @@ -43,6 +43,8 @@ prerelease=false echo $version | grep '.*rc.*' >/dev/null [ $? -eq 0 ] && prerelease=true +stable_branch=stable + ref=$(git symbolic-ref HEAD) [ $? -ne 0 ] && echo 'Error: Failed current branch' && exit 1 branch=${ref##*/} @@ -86,7 +88,7 @@ echo [[ ! $REPLY =~ ^[Yy]$ ]] && echo 'Release aborted.' && exit 1 # Check if version is in the changelog -grep '^# '"$version" ChangeLog.md >/dev/null +grep '^# '"$version" CHANGELOG.md >/dev/null [ $? -ne 0 ] && echo 'Error: Version is not present in the changelog' && exit 1 # Check for uncommitted changes @@ -119,11 +121,11 @@ echo 'Creating tag...' echo found=no while read -r line; do - if [[ "$line" == *: ]]; then - [ "$line" == "$version:" ] && found=yes || found=no + if [[ "$line" == "# "* ]]; then + [ "$line" == "# $version" ] && found=yes || found=no fi [ "$found" == 'yes' ] && [ "${line:0:1}" == '*' ] && echo "$line" - done < ChangeLog.md + done < CHANGELOG.md } > /tmp/solaar-changelog [ -z "$DRY_RUN" ] && git tag -s $version -F /tmp/solaar-changelog >/dev/null || true [ $? -ne 0 ] && echo -e '\nError: Failed to create tag' && exit 1 @@ -133,6 +135,20 @@ echo 'Pushing tag...' [ -z "$DRY_RUN" ] && git push $remote $version >/dev/null || true [ $? -ne 0 ] && echo -e '\nError: Failed to push tag' && exit 1 +# Point stable branch to latest version tag +echo 'Updating stable branch...' +if [[ -z "$DRY_RUN" && $prerelease == "false" ]] +then + # Check if stable branch does not exist + git rev-list --max-count=1 $stable_branch >/dev/null 2>/dev/null + [ $? -ne 0 ] && echo -e '\nWarning: Creating stable branch for a first time' && git branch $stable_branch + # Fast forward and push stable branch + git checkout $stable_branch + git merge --ff $version + git push $remote $stable_branch >/dev/null || true + git checkout $branch +fi + # Create github release body() { cat <= 2.0)', 'gi.repository.Gtk (>= 3.0)'], - python_requires='>=3.7', + platforms=["linux"], + python_requires=">=3.8", install_requires=[ 'evdev (>= 1.1.2) ; platform_system=="Linux"', - 'pyudev (>= 0.13)', - 'PyYAML (>= 3.12)', - 'python-xlib (>= 0.27)', - 'psutil (>= 5.4.3)', - 'dbus-python (>=1.3.2)', + "pyudev (>= 0.13)", + "PyYAML (>= 3.12)", + "python-xlib (>= 0.27)", + "psutil (>= 5.4.3)", + 'dbus-python ; platform_system=="Linux"', + "PyGObject", + "pycairo", + "typing_extensions", ], extras_require={ - 'report-descriptor': ['hid-parser'], - 'desktop-notifications': ['Notify (>= 0.7)'], - 'git-commit': ['python-git-info'], + "report-descriptor": ["hid-parser"], + "desktop-notifications": ["Notify (>= 0.7)"], + "git-commit": ["python-git-info"], + "test": ["pytest", "pytest-mock", "pytest-cov"], + "dev": ["ruff"], }, - package_dir={'': 'lib'}, - packages=['keysyms', 'hidapi', 'logitech_receiver', 'solaar', 'solaar.ui', 'solaar.cli'], + package_dir={"": "lib"}, + packages=find_packages(where="lib"), data_files=list(_data_files()), include_package_data=True, - scripts=_glob('bin/*'), + entry_points={ + "console_scripts": [ + "solaar = solaar.gtk:main", + ], + }, ) diff --git a/share/applications/solaar.desktop b/share/applications/solaar.desktop index 8f11e206..726d151c 100644 --- a/share/applications/solaar.desktop +++ b/share/applications/solaar.desktop @@ -4,7 +4,13 @@ Comment=Logitech Unifying Receiver peripherals manager Comment[fr]=Gestionnaire de périphériques pour les récepteurs Logitech Unifying Comment[hr]=Upravitelj Logitechovih uređaja povezanih putem Unifying i Nano prijemnika Comment[ru]=Управление приёмником Logitech Unifying Receiver +Comment[de]=Logitech Unifying Empfänger Geräteverwaltung Comment[es]=Administrador de periféricos de Logitech Receptor Unifying +Comment[pl]=Menedżer urządzeń peryferyjnych odbiornika Logitech Unifying +Comment[sv]=Kringutrustningshanterare för Logitech Unifying-mottagare +Comment[zh_CN]=罗技优联设备管理器 +Comment[zh_TW]=羅技Unifying 裝置管理器 +Comment[zh_HK]=羅技Unifying 裝置管理器 Exec=solaar Icon=solaar StartupNotify=true diff --git a/share/autostart/solaar.desktop b/share/autostart/solaar.desktop index 76208724..328d9245 100644 --- a/share/autostart/solaar.desktop +++ b/share/autostart/solaar.desktop @@ -7,6 +7,10 @@ Comment[ru]=Управление приёмником Logitech Unifying Receiver Comment[de]=Logitech Unifying Empfänger Geräteverwaltung Comment[es]=Administrador de periféricos de Logitech Receptor Unifying Comment[pl]=Menedżer urządzeń peryferyjnych odbiornika Logitech Unifying +Comment[sv]=Kringutrustningshanterare för Logitech Unifying-mottagare +Comment[zh_CN]=罗技优联设备管理器 +Comment[zh_TW]=羅技Unifying 裝置管理器 +Comment[zh_HK]=羅技Unifying 裝置管理器 Exec=solaar --window=hide Icon=solaar StartupNotify=true diff --git a/share/solaar/icons/solaar-attention-dark-filled.svg b/share/solaar/icons/solaar-attention-dark-filled.svg new file mode 100644 index 00000000..c3945be1 --- /dev/null +++ b/share/solaar/icons/solaar-attention-dark-filled.svg @@ -0,0 +1,190 @@ + + + +Solaar attentionimage/svg+xmlSolaar attentionDaniel Pavel2013-06-25solaar-attention diff --git a/share/solaar/icons/solaar-filled.svg b/share/solaar/icons/solaar-filled.svg new file mode 100644 index 00000000..521c56ae --- /dev/null +++ b/share/solaar/icons/solaar-filled.svg @@ -0,0 +1,129 @@ + + + +Solaarimage/svg+xmlSolaarDaniel Pavel2013-06-25solaar diff --git a/share/solaar/icons/solaar-init-dark-filled.svg b/share/solaar/icons/solaar-init-dark-filled.svg new file mode 100644 index 00000000..9a14b24d --- /dev/null +++ b/share/solaar/icons/solaar-init-dark-filled.svg @@ -0,0 +1,234 @@ + + + +Solaar initimage/svg+xmlSolaar initDaniel Pavel2013-06-25solaar-init diff --git a/share/solaar/icons/solaar-init-dark-rotate-filled.svg b/share/solaar/icons/solaar-init-dark-rotate-filled.svg new file mode 100644 index 00000000..c42228a2 --- /dev/null +++ b/share/solaar/icons/solaar-init-dark-rotate-filled.svg @@ -0,0 +1,241 @@ + + + +Solaar initimage/svg+xmlSolaar initDaniel Pavel2013-06-25solaar-init diff --git a/share/solaar/icons/light_000.png b/share/solaar/icons/solaar-light_000.png similarity index 100% rename from share/solaar/icons/light_000.png rename to share/solaar/icons/solaar-light_000.png diff --git a/share/solaar/icons/light_020.png b/share/solaar/icons/solaar-light_020.png similarity index 100% rename from share/solaar/icons/light_020.png rename to share/solaar/icons/solaar-light_020.png diff --git a/share/solaar/icons/light_040.png b/share/solaar/icons/solaar-light_040.png similarity index 100% rename from share/solaar/icons/light_040.png rename to share/solaar/icons/solaar-light_040.png diff --git a/share/solaar/icons/light_060.png b/share/solaar/icons/solaar-light_060.png similarity index 100% rename from share/solaar/icons/light_060.png rename to share/solaar/icons/solaar-light_060.png diff --git a/share/solaar/icons/light_080.png b/share/solaar/icons/solaar-light_080.png similarity index 100% rename from share/solaar/icons/light_080.png rename to share/solaar/icons/solaar-light_080.png diff --git a/share/solaar/icons/light_100.png b/share/solaar/icons/solaar-light_100.png similarity index 100% rename from share/solaar/icons/light_100.png rename to share/solaar/icons/solaar-light_100.png diff --git a/share/solaar/icons/light_unknown.png b/share/solaar/icons/solaar-light_unknown.png similarity index 100% rename from share/solaar/icons/light_unknown.png rename to share/solaar/icons/solaar-light_unknown.png diff --git a/share/solaar/icons/solaar-symbolic-dark-filled.svg b/share/solaar/icons/solaar-symbolic-dark-filled.svg new file mode 100644 index 00000000..39c518d3 --- /dev/null +++ b/share/solaar/icons/solaar-symbolic-dark-filled.svg @@ -0,0 +1,182 @@ + + + +Solaarimage/svg+xmlSolaarDaniel Pavel2013-06-25solaar diff --git a/share/solaar/icons/solaar-symbolic-filled.svg b/share/solaar/icons/solaar-symbolic-filled.svg new file mode 100644 index 00000000..71d722c2 --- /dev/null +++ b/share/solaar/icons/solaar-symbolic-filled.svg @@ -0,0 +1,182 @@ + + + +Solaarimage/svg+xmlSolaarDaniel Pavel2013-06-25solaar diff --git a/share/solaar/io.github.pwr_solaar.solaar.metainfo.xml b/share/solaar/io.github.pwr_solaar.solaar.metainfo.xml index 84343947..74c90c6a 100644 --- a/share/solaar/io.github.pwr_solaar.solaar.metainfo.xml +++ b/share/solaar/io.github.pwr_solaar.solaar.metainfo.xml @@ -5,7 +5,11 @@ Solaar Linux manager for Logitech keyboards, mice, and trackpads - https://github.com/pwr-Solaar/Solaar + + pwr-Solaar + + https://pwr-solaar.github.io/Solaar/ + https://github.com/pwr-Solaar/Solaar pfpschneider_AT_gmail.com @@ -33,17 +37,24 @@ solaar.desktop - https://raw.githubusercontent.com/pwr-Solaar/Solaar/master/docs/Solaar-main-window-button-actions.png + https://raw.githubusercontent.com/pwr-Solaar/Solaar/master/docs/screenshots/Solaar-main-window-button-actions.png - https://raw.githubusercontent.com/pwr-Solaar/Solaar/master/docs/Solaar-main-window-receiver.png + https://raw.githubusercontent.com/pwr-Solaar/Solaar/master/docs/screenshots/Solaar-main-window-receiver.png - https://raw.githubusercontent.com/pwr-Solaar/Solaar/master/docs/Solaar-menu.png + https://raw.githubusercontent.com/pwr-Solaar/Solaar/master/docs/screenshots/Solaar-menu.png + + + + + + + diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/hid_parser/__init__.py b/tests/hid_parser/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/hid_parser/test_data.py b/tests/hid_parser/test_data.py new file mode 100644 index 00000000..134c31c1 --- /dev/null +++ b/tests/hid_parser/test_data.py @@ -0,0 +1,15 @@ +from hid_parser.data import Button +from hid_parser.data import Consumer + + +def test_consumer(): + consumer = Consumer() + + assert consumer.PLAY_PAUSE == 0xCD + + +def test_button(): + button = Button() + + assert button.NO_BUTTON == 0x0 + assert button.BUTTON_1 == 0x1 diff --git a/tests/hidapi/__init__.py b/tests/hidapi/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/hidapi/test_hidapi.py b/tests/hidapi/test_hidapi.py new file mode 100644 index 00000000..aef76891 --- /dev/null +++ b/tests/hidapi/test_hidapi.py @@ -0,0 +1,12 @@ +import platform + +from unittest import mock + +if platform.system() == "Linux": + import hidapi.udev_impl as hidapi +else: + import hidapi.hidapi_impl as hidapi + + +def test_find_paired_node(): + hidapi.enumerate(mock.Mock()) diff --git a/tests/logitech_receiver/__init__.py b/tests/logitech_receiver/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/logitech_receiver/fake_hidpp.py b/tests/logitech_receiver/fake_hidpp.py new file mode 100644 index 00000000..7758f2f5 --- /dev/null +++ b/tests/logitech_receiver/fake_hidpp.py @@ -0,0 +1,501 @@ +## Copyright (C) 2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""HID++ data and functions common to several logitech_receiver test files""" + +from __future__ import annotations + +import errno +import threading + +from dataclasses import dataclass +from dataclasses import field +from struct import pack +from typing import Any +from typing import Optional + +from logitech_receiver import device +from logitech_receiver import hidpp20 +from solaar import configuration + + +def open_path(path: Optional[str]) -> int: + if path is None: + raise OSError(errno.EACCES, "Fake access error") + return int(path, 16) # can raise exception + + +def ping(responses, handle, devnumber, long_message=False): + for r in responses: + if handle == r.handle and devnumber == r.devnumber and r.id == 0x0010: + return r.response + + +def request( + responses, + handle, + devnumber, + id, + *params, + no_reply=False, + return_error=False, + long_message=False, + protocol=1.0, +): + params = b"".join(pack("B", p) if isinstance(p, int) else p for p in params) + print("REQUEST ", hex(handle), hex(devnumber), hex(id), params.hex()) + for r in responses: + if handle == r.handle and devnumber == r.devnumber and r.id == id and bytes.fromhex(r.params) == params: + print("RESPONSE", hex(r.handle), hex(r.devnumber), hex(r.id), r.params, r.response) + return bytes.fromhex(r.response) if r.response is not None else None + + +@dataclass +class Response: + response: str | float + id: int + params: str = "" + handle: int = 0x11 + devnumber: int = 0xFF + no_reply: bool = False + + +def replace_number(responses, number): # change the devnumber for a list of responses + return [Response(r.response, r.id, r.params, r.handle, number, r.no_reply) for r in responses] + + +def adjust_responses_index(index, responses): # change index-4 responses to index + return [Response(r.response, r.id - 0x400 + (index << 8), r.params, r.handle, r.devnumber, r.no_reply) for r in responses] + + +r_empty = [ # a HID++ device with no responses except for ping + Response(1.0, 0x0010), # ping +] + +r_keyboard_1 = [ # a HID++ 1.0 keyboard + Response(1.0, 0x0010), # ping + Response("001234", 0x81F1, "01"), # firmware + Response("003412", 0x81F1, "02"), # firmware + Response("002345", 0x81F1, "03"), # firmware + Response("003456", 0x81F1, "04"), # firmware + Response("050050", 0x8107), # battery status +] + +r_keyboard_2 = [ # a HID++ 2.0 keyboard + Response(4.2, 0x0010), # ping + Response("010001", 0x0000, "0001"), # feature set at 0x01 + Response("020003", 0x0000, "0020"), # CONFIG_CHANGE at 0x02 + Response("030001", 0x0000, "0003"), # device information at 0x03 + Response("040003", 0x0000, "0100"), # unknown 0100 at 0x04 + Response("050003", 0x0000, "1B04"), # reprogrammable keys V4 at 0x05 + Response("060003", 0x0000, "0007"), # device friendly name at 0x06 + Response("070003", 0x0000, "0005"), # device name at 0x07 + Response("080003", 0x0000, "1000"), # battery status at 0x08 + Response("08", 0x0100), # 8 features + Response("00010001", 0x0110, "01"), # feature set at 0x01 + Response("00200003", 0x0110, "02"), # CONFIG_CHANGE at 0x02 + Response("00030001", 0x0110, "03"), # device information at 0x03 + Response("01000003", 0x0110, "04"), # unknown 0100 at 0x04 + Response("1B040003", 0x0110, "05"), # reprogrammable keys V4 at 0x05 + Response("00070003", 0x0000, "06"), # device friendly name at 0x06 + Response("00050003", 0x0000, "07"), # device name at 0x07 + Response("10000001", 0x0110, "08"), # battery status at 0x02 + Response("0212345678000D1234567890ABAA01", 0x0300), # device information + Response("04", 0x0500), # reprogrammable keys V4 + Response("00110012AB010203CD00", 0x0510, "00"), # reprogrammable keys V4 + Response("01110022AB010203CD00", 0x0510, "01"), # reprogrammable keys V4 + Response("00010111AB010203CD00", 0x0510, "02"), # reprogrammable keys V4 + Response("03110032AB010204CD00", 0x0510, "03"), # reprogrammable keys V4 + Response("00030333AB010203CD00", 0x0510, "04"), # reprogrammable keys V4 + Response("12", 0x0600), # friendly namme + Response("004142434445464748494A4B4C4D4E", 0x0610, "00"), + Response("0E4F50515253000000000000000000", 0x0610, "0E"), + Response("12", 0x0700), # name and kind + Response("4142434445464748494A4B4C4D4E4F", 0x0710, "00"), + Response("505152530000000000000000000000", 0x0710, "0F"), + Response("00", 0x0720), + Response("12345678", 0x0800), # battery status +] + +r_mouse_1 = [ # a HID++ 1.0 mouse + Response(1.0, 0x0010), # ping +] + +r_mouse_2 = [ # a HID++ 2.0 mouse with few responses except for ping + Response(4.2, 0x0010), # ping +] + +r_mouse_3 = [ # a HID++ 2.0 mouse + Response(4.5, 0x0010), # ping + Response("010001", 0x0000, "0001"), # feature set at 0x01 + Response("020002", 0x0000, "8060"), # report rate at 0x02 + Response("040001", 0x0000, "0003"), # device information at 0x04 + Response("050002", 0x0000, "0005"), # device type and name at 0x05 + Response("08", 0x0100), # 8 features + Response("00010001", 0x0110, "01"), # feature set at 0x01 + Response("80600002", 0x0110, "02"), # report rate at 0x02 + Response("00030001", 0x0110, "04"), # device information at 0x04 + Response("00050002", 0x0110, "05"), # device type and name at 0x05 + Response("09", 0x0210), # report rate - current rate + Response("03123456790008123456780000AA01", 0x0400), # device information + Response("0141424302030100", 0x0410, "00"), # firmware 0 + Response("0241", 0x0410, "01"), # firmware 1 + Response("05", 0x0410, "02"), # firmware 2 + Response("12", 0x0500), # name count - 18 characters + Response("414241424142414241424142414241", 0x0510, "00"), # name - first 15 characters + Response("444544000000000000000000000000", 0x0510, "0F"), # name - last 3 characters +] + + +responses_key = [ # responses for Reprogrammable Keys V4 at 0x05 + Response("08", 0x0500), # Reprogrammable Keys V4 count + Response("00500038010001010400000000000000", 0x0510, "00"), # left button + Response("00510039010001010400000000000000", 0x0510, "01"), # right button + Response("0052003A310003070500000000000000", 0x0510, "02"), # middle button + Response("0053003C710002030100000000000000", 0x0510, "03"), # back button + Response("0056003E710002030100000000000000", 0x0510, "04"), # forward button + Response("00C300A9310003070300000000000000", 0x0510, "05"), # smart shift? + Response("00C4009D310003070500000000000000", 0x0510, "06"), # ? + Response("00D700B4A00004000300000000000000", 0x0510, "07"), # ? + Response("00500000000000000000000000000000", 0x0520, "0050"), # left button + Response("00510000000000000000000000000000", 0x0520, "0051"), # ... + Response("00520100500000000000000000000000", 0x0520, "0052"), + Response("00530500000000000000000000000000", 0x0520, "0053"), + Response("00561100000000000000000000000000", 0x0520, "0056"), + Response("00C30000000000000000000000000000", 0x0520, "00C3"), + Response("00C40000500000000000000000000000", 0x0520, "00C4"), + Response("00D70000510000000000000000000000", 0x0520, "00D7"), + Response("0041", 0x0400), # flags + Response("0401", 0x0410), # count + Response("0050", 0x0420, "00FF"), # left button + Response("0051", 0x0420, "01FF"), # right button + Response("0052", 0x0420, "02FF"), # middle button + Response("0053", 0x0420, "03FF"), # back button + Response("0050000100500000", 0x0430, "0050FF"), # left button current + Response("0051000100500001", 0x0430, "0051FF"), # right button current + Response("0052000100500001", 0x0430, "0052FF"), # middle button current + Response("0053000100500001", 0x0430, "0053FF"), # back button current + Response("0050FF01005000", 0x0440, "0050FF01005000"), # left button write + Response("0051FF01005000", 0x0440, "0051FF01005000"), # right button write + Response("0051FF01005100", 0x0440, "0051FF01005100"), # right button set write +] + +responses_remap = [ # responses for Persistent Remappable Actions at 0x04 and reprogrammable keys at 0x05 + Response("0041", 0x0400), + Response("03", 0x0410), + Response("0301", 0x0410, "00"), + Response("0050", 0x0420, "00FF"), + Response("0050000200010001", 0x0430, "0050FF"), # Left Button + Response("0051", 0x0420, "01FF"), + Response("0051000200010000", 0x0430, "0051FF"), # Left Button + Response("0052", 0x0420, "02FF"), + Response("0052000100510000", 0x0430, "0052FF"), # key DOWN + Response("050002", 0x0000, "1B04"), # REPROGRAMMABLE_KEYS_V4 +] + responses_key + +responses_gestures = [ # the commented-out messages are not used by either the setting or other testing + Response("4203410141020400320480148C21A301", 0x0400, "0000"), # items + Response("A302A11EA30A4105822C852DAD2AAD2B", 0x0400, "0008"), + Response("8F408F418F434204AF54912282558264", 0x0400, "0010"), + Response("01000000000000000000000000000000", 0x0400, "0018"), + Response("01000000000000000000000000000000", 0x0410, "000101"), # enable + # Response("02000000000000000000000000000000", 0x0410, "000102"), + # Response("04000000000000000000000000000000", 0x0410, "000104"), + # Response("08000000000000000000000000000000", 0x0410, "000108"), + Response("00000000000000000000000000000000", 0x0410, "000110"), + # Response("20000000000000000000000000000000", 0x0410, "000120"), + # Response("40000000000000000000000000000000", 0x0410, "000140"), + # Response("00000000000000000000000000000000", 0x0410, "000180"), + # Response("00000000000000000000000000000000", 0x0410, "010101"), + # Response("00000000000000000000000000000000", 0x0410, "010102"), + # Response("04000000000000000000000000000000", 0x0410, "010104"), + # Response("00000000000000000000000000000000", 0x0410, "010108"), + Response("6F000000000000000000000000000000", 0x0410, "0001FF"), + Response("04000000000000000000000000000000", 0x0410, "01010F"), + Response("00000000000000000000000000000000", 0x0430, "000101"), # divert + # Response("00000000000000000000000000000000", 0x0430, "000102"), + # Response("00000000000000000000000000000000", 0x0430, "000104"), + # Response("00000000000000000000000000000000", 0x0430, "000108"), + Response("00000000000000000000000000000000", 0x0430, "000110"), + # Response("00000000000000000000000000000000", 0x0430, "000120"), + # Response("00000000000000000000000000000000", 0x0430, "000140"), + # Response("00000000000000000000000000000000", 0x0430, "000180"), + # Response("00000000000000000000000000000000", 0x0430, "010101"), + # Response("00000000000000000000000000000000", 0x0430, "010102"), + Response("00000000000000000000000000000000", 0x0430, "0001FF"), + Response("00000000000000000000000000000000", 0x0430, "010103"), + Response("08000000000000000000000000000000", 0x0450, "01FF"), + Response("08000000000000000000000000000000", 0x0450, "02FF"), + Response("08000000000000000000000000000000", 0x0450, "03FF"), + Response("00040000000000000000000000000000", 0x0450, "04FF"), + Response("5C020000000000000000000000000000", 0x0450, "05FF"), + Response("01000000000000000000000000000000", 0x0460, "00FF"), + Response("01000000000000000000000000000000", 0x0470, "00FF"), + Response("01", 0x0420, "00010101"), # set index 1 + Response("00", 0x0420, "00010100"), # unset index 1 + Response("01", 0x0420, "00011010"), # set index 4 + Response("00", 0x0420, "00011000"), # unset index 4 + Response("01", 0x0440, "00010101"), # divert index 1 + Response("00", 0x0440, "00010100"), # undivert index 1 + Response("000080FF", 0x0480, "000080FF"), # write param 0 + Response("000180FF", 0x0480, "000180FF"), # write param 0 +] + +zone_responses_1 = [ # responses for COLOR LED EFFECTS + Response("00000102", 0x0710, "00FF00"), + Response("0000000300040005", 0x0720, "000000"), + Response("0001000B00080009", 0x0720, "000100"), +] +zone_responses_2 = [ # responses for RGB EFFECTS + Response("0000000102", 0x0700, "00FF00"), + Response("0000000300040005", 0x0700, "000000"), + Response("0001000200080009", 0x0700, "000100"), +] +effects_responses_1 = [Response("0100000001", 0x0700)] + zone_responses_1 +effects_responses_2 = [Response("FFFF0100000001", 0x0700, "FFFF00")] + zone_responses_2 + +responses_profiles = [ # OnboardProfile in RAM + Response("0104010101020100FE0200", 0x0900), + Response("000101FF", 0x0950, "00000000"), + Response("FFFFFFFF", 0x0950, "00000004"), + Response("01010290018003000700140028FFFFFF", 0x0950, "00010000"), + Response("FFFF0000000000000000000000000000", 0x0950, "00010010"), + Response("8000FFFF900aFF00800204548000FFFF", 0x0950, "00010020"), + Response("900aFF00800204548000FFFF900aFF00", 0x0950, "00010030"), + Response("800204548000FFFF900aFF0080020454", 0x0950, "00010040"), + Response("8000FFFF900aFF00800204548000FFFF", 0x0950, "00010050"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "00010060"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "00010070"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "00010080"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "00010090"), + Response("54004500370000000000000000000000", 0x0950, "000100A0"), + Response("00000000000000000000000000000000", 0x0950, "000100B0"), + Response("00000000000000000000000000000000", 0x0950, "000100C0"), + Response("0A01020300500407000000FFFFFFFFFF", 0x0950, "000100D0"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "000100E0"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFF7C81AB", 0x0950, "000100EE"), +] +responses_profiles_rom = [ # OnboardProfile in ROM + Response("0104010101020100FE0200", 0x0900), + Response("00000000", 0x0950, "00000000"), + Response("010101FF", 0x0950, "01000000"), + Response("FFFFFFFF", 0x0950, "01000004"), + Response("01010290018003000700140028FFFFFF", 0x0950, "01010000"), + Response("FFFF0000000000000000000000000000", 0x0950, "01010010"), + Response("8000FFFF900aFF00800204548000FFFF", 0x0950, "01010020"), + Response("900aFF00800204548000FFFF900aFF00", 0x0950, "01010030"), + Response("800204548000FFFF900aFF0080020454", 0x0950, "01010040"), + Response("8000FFFF900aFF00800204548000FFFF", 0x0950, "01010050"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "01010060"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "01010070"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "01010080"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "01010090"), + Response("54004500370000000000000000000000", 0x0950, "010100A0"), + Response("00000000000000000000000000000000", 0x0950, "010100B0"), + Response("00000000000000000000000000000000", 0x0950, "010100C0"), + Response("0A01020300500407000000FFFFFFFFFF", 0x0950, "010100D0"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "010100E0"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFF7C81AB", 0x0950, "010100EE"), +] +responses_profiles_rom_2 = [ # OnboardProfile in ROM + Response("0104010101020100FE0200", 0x0900), + Response("FFFFFFFF", 0x0950, "00000000"), + Response("010101FF", 0x0950, "01000000"), + Response("FFFFFFFF", 0x0950, "01000004"), + Response("01010290018003000700140028FFFFFF", 0x0950, "01010000"), + Response("FFFF0000000000000000000000000000", 0x0950, "01010010"), + Response("8000FFFF900aFF00800204548000FFFF", 0x0950, "01010020"), + Response("900aFF00800204548000FFFF900aFF00", 0x0950, "01010030"), + Response("800204548000FFFF900aFF0080020454", 0x0950, "01010040"), + Response("8000FFFF900aFF00800204548000FFFF", 0x0950, "01010050"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "01010060"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "01010070"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "01010080"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "01010090"), + Response("54004500370000000000000000000000", 0x0950, "010100A0"), + Response("00000000000000000000000000000000", 0x0950, "010100B0"), + Response("00000000000000000000000000000000", 0x0950, "010100C0"), + Response("0A01020300500407000000FFFFFFFFFF", 0x0950, "010100D0"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0950, "010100E0"), + Response("FFFFFFFFFFFFFFFFFFFFFFFFFF7C81AB", 0x0950, "010100EE"), +] + +complex_responses_1 = [ # COLOR_LED_EFFECTS + Response(4.2, 0x0010), # ping + Response("010001", 0x0000, "0001"), # FEATURE SET at x01 + Response("020001", 0x0000, "0020"), # CONFIG_CHANGE at x02 + Response("0A", 0x0100), # 10 features + Response("070001", 0x0000, "8070"), # COLOR_LED_EFFECTS at 0x07 + *effects_responses_1, +] + +complex_responses_2 = [ # RGB_EFFECTS + reprogrammable keys + persistent actions + Response(4.2, 0x0010), # ping + Response("010001", 0x0000, "0001"), # FEATURE SET at x01 + Response("020001", 0x0000, "0020"), # CONFIG_CHANGE at x02 + Response("0A", 0x0100), # 10 features + Response("070001", 0x0000, "8071"), # RGB_EFFECTS at 0x07 + *effects_responses_2, + Response("040001", 0x0000, "1C00"), # Persistent Remappable Actions at 0x04 + *responses_remap, + Response("080001", 0x0000, "6501"), # Gestures at 0x08 + *adjust_responses_index(8, responses_gestures), + Response("060003", 0x0000, "1982"), # Backlight 2 at 0x06 + Response("010118000001020003000400", 0x0600), + Response("090003", 0x0000, "8100"), # Onboard Profiles at 0x09 + *responses_profiles, +] + +responses_speedchange = [ + Response("0100", 0x0400), + Response("010001", 0x0000, "0001"), # FEATURE SET at x01 + Response("0A", 0x0100), # 10 features + Response("0120", 0x0410, "0120"), + Response("050001", 0x0000, "1B04"), # REPROG_CONTROLS_V4 + Response("01", 0x0500), + Response("00ED009D310003070500000000000000", 0x0510, "00"), # DPI Change + Response("00ED0000000000000000000000000000", 0x0520, "00ED"), # DPI Change current + Response("060000", 0x0000, "2205"), # POINTER_SPEED +] + + +# A fake device that uses provided data (responses) to respond to HID++ commands. +# Some methods from the real device are used to set up data structures needed for settings +@dataclass +class Device: + name: str = "TESTD" + online: bool = True + protocol: float = 2.0 + responses: Any = field(default_factory=list) + codename: str = "TESTC" + feature: Optional[int] = None + offset: Optional[int] = 4 + version: Optional[int] = 0 + wpid: Optional[str] = "0000" + setting_callback: Any = None + centurion: bool = False + sliding = profiles = _backlight = _keys = _remap_keys = _led_effects = _gestures = None + _gestures_lock = threading.Lock() + number = "d1" + present = True + + read_register = device.Device.read_register + write_register = device.Device.write_register + backlight = device.Device.backlight + keys = device.Device.keys + remap_keys = device.Device.remap_keys + led_effects = device.Device.led_effects + gestures = device.Device.gestures + __hash__ = device.Device.__hash__ + feature_request = device.Device.feature_request + _infer_kind_centurion = device.Device._infer_kind_centurion + + def __post_init__(self): + self._name = self.name + self._protocol = self.protocol + self.persister = configuration._DeviceEntry() + self.features = hidpp20.FeaturesArray(self) + self.settings = [] + self.receiver = [] + if self.feature is not None: + self.features = hidpp20.FeaturesArray(self) + self.responses = [ + Response("010001", 0x0000, "0001"), + Response("20", 0x0100), + ] + self.responses + self.responses.append( + Response( + f"{int(self.offset):0>2X}00{int(self.version):0>2X}", + 0x0000, + f"{int(self.feature):0>4X}", + ) + ) + if self.setting_callback is None: + self.setting_callback = lambda x, y, z: None + self.add_notification_handler = lambda x, y: None + # Centurion bridge responses: keyed by (sub_feat_idx, sub_function) -> response bytes + if not hasattr(self, "_bridge_responses"): + self._bridge_responses = {} + + def request(self, id, *params, no_reply=False, long_message=False, protocol=2.0): + params = b"".join(pack("B", p) if isinstance(p, int) else p for p in params) + print("REQUEST ", self._name, hex(id), params.hex().upper()) + for r in self.responses: + if id == r.id and params == bytes.fromhex(r.params): + print("RESPONSE", self._name, hex(r.id), r.params, r.response) + return bytes.fromhex(r.response) if isinstance(r.response, str) else r.response + print("RESPONSE", self._name, None) + + def centurion_bridge_request(self, sub_feat_idx, sub_function=0x00, *params, no_reply=False): + """Fake bridge request — looks up (sub_feat_idx, sub_function, params) in _bridge_responses.""" + params_bytes = b"".join(pack("B", p) if isinstance(p, int) else p for p in params) if params else b"" + key = (sub_feat_idx, sub_function, params_bytes.hex().upper()) + print("BRIDGE ", self._name, f"sub_idx={sub_feat_idx} func={sub_function} params={params_bytes.hex().upper()}") + result = self._bridge_responses.get(key) + if result is not None: + print("BRIDGE_R", self._name, result.hex().upper()) + return result + # Try without params for convenience + key_no_params = (sub_feat_idx, sub_function, "") + result = self._bridge_responses.get(key_no_params) + if result is not None: + print("BRIDGE_R", self._name, result.hex().upper()) + return result + print("BRIDGE_R", self._name, None) + return None + + def ping(self, handle=None, devnumber=None, long_message=False): + print("PING", self._protocol) + return self._protocol + + def handle_notification(self, handle): + pass + + def changed(self, *args, **kwargs): + pass + + def set_battery_info(self, *args, **kwargs): + pass + + def status_string(self): + pass + + +# Centurion headset (PRO X 2 LIGHTSPEED) parent device responses. +# Parent has 5 features: ROOT(0), FeatureSet(1), DeviceInfo(2), CentPPBridge(3), GenericDFU(4) +# Feature 0x0003 on Centurion = CentPPBridge (NOT FirmwareInfo). +r_centurion_headset = [ + Response(2.6, 0x0010), # ping (protocol 2.6) + Response("010001", 0x0000, "0001"), # FeatureSet at index 1 + Response("020001", 0x0000, "0100"), # DeviceInfo at index 2 + Response("030001", 0x0000, "0003"), # CentPPBridge at index 3 + Response("040001", 0x0000, "010A"), # GenericDFU at index 4 + Response("05", 0x0100), # feature count = 5 (includes ROOT on Centurion) + # FeatureSet.getFeatureID responses: [remaining_count, feat_hi, feat_lo, type, flags] + Response("0400000000", 0x0110, "00"), # index 0: ROOT (0x0000) + Response("0300010001", 0x0110, "01"), # index 1: FeatureSet (0x0001) + Response("0201000001", 0x0110, "02"), # index 2: DeviceInfo (0x0100) + Response("0100030001", 0x0110, "03"), # index 3: CentPPBridge (0x0003) + Response("00010A0001", 0x0110, "04"), # index 4: GenericDFU (0x010A) +] + + +def match_requests(number, responses, call_args_list): + for i in range(0 - number, 0): + param = b"".join(pack("B", p) if isinstance(p, int) else p for p in call_args_list[i][0][1:]).hex().upper() + print("MATCH", i, hex(call_args_list[i][0][0]), param, hex(responses[i].id), responses[i].params) + assert call_args_list[i][0][0] == responses[i].id + assert param == responses[i].params diff --git a/tests/logitech_receiver/test_base.py b/tests/logitech_receiver/test_base.py new file mode 100644 index 00000000..a02696fd --- /dev/null +++ b/tests/logitech_receiver/test_base.py @@ -0,0 +1,202 @@ +import struct +import sys + +from typing import Union +from unittest import mock + +import pytest + +from logitech_receiver import base +from logitech_receiver import exceptions +from logitech_receiver.base import HIDPP_SHORT_MESSAGE_ID +from logitech_receiver.common import LOGITECH_VENDOR_ID +from logitech_receiver.common import BusID +from logitech_receiver.hidpp10_constants import ErrorCode as Hidpp10Error +from logitech_receiver.hidpp20_constants import ErrorCode as Hidpp20Error + + +@pytest.mark.parametrize( + "usb_id, expected_name, expected_receiver_kind", + [ + (0xC548, "Bolt Receiver", "bolt"), + (0xC52B, "Unifying Receiver", "unifying"), + (0xC531, "Nano Receiver", "nano"), + (0xC53F, "Lightspeed Receiver", None), + (0xC517, "EX100 Receiver 27 Mhz", "27Mhz"), + ], +) +def test_product_information(usb_id, expected_name, expected_receiver_kind): + res = base.product_information(usb_id) + + assert res["name"] == expected_name + assert isinstance(res["vendor_id"], int) + assert isinstance(res["product_id"], int) + + if expected_receiver_kind: + assert res["receiver_kind"] == expected_receiver_kind + + +def test_filter_receivers_known(): + bus_id = 2 + product_id = 0xC548 + + receiver_info = base.get_known_receiver_info(bus_id, LOGITECH_VENDOR_ID, product_id) + + assert receiver_info["name"] == "Bolt Receiver" + assert receiver_info["receiver_kind"] == "bolt" + + +def test_filter_receivers_unknown(): + bus_id = 1 + product_id = 0xC500 + + receiver_info = base.get_known_receiver_info(bus_id, LOGITECH_VENDOR_ID, product_id) + + assert receiver_info["bus_id"] == bus_id + assert receiver_info["product_id"] == product_id + + +@pytest.mark.parametrize( + "product_id, bus, hidpp_short, hidpp_long, expected", + [ + (0xC548, BusID.USB, True, False, {"name": "Bolt Receiver", "usb_interface": 2}), + (0xC07D, BusID.USB, True, False, {"usb_interface": 1}), + (0xC07E, BusID.USB, False, True, {"usb_interface": 1}), + (0xC07E, BusID.BLUETOOTH, False, True, {"bus_id": 5}), + (0xA07E, BusID.USB, False, True, {"product_id": 0xA07E}), + (0xA07C, BusID.USB, False, False, None), + (0xC07F, BusID.USB, None, None, {"usb_interface": 2}), + (0xC07F, BusID.BLUETOOTH, None, None, None), + (0xB013, BusID.BLUETOOTH, None, None, {"product_id": 0xB013}), + ], +) +def test_filter_products_of_interest(product_id, bus, hidpp_short, hidpp_long, expected): + receiver_info = base.filter_products_of_interest( + bus, + LOGITECH_VENDOR_ID, + product_id, + hidpp_short=hidpp_short, + hidpp_long=hidpp_long, + ) + + if expected is None: + assert receiver_info == expected + else: + assert all([receiver_info[key] == expected_value for key, expected_value in expected.items()]) + assert receiver_info["vendor_id"] == LOGITECH_VENDOR_ID + assert receiver_info["product_id"] + + +def test_match(): + record = {"vendor_id": LOGITECH_VENDOR_ID} + + res = base._match_device(record, 0, LOGITECH_VENDOR_ID, 0) + + assert res is True + + +@pytest.mark.parametrize( + "report_id, sub_id, address, valid_notification", + [ + (0x1, 0x72, 0x57, True), + (0x1, 0x40, 0x63, True), + (0x1, 0x40, 0x71, True), + (0x1, 0x80, 0x71, False), + (0x1, 0x00, 0x70, False), + (0x20, 0x09, 0x71, False), + (0x1, 0x37, 0x71, False), + ], +) +def test_make_notification(report_id, sub_id, address, valid_notification): + devnumber = 123 + data = bytes([sub_id, address, 0x02, 0x03, 0x04]) + + result = base.make_notification(report_id, devnumber, data) + + if valid_notification: + assert isinstance(result, base.HIDPPNotification) + assert result.report_id == report_id + assert result.devnumber == devnumber + assert result.sub_id == sub_id + assert result.address == address + assert result.data == bytes([0x02, 0x03, 0x04]) + else: + assert result is None + + +def test_get_next_sw_id(): + res1 = base._get_next_sw_id() + res2 = base._get_next_sw_id() + + assert res1 == 2 + assert res2 == 3 + + +@pytest.mark.parametrize( + "prefix, error_code, return_error, raise_exception", + [ + (b"\x8f", Hidpp10Error.INVALID_SUB_ID_COMMAND, False, False), + (b"\x8f", Hidpp10Error.INVALID_SUB_ID_COMMAND, True, False), + (b"\xff", Hidpp20Error.UNKNOWN, False, True), + ], +) +def test_request_errors( + prefix: bytes, error_code: Union[Hidpp10Error, Hidpp20Error], return_error: bool, raise_exception: bool +): + handle = 0 + device_number = 66 + + next_sw_id = 0x02 + reply_data_sw_id = struct.pack("!H", 0x0000 | next_sw_id) + + with mock.patch( + "logitech_receiver.base._read", + return_value=(HIDPP_SHORT_MESSAGE_ID, device_number, prefix + reply_data_sw_id + struct.pack("B", error_code)), + ), mock.patch("logitech_receiver.base._read_input_buffer"), mock.patch( + "logitech_receiver.base.write", return_value=None + ), mock.patch("logitech_receiver.base._get_next_sw_id", return_value=next_sw_id): + if raise_exception: + with pytest.raises(exceptions.FeatureCallError) as context: + base.request(handle, device_number, next_sw_id, return_error=return_error) + assert context.value.number == device_number + assert context.value.request == next_sw_id + assert context.value.error == error_code + assert context.value.params == b"" + + else: + result = base.request(handle, device_number, next_sw_id, return_error=return_error) + assert result == (error_code if return_error else None) + + +@pytest.mark.skipif(sys.platform == "darwin", reason="Test only runs on Linux") +@pytest.mark.parametrize( + "simulated_error, expected_result", + [ + (Hidpp10Error.INVALID_SUB_ID_COMMAND, 1.0), + (Hidpp10Error.RESOURCE_ERROR, None), + (Hidpp10Error.CONNECTION_REQUEST_FAILED, None), + (Hidpp10Error.UNKNOWN_DEVICE, exceptions.NoSuchDevice), + ], +) +def test_ping_errors(simulated_error: Hidpp10Error, expected_result): + handle = 1 + device_number = 1 + + next_sw_id = 0x05 + reply_data_sw_id = struct.pack("!H", 0x0010 | next_sw_id) + + with mock.patch( + "logitech_receiver.base._read", + return_value=(HIDPP_SHORT_MESSAGE_ID, device_number, b"\x8f" + reply_data_sw_id + bytes([simulated_error])), + ), mock.patch("logitech_receiver.base._read_input_buffer"), mock.patch( + "logitech_receiver.base.write", return_value=None + ), mock.patch("logitech_receiver.base._get_next_sw_id", return_value=next_sw_id): + if isinstance(expected_result, type) and issubclass(expected_result, Exception): + with pytest.raises(expected_result) as context: + base.ping(handle=handle, devnumber=device_number) + assert context.value.number == device_number + assert context.value.request == struct.unpack("!H", reply_data_sw_id)[0] + + else: + result = base.ping(handle=handle, devnumber=device_number) + assert result == expected_result diff --git a/tests/logitech_receiver/test_base_usb.py b/tests/logitech_receiver/test_base_usb.py new file mode 100644 index 00000000..b911f7ed --- /dev/null +++ b/tests/logitech_receiver/test_base_usb.py @@ -0,0 +1,30 @@ +import pytest + +from logitech_receiver import base_usb +from logitech_receiver.common import LOGITECH_VENDOR_ID + + +def test_ensure_known_receivers_mappings_are_valid(): + for key, receiver in base_usb.KNOWN_RECEIVERS.items(): + assert key == receiver["product_id"] + + +def test_get_receiver_info(): + expected = { + "vendor_id": LOGITECH_VENDOR_ID, + "product_id": 0xC548, + "usb_interface": 2, + "name": "Bolt Receiver", + "receiver_kind": "bolt", + "max_devices": 6, + "may_unpair": True, + } + + res = base_usb.get_receiver_info(0xC548) + + assert res == expected + + +def test_get_receiver_info_unknown_device_fails(): + with pytest.raises(ValueError): + base_usb.get_receiver_info(0xC500) diff --git a/tests/logitech_receiver/test_common.py b/tests/logitech_receiver/test_common.py new file mode 100644 index 00000000..b4ed9512 --- /dev/null +++ b/tests/logitech_receiver/test_common.py @@ -0,0 +1,276 @@ +from enum import IntFlag + +import pytest +import yaml + +from logitech_receiver import common + + +def test_crc16(): + value = b"123456789" + expected = 0x29B1 + + result = common.crc16(value) + + assert result == expected + + +def test_named_int(): + named_int = common.NamedInt(0x2, "pulse") + + assert named_int.name == "pulse" + assert named_int == 2 + assert repr(named_int) == "NamedInt(2, 'pulse')" + + +def test_named_int_comparison(): + named_int = common.NamedInt(0, "entry") + named_int_equal = common.NamedInt(0, "entry") + named_int_unequal_name = common.NamedInt(0, "unequal") + named_int_unequal_value = common.NamedInt(5, "entry") + named_int_unequal = common.NamedInt(2, "unequal") + + assert named_int == named_int_equal + assert named_int != named_int_unequal_name + assert named_int != named_int_unequal_value + assert named_int != named_int_unequal + assert named_int is not None + assert named_int == 0 + assert named_int == "entry" + + +def test_named_int_comparison_exception(): + named_int = common.NamedInt(0, "entry") + + with pytest.raises(TypeError): + assert named_int == b"\x00" + + +def test_named_int_conversions(): + named_int = common.NamedInt(2, "two") + + assert named_int.bytes() == b"\x00\x02" + assert str(named_int) == "two" + + +def test_named_int_yaml(): + named_int = common.NamedInt(2, "two") + + yaml_string = yaml.dump(named_int) + + # assert yaml_string == "!NamedInt {name: two, value: 2}\n" + + yaml_load = yaml.safe_load(yaml_string) + + assert yaml_load == named_int + + +def test_named_ints(): + named_ints = common.NamedInts(empty=0, critical=5, low=20, good=50, full=90) + + assert named_ints.empty == 0 + assert named_ints.empty.name == "empty" + assert named_ints.critical == 5 + assert named_ints.critical.name == "critical" + assert named_ints.low == 20 + assert named_ints.low.name == "low" + assert named_ints.good == 50 + assert named_ints.good.name == "good" + assert named_ints.full == 90 + assert named_ints.full.name == "full" + + assert len(named_ints) == 5 + assert 5 in named_ints + assert 6 not in named_ints + assert "critical" in named_ints + assert "easy" not in named_ints + assert common.NamedInt(5, "critical") in named_ints + assert common.NamedInt(5, "five") not in named_ints + assert common.NamedInt(6, "critical") not in named_ints + assert named_ints[5] == "critical" + assert named_ints["critical"] == "critical" + assert named_ints[66] is None + assert named_ints["5"] is None + assert len(named_ints[:]) == len(named_ints) + assert len(named_ints[0:100]) == len(named_ints) + assert len(named_ints[5:90]) == 3 + assert len(named_ints[5:]) == 4 + assert named_ints[90:5] == [] + + +def test_named_ints_fallback(): + named_ints = common.NamedInts(empty=0, critical=5, low=20, good=50, full=90) + named_ints._fallback = lambda x: str(x) + + fallback = named_ints[80] + + assert fallback == common.NamedInt(80, "80") + + +def test_named_ints_list(): + named_ints_list = common.NamedInts.list([0, 5, 20, 50, 90]) + + assert len(named_ints_list) == 5 + assert 50 in named_ints_list + assert 60 not in named_ints_list + + +def test_named_ints_range(): + named_ints_range = common.NamedInts.range(0, 5) + + assert len(named_ints_range) == 6 + assert 4 in named_ints_range + assert 6 not in named_ints_range + + +@pytest.mark.parametrize( + "code, expected_flags", + [ + (0, []), + (0b0010, ["two"]), + (0b0101, ["one", "three"]), + (0b1001, ["one", "unknown:000008"]), + ], +) +def test_named_ints_flag_names(code, expected_flags): + named_ints_flag_bits = common.NamedInts(one=0b001, two=0b010, three=0b100) + + flags = list(named_ints_flag_bits.flag_names(code)) + + assert flags == expected_flags + + +@pytest.mark.parametrize( + "code, expected_flags", + [ + (0, []), + (0b0010, ["two"]), + (0b0101, ["one", "three"]), + (0b1001, ["one", "unknown:000008"]), + ], +) +def test_flag_names(code, expected_flags): + class ExampleFlag(IntFlag): + one = 0x1 + two = 0x2 + three = 0x4 + + flags = common.flag_names(ExampleFlag, code) + + assert list(flags) == expected_flags + + +def test_named_ints_setitem(): + named_ints = common.NamedInts(empty=0, critical=5, low=20, good=50, full=90) + + named_ints[55] = "better" + named_ints[60] = common.NamedInt(60, "sixty") + with pytest.raises(TypeError): + named_ints[70] = 70 + with pytest.raises(ValueError): + named_ints[70] = "empty" + with pytest.raises(ValueError): + named_ints[50] = "new" + + assert named_ints[55] == "better" + assert named_ints[60] == "sixty" + + +def test_named_ints_other(): + named_ints = common.NamedInts(empty=0, critical=5) + named_ints_2 = common.NamedInts(good=50) + + union = named_ints.__or__(named_ints_2) + + assert list(named_ints) == [common.NamedInt(0, "empty"), common.NamedInt(5, "critical")] + assert len(named_ints) == 2 + assert repr(named_ints) == "NamedInts(NamedInt(0, 'empty'), NamedInt(5, 'critical'))" + assert len(union) == 3 + assert list(union) == [common.NamedInt(0, "empty"), common.NamedInt(5, "critical"), common.NamedInt(50, "good")] + + +def test_unsorted_named_ints(): + named_ints = common.UnsortedNamedInts(critical=5, empty=0) + named_ints_2 = common.UnsortedNamedInts(good=50) + + union = named_ints.__or__(named_ints_2) + unionr = named_ints_2.__or__(named_ints) + + assert len(union) == 3 + assert list(union) == [common.NamedInt(5, "critical"), common.NamedInt(0, "empty"), common.NamedInt(50, "good")] + assert len(unionr) == 3 + assert list(unionr) == [common.NamedInt(50, "good"), common.NamedInt(5, "critical"), common.NamedInt(0, "empty")] + + +@pytest.mark.parametrize( + "bytes_input, expected_output", + [ + (b"\x01\x02\x03\x04", "01020304"), + (b"", ""), + ], +) +def test_strhex(bytes_input, expected_output): + result = common.strhex(bytes_input) + + assert result == expected_output + + +def test_bytest2int(): + value = b"\x12\x34\x56\x78" + expected = 0x12345678 + + result = common.bytes2int(value) + + assert result == expected + + +def test_int2bytes(): + value = 0x12345678 + expected = b"\x12\x34\x56\x78" + + result = common.int2bytes(value) + + assert result == expected + + +def test_kw_exception(): + e = common.KwException(foo=0, bar="bar") + + assert e.foo == 0 + assert e.bar == "bar" + + +@pytest.mark.parametrize( + "status, expected_level, expected_ok, expected_charging, expected_string", + [ + (common.BatteryStatus.FULL, common.BatteryLevelApproximation.FULL, True, True, "Battery: full (full)"), + (common.BatteryStatus.ALMOST_FULL, common.BatteryLevelApproximation.GOOD, True, True, "Battery: good (almost full)"), + (common.BatteryStatus.RECHARGING, common.BatteryLevelApproximation.GOOD, True, True, "Battery: good (recharging)"), + ( + common.BatteryStatus.SLOW_RECHARGE, + common.BatteryLevelApproximation.LOW, + True, + True, + "Battery: low (slow recharge)", + ), + (common.BatteryStatus.DISCHARGING, None, True, False, ""), + ], +) +def test_battery(status, expected_level, expected_ok, expected_charging, expected_string): + battery = common.Battery(None, None, status, None) + + assert battery.status == status + assert battery.level == expected_level + assert battery.ok() == expected_ok + assert battery.charging() == expected_charging + assert battery.to_str() == expected_string + + +def test_battery_2(): + battery = common.Battery(50, None, common.BatteryStatus.DISCHARGING, None) + + assert battery.status == common.BatteryStatus.DISCHARGING + assert battery.level == 50 + assert battery.ok() + assert not battery.charging() + assert battery.to_str() == "Battery: 50% (discharging)" diff --git a/tests/logitech_receiver/test_desktop_notifications.py b/tests/logitech_receiver/test_desktop_notifications.py new file mode 100644 index 00000000..22905862 --- /dev/null +++ b/tests/logitech_receiver/test_desktop_notifications.py @@ -0,0 +1,29 @@ +from unittest import mock + +from logitech_receiver import desktop_notifications + +# depends on external environment, so make some tests dependent on availability + + +def test_init(): + result = desktop_notifications.init() + + assert result == desktop_notifications.available + + +def test_uninit(): + assert desktop_notifications.uninit() is None + + +class MockDevice(mock.Mock): + name = "MockDevice" + + def close(): + return True + + +def test_show(): + dev = MockDevice() + reason = "unknown" + result = desktop_notifications.show(dev, reason) + assert result is not None if desktop_notifications.available else result is None diff --git a/tests/logitech_receiver/test_device.py b/tests/logitech_receiver/test_device.py new file mode 100644 index 00000000..b291dba9 --- /dev/null +++ b/tests/logitech_receiver/test_device.py @@ -0,0 +1,376 @@ +## Copyright (C) 2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from dataclasses import dataclass +from functools import partial +from typing import Optional + +import pytest + +from logitech_receiver import common +from logitech_receiver import device +from logitech_receiver import hidpp20 +from logitech_receiver.common import BatteryLevelApproximation +from logitech_receiver.common import BatteryStatus + +from . import fake_hidpp + + +class LowLevelInterfaceFake: + def __init__(self, responses=None): + self.responses = responses + + def open_path(self, path) -> int: + return fake_hidpp.open_path(path) + + def find_paired_node(self, receiver_path: str, index: int, timeout: int): + return None + + def request(self, response, *args, **kwargs): + func = partial(fake_hidpp.request, self.responses) + return func(response, *args, **kwargs) + + def ping(self, response, *args, **kwargs): + func = partial(fake_hidpp.ping, self.responses) + return func(response, *args, **kwargs) + + def close(self, *args, **kwargs): + pass + + +@dataclass +class DeviceInfoStub: + path: str + product_id: str + vendor_id: int = 1133 + hidpp_short: bool = False + hidpp_long: bool = True + bus_id: int = 0x0003 # USB + serial: str = "aa:aa:aa;aa" + centurion: bool = False + + +di_bad_handle = DeviceInfoStub(None, product_id="CCCC") +di_error = DeviceInfoStub(11, product_id="CCCC") +di_CCCC = DeviceInfoStub("11", product_id="CCCC") +di_C318 = DeviceInfoStub("11", product_id="C318") +di_B530 = DeviceInfoStub("11", product_id="B350", bus_id=0x0005) +di_C068 = DeviceInfoStub("11", product_id="C06B") +di_C08A = DeviceInfoStub("11", product_id="C08A") +di_DDDD = DeviceInfoStub("11", product_id="DDDD") +di_0AF7 = DeviceInfoStub("11", product_id="0AF7", centurion=True) + + +@pytest.mark.parametrize( + "device_info, responses, expected_success", + [ + (di_bad_handle, fake_hidpp.r_empty, None), + (di_error, fake_hidpp.r_empty, False), + (di_CCCC, fake_hidpp.r_empty, True), + (di_0AF7, fake_hidpp.r_empty, True), + ], +) +def test_create_device(device_info, responses, expected_success): + low_level_mock = LowLevelInterfaceFake(responses) + if expected_success is None: + with pytest.raises(PermissionError): + device.create_device(low_level_mock, device_info) + elif not expected_success: + with pytest.raises(Exception): # noqa: B017 + device.create_device(low_level_mock, device_info) + else: + test_device = device.create_device(low_level_mock, device_info) + assert bool(test_device) == expected_success + + +def test_create_centurion_device(): + """Test that a centurion device gets hidpp_long forced to True and centurion flag set.""" + from logitech_receiver import base + + low_level_mock = LowLevelInterfaceFake(fake_hidpp.r_empty) + test_device = device.create_device(low_level_mock, di_0AF7) + + assert test_device is not None + assert test_device.centurion is True + assert test_device.hidpp_long is True + assert int(test_device.handle) in base._centurion_handles + + # Clean up + base._centurion_handles.discard(int(test_device.handle)) + + +@pytest.mark.parametrize( + "device_info, responses, expected_codename, expected_name, expected_kind", + [(di_CCCC, fake_hidpp.r_empty, "?? (CCCC)", "Unknown device CCCC", "?")], +) +def test_device_name(device_info, responses, expected_codename, expected_name, expected_kind): + low_level = LowLevelInterfaceFake(responses) + + test_device = device.create_device(low_level, device_info) + + assert test_device.codename == expected_codename + assert test_device.name == expected_name + assert test_device.kind == expected_kind + + +@pytest.mark.parametrize( + "device_info, responses, handle, _name, _codename, number, protocol, registers", + zip( + [di_CCCC, di_C318, di_B530, di_C068, di_C08A, di_DDDD], + [ + fake_hidpp.r_empty, + fake_hidpp.r_keyboard_1, + fake_hidpp.r_keyboard_2, + fake_hidpp.r_mouse_1, + fake_hidpp.r_mouse_2, + fake_hidpp.r_mouse_3, + ], + [0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + [None, "Illuminated Keyboard", "Craft Advanced Keyboard", "G700 Gaming Mouse", "MX Vertical Wireless Mouse", None], + [None, "Illuminated", "Craft", "G700", "MX Vertical", None], + [0xFF, 0x0, 0xFF, 0x0, 0xFF, 0xFF], + [1.0, 1.0, 4.5, 1.0, 4.5, 4.5], + [[], [], [], (common.NamedInt(7, "battery status"), common.NamedInt(81, "three leds")), [], []], + ), +) +def test_device_info(device_info, responses, handle, _name, _codename, number, protocol, registers): + test_device = device.Device(LowLevelInterfaceFake(responses), None, None, None, handle=handle, device_info=device_info) + + assert test_device.handle == handle + assert test_device._name == _name + assert test_device._codename == _codename + assert test_device.number == number + assert test_device._protocol == protocol + assert test_device.registers == registers + + assert bool(test_device) + + test_device.__del__() + assert not bool(test_device) + + +@dataclass +class FakeReceiver: + path: str = "11" + handle: int = 0x11 + codename: Optional[str] = None + + def device_codename(self, number): + return self.codename + + def __contains__(self, dev): + return True + + +pi_CCCC = {"wpid": "CCCC", "kind": 0, "serial": None, "polling": "1ms", "power_switch": "top"} +pi_2011 = {"wpid": "2011", "kind": 1, "serial": "1234", "polling": "2ms", "power_switch": "bottom"} +pi_4066 = {"wpid": "4066", "kind": 1, "serial": "5678", "polling": "4ms", "power_switch": "left"} +pi_1007 = {"wpid": "1007", "kind": 2, "serial": "1234", "polling": "8ms", "power_switch": "right"} +pi_407B = {"wpid": "407B", "kind": 2, "serial": "5678", "polling": "1ms", "power_switch": "left"} +pi_DDDD = {"wpid": "DDDD", "kind": 2, "serial": "1234", "polling": "2ms", "power_switch": "top"} + + +@pytest.mark.parametrize( + "number, pairing_info, responses, handle, _name, codename, p, p2, name", + zip( + range(1, 7), + [pi_CCCC, pi_2011, pi_4066, pi_1007, pi_407B, pi_DDDD], + [ + fake_hidpp.r_empty, + fake_hidpp.r_keyboard_1, + fake_hidpp.r_keyboard_2, + fake_hidpp.r_mouse_1, + fake_hidpp.r_mouse_2, + fake_hidpp.r_mouse_3, + ], + [0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + [None, "Wireless Keyboard K520", "Craft Advanced Keyboard", "MX Air", "MX Vertical Wireless Mouse", None], + ["CODE", "K520", "Craft", "MX Air", "MX Vertical", "CODE"], + [None, 1.0, 4.5, 1.0, 4.5, None], + [1.0, 1.0, 4.5, 1.0, 4.5, 4.5], + [ + "CODE", + "Wireless Keyboard K520", + "Craft Advanced Keyboard", + "MX Air", + "MX Vertical Wireless Mouse", + "ABABABABABABABADED", + ], + ), +) +def test_device_receiver(number, pairing_info, responses, handle, _name, codename, p, p2, name): + low_level = LowLevelInterfaceFake(responses) + low_level.request = partial(fake_hidpp.request, fake_hidpp.replace_number(responses, number)) + low_level.ping = partial(fake_hidpp.ping, fake_hidpp.replace_number(responses, number)) + + test_device = device.Device(low_level, FakeReceiver(codename="CODE"), number, True, pairing_info, handle=handle) + test_device.receiver.device = test_device + + assert test_device.handle == handle + assert test_device._name == _name + assert test_device.codename == codename + assert test_device.number == number + assert test_device._protocol == p + assert test_device.protocol == p2 + assert test_device.codename == codename + assert test_device.name == name + + assert test_device == test_device + assert not (test_device != test_device) + assert bool(test_device) + + test_device.__del__() + + +@pytest.mark.parametrize( + "number, info, responses, handle, unitId, modelId, task_id, kind, firmware, serial, id, psl, rate", + zip( + range(1, 7), + [pi_CCCC, pi_2011, pi_4066, pi_1007, pi_407B, pi_DDDD], + [ + fake_hidpp.r_empty, + fake_hidpp.r_keyboard_1, + fake_hidpp.r_keyboard_2, + fake_hidpp.r_mouse_1, + fake_hidpp.r_mouse_2, + fake_hidpp.r_mouse_3, + ], + [None, 0x11, 0x11, 0x11, 0x11, 0x11], + [None, None, "12345678", None, None, "12345679"], # unitId + [None, None, "1234567890AB", None, None, "123456780000"], # modelId + [None, None, {"btid": "1234", "wpid": "5678", "usbid": "90AB"}, None, None, {"usbid": "1234"}], # tid_map + ["?", 1, 1, 2, 2, 2], # kind + [(), True, (), (), (), True], # firmware + [None, "1234", "5678", "1234", "5678", "1234"], # serial + ["", "1234", "12345678", "1234", "5678", "12345679"], # id + ["top", "bottom", "left", "right", "left", "top"], # power switch location + ["1ms", "2ms", "4ms", "8ms", "1ms", "9ms"], # polling rate + ), +) +def test_device_ids(number, info, responses, handle, unitId, modelId, task_id, kind, firmware, serial, id, psl, rate): + low_level = LowLevelInterfaceFake(responses) + low_level.request = partial(fake_hidpp.request, fake_hidpp.replace_number(responses, number)) + low_level.ping = partial(fake_hidpp.ping, fake_hidpp.replace_number(responses, number)) + + test_device = device.Device(low_level, FakeReceiver(), number, True, info, handle=handle) + + assert test_device.unitId == unitId + assert test_device.modelId == modelId + assert test_device.tid_map == task_id + assert test_device.kind == kind + assert test_device.firmware == firmware or len(test_device.firmware) > 0 and firmware is True + assert test_device.id == id + assert test_device.power_switch_location == psl + assert test_device.polling_rate == rate + + +class FakeDevice(device.Device): # a fully functional Device but its HID++ functions look at local data + def __init__(self, responses, *args, **kwargs): + self.responses = responses + super().__init__(LowLevelInterfaceFake(responses), *args, **kwargs) + + request = fake_hidpp.Device.request + ping = fake_hidpp.Device.ping + + +@pytest.mark.parametrize( + "device_info, responses, protocol, led, keys, remap, gestures, backlight, profiles", + [ + (di_CCCC, fake_hidpp.r_empty, 1.0, type(None), None, None, None, None, None), + (di_C318, fake_hidpp.r_empty, 1.0, type(None), None, None, None, None, None), + (di_B530, fake_hidpp.r_keyboard_1, 1.0, type(None), None, None, None, None, None), + (di_B530, fake_hidpp.r_keyboard_2, 2.0, type(None), 4, 0, 0, None, None), + (di_B530, fake_hidpp.complex_responses_1, 4.5, hidpp20.LEDEffectsInfo, 0, 0, 0, None, None), + (di_B530, fake_hidpp.complex_responses_2, 4.5, hidpp20.RGBEffectsInfo, 8, 3, 1, True, True), + ], +) +def test_device_complex(device_info, responses, protocol, led, keys, remap, gestures, backlight, profiles, mocker): + test_device = FakeDevice(responses, None, None, True, device_info=device_info) + test_device._name = "TestDevice" + test_device._protocol = protocol + spy_request = mocker.spy(test_device, "request") + + assert type(test_device.led_effects) == led + if keys is None: + assert test_device.keys == keys + else: + assert len(test_device.keys) == keys + if remap is None: + assert test_device.remap_keys == remap + else: + assert len(test_device.remap_keys) == remap + assert (test_device.gestures is None) == (gestures is None) + assert (test_device.backlight is None) == (backlight is None) + assert (test_device.profiles is None) == (profiles is None) + + test_device.set_configuration(55) + if protocol > 1.0: + spy_request.assert_called_with(0x210, 55, no_reply=False) + test_device.reset() + if protocol > 1.0: + spy_request.assert_called_with(0x210, 0, no_reply=False) + + +@pytest.mark.parametrize( + "device_info, responses, protocol, p, persister, settings", + [ + (di_CCCC, fake_hidpp.r_empty, 1.0, None, None, 0), + (di_C318, fake_hidpp.r_empty, 1.0, {}, {}, 0), + (di_C318, fake_hidpp.r_keyboard_1, 1.0, {"n": "n"}, {"n": "n"}, 1), + (di_B530, fake_hidpp.r_keyboard_2, 4.5, {"m": "m"}, {"m": "m"}, 1), + (di_C068, fake_hidpp.r_mouse_1, 1.0, {"o": "o"}, {"o": "o"}, 2), + (di_C08A, fake_hidpp.r_mouse_2, 4.5, {"p": "p"}, {"p": "p"}, 0), + ], +) +def test_device_settings(device_info, responses, protocol, p, persister, settings, mocker): + mocker.patch("solaar.configuration.persister", return_value=p) + test_device = FakeDevice(responses, None, None, True, device_info=device_info) + test_device._name = "TestDevice" + test_device._protocol = protocol + + assert test_device.persister == persister + assert len(test_device.settings) == settings + + +@pytest.mark.parametrize( + "device_info, responses, protocol, expected_battery, changed", + [ + (di_C318, fake_hidpp.r_empty, 1.0, None, {"active": True, "alert": 0, "reason": None}), + ( + di_C318, + fake_hidpp.r_keyboard_1, + 1.0, + common.Battery(BatteryLevelApproximation.GOOD.value, None, BatteryStatus.DISCHARGING, None), + {"active": True, "alert": 0, "reason": None}, + ), + ( + di_B530, + fake_hidpp.r_keyboard_2, + 4.5, + common.Battery(18, 52, None, None), + {"active": True, "alert": 0, "reason": None}, + ), + ], +) +def test_device_battery(device_info, responses, protocol, expected_battery, changed, mocker): + test_device = FakeDevice(responses, None, None, online=True, device_info=device_info) + test_device._name = "TestDevice" + test_device._protocol = protocol + spy_changed = mocker.spy(test_device, "changed") + + assert test_device.battery() == expected_battery + test_device.read_battery() + spy_changed.assert_called_with(**changed) diff --git a/tests/logitech_receiver/test_diversion.py b/tests/logitech_receiver/test_diversion.py new file mode 100644 index 00000000..70aba163 --- /dev/null +++ b/tests/logitech_receiver/test_diversion.py @@ -0,0 +1,127 @@ +import textwrap + +from unittest import mock +from unittest.mock import mock_open + +import pytest + +from logitech_receiver import diversion +from logitech_receiver.base import HIDPPNotification +from logitech_receiver.hidpp20_constants import SupportedFeature + + +@pytest.fixture +def rule_config(): + rule_content = """ + %YAML 1.3 + --- + - MouseGesture: Mouse Left + - KeyPress: + - [Control_L, Alt_L, Left] + - click + ... + --- + - MouseGesture: Mouse Up + - KeyPress: + - [Super_L, Up] + - click + ... + --- + - Test: [thumb_wheel_up, 10] + - KeyPress: + - [Control_L, Page_Down] + - click + ... + --- + """ + return textwrap.dedent(rule_content) + + +def test_load_rule_config(rule_config): + expected_rules = [ + [ + diversion.MouseGesture, + diversion.KeyPress, + ], + [diversion.MouseGesture, diversion.KeyPress], + [diversion.Test, diversion.KeyPress], + ] + + with mock.patch("builtins.open", new=mock_open(read_data=rule_config)): + loaded_rules = diversion._load_rule_config(file_path=mock.Mock()) + + assert len(loaded_rules.components) == 2 # predefined and user configured rules + user_configured_rules = loaded_rules.components[0] + assert isinstance(user_configured_rules, diversion.Rule) + + for components, expected_components in zip(user_configured_rules.components, expected_rules): + for component, expected_component in zip(components.components, expected_components): + assert isinstance(component, expected_component) + + +def test_diversion_rule(): + args = [ + { + "Rule": [ # Implement problematic keys for Craft and MX Master + {"Rule": [{"Key": ["Brightness Down", "pressed"]}, {"KeyPress": "XF86_MonBrightnessDown"}]}, + {"Rule": [{"Key": ["Brightness Up", "pressed"]}, {"KeyPress": "XF86_MonBrightnessUp"}]}, + ] + }, + ] + + rule = diversion.Rule(args) + + assert len(rule.components) == 1 + root_rule = rule.components[0] + assert isinstance(root_rule, diversion.Rule) + + assert len(root_rule.components) == 2 + for component in root_rule.components: + assert isinstance(component, diversion.Rule) + assert len(component.components) == 2 + + key = component.components[0] + assert isinstance(key, diversion.Key) + key = component.components[1] + assert isinstance(key, diversion.KeyPress) + + +def test_key_is_down(): + result = diversion.key_is_down(key=diversion.CONTROL.G2) + + assert result is False + + +def test_feature(): + expected_data = {"Feature": "CONFIG CHANGE"} + + result = diversion.Feature("CONFIG_CHANGE") + + assert result.data() == expected_data + + +@pytest.mark.parametrize( + "feature, data", + [ + ( + SupportedFeature.REPROG_CONTROLS_V4, + [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], + ), + (SupportedFeature.GKEY, [0x01, 0x02, 0x03, 0x04]), + (SupportedFeature.MKEYS, [0x01, 0x02, 0x03, 0x04]), + (SupportedFeature.MR, [0x01, 0x02, 0x03, 0x04]), + (SupportedFeature.THUMB_WHEEL, [0x01, 0x02, 0x03, 0x04, 0x05]), + (SupportedFeature.DEVICE_UNIT_ID, [0x01, 0x02, 0x03, 0x04, 0x05]), + ], +) +def test_process_notification(feature, data): + device_mock = mock.Mock() + notification = HIDPPNotification( + report_id=0x01, + devnumber=1, + sub_id=0x13, + address=0x00, + data=bytes(data), + ) + + diversion.process_notification(device_mock, notification, feature) diff --git a/tests/logitech_receiver/test_hidpp10.py b/tests/logitech_receiver/test_hidpp10.py new file mode 100644 index 00000000..368bc896 --- /dev/null +++ b/tests/logitech_receiver/test_hidpp10.py @@ -0,0 +1,377 @@ +from dataclasses import dataclass +from dataclasses import field +from typing import Any +from typing import List +from typing import Optional + +import pytest + +from logitech_receiver import common +from logitech_receiver import hidpp10 +from logitech_receiver import hidpp10_constants +from logitech_receiver.hidpp10_constants import PairingError +from logitech_receiver.hidpp10_constants import Registers + +_hidpp10 = hidpp10.Hidpp10() + + +@dataclass +class Response: + response: Optional[str] + request_id: int + params: Any + + +@dataclass +class Device: + name: str = "Device" + online: bool = True + kind: str = "fake" + protocol: float = 1.0 + isDevice: bool = False # incorrect, but useful here + registers: List[Registers] = field(default_factory=list) + responses: List[Response] = field(default_factory=list) + + def request(self, id, params=None, no_reply=False): + if params is None: + params = [] + print("REQUEST ", self.name, hex(id), params) + for r in self.responses: + if id == r.request_id and params == r.params: + print("RESPONSE", self.name, hex(r.request_id), r.params, r.response) + return bytes.fromhex(r.response) if r.response is not None else None + + +device_offline = Device("OFFLINE", False) +device_leds = Device("LEDS", True, registers=[Registers.THREE_LEDS, Registers.BATTERY_STATUS]) +device_features = Device("FEATURES", True, protocol=4.5) + +registers_standard = [Registers.BATTERY_STATUS, Registers.FIRMWARE] +responses_standard = [ + Response("555555", 0x8100 | Registers.BATTERY_STATUS, 0x00), + Response("666666", 0x8100 | Registers.BATTERY_STATUS, 0x10), + Response("777777", 0x8000 | Registers.BATTERY_STATUS, 0x00), + Response("888888", 0x8000 | Registers.BATTERY_STATUS, 0x10), + Response("052100", 0x8100 | Registers.BATTERY_STATUS, []), + Response("ABCDEF", 0x8100 | Registers.FIRMWARE, 0x01), + Response("ABCDEF", 0x8100 | Registers.FIRMWARE, 0x02), + Response("ABCDEF", 0x8100 | Registers.FIRMWARE, 0x03), + Response("ABCDEF", 0x8100 | Registers.FIRMWARE, 0x04), + Response("000900", 0x8100 | Registers.NOTIFICATIONS, []), + Response("101010", 0x8100 | Registers.MOUSE_BUTTON_FLAGS, []), + Response("010101", 0x8100 | Registers.KEYBOARD_FN_SWAP, []), + Response("020202", 0x8100 | Registers.DEVICES_CONFIGURATION, []), + Response("030303", 0x8000 | Registers.DEVICES_CONFIGURATION, 0x00), +] +device_standard = Device("STANDARD", True, registers=registers_standard, responses=responses_standard) + + +@pytest.mark.parametrize( + "device, register, param, expected_result", + [ + (device_offline, Registers.THREE_LEDS, 0x00, None), + (device_standard, Registers.THREE_LEDS, 0x00, None), + (device_standard, Registers.BATTERY_STATUS, 0x00, "555555"), + (device_standard, Registers.BATTERY_STATUS, 0x10, "666666"), + ], +) +def test_read_register(device, register, param, expected_result, mocker): + spy_request = mocker.spy(device, "request") + + result = hidpp10.read_register(device, register, param) + + assert result == (bytes.fromhex(expected_result) if expected_result else None) + spy_request.assert_called_once_with(0x8100 | register, param) + + +@pytest.mark.parametrize( + "device, register, param, expected_result", + [ + (device_offline, Registers.THREE_LEDS, 0x00, None), + (device_standard, Registers.THREE_LEDS, 0x00, None), + (device_standard, Registers.BATTERY_STATUS, 0x00, "777777"), + (device_standard, Registers.BATTERY_STATUS, 0x10, "888888"), + ], +) +def test_write_register(device, register, param, expected_result, mocker): + spy_request = mocker.spy(device, "request") + + result = hidpp10.write_register(device, register, param) + + assert result == (bytes.fromhex(expected_result) if expected_result else None) + spy_request.assert_called_once_with(0x8000 | register, param) + + +def device_charge(name, response): + responses = [Response(response, 0x8100 | Registers.BATTERY_CHARGE, [])] + return Device(name, registers=[], responses=responses) + + +device_charge1 = device_charge("DISCHARGING", "550030") +device_charge2 = device_charge("RECHARGING", "440050") +device_charge3 = device_charge("FULL", "600090") +device_charge4 = device_charge("OTHER", "220000") + + +def device_status(name, response): + responses = [Response(response, 0x8100 | Registers.BATTERY_STATUS, [])] + return Device(name, registers=[], responses=responses) + + +device_status1 = device_status("FULL", "072200") +device_status2 = device_status("GOOD", "052100") +device_status3 = device_status("LOW", "032200") +device_status4 = device_status("CRITICAL", "010100") +device_status5 = device_status("EMPTY", "000000") +device_status6 = device_status("NOSTATUS", "002200") + + +@pytest.mark.parametrize( + "device, expected_result, expected_register", + [ + (device_offline, None, None), + (device_features, None, None), + (device_leds, None, None), + ( + device_standard, + common.Battery(common.BatteryLevelApproximation.GOOD, None, common.BatteryStatus.RECHARGING, None), + Registers.BATTERY_STATUS, + ), + (device_charge1, common.Battery(0x55, None, common.BatteryStatus.DISCHARGING, None), Registers.BATTERY_CHARGE), + ( + device_charge2, + common.Battery(0x44, None, common.BatteryStatus.RECHARGING, None), + Registers.BATTERY_CHARGE, + ), + ( + device_charge3, + common.Battery(0x60, None, common.BatteryStatus.FULL, None), + Registers.BATTERY_CHARGE, + ), + (device_charge4, common.Battery(0x22, None, None, None), Registers.BATTERY_CHARGE), + ( + device_status1, + common.Battery(common.BatteryLevelApproximation.FULL, None, common.BatteryStatus.FULL, None), + Registers.BATTERY_STATUS, + ), + ( + device_status2, + common.Battery(common.BatteryLevelApproximation.GOOD, None, common.BatteryStatus.RECHARGING, None), + Registers.BATTERY_STATUS, + ), + ( + device_status3, + common.Battery(common.BatteryLevelApproximation.LOW, None, common.BatteryStatus.FULL, None), + Registers.BATTERY_STATUS, + ), + ( + device_status4, + common.Battery(common.BatteryLevelApproximation.CRITICAL, None, None, None), + Registers.BATTERY_STATUS, + ), + ( + device_status5, + common.Battery(common.BatteryLevelApproximation.EMPTY, None, common.BatteryStatus.DISCHARGING, None), + Registers.BATTERY_STATUS, + ), + ( + device_status6, + common.Battery(None, None, common.BatteryStatus.FULL, None), + Registers.BATTERY_STATUS, + ), + ], +) +def test_hidpp10_get_battery(device, expected_result, expected_register): + result = _hidpp10.get_battery(device) + + assert result == expected_result + if expected_register is not None: + assert expected_register in device.registers + + +@pytest.mark.parametrize( + "device, expected_firmwares", + [ + (device_offline, []), + ( + device_standard, + [ + common.FirmwareKind.Firmware, + common.FirmwareKind.Bootloader, + common.FirmwareKind.Other, + ], + ), + ], +) +def test_hidpp10_get_firmware(device, expected_firmwares): + firmwares = _hidpp10.get_firmware(device) + + if not expected_firmwares: + assert firmwares is None + else: + firmware_types = [firmware.kind for firmware in firmwares] + assert firmware_types == expected_firmwares + assert len(firmwares) == len(expected_firmwares) + + +@pytest.mark.parametrize( + "device, level, charging, warning, p1, p2", + [ + (device_leds, common.BatteryLevelApproximation.EMPTY, False, False, 0x33, 0x00), + (device_leds, common.BatteryLevelApproximation.CRITICAL, False, False, 0x22, 0x00), + (device_leds, common.BatteryLevelApproximation.LOW, False, False, 0x20, 0x00), + (device_leds, common.BatteryLevelApproximation.GOOD, False, False, 0x20, 0x02), + (device_leds, common.BatteryLevelApproximation.FULL, False, False, 0x20, 0x22), + (device_leds, None, True, False, 0x30, 0x33), + (device_leds, None, False, True, 0x02, 0x00), + (device_leds, None, False, False, 0x11, 0x11), + ], +) +def test_set_3leds(device, level, charging, warning, p1, p2, mocker): + spy_request = mocker.spy(device, "request") + + _hidpp10.set_3leds(device, level, charging, warning) + + spy_request.assert_called_once_with(0x8000 | Registers.THREE_LEDS, p1, p2) + + +@pytest.mark.parametrize("device", [device_offline, device_features]) +def test_set_3leds_missing(device, mocker): + spy_request = mocker.spy(device, "request") + + _hidpp10.set_3leds(device) + + assert spy_request.call_count == 0 + + +@pytest.mark.parametrize("device", [device_standard]) +def test_get_notification_flags(device): + result = _hidpp10.get_notification_flags(device) + + assert result == hidpp10_constants.NotificationFlag(int("000900", 16)) + + +def test_set_notification_flags(mocker): + device = device_standard + spy_request = mocker.spy(device, "request") + + result = _hidpp10.set_notification_flags( + device, hidpp10_constants.NotificationFlag.BATTERY_STATUS, hidpp10_constants.NotificationFlag.WIRELESS + ) + + spy_request.assert_called_once_with(0x8000 | Registers.NOTIFICATIONS, b"\x10\x01\x00") + assert result is not None + + +def test_set_notification_flags_bad(mocker): + device = device_features + spy_request = mocker.spy(device, "request") + + result = _hidpp10.set_notification_flags( + device, hidpp10_constants.NotificationFlag.BATTERY_STATUS, hidpp10_constants.NotificationFlag.WIRELESS + ) + + assert spy_request.call_count == 0 + assert result is None + + +@pytest.mark.parametrize( + "flag_bits, expected_names", + [ + # doesn't work in Python 3.8 and 3.10 for some reason (None, ""), + (hidpp10_constants.NotificationFlag(0x0), "none"), + (hidpp10_constants.NotificationFlag(0x001000), "multi touch"), + (hidpp10_constants.NotificationFlag(0x080000), "mouse extra buttons"), + ( + hidpp10_constants.NotificationFlag(0x080400), + ("link quality\n mouse extra buttons"), + ), + ], +) +def test_notification_flag_str(flag_bits, expected_names): + flag_names = hidpp10_constants.flags_to_str( + hidpp10_constants.NotificationFlag(flag_bits) if flag_bits is not None else None, fallback="none" + ) + + assert flag_names == expected_names + + +@pytest.mark.parametrize( + "flags, expected", + [ + (None, []), + # composite flag, .name is None on Python < 3.11 + (hidpp10_constants.NotificationFlag(0x000900), ["software present", "wireless"]), + (hidpp10_constants.NotificationFlag(0x100000), ["battery status"]), + (hidpp10_constants.NotificationFlag(0x080000), ["mouse extra buttons"]), + ], +) +def test_notification_flag_names(flags, expected): + result = hidpp10_constants.NotificationFlag.flag_names(flags) + assert result == expected + + +def test_notification_flag_names_none_does_not_crash(): + """Regression test for #3184: flag_names crashes with AttributeError when + flags.name is None, which happens with certain receiver firmware versions + (e.g. Nano C52F reporting 0x000900).""" + flags_with_none_name = hidpp10_constants.NotificationFlag(0x000900) + result = hidpp10_constants.NotificationFlag.flag_names(flags_with_none_name) + assert "software present" in result + assert "wireless" in result + assert isinstance(result, list) + + +def test_get_device_features(): + result = _hidpp10.get_device_features(device_standard) + + assert result == int("101010", 16) + + +@pytest.mark.parametrize( + "device, register, expected_result", + [ + (device_standard, Registers.BATTERY_STATUS, "052100"), + (device_standard, Registers.MOUSE_BUTTON_FLAGS, "101010"), + (device_standard, Registers.KEYBOARD_ILLUMINATION, None), + (device_features, Registers.KEYBOARD_ILLUMINATION, None), + ], +) +def test_get_register(device, register, expected_result): + result = _hidpp10._get_register(device, register) + + assert result == (int(expected_result, 16) if expected_result is not None else None) + + +@pytest.mark.parametrize( + "device, expected_result", + [ + (device_standard, 2), + (device_features, None), + ], +) +def test_get_configuration_pending_flags(device, expected_result): + result = hidpp10.get_configuration_pending_flags(device) + + assert result == expected_result + + +@pytest.mark.parametrize( + "device, expected_result", + [ + (device_standard, True), + (device_features, False), + ], +) +def test_set_configuration_pending_flags(device, expected_result): + result = hidpp10.set_configuration_pending_flags(device, 0x00) + + assert result == expected_result + + +def test_pairing_error(): + expected_label = "device not supported" + + res = PairingError.DEVICE_NOT_SUPPORTED.label + + assert res == expected_label diff --git a/tests/logitech_receiver/test_hidpp20_complex.py b/tests/logitech_receiver/test_hidpp20_complex.py new file mode 100644 index 00000000..1e3ef9dc --- /dev/null +++ b/tests/logitech_receiver/test_hidpp20_complex.py @@ -0,0 +1,1282 @@ +## Copyright (C) 2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import pytest +import yaml + +from logitech_receiver import common +from logitech_receiver import exceptions +from logitech_receiver import hidpp20 +from logitech_receiver import hidpp20_constants +from logitech_receiver import special_keys +from logitech_receiver.device import CenturionReceiver +from logitech_receiver.hidpp20 import KeyFlag +from logitech_receiver.hidpp20 import MappingFlag +from logitech_receiver.hidpp20_constants import GestureId + +from . import fake_hidpp + +_hidpp20 = hidpp20.Hidpp20() + +device_offline = fake_hidpp.Device("REGISTERS", False) +device_registers = fake_hidpp.Device("OFFLINE", True, 1.0) +device_nofeatures = fake_hidpp.Device("NOFEATURES", True, 4.5) +device_zerofeatures = fake_hidpp.Device("ZEROFEATURES", True, 4.5, [fake_hidpp.Response("0000", 0x0000, "0001")]) +device_broken = fake_hidpp.Device( + "BROKEN", True, 4.5, [fake_hidpp.Response("0500", 0x0000, "0001"), fake_hidpp.Response(None, 0x0100)] +) +device_standard = fake_hidpp.Device("STANDARD", True, 4.5, fake_hidpp.r_keyboard_2) + + +@pytest.mark.parametrize( + "device, expected_result, expected_count", + [ + (device_offline, False, 0), + (device_registers, False, 0), + (device_nofeatures, False, 0), + (device_zerofeatures, False, 0), + (device_broken, False, 0), + (device_standard, True, 9), + ], +) +def test_FeaturesArray_check(device, expected_result, expected_count): + featuresarray = hidpp20.FeaturesArray(device) + + result = featuresarray._check() + result2 = featuresarray._check() + + assert result == expected_result + assert result2 == expected_result + assert (hidpp20_constants.SupportedFeature.ROOT in featuresarray) == expected_result + assert len(featuresarray) == expected_count + assert bool(featuresarray) == expected_result + + +@pytest.mark.parametrize( + "device, expected0, expected1, expected2, expected5, expected5v", + [ + (device_zerofeatures, None, None, None, None, None), + (device_standard, 0x0000, 0x0001, 0x0020, hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4, 3), + ], +) +def test_FeaturesArray_get_feature(device, expected0, expected1, expected2, expected5, expected5v): + featuresarray = hidpp20.FeaturesArray(device) + device.features = featuresarray + + result0 = featuresarray.get_feature(0) + result1 = featuresarray.get_feature(1) + result2 = featuresarray.get_feature(2) + result5 = featuresarray.get_feature(5) + result2r = featuresarray.get_feature(2) + result5v = featuresarray.get_feature_version(hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4) + + assert result0 == expected0 + assert result1 == expected1 + assert result2 == expected2 + assert result2r == expected2 + assert result5 == expected5 + assert result5v == expected5v + + +@pytest.mark.parametrize( + "device, expected_result", + [ + (device_zerofeatures, []), + ( + device_standard, + [ + (hidpp20_constants.SupportedFeature.ROOT, 0), + (hidpp20_constants.SupportedFeature.FEATURE_SET, 1), + (hidpp20_constants.SupportedFeature.CONFIG_CHANGE, 2), + (hidpp20_constants.SupportedFeature.DEVICE_FW_VERSION, 3), + (hidpp20_constants.SupportedFeature.CENTURION_DEVICE_INFO, 4), + (hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4, 5), + # Indices 6 and 7 have no responses — get_feature returns None, enumerate skips them + (hidpp20_constants.SupportedFeature.BATTERY_STATUS, 8), + ], + ), + ], +) +def test_FeaturesArray_enumerate(device, expected_result): + featuresarray = hidpp20.FeaturesArray(device) + + result = list(featuresarray.enumerate()) + + assert result == expected_result + + +def test_FeaturesArray_setitem(): + featuresarray = hidpp20.FeaturesArray(device_standard) + + featuresarray[hidpp20_constants.SupportedFeature.ROOT] = 3 + featuresarray[hidpp20_constants.SupportedFeature.FEATURE_SET] = 5 + featuresarray[hidpp20_constants.SupportedFeature.FEATURE_SET] = 4 + + assert featuresarray[hidpp20_constants.SupportedFeature.FEATURE_SET] == 4 + assert featuresarray.inverse[4] == hidpp20_constants.SupportedFeature.FEATURE_SET + + +def test_FeaturesArray_delitem(): + featuresarray = hidpp20.FeaturesArray(device_standard) + + with pytest.raises(ValueError): + del featuresarray[5] + + +@pytest.mark.parametrize( + "device, expected0, expected1, expected2, expected1v", + [(device_zerofeatures, None, None, None, None), (device_standard, 0, 5, None, 3)], +) +def test_FeaturesArray_getitem(device, expected0, expected1, expected2, expected1v): + featuresarray = hidpp20.FeaturesArray(device) + device.features = featuresarray + + result_get0 = featuresarray[hidpp20_constants.SupportedFeature.ROOT] + result_get1 = featuresarray[hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4] + result_get2 = featuresarray[hidpp20_constants.SupportedFeature.GKEY] + result_1v = featuresarray.get_feature_version(hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4) + + assert result_get0 == expected0 + assert result_get1 == expected1 + assert result_get2 == expected2 + assert result_1v == expected1v + + +@pytest.mark.parametrize( + "device, index, cid, task_id, flags, default_task, expected_flags", + [ + (device_standard, 2, 1, 1, 0x30, "Volume Up", KeyFlag.REPROGRAMMABLE | KeyFlag.DIVERTABLE), + (device_standard, 1, 2, 2, 0x20, "Volume Down", KeyFlag.DIVERTABLE), + ], +) +def test_reprogrammable_key_key(device, index, cid, task_id, flags, default_task, expected_flags): + key = hidpp20.ReprogrammableKey(device, index, cid, task_id, flags) + + assert key._device == device + assert key.index == index + assert key._cid == cid + assert key._tid == task_id + assert key._flags == flags + assert key.key == special_keys.CONTROL[cid] + assert key.default_task == common.NamedInt(cid, default_task) + assert key.flags == expected_flags + + +@pytest.mark.parametrize( + "device, index, cid, task_id, flags, pos, group, gmask, default_task, expected_flags, group_names", + [ + ( + device_standard, + 1, + 0x51, + 0x39, + 0x60, + 0, + 1, + 1, + "Right Click", + KeyFlag.DIVERTABLE | KeyFlag.PERSISTENTLY_DIVERTABLE, + ["g1"], + ), + ( + device_standard, + 2, + 0x52, + 0x3A, + 0x11, + 1, + 2, + 3, + "Mouse Middle Button", + KeyFlag.MSE | KeyFlag.REPROGRAMMABLE, + ["g1", "g2"], + ), + ( + device_standard, + 3, + 0x53, + 0x3C, + 0x110, + 2, + 2, + 7, + "Mouse Back Button", + KeyFlag.REPROGRAMMABLE | KeyFlag.RAW_XY, + ["g1", "g2", "g3"], + ), + ], +) +def test_reprogrammable_key_v4_key( + device, index, cid, task_id, flags, pos, group, gmask, default_task, expected_flags, group_names +): + key = hidpp20.ReprogrammableKeyV4(device, index, cid, task_id, flags, pos, group, gmask) + + assert key._device == device + assert key.index == index + assert key._cid == cid + assert key._tid == task_id + assert key._flags == flags + assert key.pos == pos + assert key.group == group + assert key._gmask == gmask + assert key.key == special_keys.CONTROL[cid] + assert key.default_task == common.NamedInt(cid, default_task) + assert key.flags == expected_flags + assert list(key.group_mask) == group_names + + +@pytest.mark.parametrize( + "responses, index, mapped_to, remappable_to, expected_mapping_flags", + [ + (fake_hidpp.responses_key, 1, "Right Click", common.UnsortedNamedInts(Right_Click=81, Left_Click=80), MappingFlag(0)), + (fake_hidpp.responses_key, 2, "Left Click", None, MappingFlag.DIVERTED), + (fake_hidpp.responses_key, 3, "Mouse Back Button", None, MappingFlag.DIVERTED | MappingFlag.PERSISTENTLY_DIVERTED), + (fake_hidpp.responses_key, 4, "Mouse Forward Button", None, MappingFlag.DIVERTED | MappingFlag.RAW_XY_DIVERTED), + ], +) +# these fields need access all the key data, so start by setting up a device and its key data +def test_reprogrammable_key_v4_query(responses, index, mapped_to, remappable_to, expected_mapping_flags): + device = fake_hidpp.Device( + "KEY", responses=responses, feature=hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4, offset=5 + ) + device._keys = _hidpp20.get_keys(device) + + key = device.keys[index] + + assert key.mapped_to == mapped_to + assert (key.remappable_to == remappable_to) or remappable_to is None + assert key.mapping_flags == expected_mapping_flags + + +@pytest.mark.parametrize( + "responses, index, diverted, persistently_diverted, rawXY_reporting, remap, sets", + [ + (fake_hidpp.responses_key, 1, True, False, True, 0x52, ["0051080000"]), + (fake_hidpp.responses_key, 2, False, True, False, 0x51, ["0052020000", "0052200000", "0052000051"]), + (fake_hidpp.responses_key, 3, False, True, True, 0x50, ["0053020000", "00530C0000", "0053300000", "0053000050"]), + (fake_hidpp.responses_key, 4, False, False, False, 0x50, ["0056020000", "0056080000", "0056200000", "0056000050"]), + ], +) +def test_reprogrammable_key_v4_set(responses, index, diverted, persistently_diverted, rawXY_reporting, remap, sets, mocker): + responses += [fake_hidpp.Response(r, 0x530, r) for r in sets] + device = fake_hidpp.Device( + "KEY", responses=responses, feature=hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4, offset=5 + ) + device._keys = _hidpp20.get_keys(device) + device._keys._ensure_all_keys_queried() # do this now so that the last requests are sets + spy_request = mocker.spy(device, "request") + + key = device.keys[index] + _mapping_flags = key.mapping_flags + + if hidpp20.KeyFlag.DIVERTABLE in key.flags or not diverted: + key.set_diverted(diverted) + else: + with pytest.raises(exceptions.FeatureNotSupported): + key.set_diverted(diverted) + assert (MappingFlag.DIVERTED in key.mapping_flags) == (diverted and hidpp20.KeyFlag.DIVERTABLE in key.flags) + + if hidpp20.KeyFlag.PERSISTENTLY_DIVERTABLE in key.flags or not persistently_diverted: + key.set_persistently_diverted(persistently_diverted) + else: + with pytest.raises(exceptions.FeatureNotSupported): + key.set_persistently_diverted(persistently_diverted) + assert (hidpp20.MappingFlag.PERSISTENTLY_DIVERTED in key.mapping_flags) == ( + persistently_diverted and hidpp20.KeyFlag.PERSISTENTLY_DIVERTABLE in key.flags + ) + + if hidpp20.KeyFlag.RAW_XY in key.flags or not rawXY_reporting: + key.set_rawXY_reporting(rawXY_reporting) + else: + with pytest.raises(exceptions.FeatureNotSupported): + key.set_rawXY_reporting(rawXY_reporting) + assert (MappingFlag.RAW_XY_DIVERTED in key.mapping_flags) == (rawXY_reporting and hidpp20.KeyFlag.RAW_XY in key.flags) + + if remap in key.remappable_to or remap == 0: + key.remap(remap) + else: + with pytest.raises(exceptions.FeatureNotSupported): + key.remap(remap) + assert (key.mapped_to == remap) or (remap not in key.remappable_to and remap != 0) + + fake_hidpp.match_requests(len(sets), responses, spy_request.call_args_list) + + +@pytest.mark.parametrize( + "r, index, cid, actionId, remapped, mask, status, action, modifiers, byts, remap", + [ + (fake_hidpp.responses_key, 1, 0x0051, 0x02, 0x0002, 0x01, 0, "Mouse Button: 2", "Cntrl+", "02000201", "01000400"), + (fake_hidpp.responses_key, 2, 0x0052, 0x01, 0x0001, 0x00, 1, "Key: 1", "", "01000100", "02005004"), + (fake_hidpp.responses_key, 3, 0x0053, 0x02, 0x0001, 0x00, 1, "Mouse Button: 1", "", "02000100", "7FFFFFFF"), + ], +) +def test_remappable_action(r, index, cid, actionId, remapped, mask, status, action, modifiers, byts, remap, mocker): + if int(remap, 16) == special_keys.KEYS_Default: + responses = r + [ + fake_hidpp.Response("040000", 0x0000, "1C00"), + fake_hidpp.Response("00", 0x450, f"{cid:04X}" + "FF"), + ] + else: + responses = r + [ + fake_hidpp.Response("040000", 0x0000, "1C00"), + fake_hidpp.Response("00", 0x440, f"{cid:04X}" + "FF" + remap), + ] + device = fake_hidpp.Device( + "KEY", responses=responses, feature=hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4, offset=5 + ) + key = hidpp20.PersistentRemappableAction(device, index, cid, actionId, remapped, mask, status) + spy_request = mocker.spy(device, "request") + + assert key._device == device + assert key.index == index + assert key._cid == cid + assert key.actionId == actionId + assert key.remapped == remapped + assert key._modifierMask == mask + assert key.cidStatus == status + assert key.key == special_keys.CONTROL[cid] + assert key.actionType == special_keys.ACTIONID[actionId] + assert key.action == action + assert key.modifiers == modifiers + assert key.data_bytes.hex().upper() == byts + + key.remap(bytes.fromhex(remap)) + assert key.data_bytes.hex().upper() == (byts if int(remap, 16) == special_keys.KEYS_Default else remap) + + if int(remap, 16) != special_keys.KEYS_Default: + fake_hidpp.match_requests(1, responses, spy_request.call_args_list) + + +# KeysArray methods tested in KeysArrayV4 + +# KeysArrayV2 not tested as there is no documentation + + +@pytest.mark.parametrize( + "device, index", [(device_zerofeatures, -1), (device_zerofeatures, 5), (device_standard, -1), (device_standard, 6)] +) +def test_KeysArrayV4_index_error(device, index): + keysarray = hidpp20.KeysArrayV4(device, 5) + + with pytest.raises(IndexError): + keysarray[index] + + with pytest.raises(IndexError): + keysarray._query_key(index) + + +@pytest.mark.parametrize("device, index, top, cid", [(device_standard, 0, 2, 0x0011), (device_standard, 4, 5, 0x0003)]) +def test_KeysArrayV4_query_key(device, index, top, cid): + keysarray = hidpp20.KeysArrayV4(device, 5) + + keysarray._query_key(index) + + assert keysarray.keys[index]._cid == cid + assert len(keysarray[index:top]) == top - index + assert len(list(keysarray)) == 5 + + +@pytest.mark.parametrize( + "device, count, index, cid, task_id, flags, pos, group, gmask", + [ + (device_standard, 4, 0, 0x0011, 0x0012, 0xCDAB, 1, 2, 3), + (device_standard, 6, 1, 0x0111, 0x0022, 0xCDAB, 1, 2, 3), + (device_standard, 8, 3, 0x0311, 0x0032, 0xCDAB, 1, 2, 4), + ], +) +def test_KeysArrayV4__getitem(device, count, index, cid, task_id, flags, pos, group, gmask): + keysarray = hidpp20.KeysArrayV4(device, count) + + result = keysarray[index] + + assert result._device == device + assert result.index == index + assert result._cid == cid + assert result._tid == task_id + assert result._flags == flags + assert result.pos == pos + assert result.group == group + assert result._gmask == gmask + + +@pytest.mark.parametrize( + "key, index", [(special_keys.CONTROL.Volume_Up_old, 2), (special_keys.CONTROL.Mute, 4), (special_keys.CONTROL.Next, None)] +) +def test_KeysArrayV4_index(key, index): + keysarray = hidpp20.KeysArrayV4(device_standard, 7) + + result = keysarray.index(key) + + assert result == index + + +device_key = fake_hidpp.Device( + "KEY", responses=fake_hidpp.responses_key, feature=hidpp20_constants.SupportedFeature.REPROG_CONTROLS_V4, offset=5 +) + + +@pytest.mark.parametrize( + "key, expected_index, expected_mapped_to, expected_remappable_to", + [ + ( + special_keys.CONTROL.Left_Button, + 0, + common.NamedInt(0x50, "Left Click"), + [common.NamedInt(0x50, "Left Click"), common.NamedInt(0x51, "Right Click")], + ), + ( + special_keys.CONTROL.Right_Button, + 1, + common.NamedInt(0x51, "Right Click"), + [common.NamedInt(0x51, "Right Click"), common.NamedInt(0x50, "Left Click")], + ), + (special_keys.CONTROL.Middle_Button, 2, common.NamedInt(0x50, "Left Click"), None), + (special_keys.CONTROL.Back_Button, 3, common.NamedInt(0x53, "Mouse Back Button"), None), + (special_keys.CONTROL.Forward_Button, 4, common.NamedInt(0x56, "Mouse Forward Button"), None), + (special_keys.CONTROL.Mouse_Gesture_Button, 5, common.NamedInt(0xC3, "Gesture Button Navigation"), None), + (special_keys.CONTROL.Smart_Shift, 6, common.NamedInt(0x50, "Left Click"), None), + (special_keys.CONTROL.Virtual_Gesture_Button, 7, common.NamedInt(0x51, "Right Click"), None), + ], +) +def test_keys_array_v4_key(key, expected_index, expected_mapped_to, expected_remappable_to): + device_key._keys = _hidpp20.get_keys(device_key) + device_key._keys._ensure_all_keys_queried() + + index = device_key._keys.index(key) + mapped_to = device_key._keys[expected_index].mapped_to + remappable_to = device_key._keys[expected_index].remappable_to + + assert index == expected_index + assert mapped_to == expected_mapped_to + if expected_remappable_to is not None: + assert list(remappable_to) == expected_remappable_to + + +@pytest.mark.parametrize( + "device, index", [(device_zerofeatures, -1), (device_zerofeatures, 5), (device_standard, -1), (device_standard, 6)] +) +def test_KeysArrayPersistent_index_error(device, index): + keysarray = hidpp20.KeysArrayPersistent(device, 5) + + with pytest.raises(IndexError): + keysarray[index] + + with pytest.raises(IndexError): + keysarray._query_key(index) + + +@pytest.mark.parametrize( + "responses, key, index, mapped_to, capabilities", + [ + (fake_hidpp.responses_remap, special_keys.CONTROL.Left_Button, 0, common.NamedInt(0x01, "Mouse Button Left"), 0x41), + (fake_hidpp.responses_remap, special_keys.CONTROL.Right_Button, 1, common.NamedInt(0x01, "Mouse Button Left"), 0x41), + (fake_hidpp.responses_remap, special_keys.CONTROL.Middle_Button, 2, common.NamedInt(0x51, "DOWN"), 0x41), + ], +) +def test_KeysArrayPersistent_key(responses, key, index, mapped_to, capabilities): + device = fake_hidpp.Device( + "REMAP", responses=responses, feature=hidpp20_constants.SupportedFeature.PERSISTENT_REMAPPABLE_ACTION + ) + device._remap_keys = _hidpp20.get_remap_keys(device) + device._remap_keys._ensure_all_keys_queried() + + assert device._remap_keys.index(key) == index + assert device._remap_keys[index].remapped == mapped_to + assert device._remap_keys.capabilities == capabilities + + +@pytest.mark.parametrize( + "id, length, minimum, maximum, widget, min, max, wid, string", + [ + ("left", 1, 5, 8, "Widget", 5, 8, "Widget", "left"), + ("left", 1, None, None, None, 0, 255, "Scale", "left"), + ], +) +def test_SubParam(id, length, minimum, maximum, widget, min, max, wid, string): + subparam = hidpp20.SubParam(id, length, minimum, maximum, widget) + + assert subparam.id == id + assert subparam.length == length + assert subparam.minimum == min + assert subparam.maximum == max + assert subparam.widget == wid + assert subparam.__str__() == string + assert subparam.__repr__() == string + + +@pytest.mark.parametrize( + "device, low, high, next_index, next_diversion_index, name, cbe, si, sdi, eom, dom", + [ + (device_standard, 0x01, 0x01, 5, 10, GestureId.TAP_1_FINGER, True, 5, None, (0, 0x20), (None, None)), + (device_standard, 0x03, 0x02, 6, 11, GestureId.TAP_3_FINGER, False, None, 11, (None, None), (1, 0x08)), + ], +) +def test_gesture(device, low, high, next_index, next_diversion_index, name, cbe, si, sdi, eom, dom): + gesture = hidpp20.Gesture(device, low, high, next_index, next_diversion_index) + + assert gesture._device == device + assert gesture.id == low + assert gesture.gesture == name + assert gesture.can_be_enabled == cbe + assert gesture.can_be_enabled == cbe + assert gesture.index == si + assert gesture.diversion_index == sdi + assert gesture.enable_offset_mask() == eom + assert gesture.diversion_offset_mask() == dom + assert gesture.as_int() == low + assert int(gesture) == low + + +@pytest.mark.parametrize( + "responses, gest, enabled, diverted, set_result, unset_result, divert_result, undivert_result", + [ + (fake_hidpp.responses_gestures, 20, None, None, None, None, None, None), + (fake_hidpp.responses_gestures, 1, True, False, "01", "00", "01", "00"), + (fake_hidpp.responses_gestures, 45, False, None, "01", "00", None, None), + ], +) +def test_Gesture_set(responses, gest, enabled, diverted, set_result, unset_result, divert_result, undivert_result): + device = fake_hidpp.Device("GESTURE", responses=responses, feature=hidpp20_constants.SupportedFeature.GESTURE_2) + gestures = _hidpp20.get_gestures(device) + + gesture = gestures.gesture(gest) + + assert gesture.enabled() == enabled + assert gesture.diverted() == diverted + assert gesture.set(True) == (bytes.fromhex(set_result) if set_result is not None else None) + assert gesture.set(False) == (bytes.fromhex(unset_result) if unset_result is not None else None) + assert gesture.divert(True) == (bytes.fromhex(divert_result) if divert_result is not None else None) + assert gesture.divert(False) == (bytes.fromhex(undivert_result) if undivert_result is not None else None) + + +@pytest.mark.parametrize( + "responses, prm, id, index, size, value, default_value, write1, write2", + [ + (fake_hidpp.responses_gestures, 4, hidpp20_constants.ParamId.SCALE_FACTOR, 0, 2, 256, 256, "0080", "0180"), + ], +) +def test_param(responses, prm, id, index, size, value, default_value, write1, write2): + device = fake_hidpp.Device("GESTURE", responses=responses, feature=hidpp20_constants.SupportedFeature.GESTURE_2) + gestures = _hidpp20.get_gestures(device) + + param = gestures.param(prm) + + assert param.id == id + assert param.index == index + assert param.size == size + assert param.value == value + assert param.default_value == default_value + assert param.param == id + assert int(param) == id + assert param.write(bytes.fromhex(write1)).hex().upper() == f"{index:02X}" + write1 + "FF" + assert param.write(bytes.fromhex(write2)).hex().upper() == f"{index:02X}" + write2 + "FF" + + +@pytest.mark.parametrize( + "responses, id, s, byte_count, expected_value, expected_string", + [ + (fake_hidpp.responses_gestures, 1, hidpp20.SpecGesture.DVI_FIELD_WIDTH, 1, 8, "[dvi field width=8]"), + (fake_hidpp.responses_gestures, 2, hidpp20.SpecGesture.FIELD_WIDTHS, 1, 8, "[field widths=8]"), + (fake_hidpp.responses_gestures, 3, hidpp20.SpecGesture.PERIOD_UNIT, 2, 2048, "[period unit=2048]"), + ], +) +def test_spec(responses, id, s, byte_count, expected_value, expected_string): + device = fake_hidpp.Device("GESTURE", responses=responses, feature=hidpp20_constants.SupportedFeature.GESTURE_2) + gestures = _hidpp20.get_gestures(device) + + spec = gestures.specs[id] + + assert spec.id == id + assert spec.spec == s + assert spec.byte_count == byte_count + assert spec.value == expected_value + assert repr(spec) == expected_string + + +def test_Gestures(): + device = fake_hidpp.Device( + "GESTURES", responses=fake_hidpp.responses_gestures, feature=hidpp20_constants.SupportedFeature.GESTURE_2 + ) + gestures = _hidpp20.get_gestures(device) + + assert gestures + + assert len(gestures.gestures) == 17 + assert gestures.gesture(20) == gestures.gestures[20] + assert gestures.gesture_enabled(20) is None + assert gestures.gesture_enabled(1) is True + assert gestures.gesture_enabled(45) is False + assert gestures.enable_gesture(20) is None + assert gestures.enable_gesture(45) == bytes.fromhex("01") + assert gestures.disable_gesture(20) is None + assert gestures.disable_gesture(45) == bytes.fromhex("00") + + assert len(gestures.params) == 1 + assert gestures.param(4) == gestures.params[4] + assert gestures.get_param(4) == 256 + assert gestures.set_param(4, 128) is None + + assert len(gestures.specs) == 5 + assert gestures.specs[2].value == 8 + assert gestures.specs[4].value == 4 + + +responses_backlight = [ + fake_hidpp.Response("010118000001020003000400", 0x0400), + fake_hidpp.Response("0101FF00020003000400", 0x0410, "0101FF00020003000400"), +] + +device_backlight = fake_hidpp.Device( + "BACKLIGHT", responses=responses_backlight, feature=hidpp20_constants.SupportedFeature.BACKLIGHT2 +) + + +def test_Backlight(): + backlight = _hidpp20.get_backlight(device_backlight) + result = backlight.write() + + assert backlight + assert backlight.auto_supported + assert backlight.temp_supported + assert not backlight.perm_supported + assert backlight.dho == 0x0002 + assert backlight.dhi == 0x0003 + assert backlight.dpow == 0x0004 + assert result is not None + + +@pytest.mark.parametrize( + "hex, ID, color, speed, period, intensity, ramp, form", + [ + ("FFFFFFFFFFFFFFFFFFFFFF", None, None, None, None, None, None, None), + ("0000000000000000000000", common.NamedInt(0x0, "Disabled"), None, None, None, None, None, None), + ("0120304010000000000000", common.NamedInt(0x1, "Static"), 0x203040, None, None, None, 0x10, None), + ("0220304010000000000000", common.NamedInt(0x2, "Pulse"), 0x203040, 0x10, None, None, None, None), + ("0800000000000000000000", common.NamedInt(0x8, "Boot"), None, None, None, None, None, None), + ("0300000000005000000000", common.NamedInt(0x3, "Cycle"), None, None, 0x5000, 0x00, None, None), + ("0A20304010005020000000", common.NamedInt(0xA, "Breathe"), 0x203040, None, 0x1000, 0x20, None, 0x50), + ("0B20304000100000000000", common.NamedInt(0xB, "Ripple"), 0x203040, None, 0x1000, None, None, None), + ("0A01020300500407000000", common.NamedInt(0xA, "Breathe"), 0x010203, None, 0x0050, 0x07, None, 0x04), + ], +) +def test_LEDEffectSetting(hex, ID, color, speed, period, intensity, ramp, form): + byt = bytes.fromhex(hex) + setting = hidpp20.LEDEffectSetting.from_bytes(byt) + + assert setting.ID == ID + if ID is None: + assert setting.bytes == byt + else: + assert getattr(setting, "color", None) == color + assert getattr(setting, "speed", None) == speed + assert getattr(setting, "period", None) == period + assert getattr(setting, "intensity", None) == intensity + assert getattr(setting, "ramp", None) == ramp + assert getattr(setting, "form", None) == form + + assert setting.to_bytes() == byt + assert yaml.safe_load(yaml.dump(setting)) == setting + assert yaml.safe_load(str(setting)) == setting + + +@pytest.mark.parametrize( + "feature, function, response, ID, capabilities, period", + [ + [ + hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS, + 0x20, + fake_hidpp.Response("0102000300040005", 0x0420, "010200"), + 3, + 4, + 5, + ], + [ + hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS, + 0x20, + fake_hidpp.Response("0102000700080009", 0x0420, "010200"), + 7, + 8, + 9, + ], + ], +) +def test_LEDEffectInfo(feature, function, response, ID, capabilities, period): + device = fake_hidpp.Device(feature=feature, responses=[response]) + + info = hidpp20.LEDEffectInfo(feature, function, device, 1, 2) + + assert info.zindex == 1 + assert info.index == 2 + assert info.ID == ID + assert info.capabilities == capabilities + assert info.period == period + + +@pytest.mark.parametrize( + "feature, function, offset, effect_function, responses, index, location, count, id_1", + [ + [hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS, 0x10, 0, 0x20, fake_hidpp.zone_responses_1, 0, 1, 2, 0xB], + [hidpp20_constants.SupportedFeature.RGB_EFFECTS, 0x00, 1, 0x00, fake_hidpp.zone_responses_2, 0, 1, 2, 2], + ], +) +def test_LEDZoneInfo(feature, function, offset, effect_function, responses, index, location, count, id_1): + device = fake_hidpp.Device(feature=feature, responses=responses, offset=0x07) + + zone = hidpp20.LEDZoneInfo(feature, function, offset, effect_function, device, index) + + assert zone.index == index + assert zone.location == location + assert zone.count == count + assert len(zone.effects) == count + assert zone.effects[1].ID == id_1 + + +@pytest.mark.parametrize( + "responses, setting, expected_command", + [ + [fake_hidpp.zone_responses_1, hidpp20.LEDEffectSetting(ID=0), None], + [fake_hidpp.zone_responses_1, hidpp20.LEDEffectSetting(ID=3, period=0x20, intensity=0x50), "000000000000000020500000"], + [ + fake_hidpp.zone_responses_1, + hidpp20.LEDEffectSetting(ID=0xB, color=0x808080, period=0x20), + "000180808000002000000000", + ], + ], +) +def test_LEDZoneInfo_to_command(responses, setting, expected_command): + device = fake_hidpp.Device(feature=hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS, responses=responses, offset=0x07) + zone = hidpp20.LEDZoneInfo(hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS, 0x10, 0, 0x20, device, 0) + + command = zone.to_command(setting) + + assert command == (bytes.fromhex(expected_command) if expected_command is not None else None) + + +@pytest.mark.parametrize( + "feature, cls, responses, readable, count, count_0", + [ + [ + hidpp20_constants.SupportedFeature.COLOR_LED_EFFECTS, + hidpp20.LEDEffectsInfo, + fake_hidpp.effects_responses_1, + 1, + 1, + 2, + ], + [hidpp20_constants.SupportedFeature.RGB_EFFECTS, hidpp20.RGBEffectsInfo, fake_hidpp.effects_responses_2, 1, 1, 2], + ], +) +def test_LED_RGB_EffectsInfo(feature, cls, responses, readable, count, count_0): + device = fake_hidpp.Device(feature=feature, responses=responses, offset=0x07) + + effects = cls(device) + + assert effects.readable == readable + assert effects.count == count + assert effects.zones[0].count == count_0 + + +@pytest.mark.parametrize( + "hex, expected_behavior, sector, address, typ, val, modifiers, data, byt", + [ + ("05010203", 0x0, 0x501, 0x0203, None, None, None, None, None), + ("15020304", 0x1, 0x502, 0x0304, None, None, None, None, None), + ("8000FFFF", 0x8, None, None, 0x00, None, None, None, None), + ("80010102", 0x8, None, None, 0x01, 0x0102, None, None, None), + ("80020454", 0x8, None, None, 0x02, 0x54, 0x04, None, None), + ("80030454", 0x8, None, None, 0x03, 0x0454, None, None, None), + ("900AFF01", 0x9, None, None, None, 0x0A, None, 0x01, None), + ("709090A0", 0x7, None, None, None, None, None, None, b"\x70\x90\x90\xa0"), + ], +) +def test_button_bytes(hex, expected_behavior, sector, address, typ, val, modifiers, data, byt): + button = hidpp20.Button.from_bytes(bytes.fromhex(hex)) + + assert getattr(button, "behavior", None) == expected_behavior + assert getattr(button, "sector", None) == sector + assert getattr(button, "address", None) == address + assert getattr(button, "type", None) == typ + assert getattr(button, "value", None) == val + assert getattr(button, "modifiers", None) == modifiers + assert getattr(button, "data", None) == data + assert getattr(button, "bytes", None) == byt + assert button.to_bytes().hex().upper() == hex + assert yaml.safe_load(yaml.dump(button)).to_bytes().hex().upper() == hex + + +hex1 = ( + "01010290018003000700140028FFFFFF" + "FFFF0000000000000000000000000000" + "8000FFFF900AFF00800204548000FFFF" + "900AFF00800204548000FFFF900AFF00" + "800204548000FFFF900AFF0080020454" + "8000FFFF900AFF00800204548000FFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "54004500370000000000000000000000" + "00000000000000000000000000000000" + "00000000000000000000000000000000" + "0A01020300500407000000FFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFF7C81" +) +hex2 = ( + "01010290018003000700140028FFFFFF" + "FFFF0000000000000000000000000000" + "8000FFFF900AFF00800204548000FFFF" + "900AFF00800204548000FFFF900AFF00" + "800204548000FFFF900AFF0080020454" + "8000FFFF900AFF00800204548000FFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "0A01020300500407000000FFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFF27C9" +) +hex3 = ( + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFF2307" +) + + +@pytest.mark.parametrize( + "hex, name, sector, enabled, buttons, gbuttons, resolutions, button, lighting", + [ + (hex1, "TE7", 2, 1, 16, 0, [0x0190, 0x0380, 0x0700, 0x1400, 0x2800], "8000FFFF", "0A01020300500407000000"), + (hex2, "", 2, 1, 16, 0, [0x0190, 0x0380, 0x0700, 0x1400, 0x2800], "8000FFFF", "0A01020300500407000000"), + (hex3, "", 2, 1, 16, 0, [0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF], "FFFFFFFF", "FFFFFFFFFFFFFFFFFFFFFF"), + ], +) +def test_onboard_profile_bytes(hex, name, sector, enabled, buttons, gbuttons, resolutions, button, lighting): + profile = hidpp20.OnboardProfile.from_bytes(sector, enabled, buttons, gbuttons, bytes.fromhex(hex)) + + assert profile.name == name + assert profile.sector == sector + assert profile.resolutions == resolutions + assert profile.buttons[0].to_bytes().hex().upper() == button + assert profile.lighting[0].to_bytes().hex().upper() == lighting + + assert profile.to_bytes(len(hex) // 2).hex().upper() == hex + assert yaml.safe_load(yaml.dump(profile)).to_bytes(len(hex) // 2).hex().upper() == hex + + +@pytest.mark.parametrize( + "responses, name, count, buttons, gbuttons, sectors, size", + [ + (fake_hidpp.responses_profiles, "ONB", 1, 2, 2, 1, 254), + (fake_hidpp.responses_profiles_rom, "ONB", 1, 2, 2, 1, 254), + (fake_hidpp.responses_profiles_rom_2, "ONB", 1, 2, 2, 1, 254), + ], +) +def test_onboard_profiles_device(responses, name, count, buttons, gbuttons, sectors, size): + device = fake_hidpp.Device( + name, True, 4.5, responses=responses, feature=hidpp20_constants.SupportedFeature.ONBOARD_PROFILES, offset=0x9 + ) + device._profiles = None + profiles = _hidpp20.get_profiles(device) + + assert profiles + assert profiles.version == hidpp20.OnboardProfilesVersion + assert profiles.name == name + assert profiles.count == count + assert profiles.buttons == buttons + assert profiles.gbuttons == gbuttons + assert profiles.sectors == sectors + assert profiles.size == size + assert len(profiles.profiles) == count + + yml_dump = yaml.dump(profiles) + assert yaml.safe_load(yml_dump).to_bytes().hex() == profiles.to_bytes().hex() + + +# --- Centurion (PRO X 2 LIGHTSPEED headset) tests --- + +device_centurion = fake_hidpp.Device("CENTURION", True, 2.6, fake_hidpp.r_centurion_headset, centurion=True) + + +def test_centurion_parent_feature_discovery(): + """Parent feature enumeration discovers CentPPBridge at index 3 and stores bridge index.""" + dev = fake_hidpp.Device("CENT_PARENT", True, 2.6, fake_hidpp.r_centurion_headset, centurion=True) + featuresarray = hidpp20.FeaturesArray(dev) + dev.features = featuresarray + + result = featuresarray._check() + + assert result is True + assert featuresarray.count == 5 + # Parent features registered + assert featuresarray[hidpp20_constants.SupportedFeature.ROOT] == 0 + assert featuresarray[hidpp20_constants.SupportedFeature.FEATURE_SET] == 1 + assert featuresarray[hidpp20_constants.SupportedFeature.CENTURION_DEVICE_INFO] == 2 + assert featuresarray[hidpp20_constants.SupportedFeature.CENTURION_GENERIC_DFU] == 4 + # Feature 0x0003 = CentPPBridge on Centurion (stored as DEVICE_FW_VERSION since same ID) + assert featuresarray[hidpp20_constants.SupportedFeature.DEVICE_FW_VERSION] == 3 + # Bridge index stored on device + assert dev._centurion_bridge_index == 3 + assert hasattr(dev, "_centurion_sub_features") + + +def test_centurion_sub_device_feature_discovery(): + """Sub-device feature discovery routes through bridge and populates _centurion_sub_features.""" + dev = fake_hidpp.Device("CENT_SUB", True, 2.6, fake_hidpp.r_centurion_headset, centurion=True) + # Set up bridge responses for sub-device discovery: + # 1. CenturionRoot.GetFeature(0x0001) -> FeatureSet at sub-index 1 + # 2. CenturionFeatureSet.GetFeatureId(index=0) -> bulk feature list + dev._bridge_responses = { + # CenturionRoot(idx=0).GetFeature(func=0) with feature_id=0x0001 -> sub_fs_index=1 + (0x00, 0x00, "0001"): bytes([0x01, 0x00, 0x00]), + # CenturionFeatureSet(idx=1).GetFeatureId(func=0x10, start=0) -> 3 features + # Response: [count, (feat_hi, feat_lo, type, flags) × count] + (0x01, 0x10, "00"): bytes( + [ + 0x03, # 3 features + 0x06, + 0x04, + 0x00, + 0x00, # HEADSET_AUDIO_SIDETONE (0x0604) at sub-idx 0 + 0x06, + 0x01, + 0x00, + 0x00, # HEADSET_MIC_MUTE (0x0601) at sub-idx 1 + 0x06, + 0x11, + 0x00, + 0x00, # HEADSET_MIC_GAIN (0x0611) at sub-idx 2 + ] + ), + } + featuresarray = hidpp20.FeaturesArray(dev) + dev.features = featuresarray + + featuresarray._check() + + # Sub-device features should be discovered + assert hidpp20_constants.SupportedFeature.HEADSET_AUDIO_SIDETONE in dev._centurion_sub_features + assert hidpp20_constants.SupportedFeature.HEADSET_MIC_MUTE in dev._centurion_sub_features + assert hidpp20_constants.SupportedFeature.HEADSET_MIC_GAIN in dev._centurion_sub_features + # Sub-device features should be in features array with their sub-device indices + assert featuresarray[hidpp20_constants.SupportedFeature.HEADSET_AUDIO_SIDETONE] == 0 + assert featuresarray[hidpp20_constants.SupportedFeature.HEADSET_MIC_MUTE] == 1 + assert featuresarray[hidpp20_constants.SupportedFeature.HEADSET_MIC_GAIN] == 2 + # _centurion_sub_indices should map ALL sub-device features to their sub-device indices + assert dev._centurion_sub_indices[hidpp20_constants.SupportedFeature.HEADSET_AUDIO_SIDETONE] == 0 + assert dev._centurion_sub_indices[hidpp20_constants.SupportedFeature.HEADSET_MIC_MUTE] == 1 + assert dev._centurion_sub_indices[hidpp20_constants.SupportedFeature.HEADSET_MIC_GAIN] == 2 + + +def test_centurion_feature_request_routes_sub_device(): + """feature_request() routes sub-device features through centurion_bridge_request().""" + dev = fake_hidpp.Device("CENT_ROUTE", True, 2.6, fake_hidpp.r_centurion_headset, centurion=True) + # Manually set up sub-device state (simulating completed discovery) + dev.features.count = 5 # mark discovery as complete so _check() short-circuits + dev._centurion_bridge_index = 3 + dev._centurion_sub_features = {hidpp20_constants.SupportedFeature.HEADSET_AUDIO_SIDETONE} + dev.features[hidpp20_constants.SupportedFeature.HEADSET_AUDIO_SIDETONE] = 7 # sub-device index + # Set up bridge response for GetSidetoneLevel + dev._bridge_responses = { + (7, 0x00, ""): bytes([0x01, 0x00, 0x32]), # mic_id=1, mute=0, level=50 + } + + result = dev.feature_request(hidpp20_constants.SupportedFeature.HEADSET_AUDIO_SIDETONE, 0x00) + + assert result is not None + assert result == bytes([0x01, 0x00, 0x32]) + + +def test_centurion_feature_request_parent_not_routed(): + """feature_request() does NOT route parent features through bridge.""" + dev = fake_hidpp.Device("CENT_PARENT2", True, 2.6, fake_hidpp.r_centurion_headset, centurion=True) + dev._centurion_bridge_index = 3 + dev._centurion_sub_features = {hidpp20_constants.SupportedFeature.HEADSET_AUDIO_SIDETONE} + + # FeatureSet is a parent feature — should go through normal request(), not bridge + featuresarray = hidpp20.FeaturesArray(dev) + dev.features = featuresarray + featuresarray._check() + + # CENTURION_DEVICE_INFO is a parent feature at index 2 — requesting it should + # NOT go through bridge, it should go through the normal hidpp20.feature_request path + assert hidpp20_constants.SupportedFeature.CENTURION_DEVICE_INFO not in dev._centurion_sub_features + + +def test_centurion_bridge_request_write(): + """centurion_bridge_request with no_reply=True returns None immediately.""" + dev = fake_hidpp.Device("CENT_WRITE", True, 2.6, fake_hidpp.r_centurion_headset, centurion=True) + dev._centurion_bridge_index = 3 + dev._centurion_sub_features = {hidpp20_constants.SupportedFeature.HEADSET_AUDIO_SIDETONE} + dev.features[hidpp20_constants.SupportedFeature.HEADSET_AUDIO_SIDETONE] = 7 + dev._bridge_responses = {} # no responses needed for no_reply + + result = dev.centurion_bridge_request(7, 0x10, 0x32, no_reply=True) + + assert result is None + + +def test_centurion_firmware_dedup(): + """get_firmware_centurion() deduplicates identical firmware entries.""" + # Simulate parent device that returns the same firmware for every entity index + fw_response = "00" + "00" + "0105" + "04" + "44303031" + "00" * 20 # type=0, ver=1.05, name="D001" + responses = fake_hidpp.r_centurion_headset + [fake_hidpp.Response(fw_response, 0x0210, f"{i:02X}") for i in range(8)] + dev = fake_hidpp.Device("CENT_DEDUP", True, 2.6, responses, centurion=True) + + fw = _hidpp20.get_firmware_centurion(dev) + + # Should only get 1 entry, not 8 + assert fw is not None + assert len(fw) == 1 + assert fw[0].name == "D001" + + +def test_centurion_sub_device_firmware(): + """get_firmware_centurion_sub() queries sub-device firmware via bridge.""" + dev = fake_hidpp.Device("CENT_SUBFW", True, 2.6, fake_hidpp.r_centurion_headset, centurion=True) + dev._centurion_bridge_index = 3 + dev._centurion_sub_features = set() + dev._centurion_sub_indices = {hidpp20_constants.SupportedFeature.CENTURION_DEVICE_INFO: 2} + # Sub-device firmware: type=0 (firmware), ver=3.02, name="H001" + dev._bridge_responses = { + (2, 0x10, "00"): bytes([0x00, 0x00, 0x03, 0x02, 0x04]) + b"H001", + (2, 0x10, "01"): bytes([0x00, 0x00, 0x03, 0x02, 0x04]) + b"H001", # duplicate → dedup stops + } + + fw = _hidpp20.get_firmware_centurion_sub(dev) + + assert fw is not None + assert len(fw) == 1 + assert fw[0].name == "H001" + assert fw[0].version == "3.02" + + +def test_centurion_sub_device_serial(): + """get_serial_centurion_sub() queries sub-device serial via bridge.""" + dev = fake_hidpp.Device("CENT_SUBSER", True, 2.6, fake_hidpp.r_centurion_headset, centurion=True) + dev._centurion_bridge_index = 3 + dev._centurion_sub_features = set() + dev._centurion_sub_indices = {hidpp20_constants.SupportedFeature.CENTURION_DEVICE_INFO: 2} + dev._bridge_responses = { + (2, 0x20, ""): bytes([0x0C]) + b"ABC123DEF456", + } + + serial = _hidpp20.get_serial_centurion_sub(dev) + + assert serial == "ABC123DEF456" + + +def test_centurion_sub_device_hardware_info(): + """get_hardware_info_centurion_sub() queries sub-device hardware info via bridge.""" + dev = fake_hidpp.Device("CENT_SUBHW", True, 2.6, fake_hidpp.r_centurion_headset, centurion=True) + dev._centurion_bridge_index = 3 + dev._centurion_sub_features = set() + dev._centurion_sub_indices = {hidpp20_constants.SupportedFeature.CENTURION_DEVICE_INFO: 2} + dev._bridge_responses = { + (2, 0x00, ""): bytes([0x01, 0x03, 0x0A, 0xF7]), + } + + hw_info = _hidpp20.get_hardware_info_centurion_sub(dev) + + assert hw_info is not None + model_id, hw_rev, product_id = hw_info + assert model_id == 1 + assert hw_rev == 3 + assert product_id == 0x0AF7 + + +def test_centurion_kind_inference(): + """Centurion device with 0x06xx audio features infers kind=headset.""" + dev = fake_hidpp.Device("CENT_KIND", True, 2.6, fake_hidpp.r_centurion_headset, centurion=True) + dev._centurion_sub_features = { + hidpp20_constants.SupportedFeature.HEADSET_AUDIO_SIDETONE, + hidpp20_constants.SupportedFeature.HEADSET_MIC_MUTE, + } + + kind = dev._infer_kind_centurion() + + from logitech_receiver import hidpp10_constants + + assert kind == hidpp10_constants.DEVICE_KIND.headset + + +# --- CenturionReceiver tests --- + + +class FakeCenturionDeviceInfo: + """Minimal device_info for CenturionReceiver tests.""" + + def __init__(self, path="/dev/hidraw99", product_id="0AF0", product=None, centurion=True): + self.path = path + self.product_id = product_id + self.product = product + self.centurion = centurion + self.isDevice = True + + +class FakeLowLevel: + """Minimal low_level for CenturionReceiver tests.""" + + def __init__(self, ping_protocol=2.6): + self.ping_protocol = ping_protocol + self.opened_paths = [] + self.closed_handles = [] + + def open_path(self, path): + self.opened_paths.append(path) + return 0x99 + + def ping(self, handle, number, long_message=False): + return self.ping_protocol + + def request(self, handle, devnumber, request_id, *params, **kwargs): + return None + + def close(self, handle, *args, **kwargs): + self.closed_handles.append(handle) + return True + + def find_paired_node(self, receiver_path, index, timeout): + return None + + +def test_centurion_receiver_attributes(): + """CenturionReceiver has correct receiver-like attributes.""" + info = FakeCenturionDeviceInfo(product="PRO X 2 LIGHTSPEED") + recv = CenturionReceiver(FakeLowLevel(), 0x99, info) + + assert recv.kind is None + assert recv.isDevice is False + assert recv.number == 0xFF + assert recv.max_devices == 1 + assert recv.may_unpair is False + assert recv.re_pairs is False + assert recv.handle == 0x99 + assert recv.path == "/dev/hidraw99" + assert recv.product_id == "0AF0" + assert recv.name == "Centurion Receiver" + assert recv.serial is None + assert recv.pairing is not None + assert recv.pairing.lock_open is False + assert bool(recv) is True + + +def test_centurion_receiver_container_empty(): + """Empty CenturionReceiver has correct container behavior.""" + info = FakeCenturionDeviceInfo() + recv = CenturionReceiver(FakeLowLevel(), 0x99, info) + + assert len(recv) == 0 + assert recv.count() == 0 + assert 1 not in recv + assert list(recv) == [] + assert recv.status_string() == "No devices." + + +def test_centurion_receiver_container_with_device(): + """CenturionReceiver with a child device has correct container behavior.""" + info = FakeCenturionDeviceInfo() + recv = CenturionReceiver(FakeLowLevel(), 0x99, info) + + # Simulate adding a child device (a simple mock) + class FakeChild: + number = 1 + + def close(self): + pass + + recv._devices[1] = FakeChild() + + assert len(recv) == 1 + assert recv.count() == 1 + assert 1 in recv + assert 2 not in recv + assert recv[1] is not None + assert recv.status_string() == "1 device connected." + with pytest.raises(IndexError): + recv[2] + + +def test_centurion_receiver_enable_connection_notifications(): + """CenturionReceiver.enable_connection_notifications() returns False.""" + info = FakeCenturionDeviceInfo() + recv = CenturionReceiver(FakeLowLevel(), 0x99, info) + + assert recv.enable_connection_notifications() is False + assert recv.remaining_pairings() is None + + +def test_centurion_receiver_device_codename(): + """CenturionReceiver.device_codename() returns USB product name.""" + info = FakeCenturionDeviceInfo(product="PRO X 2 LIGHTSPEED") + recv = CenturionReceiver(FakeLowLevel(), 0x99, info) + + assert recv.device_codename(1) == "PRO X 2 LIGHTSPEED" + + +def test_centurion_receiver_close(): + """CenturionReceiver.close() closes handle and clears devices.""" + low_level = FakeLowLevel() + info = FakeCenturionDeviceInfo() + recv = CenturionReceiver(low_level, 0x99, info) + + class FakeChild: + closed = False + + def close(self): + self.closed = True + + child = FakeChild() + recv._devices[1] = child + + recv.close() + + assert recv.handle is None + assert len(recv._devices) == 0 + assert child.closed is True + assert 0x99 in low_level.closed_handles + assert bool(recv) is False + + +def test_centurion_receiver_changed_callback(): + """CenturionReceiver.changed() invokes status_callback.""" + info = FakeCenturionDeviceInfo() + recv = CenturionReceiver(FakeLowLevel(), 0x99, info) + calls = [] + recv.status_callback = lambda *args, **kwargs: calls.append((args, kwargs)) + + recv.changed() + + assert len(calls) == 1 + assert calls[0][0][0] is recv diff --git a/tests/logitech_receiver/test_hidpp20_simple.py b/tests/logitech_receiver/test_hidpp20_simple.py new file mode 100644 index 00000000..f68c0992 --- /dev/null +++ b/tests/logitech_receiver/test_hidpp20_simple.py @@ -0,0 +1,484 @@ +## Copyright (C) 2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import pytest + +from logitech_receiver import common +from logitech_receiver import hidpp20 +from logitech_receiver import hidpp20_constants +from logitech_receiver.hidpp20_constants import SupportedFeature + +from . import fake_hidpp + +_hidpp20 = hidpp20.Hidpp20() + + +def test_get_firmware(): + responses = [ + fake_hidpp.Response("02FFFF", 0x0400), + fake_hidpp.Response("01414243030401000101000102030405", 0x0410, "00"), + fake_hidpp.Response("02414243030401000101000102030405", 0x0410, "01"), + ] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.DEVICE_FW_VERSION) + + result = _hidpp20.get_firmware(device) + + assert len(result) == 2 + assert isinstance(result[0], common.FirmwareInfo) + assert isinstance(result[1], common.FirmwareInfo) + + +def test_get_ids(): + responses = [fake_hidpp.Response("FF12345678000D123456789ABC", 0x0400)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.DEVICE_FW_VERSION) + + unitId, modelId, tid_map = _hidpp20.get_ids(device) + + assert unitId == "12345678" + assert modelId == "123456789ABC" + assert tid_map == {"btid": "1234", "wpid": "5678", "usbid": "9ABC"} + + +def test_get_kind(): + responses = [fake_hidpp.Response("00", 0x0420)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.DEVICE_NAME) + + result = _hidpp20.get_kind(device) + + assert result == "keyboard" + assert result == 1 + + +def test_get_name(): + responses = [ + fake_hidpp.Response("12", 0x0400), + fake_hidpp.Response("4142434445464748494A4B4C4D4E4F", 0x0410, "00"), + fake_hidpp.Response("505152530000000000000000000000", 0x0410, "0F"), + ] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.DEVICE_NAME) + + result = _hidpp20.get_name(device) + + assert result == "ABCDEFGHIJKLMNOPQR" + + +def test_get_friendly_name(): + responses = [ + fake_hidpp.Response("12", 0x0400), + fake_hidpp.Response("004142434445464748494A4B4C4D4E", 0x0410, "00"), + fake_hidpp.Response("0E4F50515253000000000000000000", 0x0410, "0E"), + ] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.DEVICE_FRIENDLY_NAME) + + result = _hidpp20.get_friendly_name(device) + + assert result == "ABCDEFGHIJKLMNOPQR" + + +def test_get_battery_status(): + responses = [fake_hidpp.Response("502000FFFF", 0x0400)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.BATTERY_STATUS) + + feature, battery = _hidpp20.get_battery_status(device) + + assert feature == SupportedFeature.BATTERY_STATUS + assert battery.level == 80 + assert battery.next_level == 32 + assert battery.status == common.BatteryStatus.DISCHARGING + + +def test_get_battery_voltage(): + responses = [fake_hidpp.Response("1000FFFFFF", 0x0400)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.BATTERY_VOLTAGE) + + feature, battery = _hidpp20.get_battery_voltage(device) + + assert feature == SupportedFeature.BATTERY_VOLTAGE + assert battery.level == 92 + assert common.BatteryStatus.RECHARGING in battery.status + assert battery.voltage == 0x1000 + + +def test_get_battery_unified(): + responses = [fake_hidpp.Response("500100FFFF", 0x0410)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.UNIFIED_BATTERY) + + feature, battery = _hidpp20.get_battery_unified(device) + + assert feature == SupportedFeature.UNIFIED_BATTERY + assert battery.level == 80 + assert battery.status == common.BatteryStatus.DISCHARGING + + +def test_get_adc_measurement(): + responses = [fake_hidpp.Response("100003", 0x0400)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.ADC_MEASUREMENT) + + feature, battery = _hidpp20.get_adc_measurement(device) + + assert feature == SupportedFeature.ADC_MEASUREMENT + assert battery.level == 92 + assert battery.status == common.BatteryStatus.RECHARGING + assert battery.voltage == 0x1000 + + +def test_get_battery(): + responses = [fake_hidpp.Response("502000FFFF", 0x0400)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.BATTERY_STATUS) + + feature, battery = _hidpp20.get_battery(device, SupportedFeature.BATTERY_STATUS) + + assert feature == SupportedFeature.BATTERY_STATUS + assert battery.level == 80 + assert battery.next_level == 32 + assert battery.status == common.BatteryStatus.DISCHARGING + + +def test_get_battery_none(): + responses = [ + fake_hidpp.Response(None, 0x0000, f"{int(SupportedFeature.BATTERY_STATUS):0>4X}"), + fake_hidpp.Response(None, 0x0000, f"{int(SupportedFeature.BATTERY_VOLTAGE):0>4X}"), + fake_hidpp.Response("500100ffff", 0x0410), + ] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.UNIFIED_BATTERY) + + feature, battery = _hidpp20.get_battery(device, None) + + assert feature == SupportedFeature.UNIFIED_BATTERY + assert battery.level == 80 + assert battery.status == common.BatteryStatus.DISCHARGING + + +# get_keys is in test_hidpp20_complex +# get_remap_keys is in test_hidpp20_complex +# TODO get_gestures is complex +# get_backlight is in test_hidpp20_complex +# get_profiles is in test_hidpp20_complex + + +def test_get_mouse_pointer_info(): + responses = [fake_hidpp.Response("01000A", 0x0400)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.MOUSE_POINTER) + + result = _hidpp20.get_mouse_pointer_info(device) + + assert result == { + "dpi": 0x100, + "acceleration": "med", + "suggest_os_ballistics": False, + "suggest_vertical_orientation": True, + } + + +def test_get_vertical_scrolling_info(): + responses = [fake_hidpp.Response("01080C", 0x0400)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.VERTICAL_SCROLLING) + + result = _hidpp20.get_vertical_scrolling_info(device) + + assert result == {"roller": "standard", "ratchet": 8, "lines": 12} + + +def test_get_hi_res_scrolling_info(): + responses = [fake_hidpp.Response("0102", 0x0400)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.HI_RES_SCROLLING) + + mode, resolution = _hidpp20.get_hi_res_scrolling_info(device) + + assert mode == 1 + assert resolution == 2 + + +def test_get_pointer_speed_info(): + responses = [fake_hidpp.Response("0102", 0x0400)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.POINTER_SPEED) + + result = _hidpp20.get_pointer_speed_info(device) + + assert result == 0x0102 / 256 + + +def test_get_lowres_wheel_status(): + responses = [fake_hidpp.Response("01", 0x0400)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.LOWRES_WHEEL) + + result = _hidpp20.get_lowres_wheel_status(device) + + assert result == "HID++" + + +def test_get_hires_wheel(): + responses = [ + fake_hidpp.Response("010C", 0x0400), + fake_hidpp.Response("05FF", 0x0410), + fake_hidpp.Response("03FF", 0x0430), + ] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.HIRES_WHEEL) + + multi, has_invert, has_ratchet, inv, res, target, ratchet = _hidpp20.get_hires_wheel(device) + + assert multi == 1 + assert has_invert is True + assert has_ratchet is True + assert inv is True + assert res is False + assert target is True + assert ratchet is True + + +def test_get_new_fn_inversion(): + responses = [fake_hidpp.Response("0300", 0x0400)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.NEW_FN_INVERSION) + + result = _hidpp20.get_new_fn_inversion(device) + + assert result == (True, False) + + +@pytest.fixture +def mock_gethostname(mocker): + mocker.patch("socket.gethostname", return_value="ABCDEFG.foo.org") + + +@pytest.mark.parametrize( + "responses, expected_result", + [ + ([fake_hidpp.Response(None, 0x0400)], {}), + ([fake_hidpp.Response("02000000", 0x0400)], {}), + ( + [ + fake_hidpp.Response("03000200", 0x0400), + fake_hidpp.Response("FF01FFFF05FFFF", 0x0410, "00"), + fake_hidpp.Response("0000414243444500FFFFFFFFFF", 0x0430, "0000"), + fake_hidpp.Response("FF01FFFF10FFFF", 0x0410, "01"), + fake_hidpp.Response("01004142434445464748494A4B4C4D", 0x0430, "0100"), + fake_hidpp.Response("01134E4F5000FFFFFFFFFFFFFFFFFF", 0x0430, "010E"), + fake_hidpp.Response("000000000008", 0x0410, "00"), + fake_hidpp.Response("0208", 0x0440, "000041424344454647"), + ], + {0: (True, "ABCDEFG"), 1: (True, "ABCDEFGHIJKLMNO")}, + ), + ], +) +def test_get_host_names(responses, expected_result, mock_gethostname): + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.HOSTS_INFO) + + result = _hidpp20.get_host_names(device) + + assert result == expected_result + + +@pytest.mark.parametrize( + "responses, expected_result", + [ + ([fake_hidpp.Response(None, 0x0400)], None), + ( + [ + fake_hidpp.Response("03000002", 0x0400), + fake_hidpp.Response("000000000008", 0x0410, "02"), + fake_hidpp.Response("020E", 0x0440, "02004142434445464748494A4B4C4D4E"), + ], + True, + ), + ( + [ + fake_hidpp.Response("03000002", 0x0400), + fake_hidpp.Response("000000000014", 0x0410, "02"), + fake_hidpp.Response("020E", 0x0440, "02004142434445464748494A4B4C4D4E"), + fake_hidpp.Response("0214", 0x0440, "020E4F505152535455565758"), + ], + True, + ), + ], +) +def test_set_host_name(responses, expected_result): + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.HOSTS_INFO) + + result = _hidpp20.set_host_name(device, "ABCDEFGHIJKLMNOPQRSTUVWX") + + assert result == expected_result + + +def test_get_onboard_mode(): + responses = [fake_hidpp.Response("03FFFFFFFF", 0x0420)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.ONBOARD_PROFILES) + + result = _hidpp20.get_onboard_mode(device) + + assert result == 0x3 + + +def test_set_onboard_mode(): + responses = [fake_hidpp.Response("03FFFFFFFF", 0x0410, "03")] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.ONBOARD_PROFILES) + + res = _hidpp20.set_onboard_mode(device, 0x3) + + assert res is not None + + +@pytest.mark.parametrize( + "responses, expected_result", + [ + ([fake_hidpp.Response("03FFFF", 0x0420)], "1ms"), + ( + [ + fake_hidpp.Response(None, 0x0000, f"{int(SupportedFeature.REPORT_RATE):04X}"), + fake_hidpp.Response("04FFFF", 0x0420), + ], + "500us", + ), + ], +) +def test_get_polling_rate( + responses, + expected_result, +): + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.EXTENDED_ADJUSTABLE_REPORT_RATE) + + result = _hidpp20.get_polling_rate(device) + + assert result == expected_result + + +def test_get_remaining_pairing(): + responses = [fake_hidpp.Response("03FFFF", 0x0400)] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.REMAINING_PAIRING) + + result = _hidpp20.get_remaining_pairing(device) + + assert result == 0x03 + + +def test_config_change(): + responses = [fake_hidpp.Response("03FFFF", 0x0410, "02")] + device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.CONFIG_CHANGE) + + result = _hidpp20.config_change(device, 0x2) + + assert result == bytes.fromhex("03FFFF") + + +def test_decipher_battery_status(): + report = b"\x50\x20\x00\xff\xff" + + feature, battery = hidpp20.decipher_battery_status(report) + + assert feature == SupportedFeature.BATTERY_STATUS + assert battery.level == 80 + assert battery.next_level == 32 + assert battery.status == common.BatteryStatus.DISCHARGING + + +def test_decipher_battery_voltage(): + report = b"\x10\x00\xff\xff\xff" + + feature, battery = hidpp20.decipher_battery_voltage(report) + + assert feature == SupportedFeature.BATTERY_VOLTAGE + assert battery.level == 92 + assert common.BatteryStatus.RECHARGING in battery.status + assert battery.voltage == 0x1000 + + +def test_decipher_battery_unified(): + report = b"\x50\x01\x00\xff\xff" + + feature, battery = hidpp20.decipher_battery_unified(report) + + assert feature == SupportedFeature.UNIFIED_BATTERY + assert battery.level == 80 + assert battery.status == common.BatteryStatus.DISCHARGING + + +def test_decipher_adc_measurement(): + report = b"\x10\x00\x03" + + feature, battery = hidpp20.decipher_adc_measurement(report) + + assert feature == SupportedFeature.ADC_MEASUREMENT + assert battery.level == 92 + assert battery.status == common.BatteryStatus.RECHARGING + assert battery.voltage == 0x1000 + + +@pytest.mark.parametrize( + "code, expected_flags", + [ + (0x01, ["unknown:000001"]), + (0x0F, ["unknown:00000F"]), + (0xF0, ["internal", "hidden", "obsolete", "unknown:000010"]), + (0x20, ["internal"]), + (0x33, ["internal", "unknown:000013"]), + (0x3F, ["internal", "unknown:00001F"]), + (0x40, ["hidden"]), + (0x50, ["hidden", "unknown:000010"]), + (0x5F, ["hidden", "unknown:00001F"]), + (0x7F, ["internal", "hidden", "unknown:00001F"]), + (0x80, ["obsolete"]), + (0xA0, ["internal", "obsolete"]), + (0xE0, ["internal", "hidden", "obsolete"]), + (0xFF, ["internal", "hidden", "obsolete", "unknown:00001F"]), + ], +) +def test_feature_flag_names(code, expected_flags): + flags = common.flag_names(hidpp20_constants.FeatureFlag, code) + + assert list(flags) == expected_flags + + +@pytest.mark.parametrize( + "code, expected_name", + [ + (0x00, "Unknown Location"), + (0x03, "Left Side"), + ], +) +def test_led_zone_locations(code, expected_name): + assert hidpp20.LEDZoneLocations[code] == expected_name + + +@pytest.mark.parametrize( + "millivolt, expected_percentage", + [ + (-1234, 0), + (500, 0), + (2000, 0), + (3500, 0), + (3519, 0), + (3520, 1), + (3559, 1), + (3579, 2), + (3646, 5), + (3671, 10), + (3717, 20), + (3751, 30), + (3778, 40), + (3811, 50), + (3859, 60), + (3922, 70), + (3989, 80), + (4067, 90), + (4180, 99), + (4181, 100), + (4186, 100), + (4500, 100), + ], +) +def test_estimate_battery_level_percentage(millivolt, expected_percentage): + percentage = hidpp20.estimate_battery_level_percentage(millivolt) + + assert percentage == expected_percentage diff --git a/tests/logitech_receiver/test_notifications.py b/tests/logitech_receiver/test_notifications.py new file mode 100644 index 00000000..cbe51aab --- /dev/null +++ b/tests/logitech_receiver/test_notifications.py @@ -0,0 +1,338 @@ +import pytest + +from logitech_receiver import notifications +from logitech_receiver.base import HIDPPNotification +from logitech_receiver.common import Notification +from logitech_receiver.hidpp10_constants import BoltPairingError +from logitech_receiver.hidpp10_constants import PairingError +from logitech_receiver.hidpp10_constants import Registers +from logitech_receiver.hidpp20_constants import SupportedFeature +from logitech_receiver.receiver import Receiver + +from . import fake_hidpp + + +class MockLowLevelInterface: + def open_path(self, path): + pass + + def find_paired_node_wpid(self, receiver_path: str, index: int): + pass + + def ping(self, handle, number, long_message=False): + pass + + def request(self, handle, devnumber, request_id, *params, **kwargs): + pass + + def find_paired_node(self, receiver_path: str, index: int, timeout: int): + return None + + def close(self, device_handle) -> None: + pass + + +@pytest.mark.parametrize( + "sub_id, notification_data, expected_error, expected_new_device", + [ + (Registers.DISCOVERY_STATUS_NOTIFICATION, b"\x01", BoltPairingError.DEVICE_TIMEOUT, None), + ( + Registers.DEVICE_DISCOVERY_NOTIFICATION, + b"\x01\x01\x01\x01\x01\x01\x01\x01\x01", + None, + None, + ), + (Registers.PAIRING_STATUS_NOTIFICATION, b"\x02", BoltPairingError.FAILED, None), + (Notification.PAIRING_LOCK, b"\x01", PairingError.DEVICE_TIMEOUT, None), + (Notification.PAIRING_LOCK, b"\x02", PairingError.DEVICE_NOT_SUPPORTED, None), + (Notification.PAIRING_LOCK, b"\x03", PairingError.TOO_MANY_DEVICES, None), + (Notification.PAIRING_LOCK, b"\x06", PairingError.SEQUENCE_TIMEOUT, None), + (Registers.PASSKEY_REQUEST_NOTIFICATION, b"\x06", None, None), + (Registers.PASSKEY_PRESSED_NOTIFICATION, b"\x06", None, None), + ], +) +def test_process_receiver_notification(sub_id, notification_data, expected_error, expected_new_device): + receiver: Receiver = Receiver(MockLowLevelInterface(), None, {}, True, None, None) + notification = HIDPPNotification(0, 0, sub_id, 0x02, notification_data) + + result = notifications.process_receiver_notification(receiver, notification) + + assert result + assert receiver.pairing.error == (None if expected_error is None else expected_error.label) + assert receiver.pairing.new_device is expected_new_device + + +@pytest.mark.parametrize( + "hidpp_notification, expected", + [ + (HIDPPNotification(0, 0, sub_id=Registers.BATTERY_STATUS, address=0, data=b"0x01"), False), + (HIDPPNotification(0, 0, sub_id=Notification.NO_OPERATION, address=0, data=b"0x01"), False), + (HIDPPNotification(0, 0, sub_id=0x40, address=0, data=b"0x01"), True), + ], +) +def test_process_device_notification(hidpp_notification, expected): + device = fake_hidpp.Device() + + result = notifications.process_device_notification(device, hidpp_notification) + + assert result == expected + + +@pytest.mark.parametrize( + "hidpp_notification, expected", + [ + (HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0, data=b"0x01"), True), + (HIDPPNotification(0, 0, sub_id=Notification.DJ_PAIRING, address=0, data=b"0x01"), True), + (HIDPPNotification(0, 0, sub_id=Notification.CONNECTED, address=0, data=b"0x01"), True), + (HIDPPNotification(0, 0, sub_id=Notification.RAW_INPUT, address=0, data=b"0x01"), None), + ], +) +def test_process_dj_notification(hidpp_notification, expected): + device = fake_hidpp.Device() + + result = notifications._process_dj_notification(device, hidpp_notification) + + assert result == expected + + +@pytest.mark.parametrize( + "hidpp_notification, expected", + [ + (HIDPPNotification(0, 0, sub_id=Registers.BATTERY_STATUS, address=0, data=b"\x01\x00"), True), + (HIDPPNotification(0, 0, sub_id=Registers.BATTERY_CHARGE, address=0, data=b"0x01\x00"), True), + (HIDPPNotification(0, 0, sub_id=Notification.RAW_INPUT, address=0, data=b"0x01"), None), + ], +) +def test_process_hidpp10_custom_notification(hidpp_notification, expected): + device = fake_hidpp.Device() + + result = notifications._process_hidpp10_custom_notification(device, hidpp_notification) + + assert result == expected + + +@pytest.mark.parametrize( + "hidpp_notification, expected", + [ + (HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x02, data=b"0x01"), True), + (HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x00, data=b"0x01"), True), + (HIDPPNotification(0, 0, sub_id=Notification.DJ_PAIRING, address=0x00, data=b"0x01"), True), + (HIDPPNotification(0, 0, sub_id=Notification.DJ_PAIRING, address=0x02, data=b"0x01"), True), + (HIDPPNotification(0, 0, sub_id=Notification.DJ_PAIRING, address=0x03, data=b"0x01"), True), + (HIDPPNotification(0, 0, sub_id=Notification.DJ_PAIRING, address=0x03, data=b"0x4040"), True), + (HIDPPNotification(0, 0, sub_id=Notification.RAW_INPUT, address=0x00, data=b"0x01"), True), + (HIDPPNotification(0, 0, sub_id=Notification.POWER, address=0x00, data=b"0x01"), True), + (HIDPPNotification(0, 0, sub_id=Notification.POWER, address=0x01, data=b"0x01"), True), + (HIDPPNotification(0, 0, sub_id=Notification.PAIRING_LOCK, address=0x01, data=b"0x01"), None), + ], +) +def test_process_hidpp10_notification(hidpp_notification, expected): + fake_device = fake_hidpp.Device() + fake_device.receiver = ["rec1", "rec2"] + + result = notifications._process_hidpp10_notification(fake_device, hidpp_notification) + + assert result == expected + + +@pytest.mark.parametrize( + "hidpp_notification, feature", + [ + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x00, data=b"0x01"), + SupportedFeature.BATTERY_STATUS, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x02, data=b"0x01"), + SupportedFeature.BATTERY_STATUS, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x00, data=b"0x01"), + SupportedFeature.BATTERY_VOLTAGE, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x05, data=b"0x01"), + SupportedFeature.BATTERY_VOLTAGE, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x00, data=b"0x01"), + SupportedFeature.UNIFIED_BATTERY, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x02, data=b"0x01"), + SupportedFeature.UNIFIED_BATTERY, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x00, data=b"0x01"), + SupportedFeature.ADC_MEASUREMENT, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x02, data=b"0x01"), + SupportedFeature.ADC_MEASUREMENT, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x00, data=b"01234GOOD"), + SupportedFeature.SOLAR_DASHBOARD, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x10, data=b"01234GOOD"), + SupportedFeature.SOLAR_DASHBOARD, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x20, data=b"01234GOOD"), + SupportedFeature.SOLAR_DASHBOARD, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x02, data=b"01234GOOD"), + SupportedFeature.SOLAR_DASHBOARD, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x02, data=b"CHARGENOTGOOD"), + SupportedFeature.SOLAR_DASHBOARD, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x00, data=b"\x01\x01\x02"), + SupportedFeature.WIRELESS_DEVICE_STATUS, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x02, data=b"0x01"), + SupportedFeature.WIRELESS_DEVICE_STATUS, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x00, data=b"0x01"), + SupportedFeature.TOUCHMOUSE_RAW_POINTS, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x10, data=b"0x01"), + SupportedFeature.TOUCHMOUSE_RAW_POINTS, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x05, data=b"0x01"), + SupportedFeature.TOUCHMOUSE_RAW_POINTS, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x00, data=b"0x01"), + SupportedFeature.REPROG_CONTROLS, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x02, data=b"0x01"), + SupportedFeature.REPROG_CONTROLS, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x00, data=b"0x01"), + SupportedFeature.BACKLIGHT2, + ), + ( + HIDPPNotification( + 0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x00, data=b"\x01\x01\x01\x01\x01\x01\x01\x01" + ), + SupportedFeature.REPROG_CONTROLS_V4, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x10, data=b"0x01"), + SupportedFeature.REPROG_CONTROLS_V4, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x20, data=b"0x01"), + SupportedFeature.REPROG_CONTROLS_V4, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x00, data=b"0x01"), + SupportedFeature.HIRES_WHEEL, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x10, data=b"0x01"), + SupportedFeature.HIRES_WHEEL, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x02, data=b"0x01"), + SupportedFeature.HIRES_WHEEL, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x00, data=b"0x01"), + SupportedFeature.ONBOARD_PROFILES, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x20, data=b"0x01"), + SupportedFeature.ONBOARD_PROFILES, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x00, data=b"0x01"), + SupportedFeature.BRIGHTNESS_CONTROL, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x10, data=b"0x01"), + SupportedFeature.BRIGHTNESS_CONTROL, + ), + ( + HIDPPNotification(0, 0, sub_id=Notification.CONNECT_DISCONNECT, address=0x20, data=b"0x01"), + SupportedFeature.BRIGHTNESS_CONTROL, + ), + ], +) +def test_process_feature_notification(mocker, hidpp_notification, feature): + fake_device = fake_hidpp.Device() + fake_device.receiver = ["rec1", "rec2"] + + result = notifications._process_feature_notification(fake_device, hidpp_notification) + + assert result is True + + +def test_process_receiver_notification_invalid(mocker): + invalid_sub_id = 0x30 + notification_data = b"\x02" + notification = HIDPPNotification(0, 0, invalid_sub_id, 0, notification_data) + mock_receiver = mocker.Mock() + + with pytest.raises(AssertionError): + notifications.process_receiver_notification(mock_receiver, notification) + + +@pytest.mark.parametrize( + "sub_id, notification_data, expected", + [ + (Notification.NO_OPERATION, b"\x00", False), + ], +) +def test_process_device_notification_extended(mocker, sub_id, notification_data, expected): + device = mocker.Mock() + device.handle_notification.return_value = None + device.protocol = 2.0 + notification = HIDPPNotification(0, 0, sub_id, 0, notification_data) + + result = notifications.process_device_notification(device, notification) + + assert result == expected + + +def test_handle_device_discovery(): + receiver: Receiver = Receiver(MockLowLevelInterface(), None, {}, True, None, None) + sub_id = Registers.DISCOVERY_STATUS_NOTIFICATION + data = b"\x01\x02\x03\x04\x05\x06" + notification = HIDPPNotification(0, 0, sub_id, 0, data) + + result = notifications.handle_device_discovery(receiver, notification) + + assert result + + +def test_handle_passkey_request(mocker): + receiver_mock = mocker.Mock() + data = b"\x01" + notification = HIDPPNotification(0, 0, 0, 0, data) + + result = notifications.handle_passkey_request(receiver_mock, notification) + + assert result is True + + +def test_handle_passkey_pressed(mocker): + receiver = mocker.Mock() + sub_id = Registers.DISCOVERY_STATUS_NOTIFICATION + data = b"\x01\x02\x03\x04\x05\x06" + notification = HIDPPNotification(0, 0, sub_id, 0, data) + + result = notifications.handle_passkey_pressed(receiver, notification) + + assert result is True diff --git a/tests/logitech_receiver/test_receiver.py b/tests/logitech_receiver/test_receiver.py new file mode 100644 index 00000000..2e8775ec --- /dev/null +++ b/tests/logitech_receiver/test_receiver.py @@ -0,0 +1,371 @@ +from dataclasses import dataclass +from functools import partial +from unittest import mock + +import pytest + +from logitech_receiver import base +from logitech_receiver import common +from logitech_receiver import exceptions +from logitech_receiver import receiver + +from . import fake_hidpp + + +@pytest.fixture +def nano_recv(): + device_info = DeviceInfo("12", product_id=0xC534) + mock_low_level = LowLevelInterfaceFake(responses_lacking) + yield receiver.create_receiver(mock_low_level, device_info, lambda x: x) + + +class LowLevelInterfaceFake: + def __init__(self, responses=None): + self.responses = responses + + def open_path(self, path): + return fake_hidpp.open_path(path) + + def product_information(self, usb_id: int) -> dict: + return base.product_information(usb_id) + + def find_paired_node(self, receiver_path: str, index: int, timeout: int): + return None + + def request(self, response, *args, **kwargs): + func = partial(fake_hidpp.request, self.responses) + return func(response, *args, **kwargs) + + def ping(self, response, *args, **kwargs): + func = partial(fake_hidpp.ping, self.responses) + return func(response, *args, **kwargs) + + def close(self, *args, **kwargs): + pass + + +@pytest.mark.parametrize( + "index, expected_kind", + [ + (0, None), + (1, 2), # mouse + (2, 2), # mouse + (3, 1), # keyboard + (4, 3), # numpad + (5, None), + ], +) +def test_get_kind_from_index(index, expected_kind): + mock_receiver = mock.Mock() + + if expected_kind: + assert receiver._get_kind_from_index(mock_receiver, index) == expected_kind + else: + with pytest.raises(exceptions.NoSuchDevice): + receiver._get_kind_from_index(mock_receiver, index) + + +@dataclass +class DeviceInfo: + path: str + vendor_id: int = 1133 + product_id: int = 0xC52B + + +responses_unifying = [ + fake_hidpp.Response("000000", 0x8003, "FF"), + fake_hidpp.Response("000300", 0x8102), + fake_hidpp.Response("0316CC9CB40506220000000000000000", 0x83B5, "03"), + fake_hidpp.Response("20200840820402020700000000000000", 0x83B5, "20"), + fake_hidpp.Response("21211420110400010D1A000000000000", 0x83B5, "21"), + fake_hidpp.Response("22220840660402010700000000020000", 0x83B5, "22"), + fake_hidpp.Response("30198E3EB80600000001000000000000", 0x83B5, "30"), + fake_hidpp.Response("31811119511A40000002000000000000", 0x83B5, "31"), + fake_hidpp.Response("32112C46EA1E40000003000000000000", 0x83B5, "32"), + fake_hidpp.Response("400B4D58204D61737465722033000000", 0x83B5, "40"), + fake_hidpp.Response("41044B35323020202020202020202020", 0x83B5, "41"), + fake_hidpp.Response("42054372616674000000000000000000", 0x83B5, "42"), + fake_hidpp.Response("012411", 0x81F1, "01"), + fake_hidpp.Response("020036", 0x81F1, "02"), + fake_hidpp.Response("03AAAC", 0x81F1, "03"), + fake_hidpp.Response("040209", 0x81F1, "04"), +] +responses_c534 = [ + fake_hidpp.Response("000000", 0x8003, "FF", handle=0x12), + fake_hidpp.Response("000209", 0x8102, handle=0x12), + fake_hidpp.Response("0316CC9CB40502220000000000000000", 0x83B5, "03", handle=0x12), + fake_hidpp.Response("00000445AB", 0x83B5, "04", handle=0x12), +] +responses_unusual = [ + fake_hidpp.Response("000000", 0x8003, "FF", handle=0x13), + fake_hidpp.Response("000300", 0x8102, handle=0x13), + fake_hidpp.Response("00000445AB", 0x83B5, "04", handle=0x13), + fake_hidpp.Response("0326CC9CB40508220000000000000000", 0x83B5, "03", handle=0x13), +] +responses_lacking = [ + fake_hidpp.Response("000000", 0x8003, "FF", handle=0x14), + fake_hidpp.Response("000300", 0x8102, handle=0x14), +] + +mouse_info = { + "kind": common.NamedInt(2, "mouse"), + "polling": "8ms", + "power_switch": common.NamedInt(1, "base"), + "serial": "198E3EB8", + "wpid": "4082", +} +c534_info = {"kind": common.NamedInt(0, "unknown"), "polling": "", "power_switch": "(unknown)", "serial": None, "wpid": "45AB"} + + +@pytest.mark.parametrize( + "device_info, responses, handle, serial, max_devices, ", + [ + (DeviceInfo(path=None), [], False, None, None), + (DeviceInfo(path=11), [], None, None, None), + (DeviceInfo(path="11"), responses_unifying, 0x11, "16CC9CB4", 6), + (DeviceInfo(path="12", product_id=0xC534), responses_c534, 0x12, "16CC9CB4", 2), + (DeviceInfo(path="12", product_id=0xC539), responses_c534, 0x12, "16CC9CB4", 2), + (DeviceInfo(path="13"), responses_unusual, 0x13, "26CC9CB4", 1), + (DeviceInfo(path="14"), responses_lacking, 0x14, None, 1), + ], +) +def test_receiver_factory_create_receiver(device_info, responses, handle, serial, max_devices): + mock_low_level = LowLevelInterfaceFake(responses) + + if handle is False: + with pytest.raises(Exception): # noqa: B017 + receiver.create_receiver(mock_low_level, device_info, lambda x: x) + elif handle is None: + r = receiver.create_receiver(mock_low_level, device_info, lambda x: x) + assert r is None + else: + r = receiver.create_receiver(mock_low_level, device_info, lambda x: x) + assert r.handle == handle + assert r.serial == serial + assert r.max_devices == max_devices + + +@pytest.mark.parametrize( + "device_info, responses, firmware, codename, remaining_pairings, pairing_info, count", + [ + (DeviceInfo("11"), responses_unifying, 3, "K520", -1, mouse_info, 3), + (DeviceInfo("12", product_id=0xC534), responses_c534, None, None, 4, c534_info, 2), + (DeviceInfo("13", product_id=0xCCCC), responses_unusual, None, None, -1, c534_info, 3), + ], +) +def test_receiver_factory_props(device_info, responses, firmware, codename, remaining_pairings, pairing_info, count): + mock_low_level = LowLevelInterfaceFake(responses) + + r = receiver.create_receiver(mock_low_level, device_info, lambda x: x) + + assert len(r.firmware) == firmware if firmware is not None else firmware is None + assert r.device_codename(2) == codename + assert r.remaining_pairings() == remaining_pairings + assert r.device_pairing_information(1) == pairing_info + assert r.count() == count + + +@pytest.mark.parametrize( + "device_info, responses, status_str, strng", + [ + (DeviceInfo("11"), responses_unifying, "No paired devices.", ""), + (DeviceInfo("12", product_id=0xC534), responses_c534, "No paired devices.", ""), + (DeviceInfo("13", product_id=0xCCCC), responses_unusual, "No paired devices.", ""), + ], +) +def test_receiver_factory_string(device_info, responses, status_str, strng): + mock_low_level = LowLevelInterfaceFake(responses) + + r = receiver.create_receiver(mock_low_level, device_info, lambda x: x) + + assert r.status_string() == status_str + assert str(r) == strng + + +@pytest.mark.parametrize( + "device_info, responses", + [ + (DeviceInfo("14"), responses_lacking), + (DeviceInfo("14", product_id="C534"), responses_lacking), + ], +) +def test_receiver_factory_no_device(device_info, responses): + mock_low_level = LowLevelInterfaceFake(responses) + + r = receiver.create_receiver(mock_low_level, device_info, lambda x: x) + + with pytest.raises(exceptions.NoSuchDevice): + r.device_pairing_information(1) + + +@pytest.mark.parametrize( + "address, data, expected_online, expected_encrypted", + [ + (0x03, b"\x01\x02\x03", True, False), + (0x10, b"\x61\x02\x03", False, True), + ], +) +def test_notification_information_nano_receiver(nano_recv, address, data, expected_online, expected_encrypted): + _number = 0 + notification = base.HIDPPNotification( + report_id=0x01, + devnumber=0x52C, + sub_id=0, + address=address, + data=data, + ) + online, encrypted, wpid, kind = nano_recv.notification_information(_number, notification) + + assert online == expected_online + assert encrypted == expected_encrypted + assert wpid == "0302" + assert kind == "keyboard" + + +def test_extract_serial_number(): + response = b'\x03\x16\xcc\x9c\xb4\x05\x06"\x00\x00\x00\x00\x00\x00\x00\x00' + + serial_number = receiver.extract_serial(response[1:5]) + + assert serial_number == "16CC9CB4" + + +def test_extract_max_devices(): + response = b'\x03\x16\xcc\x9c\xb4\x05\x06"\x00\x00\x00\x00\x00\x00\x00\x00' + + max_devices = receiver.extract_max_devices(response) + + assert max_devices == 6 + + +@pytest.mark.parametrize( + "response, expected_remaining_pairings", + [ + (b"\x00\x03\x00", -1), + (b"\x00\x02\t", 4), + ], +) +def test_extract_remaining_pairings(response, expected_remaining_pairings): + remaining_pairings = receiver.extract_remaining_pairings(response) + + assert remaining_pairings == expected_remaining_pairings + + +def test_extract_codename(): + response = b"A\x04K520" + + codename = receiver.extract_codename(response) + + assert codename == "K520" + + +@pytest.mark.parametrize( + "power_switch_byte, expected_location", + [ + (b"\x01", "base"), + (b"\x09", "top_edge"), + (b"\x0c", "bottom_edge"), + (b"\x00", "unknown"), + (b"\x0f", "unknown"), + ], +) +def test_extract_power_switch_location(power_switch_byte, expected_location): + response = b"\x19\x8e>\xb8\x06\x00\x00\x00\x00" + power_switch_byte + b"\x00\x00\x00\x00\x00" + + ps_location = receiver.extract_power_switch_location(response) + + assert ps_location == expected_location + + +def test_extract_connection_count(): + response = b"\x00\x03\x00" + + connection_count = receiver.extract_connection_count(response) + + assert connection_count == 3 + + +def test_extract_wpid(): + response = b"@\x82" + + res = receiver.extract_wpid(response) + + assert res == "4082" + + +def test_extract_polling_rate(): + response = b"\x08@\x82\x04\x02\x02\x07\x00\x00\x00\x00\x00\x00\x00" + + polling_rate = receiver.extract_polling_rate(response) + + assert polling_rate == 130 + + +@pytest.mark.parametrize( + "data, expected_device_kind", + [ + (0x00, "unknown"), + (0x03, "numpad"), + ], +) +def test_extract_device_kind(data, expected_device_kind): + device_kind = receiver.extract_device_kind(data) + + assert str(device_kind) == expected_device_kind + + +def _make_fake_receiver(cached_devices=None): + r = mock.Mock(spec=receiver.Receiver) + r.handle = 0x12 + r._devices = dict(cached_devices or {}) + r.force_unpair_slot = partial(receiver.Receiver.force_unpair_slot, r) + return r + + +def test_force_unpair_slot_empty_slot_acknowledged(): + r = _make_fake_receiver(cached_devices={}) + r._unpair_device_per_receiver = mock.Mock(return_value=b"\x00") + + assert r.force_unpair_slot(2) is True + r._unpair_device_per_receiver.assert_called_once_with(2) + assert 2 not in r._devices + + +def test_force_unpair_slot_stale_sentinel_cleared(): + # "Device not found" state: slot present in cache but value is None. + r = _make_fake_receiver(cached_devices={2: None}) + r._unpair_device_per_receiver = mock.Mock(return_value=b"\x00") + + assert r.force_unpair_slot(2) is True + assert 2 not in r._devices + + +def test_force_unpair_slot_active_device_invalidated(): + dev = mock.Mock() + dev.online = True + dev.wpid = "40B4" + r = _make_fake_receiver(cached_devices={1: dev}) + r._unpair_device_per_receiver = mock.Mock(return_value=b"\x00") + + assert r.force_unpair_slot(1) is True + assert dev.online is False + assert dev.wpid is None + assert 1 not in r._devices + + +def test_force_unpair_slot_register_write_failed(): + r = _make_fake_receiver(cached_devices={2: None}) + r._unpair_device_per_receiver = mock.Mock(return_value=None) + + assert r.force_unpair_slot(2) is False + # On failure, cache state is preserved so Solaar's model stays honest. + assert r._devices == {2: None} + + +def test_force_unpair_slot_no_handle(): + r = _make_fake_receiver() + r.handle = None + r._unpair_device_per_receiver = mock.Mock() + + assert r.force_unpair_slot(2) is False + r._unpair_device_per_receiver.assert_not_called() diff --git a/tests/logitech_receiver/test_setting_templates.py b/tests/logitech_receiver/test_setting_templates.py new file mode 100644 index 00000000..69793819 --- /dev/null +++ b/tests/logitech_receiver/test_setting_templates.py @@ -0,0 +1,913 @@ +## Copyright (C) 2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/ +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""The tests work by creating a faked device (from the hidpp module) that uses provided data as responses to HID++ commands. +The device uses some methods from the real device to set up data structures that are needed for some tests. +""" + +from dataclasses import dataclass +from typing import Any + +import pytest + +from logitech_receiver import common +from logitech_receiver import hidpp20 +from logitech_receiver import hidpp20_constants +from logitech_receiver import settings_templates +from logitech_receiver import settings_validator +from logitech_receiver import special_keys + +from . import fake_hidpp + +# Per-key colors: any 24-bit RGB; matches PerKeyLighting.validator_class._COLOR_RANGE. +_PERKEY_COLOR_RANGE = settings_validator.Range(min=0, max=0xFFFFFF, byte_count=3) + +# TODO action part of DpiSlidingXY, MouseGesturesXY + + +class Setup: + def __init__(self, test, *params): + self.test = test + self.responses = [r for r in params if isinstance(r, fake_hidpp.Response)] + self.choices = None if isinstance(params[0], fake_hidpp.Response) else params[0] + + +@dataclass +class RegisterTest: + sclass: Any + initial_value: Any = False + write_value: Any = True + write_params: str = "01" + + +register_tests = [ + Setup( + RegisterTest(settings_templates.RegisterHandDetection, False, True, [b"\x00\x00\x00"]), + fake_hidpp.Response("000030", 0x8101), # keyboard_hand_detection + fake_hidpp.Response("000000", 0x8001, "000000"), + ), + Setup( + RegisterTest(settings_templates.RegisterHandDetection, True, False, [b"\x00\x00\x30"]), + fake_hidpp.Response("000000", 0x8101), # keyboard_hand_detection + fake_hidpp.Response("000030", 0x8001, "000030"), + ), + Setup( + RegisterTest(settings_templates.RegisterSmoothScroll, False, True, [b"\x40"]), + fake_hidpp.Response("00", 0x8101), # mouse_button_flags + fake_hidpp.Response("40", 0x8001, "40"), + ), + Setup( + RegisterTest(settings_templates.RegisterSideScroll, True, False, [b"\x00"]), + fake_hidpp.Response("02", 0x8101), # mouse_button_flags + fake_hidpp.Response("00", 0x8001, "00"), + ), + Setup( + RegisterTest(settings_templates.RegisterFnSwap, False, True, [b"\x00\x01"]), + fake_hidpp.Response("0000", 0x8109), # keyboard_fn_swap + fake_hidpp.Response("0001", 0x8009, "0001"), + ), + Setup( + RegisterTest( + settings_templates._PerformanceMXDpi, common.NamedInt(0x88, "800"), common.NamedInt(0x89, "900"), [b"\x89"] + ), + fake_hidpp.Response("88", 0x8163), # mouse_dpi + fake_hidpp.Response("89", 0x8063, "89"), + ), +] + + +@pytest.mark.parametrize("test", register_tests) +def test_register_template(test, mocker): + device = fake_hidpp.Device(protocol=1.0, responses=test.responses) + spy_request = mocker.spy(device, "request") + + setting = test.test.sclass.build(device) + value = setting.read(cached=False) + cached_value = setting.read(cached=True) + write_value = setting.write(test.test.write_value) + + assert setting is not None + assert value == test.test.initial_value + assert cached_value == test.test.initial_value + assert write_value == test.test.write_value + spy_request.assert_called_with(test.test.sclass.register + 0x8000, *test.test.write_params) + + +@dataclass +class FeatureTest: + sclass: Any + initial_value: Any = False + write_value: Any = True + matched_calls: int = 1 + offset: int = 0x04 + version: int = 0x00 + rewrite: bool = False + readable: bool = True + + +simple_tests = [ + Setup( + FeatureTest(settings_templates.K375sFnSwap, False, True, offset=0x06), + fake_hidpp.Response("FF0001", 0x0600, "FF"), + fake_hidpp.Response("FF0101", 0x0610, "FF01"), + ), + Setup( + FeatureTest(settings_templates.K375sFnSwap, False, True, offset=0x06), + fake_hidpp.Response("050001", 0x0000, "1815"), # HOSTS_INFO + fake_hidpp.Response("FF0001", 0x0600, "FF"), + fake_hidpp.Response("FF0101", 0x0610, "FF01"), + ), + Setup( + FeatureTest(settings_templates.K375sFnSwap, False, True, offset=0x06), + fake_hidpp.Response("050001", 0x0000, "1815"), # HOSTS_INFO + fake_hidpp.Response("07050301", 0x0500), # current host is 0x01, i.e., host 2 + fake_hidpp.Response("010001", 0x0600, "01"), + fake_hidpp.Response("010101", 0x0610, "0101"), + ), + Setup( + FeatureTest(settings_templates.FnSwap, True, False), + fake_hidpp.Response("01", 0x0400), + fake_hidpp.Response("00", 0x0410, "00"), + ), + Setup( + FeatureTest(settings_templates.NewFnSwap, True, False), + fake_hidpp.Response("01", 0x0400), + fake_hidpp.Response("00", 0x0410, "00"), + ), + # Setup( # Backlight has caused problems + # FeatureTest(settings_templates.Backlight, 0, 5, offset=0x06), + # fake_hidpp.Response("00", 0x0600), + # fake_hidpp.Response("05", 0x0610, "05"), + # ), + Setup( + FeatureTest(settings_templates.Backlight2DurationHandsOut, 80, 160, version=0x03), + fake_hidpp.Response("011830000000100040006000", 0x0400), + fake_hidpp.Response("0118FF00200040006000", 0x0410, "0118FF00200040006000"), + ), + Setup( + FeatureTest(settings_templates.Backlight2DurationHandsIn, 320, 160, version=0x03), + fake_hidpp.Response("011830000000200040006000", 0x0400), + fake_hidpp.Response("0118FF00200020006000", 0x0410, "0118FF00200020006000"), + ), + Setup( + FeatureTest(settings_templates.Backlight2DurationPowered, 480, 80, version=0x03), + fake_hidpp.Response("011830000000200040006000", 0x0400), + fake_hidpp.Response("0118FF00200040001000", 0x0410, "0118FF00200040001000"), + ), + Setup( + FeatureTest(settings_templates.Backlight3, 0x50, 0x70), + fake_hidpp.Response("50", 0x0410), + fake_hidpp.Response("70", 0x0420, "007009"), + ), + Setup( + FeatureTest(settings_templates.HiResScroll, True, False), + fake_hidpp.Response("01", 0x0400), + fake_hidpp.Response("00", 0x0410, "00"), + ), + Setup( + FeatureTest(settings_templates.LowresMode, False, True), + fake_hidpp.Response("00", 0x0400), + fake_hidpp.Response("01", 0x0410, "01"), + ), + Setup( + FeatureTest(settings_templates.HiresSmoothInvert, True, False), + fake_hidpp.Response("06", 0x0410), + fake_hidpp.Response("02", 0x0420, "02"), + ), + Setup( + FeatureTest(settings_templates.HiresSmoothResolution, True, False), + fake_hidpp.Response("06", 0x0410), + fake_hidpp.Response("04", 0x0420, "04"), + ), + Setup( + FeatureTest(settings_templates.HiresMode, False, True), + fake_hidpp.Response("06", 0x0410), + fake_hidpp.Response("07", 0x0420, "07"), + ), + Setup( + FeatureTest(settings_templates.PointerSpeed, 0x0100, 0x0120), + fake_hidpp.Response("0100", 0x0400), + fake_hidpp.Response("0120", 0x0410, "0120"), + ), + Setup( + FeatureTest(settings_templates.ThumbMode, True, False), + fake_hidpp.Response("0100", 0x0410), + fake_hidpp.Response("0000", 0x0420, "0000"), + ), + Setup( + FeatureTest(settings_templates.ThumbInvert, False, True), + fake_hidpp.Response("0100", 0x0410), + fake_hidpp.Response("0101", 0x0420, "0101"), + ), + Setup( + FeatureTest(settings_templates.DivertCrown, False, True), + fake_hidpp.Response("01", 0x0410), + fake_hidpp.Response("02", 0x0420, "02"), + ), + Setup( + FeatureTest(settings_templates.CrownSmooth, True, False), + fake_hidpp.Response("0001", 0x0410), + fake_hidpp.Response("0002", 0x0420, "0002"), + ), + Setup( + FeatureTest(settings_templates.DivertGkeys, False, True), + fake_hidpp.Response("01", 0x0420, "01"), + ), + Setup( + FeatureTest(settings_templates.ScrollRatchet, 2, 1), + fake_hidpp.Response("02", 0x0400), + fake_hidpp.Response("01", 0x0410, "01"), + ), + Setup( + FeatureTest(settings_templates.SmartShift, 1, 10), + fake_hidpp.Response("0100", 0x0400), + fake_hidpp.Response("000A", 0x0410, "000A"), + ), + Setup( + FeatureTest(settings_templates.SmartShift, 5, 50), + fake_hidpp.Response("0005", 0x0400), + fake_hidpp.Response("00FF", 0x0410, "00FF"), + ), + Setup( + FeatureTest(settings_templates.SmartShiftEnhanced, 5, 50), + fake_hidpp.Response("0005", 0x0410), + fake_hidpp.Response("00FF", 0x0420, "00FF"), + ), + Setup( + FeatureTest(settings_templates.DisableKeyboardKeys, {1: True, 8: True}, {1: False, 8: True}), + fake_hidpp.Response("09", 0x0400), + fake_hidpp.Response("09", 0x0410), + fake_hidpp.Response("08", 0x0420, "08"), + ), + Setup( + FeatureTest(settings_templates.DualPlatform, 0, 1), + fake_hidpp.Response("00", 0x0400), + fake_hidpp.Response("01", 0x0420, "01"), + ), + Setup( + FeatureTest(settings_templates.MKeyLEDs, {1: False, 2: False, 4: False}, {1: False, 2: True, 4: True}), + fake_hidpp.Response("03", 0x0400), + fake_hidpp.Response("06", 0x0410, "06"), + ), + Setup( + FeatureTest(settings_templates.MRKeyLED, False, True), + fake_hidpp.Response("01", 0x0400, "01"), + ), + Setup( + FeatureTest(settings_templates.Sidetone, 5, 0xA), + fake_hidpp.Response("05", 0x0400), + fake_hidpp.Response("0A", 0x0410, "0A"), + ), + Setup( + FeatureTest(settings_templates.ADCPower, 5, 0xA, version=0x03), + fake_hidpp.Response("05", 0x0410), + fake_hidpp.Response("0A", 0x0420, "0A"), + ), + Setup( + FeatureTest(settings_templates.LEDControl, 0, 1), + fake_hidpp.Response("00", 0x0470), + fake_hidpp.Response("01", 0x0480, "01"), + ), + Setup( + FeatureTest( + settings_templates.LEDZoneSetting, + hidpp20.LEDEffectSetting(ID=3, intensity=0x50, period=0x100), + hidpp20.LEDEffectSetting(ID=3, intensity=0x50, period=0x101), + ), + fake_hidpp.Response("0100000001", 0x0400), + fake_hidpp.Response("00000102", 0x0410, "00FF00"), + fake_hidpp.Response("0000000300040005", 0x0420, "000000"), + fake_hidpp.Response("0001000B00080009", 0x0420, "000100"), + fake_hidpp.Response("000000000000010050", 0x04E0, "00"), + fake_hidpp.Response("000000000000000101500000", 0x0430, "000000000000000101500000"), + ), + Setup( + FeatureTest(settings_templates.RGBControl, 0, 1), + fake_hidpp.Response("0000", 0x0450), + fake_hidpp.Response("010100", 0x0450, "0101"), + ), + Setup( + FeatureTest( + settings_templates.RGBEffectSetting, + hidpp20.LEDEffectSetting(ID=3, intensity=0x50, period=0x100), + hidpp20.LEDEffectSetting(ID=2, color=0x505050, speed=0x50), + readable=False, + ), + fake_hidpp.Response("FFFF0100000001", 0x0400, "FFFF00"), + fake_hidpp.Response("0000000102", 0x0400, "00FF00"), + fake_hidpp.Response("0000000300040005", 0x0400, "000000"), + fake_hidpp.Response("0001000200080009", 0x0400, "000100"), + fake_hidpp.Response("000000000000010050", 0x04E0, "00"), + fake_hidpp.Response("00015050505000000000000001", 0x0410, "00015050505000000000000001"), + ), + Setup( + FeatureTest( + settings_templates.RGBEffectSetting, + None, + hidpp20.LEDEffectSetting(ID=3, intensity=0x60, period=0x101), + readable=False, + ), + fake_hidpp.Response("FFFF0100000001", 0x0400, "FFFF00"), + fake_hidpp.Response("0000000102", 0x0400, "00FF00"), + fake_hidpp.Response("0000000300040005", 0x0400, "000000"), + fake_hidpp.Response("0001000200080009", 0x0400, "000100"), + fake_hidpp.Response("00000000000000010160000001", 0x0410, "00000000000000010160000001"), + ), + Setup( + FeatureTest( + settings_templates.RGBEffectSetting, + None, + hidpp20.LEDEffectSetting(ID=3, intensity=0x60, period=0x101), + readable=False, + ), + fake_hidpp.Response("FF000200020004000000000000000000", 0x0400, "FFFF00"), + fake_hidpp.Response("00000002040000000000000000000000", 0x0400, "00FF00"), + fake_hidpp.Response("00000000000000000000000000000000", 0x0400, "000000"), + fake_hidpp.Response("00010001000000000000000000000000", 0x0400, "000100"), + fake_hidpp.Response("00020003C00503E00000000000000000", 0x0400, "000200"), + fake_hidpp.Response("0003000AC0011E0B0000000000000000", 0x0400, "000300"), + fake_hidpp.Response("01000001070000000000000000000000", 0x0400, "01FF00"), + fake_hidpp.Response("01000000000000000000000000000000", 0x0400, "010000"), + fake_hidpp.Response("01010001000000000000000000000000", 0x0400, "010100"), + fake_hidpp.Response("0102000AC0011E0B0000000000000000", 0x0400, "010200"), + fake_hidpp.Response("01030003C00503E00000000000000000", 0x0400, "010300"), + fake_hidpp.Response("01040004DCE1001E0000000000000000", 0x0400, "010400"), + fake_hidpp.Response("0105000B000000320000000000000000", 0x0400, "010500"), + fake_hidpp.Response("0106000C001B02340000000000000000", 0x0400, "010600"), + fake_hidpp.Response("00020000000000010160000001", 0x0410, "00020000000000010160000001"), + ), + Setup( + FeatureTest(settings_templates.Backlight2, 0xFF, 0x00), + common.NamedInts(Disabled=0xFF, Enabled=0x00), + fake_hidpp.Response("000201000000000000000000", 0x0400), + fake_hidpp.Response("010201", 0x0410, "0102FF00000000000000"), + ), + Setup( + FeatureTest(settings_templates.Backlight2, 0x03, 0xFF), + common.NamedInts(Disabled=0xFF, Automatic=0x01, Manual=0x03), + fake_hidpp.Response("011838000000000000000000", 0x0400), + fake_hidpp.Response("001801", 0x0410, "0018FF00000000000000"), + ), + Setup( + FeatureTest(settings_templates.Backlight2Level, 0, 3, version=0x03), + [0, 4], + fake_hidpp.Response("011830000000000000000000", 0x0400), + fake_hidpp.Response("05", 0x0420), + fake_hidpp.Response("01180103000000000000", 0x0410, "0118FF03000000000000"), + ), + Setup( + FeatureTest(settings_templates.Backlight2Level, 0, 2, version=0x03), + [0, 4], + fake_hidpp.Response("011830000000000000000000", 0x0400), + fake_hidpp.Response("05", 0x0420), + fake_hidpp.Response("01180102000000000000", 0x0410, "0118FF02000000000000"), + ), + Setup( + FeatureTest(settings_templates.OnboardProfiles, 0, 1, offset=0x0C), + common.NamedInts(**{"Disabled": 0, "Profile 1": 1, "Profile 2": 2}), + fake_hidpp.Response("01030001010101000101", 0x0C00), + fake_hidpp.Response("00010100000201FFFFFFFFFFFFFFFFFF", 0x0C50, "00000000"), + fake_hidpp.Response("000201FFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0C50, "00000004"), + fake_hidpp.Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0C50, "00000008"), + fake_hidpp.Response("02", 0x0C20), + fake_hidpp.Response("01", 0x0C10, "01"), + fake_hidpp.Response("0001", 0x0C30, "0001"), + ), + Setup( + FeatureTest(settings_templates.OnboardProfiles, 1, 0, offset=0x0C), + common.NamedInts(**{"Disabled": 0, "Profile 1": 1, "Profile 2": 2}), + fake_hidpp.Response("01030001010101000101", 0x0C00), + fake_hidpp.Response("00010100000201FFFFFFFFFFFFFFFFFF", 0x0C50, "00000000"), + fake_hidpp.Response("000201FFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0C50, "00000004"), + fake_hidpp.Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0C50, "00000008"), + fake_hidpp.Response("01", 0x0C20), + fake_hidpp.Response("0001", 0x0C40), + fake_hidpp.Response("02", 0x0C10, "02"), + ), + Setup( + FeatureTest(settings_templates.ReportRate, 1, 5, offset=0x0C), + common.NamedInts(**{"1ms": 1, "2ms": 2, "5ms": 5, "6ms": 6}), + fake_hidpp.Response("33", 0x0C00), + fake_hidpp.Response("01", 0x0C10), + fake_hidpp.Response("05", 0x0C20, "05"), + ), + Setup( + FeatureTest(settings_templates.ExtendedReportRate, 1, 5, offset=0x0C), + common.NamedInts(**{"8ms": 0, "4ms": 1, "500us": 4, "250us": 5}), + fake_hidpp.Response("33", 0x0C10), + fake_hidpp.Response("01", 0x0C20), + fake_hidpp.Response("05", 0x0C30, "05"), + ), + Setup( + FeatureTest(settings_templates.AdjustableDpi, 800, 400, version=0x03), + common.NamedInts.list([400, 800, 1600]), + fake_hidpp.Response("000190032006400000", 0x0410, "000000"), + fake_hidpp.Response("000320", 0x0420), + fake_hidpp.Response("000190", 0x0430, "000190"), + ), + Setup( + FeatureTest(settings_templates.AdjustableDpi, 256, 512, version=0x03), + common.NamedInts.list([256, 512]), + fake_hidpp.Response("000100e10002000000", 0x0410, "000000"), + fake_hidpp.Response("000100", 0x0420), + fake_hidpp.Response("000200", 0x0430, "000200"), + ), + Setup( + FeatureTest(settings_templates.AdjustableDpi, 400, 800, version=0x03), + common.NamedInts.list([400, 800, 1200, 1600]), + fake_hidpp.Response("000190E19006400000000000000000", 0x0410, "000000"), + fake_hidpp.Response("000190", 0x0420), + fake_hidpp.Response("000320", 0x0430, "000320"), + ), + Setup( + FeatureTest(settings_templates.Multiplatform, 0, 1), + common.NamedInts(**{"MacOS 0.1-0.5": 0, "iOS 0.1-0.7": 1, "Linux 0.2-0.9": 2, "Windows 0.3-0.9": 3}), + fake_hidpp.Response("020004000001", 0x0400), + fake_hidpp.Response("00FF200000010005", 0x0410, "00"), + fake_hidpp.Response("01FF400000010007", 0x0410, "01"), + fake_hidpp.Response("02FF040000020009", 0x0410, "02"), + fake_hidpp.Response("03FF010000030009", 0x0410, "03"), + fake_hidpp.Response("FF01", 0x0430, "FF01"), + ), + Setup( + FeatureTest(settings_templates.ChangeHost, 1, 0), + common.NamedInts(**{"1:ABCDEF": 0, "2:GHIJKL": 1}), + fake_hidpp.Response("050003", 0x0000, "1815"), # HOSTS_INFO + fake_hidpp.Response("01000200", 0x0500), + fake_hidpp.Response("000100000600", 0x0510, "00"), + fake_hidpp.Response("000041424344454600", 0x0530, "0000"), + fake_hidpp.Response("000100000600", 0x0510, "01"), + fake_hidpp.Response("00004748494A4B4C00", 0x0530, "0100"), + fake_hidpp.Response("0201", 0x0400), + fake_hidpp.Response(True, 0x0410, "00"), + ), + Setup( + FeatureTest(settings_templates.BrightnessControl, 0x10, 0x20), + [0, 80], + fake_hidpp.Response("00505100000000", 0x0400), # 0 to 80, all acceptable, no separate on/off + fake_hidpp.Response("10", 0x0410), # brightness 16 + fake_hidpp.Response("0020", 0x0420, "0020"), # set brightness 32 + ), + Setup( + FeatureTest(settings_templates.BrightnessControl, 0x10, 0x00), + [0, 80], + fake_hidpp.Response("00505104000000", 0x0400), # 0 to 80, all acceptable, separate on/off + fake_hidpp.Response("10", 0x0410), # brightness 16 + fake_hidpp.Response("01", 0x0430), # on + fake_hidpp.Response("00", 0x0440), # set off + fake_hidpp.Response("0000", 0x0420, "0000"), # set brightness 0 + ), + Setup( + FeatureTest(settings_templates.BrightnessControl, 0x00, 0x20), + [0, 80], + fake_hidpp.Response("00505104000000", 0x0400), # 0 to 80, all acceptable, separate on/off + fake_hidpp.Response("10", 0x0410), # brightness 16 + fake_hidpp.Response("00", 0x0430), # off + fake_hidpp.Response("01", 0x0440), # set on + fake_hidpp.Response("0020", 0x0420, "0020"), # set brightness 32 + ), + Setup( + FeatureTest(settings_templates.BrightnessControl, 0x20, 0x08), + [0, 80], + fake_hidpp.Response("00504104001000", 0x0400), # 16 to 80, all acceptable, separate on/off + fake_hidpp.Response("20", 0x0410), # brightness 32 + fake_hidpp.Response("01", 0x0430), # on + fake_hidpp.Response("00", 0x0440, "00"), # set off + ), + Setup( + FeatureTest(settings_templates.SpeedChange, 0, None, 0), # need to set up all settings to successfully write + common.NamedInts(**{"Off": 0, "DPI Change": 0xED}), + *fake_hidpp.responses_speedchange, + ), +] + + +@pytest.fixture +def mock_gethostname(mocker): + mocker.patch("socket.gethostname", return_value="ABCDEF.foo.org") + + +@pytest.mark.parametrize("test", simple_tests) +def test_simple_template(test, mocker, mock_gethostname): + tst = test.test + print("TEST", tst.sclass, tst.sclass.feature) + device = fake_hidpp.Device(responses=test.responses, feature=tst.sclass.feature, offset=tst.offset, version=tst.version) + spy_request = mocker.spy(device, "request") + + setting = settings_templates.check_feature(device, tst.sclass) + assert setting is not None + if isinstance(setting, list): + setting = setting[0] + if isinstance(test.choices, list): + assert setting._validator.min_value == test.choices[0] + assert setting._validator.max_value == test.choices[1] + elif test.choices is not None: + assert setting.choices == test.choices + + if tst.readable: + value = setting.read(cached=False) + assert value == tst.initial_value + + cached_value = setting.read(cached=True) + assert cached_value == tst.initial_value + + write_value = setting.write(tst.write_value) if tst.write_value is not None else None + assert write_value == tst.write_value + + fake_hidpp.match_requests(tst.matched_calls, test.responses, spy_request.call_args_list) + + +responses_reprog_controls = [ + fake_hidpp.Response("03", 0x0500), + fake_hidpp.Response("00500038010001010400000000000000", 0x0510, "00"), # left button + fake_hidpp.Response("00510039010001010400000000000000", 0x0510, "01"), # right button + fake_hidpp.Response("00C4009D310003070500000000000000", 0x0510, "02"), # smart shift + fake_hidpp.Response("00500000000000000000000000000000", 0x0520, "0050"), # left button current + fake_hidpp.Response("00510000500000000000000000000000", 0x0520, "0051"), # right button current + fake_hidpp.Response("00C40000000000000000000000000000", 0x0520, "00C4"), # smart shift current + fake_hidpp.Response("00500005000000000000000000000000", 0x0530, "0050000050"), # left button write + fake_hidpp.Response("00510005000000000000000000000000", 0x0530, "0051000050"), # right button write + fake_hidpp.Response("00C4000C400000000000000000000000", 0x0530, "00C40000C4"), # smart shift write +] + +key_tests = [ + Setup( + FeatureTest(settings_templates.ReprogrammableKeys, {0x50: 0x50, 0x51: 0x50, 0xC4: 0xC4}, {0x51: 0x51}, 4, offset=0x05), + { + common.NamedInt(0x50, "Left Button"): common.UnsortedNamedInts(Left_Click=0x50, Right_Click=0x51), + common.NamedInt(0x51, "Right Button"): common.UnsortedNamedInts(Right_Click=0x51, Left_Click=0x50), + common.NamedInt(0xC4, "Smart Shift"): common.UnsortedNamedInts(Smart_Shift=0xC4, Left_Click=80, Right_Click=81), + }, + *responses_reprog_controls, + fake_hidpp.Response("0051000051", 0x0530, "0051000051"), # right button set write + ), + Setup( + FeatureTest(settings_templates.DivertKeys, {0xC4: 0}, {0xC4: 1}, 2, offset=0x05), + {common.NamedInt(0xC4, "Smart Shift"): common.NamedInts(Regular=0, Diverted=1, Mouse_Gestures=2)}, + *responses_reprog_controls, + fake_hidpp.Response("00C4020000", 0x0530, "00C4020000"), # Smart Shift write + fake_hidpp.Response("00C4030000", 0x0530, "00C4030000"), # Smart Shift divert write + ), + Setup( + FeatureTest(settings_templates.DivertKeys, {0xC4: 0}, {0xC4: 2}, 2, offset=0x05), + {common.NamedInt(0xC4, "Smart Shift"): common.NamedInts(Regular=0, Diverted=1, Mouse_Gestures=2, Sliding_DPI=3)}, + *responses_reprog_controls, + fake_hidpp.Response("0A0001", 0x0000, "2201"), # ADJUSTABLE_DPI + fake_hidpp.Response("00C4300000", 0x0530, "00C4300000"), # Smart Shift write + fake_hidpp.Response("00C4030000", 0x0530, "00C4030000"), # Smart Shift divert write + ), + Setup( + FeatureTest(settings_templates.PersistentRemappableAction, {80: 16797696, 81: 16797696}, {0x51: 16797952}, 3), + { + common.NamedInt(80, "Left Button"): special_keys.KEYS_KEYS_CONSUMER, + common.NamedInt(81, "Right Button"): special_keys.KEYS_KEYS_CONSUMER, + }, + fake_hidpp.Response("050001", 0x0000, "1B04"), # REPROG_CONTROLS_V4 + *responses_reprog_controls, + fake_hidpp.Response("0041", 0x0400), + fake_hidpp.Response("0201", 0x0410), + fake_hidpp.Response("02", 0x0400), + fake_hidpp.Response("0050", 0x0420, "00FF"), # left button + fake_hidpp.Response("0051", 0x0420, "01FF"), # right button + fake_hidpp.Response("0050000100500000", 0x0430, "0050FF"), # left button current + fake_hidpp.Response("0051000100500001", 0x0430, "0051FF"), # right button current + fake_hidpp.Response("0050FF01005000", 0x0440, "0050FF01005000"), # left button write + fake_hidpp.Response("0051FF01005000", 0x0440, "0051FF01005000"), # right button write + fake_hidpp.Response("0051FF01005100", 0x0440, "0051FF01005100"), # right button set write + ), + Setup( + FeatureTest( + settings_templates.Gesture2Gestures, + { + 1: True, + 2: True, + 30: True, + 10: True, + 45: False, + 42: True, + 43: True, + 64: False, + 65: False, + 67: False, + 84: True, + 34: False, + }, + {45: True}, + 4, + ), + *fake_hidpp.responses_gestures, + fake_hidpp.Response("0001FF6F", 0x0420, "0001FF6F"), # write + fake_hidpp.Response("01010F04", 0x0420, "01010F04"), + fake_hidpp.Response("0001FF7F", 0x0420, "0001FF7F"), # write 45 + fake_hidpp.Response("01010F04", 0x0420, "01010F04"), + ), + Setup( + FeatureTest( + settings_templates.Gesture2Divert, + {1: False, 2: False, 10: False, 44: False, 64: False, 65: False, 67: False, 84: False, 85: False, 100: False}, + {44: True}, + 4, + ), + *fake_hidpp.responses_gestures, + fake_hidpp.Response("0001FF00", 0x0440, "0001FF00"), # write + fake_hidpp.Response("01010300", 0x0440, "01010300"), + fake_hidpp.Response("0001FF08", 0x0440, "0001FF08"), # write 44 + fake_hidpp.Response("01010300", 0x0440, "01010300"), + ), + Setup( + FeatureTest(settings_templates.Gesture2Params, {4: {"scale": 256}}, {4: {"scale": 128}}, 2), + *fake_hidpp.responses_gestures, + fake_hidpp.Response("000100FF000000000000000000000000", 0x0480, "000100FF"), + fake_hidpp.Response("000080FF000000000000000000000000", 0x0480, "000080FF"), + ), + Setup( + FeatureTest(settings_templates.Equalizer, {0: -0x20, 1: 0x10}, {1: 0x18}, 2), + [-32, 32], + fake_hidpp.Response("0220000000", 0x0400), + fake_hidpp.Response("0000800100000000000000", 0x0410, "00"), + fake_hidpp.Response("E010", 0x0420, "00"), + fake_hidpp.Response("E010", 0x0430, "02E010"), + fake_hidpp.Response("E018", 0x0430, "02E018"), + ), + Setup( # HeadsetOnboardEQ: 2 bands, 128Hz/-2dB/Q10 and 256Hz/+3dB/Q10 + FeatureTest(settings_templates.HeadsetOnboardEQ, {0: -2, 1: 3}, {1: 5}, 2), + [-12, 12], + fake_hidpp.Response("8000000002", 0x0400), # GetEQInfos: has_hw_eq, 2 bands + fake_hidpp.Response("00020080FE0A0100030A", 0x0410, "00"), # GetEQParameters + fake_hidpp.Response( # SetEQParameters: write initial values back (slot 0x00) + "00", + 0x0420, + "00020080FE0A0100030A" + "055AE300" # mystery bytes (from pcap) + "030E00020000000100170002" + "007A6B00D69D94008C516B00C82380005DC27F0075" + "906B0022B6940002226B00B44080007EA37F00C1C3040044" + "0200170002" + "00506B00900F95006ED56A00D185800060477F00CA" + "906B00229D950052496A00A12E810085EC7E0075C40400AC", + ), + fake_hidpp.Response( # SetEQParameters: persist initial values (slot 0x80) + "00", + 0x0420, + "80020080FE0A0100030A" + "055AE300" + "030E00020000000100170002" + "007A6B00D69D94008C516B00C82380005DC27F0075" + "906B0022B6940002226B00B44080007EA37F00C1C3040044" + "0200170002" + "00506B00900F95006ED56A00D185800060477F00CA" + "906B00229D950052496A00A12E810085EC7E0075C40400AC", + ), + fake_hidpp.Response( # SetEQParameters: write updated band 1 gain=5 (slot 0x00) + "00", + 0x0420, + "00020080FE0A0100050A" + "055AE300" + "030E00020000000100170002" + "006F6B00F5A894006A466B00EB2380005DC27F0075" + "906B0022BC9400AA156B00613B80007CAD7F00C6C30400BF" + "0200170002" + "00306B00272F9500BAB56A008C85800060477F00CA" + "906B0022B1950002226A000E1F8100AB0A7F004FC604001D", + ), + fake_hidpp.Response( # SetEQParameters: persist updated values (slot 0x80) + "00", + 0x0420, + "80020080FE0A0100050A" + "055AE300" + "030E00020000000100170002" + "006F6B00F5A894006A466B00EB2380005DC27F0075" + "906B0022BC9400AA156B00613B80007CAD7F00C6C30400BF" + "0200170002" + "00306B00272F9500BAB56A008C85800060477F00CA" + "906B0022B1950002226A000E1F8100AB0A7F004FC604001D", + ), + ), + Setup( + FeatureTest(settings_templates.PerKeyLighting, {1: -1, 2: -1, 9: -1, 10: -1, 113: -1}, {2: 0xFF0000}, 4, 4, 0, 1), + { + common.NamedInt(1, "A"): _PERKEY_COLOR_RANGE, + common.NamedInt(2, "B"): _PERKEY_COLOR_RANGE, + common.NamedInt(9, "I"): _PERKEY_COLOR_RANGE, + common.NamedInt(10, "J"): _PERKEY_COLOR_RANGE, + common.NamedInt(113, "KEY 113"): _PERKEY_COLOR_RANGE, + }, + fake_hidpp.Response("00000606000000000000000000000000", 0x0400, "0000"), # first group of keys + fake_hidpp.Response("00000200000000000000000000000000", 0x0400, "0001"), # second group of keys + fake_hidpp.Response("00000000000000000000000000000000", 0x0400, "0002"), # last group of keys + fake_hidpp.Response("02FF0000", 0x0410, "02FF0000"), # write one value + fake_hidpp.Response("00", 0x0470, "00"), # finish + fake_hidpp.Response("02FF0000", 0x0410, "02FF0000"), # write one value + fake_hidpp.Response("00", 0x0470, "00"), # finish + ), + Setup( + FeatureTest( + settings_templates.PerKeyLighting, + {1: -1, 2: -1, 9: -1, 10: -1, 113: -1}, + {2: 0xFF0000, 9: 0xFF0000, 10: 0xFF0000}, + 8, + 4, + 0, + 1, + ), + { + common.NamedInt(1, "A"): _PERKEY_COLOR_RANGE, + common.NamedInt(2, "B"): _PERKEY_COLOR_RANGE, + common.NamedInt(9, "I"): _PERKEY_COLOR_RANGE, + common.NamedInt(10, "J"): _PERKEY_COLOR_RANGE, + common.NamedInt(113, "KEY 113"): _PERKEY_COLOR_RANGE, + }, + fake_hidpp.Response("00000606000000000000000000000000", 0x0400, "0000"), # first group of keys + fake_hidpp.Response("00000200000000000000000000000000", 0x0400, "0001"), # second group of keys + fake_hidpp.Response("00000000000000000000000000000000", 0x0400, "0002"), # last group of keys + fake_hidpp.Response("02FF0000", 0x0410, "02FF0000"), # write one value + fake_hidpp.Response("00", 0x0470, "00"), # finish + fake_hidpp.Response("09FF0000", 0x0410, "09FF0000"), # write one value + fake_hidpp.Response("00", 0x0470, "00"), # finish + fake_hidpp.Response("0AFF0000", 0x0410, "0AFF0000"), # write one value + fake_hidpp.Response("00", 0x0470, "00"), # finish + fake_hidpp.Response("02FF000009FF00000AFF0000", 0x0410, "02FF000009FF00000AFF0000"), # write three values + fake_hidpp.Response("00", 0x0470, "00"), # finish + ), + Setup( + FeatureTest( + settings_templates.PerKeyLighting, + {1: -1, 2: -1, 9: -1, 10: -1, 113: -1, 114: -1}, + {1: 0xFF0000, 2: 0xFF0000, 9: 0xFF0000, 10: 0xFF0000, 113: 0x00FF00, 114: 0xFF0000}, + 15, + 4, + 0, + 1, + ), + { + common.NamedInt(1, "A"): _PERKEY_COLOR_RANGE, + common.NamedInt(2, "B"): _PERKEY_COLOR_RANGE, + common.NamedInt(9, "I"): _PERKEY_COLOR_RANGE, + common.NamedInt(10, "J"): _PERKEY_COLOR_RANGE, + common.NamedInt(113, "KEY 113"): _PERKEY_COLOR_RANGE, + common.NamedInt(114, "KEY 114"): _PERKEY_COLOR_RANGE, + }, + fake_hidpp.Response("00000606000000000000000000000000", 0x0400, "0000"), # first group of keys + fake_hidpp.Response("00000600000000000000000000000000", 0x0400, "0001"), # second group of keys + fake_hidpp.Response("00000000000000000000000000000000", 0x0400, "0002"), # last group of keys + fake_hidpp.Response("01FF0000", 0x0410, "01FF0000"), # write one value + fake_hidpp.Response("00", 0x0470, "00"), # finish + fake_hidpp.Response("02FF0000", 0x0410, "02FF0000"), # write one value + fake_hidpp.Response("00", 0x0470, "00"), # finish + fake_hidpp.Response("09FF0000", 0x0410, "09FF0000"), # write one value + fake_hidpp.Response("00", 0x0470, "00"), # finish + fake_hidpp.Response("0AFF0000", 0x0410, "0AFF0000"), # write one value + fake_hidpp.Response("00", 0x0470, "00"), # finish + fake_hidpp.Response("7100FF00", 0x0410, "7100FF00"), # write one value + fake_hidpp.Response("00", 0x0470, "00"), # finish + fake_hidpp.Response("72FF0000", 0x0410, "72FF0000"), # write one value + fake_hidpp.Response("00", 0x0470, "00"), # finish + fake_hidpp.Response("FF00000102090A72", 0x460, "FF00000102090A72"), # write one value for five keys + fake_hidpp.Response("7100FF00", 0x0410, "7100FF00"), # write one value + fake_hidpp.Response("00", 0x0470, "00"), # finish + ), + Setup( + FeatureTest(settings_templates.ExtendedAdjustableDpi, {0: 256}, {0: 512}, 2, offset=0x9), + {common.NamedInt(0, "X"): common.NamedInts.list([256, 512])}, + fake_hidpp.Response("000000", 0x0910, "00"), # no y direction, no lod + fake_hidpp.Response("0000000100e10002000000", 0x0920, "000000"), + fake_hidpp.Response("00010000000000000000", 0x0950), + fake_hidpp.Response("000100000000", 0x0960, "000100000000"), + fake_hidpp.Response("000200000000", 0x0960, "000200000000"), + ), + Setup( + FeatureTest(settings_templates.ExtendedAdjustableDpi, {0: 0x64, 1: 0xE4}, {0: 0x164}, 2, offset=0x9), + { + common.NamedInt(0, "X"): common.NamedInts.list([0x064, 0x074, 0x084, 0x0A4, 0x0C4, 0x0E4, 0x0124, 0x0164, 0x01C4]), + common.NamedInt(1, "Y"): common.NamedInts.list([0x064, 0x074, 0x084, 0x0A4, 0x0C4, 0x0E4, 0x0124, 0x0164]), + }, + fake_hidpp.Response("000001", 0x0910, "00"), # supports y direction, no lod + fake_hidpp.Response("0000000064E0100084E02000C4E02000", 0x0920, "000000"), + fake_hidpp.Response("000001E4E0400124E0400164E06001C4", 0x0920, "000001"), + fake_hidpp.Response("00000000000000000000000000000000", 0x0920, "000002"), + fake_hidpp.Response("0000000064E0100084E02000C4E02000", 0x0920, "000100"), + fake_hidpp.Response("000001E4E0400124E040016400000000", 0x0920, "000101"), + fake_hidpp.Response("000064007400E4007400", 0x0950), + fake_hidpp.Response("00006400E400", 0x0960, "00006400E400"), + fake_hidpp.Response("00016400E400", 0x0960, "00016400E400"), + ), + Setup( + FeatureTest(settings_templates.ExtendedAdjustableDpi, {0: 0x64, 1: 0xE4, 2: 1}, {1: 0x164}, 2, offset=0x9), + { + common.NamedInt(0, "X"): common.NamedInts.list([0x064, 0x074, 0x084, 0x0A4, 0x0C4, 0x0E4, 0x0124, 0x0164, 0x01C4]), + common.NamedInt(1, "Y"): common.NamedInts.list([0x064, 0x074, 0x084, 0x0A4, 0x0C4, 0x0E4, 0x0124, 0x0164]), + common.NamedInt(2, "LOD"): common.NamedInts(LOW=0, MEDIUM=1, HIGH=2), + }, + fake_hidpp.Response("000003", 0x0910, "00"), # supports y direction and lod + fake_hidpp.Response("0000000064E0100084E02000C4E02000", 0x0920, "000000"), + fake_hidpp.Response("000001E4E0400124E0400164E06001C4", 0x0920, "000001"), + fake_hidpp.Response("00000000000000000000000000000000", 0x0920, "000002"), + fake_hidpp.Response("0000000064E0100084E02000C4E02000", 0x0920, "000100"), + fake_hidpp.Response("000001E4E0400124E040016400000000", 0x0920, "000101"), + fake_hidpp.Response("000064007400E4007401", 0x0950), + fake_hidpp.Response("00006400E401", 0x0960, "00006400E401"), + fake_hidpp.Response("000064016401", 0x0960, "000064016401"), + ), +] + + +@pytest.mark.parametrize("test", key_tests) +def test_key_template(test, mocker): + tst = test.test + device = fake_hidpp.Device(responses=test.responses, feature=tst.sclass.feature, offset=tst.offset, version=tst.version) + spy_request = mocker.spy(device, "request") + + print("FEATURE", tst.sclass.feature) + setting = settings_templates.check_feature(device, tst.sclass) + assert setting is not None + if isinstance(setting, list): + setting = setting[0] + if isinstance(test.choices, list): + assert setting._validator.min_value == test.choices[0] + assert setting._validator.max_value == test.choices[1] + elif test.choices is not None: + assert setting.choices == test.choices + + value = setting.read(cached=False) + assert value == tst.initial_value + + write_value = setting.write(value) + assert write_value == tst.initial_value + + for key, val in tst.write_value.items(): + write_value = setting.write_key_value(key, val) + assert write_value == val + value[key] = val + + if tst.rewrite: + write_value = setting.write(value) + + fake_hidpp.match_requests(tst.matched_calls, test.responses, spy_request.call_args_list) + + +@pytest.mark.parametrize( + "responses, currentSpeed, newSpeed", + [ + (fake_hidpp.responses_speedchange, 100, 200), + (fake_hidpp.responses_speedchange, None, 250), + ], +) +def test_SpeedChange_action(responses, currentSpeed, newSpeed, mocker): + device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.SupportedFeature.POINTER_SPEED) + spy_setting_callback = mocker.spy(device, "setting_callback") + settings_templates.check_feature_settings(device, device.settings) # need to set up all the settings + device.persister = {"pointer_speed": currentSpeed, "_speed-change": newSpeed} + speed_setting = next(filter(lambda s: s.name == "speed-change", device.settings), None) + pointer_setting = next(filter(lambda s: s.name == "pointer_speed", device.settings), None) + + speed_setting.write(237) + speed_setting._rw.press_action() + + if newSpeed is not None and speed_setting is not None: + spy_setting_callback.assert_any_call(device, type(pointer_setting), [newSpeed]) + assert device.persister["_speed-change"] == currentSpeed + assert device.persister["pointer_speed"] == newSpeed + + +@pytest.mark.parametrize("test", simple_tests + key_tests) +def test_check_feature_settings(test, mocker): + tst = test.test + device = fake_hidpp.Device(responses=test.responses, feature=tst.sclass.feature, offset=tst.offset, version=tst.version) + + already_known = [] + setting = settings_templates.check_feature_settings(device, already_known) + + assert setting is True + assert already_known + + +@pytest.mark.parametrize( + "test", + [ + Setup( + FeatureTest(settings_templates.K375sFnSwap, False, True, offset=0x06), + fake_hidpp.Response("FF0001", 0x0600, "FF"), + fake_hidpp.Response("FF0101", 0x0610, "FF01"), + ) + ], +) +def test_check_feature_setting(test, mocker): + tst = test.test + device = fake_hidpp.Device(responses=test.responses, feature=tst.sclass.feature, offset=tst.offset, version=tst.version) + + setting = settings_templates.check_feature_setting(device, tst.sclass.name) + + assert setting diff --git a/tests/logitech_receiver/test_settings_validator.py b/tests/logitech_receiver/test_settings_validator.py new file mode 100644 index 00000000..383fae22 --- /dev/null +++ b/tests/logitech_receiver/test_settings_validator.py @@ -0,0 +1,25 @@ +import pytest + +from logitech_receiver import settings_validator + + +@pytest.mark.parametrize( + "current, new, expected", + [ + (False, "toggle", True), + (True, "~", False), + ("don't care", True, True), + ("don't care", "true", True), + ("don't care", "false", False), + ("don't care", "no", False), + ("don't care", "off", False), + ("don't care", "True", True), + ("don't care", "yes", True), + ("don't care", "on", True), + ("anything", "anything", None), + ], +) +def test_bool_or_toggle(current, new, expected): + result = settings_validator.bool_or_toggle(current=current, new=new) + + assert result == expected diff --git a/tests/solaar/test_gtk.py b/tests/solaar/test_gtk.py new file mode 100644 index 00000000..60fea213 --- /dev/null +++ b/tests/solaar/test_gtk.py @@ -0,0 +1,27 @@ +from solaar.gtk import create_parser + + +def test_arg_parse(): + parser = create_parser() + res = parser.parse_args([]) + + assert res.debug == 0 + assert res.hidraw_path is None + assert res.restart_on_wake_up is False + assert res.window is None + assert res.battery_icons is None + assert res.tray_icon_size is None + + +def test_arg_parse_debug(): + parser = create_parser() + res = parser.parse_args(["--debug"]) + + assert res.debug == 1 + + +def test_arg_parse_version(): + parser = create_parser() + res = parser.parse_args(["version"]) + + assert res diff --git a/tests/solaar/ui/test_about_dialog.py b/tests/solaar/ui/test_about_dialog.py new file mode 100644 index 00000000..03da235d --- /dev/null +++ b/tests/solaar/ui/test_about_dialog.py @@ -0,0 +1,27 @@ +from solaar.ui.about import about +from solaar.ui.about.model import AboutModel + + +def test_about_model(): + expected_name = "Daniel Pavel" + model = AboutModel() + + authors = model.get_authors() + + assert expected_name in authors[0] + + +def test_about_dialog(mocker): + view_mock = mocker.Mock() + + about.show(view=view_mock) + + assert view_mock.init_ui.call_count == 1 + assert view_mock.update_version_info.call_count == 1 + assert view_mock.update_description.call_count == 1 + assert view_mock.update_authors.call_count == 1 + assert view_mock.update_credits.call_count == 1 + assert view_mock.update_copyright.call_count == 1 + assert view_mock.update_translators.call_count == 1 + assert view_mock.update_website.call_count == 1 + assert view_mock.show.call_count == 1 diff --git a/tests/solaar/ui/test_common.py b/tests/solaar/ui/test_common.py new file mode 100644 index 00000000..92c9e1c6 --- /dev/null +++ b/tests/solaar/ui/test_common.py @@ -0,0 +1,28 @@ +from unittest import mock +from unittest.mock import PropertyMock + +import pytest + +from solaar.ui import common + + +@pytest.mark.parametrize( + "reason, expected_in_title, expected_in_text", + [ + ( + common.ErrorReason.PERMISSIONS, + "Permissions error", + "not have permission to open", + ), + (common.ErrorReason.NO_DEVICE, "connect to device error", "error connecting"), + (common.ErrorReason.UNPAIR, "Unpairing failed", "receiver returned an error"), + ], +) +def test_create_error_text(reason, expected_in_title, expected_in_text): + obj = mock.Mock() + obj.name = PropertyMock(return_value="test") + + title, text = common._create_error_text(reason, obj) + + assert expected_in_title in title + assert expected_in_text in text diff --git a/tests/solaar/ui/test_desktop_notifications.py b/tests/solaar/ui/test_desktop_notifications.py new file mode 100644 index 00000000..c3c16f30 --- /dev/null +++ b/tests/solaar/ui/test_desktop_notifications.py @@ -0,0 +1,39 @@ +from unittest import mock + +from solaar.ui import desktop_notifications + +# depends on external environment, so make some tests dependent on availability + + +def test_init(): + result = desktop_notifications.init() + + assert result == desktop_notifications.available + + +def test_uninit(): + assert desktop_notifications.uninit() is None + + +def test_alert(): + reason = "unknown" + assert desktop_notifications.alert(reason) is None + + +class MockDevice(mock.Mock): + name = "MockDevice" + + def close(): + return True + + +def test_show(): + dev = MockDevice() + reason = "unknown" + available = desktop_notifications.init() + + result = desktop_notifications.show(dev, reason) + if available: + assert result is not None + else: + assert result is None diff --git a/tests/solaar/ui/test_i18n.py b/tests/solaar/ui/test_i18n.py new file mode 100644 index 00000000..3df6f013 --- /dev/null +++ b/tests/solaar/ui/test_i18n.py @@ -0,0 +1,28 @@ +import locale +import os +import platform + +import pytest + +from solaar import i18n + + +@pytest.fixture +def set_locale_de(): + backup_lang = os.environ.get("LC_ALL", "") + try: + yield + finally: + os.environ["LC_ALL"] = backup_lang + i18n.set_locale_to_system_default() + + +@pytest.mark.skipif(platform.system() == "Linux", reason="Adapt test for Linux") +def test_set_locale_to_system_default(set_locale_de): + os.environ["LC_ALL"] = "de_DE.UTF-8" + i18n.set_locale_to_system_default() + + language, encoding = locale.getlocale() + + assert language == "de_DE" + assert encoding == "UTF-8" diff --git a/tests/solaar/ui/test_pair_window.py b/tests/solaar/ui/test_pair_window.py new file mode 100644 index 00000000..f31f4801 --- /dev/null +++ b/tests/solaar/ui/test_pair_window.py @@ -0,0 +1,245 @@ +from dataclasses import dataclass +from dataclasses import field +from typing import Any +from typing import List +from typing import Optional + +import gi +import pytest + +from logitech_receiver import receiver +from solaar.ui import pair_window + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk # NOQA: E402 + +gtk_init = Gtk.init_check()[0] + + +@dataclass +class Device: + name: str = "test device" + kind: str = "test kind" + + +@dataclass +class Receiver: + name: str + receiver_kind: str + _set_lock: bool = True + pairing: receiver.Pairing = field(default_factory=receiver.Pairing) + pairable: bool = True + _remaining_pairings: Optional[int] = None + + def reset_pairing(self): + self.receiver = receiver.Pairing() + + def remaining_pairings(self, cache=True): + return self._remaining_pairings + + def set_lock(self, value=False, timeout=0): + self.pairing.lock_open = self._set_lock + return self._set_lock + + def discover(self, cancel=False, timeout=30): + self.pairing.discovering = self._set_lock + return self._set_lock + + def pair_device(self, pair=True, slot=0, address=b"\0\0\0\0\0\0", authentication=0x00, entropy=20, force=False): + print("PD", self.pairable) + return self.pairable + + +@dataclass +class Assistant: + drawable: bool = True + pages: List[Any] = field(default_factory=list) + + def is_drawable(self): + return self.drawable + + def next_page(self): + return True + + def set_page_complete(self, page, b): + return True + + def commit(self): + return True + + def append_page(self, page): + self.pages.append(page) + + def remove_page(self, page): + return True + + def set_page_type(self, page, type): + return True + + def destroy(self): + pass + + +@pytest.mark.skipif(not gtk_init, reason="requires Gtk") +@pytest.mark.parametrize( + "receiver, lock_open, discovering, page_type", + [ + (Receiver("unifying", "unifying", True), True, False, Gtk.AssistantPageType.PROGRESS), + (Receiver("unifying", "unifying", False), False, False, Gtk.AssistantPageType.SUMMARY), + (Receiver("nano", "nano", True, _remaining_pairings=5), True, False, Gtk.AssistantPageType.PROGRESS), + (Receiver("nano", "nano", False), False, False, Gtk.AssistantPageType.SUMMARY), + (Receiver("bolt", "bolt", True), False, True, Gtk.AssistantPageType.PROGRESS), + (Receiver("bolt", "bolt", False), False, False, Gtk.AssistantPageType.SUMMARY), + ], +) +def test_create(receiver, lock_open, discovering, page_type): + assistant = pair_window.create(receiver) + + assert assistant is not None + assert assistant.get_page_type(assistant.get_nth_page(0)) == page_type + + assert receiver.pairing.lock_open == lock_open + assert receiver.pairing.discovering == discovering + + +@pytest.mark.parametrize( + "receiver, expected_result, expected_error", + [ + (Receiver("unifying", "unifying", True), True, False), + (Receiver("unifying", "unifying", False), False, True), + (Receiver("bolt", "bolt", True), True, False), + (Receiver("bolt", "bolt", False), False, True), + ], +) +def test_prepare(receiver, expected_result, expected_error): + result = pair_window.prepare(receiver) + + assert result == expected_result + assert bool(receiver.pairing.error) == expected_error + + +@pytest.mark.parametrize("assistant, expected_result", [(Assistant(True), True), (Assistant(False), False)]) +def test_check_lock_state_drawable(assistant, expected_result): + r = Receiver("succeed", "unifying", True, receiver.Pairing(lock_open=True)) + + result = pair_window.check_lock_state(assistant, r, 2) + + assert result == expected_result + + +@pytest.mark.skipif(not gtk_init, reason="requires Gtk") +@pytest.mark.parametrize( + "receiver, count, expected_result", + [ + (Receiver("fail", "unifying", False, receiver.Pairing(lock_open=False)), 2, False), + (Receiver("succeed", "unifying", True, receiver.Pairing(lock_open=True)), 1, True), + (Receiver("error", "unifying", True, receiver.Pairing(error="error")), 0, False), + (Receiver("new device", "unifying", True, receiver.Pairing(new_device=Device())), 2, False), + (Receiver("closed", "unifying", True, receiver.Pairing()), 2, False), + (Receiver("closed", "unifying", True, receiver.Pairing()), 1, False), + (Receiver("closed", "unifying", True, receiver.Pairing()), 0, False), + (Receiver("fail bolt", "bolt", False), 1, False), + (Receiver("succeed bolt", "bolt", True, receiver.Pairing(lock_open=True)), 0, True), + (Receiver("error bolt", "bolt", True, receiver.Pairing(error="error")), 2, False), + (Receiver("new device", "bolt", True, receiver.Pairing(lock_open=True, new_device=Device())), 1, False), + (Receiver("discovering", "bolt", True, receiver.Pairing(lock_open=True)), 1, True), + (Receiver("closed", "bolt", True, receiver.Pairing()), 2, False), + (Receiver("closed", "bolt", True, receiver.Pairing()), 1, False), + (Receiver("closed", "bolt", True, receiver.Pairing()), 0, False), + ( + Receiver( + "pass1", + "bolt", + True, + receiver.Pairing(lock_open=True, device_passkey=50, device_authentication=0x01), + ), + 0, + True, + ), + ( + Receiver( + "pass2", + "bolt", + True, + receiver.Pairing(lock_open=True, device_passkey=50, device_authentication=0x02), + ), + 0, + True, + ), + ( + Receiver( + "adt", + "bolt", + True, + receiver.Pairing(discovering=True, device_address=2, device_name=5), + pairable=True, + ), + 2, + True, + ), + ( + Receiver( + "adf", + "bolt", + True, + receiver.Pairing(discovering=True, device_address=2, device_name=5), + pairable=False, + ), + 2, + False, + ), + (Receiver("add fail", "bolt", False, receiver.Pairing(device_address=2, device_passkey=5)), 2, False), + ], +) +def test_check_lock_state(receiver, count, expected_result): + assistant = Assistant(True) + + check_state = pair_window._check_lock_state(assistant, receiver, count) + + assert check_state == expected_result + + +@pytest.mark.parametrize( + "receiver, pair_device, set_lock, discover, error", + [ + ( + Receiver("unifying", "unifying", pairing=receiver.Pairing(lock_open=False, error="error")), + 0, + 0, + 0, + None, + ), + ( + Receiver("unifying", "unifying", pairing=receiver.Pairing(lock_open=True, error="error")), + 0, + 1, + 0, + "error", + ), + (Receiver("bolt", "bolt", pairing=receiver.Pairing(lock_open=False, error="error")), 0, 0, 0, None), + (Receiver("bolt", "bolt", pairing=receiver.Pairing(lock_open=True, error="error")), 1, 0, 0, "error"), + (Receiver("bolt", "bolt", pairing=receiver.Pairing(discovering=True, error="error")), 0, 0, 1, "error"), + ], +) +def test_finish(receiver, pair_device, set_lock, discover, error, mocker): + spy_pair_device = mocker.spy(receiver, "pair_device") + spy_set_lock = mocker.spy(receiver, "set_lock") + spy_discover = mocker.spy(receiver, "discover") + assistant = Assistant(True) + + pair_window._finish(assistant, receiver) + + assert spy_pair_device.call_count == pair_device + assert spy_set_lock.call_count == set_lock + assert spy_discover.call_count == discover + assert receiver.pairing.error == error + + +@pytest.mark.skipif(not gtk_init, reason="requires Gtk") +@pytest.mark.parametrize("error", ["timeout", "device not supported", "too many devices"]) +def test_create_failure_page(error, mocker): + spy_create = mocker.spy(pair_window, "_create_page") + + pair_window._pairing_failed(Assistant(True), Receiver("nano", "nano"), error) + + assert spy_create.call_count == 1 diff --git a/tests/solaar/ui/test_probe.py b/tests/solaar/ui/test_probe.py new file mode 100644 index 00000000..8d58dc08 --- /dev/null +++ b/tests/solaar/ui/test_probe.py @@ -0,0 +1,53 @@ +from unittest import mock + +from logitech_receiver.hidpp10_constants import ErrorCode +from logitech_receiver.hidpp10_constants import Registers +from solaar.cli.probe import run + + +# Mock receiver class +class MockReceiver: + handle = 1 + isDevice = False + + def read_register(self, register, *args): + return 0 if register == Registers.RECEIVER_INFO else b"\x01\x03" + + +def test_run_register_errors(): + mock_args = mock.Mock() + mock_args.receiver = False + + mock_receiver = MockReceiver() + + # Define expected addresses to be called in order + expected_addresses = [] + + for reg in range(0, 0xFF): + expected_addresses.append((0x8100 | reg, 0)) # First short call, returns invalid_value (continue) + expected_addresses.append((0x8100 | reg, 1)) # Second short call, returns invalid_address (stop here) + + expected_addresses.append((0x8100 | (0x200 + reg), 0)) # First long call, returns invalid_value (continue) + expected_addresses.append((0x8100 | (0x200 + reg), 1)) # Second long call, returns invalid_address (stop here) + + # To record the actual addresses called + called_addresses = [] + + def mock_base_request(handle, devnumber, reg, sub, return_error=False): + called_addresses.append((reg, sub)) + if sub == 0: + return ErrorCode.INVALID_VALUE + elif sub == 1: + return ErrorCode.INVALID_ADDRESS + return b"\x01\x02" + + with mock.patch("logitech_receiver.base.request", side_effect=mock_base_request), mock.patch( + "solaar.cli.probe._print_receiver", return_value=None + ): + # Call the run function with mocked receivers and args (passing real find_receiver function) + run([mock_receiver], mock_args, None, None) + + # Evaluate that the addresses called match the expected addresses + assert ( + called_addresses == expected_addresses + ), f"Called addresses {called_addresses} do not match expected {expected_addresses}" diff --git a/tests/test_keysyms/__init__.py b/tests/test_keysyms/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_keysyms/test_keysymdef.py b/tests/test_keysyms/test_keysymdef.py new file mode 100644 index 00000000..0002d10a --- /dev/null +++ b/tests/test_keysyms/test_keysymdef.py @@ -0,0 +1,9 @@ +from keysyms import keysymdef + + +def test_keysymdef(): + key_mapping = keysymdef.key_symbols + + assert key_mapping["0"] == 48 + assert key_mapping["A"] == 65 + assert key_mapping["a"] == 97 diff --git a/tools/clean.sh b/tools/clean.sh index 4a1a0732..e3fea934 100755 --- a/tools/clean.sh +++ b/tools/clean.sh @@ -1,9 +1,9 @@ -#!/bin/sh +#!/usr/bin/env sh cd "$(dirname "$0")/.." find . -type f -name '*.py[co]' -delete find . -type d -name '__pycache__' -delete -/bin/rm --force po/*~ -/bin/rm --force --recursive share/locale/ +rm --force po/*~ +rm --force --recursive share/locale/ diff --git a/tools/create-macos-app.sh b/tools/create-macos-app.sh new file mode 100755 index 00000000..6109768c --- /dev/null +++ b/tools/create-macos-app.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +# Helper to build a minimal macOS .app wrapper for Solaar. +set -euo pipefail + +APP_ROOT=${1:-/Applications/Solaar.app} +SOLAAR_PATH=${SOLAAR_PATH:-solaar} +SOLAAR_RESOLVED_PATH=$(command -v "${SOLAAR_PATH}" 2>/dev/null || echo "") +if [ -z "${SOLAAR_RESOLVED_PATH}" ]; then + echo "Error: '${SOLAAR_PATH}' not found" >&2 + exit 1 +fi +ICON_SOURCE=${ICON_SOURCE:-share/solaar/icons/solaar.svg} + +case "${APP_ROOT}" in + ""|"/"|".") + echo "Error: Refusing to create app bundle at unsafe location: \"${APP_ROOT}\"" >&2 + exit 1 + ;; +esac + +echo "Creating Solaar app bundle at ${APP_ROOT}" +rm -rf "${APP_ROOT}" + +APP_CONTENTS="${APP_ROOT}/Contents" +MACOS_DIR="${APP_CONTENTS}/MacOS" +RESOURCES_DIR="${APP_CONTENTS}/Resources" + +mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}" + +WRAPPER="${MACOS_DIR}/solaar-wrapper" +cat > "${WRAPPER}" <