pax_global_header00006660000000000000000000000064152202236760014517gustar00rootroot0000000000000052 comment=b2afd88129533997eb3112fe9b8e75e6d9e2b1b4 Solaar-1.1.20/000077500000000000000000000000001522022367600130215ustar00rootroot00000000000000Solaar-1.1.20/.coveragerc000066400000000000000000000004271522022367600151450ustar00rootroot00000000000000[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 Solaar-1.1.20/.git-blame-ignore-revs000066400000000000000000000004661522022367600171270ustar00rootroot00000000000000# yapf bulk change 72a8d311bce64b1536c08e754d22bb91efb66460 # isort bulk change e6369e0c3c240715a0a2daede6c3b225ed63cf60 # pre-commit bulk change 33521558ed7007c24b24127f1448c2c23ecbfb35 # flake8 bulk change 627185079f65c9cc471194b7927c833682e4a7a3 # yapf style update 27c90fa736d8a7a1ef6acda4345d3599862e185c Solaar-1.1.20/.github/000077500000000000000000000000001522022367600143615ustar00rootroot00000000000000Solaar-1.1.20/.github/ISSUE_TEMPLATE/000077500000000000000000000000001522022367600165445ustar00rootroot00000000000000Solaar-1.1.20/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000040411522022367600212350ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Information** - Solaar version (`solaar --version` or `git describe --tags` if cloned from this repository): - Distribution: - Kernel version (ex. `uname -srmo`): - Output of `solaar show`:
``` SOLAAR SHOW OUTPUT HERE ```
- Contents of `~/.config/solaar/config.yaml` (or `~/.config/solaar/config.json` if `~/.config/solaar/config.yaml` not present):
``` CONTENTS HERE ```
- Errors or warrnings from Solaar: **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Screenshots** If applicable, add screenshots to help explain your problem. **Additional context** Add any other context about the problem here. Solaar-1.1.20/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000017451522022367600223000ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **Information** - Solaar version (`solaar --version` and `git describe --tags`): - Distribution: - Kernel version (ex. `uname -srmo`): - Output of `solaar show` for the target device (if applicable):
``` OUTPUT HERE ```
**Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. Solaar-1.1.20/.github/workflows/000077500000000000000000000000001522022367600164165ustar00rootroot00000000000000Solaar-1.1.20/.github/workflows/checks.yml000066400000000000000000000004271522022367600204040ustar00rootroot00000000000000name: checks on: [push, pull_request] jobs: pre-commit: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 - name: Run pre-commit uses: pre-commit/action@v3.0.0 Solaar-1.1.20/.github/workflows/gh-pages.yml000066400000000000000000000017561522022367600206450ustar00rootroot00000000000000name: 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 Solaar-1.1.20/.github/workflows/tests.yml000066400000000000000000000043751522022367600203140ustar00rootroot00000000000000name: 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 }} Solaar-1.1.20/.gitignore000066400000000000000000000004251522022367600150120ustar00rootroot00000000000000*.pyc *.pyo __pycache__/ *.log *.mo /lib/Solaar.egg-info/ /lib/solaar.egg-info/ /lib/solaar/commit /build/ /sdist/ /dist/ /deb_dist/ /MANIFEST .coverage /htmlcov/ /docs/captures/ /share/logitech_icons/ /share/locale/ /po/*.po~ /.idea/ .DS_Store ._* Pipfile Pipfile.lock Solaar-1.1.20/.pre-commit-config.yaml000066400000000000000000000007311522022367600173030ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.3.0 hooks: - id: check-ast - id: check-builtin-literals - id: check-merge-conflict - id: check-yaml - id: check-toml - id: debug-statements - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.2.2 hooks: - id: ruff name: ruff lint args: [--fix, --exit-non-zero-on-fix] - id: ruff-format name: ruff format Solaar-1.1.20/.python-version000066400000000000000000000000051522022367600160210ustar00rootroot000000000000003.11 Solaar-1.1.20/CHANGELOG.md000066400000000000000000001361271522022367600146440ustar00rootroot00000000000000# 1.1.20 * Add Irish translation * Don't use old settings when reusing sboxes * Add POUND and ISO_BACKSLASH cells to MAIN_ISO key layout * Wrap argv in list for Gio.Application.run (PyGObject 3.56) * Mock libnotify to not perform notifications when doing tests * Isolate testing from real configuration file * Update handling of headset RGB controls * Update equalizer processing * Hide read-only paramaters from the UI * Better support for G522 Lightspeed headset * Use mostly full names for direct-USB codenames * Use correct icon for CENTURION devices * rgb_control: honor the off state — don't auto-claim, init, or shutdown LEDs * base: fix sw_id at 0x0B instead of rotating 0x2..0xF (#3218) * perkey/canvas: allow rect/gradient anchors in grid gaps * config_panel: don't show failed-write alert for unreadable settings * listener: share bluez-watch wiring across Centurion-direct and standard device paths * rgb_power.perkey_has_paint: gate on IGNORE only, not on != True * about: add Ken Sanislo to Additional Programming credits * Add RGB lighting persistence and software LED power management for G515 * ui: Show offline status for receiver-paired device batteries (#3217) * LEDControl / RGBControl: render as Gtk.Switch instead of a 2-option combo (#3215) * device: Fix operator precedence bug and end-of-configuration timing in device.changed() (#3173) * PerKeyLighting: drop misleading live-read output in solaar show * perkey: label G502 X LEDs by zone id, not letter * PerKey gradient swatch: align gradient endpoints to visible corners * PerKey gradient swatch: Tabler "square" outline around the gradient * PerKey dialog: one window per device, keyed by firmware unit-id * PerKey dialog: size window from measured natural size * PerKey icons: read theme fg from style-updated, not Settings notify * PerKey canvas: symmetric hash stripes for unset cells * PerKey palette: replace hashed unset swatch with palette-off icon * PerKeyEditor: rebuild tool icons on GTK theme change * PerKeyEditor: replace tool button labels with icons * common: render RGB color values as 0xrrggbb in config and solaar show * Better display of LED effects for some devices in solaar show. * Fix bug affecting using solaar config to change range-based settings. * Add regional keyboard layouts * Use per-key RGB color painter * Fix bug in notification flag handling * Fix bug in HID parser * Update Swedish, German, Polish, Chinese, and French translations * Use battery-level-N icons when available * Document haptic capabilities * Support per-slot unpair on Lightspeed receivers * Fix bugs related to integer flags in older versions of Python * Add mention of Centurion protocol support * Treat empty hidraw read as device removal (EOF) (#3174) * fix interface for K845 * support PRO X 2 LIGHTSPEED headphones Centurion features (#3150) * Fix crash in NotificationFlag.flag_names when flags is None (#3185) * Add PRO X 2 Superstrike mouse support with HITS tuning settings (#3132) * Add names for some HID++ 2.0 features and sort by ID (#3153) * Don't use Logitech for codename * Put lock around getting device name * Fix bug when showing device notification flags * Be defensive about no device features * Add feature x1b04 flag sent by M510 4004 * Remove incorrect descriptor for WPID 4004 * Better handling of missing devices * Improve RHEL installation guide and add automated install example (#3162) * Remove use of XTest and use uinput in all cases * Add installation guide for Solaar on RHEL 10 (#3158) * Use console_scripts entry point for pipx compatibility * Skip Logitech webcams to prevent them from locking up during HID++ checks on Macs * Downgrade ping no such device to informational log entry * Recover from guessing the wrong number for direct-connected HID++ 1.0 devices * Tolerate devices with no unitId * Correctly handle timeout in Bolt discovery * Update or add fr, pt_BR, sk, Ukrainian, Finnish, Bulgarian translations * Handle missing receiver_path more gracefully * Handle inaccessiable devices when determining protocol * Be defensive when showing features in solaar show # 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 * Permit bluetooth devices in hidconsole * Add descriptor for Logitech MX Revolution Mouse M-RCL 124 * Improve determination for short and long messages * Add descriptor for G500s * Fix bug in scan-registers * Add single depress and release options for rule mouse click action * Add rule condition for hostname * Update keysym generation to current list of keysyms * Allow device 0 in hidconsole * Upgrade messages when no supported device found * Documentation update for the gnome extension for better Solaar rule support * Remove udev-acl tag from udev rules * Add support for process condition in Wayland * Update French, Chinese, and German translations * Add G733 Headset * Restore tools/clean.sh * Add Bluetooth Keyboard C714 * Update several device descriptions * Update scan-registers.sh * Remove assertion on last byte of ping responses * Add symbolic version of solaar icon * Fix bug when finding name or codename * Update documentation * Put version in initial INFO logging message # 1.1.9 * Add descriptors for G535 wireless gaming headset and wireless keyboard EX110 * Update Greek translation * Fix minor issues in documentation * Remove some deprecated GTK code * Use zero exit code for kill interrupts * Add rule Test condition for battery charging * Get wpid for 28Mz devices from udev when enumerating * Add Device condition to rules * Don't show wireless link or battery information when unknown or not present * Add desccriptor for G9x and LX7 mice * Fix bug in determining kind of devices for 27Mz receivers * Set initial lock status of smooth scrolling features to ignore * Fix glitch in configuration file update when changing versions * Add more debugging output for rules * Clean up pinging code * Put initial ping of direct-connected devices inside listener thread * Read and check before write of range settings * Improve pairing determination * Cut off determination of receiver devices when all have been found * Remove derived configuration fields when Solaar version changes * Allow device descriptors without name and codename * Filter and escape technical detail fields * Add setting for ADC power managemen * Correctly determine whether to ping with a long HID++ message * Add description for K470 keyboard from the MK470 combo (#1945) * Add setting value for mouse gestures * Update German and French translations * Remove old clean.sh and monitor.py tools * Retry opening device if permissions error encountered * Better handlling of IO errors at device creation * Add KeyIsDown rule condition to check whether a diverted key is down * Clean up device and receiver creation # 1.1.8 * Add parameter to thumb wheel rule conditions * Rename Serbian translation file * Update Polish translation * Improve handling of wheel notifications * Only record changes of scroll wheel ratchet * Don't mark setting as absent if there are device errors * More robust access to keyword exception fields * Add support for config change feature * Clean up GTK code * Update documentation on mouse gestures and known issues * Get commit information from git describe and dpkg-parsechangelog * Update pre-commit tools to current versions * Update installation instructions * Add 8-character commit hash to version information * Update solaar.desktop (#1857) * Get release in setup.py by reading lib/solaar/__init__.py * Remove version number from docs/_config.yml * Update solaar.desktop * Update es.po * Clarify status of C542 receiver * Update devices directory * Add description of several devices * Show name of disconnecting receiver or device in disconnect warning * Reduce standard size of rule editor window * Remove unnecessary dependency on typing_extensions # 1.1.7 * Add dependency on typing_extension to setup.py * Don't defer saves in CLI and don't require Gtk in CLI * Be more permissive in recognizing HID++ report descriptors * Update Polish and Croatian translations * Switch scroll ratcheting in response to scroll ratchet button notification * Add setting to turn scroll ratchet on and off * Eliminate visual glitch when updating range setting * Make hid-parser an optional dependency # 1.1.6 * Only update remaining pairings after successful pairing * Check for presence of status attribute when resuming * Update Polish and Croation translations * Don't add non-existant key in raw xy processing * Add special keys from MX Mechanical Mini * Fix processing of HID++ 1.0 battery reports * Use report descriptors to determine suitable devices * Handle exceptions when processing configuration file * Add Logitech PRO Gaming Keyboard * Fix bad entries in divert-keys when found * Correctly convert old-style diversions to new style and remove old ones * Add optional save argument to write_key_value methods * Use device name in configuration entries if device modelId is zeroes * Don't show normal DJ messages in debug log * Add Later rule action * Correctly record battery feature when ADC produces error * Print feature call errors in solaar show instead of terminating * Use ADC notifications to set device inactive and active * Add one to feature count to count ROOT feature * Don't check modifiers for KeyPress actions that are not clicks * Augment comments on what Solaar cannot do * Fix bug in printing closed handle * Use only product records to determine which receivers unpair * Add conditional delay to get around race with Linux HID++ driver # 1.1.5 * Add G213 Prodigy Gaming Keyboard * Update Turkish, Croation, and zh_CN translations * Move old device descriptions to new locations and clean up * Increase minimum stopped time between mouse movements * Fix bug with non-recognized empty mouse movement * Allow config panel to scroll * Add descriptions for K360 4004, MX Master 4017, Lightspeed C541, and G915 407D * Change security tooltip for unencrypted wireless connections * Add Norwegian Bokmal translation * Update documentation on continuously running GUI, diverted keys, and changing hosts * Solaar config no longer applies settings * Add description of rule components to rule editor * Ignore first movement for mouse gestures when reprog controls version is 5 or more * Add Active rule condition to test whether a device is active * Terminate iteration over receiver devices when all have been found * Downgrade Set prolem messages from error to warning * Log informational messages and above to a temporary file * Don't produce warnings when editing rules in rule editor * Fix bug that didn't save rules file if there were no rules * Allow devices with connection number larger than max_devices * Show more registers in solaar probe * Show feature versions in solaar show * Fix problems with deleting devices on exit * Update German translation * Update device documentation directory * Add descriptors for G502 Proteus Spectrum Optical Mouse and G935 headset * Record feature versions * Add Serbian translation * Fix circular import in rules * Keep track of devices and use in rules * Update zh_CN and zh_TW translations * Convert old-style mouse gestures and sliding dpi settings to new style * Allow multiple keys for mouse gestures and sliding dpi * Defer saving configuration file for a while to cut down on the number if times it is saved * Use correct functions for unified battery and battery voltage features. * Add defensive check for missing keys array in mouse gestures setting * Better presentation of battery information * Add support for ADC MEASUREMENT battery feature * Add information on how to find divertable key names in solaar show output * Better warnings for feature call errors with reprogrammable key and gesture settings * Increase entry size for ChoiceControlBig # 1.1.4 * Update Croatian translation * Handle unanticipated values for persistent remappable action as default * Improve system tray interface documentation * Remove blank lines from system tray popup * Update Polish translation * Add depress and release options to KeyPress rule action * Only close existing devices for receiver * Differentiate debug messages for different setting types * Implement packed range setting in config panel * Only produce warning for unimplemented display of setting * Add sidetone and equalizer settings for headsets * Add packed ranges setting * Use built-ins for conversions between int and byte string * Don't add HI_RES wheel ecodes to evdev device codes * Handle null key in persistent remappable actions when device does not respond * Consult polling rate feature for polling rate * Eliminate extra file handles * Close file handles for receiver-connected devices * Access to more keys in KeyPress rule action when in other keyboard groups * Update Turkish translation * Put battery levels in tray menu labels * Reduce warnings for inaccessible keys in KeyPress rule action * Better determination of keycodes and insert needed shifts in KeyPress actions. * Be careful when processing notifications for partly set up devices. * Clear out fields for empty persistent remappable action. * Fix message generation when device is offline. * Fix bug in feature lookup. * Add support for G533 Headset and G502 Gaming Mouse * Improve device features handling. * Keep trying settings that have worked in the past. * Store None in persisters for non-persisting settings. * Only create configuration entries for off-line devices if they have a serial number. # 1.1.3 * Update documentation files * Lower remove python 3.7 constructs and lower python dependency to 3.6 * Fix bug in xtest mouse scrolling * Allow mouse gesture setting for non-mice, e.g., trackballs * Print message when there is another Solaar process running * Fix determination of whether to force read before writing boolean setting * Add recent diversion features to default list in rules UI * Add setting to divert hires scroll wheel * Don't use device kind to determine how to handle notifications * Update Polish, Russian, and zh_CN translations * Remove pggettext so as to not require Python 3.8 * Remove Python 2 unicode support * Store keys in configuration file as int instead of str * Use yaml for configuration file instead of json. * Remove process-dependent rules from built-in rules. * Handle situation where GTK application cannot be set up in CLI. * Remove obsolete upower signals. * Appinfo/metadata fixes including using correct APP_ID. * Fix bugs in solaar config. * Add full set of mouse buttons in persistent remappable actions setting. * Improve rule behaviour under Wayland and when Xtest or X11 not available. * Fix up and document dependencies (evdev, Python, ...). # 1.1.2 * Update documentation on supported devices and translations * Include evdev as a dependency * Try to use uinput for fake input if XTest extension not available * Add Nano receiver C542 for M190 mice * Broaden range of HID++ Bluetooth devices * Add setting to divert gestures * Rule editor can edit all rule components * Configuration via solaar config takes effect in Solaar UI if it is running * Add setting to disable Onboard Profiles and decouple from Polling Rate setting * Add setting for PERSISTENT_REMAPPABLE_ACTION, common keyboard and mouse cases only * Split Test rule condition into Test and TestBytes and support in rule editor * Fix bug in speed-change setting * Support Backlight3 feature * Ensure that settings are pushed in resume * Update German, Polish, and zh_CN translations * Determine device number for direct-connected devices from protocol * fix bug in add and delete button actions in rule editor * dispose of no-op notifications quickly * add rule condition for checking device settings * use local file for conversion from key names to keysyms * get keyboard group and use to get correct keycodes (X11 only) * improve how rules work under Wayland * add settings for M-Key LEDs and MR-Key LED * fix bug in unpacking M and MR key notification * add G815 keyboard and MX518 mouse * add new special keys for recent keyboards * track M and MR keys for use in rules * make sure that device is online when searching for devices in solaar show * don't check for device kind in dpi sliding setting * fix problem with devices that report 0 DPI * handle missing divert-setting in action RW for settings * add id property (unitId or serial) and don't use ? for unknown serial * fix contains for NamedInts and eliminate use of has_element * check for xtest and disable modifier checking if not available * improve determination of gesture information * add Set rules to rule GUI * add gesture params to Set rules * hide system tray when there are no devices to control * add G733 headset, G9 mouse, G502 Hero mouse * Use greyscale solaar icon in tray when using symbolic icons * Fix bugs in solaar config * Use classes for settings to hep with modularity * Accept '~' and Toggle for toggling boolean settings in cli and rules * handle errors when writing to devices * refactor config_panel.py to use classes for widgets * add rules action to set Solaar settings * decrease amount of logging at each debug level * don't stretch toggles in settings * use key structure for key remapping setting * optimize ReprogrammableKey implementation * keep track of settings that are absent from device * add G512 keyboard and G402 mouse * reformat descriptors.py * use feature numbers for reprogrammable key versions * don't use new_from_icon_set in menu as it is deprecated # 1.1.1 * Keep left pane in Solaar main window the same size * Fix crash when checking a process condition when X11 not running * Add version number to output of solaar show * Fix crash when pinging a device with unknown protocol * Display battery percentage estimates from battery voltage * Add minimal support for Logitech PRO X Wireless Gaming Headset * Push settings when device requests software reconfiguration * Fix read for key/button diversion setting * Add modalias information to Solaar metainfo * Don't do on-screen notifications when devices are powered on * Add setting to switch crown between smooth and ratchet scrolling * Add write_prefix_bytes argument to Boolean validator * Update Russian and Spanish translations * New shell script tools to help determine capabilities of receivers * Add special keys for MX Keys for Business and MX Keys Mini * Improve tray menu ordering * Add --tray-icon-size option to get around bugs in some tray implementations # 1.1.0 * Fix bug when adding receiver to tray menu * Add Catalan translation. * Add toggle command to solaar config to toggle boolean settings * Don't select windows with no PID when looking for focus window * Catch errors when applying settings so that other settings are not affected * Add support for Bolt receivers and devices, including pairing * Revise method for creating system tray menu * Remove obsolete code (mostly Python 2 compatibility code) * Add support for PRO X Wireless Mouse, G914 TKL keyboard * Ignore more notifications that come to a device listener * Handle more device connection protocols * Update usage and rules documentation * Change emojis to text in documentation * Pare down device documentation so as not to require frequent updates * Add information about M500S mouse * Reimplement MOUSE GESTURE and DPI SLIDING settings * Add setting for DPI CHANGE button to switch sensitivity * Use file name instead of icon name for tray to avoid XFCE bug * Update documentation on implemented features and mouse gestures * Update Polish, Japanese, and Spanish translations * Make Quit and About strings more translatable # 1.0.7 * Don't use time_ns so as not to require Python 3.7 * Correctly determine setting box in change_click method * Handle fake Nano connection notifications * Lock on actual handle, not just on handle number * Mark Nano receiver C52F as not unpairing * Upgrade pairing/unpairing documentation * Don't signal status change when battery changes from None to None. * Add Japanese translation * Use first word of name for code name if no other code name available. * Better determination of when to add SW ID. * Check for more HID++ feature request failing. * Fix bug with new_fn_inversion setting. * Use correct device number for directly connected devices * Add debug message when candidate device found * Update Polish, Taiwanese, and Brazilian Portuguese translations * Add MouseProcess a rule condition like Process but for the window under the mouse * Add parameters for binary settings to support prefixes * Add locks to serialize requests to devices * Fix bug when reprog key requests returns None * Fix bug for empty process name and class * Rules can now trigger on both pressing and releasing a diverted key * Upgrade mouse gestures to allow sequences of movements * Fix gkeys diversion faked read * Add support for Logitech g pro x superlight receiver * Convert HID++ 2.0 device kinds to enhanced HID++ 1.0 device kinds * Update about window, bug report templates, and supported kernels. # 1.0.6 * Update sliding DPI to look for suitable keys * Add mouse gestures that can trigger rules * Complain if receivers do not support connection notification * In polling rate setting, only modify onboard profiles when actually writing polling rate * Add ability to ignore settings. * Use symbols for receiver sub-registers * Add support for wired G700 * Do not set attention icon * Replace deprecated GTK stock menu icons * Better handling of icons in tray and tray menus * Receiver c52e does not unpair * Match active WM_CLASS as well as active process name in rules * Correctly set icon theme value when regular battery icons are not available * Handle exception when device is not available when device is being added * Perform initial activation of devices in listener threads * Keep track of serial numbers in the configuration file * Don't update settings for non-active devices * Set the current host name if not stored on the device * Add setting for SMART SHIFT ENHANCED feature * Don't unnecessarily use long messages for HID++ 1.0 commands * Correctly select choices in solaar config and use 1-origin addressing * Add quirk for G915 TKL keyboard because its host mode interferes with its Fn keys * Show command outputs both saved and on-device settings * Update documentation * Fix bug in hidconsole * Update French translation # 1.0.5 * Update documentation on devices forgetting settings. * Improve help messages * Fix bug in finding receiver to pair * Solaar config command can set keyed settings. * Add setting for polling rate * Use long HID++ messages for all 2.0 requests * Update German, Italian, and Polish translations * Solaar config command no longer selects paired but unconnected devices * Show HID++ 1.0 remaining pairings value in solaar show for devices that support it * Add option to not use battery icons in system tray. * Update Polish and Dutch translation. * Add Czech translation. * Remove information on SUSE package as it is very old. * Turn GKEY notifications into Gn key keypresses that can trigger rules. * Push device settings to devices after suspend when device is immediately active. * Reduce unnecessary saving of configuration file. * Better handling of disconnected devices. * Implement GUI to edit rules. * Implement rule-base processing of HID++ feature notifications (depends on X11). * Add settings for diversion of crown and remappable keys. * Access widgets by name instead of by index. * Implement UNIFIED_BATTERY feature and use in battery reports. * Add a clickable lock icon that determines where each setting can be changed. # 1.0.4 * Update pt_BR translation * Support USB and BT connected devices that are not in descriptors.py * Use FRIENDLY NAME for codename if needed and available. * Extract manufacturer and product ID from Udev HID information. * Add Bluetooth and USB product IDs to device descriptors records. * Support Bluetooth-connected devices. * Add model ID and unit ID to device identification. * Support changing DPI by pressing DPI Switch button and sliding horizontally * Add device-specific notification handlers. * Add MX Vertical USB information. * Udev rule adds seat permissions for all Logitech devices. * Support USB-connected devices in GUI. * Make probe and config work for USB-connected devices. * Improve strings and display for settings. * Correctly handle non-unifying connection notifications. * Update GUI strings for several settings. * Better support for EX100 and devices that connect to it. * Partial support for feature GESTURE_2. * Simplify interface for settings. * Use DJ connection notifications to set device active status * Udev rule sets seat write permissions for hidraw nodes for device as well as receivers. * Handle USB devices that use HID++ protocol in CLI. * Use device hidraw nodes where possible. * Handle receivers with serial numbers that don't provide number of pairings. * Ignore exceptions when setting locale. * Correctly discover settings that share a name. * Don't show pop-up notifications at startup. * Keep battery voltage updated in GUI. * Add Portuguese translation. * Update several translations. * Add Lightspeed receivers c545 and c541. * Reimplement REPROG_CONTROLS data structure. # 1.0.3 * Clean up documentation files. * Update documentation on installation. * Update Swedish and French translations. * Add Norwegian Nynorsk and Danish translations. * Fix bug handling DJ pairing notifications. * Add Norwegian Bokmål translation. * Remove deprecated solaar-cli application. * Don't install udev or autostart files from python (or pip). * Solaar needs Python 3.6+ and probably needs kernel 5.2+ * Handle exceptions on dynamic settings when device is not connected. * Fix infinite loop on some low-level write errors * Add support for EX100 keyboard/mouse and receiver (046d:c517) * Add two settings for THUMB_WHEEL feature - inversion and reporting via HID++ * Update German translation * Use REPORT RATE feature when available to determine polling rate. * Improve config command speed when not printing all settings * Improve config command handling and checking of arguments * Add setting for CHANGE_HOST feature * Add argument to settings for values that are not to persist * Add argument to settings to not wait for reply when writing a value to device * Add argument to not wait for reply from request to device * Add settings for MULTIPLATFORM and DUALPLATFORM features * Remove Logitech documents from documentation directory * Change config command to not read all settings when only printing or showing one * Display hosts info in 'solaar show' if device supports it * Remove non-working smooth-scroll from M510 v1 * Add yapf and flake8 code style checks * Fix feature k375s Fn inversion * Update controls (keys and buttons) and tasks (actions) * Improved way to specify feature settings. * Don't abort on device notifications with unexpected device numbers, just warn. * Keep track of non-features so as not to ask device multiple times. * Implement KEYBOARD DISABLE KEYS feature. * Don't create notifications for DJ device activity reports. * Update a few special keys and actions. * Add keyed choice settings in configuration panel. * Support remappable keys from reprogrammable keys v4 feature. * Add setting class for keyed choice. * Only check for features once per device. * Use settings interface to show feature values in `solaar show` if no special code for feature. * Remove maximum window size. * Process battery voltage notifications. * Display battery voltage information in main window if regular battery information not available. * Show next battery level where available. * Update list of implemented features and provide information on how to implement features. * Add c53d as a Lightspeed receiver. # 1.0.2 * Add usage document * Don't produce error dialog for inaccessible receivers with access control lists. * Add option --battery-icons=symbolic to use symbolic icons if available. * Update French translation * Update installation documentation * Remove packaging directory tree as it is not maintained * Pip installs udev rule and solaar autostart when doing install without --user flag * Use Solaar icon instead of a missing battery icon * Use only standard icons for battery levels. Symbolic icons do not change to white in dark themes because of problems external to Solaar. * Better reporting of battery levels when charging for some devices. * Add information on K600 TV, M350 WIPD 4080, and MX Keys * Remove assertion requiring receivers to still be in window when they are updated. * Augment long description of Solaar showing up in repositories. * Update installation directions. * Install udev rule as well as autostart file when doing system install. * Add support for Ayatana AppIndicator. * Use setuptools icon directory on system installs when not using pip. * Add receiver C517 and several older devices. * Improved translations for polish. * Bypass bug in appindicator when solaar is file in current directory. * Don't check that device kind matches feature kind. * Better determination of icons for battery levels. * Use Ayatana AppIndicator if available. * Improve error reporting when required system packages are not install. * Better tooltip description * Add release script to help when creating releases * Look up tray icon filenames to get around a bug in libappindicator. * Make the default behavior be to show the main window at startup. * Support c537 nano receiver * Add logind signals for suspend/resume. * Remove solaar-gnome3 package * Ignore features for devices that don't follow feature specification * Add probe command to command-line interface to dump receiver registers * Don't terminate on malformed or unknown messages * Create fewer internal notifications for messages from devices * Add a button to the main window to terminate (quit) Solaar * Set up nano receivers as receivers with no unpairing and with re-pairing * Set up c534 as receiver with max 2 pairings, no unpairing, re-pairing * Better support receivers that do not unpair or when pairing replace existing pairings * Add information about receiver pairing to receiver data structure * Better support devices that only allow a limited number of total re-pairings * Add --window option to control main window visibility and tray usage * Ignore receiver if USB id is not retrieved * Fix bug with double deleting when devices are disconnected * Determine some receiver information from data structure for USB ids * Treat battery level of 0 as unknown * Fix bug on devices with no serial number * Drop support for python2, and use python3 throughout * Fix bug in remembering features discovered from device reports * Show icons in main window device list * Count offline devices when determining whether pairing is possible * Update French, Dutch, German, and Croatian translations * Better icons for battery levels * Support DPI, Backlight 2, Battery Voltage features * Support M585, M590, M330, MX Master 2s and 3, new M310, new K800, craft keyboard * Documentation improvements * Clean up directory structure and remove unused files # 1.0.1 * Updated the repo url. * Fixed typo which was crashing the application. * Improved the HID write routine which was causing issues on some devices. * Fix non-unifying receivers in Linux 5.2. * Add new Lightspeed receiver (used in the G305) # 1.0.0 * Too many to track... # 0.9.3 * Merged solaar-cli functionality into main solaar. * Scrolling over the systray icon switches between multiple peripherals. * Swedish translation courtesy of Daniel Zippert and Emelie Snecker * French translation courtesy of Papoteur, David Geiger and Damien Lallement. * Fixed some untranslated strings. # 0.9.2 * Added support for hand detection on the K800. * Added support for V550 and V450 Nano. * Fixed side-scrolling with the M705 Marathon. * Fixed identification of the T650 Touchpad. * Added internationalization support and romanian translation. * Polish translation courtesy of Adrian Piotrowicz. # 0.9.1 * When devices report a battery alert, only show the alert once. * Make sure devices in the window tree are sorted by registration index. * Added an autostart .desktop file. * Replaced single-instance code with GtkApplication. * Fixed identification of the M505 mouse. * Fixed an occasional windowing layout bug with the C52F Nano Receiver. # 0.9.0 * New single-window UI. * Performance MX leds show the current battery charge. * Support the VX Nano mouse. * Faster and more accurate detection of devices. * If upower is accessible through DBus, handle suspend/resume. * Replaced Solaar icons with SVGs. * Running solaar-cli in parallel with solaar is now less likely to cause issues. * Bugfixes to saving and applying device settings. * Properly handle ^C when running in console. # 0.8.9 * Improved support for gnome-shell/Unity. * Persist devices settings between runs. * Fixed reading of MK700 keyboard battery status. * Use battery icons from the current theme instead of custom ones. * Debian/Ubuntu packages now depend on an icon theme, to make sure no missing icons appear in the application window. * Fixed missing icons under Kubuntu. * Many more bug-fixes and reliability improvements. # 0.8.8 * Partial support for some Nano receivers. * Improved support for some devices: M510, K800, Performance MX. * Improved battery support for some HID++ 1.0 devices. * Properly handle device loss on computer sleep/wake. * Better handling of receiver adding and removal at runtime. * Removed a few more unhelpful notifications. * Incipient support for multiple connected receivers. * More Python 3 fixes. # 0.8.7 * Don't show the "device disconnected" notification, it can be annoying and not very useful. * More robust detection of systray icon visibility. # 0.8.6 * Ensure the Gtk application is single-instance. * Fix identifying available dpi values. * Fixed locating application icons when installed in a custom prefix. * Fixed some icon names for the oxygen theme. * Python 3 fixes. Solaar-1.1.20/COPYRIGHT000066400000000000000000000012101522022367600143060ustar00rootroot00000000000000Copyright 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, version 2. 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 . Solaar-1.1.20/LICENSE.txt000066400000000000000000000432541522022367600146540ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 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. GNU GENERAL PUBLIC LICENSE 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. Copyright (C) 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. 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. , 1 April 1989 Ty Coon, 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. Solaar-1.1.20/MANIFEST.in000066400000000000000000000002271522022367600145600ustar00rootroot00000000000000include COPYRIGHT LICENSE.txt README.md CHANGELOG.md lib/solaar/version lib/solaar/commit recursive-include rules.d * recursive-include share/locale * Solaar-1.1.20/Makefile000066400000000000000000000040461522022367600144650ustar00rootroot00000000000000UDEV_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 Solaar-1.1.20/README.md000066400000000000000000000063231522022367600143040ustar00rootroot00000000000000# 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 Solaar-1.1.20/RELEASE.md000066400000000000000000000012311522022367600144200ustar00rootroot00000000000000# Solaar releases ## Please read before making a release We support two type of releases: normal releases (ex. `1.0.0`) and release 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 information to `share/solaar/io.github.pwr_solaar.solaar.metainfo.xml` - Create a commit that starts with `release VERSION` - Push commit to Solaar repository and merge it - Invoke `./release.sh` - Git tags are signed so you must have GPG set up - You are required to have a github token with `public_repo` access in `~/.github-token` Solaar-1.1.20/RHEL.md000066400000000000000000000052631522022367600141030ustar00rootroot00000000000000# 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' ``` Solaar-1.1.20/Release_Notes.md000066400000000000000000000265731522022367600161100ustar00rootroot00000000000000# Notes on Major Changes in Releases ## Version 1.1.20 * Solaar has much better support for the LEDs on some newer devices, such as the G515 Lightspeed TKL. * Solaar now supports the Centurion protocol, a variation of the HIDP++ protocol that is used by several headsets. * Solaar now uses uinput for all simulated input, even in X11. As a result there is no need for a separate udev rule for Wayland, and it may be removed in future. ## 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. * There is a new rule condition to check the hostname. ## Version 1.1.9 * Solaar now exits with at 0 exit code when killed. * Two Solaar settings can interfere with the implementation of smooth scrolling in modern Linux HID++ drivers. These settings are initially set to ignore so that this interference does not happen. * The Device rule condition checks for the device that produced a notification. * The KeyIsDown rule condition checks whether a *diverted* rule key is down. ## Version 1.1.8 * The thumb wheel rule conditions take an optional parameter that checks for total signed thumb wheel movement. ## Version 1.1.7 * Solaar responds to scroll wheel ratchet notifications by changing its scroll wheel ratcheting. * Solaar processing of report descriptors is optional, depending on whether the package hid-parser is available. ## Version 1.1.6 * Solaar requires Python version 3.7. * Solaar uses report descriptors to recognize unknown devices that use HID++. * The Later rule action takes an integer delay in seconds and one or more rule components. The action immediately completes while also scheduling the rule components for later exection. ## Version 1.1.5 * The Active rule condition takes the serial number or unitID of a device and checks whether the device is active. A device is active if it is connected (via a receiver, USB or Bluetooth), not turned off, and not in a power-saving state. This condition can be used to check whether changing a setting on the device will have any effect, as devices respond to messages only when active. * Solaar logs warnings and errors to a file in the user's temporary file directory. This file is deleted when Solaar exists normally. If Solaar is run with `-dd` or `-ddd` informational messages are also logged in the file. * If the first element of a Mouse Gesture rule condition is a key or button name then that name must be the same as the name of the key or button that initiated the mouse gesture. * The Sliding DPI and Mouse Gestures are now set up using the Key/Button Diversion setting. Changing a key or button to Sliding DPI makes it initiate the sliding DPI changing. Changing a key or button to Mouse Gestures makes it initiate a mouse gesture. There can be multiple keys or buttons for sliding DPI and multiple keys or buttons for mouse gestures. * Solaar waits a few seconds to save settings changes to its configuration file. If you interrupt Solaar soon after changing a setting the change might not be saved. ## Version 1.1.4 * There are settings for sidetone and equalizer gains for headsets. * The KeyPress action can now either deppress, release, or click (default) keys. * The KeyPress action now inserts shift and level 3 keypresses if simulating a key symbol requires one or both of them. So producing a "B" no longer requires adding a shift keysymbol. ## Version 1.1.3 * Solaar uses yaml for configuration files, converting the json configuration file to yaml if necessary. * Solaar rules work better under Wayland but still cannot access the current process nor the current keyboard modifiers. * Solaar uses uinput for simulating input in Wayland. See https://pwr-solaar.github.io/Solaar/rules for instructions on setting up uinput correctly. ## Version 1.1.2 * Solaar now depends on Python evdev. It can be installed if needed via `pip install --user evdev` or, in most distributions, by installing the python3-evdev package. * Solaar rules partly work under Wayland. There is no access to the current process in Wayland. Simulated input uses uinput if XTest extension not available, requiring read and write permissions on /dev/uinput. * There is a setting to divert gestures so that they can trigger rules. * There is a setting to disable Onboard Profiles, which can interfere with the Polling Rate and M-Key LEDs settings. The Polling Rate setting no longer disables onboard profiles. * There is a setting for the Persistent Remappable Keys feature. * There is a new rule condition that tests device settings. * There are new settings to set M-Key LEDs and MR-Key LED. * There is a new kind of Solaar rule action to change settings for devices. ## Version 1.1.1 * There is a new setting to switch keyboard crowns between smooth and ratchet scrolling. ## Version 1.1.0 * Solaar now supports Bolt receivers and devices that connect to them, including authentication of devices when pairing. * A setting has been added for the DPI CHANGE button to switch movement sensitivity. ## Version 1.0.7 * Solaar rules can now trigger on both pressing and releasing a diverted key. * The new rule condition MouseProcess is like the Process condition except for the process of the window under the mouse. * Mouse gestures have been upgraded. A mouse gesture is now a sequence of movements separated by no movement periods while the mouse gesture button is held down. The MouseGesture rule condition matches mouse gesture sequences. The old mouse-up, etc., tests are converted to MouseGesture conditions. ## Version 1.0.6 * The sliding DPI setting now looks for suitable keys to use to trigger its effects. * If a mouse has a suitable button it can generate mouse gestures, which trigger rule processing. Mouse gestures need to be turned on and the button diverted to produce mouse gestures. * Settings can now be ignored by clicking on the icon at the right-hand edge of a setting until the dialog error icon (usually a red icon) appears. Solaar will not try to restore the value for an ignored setting. * Icon handling in the tray and the tray menu has been updated to work better with some system tray implementations. * The process rule condition also matches against the current X11 WM_CLASS. * The SMART SHIFT ENHANCED feature is supported. ## Version 1.0.5 * Solaar has rules that can perform actions such as pressing keys or scrolling when certain HID++ feature notifications happen. Users can change these rules either by editing ~/.config/solaar/rules.yaml or via a GUI. Rules depend on X11 and so are only available under X11. This is an experimental feature for Solaar and may undergo changes in the future. * Each setting has a clickable lock icon that determines whether the setting can be changed. ## Version 1.0.4 * Devices that connect directly via Bluetooth or USB are now supported. These devices show up in the GUI as separate lines, not under a receiver. A device that is directly connected and also paired to a receiver will show up twice, but the entry under the receiver will not be active. With this change identifying devices becomes more difficult so occasionally check your Solaar configuration file (at ~/.config/solaar/config.json) to see that there is only one entry for each of your devices. * There are new settings for gestures, thumb wheels, adjusting the wheel ratchet behavior, and changing DPI using a DPI Switch button. * Solaar's Udev rule now adds seat permissions for all Logitech devices. Users who install Solaar themselves will have to install the new Udev rule and activate the rule. One way to do this is to restart the user's computer. ## Version 1.0.3 * The separate deprecated solaar-cli command has been removed. * Devices can be switched between hosts using the Change Host setting. The device will try to connect to the other host. Some devices will detect that there is no active host on the other connections and reconnect back. ## Version 1.0.2 * The separate unneeded solaar-gnome3 command has been removed. The packaging directories have been removed. * Non-unifying receivers are modelled better. Many of them cannot unpair but instead new pairings replace existing pairings. * Battery icon selection has been simplified. Solaar-1.1.20/bin/000077500000000000000000000000001522022367600135715ustar00rootroot00000000000000Solaar-1.1.20/bin/solaar000077500000000000000000000026161522022367600150050ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- python-mode -*- # -*- coding: UTF-8 -*- ## 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. def init_paths(): """Make the app work in the source tree.""" import os.path import sys 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 = os.path.join(location, "solaar", "__init__.py") if os.path.exists(init_py): sys.path[0] = location break if __name__ == "__main__": init_paths() import solaar.gtk solaar.gtk.main() Solaar-1.1.20/docs/000077500000000000000000000000001522022367600137515ustar00rootroot00000000000000Solaar-1.1.20/docs/.gitignore000066400000000000000000000001241522022367600157360ustar00rootroot00000000000000# ignore documentation-specific files .jekyll-metadata Gemfile Gemfile.lock _site/ Solaar-1.1.20/docs/20121210110342697.pdf000066400000000000000000005320701522022367600162640ustar00rootroot00000000000000%PDF-1.4 % 4 0 obj <> stream JFIF,,C 2!=,.$2I@LKG@FEPZsbPUmVEFdemw{N`}s~|C;!!;|SFS|||||||||||||||||||||||||||||||||||||||||||||||||| " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?𢁠[`sج נ((((((PEPEPEPEPEPc=h-((1EPEPEPEPEPEP`QZ((((( ( ( ( ( ( ( ( ( KE&)hP@(((((((((( ( ( ( ( ( ( ( ( (KEQEQEQEQEQEQEQEQE&(((((((((((((((((((((((((((((((((((((PEPEPEPEPEPEPEPEPEPEPEQEQEQEQEQEQERw(((((((((((((((((()1KEQEbLQZ(0=( Z(6ؿ:Mv`zR@ اGFPmR@)0=)h h{jf((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((+c<~!jWtC5@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@~DԬv N ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((/ß?6Jy?6J(((((((((((((((((((((((((((((((((((((((xQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEᱍ#}R9 XޓCjԠ(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ڵ++_?6Z(((((((((((EѻQ@88HҀEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPW cjլ F=A֠(((((((((((((((((((((((((((((((()H:(((((()I:(((((((((((((((((((((Lh(((((((((((((((((((((((((((( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (2|21C֬ a$f((((((((((((((((((((((((((((( #5Rg GQSAuɠF욊k֙ % ;NW&9e Q9a:Iݖrq* ^9'&iV*2rM?ZAEFҁ~cU^HE(*0rؽE GCU*O"F.NȵEPHQETfdjnoiQERI.'\EV[lE.J1rصEU{WX=hYa=ZM;x8RA2ʹ{ҹ-QA!EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPH-QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE,ʹ?\Rq@ EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPG??묟z̓?ѭz(((((((((((((((((((((((((((((<}5j PU}*k22.ߧZ3!ܩniu:c(OSU wJ}Ny'D<߽Uh#L,FAb;c+Ko9]<IJxJ\,"ʁqCTOLQz2F`A܎ΰ]qW)03s@)S6kvd<:ӚBzz @AA~?]I !$[ p+@9(8>d+A39bTN ^,UA4{OGZYHj:w[0(2 #oGPN8QAP+%0\`^[6L W(jm;'cAQ7P5~Ls'Gp*6d`6O|օ U؎8#l%@4PBAEPHQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEო+H?Zc2A<((((((((((((((((((((((((((((((*+y?,Ҭ) qpZ`AA L~${UcgsVh aEPfQEQEQES]8'V7>b5 &'Ty pjQ-QAQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE&G?:\Q@ E&E-QEQEQEQEQEQER1ڤ VtQE ( ( ( ( ( ( ( L□QC)恫75^o1vE-[ӮFh+rQ[&>1Š(ALF)uȠkr;iI ⦪Zs| & 7^qV(%(AEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP/NtGÏֶ˜ϟ_VQEQEQEQEQHsڀ(((((((((((((((((((((*)H.~ց۲%ܛ&:Š(AEP{e]eOCUW4kB IEYQEaEPEPEPEPIKEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEI`*J((x/DkZ@l,m [BV-"+ϵJ4&Sv:n=+u&Ȗ&S0mOT.p%qRve)Kg Ţ((((*e]0]4M&0RvHQԊQF4 2)hECdϖNZvbc>h5˲T4JIiQMFquAEPE򿊁Rl] +f5m1w7zQoAZè($ʚ2שs*+JTlW)sj6bsl :TmnR;|9g s7bőXO(jv^:}E[de*Û{eÊ;3 ;OltJ)R+oZx5:yc]eX'Ys85+Ȉ2`Ӱ*8Pg=Ey`yGF r A4B "HcH^+n*H=A^ƅQ-?v㍫Zbf͸(IecyBgiē. <{ e3ޞ*lj}#֦8Р@5n]P=b(#>s;ɹU5jHQF u(1MQA!T-ܸ/9>v ]ѕ50CT s>c4:{)R*FD;N\]ċ%RE7wr&6}yhm1 agI%rTPϹp$G*95%P QEHIdKEn,H#)}Zb!GsvojE{Y}EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEso[c +<_PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEQsiɨ {;Ee,H BOk~*/' ?,Sͮ8!@X-CI3 D\eG5GN?ZHHǞ(^D\tW,`sGh>ytzA}*ʅ@ZμᵚTyrNj-;I$}3 cր7袊@QMwXԳ@ƅ fm6?J~dq5~($ڃ%QQA)QLQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@&-QEQEQEQE(ALR wmGB(54-!bYY 0 U47TZ@_܍!U>%PCmQE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (9{O7?B (((((((((((((((() ɥ`H=ciWr\aITu\:A]E6ERM} 7pڹ>l1Xus<UTzvq-XT/SNk, }99Z:fms`%b`Oj\6gFoLժ⯬%,VSu:Lq,.A'fC5'+`H=`k"{ U"EJ=!qd"Rp*/dby.%1*P0PZn68$ S袃-Š((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((" #dnSkKco}*P9I݅Q@(V@|I@`(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQExC?eS!] s[o+((((((((((((((((*kl-g04Lj$<ti.9Cli!eX@5ɪNsT` }xB;FnLh_ص)Rr)$fhYE$AV4mC'r] ޠlf MxE~[4EK7AQ4e%$@k}PP"bz{۬MCP @_3ZCM(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQES<<>FA₢rQA"dgjW7;Iky@O^ zQ@Q@!8KQ\kwhWv$#Z` TQv (B(((Jy%jEP ( ڟ X YPI!1Ulix9'4]Ϊ2 D_EP ((((((((((((((.È*s.egq!2%~sO{(uZzrRiQLW ( ( ( ( ( ( ( ( ( ( ( ( ( (9{O;|=.[ ((((((((((((((((ӮTw ER?Jt؊\zI+/꫘aAI=ZCL2YfqJ^r3jAdѩ^Cryqlc&]_PGfQ NN z3'ԫ=[O{N؁&@HtZ@r:gR`~ۭyzz{y}]O嚭+L>Vr8vq|k~Ah@tTQE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ϋ_+BfPkK4( :£") w\}Lfz?,tQ 2;BAj1_`jA5 (0(a#֖`v"~Tr&^RJϽD~$ QE ( _*.:EBE|U%+I 1jeJ6_9?$]çjWPU YkvjY Iok;6fsAqMo0=1-V]ç$[R$PfyıEG XqrP$8ȠJnȵED."#;9eFVcAEPEP &ɷ8SRCr9fA;$ =[5<0tf=qI3PKf84*1asBC#  kd~uG:s0ȁj ns$?)REU. ={PCNq`:dVt{Wp#55| ;P\\EC4:j1{'޴&TUH Τks>"C>!uqkF?F!¸'Ң@A4Hq,v\Ϊ2F1'ӥ(fIUKx%`7ɤSa#шD5bZyʑRFOYqi\*in}.1gҠRkźAq4Ȳ eiB)8T\O&LQh6*6xU` @Ţ($((((((((((#AI]s?tTQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQERE-ۡrnXMt}wĐo$A?κ:l2KtkF}w3QKvi@{9 ~q,grX1Yr4JZUx$H84'gmYիx C?x!Wu%1p:(Y 3a٧YT;}f_,~^Oȧ=4[U]Eىܻ$+Ѫ_ '|gӑRI5NLUzd5Ct)$P(R fVI_̙ڲ_(@oE((((((4PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPHzqKE ZZ(((((((((((((((((((((((((((((@00:RU5D 5n4r̅[CRU53Vz :56SlmUiJL0^wNQ)+YGU8[IڐLr\xPI_sbX4#Ef7 ejNjWvylG:Pd'm0g+b<jp4Ļsb2I= ,H rVr Ңxr JѬ`ڑ^Bfr*~jUe s`w \,/dhN2i~?#Y5 J51=(\9uow$̪5] KAi(JeV*OqOZ~AӲcԄ9ϹB~e9W ZUWPϡ)[az+zedg6LUI*re[ 9:U:QA|MUNv($W6wlOtQOүA{R(O)Rj?OsV(ROW1z=3M}=b5r'ܨXeFWQA.n[QA!EP%K-pnZт(p,MTZ_RGZ잍 5vqV9K(921j@ҝE)[DXeIS`.1u{X!$4o{(+ϹVy7R.Vh^W!6BZć!s榢s˹Zk$PR-cZE1VMqUM씭]i`3Y$a)F$r))nJeH⢴⍄9Q@U%!5d3E\ݬʍo$wjyQ*(%ɵanRzD]E]Xl,{c2]c`xZP( ((((((((((>]@<(((((((((((((((((MO ;m5qfV?RzV6[>RY:Tk5z@T]tjqGh?@Q@Q@`ڮ2\ӗ`G5YZEshCqš$_?Vqk@#ӎ?((kr@'6)wE"Si,gQEduwr&r.GE((((M;oӂIEPEPEPEPEPEPEPEPEP\ψo3QG󮚹u_*@8ҿk^JLvDa@(((yCp <FAȠvbE( QEqʍ/JZ0H?Ju ()Cp{#v\7!c֬ZB 9#AkEQ@ݖ[w*pqڢi"!H5bP lLUpEW(@>`L<ÜPvl$)Q1E1XX✬C)4E(((((((((((((((((((((((((L-!=h(((((((((((LzZ(3P=x(((((((((((IH0 6+x9EԴP>gk\(QEQEQEQEQEQEQEQERt+i{n ۸zM-}.B֓4hg8QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEx8S(9_#T>/j(((((((((((((((((([Ns+E jW9fb&?[tcx=֑>9Y:d٬?g(>VǶh@Sdvmr2O_\U][v%˞dnyFqZZ6 ̸PutTsM$GsYĻп#PmyAJ w7VrHk0<9\tnDUy AHR"m͑Ҵ4kۛY<8$*{ ڱkzuP3RI"DuU*f-KP{R =hx|2+LIYeYi#`Ҩhڤt \X9=1ڀ: +[{ي}x5H(]K*5 )h?(I$W?zhizDJK``* pv?hאO"ȃQY*2" 9 Wzԓ+-cq\Vwg*ya֓Fӣ&`{()V 2MIEsa~~&#S3iΑѹ_\hwc܏4YےPJF^+cDb. SR 5Rk/Bx^BT#χYaV9c$pM_= ˺-RuzW51ULU9pAk6daER*23KEQEQEQEQEQEQEQEQEQEſXWQ\Ơ;yt袊@QEQE$X,qOn^=ߥԣmK!~,mpYKm#Er[=Q`6x4SwI"ƻUsyk)oxCʜ֩ l6Þ&ȧ8ݽKh$af{9*% R {bjG=ҟ|_85 xn<ի!JC^TNJA`"+whD E,qT"FQm&ndPr}jl.Y`%9;K:bYYk)ƲѸX`OwRvPz%ۏ*1us .%9mJ=Ŏl8 p)47 0)hTY!9<ʽ֡ˬ؜+JHɃ3RYizu4n(EPEPEPEPEPEPEPEPEPEP\֣s/3y}i<WS@tQE ( ( KQd;*!˕ӝH#Ӭs_jF׽p)@=Fih B20ih*c<Ө ( ( ( ( (U>() EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPW >IoKlmOԒ((((((((((((((((((((((((((((((((((((((((((((<uf ]j<٫(((((((((((((((((=r`q qW&~]r?X)QEQEQEQEQEVhe={bj7Prt҄y[Ne]w:B 2'BXHcXU' cZZ@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEkeW72ֶMQELZRUw/n`@Ţ(QE|;Őw"շ(afjItԖOGu⑼i,QE((**qwb d ISP Y(QEQEQEQUc@[رUo7_JXnkTҮ}E$%qy)=zQ8Zz]94Nm4Scq$j*/SA TgVu^) dY7€*7,MVhfQE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ('4Z\IrNBURƭ\( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (9gt_z"ys- ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (Zpc|}of3H?V)QEQEQHN:, O4( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ._5+U50QMQEB2CMiTq!c*Pg)9;($((>ԴQEQEQEQEQEQEQEQEQEQEQEQEQER2aKEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE*$q>p.T{f$&Š((((((((((((((((((((((((((((((((((((((((((((Oo8WG\#_Һ(((((((((((((((((¬:uWR#]MQE (2 F2AYSkwF:/_ d^ Y,đ5xm4gHF7=遏 jANLqU9렮 r`֗t˧Ws[Ճ;4G1!QOm>?IHަwV)QAQvwo' KT25i4QEQ@Q@^Ճf'jX}6ڬA~VRAZ4r ($tQE+*ZgQA[ym2m6#Tkbu AA^T:(>FA8zv1u5(AAm[ EP@QEQEQH4QEQEQEQEQEQH̨bM8C J["`giQFq@ yc4r2*-lO+V@75Jj;qjNHh&Q:((((((((((((((((((((((((((((((((((}D=gAus9?҃ZZ^]01KEQ@Q@Q@Q@Q@Q@b ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (9 5:}*+¿wPoPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP'q:*ҺDJj_-~ ki|߮) L6K/r3U,C4n>Ӊ#q+Ee@C55] );hYz֡{WH$r@p=kS얼ؒ  Ѐm̰N' w$M+íiGÑ/pGJX68>>^&<9ٮ_of JHH&28"zdxM¡&({iXrEu?R{G⡞h2]GIYCG}I.m~b_ s AFt`>Rt>Xu; q (s3N?IKjHFCZk}1}xBLˏ 5"p) 8V[(>PN0lȠ=yU+UJSAߕHl=8J؎H=xu'q*JIԯpcw#hPjQ 8b>^H (V,2!hO'8@]l`jP4k#AN%HxβwxcҶa nDP@lN0 c9?T98uSn}j X^@5;h w)fKweu/M]ӼLet@o[`N؏DSެiWbBvչ UXqo 䌎}*g,Y 岒G\#P]ro(3fXQ]y &TIV-Մ@IM+`p¡Hnx`55k4N 2]G33[kLM?Mn6=ҤKH( Pe/OsWjV8qV4aMvڌރ4s, ƛA1Wv%RVM\VMsVɋnf'QsIQz6f(VOZ{0QqIïkMH%#9F=kTeFW9iE s0@уFky@v.?r8+³3s@r ~WFz"|?k+_$XF2A3ӽVl Ƞ(QYo3wWn|:MuOƷ|߿j7cSf,V41RXipK,3&z@1G +)b(;Z)FT8!\Mt4PbhvbGB:H& xm-vJ8jMOEVX!nF3REQEQEQEQEQE&sZZ(B3KEA+LS 玠(t+X_t9V!PLӕU*v(((+@+~Jh(EP[ϔ#n@ޢ}jH.NxQEmWeۯ~j?UB Uf5<•+,cxn҃y%'w|;+0iQdLŌS[CǂrLI rZ*`.AdAASc *8<2*3`7J`k5 fXAסeH"eA8OA.Obj 6&jњ1`CQ#K47՗?g8j+fBKL͚qKb@01KAQE^76:j #A`y^# ުZ[<?Ae5rKm,SmlYe'${.*x4+(VDG?>Ԑ鶐6R3VQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@GRVzgn)$YCƀEPEPEPEPEPEPE! u }ih((Hyzʜ+zu)utSS_:Q@][¹RE-+A%(3H$ Z(((IJ0뚅laW݌zE)I+& `5#(Wh)THju(((((((((((((?2PH8SLUEh),71+!Вqlrh>N0+XpCojr9oSS*Nl(((*1x?JHJ=h.d6 ^F0EG9\H 4ݛ379jX_4T0-3;Š((((((((((((((((((((((((((,jYxv>(((KB\ӆeɭ |V (((((((((((((((((((((((((((((((((((((((((((((s3:ج 3ֶ(((((((((((((((((('_8LVw.#򼞲)ݏlm80,E(Rp5">3-(.l&OuT@=X杀ޢY$DuƪBsc֢vd⣒(2u9Elu 1\6OjYln,Olҋ4s$áAO#11=BG?kRPLgmM@Sh݊a@ou{Gك# 5-er8 QEQEQEQXkrG5È98 ( 51A5r9$7@HL.{qkV74IlEPEP"ʮt@1k TzO(栻u J1t\F_?ֺl(EPEP?`h| MnH۞> ^C%ISH0:(Q@aGT"+4.O?jh #]TGEˤ1Bm9U@QEWIS. f/҂QESd1j}kQ@Q@Q@ uY]A^~[M(B((((<zSjffAn+( ( Fӎ)YL9 'je4WN5AUfT3\8'>YP:4r:4aD?iFg֖RCZQEQEQEb((((((((((((((((((((((((((((((((((BQ@[ $n(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQH((((((((((((((((((((((((((((? ?O +#_ ϬU@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ح,uZ.HW\@[dX1_@қQIH}ˌVYfPW>?֥KHV*KwE>U"$v9;W0/*(84ș[a#%@S:@8rvfi $B&,jCe}#4qW5SO9mOȩ.Moω0{c0/hmtrJ*O=0MQhKʙ:VS0 t2'U}=cOX3zFeS+gzWqYN`mK~L7ȇSxv a+Kw<_ΰ,%'L:wɮ]84(ǻTңݪIN%Pd˨Ժ ̠LtaY3ŐTsUmːZ\O KxZY[j(5mY1x}Hyq l?"&w8X2zKY>wk&V*>V 1uBV#1jEs/}jf6 =Gz+ҘQ]H2lؒ3m}LJǯ'\VM\cy=v[@av,,Z<8+sY<>.o,'rIax&5*E9c=$I vUF:Y>U9@yޫ[hR2,ހ |:. rё,^}?2vQgFs@&G^Ti<8 f \ypsBN#,ild*Fsi%=FbU䷕fsԁӄS| $S"kg,\, rUEg>dcsU`+x)A Qt>q KFqVȮuÎHBbVVܩi0Vh߃*IPgpzRk4BF\(7rb$摯8D-R (A?/ᚙcDnDEŰ{QAwaEP ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((('?Oj<^j((((((((((((((((('cUn)ƒ jkb~R?_ ֢8;sTD.«3&dՋh-|\c*8 }@+5m3;B#[It7Ck[qֲ;l! Rr޴nlV)Y@j]+N[y1c2 w\v*XAzL:F hH~BKb\i8=iÌeUt4P]m' )bxҬ]gsPt:-Xݹ9_B x<.riQ@Q@Q@s~#O؉$׮H^R2~d4٣oZUJWWT#"Ť)Y<+گR*Ps3dYqY3Ͻj\<7,G zPkG i1Nӧ]XK;H H#ԑo''6m2kfc9-g2V @Up-?2#TpHY[7sVpحeD;ڨjcDYUAֆ˾xcv]Rnx#XeUfrBwj6֬Q@:}HӊtVBrϩ楢yk\Ua-.űҋR_͈:(RlPa$gMi1Xw L0]cN9fcRKg ye@V(i'ԥ?xOl" mNJr:t($()`R@ KEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE1\pZ}Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@RdzIFr1@ E4Ȁd4hI)t_?IB$D[G} >- gyl q [?B[x@Q5tLup'h\^)RsQ@P}H4"i7|Tk4i |hj*)nÝ6?ʚnGlgT= PJA84϶N홠 4U3 5@uIb"Efzu2k$p4 8ߟLQmr, p#@!h^_ڶpI Sgmb!~}5K 2)<:Rni:IxUO %# $Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@><8 ֵdhĚkPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPWt9*܏ZbxbD_?}bxm?,.lʱsPeE=nҌpKӞi% 8Oʜ]Ӄ.Qss(Qt: ~T)@=M>Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ OG ʰmSmQ@ 5%1Q)Q@Q@Q@Q@Q@Q@Q@axIջ +vH ^BO0gz6?:Ԭ  9'MhPEPEPH@=ihQ@Q@Q@5:=Q@L]DkN<~wҤQEQ@!Pzih((((((((((((((((((( ұ]bg{K=8߃Ԛ[w}?bi'G1Y::O;,g1u-1u ~k3s]:3g>E#ѝbdf26~ahOK:5K3 &䍹S@,F?׏sH ܧG3h:U90ӗKQŴg3Lk~Ѭ@S:Ϗh:ajFog)Xk6'>V#Ņ:}xvmXH*iNb?:jZ[0FGM +.dĀqn8̟Bs#f6(ld 4jǑ)+bAQ@ddy4xnqf1"T $:Y1M>!Z 4G@^>[?ZEנcF>0zƜ(F:"˜oT^"B:uGE=@+}g+ U4,`uVKH a9}OZH; lS} `*iכ?-j*P( @?*Xbœ5ؤ_¶( _ْ=]>3SI:gu8?ʜ5%~¶ 6nKeϡ1uۭ6+o@V?55B5n@V3[|ځꧭ;?[PjZm4Ta'5EaXpܥ7z2G_[tQp1 ư@9i~ӫ->G\ EXW&=)Ln( MoE(uyq kf. ]h>/V\ _t)V)?[4Qp1i̴j#$]а ע-#IUӥ%  (RY^e̛B_N:}ǔfV\ E\rVSOLeSڴPgIǴ鋷Ea$+R:A1#VE+u t贸B~yr9"F) aDC:v#Qi3aօ\ NJN<ǚѢV+R{(L F9f@ec'Ǥb\@;yk_,-ȶ[E| @VK(Uٶ!m#p*+Tp( !4O"xUʓý?jt~T( :@w69R*IE4 _''ߊuxLdǣR@-PEPH@=ihQқh?ȩhż#QlP1cNU҂NJʝE7bt~Tm_ʝE&h# Z(t#PБP14$ŃFA4ė1*M@2%QH(((((((((((( cԬJ(((((((((((((((((Rӣ?ցϸ5 6Q2+\tER(((((((((((((((((((((((((((׉?jV~ Ѐgڴ3M.:((((((((((((((((((((((((((((((K̡gӊM4<#>ֵnPm,`EcUv?8EnW7 m K!¨ZO#ob1Ƕ?ƀ35ˉ4J>OMd-VL|1oʵcyJF~]Sk;`"&C#X涍9XZEQ@R2R #8#EA1O((((((()ʑ(g8=@(((B@@ EPEPE5H.r@iQEQEG (PZ܋wAVPR3R@Q'UU]drJEPE2NQK9 Ojť 1qUpu&ˮ,笧?^@ EU>e""fG= NydMa耎[@)qa6-.ƿyI$H/#UMVϹM&SGݽ)R٦q-,V,SO[H]͎w>e|y ք_NOG֟ )a#]?_z`8 3qZZB2)^nEEr#;KDvOpR] qH1s 0924袊@QEQEQEQEQEQEQEQEQEQEQEQEghCniyִk;B=r@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@^$#Pz1Q֍ż`t?gxgG9r": ((iHuL_$Cjq遯#N<. :r ڮ:Ң5$?kiR)jj1+[;*㬖Nl̀k[[k])ML"k`4xPjMLpdoƗ[͖ ڇ4nϪab"RFch&?]eԣPTs,2yA§Xwj (iH"Bۧ3R@P2IU#Hs Pڕ"bIwXлUQOAYR,=z~o:\G :Ӥ&VnzЪ@DAǠ X=j8<žI 0$u:*HQM+!=MIkm_Ğ@HWwްۮ]wA}..nǢSVJ w7sj;kdB$噹,}ʐ^F =TFpegI@9y3<2OIQ$*I# 80I\!#IEA=ʱ{_POqޠ KTm. ‡J.5 KYDSΑ*.R F@e @HFeV-Zb98ɫy@r{Z}#\ MV+yG_ ֖! {rY1MVᰒ5\@j`]L$ϕzn s !1#%T-Jr1ҟo~od(Ì{Sċy:mIŽMfG LN2), lp3T{M_\u>-\ (9}ϽK}y5K`wL 'R)צj[(;1 +;WEc vuRdmNx=9FO4JDQFIWW\MŸnIzO9M03q H?}5rXP%Tk6;YE} f9ڽ}})=iwg>y!A$8p&4=(.f\_؀jԛMߏ9rs)EZP0_݋9oҴ pUn\ fFqeW5>Y$=1W0YicEDPª^K;o)g}b lUbXSj9$'[@ЬQQ>楪^HT\$C0di.뫑"¨4$==m K+mUj;g[6I GE ow;\rW2D?}>r9 n@p3z]" j2'O3=o +;?]&;x*)"!Eڠ`b+fY!0Y$x} Ȟ[nžR KEVf2f=?m41*R,:V *\(P PM+1FOff|aU(P@cր<)cy @` *8WW2?V<ҥPeԚN*9?.eܻc ތ<с8i6:(;Ff>\WE\ & RΔ(@QEQEQEQEQEQEQEQEQEQEQEQEgB @YKWh((((((((((((((((Α/8QXG C6D5TA`-QH(((CҖ ( ( ( ( ( ( ( )K@Q@ F-QEQE҈I%A'(U_ Z((((('Ḋp' KEPEPEPEPEPL*w;v*I+qҀ3TӔ)=kNH>O+FEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPXvb?*5]cL u{ogpjzkƏ譎 t!=\ *xHTB; mu{;*|}N@|&ASI 8S֞5VdhHu5D[=iW[Ppvƀ5g-.Zd6!mHbI9ʵ1Dp3 hVY#hqJB"Co@XKs!ade@uTz\Zo˱r?:t3j1bv> !ծ[ʮz ad-żi',Bc=nB}Ωol8aZ( =@Yiq"n[N)p֋bMh.A!}k-b@Z9)o99 @ hJٸeqpzU%ZxV!wdq5y FpY F@HQ@Գҿ,Y8݌ \{n5,qkZ6ǩWܦĉ{B0= +Y>XM@j0BI朲j\}c@E.j$*D3iRMBA;z/Vgm$pzT꣍iA{>^;mh9ZVګH.rX԰8ޣWՉ I,%+cZYj턚9Uv1;`sM]L w FG8zzƹi#L~UFF:)|C֡/y1h'bIdY/.LJjגFu4C4뿶Zaj3#ǚ=Ӆ<( bE 28m3M,sjEdˣIK,HێyRgK%̲A?)(8y{d֭ vrrMI" #d9)PMJ|ɦ%Vv]*Z(;Z)T202֨NדoN1[ GsϭZ2-P`VV]qԴPK6(!⣋G,Y#f$~U~@008`acG ԔP0[ےa#'QF"bh((eV`u@ uSYCqu }-Pf$ƄiF=-R`R@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@s1bU~Z諝|L3QH(((((((((((( @_hV~ {/ʴ((((((((((((((((((7\؅Si/5Y,t' (=+>Z)iʖ:q@4R3R@OjDuC#SЃ@llP8րEsCO7d|oc3@oJէ{I>_]%QM2"RhQEQEQEQEQEQTKƱi0 zrhѯդ@`ezР(((-V8@FB542, @(R+k :(y=`fku ~d\ME% :km#<3ޖ+ڱd_~u_Q|d|PV [䲃a' ?t5KbT{ j2nW&=GZ{)Ds(PQk5٣rAygmxq M pv hMv6Zl3A}đ#>$LbT,odBF`RkmơFA$Knl -i;!=sdzͼ1B3AUZU ڀ45Mm. +fn٪ W6q߭Ɠq-?0IyqU$+hO\(c9Urs#1xVqcP%8|Ȇ>o⥴ ,ڳ%{@ڭҗ32z*Z:.+\ {7A׾c'ӣd`՟F ( k bԱd?i5arV5M' ۗCcܬ@zի{ {hL16H<@[bc[F [GUތ? 2#50-ghBK#w% &owuHFvmV9 V *$IZ$E*ZB2H' ?LS02TEm,2:js,AYbk MV;٭Lmz6=*sCDp ֥92FpޮhbvfSט46L_tʚULpmn1;e‘O\ĐD(@ROH 铊筵+إ%gvq?\.vQ< *g%g$T-*6WKw4Edr$PFX8yLF0̠@E`7'׊ܬ1umP]1fh(((((((((((((((((((((((((((((((((((( QEQEQEQEQEjE_c.}y5Fgߢ)QEQEQEQEQEQEQEQEQEQEQEQEGF(+Rg\WU((((((((((((((((( FOBMi1 Eex K j[[3 ׊`I\ ] Ulr;5tTbI)*SHR[M,=F}Mt:n1̬`%Oxu#'?j5i"d?m_p_T+ ,+ PȪ3Ҁ9ɖy<1l@bVL 8R@Cf%q؀v6Ezi$6Jj[gl5X^8^*FpkoJeFFH\qWs0*dgt$$(Bx<@ce'?tߧ p9g^F[}E]0Q1aѳҺԊ8w f_1X4d(@2h%nguI|ujݷgbc<;j3@՝cQT"C2ִh{Qgghw6  .q[PedtR]h\L.zJ) VtVVrKz,Q wR(i6C KYedHPY,(*HHcXP(QKHzZ59xZXHQP2fǶ7'koIs&lǯ8()h ( ( ( ( ( ( ( ( ( IeǞ988hN?媜cM%,wC?4d#~&ݬ&ܪ07V (EPEPEPEPEPEP$q?|EOE)4VS4c ѣ2@ʏX}V{PH1—U .QTS9%К:AEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPYZ_?5YzQ#PԔzJԢ(?Kb^n<Ҳrmq(}2m9Oy!#l~JVҞxFQϦi]DJyu4EᴽB?{Hb;7uȨ@bˏ[3QvTZq+đDIJʪ{Pͧ[$*q{v2`WotIREYB5loc@J-;Ջ_t-z4fH5QsqRlH歮o4 +3LIq򼠏pqkhՈ,#J "U%9SզPUwU$n{$Y'(ݱZlwDWZ}fwf0tō?Ր-UX_fEiny=*އ!Q3nj }6)dx>Б +ض-D"-jEp.yTS2Mk~6n+z(ZVw'Hk7\&K$*> 1$RO^(e' zr\KnZJۃjVD3.zP'xp0L tQE ( ( Yѕ@2/)ZEajf+=1lhj3ixZTEfeP:ӨT~vwuL&:a#qZm-ąvԔ+^L1T.oRkdܦFRxqFOSPNKD=Λr*qئ(Q@Q@Q@Q@Q@Q@ih0Q6c!\T}G<܏Ϛ5F|c5ri.zbQE ( ( ( ( ( ( ( ( ( ( vUYK=1B- [¿0ݞح d4yL hEPEPEPEPEPEPEPEPEPEP U&6犳E)5Aซ v;2?JH@#f$BQzz/kڣ%gځZ/f[è>b*D> j)(((((((((((((((4 j8QVk'MXԇZ`kEʚGg$ʼj.1qvx+hd_Lq1&G PǭO %Nh#&?ȢsgZI#tjOH&~<Ԝd@"tETx^?\ ׮N H?Zwy"LQɋy(?[w tǮM1u80 P‹zz\bkV(((((((((((((((((((((((((()1PEPEPEPEPEPEPEPEPEPEPEPEPEPEPXw89Ϲ԰5|cЍ4QH*&>?{UNnwTmBr)*M7S' .gѢ%?^9l,D$[Ҭz-|g=\((((((( OʭM/ArU(((((((((((((((((!ʤ5sJ92_NՒe:ޟi4 `MER(((((((((((((((((((((((((((ZΕs$խb̀m'8֓Qq>pbCۨ/ H6O|\%?L4D]E ( ( )>wc=)QM+xc: ( ( ( ( ( ( ( ( (zKh9 SQ@kb 1ӃÌZEs!5*`i oT g؏c@[\ӦjDSJ@{7ԵE5\eH4 Š((((((((((((?ⲬF}Z5K}=gW#kmR9G690:PisY?Z:xO#4/+w–-u$?lBkwA"E^0z8 z+ Џa/?Ɨz '"5E5|C h\ȢlQX(B}G|E?ɓ$ ٤qvP<@X +8 n)Gc9Ż @mRV8`{H# z D[)[+`(j Ob}:m'NPyEl5C!6Z7(#9)KRsn3+EbN3lsTM 2v@V4Eݘ54Eam]dnNxGBIQ`7ܜZں_Eܢw/siX + u]@+Rk6JqSV~}@4 6d7llXU5o4U'<4X +ouR9T [=6lXWEAN:ܵ עkX_l"\s shVA^gh"<ۏEڢDg1Cͭ1 9tEcyʌh/cL2?Z٢XDg =Z>Z`P3@~(Xa jQYRG.6M .)-e3ģע-iG)=pH@Tn։8ԂWwfJ٢_?›mdItEcX mR ql-` eukZTV8ո֣7Q[ +4LP&j{#4Ecf]HE;* 2:ע#n~Rt94բH#B2g<i{Nuw}N?06h.j2i~|1 J+;u 4hrMo”`7s~tע_BB`é,N EcC6)Ed 7dGFC1)IY+G(>n'(l~Ic[ؒDi_QoQՇYY 9+6l~_1o -oOγ?,@H#*cd_βLJmc}bMmNzE6j!؎Ɯt{b?)`N>>YSt%4k g)??(Md:hzFsp84Y۳覜4S&˲f m~}QiZe]fB=h:]h}Tr#ia/5piV#]4 0-h:_FT^/u;yY~d]׭ҬmӞK{K.Q;E-4V1VƋQEL!1STlI/)_Hε(d Bv9*Z0mwn0iEˎY?.T]K3U(ܰ%H#EIE "iTSu6ҭQ@E*bc1j950Bu"#wLL !3I"p~N_#WH48G&_3h̜JStlG!#Z4[pLL趍f9L'vE4>ߖpEmm`ccܤ'Ѡ.Ȫ:0p=j8+ynislN:mRe@Q iD9'5 93#i}qyl훭G(ZSy6檝nIn j}U[S#V=v 4\Տ E'eoX'Ո,E(=|Gl"X~; ZMzF`r¤iEOC֢3 /cv[Ө_vix%:).3Xx (Tr_X[800il uW0Ge;;Wi:($j:C1vo5VR?1kQ@; cӧ:()Y--Ǭ}kQE6c >lE6ysGoăeCEѯV7P!*swJ袐Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@FF,(iXFE0}p\]JzR N4hyAM*P?qh{."?!)vWԵEE8oΤZ(Q@Rfh`{Ө((((( [Kx^ @F8dk O0-dXG c46̨Sn8tRi6-ҝ*2;(iM?*AYqn1ޮQ@dfN=EI;s?TPaaj-jV'ѡʊ`F4EaH(knD1<6.>(3MCQH-"$R@ 0NLhOɋy'*J(1#H?"&,L%-UϮ)vs-O(SSG@DQS=&P`zQ)h(((((((((((((((((((((((((((((((((((((((((((((+ \ ?@JZVQH(KBrqIENkI[eG ~+;hԸOsUc'*JFh1>P>X=~]G? cGҀRZxt/4}ܾ彄$}EH0!0(~%.zu&㟥\̐ %( Š(((m9jɵxkPQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@qLEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPE'9(((uN6ZޮwćeOdΚq~H((((CҫZNϔlEPHQEQEQMVHPh+è>6GD?2o/ BzkR*%(2 ( Z@Q*xZ((((((((((((((((((((((((((((((((((((((((((((((((((()y-QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQERSKEWx*\[@[^O\ -}KXJ6ղIK.Pt(&S( ( ( ( ɇ?\ֵclj#PQ@!8ԵWRJv(2/??:Dzag[qWWBIOz}nc"ƤpKlrT)$4eN~aǽC~򨪧B#jÁ#&glǩ[&wJ5E|? ˿sBdo f5R3/_Ω?84piH?Fh sU>:YPgTZfŌ}违S:-N"3Zi0Qma\ˏnj,BHOMVÑ6ʠv5b?/乓 `Ѡ[\\eۓZQT?mmle33KI,-p!3U\€;uSЃໂa]Kov$1 " PZiYK4[K@*z1@#9ՙ}}o$v2d@W[xȤQEQEQE!z;yZ+BHjPԆPkOWViQ@Q@Q@Q@Q@Q@R (((Y( OFF=F)PEP)h ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( $+ ??o?SaS@n?p )@Gj]fêZ\anTOZW٧)2ҝL* &)5j8|,{0V"CN]jCT-ѹ_OwQvoEQm@,]VEJEUKYqS[&\~?Z p|)Irjm3>KU@)_Ɉ)m7KEPsQ@Ǵ?STVTQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEV`Q <ǫN7Lr`4EPT5?%Q T2~ h ЍhV~sE?AhAs[@ZF 5oz1m7R)$idsl_zT=kA(LT2:HU9@,HKXΕ5RF2[稭](p00XdQEy (*jeU.'V~_+KgB~=Ly1}xVWQԚ@Y VcwU=,Ú|qFL#(}Rk0Lۛn>ï@Sa\=j @h%(zm1H쫴Y zzSЬկ$lǖH瓞k]w){ԣ=ih(( b+&8V^6 Ķ )j6Ilcnކ<;+}`H?ʘQE ( ( 789=6AzAQviX6|qVk.8Xw-c]:~߰ŌƔY??S@wmΣ<?XD?T/DQPتkD ut>hP^ڳmp0*R걨?J($AH$V}Ć`tiZʡ+V!\ }h6VXcD;S袃&,7:nD()OK2"ewylg7ZE)v"<CRQ@9EqghEmA%iRq)UlɜF+OO}'qTM&S$֬ik99"SjһܿEUDsD&w/)599Wh6:cGncړQՔ5v85/R5T/3ne}j)7p(#t*b#TQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEVFߛykf>X)_CX|̘ ϯkoă:z?}85Zwv! ŶE_X#/B*fBzv4'E<+qJg'@QEQEQER7?JZlh^ &0U *lH;#ӊ(3mŠ(AEPEPEPEPEPEPEPEPEPEPIZ(0=(h`zR@ 0=)hR@ (>MQzʖMQKE&(Ҍ Z(0=)qEQEQEQEQEQEQEQEQEQEQEQEQEQEV/G6͸q[ULb=ҚeqJH'HU0KEQEQEQEQEUK!BuZ n.JZ̢+Nqizmg !hF4 :AQQEQ@Q@Q@Q@1G)"@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@d1X`9Vd$p`-T-14ږ4X 76OkÄ g?nJٔXzv]L°sSkZ7N]RaX ae*+ FխK-[^DT]@: Z٨ՇEea)OK 7Iq ,*jGZ>~X\ҧ})ƥ:ŻeJsM$EjYWqU kk>C} ,Ecal11|Ee.?GʑtX ZtijVi?O\}x81`E"r{`GlrA=h6ۻ5G‘n|S?[EkY'VayjųgEej˜}G8 {*,9>0jğ`M^ѻl {'ɢkY_o=B_޳ǩEdx8t}P$`5謖@ǟE-Ƥ& ^Ef ?~6zP?ƋEf8XhFԉX *+9Ppcj0ڦx^=Eբj%)u"‹Ee00GMeHq `5Q~ Z+(A|L:f_΀5( \`FRϽiY?b3_ߜf4Ee/3MM>1s+ޟi&t¤ 34Ec.Nc#>ӿo^ր5. ȹ8s/g&Ge*vu${:D8E4i3'G.Ga@rx~u4_\>+ŏ@:(hϵ5 @b}:O6?YsȤ: EO?c<֗@3qk(R0nEiФ702`+8hPO><߀{vB?U|v?3 ?Rpy?U#x.zqKmi?B{UnGސ~"4Ku$hmXL6fCQldOK]Lznl>w:id:;AقNZ;2szA!N}xcKy|8 qNt;C{]6NMC9'|fX^42Z'ċJa_ƀ/X=ղ;+zՊJYg׊c7[} BH  X'LRg@Q@Q@Q@Q@8G^^?ee'1v(*+L03u Ow OYS ʀѤY(fQEQEQE58U))QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEVTEj: Zլ6и J( >=|Iآ3C_}ť>۸:(E)&9hПu(`F袏*<\>o_ʐ }-3Q@ ڣʗh- P( L Z((((((((((((((((((((((((((((((((((>2=x&/$H? ]q%:4jJ]6kSX$PЂqkfIFRHHd_O *7zA(~65(p4R?܉GTTlD|TMhx!(NsXS&n1 ZH3ޘ̒4i ɪk @6lR\d"}f>Z)Fr1GVp _? j)#)>Li0U$ wQI/q`-ϖ#׽V,b:gb b",@=)Z_ ó)4QETn"b[EEk8IW ԴQEQERdZo{ ed tTQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEdR@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ w1I3ĭ5QEQEQEQEQEQEQEe*qA.,iiGVjlUf 'qiجy  OU-CiCRg(({#TN@患kKEGD I@QE(((((*$:<^td`TeEm7O큊AU"JZJZ Š(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((y$ܩ֖VdܼzJrJ(Q@Q@Q@Q@Q@e5;~ZqsPע(MOM>7??ʀ3|; j^1`@cV7#~:9Q\e#iUSs7 L@WŒoκ; yhx#'}gFoD_b<9)QnćeOcNݧƸRj)Sy` 4l( }hd@*wRi)O,*W9>Hl<:)86sA5ꭾRQP@VEƴ#X#2OAހ5~]LD <ӵl~ZEPEPE%h((((((((׮嶷`m#`+)c^l#wG֫34R^# ;Б4gv 2I\- p=O[<C.P=h._JU`ܹi21;tjAi<;lQ E($kӀwYe9fCuڦS]RѮq@h@Rdg [kc }JH2GZZڍ̯F*啪[,JrG$`X)QEQEQE7zg>QEQE5cBUFI=U+]Vs .KT@Q@V+Fqpi,{+d ET sɲHрȩTs4bX|=GZ$"B0UMsA|nP0}*K&Ԑz4y1ᩁ\A"șT椨x#X 1rvD!N@&u ="|銯;d z ~OE5Hu QEQEQEQEQEVV&5m1cbk'Q֛TEPQIQǼTCAxح:'M9õjPMuQ@5ܣ#hfiZ13gwF,<Huw3E+$o߭Mݥ֜Ę$z0od;\uj+ obey%NZP E-44=b-3 ÷FH%N=^cB&=@*Ֆoc;| *2}p? &ȉ >sXIgK3 g^Gl4nWv Y|+cʷ]CJ9^I z aaGfrźNz狇ˁ[Xr`Rh#!tx (`?>jL} V  +t*M#HO1h;Tu4u g$o# =Mro.tT/}gsw`J?aZd &1נ ڬI42Gsj˿DvbThPbgyn:3.G2\!l].to,zM_2Eu 3յ!45QHDZ>̢ y%@y'uEHTVa= c<<Y+ހ9k{ˋIbAJ'e# * MnYCeJ$s@>!bu8P?ԓm_E=U{M{qBZsK) 2sלr(Ru}>սcC}? 8 6Ŏ׶)t%4#o2;q`zP}˦9U1jεk5y<S]CHK\855 P=HC%aP[M#V9Jb(<~4&[}B%W&9_k/c[[#rl9 :IkZuR:3v(EPEPEPYLVgk˻Iw JԖ(¥u?'SߚҬ{?Sm#΀*Ҭ۶S-6R*2֎U1zPh۩l5K _YVLlMBwq"kF ;!';c^Rw%*y*$&2)+t30|۶:ր FUqzuQA+xHz2]*eR8")D :j{e(bSt9ɫ3[$z#HBj}FP6}jXJ0MMEsݕ T. zR@ݐ5 rc"QS9QEC)F* $zP@e Oh(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((ׅ=ˌf_kG]? Ќ" !W2 dYƀy,$a;NN7Lqr:)̅2I%DFq ,Py4fTn[LFG9o6}Gҥ("m=QZ;7&6g&6'vw:6tV$JdSnYGUtP>z{g\Wbk8b͐O\U(vօxᏢdS2+PG:ۻb*P0AKEQEQEQEQEQEZՕhJQE$t*ug{7>xJƘf((Fyu%UUޮAG( ( ("<Hj[P?01)h ( ( ( ( ( ( ( ( ( (-5S((((((((((((( quk, ugh}Ŕ…`f(/-#.vإX'ޭQ@Q@ Z(((('e`];LO2vrگQ@Q@Q@q:06LOv5j _1SMFE-Ci'i @jj@QEQEQEQEQEQEQEQEQEQEQEQEQEQEGZҮ=?^mҧ'PI'^JPٜzf5U5\?iVv v` z~5AeeNOSAZ(((((((((((fbrM>p(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQER0ܤP5/gkfEmn@ޣ5(+]QEG0Kpt 1Vd`V=z -"Z(0CȠF'4dgjL_07'vv-+b; HCC@%kرEPHQEQEQEQEVNH4?YZt@jESdVCNJq@]hjV_@#VDʆE*Z>=α:b[Z#E7hL +hdL?|u @VoSq룏ͦ^\d 4(Ѥ9{ۃlSr.s[1d+?(dSB#4ioptФ#H_2 yIsL ԙItuf^顥˶$顤̏Z2=j]ݻ_4 *9*! dztrxAZcs@7/F?:tR*M*Ԝo_Ϊed~h]3@tlyǘȪv\y4ٶ}S ̃)D*,pmJSZnPtCZ'(| m4|ҔijE`{ 0c>tx(Ds&=w vEmКQُwOʘX#@u?WɧqxQ6̜?ʏ3O-D|q5wOԡCCp]Rmc5 'x##]QuQH((((((((lsv+3ff 4QEQE&P-2(#ꪭwrTʰ@yd쒿 ٭X${Vh((B3EQEQEQEQEQEQEQEQEQEQEV~.7tb*35>oZ*i:}NOf ׇK\0uY\db'ӿm\U6ۜZ(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( V,J`&k7PM؃QmO)u:[梼uK1V~! cJ]\*GN*M1m2|M)iu:$i1^qTogVy,WaRkpdB/R$Xjt*[(9IFȒ)CޕZzȎV(%2 )dd\8J29KݪAP)>n(c|z;n1ME5(e`[);QAEPE>Kߊ݈55Z[njNײ (u2FnoZՓi珥4QH/t9ITgC+R|81I+Z0,CcF1߬-9H 8QV0 (:u /RdR^\F<|'$S78\`:lԐnb*xU%RJ9hԷ ` ܶ),9 *?zb;((EPEeڛZ%nW`sX&y60:cY:^j {,4K @ֆt66GU l;UQOjuex4݌qA mvJ}6Zw n  I “,8QX 44FCo88E$[ Ҁ6@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEr'ܚ. _pG\4yti$9vKSDLZ.׭},-Cu/(Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@SՀ:e=6ʩtیpvA5OI$Č,U*ƕ9T5)ðPpƟl?*U4L'CTNGu%(QEQEQEQEQMf 9PEPEPEJ((((((((((((((((((((((((((4QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEɣbMb5Lc#k_KteY$wBeX -=1OlXFx<}1Ɲ}AĖ[r+K?mBYhџЊ׆-l,2Վ+ق#lҀ75MAtUm!seG; [P=ixEgTj7v!W@n`c +u:+!Er(an+uGY (@QEQEQEQEQEQEQEQEQEQEQEQEQEb q5vikA5dl_H~(EPEPEPEPEPEPEPEPEPEPUfm`}2Ww\ƋbI˩V q@˪7Q7֡7_ngg{g Vdw Sr$6  qPaS?Lԟ G 82Tֳ6ݒ{_?Tս7F63L۾\ 8#T{}$85H(((((((k:Uvj:m?4i_ b{ ang׊@R8nr0>Wk?\\ЩDžK6?췊WjJ-As7PkI}Ktqc(FګZVv{a9VM ZrD 4x#W4IE%؟PIdpZ!F0rOSBtt{2LCP׵gXɋ S߆!d Ӵ{Iɪmy)rEWim7c+IfНy:LRm&lsPC H uV\RlNj\!.L`թ&T8c5pk9 88vIXÎ^`3j[h OtIHw0qR!F9秥Z6'ȇUQAQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQER)i!ft((((((((((((((((((((((((((((((((((((((((((((( dP# ^FJb,dPAuӢH`ih1 ( lf8ku*hPGWPr :A7q$O'KP[dYg:0((FjZR/JZDQ@cc/O8ElVFfaN~tQE d_OK4c ?jV_?V02,@:R+b_纂?J٤Y>$ra ygjC=XҀ)TqL3đ+X\+/%jЦJ\NRhՍ*(f@&cɍ1 )1KEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEbs&u ҳE!<ÃZVE0#2\u5!VГSwFpT}k;W[D*W,ǻf65.FԇOC5\M+"`W]c+Me 0΀)f 2MUԭˈ `F8( (+^0Y O58 G n`.alC?JM奶x;l `jQE((((~l.[&?szJ Ryzz{G}# Dʹ"|!uQL 1ӗd n9}k +,?SO=653zǞb *#=̆[g?yrHimE$rk"HumBU(1 {^DYusKg9X3F\Ѽ}8ma?u;ArKdRx{S,+oaΆOv>e Lbyq:9(o<09E5$l|̦ۚK>?QH(( K4{[Ve]뭮ghG=V?PEPEPEPEPEPEPEPTbL$6tr˺_,x6pc?* KC[KӌbkAs`g@ 0]:ORT~.I}(.ćZǺ})f[7*uͲRJ3c@RfEwC>S'.- H=JKcZjN%i,y,w:=٨tVk\|mNUez, >AŬ#v} (N5\trbƀ4VMcEӢ; &g?~ԏLԢĒ^ t٫ ) *+7տ?V *+=au ?΅T hB ԻB>6?~m q|#i~y?~1@hMozz^8Cit9 Ui|:BZ^wh@^[-/1?FzldwUҚ,jԢEP[ P' a+}`JEgeRtGJiչk{3 *+5h?Ү?>S@TVybSGL|fMMhQYG#4cA&IMhYEL }L4[E cY;duogY;4"u5S2 liY3e'ع`[S֗zx~uLhc4d~ι=NM[28ܿly֪c~ʇ9t9@ "gHn`^F?BeXd'I`3@`q} >m?bʰHq,sC| myH4jVG^ٶ@N6V@6: O03όƤO$E9#=䍣#Ա~?}Tt!NV(Q@Nju{pnC 2+}TSE#1"jXu>ӫ X,J1E in :~qh:֞?#V (y14EŚ~Fayğd&yQJP:(0(6O\*l#_(RP1RՆ6b? iSGI?*:4v.vN홭,AFeo4nFW)mpOLR)۝Rz1ˣ,BqE09eTm߂N ?)0=s ,DH?ִ5ȧFNc<7itEAL bWȠ U2`{KYs>MŊЏQKso/iLT$3q}(((((:r*K j9FJ.Z9DS|iZi;cVNzX&qsRT?yy SL)afp!g5NVYt 0\P38p*Y!Nj͜LqLt>D^3?NDd.@.3V-HV8j}TD 򮲹 YK5ܫ6XzS e@Ǡ3\йӦ0̣cjLFPwy$VQXo@Zy+i di#uQ@Q@Q@Q@Q@Q@%hkRГi 8ϖҳXH.cT֐٫f?~I$5FPVI7Ҁ!5$x䴄8V&,6Ir7Fۊ`F~UK_ \O,kl=PQK19HV-=?zk\\j :NR}(XE3;]ZMkMwK%W4)`S23-## uQE ( ($q@ M{8 iOC@߆e_k[åc>]MQEQEQEQEQEQEQESUmNV&~~mR[Mbu#ļ~f wZTQ2hoߌڷvnO]֝14OD_}*fITvvRwfVĭVk#MbeNEk[>0.&1mUOLUe_QQp2O'$znldT(1GJ piTEBhf1G8$ =*KXNOgfw+(=sSDHF3@Uqr)DnPdyrf8歼Ȍ$.⠹ ʹ9iؿY+0[^8ӊePӌU=:$=*2Oڂ%EQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@PuWG(mϷ&,M/+2çjGDT?@FV8iC7p/QPZAyx$ TQE13E@k4\S?5b ( ( ( ( ( ( PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPֹMVZyCY>EšWKm3n DGBKH((((( -Wc˟0gj# ­I.748qU$IUm02޵(9F9f4ʷ95@XE)4 :EQ@Q@Q@Q@#}KEE `*Z$$GXE/CM(E ׁ8+j5&0MERMOM>D@\iHO+V6Oih2T~+".I?1L G"J3q9*FVZS4Gq "&ҢqF 09ۧefeVj8isH ,}ن?![6 "RfsY%$1AOH@[N* (" #dn0k2c-y2l+}b]H3qH\ kvKmfFii[㔅c&GK$Kps@p\n;A z6=ijDS}*ߢھoBӠ=}o26P?/&(-!-psƗ<sWFI 2F+B֟> =<ֳ!;2]Aut];@E,sPvmum?-X5Yϭ["Ff4Ee&j$YN9}#4X vJ9\[gҫć*1>j inב,r+n>*ǻ!5'.uzUlY|mօ!$@0ZAn"F;nWsʦ]Vl&tL:.qRE0-iJ9䚭k M1Spϻ u48„{cӭ mu9.g )8H WUn0oU8iV[EC! ?EW6¿VBm-;9o% jYndi}SUp =YEVt%g29 AV0rNxɸXdF~VchZ-p)kw#niEs;U%?mM9_$JBH UMA 68ۓX_8ԧ/|&Lo +;Mk>(T59n6b36m8Y@ UF4XhD%ͥ 7ف:/Z.P$$95'Y2 UOk.;oSJ~py(G(X|(Aygdu=RO Kz&rϲPڤ:BYcMkKhi\5ԧfDV]"Ur1mdݷ div`BHlm2*zQ[_tCS!818E\ʱd:MlIGEC?cJ2}XbbF<>.d4M{-Rܩ7b@HS]$zUL`RG5obt~T\SAs$UbG񮶐(((((((*+ FR7je'€iU>ۜy*͔mS7| FoO5qG`2GJ? ;Q@ }Dq?k{E*GzAI%*"g5VW24֪rJ`:(EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPPqg9mMP.)63MMKYiΝl*@Aze:l?J}?ͿA1m&ܞ?)]*Nx'5~ ( ( ( ( ( ( ( E>lg=h((((((((((((((j pպ:ЀDJf~ rs )6\S Z!O* az-$(fϨƒE$K$v ) b~q qH@ڞTK1Vԇ^OAW[kd 0&nR45ݺ4𬻂[ffajM>N=jH3*}M r*735>* Վ(oSPE5Qrzm᧌0JVHQIUtd3|<ҢF")$ mg - id@:*No/O_C_݋G<TLo M{\k[ vwA{|A-Y&#=tqP*ܷʲʈ[8KMwXѝFId~5].VG o˷b݇ 9xò8=@zMR]^Ohf_;ܥcMբo2Vp:Tm~Y0.T3AnTO*F[gRiaS2@YVGQʹ'<[GN$u܅U$VxcZqoE^eq^˜ZO +k~OV5c@UFoϕ#qi-Esp4@ڥuMPO{#Xf8i{K f7+ù (&2#zUաٲռ߄P8ϽYy Lg9*Wk{p>O.ަPE#2%w5RMV3: } +]!E3zl?vV7wYp8&[,0gEɨNg峇ctA4t,ۮ\M1W  kuqr9f=X55iQTd ljPTo5hC F1XԘlcOzЬU0*+b'fYOzi OjM.5RUE)mg oDR< 9?Ґ.i?(ҳa6 x4y>bv皒 VmӸtURO)4W=zPWћL<#"@U^v6ݤ9V.U(nnb̝qzϟT[f۸;Ҁ%͋kf Ѩ㵅bp??sRX"!D,{ ѲBp+'궜}?1՚@QEQEQEQEQEQEQEQEQEQEQEQEQEQE].b Jo2ԳER +_$\ZcN5r=zT7HXe$;/֜5DxtqwN[Wff)kҶqF`u(1@ȎiZHbo|M\\k$mzq@ZۼN; ?[[?(c֐QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEeKi֌?SY bk?GT(EPEPEPEPEPEPEPE"ʐG4QEQEQEQEQEQEQEQEQEQETr~S޼/a|s`GqV(((((((((M>7;PYΙjO_V\U*9?X+ Gts_S0kB ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( nj՚}*脶zf~Sz (klҤ/1_^ aSh=NJlHc{V[xyS'^B7۪A?!`L v,2+N.L ?THizjj1NF9+8x \{nZS@=H_8S zn)H$|``` wlv avSAAh>!xG}#sO:XWpwcfo:cAGLREo OM)&z nOrq0hlq_4a]br/jrrhN;DRPAZ>b;St6lHgm;hkmbY̊;v5X'Vn=ʵ #PGm\a7HFsJg8zjck7 b4EFu@\ HgΟa_ur#\ij:dZFwtdQt s[4RWl7 P) t֍$YȬGmJ3ӐqVgEk02݁Ѣ{A{*+HFU[@?-Ę V[HI=/lE6 joIL x1֥ }A}2}S*tvYU(a%rX5fk MRlz(%4՚Y)iZÊ( wl ۃ'FSUƅnduER znfcҮ2R Z(,hjN 1?nHsZPV% yڃ=jZAK@s4$nX}oi)0c}*QEZ 8bMES:4ZPv% @A p5b*s!֝PEPq,`Xp5%mi/mYEAwg >TYs0jE5E2(8Fo, ;:a4P+}*U%m-U+_2H$*!Vc6g4+wH{8I_r#5,\&eԟb9p0:*sOLɀ G?Zg^@Vu§q15-ͬ7q0GsVZzyՏ$ HtH%*1e9T}#\!bA"~Ū3/Ӆs͠ K}*a*!:sX#OO{bCQW 7M@Z]Y&@11;(lvwX`zy9tAZV4sm&r>kl+$On~V>iIF]Ԋ*6NI7~+V=A #Q.zIFR?~;<3!آ)cIhPXprF1۵wTҊz?sv^Me9 H&5{y.t% V"BŠՂjj`rmJZ[]HF$L1,hQ4̙?*[g{fudKf*@1nvwOXFcp85Yij$g!GjjvjH!` D:;n/xC!ZOQHۙW)QEU#/Erv3Wk%5t1PSEPEPE\=+LasPCEPEPEPE\Z[o#hQP\%պLt55Rm)h((('Dc)8ԧby랴hd.nsV\Gnvp~e!Aat.?:Zyn!́~yܽ(֡1ʗiWcۑL )5qр4@QEQEWR2-EZnNm?CçY[֭fJ%V ؗFzQܵ͐26RA?ʬ'k*ʚв1+#QH(@A( ϲY0q]8!5-mvRW'qI#}qU8KgM=N!Lv.*KoᷙTyK#RzUo^B`mfoeqdJh,VCoHN'F#kWH%\+:=A "-ܨ+&ѓs}(ǰfR|Zfci:$3s[h4A{ckZr239?[/XY g9D ꞗki$0I=I((((((()M>/t}(K kRѳk*WSӮU]L\p h9G7 1u@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@W]76UbRs(%SBbcF@WMEaV`GkB õ@W)zd4NFec*k 9 Io䑝- @7͞O44)ĉrBǸi;}I RkT/Xė#o?Etk9˟'Bbj 2i0cN`jiX@f ( ( ( ( ( ( ( ( ( @1ܟ-QEQEQEQEU Sǡ:q{qA> 袊 K[gxVbx [g:RP8PM[ :'?cY_Vds``3Ϯk`e CL?µ%OT^ֵIeŶok= qfN٦Ԍ*dWڗTm4oX$c $Q흢t]-?Zѕ5<@38dT./_j]Jl!;##4g-w iЀwoSՔ|oF%}q3m0N6֫Ad>xhhuK# 7h EekMnp2I vUo Wt A٠ I6j&Ȧ3!aUy͂BUhe)H+V4"\3mCW_ȎX玵Mev8[ 8=)61+;@&>ZRX U(FۇS@V=ψ-HFN +&Y#eiQΗ uh5Vu?+uUxqw#סj- # ;_WDV Ρ@OZBK ƪC>~IyqbU}B. &PGYe1l\nsQ7x¥_t>2)A}\yW2T5L!N}]F3 j<?VZ)j^8L65 YQYy(Es#a Zȸv$9G>w9eP28ɭ}9nJ:sȹP0ҟgE8s"zgޢm.c>Z^.gP@nM-PEPX1ByqbN3[ufXPg(EPEPEPEPEPEPLKiN!sɠ N?r8̶1厕r 2?5rjMC8δk7ApiPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPHr{@4𪓅Ӑnc]'8{zkuڙ rie(oeRHs)ݬ; S^.stn â7(CΊ,EPw~t5a2_ድ.\̱" Ci}\%I&-g|1C=k7ryH5C=?,g bxTm7y*K~ud,TPx[r;pր:(Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@%-QEQEQEQEQEQEQEQEQEQEQEQEV~A#Zj[܀N8@4V@AH8o(`8?S4V+xBԟǿ?EۢT?R7@5j!:_HmQX F?܃n ~Aǐ8z,4et*DT(g&w nr<]0B Y &ۥV"0 x~W:-id_(n9ʹoOWe.1tT ((((((((((((((*%Z[Q@J>KsnV/0D_ 9E>S0=: FKH I [KlV7`f O^) xGf@X1:P:줄}2QȮ[N:mI2>*:?jERҠ;)'M@[].`I SnrElwR 3#In? $h3ir^?kne?F'Tݖ*a{C~ܶ;EF̕ݞA/7u=|=d>aPM!+ɸI涴{ rH*H4($$YeXW)OT{'H\rֹ^y6S¼һ(ӬԜɅ[=a[ˋ}1@*Zbhrt=A&7K'!d5Yy2Ż/.0zӖVcP0li!gnF2sҢGYX󸑌ƻk!"=XM>_ii4 7q;MGM[8v, (xzݕe_Xx~I7H$ME K;q eN['5=PT5=1urx5~e(:32MY[h?-a@ 1R@ionCޛ;L̎v_I}Tƀ9#2 7ul*̊#4Aɮ4ƨu@ZV҉C!S.k{L]hH u>15$vW#= Z[X# ;m6PTtp)P`cej\9pU(aʚ5xEI,h zS(((( ió `UG^R\zhۭ!lc1LN$h$$*vJaflsƓŏJ]HGSZQ[b_,W՚) [ 7n|g;kA-EcQTRh0Ӄǵ- oğuzTPNium}us r=y&Ey/!sҶ)6i5pm*s<\j.E4[L#Յ'?*x5`reS]s7c"Jh`gkكԩ4w''ݢQ v晨h7dAJ) (|7iY &%nС"sʺ6u_}M( ECwlv A#əf@5QH 'l4^ 98LWEE;QE(+/^CPA`{֥!d2.U"#`x5qM.cXEt1ARTV3j9̗262=꼚Fv[O oy_njqX Ð}X:t [̀㰐Q--=9٧dc`4VPX݀/zv`?D5!,O\J4(6}d Gq֭kxR?%V=HF0)Ԙ(((((((0=1Nɟ-)˳JZU4ߦ[60|VapxUuXϹv7 vV4:Ѭiδ((((((((((((((((((((((B@Z'~~jِVmoV(r6u7nQɭZBh.M:V(.]Fhס֙'|mE#p0080\ghstޢ15EñbOV`\8"hq8Ϝ+kT 89ħ>sE 1AG?}R=Ed J`h mGV4?ۗßz6٢NdE.{d0ġOrh-E(D}sEF5;"Ey5K#(Agl E/-_S 1z`uKQ&/I|v@4ӫY g@ܜ"PZ)o.֣۱"B@S@m#)E?(^쒑O H@?dȤK+hGo@5|AwoSڟ|S4!ů>֨'~I"ng**t`:y`պ@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQ`x*۬O!@m/JZd|aOTjZC7[1\GX<[4Ǚsx$:=+"S\bR Z@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEf#`^b#V@:w98qZJB@@C}+_kWıhs f@Q@Q@Q@fhez?iRxTӢS U,/dڥJ};;y^5ꄪq@QY:yvT==+Z (REQE)UPinR+s,e8h7ΏxO1w<.gX, O\ / HoHet#4Z(Q@VOo0!Nܳc +QLH'#9#Ys_#-&9`;J+}?V=$v˟Ze4 ]*Ǯ=(r(EPEPE8ހ(((((bN8Sdb P-?6pzsWꆆX6庐s@W X\g~bj:}*>:HvviPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPLS;N:Tzn|2z]efb9̍X -k1/QOuleUWnz)9GEg~,/8w٢ )H#el&2kn:A(/(;DФ!W60d #(>`W$0QQcµ"*?/( GHK-Vϖ_ʗbt~T Sn?R1-S?*6A@:ڑy=OV OٿͿ A'L|V5Rnʚuql utVe%kicS@tGyG2-\r[PdF|Gy0չEakjY6SSe(:m Rؒ; ? TgN7x+׭(jq?֐k'5EbƱ (0mƶb8oT5E~?)kG{kn..l"Z>V\ b} Z#+f.9Z? ~jO#Z=ճEk=R\yAV\ oj`])Zi~ԙ(ހ~ ߎF\ QŨ4K9[٢{~Ɛi7'\ aA}֏'o5EhX#4܅j2ۮ?lE:-cuƗ`A]BQQ׭(N~Qק&,rU +^.\]̉)OPTsQ>w.lJc?]Or )S*EE[ԊrhH.MZPwI徺_KG9k鹇jQ@e_N€;dj@сnn{iciƴh ArOo%i(8h{= ƑKSҢ3m0F=%aG%o8VѢ3ERHY94ؖ=cv?ִh 9ȈmؖB8 o<~EgO~,MؚhkB`@N=)`.=2qW KX/KXI=cP1Xk)3nt&X:|66ќV N" hU(XZ7޶7.}bjO.h}gEn"7qZ[PO?ɿ3I똫SJe/tQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEV/m=[UL8rtS893U%QHzP?F"1XǗu>qɭ`eH,pY2rֵ)QU/\$҅b3ʺ_&T ,ˮ!^uvUsHQH((((((}iPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPv?Y# |75v#&#I`WРT77r%Nn{K;(1""(;V xV8vK]&IIJFN98z͔SR +;2=>=:kd2ϧ^+K$upNShRH FzVv-;%`&NO8^YI;>AuyK`f(u[nS#9dž>oIAȞ`zumF"+ʰ4[2[YQH z[nś "X ke #ہsTFknPSjY( i.@+>j__q;NP-!8ϭm3iL/8QEeד.gj'[^EgYR@FrZfS2+gY78>F8EgxzHw jgj.B_8 ]G=bsmmvjq(8<{b+Iax~'"4#4E(((((((()p}Ԅd@4"NNOЬcJ{kF d=66*O2T(tܜdgZӬ0ujPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEp8WO.1=5nivUb̔xUƬ/wcygTRš9\ec_E\:uV- @ cp^ ozbiVQoӽv5^_&gCTN#c@^2&Z: nvʏƱĄFNXchyVQz )i23=w J<8sS)g!T {P}b`-0+B (2FFGZZ(+a1Oь3sq] H@gU''UH@YA4) !x9Zg9FMjR((((((((((((((((((((((((((((((((((((((q\T/Ǭm-#2ߐp i6?QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEV?3kbP2 g@EIVt" $3UfZQ`fyL@Xz֮iuRyR$c; 1T!FqqvHdX9FM9] X`ޝE 2FXZ.c+1@?hQ@}0y)* qLuzkN@(.3ͷ ok+Z@p^mmd"xq#EdIo2B3z٥8)ej}3JG#n =2Ifޱx?.yc$O5w#t9L=giܿ\,r6sZIZk!$2ܞvFge" IQմ,!w`r:5ٹQF'9QE }dcujEq*iwU̦pByo5:+:f86D^X`#v"]0m ]%sRAu^w}] 9Zcwj 戌B?J@`hVp]LB03*OF}c@Vg0ޢA~գ,*ۻ}ܰȦ-Cip)L&ik g qڬi$< 5m6c$du7_c8\WO\Ij  Z,O`X:rd$WRes4T 2x9Kx.YXA3֝ Aى?.2-[`sb3]\2𤱜C8,mm2C#V(Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ A-Pv?ZʩkͷJܨE^R)Y I=%ClB#z3M5Ɋ"^ s\;]*Kր;{aYb9Vk/@GK`%}H(asQQ]v<ffa`AsLlo6 gt4.UTk[=Ĉvh\WSW G~n>~8WkQ5/"!u1%-[PKkV% Til"zHXxqz(EPEPEPPCSS%wr)jfVv (Q@Q@Q@pS!J\(AEPEPEPEPEPEPEPEPHzZ:g:ZgЍin ?;3ZTS&8&LS27?zlVQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEKM~Z59?AVꖞ~{'QWi gY!*z{v\ܜPuLk/"9KEFPdPkw1^4Lxuܿ֯ο$E0&t+ޭ"\U{LOV`&( 9=-&)_N@QEQEQEQE888=-ÅČ}!8EPEPEPEPEPEPEPw((((((((((((((() -!dRqN(((((((((02 g*>FRJ}* `kBhKWh((((((((((((((((<8Y~ ӿbNma?STD1SEP?pYk^xްVe^65ͫĎ2CWY oߊ#Z"x,C޴mn/ʟ2@y"UM<1A>Q sDzdڐkY) =xdZ$[QH^+KCrHZEcN;ն!FIGY20e= K$I\1g1 T]2n`2=zj ie,軙@/4ZIbriwXĜgh[=$m<jGe`LjZ{s&(a52R5 Mަ?$ kv0eɊvD\[Pİđ¨z֖H$&鮬դ ȼ1 S$"B0U$N9iQEQE2HaC铡8hu'=quq-h8cSwOtZ cdh$sޝ(vE$A4V{i1R0v@4Xm HM$~5  ,CythgNZ+[#Aw [3]lƋQ\r:ĆQWiDcǪ`::+k]qٶ>eZaՇEѢ$mS8эƭ8=E EPEPEPEPEPn30t]1T5u'rmJ:Y`K <_]D,s_O58b$ Η-ppH'P-[\*2=OcGmQʩ|znaU>qn$Enp4_풘%P$A smOKXhqϵI<vO_q@ab=čЀ8ՃU"n#kE,!F:QD$({ZQE OB+Q%fx%wAW4ir.F,@JŕݪZϼb!lP]5x=ij3س}x4$(KZNQ$ d#g #x ֦}AXPvK1 õУ~f{,5W#\ku;\EKi^KZKxI%83W+<m {J* =cEdN=03Xv#Vfkq0!d!Aie1O$j=iRČOA@^#Cta}=;I-` <Xc2e=CoamlۢhH`ePByA.DZ$ʺMKEO7h Rg"̞cLT\ }h =[ⵋ˅vsexlO0nsqZ^^ZI 88>eP%4d Um'K@%R.yOjl%T}oL` ѦF.순HHmXn0r95f@ssx~e 2xy),6$cZ4P>|8SuLj+$=kb BDU_$2$O EW~0O~5Ut;E<= VvQ*袀 ( ( ( ( (3KWR5@(ր1@RyEZI;PIT0ОAkw/Gkn]YmoQL @0è=iO].7y]=o]\-V&]FD`TvZWED< e*ZdG2B*(m}޸Q@Q@Q@p@)kW{kxek7k`MER(((IT5"xtM{EPfQEToS - =MMpJ]DIi] (3 oi$JEMљq5Sa 1%טWZTR5Y"3e BA?34S3SSk9h)re>aD1Vbk=8 ڠ*9\P4(fMÏQRP YمQ@(((((((((((((((((((qkPcEO:*U$q?5j ( ( ( ( J(QHzRZJ)h)h ( ( ( ( ( ( ( ( ( ( ( (STDGAiQPH+J ( ( ( ( ( ( ( ),@4V"9IEPEPEPEPEPEPEPH*c9$K1$> o1h[_9H_scY\KEj((((((((((()s 4GAE0MhVn$~*((((((((((((((((+/98֥PB27 O}s_Vj[gy@Q@<Y5XtIMlS+SqZՑjzi$Jנ(EPEPEPEPM(CiPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPviv^Zo* rt*ZkÃy(Q"FYyK;Ui'Ӛ8z?4k{m)ei*o''02L/fq3՜>?)L>S|˷o=Vvv\[R)bp9 cMN[f!?Ϯ!#KO*s@?\%݁Y66WZlƧVo.[R+E&LV5(kyU~Mp.W\έqȏW#ku;A#tTTVwN;oϷjZ@QEQEQlGLt}*2歧J e!h#PdRz u%s IES$}  +{|fX'DVcNyHj0c(+ ybҧʈ/~%QEQES]Pr2(h sҀjH ȥ'&STrBGj}Ղ( c$3t{Ҭti֭<@y?l??l|Ҷ(/65=2au₢FS +F-{cZuiQLw3CD.1j=AAٸ5H27bhPYe-1j(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQER7?JgӞr jU،`Ɵժ(((kdY\)vڿZq\μYu>mVߟ۬pd9`"Z7m}kPBNHҴmA3u5Gzu%-Z))h(J)hwqYdhONPYJ#/QEQEQEQEQEV%ƴ: gi^ֶV#ݬ!*}N4Vq .?jMpK"*o% >[Q޺xc{{tQH((((((nAGObܓE;_'8?JML M_SQ61g?M6hf>t;lO}RA-QH(((((P4Y6ư`# P0${WHt+B|u=˱T3L t*PlqJueW6qK2mPjRLEQEQEQEQEQEQEQEQEQES_7ҝHt(#ÜZ̹ ج,x@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@g뀶uܿδ+?[Ә &?mʭU]7>9j (1 $edh"85L d0%VdX/V%-R(((((((((((((((((((((((((t ?έǔ0(BQK|vǤE6B$#ЎnH2IykZ,pBl`I)_*ͪF#r,jl'XdC!@yVFAuV)RݦC?X W!vӚݢ^w,05-މouv. 1EjTs n3@ eCXȽh56-ٹ@F0jfo&4ټ[i b@qv5hapK2ڡF8kH siVSd.NI˧MU(:}mj|A9'5KF'2`zEWi< FL0zN ( Cq֖jn7w|tQES>֭F1ҫ^*_0asPg̓S@}bR=j$#®cqzV/VAtFQAQE*yɐGvJ]ݸu5m!hruUQJu KAUynJGR)>_9* (-Yj:L>^5-U!]jM$ (FB Vyp:PqV֫+n=qApkiR 2Hސ۝#hGkh2

hȣ dQW؉amj&f!N1ԵfA0I\Y-r2 1i#oOZxK)7 :(ɿɏ}51H4S`= k#Dh_[2t It%K9/4߁Tr[*@SL6|Ns@5uh(۞{6f'q%et4e&;~cԙ [qҢGk'KةbUԁ ~uzY4%+-f\؇m:¥TRd]LxrzK;„}?7nZȤjO]7:#""$$JZf\ܤ6 5PKm(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQER#}( ^Yvg3KǗ>@ KIE-Q@Q@>#GXgJ8സaTucԐF]@vQJRQHQ@Q@%-Q@Q@cwj'oZجMBD[T(#S@U6_7E|Vz3¢Fğ-ϡm=hՙ(˜cRP%}+ .E}3Ag42ȤKo.IkXgf s\$q]JCOҚΑI7Oczqڌxl6>[35L1@O-I$6k2 Ki"ꨦ*FA@QEQEU,pY*b3ArPcPf15% 4wk\*ȪRmdN9Z IߗH=q֮"pPe)_dQE id0sV(ڰQEq'%P6QE ( ( ( (+\ cUoc9W,s鞕50uv]!EM4RF)h(NmgzVcs巸O)lzu!h#ë!?Xϑ8*۠6#ZQ\@Q -JJ,02NxWVn;U ։;QAQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQIK@Q@5})ԍʟSvYY&yG= iS)QEQEQEQEQEQER ;EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQIF-c2H=*/gifg}/}gOCrM_= Am}mw̮GP:¬Pn骪r7*:rB ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( 6Lz*Qq.}W]ِc3WjsM^( m"g#yЍlVF5M}.?席Z?kPER(((((((((((((((((((((,Sz} t(((;^c}0cMAt/[1e2YZUɷ.wł+nEތș巷ԎieXyaO^U{-Vye^;°째E`sfc.>r펀b:[{X-"H5,GRkW"c;{RxVk;vLnrGP=PjY13= ,H#Iob.6RC$ XRON?f/dǰyETИ.qrM V %I;GAL֭g;C|jru>Kw'q#nTd~ \0ʐGXd6IèS÷B?8_Bx fѮQrm'&W]1\~kěc׊۳У\ہVPjN\W+l[VF¥O`G4|E O$|@6Q[$TwHBx\?րJīr5W#k@c2}?UP(@QEWfTZPH3:ʖȎv+ Qh81<:T6WVA#H;FdĮ;y V܎*s\iQA!EPEPEPEPEPEPEPEPEPEPEPE `sRGܐI\z>U%h= -!@.?ζ,j@kn ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (= U]cZT}̯?".#y1(2i-59]֔jOln(Vw8ϦG!EѢN(BpO˧NJ,vĪgPf9Eբ{2^oOM4/EբN#50FwlQYvi15|iEԣj祴{njQY[mƌ=v}2hRƲ@T`Ӽ\EiQYX͸iBO?쟭/ER?P.9lKXH]}X`?֘bF~!Ӭ| p/RSj? T^QU(pRhHcL?lOKMذd[@:C)Tci|aJssOǢW-3RQ#؞”kVg7UAK@Z$&?Hm#dkF]f8XnIlI*(;Xc"*Tv =PZ4Pw(ѹ%Ѣ?.֛g2\{ȹ)o01Js!3ƘkJ`fHaN7z@xF+B7k(i~GpE3퇾iw<_ZTQp2ַ L}M/N?{էE0k fKPO .\kQ5n[jZ/6& EZq}fS7?zAi?zӢ=G_P, iu Ң}}RLF)Fv?'1 Ңr['SǠRd.*(-FW#ukN.chstu4Ѡ[g&[mMj@g@??4?cw jQ@GY3/!Ԣ3b3~5ֽBH΅[~6嘜 EǓ `_#ӚѬ=~}y((((((((((((((((* &?Uꥬg.vZn T4_D= (zyz3θFZZc+'_̦QE ( ( ( ( ( ( ( (8 sϵ-PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPvf_*m/At~?H;sU䲷_2Hje*$3A6qQAiolX F[Q֡/ơnXUij )+ǥ:;_!@1ثTP-3N]:'Ur,H:}2_6XA\5hpRiċH#!DQgZk=庐 gV-3vfڳ]4Op Zи.axW5OOѠȬI3vENM2K)Udt'jQEKSơ 򌍹O^ج_;J2wutqi/3 (Z((( X=XtTjY_cByVR3|7y#A&eS?:]XaAK4^f8`*E4eŏz KVX阄c&arim|@V=).:TS#Ob3RI0`X"io3v}MxR҃HvrI4@ܸ#JAs8E|O%QsEI򦖥zF0+Fg;yh=[Iʤ!R/dQM+Jp5X$!Qb17ùkpr1@ bǝ)>c/QUЦ?|4[29fX=MV>,k )`VbI^ާ>zғsZzfrfQq R@< Z ޛQ-A*lwadis0*֝ SzK\5)݌qvjmz8D;(_ZVR((){PEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEc=i1KE&)h ( ( ( ( ( ( ( ( ( (3tO|uY((j{\5i((((((((-VHSPOz:LI[|4M-fLAђG'mXa2 JqڠQX\[E(QEQEQEQEQEQEU-\\g֮=WAPZ:bа4B5@Q@:&ÿkZrEv.[5M*OQ*iEegL>+N?k(Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@u?.A`W8?/mܠ 88\h*/XyyvuFK'M:1ݝO,WԓXgqh.2FpOYך{%pUNvh IoQT%`IWAG0>խSPR[ H`4$WLɷ#mexጄvRky6֘n!f;^_hɷy@ݴg㮯gX(=Er[#WZVJ,qs(1FXw4W=oj7[{xa׷[3HҨG PsHEQEQEQEQEQER' vr3Vꥮ *܎w) gK1YY nR=k-32E|sSR/JZ m!QEkTP4@QEQEQEQEQETN&g_ QE((WM q<hGA&RѾMOPB>mMmV&^6Oặ3 ;QEbht:cBkn4vIl(EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPHzZCҀ)XȋfB8`d{]B»[+J޻ -ͺHHJ915xZU6H-wMHc[t MX\n\(;\sʌR7pkO#sN+"| GJQX:\o.#bZ:5ĉ4 k/4}R[LdG84̖(C+{y7sg;QZk ܮd9<>3c*@\P2Oz\gv_6_ghD +w֨/qr}~O=OM \ʠivH$#6]E̻9!2t{܍~u%QI,1X+XDP8@&mTet" p>sD9*Zkk!(9pO_RVʗ 3W9jQH e2F'$pۢ"?MX ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (2Ln Z@ wJҦ(@QEQEQEQEQE#0U,zYr׌DsB5$㹦Tc5=[QAEPEPEPEPEPEPU5OjV6f3@4JtNNZTQEdݜ?I}}Okh3iFшY ]cz_W=LMER(((((((((((((((((((((((((@~Av ֓Z8%)4BFQE 9^ Vǽ>@pӕAkacyWjp47E& v 'ߔd+J@gh{E*!z I9^N*VAj>e( v[)53yz,x5zx~t6C6~zQ)+un4$_ 5^ъI_kQAR3cKYz8# yb"q/"T4BxvaP1 RlQYZifI<dP_'EfE,ɲI  ֟Z P( aLB>aWYVdq98*Q^@pG7C#и]MTQu4Š(F&gƶC?6ER((((((((((((((((((((((7L-A<bեYz2Ňޅ0~B)cIhPgv9fEPFv((()(=(Z( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (34~Ӭ$6g@!<δk3AN럝iEP&?8IG}>O6vƞ}Xx ?S\pP(Q@Q@Q@ LPEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEi3!L'L\}K.|)Ѝ04k/]$'Io+{S~R}Is3&FޯCЮo 'X8P@ֶ5-fqw\v%i rB* vre8`>][PKTbh0ʐG-Ql^$J5d#sR^FUiP~nȯf6,X!GSBzʋX@eOJvX:i{I/y9MwI$# G'n3iApAwfɼx؎@r ڀ:it  z k]"m"qsת:EޭѓC1 }Ԣ$`P RLw~+@AQol5}fd1=>c V)_)c`$dWE1@aQ}bp U  GhR5tRo X5;Y֭@5yngV=ׇa985Q@:\:L1;%1=OqSRYZ2͏1q+B{jqA5&L+zi[ZH$INjPER{YeM(*)7kp4.G&)pŏAIE=-55PCww (Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@:&v_oVjܦ(@bh&űm&1#[tQH(((((((((((((((((((((( 6{W1S5](@QEQYچq@4T693ڦ((((((*`dEP}^[VLܚˊ,jڀH*;QE((((((U$4U" ^֤[[vAhCNF)IWk|۽HTcؓںl(EPEPEPEPEPEjH#(ą$ p+5x/!;V&ɮT(wϥb֦&JncS)QEQEQEQEQEIXS\ͣ0c̆H]e"RxٰbU ӾZA)>Yyjn,yR:{a") >(EPEPEP~wgY Qs6kF ( ( ( (bRIҁ}<*V6BS#F>aV`thJZ(((((((((PEQETW_7T 6~!ZP0G(DP۬M/=yЫn̿Νts4_FOty3G@/E(((((((((((((((((((((((((tz?WvS >U>sN(EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEQEQEQEQE%-PEPEPEP?gxzukzVrwll(&Ƶr=wxms ݦ(@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEP?e}!jY`h`QE ³y&EuR+R?04kdֲpf@ۘxVt9E!wn=vVknGw>( @QEQEQEQEQEQE6爓jSֺJʟEI!,4V]QZF !ϩZ@@;ʳԦ,~Pݍu4Tb[ZS0OH((((((giL.I)T$U]񲃌3\ڛH7tZhkG%¬|b*4K NpyWRYhf{ 6WK$6+ߢ)QEQEQEQEQEU^sO"5zVf@u9\?IV鞌)W_̾a+~O9s>5n=:S~f%:#9jmڱh!Uc]OViQEQEQEQEQEQEV~jX0*OJߪ IOeb3E_ZX'$AߙM~usJ~,^c#`jQE((((()03 @zQ,mo)HCkz((((ӳD1GZ5B'@QEQERVvs٠ŲIg ޔa-M< !9n[֦nIh((((((((((((mRTs3Ä9I(zBzIؠ(  ջXIT?UMGYl9\~bDrӵݦM~FH:j:Ң)QEQEQEQEQEQEQER M-QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEOV뙪i͓eb=ާ ۟ʫxAN(EG-Q@Q@Q@Q@RZ( ( ( ( ( ( ( ( (9 ( ( (-Q@Q@Q@&y-QEQEQEǿ+z0m)?*i (K]9xڶkϟO?`QE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (3~縇h]df) (s\j0;03 5sM{"OP:ԖĶ!C ^ D_evZЂ_:GXr)ZQ~EPEPEPEPEPEPEPQ h`O )*jl_QhJ?M>|:Ɯ΀4h@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEVԀ:u= mXw՝DgO*8I~Ԣ)TLJ')&-BI[̗FVboj_l#xTQEQEQEQEQEQEQEQEQEQEQEQEQERR@Q@Q@Q@Q@ʳȱATt[ָK<iEQEQEQEQEQEQEcab'91n|_]6ERC0nV>#`QE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (33#Qõhu1֍6ER(((((EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPutUYvl#?\3 Hϭ1K$O>g->89OH1#Tc4(((((((( Z( ( ( ( ( ( ( d18SO(G?_UlPEPR샕-5X3oX1ۦg:|uPD0U@T7Ski&#;zV5.gHfF!3[0RFg-Q@Q@Q@S]U(59 N*(+RZ!!q`jWkQs)7設a|_jPЩVSO~3W+R?`QE dvp;@Au+vc)zQ v@:*J(OFQU%XxčfP?n#tΡ?MOk(@az1XQ`x{kv)QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEhz$Z̶j|v_ V6ER(((((((((((((((((((((((((((((((((((((((((((((((I0y &$$PIt+£X|Z EuC ? DT(f[ ՠ&Š((((((((((((((((= -!hÄfcJ٬o.ϴ/#[4QEirn$$-_%pv]E6]PܓSmN9&o~]JҶd;T`KwE3 sȠ F9#WnۦF- #>s0* ʁڀ5MI"DHq8Ҳ,!  ׃Xkɟh%550+}ERz`EtF'/ HWJHsam$<.zn6I\V\KYʑw`5<;Kum[ila%,']} Yh9 EIнtd T(3m]r`|ѣg*̄x0/:(((((((((((((((() @'u=sbx&k>G5MQE% 9cX`y;_l|WOMWSu?UK&.1UT܀qW |c (Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@%? ]/\ Ѿ*,n fec"(f( ˭+ƹ3H˂*{/+jo:l{^sq yO5|0L : S|}vOG$vn.*Nx kxQ:Zp8!6ElZiqִ.+y1dUKdHc裯h!4+-HD"nQE5\ Ƣ}U.A|p775EI"V;ygս?K_ >v{Ԋ@v\ ?+gwv:^:}(GE yϨ,?TçakÞ)E>>V\ _j}X^`dj.'3vRMd}G[Qp1Xr?Y+ZbX9yﻧJںE°Bj٢mqp֣?Zcux?ƒXϹ#?ʶhb,Ol P>yyxR5Tc?€7h-R3M8>LP4P9-}u Eb?R9=Fu&97k + i&d]ytEaǣ׬f?֗ק3@tV0Sx߈?JtikzB:c`rnicA;#k<e6Ïg3>@b}:i%iPv[2~7票1܅zbHIEsWڛ!C mu 툞>^ @p3]5OOk51r)QEQEQEG4Te})TR_ʭTN( ( ( ( ( ((( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (9_rLџ+n|< cNS`QE 0;|I5X7G*8L~FQE((((((((((((((((((((((@?PO+N!uaX?Jl(EPEPEPEPEPEPEPEPEPEPE((((((((((((+/]V#d8w5\vqj2/hAM]<>4&kYq</Zimwq3*uǯ HܸP9>3M ZCORsV㰸&xW Vcntb@*IQ`7$g#؀uۿp°+ZOkV)QEQHFihFAȥ(((((((d1*laV0`EPEPEPEPEPEPEPEPEC=63H>Y20e=EQEQEu?ay\Xʸ1J3KkL0Yǹ+@i]3[{(nKm[ sSGu ܢŦ3q e +)l/_=1M6x8TEcfߖ|w9t˽ B\x_ROm:蓋1hN˹#I]&|Ұ Z+,/aѦc^ɏbG z+/n-ؖhMMjdzSHt돯 s'CК#VR1]Կ6Y.ޠ MѹGβCN3ni?J8޿l_γ?iF0dlu@^l`gztyEf c UЭ>8 ,c?iTN\ H;u?3N_70G} O>txxU!YӞ3@l?Р^qЪؖGS,h q(7UVKQźPhQn#iuCim9CTh Ju+A`?QhY?SGM4AZ4cߡɴ !9Ɨ.9(OѠ5k6M V~F[ UXgڕlm+FBڽLBL85/-wp8[A0NzQZ ~SMՑ#ǐ* W=Hu qjȳ dRKv0FT:ՠd *̥0 )tUͿ,?ͺY9OCy4hE1$gsN:Ÿ8+ >b**)F>A -drI+CȋQc/@[R}Wb ϷZA@TSD<ҁvfs%"1+E"P0NP C]3ХqMjlOK: el6R**aVLQz@-ʽmu4kr3hG [J]^[&*:_UQfXc<Ed.xbI]GٟOV>qnHSFt_h+fѽ*HǦAF ~ lQ@R,@N(5EdUoj"ɪ}Ң2m$ɦTkEY51BGgW9[sӢ3v|]&h M[w,N\kF.w񋈳Vh󟽴bhd~TRua-}1kEX5Rg OF?lEKmSLm\c?¶c}WqH~ :Zbv<ԿlMFi8Z(wI:]?MkE:]R{j#^1 շE K7yhŸA\v_v_7#'uQp9vrp$b2{S뢢4*}?Ҧ_ q?z 6ienëɫ4Q@Q@z[)nV3Zd3ɭ`QE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (2#[^EkP6O'MQE(I]cjr`ڇ!z?ZOMa>6`l|KiF% |j! QQ@Q@Q@Q@Q@Q@V71@n%)8 ˻|%>Dg*SZ= HoAY " (Q@Q@Q@Q@Q@Q@ E'UFx $8M{VXAu .""6#~_MڋbI&$i&X]|]Zgmc֢)QERRP\׊aWKX>(O[ELJhiSi=$R3#n6svQՔ;KHq~Ի7 ?mC~oe}V*fmL}홈1!]^;à]y@QřlnJn}xPr/žY@~S\%ĒA'&r }3g1>(ە:P 5]滪c-651QE ( ( )8fT+=i]رEWI|v+%2I( 1tQ`&i|/.@iTXTB۟o9vr7; "Ob/OТeҟ}ƮQ@WB449T(op(QEQEQEQEQEQEQEQEw ]$gt{3ZՍzAj lI`QE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (0k|nV&규rKdmA99 ER03 EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE#6$aعg}#^®BEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEbjMZ#9%Gkn5ڦE((((((((((((h IPEPEPEPEPEPEPEPEPEPEPi57EiVS7N!^s>ZQH(sQ,9^e:f6EqPf0'NpE;HkFcc֮iL$}ESAG3Zh* uQH(((((nEԌ;w#dy$*(ms%x̹VV}Usm)gY[I/? ; Z(Q@Q@Q@Q@Q@Q@Q@E#19%{ԖZl\3&0\PEPEPEPX3cɪ,]|_rGTbOn4 aaUVyfP&] oɎO/P>r0gR=z.8-+KH^b0*bPʖ+U$ЯP4[99ɩ-Yل,X4Kk54L@cP2.+㸩bDz}*G`Vd'odz Z3Jd+R+gPNzj{3Dɱ@tQE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (2OlǪ7J˺>qjS)QEQEQEfPEPEPEPEPEPEPEPEPEPEPEPEP&ރ۬M7jw~cMQE(((((((((((((((((((((((((((((()oʟU.̙b#A]LFcՊEPtQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEbj fͽ@ ׭֛`QE ( ( ( ( ( ( ( ( ( ( (2azZCҖ ( ( ( ( ( ( ( ( ( (2#\#͏Vdjl(EPEPEPEPEPEPEPEPEPEPHI`dԴPEPEPEPEPEPEPEPEPEPEPEPHii6ُ5VB9o]1$kΡkrZZI1z:s:fvq"wp+gWv%ڧΟ)^ۿÚӬ[Vs"w\`VnLn]h81ōӫ H0 (]* #8#5Fͮ^Xs]h,ȌWCʾƀ:!CHds4[[| r`A@ \k`1]?Sn"նH{z晬qn$trcY PybQ@Q@Q@Q@Q@Q@! ( ( ( ( ( ( ( ( ( ( ( (3.OlZu{:{Zt(@QEQEQEQEQEQEQEQEQEQEQEQEQE2I<WdRҀ ( (0H k[tzgMQE(.1;0ֳtjcprC#g"'?uI#Օyp ?9iU$[fv8-AyAr)] vQER(\m1{yoJg9,rN+jrhLPGIDP:7jP@G.^(Q*45QH5XVfD Z5#'~BuDg'"h;h~m%MjZLsK@Q@Q@Q@Q@Q@Q@Q@Q@Q@88:uQEQEQEQEQEQEQEQEQEQEɤ-ߵ>Jyv(ʎv>> պUETRww (B(((((((((*=:(((((((((((((ɥ ( ( ( ( (0u{nsŒoW?C=Cژ ER(((((((((((((((((-ޖh9Sr;((ƛqkf'$x"Mm`QE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ()jN@fxj,Mq!?ª?Z۸΂HW;attۗFpژ ľLA5}L0=-K};Wl s#Ҁ5d N}Zkwq*=>KWIE!jR2bc?Y5Ke+ǻp('Rlꓓ.j3[ lT0Yrd9#?ZvoDϩ94ۋ|ۍ8h2@~wqYVT|[_Yn8R[g5bXk B"IZK]ZO.9>rx 1*N5]rz-}T~p ;n ^JX$r@EҗRycIa#k. fou#kNn! q"o*0SVp>2?5\b6׭<8Q\`T]gdAU$Vɂ$IYxv5f+Be6}::svX _(Sۙ{VI,%9ɩ]#JuE:@YWOrW*y>c9]5g9?կ!x(xv.6N9UTRd▐ V)."PSإ@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEG4ZűwBizb۲QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQERn:˟LP~D%>a_*v劉#0d'-Р EdM)>myus9j~lEPmfzkVD hQY#2w Ic?@tVbmdNn2 iYm `CNmr@'?7ԧW;ZjJ.̄Ve$ZG)i6>S04@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQER0 4}ݩ]PEPEP%Lj!_kn.H!5MQE((((((((((p-޿:o_ΓO@fFL>)2&KEBnɚ?S q@hGThZc>zcEET:BqHڭ\6f?IՖqSNhQYYɦnZ붋Ef6fǠog#mZݶ~~#8FRN鷸!)c^.(:+l>nL;w?VCVR0GelM($ddӼ۸i@&A9>JsܐWN~<unhF#QS#9Jj1Yw祑'} k+6=I$T38P=ik-F7q=aq|-6=[O4FJh ܧ".wj@G2*{GP7&r6 5J[/Dz3ͣyn5@76nőζE0Os5ƴE_H!]Z I 4QprA!͞Z=7eUw7ץ^Ep%_{~[t$8?ukD]3Tc? /ivm |铜S&}-z8U?`o\f[S'Jco`]Ĩ=5Di7Ԯ3ߦ*13bMF]UKvrq;[BLk垪exI_W'4.t:ޜwsVԥ&Ƈ#0/)A?(:P>ρ pi,tK)_i{G} -$&Non? in#V񏳏Ni?,ßrƀ,iY_ФӲ:AqܮMGa1s)>٣^~&˺iX4a,/iۚ~3㦥UmS-!E0 ˬذț4OV?*nq̜i?qgm\[+U9[xtSEg7-kB[xT`D)|"2Lja92dvȤ:fՅj""OSThEnXLލhm`~buH8'+/ + !ؐ)0(z{Nhjk]XnFMe lxֲ@ 9N]c@f*vBM~-si}{} Cyn?g"A3I8qv( yl Vo^E1ʵhe"jɒ,ǵ)V 0~֥\ V'jE\|hsZQp2ͮH{ FnҵhdyY*`^hQEzJO9mN+V.P~Ɛ鷥?5E1ۖ9s܁צ*^he&MkiخO7=?ɭz)\ ѝ:]O?G3]9Ƶp2 ϵ5t۽@z(CC\ Sz'ZR@?备 I%ϮEiE%1ɡtKU˟ƴ m9Z= EQm&՟qVƚ5HqF5Eg FqZPh'c%؃ع(<$4v ֍%ۏW5+Vnr&Q@%`ȩ(#U>bEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEG;B~cF(sG+[cZK{sc/UTx+03֢AAO>OѢ3'd_Pr*y}A@XeQ+Zuf'z֦ER(((((((((((((((( ɬkQԗSObEXXX43|0)GԔQAwՅQ@((((((((((((((((((((((((((py5-V8%ƥŠ(AEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEs@ EPEPEPEPEPEPEPEPEPEPvL~f+S\6 nS`Sb3i*=g鷪K"չYW2eymԎƄ? ܷ$ߒI1R$p !жբ`W~th7x嚁 C]pʞ@ C(jZvC)wnj˿ +VS(!)]Jcթq;6HqTnK'8"(lH=ZS#oր>2NY~SW5!_Z>y |օ ܦ]7@rt{W.FKIylHvSi37VcW(Gݦw fbAlV^D9œ.kAyj𓴞A5gD]6.1)cfd9)9)%ڋRE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (1@VL" V `-QH((((((((((((((*xnmF79뛍Hi=j-msP\R搶JeV(97'vQE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (#qۡUu51 V-QEQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@?b,x?mGcη (Q@Q@Q@s ʡ$*5VݠYpB)0<>GhclbjiٱhT#* )qQE`REQEQEQEQEQEQEQEQE%-PE5qyPEPEPERREPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPHN;f@sKEQEQIqK@Q@Q@Q@Q@Q@Q@^ !mbb3@@֚d'+'ă6HFx~CZ)(Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@f%TJҢ>S65wg j>ѽ{ԴP9OQEQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@2W=Sْ`z nf0ƦŠ(AEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPErcu\u?Zi (Q@Q@Q@Q@Q@Q@Q@Q@Q@TsOoL nP+8$TQEQEQEQEQEQEQEQEQEQE%Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@>!\!ԌԁY#o VG1!QLE((((((F`X5VM ދBų8f#fdQQA!2IhY q< ^ 2qAD uCj;htnEP (%21Qe9eTr:tT(@QEQEQEQEQEQHHPI ;AmnR)TowEŲv< ۮSu?j)p|AI{ld6Hvێ1KH(((((((((((JZ(((((((((>+W#׭jVg?[8e}5L)QEQEQEQEQEQE K{UaNh$TR(qr¢9Y@4@~OterxSȕ+d6>bT=jŽțvF("te*JjOjv'!ǡ12a(b)=a$'`j EuZEGт'h[u1' Syy\[;I"&ʖf op(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEROH*X׫ui]ZBOOHj;R ݅Q@(((((((((((((((((((((((((((((((((((((((((((((((((((((((( MYsZ3Ыnuo ZzeGյL(EPEPuZY Lm:䫃uQcN1nbmeH#b/viY@QEQEQEQEQEsv:}̺Irsf$}ONƵ(p9[Τ"n95^ Q0]$H1"+B3BE @(QE ( ( ( ( ((ImY* B$.k+ 2PrOHZEO_Ӵ XbVP u' JFp20iQEQEQE&~lRm5Ol ( ( .nP$-ZP{[(m'=5F3Z~EawKt=唍nQEQEQEQEQE&Z(((((((((-PEPEPEPE'9(((  F7kJ/I7ִRLE((((((tW BUk’ hGEIW=9"X{y#L$; Be㍋m*եݛ~sʫj+eNjo(ҧme)# 3RQHV8 d`@97 ZyU5P QE(((((((((((((((((((((((((((((yw+-2C1GB;Q5HAw>\Pk槙[;snZIQA!EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPd%Q*+i9OT-۷AӟARqe(0(LPEQEQEQE%-Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@m=O=[uq} ٦ER((((((((((((((((((@KMQuQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEeAִ Q?PִccQ@QEQEQEQEQEQEQEQEQEQEQQ\iJĀ-giDhT+QAEPEPEPEPEPEPEPEPEPEPEPEPEPHH'ڃqfԏ?uޯ  'ss\9Ns)Tmx(g8QE!8LA"SQEQEQE^ IJ= PӏPi (0(`avqҦPit * F^#23A.-$QEQEQEQEEqn"`HF5!8U{k;ێŚ((((((((()cPH$PaAJo28ӜjXF dgڃofSn*ۗhe8Y{ԑPC-Yc\pjhISr$)*#czS&E֑0S@X0ʐG-T+s. NAv1R6-Yhub@ A#.X EEugEZCSr )[Ϩ#jƧ|@Ca$ӒǗց+UfkȑuuʐGqTSO+l{T1jHH#oւ$-MGY2N((((((((((((((((((((((((((((((((((U\v wa؊خz{BsMt4QH(((((((((((((((((((():u((((((#Ro_΀E4ȃi ю PUq>m>|X(z*?iK87QgER:TV$rJEP}f:?#M݋, hT?sǚ}liMB:# >:O\Vh4U'?vBr9ǩ`/Yj3ơa>G}y6|<NhT>ӨgluT..*s~j |Ƃ?y y8pż/'4$dqbo|À)6pF\Ffב,3>nI>*?>/违,rq}((Q@ B{YP?(FmUaf`:AMrH_ZDa`@.̰@NR7h.$Vɩ+:h S%PK}c$cYWت"Ʒ11|g4T\ H%@`vEPjU<$'4)5Ө2 ( BýR(=L, N2c݃Ls*ziPOM *6j)-2T`.ͺR13`8{X*ȷG?iph)ԋ)VF.N47єg ެPcҢ$lx5fחgYdkBKxP 銍,^ւR 6EK)ŸJFDZhF'>2wJGuҝ-#}h^ӟ\Rxn(kdf̛8e*#'r+VtZ[z.EI,Lw6*z(3DW[#f Zv ֭5XaI9-ܞ[%gV֠E^NmȒydsfIPð5#HQKf/ҕQ3$^W dP$C ?RPM )Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@! /a{vib2}sx#;0⺪RYj[WgVͪ\iii'HL] J+)w|ɃTM^pIX\{ƁNOrhk#֚d@p]GY#Bl{a`#/6[v1D!MD8ϝ? IpR%Q_hz)F?Bco=$ǧF^dOSQSsUFn:I(GTh>h4hږC^cd|?L41@tȶ(7QֲDfy~jO=:b}Q`jˏ6Om%aMY пg䵂5 FT۲784lCgo&7O1R$R}Dcv h`lyp!=sZ>DAyihPap?ui$}XNԢ2ƩrN0Ͻ*wLHl~aZtPXlzoc\+ZmJxlhgj($]j/qN3jh:jԢ2Dv5֭nQ49jڎl6A۰\:ע3>ۨӹA 6!(E3::`SL`>kۖ(P:Z[j94˹tf!HsWD{yo=1@ EPEPEPEPEPEPEPEPI(A((((((((( yymGOK~J/.ܚnXs-ʱK$bK$nz5 Dl1TVR, Q[[-m$ Mrdsv$_H$.m7 $CWY"ΈSZnfsj֤  [Gfr9%v(M]'X r>#Tm[k0O[7sjEud @` `1&uIEwbP)hQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@sZV˶1Ȭk˖T胊|WREԖj]NEʚTOuuJ0AVI V}xYb]r{UhCɸvMA+')L r' {r[F$44mʀRWȭRIDN5,bX7CUO&>NTuܱ)@?vGj4@Iփ)(c ( A(¯Rz d)Y`@2 }j(v)H6wTFsAn*ZBC<\ ''p t#($gn3#iRKkn=&Os#4O31>P?k>dGB~DT@QA.MŠ*3*D}JTS!M:zԊw(#gk<ȝF*8`dVh|ܣ H_yQ(3֣0DzƿIEMIxAϖKE(QEEqP~đ ")P;X(QLg OzEPEP]B?2ձy;ez[_:`JKES9((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((e Z(((((((((((((((((((((((((((((((((((((((((((((((((((܈@PFOZUcj *wyJI䚛RA-Š(AEPEPEPEPEPEPEPEPEPEPEPEPTPĎUγs%G/5 lj)TPe(1)"I=)-`|5R?RAApWv(jlQE4ȃ u6BDlW82B )zVҭjF֕RNU{&êՊlOB(".- ޥyS/N* (led[4sFԤe(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEV}{"օg dl~Iy_"u8WnBMler޴.YOJBG.^І\@'i@U#AaE.HB\H$օ2HU".WpINPz騋A)}(AL@SN+>t)45ʓۛxU.XWM P\L\KEQ@Q@Q@Q@ eb~QW9=P;Q@((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((()#.C1sz}(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEۈ [EG> (Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@m譌dN((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((C?(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((endstream endobj 5 0 obj <> stream 0.24000 0 0 0.24000 0 0 cm q 2480 0 0 1748 0 0 cm /Im1 Do Q endstream endobj 6 0 obj 60 endobj 7 0 obj <> >> endobj 8 0 obj <> endobj 3 0 obj <> endobj 1 0 obj <> endobj 2 0 obj <> endobj %RRR010createpdf*001.00.200**********1000 xref 0 9 0000000000 65535 f 0000176802 00000 n 0000176862 00000 n 0000176742 00000 n 0000000015 00000 n 0000176424 00000 n 0000176535 00000 n 0000176553 00000 n 0000176618 00000 n trailer <> startxref 176951 %%EOF Solaar-1.1.20/docs/LICENSE.txt000066400000000000000000000421631522022367600156020ustar00rootroot00000000000000GNU 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. Solaar-1.1.20/docs/PRO_X2_SUPERSTRIKE_CLI.md000066400000000000000000000224571522022367600177050ustar00rootroot00000000000000# 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. Solaar-1.1.20/docs/README.text000066400000000000000000000004651522022367600156210ustar00rootroot00000000000000# Documentation Readme This project's documentation is hosted using GitHub pages, which serves static pages using Jekyll. [Please refer to the official documentation for instructions for how to build the site locally.](https://help.github.com/articles/setting-up-your-github-pages-site-locally-with-jekyll/) Solaar-1.1.20/docs/capabilities.md000066400000000000000000000465311522022367600167350ustar00rootroot00000000000000--- title: Solaar Capabilities layout: page --- # Solaar Capabilities [**Solaar**][solaar] reports on and controls [Logitech][logitech] devices (keyboards, mice, and trackballs) that connect to your computer via a Logitech USB receiver (a very small piece of hardware that plugs into one of your USB ports). Solaar is designed to detect all connected devices, and at the very least display some basic information about them. At this moment, all [Unifying][unifying] receivers are supported (e.g., devices with USB ID `046d:c52b` or `046d:c532`) as are several Lightspeed Receivers and many Nano receivers. Solaar also reports on and controls some Logitech devices that directly connect to your computer using a USB cable or via Bluetooth. Not all such devices supported in Solaar as information needs to be added to Solaar for each device type that directly connects. ## HID++ and Centurion 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 Logitech receivers to communicate with multiple devices and modify some features of the device. As the HID++ protocol is proprietary, many aspects of it are unknown. Some information about HID++ has been obtained from Logitech but even that is subject to change and extension. There are several versions of HID++ and many Logitech receivers and devices that utilize it. Different receivers and devices implement different portions of HID++ so even if two devices appear to be the same in both physical appearance and behavior they may work differently underneath. (For example, there are versions of the M510 mouse that use different versions of the HID++ protocol.) 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 its wireless PID as reported by Solaar. The software identity of devices that connect via a USB cable or via bluetooth can be determined by their USB or Bluetooth product ID. ## Pairing and Unpairing Solaar is able to pair and unpair devices with receivers as supported by the device and receiver. 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 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. 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. It should be possible to connect any device with a Unifying logo on it to any receiver 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 features and their support see [the features page](features.md). 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 characters that are sent to programs. That is the province of HID device drivers and other software (such as X11). Settings can only be changed in the Solaar GUI when they are unlocked. To unlock a setting click on the icon at the right-hand edge of the setting until an unlocked lock appears (with tooltop "Changes allowed"). Solaar keeps track of most of the changeable settings of a device. Devices forget most changed settings when the device is turned off or goes into a power-saving mode. Exceptions include the setting to change the host the device is connected to, the setting to persistently change what a key or button does, and, for some devices, the fn-swap setting. When Solaar starts, it restores on-line devices to their previously-known state except for host connection and persistent key and button changes and while running Solaar restores devices to their previously-known state when the device itself comes on line. Setting information is stored in the file `~/.config/solaar/config.yaml`. Updating of a setting can be turned off in the Solaar GUI by clicking on the icon at the right-hand edge of the setting until a red icon appears (with tooltip "Ignore this setting"). 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 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 state is changed by some other means, even sometimes by another invocation of Solaar, this cached information may become incorrect. Currently there is no way to force an update of the cached information besides restarting Solaar. Logitech receivers and devices have firmware in them. Some firmware can be updated using Logitech software in Windows. For example, there are security issues with some Logitech receivers and devices and Logitech has firmware updates to alleviate these problems. Some Logitech firmware can also be updated in Linux using `fwupdmgr`. WARNING: Updating firmware can cause a piece of hardware to become permanently non-functional if something goes wrong with the update or the update installs the wrong firmware. ## Other Solaar Capabilities Solaar has a few capabilities that go beyond simply changing device settings. ### Rule-based Processing of HID++ Notifications Solaar can process HID++ Notifications from devices to, for example, change the speed of some thumb wheels. These notifications are only sent for actions that are set in Solaar to their HID++ setting (also known as diverted). For more information on this capability of Solaar see [the rules page](https://pwr-solaar.github.io/Solaar/rules). Users can edit rules using a GUI by clicking on the `Rule Editor` button in the Solaar main window. ### Sliding DPI A few mice (such as the MX Vertical) have a button that is supposed to be used to change the sensitivity (DPI) of the mouse by pressing the button and moving the mouse left and right. Other mice (such as the MX Master 3) don't have a button specific for this purpose but have buttons that can be used for it. The `Key/Button Diversion` setting can assign buttons to adjust sensitivity by setting the value for the button to `Sliding DPI`. This capability is only present if the device supports changing the DPI in this way. Pressing a button when it is set to `Sliding DPI` causes the mouse pointer to stop moving. When the button is released a new Sensitivity (DPI) value is applied to the mouse, depending on how far right or left the mouse is moved. If the mouse is moved only a little bit the previous value that was set is applied to the mouse. Notifications from Solaar are displayed showing the setting that will be applied. ### Mouse Gestures Some mice (such as the MX Master 3) have a button that is supposed to be used to create up/down/left/right mouse gestures. Other mice (such as the MX Vertical) don't have a button specific for this purpose but have buttons that can be used for it. The `Key/Button Diversion` setting can assign buttons to initiate mouse gestures by setting the value for the button to `Mouse Gestures`. This capability is only present if the device can support it. Pressing a button when it is set to `Mouse Gestures` causes the mouse pointer to stop moving. When the button is released a `MOUSE_GESTURE` notification with the mouse movements and diverted key presses 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. This allows users to close Solaar and reopen from the tray. This aspect of Solaar depends on having an active system tray which may require some special setup when using Gnome, particularly under Wayland. If you are running Gnome, you most likely need the `gnome-shell-extension-appindicator` package installed. In Fedora, this can be done by running ``` sudo dnf install gnome-shell-extension-appindicator ``` The likely command in Ubuntu and related distributions is ``` sudo apt install gnome-shell-extension-appindicator ``` You may have to log out and log in again before the system tray shows up. ## Battery Icons For many devices, Solaar shows the approximate battery level via icons that show up in both the main window and the system tray. In previous versions several heuristics determined which icon names to use for this purpose, but as more and more battery icon schemes have been developed this has become impossible to do well. Solaar now uses the eleven standard battery icon names `battery-{full,good,low,critical,empty}[-charging]` and `battery-missing`. Solaar will use the symbolic versions of these icons if started with the option `--battery-icons=symbolic`. Because of external bugs, these symbolic icons may be nearly invisible in dark themes. [solaar]: https://github.com/pwr-Solaar/Solaar [logitech]: https://www.logitech.com [unifying]: https://en.wikipedia.org/wiki/Logitech_Unifying_receiver Solaar-1.1.20/docs/debian.md000066400000000000000000000003731522022367600155200ustar00rootroot00000000000000--- title: Debian Repository layout: page --- # Debian repository Solaar is now part of the [official Debian repository](https://packages.debian.org/solaar). To install it on your Debian machine, use the following command: `sudo apt install solaar` Solaar-1.1.20/docs/devices.md000066400000000000000000000212701522022367600157170ustar00rootroot00000000000000--- title: Supported Devices layout: page --- # Supported receivers and devices 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 all Logitech devices (keyboards, mice, trackballs, touchpads, and headsets) that can connect to supported receivers. 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. The directory contains edited output of `solaar show` on many devices and can be used to see what Solaar can do with the device. ## Supporting old devices Some old Logitech devices use an old version of HID++. For Solaar to support these devices well, Solaar needs some information about them. 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 Adding a new receiver requires knowing whether the receiver is a regular Unifying receiver, a Nano receiver, a Bolt receiver, or a Lightspeed receiver. This can generally be found using `lsusb`. To add a new receiver to the Solaar code add a line to `../lib/logitech_receiver/base_usb.py` defining the receiver as one of these. If the receiver has an unusual number of pairing slots, then this also needs to be specified. Then add the receiver to the tuple of receivers (ALL). ### Supported Receivers | USB ID | Kind | Max Paired Devices | ------------|------------|--------------------| | 046d:c517 | 27MHz | 4 | | 046d:c518 | Nano | 1 | | 046d:c51a | Nano | 1 | | 046d:c51b | Nano | 1 | | 046d:c521 | Nano | 1 | | 046d:c525 | Nano | 1 | | 046d:c526 | Nano | 1 | | 046d:c52b | Unifying | 6 | | 046d:c52e | Nano | 1 | | 046d:c52f | Nano | 1 | | 046d:c531 | Nano | 1 | | 046d:c532 | Unifying | 6 | | 046d:c534 | Nano | 2 | | 046d:c537 | Nano | 2 | | 046d:c539 | Lightspeed | 1 | | 046d:c53a | Lightspeed | 1 | | 046d:c53d | Lightspeed | 1 | | 046d:c53f | Lightspeed | 1 | | 046d:c541 | Lightspeed | 1 | | 046d:c542 | Nano | 1 | | 046d:c545 | Lightspeed | 1 | | 046d:c547 | Lightspeed | 1 | | 046d:c548 | Bolt | 6 | | 17ef:6042 | Nano | 1 | Some Nano receivers are only partly supported as they do not implement the full HID++ 1.0 protocol. Some Nano receivers are not supported as they do not implement the HID++ protocol at all. Receivers with USB ID 046d:c542 fall into this category. The receiver with USB ID 046d:c517 is an old 27 MHz receiver, supporting only a subset of the HID++ 1.0 protocol. Only hardware pairing is supported. ## 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. The tables concentrate on older devices that have explicit support information in Solaar and are not being updated for new devices that are supported by Solaar. Note that Logitech has the annoying habit of reusing Device names (e.g., M185) so what is important for support is the USB WPID or Bluetooth model ID. ### Keyboards (Unifying) | Device | WPID | HID++ | |------------------|------|-------| | K230 | 400D | 2.0 | | K270 | 4003 | 2.0 | | K340 | 2007 | 1.0 | | K350 | 200A | 1.0 | | K360 | 4004 | 2.0 | | K375s | 4071 | | | K400 Touch | 400E | 2.0 | | K400 Touch | 4024 | 2.0 | | K400 Plus | 404D | 2.0 | | K520 | 2011 | 1.0 | | K600 TV | 4078 | 2.0 | | K750 Solar | 4002 | 2.0 | | K780 | 405B | 2.0 | | K800 Illuminated | 2010 | 1.0 | | K800 (new ver) | 406E | 2.0 | | K830 Illuminated | 4032 | 2.0 | | MX Keys | 408A | 2.0 | | N545 | 2006 | | | TK820 | | 2.0 | | Craft | 4066 | 2.0 | ### Keyboards (Lightspeed) | Device | WPID | HID++ | |------------------|------|-------| | G915 TKL | 408E | 4.2 | ### Mice (Unifying) | Device | WPID | HID++ | |------------------|------|-------| | M150 | 4022 | 2.0 | | M185 | 4055 | 2.0 | | M310 | 4031 | 2.0 | | M310 | 4055 | 2.0 | | M317 | | | | M325 | 400A | 2.0 | | M330 | | 2.0 | | M345 | 4017 | 2.0 | | M350 | 101C | 1.0 | | M350 | 4080 | 2.0 | | M505 | 101D | 1.0 | | M510 | 1025 | 1.0 | | M510 | 4051 | 2.0 | | M515 Couch | 4007 | 2.0 | | M525 | 4013 | 2.0 | | M560 | | 2.0 | | M585 | 406B | 2.0 | | M590 | 406B | 2.0 | | M600 Touch | 401A | 2.0 | | M705 Marathon | 101B | 1.0 | | M705 Marathon | 406D | 2.0 | | M720 Triathlon | 405E | 2.0 | | T400 Zone Touch | | 2.0 | | T620 Touch | | 2.0 | | Performance MX | 101A | 1.0 | | Anywhere MX | 1017 | 1.0 | | Anywhere MX 2 | 404A | 2.0 | | MX Master | 4041 | 2.0 | | MX Master 2S | 4069 | 2.0 | | Cube | | 2.0 | | MX Vertical | 407B | 2.0 | ### Mice (Nano) | Device | WPID | HID++ | |------------------|------|-------| | G7 | 1002 | 1.0 | | G700 | 1023 | 1.0 | | G700s | 102A | 1.0 | | V450 Nano | 1011 | 1.0 | | V550 Nano | 1013 | 1.0 | | VX Nano | 100B | 1.0 | | VX Nano | 100F | 1.0 | | M175 | 4008 | | | M185 (old) | 4038 | 2.0 | | M185 (new) | 4054 | 2.0 | | M187 | 4019 | 2.0 | | M215 | 1020 | 1.0 | | M235 | 4055 | 2.0 | | M305 | 101F | 1.0 | | M310 | 1024 | 1.0 | | M315 | | | | M330 | | ?.? | | MX 1100 | 1014 | 1.0 | * (old): M185 with P/N: 810-003496 * (new): M185 with P/N: 810-005238 or 810-005232 ### Mice (Mini) | Device | WPID | HID++ | |-------------------|------|-------| | MX610 | 1001 | 1.0 | | MX610 left handed | 1004 | 1.0 | | MX620 | 100A | 1.0 | | MX620 | 1016 | 1.0 | | V400 | 1003 | 1.0 | | V450 | 1005 | 1.0 | | VX Revolution | 1006 | 1.0 | | VX Revolution | 100D | 1.0 | | MX Air | 1007 | 1.0 | | MX Air | 100E | 1.0 | | MX Revolution | 1008 | 1.0 | | MX Revolution | 100C | 1.0 | ### Mice (Lightspeed) | 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) | Device | WPID | HID++ | |-------------------|------|-------| | M570 Trackball | | 1.0 | | MX Ergo Trackball | | 2.0 | ### Touchpads (Unifying) | Device | WPID | HID++ | |------------------|------|-------| | Wireless Touch | 4011 | 2.0 | | T650 Touchpad | 4101 | 2.0 | ### Mice and Keyboards sold as combos | Device | WPID | HID++ | |------------------|------|-------| | MK220 | | 2.0 | | MK270 | 4023 | 2.0 | | MK320 | 200F | | | MK330 | | | | MK345 | 4023 | 2.0 | | MK520 | | M2/K1 | | MK550 | | | | MK700 | 2008 | 1.0 | | MK710 | | 1.0 | | EX100 keyboard | 0065 | 1.0 | | EX100 mouse | 003f | 1.0 | * The EX100 is an old, pre-Unifying receiver and device set, supporting only some HID++ 1.0 features Solaar-1.1.20/docs/devices/000077500000000000000000000000001522022367600153735ustar00rootroot00000000000000Solaar-1.1.20/docs/devices/00README.txt000066400000000000000000000046141522022367600172360ustar00rootroot00000000000000Files in this directory are edited output from `solaar show` providing information about devices and receivers that Solaar supports. This directory does not contain information about all devices and receivers that Solaar supports. Information is generally only added when provided in a Solaar issue. Directions for constructing the files are given below. The files Unifying Receiver C52B.txt Craft Advanced Keyboard 4066.txt Craft Advanced Keyboard B350.txt MX Master 3 Wireless Mouse 4082.txt MX Master 3 Wireless Mouse B023.txt are good examples of following the directions below. File Naming Logitech device names are often reused so the names of files can't just be the device name. File names start with the name of the device or receiver as given in the first line of of output for the device. The file name continues with a space and the WPID, if the device is connected to a receiver, or the second half of the USB ID, if the device is connected via USB or Bluetooth. The WPID or USB ID are in upper case. As devices can behave differently when connected via a receiver or USB or Bluetooth there should be a file for each possible connection method. Files that do not follow this naming convention are retained for historical purposes. File Contents Each file should contain the output of `solaar show NAME` where NAME is enough of the full name of a device or receiver to identify it. The output of `solaar show` will provide information on all connnected devices and receivers including their names. The output of `solaar show NAME` can be edited to remove serial numbers and variable information such as the current values of settings. Passing the style requirements for Solaar documentation may require removing trailing white space on lines. For older devices probes of the device registers should be included but for newer devices this is not necessary. Unifying receivers can pair with any device that has the Unifying logo. Bolt receivers can pair with any device that has the Bolt logo. Nano and Lightspeed receivers can only pair with certain devices, so the end of their files should state the devices that they have been seen to be paired with or are part of. Updating Files Newer versions of Solaar add support for more settings so it is useful to provide updated versions of these files if there is information from the current version of `solaar show NAME` that is not in the existing file. Solaar-1.1.20/docs/devices/Bluetooth Multi-Device Keyboard K380 B342.txt000066400000000000000000000137141522022367600251010ustar00rootroot00000000000000Solaar version 1.1.4 1: Bluetooth Multi-Device Keyboard K380 Device path : /dev/hidraw5 USB id : 046d:B342 Codename : Keyboard K380 Kind : ? Protocol : HID++ 4.5 Serial number: Model ID: B34200000000 Unit ID: 16000000 Firmware: RBK 42.01.B0017 Hardware: 72 Supports 14 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Firmware RBK 42.01.B0017 B342C18DBFDD Firmware: Hardware 72 Unit ID: 16000000 Model ID: B34200000000 Transport IDs: {'btid': 'B342'} 3: DEVICE NAME {0005} Name: Bluetooth Multi-Device Keyboard K380 Kind: keyboard 4: DEVICE FRIENDLY NAME {0007} Friendly Name: Keyboard K380 5: RESET {0020} 6: BATTERY STATUS {1000} Battery: 20%, discharging, next level 5%. 7: CHANGE HOST {1814} Change Host : 1:Luay-A 8: HOSTS INFO {1815} Host 0 (paired): Luay-A Host 1 (paired): BCM20703A2 Generic UART UHE App Host 2 (unpaired): 9: REPROG CONTROLS V4 {1B04} Key/Button Diversion (saved): {Volume Up:Regular, Volume Down:Regular, Mute:Regular, Play/Pause:Regular, Next:Regular, Previous:Regular, Search:Regular, Multiplatform App Switch:Regular, Multiplatform Home:Regular, Multiplatform Menu:Diverted, Multiplatform Back:Regular, Multiplatform Insert:Regular, Screen Capture/Print Screen:Regular, Fn Down:Regular, Fn Up:Regular, Multiplatform Lock:Regular} Key/Button Diversion : {Volume Up:Regular, Volume Down:Regular, Mute:Regular, Play/Pause:Regular, Next:Regular, Previous:Regular, Search:Regular, Multiplatform App Switch:Regular, Multiplatform Home:Regular, Multiplatform Menu:Diverted, Multiplatform Back:Regular, Multiplatform Insert:Regular, Screen Capture/Print Screen:Regular, Fn Down:Regular, Fn Up:Regular, Multiplatform Lock:Regular} 10: unknown:1E00 {1E00} hidden 11: NEW FN INVERSION {40A2} Fn-swap: disabled Fn-swap default: enabled Swap Fx function (saved): False Swap Fx function : False 12: LOCK KEY STATE {4220} 13: KEYBOARD DISABLE KEYS {4521} Disable keys (saved): {Caps Lock:False, Scroll Lock:False, Insert:True, Win:False} Disable keys : {Caps Lock:False, Scroll Lock:False, Insert:True, Win:False} Has 16 reprogrammable keys: 0: Multiplatform Home , default: MultiPlatform Home => MultiPlatform Home is FN, FN sensitive, reprogrammable, divertable, pos:4, group:0, group mask:empty reporting: default 1: Multiplatform App Switch , default: MultiPlatform App Switch => MultiPlatform App Switch is FN, FN sensitive, reprogrammable, divertable, pos:5, group:0, group mask:empty reporting: default 2: Multiplatform Menu , default: MultiPlatform Menu => MultiPlatform Menu is FN, FN sensitive, reprogrammable, divertable, pos:6, group:0, group mask:empty reporting: diverted 3: Multiplatform Back , default: MultiPlatform Back => MultiPlatform Back is FN, FN sensitive, reprogrammable, divertable, pos:7, group:0, group mask:empty reporting: default 4: Previous , default: Previous => Previous is FN, FN sensitive, divertable, pos:8, group:0, group mask:empty reporting: default 5: Play/Pause , default: Play/Pause => Play/Pause is FN, FN sensitive, divertable, pos:9, group:0, group mask:empty reporting: default 6: Next , default: Next => Next is FN, FN sensitive, divertable, pos:10, group:0, group mask:empty reporting: default 7: Mute , default: Mute => Mute is FN, FN sensitive, divertable, pos:11, group:0, group mask:empty reporting: default 8: Volume Down , default: Volume Down => Volume Down is FN, FN sensitive, divertable, pos:12, group:0, group mask:empty reporting: default 9: Volume Up , default: Volume Up => Volume Up is FN, FN sensitive, divertable, pos:0, group:0, group mask:empty reporting: default 10: Multiplatform Insert , default: Switch Language => Switch Language FN sensitive, reprogrammable, divertable, pos:0, group:0, group mask:empty reporting: default 11: Fn Up , default: unknown:0070 => unknown:0070 is FN, divertable, pos:0, group:0, group mask:empty reporting: default 12: Fn Down , default: unknown:006F => unknown:006F is FN, divertable, pos:0, group:0, group mask:empty reporting: default 13: Screen Capture/Print Screen, default: Screen Capture => Screen Capture is FN, reprogrammable, divertable, pos:0, group:0, group mask:empty reporting: default 14: Search , default: Search Files => Search Files is FN, reprogrammable, divertable, pos:0, group:0, group mask:empty reporting: default 15: Multiplatform Lock , default: WindowsLock => WindowsLock is FN, reprogrammable, divertable, pos:0, group:0, group mask:empty reporting: default Battery: 20%, discharging, next level 5%. Solaar-1.1.20/docs/devices/Bolt Receiver C548.txt000066400000000000000000000004371522022367600211710ustar00rootroot00000000000000Solaar version 1.1.4 Bolt Receiver Device path : /dev/hidraw8 USB id : 046d:C548 Serial : 38423638313332394241363434313837 Has 2 paired device(s) out of a maximum of 6. Notifications: wireless, software present (0x000900) Device activity counters: 1=252, 2=135 Solaar-1.1.20/docs/devices/Candy companion chip 405F.txt000066400000000000000000000023371522022367600224460ustar00rootroot00000000000000 7: Candy companion chip Device path : /dev/hidraw11 Codename : Candy Kind : touchpad Protocol : HID++ 4.2 Serial number: 5B2B9A98 Model ID: 405F00000000 Unit ID: 32314707 Firmware: CC 07.00.B0010 Bootloader: BOT 32.00.B0010 Supports 11 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Firmware CC 07.00.B0010 405F Firmware: Bootloader BOT 32.00.B0010 405F Unit ID: 32314707 Model ID: 405F00000000 Transport IDs: {'wpid': '405F'} 3: DEVICE NAME {0005} Name: Candy companion chip Kind: touchpad 4: unknown:18A1 {18A1} internal, hidden 5: unknown:1E00 {1E00} hidden 6: unknown:1EB0 {1EB0} internal, hidden 7: DFUCONTROL SIGNED {00C2} 8: unknown:1801 {1801} internal, hidden 9: DEVICE RESET {1802} internal, hidden 10: unknown:1803 {1803} internal, hidden Battery status unavailable. Part of the G PowerPlay Wireless Mouse Pad. Solaar-1.1.20/docs/devices/Couch Mouse M515 4007.txt000066400000000000000000000027461522022367600212420ustar00rootroot000000000000001: Couch Mouse M515 Codename : M515 Kind : mouse Wireless PID : 4007 Protocol : HID++ 2.0 Polling rate : 8 ms Serial number: BED587E9 Firmware: RQM 24.00.B0023 Bootloader: DFU 00.02.B0010 The power switch is located on the base. Supports 16 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: DFUCONTROL {00C0} 5: BATTERY STATUS {1000} 6: unknown:1A30 {1A30} hidden 7: REPROG CONTROLS {1B00} 8: WIRELESS DEVICE STATUS {1D4B} 9: unknown:1DF3 {1DF3} hidden 10: VERTICAL SCROLLING {2100} 11: HI RES SCROLLING {2120} 12: MOUSE POINTER {2200} 13: unknown:1F02 {1F02} hidden 14: unknown:1F03 {1F03} hidden 15: unknown:1E80 {1E80} hidden Has 5 reprogrammable keys: 0: LEFT CLICK => LeftClick mse, reprogrammable 1: RIGHT CLICK => RightClick mse, reprogrammable 2: MIDDLE BUTTON => MiddleMouseButton mse, reprogrammable 3: BACK AS BUTTON 4 => BackEx mse, reprogrammable 4: FORWARD AS BUTTON 5 => BrowserForwardEx mse, reprogrammable Battery: 65%, discharging, Solaar-1.1.20/docs/devices/Craft Advanced Keyboard 4066.txt000066400000000000000000000301221522022367600227600ustar00rootroot00000000000000Solaar version 1.1.4 2: Craft Advanced Keyboard Device path : /dev/hidraw4 WPID : 4066 Codename : Craft Kind : keyboard Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: 112C46EA Model ID: B35040660000 Unit ID: 43DAF041 Bootloader: BOT 41.01.B0015 Firmware: MPK 07.01.B0015 Other: Other: The power switch is located on the edge of top right corner. Supports 38 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 41.01.B0015 0000B6A2C54601 Firmware: Firmware MPK 07.01.B0015 4066B6A2C54601 Firmware: Other Firmware: Other Unit ID: 43DAF041 Model ID: B35040660000 Transport IDs: {'btleid': 'B350', 'wpid': '4066'} 3: DEVICE NAME {0005} Name: Craft Advanced Keyboard Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: DEVICE FRIENDLY NAME {0007} Friendly Name: Keyboard Craft 7: BATTERY STATUS {1000} Battery: 50%, discharging, next level 20%. 8: CHANGE HOST {1814} Change Host : 1:idefix 9: HOSTS INFO {1815} Host 0 (paired): idefix Host 1 (paired): idefix Host 2 (paired): Galaxy Tab S6 10: BACKLIGHT2 {1982} Backlight (saved): True Backlight : True 11: REPROG CONTROLS V4 {1B04} Key/Button Diversion (saved): {Calculator:Regular, Show Desktop:Diverted, Lock PC:Regular, Screen Capture/Print Screen:Regular, Brightness Down:Regular, Brightness Up:Regular, Host Switch Channel 1:Regular, Host Switch Channel 2:Diverted, Host Switch Channel 3:Diverted, Mission Control/Task View:Regular, Dashboard Launchpad/Action Center: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, Right Arrow:Regular, Left Arrow:Regular} Key/Button Diversion : {Calculator:Regular, Show Desktop:Diverted, Lock PC:Regular, Screen Capture/Print Screen:Regular, Brightness Down:Regular, Brightness Up:Regular, Host Switch Channel 1:Regular, Host Switch Channel 2:Diverted, Host Switch Channel 3:Diverted, Mission Control/Task View:Regular, Dashboard Launchpad/Action Center: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, Right Arrow:Regular, Left Arrow:Regular} 12: PERSISTENT REMAPPABLE ACTION {1C00} Persistent Key/Button Mapping : {Calculator:AL Calculator, Show Desktop:Meta+D, Lock PC:Meta+L, Screen Capture/Print Screen:SYSRQ, Brightness Down:Brightness Down, Brightness Up:Brightness Up, Mission Control/Task View:Meta+TAB, Dashboard Launchpad/Action Center:Meta+A, Backlight Down:No Output (only as default), Backlight Up:No Output (only as default), Previous Fn:Scan Previous Track, Play/Pause Fn:Play/Pause, Next Fn:Scan Next Track, Mute Fn:Mute, Volume Down Fn:No Output, Volume Up Fn:Volume Up, App Contextual Menu/Right Click:COMPOSE} 13: K375S FN INVERSION {40A3} Swap Fx function (saved): True Swap Fx function : True 14: ENCRYPTION {4100} 15: LOCK KEY STATE {4220} 16: KEYBOARD DISABLE KEYS {4521} 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} 17: MULTIPLATFORM {4531} Set OS (saved): Windows Set OS : Windows 18: CROWN {4600} Crown smooth scroll (saved): False Crown smooth scroll : False Divert crown events (saved): True Divert crown events : True 19: DFUCONTROL SIGNED {00C2} 20: unknown:1803 {1803} internal, hidden 21: CONFIG DEVICE PROPS {1806} internal, hidden 22: unknown:1813 {1813} internal, hidden 23: OOBSTATE {1805} internal, hidden 24: unknown:1830 {1830} internal, hidden 25: unknown:1890 {1890} internal, hidden 26: unknown:1891 {1891} internal, hidden 27: unknown:1801 {1801} internal, hidden 28: unknown:18A1 {18A1} internal, hidden 29: unknown:9280 {9280} internal, hidden 30: unknown:1A20 {1A20} internal, hidden 31: unknown:1DF3 {1DF3} internal, hidden 32: unknown:1E00 {1E00} hidden 33: unknown:1EB0 {1EB0} internal, hidden 34: unknown:1861 {1861} internal, hidden 35: unknown:18B0 {18B0} internal, hidden 36: unknown:92C0 {92C0} internal, hidden 37: unknown:9203 {9203} internal, hidden Has 24 reprogrammable keys: 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: diverted 2: Host Switch Channel 3 , default: HostSwitch Channel 3 => HostSwitch Channel 3 nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: diverted 3: Brightness Down , default: Brightness Down => Brightness Down is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:1, group:0, group mask:empty reporting: default 4: Brightness Up , default: Brightness Up => Brightness Up is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:2, group:0, group mask:empty reporting: default 5: Mission Control/Task View , default: Mission Control/Task View => Mission Control/Task View is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:3, group:0, group mask:empty reporting: default 6: Dashboard Launchpad/Action Center, default: Dashboard Launchpad/Action Center => Dashboard Launchpad/Action Center is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:4, group:0, group mask:empty reporting: default 7: Show Desktop , default: Show Desktop => Show Desktop is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:5, group:0, group mask:empty reporting: diverted 8: Backlight Down , default: Backlight Down => Backlight Down is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:6, group:0, group mask:empty reporting: default 9: Backlight Up , default: Backlight Up => Backlight Up is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:7, group:0, group mask:empty reporting: default 10: Previous Fn , default: Previous => Previous is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:8, group:0, group mask:empty reporting: default 11: Play/Pause Fn , default: Play/Pause => Play/Pause is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:9, group:0, group mask:empty reporting: default 12: Next Fn , default: Next => Next is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:10, group:0, group mask:empty reporting: default 13: Mute Fn , default: Mute => Mute is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:11, group:0, group mask:empty reporting: default 14: Volume Down Fn , default: Volume Down => Volume Down is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:12, group:0, group mask:empty reporting: default 15: Volume Up Fn , default: Volume Up => Volume Up nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 16: Calculator , default: Calculator => Calculator nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 17: Screen Capture/Print Screen, default: Screen Capture => Screen Capture nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 18: App Contextual Menu/Right Click, default: Right Click/App Contextual Menu => Right Click/App Contextual Menu nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 19: Lock PC , default: WindowsLock => WindowsLock nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 20: Left Arrow , default: Keyboard Left Arrow => Keyboard Left Arrow nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 21: Right Arrow , default: Keyboard Right Arrow => Keyboard Right Arrow nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 22: F Lock , default: Do Nothing One => Do Nothing One is FN, pos:0, group:0, group mask:empty reporting: default 23: FN Key , default: Do Nothing One => Do Nothing One nonstandard, pos:0, group:0, group mask:empty reporting: default Has 17 persistent remappable keys: 0: Brightness Down => Consumer: Brightness Down (remapped) 1: Brightness Up => Consumer: Brightness Up (remapped) 2: Mission Control/Task View => Key: Meta+TAB 3: Dashboard Launchpad/Action Center => Key: Meta+A 4: Show Desktop => Key: Meta+D 5: Backlight Down => None 6: Backlight Up => None 7: Previous Fn => Consumer: Scan Previous Track 8: Play/Pause Fn => Consumer: Play/Pause 9: Next Fn => Consumer: Scan Next Track 10: Mute Fn => Consumer: Mute 11: Volume Down Fn => Key: No Output (remapped) 12: Volume Up Fn => Consumer: Volume Up 13: Calculator => Consumer: AL Calculator 14: Screen Capture/Print Screen => Key: SYSRQ 15: App Contextual Menu/Right Click => Key: COMPOSE 16: Lock PC => Key: Meta+L Battery: 50%, discharging, next level 20%. Solaar-1.1.20/docs/devices/Craft Advanced Keyboard B350.txt000066400000000000000000000272241522022367600230030ustar00rootroot00000000000000Solaar version 1.1.4 1: Craft Advanced Keyboard Device path : /dev/hidraw5 USB id : 046d:B350 Codename : Craft Kind : keyboard Protocol : HID++ 4.5 Serial number: Model ID: B35040660000 Unit ID: 43DAF041 Bootloader: BOT 41.01.B0015 Firmware: MPK 07.01.B0015 Other: Other: Supports 33 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 41.01.B0015 0000B6A2C54601 Firmware: Firmware MPK 07.01.B0015 B350B6A2C54601 Firmware: Other Firmware: Other Unit ID: 43DAF041 Model ID: B35040660000 Transport IDs: {'btleid': 'B350', 'wpid': '4066'} 3: DEVICE NAME {0005} Name: Craft Advanced Keyboard Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: DEVICE FRIENDLY NAME {0007} Friendly Name: Keyboard Craft 7: BATTERY STATUS {1000} Battery: 50%, discharging, next level 20%. 8: CHANGE HOST {1814} Change Host : 2:idefix 9: HOSTS INFO {1815} Host 0 (paired): idefix Host 1 (paired): idefix Host 2 (paired): Galaxy Tab S6 10: BACKLIGHT2 {1982} Backlight (saved): True Backlight : True 11: REPROG CONTROLS V4 {1B04} Key/Button Diversion (saved): {Calculator:Regular, Show Desktop:Diverted, Lock PC:Regular, Screen Capture/Print Screen:Regular, Brightness Down:Regular, Brightness Up:Regular, Host Switch Channel 1:Regular, Host Switch Channel 2:Diverted, Host Switch Channel 3:Diverted, Mission Control/Task View:Regular, Dashboard Launchpad/Action Center: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, Right Arrow:Regular, Left Arrow:Regular} Key/Button Diversion : {Calculator:Regular, Show Desktop:Diverted, Lock PC:Regular, Screen Capture/Print Screen:Regular, Brightness Down:Regular, Brightness Up:Regular, Host Switch Channel 1:Regular, Host Switch Channel 2:Diverted, Host Switch Channel 3:Diverted, Mission Control/Task View:Regular, Dashboard Launchpad/Action Center: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, Right Arrow:Regular, Left Arrow:Regular} 12: PERSISTENT REMAPPABLE ACTION {1C00} Persistent Key/Button Mapping : {Calculator:AL Calculator, Show Desktop:Meta+D, Lock PC:Meta+L, Screen Capture/Print Screen:SYSRQ, Brightness Down:No Output (only as default), Brightness Up:No Output (only as default), Mission Control/Task View:Meta+TAB, Dashboard Launchpad/Action Center:Meta+A, Backlight Down:No Output (only as default), Backlight Up:No Output (only as default), Previous Fn:Scan Previous Track, Play/Pause Fn:Play/Pause, Next Fn:Scan Next Track, Mute Fn:Mute, Volume Down Fn:Volume Down, Volume Up Fn:Volume Up, App Contextual Menu/Right Click:COMPOSE} 13: K375S FN INVERSION {40A3} Swap Fx function (saved): True Swap Fx function : True 14: ENCRYPTION {4100} 15: LOCK KEY STATE {4220} 16: KEYBOARD DISABLE KEYS {4521} 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} 17: MULTIPLATFORM {4531} Set OS (saved): Windows Set OS : Windows 18: CROWN {4600} Crown smooth scroll (saved): False Crown smooth scroll : False Divert crown events (saved): True Divert crown events : True 19: DFUCONTROL SIGNED {00C2} 20: unknown:1803 {1803} internal, hidden 21: unknown:1813 {1813} internal, hidden 22: unknown:1830 {1830} internal, hidden 23: unknown:1801 {1801} internal, hidden 24: unknown:18A1 {18A1} internal, hidden 25: unknown:1A20 {1A20} internal, hidden 26: unknown:1DF3 {1DF3} internal, hidden 27: unknown:1E00 {1E00} hidden 28: unknown:1EB0 {1EB0} internal, hidden 29: unknown:1861 {1861} internal, hidden 30: unknown:18B0 {18B0} internal, hidden 31: unknown:92C0 {92C0} internal, hidden 32: unknown:9203 {9203} internal, hidden Has 24 reprogrammable keys: 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: diverted 2: Host Switch Channel 3 , default: HostSwitch Channel 3 => HostSwitch Channel 3 nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: diverted 3: Brightness Down , default: Brightness Down => Brightness Down is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:1, group:0, group mask:empty reporting: default 4: Brightness Up , default: Brightness Up => Brightness Up is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:2, group:0, group mask:empty reporting: default 5: Mission Control/Task View , default: Mission Control/Task View => Mission Control/Task View is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:3, group:0, group mask:empty reporting: default 6: Dashboard Launchpad/Action Center, default: Dashboard Launchpad/Action Center => Dashboard Launchpad/Action Center is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:4, group:0, group mask:empty reporting: default 7: Show Desktop , default: Show Desktop => Show Desktop is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:5, group:0, group mask:empty reporting: diverted 8: Backlight Down , default: Backlight Down => Backlight Down is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:6, group:0, group mask:empty reporting: default 9: Backlight Up , default: Backlight Up => Backlight Up is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:7, group:0, group mask:empty reporting: default 10: Previous Fn , default: Previous => Previous is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:8, group:0, group mask:empty reporting: default 11: Play/Pause Fn , default: Play/Pause => Play/Pause is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:9, group:0, group mask:empty reporting: default 12: Next Fn , default: Next => Next is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:10, group:0, group mask:empty reporting: default 13: Mute Fn , default: Mute => Mute is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:11, group:0, group mask:empty reporting: default 14: Volume Down Fn , default: Volume Down => Volume Down is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:12, group:0, group mask:empty reporting: default 15: Volume Up Fn , default: Volume Up => Volume Up nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 16: Calculator , default: Calculator => Calculator nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 17: Screen Capture/Print Screen, default: Screen Capture => Screen Capture nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 18: App Contextual Menu/Right Click, default: Right Click/App Contextual Menu => Right Click/App Contextual Menu nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 19: Lock PC , default: WindowsLock => WindowsLock nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 20: Left Arrow , default: Keyboard Left Arrow => Keyboard Left Arrow nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 21: Right Arrow , default: Keyboard Right Arrow => Keyboard Right Arrow nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 22: F Lock , default: Do Nothing One => Do Nothing One is FN, pos:0, group:0, group mask:empty reporting: default 23: FN Key , default: Do Nothing One => Do Nothing One nonstandard, pos:0, group:0, group mask:empty reporting: default Has 17 persistent remappable keys: 0: Brightness Down => None 1: Brightness Up => None 2: Mission Control/Task View => Key: Meta+TAB 3: Dashboard Launchpad/Action Center => Key: Meta+A 4: Show Desktop => Key: Meta+D 5: Backlight Down => None 6: Backlight Up => None 7: Previous Fn => Consumer: Scan Previous Track 8: Play/Pause Fn => Consumer: Play/Pause 9: Next Fn => Consumer: Scan Next Track 10: Mute Fn => Consumer: Mute 11: Volume Down Fn => Consumer: Volume Down 12: Volume Up Fn => Consumer: Volume Up 13: Calculator => Consumer: AL Calculator 14: Screen Capture/Print Screen => Key: SYSRQ 15: App Contextual Menu/Right Click => Key: COMPOSE 16: Lock PC => Key: Meta+L Battery: 50%, discharging, next level 20%. Solaar-1.1.20/docs/devices/ERGO M575 Trackball 4096.txt000066400000000000000000000106431522022367600215550ustar00rootroot00000000000000Solaar version 1.1.4 1: ERGO M575 Trackball Device path : /dev/hidraw5 WPID : 4096 Codename : ERGO M575 Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: 460DFDC2 Model ID: B02740960000 Unit ID: 460DFDC2 Bootloader: BOT 40.00.B0009 Firmware: MPM 26.00.B0009 Other: The power switch is located on the base. Supports 25 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 40.00.B0009 0000ABF1CB8B01 Firmware: Firmware MPM 26.00.B0009 4096ABF1CB8B01 Firmware: Other Unit ID: 460DFDC2 Model ID: B02740960000 Transport IDs: {'btleid': 'B027', 'wpid': '4096'} 3: DEVICE NAME {0005} Name: ERGO M575 Trackball Kind: trackball 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: DEVICE FRIENDLY NAME {0007} Friendly Name: ERGO M575 7: UNIFIED BATTERY {1004} 8: REPROG CONTROLS V4 {1B04} DPI Sliding Adjustment (saved): Off DPI Sliding Adjustment : Off Mouse Gestures (saved): Off Mouse Gestures : Off Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button} 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} Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular} 9: ADJUSTABLE DPI {2201} Sensitivity (DPI) (saved): 500 Sensitivity (DPI) : 500 10: XY STATS {2250} 11: DFUCONTROL SIGNED {00C2} 12: DEVICE RESET {1802} internal, hidden 13: unknown:1803 {1803} internal, hidden 14: CONFIG DEVICE PROPS {1806} internal, hidden 15: unknown:1812 {1812} internal, hidden 16: OOBSTATE {1805} internal, hidden 17: unknown:1830 {1830} internal, hidden 18: unknown:1890 {1890} internal, hidden 19: unknown:1891 {1891} internal, hidden 20: unknown:18A1 {18A1} internal, hidden 21: unknown:1E00 {1E00} hidden 22: unknown:1EB0 {1EB0} internal, hidden 23: unknown:1861 {1861} internal, hidden 24: unknown:1E22 {1E22} internal, hidden Has 6 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, reprogrammable, analytics key events, pos:0, group:1, group mask:g1 reporting: default 1: Right Button , default: Right Click => Right Click mse, reprogrammable, 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, persistently divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 reporting: default 3: Forward Button , default: Mouse Forward Button => Mouse Forward Button mse, reprogrammable, divertable, persistently divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 reporting: default 4: Back Button , default: Mouse Back Button => Mouse Back Button mse, reprogrammable, divertable, persistently divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 reporting: default 5: 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: 85%, discharging. Solaar-1.1.20/docs/devices/EX100 Receiver 27 Mhz C517.text000066400000000000000000000157641522022367600221300ustar00rootroot00000000000000solaar version 1.1.11-80-gdea496f EX100 Receiver 27 Mhz Device path : /dev/hidraw2 USB id : 046d:C517 Serial : None 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 Device Activity 0xb3: None Pairing Register 0xb5 0x00: None Pairing Register 0xb5 0x01: None Pairing Register 0xb5 0x02: None Pairing Register 0xb5 0x03: None Pairing Register 0xb5 0x04: None Pairing Register 0xb5 0x05: None Pairing Register 0xb5 0x06: None Pairing Register 0xb5 0x07: None Pairing Register 0xb5 0x08: None Pairing Register 0xb5 0x09: None Pairing Register 0xb5 0x0a: None Pairing Register 0xb5 0x0b: None Pairing Register 0xb5 0x0c: None Pairing Register 0xb5 0x0d: None Pairing Register 0xb5 0x0e: None Pairing Register 0xb5 0x0f: None Pairing Register 0xb5 0x10: None Pairing Register 0xb5 0x20: None Pairing Register 0xb5 0x30: None Pairing Register 0xb5 0x50: None Pairing Name 0xb5 0x40: None Pairing Name 0xb5 0x60 0x1: 0 None Pairing Name 0xb5 0x60 0x2: 0 None Pairing Name 0xb5 0x60 0x3: 0 None Pairing Register 0xb5 0x11: None Pairing Register 0xb5 0x21: None Pairing Register 0xb5 0x31: None Pairing Register 0xb5 0x51: None Pairing Name 0xb5 0x41: None Pairing Name 0xb5 0x61 0x1: 0 None Pairing Name 0xb5 0x61 0x2: 0 None Pairing Name 0xb5 0x61 0x3: 0 None Pairing Register 0xb5 0x12: None Pairing Register 0xb5 0x22: None Pairing Register 0xb5 0x32: None Pairing Register 0xb5 0x52: None Pairing Name 0xb5 0x42: None Pairing Name 0xb5 0x62 0x1: 0 None Pairing Name 0xb5 0x62 0x2: 0 None Pairing Name 0xb5 0x62 0x3: 0 None Pairing Register 0xb5 0x13: None Pairing Register 0xb5 0x23: None Pairing Register 0xb5 0x33: None Pairing Register 0xb5 0x53: None Pairing Name 0xb5 0x43: None Pairing Name 0xb5 0x63 0x1: 0 None Pairing Name 0xb5 0x63 0x2: 0 None Pairing Name 0xb5 0x63 0x3: 0 None Pairing Register 0xb5 0x14: None Pairing Register 0xb5 0x24: None Pairing Register 0xb5 0x34: None Pairing Register 0xb5 0x54: None Pairing Name 0xb5 0x44: None Pairing Name 0xb5 0x64 0x1: 0 None Pairing Name 0xb5 0x64 0x2: 0 None Pairing Name 0xb5 0x64 0x3: 0 None Pairing Register 0xb5 0x15: None Pairing Register 0xb5 0x25: None Pairing Register 0xb5 0x35: None Pairing Register 0xb5 0x55: None Pairing Name 0xb5 0x45: None Pairing Name 0xb5 0x65 0x1: 0 None Pairing Name 0xb5 0x65 0x2: 0 None Pairing Name 0xb5 0x65 0x3: 0 None Pairing Register 0xb5 0x16: None Pairing Register 0xb5 0x26: None Pairing Register 0xb5 0x36: None Pairing Register 0xb5 0x56: None Pairing Name 0xb5 0x46: None Pairing Name 0xb5 0x66 0x1: 0 None Pairing Name 0xb5 0x66 0x2: 0 None Pairing Name 0xb5 0x66 0x3: 0 None Firmware 0xf1 0x00: None Firmware 0xf1 0x01: None Firmware 0xf1 0x02: None Firmware 0xf1 0x03: None Firmware 0xf1 0x04: None Register Short 0x00 0x00: 0x000100 Register Long 0x00 0x00: invalid SubID/command ... Register Long 0x00 0xfe: invalid SubID/command Register Short 0x01 0x00: 0x000200 Register Long 0x01 0x00: invalid SubID/command Register Long 0x01 0x01: invalid SubID/command Register Long 0x01 0x02: invalid SubID/command ... ./scan-registers.sh ff /dev/hidraw4 # Old notification flags: 000100 >> ( 0.035) [10 FF 8100 000100] '\x10\xff\x81\x00\x00\x01\x00' << ( 0.015) [10 FF 8101 000000] '\x10\xff\x81\x01\x00\x00\x00' >> ( 0.020) [10 FF 8101 000200] '\x10\xff\x81\x01\x00\x02\x00' << ( 0.030) [10 FF 8102 000000] '\x10\xff\x81\x02\x00\x00\x00' >> ( 0.036) [10 FF 8102 000200] '\x10\xff\x81\x02\x00\x02\x00' -- << ( 0.142) [10 FF 8109 000000] '\x10\xff\x81\t\x00\x00\x00' >> ( 0.148) [10 FF 8109 010000] '\x10\xff\x81\t\x01\x00\x00' -- << ( 1.790) [10 FF 8170 000000] '\x10\xff\x81p\x00\x00\x00' >> ( 1.796) [10 FF 8170 012100] '\x10\xff\x81p\x01!\x00' << ( 1.806) [10 FF 8171 000000] '\x10\xff\x81q\x00\x00\x00' >> ( 1.812) [10 FF 8171 011200] '\x10\xff\x81q\x01\x12\x00' -- << ( 1.838) [10 FF 8173 000000] '\x10\xff\x81s\x00\x00\x00' >> ( 1.844) [10 FF 8173 643F00] '\x10\xff\x81sd?\x00' -- << ( 2.046) [10 FF 8180 000000] '\x10\xff\x81\x80\x00\x00\x00' >> ( 2.052) [10 FF 8180 030000] '\x10\xff\x81\x80\x03\x00\x00' -- << ( 3.326) [10 FF 81D0 000000] '\x10\xff\x81\xd0\x00\x00\x00' >> ( 3.332) [10 FF 81D0 000000] '\x10\xff\x81\xd0\x00\x00\x00' devices 01 mouse Red button pressed >> ( 1676.106) [10 01 0810 000000] '\x10\x01\x08\x10\x00\x00\x00' >> ( 1676.114) [10 01 4600 000021] '\x10\x01F\x00\x00\x00!' >> ( 1676.122) [10 FF 4600 211100] '\x10\xffF\x00!\x11\x00' Power lewel? ?? Input: 10 01 81 07 00 00 00 << ( 1739.032) [10 01 8107 000000] '\x10\x01\x81\x07\x00\x00\x00' >> ( 1739.040) [10 01 8107 030000] '\x10\x01\x81\x07\x03\x00\x00' [10 01 8107 070000] '\x10\x01\x81\x07\x07\x00\x00' power change >> ( 2441.563) [10 01 0703 000000] '\x10\x01\x07\x03\x00\x00\x00' >> ( 100.159) [10 01 0707 000000] '\x10\x01\x07\x07\x00\x00\x00' enable power event << ( 59.190) [10 01 8000 100000] '\x10\x01\x80\x00\x10\x00\x00' >> ( 59.193) [10 01 8000 000000] '\x10\x01\x80\x00\x00\x00\x00' 03 keyboard Power level? ?? Input: 10 03 81 07 00 00 00 << ( 1777.961) [10 03 8107 000000] '\x10\x03\x81\x07\x00\x00\x00' >> ( 1777.967) [10 03 8107 070000] '\x10\x03\x81\x07\x07\x00\x00' power on >> ( 1571.805) [10 03 0810 000000] '\x10\x03\x08\x10\x00\x00\x00' >> ( 1574.709) [10 03 0800 000000] '\x10\x03\x08\x00\x00\x00\x00' red button pressed >> ( 1619.043) [10 03 0810 000000] '\x10\x03\x08\x10\x00\x00\x00' >> ( 1619.051) [10 03 4600 000011] '\x10\x03F\x00\x00\x00\x11' >> ( 1619.059) [10 FF 4600 221100] '\x10\xffF\x00"\x11\x00' >> ( 1621.747) [10 03 0800 000000] '\x10\x03\x08\x00\x00\x00\x00' Fn pressed >> ( 1651.715) [10 03 032C 100000] '\x10\x03\x03,\x10\x00\x00' >> ( 1652.170) [10 03 0300 000000] '\x10\x03\x03\x00\x00\x00\x00' Battery status: 1.9V critical 2.3V low 2.5V full Solaar-1.1.20/docs/devices/G Pro Wireless Gaming Mouse 4079.txt000066400000000000000000000051751522022367600234510ustar00rootroot00000000000000Solaar version 1.1.4rc1 1: G Pro Wireless Gaming Mouse Device path : /dev/hidraw10 WPID : 4079 Codename : G Pro Kind : mouse Protocol : HID++ 4.2 Polling rate : 2 ms (500Hz) Serial number: 40B217C0 Model ID: 4079C0880000 Unit ID: 40B217C0 Bootloader: BOT 74.02.B0026 Firmware: MPM 15.02.B0026 Other: The power switch is located on the base. Supports 28 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 74.02.B0026 AADF85EE714A Firmware: Firmware MPM 15.02.B0026 407985EE714A Firmware: Other Unit ID: 40B217C0 Model ID: 4079C0880000 Transport IDs: {'wpid': '4079', 'usbid': 'C088'} 3: DEVICE NAME {0005} Name: G Pro Wireless Gaming Mouse Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: BATTERY VOLTAGE {1001} Battery: 3926mV, discharging, 70. 7: COLOR LED EFFECTS {8070} 8: LED CONTROL {1300} 9: ONBOARD PROFILES {8100} Device Mode: On-Board Onboard Profiles (saved): Enable Onboard Profiles : Enable 10: MOUSE BUTTON SPY {8110} 11: REPORT RATE {8060} Polling Rate (ms): 2 Polling Rate (ms) (saved): 2 Polling Rate (ms) : 2 12: ADJUSTABLE DPI {2201} Sensitivity (DPI) (saved): 1600 Sensitivity (DPI) : 1600 13: DEVICE RESET {1802} internal, hidden 14: unknown:1803 {1803} internal, hidden 15: OOBSTATE {1805} internal, hidden 16: CONFIG DEVICE PROPS {1806} internal, hidden 17: unknown:1811 {1811} 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:1DF3 {1DF3} internal, hidden 25: unknown:1E00 {1E00} hidden 26: unknown:1EB0 {1EB0} internal, hidden 27: unknown:1863 {1863} internal, hidden Battery: 3926mV, discharging, 70. Solaar-1.1.20/docs/devices/G213 Prodigy Gaming Keyboard C366.txt000066400000000000000000000030351522022367600234550ustar00rootroot00000000000000Solaar version 1.1.5rc1 USB and Bluetooth Devices 1: G213 Prodigy Gaming Keyboard Device path : /dev/hidraw2 USB id : 046d:C336 Codename : G213 Kind : keyboard Protocol : HID++ 4.2 Polling rate : 1 ms (1000Hz) Serial number: Model ID: C33600000000 Unit ID: 31314709 Firmware: U1 09.00.B0006 Bootloader: BOT 31.00.B0002 Supports 15 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V2 Firmware: Firmware U1 09.00.B0006 C336 Firmware: Bootloader BOT 31.00.B0002 AAC8 Unit ID: 31314709 Model ID: C33600000000 Transport IDs: {'usbid': 'C336'} 3: KEYBOARD DISABLE BY USAGE {4522} V0 4: DEVICE NAME {0005} V0 Name: Gaming Keyboard G213 Kind: keyboard 5: unknown:1E00 {1E00} V0 hidden 6: KEYBOARD LAYOUT 2 {4540} V0 7: unknown:1EB0 {1EB0} V0 internal, hidden 8: REPORT RATE {8060} V0 Polling Rate (ms): 1 Polling Rate (ms) : 1 9: DFUCONTROL SIGNED {00C2} V0 10: unknown:1801 {1801} V0 internal, hidden 11: DEVICE RESET {1802} V0 internal, hidden 12: COLOR LED EFFECTS {8070} V5 13: unknown:1821 {1821} V0 14: REPORT HID USAGE {1BC0} V0 Battery: N/A, None. Solaar-1.1.20/docs/devices/G304 Lightspeed Wireless Gaming Mouse 4074.txt000066400000000000000000000051761522022367600252240ustar00rootroot00000000000000solaar version 1.1.8 1: G304 Lightspeed Wireless Gaming Mouse Device path : /dev/hidraw6 WPID : 4074 Codename : G304 Kind : mouse Protocol : HID++ 4.2 Polling rate : 8 ms (125Hz) Serial number: B2D05D23 Model ID: 407400000000 Unit ID: EB490C63 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: EB490C63 Model ID: 407400000000 Transport IDs: {'wpid': '4074'} 3: DEVICE NAME {0005} V0 Name: G304 Lightspeed Wireless Gaming Mouse Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} V0 5: BATTERY STATUS {1000} V0 Battery: 90%, discharging, next level 50%. 6: COLOR LED EFFECTS {8070} V6 7: ONBOARD PROFILES {8100} V0 Device Mode: Host Onboard Profiles (saved): Disable Onboard Profiles : Disable 8: MOUSE BUTTON SPY {8110} V0 9: REPORT RATE {8060} V0 Polling Rate (ms): 8 Polling Rate (ms) (saved): 8 Polling Rate (ms) : 8 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): 2200 Sensitivity (DPI) : 2200 Battery: 90%, discharging, next level 50%. Solaar-1.1.20/docs/devices/G305 Lightspeed Wireless Gaming Mouse 4074.text000066400000000000000000000052011522022367600253570ustar00rootroot00000000000000solaar 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%. Solaar-1.1.20/docs/devices/G502 Gaming Mouse C07D.text000066400000000000000000000036041522022367600215730ustar00rootroot00000000000000Solaar version 1.1.5 1: G502 Gaming Mouse Device path : /dev/hidraw1 USB id : 046d:C07D Codename : G502 Kind : mouse Protocol : HID++ 4.2 Polling rate : 1 ms (1000Hz) Serial number: Model ID: 000000000000 Unit ID: 00000000 Firmware: U 88.02.B0017 Bootloader: BOT 14.00.B0007 Hardware: 72 Other: Supports 19 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: FEATURE INFO {0002} V0 3: DEVICE FW VERSION {0003} V0 Firmware: Firmware U 88.02.B0017 Firmware: Bootloader BOT 14.00.B0007 Firmware: Hardware 72 Firmware: Other Unit ID: 00000000 Model ID: 000000000000 Transport IDs: {} 4: DEVICE NAME {0005} V0 Name: Tunable FPS Gaming Mouse G502 Kind: mouse 5: LED CONTROL {1300} V0 6: unknown:18A1 {18A1} V0 internal, hidden 7: unknown:1E00 {1E00} V0 hidden 8: unknown:1E20 {1E20} V0 9: unknown:1EB0 {1EB0} V0 internal, hidden 10: ADJUSTABLE DPI {2201} V0 Sensitivity (DPI) : 800 11: ANGLE SNAPPING {2230} V0 12: SURFACE TUNING {2240} V0 13: REPORT RATE {8060} V0 Polling Rate (ms): 1 Polling Rate (ms) : 1 14: ONBOARD PROFILES {8100} V0 Device Mode: Host Onboard Profiles : Disable 15: MOUSE BUTTON SPY {8110} V0 16: unknown:1850 {1850} V0 internal, hidden 17: DFUCONTROL UNSIGNED {00C1} V0 18: unknown:1801 {1801} V0 internal, hidden Battery: N/A, None. Solaar-1.1.20/docs/devices/G502 Lightspeed Wireless Gaming Mouse 407F.txt000066400000000000000000000071621522022367600252430ustar00rootroot00000000000000solaar version 1.1.12rc1 1: G502 Gaming Mouse Device path : /dev/hidraw20 WPID : 407F Codename : G502 Kind : mouse Protocol : HID++ 4.2 Report Rate : 1ms Serial number: DDDAADBC Model ID: 407FC08D0000 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 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V2 Firmware: Bootloader BOT 92.00.B0008 AAEF21F1FA5F Firmware: Firmware MPM 17.00.B0008 407F21F1FA5F Firmware: Other 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: CONFIG CHANGE {0020} V0 Configuration: 11000000000000000000000000000000 6: BATTERY VOLTAGE {1001} V2 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): 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): 900 Sensitivity (DPI) : 900 13: DEVICE RESET {1802} V0 internal, hidden 14: unknown:1803 {1803} V0 internal, hidden 15: OOBSTATE {1805} V0 internal, hidden 16: CONFIG DEVICE PROPS {1806} V4 internal, hidden 17: unknown:1811 {1811} V0 internal, hidden 18: unknown:1830 {1830} V0 internal, hidden 19: unknown:1890 {1890} V4 internal, hidden 20: unknown:1891 {1891} V4 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:1DF3 {1DF3} V0 internal, hidden 25: unknown:1E00 {1E00} V0 hidden 26: unknown:1EB0 {1EB0} V0 internal, hidden 27: unknown:1863 {1863} V0 internal, hidden 28: unknown:1E22 {1E22} 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 Battery: 90% 4166mV , discharging. Solaar-1.1.20/docs/devices/G502 Proteus Spectrum Optical Mouse C332.txt000066400000000000000000000032241522022367600247770ustar00rootroot00000000000000solaar version 1.1.9 2: G502 Proteus Spectrum Optical Mouse Device path : /dev/hidraw4 USB id : 046d:C332 Codename : G502 Proteus Spectrum Kind : mouse Protocol : HID++ 4.2 Polling rate : 1 ms (1000Hz) Serial number: Model ID: C33200000000 Unit ID: 31374706 Firmware: U1 03.02.B0012 Bootloader: BOT 14.00.B0007 Supports 20 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: COLOR LED EFFECTS {8070} V3 3: DEVICE FW VERSION {0003} V1 Firmware: Firmware U1 03.02.B0012 C332 Firmware: Bootloader BOT 14.00.B0007 AABF Unit ID: 31374706 Model ID: C33200000000 Transport IDs: {'usbid': 'C332'} 4: DEVICE NAME {0005} V0 Name: Tunable RGB Gaming Mouse G502 Kind: mouse 5: LED CONTROL {1300} V0 6: unknown:18A1 {18A1} V0 internal, hidden 7: unknown:1E00 {1E00} V0 hidden 8: unknown:1E20 {1E20} V0 9: unknown:1EB0 {1EB0} V0 internal, hidden 10: ADJUSTABLE DPI {2201} V1 Sensitivity (DPI) (saved): 7000 Sensitivity (DPI) : 7000 11: ANGLE SNAPPING {2230} V0 12: SURFACE TUNING {2240} V0 13: REPORT RATE {8060} V0 Polling Rate (ms): 1 Polling Rate (ms) (saved): 1 Polling Rate (ms) : 1 14: ONBOARD PROFILES {8100} V0 Device Mode: On-Board Onboard Profiles (saved): Enable Onboard Profiles : Enable 15: MOUSE BUTTON SPY {8110} V0 16: unknown:1850 {1850} V0 internal, hidden 17: DFUCONTROL UNSIGNED {00C1} V0 18: unknown:1801 {1801} V0 internal, hidden 19: DEVICE RESET {1802} V0 internal, hidden Battery status unavailable. Solaar-1.1.20/docs/devices/G502 SE Hero Gaming Mouse C08B.txt000066400000000000000000000035141522022367600225730ustar00rootroot000000000000001: G502 SE Hero Gaming Mouse Device path : /dev/hidraw7 USB id : 046d:C08B Codename : G502 Hero Kind : mouse Protocol : HID++ 4.2 Polling rate : 1 ms (1000Hz) Serial number: Model ID: C08B00000000 Unit ID: 30324703 Firmware: U1 27.03.B0010 Bootloader: BOT 81.00.B0002 Supports 19 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: COLOR LED EFFECTS {8070} 3: DEVICE FW VERSION {0003} Firmware: Firmware U1 27.03.B0010 C08B Firmware: Bootloader BOT 81.00.B0002 AAE6 Unit ID: 30324703 Model ID: C08B00000000 Transport IDs: {'usbid': 'C08B'} 4: DEVICE NAME {0005} Name: G502 HERO Gaming Mouse Kind: mouse 5: LED CONTROL {1300} 6: unknown:18A1 {18A1} internal, hidden 7: unknown:1E00 {1E00} hidden 8: unknown:1E22 {1E22} internal, hidden 9: unknown:1EB0 {1EB0} internal, hidden 10: ADJUSTABLE DPI {2201} Sensitivity (DPI) (saved): 2400 Sensitivity (DPI) : 2400 11: REPORT RATE {8060} Polling Rate (ms): 1 Polling Rate (ms) (saved): 1 Polling Rate (ms) : 1 12: ONBOARD PROFILES {8100} Device Mode: Host 13: MOUSE BUTTON SPY {8110} 14: DFUCONTROL SIGNED {00C2} 15: unknown:1801 {1801} internal, hidden 16: DEVICE RESET {1802} internal, hidden 17: CONFIG DEVICE PROPS {1806} internal, hidden 18: unknown:18B1 {18B1} internal, hidden Battery status unavailable. Solaar-1.1.20/docs/devices/G502 X C099.txt000066400000000000000000000054201522022367600173470ustar00rootroot00000000000000Solaar version 1.1.7 1: G502 X Device path : /dev/hidraw2 USB id : 046d:C099 Codename : G502 Kind : mouse Protocol : HID++ 4.2 Polling rate : 1 ms (1000Hz) Serial number: Model ID: C09900000000 Unit ID: 42004C00 Bootloader: BL1 59.00.B0002 Firmware: U1 60.00.B0009 Supports 22 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V4 Firmware: Bootloader BL1 59.00.B0002 AB17 Firmware: Firmware U1 60.00.B0009 C099 Unit ID: 42004C00 Model ID: C09900000000 Transport IDs: {'usbid': 'C099'} 3: DEVICE NAME {0005} V0 Name: G502 X Kind: mouse 4: RESET {0020} V0 5: DFUCONTROL SIGNED {00C2} V0 6: unknown:1801 {1801} V0 internal, hidden, unknown:000010 7: DEVICE RESET {1802} V0 internal, hidden, unknown:000010 8: OOBSTATE {1805} V0 internal, hidden 9: CONFIG DEVICE PROPS {1806} V8 internal, hidden, unknown:000010 10: unknown:1875 {1875} V0 internal, hidden, unknown:000010 11: unknown:18A1 {18A1} V0 internal, hidden, unknown:000010 12: unknown:18B1 {18B1} V0 internal, hidden, unknown:000010 13: unknown:18C0 {18C0} V0 internal, hidden, unknown:000010 14: unknown:1E00 {1E00} V0 hidden 15: unknown:1E22 {1E22} V0 internal, hidden, unknown:000010 16: unknown:1EB0 {1EB0} V0 internal, hidden, unknown:000010 17: HIRES WHEEL {2121} V1 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 18: ADJUSTABLE DPI {2201} V2 Sensitivity (DPI) (saved): 1600 Sensitivity (DPI) : 1600 19: REPORT RATE {8060} V0 Polling Rate (ms): 1 Polling Rate (ms) (saved): 1 Polling Rate (ms) : 1 20: ONBOARD PROFILES {8100} V0 Device Mode: On-Board Onboard Profiles (saved): Enable Onboard Profiles : Enable 21: MOUSE BUTTON SPY {8110} V0 Battery status unavailable. Solaar-1.1.20/docs/devices/G502 X PLUS 4099.txt000066400000000000000000000110261522022367600200730ustar00rootroot00000000000000solaar 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. Solaar-1.1.20/docs/devices/G515 LS TKL 40B4.text000066400000000000000000000143101522022367600202250ustar00rootroot00000000000000solaar 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. Solaar-1.1.20/docs/devices/G535 Wireless Gaming Headset 0AC4.txt000066400000000000000000000020011522022367600234570ustar00rootroot00000000000000solaar version 1.1.8 USB and Bluetooth Devices 1: G535 Wireless Gaming Headset Device path : /dev/hidraw2 USB id : 046d:0AC4 Codename : G535 Kind : ? Protocol : HID++ 4.2 Serial number: Model ID: 000000000AC4 Unit ID: FFFFFFFF Firmware: U1 90.00.B0200 Supports 6 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V2 Firmware: Firmware U1 90.00.B0200 0AC4 Unit ID: FFFFFFFF Model ID: 000000000AC4 Transport IDs: {'btid': '0000', 'btleid': '0000'} 3: DEVICE NAME {0005} V0 Name: G535 Wireless Gaming Headset Kind: None 4: SIDETONE {8300} V0 Sidetone (saved): 0 Sidetone : 0 5: ADC MEASUREMENT {1F20} V0 Battery: 60% 3920mV , discharging. Battery: 60% 3920mV , discharging. Solaar-1.1.20/docs/devices/G600 Gaming Mouse C24A.txt000066400000000000000000000147251522022367600214270ustar00rootroot00000000000000This mouse does not use HID++ [root@gpiro device]# lsusb -vv -d 046d:c24a Bus 003 Device 002: ID 046d:c24a Logitech, Inc. G600 Gaming Mouse Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 32 idVendor 0x046d Logitech, Inc. idProduct 0xc24a G600 Gaming Mouse bcdDevice 77.02 iManufacturer 1 Logitech iProduct 2 Gaming Mouse G600 iSerial 3 385226BFEFD20017 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x003b bNumInterfaces 2 bConfigurationValue 1 iConfiguration 4 U77.02_B0017 bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 500mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 1 Boot Interface Subclass bInterfaceProtocol 2 Mouse iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 67 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0009 1x 9 bytes bInterval 1 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 1 Keyboard iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.11 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 135 Report Descriptors: ** UNAVAILABLE ** Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0020 1x 32 bytes bInterval 1 can't get device qualifier: Resource temporarily unavailable can't get debug descriptor: Resource temporarily unavailable Device Status: 0x0000 (Bus Powered) /sys/devices/pci0000:00/0000:00:08.1/0000:05:00.4/usb3/3-2/3-2:1.1/0003:046D:C24A.0007/report_descriptor Usage Page (Desktop), ; Generic desktop controls (01h) Usage (Keyboard), ; Keyboard (06h, application collection) Collection (Application), Report ID (1), Usage Page (Keyboard), ; Keyboard/keypad (07h) Usage Minimum (KB Leftcontrol), ; Keyboard left control (E0h, dynamic value) Usage Maximum (KB Right GUI), ; Keyboard right GUI (E7h, dynamic value) Logical Minimum (0), Logical Maximum (1), Report Size (1), Report Count (8), Input (Variable), Report Size (8), Report Count (5), Logical Minimum (0), Logical Maximum (164), Usage Minimum (None), ; No event (00h, selector) Usage Maximum (KB ExSel), ; Keyboard ExSel (A4h, selector) Input, End Collection, Usage Page (FF80h), ; FF80h, vendor-defined Usage (80h), Collection (Application), Report ID (128), Usage (80h), Report Size (8), Report Count (5), Input (Variable), Report ID (246), Usage (F6h), Report Size (8), Report Count (7), Input (Variable), Report ID (240), Usage (F0h), Report Count (3), Feature (Variable), Report ID (241), Usage (F1h), Report Count (7), Feature (Variable), Report ID (242), Usage (F2h), Report Count (4), Feature (Variable), Report ID (243), Usage (F3h), Report Count (153), Feature (Variable), Report ID (244), Usage (F4h), Report Count (153), Feature (Variable), Report ID (245), Usage (F5h), Report Count (153), Feature (Variable), Report ID (246), Usage (F6h), Report Count (7), Feature (Variable), Report ID (247), Usage (F7h), Report Size (8), Report Count (31), Input (Variable), End Collection /sys/devices/pci0000:00/0000:00:08.1/0000:05:00.4/usb3/3-2/3-2:1.0/0003:046D:C24A.0006/report_descriptor Usage Page (Desktop), ; Generic desktop controls (01h) Usage (Mouse), ; Mouse (02h, application collection) Collection (Application), Usage (Pointer), ; Pointer (01h, physical collection) Collection (Physical), Usage Page (Button), ; Button (09h) Usage Minimum (01h), Usage Maximum (10h), Logical Minimum (0), Logical Maximum (1), Report Size (1), Report Count (16), Input (Variable), Usage Page (Desktop), ; Generic desktop controls (01h) Usage (X), ; X (30h, dynamic value) Usage (Y), ; Y (31h, dynamic value) Logical Minimum (-32767), Logical Maximum (32767), Report Size (16), Report Count (2), Input (Variable, Relative), Usage (Wheel), ; Wheel (38h, dynamic value) Report Size (8), Report Count (1), Logical Minimum (-127), Logical Maximum (127), Input (Variable, Relative), Usage Page (Consumer), ; Consumer (0Ch) Usage (AC Pan), ; AC pan (0238h, linear control) Report Count (1), Input (Variable, Relative), End Collection, End Collection Solaar-1.1.20/docs/devices/G604 Wireless Gaming Mouse 4085.txt000066400000000000000000000072361522022367600231570ustar00rootroot00000000000000solaar 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%. Solaar-1.1.20/docs/devices/G613 Wireless Mechanical Gaming Keyboard 4065.txt000066400000000000000000000071471522022367600256330ustar00rootroot00000000000000solaar version 1.1.19-25-g7520c9cc 1: G613 Wireless Mechanical Gaming Keyboard Device path : None WPID : 4065 Codename : G613 Kind : keyboard Protocol : HID++ 4.2 Report Rate : 1ms Serial number: 710EC3A3 Model ID: B34F40650000 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} 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} V0 5: CONFIG CHANGE {0020} V0 Configuration: 11000000000000000000000000000000 6: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: G613 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} 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 persistently_divertable, divertable, pos:1, group:0, group mask:empty reporting: default 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: 80%, BatteryStatus.DISCHARGING, next level 50%. Solaar-1.1.20/docs/devices/G703 Wired-Wireless Gaming Mouse 4070.txt000066400000000000000000000035741522022367600242220ustar00rootroot00000000000000 1: G703 Wired/Wireless Gaming Mouse Codename : G703 Kind : mouse Wireless PID : 4070 Protocol : HID++ 4.2 Polling rate : 1 ms (1000Hz) Serial number: AFE674F7 Firmware: MPM 14.02.B0007 Bootloader: BOT 64.02.B0007 Other: The power switch is located on the base. Supports 29 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: BATTERY VOLTAGE {1001} 5: unknown:1863 {1863} internal, hidden 6: unknown:18A1 {18A1} internal, hidden 7: unknown:1E00 {1E00} hidden 8: unknown:1E20 {1E20} 9: unknown:1EB0 {1EB0} internal, hidden 10: ADJUSTABLE DPI {2201} Sensitivity (DPI): 1600 11: ANGLE SNAPPING {2230} 12: SURFACE TUNING {2240} 13: REPORT RATE {8060} 14: ONBOARD PROFILES {8100} 15: MOUSE BUTTON SPY {8110} 16: unknown:1850 {1850} internal, hidden 17: DFUCONTROL SIGNED {00C2} 18: unknown:1801 {1801} internal, hidden 19: DEVICE RESET {1802} internal, hidden 20: unknown:1803 {1803} internal, hidden 21: unknown:1890 {1890} internal, hidden 22: unknown:1811 {1811} internal, hidden 23: LATENCY MONITORING {8111} 24: COLOR LED EFFECTS {8070} 25: unknown:1809 {1809} 26: unknown:1830 {1830} internal, hidden 27: OOBSTATE {1805} internal, hidden 28: CONFIG DEVICE PROPS {1806} internal, hidden Battery: 3807mV, discharging, average Solaar-1.1.20/docs/devices/G733 Gaming Headset 0AB5.text000066400000000000000000000035741522022367600220660ustar00rootroot00000000000000Solaar version 1.1.19 G733 Gaming Headset Device path : /dev/hidraw0 USB id : 046d:0AB5 Codename : G733 Headset Kind : headset Protocol : HID++ 4.2 Serial number: Model ID: 0AB500000000 Unit ID: FFFFFFFF 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: 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} 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 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 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. Solaar-1.1.20/docs/devices/G733 Gaming Headset 0AFE.text000066400000000000000000000034421522022367600221040ustar00rootroot00000000000000solaar 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. Solaar-1.1.20/docs/devices/G815 Mechanical Keyboard C33F.txt000066400000000000000000000037611522022367600227340ustar00rootroot00000000000000solaar version 1.1.9 1: G815 Mechanical Keyboard Device path : /dev/hidraw2 USB id : 046d:C33F Codename : G815 Kind : keyboard Protocol : HID++ 4.2 Polling rate : 1 ms (1000Hz) Serial number: Model ID: C33F00000000 Unit ID: 35304716 Bootloader: BOT 84.00.B0003 Firmware: U1 31.02.B0018 Other: Other: 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 84.00.B0003 AAEA Firmware: Firmware U1 31.02.B0018 C33F Firmware: Other Firmware: Other Firmware: Other Unit ID: 35304716 Model ID: C33F00000000 Transport IDs: {'usbid': 'C33F'} 3: DEVICE NAME {0005} V0 Name: G815 RGB MECHANICAL GAMING KEYBOARD Kind: keyboard 4: CONFIG CHANGE {0020} V0 5: DFUCONTROL SIGNED {00C2} V0 6: DFU {00D0} V0 7: REPORT HID USAGE {1BC0} V0 8: KEYBOARD DISABLE BY USAGE {4522} V0 9: KEYBOARD LAYOUT 2 {4540} V0 10: GKEY {8010} V0 Divert G Keys (saved): True Divert G Keys : False 11: MKEYS {8020} V0 M-Key LEDs (saved): {M1:False, M2:False, M3:False} M-Key LEDs : {M1:False, M2:False, M3:False} 12: MR {8030} V0 MR-Key LED (saved): False MR-Key LED : False 13: BRIGHTNESS CONTROL {8040} V0 14: REPORT RATE {8060} V0 Polling Rate (ms): 1 Polling Rate (ms) (saved): 1 Polling Rate (ms) : 1 15: RGB EFFECTS {8071} V0 16: PER KEY LIGHTING V2 {8081} V2 17: ONBOARD PROFILES {8100} V0 Device Mode: Host Onboard Profiles (saved): Disable Onboard Profiles : Disable 18: unknown:1801 {1801} V0 internal, hidden 19: DEVICE RESET {1802} V0 internal, hidden 20: CONFIG DEVICE PROPS {1806} V5 internal, hidden 21: unknown:18B0 {18B0} V0 internal, hidden 22: unknown:1E00 {1E00} V0 hidden 23: unknown:1EB0 {1EB0} V0 internal, hidden Battery status unavailable. Solaar-1.1.20/docs/devices/G903 LIGHTSPEED Wireless Gaming Mouse 4087.txt000066400000000000000000000112631522022367600245670ustar00rootroot00000000000000solaar version 1.1.8rc3+git1940-4e7b6b3 1: G903 LIGHTSPEED Wireless Gaming Mouse w/ HERO Device path : /dev/hidraw13 WPID : 4087 Codename : G903 LS Kind : mouse Protocol : HID++ 4.2 Polling rate : 1 ms (1000Hz) Serial number: 3EF038B9 Model ID: 4087C0910000 Unit ID: 3EF038B9 Bootloader: BL1 06.01.B0013 Firmware: MPM 23.01.B0013 Other: The power switch is located on the base. Supports 31 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V2 Firmware: Bootloader BL1 06.01.B0013 0000047072FE Firmware: Firmware MPM 23.01.B0013 4087047072FE Firmware: Other Unit ID: 3EF038B9 Model ID: 4087C0910000 Transport IDs: {'wpid': '4087', 'usbid': 'C091'} 3: DEVICE NAME {0005} V0 Name: G903 LIGHTSPEED Wireless Gaming Mouse w/ HERO Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} V0 5: CONFIG CHANGE {0020} V0 6: BATTERY VOLTAGE {1001} V2 Battery: 90% 4079mV , discharging. 7: RGB EFFECTS {8071} V0 8: ONBOARD PROFILES {8100} V0 Device Mode: On-Board Onboard Profiles (saved): Enable Onboard Profiles : Enable 9: MOUSE BUTTON SPY {8110} V0 10: REPORT RATE {8060} V0 Polling Rate (ms): 1 Polling Rate (ms) (saved): 1 Polling Rate (ms) : 1 11: ADJUSTABLE DPI {2201} V1 Sensitivity (DPI) (saved): 6400 Sensitivity (DPI) : 6400 12: DFUCONTROL SIGNED {00C2} V0 13: DEVICE RESET {1802} V0 internal, hidden 14: unknown:1803 {1803} V0 internal, hidden 15: OOBSTATE {1805} V0 internal, hidden 16: CONFIG DEVICE PROPS {1806} V4 internal, hidden 17: unknown:1811 {1811} V0 internal, hidden 18: unknown:1830 {1830} V0 internal, hidden 19: unknown:1890 {1890} V4 internal, hidden 20: unknown:1891 {1891} V4 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:1DF3 {1DF3} V0 internal, hidden 25: unknown:1E00 {1E00} V0 hidden 26: unknown:1EB0 {1EB0} V0 internal, hidden 27: unknown:1863 {1863} V0 internal, hidden 28: unknown:1E22 {1E22} V0 internal, hidden 29: 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 30: unknown:18C0 {18C0} V0 internal, hidden Battery: 90% 4079mV , discharging. 7: Candy companion chip Device path : /dev/hidraw14 Codename : Candy Kind : touchpad Protocol : HID++ 4.2 Serial number: 4E4E9946 Model ID: 405F00000000 Unit ID: 34304713 Firmware: CC 07.00.B0010 Bootloader: BOT 32.00.B0010 Supports 12 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V2 Firmware: Firmware CC 07.00.B0010 405F Firmware: Bootloader BOT 32.00.B0010 405F Unit ID: 34304713 Model ID: 405F00000000 Transport IDs: {'wpid': '405F'} 3: DEVICE NAME {0005} V0 Name: Candy companion chip Kind: touchpad 4: unknown:18A1 {18A1} V0 internal, hidden 5: unknown:1E00 {1E00} V0 hidden 6: unknown:1EB0 {1EB0} V0 internal, hidden 7: DFUCONTROL SIGNED {00C2} V0 8: unknown:1801 {1801} V0 internal, hidden 9: DEVICE RESET {1802} V0 internal, hidden 10: unknown:1803 {1803} V0 internal, hidden 11: COLOR LED EFFECTS {8070} V4 Battery status unavailable. Solaar-1.1.20/docs/devices/G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard 408E.txt000066400000000000000000000101671522022367600302750ustar00rootroot00000000000000Solaar version 1.1.4 1: G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard Device path : None WPID : 408E Codename : G915 TKL Kind : keyboard Protocol : HID++ 4.2 Polling rate : 1 ms (1000Hz) Serial number: F085CB9D Model ID: B35F408EC343 Unit ID: F085CB9D Bootloader: BL1 12.00.B0017 Other: Firmware: MPK 14.01.B0021 Other: Other: The power switch is located on the top left corner. Supports 37 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BL1 12.00.B0017 00008B79E978 Firmware: Other Firmware: Firmware MPK 14.01.B0021 408E5E599E54 Firmware: Other Firmware: Other Unit ID: F085CB9D Model ID: B35F408EC343 Transport IDs: {'btleid': 'B35F', 'wpid': '408E', 'usbid': 'C343'} 3: DEVICE NAME {0005} Name: G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: DEVICE FRIENDLY NAME {0007} Friendly Name: G915 TKL 7: BATTERY VOLTAGE {1001} Battery: 3923mV, discharging, 70. 8: CHANGE HOST {1814} Change Host : 1:archlinux 9: HOSTS INFO {1815} Host 0 (paired): archlinux Host 1 (paired): 10: RGB EFFECTS {8071} 11: PER KEY LIGHTING V2 {8081} 12: REPROG CONTROLS V4 {1B04} 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} 14: ENCRYPTION {4100} 15: KEYBOARD DISABLE BY USAGE {4522} 16: KEYBOARD LAYOUT 2 {4540} 17: GKEY {8010} Divert G Keys (saved): False Divert G Keys : False 18: MKEYS {8020} M-Key LEDs (saved): {M1:False, M2:False, M3:False} M-Key LEDs : {M1:False, M2:False, M3:False} 19: MR {8030} MR-Key LED (saved): False MR-Key LED : False 20: BRIGHTNESS CONTROL {8040} 21: ONBOARD PROFILES {8100} Device Mode: On-Board Onboard Profiles (saved): Enable Onboard Profiles : Enable 22: REPORT RATE {8060} Polling Rate (ms): 1 Polling Rate (ms) (saved): 1 Polling Rate (ms) : 1 23: DFUCONTROL SIGNED {00C2} 24: DFU {00D0} 25: DEVICE RESET {1802} internal, hidden 26: unknown:1803 {1803} internal, hidden 27: CONFIG DEVICE PROPS {1806} internal, hidden 28: unknown:1813 {1813} internal, hidden 29: OOBSTATE {1805} internal, hidden 30: unknown:1830 {1830} internal, hidden 31: unknown:1890 {1890} internal, hidden 32: unknown:1891 {1891} internal, hidden 33: unknown:18A1 {18A1} internal, hidden 34: unknown:1E00 {1E00} hidden 35: unknown:1EB0 {1EB0} internal, hidden 36: unknown:1861 {1861} 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: 3923mV, discharging, 70. Solaar-1.1.20/docs/devices/G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD 407C.text000066400000000000000000000176151522022367600246200ustar00rootroot00000000000000solaar 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. Solaar-1.1.20/docs/devices/G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD C33E.text000066400000000000000000000104141522022367600246260ustar00rootroot00000000000000solaar 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. Solaar-1.1.20/docs/devices/G915 WIRELESS RGB Mechanical Gaming Keyboard 407E.txt000066400000000000000000000075321522022367600257120ustar00rootroot00000000000000Solaar version 1.1.1 1: G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD Device path : None WPID : 407C Codename : G915 KEYBOARD Kind : keyboard Protocol : HID++ 4.2 Polling rate : 8 ms (125Hz) Serial number: 7FDC464D Model ID: B354407CC33E Unit ID: 7FDC464D Bootloader: BOT 77.01.B0035 Other: Firmware: MPK 09.01.B0035 Other: Other: The power switch is located on the top left corner. Supports 39 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 77.01.B0035 0000691BEBFB Firmware: Other Firmware: Firmware MPK 09.01.B0035 407C691BEBFB Firmware: Other Firmware: Other Unit ID: 7FDC464D Model ID: B354407CC33E Transport IDs: {'btleid': 'B354', 'wpid': '407C', 'usbid': 'C33E'} 3: DEVICE NAME {0005} Name: G915 WIRELESS RGB MECHANICAL GAMING KEYBOARD Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: DEVICE FRIENDLY NAME {0007} Friendly Name: G915 KEYBOARD 7: BATTERY VOLTAGE {1001} Battery: 3914mV, discharging, 60. 8: CHANGE HOST {1814} Change Host : 1:evoxs 9: HOSTS INFO {1815} Host 0 (paired): evoxs Host 1 (unpaired): 10: RGB EFFECTS {8071} 11: PER KEY LIGHTING V2 {8081} 12: REPROG CONTROLS V4 {1B04} Key/Button Diversion (saved): {'209': 0, '210': 0} Key/Button Diversion : {'209': 0, '210': 0} 13: REPORT HID USAGE {1BC0} 14: ENCRYPTION {4100} 15: KEYBOARD DISABLE BY USAGE {4522} 16: KEYBOARD LAYOUT 2 {4540} 17: GKEY {8010} Divert G Keys (saved): False Divert G Keys : False 18: MKEYS {8020} 19: MR {8030} 20: BRIGHTNESS CONTROL {8040} 21: ONBOARD PROFILES {8100} Device Mode: Host 22: REPORT RATE {8060} Polling Rate (ms): 1 Polling Rate (ms) (saved): 1 Polling Rate (ms) : 1 23: DFUCONTROL SIGNED {00C2} 24: DFU {00D0} 25: DEVICE RESET {1802} internal, hidden 26: unknown:1803 {1803} internal, hidden 27: CONFIG DEVICE PROPS {1806} internal, hidden 28: unknown:1813 {1813} internal, hidden 29: OOBSTATE {1805} internal, hidden 30: unknown:1830 {1830} internal, hidden 31: unknown:1890 {1890} internal, hidden 32: unknown:1891 {1891} internal, hidden 33: unknown:18A1 {18A1} internal, hidden 34: unknown:1DF3 {1DF3} internal, hidden 35: unknown:1E00 {1E00} hidden 36: unknown:1EB0 {1EB0} internal, hidden 37: unknown:1861 {1861} internal, hidden 38: unknown:18B0 {18B0} 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: 3914mV, discharging, 60. Solaar-1.1.20/docs/devices/G935 Gaming Headset 0A87.txt000066400000000000000000000022531522022367600217060ustar00rootroot00000000000000Solaar version 1.1.7 1: G935 Gaming Headset Device path : /dev/hidraw2 USB id : 046d:0A87 Codename : G935 Headset Kind : headset Protocol : HID++ 4.2 Serial number: Model ID: 000000000A87 Unit ID: FFFFFFFF Firmware: U1 29.00.B0012 Supports 9 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V2 Firmware: Firmware U1 29.00.B0012 0A87 Unit ID: FFFFFFFF Model ID: 000000000A87 Transport IDs: {'btid': '0000', 'btleid': '0000'} 3: DEVICE NAME {0005} V0 Name: G935 Gaming Headset Kind: None 4: COLOR LED EFFECTS {8070} V0 5: GKEY {8010} V0 Divert G Keys (saved): False Divert G Keys : False 6: EQUALIZER {8310} V0 7: SIDETONE {8300} V0 Sidetone (saved): 0 Sidetone : 0 8: ADC MEASUREMENT {1F20} V0 Battery status unavailable. Battery status unavailable. Solaar-1.1.20/docs/devices/Illuminated Keyboard C318.txt000066400000000000000000000005351522022367600225260ustar00rootroot00000000000000solaar version 1.1.8-29-g0ae14c7 1: Illuminated Keyboard Device path : /dev/hidraw1 USB id : 046d:C318 Codename : Illuminated Kind : keyboard Protocol : HID++ 1.0 Serial number: Firmware: 55.01.B0025 Notifications: (none). Features: (none) Battery status unavailable. Solaar-1.1.20/docs/devices/Illuminated Living-Room Keyboard K830 4032.txt000066400000000000000000000033561522022367600252350ustar00rootroot00000000000000# Provided by Mikkel Munch Mortensen (solaar) 3: Illuminated Living-Room Keyboard K830 Codename : K830 Kind : keyboard Wireless PID : 4032 Protocol : HID++ 4.1 Polling rate : 8 ms (125Hz) Serial number: 9F7C6FD7 Firmware: RQK 56.00.B0020 The power switch is located on the edge of top right corner. Supports 27 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: WIRELESS DEVICE STATUS {1D4B} 5: unknown:0020 {0020} 6: BATTERY STATUS {1000} 7: BACKLIGHT {1981} 8: unknown:1B04 {1B04} 9: unknown:2005 {2005} 10: NEW FN INVERSION {40A2} 11: ENCRYPTION {4100} 12: unknown:4521 {4521} 13: TOUCHPAD RAW XY {6100} hidden 14: unknown:6501 {6501} 15: unknown:00C1 {00C1} 16: unknown:1811 {1811} internal, hidden 17: unknown:1830 {1830} internal, hidden 18: unknown:1890 {1890} internal, hidden 19: unknown:18A0 {18A0} internal, hidden 20: unknown:1DF3 {1DF3} internal, hidden 21: unknown:1E00 {1E00} hidden 22: unknown:1EB0 {1EB0} internal, hidden 23: unknown:1861 {1861} internal, hidden 24: unknown:1A20 {1A20} internal, hidden 25: unknown:18B0 {18B0} internal, hidden 26: unknown:1F07 {1F07} internal, hidden Battery: 50%, discharging. Solaar-1.1.20/docs/devices/K850 Performance Wireless Keyboard 4062.txt000066400000000000000000000203251522022367600246620ustar00rootroot00000000000000Solaar version 1.1.1 2: K850 Performance Wireless Keyboard Device path : /dev/hidraw2 WPID : 4062 Codename : K850 Kind : keyboard Protocol : HID++ 4.5 Polling rate : 20 ms (50Hz) Serial number: E2A15F0B Model ID: B34D40620000 Unit ID: 178D05AD Bootloader: BOT 43.01.B0004 Firmware: MPK 04.03.B0015 Other: The power switch is located on the edge of top right corner. Supports 31 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 43.01.B0004 00002FD362F001 Firmware: Firmware MPK 04.03.B0015 40622FD362F001 Firmware: Other Unit ID: 178D05AD Model ID: B34D40620000 Transport IDs: {'btleid': 'B34D', 'wpid': '4062'} 3: DEVICE NAME {0005} Name: K850 Performance Wireless Keyboard Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: DEVICE FRIENDLY NAME {0007} Friendly Name: Keyboard K850 7: BATTERY STATUS {1000} Battery: 90%, discharging, next level 50%. 8: CHANGE HOST {1814} Change Host : 2:desktop 9: HOSTS INFO {1815} Host 0 (paired): rpi Host 1 (paired): desktop Host 2 (paired): laptop 10: REPROG CONTROLS V4 {1B04} Key/Button Diversion (saved): {'1': 0, '10': 0, '189': 0, '191': 0, '192': 0, '193': 0, '194': 0, '2': 0, '209': 0, '210': 0, '211': 0, '212': 0, '213': 0, '214': 0, '221': 0, '3': 0, '4': 0, '5': 0, '6': 0} Key/Button Diversion : {'209': 0, '210': 0, '211': 0, '213': 0, '214': 0, '189': 0, '212': 0, '6': 0, '4': 0, '5': 0, '3': 0, '2': 0, '1': 0, '194': 0, '221': 0, '191': 0, '10': 0, '193': 0, '192': 0} 11: PERSISTENT REMAPPABLE ACTION {1C00} 12: K375S FN INVERSION {40A3} Swap Fx function (saved): False Swap Fx function : False 13: ENCRYPTION {4100} 14: LOCK KEY STATE {4220} 15: KEYBOARD DISABLE KEYS {4521} Disable keys (saved): {'1': False, '16': False, '2': False, '4': False, '8': False} Disable keys : {'1': False, '2': False, '4': False, '8': False, '16': False} 16: MULTIPLATFORM {4531} Set OS (saved): 0 Set OS : Windows 17: DFUCONTROL SIGNED {00C2} 18: unknown:1803 {1803} internal, hidden 19: CONFIG DEVICE PROPS {1806} internal, hidden 20: OOBSTATE {1805} internal, hidden 21: unknown:1813 {1813} internal, hidden 22: unknown:1830 {1830} internal, hidden 23: unknown:1861 {1861} 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:18B0 {18B0} internal, hidden Has 21 reprogrammable keys: 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 2: Host Switch Channel 3 , default: HostSwitch Channel 3 => HostSwitch Channel 3 nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 3: MultiPlatform Home/Mission Control, default: Multiplatform Home/Mission Control => Multiplatform Home/Mission Control is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:1, group:0, group mask:empty reporting: default 4: MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad, default: Multiplatform Menu/Launchpad => Multiplatform Menu/Launchpad is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:2, group:0, group mask:empty reporting: default 5: Multiplatform Back , default: MultiPlatform Back => MultiPlatform Back is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:3, group:0, group mask:empty reporting: default 6: MultiPlatform Search , default: Multiplatform Search => Multiplatform Search is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:4, group:0, group mask:empty reporting: default 7: Previous , default: Previous => Previous is FN, FN sensitive, divertable, persistently divertable, pos:5, group:0, group mask:empty reporting: default 8: Play/Pause , default: Play/Pause => Play/Pause is FN, FN sensitive, divertable, persistently divertable, pos:6, group:0, group mask:empty reporting: default 9: Next , default: Next => Next is FN, FN sensitive, divertable, persistently divertable, pos:7, group:0, group mask:empty reporting: default 10: Mute , default: Mute => Mute is FN, FN sensitive, divertable, persistently divertable, pos:8, group:0, group mask:empty reporting: default 11: Volume Down , default: Volume Down => Volume Down is FN, FN sensitive, divertable, persistently divertable, pos:9, group:0, group mask:empty reporting: default 12: Volume Up , default: Volume Up => Volume Up is FN, FN sensitive, divertable, persistently divertable, pos:10, group:0, group mask:empty reporting: default 13: Multiplatform Lock , default: WindowsLock => WindowsLock is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:11, group:0, group mask:empty reporting: default 14: Multi Platform Language Switch, default: Multiplatform Language Switch => Multiplatform Language Switch is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:12, group:0, group mask:empty reporting: default 15: Screen Capture/Print Screen, default: Screen Capture => Screen Capture is FN, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 16: Calculator , default: Calculator => Calculator is FN, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 17: Fn Up , default: unknown:0070 => unknown:0070 is FN, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 18: Fn Down , default: unknown:006F => unknown:006F is FN, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 19: F Lock , default: Do Nothing One => Do Nothing One is FN, pos:0, group:0, group mask:empty reporting: default 20: unknown:0034 , default: Do Nothing One => Do Nothing One nonstandard, pos:0, group:0, group mask:empty reporting: default Battery: 90%, discharging, next level 50%. Solaar-1.1.20/docs/devices/K850 Performance Wireless Keyboard B34D.txt000066400000000000000000000227141522022367600247270ustar00rootroot00000000000000Solaar version 1.1.4 2: K850 Performance Wireless Keyboard Device path : /dev/hidraw1 USB id : 046d:B34D Codename : Keyboard K850 Kind : ? Protocol : HID++ 4.5 Serial number: Model ID: B34D40620000 Unit ID: 420F6F9D Bootloader: BOT 43.01.B0004 Firmware: MPK 04.03.B0015 Other: Supports 17 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 43.01.B0004 00002FD362F001 Firmware: Firmware MPK 04.03.B0015 B34D2FD362F001 Firmware: Other Unit ID: 420F6F9D Model ID: B34D40620000 Transport IDs: {'btleid': 'B34D', 'wpid': '4062'} 3: DEVICE NAME {0005} Name: K850 Performance Wireless Keyboard Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: DEVICE FRIENDLY NAME {0007} Friendly Name: Keyboard K850 7: BATTERY STATUS {1000} Battery: 90%, discharging, next level 50%. 8: CHANGE HOST {1814} Change Host : 3:shoaib-MacBookPro 9: HOSTS INFO {1815} Host 0 (paired): Host 1 (paired): Shoaib’s MacBook Pro Host 2 (paired): shoaib-MacBookPro 10: REPROG CONTROLS V4 {1B04} Key/Button Diversion (saved): {Volume Up:Regular, Volume Down:Regular, Mute:Regular, Play/Pause:Regular, Next:Regular, Previous:Regular, Calculator:Regular, Multiplatform Back:Regular, Screen Capture/Print Screen:Regular, Fn Down:Regular, Fn Up:Regular, Multiplatform Lock:Regular, Host Switch Channel 1:Regular, Host Switch Channel 2:Regular, Host Switch Channel 3:Regular, MultiPlatform Search:Regular, MultiPlatform Home/Mission Control:Regular, MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad:Regular, Multi Platform Language Switch:Regular} Key/Button Diversion : {Volume Up:Regular, Volume Down:Regular, Mute:Regular, Play/Pause:Regular, Next:Regular, Previous:Regular, Calculator:Regular, Multiplatform Back:Regular, Screen Capture/Print Screen:Regular, Fn Down:Regular, Fn Up:Regular, Multiplatform Lock:Regular, Host Switch Channel 1:Regular, Host Switch Channel 2:Regular, Host Switch Channel 3:Regular, MultiPlatform Search:Regular, MultiPlatform Home/Mission Control:Regular, MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad:Regular, Multi Platform Language Switch:Regular} 11: PERSISTENT REMAPPABLE ACTION {1C00} Persistent Key/Button Mapping : {Volume Up:Volume Up, Volume Down:Volume Down, Mute:Mute, Play/Pause:Play/Pause, Next:Scan Next Track, Previous:Scan Previous Track, Calculator:AL Calculator, Multiplatform Back:AC Back, Screen Capture/Print Screen:SYSRQ, Multiplatform Lock:Default, MultiPlatform Search:AC Search, MultiPlatform Home/Mission Control:AC Home, MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad:COMPOSE, Multi Platform Language Switch:Alt+Shift+No Output} 12: K375S FN INVERSION {40A3} Swap Fx function (saved): False Swap Fx function : False 13: ENCRYPTION {4100} 14: LOCK KEY STATE {4220} 15: KEYBOARD DISABLE KEYS {4521} 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} Set OS (saved): Windows Set OS : Windows Has 21 reprogrammable keys: 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 2: Host Switch Channel 3 , default: HostSwitch Channel 3 => HostSwitch Channel 3 nonstandard, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 3: MultiPlatform Home/Mission Control, default: Multiplatform Home/Mission Control => Multiplatform Home/Mission Control is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:1, group:0, group mask:empty reporting: default 4: MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad, default: Multiplatform Menu/Launchpad => Multiplatform Menu/Launchpad is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:2, group:0, group mask:empty reporting: default 5: Multiplatform Back , default: MultiPlatform Back => MultiPlatform Back is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:3, group:0, group mask:empty reporting: default 6: MultiPlatform Search , default: Multiplatform Search => Multiplatform Search is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:4, group:0, group mask:empty reporting: default 7: Previous , default: Previous => Previous is FN, FN sensitive, divertable, persistently divertable, pos:5, group:0, group mask:empty reporting: default 8: Play/Pause , default: Play/Pause => Play/Pause is FN, FN sensitive, divertable, persistently divertable, pos:6, group:0, group mask:empty reporting: default 9: Next , default: Next => Next is FN, FN sensitive, divertable, persistently divertable, pos:7, group:0, group mask:empty reporting: default 10: Mute , default: Mute => Mute is FN, FN sensitive, divertable, persistently divertable, pos:8, group:0, group mask:empty reporting: default 11: Volume Down , default: Volume Down => Volume Down is FN, FN sensitive, divertable, persistently divertable, pos:9, group:0, group mask:empty reporting: default 12: Volume Up , default: Volume Up => Volume Up is FN, FN sensitive, divertable, persistently divertable, pos:10, group:0, group mask:empty reporting: default 13: Multiplatform Lock , default: WindowsLock => WindowsLock is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:11, group:0, group mask:empty reporting: default 14: Multi Platform Language Switch, default: Multiplatform Language Switch => Multiplatform Language Switch is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:12, group:0, group mask:empty reporting: default 15: Screen Capture/Print Screen, default: Screen Capture => Screen Capture is FN, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 16: Calculator , default: Calculator => Calculator is FN, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 17: Fn Up , default: unknown:0070 => unknown:0070 is FN, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 18: Fn Down , default: unknown:006F => unknown:006F is FN, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 19: F Lock , default: Do Nothing One => Do Nothing One is FN, pos:0, group:0, group mask:empty reporting: default 20: FN Key , default: Do Nothing One => Do Nothing One nonstandard, pos:0, group:0, group mask:empty reporting: default Has 14 persistent remappable keys: 0: MultiPlatform Home/Mission Control => Consumer: AC Home 1: MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad => Key: COMPOSE 2: Multiplatform Back => Consumer: AC Back 3: MultiPlatform Search => Consumer: AC Search 4: Previous => Consumer: Scan Previous Track 5: Play/Pause => Consumer: Play/Pause 6: Next => Consumer: Scan Next Track 7: Mute => Consumer: Mute 8: Volume Down => Consumer: Volume Down 9: Volume Up => Consumer: Volume Up 10: Multiplatform Lock => Unknown 11: Multi Platform Language Switch => Key: Alt+Shift+No Output 12: Screen Capture/Print Screen => Key: SYSRQ 13: Calculator => Consumer: AL Calculator Battery: 90%, discharging, next level 50%. Solaar-1.1.20/docs/devices/Keyboard K600 TV 4078.txt000066400000000000000000000311601522022367600211730ustar00rootroot00000000000000Solaar version 1.1.7 1: Keyboard K600 TV Device path : /dev/hidraw3 WPID : 4078 Codename : K600 TV Kind : keyboard Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: C4E873FC Model ID: B35340780000 Unit ID: CA575869 Bootloader: BOT 73.00.B0015 Firmware: MPK 08.00.B0015 Other: Other: Supports 36 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V2 Firmware: Bootloader BOT 73.00.B0015 00001E30DA07 Firmware: Firmware MPK 08.00.B0015 40781E30DA07 Firmware: Other Firmware: Other Unit ID: CA575869 Model ID: B35340780000 Transport IDs: {'btleid': 'B353', 'wpid': '4078'} 3: DEVICE NAME {0005} V0 Name: Keyboard K600 TV Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} V0 5: RESET {0020} V0 6: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: Keyboard K600 T 7: BATTERY STATUS {1000} V1 Battery: 90%, discharging, next level 50%. 8: REPROG CONTROLS V4 {1B04} V4 Desvio de Tecla/Botón (saved): {Fn Left Click:Normal, Multiplatform App Switch:Normal, Multiplatform Back:Normal, Multiplatform Insert:Normal, Brightness Down:Normal, Brightness Up:Normal, Host Switch Channel 1:Normal, Host Switch Channel 2:Normal, Host Switch Channel 3:Normal, MultiPlatform Search:Normal, MultiPlatform Home/Mission Control:Normal, MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad:Normal, Previous Fn:Normal, Play/Pause Fn:Normal, Next Fn:Normal, Mute Fn:Normal, Volume Down Fn:Normal, Volume Up Fn:Normal} Desvio de Tecla/Botón : {Fn Left Click:Normal, Multiplatform App Switch:Normal, Multiplatform Back:Normal, Multiplatform Insert:Normal, Brightness Down:Normal, Brightness Up:Normal, Host Switch Channel 1:Normal, Host Switch Channel 2:Normal, Host Switch Channel 3:Normal, MultiPlatform Search:Normal, MultiPlatform Home/Mission Control:Normal, MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad:Normal, Previous Fn:Normal, Play/Pause Fn:Normal, Next Fn:Normal, Mute Fn:Normal, Volume Down Fn:Normal, Volume Up Fn:Normal} 9: PERSISTENT REMAPPABLE ACTION {1C00} V0 Persistent Key/Button Mapping : {Multiplatform App Switch:No Output (only as default), Multiplatform Back:No Output (only as default), Multiplatform Insert:No Output (only as default), Brightness Down:No Output (only as default), Brightness Up:No Output (only as default), MultiPlatform Search:No Output (only as default), MultiPlatform Home/Mission Control:No Output (only as default), MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad:No Output (only as default), Previous Fn:No Output (only as default), Play/Pause Fn:No Output (only as default), Next Fn:No Output (only as default), Mute Fn:No Output (only as default), Volume Down Fn:No Output (only as default), Volume Up Fn:No Output (only as default)} 10: SWAP BUTTON CANCEL {2005} V0 11: CHANGE HOST {1814} V1 Cambiar Host : 1:romanescu 12: HOSTS INFO {1815} V1 Host 0 (paired): romanescu Host 1 (unpaired): Host 2 (unpaired): 13: NEW FN INVERSION {40A2} V0 Fn-swap: enabled Fn-swap default: enabled Intercambiar función Fx (saved): True Intercambiar función Fx : True 14: ENCRYPTION {4100} V0 15: LOCK KEY STATE {4220} V0 16: KEYBOARD DISABLE KEYS {4521} V0 Desactivar teclas (saved): {Caps Lock:False, Insert:False, Win:False} Desactivar teclas : {Caps Lock:False, Insert:False, Win:False} 17: MULTIPLATFORM {4531} V1 Especificar SO (saved): Windows Especificar SO : Windows 18: TOUCHPAD RAW XY {6100} V0 19: GESTURE 2 {6501} V0 Gestos (saved): {1: True, 2: True, 4: True, 5: True, 10: True, 30: True, 34: False, 42: True, 43: True, 45: False, 84: True} Gestos : {1: True, 2: True, 4: True, 5: True, 30: True, 10: True, 45: False, 42: True, 43: True, 84: True, 34: False} Gestures Diversion (saved): {1: False, 2: False, 4: False, 5: False, 10: False, 44: False, 84: False, 85: False, 100: False} Gestures Diversion : {1: False, 2: False, 4: False, 5: False, 10: False, 44: False, 84: False, 85: False, 100: False} Parámetros de gestos (saved): {4: {'scale': 256}} Parámetros de gestos : {4: {'scale': 256}} 20: DFUCONTROL SIGNED {00C2} V0 21: DEVICE RESET {1802} V0 internal, hidden 22: unknown:1803 {1803} V0 internal, hidden 23: CONFIG DEVICE PROPS {1806} V5 internal, hidden 24: unknown:1813 {1813} V0 internal, hidden 25: OOBSTATE {1805} V0 internal, hidden 26: unknown:1830 {1830} V0 internal, hidden 27: unknown:1890 {1890} V2 internal, hidden 28: unknown:1891 {1891} V2 internal, hidden 29: unknown:18A1 {18A1} V0 internal, hidden 30: unknown:1DF3 {1DF3} V0 internal, hidden 31: unknown:1E00 {1E00} V0 hidden 32: unknown:1EB0 {1EB0} V0 internal, hidden 33: unknown:1861 {1861} V0 internal, hidden 34: unknown:18B0 {18B0} V0 internal, hidden 35: unknown:1F11 {1F11} V0 internal, hidden Has 19 reprogrammable keys: 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 is FN, FN sensitive, divertable, persistently divertable, pos:1, group:0, group mask:empty reporting: default 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 is FN, FN sensitive, divertable, persistently divertable, pos:2, group:0, group mask:empty reporting: default 2: Host Switch Channel 3 , default: HostSwitch Channel 3 => HostSwitch Channel 3 is FN, FN sensitive, divertable, persistently divertable, pos:3, group:0, group mask:empty reporting: default 3: MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad, default: Multiplatform Menu/Launchpad => Multiplatform Menu/Launchpad is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:4, group:0, group mask:empty reporting: default 4: Brightness Down , default: Brightness Down => Brightness Down is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:5, group:0, group mask:empty reporting: default 5: Brightness Up , default: Brightness Up => Brightness Up is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:6, group:0, group mask:empty reporting: default 6: Previous Fn , default: Previous => Previous is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:7, group:0, group mask:empty reporting: default 7: Play/Pause Fn , default: Play/Pause => Play/Pause is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:8, group:0, group mask:empty reporting: default 8: Next Fn , default: Next => Next is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:9, group:0, group mask:empty reporting: default 9: Mute Fn , default: Mute => Mute is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:10, group:0, group mask:empty reporting: default 10: Volume Down Fn , default: Volume Down => Volume Down is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:11, group:0, group mask:empty reporting: default 11: Volume Up Fn , default: Volume Up => Volume Up is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:12, group:0, group mask:empty reporting: default 12: Multiplatform Insert , default: Switch Language => Switch Language is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 13: MultiPlatform Home/Mission Control, default: Multiplatform Home/Mission Control => Multiplatform Home/Mission Control nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 14: Multiplatform Back , default: MultiPlatform Back => MultiPlatform Back nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 15: Multiplatform App Switch , default: MultiPlatform App Switch => MultiPlatform App Switch nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 16: MultiPlatform Search , default: Multiplatform Search => Multiplatform Search nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 17: Fn Left Click , default: ShowUI => ShowUI divertable, pos:0, group:0, group mask:empty reporting: default 18: FN Key , default: Do Nothing One => Do Nothing One nonstandard, pos:0, group:0, group mask:empty reporting: default Has 14 persistent remappable keys: 0: MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad => None 1: Brightness Down => None 2: Brightness Up => None 3: Previous Fn => None 4: Play/Pause Fn => None 5: Next Fn => None 6: Mute Fn => None 7: Volume Down Fn => None 8: Volume Up Fn => None 9: Multiplatform Insert => None 10: MultiPlatform Home/Mission Control => None 11: Multiplatform Back => None 12: Multiplatform App Switch => None 13: MultiPlatform Search => None Has 16 gesture(s), 1 param(s) and 5 spec(s): Track1Finger Enabled(None): None Diverted:(None) None TapGestures Enabled(None): None Diverted:(None) None Tap1Finger Enabled( 0): True Diverted:( 0) False Tap2Finger Enabled( 1): True Diverted:( 1) False Click1Finger Enabled( 2): True Diverted:( 2) False Click2Finger Enabled( 3): True Diverted:( 3) False TapDrag1Finger Enabled( 4): True Diverted:(None) None DoubleTap1Finger Enabled( 5): True Diverted:( 4) False Scroll2FingerStateless Enabled(None): None Diverted:( 5) False NaturalScrolling Enabled( 6): False Diverted:(None) None Scroll2FingerHoriz Enabled( 7): True Diverted:(None) None Scroll2FingerVert Enabled( 8): True Diverted:(None) None Zoom2FingerStateless Enabled( 9): True Diverted:( 6) False FnClickGestureSuppression Enabled( 10): False Diverted:(None) None TwoFingersPresent Enabled(None): None Diverted:( 7) False DeviceSpecificRawData Enabled(None): None Diverted:( 8) False ScaleFactor Value ( 0): 256 [Default: 256] period unit Spec ( 3): 2048 DVI field width Spec ( 1): 8 field widths Spec ( 2): 8 multiplier Spec ( 5): None resolution Spec ( 4): 4 Battery: 90%, discharging, next level 50%. Solaar-1.1.20/docs/devices/LIFT For Business B033.txt000066400000000000000000000124011522022367600215430ustar00rootroot00000000000000solaar version 1.1.8 2: LIFT For Business Device path : None WPID : B033 Codename : LIFT B Kind : mouse Protocol : HID++ 4.5 Serial number: A67F904D Model ID: B03300000000 Unit ID: A67F904D Bootloader: BL1 56.01.B0010 Firmware: RBM 21.01.B0010 Other: 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: Bootloader BL1 56.01.B0010 B033B0706FCD Firmware: Firmware RBM 21.01.B0010 B033B0706FCD Firmware: Other Unit ID: A67F904D Model ID: B03300000000 Transport IDs: {'btleid': 'B033'} 3: DEVICE NAME {0005} V0 Name: LIFT For Business Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} V0 5: CONFIG CHANGE {0020} V0 6: CRYPTO ID {0021} V1 7: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: LIFT B 8: UNIFIED BATTERY {1004} V3 Battery: 100%, discharging. 9: REPROG CONTROLS V4 {1B04} V5 Key/Button Actions (saved): {Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, DPI Switch:DPI Switch} Key/Button Actions : {Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, DPI Switch:DPI Switch} Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, DPI Switch:Regular} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, DPI Switch:Regular} 10: CHANGE HOST {1814} V1 Change Host : 1:feathora 11: HOSTS INFO {1815} V2 Host 0 (paired): feathora Host 1 (unpaired): Host 2 (unpaired): 12: XY STATS {2250} V1 13: LOWRES WHEEL {2130} V0 Wheel Reports: HID Scroll Wheel Diversion (saved): False Scroll Wheel Diversion : False 14: ADJUSTABLE DPI {2201} V2 Sensitivity (DPI) (saved): 1600 Sensitivity (DPI) : 1600 15: DFUCONTROL {00C3} V0 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:1816 {1816} V0 internal, hidden, unknown:000010 20: OOBSTATE {1805} V0 internal, hidden 21: unknown:1830 {1830} V0 internal, hidden, unknown:000010 22: unknown:1891 {1891} V7 internal, hidden, unknown:000008 23: unknown:18A1 {18A1} V0 internal, hidden, unknown:000010 24: unknown:1E00 {1E00} V0 hidden 25: unknown:1E02 {1E02} V0 internal, hidden 26: unknown:1E22 {1E22} V1 internal, hidden, unknown:000010 27: unknown:1602 {1602} V0 28: unknown:1EB0 {1EB0} V0 internal, hidden, unknown:000010 29: unknown:1861 {1861} V1 internal, hidden, unknown:000010 30: unknown:18B1 {18B1} V0 internal, hidden, unknown:000010 31: unknown:920A {920A} 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:empty reporting: default 1: Right Button , default: Right Click => Right Click mse, analytics key events, pos:0, group:1, group mask:empty reporting: default 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:3, group mask:g1,g2,g3 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: DPI Switch , default: DPI Switch => DPI Switch mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:3, group mask:g1,g2,g3 reporting: default 6: 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: 100%, discharging. Solaar-1.1.20/docs/devices/LIFT VERTICAL ERGONOMIC MOUSE B031.txt000066400000000000000000000124021522022367600226250ustar00rootroot00000000000000Solaar version 1.1.4 2: LIFT VERTICAL ERGONOMIC MOUSE Device path : None WPID : B031 Codename : LIFT Kind : mouse Protocol : HID++ 4.5 Serial number: E86E35D0 Model ID: B03100000000 Unit ID: E86E35D0 Bootloader: BL1 56.00.B0008 Firmware: RBM 21.00.B0008 Other: The power switch is located on the (unknown). Supports 31 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BL1 56.00.B0008 B0317599BD37 Firmware: Firmware RBM 21.00.B0008 B0317599BD37 Firmware: Other Unit ID: E86E35D0 Model ID: B03100000000 Transport IDs: {'btleid': 'B031'} 3: DEVICE NAME {0005} Name: LIFT VERTICAL ERGONOMIC MOUSE Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: CRYPTO ID {0021} 7: DEVICE FRIENDLY NAME {0007} Friendly Name: LIFT 8: UNIFIED BATTERY {1004} 9: REPROG CONTROLS V4 {1B04} DPI Sliding Adjustment (saved): DPI Switch DPI Sliding Adjustment : Off Mouse Gestures (saved): Off Mouse Gestures : Off Key/Button Actions (saved): {Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, DPI Switch:DPI Switch} Key/Button Actions : {Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, DPI Switch:DPI Switch} Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, DPI Switch:Diverted} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, DPI Switch:Diverted} 10: CHANGE HOST {1814} Change Host : 1:gtronick 11: HOSTS INFO {1815} Host 0 (paired): gtronick Host 1 (paired): LAP0707 Host 2 (unpaired): 12: XY STATS {2250} 13: LOWRES WHEEL {2130} Wheel Reports: HID Scroll Wheel Diversion (saved): False Scroll Wheel Diversion : False 14: ADJUSTABLE DPI {2201} Sensitivity (DPI) (saved): 2400 Sensitivity (DPI) : 2400 15: DFUCONTROL {00C3} 16: DEVICE RESET {1802} internal, hidden, unknown:000010 17: unknown:1803 {1803} internal, hidden, unknown:000010 18: CONFIG DEVICE PROPS {1806} internal, hidden, unknown:000010 19: unknown:1816 {1816} internal, hidden, unknown:000010 20: OOBSTATE {1805} internal, hidden 21: unknown:1830 {1830} internal, hidden, unknown:000010 22: unknown:1891 {1891} internal, hidden, unknown:000008 23: unknown:18A1 {18A1} internal, hidden, unknown:000010 24: unknown:1E00 {1E00} hidden 25: unknown:1E02 {1E02} internal, hidden 26: unknown:1E22 {1E22} internal, hidden, unknown:000010 27: unknown:1602 {1602} 28: unknown:1EB0 {1EB0} internal, hidden, unknown:000010 29: unknown:1861 {1861} internal, hidden, unknown:000010 30: unknown:18B1 {18B1} 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:empty reporting: default 1: Right Button , default: Right Click => Right Click mse, analytics key events, pos:0, group:1, group mask:empty reporting: default 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:3, group mask:g1,g2,g3 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: DPI Switch , default: DPI Switch => DPI Switch mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:3, group mask:g1,g2,g3 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:4, group mask:empty reporting: default Battery: 100%, discharging. Solaar-1.1.20/docs/devices/Lightspeed Receiver C539.txt000066400000000000000000000006561522022367600223640ustar00rootroot00000000000000Solaar version 1.1.4 Lightspeed Receiver Device path : /dev/hidraw8 USB id : 046d:C539 Serial : C1469DD4 Firmware : 39.06.B0040 Bootloader : 02.09 Other : AA.BE Has 1 paired device(s) out of a maximum of 1. Notifications: wireless, software present (0x000900) Device activity counters: (empty) Seen paired with G502 Gaming Mouse 407F Seen paired with G Pro Wireless Gaming Mouse 4079 Solaar-1.1.20/docs/devices/Lightspeed Receiver C53A.txt000066400000000000000000000007101522022367600223630ustar00rootroot00000000000000Lightspeed Receiver Device path : /dev/hidraw9 USB id : 046d:C53A Serial : 5B2B9A98 Firmware : 40.03.B0014 Bootloader : 02.09 Other : AA.CA Has 2 paired device(s) out of a maximum of 1. Notifications: wireless, software present (0x000900) Device activity counters: (empty) Seen as part of a G PowerPlay Wireless Mouse Pad with a Candy companion chip paired a number 7 Seen paired with a G502 Gaming Mouse 407F. Solaar-1.1.20/docs/devices/Lightspeed Receiver C53D.txt000066400000000000000000000006231522022367600223710ustar00rootroot00000000000000Solaar version 1.1.3 Lightspeed Receiver Device path : /dev/hidraw2 USB id : 046d:C53D Serial : C271A999 Firmware : 43.01.B0009 Bootloader : 02.10 Other : AA.D2 Has 1 paired device(s) out of a maximum of 1. Notifications: wireless, software present (0x000900) Device activity counters: (empty) Seen paired with G613 Wireless Mechanical Gaming Keyboard 4065 Solaar-1.1.20/docs/devices/Lightspeed Receiver C53F.txt000066400000000000000000000004641522022367600223760ustar00rootroot00000000000000Lightspeed Receiver Device path : /dev/hidraw3 USB id : 046d:C53F Serial : Firmware : 44.01.B0005 Bootloader : 00.02 Other : AA.DE Has 0 paired device(s) out of a maximum of 1. Notifications: wireless, software present (0x000900) Device activity counters: (empty) Solaar-1.1.20/docs/devices/Lightspeed Receiver C541.txt000066400000000000000000000006271522022367600223530ustar00rootroot00000000000000Solaar version 1.1.1 Lightspeed Receiver Device path : /dev/hidraw4 USB id : 046d:C541 Serial : 18E8EC31 Firmware : 01.01.B0027 Bootloader : 01.27 Other : B9.BC Has 1 paired device(s) out of a maximum of 1. Notifications: wireless, software present (0x000900) Device activity counters: (empty) Seen paired with G915 WIRELESS RGB Mechanical Gaming Keyboard 407E Solaar-1.1.20/docs/devices/Lightspeed Receiver C545.txt000066400000000000000000000006431522022367600223550ustar00rootroot00000000000000Solaar version 1.1.4 Lightspeed Receiver Device path : /dev/hidraw5 USB id : 046d:C545 Serial : AD1567FD Firmware : 02.01.B0006 Bootloader : 00.01 Other : 3D.88 Has 1 paired device(s) out of a maximum of 2. Notifications: wireless, software present (0x000900) Device activity counters: 1=22 Seen paired with G915 TKL LIGHTSPEED Wireless RGB Mechanical Gaming Keyboard 408E Solaar-1.1.20/docs/devices/Lightspeed Receiver C547.txt000066400000000000000000000005731522022367600223610ustar00rootroot00000000000000Solaar version 1.1.3 Lightspeed Receiver Device path : /dev/hidraw6 USB id : 046d:C547 Serial : 9B341CD1 Firmware : 04.02.B0009 Bootloader : 00.05 Other : F3.24 Has 1 paired device(s) out of a maximum of 2. Notifications: wireless, software present (0x000900) Device activity counters: 1=171 Seen paired with the PRO X Wireless 4093 Solaar-1.1.20/docs/devices/Logi Pop Keys B365.txt000066400000000000000000000213451522022367600210460ustar00rootroot00000000000000solaar version 1.1.8 Bolt Receiver Device path : /dev/hidraw2 USB id : 046d:C548 Serial : 31454343464242444143334635323035 Has 1 paired device(s) out of a maximum of 6. Notifications: wireless, software present (0x000900) Device activity counters: 1=28 1: Logi POP Keys Device path : None WPID : B365 Codename : Logi POP Keys Kind : keyboard Protocol : HID++ 4.5 Serial number: D1F99582 Model ID: B36500000000 Unit ID: D1F99582 Bootloader: BL1 44.01.B0008 Firmware: RBK 69.01.B0008 Other: The power switch is located on the (unknown). Supports 31 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V4 Firmware: Bootloader BL1 44.01.B0008 B3652BE8BAF4 Firmware: Firmware RBK 69.01.B0008 B3652BE8BAF4 Firmware: Other Unit ID: D1F99582 Model ID: B36500000000 Transport IDs: {'btleid': 'B365'} 3: DEVICE NAME {0005} V0 Name: Logi POP Keys Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} V0 5: CONFIG CHANGE {0020} V0 6: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: Logi POP Keys 7: UNIFIED BATTERY {1004} V3 Battery: 100%, discharging. 8: REPROG CONTROLS V4 {1B04} V5 Key/Button Diversion (saved): {Show Desktop:Regular, Previous Fn:Regular, Play/Pause Fn:Regular, Next Fn:Regular, Mute Fn:Regular, Volume Down Fn:Regular, Volume Up Fn:Regular, Voice Dictation:Regular, Emoji Smiley Heart Eyes:Regular, Emoji Crying Face:Regular, Emoji Smiley:Regular, Emoji Smilie With Tears:Regular, Open Emoji Panel:Regular, Snipping Tool:Regular, Mute Microphone:Regular} Key/Button Diversion : {Show Desktop:Regular, Previous Fn:Regular, Play/Pause Fn:Regular, Next Fn:Regular, Mute Fn:Regular, Volume Down Fn:Regular, Volume Up Fn:Regular, Voice Dictation:Regular, Emoji Smiley Heart Eyes:Regular, Emoji Crying Face:Regular, Emoji Smiley:Regular, Emoji Smilie With Tears:Regular, Open Emoji Panel:Regular, Snipping Tool:Regular, Mute Microphone:Regular} 9: CHANGE HOST {1814} V1 Change Host : 1:astra 10: HOSTS INFO {1815} V2 Host 0 (paired): astra Host 1 (unpaired): Host 2 (unpaired): 11: K375S FN INVERSION {40A3} V0 Swap Fx function (saved): False Swap Fx function : False 12: LOCK KEY STATE {4220} V0 13: KEYBOARD DISABLE KEYS {4521} V0 Disable keys (saved): {Caps Lock:False, Insert:False, Win:False} Disable keys : {Caps Lock:False, Insert:False, Win:False} 14: MULTIPLATFORM {4531} V1 Set OS (saved): Windows Set OS : Windows 15: KEYBOARD LAYOUT 2 {4540} V0 16: DFUCONTROL {00C3} V0 17: DEVICE RESET {1802} V0 internal, hidden, unknown:000010 18: unknown:1803 {1803} V0 internal, hidden, unknown:000010 19: CONFIG DEVICE PROPS {1806} V8 internal, hidden, unknown:000010 20: unknown:1816 {1816} V0 internal, hidden, unknown:000010 21: OOBSTATE {1805} V0 internal, hidden 22: unknown:1830 {1830} V0 internal, hidden, unknown:000010 23: unknown:1891 {1891} V7 internal, hidden, unknown:000008 24: unknown:18A1 {18A1} V0 internal, hidden, unknown:000010 25: unknown:1E00 {1E00} V0 hidden 26: unknown:1E02 {1E02} V0 internal, hidden 27: unknown:1602 {1602} V0 28: unknown:1EB0 {1EB0} V0 internal, hidden, unknown:000010 29: unknown:1861 {1861} V1 internal, hidden, unknown:000010 30: unknown:18B0 {18B0} V0 internal, hidden, unknown:000010 Has 20 reprogrammable keys: 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 is FN, FN sensitive, analytics key events, pos:1, group:0, group mask:empty reporting: default 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 is FN, FN sensitive, analytics key events, pos:2, group:0, group mask:empty reporting: default 2: Host Switch Channel 3 , default: HostSwitch Channel 3 => HostSwitch Channel 3 is FN, FN sensitive, analytics key events, pos:3, group:0, group mask:empty reporting: default 3: Show Desktop , default: Show Desktop => Show Desktop is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:4, group:0, group mask:empty reporting: default 4: Snipping Tool , default: Snipping Tool => Snipping Tool is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:5, group:0, group mask:empty reporting: default 5: Mute Microphone , default: Mute Microphone => Mute Microphone is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:6, group:0, group mask:empty reporting: default 6: Previous Fn , default: Previous => Previous is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:7, group:0, group mask:empty reporting: default 7: Play/Pause Fn , default: Play/Pause => Play/Pause is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:8, group:0, group mask:empty reporting: default 8: Next Fn , default: Next => Next is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:9, group:0, group mask:empty reporting: default 9: Mute Fn , default: Mute => Mute is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:10, group:0, group mask:empty reporting: default 10: Volume Down Fn , default: Volume Down => Volume Down is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:11, group:0, group mask:empty reporting: default 11: Volume Up Fn , default: Volume Up => Volume Up is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:12, group:0, group mask:empty reporting: default 12: Voice Dictation , default: Voice Dictation => Voice Dictation nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 13: Emoji Smiley Heart Eyes , default: Emoji Smiling Face With Heart Shaped Eyes => Emoji Smiling Face With Heart Shaped Eyes nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 14: Emoji Crying Face , default: Emoji Loudly Crying Face => Emoji Loudly Crying Face nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 15: Emoji Smiley , default: Emoji Smiley => Emoji Smiley nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 16: Emoji Smilie With Tears , default: Emoji Smiley With Tears => Emoji Smiley With Tears nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 17: Open Emoji Panel , default: Open Emoji Panel => Open Emoji Panel nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 18: F Lock , default: Do Nothing One => Do Nothing One is FN, analytics key events, pos:0, group:0, group mask:empty reporting: default 19: FN Key , default: Do Nothing One => Do Nothing One nonstandard, analytics key events, pos:0, group:0, group mask:empty reporting: default Battery: 100%, discharging. Solaar-1.1.20/docs/devices/Logitech G933 Gaming Wireless Headset 0A5B.txt000066400000000000000000000036551522022367600252200ustar00rootroot00000000000000solaar 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. Solaar-1.1.20/docs/devices/Logitech PRO X Wireless Gaming Headset 0ABA.txt000066400000000000000000000034551522022367600254350ustar00rootroot00000000000000 1: Logitech PRO X Wireless Gaming Headset Device path : /dev/hidraw2 USB id : 046d:0ABA Codename : PRO Headset Kind : headset Protocol : HID++ 4.2 Serial number: Model ID: 000000000ABA Unit ID: FFFFFFFF Firmware: U1 12.01.B0203 Supports 6 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Firmware U1 12.01.B0203 0ABA Unit ID: FFFFFFFF Model ID: 000000000ABA Transport IDs: {'btid': '0000', 'btleid': '0000'} 3: DEVICE NAME {0005} Name: PRO X Wireless Gaming Headset Kind: None 4: EQUALIZER {8310} 5: SIDETONE {8300} 6: ADC MEASUREMENT {1F20} Battery status unavailable. 2: Logitech PRO X Wireless Gaming Headset Device path : /dev/hidraw5 USB id : 046d:0ABA Codename : PRO Headset Kind : headset Protocol : HID++ 4.2 Serial number: Model ID: 000000000ABA Unit ID: FFFFFFFF Firmware: U1 12.01.B0203 Supports 6 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Firmware U1 12.01.B0203 0ABA Unit ID: FFFFFFFF Model ID: 000000000ABA Transport IDs: {'btid': '0000', 'btleid': '0000'} 3: DEVICE NAME {0005} Name: PRO X Wireless Gaming Headset Kind: None 4: EQUALIZER {8310} 5: SIDETONE {8300} Sidetone (saved): 0 Sidetone : 0 Battery status unavailable. Solaar-1.1.20/docs/devices/M720 Triathlon Multi-Device Mouse 405E.txt000066400000000000000000000137641522022367600244400ustar00rootroot00000000000000Solaar version 1.1.1 1: M720 Triathlon Multi-Device Mouse Device path : /dev/hidraw1 WPID : 405E Codename : M720 Triathlon Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: 205F2717 Model ID: B015405E0000 Unit ID: C738473C Bootloader: BOT 34.01.B0005 Firmware: MPM 04.10.B0013 Other: The power switch is located on the base. Supports 36 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 34.01.B0005 000000A96719 Firmware: Firmware MPM 04.10.B0013 405E00A96719 Firmware: Other Unit ID: C738473C Model ID: B015405E0000 Transport IDs: {'btleid': 'B015', 'wpid': '405E'} 3: DEVICE NAME {0005} Name: M720 Triathlon Multi-Device Mouse Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: CRYPTO ID {0021} 7: DEVICE FRIENDLY NAME {0007} Friendly Name: M720 Triathlon 8: BATTERY STATUS {1000} Battery: 90%, discharging, next level 50%. 9: CHANGE HOST {1814} Change Host : 2:desktop 10: HOSTS INFO {1815} Host 0 (paired): rpi Host 1 (paired): desktop Host 2 (paired): laptop 11: REPROG CONTROLS V4 {1B04} Key/Button Actions (saved): {'208': 208, '80': 80, '81': 81, '82': 82, '83': 83, '86': 86, '91': 91, '93': 93} Key/Button Actions : {'80': 80, '81': 81, '82': 82, '83': 83, '86': 86, '91': 91, '93': 93, '208': 208} Key/Button Diversion (saved): {'208': 0, '82': 0, '83': 0, '86': 0, '91': 0, '93': 0} Key/Button Diversion : {'82': 0, '83': 0, '86': 0, '91': 0, '93': 0, '208': 0} 12: PERSISTENT REMAPPABLE ACTION {1C00} 13: POINTER SPEED {2205} Pointer Speed: 1.0 Sensitivity (Pointer Speed) (saved): 256 Sensitivity (Pointer Speed) : 256 14: VERTICAL SCROLLING {2100} Roller type: 3G Ratchet per turn: 24 Scroll lines: 0 15: DFUCONTROL UNSIGNED {00C1} 16: DFUCONTROL SIGNED {00C2} 17: DEVICE RESET {1802} internal, hidden 18: unknown:1803 {1803} internal, hidden 19: CONFIG DEVICE PROPS {1806} internal, hidden 20: OOBSTATE {1805} internal, hidden 21: unknown:1813 {1813} internal, hidden 22: unknown:1830 {1830} internal, hidden 23: unknown:1861 {1861} 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:18B1 {18B1} internal, hidden 31: unknown:1850 {1850} internal, hidden 32: unknown:1E22 {1E22} 33: unknown:1F03 {1F03} internal, hidden 34: unknown:18C0 {18C0} internal, hidden 35: HIRES WHEEL {2121} 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 Has 9 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 1: Right Button , default: Right Click => Right Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 3: Back Button , default: Mouse Back Button => Mouse Back Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 4: Forward Button , default: Mouse Forward Button => Mouse Forward Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 5: Left Tilt , default: Mouse Scroll Left Button => Mouse Scroll Left Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 6: Right Tilt , default: Mouse Scroll Right Button => Mouse Scroll Right Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 7: MultiPlatform Gesture Button, default: Multiplatform Gesture Button => Multiplatform Gesture Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 8: 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: 90%, discharging, next level 50%. Solaar-1.1.20/docs/devices/M720 Triathlon Multi-Device Mouse B015.txt000066400000000000000000000155321522022367600244250ustar00rootroot00000000000000Solaar version 1.1.4 1: M720 Triathlon Multi-Device Mouse Device path : /dev/hidraw0 USB id : 046d:B015 Codename : M720 Triathlon Kind : mouse Protocol : HID++ 4.5 Serial number: Model ID: B015405E0000 Unit ID: E167BBCD Bootloader: BOT 34.01.B0005 Firmware: MPM 04.10.B0013 Other: Supports 30 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 34.01.B0005 000000A96719 Firmware: Firmware MPM 04.10.B0013 B01500A96719 Firmware: Other Unit ID: E167BBCD Model ID: B015405E0000 Transport IDs: {'btleid': 'B015', 'wpid': '405E'} 3: DEVICE NAME {0005} Name: M720 Triathlon Multi-Device Mouse Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: CRYPTO ID {0021} 7: DEVICE FRIENDLY NAME {0007} Friendly Name: M720 Triathlon 8: BATTERY STATUS {1000} Battery: 50%, discharging, next level 20%. 9: CHANGE HOST {1814} Change Host : 3:shoaib-MacBookPro 10: HOSTS INFO {1815} Host 0 (paired): Host 1 (paired): Shoaib’s MacBook Pro Host 2 (paired): shoaib-MacBookPro 11: REPROG CONTROLS V4 {1B04} Mouse Gestures (saved): Off Mouse Gestures : Off Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Left Tilt:Mouse Scroll Left Button , Right Tilt:Mouse Scroll Right Button, MultiPlatform Gesture Button:Multiplatform Gesture Button} 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, Left Tilt:Mouse Scroll Left Button , Right Tilt:Mouse Scroll Right Button, MultiPlatform Gesture Button:Multiplatform Gesture Button} Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Left Tilt:Regular, Right Tilt:Regular, MultiPlatform Gesture Button:Regular} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Left Tilt:Regular, Right Tilt:Regular, MultiPlatform Gesture Button:Regular} 12: PERSISTENT REMAPPABLE ACTION {1C00} Persistent Key/Button Mapping : {Left Button:Mouse Button Left, Right Button:Mouse Button Right, Middle Button:Mouse Button Middle, Back Button:Mouse Button Back, Forward Button:Mouse Button Forward, Left Tilt:Horizontal Scroll Left, Right Tilt:Horizontal Scroll Right, MultiPlatform Gesture Button:Alt+Cntrl+TAB} 13: POINTER SPEED {2205} Pointer Speed: 1.0 Sensitivity (Pointer Speed) (saved): 256 Sensitivity (Pointer Speed) : 256 14: VERTICAL SCROLLING {2100} Roller type: 3G Ratchet per turn: 24 Scroll lines: 0 15: DFUCONTROL UNSIGNED {00C1} 16: DFUCONTROL SIGNED {00C2} 17: unknown:1803 {1803} internal, hidden 18: unknown:1813 {1813} internal, hidden 19: unknown:1830 {1830} internal, hidden 20: unknown:18A1 {18A1} internal, hidden 21: unknown:1DF3 {1DF3} internal, hidden 22: unknown:1E00 {1E00} hidden 23: unknown:1EB0 {1EB0} internal, hidden 24: unknown:1861 {1861} internal, hidden 25: unknown:18B1 {18B1} internal, hidden 26: unknown:1850 {1850} internal, hidden 27: unknown:1E22 {1E22} 28: unknown:1F03 {1F03} internal, hidden 29: unknown:18C0 {18C0} internal, hidden Has 9 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 1: Right Button , default: Right Click => Right Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 3: Back Button , default: Mouse Back Button => Mouse Back Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 4: Forward Button , default: Mouse Forward Button => Mouse Forward Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 5: Left Tilt , default: Mouse Scroll Left Button => Mouse Scroll Left Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 6: Right Tilt , default: Mouse Scroll Right Button => Mouse Scroll Right Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 7: MultiPlatform Gesture Button, default: Multiplatform Gesture Button => Multiplatform Gesture Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 8: 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 Has 8 persistent remappable keys: 0: Left Button => Mouse Button: Mouse Button Left 1: Right Button => Mouse Button: Mouse Button Right 2: Middle Button => Mouse Button: Mouse Button Middle 3: Back Button => Mouse Button: Mouse Button Back 4: Forward Button => Mouse Button: Mouse Button Forward 5: Left Tilt => Horizontal Scroll: Horizontal Scroll Left 6: Right Tilt => Horizontal Scroll: Horizontal Scroll Right 7: MultiPlatform Gesture Button => Key: Alt+Cntrl+TAB Battery: 50%, discharging, next level 20%. Solaar-1.1.20/docs/devices/MX Anywhere 3 4090.txt000066400000000000000000000130601522022367600207230ustar00rootroot00000000000000Solaar version 1.1.4 1: MX Anywhere 3 Device path : /dev/hidraw3 WPID : 4090 Codename : MX Anywhere 3 Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: 8F4A05EA Model ID: B02540900000 Unit ID: 8F4A05EA Bootloader: BL1 13.01.B0015 Firmware: MPM 24.01.B0015 Other: The power switch is located on the base. Supports 32 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V4 Firmware: Bootloader BL1 13.01.B0015 0000AACD8D78 Firmware: Firmware MPM 24.01.B0015 4090AACD8D78 Firmware: Other Unit ID: 8F4A05EA Model ID: B02540900000 Transport IDs: {'btleid': 'B025', 'wpid': '4090'} 3: DEVICE NAME {0005} V0 Name: MX Anywhere 3 Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} V0 5: RESET {0020} V0 6: CRYPTO ID {0021} V1 7: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: MX Anywhere 3 8: UNIFIED BATTERY {1004} V0 Battery: 65%, discharging. 9: REPROG CONTROLS V4 {1B04} V5 Key/Button Actions (saved): {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 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 (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Smart Shift:Regular} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Smart Shift:Regular} 10: CHANGE HOST {1814} V1 Change Host : 1:apollo 11: XY STATS {2250} V1 12: ADJUSTABLE DPI {2201} V1 Sensibilidade (DPI) (saved): 4000 Sensibilidade (DPI) : 4000 13: SMART SHIFT ENHANCED {2111} V0 Scroll Wheel Rachet (saved): 12 Scroll Wheel Rachet : 12 14: 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 (saved): False Scroll Wheel Direction : False Scroll Wheel Resolution (saved): False Scroll Wheel Resolution : False Scroll Wheel Diversion (saved): False Scroll Wheel Diversion : False 15: WHEEL STATS {2251} V0 16: DFUCONTROL SIGNED {00C2} V0 17: DEVICE RESET {1802} V0 internal, hidden 18: unknown:1803 {1803} V0 internal, hidden 19: CONFIG DEVICE PROPS {1806} V7 internal, hidden 20: unknown:1812 {1812} V0 internal, hidden 21: OOBSTATE {1805} V0 internal, hidden 22: unknown:1830 {1830} V0 internal, hidden 23: unknown:1890 {1890} V5 internal, hidden 24: unknown:1891 {1891} V5 internal, hidden 25: unknown:18A1 {18A1} V0 internal, hidden 26: unknown:1E00 {1E00} V0 hidden 27: unknown:1EB0 {1EB0} V0 internal, hidden 28: unknown:1861 {1861} V0 internal, hidden 29: unknown:9001 {9001} V0 internal, hidden 30: unknown:1E22 {1E22} V0 internal, hidden 31: unknown:9205 {9205} V0 internal, hidden 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: default 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: 65%, discharging. Solaar-1.1.20/docs/devices/MX Anywhere 3 B025.txt000066400000000000000000000125261522022367600207450ustar00rootroot00000000000000Solaar version 1.1.4 1: MX Anywhere 3 Device path : /dev/hidraw1 USB id : 046d:B025 Codename : MX Anywhere 3 Kind : mouse Protocol : HID++ 4.5 Serial number: Model ID: B02540900000 Unit ID: 179ADDDA Bootloader: BL1 13.00.B0014 Firmware: MPM 24.00.B0014 Other: Supports 29 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BL1 13.00.B0014 000095D7D19A Firmware: Firmware MPM 24.00.B0014 B02595D7D19A Firmware: Other Unit ID: 179ADDDA Model ID: B02540900000 Transport IDs: {'btleid': 'B025', 'wpid': '4090'} 3: DEVICE NAME {0005} Name: MX Anywhere 3 Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: CRYPTO ID {0021} 7: DEVICE FRIENDLY NAME {0007} Friendly Name: MX Anywhere 3 8: UNIFIED BATTERY {1004} Battery: N/A, None. 9: REPROG CONTROLS V4 {1B04} DPI Sliding Adjustment (saved): Off DPI Sliding Adjustment : Off Mouse Gestures (saved): Off Mouse Gestures : Off Key/Button Actions (saved): {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 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 (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Smart Shift:Regular} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Smart Shift:Regular} 10: CHANGE HOST {1814} Change Host : 1:zerocool 11: XY STATS {2250} 12: ADJUSTABLE DPI {2201} Sensitivity (DPI) (saved): 1000 Sensitivity (DPI) : 1000 13: SMART SHIFT ENHANCED {2111} Scroll Wheel Rachet (saved): 12 Scroll Wheel Rachet : 12 14: HIRES WHEEL {2121} Multiplier: 15 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 15: WHEEL STATS {2251} 16: DFUCONTROL SIGNED {00C2} 17: DEVICE RESET {1802} internal, hidden 18: unknown:1803 {1803} internal, hidden 19: CONFIG DEVICE PROPS {1806} internal, hidden 20: unknown:1812 {1812} internal, hidden 21: OOBSTATE {1805} internal, hidden 22: unknown:1830 {1830} internal, hidden 23: unknown:18A1 {18A1} internal, hidden 24: unknown:1E00 {1E00} hidden 25: unknown:1EB0 {1EB0} internal, hidden 26: unknown:1861 {1861} internal, hidden 27: unknown:9300 {9300} internal, hidden 28: unknown:9001 {9001} internal, hidden 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: default 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: N/A, None. Solaar-1.1.20/docs/devices/MX Anywhere 3 for Business B02D.txt000066400000000000000000000130041522022367600232770ustar00rootroot00000000000000solaar 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. Solaar-1.1.20/docs/devices/MX Ergo Multi-Device Trackball 406F.txt000066400000000000000000000161041522022367600240670ustar00rootroot00000000000000Solaar version 1.1.4 1: MX Ergo Multi-Device Trackball Device path : /dev/hidraw4 WPID : 406F Codename : MX Ergo Kind : trackball Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: 0E0F20E7 Model ID: B01D406F0000 Unit ID: 13AF9325 Bootloader: BOT 49.00.B0001 Firmware: MPM 06.03.B0022 Other: The power switch is located on the base. Supports 35 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 49.00.B0001 0000CDE885F3 Firmware: Firmware MPM 06.03.B0022 406FCDE885F3 Firmware: Other Unit ID: 13AF9325 Model ID: B01D406F0000 Transport IDs: {'btleid': 'B01D', 'wpid': '406F'} 3: DEVICE NAME {0005} Name: MX Ergo Multi-Device Trackball Kind: trackball 4: WIRELESS DEVICE STATUS {1D4B} 5: DEVICE FRIENDLY NAME {0007} Friendly Name: MX Ergo 6: RESET {0020} 7: CRYPTO ID {0021} 8: BATTERY STATUS {1000} Battery: 50%, discharging, next level 20%. 9: LED CONTROL {1300} 10: REPROG CONTROLS V4 {1B04} Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Left Tilt:Mouse Scroll Left Button , Right Tilt:Mouse Scroll Right Button, DPI Change:DPI Change} 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, Left Tilt:Mouse Scroll Left Button , Right Tilt:Mouse Scroll Right Button, DPI Change:DPI Change} Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Left Tilt:Regular, Right Tilt:Regular, DPI Change:Regular} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Left Tilt:Regular, Right Tilt:Regular, DPI Change:Regular} 11: PERSISTENT REMAPPABLE ACTION {1C00} Persistent Key/Button Mapping : {Left Button:Mouse Button Left, Right Button:Mouse Button Right, Middle Button:Mouse Button Middle, Back Button:Mouse Button Back, Forward Button:Mouse Button Forward, Left Tilt:Horizontal Scroll Left, Right Tilt:Horizontal Scroll Right, DPI Change:Mouse Button DPI} 12: POINTER AXIS ORIENTATION {2006} 13: POINTER SPEED {2205} Pointer Speed: 1.046875 Sensitivity (Pointer Speed) (saved): 268 Sensitivity (Pointer Speed) : 268 Sensitivity Switching (saved): Off Sensitivity Switching : Off 14: VERTICAL SCROLLING {2100} Roller type: standard Ratchet per turn: 18 Scroll lines: 0 15: DFUCONTROL SIGNED {00C2} 16: DEVICE RESET {1802} internal, hidden 17: unknown:1803 {1803} internal, hidden 18: CONFIG DEVICE PROPS {1806} internal, hidden 19: OOBSTATE {1805} internal, hidden 20: unknown:1813 {1813} internal, hidden 21: CHANGE HOST {1814} Change Host : 1:razorback 22: HOSTS INFO {1815} Host 0 (paired): razorback Host 1 (unpaired): Host 2 (unpaired): 23: unknown:1830 {1830} internal, hidden 24: unknown:1861 {1861} internal, hidden 25: unknown:1890 {1890} internal, hidden 26: unknown:1891 {1891} internal, hidden 27: unknown:18A1 {18A1} internal, hidden 28: unknown:1DF3 {1DF3} internal, hidden 29: unknown:1E00 {1E00} hidden 30: unknown:1EB0 {1EB0} internal, hidden 31: unknown:18B1 {18B1} internal, hidden 32: unknown:1850 {1850} internal, hidden 33: unknown:1F03 {1F03} internal, hidden 34: unknown:18C0 {18C0} internal, hidden Has 9 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 1: Right Button , default: Right Click => Right Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 3: Back Button , default: Mouse Back Button => Mouse Back Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 4: Forward Button , default: Mouse Forward Button => Mouse Forward Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 5: DPI Change , default: DPI Change => DPI Change mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 6: Left Tilt , default: Mouse Scroll Left Button => Mouse Scroll Left Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 7: Right Tilt , default: Mouse Scroll Right Button => Mouse Scroll Right Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 8: 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 Has 8 persistent remappable keys: 0: Left Button => Mouse Button: Mouse Button Left 1: Right Button => Mouse Button: Mouse Button Right 2: Middle Button => Mouse Button: Mouse Button Middle 3: Back Button => Mouse Button: Mouse Button Back 4: Forward Button => Mouse Button: Mouse Button Forward 5: DPI Change => Mouse Button: Mouse Button DPI 6: Left Tilt => Horizontal Scroll: Horizontal Scroll Left 7: Right Tilt => Horizontal Scroll: Horizontal Scroll Right Battery: 50%, discharging, next level 20%. Solaar-1.1.20/docs/devices/MX Keys Keyboard 408A.txt000066400000000000000000000247471522022367600215100ustar00rootroot00000000000000Solaar version 1.1.4 1: MX Keys Keyboard Device path : /dev/hidraw1 WPID : 408A Codename : MX Keys Kind : keyboard Protocol : HID++ 4.5 Polling rate : 20 ms (50Hz) Serial number: F359F7AC Model ID: B35B408A0000 Unit ID: F359F7AC Bootloader: BL1 08.00.B0011 Firmware: MPK 12.00.B0011 Other: The power switch is located on the edge of top right corner. Supports 32 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V2 Firmware: Bootloader BL1 08.00.B0011 00008169E8BB Firmware: Firmware MPK 12.00.B0011 408A8169E8BB Firmware: Other Unit ID: F359F7AC Model ID: B35B408A0000 Transport IDs: {'btleid': 'B35B', 'wpid': '408A'} 3: DEVICE NAME {0005} V0 Name: MX Keys Wireless Keyboard Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} V0 5: RESET {0020} V0 6: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: MX Keys 7: BATTERY STATUS {1000} V1 Battery: 50%, discharging, next level 20%. 8: REPROG CONTROLS V4 {1B04} V4 Key/Button Diversion (saved): {Calculator:Regular, Show Desktop:Regular, Lock PC:Regular, Screen Capture/Print Screen:Regular, Brightness Down:Regular, Brightness Up:Regular, Host Switch Channel 1:Regular, Host Switch Channel 2:Regular, Host Switch Channel 3:Regular, Mission Control/Task View:Regular, Dashboard Launchpad/Action Center: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, Right Arrow:Regular, Left Arrow:Regular} Key/Button Diversion : {Calculator:Regular, Show Desktop:Regular, Lock PC:Regular, Screen Capture/Print Screen:Regular, Brightness Down:Regular, Brightness Up:Regular, Host Switch Channel 1:Regular, Host Switch Channel 2:Regular, Host Switch Channel 3:Regular, Mission Control/Task View:Regular, Dashboard Launchpad/Action Center: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, Right Arrow:Regular, Left Arrow:Regular} 9: CHANGE HOST {1814} V1 10: HOSTS INFO {1815} V1 Host 0 (paired): brandy Host 1 (paired): Pixel 2 Host 2 (paired): devlaptop002 11: BACKLIGHT2 {1982} V1 Backlight (saved): True Backlight : True 12: K375S FN INVERSION {40A3} V0 Swap Fx function (saved): False Swap Fx function : False 13: ENCRYPTION {4100} V0 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): Windows Set OS : Windows 17: DFUCONTROL SIGNED {00C2} V0 18: DEVICE RESET {1802} V0 internal, hidden 19: unknown:1803 {1803} V0 internal, hidden 20: CONFIG DEVICE PROPS {1806} V5 internal, hidden 21: unknown:1813 {1813} V0 internal, hidden 22: OOBSTATE {1805} V0 internal, hidden 23: unknown:1830 {1830} V0 internal, hidden 24: unknown:1890 {1890} V5 internal, hidden 25: unknown:1891 {1891} V5 internal, hidden 26: unknown:18A1 {18A1} V0 internal, hidden 27: unknown:1DF3 {1DF3} V0 internal, hidden 28: unknown:1E00 {1E00} V0 hidden 29: unknown:1EB0 {1EB0} V0 internal, hidden 30: unknown:1861 {1861} V0 internal, hidden 31: unknown:1A20 {1A20} V0 internal, hidden Has 24 reprogrammable keys: 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 nonstandard, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 nonstandard, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 2: Host Switch Channel 3 , default: HostSwitch Channel 3 => HostSwitch Channel 3 nonstandard, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 3: Brightness Down , default: Brightness Down => Brightness Down is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:1, group:0, group mask:empty reporting: default 4: Brightness Up , default: Brightness Up => Brightness Up is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:2, group:0, group mask:empty reporting: default 5: Mission Control/Task View , default: Mission Control/Task View => Mission Control/Task View is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:3, group:0, group mask:empty reporting: default 6: Dashboard Launchpad/Action Center, default: Dashboard Launchpad/Action Center => Dashboard Launchpad/Action Center is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:4, group:0, group mask:empty reporting: default 7: Show Desktop , default: Show Desktop => Show Desktop is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:5, group:0, group mask:empty reporting: default 8: Backlight Down , default: Backlight Down => Backlight Down is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:6, group:0, group mask:empty reporting: default 9: Backlight Up , default: Backlight Up => Backlight Up is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:7, group:0, group mask:empty reporting: default 10: Previous Fn , default: Previous => Previous is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:8, group:0, group mask:empty reporting: default 11: Play/Pause Fn , default: Play/Pause => Play/Pause is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:9, group:0, group mask:empty reporting: default 12: Next Fn , default: Next => Next is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:10, group:0, group mask:empty reporting: default 13: Mute Fn , default: Mute => Mute is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:11, group:0, group mask:empty reporting: default 14: Volume Down Fn , default: Volume Down => Volume Down is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:12, group:0, group mask:empty reporting: default 15: Volume Up Fn , default: Volume Up => Volume Up nonstandard, reprogrammable, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 16: Calculator , default: Calculator => Calculator nonstandard, reprogrammable, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 17: Screen Capture/Print Screen, default: Screen Capture => Screen Capture nonstandard, reprogrammable, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 18: App Contextual Menu/Right Click, default: Right Click/App Contextual Menu => Right Click/App Contextual Menu nonstandard, reprogrammable, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 19: Lock PC , default: WindowsLock => WindowsLock nonstandard, reprogrammable, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 20: Left Arrow , default: Keyboard Left Arrow => Keyboard Left Arrow nonstandard, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 21: Right Arrow , default: Keyboard Right Arrow => Keyboard Right Arrow nonstandard, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 22: F Lock , default: Do Nothing One => Do Nothing One is FN, analytics key events, pos:0, group:0, group mask:empty reporting: default 23: FN Key , default: Do Nothing One => Do Nothing One nonstandard, analytics key events, pos:0, group:0, group mask:empty reporting: default Battery: 50%, discharging, next level 20%. Solaar-1.1.20/docs/devices/MX Keys Keyboard B35B.txt000066400000000000000000000241531522022367600215160ustar00rootroot00000000000000Solaar version 1.1.4rc1 1: MX Keys Keyboard Device path : /dev/hidraw8 USB id : 046d:B35B Codename : MX Keys Kind : keyboard Protocol : HID++ 4.5 Serial number: Model ID: B35B408A0000 Unit ID: EC96CD5C Bootloader: BL1 08.00.B0011 Firmware: MPK 12.01.B0013 Other: Supports 29 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BL1 08.00.B0011 00008169E8BB Firmware: Firmware MPK 12.01.B0013 B35BFE037737 Firmware: Other Unit ID: EC96CD5C Model ID: B35B408A0000 Transport IDs: {'btleid': 'B35B', 'wpid': '408A'} 3: DEVICE NAME {0005} Name: MX Keys Wireless Keyboard Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: DEVICE FRIENDLY NAME {0007} Friendly Name: MX Keys 7: BATTERY STATUS {1000} Battery: 100%, discharging, next level 50%. 8: REPROG CONTROLS V4 {1B04} Key/Button Diversion (saved): {Calculator:Regular, Show Desktop:Regular, Lock PC:Regular, Screen Capture/Print Screen:Regular, Brightness Down:Regular, Brightness Up:Regular, Host Switch Channel 1:Regular, Host Switch Channel 2:Regular, Host Switch Channel 3:Regular, Mission Control/Task View:Regular, Dashboard Launchpad/Action Center: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, Right Arrow:Regular, Left Arrow:Regular} Key/Button Diversion : {Calculator:Regular, Show Desktop:Regular, Lock PC:Regular, Screen Capture/Print Screen:Regular, Brightness Down:Regular, Brightness Up:Regular, Host Switch Channel 1:Regular, Host Switch Channel 2:Regular, Host Switch Channel 3:Regular, Mission Control/Task View:Regular, Dashboard Launchpad/Action Center: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, Right Arrow:Regular, Left Arrow:Regular} 9: CHANGE HOST {1814} Change Host : 1:ak 10: HOSTS INFO {1815} Host 0 (paired): ak Host 1 (unpaired): Host 2 (unpaired): 11: BACKLIGHT2 {1982} Backlight (saved): True Backlight : True 12: K375S FN INVERSION {40A3} Swap Fx function (saved): True Swap Fx function : True 13: ENCRYPTION {4100} 14: LOCK KEY STATE {4220} 15: KEYBOARD DISABLE KEYS {4521} 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} Set OS (saved): MacOS Set OS : MacOS 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:18A1 {18A1} internal, hidden 25: unknown:1E00 {1E00} hidden 26: unknown:1EB0 {1EB0} internal, hidden 27: unknown:1861 {1861} internal, hidden 28: unknown:1A20 {1A20} internal, hidden Has 24 reprogrammable keys: 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 nonstandard, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 nonstandard, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 2: Host Switch Channel 3 , default: HostSwitch Channel 3 => HostSwitch Channel 3 nonstandard, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 3: Brightness Down , default: Brightness Down => Brightness Down is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:1, group:0, group mask:empty reporting: default 4: Brightness Up , default: Brightness Up => Brightness Up is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:2, group:0, group mask:empty reporting: default 5: Mission Control/Task View , default: Mission Control/Task View => Mission Control/Task View is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:3, group:0, group mask:empty reporting: default 6: Dashboard Launchpad/Action Center, default: Dashboard Launchpad/Action Center => Dashboard Launchpad/Action Center is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:4, group:0, group mask:empty reporting: default 7: Show Desktop , default: Show Desktop => Show Desktop is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:5, group:0, group mask:empty reporting: default 8: Backlight Down , default: Backlight Down => Backlight Down is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:6, group:0, group mask:empty reporting: default 9: Backlight Up , default: Backlight Up => Backlight Up is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:7, group:0, group mask:empty reporting: default 10: Previous Fn , default: Previous => Previous is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:8, group:0, group mask:empty reporting: default 11: Play/Pause Fn , default: Play/Pause => Play/Pause is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:9, group:0, group mask:empty reporting: default 12: Next Fn , default: Next => Next is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:10, group:0, group mask:empty reporting: default 13: Mute Fn , default: Mute => Mute is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:11, group:0, group mask:empty reporting: default 14: Volume Down Fn , default: Volume Down => Volume Down is FN, FN sensitive, reprogrammable, divertable, persistently divertable, analytics key events, pos:12, group:0, group mask:empty reporting: default 15: Volume Up Fn , default: Volume Up => Volume Up nonstandard, reprogrammable, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 16: Calculator , default: Calculator => Calculator nonstandard, reprogrammable, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 17: Screen Capture/Print Screen, default: Screen Capture => Screen Capture nonstandard, reprogrammable, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 18: App Contextual Menu/Right Click, default: Right Click/App Contextual Menu => Right Click/App Contextual Menu nonstandard, reprogrammable, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 19: Lock PC , default: WindowsLock => WindowsLock nonstandard, reprogrammable, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 20: Left Arrow , default: Keyboard Left Arrow => Keyboard Left Arrow nonstandard, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 21: Right Arrow , default: Keyboard Right Arrow => Keyboard Right Arrow nonstandard, divertable, persistently divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 22: F Lock , default: Do Nothing One => Do Nothing One is FN, analytics key events, pos:0, group:0, group mask:empty reporting: default 23: FN Key , default: Do Nothing One => Do Nothing One nonstandard, analytics key events, pos:0, group:0, group mask:empty reporting: default Battery: 100%, discharging, next level 50%. Solaar-1.1.20/docs/devices/MX Keys Mini B369.txt000066400000000000000000000162601522022367600206420ustar00rootroot00000000000000Solaar version 1.1.4 255: MX Keys Mini Device path : /dev/hidraw6 USB id : 046d:B369 Codename : MX Keys Mini Kind : ? Protocol : HID++ 4.5 Serial number: Model ID: B36900000000 Unit ID: 42BABA25 Bootloader: BL1 53.01.B0006 Firmware: RBK 73.01.B0006 Other: Supports 31 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BL1 53.01.B0006 B369FD70F4 Firmware: Firmware RBK 73.01.B0006 B369FD70F4 Firmware: Other Unit ID: 42BABA25 Model ID: B36900000000 Transport IDs: {'btleid': 'B369'} 3: DEVICE NAME {0005} Name: MX Keys Mini Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: DEVICE FRIENDLY NAME {0007} Friendly Name: MX Keys Mini 7: UNIFIED BATTERY {1004} 8: REPROG CONTROLS V4 {1B04} Key/Button Diversion (saved): {Backlight Down:Regular, Backlight Up:Regular, Play/Pause Fn:Regular, Mute Fn:Regular, Volume Down Fn:Regular, Volume Up Fn:Regular, Voice Dictation:Regular, Open Emoji Panel:Regular, Snipping Tool:Regular, Delete:Regular, Mute Microphone:Regular} Key/Button Diversion : {Backlight Down:Regular, Backlight Up:Regular, Play/Pause Fn:Regular, Mute Fn:Regular, Volume Down Fn:Regular, Volume Up Fn:Regular, Voice Dictation:Regular, Open Emoji Panel:Regular, Snipping Tool:Regular, Delete:Regular, Mute Microphone:Regular} 9: CHANGE HOST {1814} Change Host : 3:fedora 10: HOSTS INFO {1815} Host 0 (paired): U308198 Host 1 (paired): DESKTOP-9C4GF7M Host 2 (paired): fedora 11: BACKLIGHT2 {1982} Backlight (saved): True Backlight : True 12: K375S FN INVERSION {40A3} Swap Fx function (saved): False Swap Fx function : False 13: LOCK KEY STATE {4220} 14: KEYBOARD DISABLE KEYS {4521} Disable keys (saved): {Caps Lock:False, Insert:False, Win:False} Disable keys : {Caps Lock:False, Insert:False, Win:False} 15: MULTIPLATFORM {4531} Set OS (saved): Windows Set OS : Windows 16: DFUCONTROL {00C3} 17: DEVICE RESET {1802} internal, hidden, unknown:000010 18: unknown:1803 {1803} internal, hidden, unknown:000010 19: CONFIG DEVICE PROPS {1806} internal, hidden, unknown:000010 20: unknown:1816 {1816} internal, hidden, unknown:000010 21: OOBSTATE {1805} internal, hidden 22: unknown:1830 {1830} internal, hidden, unknown:000010 23: unknown:1891 {1891} internal, hidden, unknown:000008 24: unknown:18A1 {18A1} internal, hidden, unknown:000010 25: unknown:1E00 {1E00} hidden 26: unknown:1E02 {1E02} internal, hidden 27: unknown:1602 {1602} 28: unknown:1EB0 {1EB0} internal, hidden, unknown:000010 29: unknown:1861 {1861} internal, hidden, unknown:000010 30: unknown:1A20 {1A20} internal, hidden, unknown:000010 Has 16 reprogrammable keys: 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 is FN, FN sensitive, analytics key events, pos:1, group:0, group mask:empty reporting: default 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 is FN, FN sensitive, analytics key events, pos:2, group:0, group mask:empty reporting: default 2: Host Switch Channel 3 , default: HostSwitch Channel 3 => HostSwitch Channel 3 is FN, FN sensitive, analytics key events, pos:3, group:0, group mask:empty reporting: default 3: Backlight Down , default: Backlight Down => Backlight Down is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:4, group:0, group mask:empty reporting: default 4: Backlight Up , default: Backlight Up => Backlight Up is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:5, group:0, group mask:empty reporting: default 5: Voice Dictation , default: Voice Dictation => Voice Dictation is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:6, group:0, group mask:empty reporting: default 6: Open Emoji Panel , default: Open Emoji Panel => Open Emoji Panel is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:7, group:0, group mask:empty reporting: default 7: Snipping Tool , default: Snipping Tool => Snipping Tool is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:8, group:0, group mask:empty reporting: default 8: Mute Microphone , default: Mute Microphone => Mute Microphone is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:9, group:0, group mask:empty reporting: default 9: Play/Pause Fn , default: Play/Pause => Play/Pause 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:13, group:0, group mask:empty reporting: default 13: Delete , default: Delete => Delete nonstandard, reprogrammable, divertable, analytics key events, pos:14, group:0, group mask:empty reporting: default 14: F Lock , default: Do Nothing One => Do Nothing One is FN, analytics key events, pos:0, group:0, group mask:empty reporting: default 15: FN Key , default: Do Nothing One => Do Nothing One nonstandard, analytics key events, pos:0, group:0, group mask:empty reporting: default Battery: 40%, discharging. Solaar-1.1.20/docs/devices/MX Keys S B378.text000066400000000000000000000220471522022367600203150ustar00rootroot00000000000000solaar 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. Solaar-1.1.20/docs/devices/MX Keys for Business B363.text000066400000000000000000000237031522022367600224470ustar00rootroot00000000000000Solaar version 1.1.5 1: MX Keys for Business Device path : None WPID : B363 Codename : MX Keys B Kind : keyboard Protocol : HID++ 4.5 Serial number: C8457F05 Model ID: B36300000000 Unit ID: C8457F05 Bootloader: BL1 31.00.B0009 Firmware: RBK 68.00.B0009 Other: 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: Bootloader BL1 31.00.B0009 B3636927CBB5 Firmware: Firmware RBK 68.00.B0009 B3636927CBB5 Firmware: Other Unit ID: C8457F05 Model ID: B36300000000 Transport IDs: {'btleid': 'B363'} 3: DEVICE NAME {0005} V0 Name: MX Keys for Business Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} V0 5: RESET {0020} V0 6: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: MX Keys B 7: UNIFIED BATTERY {1004} V2 Battery: 60%, discharging. 8: REPROG CONTROLS V4 {1B04} V5 Key/Button Diversion (saved): {Calculator:Regular, Lock PC:Regular, Brightness Down:Diverted, Brightness Up:Regular, Host Switch Channel 1:Regular, Host Switch Channel 2:Regular, Host Switch Channel 3: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, Right Arrow:Regular, Left Arrow:Regular, Voice Dictation:Regular, Open Emoji Panel:Regular, Snipping Tool:Regular, Mute Microphone:Regular} Key/Button Diversion : {Calculator:Regular, Lock PC:Regular, Brightness Down:Diverted, Brightness Up:Regular, Host Switch Channel 1:Regular, Host Switch Channel 2:Regular, Host Switch Channel 3: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, Right Arrow:Regular, Left Arrow:Regular, Voice Dictation:Regular, Open Emoji Panel:Regular, Snipping Tool:Regular, Mute Microphone:Regular} 9: CHANGE HOST {1814} V1 Change Host : 1:Ryzen 10: HOSTS INFO {1815} V1 Host 0 (paired): Ryzen Host 1 (paired): Ryzen Host 2 (paired): Mi 10 Lite 5G 11: BACKLIGHT2 {1982} V1 Backlight (saved): True Backlight : True 12: K375S FN INVERSION {40A3} V0 Swap Fx function (saved): False Swap Fx function : False 13: LOCK KEY STATE {4220} V0 14: 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} 15: MULTIPLATFORM {4531} V1 Set OS (saved): Windows Set OS : Windows 16: DFUCONTROL {00C3} V0 17: DEVICE RESET {1802} V0 internal, hidden, unknown:000010 18: unknown:1803 {1803} V0 internal, hidden, unknown:000010 19: CONFIG DEVICE PROPS {1806} V8 internal, hidden, unknown:000010 20: unknown:1816 {1816} V0 internal, hidden, unknown:000010 21: OOBSTATE {1805} V0 internal, hidden 22: unknown:1830 {1830} V0 internal, hidden, unknown:000010 23: unknown:1891 {1891} V6 internal, hidden, unknown:000008 24: unknown:18A1 {18A1} V0 internal, hidden, unknown:000010 25: unknown:1E00 {1E00} V0 hidden 26: unknown:1E02 {1E02} V0 internal, hidden 27: unknown:1602 {1602} V0 28: unknown:1EB0 {1EB0} V0 internal, hidden, unknown:000010 29: unknown:1861 {1861} V0 internal, hidden, unknown:000010 30: unknown:1A20 {1A20} V0 internal, hidden, unknown:000010 31: unknown:18B0 {18B0} V0 internal, hidden, unknown:000010 Has 24 reprogrammable keys: 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 nonstandard, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 nonstandard, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 2: Host Switch Channel 3 , default: HostSwitch Channel 3 => HostSwitch Channel 3 nonstandard, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 3: Brightness Down , default: Brightness Down => Brightness Down is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:1, group:0, group mask:empty reporting: diverted 4: 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 5: 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 6: 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 7: 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 8: 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 9: 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 10: Previous Fn , default: Previous => Previous is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:8, group:0, group mask:empty reporting: default 11: 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 12: Next Fn , default: Next => Next is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:10, group:0, group mask:empty reporting: default 13: Mute Fn , default: Mute => Mute is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:11, group:0, group mask:empty reporting: default 14: 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 15: Volume Up Fn , default: Volume Up => Volume Up nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 16: Calculator , default: Calculator => Calculator nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 17: Snipping Tool , default: Snipping Tool => Snipping Tool nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 18: 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 19: Lock PC , default: WindowsLock => WindowsLock nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 20: Left Arrow , default: Keyboard Left Arrow => Keyboard Left Arrow nonstandard, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 21: Right Arrow , default: Keyboard Right Arrow => Keyboard Right Arrow nonstandard, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 22: F Lock , default: Do Nothing One => Do Nothing One is FN, analytics key events, pos:0, group:0, group mask:empty reporting: default 23: FN Key , default: Do Nothing One => Do Nothing One nonstandard, analytics key events, pos:0, group:0, group mask:empty reporting: default Battery: 60%, discharging. Solaar-1.1.20/docs/devices/MX Master 3 Wireless Mouse 4082.txt000066400000000000000000000142451522022367600232320ustar00rootroot00000000000000Solaar version 1.1.4 1: MX Master 3 Wireless Mouse Device path : /dev/hidraw2 WPID : 4082 Codename : MX Master 3 Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: 198E3EB8 Model ID: B02340820000 Unit ID: 198E3EB8 Bootloader: BOT 95.01.B0015 Firmware: MPM 19.01.B0015 Other: The power switch is located on the base. Supports 34 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 95.01.B0015 00006A9CA89D Firmware: Firmware MPM 19.01.B0015 40826A9CA89D Firmware: Other Unit ID: 198E3EB8 Model ID: B02340820000 Transport IDs: {'btleid': 'B023', 'wpid': '4082'} 3: DEVICE NAME {0005} Name: Wireless Mouse MX Master 3 Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: CRYPTO ID {0021} 7: DEVICE FRIENDLY NAME {0007} Friendly Name: MX Master 3 8: BATTERY STATUS {1000} Battery: 100%, discharging, next level 50%. 9: REPROG CONTROLS V4 {1B04} Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift} 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, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift} Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Mouse Gesture Button:Mouse Gestures, Smart Shift:Sliding DPI} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Mouse Gesture Button:Regular, Smart Shift:Regular} 10: CHANGE HOST {1814} Change Host : 1:idefix 11: XY STATS {2250} 12: ADJUSTABLE DPI {2201} Sensitivity (DPI) (saved): 1600 Sensitivity (DPI) : 1600 13: SMART SHIFT {2110} Scroll Wheel Rachet (saved): 32 Scroll Wheel Rachet : 32 14: HIRES WHEEL {2121} Multiplier: 15 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 15: THUMB WHEEL {2150} Thumb Wheel Direction (saved): False Thumb Wheel Direction : False Thumb Wheel Diversion (saved): False Thumb Wheel Diversion : False 16: WHEEL STATS {2251} 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:9001 {9001} internal, hidden 32: unknown:9203 {9203} internal, hidden 33: unknown:9205 {9205} internal, hidden Has 8 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:3, group mask:g1,g2,g3 reporting: default 3: Back Button , default: Mouse Back Button => Mouse Back Button mse, reprogrammable, divertable, raw XY, analytics key events, 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, pos:0, group:2, group mask:g1,g2 reporting: default 5: Mouse Gesture Button , default: Gesture Button Navigation => Gesture Button Navigation mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:3, group mask:g1,g2,g3 reporting: default 6: Smart Shift , default: Smart Shift => Smart Shift mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:3, group mask:g1,g2,g3 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: 100%, discharging, next level 50%. Solaar-1.1.20/docs/devices/MX Master 3 Wireless Mouse B023.txt000066400000000000000000000135361522022367600232450ustar00rootroot00000000000000Solaar version 1.1.4 1: MX Master 3 Wireless Mouse Device path : /dev/hidraw5 USB id : 046d:B023 Codename : MX Master 3 Kind : mouse Protocol : HID++ 4.5 Serial number: Model ID: B02340820000 Unit ID: 198E3EB8 Bootloader: BOT 95.01.B0015 Firmware: MPM 19.01.B0015 Other: Supports 30 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 95.01.B0015 00006A9CA89D Firmware: Firmware MPM 19.01.B0015 B0236A9CA89D Firmware: Other Unit ID: 198E3EB8 Model ID: B02340820000 Transport IDs: {'btleid': 'B023', 'wpid': '4082'} 3: DEVICE NAME {0005} Name: Wireless Mouse MX Master 3 Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: CRYPTO ID {0021} 7: DEVICE FRIENDLY NAME {0007} Friendly Name: MX Master 3 8: BATTERY STATUS {1000} Battery: 100%, discharging, next level 50%. 9: REPROG CONTROLS V4 {1B04} Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift} 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, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift} Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Mouse Gesture Button:Mouse Gestures, Smart Shift:Sliding DPI} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Mouse Gesture Button:Regular, Smart Shift:Regular} 10: CHANGE HOST {1814} Change Host : 2:idefix 11: XY STATS {2250} 12: ADJUSTABLE DPI {2201} Sensitivity (DPI) (saved): 1600 Sensitivity (DPI) : 1600 13: SMART SHIFT {2110} Scroll Wheel Rachet (saved): 32 Scroll Wheel Rachet : 32 14: HIRES WHEEL {2121} Multiplier: 15 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 15: THUMB WHEEL {2150} Thumb Wheel Direction (saved): False Thumb Wheel Direction : False Thumb Wheel Diversion (saved): False Thumb Wheel Diversion : False 16: WHEEL STATS {2251} 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:18A1 {18A1} internal, hidden 25: unknown:1E00 {1E00} hidden 26: unknown:1EB0 {1EB0} internal, hidden 27: unknown:1861 {1861} internal, hidden 28: unknown:9300 {9300} internal, hidden 29: unknown:9001 {9001} internal, hidden Has 8 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:3, group mask:g1,g2,g3 reporting: default 3: Back Button , default: Mouse Back Button => Mouse Back Button mse, reprogrammable, divertable, raw XY, analytics key events, 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, pos:0, group:2, group mask:g1,g2 reporting: default 5: Mouse Gesture Button , default: Gesture Button Navigation => Gesture Button Navigation mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:3, group mask:g1,g2,g3 reporting: default 6: Smart Shift , default: Smart Shift => Smart Shift mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:3, group mask:g1,g2,g3 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: 100%, discharging, next level 50%. Solaar-1.1.20/docs/devices/MX Master 3 for Business B028.text000066400000000000000000000150511522022367600231050ustar00rootroot00000000000000solaar version 1.1.8 1: MX Master 3 for Business Device path : None WPID : B028 Codename : MX Master 3 B Kind : mouse Protocol : HID++ 4.5 Serial number: 18F3413B Model ID: B02800000000 Unit ID: 18F3413B Bootloader: BL1 41.00.B0009 Firmware: RBM 14.00.B0009 Other: 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: Bootloader BL1 41.00.B0009 B0281D13EFC0 Firmware: Firmware RBM 14.00.B0009 B0281D13EFC0 Firmware: Other Unit ID: 18F3413B Model ID: B02800000000 Transport IDs: {'btleid': 'B028'} 3: DEVICE NAME {0005} V0 Name: MX Master 3 for Business Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} V0 5: CONFIG CHANGE {0020} V0 6: CRYPTO ID {0021} V1 7: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: MX Master 3 B 8: UNIFIED BATTERY {1004} V2 Battery: 95%, discharging. 9: REPROG CONTROLS V4 {1B04} V5 Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift} 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, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift} Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Mouse Gesture Button:Regular, Smart Shift:Regular} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Mouse Gesture Button:Regular, Smart Shift:Regular} 10: CHANGE HOST {1814} V1 Change Host : 1:bork 11: XY STATS {2250} V1 12: ADJUSTABLE DPI {2201} V2 Sensitivity (DPI) (saved): 1000 Sensitivity (DPI) : 1000 13: SMART SHIFT {2110} V0 Scroll Wheel Ratcheted (saved): Freespinning Scroll Wheel Ratcheted : Freespinning Scroll Wheel Ratchet Speed (saved): 1 Scroll Wheel Ratchet Speed : 1 14: HIRES WHEEL {2121} V1 Multiplier: 15 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 15: THUMB WHEEL {2150} V0 Thumb Wheel Direction (saved): False Thumb Wheel Direction : False Thumb Wheel Diversion (saved): False Thumb 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} V6 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} V0 internal, hidden, unknown:000010 31: unknown:9300 {9300} V0 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 8 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, 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, pos:0, group:2, group mask:g1,g2 reporting: default 5: Mouse Gesture Button , default: Gesture Button Navigation => Gesture Button Navigation mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 reporting: default 6: Smart Shift , default: Smart Shift => Smart Shift mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 reporting: default 7: 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: 95%, discharging. Solaar-1.1.20/docs/devices/MX Master 3S B034.txt000066400000000000000000000154071522022367600205420ustar00rootroot00000000000000Solaar version 1.1.7 2: MX Master 3S Device path : None WPID : B034 Codename : MX Master 3S Kind : mouse Protocol : HID++ 4.5 Serial number: CD2A6BB7 Model ID: B03400000000 Unit ID: CD2A6BB7 Bootloader: BL1 69.00.B0003 Firmware: RBM 22.00.B0003 Other: The power switch is located on the (unknown). Supports 36 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V4 Firmware: Bootloader BL1 69.00.B0003 B03438FCA4E6 Firmware: Firmware RBM 22.00.B0003 B03438FCA4E6 Firmware: Other Unit ID: CD2A6BB7 Model ID: B03400000000 Transport IDs: {'btleid': 'B034'} 3: DEVICE NAME {0005} V0 Name: MX Master 3S Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} V0 5: RESET {0020} V0 6: CRYPTO ID {0021} V1 7: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: MX Master 3S 8: UNIFIED BATTERY {1004} V3 Battery: 25%, discharging. 9: REPROG CONTROLS V4 {1B04} V5 Acciones de Tecla/Botón (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift} Acciones de Tecla/Botón : {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift} Desvio de Tecla/Botón (saved): {Middle Button:Normal, Back Button:Normal, Forward Button:Normal, Mouse Gesture Button:Normal, Smart Shift:Normal} Desvio de Tecla/Botón : {Middle Button:Normal, Back Button:Normal, Forward Button:Normal, Mouse Gesture Button:Normal, Smart Shift:Normal} 10: CHANGE HOST {1814} V1 Cambiar Host : 1:romanescu 11: HOSTS INFO {1815} V2 Host 0 (paired): romanescu Host 1 (unpaired): Host 2 (unpaired): 12: XY STATS {2250} V1 13: ADJUSTABLE DPI {2201} V2 Sensibilidad (PPP) (saved): 1000 Sensibilidad (PPP) : 1000 14: SMART SHIFT {2110} V0 Scroll Wheel Ratcheted (saved): Freespinning Scroll Wheel Ratcheted : Freespinning Scroll Wheel Ratchet Speed (saved): 0 Scroll Wheel Ratchet Speed : 1 15: HIRES WHEEL {2121} V1 Multiplier: 15 Has invert: Normal wheel motion Has ratchet switch: Free wheel mode Low resolution mode HID notification Dirección Rueda Desplazamiento (saved): False Dirección Rueda Desplazamiento : False Resolución Rueda Desplazamiento (saved): False Resolución Rueda Desplazamiento : False Desvio Rueda Desplazamiento (saved): False Desvio Rueda Desplazamiento : False 16: THUMB WHEEL {2150} V0 Dirección de la Rueda del Pulgar (saved): False Dirección de la Rueda del Pulgar : False Desvío de la Rueda del Pulgar (saved): False Desvío de la Rueda del Pulgar : False 17: WHEEL STATS {2251} V0 18: DFUCONTROL {00C3} V0 19: DEVICE RESET {1802} V0 internal, hidden, unknown:000010 20: unknown:1803 {1803} V0 internal, hidden, unknown:000010 21: CONFIG DEVICE PROPS {1806} V8 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:9300 {9300} V0 internal, hidden, unknown:000010 33: unknown:9001 {9001} V0 internal, hidden, unknown:000010 34: unknown:1E22 {1E22} V0 internal, hidden, unknown:000010 35: unknown:9205 {9205} V0 internal, hidden, unknown:000010 Has 8 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, 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, pos:0, group:2, group mask:g1,g2 reporting: default 5: Mouse Gesture Button , default: Gesture Button Navigation => Gesture Button Navigation mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 reporting: default 6: Smart Shift , default: Smart Shift => Smart Shift mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 reporting: default 7: 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: 25%, discharging. Solaar-1.1.20/docs/devices/MX Master 4.text000066400000000000000000000174411522022367600201740ustar00rootroot00000000000000solaar version 1.1.16-7-g2cb5fa4b MX Master 4 Device path : /dev/hidraw7 USB id : 046d:B042 Codename : MX Master 4 Kind : mouse Protocol : HID++ 4.5 Serial number: Model ID: B04200000000 Unit ID: F03B4069 1: LD 04.00 0: RBM 27.00.B0015 0: RBM 27.00.B0015 2: 72 Supports 46 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V8 Firmware: 1 LD 04.00 0000642BA1A1 Firmware: 0 RBM 27.00.B0015 B042F21A379A Firmware: 0 RBM 27.00.B0015 B042F21A379A Firmware: 2 72 Unit ID: F03B4069 Model ID: B04200000000 Transport IDs: {'btleid': 'B042'} 3: DEVICE NAME {0005} V5 Name: MX Master 4 Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} V0 5: CONFIG CHANGE {0020} V1 Configuration: 11000000000000000000000000000000 6: CRYPTO ID {0021} V1 7: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: MX Master 4 8: unknown:0011 {1100} V0 9: UNIFIED BATTERY {1004} V5 Battery: 45%, BatteryStatus.DISCHARGING. 10: unknown:1701 {0117} V0 11: unknown:19B0 {B019} V0 12: unknown:19C0 {C019} V0 13: REPROG CONTROLS V4 {1B04} V6 Key/Button Actions (saved): {Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button, Smart Shift:Smart Shift, Haptic:unknown:0109} Key/Button Actions : {Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button, Smart Shift:Smart Shift, Haptic:unknown:0109} Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Mouse Gesture Button:Regular, Smart Shift:Regular, Haptic:Regular} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Mouse Gesture Button:Regular, Smart Shift:Regular, Haptic:Regular} 14: CHANGE HOST {1814} V2 Change Host : 1:idefix 15: HOSTS INFO {1815} V2 Host 0 (paired): idefix Host 1 (paired): idefix Host 2 (unpaired): 16: XY STATS {2250} V1 17: SMART SHIFT ENHANCED {2111} V0 Scroll Wheel Ratcheted (saved): Ratcheted Scroll Wheel Ratcheted : Ratcheted Scroll Wheel Ratchet Speed (saved): 10 Scroll Wheel Ratchet Speed : 10 18: 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 (saved): False Scroll Wheel Direction : False Scroll Wheel Resolution (saved): False Scroll Wheel Resolution : False Scroll Wheel Diversion (saved): False Scroll Wheel Diversion : False 19: THUMB WHEEL {2150} V0 Thumb Wheel Direction (saved): True Thumb Wheel Direction : True Thumb Wheel Diversion (saved): False Thumb Wheel Diversion : False 20: ADJUSTABLE DPI {2201} V2 Sensitivity (DPI) (saved): 1000 Sensitivity (DPI) : 1000 21: WHEEL STATS {2251} V0 22: unknown:00D1 {D100} V0 23: DEVICE RESET {1802} V0 24: unknown:1803 {0318} V0 internal, hidden, unknown:000010 25: unknown:1807 {0718} V0 internal, hidden, unknown:000010 26: unknown:1816 {1618} V0 internal, hidden, unknown:000010 27: OOBSTATE {1805} V0 28: unknown:1830 {3018} V0 internal, hidden, unknown:000010 29: unknown:1891 {9118} V0 internal, hidden, unknown:000008 30: unknown:18A1 {A118} V0 internal, hidden, unknown:000010 31: unknown:1E00 {001E} V0 hidden 32: unknown:1E02 {021E} V0 internal, hidden 33: unknown:1E22 {221E} V0 internal, hidden, unknown:000010 34: unknown:1E30 {301E} V0 internal, hidden, unknown:000010 35: unknown:1602 {0216} V0 36: unknown:1EB0 {B01E} V0 internal, hidden, unknown:000010 37: unknown:1861 {6118} V0 internal, hidden, unknown:000010 38: unknown:9205 {0592} V0 internal, hidden, unknown:000010 39: unknown:9201 {0192} V0 internal, hidden, unknown:000010 40: unknown:9300 {0093} V0 internal, hidden, unknown:000010 41: unknown:9401 {0194} V0 internal, hidden, unknown:000010 42: unknown:9402 {0294} V0 internal, hidden, unknown:000010 43: unknown:9001 {0190} V0 internal, hidden, unknown:000010 44: unknown:18B1 {B118} V0 internal, hidden, unknown:000010 45: unknown:18C0 {C018} V0 internal, hidden, unknown:000010 Has 9 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: Back Button , default: Mouse Back Button => Mouse Back Button analytics_key_events, raw_xy, divertable, reprogrammable, mse, pos:0, group:2, group mask:g1,g2 reporting: default 4: Forward Button , default: Mouse Forward Button => Mouse Forward Button analytics_key_events, raw_xy, divertable, reprogrammable, mse, pos:0, group:2, group mask:g1,g2 reporting: default 5: Mouse Gesture Button , default: Gesture Button => Gesture Button analytics_key_events, raw_xy, divertable, reprogrammable, mse, pos:0, group:2, group mask:g1,g2 reporting: default 6: Smart Shift , default: Smart Shift => Smart Shift analytics_key_events, raw_xy, divertable, reprogrammable, mse, pos:0, group:2, group mask:g1,g2 reporting: default 7: Haptic , default: unknown:0109 => Unknown_1a0 analytics_key_events, raw_xy, divertable, reprogrammable, mse, pos:0, group:2, group mask:g1,g2 reporting: default 8: 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: 45%, BatteryStatus.DISCHARGING. Solaar-1.1.20/docs/devices/MX Mechanical B366.txt000066400000000000000000000340261522022367600210730ustar00rootroot00000000000000Solaar version 1.1.7 1: MX Mechanical Device path : None WPID : B366 Codename : MX MCHNCL Kind : keyboard Protocol : HID++ 4.5 Serial number: 2DF63C09 Model ID: B36600000000 Unit ID: 2DF63C09 Bootloader: BL1 45.00.B0016 Firmware: RBK 71.00.B0016 Other: The power switch is located on the (unknown). Supports 33 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V4 Firmware: Bootloader BL1 45.00.B0016 B3662CB9B8E2 Firmware: Firmware RBK 71.00.B0016 B3662CB9B8E2 Firmware: Other Unit ID: 2DF63C09 Model ID: B36600000000 Transport IDs: {'btleid': 'B366'} 3: DEVICE NAME {0005} V0 Name: MX Mechanical Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} V0 5: RESET {0020} V0 6: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: MX MCHNCL 7: UNIFIED BATTERY {1004} V3 Battery: 50%, discharging. 8: REPROG CONTROLS V4 {1B04} V5 Desvio de Tecla/Botón (saved): {Calculator:Normal, Show Desktop:Normal, Lock PC:Normal, Brightness Down:Normal, Brightness Up:Normal, MultiPlatform Search:Normal, Backlight Down:Normal, Backlight Up:Normal, Previous Fn:Normal, Play/Pause Fn:Normal, Next Fn:Normal, Mute Fn:Normal, Volume Down Fn:Normal, Volume Up Fn:Normal, Voice Dictation:Normal, Open Emoji Panel:Normal, Snipping Tool:Normal, Grave Accent:Normal, Tab Key:Normal, Caps Lock:Normal, Left Shift:Normal, Left Control:Normal, Left Option/Start:Normal, Left Command/Alt:Normal, Right Command/Alt:Normal, Right Option/Start:Normal, Right Control:Normal, Right Shift:Normal, Insert:Normal, Delete:Normal, Home:Normal, End:Normal, Page Up:Normal, Page Down:Normal, Mute Microphone:Normal, Backslash:Normal} Desvio de Tecla/Botón : {Calculator:Normal, Show Desktop:Normal, Lock PC:Normal, Brightness Down:Normal, Brightness Up:Normal, MultiPlatform Search:Normal, Backlight Down:Normal, Backlight Up:Normal, Previous Fn:Normal, Play/Pause Fn:Normal, Next Fn:Normal, Mute Fn:Normal, Volume Down Fn:Normal, Volume Up Fn:Normal, Voice Dictation:Normal, Open Emoji Panel:Normal, Snipping Tool:Normal, Grave Accent:Normal, Tab Key:Normal, Caps Lock:Normal, Left Shift:Normal, Left Control:Normal, Left Option/Start:Normal, Left Command/Alt:Normal, Right Command/Alt:Normal, Right Option/Start:Normal, Right Control:Normal, Right Shift:Normal, Insert:Normal, Delete:Normal, Home:Normal, End:Normal, Page Up:Normal, Page Down:Normal, Mute Microphone:Normal, Backslash:Normal} 9: CHANGE HOST {1814} V1 Cambiar Host : 1:romanescu 10: HOSTS INFO {1815} V2 Host 0 (paired): romanescu Host 1 (unpaired): Host 2 (unpaired): 11: BACKLIGHT2 {1982} V2 Retroiluminación (saved): True Retroiluminación : True 12: K375S FN INVERSION {40A3} V0 Intercambiar función Fx (saved): True Intercambiar función Fx : True 13: LOCK KEY STATE {4220} V0 14: KEYBOARD DISABLE KEYS {4521} V0 Desactivar teclas (saved): {Caps Lock:False, Num Lock:False, Scroll Lock:False, Insert:False, Win:False} Desactivar teclas : {Caps Lock:False, Num Lock:False, Scroll Lock:False, Insert:False, Win:False} 15: MULTIPLATFORM {4531} V1 Especificar SO (saved): Windows Especificar SO : Windows 16: KEYBOARD LAYOUT 2 {4540} 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:1A20 {1A20} V0 internal, hidden, unknown:000010 32: unknown:18B0 {18B0} V0 internal, hidden, unknown:000010 Has 41 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: Snipping Tool , default: Snipping Tool => Snipping Tool is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:7, group:0, group mask:empty reporting: default 7: Mute Microphone , default: Mute Microphone => Mute Microphone is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:8, group:0, group mask:empty reporting: default 8: Previous Fn , default: Previous => Previous is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:9, group:0, group mask:empty reporting: default 9: Play/Pause Fn , default: Play/Pause => Play/Pause is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:10, group:0, group mask:empty reporting: default 10: Next Fn , default: Next => Next is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:11, group:0, group mask:empty reporting: default 11: Mute Fn , default: Mute => Mute is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:12, group:0, group mask:empty reporting: default 12: Volume Down Fn , default: Volume Down => Volume Down nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 13: Volume Up Fn , default: Volume Up => Volume Up nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 14: Grave Accent , default: Grave Accent => Grave Accent reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 15: Tab Key , default: Standard Tab Key => Standard Tab Key reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 16: Caps Lock , default: Caps Lock => Caps Lock reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 17: Left Shift , default: Left Shift => Left Shift reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 18: Left Control , default: Left Control => Left Control reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 19: Left Option/Start , default: Left Option/Start => Left Option/Start reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 20: Left Command/Alt , default: Left Command/Alt => Left Command/Alt reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 21: Right Command/Alt , default: Right Command/Alt => Right Command/Alt reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 22: Right Option/Start , default: Right Option/Start => Right Option/Start reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 23: Right Control , default: Right Control => Right Control reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 24: Right Shift , default: Right Shift => Right Shift reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 25: Backslash , default: Backslash => Backslash reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 26: Insert , default: Insert => Insert reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 27: Delete , default: Delete => Delete reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 28: Home , default: Home => Home reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 29: End , default: End => End reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 30: Page Up , default: Page Up => Page Up reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 31: Page Down , default: Page Down => Page Down reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 32: Calculator , default: Calculator => Calculator nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 33: Show Desktop , default: Show Desktop => Show Desktop nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 34: MultiPlatform Search , default: Multiplatform Search => Multiplatform Search nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 35: Lock PC , default: WindowsLock => WindowsLock nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 36: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 nonstandard, analytics key events, pos:0, group:0, group mask:empty reporting: default 37: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 nonstandard, analytics key events, pos:0, group:0, group mask:empty reporting: default 38: Host Switch Channel 3 , default: HostSwitch Channel 3 => HostSwitch Channel 3 nonstandard, analytics key events, pos:0, group:0, group mask:empty reporting: default 39: F Lock , default: Do Nothing One => Do Nothing One is FN, analytics key events, pos:0, group:0, group mask:empty reporting: default 40: FN Key , default: Do Nothing One => Do Nothing One nonstandard, analytics key events, pos:0, group:0, group mask:empty reporting: default Battery: 50%, discharging. Solaar-1.1.20/docs/devices/MX Mechanical Mini B367.txt000066400000000000000000000271031522022367600217470ustar00rootroot00000000000000Solaar version 1.1.4 1: MX Mechanical Mini Device path : None WPID : B367 Codename : MX MCHNCL M Kind : keyboard Protocol : HID++ 4.5 Serial number: D4E94F5C Model ID: B36700000000 Unit ID: D4E94F5C Bootloader: BL1 50.00.B0010 Firmware: RBK 72.00.B0010 Other: 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: Bootloader BL1 50.00.B0010 B367A91B7FC8 Firmware: Firmware RBK 72.00.B0010 B367A91B7FC8 Firmware: Other Unit ID: D4E94F5C Model ID: B36700000000 Transport IDs: {'btleid': 'B367'} 3: DEVICE NAME {0005} V0 Name: MX Mechanical Mini Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} V0 5: RESET {0020} V0 6: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: MX MCHNCL M 7: UNIFIED BATTERY {1004} V3 Battery: 90%, discharging. 8: REPROG CONTROLS V4 {1B04} V5 Key/Button Diversion (saved): {MultiPlatform Search:Regular, Backlight Down:Regular, Backlight Up:Regular, Mute Fn:Regular, Volume Down Fn:Regular, Volume Up Fn:Regular, Voice Dictation:Regular, Open Emoji Panel:Regular, Snipping Tool:Regular, Grave Accent:Regular, Tab Key:Regular, Caps Lock:Regular, Left Shift:Regular, Left Control:Regular, Left Option/Start:Regular, Left Command/Alt:Regular, Right Command/Alt:Regular, Right Shift:Regular, Delete:Regular, Home:Regular, End:Regular, Page Up:Regular, Page Down:Regular, Mute Microphone:Regular, Backslash:Regular, unknown:013C:Regular, unknown:0141:Regular} Key/Button Diversion : {MultiPlatform Search:Regular, Backlight Down:Regular, Backlight Up:Regular, Mute Fn:Regular, Volume Down Fn:Regular, Volume Up Fn:Regular, Voice Dictation:Regular, Open Emoji Panel:Regular, Snipping Tool:Regular, Grave Accent:Regular, Tab Key:Regular, Caps Lock:Regular, Left Shift:Regular, Left Control:Regular, Left Option/Start:Regular, Left Command/Alt:Regular, Right Command/Alt:Regular, Right Shift:Regular, Delete:Regular, Home:Regular, End:Regular, Page Up:Regular, Page Down:Regular, Mute Microphone:Regular, Backslash:Regular, unknown:013C:Regular, unknown:0141:Regular} 9: CHANGE HOST {1814} V1 Change Host : 1:chimera 10: HOSTS INFO {1815} V2 Host 0 (paired): chimera Host 1 (paired): Logitech Bolt receiver Host 2 (unpaired): 11: BACKLIGHT2 {1982} V2 Backlight (saved): True Backlight : True 12: K375S FN INVERSION {40A3} V0 Swap Fx function (saved): True Swap Fx function : True 13: LOCK KEY STATE {4220} V0 14: KEYBOARD DISABLE KEYS {4521} V0 Disable keys (saved): {Caps Lock:False, Scroll Lock:False, Insert:False, Win:False} Disable keys : {Caps Lock:False, Scroll Lock:False, Insert:False, Win:False} 15: MULTIPLATFORM {4531} V1 Set OS (saved): Windows Set OS : Windows 16: KEYBOARD LAYOUT 2 {4540} 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:1A20 {1A20} V0 internal, hidden, unknown:000010 Has 32 reprogrammable keys: 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 is FN, FN sensitive, analytics key events, pos:1, group:0, group mask:empty reporting: default 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 is FN, FN sensitive, analytics key events, pos:2, group:0, group mask:empty reporting: default 2: Host Switch Channel 3 , default: HostSwitch Channel 3 => HostSwitch Channel 3 is FN, FN sensitive, analytics key events, pos:3, group:0, group mask:empty reporting: default 3: Backlight Down , default: Backlight Down => Backlight Down is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:4, group:0, group mask:empty reporting: default 4: Backlight Up , default: Backlight Up => Backlight Up is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:5, group:0, group mask:empty reporting: default 5: Voice Dictation , default: Voice Dictation => Voice Dictation is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:6, group:0, group mask:empty reporting: default 6: Open Emoji Panel , default: Open Emoji Panel => Open Emoji Panel is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:7, group:0, group mask:empty reporting: default 7: Snipping Tool , default: Snipping Tool => Snipping Tool is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:8, group:0, group mask:empty reporting: default 8: Mute Microphone , default: Mute Microphone => Mute Microphone is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:9, group:0, group mask:empty reporting: default 9: MultiPlatform Search , default: Multiplatform Search => Multiplatform Search is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:10, group:0, group mask:empty reporting: default 10: unknown:0141 , default: unknown:00FD => unknown:00FD is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:11, group:0, group mask:empty reporting: default 11: Mute Fn , default: Mute => Mute is FN, FN sensitive, reprogrammable, divertable, analytics key events, pos:12, group:0, group mask:empty reporting: default 12: Volume Down Fn , default: Volume Down => Volume Down nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 13: Volume Up Fn , default: Volume Up => Volume Up nonstandard, reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 14: Grave Accent , default: Grave Accent => Grave Accent reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 15: Tab Key , default: Standard Tab Key => Standard Tab Key reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 16: Caps Lock , default: Caps Lock => Caps Lock reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 17: Left Shift , default: Left Shift => Left Shift reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 18: Left Control , default: Left Control => Left Control reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 19: Left Option/Start , default: Left Option/Start => Left Option/Start reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 20: Left Command/Alt , default: Left Command/Alt => Left Command/Alt reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 21: Right Command/Alt , default: Right Command/Alt => Right Command/Alt reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 22: unknown:013C , default: Right Option/Start/2 => Right Option/Start/2 reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 23: Right Shift , default: Right Shift => Right Shift reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 24: Backslash , default: Backslash => Backslash reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 25: Delete , default: Delete => Delete reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 26: Home , default: Home => Home reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 27: End , default: End => End reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 28: Page Up , default: Page Up => Page Up reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 29: Page Down , default: Page Down => Page Down reprogrammable, divertable, analytics key events, pos:0, group:0, group mask:empty reporting: default 30: F Lock , default: Do Nothing One => Do Nothing One is FN, analytics key events, pos:0, group:0, group mask:empty reporting: default 31: FN Key , default: Do Nothing One => Do Nothing One nonstandard, analytics key events, pos:0, group:0, group mask:empty reporting: default Battery: 90%, discharging. Solaar-1.1.20/docs/devices/MX Vertical Wireless Mouse 407B.txt000066400000000000000000000140731522022367600234430ustar00rootroot00000000000000Solaar version 1.1.3 2: MX Vertical Wireless Mouse Device path : /dev/hidraw5 WPID : 407B Codename : MX Vertical Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: 87A544C3 Model ID: B020407BC08A Unit ID: 7818C6EE Bootloader: BOT 78.00.B0009 Firmware: MPM 16.00.B0009 Other: Supports 34 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 78.00.B0009 AAE28E5B17FF Firmware: Firmware MPM 16.00.B0009 407B8E5B17FF Firmware: Other Unit ID: 7818C6EE Model ID: B020407BC08A Transport IDs: {'btleid': 'B020', 'wpid': '407B', 'usbid': 'C08A'} 3: DEVICE NAME {0005} Name: MX Vertical Advanced Ergonomic Mouse Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: CRYPTO ID {0021} 7: DEVICE FRIENDLY NAME {0007} Friendly Name: MX Vertical 8: BATTERY STATUS {1000} Battery: 50%, discharging, next level 20%. 9: unknown:1002 {1002} 10: REPROG CONTROLS V4 {1B04} DPI Sliding Adjustment (saved): Off DPI Sliding Adjustment : Off Mouse Gestures (saved): Off Mouse Gestures : Off Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, DPI Switch:DPI Switch} 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, DPI Switch:DPI Switch} Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Diverted, Forward Button:Diverted, ?, DPI Switch:Diverted} Key/Button Diversion : {Middle Button:Regular, Back Button:Diverted, Forward Button:Diverted, DPI Switch:Diverted} 11: PERSISTENT REMAPPABLE ACTION {1C00} 12: CHANGE HOST {1814} Change Host : 1:pudim 13: HOSTS INFO {1815} Host 0 (unpaired): pudim Host 1 (unpaired): Host 2 (unpaired): 14: XY STATS {2250} 15: unknown:18B1 {18B1} internal, hidden 16: VERTICAL SCROLLING {2100} Roller type: standard Ratchet per turn: 24 Scroll lines: 0 17: LOWRES WHEEL {2130} Wheel Reports: HID Scroll Wheel Diversion (saved): False Scroll Wheel Diversion : False 18: ADJUSTABLE DPI {2201} Sensitivity (DPI) (saved): 2800 Sensitivity (DPI) : 2800 19: DEVICE RESET {1802} internal, hidden 20: unknown:1803 {1803} internal, hidden 21: CONFIG DEVICE PROPS {1806} internal, hidden 22: unknown:1813 {1813} internal, hidden 23: OOBSTATE {1805} internal, hidden 24: unknown:1830 {1830} internal, hidden 25: unknown:1890 {1890} internal, hidden 26: unknown:1891 {1891} internal, hidden 27: unknown:18A1 {18A1} internal, hidden 28: unknown:1DF3 {1DF3} internal, hidden 29: unknown:1E00 {1E00} hidden 30: unknown:1EB0 {1EB0} internal, hidden 31: unknown:1E22 {1E22} internal, hidden 32: unknown:1861 {1861} internal, hidden 33: unknown:18C0 {18C0} internal, hidden Has 7 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, reprogrammable, analytics key events, pos:0, group:1, group mask:g1 reporting: default 1: Right Button , default: Right Click => Right Click mse, reprogrammable, 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, persistently 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, persistently divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 reporting: diverted 4: Forward Button , default: Mouse Forward Button => Mouse Forward Button mse, reprogrammable, divertable, persistently divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 reporting: diverted 5: DPI Switch , default: DPI Switch => DPI Switch mse, reprogrammable, divertable, persistently divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 reporting: 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 Has 6 persistent remappable keys: 0: Left Button => Mouse Button: Mouse Button Left 1: Right Button => Mouse Button: Mouse Button Right 2: Middle Button => Mouse Button: Mouse Button Middle 3: Back Button => Mouse Button: Mouse Button Back 4: Forward Button => Mouse Button: Mouse Button Forward 5: unknown:0000 => None Battery: 50%, discharging, next level 20%. Solaar-1.1.20/docs/devices/MX Vertical Wireless Mouse B020.txt000066400000000000000000000114761522022367600234360ustar00rootroot00000000000000Solaar version 1.1.3 1: MX Vertical Wireless Mouse Device path : /dev/hidraw0 USB id : 046d:B020 Codename : MX Vertical Kind : mouse Protocol : HID++ 4.5 Serial number: Model ID: B020407BC08A Unit ID: 70926049 Bootloader: BOT 78.00.B0009 Firmware: MPM 16.00.B0009 Other: Supports 30 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 78.00.B0009 AAE28E5B17FF Firmware: Firmware MPM 16.00.B0009 B0208E5B17FF Firmware: Other Unit ID: 70926049 Model ID: B020407BC08A Transport IDs: {'btleid': 'B020', 'wpid': '407B', 'usbid': 'C08A'} 3: DEVICE NAME {0005} Name: MX Vertical Advanced Ergonomic Mouse Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: CRYPTO ID {0021} 7: DEVICE FRIENDLY NAME {0007} Friendly Name: MX Vertical 8: BATTERY STATUS {1000} Battery: 90%, discharging, next level 50%. 9: unknown:1002 {1002} 10: REPROG CONTROLS V4 {1B04} 11: PERSISTENT REMAPPABLE ACTION {1C00} 12: CHANGE HOST {1814} 13: HOSTS INFO {1815} Host 0 (unpaired): minerva Host 1 (unpaired): theseus Host 2 (unpaired): athena 14: XY STATS {2250} 15: unknown:18B1 {18B1} internal, hidden 16: VERTICAL SCROLLING {2100} Roller type: standard Ratchet per turn: 24 Scroll lines: 0 17: LOWRES WHEEL {2130} Wheel Reports: HID Scroll Wheel Diversion (saved): False Scroll Wheel Diversion : False 18: ADJUSTABLE DPI {2201} 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:18A1 {18A1} internal, hidden 25: unknown:1E00 {1E00} hidden 26: unknown:1EB0 {1EB0} internal, hidden 27: unknown:1E22 {1E22} internal, hidden 28: unknown:1861 {1861} internal, hidden 29: unknown:18C0 {18C0} internal, hidden Has 7 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, reprogrammable, analytics key events, pos:0, group:1, group mask:g1 reporting: default 1: Right Button , default: Right Click => Right Click mse, reprogrammable, 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, persistently 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, persistently divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 reporting: default 4: Forward Button , default: Mouse Forward Button => Mouse Forward Button mse, reprogrammable, divertable, persistently divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 reporting: default 5: DPI Switch , default: DPI Switch => DPI Switch mse, reprogrammable, divertable, persistently divertable, raw XY, analytics key events, pos:0, group:2, group mask:g1,g2 reporting: default 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 Has 6 persistent remappable keys: 0: Left Button => Mouse Button: Mouse Button Left 1: Right Button => Mouse Button: Mouse Button Right 2: Middle Button => Mouse Button: Mouse Button Middle 3: Back Button => Mouse Button: Mouse Button Back 4: Forward Button => Mouse Button: Mouse Button Forward 5: unknown:0000 => None Battery: 90%, discharging, next level 50%. Solaar-1.1.20/docs/devices/Marathon Mouse M705 101B.txt000066400000000000000000000042611522022367600217560ustar00rootroot00000000000000Solaar version 1.1.5 1: Marathon Mouse M705 (M-R0009) Device path : /dev/hidraw1 WPID : 101B Codename : M705 (M-R0009) Kind : mouse Protocol : HID++ 1.0 Polling rate : 8 ms (125Hz) Serial number: 23691D3D Firmware: 17.01.B0017 Bootloader: 02.06 Other: 00.04 The power switch is located on the base. Notifications: battery status (0x100000). Features: special buttons, scroll accel (0x420000) Battery: 65%, discharging. registers: # writing 0x10 in this register will generate an event # 10 02 0Dxx yyzz00 # where 0D happens to be the battery register number # xx is the battery charge # yy, zz ? << ( 0.001) [10 02 8100 000000] '\x10\x02\x81\x00\x00\x00\x00' >> ( 1.132) [10 02 8100 100000] '\x10\x02\x81\x00\x10\x00\x00' # smooth scroll - possible values # - 00 (off) # - 02 ?, apparently off as well, default value at power-on # - 0x40 (on) << ( 2.005) [10 02 8101 000000] '\x10\x02\x81\x01\x00\x00\x00' >> ( 2.052) [10 02 8101 020000] '\x10\x02\x81\x01\x02\x00\x00' # battery status: percentage full, ?, ? << ( 14.835) [10 02 810D 000000] '\x10\x02\x81\r\x00\x00\x00' >> ( 14.847) [10 02 810D 644734] '\x10\x02\x81\rdG4' # accepts mask 0xF1 # setting 0x10 turns off the movement events (but buttons still work) << ( 221.495) [10 02 81D0 000000] '\x10\x02\x81\xd0\x00\x00\x00' >> ( 221.509) [10 02 81D0 000000] '\x10\x02\x81\xd0\x00\x00\x00' # appears to be read-only? << ( 223.527) [10 02 81D2 000000] '\x10\x02\x81\xd2\x00\x00\x00' >> ( 223.540) [10 02 81D2 000003] '\x10\x02\x81\xd2\x00\x00\x03' # appears to be read-only? << ( 225.557) [10 02 81D4 000000] '\x10\x02\x81\xd4\x00\x00\x00' >> ( 225.571) [10 02 81D4 000004] '\x10\x02\x81\xd4\x00\x00\x04' # read-only, 01-04 firmware info << ( 259.270) [10 02 81F1 000000] '\x10\x02\x81\xf1\x00\x00\x00' >> ( 259.283) [10 02 8F81 F10300] '\x10\x02\x8f\x81\xf1\x03\x00' # writing 01 here will trigger an avalanche of events, most likely # raw input from the mouse; disable by writing 00 << ( 261.300) [10 02 81F3 000000] '\x10\x02\x81\xf3\x00\x00\x00' >> ( 261.315) [10 02 81F3 000000] '\x10\x02\x81\xf3\x00\x00\x00' Solaar-1.1.20/docs/devices/Marathon Mouse M705 406D.txt000066400000000000000000000222401522022367600217650ustar00rootroot00000000000000 3: Marathon Mouse M705 (M-R0073) Device path : /dev/hidraw3 WPID : 406D Codename : M705 (M-R0073) Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: B14D65DA Model ID: 406D00000000 Unit ID: 5E1A15B8 Bootloader: BOT 59.00.B0002 Firmware: RQM 67.01.B0005 The power switch is located on the base. Supports 26 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 59.00.B0002 406D5F5048B901 Firmware: Firmware RQM 67.01.B0005 406D5F5048B901 Unit ID: 5E1A15B8 Model ID: 406D00000000 Transport IDs: {'wpid': '406D'} 3: DEVICE NAME {0005} Name: Marathon Mouse/Performance Plus M705 Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: BATTERY STATUS {1000} Battery: 50%, discharging, next level 20%. 7: REPROG CONTROLS V4 {1B04} Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, 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:Mouse Middle Button, 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:Regular, Forward Button:Regular, Left Tilt:Regular, Right Tilt:Regular} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Left Tilt:Regular, Right Tilt:Regular} 8: POINTER SPEED {2205} Pointer Speed: 1.0 Sensitivity (Pointer Speed) (saved): 256 Sensitivity (Pointer Speed) : 256 9: VERTICAL SCROLLING {2100} Roller type: 3G Ratchet per turn: 24 Scroll lines: 0 10: DFUCONTROL SIGNED {00C2} 11: DEVICE RESET {1802} internal, hidden 12: unknown:1803 {1803} internal, hidden 13: CONFIG DEVICE PROPS {1806} internal, hidden 14: unknown:1810 {1810} internal, hidden 15: unknown:1830 {1830} internal, hidden 16: unknown:1890 {1890} internal, hidden 17: unknown:18A1 {18A1} internal, hidden 18: unknown:1DF3 {1DF3} internal, hidden 19: unknown:1E00 {1E00} hidden 20: unknown:1EB0 {1EB0} internal, hidden 21: unknown:1861 {1861} internal, hidden 22: unknown:18B1 {18B1} internal, hidden 23: unknown:1850 {1850} internal, hidden 24: unknown:1F03 {1F03} internal, hidden 25: unknown:18C0 {18C0} internal, hidden Has 7 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 1: Right Button , default: Right Click => Right Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2 reporting: default 3: Back Button , default: Mouse Back Button => Mouse Back Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2 reporting: default 4: Forward Button , default: Mouse Forward Button => Mouse Forward Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2 reporting: default 5: Left Tilt , default: Mouse Scroll Left Button => Mouse Scroll Left Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2 reporting: default 6: Right Tilt , default: Mouse Scroll Right Button => Mouse Scroll Right Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2 reporting: default Battery: 50%, discharging, next level 20%. 3: Marathon Mouse M705 (M-R0073) Device path : /dev/hidraw3 WPID : 406D Codename : M705 (M-R0073) Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: B14D65DA Model ID: 406D00000000 Unit ID: 5E1A15B8 Bootloader: BOT 59.00.B0002 Firmware: RQM 67.01.B0005 The power switch is located on the base. Supports 27 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 59.00.B0002 406D5F5048B901 Firmware: Firmware RQM 67.01.B0005 406D5F5048B901 Unit ID: 5E1A15B8 Model ID: 406D00000000 Transport IDs: {'wpid': '406D'} 3: DEVICE NAME {0005} Name: Marathon Mouse/Performance Plus M705 Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: BATTERY STATUS {1000} Battery: 50%, discharging, next level 20%. 7: REPROG CONTROLS V4 {1B04} 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, Left Tilt:Mouse Scroll Left Button , Right Tilt:Mouse Scroll Right Button} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Left Tilt:Regular, Right Tilt:Regular} 8: POINTER SPEED {2205} Pointer Speed: 1.0 Sensitivity (Pointer Speed) : 256 9: VERTICAL SCROLLING {2100} Roller type: 3G Ratchet per turn: 24 Scroll lines: 0 10: DFUCONTROL SIGNED {00C2} 11: DEVICE RESET {1802} internal, hidden 12: unknown:1803 {1803} internal, hidden 13: CONFIG DEVICE PROPS {1806} internal, hidden 14: unknown:1810 {1810} internal, hidden 15: unknown:1830 {1830} internal, hidden 16: unknown:1890 {1890} internal, hidden 17: unknown:18A1 {18A1} internal, hidden 18: unknown:1DF3 {1DF3} internal, hidden 19: unknown:1E00 {1E00} hidden 20: unknown:1EB0 {1EB0} internal, hidden 21: unknown:1861 {1861} internal, hidden 22: unknown:18B1 {18B1} internal, hidden 23: unknown:1850 {1850} internal, hidden 24: unknown:1F03 {1F03} internal, hidden 25: unknown:18C0 {18C0} internal, hidden 26: HIRES WHEEL {2121} Multiplier: 8 Has invert: Normal wheel motion Has ratchet switch: Normal wheel mode Low resolution mode HID notification Scroll Wheel Direction : False Scroll Wheel Resolution : False Has 7 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 1: Right Button , default: Right Click => Right Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2 reporting: default 3: Back Button , default: Mouse Back Button => Mouse Back Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2 reporting: default 4: Forward Button , default: Mouse Forward Button => Mouse Forward Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2 reporting: default 5: Left Tilt , default: Mouse Scroll Left Button => Mouse Scroll Left Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2 reporting: default 6: Right Tilt , default: Mouse Scroll Right Button => Mouse Scroll Right Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2 reporting: default Battery: 50%, discharging, next level 20%. Solaar-1.1.20/docs/devices/Multi Device Silent Mouse M585-M590 406B.txt000066400000000000000000000154271522022367600243520ustar00rootroot00000000000000Solaar version 1.1.7 2: Multi Device Silent Mouse M585/M590 Device path : /dev/hidraw2 WPID : 406B Codename : M585/M590 Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: 37216581 Model ID: B01B406B0000 Unit ID: F2D653C0 Bootloader: BOT 48.00.B0001 Firmware: MPM 05.02.B0007 Other: The power switch is located on the base. Supports 34 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V2 Firmware: Bootloader BOT 48.00.B0001 00005BEB5EE001 Firmware: Firmware MPM 05.02.B0007 406B5BEB5EE001 Firmware: Other Unit ID: F2D653C0 Model ID: B01B406B0000 Transport IDs: {'btleid': 'B01B', 'wpid': '406B'} 3: DEVICE NAME {0005} V0 Name: M585/M590 Multi-Device Mouse Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} V0 5: RESET {0020} V0 6: CRYPTO ID {0021} V0 7: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: M585/M590 8: BATTERY STATUS {1000} V0 Battery: 50%, discharging, next level 20%. 9: CHANGE HOST {1814} V1 Cambiar Host : 1:romanescu 10: HOSTS INFO {1815} V1 Host 0 (paired): romanescu Host 1 (paired): espinaca 11: REPROG CONTROLS V4 {1B04} V3 Acciones de Tecla/Botón (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Left Tilt:Mouse Scroll Left Button , Right Tilt:Mouse Scroll Right Button} Acciones de Tecla/Botón : {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Left Tilt:Mouse Scroll Left Button , Right Tilt:Mouse Scroll Right Button} Desvio de Tecla/Botón (saved): {Middle Button:Normal, Back Button:Normal, Forward Button:Normal, Left Tilt:Normal, Right Tilt:Normal} Desvio de Tecla/Botón : {Middle Button:Normal, Back Button:Normal, Forward Button:Normal, Left Tilt:Normal, Right Tilt:Normal} 12: PERSISTENT REMAPPABLE ACTION {1C00} V0 Persistent Key/Button Mapping : {Left Button:Mouse Button Left, Right Button:Mouse Button Right, Middle Button:Mouse Button Middle, Back Button:Mouse Button Back, Forward Button:Mouse Button Forward, Left Tilt:Horizontal Scroll Left, Right Tilt:Horizontal Scroll Right} 13: POINTER SPEED {2205} V0 Pointer Speed: 1.0 Sensibilidad (Velocidad del puntero) (saved): 256 Sensibilidad (Velocidad del puntero) : 256 14: VERTICAL SCROLLING {2100} V0 Roller type: standard Ratchet per turn: 18 Scroll lines: 0 15: DFUCONTROL SIGNED {00C2} V0 16: DEVICE RESET {1802} V0 internal, hidden 17: unknown:1803 {1803} V0 internal, hidden 18: CONFIG DEVICE PROPS {1806} V0 internal, hidden 19: OOBSTATE {1805} V0 internal, hidden 20: unknown:1813 {1813} V0 internal, hidden 21: unknown:1830 {1830} V0 internal, hidden 22: unknown:1861 {1861} V0 internal, hidden 23: unknown:1890 {1890} V0 internal, hidden 24: unknown:1891 {1891} V0 internal, hidden 25: unknown:18A1 {18A1} V0 internal, hidden 26: unknown:1DF3 {1DF3} V0 internal, hidden 27: unknown:1E00 {1E00} V0 hidden 28: unknown:1EB0 {1EB0} V0 internal, hidden 29: unknown:18B1 {18B1} V0 internal, hidden 30: unknown:1850 {1850} V0 internal, hidden 31: unknown:1F03 {1F03} V0 internal, hidden 32: unknown:18C0 {18C0} V0 internal, hidden 33: LOWRES WHEEL {2130} V0 Wheel Reports: HID Desvio Rueda Desplazamiento (saved): False Desvio Rueda Desplazamiento : False Has 8 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 1: Right Button , default: Right Click => Right Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 3: Back Button , default: Mouse Back Button => Mouse Back Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 4: Forward Button , default: Mouse Forward Button => Mouse Forward Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 5: Left Tilt , default: Mouse Scroll Left Button => Mouse Scroll Left Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 6: Right Tilt , default: Mouse Scroll Right Button => Mouse Scroll Right Button mse, reprogrammable, divertable, persistently divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default 7: 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 Has 7 persistent remappable keys: 0: Left Button => Mouse Button: Mouse Button Left 1: Right Button => Mouse Button: Mouse Button Right 2: Middle Button => Mouse Button: Mouse Button Middle 3: Back Button => Mouse Button: Mouse Button Back 4: Forward Button => Mouse Button: Mouse Button Forward 5: Left Tilt => Horizontal Scroll: Horizontal Scroll Left 6: Right Tilt => Horizontal Scroll: Horizontal Scroll Right Battery: 50%, discharging, next level 20%. Solaar-1.1.20/docs/devices/Nano Receiver C52F.txt000066400000000000000000000003451522022367600211760ustar00rootroot00000000000000Nano Receiver Device path : /dev/hidraw3 USB id : 046d:c52f Serial : 6D0342C5 Firmware : 30.00.B0009 Has 1 paired device(s) out of a maximum of 1. Notifications: (none) Seen with Marathon Mouse M705 Solaar-1.1.20/docs/devices/Nano Receiver C534.txt000066400000000000000000000005571522022367600211620ustar00rootroot00000000000000Solaar version 1.1.4rc2 Nano Receiver Device path : /dev/hidraw1 USB id : 046d:C534 Serial : None Firmware : 29.01.B0016 Has 2 paired device(s) out of a maximum of 2. Has 18 successful pairing(s) remaining. Notifications: wireless, software present (0x000900) Seen paired with Wireless Keyboard 4075 and Wireless Mouse M185 new 4054 Solaar-1.1.20/docs/devices/Nano Receiver C535.txt000066400000000000000000000004451522022367600211570ustar00rootroot00000000000000Solaar version 1.1.5 Nano Receiver Device path : /dev/hidraw5 USB id : 046d:C535 Serial : 1F3F94FC Firmware : 31.00.B0001 Has 1 paired device(s) out of a maximum of 1. Has 39 successful pairing(s) remaining. Notifications: wireless, software present (0x000900) Solaar-1.1.20/docs/devices/Number Pad N545 2006.txt000066400000000000000000000007351522022367600210420ustar00rootroot00000000000000solaar version 1.1.8 3: Number Pad N545 Device path : /dev/hidraw3 WPID : 2006 Codename : N545 Kind : numpad Protocol : HID++ 1.0 Polling rate : 20 ms (50Hz) Serial number: 900A4D98 Firmware: 13.00.B0037 Bootloader: 02.03 Other: 00.01 The power switch is located on the base. Notifications: battery status (0x100000). Features: (none) Battery: full, discharging. Solaar-1.1.20/docs/devices/PRO X 2 40A9.text000066400000000000000000000061441522022367600176560ustar00rootroot00000000000000solaar 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. Solaar-1.1.20/docs/devices/PRO X 2 LIGHTSPEED 0AF7.text000066400000000000000000000032221522022367600213010ustar00rootroot00000000000000solaar 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. Solaar-1.1.20/docs/devices/PRO X 2 Superstrike 40BD.txt000066400000000000000000000104411522022367600220210ustar00rootroot00000000000000Solaar 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++. Solaar-1.1.20/docs/devices/PRO X Wireless 4093.txt000066400000000000000000000053741522022367600211330ustar00rootroot00000000000000Solaar version 1.1.14 2: PRO X Wireless Device path : None WPID : 4093 Codename : PRO X Kind : mouse Protocol : HID++ 4.2 Report Rate : 1ms Serial number: 8B24D1D1 Model ID: 4093C0940000 Unit ID: 8B24D1D1 1: BL1 25.01.B0018 3: 0: MPM 25.01.B0018 Supports 28 HID++ 2.0 features: 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} 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. Solaar-1.1.20/docs/devices/Rechargeable Trackpad T651 B00C.txt000066400000000000000000000021721522022367600232410ustar00rootroot00000000000000Solaar version 1.1.7 1: Rechargeable Trackpad T651 Device path : /dev/hidraw9 USB id : 046d:B00C Codename : Rechargeable Kind : touchpad Protocol : HID++ 2.0 Serial number: Model ID: 000000000000 Unit ID: 00000000 Firmware: RBM 05.00.B0026 Other: Supports 9 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: FEATURE INFO {0002} V0 3: DEVICE FW VERSION {0003} V0 Firmware: Firmware RBM 05.00.B0026 B00C Firmware: Other Unit ID: 00000000 Model ID: 000000000000 Transport IDs: {} 4: DEVICE NAME {0005} V0 Name: Rechargeable Trackpad T651 Kind: touchpad 5: unknown:1E00 {1E00} V0 hidden 6: GENERIC TEST {1800} V0 internal, hidden 7: BATTERY STATUS {1000} V0 Battery: 90%, discharging, next level 70%. 8: TOUCHPAD RAW XY {6100} V0 Battery: 90%, discharging, next level 70%. Solaar-1.1.20/docs/devices/Signature M550.text000066400000000000000000000077701522022367600206640ustar00rootroot00000000000000solaar 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. Solaar-1.1.20/docs/devices/Signature M650 L Mouse B02A.txt000066400000000000000000000113321522022367600223370ustar00rootroot00000000000000Solaar version 1.1.3 2: Signature M650 L Mouse Device path : None WPID : B02A Codename : Logi M650 L Kind : mouse Protocol : HID++ 4.5 Serial number: 46756ABD Model ID: B02A00000000 Unit ID: 46756ABD Bootloader: BL1 38.01.B0011 Firmware: RBM 16.02.B0013 Other: The power switch is located on the (unknown). Supports 29 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BL1 38.01.B0011 B02ADFC9033D Firmware: Firmware RBM 16.02.B0013 B02AB0706FCD Firmware: Other Unit ID: 46756ABD Model ID: B02A00000000 Transport IDs: {'btleid': 'B02A'} 3: DEVICE NAME {0005} Name: Signature M650 L Mouse Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: DEVICE FRIENDLY NAME {0007} Friendly Name: Logi M650 L 7: UNIFIED BATTERY {1004} 8: REPROG CONTROLS V4 {1B04} DPI Sliding Adjustment (saved): Off DPI Sliding Adjustment : Off Mouse Gestures (saved): Off Mouse Gestures : Off Key/Button Actions (saved): {Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button} Key/Button Actions : {Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button} Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular} 9: HOSTS INFO {1815} Host 0 (paired): legion15 10: XY STATS 11: LOWRES WHEEL {2130} Wheel Reports: HID Scroll Wheel Diversion (saved): False Scroll Wheel Diversion : False 12: ADJUSTABLE DPI {2201} Sensitivity (DPI) (saved): 1200 Sensitivity (DPI) : 1200 13: DFUCONTROL {00C3} 14: DEVICE RESET {1802} internal, hidden, unknown:000010 15: unknown:1803 {1803} internal, hidden, unknown:000010 16: CONFIG DEVICE PROPS {1806} internal, hidden, unknown:000010 17: unknown:1816 {1816} internal, hidden, unknown:000010 18: OOBSTATE {1805} internal, hidden 19: unknown:1830 {1830} internal, hidden, unknown:000010 20: unknown:1891 {1891} internal, hidden, unknown:000008 21: unknown:18A1 {18A1} internal, hidden, unknown:000010 22: unknown:1E00 {1E00} hidden 23: unknown:1E02 {1E02} internal, hidden 24: unknown:1E22 {1E22} internal, hidden, unknown:000010 25: unknown:1602 {1602} 26: unknown:1EB0 {1EB0} internal, hidden, unknown:000010 27: unknown:1861 {1861} internal, hidden, unknown:000010 28: unknown:18B1 {18B1} internal, hidden, unknown:000010 Has 6 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, analytics key events, pos:0, group:1, group mask:empty reporting: default 1: Right Button , default: Right Click => Right Click mse, analytics key events, pos:0, group:1, group mask:empty reporting: default 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, raw XY, analytics key events, pos:0, group:3, group mask:g1,g2,g3 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: 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: 95%, discharging. Solaar-1.1.20/docs/devices/Unifying Receiver C52B.txt000066400000000000000000000005261522022367600220700ustar00rootroot00000000000000Solaar version 1.1.4 Unifying Receiver Device path : /dev/hidraw0 USB id : 046d:C52B Serial : E0662192 Firmware : 24.11.B0036 Bootloader : 02.09 Other : AA.AC Has 2 paired device(s) out of a maximum of 6. Notifications: wireless, software present (0x000900) Device activity counters: 1=225, 2=132 Solaar-1.1.20/docs/devices/Wireless All-in-One Keyboard TK820 4102.txt000066400000000000000000000075321522022367600243750ustar00rootroot00000000000000Solaar version 1.1.1 1: Wireless All-in-One Keyboard TK820 Device path : /dev/hidraw5 WPID : 4102 Codename : TK820 Kind : keyboard Protocol : HID++ 2.0 Polling rate : 8 ms (125Hz) Serial number: 7D7699C9 Model ID: 000000000000 Unit ID: 00000000 Firmware: RQK 52.00.B0025 Bootloader: BL 02.00.B0001 Hardware: 72 Other: The power switch is located on the top right corner. Supports 28 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: FEATURE INFO {0002} 3: DEVICE FW VERSION {0003} Firmware: Firmware RQK 52.00.B0025 4102 Firmware: Bootloader BL 02.00.B0001 4102 Firmware: Hardware 72 Firmware: Other Unit ID: 00000000 Model ID: 000000000000 Transport IDs: {} 4: DEVICE NAME {0005} Name: Wireless All-in-One Keyboard TK820 Kind: keyboard 5: BATTERY STATUS {1000} Battery: 20%, discharging, next level 5%. 6: WIRELESS DEVICE STATUS {1D4B} 7: unknown:1DF3 {1DF3} internal, hidden 8: REPROG CONTROLS {1B00} 9: unknown:1F03 {1F03} internal, hidden 10: FN INVERSION {40A0} Swap Fx function (saved): True Swap Fx function : True 11: NEW FN INVERSION {40A2} Fn-swap: enabled Fn-swap default: enabled 12: MOUSE POINTER {2200} DPI: 1000 Acceleration: low Override OS ballistics No vertical tuning, standard mice 13: DFUCONTROL LEGACY {00C0} 14: unknown:1E80 {1E80} internal, hidden 15: TOUCHPAD RAW XY {6100} 16: unknown:1860 {1860} internal, hidden 17: unknown:1E00 {1E00} hidden 18: REPROG CONTROLS V3 {1B03} 19: unknown:1890 {1890} internal, hidden 20: unknown:18E6 {18E6} internal, hidden 21: unknown:18A0 {18A0} internal, hidden 22: unknown:1830 {1830} internal, hidden 23: unknown:1F04 {1F04} internal, hidden 24: unknown:1EA1 {1EA1} internal, hidden 25: unknown:18B0 {18B0} internal, hidden 26: ENCRYPTION {4100} 27: KEYBOARD LAYOUT {4520} Has 15 reprogrammable keys: 0: Fn Left Click => ShowUI divertable 1: Metro Appswitch => MetroAppSwitch is FN, reprogrammable 2: Metro Search => MetroSearch is FN, reprogrammable 3: unknown:00B2 => MetroShare2 is FN, reprogrammable 4: unknown:00B3 => MetroDevices2 is FN, reprogrammable 5: unknown:00B4 => MetroSettings2 is FN, reprogrammable 6: Previous => Previous is FN 7: Play/Pause => Play/Pause is FN 8: Next => Next is FN 9: Mute => Mute is FN 10: Volume Down => Volume Down is FN 11: Volume Up => Volume Up is FN 12: Sleep => Sleep is FN, reprogrammable 13: Left Button => Left Click mse 14: Right Button => Right Click mse Battery: 20%, discharging, next level 5 Solaar-1.1.20/docs/devices/Wireless Illuminated Keyboard K800 2010.txt000066400000000000000000000056311522022367600246550ustar00rootroot00000000000000 2: Wireless Illuminated Keyboard K800 Codename : K800 Kind : keyboard Wireless PID : 2010 Protocol : HID++ 1.0 Polling rate : 20 ms (50Hz) Serial number: FFC65665 Firmware: 22.01.B0019 Bootloader: 02.01 Other: 00.07 The power switch is located on the top right corner. Notifications: battery status (0x100000). Battery: full, discharging. # 0x00 - Enabled Notifications. rw (see HID++ 1.0 spec) << ( 0.055) [10 02 8100 000000] '\x10\x02\x81\x00\x00\x00\x00' >> ( 0.084) [10 02 8100 000000] '\x10\x02\x81\x00\x00\x00\x00' # 0x01 - Keyboard hand detection. rw, last param is 00 when hand detection is # enabled, 30 when disabled. (when enabled, keyboard will light up if not # already when hovering over the front) << ( 1.085) [10 02 8101 000000] '\x10\x02\x81\x01\x00\x00\x00' >> ( 1.114) [10 02 8101 000000] '\x10\x02\x81\x01\x00\x00\x00' # 0x07 - Battery status (3 = one bar; 1 = red/critical; 5=two bars; 7=three # bars/full. Second returned param is 25 when keyboard is charging ) << ( 7.327) [10 02 8107 000000] '\x10\x02\x81\x07\x00\x00\x00' >> ( 7.368) [10 02 8107 030000] '\x10\x02\x81\x07\x03\x00\x00' # 0x09 - F key function. rw (read: status, set/get: 00 01 00 means swap # functions, 00 00 00 means do not swap functions) << ( 9.411) [10 02 8109 000000] '\x10\x02\x81\t\x00\x00\x00' >> ( 9.440) [10 02 8109 000000] '\x10\x02\x81\t\x00\x00\x00' # 0x17 - Illumination info r/w. Last param: 02 to disable backlight, 01 to # enable backlight << ( 24.965) [10 02 8117 000000] '\x10\x02\x81\x17\x00\x00\x00' >> ( 24.988) [10 02 8117 3C0001] '\x10\x02\x81\x17<\x00\x01' # 0x51 - ? << ( 99.294) [10 02 8151 000000] '\x10\x02\x81Q\x00\x00\x00' >> ( 99.543) [10 02 8151 000000] '\x10\x02\x81Q\x00\x00\x00' # 0x54 - ? << ( 103.046) [10 02 8154 000000] '\x10\x02\x81T\x00\x00\x00' >> ( 103.295) [10 02 8154 FF0000] '\x10\x02\x81T\xff\x00\x00' # 0xD0 - ? << ( 253.860) [10 02 81D0 000000] '\x10\x02\x81\xd0\x00\x00\x00' >> ( 253.883) [10 02 81D0 000000] '\x10\x02\x81\xd0\x00\x00\x00' # 0xF1 - Version info (params 0n 00 00 where n is 1..4) << ( 289.991) [10 02 81F1 000000] '\x10\x02\x81\xf1\x00\x00\x00' >> ( 290.032) [10 02 8F81 F10300] '\x10\x02\x8f\x81\xf1\x03\x00' # 0xF3 - ? << ( 292.075) [10 02 81F3 000000] '\x10\x02\x81\xf3\x00\x00\x00' >> ( 292.116) [10 02 81F3 000000] '\x10\x02\x81\xf3\x00\x00\x00' # 0x0F - This changes, the last commented line was observed in an earlier run << ( 17.728) [10 02 830F 000000] '\x10\x02\x83\x0f\x00\x00\x00' >> ( 17.976) [11 02 830F FFFB00000240025C000000000FF90080] '\x11\x02\x83\x0f\xff\xfb\x00\x00\x02@\x02\\\x00\x00\x00\x00\x0f\xf9\x00\x80' #>> ( 17.999) [11 02 830F FFFC007F0243025D000000000FF60080] '\x11\x02\x83\x0f\xff\xfc\x00\x7f\x02C\x02]\x00\x00\x00\x00\x0f\xf6\x00\x80' # See also https://git.lekensteyn.nl/ltunify/tree/registers.txt for a verbose # meaning of registers and params. Solaar-1.1.20/docs/devices/Wireless Illuminated Keyboard K800 new 406E.txt000066400000000000000000000100471522022367600254600ustar00rootroot00000000000000 2: Wireless Illuminated Keyboard K800 new Codename : new Kind : keyboard Wireless PID : 406E Protocol : HID++ 4.5 Polling rate : 20 ms (50Hz) Serial number: 636E1413 Bootloader: BOT 60.00.B0002 Firmware: RQK 68.01.B0009 The power switch is located on the top right corner. Supports 25 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: BATTERY STATUS {1000} 7: unknown:1983 {1983} 8: REPROG CONTROLS V4 {1B04} 9: FN INVERSION {40A0} 10: ENCRYPTION {4100} 11: KEYBOARD DISABLE {4521} 12: unknown:00C2 {00C2} 13: unknown:1803 {1803} internal, hidden 14: unknown:1806 {1806} internal, hidden 15: unknown:1811 {1811} internal, hidden 17: unknown:1890 {1890} internal, hidden 18: unknown:18A1 {18A1} internal, hidden 19: unknown:1DF3 {1DF3} internal, hidden 20: unknown:1E00 {1E00} hidden 21: unknown:1EB0 {1EB0} internal, hidden 22: unknown:1861 {1861} internal, hidden 23: unknown:1A20 {1A20} internal, hidden 24: unknown:18B0 {18B0} internal, hidden Has 15 reprogrammable keys: 0: MY HOME , default: HomePage => MY HOME is FN, FN sensitive, reprogrammable, divertable, pos:1, group:0, gmask:0 1: Mail , default: Email => Mail is FN, FN sensitive, reprogrammable, divertable, pos:2, group:0, gmask:0 2: SEARCH , default: SearchForFiles => SEARCH is FN, FN sensitive, reprogrammable, divertable, pos:3, group:0, gmask:0 3: Application Switcher , default: Application Switcher => Application Switcher is FN, FN sensitive, reprogrammable, divertable, pos:4, group:0, gmask:0 4: unknown:00E2 , default: unknown:00C1 => unknown:00E2 is FN, FN sensitive, divertable, pos:5, group:0, gmask:0 5: unknown:00E3 , default: unknown:00C2 => unknown:00E3 is FN, FN sensitive, divertable, pos:6, group:0, gmask:0 6: SLEEP , default: Sleep => SLEEP is FN, FN sensitive, reprogrammable, divertable, pos:8, group:0, gmask:0 7: MEDIA PLAYER , default: Music => MEDIA PLAYER is FN, FN sensitive, reprogrammable, divertable, pos:9, group:0, gmask:0 8: Previous , default: Previous => Previous is FN, FN sensitive, divertable, pos:10, group:0, gmask:0 9: Play/Pause , default: Play/Pause => Play/Pause is FN, FN sensitive, divertable, pos:11, group:0, gmask:0 10: Next , default: Next => Next is FN, FN sensitive, divertable, pos:12, group:0, gmask:0 11: Volume Up , default: Volume Up => Volume Up nonstandard, divertable, pos:0, group:0, gmask:0 12: Volume Down , default: Volume Down => Volume Down nonstandard, divertable, pos:0, group:0, gmask:0 13: Mute , default: Mute => Mute nonstandard, divertable, pos:0, group:0, gmask:0 14: Calculator , default: Calculator => Calculator nonstandard, reprogrammable, divertable, pos:0, group:0, gmask:0 Battery: 50%, discharging. Solaar-1.1.20/docs/devices/Wireless Keyboard 4075.txt000066400000000000000000000060221522022367600220320ustar00rootroot00000000000000Solaar version 1.1.4rc2 1: Wireless Keyboard Device path : /dev/hidraw2 WPID : 4075 Codename : Kind : keyboard Protocol : HID++ 4.5 Polling rate : 20 ms (50Hz) Serial number: 00000000 Model ID: 000000000000 Unit ID: 00000000 Firmware: RQK 71.00.B0002 Supports 19 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Firmware RQK 71.00.B0002 4075 Unit ID: 00000000 Model ID: 000000000000 Transport IDs: {} 3: DEVICE NAME {0005} Name: Wireless Keyboard Kind: keyboard 4: RESET {0020} 5: BATTERY STATUS {1000} Battery: 30%, discharging, next level 5%. 6: REPROG CONTROLS V4 {1B04} Key/Button Diversion (saved): {Calculator:Regular, Mail:Regular, My Home:Regular, Search:Regular} Key/Button Diversion : {Calculator:Regular, Mail:Regular, My Home:Regular, Search:Regular} 7: WIRELESS DEVICE STATUS {1D4B} 8: NEW FN INVERSION {40A2} Fn-swap: disabled Fn-swap default: disabled Swap Fx function (saved): False Swap Fx function : False 9: ENCRYPTION {4100} 10: LOCK KEY STATE {4220} 11: KEYBOARD DISABLE KEYS {4521} 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} 12: unknown:1810 {1810} internal, hidden 13: unknown:1830 {1830} internal, hidden 14: unknown:1890 {1890} internal, hidden 15: unknown:18A0 {18A0} internal, hidden 16: unknown:18B0 {18B0} internal, hidden 17: unknown:1DF3 {1DF3} internal, hidden 18: unknown:1E00 {1E00} hidden Has 4 reprogrammable keys: 0: My Home , default: HomePage => HomePage is FN, FN sensitive, reprogrammable, divertable, pos:1, group:0, group mask:empty reporting: default 1: Mail , default: Email => Email is FN, FN sensitive, reprogrammable, divertable, pos:2, group:0, group mask:empty reporting: default 2: Search , default: Search Files => Search Files is FN, FN sensitive, reprogrammable, divertable, pos:3, group:0, group mask:empty reporting: default 3: Calculator , default: Calculator => Calculator is FN, FN sensitive, reprogrammable, divertable, pos:4, group:0, group mask:empty reporting: default Battery: 30%, discharging, next level 5%. Solaar-1.1.20/docs/devices/Wireless Keyboard Dell KB714 4015.txt000066400000000000000000000033561522022367600234050ustar00rootroot00000000000000Solaar version 1.1.6rc4 4: Wireless Keyboard Dell KB714 Device path : /dev/hidraw6 WPID : 4015 Codename : Dell KB714 Kind : keyboard Protocol : HID++ 3.0 Polling rate : 20 ms (50Hz) Serial number: C0A37A58 Model ID: 000000000000 Unit ID: 00000000 Firmware: RQK 51.00.B0005 Supports 19 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V0 Firmware: Firmware RQK 51.00.B0005 4015 Unit ID: 00000000 Model ID: 000000000000 Transport IDs: {} 3: DEVICE NAME {0005} V0 Name: Wireless Keyboard Dell KB714 Kind: keyboard 4: BATTERY STATUS {1000} V0 Battery: 90%, discharging, next level 5%. 5: WIRELESS DEVICE STATUS {1D4B} V0 6: ENCRYPTION {4100} V0 7: KEYBOARD LAYOUT {4520} V0 8: unknown:1810 {1810} V0 internal, hidden 9: unknown:1830 {1830} V0 internal, hidden 10: unknown:1860 {1860} V0 internal, hidden 11: unknown:1890 {1890} V0 internal, hidden 12: unknown:18A0 {18A0} V0 internal, hidden 13: unknown:18B0 {18B0} V0 internal, hidden 14: REMAINING PAIRING {1DF0} V0 hidden Remaining Pairings: 54 15: unknown:1DF3 {1DF3} V0 internal, hidden 16: unknown:1E00 {1E00} V0 hidden 17: unknown:1E90 {1E90} V0 internal, hidden 18: DEVICE GROUPS {0006} V0 Battery: 90%, discharging, next level 5%. Solaar-1.1.20/docs/devices/Wireless Keyboard K220 4005.txt000066400000000000000000000037601522022367600224300ustar00rootroot00000000000000 2: Wireless Keyboard K220 Codename : K220 Kind : keyboard Wireless PID : 4005 Protocol : HID++ 2.0 Polling rate : 20 ms (50Hz) Serial number: 5BB1D72E Firmware: RQK 37.00.B0011 Supports 14 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: FEATURE INFO {0002} 3: DEVICE FW VERSION {0003} 4: DEVICE NAME {0005} 5: BATTERY STATUS {1000} 6: unknown:1820 {1820} hidden 7: REPROG CONTROLS {1B00} 8: REPROG CONTROLS V2 {1B01} 9: WIRELESS DEVICE STATUS {1D4B} 10: unknown:1DF0 {1DF0} hidden 11: unknown:1DF3 {1DF3} hidden 12: ENCRYPTION {4100} 13: KEYBOARD LAYOUT {4520} Has 12 reprogrammable keys: 0: FN F1 => Do Nothing One is FN, reprogrammable 1: FN F2 => Do Nothing One is FN, reprogrammable 2: FN F3 => Do Nothing One is FN, reprogrammable 3: FN F4 => Do Nothing One is FN, reprogrammable 4: FN F5 => Do Nothing One is FN, reprogrammable 5: FN F6 => Do Nothing One is FN, reprogrammable 6: FN F7 => Do Nothing One is FN, reprogrammable 7: FN F8 => Do Nothing One is FN, reprogrammable 8: FN F9 => Do Nothing One is FN, reprogrammable 9: Mute => Mute is FN 10: Volume Down => Volume Down is FN 11: Volume Up => Volume Up is FN Battery: 90%, discharging. Solaar-1.1.20/docs/devices/Wireless Keyboard K230 400D.txt000066400000000000000000000046661522022367600224560ustar00rootroot00000000000000 1: Wireless Keyboard K230 Device path : /dev/hidraw5 WPID : 400D Codename : K230 Kind : keyboard Protocol : HID++ 2.0 Polling rate : 20 ms (50Hz) Serial number: 9BB42D7D Model ID: 000000000000 Unit ID: 00000000 Firmware: RQK 40.00.B0011 The power switch is located on the bottom left corner. Supports 14 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: FEATURE INFO {0002} 3: DEVICE FW VERSION {0003} Firmware: Firmware RQK 40.00.B0011 400D Unit ID: 00000000 Model ID: 000000000000 Transport IDs: {} 4: DEVICE NAME {0005} Name: Wireless Keyboard K230 Kind: keyboard 5: BATTERY STATUS {1000} Battery: 70%, discharging, next level 20%. 6: unknown:1820 {1820} hidden 7: REPROG CONTROLS {1B00} 8: REPROG CONTROLS V2 {1B01} 9: WIRELESS DEVICE STATUS {1D4B} 10: REMAINING PAIRING {1DF0} hidden Remaining Pairings: 54 11: unknown:1DF3 {1DF3} hidden 12: ENCRYPTION {4100} 13: KEYBOARD LAYOUT {4520} Has 12 reprogrammable keys: 0: Fn F1 => Do Nothing One is FN, reprogrammable 1: Fn F2 => Do Nothing One is FN, reprogrammable 2: Fn F3 => Do Nothing One is FN, reprogrammable 3: Fn F4 => Do Nothing One is FN, reprogrammable 4: Fn F5 => Do Nothing One is FN, reprogrammable 5: Fn F6 => Do Nothing One is FN, reprogrammable 6: Fn F7 => Do Nothing One is FN, reprogrammable 7: Fn F8 => Do Nothing One is FN, reprogrammable 8: Fn F9 => Do Nothing One is FN, reprogrammable 9: Mute => Mute is FN 10: Volume Down => Volume Down is FN 11: Volume Up => Volume Up is FN Battery: 70%, discharging, next level 20%. Solaar-1.1.20/docs/devices/Wireless Keyboard K270(unifying) 4003.txt000066400000000000000000000032111522022367600243140ustar00rootroot00000000000000 2: Wireless Keyboard K270(unifying) Codename : K270(unifying) Kind : keyboard Wireless PID : 4003 Protocol : HID++ 2.0 Polling rate : 20 ms (50Hz) Serial number: FE8C8542 Firmware: RQK 35.00.B0017 The power switch is located on the top case. Supports 12 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: BATTERY STATUS {1000} 5: unknown:1820 {1820} hidden 6: REPROG CONTROLS {1B00} 7: WIRELESS DEVICE STATUS {1D4B} 8: unknown:1DF0 {1DF0} hidden 9: unknown:1DF3 {1DF3} hidden 10: ENCRYPTION {4100} 11: KEYBOARD LAYOUT {4520} Has 8 reprogrammable keys: 0: Play/Pause => Play/Pause nonstandard 1: Mute => Mute nonstandard 2: Volume Down => Volume Down nonstandard 3: Volume Up => Volume Up nonstandard 4: MY HOME => HomePage nonstandard, reprogrammable 5: Mail => Email nonstandard, reprogrammable 6: SLEEP => Sleep nonstandard, reprogrammable 7: Calculator => Calculator nonstandard, reprogrammable Battery: 90%, discharging. Solaar-1.1.20/docs/devices/Wireless Keyboard K360 4004.txt000066400000000000000000000025551522022367600224350ustar00rootroot00000000000000Solaar version 1.1.4 2: Wireless Keyboard K360 Device path : /dev/hidraw2 WPID : 4004 Codename : K360 Kind : keyboard Protocol : HID++ 2.0 Polling rate : 20 ms (50Hz) Serial number: 334AC01A Model ID: 000000000000 Unit ID: 00000000 Firmware: RQK 36.00.B0007 The power switch is located on the top case. Supports 12 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Firmware RQK 36.00.B0007 4004 Unit ID: 00000000 Model ID: 000000000000 Transport IDs: {} 3: DEVICE NAME {0005} Name: Wireless Keyboard K360 Kind: keyboard 4: BATTERY STATUS {1000} Battery: 70%, discharging, next level 20%. 5: unknown:1820 {1820} hidden 6: REPROG CONTROLS {1B00} 7: WIRELESS DEVICE STATUS {1D4B} 8: REMAINING PAIRING {1DF0} hidden Remaining Pairings: 55 9: unknown:1DF3 {1DF3} hidden 10: FN INVERSION {40A0} Swap Fx function (saved): False Swap Fx function : False 11: ENCRYPTION {4100} Battery: 70%, discharging, next level 20%. Solaar-1.1.20/docs/devices/Wireless Keyboard K470 4075.txt000066400000000000000000000062211522022367600224410ustar00rootroot00000000000000Solaar version 1.1.7 1: Wireless Keyboard Device path : /dev/hidraw6 WPID : 4075 Codename : Kind : keyboard Protocol : HID++ 4.5 Polling rate : 20 ms (50Hz) Serial number: 00000000 Model ID: 000000000000 Unit ID: 00000000 Firmware: RQK 71.00.B0002 Supports 20 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V0 Firmware: Firmware RQK 71.00.B0002 4075 Unit ID: 00000000 Model ID: 000000000000 Transport IDs: {} 3: DEVICE NAME {0005} V0 Name: Wireless Keyboard Kind: keyboard 4: RESET {0020} V0 5: BATTERY STATUS {1000} V0 Battery: 30%, discharging, next level 5%. 6: REPROG CONTROLS V4 {1B04} V2 Key/Button Diversion (saved): {Calculator:Regular, Mail:Regular, My Home:Regular, Search:Regular} Key/Button Diversion : {Calculator:Regular, Mail:Regular, My Home:Regular, Search:Regular} 7: WIRELESS DEVICE STATUS {1D4B} V0 8: NEW FN INVERSION {40A2} V0 Fn-swap: disabled Fn-swap default: disabled Swap Fx function (saved): False Swap Fx function : False 9: ENCRYPTION {4100} V0 10: LOCK KEY STATE {4220} V0 11: 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} 12: unknown:1810 {1810} V0 internal, hidden 13: unknown:1830 {1830} V0 internal, hidden 14: unknown:1890 {1890} V0 internal, hidden 15: unknown:18A0 {18A0} V0 internal, hidden 16: unknown:18B0 {18B0} V0 internal, hidden 17: unknown:1DF3 {1DF3} V0 internal, hidden 18: unknown:1E00 {1E00} V0 hidden 19: unknown:1868 {1868} V0 internal, hidden Has 4 reprogrammable keys: 0: My Home , default: HomePage => HomePage is FN, FN sensitive, reprogrammable, divertable, pos:1, group:0, group mask:empty reporting: default 1: Mail , default: Email => Email is FN, FN sensitive, reprogrammable, divertable, pos:2, group:0, group mask:empty reporting: default 2: Search , default: Search Files => Search Files is FN, FN sensitive, reprogrammable, divertable, pos:3, group:0, group mask:empty reporting: default 3: Calculator , default: Calculator => Calculator is FN, FN sensitive, reprogrammable, divertable, pos:4, group:0, group mask:empty reporting: default Battery: 30%, discharging, next level 5%. Solaar-1.1.20/docs/devices/Wireless Keyboard K520 2011.txt000066400000000000000000000006721522022367600224250ustar00rootroot00000000000000 2: Wireless Keyboard K520 Device path : /dev/hidraw5 WPID : 2011 Codename : K520 Kind : keyboard Protocol : HID++ 1.0 Polling rate : 20 ms (50Hz) Serial number: C17F6771 Firmware: 26.00.B0012 Other: 00.07 The power switch is located on the top case. Notifications: battery status (0x100000). Battery: full, discharging. Has Swap Fn register Solaar-1.1.20/docs/devices/Wireless Keyboard MK270 4023.txt000066400000000000000000000033001522022367600225400ustar00rootroot00000000000000Solaar version 1.1.7 1: Wireless Keyboard MK270 Device path : /dev/hidraw6 WPID : 4023 Codename : MK270 Kind : keyboard Protocol : HID++ 2.0 Polling rate : 20 ms (50Hz) Serial number: 90A794A3 Model ID: 000000000000 Unit ID: 00000000 Firmware: RQK 49.00.B0029 Supports 18 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V0 Firmware: Firmware RQK 49.00.B0029 4023 Unit ID: 00000000 Model ID: 000000000000 Transport IDs: {} 3: DEVICE NAME {0005} V0 Name: Wireless Keyboard Kind: keyboard 4: BATTERY STATUS {1000} V0 Battery: 30%, discharging, next level 5%. 5: REPROG CONTROLS {1B00} V0 6: WIRELESS DEVICE STATUS {1D4B} V0 7: FN INVERSION {40A0} V0 Swap Fx function (saved): False Swap Fx function : False 8: ENCRYPTION {4100} V0 9: KEYBOARD LAYOUT {4520} V0 10: unknown:1810 {1810} V0 internal, hidden 11: unknown:1830 {1830} V0 internal, hidden 12: unknown:1890 {1890} V0 internal, hidden 13: unknown:18A0 {18A0} V0 internal, hidden 14: unknown:18B0 {18B0} V0 internal, hidden 15: unknown:1DF3 {1DF3} V0 internal, hidden 16: unknown:1E00 {1E00} V0 hidden 17: unknown:1868 {1868} V0 internal, hidden Battery: 30%, discharging, next level 5%. Solaar-1.1.20/docs/devices/Wireless Mobile Mouse MX Anywhere 2 4072.txt000066400000000000000000000071551522022367600247510ustar00rootroot00000000000000 2: Wireless Mobile Mouse MX Anywhere 2 Codename : MX Anywhere 2 Kind : mouse Wireless PID : 4072 Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: DDAE1933 Bootloader: BOT 57.10.B0003 Firmware: MPM 13.10.B0003 Firmware: MPM 13.10.B0003 Other: The power switch is located on the base. Supports 28 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: CRYPTO ID {0021} 7: BATTERY STATUS {1000} 8: CONFIG DEVICE PROPS {1806} internal, hidden 9: CHANGE HOST {1814} 10: REPROG CONTROLS V4 {1B04} 11: ADJUSTABLE DPI {2201} 12: VERTICAL SCROLLING {2100} Roller type: 3G Ratchet per turn: 24 Scroll lines: 0 13: HIRES WHEEL {2121} Multiplier: 8 Has invert Normal wheel motion Has ratchet switch Normal wheel mode Low resolution mode HID notification High Resolution Wheel Invert: False Wheel Resolution: False 14: DFUCONTROL SIGNED {00C2} 15: unknown:1813 {1813} internal, hidden 16: unknown:1830 {1830} internal, hidden 17: unknown:1890 {1890} internal, hidden 18: unknown:1891 {1891} internal, hidden 19: unknown:18A1 {18A1} internal, hidden 20: unknown:18C0 {18C0} internal, hidden 21: unknown:1DF3 {1DF3} internal, hidden 22: unknown:1E00 {1E00} hidden 23: unknown:1EB0 {1EB0} internal, hidden 24: unknown:1803 {1803} internal, hidden 25: unknown:1861 {1861} internal, hidden 26: unknown:9001 {9001} internal, hidden 27: OOBSTATE {1805} internal, hidden Has 8 reprogrammable keys: 0: Left Button , default: Left Click => Left Button mse, pos:0, group:1, gmask:1 1: Right Button , default: Right Click => Right Button mse, pos:0, group:1, gmask:1 2: Middle Button , default: Gesture Button Navigation => Middle Button mse, reprogrammable, divertable, pos:0, group:2, gmask:11 3: Back Button , default: Mouse Back Button => Back Button mse, reprogrammable, divertable, pos:0, group:3, gmask:15 4: Forward Button , default: Mouse Forward Button => Forward Button mse, reprogrammable, divertable, pos:0, group:3, gmask:15 5: Left Tilt , default: Mouse Scroll Left Button => Left Tilt mse, reprogrammable, divertable, pos:0, group:3, gmask:15 6: Right Tilt , default: Mouse Scroll Right Button => Right Tilt mse, reprogrammable, divertable, pos:0, group:3, gmask:15 7: Virtual Gesture Button , default: Virtual Gesture Button => Virtual Gesture Button divertable, virtual, pos:0, group:4, gmask:0 Battery: 90%, discharging, next level 50%. 15:29:59,190 INFO [MainThread] logitech_receiver.receiver: : found new device 3 (4024) Solaar-1.1.20/docs/devices/Wireless Mobile Mouse MX Anywhere 2S 406A.txt000066400000000000000000000114061522022367600251040ustar00rootroot00000000000000Solaar version 1.1.3 2: Wireless Mobile Mouse MX Anywhere 2S Device path : None WPID : 406A Codename : MX Anywhere 2S Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: C53F2F0B Model ID: B01A406A0000 Unit ID: 3F714CA3 Bootloader: BOT 57.00.B0003 Firmware: MPM 13.00.B0003 Firmware: MPM 13.00.B0003 Other: The power switch is located on the base. Supports 28 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 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} Name: Wireless Mobile Mouse MX Anywhere 2S Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: BATTERY STATUS {1000} Battery: 90%, discharging, next level 50%. 7: CONFIG DEVICE PROPS {1806} internal, hidden 8: CHANGE HOST {1814} 9: REPROG CONTROLS V4 {1B04} 10: ADJUSTABLE DPI {2201} Sensitivity (DPI) (saved): 4000 Sensitivity (DPI) : 4000 11: VERTICAL SCROLLING {2100} Roller type: 3G Ratchet per turn: 24 Scroll lines: 0 12: HIRES WHEEL {2121} 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: DFUCONTROL UNSIGNED {00C1} 14: DFUCONTROL SIGNED {00C2} 15: unknown:1813 {1813} internal, hidden 16: unknown:1830 {1830} internal, hidden 17: unknown:1890 {1890} internal, hidden 18: unknown:1891 {1891} internal, hidden 19: unknown:18A1 {18A1} internal, hidden 20: unknown:18C0 {18C0} internal, hidden 21: unknown:1DF3 {1DF3} internal, hidden 22: unknown:1E00 {1E00} hidden 23: unknown:1EB0 {1EB0} internal, hidden 24: unknown:1803 {1803} internal, hidden 25: unknown:1861 {1861} internal, hidden 26: unknown:9001 {9001} internal, hidden 27: OOBSTATE {1805} 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: default 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%. Solaar-1.1.20/docs/devices/Wireless Mobile Mouse MX Anywhere 2S B01A.txt000066400000000000000000000127221522022367600251170ustar00rootroot00000000000000solaar 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%. Solaar-1.1.20/docs/devices/Wireless Mouse 4022.txt000066400000000000000000000027511522022367600213570ustar00rootroot00000000000000 2: Wireless Mouse Codename : Kind : mouse Wireless PID : 4022 Protocol : HID++ 2.0 Polling rate : 8 ms (125Hz) Serial number: 00000000 Firmware: RQM 38.00.B0044 Supports 18 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: BATTERY STATUS {1000} 5: REPROG CONTROLS {1B00} 6: WIRELESS DEVICE STATUS {1D4B} 7: VERTICAL SCROLLING {2100} 8: MOUSE POINTER {2200} 9: unknown:1810 {1810} internal, hidden 10: unknown:1830 {1830} internal, hidden 11: unknown:1850 {1850} internal, hidden 12: unknown:1890 {1890} internal, hidden 13: unknown:18B0 {18B0} internal, hidden 14: unknown:1DF3 {1DF3} internal, hidden 15: unknown:1868 {1868} internal, hidden 16: unknown:1869 {1869} internal, hidden 17: unknown:1E00 {1E00} hidden Has 3 reprogrammable keys: 0: LEFT CLICK => LeftClick mse, reprogrammable 1: RIGHT CLICK => RightClick mse, reprogrammable 2: MIDDLE BUTTON => MiddleMouseButton mse, reprogrammable Battery: 30%, discharging. Solaar-1.1.20/docs/devices/Wireless Mouse Dell WM514 4029.txt000066400000000000000000000043211522022367600227600ustar00rootroot00000000000000Solaar version 1.1.6rc4 3: Wireless Mouse Dell WM514 Device path : /dev/hidraw5 WPID : 4029 Codename : Dell WM514 Kind : mouse Protocol : HID++ 3.0 Polling rate : 8 ms (125Hz) Serial number: 17F2CD56 Model ID: 000000000000 Unit ID: 00000000 Firmware: RQM 44.00.B0013 The power switch is located on the base. Supports 23 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V0 Firmware: Firmware RQM 44.00.B0013 4029 Unit ID: 00000000 Model ID: 000000000000 Transport IDs: {} 3: DEVICE NAME {0005} V0 Name: Wireless Mouse Dell WM514 Kind: mouse 4: BATTERY STATUS {1000} V0 Battery: 70%, discharging, next level 5%. 5: unknown:1830 {1830} V0 internal, hidden 6: unknown:1860 {1860} V0 internal, hidden 7: unknown:1890 {1890} V0 internal, hidden 8: unknown:18A0 {18A0} V0 internal, hidden 9: unknown:18B0 {18B0} 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: 124 15: unknown:1E00 {1E00} V0 hidden 16: unknown:1850 {1850} 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 21: DEVICE GROUPS {0006} V0 22: unknown:1BB0 {1BB0} V0 Battery: 70%, discharging, next level 5%. Solaar-1.1.20/docs/devices/Wireless Mouse M185 new 4054.txt000066400000000000000000000056531522022367600225550ustar00rootroot00000000000000Solaar version 1.1.4rc2 2: Wireless Mouse M185 new Device path : /dev/hidraw3 WPID : 4054 Codename : M185n Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: 00000000 Model ID: 000000000000 Unit ID: 00000000 Firmware: RQM 64.00.B0008 The power switch is located on the base. Supports 19 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Firmware RQM 64.00.B0008 4054 Unit ID: 00000000 Model ID: 000000000000 Transport IDs: {} 3: DEVICE NAME {0005} Name: Wireless Mouse Kind: mouse 4: RESET {0020} 5: REPROG CONTROLS V4 {1B04} Mouse Gestures (saved): Off Mouse Gestures : Off Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button} Key/Button Actions : {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button} Key/Button Diversion (saved): {Middle Button:Regular} Key/Button Diversion : {Middle Button:Regular} 6: WIRELESS DEVICE STATUS {1D4B} 7: LOWRES WHEEL {2130} Wheel Reports: HID Scroll Wheel Diversion (saved): False Scroll Wheel Diversion : False 8: POINTER SPEED {2205} Pointer Speed: 1.0 Sensitivity (Pointer Speed) (saved): 256 Sensitivity (Pointer Speed) : 256 9: DEVICE RESET {1802} internal, hidden 10: unknown:1810 {1810} internal, hidden 11: unknown:1830 {1830} internal, hidden 12: unknown:1850 {1850} internal, hidden 13: unknown:1869 {1869} internal, hidden 14: unknown:1890 {1890} internal, hidden 15: unknown:18B1 {18B1} internal, hidden 16: unknown:1DF3 {1DF3} internal, hidden 17: unknown:1E00 {1E00} hidden 18: unknown:1F03 {1F03} internal, hidden Has 3 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 1: Right Button , default: Right Click => Right Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: default Battery status unavailable. Solaar-1.1.20/docs/devices/Wireless Mouse M185,M235,M310 4055.txt000066400000000000000000000060611522022367600230360ustar00rootroot00000000000000Solaar version 1.1.4 1: Wireless Mouse M185/M235/M310 Device path : /dev/hidraw2 WPID : 4055 Codename : M185/M235/M310 Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: 82C9A01F Model ID: 405500000000 Unit ID: 82C9A01F Firmware: RQM 65.00.B0003 The power switch is located on the base. Supports 21 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Firmware RQM 65.00.B0003 4055 Unit ID: 82C9A01F Model ID: 405500000000 Transport IDs: {'wpid': '4055'} 3: DEVICE NAME {0005} Name: Wireless Mouse Kind: mouse 4: RESET {0020} 5: BATTERY STATUS {1000} Battery: 70%, discharging, next level 5%. 6: unknown:1810 {1810} internal, hidden 7: unknown:1830 {1830} internal, hidden 8: DEVICE RESET {1802} internal, hidden 9: unknown:1862 {1862} internal, hidden 10: unknown:1890 {1890} internal, hidden 11: unknown:18A0 {18A0} internal, hidden 12: unknown:18B1 {18B1} internal, hidden 13: REPROG CONTROLS V4 {1B04} Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button} Key/Button Actions : {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button} Key/Button Diversion (saved): {Left Button:Regular, Right Button:Regular, Middle Button:Regular} Key/Button Diversion : {Left Button:Regular, Right Button:Regular, Middle Button:Regular} 14: WIRELESS DEVICE STATUS {1D4B} 15: REMAINING PAIRING {1DF0} hidden Remaining Pairings: 116 16: unknown:1DF3 {1DF3} internal, hidden 17: unknown:1E00 {1E00} hidden 18: unknown:1EB0 {1EB0} internal, hidden 19: unknown:1F03 {1F03} internal, hidden 20: LOWRES WHEEL {2130} Wheel Reports: HID Scroll Wheel Diversion (saved): False Scroll Wheel Diversion : False Has 3 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, divertable, pos:0, group:1, group mask:g1 reporting: default 1: Right Button , default: Right Click => Right Click mse, divertable, pos:0, group:1, group mask:g1 reporting: analytics key events reporting 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: unknown:000200 Battery: 70%, discharging, next level 5%. Solaar-1.1.20/docs/devices/Wireless Mouse M185.text000066400000000000000000000050101522022367600215560ustar00rootroot00000000000000solaar 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%. Solaar-1.1.20/docs/devices/Wireless Mouse M215 2nd Gen 401B.txt000066400000000000000000000034101522022367600231320ustar00rootroot00000000000000Solaar version 1.1.5 1: Wireless Mouse M215 2nd Gen Device path : None WPID : 401B Codename : Wireless Kind : mouse Protocol : HID++ 2.0 Polling rate : 8 ms (125Hz) Serial number: D225825E Model ID: 000000000000 Unit ID: 00000000 Firmware: RQM 27.12.B0029 The power switch is located on the base. Supports 13 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V0 Firmware: Firmware RQM 27.12.B0029 401B Unit ID: 00000000 Model ID: 000000000000 Transport IDs: {} 3: DEVICE NAME {0005} V0 Name: Wireless Mouse M215 2nd Gen Kind: mouse 4: BATTERY STATUS {1000} V0 Battery: 90%, discharging, next level 5%. 5: WIRELESS DEVICE STATUS {1D4B} V0 6: unknown:1DF3 {1DF3} V0 hidden 7: REPROG CONTROLS {1B00} V0 8: REMAINING PAIRING {1DF0} V0 hidden Remaining Pairings: 126 9: unknown:1F03 {1F03} V0 hidden 10: VERTICAL SCROLLING {2100} V0 Roller type: standard Ratchet per turn: 24 Scroll lines: 0 11: HI RES SCROLLING {2120} V0 Hi-res scrolling disabled Hi-res scrolling multiplier: 8 Scroll Wheel High Resolution (saved): False Scroll Wheel High Resolution : False 12: MOUSE POINTER {2200} V0 DPI: 800 Acceleration: low Override OS ballistics No vertical tuning, standard mice Battery: 90%, discharging, next level 5%. Solaar-1.1.20/docs/devices/Wireless Mouse M310 M310t 4031.txt000066400000000000000000000033321522022367600225410ustar00rootroot00000000000000 1: Wireless Mouse M310/M310t Codename : M310/M310t Kind : mouse Wireless PID : 4031 Protocol : HID++ 2.0 Polling rate : 8 ms (125Hz) Serial number: 03E8C0B4 Firmware: RQM 40.01.B0017 The power switch is located on the base. Supports 21 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: BATTERY STATUS {1000} 5: unknown:1830 {1830} internal, hidden 6: unknown:1850 {1850} internal, hidden 7: unknown:1860 {1860} internal, hidden 8: unknown:1890 {1890} internal, hidden 9: unknown:18A0 {18A0} internal, hidden 10: unknown:18C0 {18C0} internal, hidden 11: WIRELESS DEVICE STATUS {1D4B} 12: unknown:1DF3 {1DF3} internal, hidden 13: REPROG CONTROLS {1B00} 14: unknown:1DF0 {1DF0} hidden 15: unknown:1E00 {1E00} hidden 16: unknown:1E80 {1E80} internal, hidden 17: unknown:1E90 {1E90} internal, hidden 18: unknown:1F03 {1F03} internal, hidden 19: VERTICAL SCROLLING {2100} 20: MOUSE POINTER {2200} Has 3 reprogrammable keys: 0: LEFT CLICK => LeftClick mse, reprogrammable 1: RIGHT CLICK => RightClick mse, reprogrammable 2: MIDDLE BUTTON => MiddleMouseButton mse, reprogrammable Battery: 70%, discharging. Solaar-1.1.20/docs/devices/Wireless Mouse M325 400A.txt000066400000000000000000000065331522022367600220050ustar00rootroot00000000000000 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 Wireless PID : 400A Protocol : HID++ 2.0 Polling rate : 8 ms (125Hz) Serial number: ABB05E01 Firmware: RQM 27.02.B0028 The power switch is located on the base. Supports 13 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: BATTERY STATUS {1000} 5: WIRELESS DEVICE STATUS {1D4B} 6: unknown:1DF3 {1DF3} hidden 7: REPROG CONTROLS {1B00} 8: unknown:1DF0 {1DF0} hidden 9: unknown:1F03 {1F03} hidden 10: VERTICAL SCROLLING {2100} 11: HI RES SCROLLING {2120} 12: MOUSE POINTER {2200} Has 5 reprogrammable keys: 0: LEFT CLICK => LeftClick mse, reprogrammable 1: RIGHT CLICK => RightClick mse, reprogrammable 2: MIDDLE BUTTON => MiddleMouseButton mse, reprogrammable 3: BACK AS BUTTON 4 => Back mse, reprogrammable 4: FORWARD AS BUTTON 5 => BrowserForward mse, reprogrammable Solaar-1.1.20/docs/devices/Wireless Mouse M345 4017.txt000066400000000000000000000015351522022367600217730ustar00rootroot000000000000001: Wireless Mouse M345 Codename : M345 Kind : mouse Wireless PID : 4017 Protocol : HID++ 2.0 Polling rate : 8 ms Serial number: 920DC223 Firmware: RQM 27.02.B0028 The power switch is located on the base. Supports 13 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: BATTERY STATUS {1000} 5: WIRELESS DEVICE STATUS {1D4B} 6: unknown:1DF3 {1DF3} hidden 7: REPROG CONTROLS {1B00} 8: unknown:1DF0 {1DF0} hidden 9: unknown:1F03 {1F03} hidden 10: VERTICAL SCROLLING {2100} 11: HI RES SCROLLING {2120} 12: MOUSE POINTER {2200} Battery: 90%, discharging, Solaar-1.1.20/docs/devices/Wireless Mouse M510 1025.txt000066400000000000000000000007711522022367600217620ustar00rootroot00000000000000Solaar version 1.1.4 1: Wireless Mouse M510 Device path : /dev/hidraw2 WPID : 1025 Codename : M510 Kind : mouse Protocol : HID++ 1.0 Polling rate : 8 ms (125Hz) Serial number: 93E8527F Firmware: 23.01.B0016 The power switch is located on the base. Notifications: battery status (0x100000). Features: special buttons, buttons control resolution (0x820000) Battery: good, discharging. Has Side Scrolling register Solaar-1.1.20/docs/devices/Wireless Mouse M510 4051.txt000066400000000000000000000051271522022367600217640ustar00rootroot00000000000000 1: Wireless Mouse M510 Codename : M510v2 Kind : mouse Wireless PID : 4051 Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: 8989B04C Firmware: RQM 62.00.B0013 The power switch is located on the base. Supports 22 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: RESET {0020} 5: BATTERY STATUS {1000} 6: unknown:1802 {1802} internal, hidden 7: unknown:1810 {1810} internal, hidden 8: unknown:1830 {1830} internal, hidden 9: unknown:1862 {1862} internal, hidden 10: unknown:1890 {1890} internal, hidden 11: unknown:18A0 {18A0} internal, hidden 12: unknown:18B1 {18B1} internal, hidden 13: REPROG CONTROLS V4 {1B04} 14: WIRELESS DEVICE STATUS {1D4B} 15: unknown:1DF0 {1DF0} hidden 16: unknown:1DF3 {1DF3} internal, hidden 17: unknown:1E00 {1E00} hidden 18: unknown:1EB0 {1EB0} internal, hidden 19: unknown:1F03 {1F03} internal, hidden 20: LOWRES WHEEL {2130} 21: POINTER SPEED {2205} Has 7 reprogrammable keys: 0: LEFT CLICK , default: LeftClick => LEFT CLICK divertable, mse, pos:0, group:1, gmask:1 1: RIGHT CLICK , default: RightClick => RIGHT CLICK divertable, mse, pos:0, group:1, gmask:1 2: MIDDLE BUTTON , default: MiddleMouseButton => MIDDLE BUTTON divertable, mse, reprogrammable, pos:0, group:2, gmask:3 3: LEFT SCROLL AS AC PAN , default: HorzScrollLeftSet => LEFT SCROLL AS AC PAN divertable, mse, reprogrammable, pos:0, group:2, gmask:3 4: RIGHT SCROLL AS AC PAN , default: HorzScrollRightSet => RIGHT SCROLL AS AC PAN divertable, mse, reprogrammable, pos:0, group:2, gmask:3 5: BACK AS BUTTON 4 , default: BackEx => BACK AS BUTTON 4 divertable, mse, reprogrammable, pos:0, group:2, gmask:3 6: FORWARD AS BUTTON 5 , default: BrowserForwardEx => FORWARD AS BUTTON 5 divertable, mse, reprogrammable, pos:0, group:2, gmask:3 Battery: 70%, discharging. Solaar-1.1.20/docs/devices/Wireless Mouse M525 4013.txt000066400000000000000000000026011522022367600217620ustar00rootroot00000000000000 1: Wireless Mouse M525 Codename : M525 Kind : mouse Wireless PID : 4013 Protocol : HID++ 2.0 Polling rate : 8 ms (125Hz) Serial number: DAFA335E Firmware: RQM 27.02.B0028 The power switch is located on the base. Supports 13 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: BATTERY STATUS {1000} 5: WIRELESS DEVICE STATUS {1D4B} 6: unknown:1DF3 {1DF3} hidden 7: REPROG CONTROLS {1B00} 8: unknown:1DF0 {1DF0} hidden 9: unknown:1F03 {1F03} hidden 10: VERTICAL SCROLLING {2100} 11: HI RES SCROLLING {2120} 12: MOUSE POINTER {2200} Has 5 reprogrammable keys: 0: LEFT CLICK => LeftClick mse, reprogrammable 1: RIGHT CLICK => RightClick mse, reprogrammable 2: MIDDLE BUTTON => MiddleMouseButton mse, reprogrammable 3: BACK AS BUTTON 4 => Back mse, reprogrammable 4: FORWARD AS BUTTON 5 => BrowserForward mse, reprogrammable Battery: 90%, discharging. Solaar-1.1.20/docs/devices/Wireless Mouse M560 402D.txt000066400000000000000000000041521522022367600220060ustar00rootroot00000000000000 1: Wireless Mouse M560 Codename : M560 Kind : mouse Wireless PID : 402D Protocol : HID++ 2.0 Polling rate : 8 ms (125Hz) Serial number: 16E6E42A Firmware: RQM 48.00.B0015 The power switch is located on the base. Supports 22 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: BATTERY STATUS {1000} 5: unknown:1830 {1830} internal, hidden 6: unknown:1850 {1850} internal, hidden 7: unknown:1860 {1860} internal, hidden 8: unknown:1890 {1890} internal, hidden 9: unknown:18A0 {18A0} internal, hidden 10: REPROG CONTROLS V3 {1B03} 11: WIRELESS DEVICE STATUS {1D4B} 12: unknown:1DF3 {1DF3} internal, hidden 13: REPROG CONTROLS {1B00} 14: unknown:1DF0 {1DF0} hidden 15: unknown:1E00 {1E00} hidden 16: unknown:18B0 {18B0} internal, hidden 17: unknown:1E90 {1E90} internal, hidden 18: unknown:1F03 {1F03} internal, hidden 19: VERTICAL SCROLLING {2100} 20: HI RES SCROLLING {2120} 21: MOUSE POINTER {2200} Has 7 reprogrammable keys: 0: LEFT CLICK => LeftClick mse, reprogrammable 1: RIGHT CLICK => RightClick mse, reprogrammable 2: BACK AS BUTTON 4 => Win8BackHorzLeft mse, reprogrammable 3: FORWARD AS BUTTON 5 => Win8ForwardHorzRight mse, reprogrammable 4: unknown:00B0 => Win8MetroWin7Forward mse, reprogrammable 5: SHOW DESKTOP HPP => Win8ShowDesktopWin7Back mse, reprogrammable 6: unknown:00AF => Win8Charm Appswitch GifAnimation mse, reprogrammable, unknown:000020 Battery: 70%, discharging. Solaar-1.1.20/docs/devices/Wireless Mouse MX Anywhere 2 404A.txt000066400000000000000000000033651522022367600235740ustar00rootroot00000000000000 1: Wireless Mouse MX Anywhere 2 Codename : MX Anywhere 2 Kind : mouse Wireless PID : 404A Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: F3B81C5B Bootloader: BOT 23.00.B0007 Firmware: MPM 02.00.B0007 Firmware: MPM 02.00.B0007 Other: The power switch is located on the base. Supports 26 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: BATTERY STATUS {1000} 7: CHANGE HOST {1814} 8: REPROG CONTROLS V4 {1B04} 9: ADJUSTABLE DPI {2201} 10: VERTICAL SCROLLING {2100} 11: HIRES WHEEL {2121} 12: DFUCONTROL 2 {00C1} 13: unknown:1813 {1813} internal, hidden 14: unknown:1830 {1830} internal, hidden 15: unknown:1890 {1890} internal, hidden 16: unknown:1891 {1891} internal, hidden 17: unknown:18A1 {18A1} internal, hidden 18: unknown:18C0 {18C0} internal, hidden 19: unknown:1DF3 {1DF3} internal, hidden 20: unknown:1E00 {1E00} hidden 21: unknown:1EB0 {1EB0} internal, hidden 22: unknown:1803 {1803} internal, hidden 23: unknown:1861 {1861} internal, hidden 24: unknown:9000 {9000} internal, hidden 25: unknown:1805 {1805} internal, hidden Battery: 0%, recharging. Solaar-1.1.20/docs/devices/Wireless Mouse MX Master 2S 4069.txt000066400000000000000000000152611522022367600233600ustar00rootroot00000000000000Solaar version 1.1.4 1: Wireless Mouse MX Master 2S Device path : /dev/hidraw1 WPID : 4069 Codename : MX Master 2S Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: 9429B808 Model ID: B01940690000 Unit ID: 65D747F7 Bootloader: BOT 56.01.B0006 Firmware: MPM 12.01.B0006 Firmware: MPM 12.01.B0006 Other: The power switch is located on the base. Supports 31 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 56.01.B0006 406916A9FC5401 Firmware: Firmware MPM 12.01.B0006 B01916A9FC5401 Firmware: Firmware MPM 12.01.B0006 406916A9FC5401 Firmware: Other Unit ID: 65D747F7 Model ID: B01940690000 Transport IDs: {'btleid': 'B019', 'wpid': '4069'} 3: DEVICE NAME {0005} Name: Wireless Mouse MX Master 2S Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: CRYPTO ID {0021} 7: BATTERY STATUS {1000} Battery: 50%, discharging, next level 20%. 8: CONFIG DEVICE PROPS {1806} internal, hidden 9: CHANGE HOST {1814} Cambiar Host : 2:lapega 10: REPROG CONTROLS V4 {1B04} Ajustar DPI deslizando (saved): Off Ajustar DPI deslizando : Off Gestos de Ratón (saved): Off Gestos de Ratón : Off Acciones de Tecla/Botón (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift} Acciones de Tecla/Botón : {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift} Desvio de Tecla/Botón (saved): {Middle Button:Normal, Back Button:Normal, Forward Button:Normal, Mouse Gesture Button:Normal, Smart Shift:Normal} Desvio de Tecla/Botón : {Middle Button:Normal, Back Button:Normal, Forward Button:Normal, Mouse Gesture Button:Normal, Smart Shift:Normal} 11: ADJUSTABLE DPI {2201} Sensibilidad (PPP) (saved): 4000 Sensibilidad (PPP) : 4000 12: VERTICAL SCROLLING {2100} Roller type: 3G Ratchet per turn: 24 Scroll lines: 0 13: SMART SHIFT {2110} Trinquete Rueda Desplazamiento (saved): 50 Trinquete Rueda Desplazamiento : 50 14: HIRES WHEEL {2121} Multiplier: 8 Has invert: Normal wheel motion Has ratchet switch: Normal wheel mode High resolution mode HID notification Dirección Rueda Desplazamiento (saved): False Dirección Rueda Desplazamiento : False Resolución Rueda Desplazamiento (saved): True Resolución Rueda Desplazamiento : True Desvio Rueda Desplazamiento (saved): False Desvio Rueda Desplazamiento : False 15: GESTURE 2 {6501} Gestos (saved): {45: True, 46: True} Gestos : {46: True, 45: True} Gestures Diversion (saved): {46: False} Gestures Diversion : {46: False} 16: DFUCONTROL SIGNED {00C2} 17: unknown:1813 {1813} 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:18C0 {18C0} internal, hidden 23: unknown:1DF3 {1DF3} internal, hidden 24: unknown:1E00 {1E00} hidden 25: unknown:1EB0 {1EB0} internal, hidden 26: unknown:1803 {1803} internal, hidden 27: unknown:1861 {1861} internal, hidden 28: unknown:9001 {9001} internal, hidden 29: unknown:9200 {9200} internal, hidden 30: unknown:9202 {9202} 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: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3 reporting: default 3: Back Button , default: Mouse Back Button => Mouse Back Button mse, reprogrammable, divertable, raw XY, 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, pos:0, group:2, group mask:g1,g2 reporting: default 5: Mouse Gesture Button , default: Gesture Button Navigation => Gesture Button Navigation mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3 reporting: default 6: Smart Shift , default: Smart Shift => Smart Shift mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3 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 Has 2 gesture(s), 0 param(s) and 4 spec(s): Thumbwheel Enabled( 0): True Diverted:( 0) False NaturalScrolling Enabled( 1): True Diverted:(None) None period unit Spec ( 3): 800 DVI field width Spec ( 1): 8 field widths Spec ( 2): 8 resolution Spec ( 4): 2048 Battery: 50%, discharging, next level 20%. Solaar-1.1.20/docs/devices/Wireless Mouse MX Master 2S B019.txt000066400000000000000000000145561522022367600233770ustar00rootroot00000000000000Solaar version 1.1.3 1: Wireless Mouse MX Master 2S Device path : /dev/hidraw3 USB id : 046d:B019 Codename : MX Master 2S Kind : mouse Protocol : HID++ 4.5 Serial number: Model ID: B01940690000 Unit ID: D34FD133 Bootloader: BOT 56.01.B0006 Firmware: MPM 12.01.B0006 Firmware: MPM 12.01.B0006 Other: Supports 29 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 56.01.B0006 406916A9FC5401 Firmware: Firmware MPM 12.01.B0006 B01916A9FC5401 Firmware: Firmware MPM 12.01.B0006 406916A9FC5401 Firmware: Other Unit ID: D34FD133 Model ID: B01940690000 Transport IDs: {'btleid': 'B019', 'wpid': '4069'} 3: DEVICE NAME {0005} Name: Wireless Mouse MX Master 2S Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: CRYPTO ID {0021} 7: BATTERY STATUS {1000} Battery: 50%, discharging, next level 20%. 8: CONFIG DEVICE PROPS {1806} internal, hidden 9: CHANGE HOST {1814} 改变主机 : 1:yan-desktop 10: REPROG CONTROLS V4 {1B04} DPI 滑动调节 (saved): 关 DPI 滑动调节 : 关 鼠标手势 (saved): Mouse Gesture Button 鼠标手势 : 关 按键/按钮操作 (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift} 按键/按钮操作 : {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift} 按键/按钮改道 (saved): {Middle Button:常规, Back Button:改道, Forward Button:改道, Mouse Gesture Button:改道, Smart Shift:常规} 按键/按钮改道 : {Middle Button:常规, Back Button:改道, Forward Button:改道, Mouse Gesture Button:改道, Smart Shift:常规} 11: ADJUSTABLE DPI {2201} 灵敏度 (DPI) (saved): 1000 灵敏度 (DPI) : 1000 12: VERTICAL SCROLLING {2100} Roller type: 3G Ratchet per turn: 24 Scroll lines: 0 13: SMART SHIFT {2110} 滚轮分段模式 (saved): 16 滚轮分段模式 : 16 14: HIRES WHEEL {2121} Multiplier: 8 Has invert: Normal wheel motion Has ratchet switch: Normal wheel mode Low resolution mode HID notification 滚轮方向 (saved): False 滚轮方向 : False 滚轮分辨率 (saved): False 滚轮分辨率 : False 滚轮改道 (saved): False 滚轮改道 : False 15: GESTURE 2 {6501} 手势 (saved): {45: True, 46: True} 手势 : {46: True, 45: True} 手势改道 (saved): {46: False} 手势改道 : {46: False} 16: unknown:1813 {1813} internal, hidden 17: unknown:1830 {1830} internal, hidden 18: unknown:18A1 {18A1} internal, hidden 19: unknown:18C0 {18C0} internal, hidden 20: unknown:1DF3 {1DF3} internal, hidden 21: unknown:1E00 {1E00} hidden 22: unknown:1EB0 {1EB0} internal, hidden 23: unknown:1803 {1803} internal, hidden 24: unknown:1861 {1861} internal, hidden 25: unknown:9001 {9001} internal, hidden 26: unknown:9200 {9200} internal, hidden 27: unknown:9202 {9202} internal, hidden 28: OOBSTATE {1805} 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: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3 reporting: default 3: Back Button , default: Mouse Back Button => Mouse Back Button mse, reprogrammable, divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: diverted 4: Forward Button , default: Mouse Forward Button => Mouse Forward Button mse, reprogrammable, divertable, raw XY, pos:0, group:2, group mask:g1,g2 reporting: diverted 5: Mouse Gesture Button , default: Gesture Button Navigation => Gesture Button Navigation mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3 reporting: diverted, raw XY diverted 6: Smart Shift , default: Smart Shift => Smart Shift mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3 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 Has 2 gesture(s), 0 param(s) and 4 spec(s): Thumbwheel Enabled( 0): True Diverted:( 0) False NaturalScrolling Enabled( 1): True Diverted:(None) None period unit Spec ( 3): 800 DVI field width Spec ( 1): 8 field widths Spec ( 2): 8 resolution Spec ( 4): 2048 Battery: 50%, discharging, next level 20%. Solaar-1.1.20/docs/devices/Wireless Mouse MX Master 4041.txt000066400000000000000000000144401522022367600230770ustar00rootroot00000000000000 4: Wireless Mouse MX Master Device path : /dev/hidraw4 WPID : 4041 Codename : MX Master Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: ---------- Model ID: B01240410000 Unit ID: ---------- Bootloader: BOT 18.01.B0014 Firmware: MPM 11.02.B0014 Firmware: MPM 11.02.B0014 Other: The power switch is located on the base. Supports 28 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 18.01.B0014 40410862AB80 Firmware: Firmware MPM 11.02.B0014 B0120862AB80 Firmware: Firmware MPM 11.02.B0014 40410862AB80 Firmware: Other Unit ID: ------------ Model ID: B01240410000 Transport IDs: {'btleid': 'B012', 'wpid': '4041'} 3: DEVICE NAME {0005} Name: Wireless Mouse MX Master Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: BATTERY STATUS {1000} Battery: 50%, discharging, next level 20%. 7: CHANGE HOST {1814} Change Host : 1:jaaxx-elite 8: REPROG CONTROLS V4 {1B04} Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button, Smart Shift:Smart Shift} 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, Mouse Gesture Button:Gesture Button, Smart Shift:Smart Shift} Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Mouse Gesture Button:Regular, Smart Shift:Regular} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Mouse Gesture Button:Regular, Smart Shift:Regular} 9: ADJUSTABLE DPI {2201} Sensitivity (DPI) (saved): 1200 Sensitivity (DPI) : 1200 10: VERTICAL SCROLLING {2100} Roller type: 3G Ratchet per turn: 24 Scroll lines: 0 11: SMART SHIFT {2110} Scroll Wheel Rachet (saved): 8 Scroll Wheel Rachet : 8 12: HIRES WHEEL {2121} 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 13: GESTURE 2 {6501} Gestures (saved): {45: True, 46: True} Gestures : {46: True, 45: True} Gestures Diversion (saved): {46: False, 98: False, 211: False, 212: False} Gestures Diversion : {46: False, 98: False, 211: False, 212: False} 14: DFUCONTROL UNSIGNED {00C1} 15: unknown:1813 {1813} internal, hidden 16: unknown:1830 {1830} internal, hidden 17: unknown:1890 {1890} internal, hidden 18: unknown:18A1 {18A1} internal, hidden 19: unknown:18C0 {18C0} internal, hidden 20: unknown:1DF3 {1DF3} internal, hidden 21: unknown:1E00 {1E00} hidden 22: unknown:1EB0 {1EB0} internal, hidden 23: unknown:1803 {1803} internal, hidden 24: unknown:1861 {1861} internal, hidden 25: unknown:9000 {9000} internal, hidden 26: unknown:9200 {9200} internal, hidden 27: unknown:9240 {9240} internal, hidden Has 7 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 1: Right Button , default: Right Click => Right Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2,g3 reporting: default 3: Back Button , default: Mouse Back Button => Mouse Back Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2 reporting: default 4: Forward Button , default: Mouse Forward Button => Mouse Forward Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2 reporting: default 5: Mouse Gesture Button , default: Gesture Button => Gesture Button mse, reprogrammable, divertable, pos:0, group:3, group mask:g1,g2,g3 reporting: default 6: Smart Shift , default: Smart Shift => Smart Shift mse, reprogrammable, divertable, pos:0, group:3, group mask:g1,g2,g3 reporting: default Has 5 gesture(s), 0 param(s) and 4 spec(s): Thumbwheel Enabled( 0): True Diverted:( 0) False NaturalScrolling Enabled( 1): True Diverted:(None) None Finger9 Enabled(None): None Diverted:( 1) False unknown:00D3 Enabled(None): None Diverted:( 2) False unknown:00D4 Enabled(None): None Diverted:( 3) False period unit Spec ( 3): 800 DVI field width Spec ( 1): 8 field widths Spec ( 2): 8 resolution Spec ( 4): 2048 Battery: 50%, discharging, next level 20%. Solaar-1.1.20/docs/devices/Wireless Mouse MX Master 4071.txt000066400000000000000000000151261522022367600231040ustar00rootroot00000000000000Solaar version 1.1.4 3: Wireless Mouse MX Master Device path : /dev/hidraw3 WPID : 4071 Codename : MX Master Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: D3596818 Model ID: B01E40710000 Unit ID: 9803B76B Bootloader: BOT 56.10.B0005 Firmware: MPM 12.10.B0005 Firmware: MPM 12.10.B0005 Other: The power switch is located on the base. Supports 31 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 56.10.B0005 40717622661101 Firmware: Firmware MPM 12.10.B0005 B01E7622661101 Firmware: Firmware MPM 12.10.B0005 40717622661101 Firmware: Other Unit ID: 9803B76B Model ID: B01E40710000 Transport IDs: {'btleid': 'B01E', 'wpid': '4071'} 3: DEVICE NAME {0005} Name: Wireless Mouse MX Master Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: CRYPTO ID {0021} 7: BATTERY STATUS {1000} Battery: N/A, recharging, next level 0%. 8: CONFIG DEVICE PROPS {1806} internal, hidden 9: CHANGE HOST {1814} Change Host : 2:rachel 10: REPROG CONTROLS V4 {1B04} DPI Sliding Adjustment (saved): Off DPI Sliding Adjustment : Off Mouse Gestures (saved): Off Mouse Gestures : Off Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift} 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, Mouse Gesture Button:Gesture Button Navigation, Smart Shift:Smart Shift} Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Mouse Gesture Button:Regular, Smart Shift:Regular} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Mouse Gesture Button:Regular, Smart Shift:Regular} 11: ADJUSTABLE DPI {2201} Sensitivity (DPI) (saved): 1000 Sensitivity (DPI) : 1000 12: VERTICAL SCROLLING {2100} Roller type: 3G Ratchet per turn: 24 Scroll lines: 0 13: SMART SHIFT {2110} Scroll Wheel Rachet (saved): 10 Scroll Wheel Rachet : 10 14: HIRES WHEEL {2121} 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 15: GESTURE 2 {6501} Gestures (saved): {45: True, 46: True} Gestures : {46: True, 45: True} Gestures Diversion (saved): {46: False} Gestures Diversion : {46: False} 16: DFUCONTROL SIGNED {00C2} 17: unknown:1813 {1813} 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:18C0 {18C0} internal, hidden 23: unknown:1DF3 {1DF3} internal, hidden 24: unknown:1E00 {1E00} hidden 25: unknown:1EB0 {1EB0} internal, hidden 26: unknown:1803 {1803} internal, hidden 27: unknown:1861 {1861} internal, hidden 28: unknown:9001 {9001} internal, hidden 29: unknown:9200 {9200} internal, hidden 30: unknown:9202 {9202} 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: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3 reporting: default 3: Back Button , default: Mouse Back Button => Mouse Back Button mse, reprogrammable, divertable, raw XY, 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, pos:0, group:2, group mask:g1,g2 reporting: default 5: Mouse Gesture Button , default: Gesture Button Navigation => Gesture Button Navigation mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3 reporting: default 6: Smart Shift , default: Smart Shift => Smart Shift mse, reprogrammable, divertable, raw XY, pos:0, group:3, group mask:g1,g2,g3 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 Has 2 gesture(s), 0 param(s) and 4 spec(s): Thumbwheel Enabled( 0): True Diverted:( 0) False NaturalScrolling Enabled( 1): True Diverted:(None) None period unit Spec ( 3): 800 DVI field width Spec ( 1): 8 field widths Spec ( 2): 8 resolution Spec ( 4): 2048 Battery: N/A, recharging, next level 0%. Solaar-1.1.20/docs/devices/Wireless Mouse MX Master B012.txt000066400000000000000000000143631522022367600231170ustar00rootroot00000000000000Solaar version 1.1.3 1: Wireless Mouse MX Master Device path : /dev/hidraw4 USB id : 046d:B012 Codename : MX Master Kind : mouse Protocol : HID++ 4.5 Serial number: Model ID: B01240410000 Unit ID: 3E9C68EF Bootloader: BOT 18.01.B0014 Firmware: MPM 11.02.B0014 Firmware: MPM 11.02.B0014 Other: Supports 28 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 18.01.B0014 40410862AB80 Firmware: Firmware MPM 11.02.B0014 B0120862AB80 Firmware: Firmware MPM 11.02.B0014 40410862AB80 Firmware: Other Unit ID: 3E9C68EF Model ID: B01240410000 Transport IDs: {'btleid': 'B012', 'wpid': '4041'} 3: DEVICE NAME {0005} Name: Wireless Mouse MX Master Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: BATTERY STATUS {1000} Battery: 50%, discharging, next level 20%. 7: CHANGE HOST {1814} Change Host : 1:heimdall 8: REPROG CONTROLS V4 {1B04} Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button, Back Button:Mouse Back Button, Forward Button:Mouse Forward Button, Mouse Gesture Button:Gesture Button, Smart Shift:Smart Shift} 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, Mouse Gesture Button:Gesture Button, Smart Shift:Smart Shift} Key/Button Diversion (saved): {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Mouse Gesture Button:Diverted, Smart Shift:Diverted} Key/Button Diversion : {Middle Button:Regular, Back Button:Regular, Forward Button:Regular, Mouse Gesture Button:Diverted, Smart Shift:Diverted} 9: ADJUSTABLE DPI {2201} Sensitivity (DPI) (saved): 1000 Sensitivity (DPI) : 1000 10: VERTICAL SCROLLING {2100} Roller type: 3G Ratchet per turn: 24 Scroll lines: 0 11: SMART SHIFT {2110} Scroll Wheel Rachet (saved): 35 Scroll Wheel Rachet : 35 12: HIRES WHEEL {2121} 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 13: GESTURE 2 {6501} Gestures (saved): {45: True, 46: True} Gestures : {46: True, 45: True} Gestures Diversion (saved): {46: False, 98: False, 211: False, 212: False} Gestures Diversion : {46: False, 98: False, 211: False, 212: False} 14: unknown:1813 {1813} internal, hidden 15: unknown:1830 {1830} internal, hidden 16: unknown:1890 {1890} internal, hidden 17: unknown:18A1 {18A1} internal, hidden 18: unknown:18C0 {18C0} internal, hidden 19: unknown:1DF3 {1DF3} internal, hidden 20: unknown:1E00 {1E00} hidden 21: unknown:1EB0 {1EB0} internal, hidden 22: unknown:1803 {1803} internal, hidden 23: unknown:1861 {1861} internal, hidden 24: unknown:9000 {9000} internal, hidden 25: unknown:9200 {9200} internal, hidden 26: unknown:9240 {9240} internal, hidden 27: OOBSTATE {1805} internal, hidden Has 7 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 1: Right Button , default: Right Click => Right Click mse, reprogrammable, pos:0, group:1, group mask:g1 reporting: default 2: Middle Button , default: Mouse Middle Button => Mouse Middle Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2,g3 reporting: default 3: Back Button , default: Mouse Back Button => Mouse Back Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2 reporting: default 4: Forward Button , default: Mouse Forward Button => Mouse Forward Button mse, reprogrammable, divertable, pos:0, group:2, group mask:g1,g2 reporting: default 5: Mouse Gesture Button , default: Gesture Button => Gesture Button mse, reprogrammable, divertable, pos:0, group:3, group mask:g1,g2,g3 reporting: diverted 6: Smart Shift , default: Smart Shift => Smart Shift mse, reprogrammable, divertable, pos:0, group:3, group mask:g1,g2,g3 reporting: diverted Has 5 gesture(s), 0 param(s) and 4 spec(s): Thumbwheel Enabled( 0): True Diverted:( 0) False NaturalScrolling Enabled( 1): True Diverted:(None) None Finger9 Enabled(None): None Diverted:( 1) False unknown:00D3 Enabled(None): None Diverted:( 2) False unknown:00D4 Enabled(None): None Diverted:( 3) False period unit Spec ( 3): 800 DVI field width Spec ( 1): 8 field widths Spec ( 2): 8 resolution Spec ( 4): 2048 Battery: 50%, discharging, next level 20%. Solaar-1.1.20/docs/devices/Wireless Mouse Pebble M350 4080.txt000066400000000000000000000074271522022367600231470ustar00rootroot00000000000000Solaar version 1.1.4 2: Wireless Mouse Pebble M350 Device path : /dev/hidraw2 WPID : 4080 Codename : Pebble Kind : mouse Protocol : HID++ 4.5 Polling rate : 8 ms (125Hz) Serial number: 2B442F40 Model ID: B02140800000 Unit ID: DE12CCD9 Firmware: MPM 18.00.B0007 Other: The power switch is located on the base. Supports 26 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V2 Firmware: Firmware MPM 18.00.B0007 4080BEA0D686 Firmware: Other Unit ID: DE12CCD9 Model ID: B02140800000 Transport IDs: {'btleid': 'B021', 'wpid': '4080'} 3: DEVICE NAME {0005} V0 Name: M350 Wireless Mouse Kind: mouse 4: WIRELESS DEVICE STATUS {1D4B} V0 5: RESET {0020} V0 6: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: Logitech Pebble 7: BATTERY STATUS {1000} V1 Battery: 50%, discharging, next level 20%. 8: REPROG CONTROLS V4 {1B04} V4 Key/Button Actions (saved): {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button} Key/Button Actions : {Left Button:Left Click, Right Button:Right Click, Middle Button:Mouse Middle Button} Key/Button Diversion (saved): {Middle Button:Regular, ?} Key/Button Diversion : {Middle Button:Regular} 9: CHANGE HOST {1814} V1 Change Host : 1:brandy 10: ADJUSTABLE DPI {2201} V1 Sensitivity (DPI) (saved): 1000 Sensitivity (DPI) : 1000 11: LOWRES WHEEL {2130} V0 Wheel Reports: HID Scroll Wheel Diversion (saved): False Scroll Wheel Diversion : False 12: DEVICE RESET {1802} V0 internal, hidden 13: unknown:1803 {1803} V0 internal, hidden 14: CONFIG DEVICE PROPS {1806} V5 internal, hidden 15: unknown:1812 {1812} V0 internal, hidden 16: OOBSTATE {1805} V0 internal, hidden 17: unknown:1830 {1830} V0 internal, hidden 18: unknown:1890 {1890} V4 internal, hidden 19: unknown:1891 {1891} V4 internal, hidden 20: unknown:18A1 {18A1} V0 internal, hidden 21: unknown:1DF3 {1DF3} V0 internal, hidden 22: unknown:1E00 {1E00} V0 hidden 23: unknown:1EB0 {1EB0} V0 internal, hidden 24: unknown:1861 {1861} V0 internal, hidden 25: unknown:18B1 {18B1} V0 internal, hidden Has 4 reprogrammable keys: 0: Left Button , default: Left Click => Left Click mse, reprogrammable, analytics key events, pos:0, group:1, group mask:g1 reporting: default 1: Right Button , default: Right Click => Right Click mse, reprogrammable, 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: 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: 50%, discharging, next level 20%. Solaar-1.1.20/docs/devices/Wireless Multi-Device Keyboard K780 405B.txt000066400000000000000000000232221522022367600247300ustar00rootroot00000000000000Solaar version 1.1.7 1: Wireless Multi-Device Keyboard K780 Device path : /dev/hidraw4 WPID : 405B Codename : K780 Kind : keyboard Protocol : HID++ 4.5 Polling rate : 20 ms (50Hz) Serial number: 3C60AFA4 Model ID: B33B405B0000 Unit ID: 6CAF50FF Bootloader: BOT 25.01.B0006 Firmware: MPK 01.04.B0028 Other: The power switch is located on the edge of top right corner. Supports 31 HID++ 2.0 features: 0: ROOT {0000} V0 1: FEATURE SET {0001} V0 2: DEVICE FW VERSION {0003} V2 Firmware: Bootloader BOT 25.01.B0006 0000C9839F9301 Firmware: Firmware MPK 01.04.B0028 405BC9839F9301 Firmware: Other Unit ID: 6CAF50FF Model ID: B33B405B0000 Transport IDs: {'btleid': 'B33B', 'wpid': '405B'} 3: DEVICE NAME {0005} V0 Name: K780 Multi-Device Wireless Keyboard Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} V0 5: RESET {0020} V0 6: DEVICE FRIENDLY NAME {0007} V0 Friendly Name: Keyboard K780 7: BATTERY STATUS {1000} V0 Battery: 90%, discharging, next level 50%. 8: CHANGE HOST {1814} V1 Cambiar Host : 1:romanescu 9: HOSTS INFO {1815} V1 Host 0 (paired): romanescu Host 1 (paired): espinaca Host 2 (unpaired): 10: REPROG CONTROLS V4 {1B04} V3 Desvio de Tecla/Botón (saved): {Volume Up:Normal, Volume Down:Normal, Mute:Normal, Play/Pause:Normal, Next:Normal, Previous:Normal, Multiplatform Back:Normal, Multiplatform Insert:Normal, Screen Capture/Print Screen:Normal, Fn Down:Normal, Fn Up:Normal, Multiplatform Lock:Normal, Host Switch Channel 1:Normal, Host Switch Channel 2:Normal, Host Switch Channel 3:Normal, MultiPlatform Search:Normal, MultiPlatform Home/Mission Control:Normal, MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad:Normal} Desvio de Tecla/Botón : {Volume Up:Normal, Volume Down:Normal, Mute:Normal, Play/Pause:Normal, Next:Normal, Previous:Normal, Multiplatform Back:Normal, Multiplatform Insert:Normal, Screen Capture/Print Screen:Normal, Fn Down:Normal, Fn Up:Normal, Multiplatform Lock:Normal, Host Switch Channel 1:Normal, Host Switch Channel 2:Normal, Host Switch Channel 3:Normal, MultiPlatform Search:Normal, MultiPlatform Home/Mission Control:Normal, MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad:Normal} 11: PERSISTENT REMAPPABLE ACTION {1C00} V0 Persistent Key/Button Mapping : {Volume Up:Volume Up, Volume Down:Volume Down, Mute:Mute, Play/Pause:Play/Pause, Next:Scan Next Track, Previous:Scan Previous Track, Multiplatform Back:AC Back, MultiPlatform Search:AC Search, MultiPlatform Home/Mission Control:AC Home, MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad:COMPOSE} 12: NEW FN INVERSION {40A2} V0 Fn-swap: enabled Fn-swap default: enabled Intercambiar función Fx (saved): True Intercambiar función Fx : True 13: ENCRYPTION {4100} V0 14: KEYBOARD DISABLE KEYS {4521} V0 Desactivar teclas (saved): {Caps Lock:False, Num Lock:False, Scroll Lock:False, Insert:False, Win:False} Desactivar teclas : {Caps Lock:False, Num Lock:False, Scroll Lock:False, Insert:False, Win:False} 15: MULTIPLATFORM {4531} V0 Especificar SO (saved): Windows Especificar SO : Windows 16: LOCK KEY STATE {4220} V0 17: DFUCONTROL SIGNED {00C2} V0 18: unknown:1803 {1803} V0 internal, hidden 19: CONFIG DEVICE PROPS {1806} V0 internal, hidden 20: OOBSTATE {1805} V0 internal, hidden 21: unknown:1813 {1813} V0 internal, hidden 22: unknown:1830 {1830} V0 internal, hidden 23: unknown:1861 {1861} V0 internal, hidden 24: unknown:1890 {1890} V0 internal, hidden 25: unknown:1891 {1891} V0 internal, hidden 26: unknown:18A1 {18A1} V0 internal, hidden 27: unknown:1DF3 {1DF3} V0 internal, hidden 28: unknown:1E00 {1E00} V0 hidden 29: unknown:1EB0 {1EB0} V0 internal, hidden 30: unknown:18B0 {18B0} V0 internal, hidden Has 19 reprogrammable keys: 0: Host Switch Channel 1 , default: HostSwitch Channel 1 => HostSwitch Channel 1 is FN, FN sensitive, divertable, persistently divertable, pos:1, group:0, group mask:empty reporting: default 1: Host Switch Channel 2 , default: HostSwitch Channel 2 => HostSwitch Channel 2 is FN, FN sensitive, divertable, persistently divertable, pos:2, group:0, group mask:empty reporting: default 2: Host Switch Channel 3 , default: HostSwitch Channel 3 => HostSwitch Channel 3 is FN, FN sensitive, divertable, persistently divertable, pos:3, group:0, group mask:empty reporting: default 3: MultiPlatform Home/Mission Control, default: Multiplatform Home/Mission Control => Multiplatform Home/Mission Control is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:4, group:0, group mask:empty reporting: default 4: MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad, default: Multiplatform Menu/Launchpad => Multiplatform Menu/Launchpad is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:5, group:0, group mask:empty reporting: default 5: Multiplatform Back , default: MultiPlatform Back => MultiPlatform Back is FN, FN sensitive, reprogrammable, divertable, persistently divertable, pos:6, group:0, group mask:empty reporting: default 6: Previous , default: Previous => Previous is FN, FN sensitive, divertable, persistently divertable, pos:7, group:0, group mask:empty reporting: default 7: Play/Pause , default: Play/Pause => Play/Pause is FN, FN sensitive, divertable, persistently divertable, pos:8, group:0, group mask:empty reporting: default 8: Next , default: Next => Next is FN, FN sensitive, divertable, persistently divertable, pos:9, group:0, group mask:empty reporting: default 9: Mute , default: Mute => Mute is FN, FN sensitive, divertable, persistently divertable, pos:10, group:0, group mask:empty reporting: default 10: Volume Down , default: Volume Down => Volume Down is FN, FN sensitive, divertable, persistently divertable, pos:11, group:0, group mask:empty reporting: default 11: Volume Up , default: Volume Up => Volume Up is FN, FN sensitive, divertable, persistently divertable, pos:12, group:0, group mask:empty reporting: default 12: MultiPlatform Search , default: Multiplatform Search => Multiplatform Search nonstandard, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 13: Multiplatform Insert , default: Switch Language => Switch Language is FN, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 14: Screen Capture/Print Screen, default: Screen Capture => Screen Capture is FN, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 15: Multiplatform Lock , default: WindowsLock => WindowsLock is FN, reprogrammable, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 16: Fn Up , default: unknown:0070 => unknown:0070 is FN, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 17: Fn Down , default: unknown:006F => unknown:006F is FN, divertable, persistently divertable, pos:0, group:0, group mask:empty reporting: default 18: FN Key , default: Do Nothing One => Do Nothing One nonstandard, pos:0, group:0, group mask:empty reporting: default Has 10 persistent remappable keys: 0: MultiPlatform Home/Mission Control => Consumer: AC Home 1: MultiPlatform Menu/Show/Hide Virtual Keyboard/Launchpad => Key: COMPOSE 2: Multiplatform Back => Consumer: AC Back 3: Previous => Consumer: Scan Previous Track 4: Play/Pause => Consumer: Play/Pause 5: Next => Consumer: Scan Next Track 6: Mute => Consumer: Mute 7: Volume Down => Consumer: Volume Down 8: Volume Up => Consumer: Volume Up 9: MultiPlatform Search => Consumer: AC Search Battery: 90%, discharging, next level 50%. Solaar-1.1.20/docs/devices/Wireless Rechargeable Touchpad T650 4101.txt000066400000000000000000000033631522022367600250000ustar00rootroot00000000000000 1: Wireless Rechargeable Touchpad T650 Codename : T650 Kind : touchpad Wireless PID : 4101 Protocol : HID++ 2.0 Polling rate : 8 ms (125Hz) Serial number: 22205A4D Firmware: RQM 41.01.B0037 Bootloader: BL 03.00 Hardware: 72 Other: The power switch is located on the base. Supports 23 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: FEATURE INFO {0002} 3: DEVICE FW VERSION {0003} 4: DEVICE NAME {0005} 5: BATTERY STATUS {1000} 6: WIRELESS DEVICE STATUS {1D4B} 7: unknown:1DF3 {1DF3} internal, hidden 8: REPROG CONTROLS {1B00} 9: unknown:1F03 {1F03} internal, hidden 10: VERTICAL SCROLLING {2100} 11: HI RES SCROLLING {2120} 12: MOUSE POINTER {2200} 13: DFUCONTROL {00C0} 14: unknown:1E80 {1E80} internal, hidden 15: TOUCHPAD RAW XY {6100} 16: unknown:1860 {1860} internal, hidden 17: unknown:1E00 {1E00} hidden 18: REPROG CONTROLS V2 {1B01} 19: unknown:1890 {1890} internal, hidden 20: unknown:18E5 {18E5} internal, hidden 21: unknown:18A0 {18A0} internal, hidden 22: unknown:1830 {1830} internal, hidden Has 2 reprogrammable keys: 0: LEFT CLICK => LeftClick mse, reprogrammable 1: RIGHT CLICK => RightClick mse, reprogrammable Battery: 50%, discharging. Solaar-1.1.20/docs/devices/Wireless Solar Keyboard K750 4002.txt000066400000000000000000000041051522022367600234700ustar00rootroot00000000000000 2: Wireless Solar Keyboard K750 Codename : K750 Kind : keyboard Wireless PID : 4002 Protocol : HID++ 2.0 Polling rate : 20 ms (50Hz) Serial number: 5692B2EC Firmware: RQK 33.00.B0015 Bootloader: DFU 00.02.B0003 The power switch is located on the edge of top right corner. Supports 11 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} 3: DEVICE NAME {0005} 4: REPROG CONTROLS {1B00} 5: WIRELESS DEVICE STATUS {1D4B} 6: unknown:1DF3 {1DF3} hidden 7: FN INVERSION {40A0} 8: ENCRYPTION {4100} 9: SOLAR DASHBOARD {4301} 10: KEYBOARD LAYOUT {4520} Has 12 reprogrammable keys: 0: MY HOME => HomePage FN sensitive, is FN, reprogrammable 1: Mail => Email FN sensitive, is FN, reprogrammable 2: SEARCH => Search FN sensitive, is FN, reprogrammable 3: Calculator => Calculator FN sensitive, is FN, reprogrammable 4: MEDIA PLAYER => Music FN sensitive, is FN, reprogrammable 5: Previous => Previous FN sensitive, is FN 6: Play/Pause => Play/Pause FN sensitive, is FN 7: Next => Next FN sensitive, is FN 8: Mute => Mute FN sensitive, is FN 9: Volume Down => Volume Down FN sensitive, is FN 10: Volume Up => Volume Up FN sensitive, is FN 11: SLEEP => Sleep FN sensitive, is FN, reprogrammable Battery status unavailable. Solaar-1.1.20/docs/devices/Wireless Touch Keyboard K400 4024.txt000066400000000000000000000055631522022367600234770ustar00rootroot00000000000000 1: Wireless Touch Keyboard K400 Codename : K400 Kind : keyboard Wireless PID : 4024 Protocol : HID++ 2.0 Polling rate : 8 ms (125Hz) Serial number: AC5F5069 Firmware: RQK 50.01.B0013 The power switch is located on the edge of top right corner. Supports 22 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: FEATURE INFO {0002} 3: DEVICE FW VERSION {0003} 4: DEVICE NAME {0005} 5: BATTERY STATUS {1000} 6: REPROG CONTROLS {1B00} 7: REPROG CONTROLS V3 {1B03} 8: WIRELESS DEVICE STATUS {1D4B} 9: unknown:1DF0 {1DF0} hidden 10: unknown:1DF3 {1DF3} internal, hidden 11: unknown:1E00 {1E00} hidden 12: VERTICAL SCROLLING {2100} 13: HI RES SCROLLING {2120} 14: MOUSE POINTER {2200} 15: FN INVERSION {40A0} 16: NEW FN INVERSION {40A2} 17: ENCRYPTION {4100} 18: KEYBOARD LAYOUT {4520} 19: TOUCHPAD FW ITEMS {6010} 20: TOUCHPAD SW ITEMS {6011} 21: TOUCHPAD WIN8 FW ITEMS {6012} Has 18 reprogrammable keys: 0: SHOW DESKTOP => ShowDesktop is FN, reprogrammable 1: METRO APPSWITCH => MetroAppSwitch is FN, reprogrammable 2: METRO SEARCH => MetroSearch is FN, reprogrammable 3: METRO SHARE => MetroShare is FN, reprogrammable 4: METRO DEVICES => MetroDevices is FN, reprogrammable 5: METRO SETTINGS => MetroSettings is FN, reprogrammable 6: Previous => Previous is FN 7: Play/Pause => Play/Pause is FN 8: Next => Next is FN 9: Mute => Mute is FN 10: Volume Down => Volume Down is FN 11: Volume Up => Volume Up is FN 12: MEDIA PLAYER => Music nonstandard, reprogrammable 13: Lock PC => WindowsLock nonstandard, reprogrammable 14: SLEEP => Sleep nonstandard, reprogrammable 15: LEFT CLICK => LeftClick mse 16: LEFT CLICK => LeftClick mse 17: RIGHT CLICK => RightClick mse Battery: 90%, discharging. Solaar-1.1.20/docs/devices/Wireless Touch Keyboard K400 Plus 404D.txt000066400000000000000000000230441522022367600244170ustar00rootroot00000000000000Solaar version 1.1.4 1: Wireless Touch Keyboard K400 Plus Device path : /dev/hidraw2 WPID : 404D Codename : K400 Plus Kind : keyboard Protocol : HID++ 4.1 Polling rate : 8 ms (125Hz) Serial number: 6DC5C81C Model ID: 404D00000000 Unit ID: 641487CF Bootloader: BOT 22.02.B0002 Firmware: RQK 63.02.B0016 Other: The power switch is located on the top edge. Supports 23 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: DEVICE FW VERSION {0003} Firmware: Bootloader BOT 22.02.B0002 404D6217AD40 Firmware: Firmware RQK 63.02.B0016 404D6217AD40 Firmware: Other Unit ID: 641487CF Model ID: 404D00000000 Transport IDs: {'wpid': '404D'} 3: DEVICE NAME {0005} Name: Wireless Touch Keyboard K400 Plus Kind: keyboard 4: WIRELESS DEVICE STATUS {1D4B} 5: RESET {0020} 6: BATTERY STATUS {1000} Battery: 90%, discharging, next level 50%. 7: REPROG CONTROLS V4 {1B04} Key/Button Diversion (saved): {Volume Up:Regular, Volume Down:Regular, Mute:Regular, Play/Pause:Regular, Next:Regular, Previous:Regular, Application Switcher:Regular, My Home:Regular, Media Player:Regular, Search:Regular, Sleep:Regular, Contextual Menu:Regular, Back:Regular, Show Desktop:Regular, Maximize Window:Regular, Switch Screen:Regular, Fn Left Click:Regular, Second Left Click:Regular} Key/Button Diversion : {Volume Up:Regular, Volume Down:Regular, Mute:Regular, Play/Pause:Regular, Next:Regular, Previous:Regular, Application Switcher:Regular, My Home:Regular, Media Player:Regular, Search:Regular, Sleep:Regular, Contextual Menu:Regular, Back:Regular, Show Desktop:Regular, Maximize Window:Regular, Switch Screen:Regular, Fn Left Click:Regular, Second Left Click:Regular} 8: SWAP BUTTON CANCEL {2005} 9: NEW FN INVERSION {40A2} Fn-swap: enabled Fn-swap default: enabled Swap Fx function (saved): True Swap Fx function : True 10: ENCRYPTION {4100} 11: KEYBOARD DISABLE KEYS {4521} Disable keys (saved): {Caps Lock:False, Scroll Lock:False, Insert:False, Win:False} Disable keys : {Caps Lock:False, Scroll Lock:False, Insert:False, Win:False} 12: TOUCHPAD RAW XY {6100} 13: GESTURE 2 {6501} Gestures (saved): {1: True, 2: True, 10: True, 30: True, 34: False, 42: True, 43: True, 45: False, 64: False, 65: False, 67: False, 84: True} Gestures : {1: True, 2: True, 30: True, 10: True, 45: False, 42: True, 43: True, 64: False, 65: False, 67: False, 84: True, 34: False} Gestures Diversion (saved): {1: False, 2: False, 10: False, 44: False, 64: False, 65: False, 67: False, 84: False, 85: False, 100: False} Gestures Diversion : {1: False, 2: False, 10: False, 44: False, 64: False, 65: False, 67: False, 84: False, 85: False, 100: False} Gesture params (saved): {4: {'scale': 256}} Gesture params : {4: {'scale': 256}} 14: DFUCONTROL UNSIGNED {00C1} 15: unknown:1811 {1811} internal, hidden 16: unknown:1830 {1830} internal, hidden 17: unknown:1890 {1890} internal, hidden 18: unknown:1DF3 {1DF3} internal, hidden 19: unknown:1E00 {1E00} hidden 20: unknown:1EB0 {1EB0} internal, hidden 21: unknown:1861 {1861} internal, hidden 22: unknown:18B0 {18B0} internal, hidden Has 20 reprogrammable keys: 0: Second Left Click , default: Left Click => Left Click mse, divertable, pos:0, group:0, group mask:empty reporting: default 1: Back , default: Mouse Back Button => Mouse Back Button is FN, FN sensitive, reprogrammable, divertable, pos:1, group:0, group mask:empty reporting: default 2: My Home , default: HomePage => HomePage is FN, FN sensitive, reprogrammable, divertable, pos:2, group:0, group mask:empty reporting: default 3: Application Switcher , default: Application Switcher => Application Switcher is FN, FN sensitive, reprogrammable, divertable, pos:3, group:0, group mask:empty reporting: default 4: Contextual Menu , default: Right Click => Right Click is FN, FN sensitive, reprogrammable, divertable, pos:4, group:0, group mask:empty reporting: default 5: Search , default: Search Files => Search Files is FN, FN sensitive, reprogrammable, divertable, pos:5, group:0, group mask:empty reporting: default 6: Show Desktop , default: Show Desktop => Show Desktop is FN, FN sensitive, reprogrammable, divertable, pos:6, group:0, group mask:empty reporting: default 7: Maximize Window , default: Maximize Window => Maximize Window is FN, FN sensitive, reprogrammable, divertable, pos:7, group:0, group mask:empty reporting: default 8: Switch Screen , default: Switch Presentation/Switch Screen => Switch Presentation/Switch Screen is FN, FN sensitive, reprogrammable, divertable, pos:8, group:0, group mask:empty reporting: default 9: Media Player , default: Music => Music is FN, FN sensitive, reprogrammable, divertable, pos:9, group:0, group mask:empty reporting: default 10: Previous , default: Previous => Previous is FN, FN sensitive, divertable, pos:10, group:0, group mask:empty reporting: default 11: Play/Pause , default: Play/Pause => Play/Pause is FN, FN sensitive, divertable, pos:11, group:0, group mask:empty reporting: default 12: Next , default: Next => Next is FN, FN sensitive, divertable, pos:12, group:0, group mask:empty reporting: default 13: Fn Left Click , default: ShowUI => ShowUI divertable, pos:0, group:0, group mask:empty reporting: default 14: Left Button , default: Left Click => Left Click mse, pos:0, group:0, group mask:empty reporting: default 15: Right Button , default: Right Click => Right Click mse, pos:0, group:0, group mask:empty reporting: default 16: Mute , default: Mute => Mute nonstandard, divertable, pos:0, group:0, group mask:empty reporting: default 17: Volume Up , default: Volume Up => Volume Up nonstandard, divertable, pos:0, group:0, group mask:empty reporting: default 18: Volume Down , default: Volume Down => Volume Down nonstandard, divertable, pos:0, group:0, group mask:empty reporting: default 19: Sleep , default: Sleep => Sleep reprogrammable, divertable, pos:0, group:0, group mask:empty reporting: default Has 17 gesture(s), 1 param(s) and 5 spec(s): Track1Finger Enabled(None): None Diverted:(None) None TapGestures Enabled(None): None Diverted:(None) None Tap1Finger Enabled( 0): True Diverted:( 0) False Tap2Finger Enabled( 1): True Diverted:( 1) False TapDrag1Finger Enabled( 2): True Diverted:(None) None DoubleTap1Finger Enabled( 3): True Diverted:( 2) False Scroll2FingerStateless Enabled(None): None Diverted:( 3) False NaturalScrolling Enabled( 4): False Diverted:(None) None Scroll2FingerHoriz Enabled( 5): True Diverted:(None) None Scroll2FingerVert Enabled( 6): True Diverted:(None) None LeftEdgeSwipe1Finger2 Enabled( 7): False Diverted:( 4) False RightEdgeSwipe1Finger2 Enabled( 8): False Diverted:( 5) False TopEdgeSwipe1Finger2 Enabled( 9): False Diverted:( 6) False Zoom2FingerStateless Enabled( 10): True Diverted:( 7) False FnClickGestureSuppression Enabled( 11): False Diverted:(None) None TwoFingersPresent Enabled(None): None Diverted:( 8) False DeviceSpecificRawData Enabled(None): None Diverted:( 9) False ScaleFactor Value ( 0): 256 [Default: 256] period unit Spec ( 3): 2048 DVI field width Spec ( 1): 8 field widths Spec ( 2): 8 multiplier Spec ( 5): None resolution Spec ( 4): 4 Battery: 90%, discharging, next level 50%. Solaar-1.1.20/docs/devices/Wireless Trackball M570 1028.txt000066400000000000000000000006271522022367600226020ustar00rootroot00000000000000 1: Wireless Trackball M570 Codename : M570 Kind : mouse Wireless PID : 1028 Protocol : HID++ 1.0 Polling rate : 8 ms (125Hz) Serial number: 891DC05A Firmware: 26.00.B0003 Bootloader: 02.06 Other: 00.01 The power switch is located on the base. Notifications: battery status (0x100000). Battery: 100%, discharging. Solaar-1.1.20/docs/devices/Zone Touch Mouse T400 4026.txt000066400000000000000000000047101522022367600221510ustar00rootroot00000000000000 1: Zone Touch Mouse T400 Codename : T400 Kind : mouse Wireless PID : 4026 Protocol : HID++ 2.0 Polling rate : 8 ms (125Hz) Serial number: 131A3093 Firmware: RQM 39.00.B0029 Bootloader: BL 03.00 Hardware: 72 Other: The power switch is located on the base. Supports 28 HID++ 2.0 features: 0: ROOT {0000} 1: FEATURE SET {0001} 2: FEATURE INFO {0002} 3: DEVICE FW VERSION {0003} 4: DEVICE NAME {0005} 5: DFUCONTROL {00C0} 6: BATTERY STATUS {1000} 7: unknown:1802 {1802} internal, hidden 8: unknown:1810 {1810} internal, hidden 9: unknown:1830 {1830} internal, hidden 10: unknown:1850 {1850} internal, hidden 11: unknown:1860 {1860} internal, hidden 12: unknown:1890 {1890} internal, hidden 13: unknown:18A0 {18A0} internal, hidden 14: unknown:18E3 {18E3} internal, hidden 15: REPROG CONTROLS {1B00} 16: WIRELESS DEVICE STATUS {1D4B} 17: unknown:1DF3 {1DF3} internal, hidden 18: unknown:1E00 {1E00} hidden 19: unknown:1E80 {1E80} internal, hidden 20: unknown:1F03 {1F03} internal, hidden 21: unknown:1F04 {1F04} internal, hidden 22: VERTICAL SCROLLING {2100} 23: unknown:2101 {2101} hidden 24: HI RES SCROLLING {2120} 25: MOUSE POINTER {2200} 26: TOUCHMOUSE RAW POINTS {6110} hidden 27: REPROG CONTROLS V3 {1B03} Has 7 reprogrammable keys: 0: LEFT CLICK => LeftClick mse, reprogrammable 1: RIGHT CLICK => RightClick mse, reprogrammable 2: MIDDLE BUTTON => MiddleMouseButton mse, reprogrammable 3: METRO START SCREEN => MetroStartScreen mse, reprogrammable 4: ZOOMIN => Do Nothing mse, reprogrammable 5: ZOOMOUT => Do Nothing mse, reprogrammable 6: BACK HSCROLL => TouchBackForwardHorzScroll Battery: 100%, discharging. Solaar-1.1.20/docs/devices/anywhere-mx.txt000066400000000000000000000054141522022367600204040ustar00rootroot00000000000000Receiver LZ301AR-DJ M/N:C-U0007 (ltunify) Serial number: D1759614 Firmware version: 012.001.00019 Bootloader version: BL.002.014 Supported notification flags: 00 09 00 - 01: Wireless Notifications - 08: Software Present Mouse (ltunify) HID++ version: 1.0 Device index 1 Mouse Name: Anywhere MX Wireless Product ID: 1017 Serial number: 13865F99 Firmware version: 016.001.00040 Bootloader version: BL.002.010 (solaar) Unifying Receiver Device path : /dev/hidraw2 USB id : 046d:c52b Serial : D1759614 Firmware : 12.01.B0019 Bootloader : 02.14 Has 1 paired device(s) out of a maximum of 6. Notifications: (none) Device activity counters: 1=19 1: Anywhere Mouse MX Codename : Anywhere MX Kind : mouse Wireless PID : 1017 Protocol : HID++ 1.0 Polling rate : 8 ms (125Hz) Serial number: 13865F99 Firmware: 16.01.B0040 Bootloader: 02.10 Other: 00.06 The power switch is located on the base. Notifications: (none). Battery: 100%, discharging. (scan-registers) # Old notification flags: 000000 # 00 - Enabled Notifications, supported flags: Battery Status (10) >> ( 0.792) [10 01 8100 100000] b'\x10\x01\x81\x00\x10\x00\x00' # 01 - scrolling settings? # Flags: # 0x40 - Enable Smooth Scrolling # 0x02 - "confuse KDE", see https://github.com/pwr/Solaar/issues/115 << ( 0.011) [10 01 8101 000000] b'\x10\x01\x81\x01\x00\x00\x00' >> ( 1.710) [10 01 8101 020000] b'\x10\x01\x81\x01\x02\x00\x00' # 0D - battery level. first byte is remaining charge in percent; second is # (guessed) maximum?; third is charge status (30=discharging) # "10 ix 0D 64 64 30 00" is a battery notification (when enabled) << ( 9.789) [10 01 810D 000000] b'\x10\x01\x81\r\x00\x00\x00' >> ( 9.816) [10 01 810D 646430] b'\x10\x01\x81\rdd0' # 63 - DPI (range 0x80-0x8e (inclusive)) << ( 75.521) [10 01 8163 000000] b'\x10\x01\x81c\x00\x00\x00' >> ( 75.550) [10 01 8163 890000] b'\x10\x01\x81c\x89\x00\x00' # D0 - ? << ( 163.118) [10 01 81D0 000000] b'\x10\x01\x81\xd0\x00\x00\x00' >> ( 163.148) [10 01 81D0 000000] b'\x10\x01\x81\xd0\x00\x00\x00' # D4 - ? << ( 166.034) [10 01 81D4 000000] b'\x10\x01\x81\xd4\x00\x00\x00' >> ( 166.063) [10 01 81D4 000008] b'\x10\x01\x81\xd4\x00\x00\x08' # F1 - firmware/bootloader version << ( 187.172) [10 01 81F1 000000] b'\x10\x01\x81\xf1\x00\x00\x00' >> ( 187.199) [10 01 8F81 F10300] b'\x10\x01\x8f\x81\xf1\x03\x00' # F3 - ? << ( 188.629) [10 01 81F3 000000] b'\x10\x01\x81\xf3\x00\x00\x00' >> ( 188.661) [10 01 81F3 000000] b'\x10\x01\x81\xf3\x00\x00\x00' # FD - ? << ( 195.715) [10 01 83FD 000000] b'\x10\x01\x83\xfd\x00\x00\x00' >> ( 195.746) [11 01 83FD 00000000000000000000000000000000] b'\x11\x01\x83\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' Solaar-1.1.20/docs/devices/mk700.txt000066400000000000000000000017471522022367600170030ustar00rootroot00000000000000# Enabled Notifications # 10 - battery status # 02 + 01 - remap FN keys (multimedia + power buttons) >> ( 1.412) [10 02 8100 130000] '\x10\x02\x81\x00\x13\x00\x00' << ( 0.011) [10 02 8101 000000] '\x10\x02\x81\x01\x00\x00\x00' >> ( 0.276) [10 02 8101 000000] '\x10\x02\x81\x01\x00\x00\x00' # Battery status << ( 6.033) [10 02 8107 000000] '\x10\x02\x81\x07\x00\x00\x00' >> ( 6.344) [10 02 8107 070000] '\x10\x02\x81\x07\x07\x00\x00' # FN status << ( 8.055) [10 02 8109 000000] '\x10\x02\x81\t\x00\x00\x00' >> ( 8.144) [10 02 8109 000000] '\x10\x02\x81\t\x00\x00\x00' # ? << ( 208.316) [10 02 81D0 000000] '\x10\x02\x81\xd0\x00\x00\x00' >> ( 208.353) [10 02 81D0 000000] '\x10\x02\x81\xd0\x00\x00\x00' # version info << ( 237.436) [10 02 81F1 000000] '\x10\x02\x81\xf1\x00\x00\x00' >> ( 237.744) [10 02 8F81 F10300] '\x10\x02\x8f\x81\xf1\x03\x00' # ? << ( 239.459) [10 02 81F3 000000] '\x10\x02\x81\xf3\x00\x00\x00' >> ( 239.766) [10 02 81F3 000000] '\x10\x02\x81\xf3\x00\x00\x00' Solaar-1.1.20/docs/devices/performance-mx.txt000066400000000000000000000027451522022367600210670ustar00rootroot00000000000000# Notifications (r1_bit0 = battery status?) << ( 0.113) [10 01 8100 000000] '\x10\x01\x81\x00\x00\x00\x00' >> ( 0.157) [10 01 8100 100000] '\x10\x01\x81\x00\x10\x00\x00' # ? << ( 1.050) [10 01 8101 000000] '\x10\x01\x81\x01\x00\x00\x00' >> ( 1.097) [10 01 8101 020000] '\x10\x01\x81\x01\x02\x00\x00' # battery (07 means full) << ( 7.335) [10 01 8107 000000] '\x10\x01\x81\x07\x00\x00\x00' >> ( 7.382) [10 01 8107 070000] '\x10\x01\x81\x07\x07\x00\x00' # Set LEDS - ab cd 00, where a/b/c/d values are 1=off, 2=on, 3=flash # a = lower led # b = red led # c = upper led # d = middle led # below: all leds are off << ( 86.592) [10 01 8151 000000] '\x10\x01\x81Q\x00\x00\x00' >> ( 86.639) [10 01 8151 111100] '\x10\x01\x81Q\x11\x11\x00' # DPI (values in range 0x81..0x8F; logical value: 100..1500) << ( 108.430) [10 01 8163 000000] '\x10\x01\x81c\x00\x00\x00' >> ( 108.477) [10 01 8163 890000] '\x10\x01\x81c\x89\x00\x00' # ? << ( 240.505) [10 01 81D0 000000] '\x10\x01\x81\xd0\x00\x00\x00' >> ( 240.550) [10 01 81D0 000000] '\x10\x01\x81\xd0\x00\x00\x00' # ? << ( 245.690) [10 01 81D4 000000] '\x10\x01\x81\xd4\x00\x00\x00' >> ( 245.737) [10 01 81D4 000012] '\x10\x01\x81\xd4\x00\x00\x12' # Firmware/bootloader version << ( 281.016) [10 01 81F1 000000] '\x10\x01\x81\xf1\x00\x00\x00' >> ( 282.177) [10 01 8F81 F10300] '\x10\x01\x8f\x81\xf1\x03\x00' # ? << ( 284.106) [10 01 81F3 000000] '\x10\x01\x81\xf3\x00\x00\x00' >> ( 284.153) [10 01 81F3 000000] '\x10\x01\x81\xf3\x00\x00\x00' Solaar-1.1.20/docs/features.md000066400000000000000000000333241522022367600161160ustar00rootroot00000000000000--- title: List of HID++ 2.0 features layout: page --- # List of HID++ 2.0 features ## Feature status See functions in `hidpp20.py` and `settings_templates.py` Feature | ID | Status | Notes ---------------------------------------|----------|:------------------:|------ `ROOT` | `0x0000` | Supported | System `FEATURE_SET` | `0x0001` | Supported | System `FEATURE_INFO` | `0x0002` | Supported | System `DEVICE_FW_VERSION` | `0x0003` | Supported | `get_firmware`, `get_ids`, read only `DEVICE_UNIT_ID` | `0x0004` | Unsupported | `DEVICE_NAME` | `0x0005` | Supported | `get_kind`, `get_name`, read only `DEVICE_GROUPS` | `0x0006` | Unsupported | `DEVICE_FRIENDLY_NAME` | `0x0007` | Supported | `get_friendly_name`, read only `KEEP_ALIVE` | `0x0008` | Unsupported | `RESET` | `0x0020` | Unsupported | aka “Config Change” `CRYPTO_ID` | `0x0021` | Unsupported | `TARGET_SOFTWARE` | `0x0030` | Unsupported | `WIRELESS_SIGNAL_STRENGTH` | `0x0080` | Unsupported | `DFUCONTROL_LEGACY` | `0x00C0` | Unsupported | `DFUCONTROL_UNSIGNED` | `0x00C1` | Unsupported | `DFUCONTROL_SIGNED` | `0x00C2` | Unsupported | `DFU` | `0x00D0` | Unsupported | `BATTERY_STATUS` | `0x1000` | Supported | `get_battery`, read only `BATTERY_VOLTAGE` | `0x1001` | Supported | `get_voltage`, read only `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` | Supported | `Backlight` `BACKLIGHT2` | `0x1982` | Supported | `Backlight2`, ... `BACKLIGHT3` | `0x1983` | Unsupported | `PRESENTER_CONTROL` | `0x1A00` | Unsupported | `SENSOR_3D` | `0x1A01` | Unsupported | `REPROG_CONTROLS` | `0x1B00` | Unsupported | `REPROG_CONTROLS_V2` | `0x1B01` | Listing Only | `get_keys` `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` | Supported | `ADCPower` `LEFT_RIGHT_SWAP` | `0x2001` | Unsupported | `SWAP_BUTTON_CANCEL` | `0x2005` | Unsupported | `POINTER_AXIS_ORIENTATION` | `0x2006` | Unsupported | `VERTICAL_SCROLLING` | `0x2100` | Supported | `get_vertical_scrolling_info`, read only `SMART_SHIFT` | `0x2110` | Supported | `SmartShift` `SMART_SHIFT_ENHANCED` | `0x2111` | Supported | `SmartShiftEnhanced` `HI_RES_SCROLLING` | `0x2120` | Supported | `HiResScroll`, `get_hi_res_scrolling_info` `HIRES_WHEEL` | `0x2121` | Supported | `HiresSmoothInvert`, `HiresSmoothResolution`, `get_hires_wheel` `LOWRES_WHEEL` | `0x2130` | Supported | `LowresSmoothScroll`, `get_lowres_wheel_status` `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 `K375S_FN_INVERSION` | `0x40A3` | Supported | `K375sFnSwap` `ENCRYPTION` | `0x4100` | Unsupported | `LOCK_KEY_STATE` | `0x4220` | Unsupported | `SOLAR_DASHBOARD` | `0x4301` | Unsupported | `KEYBOARD_LAYOUT` | `0x4520` | Unsupported | read only `KEYBOARD_DISABLE_KEYS` | `0x4521` | Supported | `DisableKeyboardKeys` `KEYBOARD_DISABLE_BY_USAGE` | `0x4522` | Unsupported | `DUALPLATFORM` | `0x4530` | Supported | `Dualplatform`, untested `MULTIPLATFORM` | `0x4531` | Supported | `Multiplatform` `KEYBOARD_LAYOUT_2` | `0x4540` | Unsupported | read only `CROWN` | `0x4600` | Supported | `DivertCrown`, `CrownSmooth` `TOUCHPAD_FW_ITEMS` | `0x6010` | Unsupported | `TOUCHPAD_SW_ITEMS` | `0x6011` | Unsupported | `TOUCHPAD_WIN8_FW_ITEMS` | `0x6012` | Unsupported | `TAP_ENABLE` | `0x6020` | Unsupported | `TAP_ENABLE_EXTENDED` | `0x6021` | Unsupported | `CURSOR_BALLISTIC` | `0x6030` | Unsupported | `TOUCHPAD_RESOLUTION` | `0x6040` | Unsupported | `TOUCHPAD_RAW_XY` | `0x6100` | Unsupported | `TOUCHMOUSE_RAW_POINTS` | `0x6110` | Unsupported | `TOUCHMOUSE_6120` | `0x6120` | Unsupported | `GESTURE` | `0x6500` | Unsupported | `GESTURE_2` | `0x6501` | Partial Support | `Gesture2Gestures`, `Gesture2Params` `GKEY` | `0x8010` | Partial Support | `DivertGkeys` `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` | Supported | `PerKeyLighting` `MODE_STATUS` | `0x8090` | Unsupported | `ONBOARD_PROFILES` | `0x8100` | Supported | `MOUSE_BUTTON_SPY` | `0x8110` | Unsupported | `LATENCY_MONITORING` | `0x8111` | Unsupported | `GAMING_ATTACHMENTS` | `0x8120` | Unsupported | `FORCE_FEEDBACK` | `0x8123` | 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. ## Implementing a feature Features are implemented as settable features in `lib/logitech_receiver/settings_templates.py`. Some features also have direct implementation in `lib/logitech_receiver/hidpp20.py`. In most cases it should suffice to only implement the settable feature interface for each setting in the feature. That will add one or more widgets in the Solaar main window to show and change the setting, will permit storing and restoring changed settings, and will output the feature settings in `solaar show`. A setting implementation is a subclass of one of the built-in setting classes illustrated by the pointer speed setting implementation. ```python class PointerSpeed(_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} ``` A setting implementation needs a name, a label, and a description. The name is used in the persistent settings structure to store and restore changed settings and should be a valid Python identifier. (Some older settings have dashes.) The label is displayed in the Solaar main window and the description is used as a tooltip there. The label and description should be specified as translatable strings. A setting implementation for a feature (for modern devices that use the HID++ 2.0 protocol) needs a feature identifier. A setting implementation needs a reader/writer and a validator. The reader/writer is responsible for actually writing settings to the device and reading them from the device, writing and reading the byte strings that represent the setting values on the device. For most feature settings the setting implementation can just inherit the standard feature reader/writer, `FeatureRW`. Options for `FeatureRW` are supplied by the `rw_options` class variable, which is used to provide command numbers for reading and writing as well as other information needed to identify the parts of the command and response that hold the setting value and modify the reading and writing procedure. `PointerSpeed` uses the defaults; here is an example of specifying non-default commands for reading and writing: ``` rw_options = {'read_fnid': 0x10, 'write_fnid': 0x20} ``` Some old devices use registers instead and the setting needs to use the register reader/writer. Only implement a register interface for the setting if you are very brave and you have access to a device that has a register interface for the setting. Register interfaces cannot be auto-discovered and need to be stated in descriptors.py for each device with the register interface. The validator instance is responsible for turning raw values read from the device into Python data and Python data into raw values to be written to the device and validating that the Python data is acceptable for the setting. There are several possible kinds of Python data for setting interfaces, ranging from simple toggles, to ranges, to fixed lists, to dynamic choices, to maps of dynamic choices. Pointer speed is a setting whose values are integers in a range so _RangeV validator is used. Arguments to validators are specified as class variables. The _RangeV validator requires the minimum and maximum for the value as separate class variables and the byte size of the value on the device as part of `validator_options`. Splitting the minimum and maximum makes it easier for code that works with settings to determine this information. Settings that are toggles or choices work similarly, but their validators have different arguments. Map settings have more complicated validators and more arguments. Settings where the acceptable values are determined from the device subclass the validator and provide a build class method that queries the device and creates an instance of the validator. This method can also return `None`, indicating that even though the device implements the feature it does not usefully support the setting. Settings need to be added to the `SETTINGS` list so that setting discovery can be done. For more information on implementing feature settings see the comments in `lib/logitech_receiver/settings_templates.py`. Solaar-1.1.20/docs/hidpp-documentation.txt000066400000000000000000000002061522022367600204630ustar00rootroot00000000000000Documentation on HID++ can be found in the Google drive directory https://drive.google.com/drive/folders/0BxbRzx7vEV7eWmgwazJ3NUFfQ28 Solaar-1.1.20/docs/i18n.md000066400000000000000000000070761522022367600150640ustar00rootroot00000000000000--- title: Translating Solaar layout: page --- # Translating Solaar 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): 1. Get an up-to-date copy of the source files. Preferably, make a fork on GitHub and clone it locally on your machine; this way you can later make a pull request to the main project. 2. Run `./tools/po-update.sh `; it will create/update the file `./po/.po`. 3. Edit `./po/.po` with your favorite editor (just make sure it saves the file with the UTF-8 encoding). For each string in English (msgid), edit the translation (msgstr); if you leave msgstr empty, the string will remain untranslated. 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. 5. Start Solaar (`./bin/solaar`). By default, it will pick up the system language from your environment; to start it in another language, run `LANGUAGE= ./bin/solaar`. To edit the translation iteratively, just repeat from step 3. If the upstream changes, do a `git pull` and then repeat from step 2. Before opening a pull request, please run `./tools/po-update.sh ` again. This will format and sort the translation file, and ensure a minimal diff when updating a translation. ## Supported languages 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] - 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] - 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], Anton Soroko - Serbian: [Renato Kaurić][renatoka] - Slovak: [Jose Riha][jose1711] - Spanish, Castilian: Jose Luis Tirado - Swedish: John Erling Blad, [Daniel Zippert][zipperten], Emelie Snecker, Jonatan Nyberg - Turkish: Osman Karagöz - Ukrainian: Олександр Афанасьєв - Bulgarian Николай Йорданов - Irish: Aindriú Mac Giolla Eoin [Rongronggg9]: https://github.com/Rongronggg9 [papoteur]: https://github.com/papoteur [david-geiger]: https://github.com/david-geiger [damsweb]: https://github.com/damsweb [DJm00n]: https://github.com/DJm00n [jose1711]: https://github.com/jose1711 [nexces]: https://github.com/nexces [zipperten]: https://github.com/zipperten [micheleolivo]: https://github.com/micheleolivo [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 Solaar-1.1.20/docs/img/000077500000000000000000000000001522022367600145255ustar00rootroot00000000000000Solaar-1.1.20/docs/img/favicon.ico000066400000000000000000000033541522022367600166530ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYs A AnktEXtSoftwarewww.inkscape.org<iIDATXW[p[W]>$Yp-[CN'~G5ChLa(4| e 0v#a&Ӧ'Nbc'D,[J$+dY+Ç-UrCÞ3>k9w}6`0S^K7$_O9 l ;͋ HcfGEē럐o"vwMY(*1yH.NA >ysQ'?lmmmuv&0LUec;)"sYvZI_Yw0 >.h6p#SEY$(&nu|"H`:*My!(ѴQXȻܡ7+]w~s:W "*?1'.q[өfuIJEBd%+ C=rw}tH_po|+܏O|Ng`mTg;Ŭg49SzIS q{Q\ַ:>vAymr/V}<+}ꐤ.n-\z4C' &-gNJ[*|W1'bݕsTYq嫦eo :m.2P,k^vj}"/K*e @q{ހB4RHc/cnr2? A]V,* ]XOPN>X%Ypy^edWP_אhnEF^zYrI33Jz}l0H~ Qѿ;ʳH @Ҭ G _8y[%, ͂(cn{, ah02 t SNq41ի+VqfvZ4 imm1XsZݳQ S,4Ua/G+G) DYDCC^;{'%9xrD . gjkgmBٟn|[`:n4=Gei77ۉWJm’҂QbDw?wy4yH֤dNwB1>fv%Z,߈rS1,ДYqӽ6g{Bi45$(dUi06\LZ-ρ@ԂzO~Ų-J%M5"}Wϝ60Wҍ QQJ-[[ G~=6bٖ"ɑDR #nkщۚ5:{{Kacv뼾&lXj$Xxod2+s7`$z;[9q*bRu5 p*&7jb1p#bKy3R\q*xb)\qDM1 4Q\߾wzraNY_M $9o쓽DeWܾs3GũIENDB`Solaar-1.1.20/docs/img/favicon.png000066400000000000000000000033541522022367600166650ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYs A AnktEXtSoftwarewww.inkscape.org<iIDATXW[p[W]>$Yp-[CN'~G5ChLa(4| e 0v#a&Ӧ'Nbc'D,[J$+dY+Ç-UrCÞ3>k9w}6`0S^K7$_O9 l ;͋ HcfGEē럐o"vwMY(*1yH.NA >ysQ'?lmmmuv&0LUec;)"sYvZI_Yw0 >.h6p#SEY$(&nu|"H`:*My!(ѴQXȻܡ7+]w~s:W "*?1'.q[өfuIJEBd%+ C=rw}tH_po|+܏O|Ng`mTg;Ŭg49SzIS q{Q\ַ:>vAymr/V}<+}ꐤ.n-\z4C' &-gNJ[*|W1'bݕsTYq嫦eo :m.2P,k^vj}"/K*e @q{ހB4RHc/cnr2? A]V,* ]XOPN>X%Ypy^edWP_אhnEF^zYrI33Jz}l0H~ Qѿ;ʳH @Ҭ G _8y[%, ͂(cn{, ah02 t SNq41ի+VqfvZ4 imm1XsZݳQ S,4Ua/G+G) DYDCC^;{'%9xrD . gjkgmBٟn|[`:n4=Gei77ۉWJm’҂QbDw?wy4yH֤dNwB1>fv%Z,߈rS1,ДYqӽ6g{Bi45$(dUi06\LZ-ρ@ԂzO~Ų-J%M5"}Wϝ60Wҍ QQJ-[[ G~=6bٖ"ɑDR #nkщۚ5:{{Kacv뼾&lXj$Xxod2+s7`$z;[9q*bRu5 p*&7jb1p#bKy3R\q*xb)\qDM1 4Q\߾wzraNY_M $9o쓽DeWܾs3GũIENDB`Solaar-1.1.20/docs/img/solaar.svg000066400000000000000000000072651522022367600165410ustar00rootroot00000000000000 image/svg+xml Solaar-1.1.20/docs/implementation.md000066400000000000000000000403221522022367600173210ustar00rootroot00000000000000--- 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 Solaar-1.1.20/docs/index.md000066400000000000000000000205301522022367600154020ustar00rootroot00000000000000--- title: Solaar layout: default --- **Solaar** is a Linux manager for many Logitech keyboards, mice, and trackpads that connect wirelessly to a USB [Unifying][unifying], Bolt, Lightspeed, or Nano receiver; connect directly via a USB cable; or connect via Bluetooth. Solaar does not work with peripherals from other companies. Documentation here is for the current version of Solaar. Some Linux distributions distribute old versions of Solaar. If you are using an old version and something described here does not work you should upgrade 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++ and Centurion) commands. Each Logitech device implements a different subset of these commands. 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 mouse movements or keycodes by Linux drivers or other software. Solaar can be used as a GUI application, the usual case, or via its command-line interface. The Solaar GUI is meant to run continuously in the background, monitoring devices, making changes to them, and responding to some messages they emit. To this end, it is useful to have Solaar start at user login so that changes made to devices by Solaar are applied at login and throughout the user's session. Both Solaar interfaces are able to list the connected devices and show information about each device, often including battery status. Solaar is able to pair and unpair devices with receivers as supported by the device and receiver. Solaar can also control some changeable settings of devices, such as scroll wheel direction and function key behavior. Solaar keeps track of most of these settings on a per-computer basis, because devices forget most settings when powered down, and the GUI application restores them whenever a device connects. For more information on how to use Solaar see [the usage page](https://pwr-solaar.github.io/Solaar/usage), and for more information on its capabilities see [the capabilities page](https://pwr-solaar.github.io/Solaar/capabilities). 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 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 - `--window=show` (the default) starts with its main window visible, - `--window=hide` starts with its main window hidden, - `--window=only` does not use the system tray, and starts with main window visible. For more information on Solaar's command-line interface use the help option, as in `solaar --help`. Solaar has progressed past version 1.1. Problems with earlier versions should not be reported as bugs. Instead, upgrade to a recent version or manually install the current version from [GitHub](https://github.com/pwr-Solaar/Solaar). Some capabilities of Solaar have been developed by observing the behavior of Logitech receivers and devices and generalizing from these observations. If your Logitech receiver or device behaves strangely this may be caused by an incorrect behavior generalization. Please report such experiences by creating an issue in [the Solaar repository](https://github.com/pwr-Solaar/Solaar/issues). [unifying]: https://en.wikipedia.org/wiki/Logitech_Unifying_receiver ## Supported Devices 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 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]) with any Unifying receiver, and pair and unpair a Logitech device showing the Bolt logo with any Bolt receiver, and can pair and unpair Lightspeed devices with Lightspeed receivers for the same model. Solaar can pair some Logitech devices with Logitech Nano receivers, but not all Logitech devices can be paired with Nano receivers. Logitech devices without a Unifying or Bolt logo generally cannot be paired with Unifying or Bolt receivers. Solaar does not handle connecting or disconnecting via Bluetooth, 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/img/solaar.svg ## Prebuilt packages Up-to-date prebuilt packages are available for some Linux distros (e.g., Fedora 33+) 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][ppa2] - NixOS Flake package in [Svenum/Solaar-Flake][nix flake] Solaar is available from some other repositories 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 - a [Mageia package][mageia], courtesy of David Geiger Solaar uses a standard system tray implementation; solaar-gnome3 is no longer required for Gnome or Unity integration. [ppa4]: https://launchpad.net/~trebelnik-stefina [ppa2]: 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 [universe repository]: http://packages.ubuntu.com/search?keywords=solaar&searchon=names&suite=all§ion=all [nix flake]: https://github.com/Svenum/Solaar-Flake ## Manual installation See [the installation page](https://pwr-solaar.github.io/Solaar/installation) for the step-by-step procedure for manual installation. ## License This software is distributed under the terms of the [GNU Public License, v2](LICENSE.txt), or later. ## Contributing to Solaar Contributions to Solaar are very welcome. Solaar has complete or partial translations of its GUI strings in several languages. If you want to update a translation or add a new one see [the translation page](https://pwr-solaar.github.io/Solaar/i18n) for more information. If you find a bug, please check first if it has already been reported. If yes, please add additional information you may have to the existing issue. If not, please open a new bug report issue. If you can provide a fix for it, please also open a GitHub pull request. Label your commits using the naming conventions in recent commits to Solaar. 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. ## 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 contributions of many other people, including: - [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](https://github.com/clach04) - [Peter F. Patel-Schneider](https://github.com/pfps) 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. Solaar-1.1.20/docs/installation.md000066400000000000000000000153641522022367600170050ustar00rootroot00000000000000--- title: Manual Installation layout: page --- # Installing from PyPI 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` 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/lib/udev/rules.d/42-logitech-unify-permissions.rules` to `/etc/udev/rules.d` as root. ## 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 needed libraries: ``` brew update brew install hidapi gtk+3 pygobject3 ``` ### 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 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. 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` 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 `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` 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 so it is a good idea to have Gtk3 available even for the Solaar CLI. Solaar needs a library to interact with the system tray. The library that provides this interaction depends on the distribution and window system. If ayatana appindicator is available then it is best to have this library installed, e.g., by installing `libayatana-appindicator` or `gir1.2-ayatanaappindicator3-0.1` or similar, depending on distribution. Otherwise appindicator can sometimes be used, e.g., by installing `libappindicator-gtk3` or `gir1.2-appindicator3-0.1` or similar, depending on distribution. 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. 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. This package may not be available in some distributions but can be installed using pip via `pip install --user python-git-info`. If you are running a version of Python different from the system version, you may need to use pip to install projects that provide the above Python packages. Solaar runs best under X11 with the Xtest extension enabled so that Solaar rules can fake keyboard input using Xtest. Solaar also uses the X11 library to access the XKB extension, which requires installation of the X11 development package. (In Fedora this is `libX11-devel`. In other distributions it may be `libX11-dev`.) 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 manually 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`. # 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. # 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). If you install Solaar yourself you may need to create or modify this file or install a startup file under your home directory. Solaar-1.1.20/docs/issues.md000066400000000000000000000072351522022367600156150ustar00rootroot00000000000000--- 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. - Some devices, such as the G515 Lightspeed TLK, have multiple ways of controlling their LEDs, for example one way controls each LED individually and another controls multiple LEDs at once. For these devices the settings for one way should be set to ignore. Having multiple ways that are not set to ignore may result in unusual behavior. - 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. - 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 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 Linux HID++ driver 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 Linux HID++ 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. Solaar-1.1.20/docs/rules.md000066400000000000000000000443371522022367600154400ustar00rootroot00000000000000--- 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. 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. Rule features known not to work under Wayland include process and mouse process conditions, although on GNOME desktop under Wayland, you can use those with the Solaar Gnome extension installed, 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 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. ## 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 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 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 generated and rules will not be triggered by manipulating the wheel, crown, key, or button. 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 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 evaluation of a rule is terminated early if a condition component evaluates 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 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 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` 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. ### 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. Moving the mouse creates a mouse movement event. Stopping the mouse for a little while and moving it again creates another mouse movement event. 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. If the first argument is the Logitech name of a key then that argument is matched against the button that was held down to initiate mouse gesture processing. For example, a Mouse Gesture condition of `Mouse Up` -> `Mouse Up` would match pressing any Mouse Gestures button, moving the mouse upwards, pausing momentarily, moving the mouse upwards again, and releasing the button. The condition `Smart Shift` -> `Mouse Down` -> `Back Button` would match pressing the Smart Shift button (provided that it is a Mouse Gestures button!), moving the mouse downwards, clicking the Back button (provided that it is diverted!), and then releasing the Smart Shift button. 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, 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 `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, or null for the device that initiated rule processing; the internal name of a setting (which can be found from solaar config \); one or two arguments for the setting. For settings that use keys or buttons as an argument the Logtech name can be used as shown in the Solaar main window for these settings, or the numeric value for the key or button. 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. For settings that need one of a set of names as an argument the name can be used or its internal integer value, as used in the Solaar config file. `Setting` conditions check device settings of devices, provided the device is on-line. The first arguments to the condition 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 \). Most simple settings take one extra argument, the value to check the setting value against. Range setting can also take two arguments, which form an inclusive range to check against. Other settings take two arguments, a key indicating which sub-setting to check and the value to check it against. 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. 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. `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 AND with its third element is non-zero. 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 if that is non-zero otherwise True. `Test` conditions are mnemonic shorthands for meaningful feature, report, and data combinations in notifications. A `crown_right` test is the rotation amount of a `CROWN` right rotation notification. A `crown_left` test is the rotation amount of a `CROWN` left rotation notification. A `crown_right_ratchet` test is the ratchet amount of a `CROWN` right ratchet rotation notification. A `crown_left_ratchet` test is the ratchet amount of a `CROWN` left ratchet rotation notification. A `crown_tap` test is true for a `CROWN` tap notification. A `crown_start_press` test is true for the start of a `CROWN` press notification. A `crown_stop_press` test is true for the end of a `CROWN` press notification. A `crown_pressed` test is true for a `CROWN` notification with the Crown pressed. A `thumb_wheel_up` test is the rotation amount of a `THUMB WHEEL` upward rotation notification. A `thumb_wheel_down` test is the rotation amount of a `THUMB WHEEL` downward rotation notification. `lowres_wheel_up`, `lowres_wheel_down`, `hires_wheel_up`, `hires_wheel_down` are the same but for `LOWRES WHEEL` and `HIRES WHEEL`. `True` and `False` tests return True and False, respectively. Solaar keeps track of the total signed displacement of the current thumb wheel movement. This displacement is reset when the thumb wheel is inactive. `thumb_wheel_up` and `thumb_wheel_down` tests take an optional integer parameter. 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 "CTRL + A", or a two-element list with the first element as above 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, 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. Use the depress or release options with extreme care, ensuring that the depressed keys are later released, otherwise it may become difficult to use your system. The keys are depressed in forward order and released in reverse order. If a key symbol can only be produced by a shfited or level 3 keypress, e.g., "A", then Solaar will add keypresses to produce that key symbol, e.g., simulating a left shift keypress to get "A" instead of "a". If a key symbol is not available in the current keymap or needs other shift-like keys, then Solaar cannot simulate it. Under X11 Solaar can determine the current key modifiers (shift, control, etc.). Any key symbols that correspond to these modifier keys are not depressed and released when clicking. So if the shift key is currently down on a keyboard Solaar will not bother to simulate a shift key. Under Wayland this check cannot be done so the net result of a `KeyPress` action that is not a `depress` or a `release` and that contains modifier keys might be to release the modifier keys. Simulating input in Linux is complex. Solaar has to try to determine which keyboard key corresponds to which input character as it cannot directly 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. ### 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 `). Simple settings take one extra argument, the value to set the setting to. 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`. 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. ## 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. ## 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. Here is a file with six rules: ``` %YAML 1.3 --- - Key: [M2, pressed] - Set: [198E3EB8, dpi, 3000] - Execute: [notify-send, Increased mouse speed] ... --- - Key: [Host Switch Channel 2, pressed] - Set: [43DAF041, change-host, 1] - Set: [198E3EB8, change-host, 1] - Execute: [notify-send, Switched to host 2] ... --- - MouseGesture: [Mouse Up, Mouse Down] - Execute: [notify-send, Locking] - Execute: xflock4 ... - Feature: CROWN - Process: quodlibet - Rule: [ Test: crown_start_press, KeyPress: XF86_AudioMute ] - 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 ] ... --- - Feature: THUMB WHEEL - Rule: [ Modifiers: Control, Test: thumb_wheel_up, MouseScroll: [-2, 0] ] - Rule: - Modifiers: Control - Test: thumb_wheel_down - MouseScroll: [-2, 0] - Rule: [ Or: [ Test: thumb_wheel_up, Test: thumb_wheel_down ], MouseScroll: [-1, 0] ] ... --- - Feature: LOWRES WHEEL - Rule: [ Or: [ Test: lowres_wheel_up, Test: lowres_wheel_down ], MouseScroll: [0, 2] ] ... ``` ## 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](screenshots/Solaar-main-window-back-divert.png) ![Solaar-rule-back-host](screenshots/Solaar-rule-editor.png) Solaar-1.1.20/docs/screenshots/000077500000000000000000000000001522022367600163115ustar00rootroot00000000000000Solaar-1.1.20/docs/screenshots/Solaar-main-window-back-divert.png000066400000000000000000004567301522022367600246770ustar00rootroot00000000000000PNG  IHDR8(*aPsBIT|d IDATxwdGuäݙAM&rd!,0ddlccc`uۯc, +lQBIU]mޝݝᆪo V|g{oݪSUg})E U    ±27{ g    ´~:'sY{茷q    L{Y9$SrlG };<AAAA|>!*Dr<ψlHS9#AAAXv`k8q~TDAAA$ɨ23o1    ǂD_)=#AAAXx߀!/   0}j!*/lQ 2c8AAAA/ǏHAAAA8LQ^n6f3׶M5vV7&I}{   8 qlp]?-ދc0a_lp`.#C8qϣN;sk&[^f<Ҿݘwף5v&#yuf&aBTJOmAAAexJ8Kx|yFv^lT*8?<{F)61yTǜӮmd98li\_/O6V~>O'k{4G(1m%dmL602yy5K1+&J"#N t}mf!qńK6Ua䅟ODAAF9qx~ܚ7W a#//{ABEɂ:ͣfg1ۍ]1QBTea<<2Σ'G&B,U&^OQ80(lFT ֎fnM`twQ~zrogLlJuΪ DAAAx)ǐㅀ~ܚrkbj|2{7ͣfq7ϥvd:q]'5Vv'dS7|laRTP&j]:)&Q#jtrE6m"GV@ISVSf.q'nƀAAAgM;an2 mcZ׏)֌O0QT1QacьYi8 q\>}F`R Syj{Si8 Z>V1!Ӗhq!vIl2ٸ&*5O^i܃#jIdDνvT׽k/tu=Z9M7GSimvFMX1nV2ۏQAWhzfw1l#GDAEH(tA|`V("Ƒ=o-h\/g.(mZ;od2{qjM8V#44?vk6c˹}q<0ɨJNhvx&̬s)u::LD\r[v:IP ie$8»vJ2ƢX5!* +9€~.FTo~5|o[AAxPT";٭M[QjX4_7ۛ2 ƶ%7Zicgf?XL^:usRWĎ0H] vCjYAgъC5;qU- P_@1sQ)Y~zZ{ /mr 񖓗r"N6}.GΝlZGEH2j{  (qxLk**&oPziv6&"QU>ry]Ou <0y8D5iiO|.GQ|'mXe]P."]QDwmQWSLe34Q[#K{Cq7j2jp"^mʉw"}Jh{ {;?}UFcEP`| ,c|wc|f~|XbA+šAŊx4`Sqqe,G8-t'8+Y=f=yUl`Qq`ú>~u4dw8j/ urKWč 084frF{rI›?l+\񑵂  LTeڷ`HՎ[oBbF~dǺuіCijz=s5sf{( pT(˜ #úxfr;|-w\~y6I77i>i#uN 1 sBE\vYPtV}yRl;ܘtQ*S/Kz'J?$(lx֣GXTE,Tb;T dL=NčH%$raJS + E'|}ܸ֣&G[Ħ!*i4IZ8ۉHD@Pы =9Q_ [\f MMN%DEAhEW_7]yj;|wyt!h+nhWa<]. sm|gO3L l<t\2%]9 R.kAz sm<\r6o&{yӫ⊓,0DAx=Zk.nsq=ܸBhj'ک%+V;9rT"KU[Wc#^ c[- #3yDW b>Z*7zbbᬎ<$y'&ءUV"q}echTO:=81CM"jR62V)C8OB;L4w6(pG{[솰 f<6vk1F" "M]KAu#η]_KWTGsfss7ub_-Y837o]Ӷ3caź5sa;w}o?ws_C\sf~wɡ puA[֎%RE^sa~GaV1O-4!ي4`9R5d]bʌGp( p=Sce#('4T" ,@%v.F9tl*!*mUF4nt6ͣN2A V_ BkT_]-clqm$-E̬2K (>7 9^VBTA#|xŇ^E uqsѫg߮ܺew=y?7Vm#eBæjZs͜g 5dRsfO[PL2`\[(cP&A-=co{<.=.=^|q~n~S~9#s~֦j{aĎp=P'oXʼ;%Vor5I Sc1wAb-[Mlzk! LBѴDM"'?q z?>0TTo|DBl &PhM\V`wt3 ;1&&0$H2-&%y!:,*Bh< 51HҿXIG(D6F5Ƙ 6)P@V [d ðey<(Am<*Le=89Fkn9(CLNmنQdWX> 16$"rP&3$ Ӡzp\q1'pjJ9_k/5_ 7ܴ'2Zu7v<0v f98-ZNmD!jhQ9MnGK 0FM4(9MCC^"'?b43;xfj;4t8| 0  &|oROfxu1##TE*zDax8$]zC*=Q'KP[TҦ"Z#B9NԮ5v]GLP!DŽՄ S۱%kzݮcZRTWO[@LQt^T e&1M"ƘY -zr^Zw LRұ aL)l#;IPGBTAɉٿq>q>sU 9gBYrż57\\vf>O ͒0b?4XfSЅN9Uˢy98>u4ָ^Lڲ!;KRZi3= LT1VtLĩF!eVv{,p(Z;tDQHTu&6sʰc8 9jJ;TvQiJ4jp<0'Řg'G*fPe#>,5|.L&1T=/3 6Fő $^߷oPgy+eE|ŅkySuypXt d_89:fVJA$0sjZ)K^P/{jJAvijxb!tPC;±4q;Ν\ڸv;,KA?VkzIR1/eFk"E)?w%1N]JQ&;5$" 7 @GgAgyN]p<:JVJDGbtyaA1&6*Q=񄱭) b:zgdu=F?VlAAʋ0K΢xO)y#џR3T-:qEcX'!* 3R:ro?ȍwOrm(eh!5_msJhUYk:6"*! FNmsk&me" m&J6 ta;ѱ`::;WtYPP i] b(TnW9Mb6QC-lōdIj7 rlc,dD?2W9(^,%cLcĦ@UL4QC,pC0JvĔ*3+$pwX G\#;7F#3&85ԃCGA5;`|pCc. f9@k#x>fB7vۑI3ᣖ郡 QAf?+h㼵IcrʣڈÈUjM2勵Ppxڈ3iKAfGk r+0豠caKmE]Ukvۢv=fswL4~邺F@qq h7Rě.qu'lagO |M9<7:8a8=P +1K rWw77PX`*gVCLAR 'Kg62GsHak0OrؚP%bpC0ņ>Z0S(3fLi_ {um~k* Ĵ%jm::F9 Q eaƖ\Md:\PpBo1ɷY-Gɼ[>ю[{6\TE% G ١1z䂆vĘ:^5<8m҆~̒)F/>&fLMAavom\"^׫q?|;o}q}p?5Ρ*9⤕KWy>Z5%n|H=/N7Vx>׾7ߣ_6ɳ~F>8;؈M?O=Ѳoq9eE#||*~KRruyJk% GoUsrhp..6{/ |a0əgBjXb~F~J%4.UOtjH%6c3Dܚ 2籩7i&á$Daj=ly*)%?nQ{cL@g~r :{{cE"\-tL&A%?yY~=~mMo|F>¦{v)w1pa˪3_(UEwO~m;YΟԢc(yul|eo~=ј|gOl[nɲa>Gyt]9( ٷiF.|gGhkdǃ_]oŶbN|6bWk`ǔfSwvX,9.2˅]5kpn﷌PKt3l &n̺~?=wĄAElKSnXT7׳MbCvFio<EZ0LV aC[S7fmZ?kR&JƚI4Gz$N*x?m,>mlT=PsY!ECF9}̓;hA.< OPO94 =5\mg*#A'tZ;&Ux}8I8 <M8aA%r3g{\3n3<ƽ-ɜŘ֯$ӈ3#u=DTG3:,ɨUB. ףjᲀCٺ؎%.mwY*r5^8 WeݠL(!* Ա?86*;vee߶'Iedϑ-AAMAu¹yF"j$eNM-sf6B0@ &F$bDf| _/1AF׮us>8=pF!"Ag qEca MΔ^Y ݔ˜mmC6 sXs)1]͉Fsy< cs֝@tgV״ Z8"  Ʊv I/j!-VHF̨ &iY&ŁڎMcz\d)Y" ^F:^ݢn9ޯ{ʩvZIY(qۼ~zzr. FQCO F0wQ<`篢>IYݍExhժ6-Y(AAAM5lEY6F+wG<MċiuJXw=eB4Ȋz[[;VɃ{ ZGXm"DjLtlZDi3ukэ U/h!P5oZƼ8Zbcx /X4H !*   W0"v"LO蘬D$,_KTC(UyDx=q4u'|?V&B% =8$8.p}? 1Si:Th7UjC| <טTDBTAAA&i>҄J=׃RjUAeJyc;hfRCe^zsy%ΫZKTevi 3>A_c=AAAxN1lΪUXt)r A0<y]y\g6d    0 яo```-yͽˆ xqlj'H?7t۷o碋^836!   ´Hōw.CCC)?M7g>9pb7Mܵ~čIAAAAE W0 馛Z;7x#qbxxr̯ӟLZZ;#cCAg`C9p ^"} ‹{;dŊ!RVjbƎ;QJ!aFk(b uV8qIA)vdfiO;sZ0 v4G|mOZZZ毶o{abI,;~.I]vXMOP-2{qY"K:jK{jGE^5J?vRlc~J@]ʒ]Wp}\9sqn:v}?ߚ p,1PTصg]z)Q8J)Ȏ;,6{]q5=͈itTyzoGW\$ BU׿nV6=ѻ+Fk]+w˺_h7|P97`„hA$Q.7oRugdtzTHJQ &R}howO}k_+c\!f_&m';c>F?sͿ_afB9|?rˎqk9Y.=SOY@̣C6#{d Ln¦zҟg.y\[A^xXkqcCk fϞ.T* {b| }.tgwXgB5}/co\ =֓OZi-HVݷ1 )^NrZKV,: `I\{Z, ;1Psk&6f_ʇ* |W7~lhr[x|5R*䇻=l_+kW{'┓U{SVn.^t ]4~ eSi.8mFe}-kyx /TE< C(ìY=QV^ pe|ϝ$3s;G`8sT?Ry>W/^0oW ;~"goz@kkzrt~~%^+ <1tl\sK:ȑ۩=^Yީ>s¦Mq<*97' ӫx)dgTchX+~6"@-fqi;Kxzˇ;>ȗ7Norɗ˺v;Ò+61cNTMO(3xo\˒][xxv% GלE^2ah[F='lO<;)s,m =IbtT?g _i&G~~tsMl'GϤs+{%# fE|j&:[w '3̣vƎlmb~Q슱P)6=zhz6jly?O>=@"{2yLyO9ߩj713*=ÏPN,^u+W?l;PN^O͒U ̎!O|!G| A=J)D c Q|Eq B&zd1}z gtl^FoxGTb:i۵(8F;{l-Dˎk|{mM'_ý}Wff=+^n—x0 oVj"nbʮf"={Y;oTtR@=^NAn8Ҷ& wSle*?wQӨ4kNM'xӆmO[|s旼*WRoqҾͬzmWG W).XKQ8yvn(;^ŝc޻tewfg;c wp%xZ_sŖjT:f&lo|s[f!4C 8]v2V@?W\ȲsG?kѫA>}[0񯠗~fE'l_|]ӗOUΧU%>7>-ayeEvb:}>0py/?&n$8VKH23l ~-X ; ]Io5>sgs[n}M_p͡ز|N:m>m&紞Q 0!i @TP(4Q1{,| :KD&or QzfQψ5[:,YUW]řg}wY0a!e}WE ZؘA' Gk<yb?'bڻ䘟S4ϛ5 ||aP %ӺXS/W6YvūX~[Eb'Ot~ǦS_>fr.8>xO\'r:eҾlOq\plޟPڷ6eKAtܗ -O@ϐ[f]j*6_9q#o\ IDAT.8sQco(ūײzA'nn'nom_~TBzXl9+ΥUXSbmW^CEs4LlGo [}2g>_a+{_?~3SR"O\U\[)_̯7~Gaoc[PEXz>N9ei>-u?MH\,Xb.M,Mɱ]}Q]*8c)?|r6wY]L7%d["n(*۝_S_ G| AaʸnYdhhXkR)3:2RHJ֚ىY.6tvv.wx^ÕW^EE0+j}AKl=ODO'p7Q~R{:i\l?`b X},޿Ta|JG 9s"o8^U>'C\Utco8ȫ0[fq-H~Yce#wXu3:t6˩˚>kz>s=>̞x䜲:GCָթF'z'܋rۺY?4.|}7y+us^(>o>c#\{ǝ`@%A><3)k;[?} |}*pOWabГ_G]~]3}\L6zp'$ޔCl~3fN ?M9-t_1n8&:v䧧|=k oMt"n@-n޴W t}PSƔ{Jz׿UKr9~2nɛT+6A)UeVky999ȲLaa!@gV\ , AWʄZXXYr( enY4%jm{ \쨮KZn3}ԼŢSFy)S3zX$UEu)+d1r`n?rMos@͝.4K9|+ͅs1چ PWEEEhʲ)~?~,|}K*p: I4HCu#p:J_~Z<*$klfL}B? m@CΣM-\h,R c1H@3x4q=+ԣO, YHں3T U MoEjU\SϰmGFy } j\"GJvkR{Y?m V6JAAAx<f3ݺuc팿AP>D˅GQHMOs͢;8+xT[vW0^J|# gZ/vV QG Xf]qq2˟rP]'5bZWCH1Y]{!D8!7z*tuj̻¸9DFp82NX/1b8T, IFQ~<ÀIL=ULqs8`>BǾܟ/eh Wó MafڏF"^ꄨeBbf-5ijVr=V!&@cα:. NI(N.tsb>PP%`fJJZa5+t)й_<5Mђ`@2d`H$jpw|Lj.>>  Qf M2MLv6ǃi2Z}չ*Zu?@0С[/ ^w,˧nҰ}k]FIO!ͫ6Bh2~Dp: N'i9IҖ|e)-ߚ@Z: ģv*E$ART/ٞ2TOU<̡LC͜5GCwl6u9yy8Vv07ܑF25Lԧ s1چ Pkp8=v;>>>FnDZl^{C4 l=RPK/$lp8tt:Z(.oBYmlv_s C$̌Psl,Mj_mi2Nf.*P^2Y<17sPIr!bځ$X:,SӜsR0%٩9,4~Nۻ%X! 5$*$Jf}c{ nnʔa̹/pM?2i+\|Y&Xծ_]@ȄHu_^-T$B1F!dkr2>z(1HdBǟ#fܗ^%C0rvlh^6v!oՏQ8Nt-׌P:Nźy6i-Ji3)>Sc#9|8/NytRMÕ+޳9RĎR]LQuC% ~o^Wf[i=k2`/$!<`#L,С}Tɵ]ߜNm%r#6rB@׬3 A^WMP@M]Xzk ^$>' p]N6AZ) RdfeDQNgy6m3oEfo?Ov[>&ݟVnd_^5 t2W'XۇZ7GyJc(ݢyYizPeviIr 2_~MȀ<;p17ӆ21 ۶dC8|c)4 47[>/}m/l\V/jn OfRVcNsph:h BJd z=il,NWiIHHHy2HɓȒDAa!*,"??ݎ(5#GiNgÁmN{dv}' Ͷ <.~ӛFãW\V9adv|Y7P zzw&ڇ 3K&߃8\?2RIMsMlLQ8=Fei"㻇M" >:%Cs+dϿdAb2ևx/7vRES SԔu wj#nb->K6_OZyt.֟ s 8m|{ȟmt\dcUʆ Wߪ?(P rT@ߌ%t%{r$ <>%&&VN. 5{% =l*Y'd wп3#9}@7;0[} lC<ٶ{s]1e8Rh/Frn܂`nx}n^)f+:Ŀc{7dM-?*햡oёvynd:'ZJlI+Us scQbwl6ՀmZ>bh}%\77d=\,J[$K'R<΍iFbFn~ƼY>myaP/SH(W)JVsŌeŤh'PS8WvHzyYXOw\T@ߗ19\$ΟK]t\譔M0ǕR5ә>Yc['v矷VC W*~3_ғqwn ^>knDhmrcuiTzp_ǺJuGsO,tګC1kd?ıaVx^ԊrKVy[kDDmS#H~$ti^zV8bK lז _i:*VD %S;oݦmbx}}#ݧEKOJ\ZI/Oӡ=%@?$ 1n<F%ns EA!*瓜R2+¯:fIIxT#IgoϭEEfz.ws@;8E>4TVﳓ]Mt 6:(o60kI1=ny`&ͤR2K$ b|ooYWDfA\HT y@!^G=peDZx|{n .̦ifN咿p?=ڇ]Biv6'JUzIȁz!Ee 2<{-+tYϥdpTvz@$^.b Q$n fa՚QO|~">#o~u2Q&3!44tC\I~HVД~gNܑ-ܨ1:Pog+'Y3>` ߅P-CRp%a;,fuIzz,p> I(2!^ ^Bg.X֮/a@ ddTrPO,z i䏼3o jEi/^FWP"EM/q[?|՝'yq5OCxdG }OӐ-l=<(@f>5؇!rL]BwNFup*"񧠲iUlHF#7_avPY W%ѶO r1bNUX0CzO/Dp=ruBS!;х^ n J"{?KOӏM/tyHq w>T3h}6{xtXx5Bsq.>ͼ)3 3Wؿ Sה!'`?gdd aMCӄ IDAT࣓tonOQrmGQq>\9Q= KbǼȜeDPL%<MH&Wwy4Tj(ag5Ե7d׊kɍ!_&c;*BM7Ql6U/ϵ'|#;eU*%X2<=%ǐo͝0 ,O\sQL$t:qXVI:ypNBѪn7F"""'q pHaw!~.|hN&lVgj& {+ҿ_?Z8xٟ~m׮T:^x$M.ޟW >z䃔O6!l/8LU]CYYʹF̼#zK2ybR ]fImwDPDll YGϗ&C發_|r/&Өku6讔aޗL)›} lCbǦrrXDӄ`2%07">ѾLd(oG?jcoi'%N$=3-$R3|])qpM6ֻ&#u?LܳHg a\[X# H ?\ԥGYL(R|(I9Ċjי9m;u^ p%jm &6.OEaN.@|<#jB2)t 5ITP ; Yv GT3/i+:BXyˋE)&i2m85aQ5 - U]n^zwIxHOI#j QL4 K܅<]HL|4A%<An7<#8=N'MDxX]FNN99o6f,oū GCQhxOh_âqDC77nX? %Ưi{ os]_\PmpQaP#}scr6s.A5;DTYb7Э.:/wp 0?Mm˴F=ٚPNpd1s,I;xA͞}8]!I+$ )%\FlۂFfexƊИQ~iAAk y1XTָ٦'#[ۀO7-Fken}! /\"""ь'дu3";tӷys^r<2-lC^hG Ά% cO3_d57S^I巷eI2!u⮗_垄ڧ˪w&j׾5GսJAr-A mӋN>i5ģH?oф P8/}9Qr-?ZEؕBN$@{(NivjRI_\|i1jFuoJ0yݸ_6eKOt_aެ;}?:tOEq^1N oUOK' Kˈj?ȂRGKF8Ai^ZtE[9l xpP * ԑ#q ؕӉit Ҁ{PI^-~îטM9ZZff}|b4O`0hxDL9PTV "@&\ +gߧrJr"F}Wutɱof >? *ewo1c[?¡etUGK,1tO@6豞`J-DӮ'{O-uv&G0Sڡ iG6q9ki9x)q \d\?Xv9Pec$Rsmu>5jJ6D[*mjL6i2NPSH"T,psjރ$cWE6cD hM-K$-bgD>MoAHX"\{9AFir ?o:>N3$q4 !VLsԥ./7!IzوTڂ4aYu/yCxG=, KYk2?@%~;{om79Op䤂E+Z ѪA3([Jqt~gkutCxl^s߃\7k9{[$łw$ 99 @Ydt:u_.?CZUB0gx֥C[q4o@"}yČ{/dMcޤ%?*j>[_lM@0'xV({\xaB^}s4Fc:Q%PS}3٫qTZ=:¾ 7E~O|x9&93;}]TUE4̓VzVÓWtD |9{o@b'{Ŧ4ً|7Ȳ5yv[[t^'chV0HyP(:w"ʗ;)yNf çA8vs)0Z\OubƸ09RyOyvZ_?ۯ亥c(VL}l^&LVSD3q<>. ׊y,͸r͌KljKu4Y|pKےшA?ZΉ O,Z-Y; !eyJjq$?z MXfsQUݨB굜P$1n:)Fxf[j^Re^jqd c!-!P|q:zS7+62d &J)o*L'%ӎ؈&a$wa&i9(z?wUZ1HOˢ_G[b4IOSgpG6mnL p{;ч6Q3UGۻe)=[S,?-W3\{|OVaA" ͈ J)%|4u17͈#KU׌[^~ZҽLy-K,(a8cmJNL![H||N}^#]v1nӡ;}Z͝ƾ/^G":ͽNe=Yw錹1M5?VŃ+R_C| c` lcն" Z9ƊէQ$݆"Z~_#UU4 USʓ6l/(df fI",<D,TR\?Z\DJy"M?Яk_G|{2%ۗ-|AqmHG$. }+ur+3|$]??̖+x&)R9z W?"vFt:&?˙m\47V'E u7D]^&4MCT<%zPU џNI%@QP곞)g~xi Їa}{3e!g ϶bi=Bw h֞x+65[?Ӫڝ_$Pm޿c>]Q.'??&]Y6E-5G= ]-FtܶQ%^<~h6M{櫇{|!2gnѓ^t:PSؐt>H򈯓u*#Hj.G2g])ѹs;|Ϳ8l-So޶xkW|o(J0^Mj8:ֳϚ#E ǟtpפY QAljops9D$M66kJMGC %Br";'` &H*$D0ϊ*,`H޹'-syqRvchǕw6Xy't9)<AA?==-T&VsS֕bc+_l^Ewq0)'nmRsJ'x[~~k>, L{W,dl9l[eMBürUka ٽ ǧo=aq37i'+1lw:ȊrqgGBUU4*'-qz z < .w͋)N%EoCG֍Ov~cI.bdGS0vBuCR&vgHW3iYx-3RH~ƅTK/&r$1&VUs3o(cVޖŰap6m▘F%ZqSu'̘6C1}w{:x?Mmɋ!қ2հa,Яo; vppI<ۡwf<4~\u#0=Sfێ( -Н51 ɷ3*שZE.4%L6F7F.8eKYyn}{3gbe'fFm IDAT` u>hJ^oܥu7%h T [M度^Del@_ ('ֲ꤂.zc:] 9>]3msm #P?X"GgD ! P USdXM=8$ST\SENeoɷ?`\$ p[?ƷϿ0dB]F"#Ð4Τ)ԜF%?׊&ER2F#e4P(ْ9%u_`)iVt(9ٲg=Na6Z{V$鉉mT2"״Qc H%@WLr8s@" : y'}WQܚ!z'!$( Sػ]W"wdJM:{mG( $H3gΜ{ΜwNE#64Dtp#8e>ݼ~.~?G*\4xj6+[/qk4}+H1pOw#\Ź??mn5j,*EE$!EBAߙXYN5v6̛6Ԉ<?cm!/ODIA:{u8u/+6ʃKgf$vzpVjtu.ngc2V8?IfP9eٖ.[JJdl攠&9p`@w%WPws[:%{<y16MWzZ EϥxA"5vbkv@1N\o=kNxe8:gy㈛UƜK-QCG/cAdƂNÎ0L_6kA1[0Sa#)kc{V'<0&XNCtCYc|i1[qN鍗zU:,=WWR)^x(*Q?A3c?'1)̪dǭI)N6އr!15Gk>*-X)7q(BB⠺`Е5h3&փNYN6UgcejݛujZ gL?CS#F26KaɚrƎHhΜ?{NvY!zspG=3}6B-[0ХWݯQ6?yOXI.& F p97]': L ʼn(XP;\ks{SՋY9鈠Kd)I05&Ix>S04YNY!*# KYv K4<eL)=jHsE`EZ9evh. ]:܃u{p`DU5YQ~U_t')C-]~ mϿ>߅e ?>޶`UEu8)/BW >^ XE+eexts&w(X&/VB1+Y7^'2G@$+䨡:9*aait$:@E˾c(̤B3vjQ9õ=ٲurΠkr)k1uO3zf6]lOLC~ B+Rx_q:Ԭb[nơX=by)庂4v85/VoR)x]>6!.=-é3?z/ʂUAR\s1 sa((g*N$S538ض^|1nVL7|s0j-__D `DBYIf9c', B\8]u`8˖kɪSz6va01Zm~ǰW?gi;:bڇV6HI+~ t z Y-{oƈp~Xx`ߞe9G>~7Qs)_ ﮗݰ%Nj_Pʡqfl=Iydd^ª[p*ةnYA(u,XVP?}/يɰĨ&=x7x=SwWg&^c]|gxvQ߃m_~*%ġ=j7JGJ绯7RYgs`/́БH%_Y{Yu~ۍDvL?9؛9sSkƆpiqxB))ok*J+q頨xu'|d^A5Q-)dF)@eMni*n:}~ܹf`{ruʹ?$ ݩ,^Jf sM?7+ ?$'5&$=bv0`bBS7ԇ勖"'544O->V[WRRBYi֞T ꏎNYi%%%8zkdh\ ~׬!!\sAu`6wˤc#`(gqn/!_8/};[Jh([رe; CZ0n_wѲh\El_|iL;xZ6?+;s WRFir45Eqc_wF hڤ-b%kzM m?+1uKJ•3l V c.Ǫo?NMyV=fȨ)~t)"͔\gl?֛Mmy$pՃT޾~^2!Ѿ%Y7,n#{>PY({[_=IXJ/揲JoTn?VsC3^Zӈ;+:,)dy=7h*7eoϖ8ө)#Οh+#]4j{ch7|!ĵ F׌D59v ~^lc] !"~}gyJMLP)<t{㹙~zqj}IvMNY;N^Ƴ/c|ѷ+zw>ݻ8' r4Yz.f^/FSL1L{%LoΜsxaAm3l ?q/ n;W<=7my}<߸C ջ8{3(w&*Rﭖ??z˴*ؗS DcUR]Efv)պH:4@T%kN0k7um򌁭$i?֩QRRBqq1+W"??ʊJ4Mׇ&%C8}JQ"ܻ)ݯyR ˜xJd[v.On7ݮRxN? ͣ(+HU8ּz`Feʷq|sίD3;?Q'8f<*&"2 ʂ\rKm(D4T> 9[HDVd5Tc s(1XGH" s(!=8 z+ 5JLl#糃G#0Qid-:񓸰 n9pS:tnnXoXX,h} րtxkGgWrΏ/fĴS3'tb/,ңyy<Կہߦo {y;ݬr6  K3^GQZuHT^ $!H{!Ne<;;j]3XQR^_ ǧ7s!X~k?h׫vv7_dmבݎ !&=8BiZj>v=ٹcfrf^7w^!JȆyb?Ͽ[Tng?EĔodL.*JB!4x?~>=.#WGA >g7 X.:v>D9(3㦧Ϧ^A3ÄBinWּ1gq)B*sE\!*ݒtC4ÐZ,BMM+OS'B!b5B!B!= p!B!ݓB!B!= p!B!ݓB!B!= p!B!ݓB!M ӝ!B3ӝ!t'^'pזvY]z%3q9]x2y((d/p\\.;vdڴ+NgT2!h3BjG t 6W`GV9pݰ)q531f+gO-9YsoI#W'Q}wJ.WX|1˾=maa!IC 8ʪP||jN'mǢiS^Z;ᅪ`0ɾW?š! ,M;B5e՘W."vf87:vG]\{s> PT(F972Gd8FQe_^^NP`+IlpT/@7hsch'Cq9;}0 ($ZU> 5"'E!!mEBR_,}ZR*iiMދ )Eh nS.mNP>Y7ϻ>B:S~鲥xzxqs>Q`p}ׁvҐb3)_!B9 6)5YVzi*9Q|UlY~UKނ]WObe@)c4ע4u=PAVA/FSU-m ;sJwDx?w'=^-[ٕSD]LtӚ-?T-̡Bߡ;q|te8Wi;s* 4&fZtt@L^R;*0̹>z5W`iNjQ [,i's-7zY;7S33}S!w> ^a1D#<È,e lWMxL_ T"p3+9rwo#̀%0zïܺ 27cCFUa1ܯ^!B48-R#:$0%2H[F5fizVmsv\s[2ɥ24!p=Glnn`K9bb<\6WaS<F@+a?ɨ49T(F]^p0IO$y%,wo*M3"ز}NF>#|t!5೧s/0w}U0XK{ 1死O95j%+>;)wn[,|QZ]8sY>%^jy:L1 >z##ԱƯL|9K_=5 )b4aT@?a}87?D3/Xf|Cۗ_^}t(3N$4Nun?3j,CɰGG~ (Rw&6ݬnz μGF 34&Jv?`c;wS{^_sߓz!Kᢠ#Y' ^3kկ 8KS8MJU|Om'7 h$v<Of-b/a*C xFFݽ[K|ˁ9\{݅ -o;˾MB $!m[_\9p WF2k5 59htRR٧"ІZ.k@6Pm~sng+_f^AGV7a^ql~=E.jx #mw'JV"HϫWFיT*Qcr۽|?v]EϞ79Tp^Ȃۙ@bD+X{#S='Ny ;3o)G1Ј2q(_O)_G-˴{ K6NK/n1b#zbA ~iQuxל3qQ?1w:~YK"OedvX<\,?+ naˈ9}ٵ+>[;LJz6` #*Y0{{ 㹨Ns,uʖw3^zٽ'';xJBJ2dX/ }:J8BK%$.0tD\kO2uV>KRY j0I]6R;@+ϫnc<rEU:݌5LVfzZeNtbة;#C5f6N<ݲnE*=:(g̔B!Z8Ͳ2 imRY/!uN1{8Yp3jC :<{5kN|-Mhܿ96HXs$ IDAT DlۼH#!ҧw8yRWo4Q,׽+>x(@ 6{#-}o?Ƌ0WLMq^o8VAw(U?EA&kڬBc˶B4 Dα ts+vn{𭆮9T\@~uCKgSoXF{'6Pzt @Ed>JN8r,vA@TX<ͣG/Hb!@n'Bцy%2c)˫6zXj7@BC +h ik6R5n^H[FHf6rK!GдB jf|8W.h8 ,-AGVORq;=#z< [ o:O|`ޡXeߝ}Nwwh𨎆ۣl$كܟ^ (mFZe@tzp/0hm@{iڬB+$P5+\s4f,u37Pqe=8`F]j$Oa Vj@?T*J)!xv,RKIoe;!_xU%AOzPR 6=_xu*'8wf])baЁz m4c<ѡ;;tt?3Dq5vkxast& %PŁ+~{==g^h3M|2 ?F;xjEs!57e∩D+`GXv18>CoV.tv{8IWjڽ. 2#Ls#1qbu,u;6ǡ.*!-'=8-Sc&zR@ VCe mo3\$RIP}wxy7)V<"',ݖgxdU"vmLa͚o Xȫ/ L;ߝ} ]%O^O"uk<ׯ+L&n)k1T|Z2;k{/<ֶ뭦Ҥr}m<:55hxڛ$q3_VW0l;^H14:fH~0X-XNT0frpfG!8TQB= !E%"1.peldSI6:YGЉT֧呿a{] i`l[ t 5$@{gK ]۟bGNFYщN٧-SC'쿦3,k0wtgR7Hcg`˩P<=!Pmt'לv}=5P";+:;m؜n4=?&UKRz1~LF( hR4MYCFZHIIa1 >6=sf}%*# KYv K4<eqzέllkٜ^Jt. NY/{F/T46St̅/9L p!hBЅ !Z9n`aKxXz|VnbcyD=\_3s¶^|1nVLߙyWxksf]1>Yw?1&k>6l^hHp pi3- EU12uk$͆r@ڦ c67g5֮'bת-o}gً}0wh7/ߒ?Ԩ(RFP,A d6/Sp\ &Nj7wRY\b${c(ɟKV]ÚDS|>~$AHNN jLH9n5r(g)_p~*ŻVF:KS|&7o4o^e5 7eC뼑 R%T%N⼍]vBVHC!KDfAbΐAL73uqDV75If[aYO|r#E#mhҏnx+ ~wM&C)a p=8>4lb~[^ΓoƙY*ӻ7suӉ ( *?̊r 11(55hGVSChh(]cp8PUv}=3.{w?9?f|jX8/0.nR<cl/|>j,O<e^BgoQyp2O̠Gn_6cؗo3,q~YN\?fe3ɟccXx۷."B4N /6r[qzZ*qI#NEJ]^Nw6N,2K֍E1ܧe)ONz*/ʥLPqgN(((:#bJ˨) ǛBhh(Axyy.U֯'̷a,npETGN彏nػ7aфz4އUǾJ "1W* OB :BDo4QN!D xF=04g+$!9;0 xzx` ? hjjj,b6ZXZX,f3&YSDwgxkSxOc&n9Z1u_&c %&x[W&tmZBB!N:EQ0  V+,3:4Lf ~&\!uIC!Vٷ60C !g6 p!BQz&~|>{\ Gï\=oYUU!88B!(C+yk\fB4m\)Ff?@< !8C-O!REF0bcNJBJB!B!D'!B!B{B!B!D'sp!D} B!im%!mPs~ȅB!2DE!B!B!B!D'!B!B{B!B!D'!B!B{B!B!D'!B!B{ӝ!B!Lr(*ȣ~Lf3F/o}X=NO p!B!idgeRQVX=08vl5ՔSQV` a1[֌4AEY) ,"o_$!B!*+)"@ӿꁯ .,vlHN1}lU5`֦KxdedeRKN̖i#sp!B!HQ~YtFDT&0tDtl7UKמ} "cv*Z5SEzp!85ՠ< :rj陸²j4 o̧;?9ehB.QTOl^-fMl^ޱ EQ jaz45Mc ݛAIi .@:w n`ȩ̚{ }{jG t 4^qYh?bjt*w ֐q⚿HDf~qwmU+oT f+>!X1+gO-ќc!=B!ڟ rnbXڝbu?xӕݻܵ&z yOX=<@QU rG\ptСU.s!ht*wl*cVT+r=73!֣_$xzY5{i0>g'Nc/Mj8ny4/:gp972LksEAϸ? C"lR)*FUESU6\鴪|jDS_!_G^V&ZeՃQdM[!wy{с cip;EJveCsi۵ a'V!!hêqRǣ@.}NI;We87K~yqxѫI ߧ|,Zt3\w{#Ԁ!Ey!f`pHZQRXPʋlܴDdd$W"$8!Mf22vZ5@֭O%qHb,!h~lxK=I\3m/ en`ܗ&ǫM?>ަld:[^ʪmN?|*زv{L,u[ I >^9a^}Lg.c22"U,,4{$BB""Z=f8)Ȏdgs+Ȩd $8q;Yh ;vفY_ p!D[UNVٗ^xlp CPuvs~NKf}-Xڵ1ؿ[MM]VS5EEDu"';AKLW,[DLNJ~%!mKx= Ϻ9ex ᡘ?Gns8żylQæyd/gkߤOP |'t6Q'1F0tHICaJ2;\q7zFښ413xxOֳjgȹ-R ]Czf<+ȼ GQ$ug$JH(Qw;iz\SwnAɟQڣ8Rfsst6{M6|p?/(?N>{>C%j|v{FUiq =Յ_{sPz .=B !(.,8)C||(//=)?Jਮj1SV^I`P1QQ^jU*!h,1th`2~_&>Byl>Ll&;XZf͠/b;)kwUgu{S35 gXiGu|;kd3%>^I}wa7Mchbv'̼Y;_+r~'J90v ߗݏʉ돻eԜl$v<Of-b/a*C xFLvV& ',懓b:{}rV֌oHxr:>B!W]]EPHIKǏBCNJ^~d7y>ϪůcPS]{BsU[ttHADJIe֋CjMޏ Clpjdy絋9TtCHXÕǁ2R:ҹnlNL}5td~}e{|_䢐wJ3'_dW}ezzLq޹6. ĈJ^^{x.8q6YUp^ȂۙUΐ@bD+X{:#OsZ,X,VIKlۛK\\xz TU٨@1(y|- pU!,sWY'>s_gRjh~[J <&qq3\z%Ltv?zB+ϫnkE)LfZ?ϣ&T݌,R E]GaRRt. PqI&9{{$;Ե^ ΄/P{ 9{(M=NHʵST&w3׬? "pwSDXx0*:ZQ!EǮT\qO ȝl-ߺ΄k'6PgrtBqFu 7?lՌDsM&\Φ]N&DFxt"Go0ށEho!Ax ь4-$ 3rƢ?ְfvr*+J_MVj~?XB3᚛eB/Pc˶B4 Dα[T:4rhcw{oi9o+ECc4A^~ ޱ2ʀAxMkHy^Bڝ8Uc0&q'.Ϊue\2ڐ&^ ?t(g/| `;4GǦ4 t\j$ng8bqw? cmG48i_F-M [t_&1㡀NBqF5#NIy`u|z%4V#:n975x{Y2ZW p!D;zF2dµ p-h5ƴMᏵ[ׯǺ=]kZ!y`?@ODrtKUH4CARs ,M6b lk  ˢ5Hոxa#mmպw\"(/<pBE=ȡ?iԾq\p8KKѱSA>eulw`iI.7Y+owOW !gp'h8 Y-r043}Gum>J؍ jj*V]I*<:u!hoT+a]}<|3-?&ld|6m?ah401 " IDATL4j[xӉ$82h$فmNa} {&t7TO<|g[BX@\ZEPAk׵mV_+[[7j]j]RQd IHB %@}O>#B\_hȝ9sfIgyN-h3sX..rK\sz,Z={Pvp5:wѾO27B!NubS/ta0:n c(k{@Pt7ҜMsyh;> uBq3'_ͮ-UhP+>V͎G+V s̡y<͎(V+視dL1|J$ڳri 6n'FN%=@|ryH7U'0+sbh01ĝyIլB7(&MM>>5| -{C5p:X};&M^uM2C!$' [wYShNGGGx5Hࡲ2t7 5(ল(# TU֢`ĩ*1I4gV +A3Ēw ΐ@zjjQ0%M'cֱ5D3,҈ʊ꾏vAoeQfqѬF[8C} !eokm& 0poonϿ{.#e!="Ou;޼2(fu-2iR4{Doncys_@ g؃eAf~b"iR2>}9]/̈3#5t*=IJLfgA.Y[w N@ &OIFE&{kn"Pny*4ŇW^Y' \-)1ڷBpZ֖fF~HC!$_*~~o2{_Qeum:j\23)_g Q $ռf&Lo ; VwEE_ee0FcHdfzʖ?!߮?%񽌢LLcl[?tC<33b~*#.4_Ͼ<.:zl1O<nVw~Fiץts-+CgOu }Yuo! /ɌbZ[QkޕKC?|ՔhwKK!,IW/X7mX=&vL2IãQؚ)EAQ%mP&roonO{&|>MaVr8|F[!hϸ{^;jj%wn~ Mr K0D18?tH1PJSuo2L|e:,#6}!ޙmK׳sulIAmq_}CD۹f;1.~#{qLs6N%mt:)/Aww{jiið!ғ8b20a^M>w/6lp36QT|#rΏ.drX?f\(/γ|J敼 D3 د>QW<Ƌ]enZ_9\wM,N;2˒2t|ޢƒ1(IC0Re'nT"g0nFeoemΧ3%E'".}co;:DQ}B5Ii8 pSoՋleӓ_Ro=+)XR*rjB!B#in)M髺* 躎墦@G6c*mVTkFk fG;VӆbvRj0PSSCTT$Eߧ%?(OJ:~@!~hr6%yRhb'$2Pki*%ac6hM[U)m8U+ąXfl8EpK~#oϵm^CIU; TڷBFbFMb=,N❅$ŋB=3SWP픗WGP#(]rhŠ~p@׉O^Ĵׯ;bB6h!BqZk1Λt)UDtl\μe0,a>~}O4N'6 yLf3V/>>>fTo8dB!BqGgg! cjG{;w?(*j+PUՊbQ;-; p!B!'IhxF{vJTL㱻rKe[2TEeĨq'5@$!B!BDSUQF<  bؕrbuD[k3!aMx`x<.'NQ>%$!B!Bd3l88j*/tҹX~(+gjkOjd6c4'>! $*=$!B!B"£bNuUN{ҙB!B!N{B!B!iOB!B!8IC!B!= p!B!'!B!B$!B!BӞTW@!B!!sm57ފtiA;MfC `0 N p!B!@[K3Օdd6'pTUЀP!X>Mf.'FF@`q(;CB!B!Ir:(ݏ%?p@omn|>BB2 ȮC`p(rvFTlZEB!B!Ibkol^"cc ꨪ,eĨ1iHl|a/ޅ"t6$!pRbG7wtJ6[lhAaOu}f(^d^y/B􁭽bG z56}56]E'x['/L܅;5 eLNr90@?+G>GYMN ٗFMbtu/,^-n/v y+$FZ;dBo`Wy+!Qu*hr5b#C͸__}BcEkSwkpc !8=9J?rp$.qbeYꏯWh{Xr/tb,4Ekp-f /{Y520t%`Rԑc4!V)B] -M(BHxĀAC] u5Gy??_o Ҋ^ԦWVWT܃:: ^} M n,!*>ym !06rFmxY'{>,c߿`/"{]!8m:Mɱoecg9Soac% oBۂ9 `Kve I~Q?N6cAV7,}:7{ܶ@o&B!N35UƏ+r#c(sýg^FC_ -zuEu}n2jm߹s1^+!pӋ nt1[_eD4 %LzDM UM8~<;naws EԴ`W,E'yb2#oΌQr ټ癩GCGn[ 'c۬"6}{l(`S ]12#=Tm&gw5mqNM1jj򷳻 f&8:Ss <3vpœ5qio;(޾=U t8U, 38c6TȄ0uZ)]An%bD=ocC]S]\DaKh<##|PFDKԨ?u׻;{hTm!gwmرNC̀+LMqm.*L@`б7xhN@hnAinAQUPΏTzH+75cg7(^$B YnPPc,P?^9Zw5k'G[DؤK9' =wͭ;]TR9džiS1q9ne'g^!wQYF33),F#\=niP^NK̙ă\أ{5坧߱CQP}ur xߠRI0&onzB%~3u{ Z2n>ZQ/;\}D>{q^= grQ?UBqikn&0(dommA457XO}2TUEQ_T]AU`Ht͝JC!*H35~)W)z=?D Ia92v;uޕB11)gK(y$7Xt A/IqJIj*r{{ x;FNO'VfT#eD{rь:!Iu#1I=xR7+ #3{hTg Y|LE(Š7Yg 8xx/ك7oZ̼ )ZeS~}h䀷Z[;:M<Ž~Eu8g_y)MFM[e>^g[ᡇ,$pۜQ䁑jy7,"#Ȁߨ8T)[b4aT@o;WY| EUﱱj]F !FdLBHXDynق(Ҋ`5`،bƶ9KBbHҫGPOnz=9sf:>0{4g-ܲt)׍:x:gx3^2w͑4b2RyF&u[cՑoo5a wM=8c\J*3ƘWQK0 8fߧv?jnD}YdjRI47u*qBʳs(ђy`CYex00<#Vkoŧ/cKav\!;cCOѕlҽ]~1c{o4!3PUe|q픞;g.b#=/u=vA٨/'ᕕh?”:kQ0{ BXyJ|7nL: 6D3ڋziSHlB!"ۅ8x<.'o0r,΂m(f3S:Ch]GQVy^UFp!Đè 9C_z]&y;jop兛DNb57sdPa{Q=Ǎ-* sn9G9q,iiU52lxB{ 1OB-rZ +gLi/$6s}ͻq`)X[Z|n0 БMSG IcVr 8&5Vvr*h5֡a`ĤP㘔ZTKѶݸ.~^]\Bftkb ' b=j()1䕲FwdȲ>tڜe @nmP!Đx0x0Fu#}hOgP]C4p;}vw%%^%R}Ș-Nu6ɺ;cɖ]:;Z=5`$84(Kb #gTSOmWᑽ$@xX(*h 4 Jg7u:S}ְ#MͰ@G9N!+`YI~V:枅7cUS3(oGԃn0v/ĎDZ4f<#v?js6uWi6(bH2 0 =nL4Jqs!i*Lf3zQrpz/f\--X&xUW p!F5:iwٵ,i*dS7[7޺`3h4?a4c@+'|7KŶo[GA%w:mC!R#Di*0x\A px.8~371uҵ:t8,q{lڊgyUW p!iO}7 䣺rTCUXt6;'xIba9{xfbJs ϧTK=+&iSvjT 'wtSY5yR{;/(V|,,[ xb,Ϟs=LyWyW;س5wzvZ_~ՌY6id(!8eL&&58v>U=~pƜ:^ju]^~] n⬫%$}Wu$B1$9i(Ww WΨI\PY^kہݏjqQMeEQFEC3 S)RbhϾ4UWPf%=-!TO5ߣ`JN@)jfX7}e~ouHO%?̶mdXB=m{xK IDAT?py5Vuos]ǃ57<!"Ty eokm]pJH( 4MǃjX(&pX"$!C7'_䩿`[;((b&nXגj(&a@*;G<|6f7ٿ^IC_j8',so=62;IzI`FUDnv{=SHHRdU7; rں n`t"N0yJ4*27Qm#M䖵z`Ry WT|e9woQϞܵ\{o ďX6g`P@oj4d8Oa!" qiomi&4,)1oN[m>Â{L{u z<B!ɗ3~D3jg[:6/MG ;Kft 70 `U]>*{]WW̄im s3.𾭃_ee0FcHdfzʖ?!߮?%,:ad+ض~xff`TF\pi }y]t±cx7z<<2;n>`C–nz5|_=£$p%]1?<6g@,k?<64ÑЙ@]C_x !( ( D QZQ i~mUhAA(ahp_JkY9wߍqSXKrp!eIQƒl26cIELM. *i4{;g꘩ M|kןìp BԟqwK -/܉1ؓ+7ab=q~`'0>קd2ƙv uX:3F l/5B3ۖg?ٜ10:RsD 7T"ƭ_r>+;Ijܸc~6qd514:J_5Kܘry@9uď`VGMg1>܄oW`Ypzunq %p8 !*U#O nzQ!Jdt,U奌Nnu_]UI@PȀ[SUApHxuۍƗ9! tμSՂ%p5hW_n׷#!${Wݷ> ()LyaM߈y Ϻ1W< +y%of(>L:~qy$lfbWVEc&,itӎ,L'%h(dL=JP5i#0lى𻠑eYu|)zIQHo&sx_w]7?ϖygIy[gŏS/`rT(ܸOtۜ:E55++  O 3?Ë$D|.}YOWqxBS?0jj CO'鋆t]Wyuo•AłcD8(ik֎ٌg04Gp(IG|U-ܸ0?-"?9ג<)u ]VRJCCClCpZUFy};nKÆl>޲RJkpVBc JG [C9t#aq N%o좽:<*> AlsT200KPR!DӇ]B!N7.}w8 MFB&wM9,dX(|`:GphvZqc~'$HIAOIA=L| $((??L&SGp iHC!ؠ8B!iFbGƿy-2njzYZN"B#= ˅anaq88NuTՀloחbd2jtp!B!$gXB"{;UCu=; IHhogN"&f3fc`HC!B!8I||1z,i#*nAV~HX8= %*&]MuE9 D#p,B!B!N"B¨4SYZB_` fٌz[`P0~'R]QN| \Rԙ]rbuD[k!١$!B!BAiiiۅD<vz;tGEd6B *C8B!BSlnS];D!B!8B!BqړB!B!N{B!B!iOB!B!8IC!B!= p!B!'!B!B$!B!BӞ8B!BqړB!B!N{B!B!ix+ B!8V+hhu€5 (~u4RQ]KSj7 HIBB!Bi|7[яbs8رi-msK1SdvZ<~2\GB!BxGoǬڄ[!jlcケ+vmg%9XǢa90(Ckb몏XSbS0#Ǐ#!F}iۋ؛98sŬX̧$ p!B!&܊c\%?Oyr 15 c/̆;)3cZP))7VPCє&t!ɀ/!B!4jECdfѹP ? ண<ʰ~S= )O-{AFp!B!ڱ6`5ܞ YhF^;$ɊA%! p!B!uHCbdd=jjj1Pt. p!B!<'rcHSBѸVQL%_o!FB!BѼs 61E2*}v7Fj;XC5}* !DIC!B1ptu}A{>v8h(ϾO|df n!AB!BQs[P2kﱗnT"vOHv{p[-/f{ak7CB8B!'LW*6@ ɏfBPLEt%*fX&%_-_İ !~$*B!8!u}w;ĦrO0/0$6EqQ+!pRbG7wtJ6[lhAaOu}f(^pB@j2?]ŖZ7by)]T I U,V+ v'.d'MۚhjnJlt,S0Mvg!!~q4c}40} a$FGZ69gp깷-ڱST,!d̹믺X?z(ӒG,b͎6NCpPC364e]܅-a:6+-!NC٪OTT$+AhG3jt"QS!l!lo%]'v ;# |j>{Y<{x[#},k3qLv %U9k?6e7pe|x/\tdSg2&[L:7qR[]YR6~ '=ou7b~X!LSVg%Mcs8TTB'ŌOObOdqapRSNy  fp裏B ][ b6cӆDGdf߾lg$4<-9LϘ~ǔB UZ6v?q9=`,^ϓ?=_0)Mgi39ls0̞6Qhoa--C;ؒ`4i?F.'me'U >F=n[-1TD!΃5c[.C4:2B2ܶt)0qә9)!JKe}DG1_Kkb(cJC!*{!9;j g#] 3yW1:.e i?y;(m &>i2SG1Wo`gE+F3:>wUv3N&[o\B~~%5- AÏEF3c6r6oyfБVBy6+ȵl: ؔkCWLH#gOu;U۳]Mf!$nSFv1Ӛj¦ Ndq <3vpœ5qixϋQRЁ`%8j)=v&CjPFTQbȈ0!Kcₛ@FT {[-N<_ A!HGSUYIiG8r4_ 45'BGB!ˍә6^QխR?^9Zw5k'G[DؤK9' =wͭ[axߝ|hm?w'͗V8λaAF B`g0%<:mh2džuk5]߱@<<+ ~ge/F V5_]3ȤnVFpd߃q)c_}D/ n7LlΪQ;}YdjRI47u*qBʳs(ђy`CYex00<#\N;F΋O_.-\C*v~dž2+ {b=4L="iB:gyo,oll-)LlS)涡pCӒg0u=?;<ڍWmx?&ᢘv>_0a<.$B!D_gctsu%?Pg$[RQ}L^7/)=w*\1Ft{._C;f8WV/ S]uO??z\arx|W^JV6B!8. Oq1Y #1GD(Ǘ]MD~Ap{GFp!Đè 9C_z]&y;jop兛DNb57sdPa{Q=Ǎ-* sn9G9q,ijU52lxB{ 1OB-rZ +gLi/$6s}ͻq`)X[Z|ntl :jH{OX1iL/*`8CB #&%WǤPԢZui$oub2돽hzy)p2 "O( BI8l? ۍZ0+`s8p@`<)GB!D?˱;4V#nTSg^kO[?+NǾ>84Ƒ1Z2_ ml/ ''uwPS?-{养uvzj5HphPWP,aDz*3< EFApotamG a<6ms5V&OK:̭t= ?oǦNgQz^]'hH=5ucW~q77㨫Eo1֠(|{902iqۏu[ؾ.𤳇UIx?dlBq\|tj,)8[5cac㰵cwaukux WBqQDN#jt]v-K Y>Rrg@wofm: {QC';rD F2'z(ڰ N!rJ&&b@r k`w_ &şiWS3g^>m]{(cS Olpa`y$đCuZ(N_ؼa7x/T܌~ !~-HC!$_:,F]w&+k\n^^`]G ;3o ui*xĿ?)kS,,ËӲ9 NXc^r  5` g57HDjy3  2. VX);SIL}% Y)q\t Ynp)!GsUGxS}Og_ :R<^O-K}Z TAwhr*9dO~\3FYrfP|86g|F 䦛q& 5>I.gji|J5/Ndzn2aF'u|]>&fǙ]npDr-|WV^ᄗ'Pc8a^ݘG'qLNNY_L?5{__]?u?@6d{ 1DeeM5=~?m (k.ϠuՕؚ_@~[>X>G`RQ6"cCkrQQQ?t'݉~ {n;HHǺ+[/HC!,32ݷ>ePV.(*iiiLk̨ yYj!oPP}|ޥ'3W}f3)<ċ;\- 3apW2;ederEC1;jY0)ƒJDT.h$aƽ g}ȏjΗ1̼r=w{~Zn1q|y)Oz*qgƃ5_/^E>DO:o E{ yl޶Zʪ7`rb q@V5f`]mFb !ᠢ_/E*FPv.˞ +{{NB,&pkFp9v4$!lB!:EORBEi #2{Vqxi墣wyLf3V/>>>fT,t p!B!Dl͖>71[l`MUUV+ I nJC!BoM0Zmb@E_IC!jPY^>MbE$?*ʶckn !e4KH·* p!BBe$&p7Eq]FMf 'd@j)߾̱)N !Bb,B!#8B!Dʷo]vy!ۅ:^/WP]Lfbw38B!!_#rXe%X pJ숤i7C!B!- ([SC55 *B>B!p\}ڂ?0h[&B!E!*6x<^=T(B!đNB!BDPPe[zxc> W+-t/!!~5\pt:s =?Y7?$Fs+[3?pQB]M3 ̠Y+O[}9տ {+\{bL'p.=t=s@ Y85bi+yϽ'yUiWT+~ϕ7^Ō_=ÊbTU4UE9bvJ!z؄Yb!*6bRG@HDL,U%HISWOL **JJмᤤ$2*>?_: #9$!C5f Ӧ:me(p`Xכ2"ۃ@#gX,?Ĺiymgpj@7,UKx" »yӴ&P|&p ) ,s6{m4ՔS\VISϚ]w^Ĕqw76؉qT%EdO[#eEy|2Gn,ǘ~z&qqqXHrs1̔lgUu4BCY6)Sf p!ĐrU<~Re\F9J©8ގ8)gOai,_cΩYكD@[ W~) igꍸtLGH9H7DS G!j ǝ Ӹ)ct!ws)6QЎLB&wN뭕U6=Xz+6tN81.VFAfj[p("G9."CILG;tZ]F֤QthlYφuu?&0)%3.JKuNy!Df4K@?vI`p;Jq: J]ԚFb^w?.&91f[뀴WBq$312ϚFnTÍɹ#wtږ+]z)|1)v;u7_~+S:;{׹ίp7ow=';v! ([(3Uݯby#(s9.S'J`sBAt:&bvVśŘ9)XY@f&O:M |Zorq\l^jTt3W}'5]۳( pz)}-7)}….7y \>xxu@AQP?F^)=%b"l9z8׾µ|{ ̿o~_uԨsH:>#yO{ (p&}=w\y Xȝ7I2FŃڽs4nxOTȓ;lNgXz16!opK֑1G1<55Jpc Z[m GG{V6B{=$8{kfGۀu(BO/zeiGhXGj OggχфQ^Gf}yh"i`2I%YYz Y ixk wYVImn>bKI\\BMJ) gDh;;Uy(u=[XoC3fH'cXZDުmx26}u>sq6\#~lN4 O,~v~=W/9?f\̻>3YAN|knfB CEDZA4565(RW]u/!;@GG/?؝hZtt|@Ӽ?L~g*!8i?S_ysI5;.c*~fY$3¡*x1+NW:Н7)#iV>Wks禬]rRw}IUud>Ak0=q}ȝqcoomz QS>uZ]ǥIQG_xKVNÐEvI3ROI Vށ#rs {4&2'qҞcs8j\z"ϟǗ̈;bӘ<֗ڨ(Bה!0s.:rEK98~jY qm|c9s4e5zխ ^\rf't۪oW`|k䙯k I~_0w9>&\v'%:;kzXW^z͐Ô;o-KYVskF[W\7!.ښ8Jk&!∧yZ@,+ns7,]~W^Wj:bkcςj07(sEB!+]: |FO" :*k4oc̘u ƥYP1YN9T㗬pSݠ>mQC8OCLffSm\QgȂE6 WSwv5*(DdedIdMլMLzs>+z@dnG lQd⾉5QBZ\Rs"Q\ܬ|BN" n~1FB7M%Tw]jC" : *)t~}/{ *'SN_|n7;PLĦaINQr)Qgq>ӹyx6hpC3%f_?P)'qCY2sB\?lA%+̸df7DX>qfuig3#~O>f˅pY14:M3Mx{-ɄsߨnOq1|x1!>DG`60 (%(H'2o^Lt~ASq}mkJ !k:GusC5`UϜ~e$v)DT|НibLgn&W A]_KSi)6}a|zoeI~mVq*'z3QN`͔|kZ8`}]\:jH6GgvcW]eCa=+CcPule+s"܁;Zu`0vouaX<Hv>}:>ofC?٧e)+}%+Y6;+(\o%r<3.gd{P-77a !=g$r [wCp v!5љAڲ=cPF(QK&ɇ1]jKHHe2b^)dߋBq0x]o ^֯Ȭ԰Zhd SMy.hm%n8Xq33p P|neK_?DQwM-J`$n{RjE 5I>S:VlprRױXs+`e1X`zO=?*CWϚ¸:ZEݏؚqѢAD_{i[y~~3I[+:`y)wv!S%?8DHj?e{A#w%θ45ſ`+YjXfp@m  ::FľQou; &Y.BQi@klIgpVU5BSKgC‚m@AbHP׃888<^FezzYݹ\-6:8;l8v66m8vZ$a4M7nOL=i0`j8TŊ@@ÿT+Vtv8qv8j=l0)f,;?l}6ZB^Q \NVy4Ӂ4xkN^e,m!;)(^/ 4[3C[jqtwR_ۀ!,b3%gCs56ԢbɎhH$'+[ڂZ֭ԫ`ʘB@i&>҈ʚerG 寿Zt43CEc]78Hh&>sΩj$qKUEuu'NO/~PQEU=CP0E  OxuÄ{> ZVV_?^:<_C6%b0X0(77Ѵ_oGaMqOZޱ 62bn LMejݽy(E DfdO PPa-oHC@A&LLĀFw{@pkwtBZHwyj4Ň ^ıC7ֽq?w?"sMXOmb/(fw.e2~|4ؾk9S84LC g¸uvw6]11>W٠QHMCKɆ4﷯YBB !ښAVD``7B?L<$!b7S|"qln]^I?=Uy_ #6.{9Ӵ]>A1&rF2-'UkeQPnF_Ze3 ?vtCrcd*xK0--,Gk.g \,7y씯]w^ϭQ7s?XT/*$"|zewbp}oYyvgN 92@kUnRF8j1YX3cesp :VOǼe>ۜl]hS8Ĩ!cr Tuyښ.Uj~z?BV[Ӏ܄dlx6El=pp|(jJo84oQj!b75w>mR'p58Y^}>Ԅc9_ӧҼm)-\Mn$9b"sdB|F͠plnN+~M7Mj|S6Ѥ}7_k,e碼LI]q>~Oyq~=wڥuj\VN13&62'x⾴?edg7q㪷Iƈ(}T6j˷asv/A޿rL)jY| >O_%Kp M[BФ>&ɭ/ϯaɜ24Nmy߲py(NZQr/.`TGv1/=Cb9c!0j zk+:('2:u\TTTv x@wp{LyDPe_B:YwFцv~@AaGg~P:W1)qh:Ac˃ y?ϫҦubg厫u.K1S_Ȣ ŔFadMN°*9SI𻧑2/m!?Ϋy;_RT|"0˹ kNaڶ/jBBJ*㳏fȉf!Lyr/&oDǟp:u=̌A^_J']\~2#{2rCߢ]1q ^y%{ZF01`:69T 0(G_T>':B#ƺZPz7}`UcPTbnjkk1 B@` CŎ$0Z-mX]\.'*TZ"X,(W/{\G|UտqaA>YS!oMuOmuմ6OXsRIiy#FG iit4VP^eC7G|t@K8,t7*[iwj(B"c `.ZwPІBa{WS^gǥZ M .:ѽ8lmr֣M_ʔ^W!ΎbR2X蠸iaÁ[[(߾QrAEE%˿_*>DUQ~w=w: |­1ӐB!H<`0XF9a×Ohi 8,# D'=2L98tGeeT|3`^wmbkUUՊb88vZJWB!Ê1<w.*gWcN ``+(9\{iZB!&4< D-]sCMe6[#qɽ^@Z[lT@UTR1q L$!B!%ny%,?@A1v\vlB)08`+wy:  bsZю{020r ^ۅւք(DFTBѡLB!c'wV_KmLG uÀď GS}-;p\t.ʹSC] u5~_U5`21M8 8$!B!1#~$Tt!8$ Q1G {C~/!B!BB!B!= p!B!bؓB!B!= p!B!bؓB!B!= p!B!b3!B!U^ZZp]i~jd6c4?0gБB!BqFUE9AADc`4]8475aoJTL<crvQ8_8B!BC CBI3۠C`p(^ l\Olb2A}~oU5`vHt\+i!6!ҟ;l$B!Bq4RSUAR(bjDh$6!)(NSC? Ԍ1Qv!84t)>c> W+-t/!!~5\oK\|g4d{`7׊ub:fkW3_w,u%e;2VlhE3_SUfX#H5&ǽOvLA`ADÁ{\ps={B>mmvjlFqQ!>~X};$$R^tL?ihgϱ$TUnoiO#66v@.e!r4geIQ&!?v`κy&N(~.{e+c&.J~6V]@N}m?fP畿ŧ-=s@ Y8ow!EŨhShѠE@_KäB!ڊrbbdՇKIM=ۛ 1T0"%Ouh_-">1H**)+)CjG:ȨH.|逌B 9 ֘1Lg봕0cc7^oʈ8$ŏcU䵝NV. ƊF.L&aNӚ<6{@~K!%sym4ՔS\VISϚ]w^ĔHqwۻu{˶o7a1f0SrB!G!x}˸rS?2TѽqSΞh*Yɩ?S|% 6ZS/Κq S[zo$yTx?=o>;džz h퉕BbXj*%OUyYe%R(N~F`0lٲX++B#ٮzc[kkBɚJ>S*JT[IN ʎV" ߹n|IMj(?;Z3r|6vpobU^vIVbЅ5l(ma'2y,w}g52) ڈ똬]zB3]6 VgCV@WLMоMqJasFV7ӡ ɸ@2%']neV/JbR>;ZIQsS%bI3 rR m% LJ rUomj7Sb@r<2 6SVۂC9q$?vkGwP15 idMEX+@-h~,ړ*wt8<@@@5 $$%P]UŤ>29~\Lrb"Ͷi8Hf4be5`9sG c-7W> -R^{bR/v,'o2WtN?pw~cuy[1>S \]@قGr~Sn|~/NQ\s}u:QTbbô^,NCjxW1'+ I3i/_M.ڻ]ͫYmʴn5杹<ϔk{7.[/0_w/\~5?9y󸪛$p=pj_v5$؏}ӼxuԺ$IUL?["㣍uMZwֽPIou ZUɷ ~@13Hr:uv޺o$_<01GOGT(!8r45Jpc Z[m GG{V6B{=$8{kfGۀUBqSL |,sUO#=D{>UKxjGd=;~>W& "?6.+FIK*Y2ϢkȺvNH [[ǯ˲"VmÛ%˴^Fw+gsM^w~bSyg.L3J,ݪc1Wj~lƤ/}Eq8N>) BTqyMyZξt%@6~Z yoL 0rܑ7^ZL)q|,r ġB+|?&g\6L+e_ChWn=A=6y uUO%=LG:>~=W/9?ZBqut9h@xDԠH]uAoˇXSm`&cwn]uM{M"v0m}n8UO'%yS\;𦏩,e5^Ŭ8>^XBwL[^}?yvoarH=%V1LVek{!w*Ǎ㊿Ͷskꁟ*GMeJוju&E}-Y: CbّF&M"PHE^>eZ&ɻ6jXz^ )z_ӘȬI{[dQOp݋X?_N3#W01y/UQQ=mi&Qmw|wOr㻬ܷ}um|c9s4e2zխ ^\rf'5fƴhbJ a쉧sA\uo5h$f?/n໻}c:e|Ž/_⭳sv爖=W,+/u=gaJ7B,תBCNz,X,Vno6Yp\ү폽3uTnj`I{ kA1(nQ{KrB#D3/gt0u=L:*kz|&S1 K5:̚wrJ1AwSM[NS79Fl\Qϊ3=0gpTV plhTQU" Qn_=қYj8Sv;*w͆f|rfq}w*O٣莵|~(;TLDE56Ш`s3uf_׽1{ʽs3JgɔpW?X4󄳙=wdQFtos.t%朙_B!!]`=1Mx{-ɄswIǍ'__ #1GD($7_/&"q}mkJ !k:J8wJf{%N*"koXVWw-k]vkUY]^Q U:H ^Hdd҃ϗufιgn.9< YKj#ݎYӋtwB!?3&kwk_tZڢ{3o-+z AݙC(Փ7τ]ۮlx_@/a˦}RRza0e _-Mc2Tn.¨>߬xu޵|va:req <Ɩ5Z5 )N4 {qf7* ^i-ECSԺiC>A{IOxڌŧ'zՒI@UG]Bzthh`:o>2+;4,#Sg\Z_ۗ ? N&UBqS|5z|W<m[pg=ckQkzmQ#:%$$|Rt `uv6r0~Tngn'X2t!YwR9e4~8رiUp-kEV??\ɕttir6wS:Y6O5#M9GE`$84kx(8vZ` <,""_]Y]Bzqc2,ǍЀx0xYjc)>pRU݆nQe^UEB Mn8B4 "5\GA]\ )ɜĚͥ1xvof̃rbiBРz7krfƭhGptt /N/[hE8\G11 IDAT*M9GfƖ pulTq c׫]j!hmj`n.F(k{@Pt7Gsk4́qyh-M9NB!NZ9pP|3T,>4t|2lx/[vcZ"Q=da 70Դ5sF%H?_Ā^KVrD_T7(> ܇&v5Y-:*;-|Lp|g>Gڮ GP,_B!:<`c6ˣ38vL[t90w(IFB( {79h(bZqBf(/% 0귕eovҽ !8d^rg4nf+uw*[v|C;®r4T:FĞcDݵm5t0ú 51%@Mڮm3nLjD6W4(7QFa89xnlWy\4J51:~W50y38 ޿q&2/ jA5's芉ā}^Btl!ᔗXeXG F^$!S wn4CέWRS_93;F1&0ftw#Fjlb>; R[K)$Yjw|a؁t<_xt*k|q;y1^lb sB|6nc#źf֕kJ;zLρya69g)[pfL`)!S 2w7gQvV}?iùt\܀ !į`0jh]VrɌu( fKk~졔~)ްhZ K!'].drOط;oGNMpUyZKodHGo*zu5~4c _r̽L4*%2FboϘyQ}F !d CT]KbXo!P; eDTF_woײn8& JAA-Bfiwqs5o8)/-Yb gyD5H(Rew ֎aTp GdCԓA%3 !*r߰ aq:˪-ͭynn|.fe,XJCIC!~mC! (Y&*&Y3 4S/ #iF.F%bz7{i$~_ U[RT~-$N7\SK-=1[Cރ)4mWF͐+oOL۩9 ZkwP'9,;~Su8~L38oSFV!Ĺ,4<"  h: AQnHCbPTEu. *w<aT` Q--e6,N;Ng.@~~>QQ(M(7?T?:gp-$fh" [A&qKDeβleSe'sg"mߦQUEfN)U8:EԹUnuDnsUdB1ǖ\L $j!466WVCtKӲ19UdҭW_|1ӡ>edO15pNVV6Yi_*FPm+VR]' }ȫy1 p!D3;7B!Q\XH7鰓8CÛy7OX}\N4N'UUU핸2X,XVf3ڰG8dB!B Bã؟JB^oZiq(# nRup@UU, >>>@)35roJB!B!D+ h4qh:A!Da0}hq΢^oZR򲏠**]dn|rT48B!BVJ@`0Gv  bx,^UIiI ѫπz!x<]N.'2JK( ѱ J(ڞIC!B!heө38擛uIɬQT㪪ٌhߟn[ Qi[B!BF Q1GŴuS:<ٱL!B!8B!BIC!B!8B!BIC!B!8B!BIC!B! B!B4TEY TUUin9IU XFDi0 p!B! r()."63~A n9`+/%'*£ۺI "!B!^EY)G٧?F e[` 08_@SwaZ;L!B!+.#.7Zh$Sg ۺ) "!B!^UU?0{ B!BiUrn:Gں "!B!BtxB!B!D'!B!BtxB!B!D'!B!BtxB,̎n%$įaScʴF]IYavGx=ͣ` #B!D{iwnСq{ %6:A)ͦf;VBQGA~s#%؜/1tHHK\"WZ+no;35Lk<0w߱c+8B[? *"B,Իq^o@((uUQ c`=bPP(*=Τϫ7B!Z2/t.yq+&|È÷˵CEE,X=X肪WT܏pѤmmBѱe,alڙSE9`2;iݭ9.m*Jk)"ذ2rSM}g+yDk'ߕձb!1y$^ʴaqv^(I{ p )5uoì.&>2+z6э7w1 !8y9y5w`T’/г<$rQQ`?4X'R˵OtJ'**l< {nDFE`\2CBюUӦcG0K4Apۊ9eױo7G֚P Kp<G28͎_c7Mu=9UIq~f÷iz}Iftot ş37o_oȀpBqNrvhXSWĆaz+._*){K% zE6{eof)N,)ttYmkYp>]+6;:@U:-w{~YźntN^^RQP^[iMѳ5Fξe[󏒛Q]P]';%?wj̙Е˯> />sue3\/.,Qjg (RUGR~̨'W3|Ս|s279z$<=un#u<+>U||9fFG~\g3$.w?/,?V~?>F%~||W?e v O,?~_u4~/xh+=9??f=ͬ}/g%O;n; gwG~2A%j̺{7<>/i} )pp( =&cOÇofKɞp%q8E!DQ:ˇu&h@7yYy8QODm9h#d)F@mr[Ue3e6Bx?$8rfUv[SB^tW'3Xg\{X_qvcmW3upgdfS~WPb4aT@ϧO}2(f><fr͜Yמ.*Nye[:s53'}Tewa<oyu 1}]'rriW%6&AEO~˘U6y=:;`?1rң>82t.'WP=n-c&g̈d&fNaL`/q9弞slMzK?2PTwKxCS|KnZ]Ea:5#g|J=q}|R5%1QIcc"rI=d ]¼M,T5KxY0ŇeVA=w_ĔyV~X+cNѲX" 7> <unsH'2^ 96){el̞4j,]:yV@Rάtw1gDBKVbׯ>P[{^iR+\\s䈋imYkv ٕZ.מRc`PԽݽ呜ӓg3`Ua<6zVC7Lv},qIt89`WՖ8B:} VB&.vXC8byJċH~/ᇴqWTs8s(A &=뚆vFej=vJٲe/v%$$o E!¼|\!;U gU^:kZ\-bb 39bҢtqMs)TSu^K͎`+Bq væܱ-[6f f'=CZǶ8\]Z2[)k)T*NvUF\a޿W=!y#FbiJl㘐9 LP=k KV墩\0iMl6KFfӲ#,^[C,[y؋֚L:e64T@%QVZ$WKxYцK+`z;[RM% Oy9:X[.B4+XA9ˆ%ShP չZ\9ԭ G nv;gY)U8J+m6U6 UZ\$tJC!:BTz077pI*_4xlm݅{\\u0}Écm\*~r|h%k {Zv^'xnnSsL31tc̙Ʀ(? *_C͔:c_+mIO*`>D -uVZ3׉3&F\>\((F0t&·i\NYB1(,f\cKGjt8}܂A5 ie)gR@@ntYDh@ ܇3Jhw^ ۅFf n!H?WKSQnC)]좣"II @2X3$B.9)ʲՖ:Y`S TVVV/1D)҈Zfg<'C&fg2^;53PY(!I{5_d؛Szb+X|4bc^BG KF,LNWi:5gy;Ww4mqVO>o>SufPq-#cx),&zB5|lGu3I/$ IDAT΋^!W/(RgE Х] qC sB|6nc#źf֕Ϛx2Ip+fvo6!M`Cp Y֟vQ&#]Z\{iv\黦xni+98It_\ɑuw'Cb(*R3ٵ7 Awq)OxZsmǤa] 3:(²[Ȭ2?.дjdn;nǛ8zzhҨ,dǚ?M?ؙy{o؆DA0n8 Δ^/-Ù|aWQ}Ȩc[9\;jELSſ/I] 'zV߸;'奥T:[SL q?46zfGb~#B!ZI>~n3y':{ud">J;Y: L?%_%>6n T?%]q:deUR\Aw;wBf;ax^BnI3>Evs8uY|DQŘSH kH2;^5|{OFcͷpoupŏ;_wϗ+ٵ+v,9&5An[/#!&Lzx6k eAnt pRR 8"c}q:b S'zd4Cc'kSt%m{4ғ(5=2I34چAu?Q+EB]sRUQAE-kSN_k EӬl\=9ߓSCh$FHs->[\{:N|  0T*0L娖 2 Ӂٍ,` ??H|||P/w 9zWn?Swl!y~g!Uز~5}&7źr˩th(>Dji,2sJҍ):vpa+!J50Nq5"s8!Dcirǯa%pos[-O7FWok\A!wLJ32B!"u$kcJ))VMӰޭ˵[6{kf +kTʱb9`E?Ê~_ y5=op5^4&X !Bh,9*BxA?|KvoaԍOI7Kz̬WRܸZFWۧ0&^˓"DB!he2C!56w"e3Q5w/ŗ7'`}0zcWOUB!h}I!hWT aq kcגGhf&bбR !\#KTB!BIC!B!D<O[7W`P;FcR!B!įꋭc+/bKg%!B! $'q:Bhdd[7A$!B! "(8ԝ-*-qSVR̾@H%.!B!£Xu(uIUGTl\ n8B!Bt AvAh=DE!B!8B!BIC!B!8B!BIC!B!"B!Dz[7A+(J[7kB!;85!!DK9uw`8B!ht]Gu*K9ZX Mu8U5`@ 1B!:(ϡN `[x!Dݔy{9䷣B!̉e%Tҫot9h !u@06,_~B!uEED%v$:nMCQ(/ ]7@B!uMӰWU+ !Du*EB!9uM'Bs(xN n ,QB!=ӵnW=4jB!X[!D%*B!B3鉩ףnB!B!:OeYɪ1#oaueA`sj̾еW"="-tnxO,*no;\u/rŬj^/o,pc]_[+ޗMF=>itn84K|Y¡4h V"bޫ7]|;q^zwSo_8_uss4XqڷY9G+#$))\̪Vr1^-(B^w}]Y**G2qLomw(I{ p )5'޼Y]M|?߿20vs6[uE9`2;iݭ (*FUESUx`箹@ ˓=h|;97ZeG4Bb8`'!_>ZJ팚BԀk"%5h_8BRԛPÁOk{L7B[ӷ/{Ɵ1 4A3a̝ðaX,VS!= :9Wq4/X;V~y^ל3\g'a6|:VdFwfi}U\;m:A\<~DdiYx3ru3ok2_8K=,mlZ^%/%K7g|=./d%͞A}?y?㞹/s-OD)S:}6y 3|PObC2rbϻ\͒+f#O/eCtK¬{p;WNg !ח)."0 ~JҀAtڅ={2(iWuKC!=#?3SϘ.Ϛ>Wq`\_ѡ 8BtQD3D&-JB56Z;`!2 ?/;: e [Rۋ= k͎VE^Nv(B#W`Iѡ w#Eu8lߑ2e?sװ0m \]}ޛhe٤産[?2#)-DtI tg1an1Np:Kb)etl,ͺntN^^R 5 5FbҶl#}60ANlqa4+/)z<}%Tif1`Pobg%zuSywXLaL wwϚD[/􂗸-7#%WFe~c/.Arx=E ${4r !@4< ׏ ǃd:%!_2\J~/rGo% q!eܸ=Տ lw ˻7f̬#;k9zoUp"凢s:5B]vGc>Ս|s2g9z$<ǝ"_)=>Č%ՏܬxCLYɊ`A*ŏ'"ȔCXx|{D!??W~"y]LùqeQ*ss~i Tx]՟S.mﯗܬ oCb ] fw1:SWN3o<9f3϶%'GCP F|:gzђdT T2}p:]x]C~ûOo$\ 7^̰NViC{q^4A10*Wӧ>ds3A3U9f]kwry3ُ?×]c3i d=360a6zwN5|@'%vdXO30!:4Nye[:s53'}Tewa<oy&#ݧ#]7K9Dn ~P(~.W} ?`l65:pN7>^xw͜X7XM'OqvcmW3upgdfS~WqbKOhްLp+^_Dc7k.3O -`wX; ?*rc{het!Dٲ};۶hgJbp=j,\,X'Yrop󨪊`0PYUuWB+̽ߗsldS"=u7inXtbFA] ݸ n{;[˘3" Y{S>po q~sf(CB E6t&;x f<>&P/n||!;fѤg$X{qnAܫ?au2?!="H?p$K%XM AUTN]5^~h**=,u-L~WOeR^i8غv 6]wT&$po׈Y ʹ2Xf;~> x ;wR+iNKcW؈; -9U:3ery/ ݘ2%3~Cglcrƻ/PIY,>̜߼έA8DpK"zQtCtf :=ɮo~0*$Iskpu nT31B\ӈnƢ5g"J(vLnև[e!;:{v5wWˀӓjYXn Îx^-t tMQK;T:NwSIjqhlu$&857~2 2?Y8-xnүo4Ynpj.ŧjE7]]j׿:yϮB4 txzc`PԽݽ呧%YzYix<זѤ6pXrHo . x<m7-[O{:q9i/]Kwn@?uNT4w.n (x<g{ p!9C:QϏ37amb ?:ܛ'I3Ap qmTbժ[V:먣΂)w!{;9?$$rg8ܛs> c #c۠{ TManImlټ\M|ٰYyϐ+WPVQ7mahXpOa?o&H`wM#ޣ ʺtE6\RM$,άyYxc؄HȔ/`Sl+oƸ#37-aK3r|;+7#w*m[ŬO$f7ubאf컕-#IsEE'S5Tʩ>xR}1ͼָrs(A)Zz QkqіGgq K11tD׎tؐlۼM2xh-!8G腫X i{ѰqJxסJf ů=[cV!7l 'q1hpG;m^:\n74i஡JkɣR|2]A5&O3 uZR1uy Ʀ8PRNѱS1\dmYڵX`;,+״`B[^k"|oFoZwi{^XƵ;ILjֈnglak}Wpi=3㖛H띊y‘OZзO?%!?.5B0*dnJnc=Ǐa򠈓,ltTؠuG5P 4 k0UCޙ~xq4:%%68wLZĬt /} ۹la!JEQPCh,VWSь.8_8݆\N!3ej/_XϷsjjKh3J4"$Uˮ G1gVf,Sj; IDAT1b,e芕~c."^iMEBMק7޳rHg8gBv}M\ ?Oo|N6*v5GJBݠshJTus8b)oPwh%ys60uˤM7Lt7Vfgv4uX~0(kNɆdUBYg#T=$F[Fɘ]twacNu#SNTEF _As5IK+n^C|Qcs4֦Q^Yyi^n̍Ws;B8Vsp+ĝy3}+LU埄ң+av,Z 2}` O',xMG-}+8nꦂ5<`1:85]ɡ-GB W[f0v#m4L$_$5~?FuQoE?F_onHӼ53/2^u?|:MG+w~qz`ܔ/x&v.݇3۹i~^xnUd^ȢkU/| #&ri fe.FM7n>}e(8.vFx*s9pZNJ"6:ΚZI]ȃ׼ -[ywZ7epSK$ bP:5E.c &!͚CMQ*TtJ&$1.#.&S*pf" 1 ]4[ңiMAXZZy (r58¬7A Ǒ`e˅碽5/& UU @\.yKdoxGq_~)`:1)3b $%&G=HLCZp!8{ It>;-8{NxAu"9^Z74D% *^Q-NQIhvhR8KH3~9?N'x`{8=N@%,:00tէCF]SAõZ.8XVp38'>hel``;՜Oim-BFiFu5Zo^G`!JzfX"4@" EQjRFtHC!8̩ؼ|m"'bed:QBr.%'}]xQՋx2!Y'?A[*ZKB!ęqɃR;J- FQbSWo:Jpkx^Q!DHC!WKUB!DgdؔG6KMq!U.IЙMYx[|Zt[8!z8gIC!CB!g& 2NGXbH:zB!@z !BcڳB4ׯM҂C!B&4MCU幤}aA#B!Blv5 !yf9폒B!>Taᔕz;Jq>ub}bSB!>FQTU? {`oMw҄MRQV[ ? UU}:2B!>H#"2Jqa.dkZ{'MqSM&蔜BPHXn8B!9G*& pC1 o!hkL&E 8B!A6g͑Ǒ7@B!BORW.$!8S_k:IC!BVq$Q*B#^g$!B!D+Bq4B!B!B!!8[:b1 p!BVB3H`0 dT!B6 0 *(+.pZ{'KqSU6?aQ#!!B!|БFqaՕuJ"$4YB3Kz(/!`6NG q9(B!9r za=np;yBsJ>a޾͟onCB!B 0(+)!6!#n!(BlB2EytpdX!B!|a躎Q70 5" p!BCϚ?1BEA tQB!—z{@qƉ$!B!@u !hWEE!Bӑ !~mB!B!:<"B!O'BR's79< 6DEEҷOaޯ8B!e>0Nk-) !DSXFEu%$$4 عk'~)\rѥگ8W͡kvS6c3GYx?.XtZ;"O%ٻw`n !Q$t'59?) !|D7ؽ̜Bj=`O=I㤗<>f2wm@Xsfܫ̬LpU10??;#,\?- GxB_=J;#'b{`w'+dĥ|G|:7ZÊboa4"Lc"rJtbև8u׼9T7ؓڈ7_M/G5yqjGkHOU}_4G!|֞E]w6Ā0` cnj壏?bal[JB'-#ĝ,XYh((2gPKx]~.j'@J| SCqN7^敧_{Y#Gi| >/Tm?=!J4 Ftρ Н0;}[YMsyc È1*زC%gX [9 !:bzMEU9;d¸1w\v; %3;wb@-ڷ8שHIc?x Ӓ亷-dAN0,&yxꑷY]fR~^JB|6g>\OɼڣˊQɎBVīyi4w?qקat P@QLP7_x7޸9ۑjkuL ɖ !)N,+g Ng-qEXbim[%!58Sx;X|= 9O[oE67"GHtsH# +sٓWa$%9 *r2s2lDuN& w){$+Z_p$I{0PvmNf~9Jhl I-RmV騑KO0!}X~3}XǶ k$њ5// j8XջQi/<(;F+?*ݹUK[YI'/EXk+ Ըq<棌 u8񞸮5+zX=>v~&_\ MQKNy>S[݁8K1B',BQIKa$h]4QiX-VjSBtb/4- VΜe<4~ݹsݿʷ)t_lV |7θģ0Y&3^Xm[Xu̫Z 635Uo U'w;Zx:Ǟp;\q?B3/WW+\pTp(y/nk8Ikע^2*7gwij}|G<۫xVk\KQ3BLB޹|C ewqq Ԯ5n+oV>y{ιl~YT{[(4o<#\, !E0 4ev{4/Bt؋#vXΜ< W-JxCm h,kt<#;WMi RE|d|P4?4z gPexGJmI\r㵌ۉP?/yX9;n'_oLꑆ#F z?~ۿ fLኁIeM_`[P}oְB#Wf 4fi&}- 3]GSszBLtMh^XXp; ܏cRO%L=[w' 85k9u W_`G7N 1,}Dk~fg=#O-bGBwTÈQMJBvi7nnֺg`=xNanO൧2duv&x0L:ZZ.AB Cxg{cztzjh?[F6} MKh넞}qAw+El0y:,ZMn&u3pcRpF_>;󢵬̚Jjj] W{qWiL;δ'w?y:Uu_c_~44Nky^+jbFˮꣃ,aQµm~uvO-w/3 kTmo@~} VPV2C٬XtH%h&MCUTвdCGj2"8#TEe-dltAhc~&@(v||ړ fn0x<4 .Jԥ5泤1ܥBM+ZJ'3 ) +1C,]x+kb`67(Kwor  7pi q1ԣK(# ?;_+ת8C"7Nqc'9hɏ^NqYchLV*DVJqxGqv;q7,#?ӆ?[/0x@fLi-wsx\N?%PR?(j11 \>*6S+ 1y$a Ӊuai i!2Ibf,Gu/^vncLA|v@GSލW8z AUBޞ,*l5>c1m:ȶlt'$B/dbtLtNkb 5>ᨻص-ϵѧpNLS[~YJ_qD֦5yq{08-8O󖎦UՒ,fzy\Bt8֧73O`}Ɔޯ nBZ^GǽhkO.0* >}ҧ/u3ݛUdBQ4M'+k/Z\~Bt x ыfmBf.-ڇfıIM7J(*/2:LDFR^ZBA5!n$nw/=^ $g aĈ6%ו7k<ފr \ETp^*xjk ǿp0}V5gT%o wMDmuZgAԉSC'9%Q P[hQwA(۔߾Z.hWW#wof2 u@F^hM:|\UP{83q9q:qjp:jp9kp:q:kR eń&B/m<'2goօ,ѱ q'{n`h&KݭzfU gv2VnGUڶYIH1̸n~pWÃ``h<&RI?CFc!6>\hгA/ (ώRcN#}-΋3L1[1xs8oRNC8Qrs(A)ZZ] QkqіGgq K11tD׎4lPmނ <ŇBt$LׅbާS`0j%Dذr0hir81fï5Q]oz, S(cةRgU,\{҃XykTPl^a3]"\*iL>n&([eӎpnZdIS: q^iIDYkw:Ԭxnglak}Wpi=3㖛H띊5ډ޵o~ӯK C!:ΗŠx 6WfhU9 O^UcH1^rh%?KL R4F[zE9ͨM+~tr9)~lƨZϷs*X:EQ[pz0x4÷]GukmSɷn8>s~BbYY8V+VFz֧Q^xiF>ק7^M;}54MB4$%_-gM%D06xL_ρj\XHMő]Z~0(kNɆdUBYg#R=$JFɘ]J5l̩n~87S0^^]ރ5'$kM^E>SiBDKk"As5IK+d+JG-㣛4w%/xi^n̍Ws{Bt4J8&\@r9eJhxot#ck)b&1)T: ox=Ԟف=G!C IDATO?_;҂`gw4/xY%;&_n{ -`NmA6\^,ilJ;^˝3WieBE ~\AIw~ Ci%"d||: {MwPQKffnNq`ꠈJ_kB N"9LemA>ij@0i8L9'hV tSWnWO?{sXޟXI)=fǢPx ݇*p 4n~.͂zGBs&//n*X/Su؂O,iB WᇯK=~,] +] 5?cǢo)V"R/gڌL|4j7<0/w[},CAqyrϖ)`qr"/G?^߰ȱPP1 t m2408O9o-3~G6SGru oi{ W?/^1Oǒ,XOΎz鲅u0FK{ vԊ6/,r}W~ALϤY !UCʴ>ҥɷX%-:b" ]/݅" !_n"TMǎAN,F"kP0ثy\n' EaQ .l6Һ9oJ]8ߝt3tŭ:B/2~^AA흌fREոU$Nt] Ux,$$w"֜j8TTIbSB4 8Jq0a&("NAX۪쭥(/ 'FьtNZZM!5F\L(gyk?8;'8a躎idJOco ŊbE*;T@IEǑfE=X *ր@&МU8LZ.8ݽ}1Lz\rK^"{Ǩ(G8ru0:KЉI^ )1-8GbB6qB!|QG6ڳRbb`:! !ĩݽanNg-\Z v{6 ???Tej8yB!]o (aZ R}:$!B!6B-%!B!|D8Y$!B!OB4LE/B!BOB!B.MktQB!G MZTaᔕz;Jq>ub}bSB!>FQTU? {`oMw҄MRQV[ ? UU}:2B!>H#"2Jqa.dkZ{'MqSM&蔜BPHXn8B!9G*& pC1 o!hkL&E 8B!A6g͑Ǒ7@B!BORW.$!8S_k:IC!BVq$Q*B#^g$!B!D+Bq4B!B!$!B!BOB!B!d !BҢBwr8U5aM`pH8[ZB!y  d2w8MӨ PNȘؓ/)q6+$!BZuee%tYFBd2`0{vlf7\SlkӗB!8$!D0wJu:%KsDB!89|QrTO_"!Bt]CBvd2t:%өDB!B!$!B!BOB!B!$!B!BOB!B!d"e!gJ\@ѶUW:1LHal=:<B"_Ni.j]^ łn=my lڲS^Q'>6AcZXR&B_eԒ7rB`\WUzs*ADHB/L=7r\TPUV{0[8,F5T ]C ݽe~R< 7yYpSURSmʙ?V.Z ve⍗# (,6Lg܄gpj椬jdLDd$YxӘˡ.nh٫t^*YM|WRR̜y?`IHKΨJUu5yy|GL769yY$B{ٕf.|+fN7o1_Y1^^s_>DASϑsizuOlp&\7cRhr^[DNNX\ A[?~A< jkyU]UQ׺^@܅r$~ge[Wrw1j#21P[$G+J؂kn9xGՉӿkx:Y[u>B ש{wsz6ɥuK߃3ʯ2S*r9#t]gytJN$&&CdFt"#ڭ 1̙:MZrtB+gANyIi F5( ҙ㟥D^@ẔQOo[Wj_nj~m}=¯fjYbLV̬%Ō:67K`Oĩ8+CC!(2&W)ݲ7YWHkqK! *zUEiq9U{Y_pzhL9N=!D8s)wV/\ǁ0KIeqsKҼdǘሩL~iKOT4Ӗ[Z,$$$fDEF3t0+cuhlؘaOBJ/zsWL3p9[Yō؟a=OěIo5^Of_ o*QɎѲ|Qa؆2}9 ɾ7tnG̛s1А p. *ЉlU"{_Fv{)޺mp8YO| ],,XˁZտG]Ȁ oA6,[AF^{WHPUkDsv^]O}[`,JbjdV7>GNr(wfRnIV~)nH&/1<PV {`81YPwU *j<R jPTv?7BuKMY1EN_W"Hޏ=WRXQᬤʅa $,ް`i,ąP.4Mwr )qł- hBmD\UW9Lx* -…{PqFv)'{Z։WohD~2vlGZl=|m0LXj4G9T8&!qG[|ѝV0z^\oJJKIH~^7xKJ7,_LJr2UmrL p!DcTmGnL'$leO9kUAOdx(^}0O,"`|h)蹳yYOf?#hMHa&mpZW %DUZcUo2ݦ;uϼXp;o3ƦyU9=xLx}U>O ҝˏWL }k$'ͣ3.!^۳#~lδS]s|c*XcGpO6f2g\J]~!f&\Ƃ1wn=&\VWEv-=vb̀$V7wAFf5Uo UM#vt.$w*VlOY< %eE\/F9t]`|X0!2S¦؄Bp+6鶤XmuGs\÷˲1" ?oޓOrg50$ /c.sw7+ZļF߫w>Z%{׭yh'O| th *ZAd c"2ĈG7Ǿ/BNl=L܀i"q%"BR\WUM绦ի9N֨QCA G6+ pSotp:Y!+)$)=r:8N~ ֈ J4I\LT"`Thci!{GsY/=6n3ٸ)ܭK1qI~ US%ث'3tT"&ذlVNWn qCyy5kxbp_NP,vmuѹ :W:9j. vVbcݯ'avg oaw2t:ճ W (&ɊnW|K`Q;BG'7t@5^z8ck%!#ϟ|7g*3~ӻ^\dnx0}2ߙ<|.=Q*fo`|e KƋC_Fb.F `HR *BZ5|OO)ә}se\ ]{\}t̓MtSg<?w= V?8oa-UUPm _CW͔fg ss65it&0pkq-[ì%E\vMça_<~̙Lz$AùTl?W9sy:%ִg5YRLgxƤz=ї#h-+m⭬ZW %+wdӉ|pm㺑[sV)DwW걶:Q.gQ.=Vr.sw槝TVӆGUJji>{jt5OdD챦@$Fz*cKj'EG%+v+߉Nnl*'7Ȅ6lAii:ۂnK+ݕh(MȠ#NtN$4xEdi\ĕ,q/_ U1q3[ C5uQ[9` %!:õo3/GWCrU;e y{*+F5w犉=_;21JJ~p9?p&]z|+MV0F0߸ߩKmU%4n۵g+XNfJ?y4:>vbLnz\\CeW6{}f*"@f8%|ݔ̄}Ab„ |v6W d%YJh$ɐ;7jB `3PePi:#<, 4mD edRR/ѓ)+ *U+0z-bze:Ǻp_ڄ_Wtp x *î|nG3t{?D(ӚʡPTf!}rTT'T H9`"krX2Ck&2yh<0v}cōU +g^ ;wOAe/dh lR!0T6x: L]XLl2fViAr.ۓX"qc9RBQ0LuyiA_K㫞ȤQc[]`K5TW;l|uyF*`Ƥrև3dvmb̆r3+Ϳdw\f F}̈́A Tc F!-1'ǫkD;%rsszX>AW[bbM9׸;PnZ(;;aӆ2xMOHVTj\TV`4da6V`20111 5Dv =Kxh\Gq%PP*H U8^U1(fnBԔ:CǓ <5CL]Kؔ)>;O"c I;PcM%GbV >/ 7ٍ8h0}:1 F(q.6bY_?a/oeaؘ9==Kңk6Qq@V1>z\VE-eO)S$d-K 16V,a3lg"]x-q;k2P<;O߻+4 v-L\JTRR6۪<@޳\\*-.:!$"tJ3m'MlN.VxQslhsf瑨ihEp32i9й=+UdD(BڒX1eOJQq KdvN^fֆ0;֧GukzBi/ 6b.O>~9t*_wvQոфEF~^!VœYЩNkb3=aHK=Uc4ԊTe!!IO?ŭO(z8C4fFAI$t!aUe;rWg^F58^r0N~nN fl;ÊH3՟;`GsE*Wt^יtl[Ueaj7;V+ukv4k0uVWS*?ڱgW.J(9 zX>ӏHW r9l}|W/O Pr>BE,+t||Sg$0;f#ցKsCWJZwO2qy4'md9-glcWITY$zG2뷯emۈLo4ܴfpm [o0"O4Թ@-/I`JvJ/NK@ABAj*ǝr=51gi~t&zsΘI ؎dy)?h{sۙ2m:SZAFj[qf.!sVl>a;ɣP(+)J0_dTr)u(9W{H9)_m:2]4R}vNh72*\ Ir-АnEzOD-$/m٬Te~A*F*\;ڕ k(kr ;tR5k\9P9&BdgvrO/Z =SG㪖YR^}jgcw&r{1n6^cir9pD&OϞփ;iPqM"VxUo+P$=eAMzZ>%0|F"FoYM(46*T3Gwbz4>Cp\v kٖQޠTeQ>\COԸ `/?GlGQpה0L(JǺ Ř۞o'|3D :Fa50t' Nl[>7- E,7r,ƍP'6i3!y5۳)<3+HeI e&; c1DA pl~ږljx4AHgeSj}Xㇴ#%)¬M$0+v:, ][fV"##nT"!r٢G9zB97-k31@D}g}|Z9I-(y7ʨ6b3l3ոucg.vM8FT^TQDAE?IV_1n.:c\C=:?;ʺo -qW?Ǣ|NՂR)շNeۿıpL5)m€+;ҟٶs^fN zds[!ߗoJ7m?IXV5E̸<] oK|xʨ+n_i0$F^|]Ǿ߱SBH{w˥$xv4z ٲc?2Hͫ;&aQ m!CHӟ T`h226g3Uch7I! ~C{/Ne ^_Q@y7~:9*0x?MR]lI ;s'3N#hIߧᏩSA$μGٽ +*.TxF1`p8ɫMCi\\bWNt^DčQoc& c ~s'R4VS\UU,V+EEEh4jd  *ds5z ňbFue>FCQQ!!"so)U$7sk4nBGY!mb⓺;N(Ksίj '" ʍ놇$˂JscRx4gBlNVayAګ8QP ~R#-sa(&ǿO$~^FUA%\o i:]gVO{w xx`OÓƜLjʄUqu˵Ë*j* fйDT,T5xކGl`.kqln,g޴f"j**.xx١7‘TV[Qz}q{gDnn[{C#KZj+Hs&?خ}~>sHAh$#_?cIpQ'%M;+"&}xo@9h G̹ ZB{i=nNř ng~:7|:l~rp%o)̭ߏ 3)I_w'u&H5rqq!<szY뻵D^..[^tp P> O܇Nh   e,quu|h(흖RAP/Lgt . ~n+  -댎 Bj>~K xͽhDQ,AqA_`^@VO I/=AA"u| E7VTA86%ed&08 hhmO/=H  ]r  M@UU,V XNOI)DCAJYI QlV1]I dA>^NGp|Jhkzi6  ]@UUEd]4AX(MP_K'  gYŎß pn$ {jԔ|JYzAA*A@P'wh4&:8AAX/+ %)7STAA@oz&G58%==AAAAz=1EEA'` "&EQ8|ݎo+:8ABeq%fIO'.REq U㎟ǹS u,UWQ5n;ʧD Gʄ?ϙYaJF$T8qe?H(.`R+ҋ BO֐CM3l*YXd _^ז$dY7xNOBF7 ?=VNc2z"kU1jwA +2ߐݛ _Xϕ;e/ʧ$d4:׶U[_O%c\aI?7{l<W@#yTztU.$$-6TDaV*5Gl#H5h<*NgO'*1I%fx >MG5q( CF5Q?#W| &i ;yw{cc{ }y8<5@aTpHѱlJ,nQD5(\׃iSڪ;UUNǏ`wmW񪥔,("HA l2}9yjӾÎK>-ͦSִ̹*~YTk&3ma`,:¾-El!`-?޽.Kߡ$opkp{EnAzІA 6(/yU€G+<=aHLy 4vV)pο F{"%Wu{|ZPuDEDѱlJnA}j*sUhRVqh~2 1*cX`B)NmlJ=B`ĸ`(mo={?. }kU=Gsd"Z=OcqҒǚ͒PdihK #.G\HduG*LwgpW)p -ϝ>Fcȟyty~#>Ómϭ,Z[' M*PJ7>{i nΡFLBv`ХNż]xt  CV6 R=O9.yWl]7<_{_zV߷ql{^d7<.8 sd"|+a^9]2I yh"꧿}𷅿a~;,^ݍ_XcΦO٭vї={)%ȭk˪KuXBF'Dr5S.p^VGn:7x^_)gWoRVE0{q ˪=rkn1CFEykU+\OLhIkzldΓ^> UUbXmHItp Ԛ|B>L7=ؖ&դ2{f,9#⣖co=I^f '͝Hem[Er$.h2 8`] f<])dGZ.vM_F%ᦌ}H1$ >"Jˀ9wH>}w5 gc@_Z;^;ܴhU .*ŇK'OW?I>x)) IDAT$iuh%P |OYc9[ǐ Z7Bt t3n֭bW<7}<>VQVz6~#ɲK GlL[utqA'x]EpM퇏Pdu/sk(k.cڰ|]m3ظ~% [70-zwqȰH$ n+y|Z.Kfͻ 9i_̒.\J`5q;3>,ocĒ2M0.sA~!zkwo'Z-ͿI Ob'1˙vbdYjh1ִ; C0Ejes1U8m?daFqճKۈk7 6ޏg8HF0Y?ቝyW5l (M> WF͜Bd.i!%2o;jvw̛K~"Ou'ꛙ|v Ws?Mŧt ̻? Q4\Kܝ~*%1n 7?cv سVkz fl_Fl)賱F`F sjrR|jGc#1w~(ΚLn0'.2r$kۑE(9k=2^Yz ұoT2ڧyX&OC狓)l̼C[lGN3s> H_<Ճkoaf}_vE34EQQ̰DYn Iq#GWf / Q2vEi$\0ܕ%*iA]x-晧}񵶱ss49.;xgg Q6cvEżKEӏk_xbu(DAʍ~OEN] G\Ūwˈ( I 5pսߓ$mW%7<ɒ̮{I۹LJdd҈}TK9܃/ ΤRԴRUE8Ix;ɩ&]ظ:jԌ з,(fm4kbWPd`ߧj\T7OF:]Vo~an*jn5~ǮRɲhx!ر+@TG'[yFT&$~5V{zp "QtO=N~~>fY w=G.bĄj5X@j*R;H j>wO?k(/.ooyWqCb)%|_ť~CXy#k1g`>{ {Wү^Y^Ϛ6pI GiNeE'qC0Vv~WгQSUIUƻXyFn6m ,Z|Eeĥ\=ܽIy%]μA+x+#uGu$T)7k[l"q)0$P@d (%4>YM!m0irEɬͱ#eĺﻃiq P0v)5TVPj$ ס ng=;T;\[o;U2siJ8k)>R+y('ŠZ vqX1يZ;ʾɱqI sϡO¶W7ĆWי^o^멕;>6 ځ:zj ԾnE׽WE[Zocv]H#/k2R.7~c&;FZT#2لMX}NO䛋/3[N$%&xoHM=#F曈95Կ3t`5pα=OxuHH@H'Bz49?_<= VɄ$I] =Җ2<|qUYS|ʼD}Q?ם)@ݖ^jv"[puf7b4l,d4`2Wc2Vc6Uc20jpu7Y(.+Nj;v!i8>n}"2`--=5͔pjt0kއp-;ɋL$PV(J^Ɏ5YYs̎&| >UQ[ a5x垱x%l|]VwW݊vNm4:GIh +%df qjfi3}@԰=y۩'}kY_ O`z{Iڪ%i]:"#_Z҅7sɘkjK̲_;6Ի{wVCUmB̈́<Zʦ?`mߐZc\Es6 Smͬh45}e3Ww k}[gsTy!:d&F&hX#7PJɪ-#k w.7HAD韍j-TTŊfs=p"|\$}Z:zMzjJFL%5ayݞ^TPUXP3JnB|t̨ѤѣG5rt/-Fp x2|H k 2>xY7^oQ~ӫrI@'r66Mvp:+ZLNL,8f 1/^ϟŰlVuAP:< I=o6QihB$cB)6\td&O!CM]ϖʩ1k&N*"& I0mJiȴkQͥdM%%e3ëx t=%-}_OUl,6L`v3 IͰ2}W9l&O 3C5Bq<8ɷTz+zWM&tLU^|蛤&Eu߆G h=jvŭd2lT^;Z:iJ9n;d nءV>u&:?O96$-bT>`΍TNd6sR9uqr @GxV*,wi%\õc1[Pqk9XQ/]^Y4,n8wb B/˓_JWy]T5.O4DkW zœYЩNkb3=aHK=Ucη$m7+FS]EEBSv;vgmr*:C!3|6u>a9MB5e((fLܰ1zi$W_x=QZԪT_rVyVv> RҊP2ghl6a61[LQ6:LxǦ׳`N+\̳o`d%< -C$ԦWS8FKuzLOgsY{Jܶ'.{v2<.~Fj͙Ul[GY{s{6L .Pu;P{Y E̩_LD>FU^&@ڽqMٗ@?S\\4go=\ݕ^NN .^I x1I&4xX QhPKFF6v4eΘEw$~Z*OM{Ikጎk}@Tʷoe È<ݬֹ@-/I`JvJK19}РPqg$eO ch|L7RK5ɍkcBԒ4-7LnIl.W ~8m5\B |ÒwJwCBM8ᬇÒ=ը\[m9VCŗfwpj͙(F}"/Z"xݵoypZnIۺ=[Z!>!Q)Ξታi^θ€~(;4LvZQ=!NO!CGyh\r8KRA231]~;^05`>yۮXhLE&7witxgyO֧GIW*iJ(b^JǬ "62vLl~|[ rS]K6z es6-H擕q%1η#=jse]:l?76R҉"6I()Xk7h[C;&1`E Kزҟ>B{Ni^ w;AxжRi[b] e 3&/fM&7}稜~ #Xv{5e]{eq *fUUQOi?ɄY A$/$;w!zc:Jś߹.u 3#@kdVk֦m9ds&' L;ܱbEC$ Sy }M & ǠyK┣'1sI/`ٓP6s:X: KndwYV6xx y%O&^|Q!3ۮoK"/[㭝l\| Rg3uxR2Sװb j4!Lhs8? j,!.W›޵^}2kV6TXfN v?3t{ͤL81)ԔfgZ!!N{+uﷲ۹CF*+ kC/Ʌ"3qo' kÔKsFv[?9}{OH%T+"* "WzJ'@BH7e{MlIvM|<|̜g~[_aI`=O>4Y㙻piٗsV߾}W.d4{X]5BbWl18nGփ3y$\ۃDI~~,9h}8+V+&{ nKOgI̻\h25 ߤg},>``rw|z/O,1 +]SE(Z3Eøl3܌]!1 Z^?[nY-Az*t ӉۗBLu6\ٹ''x^7^&bbKNՉgmGySl|FL_/ *h@:jM`P|jcLf+Ȣq$DG̑oqwl}ol} Gk;f^bOңܻ#fR-«ǸYMKƑoG!Z~t&nw%s>9CNC~T7<){~sZ!9bV*V\}S6fA?ǸYlpn'\͟M=џoEB_ާ Ӊ;.c6\."fC(=n rEgE#&\vZ?e/Z2;)\[Kvp‰#wǷ'Yd&\ ~~[/o=jG"TVVb:4m 5{+ ٌijk Q¡Z‘zƉd~ac IDAT-g~[Fwxj7Py5V# '-+r+ayXsʚ!.drm4`eaKjny)A?o"4WR\@KėN~^&~4ӿC۶,X,ƞ9vb/7Һmg,l+F4&uQ5qz|5_N!%Dp0H2pxp yHNti͡b`r Mr G;4>qn ;L16N<~\E`'ޝ[5~2l eŰQ"H*N]psu{0>ܭGZh NnH] J.(ȌÌGx pմ',w`(mDZ k}" QZZ7́͏cba$N `Y#.f?=8 ;4c7q",O<f}CdU/k/l%6͠xtjdaŬ ӁieY=oGOO9EN;'яS0L?.7"<>f>a6p@ @0B$ż*tx^<ϑcOOphtæy_G70s+!2حmu;bX7'8N"ҧl>o؃kDycMRRR cߓŪXϬ| ˖n,fb!T!a'Dd}Jz/'J 9]싵Nw _۟F\LdpRAD:ݧdRCDDs>~1 17Y$y4.E "S2(!""G3qˉ9&YD;MO ߈zpF z<bY1AOI{ $#J!#a,hw])ixD Sy)TaYV?G%"g"˲"1%{Sҕ~/""""aI?_|;nX,ߡ R_[έKH i}JgHsp&iY]n*J8V,ߡg:'$?l I)6VueQCDDD48\1t8$&mGki8O3[O/""""IƃiX6ݧgF ӨI@OFE骗ƱOz{H9W7KQDgtVCDDD YtBĊȠ zJpȠ98DDDDDDO45QSYA b tőI|bRcJpI*/܂B I8INP,Cҳ;QCDDDDDDNJSC=5Ռ0SpJ\|"m CDDDDDDNJMe9yJn 1NBj*+;QCDDDDDDNJ  >a?ދOL"h0zD 9)ԜC fYF(!""""""""""""""2)!""""""""""""""2)!""""""")MT7 Ii;S%%6\|^<яl ˮVPt{p'dY6m`"Ecp]}v.%8DDpog j83=~,ÓN]D6>'IWpߓ_cnߕ!CBCE sFQx쪇TʮbFsI5>f(1H'd22HPX%8MVOdٓMda~ɡꏈȀfӼ)~Y^6<μI X뗳?,ߡic߿ŧKsxIDDV+[눙^2GgtN^;H]7OYףct8RZZK rՂ&SkM].Xb%̞5MQ>]. '5=u2wܓ>""U:w}!W٘췾 :𡲕_Ky?|yZ?u#a7m[1c;Uld:b_Ʌ#B?wg\?cGGl$)9lx,>@^A.EHK.=mXA!ɮ]{˥Oy&~""]Ǜ%UFlpd֟_ڹg&O Aʶar,)y9}4i>jRM[QRB%9k8'/s/ō$vkY&CrXfCzw}4~Ncƨ܄_DEArHr:Wr@9;6newYMrHLG{3DqT44<$e2a'uװwv6$S8f"qe{8X;v;Z 1׈h=9#ؚ$Hv)1%y`C !0 '{j1#9ORQ_VJY] aE|jyY=ݎ4QY^Nmc0|de: 7VSl9`iczjئ<]ޑZb 055 /Sgu>rh\ÆQW'TCDdJ^ /o t87 X0c8IN'<ӻu5D s5,+wkF7Į_ Vqf\m}8 Ǹ;EH^OΞϗ>u%K9!g'7w)|WΙuu_<EGQo|l2Iƣ7R>zlNgU[Υnf3}ݍ\1/>T;Hɦ弽Ney2#f,)Yx[#t͋;|d 5x>]hZU}u {n-Ku+twMNr\)c|C%㦡ԴNShjl69U,]ɶhи1pY7 W5/cVog\u oͿDp/gvƽ+7>6lWͬÝ|{Ǎ[3 IkދK|w; `&nUfRVp%d':6WQeoQCB)ye^XM̝賦2>7 x'6cWi>ȕp`6n>g7q`Z69Yn Gѽk1FC/́wWB{+ܳ8or*]}ť%7'GWb5`C0#? `}$۲mc,+ƯE_e%9s+!"2 ػkQç0c__Èýfa^~,%;aEG69el s}c$7G:3fϡ0r_}7^~40Wyyxܜ&9v-gEgS tx]|,~-wnKkm0 ڷڣ;ɯs~2''f_̦KC,NІʱùK6nLqsӝq蕇y96 4XNvw.\<\Ͳ=72~isEN3/?0!M2*""Z )3k cڗk.g\ȥl%E\w>p>J r{yVFNj36W5zY| җvQ=v ~ьϮ's{ 0b7|+Y FDΎloΙ@1>pIY%Nrٲ]Y,'fۘ}^|n~AKK OXp~p""COf, \Yf{63WK6Ms+))alxd!5 8Ǵ=_N>Xt6SWtqL5r1yeYW\ݳ_} \?I0Etz1K( 3)qbKy Wyv֒:n<(6NgŴg<>"7ʢa2ore0k^gv(eKIP;R8ue{ u|.j-pNeFN~LLRvhaI}aL,}{WjT.6vVjm#3tf̙@$O^N#o-^LjnN0zc$%d7kOG$9 A ȎmK;\.!1]LG5 $n\KeبTE,%2= BNÔ]L g@2q0bh:bR8.]V 6o̱a1uR*JvlM^ SvmG=&'U>a$ՔC=3Iql8Ȗޛ QTrCDDUۖ4I?w3Oj7R]LRӕAulLR3H$;ӇQLUy5<9g/mb犕$ֲ?j>>~XIlΚ5&7Ik%zXV|bZLijy q>hs3!'9J59^gx~:+!DIJ;wYtq> p{9 j*Z_(宣ac&s3οr%]=M+VMeugJL{rI5VM56N& >+Cv ͭ/n~:X D 5pg)%<~70sWIs&7"ءx&n'дP```P``8oa#/:*!"2 9Nà}|Ƕ׿wθ F׍([_AwEn Ht:N7ގY;Bm 6;!qŁ3Nnpvx(JM# OeQe9[_ы 䈈 R ֽw`$ gE2)Bk4MNEuS$')yY7T`SI,y998LYxXJg1- ;m[gŠbA M9L 7i(}natx79HJjf]!̜yGgj~X ^0x>CWчiu6^q@)y|8m4 /޶@6>{d_t#߾FP {6arz_Y둟aM&*""8ĊW`cU_ -A/Kt'ζjT,ͣHݒvY3 ўR}g; Dži>;t\B9Y̚94'7oR=Z7<$ϳO,MG7^d"V.|:&lJi髲L!=,+%o7E/:tb׽3N)3,آVN&qTFu1~ƵÜ؍kxz.+A#\dcU \y>Nn_\.N3Icci*sC|w[lƝ5O% q`24>!+]J@ˍ׌cXT[~c=<@ $L=0HIbN~^xqa.&8fG1Qy+u3חQڱ\7R6},JVboeEնU?tz f:ӦlwuvVol6\:urP5{ֿ͋˻{S4 ,jkl3 囬˛'T10IKqa`Q_^ISAiS_RB}BKvN2&6Re~ >If1,<TΝ*a[$O6~zȀbuwpxL"k:tͰ|zrs:,Kfٙ&emG/`كwdC:&ءzj'ۤ0}MК'v(oh"C ̊3=}i<>8{K/g Y~JIg!v?Xna&faVG'/}\m8)(9RNPu LDDd@eٮ&cyI#Q*waxwO9׀X6la'6Wcw;O-,TEѺ,yU}9Vd>?ca5g3-AtomX=V۱,`}) h3 ,grs3j4"uEQm0I5m,8`p%5>w_ ~ڥKXͫCƚ=M"O_poy~^`?Kٗq{5rɜ9CT]o`E_SNvn1 >7>/1=j;)2$b$呟{@44`eaKjny)\YJqe-1_b:yx"\fK!'+Һ"""k2sN#`#u"8OJ&&h1H6%$m ֮>n6 R\\Oc֧RvkpH )pg]^(8PA&?2u>_#&dtK&VM8&Cr|Q;+ 7aYU%L|M-8L*m.dĨ&""rpxH&N \$}I}PvG0S"}rz|'6X b7~OרLglF*""""zj(Ҟ""2 8_/Yw^>S߹g/dְdM%l\%AICNDDDDHT(%8DDd`0R8Gxֳl w c(7z=TKÑΜ͜4WUPW<$ BEDDDdRCDD '\Fw""""2Xh8bbX!@,aRDDDDDD,/CNz>#JpIIȤhC>F)->Djff#JpIOL")96Q_[*\,;J|BR#ZEEDDDDDDNZzV6^r%fY i󓕛7h$OX QAO AO AO AHgHJp @ zJpȠ98DDDDDDdkj@ e;9#5#Ĥ^ jUT[P?! ! b47SZ|` @zVvWCDDDDDDzjk3a2N 19DvmیUO!""""""VMe9yJn !NBj*+zu""""""2hz?_ lIͽ:F ,+97A̲zu"""""""2)!""""""""""""""2iYXPpL %8DDDDDDDN0&4]?Bm]3Q\Sds,oKU&kd8,ˢr‘0hӅ!3-LsH""""UC~s]s%y^D4G 4X5R !*n 5ۃ Dyy)%L<Vr ?n:d[DD;t&eل!0.DFǾ{;T""CTMm5S'!ZOQpXX&?s;;T5RW"IL"4RRH'4rs;an ;$/ QM|J6~\ݎ4WQVQGc L 2ItwqP[#?G 5PwkVS9Upe0rTDsWIb%V"&DpJ8pv*"""zUV`6fZ:f^qc1SRNy zNfYr ,ygd4Vg_߃3k)Hʈ |x ""C㡲&Bfq\0<1bU`6SPEDDDUl&bヒƸDdnk3w>f@Lvon`5wm(cۺMh:wTrÁ ĂzJhO'g=Ȗ ;(k\s4R'JB̜:df[Y=+^njEýǏi0 c˻qg3jd ^W&0""CTFzMo8kz2k$UCd~&Ӓ;T~~yLkj0|^LsX!ukMǯ;u`VnvN"p|m4U@k8Dil )y t6RS uPUMe!l31;.y$O&nǼas&&`eO.f6Ӈޱ@ J2eBĶM1w"""""aѪJT $\ bff`UYWF:rヌwY60#q3ǿh&ӹhM6$t8>FL~rqeA*\ᴄ$v?"'/ ׶&"?Ǝ@5Jp Y.lj6l8WF.Zt1UvGDDDdΌt9i"ι0LpLjx#^Et\))Q8Nt_wx: Fĺ8ΑA^놟TFY3z#m,¶,F됓H,J7f{zsDPCDdKKMfSXt%,~Uw"""""} 0Fr2ιXj%e1w B6~HvncGQ)u #:N6ghJp aN lE{HODzblؼ\!"""gv0s|0%m444 8g$N>q$`cwqNlm?Ntv7Ͷ7٭ l3B}x\N Mٸw7mM !n˶-2m vAYy)MMl޲cwx"""""Z^+)ALl_ҷP__@bb"Ϛm 0fb`G bCCAB6`0~(I,ֶجӉQ6VG0Fse xtXq3[w>_N*""X]-p8;IODDDDeYضev[vc@}xoL"o1*kbg ?>5r --؀:<ŪflL&dTb11&=20(!"2߿Ӧ1LͰa/DDDD3#+U^m۬~w$$$O}C+WlMc;L`W*+G +q?ݼ;0I>l[w) acJ4‘ֱ*^;ˈ=w4DEDd;{,y{)7l:s¢DDDD𺵘8">4IIIaIF[.n۶]rkn=0_Sf2lRXΛdwāEr'+f_dΔ KDA䜙dl4ppj,0S1,5&O߀fJ"r9rK3DC#_W&Naٽg/G$.ߡ 4X#ޏQYAdjΙ GV*Y0l&{Xeؾ8Svz??5Vn$>-N;Ls]uAb܋fo?X03UO Ps;d$䴶.#F#GgmoFf5PRTDi .c?Rʦ՛!/ Z""g8/ȀHMι\'%X8gjbWTY 3!pm-LK=93nֽ%VTuxӗ9u S̈Ĕ&|9׮x Grޜv 籨-n)|:w \|,HueTXŒ)|hMp7hssm2s9#.Akʷ0uf!"""rFضq-Sf9k *)B(nx"u8SH'ArŽh%Jp'!)mbϯ!8M ` bdXŎ4SWLvKL"{t_+$uND֮>nKpNC=8DDDDDD䌕iY%o1.̄`X @%9YrpKLO 9{{vr'%vv3v '\Z+3 $N83#%0$3""""""""zp b)Igp7%8DDDDDDd2MX TO2HIM(T,a.[svDDDDDDdhn057uKWA+5#CĢEH,:N HJNeM;$9AXvJrJ* I:sp]vo9eҳ|TWS/18Loܼ^'7 Sg"""""""Z|boС!*"""""""2)!""""""""""""""2)!""""""""""""""2zl8D0Nt=Pv'LFNዩE LmGT5t=Pw:'LFثmضMSc=U,+֫ #%=DeLq:'LF9Sl UUE)M yR\DX4J}]5% 4]LדIN$""2(g781la6H¡^d2 7)[Զes 'LF9dW=8jF4x/leayè,+şا=8t=_O"""CrF9zcm˲ZE%g ۶ ,O~Lu*'LF9Sh ZV [O cv,LחדPzNi,ׇ fI0Jh  s"^'8T5;DDDD'To9!"}s-zikADDTVp'"p#U !l|P,8u݊Cx\(LCUALI]_jw5pwd j["˨桯 x-"}˴ m~UpHLaq{NW=SY`z(]Jmce2*?73뷡NDDD@7-<:*_+a daQQǑQA0 0*OEQG [KXB !$dꮪG'dO:fsrNݷVu~v[OZYe$ F Q4 _ܨc@X¼Y'wZA.ihFpk/X<ZwD-4K΄'0/7llGh>Q9|r!)!ŋ&VԐچ;AAz3SYS͂%<~0at W@W57XK2/frw3oWss I.>G6 |Nox츤KVuEk;2܆BA{^xM GBz'OELm~ϒzv,9 ڟ ژ7{=W 5!NA>W`/WJ&mǗL);621aF%}/ \ UQzVKe~dx/jYZu8ٛDCQ(J g( ]fsx?pԺg{ZZwzޚ>e'ЊgSb`x>#}7t2}N"?? 2f`D=:yyyue84lY[c ꨦnښş&cVzR C ɅK (y5w<՜"azac5 l<͆2Tq'L[s=#w>jM9޳_b8hIDATx*G -xwcGƎo 6/bޢ}; c\V>,V,),x !=6ahSR?GX_׸s*VQo~6imX}REper<|(SN&aKٗ#oGObQĝ;AA~ jn.y ,E$J`{FLm 39m9}w*7}W(M{'\X;:gZ"뾝k3NQג#}ͭYWj*`0`4eF pꈦP^N"a@'I:@|dN\#*,-o.u #՜"[r[ 7jwf(Ej]\7??[zѫk"-շ@oc:!͔&4MCG"õhaSOft_&v8F*C3 x&# i_IN\rHF f珒`u⦺SXY;qX5̝A=r.j2-Z'f 2"~!  =5uzMai-h:HqK@';@|n5Gw"AokΕ?@%oRv_y~u'-bdƶ9<Âj账}>\ƃ/>e0ڛ;JzM YxR~h}>9ȖYxux ؙd)\CidM![NXDq' .6ϼ3+xJׯ, ?%^\Z:Gfa {g*@ך-91cGN#T4rR8ZDx+ @K#m\Zig\˾;o6WQ923<' C+*ȕR9*Sqf{rUvϓ"F8SsÂ*<)4V_|y ϔÕAY ݵx%y\"}oL_"ɾ.B{lDe@d.wZoLM!]ԜVն_.VݸMOdzGY˼.S>ۡsyp_h/ &On4p={0o;E7qCl!63F/ge"?+"M^%Ņ/w"Zc6eߊ]9DdQGbrAQi r+ص5@7et 5OAG#sj6?ܗ!-?m6Wv6R ۔U!Ɲ  %;f .8ޣSd&g/6LYe~qttͅ]I. 0k܇7[ 6zʔXqYI*թ}Xz_!2l׬#dc`#J>H6~֮)|5 ]0؂ٷqW׶+}YV1M+qY~Es/wΪk`&s,x.vl,Y^p8Ѩ(H5v->%K%aq'rLÛ -A΋,Z0.Wv0D)_ ؂$ٌ#g-f[n3rBtco" <+H?nY9=+!KPFA%5,ߘΠʯt+:"3PZ. _*H}r k\ٕs{;<7Ld%`yȪ"T'vˋYy1QdRTа+ kH?囍!ϫ4%ސ dlNu$S3 ٯ^ZIP;Bd[+l|CyVڥ ,ڐ&:ZKmI'puUEr.sSL>#ǹbw|a]%*J:v!#в.9z JhHXcW}z0r`/uLKR/{|-'usG~ ,-!o@B弈:<+!b /l V h?tsVXu,U.9_̵JrdZǙ+hNiLe?NU23А0EF)v<AAb O3O/ss{T2?s>ghFls#RԳͷԏNFT i*hRv-z--T|auS"#9]!JHnnafMN.Y1 lÝٞ[T<ٟ%WCʄ1W3,$гv-w7'f%T>a8D iR=z€ƅ]{9SX(;K&肥cDxhZf[3gB~e[jK]LqtV r9"'HQ@ &~6M#aZI!jԱի91J~ σlLfѴa7;Hu.d\{e[ʼn2Xh;Y Dyu0`(p\ yoؙTZ%m;#6QRa}V e|yܥ:9KVi!30\%Q&G"Fw{׺(7Pte.{_XWj^MS=;hw˙ˑNꡅ; lֹk΢JfF &}GV$A\:ŞM纁UF^|w ˝6w].!t&/"tG!]1 ??=gou4$[FE3e۾MH_I躎m2ظ'eo!sr+VnG5 /}Gw"A!+:1[׶cɻ_p˜\y!<6&>{2oF{ٰa/)G?=L5bCoV ";Ft,ϸy{iL 'ĵ-/$y~ ᢆ^~͊nhmbshjb49qc<~* \aGQ륫-]yf"\MR8q`'% !Y-F)~^|m-|/K+U+ ŏCjΜSqw>\bOs-;'>ʟDݩԋQ 2S򅞮7wD Ofת$S]F>ZEHo6TR#3u$r'gޙXir" :vLcL4LfG&a p]~yGTUEq)~k6o*,!,M&$-ͼX!|=KAQ\WFʑMXlKg9JVNNsP&YÕh) 4'<j`rgᴈ:ړ$0Y :S9KSEѬ-nSpўa\Fd6 5:(2Ú*:ņɝ,#6&abRsb@F`IWǚ<8tSD]œh"yp4dckoZ) tWvoV$#@L2\슆d $fAAj:{ 1ert(?XC[w%O И]~X1Jsɢd @+ 4%/\Q&8s2vDpT+Z7 Dv9p*^n9֚Е|%h6(s( fmcvp0:E|Go:+oy7y|.EcjcҔ8t [!{7L&#PC%Vݼ?{dďĤoAču n9I6 KiZ+5n#Ʌ{n[YJO˖ِU=cp:g0v}Q}4ի8-"~ P9~_mS=:U-UT*R+bmU-t^>=K.efiՖ45tܸu~¬isqM7v*93V-tozkoǣkj[+HZɶtHhU!  4L xAj>QWogS[Xzv̠+a-{=D\.-4LbpCAAʫ:?ZwBpC=úb@G,4x  U+Xl@UUdLU݅kWD4M6ISW$ 뼱pQtEjN#zaj vE< MQ]œ  4f"o"_j8.$23jyAh(4M#3#R+OBSxALBSk$!2A؂I:|LTU,h(4N&;+& 0.ה'xALBSSۼ58+2*LFz*gO@SU+/l0Ll\<~x'AAhD(4%ktxGHXA!ab=F%P,#2IzOBSP$ ycOpɲi"F(Wp'xALBSSF-9Rr'"Ƥdۮ˅E< MՊ'AAhD(4k4QvGE%U1ZēTv- #FMv,Bi"AAoQ*mbAAAA31!   B'8AAAAhJk]W]AAAAh$C+8AAAAhIENDB`Solaar-1.1.20/docs/screenshots/Solaar-main-window-button-actions.png000066400000000000000000002732751522022367600254560ustar00rootroot00000000000000PNG  IHDRIgpsBIT|d IDATxwWy=gZz%Ymmlc$@BMBB' ! $! 7Ԅ`)$vEq/^WҪV[os~ݹw]I+ےgyN+}y(z    #AAAg\Cgp$   `\U㷶> Ã8wQ+ڹk,/ q{)d7]~#PhTa_!>8}3ܤuR԰U!y&-[AALc<7&™XP{VI'VVR^rͣ"N*=<5Z?yocNiԶ^yc{D9l0VXiֻ;@AQ'jǁr՞*l7QThb2 )|16ۊ <!ݏ4   ™N!Պa+hdOX+TӉQ<ڞH~ p󘈉3DmUݏzCAR7tJbc"a tum&V & c>_7mJ/a8A   ,r^'[fr^ʐxe</4: AAiR7zE³ Y &&ad6qiNfNdJ;*5 ѰS0zIqS@P.6$T'^Jg/` u< 92@!L{[<И(]D{)+CAAA T2OZD&K@&Nt <5BEv|um)GUq%wb'P?k |㢇wcږ՗ȝݰ]N0UgUW9lh8lކs ӲgC:5Pa:Zѥ-aЙyZg5   ™N3dtPŻ(~c*ǝ5 Auc X<*}~:<ꕏg|m2xSo0?y8$qh\OtG=&P|a$O̾2Uk"##G 8}m=HMw7"dPFD-"IWi sTAg%HjaY{KEݫQcgSHHTyJo5JqFNԮv.*(Q*Σ^{ H'omy>>֝[1m}֙G-&eTqR%Dو鸌E"K.#.\QC-{l ވ R'ٺ%lJ.XS0N| pzfrl9EN+|Lp} =Wͼg`??5 p",EN"$Jl.Y3tJr<槠yYp~!  ILW? ZUve6ڠ1&\QQv.֠գ%΄֎TJӖq%X, BHl,*Q^=p w3m!IJ.]$A 3ȘP7zZrҝkʟ]5T˶pM=A ѱ eo3rԸ'I+2ρW7-ԕ_Qa\c*EiSO*(@*kׯX g)FT"`NGsUG(@c p(%J8UchTL8=*1(*ף3FqvIß1 =vWb+@J#AQ̊< YT!/|ʷS" bphM)U7fty4(FzMr1zJ1֞[/bZ׾ᕴ{KVy(`w>g8L՗õs3d-|1=&yd_7ʥkP##x: Xqyz'"?yԮK yrwɌ7gU@ :zLOCGSc)'E֖idj 0Ap=[%;NtYqQ.qSI_M(YM>ҹ?|`4ήɯ[R1TLe[] ̬ h.H9ÌRpyvA3 { ٳ/qQn'X;v1^łK_'#cwd ܯ/i/7/usu/yͥpYtyP0y*a7ޱ?}k۳3%|E]|7S(ѤKkɴOG_ ™JnX'`18tUxڶqcmqǪ5ALQ񃀤b^ ''UNS49t4eNӚr MSe, F<#BdYEHq2n*]IAѲ`8 ,Fb?':m=K8S{CbګG=;)2_qu8Qu,T [b[0yTq:߫gFeq!MJȃ 9ƗyrjST+Wjz]pߎqmC|xUiS}~v?py>&n^_ΏoJzn7wkZ,Yz|&_TRy\;ӹdA>I} R!ؘ#0) I"3gm5~ekIIR1_cxk4ou*nhPPRE<4鴦)Kp-)-_.1PklHT<\P5+3w Lk 77/zblT 1aPF[_TƐ 08a֣!&|VW#!(c| hZk1?6$^ȨP&YqW« ))r(8K{(Tr2Fn'  W†>ZF!DaPNhPdA#˗,.Y+܎NqYsv}~~CQIm Eʻ{xtmsmf Hzqc|z&HLS&5N㩔'P'e p&)'rjdǔM RN'otwSiF (=r9͌Olᑪʞ~F+6U]cv(ǖ#=@hrH:6E? mE(>Ip\~@YcAhE3`TKȃ sރ=`Xhe{ՋH+hsv;!6D֦t%pX<=_i*+}Ek/ӯY5c*ex MjNAt'CvS}tL0.O)KVӖ?/B:ɗ =jN'p8.1Po4 28蹓.=CZ/Ko㧥5MabG|Q/#CF"@phģgD_'(]b`Li#H#lnPZL-f8c΢R1麗CoМvQx(2'8i`xV0͝ 3]-M1|Q%{tGU*|ceWa_D#&Q gar{?vj9[M.s\Η{{nW95mJ=[ZqJE~Nk5]\¨pdEGu^YaP^ {Tz9ƟҬec*e 18~>b[[(fXQ@.?Y[1<49-.> C5c(~i#1~ "0Q6|Hi_dž8czH}CE ݠ\x].Y(ϗ90챫Hhohͬ&4-,Y0/~ǎl' Q-tJdm+2@4f-{{d5oU@#yi2k~vOQ"xܩo-b!Y8AWLJ=% Dڳ4zLXxK= 9FN([C*RAhǭǪophHSafKc#"JH +KLה(zaxMݨC kvSh7_*Mi3lhG K>O>pBF_*ї/R6zLߍC'+jDȃK@"5*2rU2=d)8v&wpk Va?Hjtl:DC4kQHBA}Oō/fк8yupa~[Ƿ}vg:׿z%fq1dc8] <!6+)`RJUoBһAU{y`r0 uA8|;wsxui^6nzl{D޷ 3:;8w~xf:Ux(uch;?I~"Љ/V-ǰ7Z_Y}xKG}QǮyd| yA8+%9\f5^oj|cƻ.E.˟w7]F-)ڛLGKZ- :4gM#[xkk^]H@%G{JϦ~ɻf!>G5qo#lv~^usA([a9UJ !0ccND4x`Bɾ$0*hWt,K(Q [S%g2"/ׅ:WBy׮Q %b [!hiʅ(c@R6IA\?GMm5cG m{͝(c%z (Bk@GOfw7`\+j `!J8A[P0 .B1 +/;QW,Mw<W 2i<A&:s.)e=c9< ):diG&R!JBT*Yܚ1eCLVd[6 4A3/ _9F;\^Nc')'6RG9#G) w̯S(:r7JzD d4$=*}&]%gyu$΍Rhbn=j׋>'˝doG{(OaZZ׷ۜ0%^-ŗf2ѠR1" @OBA X#BA==B W`ۮ}l&av?MA3esZ֑2F: rB &1NL(0TbflyX@tYJߓ pV:^`P?VPsu,BHT IYVmSU?pN6M!L[8TQ6^uU+b;j z5;L!r6 ]AAAq$Pz!@wA"Q tgH*r,@`cL0+IυZZCB<^]nrqW<( LRnTGnp*bKPV˯7h a>i; -YMIUbGKAjJIz$T\*8y5af\jLVi uAAAHLd  1Q7^چ5&^d uvH mf=7zӄD` *J.a;)U&8 F'+=Z<k@B yAAAؚ)ֵHY͘"F1'߈;֎}SPo,&cS9tMv0jB6Z2%>oSiߨ0nRƤ_\dMx^ʟ   s~Ky*5Ys+o$3YT\(vԫIX۟I75[|&: 0L٘Ġ    r ```}WxG\\.ssJ)n6 @)B׿bvJ$! 3Ky 4?]OgoA8G[P62L}YANw=ҥKdppu)J`Ow7JieH0c fϞ͖[85葟M Bcl;qغe.+济uo+kQGdjI{uPd:08K|?Y6u}m|~:غQFEvBte`vF[䘹d!ӧxO˺ؽ˴K=Tmqo)ws461mB^}[[? 7f=?Ȧ7L[.S1bȾxK_8RT*c-BEkTP(l26m9]i2AAAhW=Fw򍯼e5ׯ]'~I^Z7}1pg= x=U;(UEϼ~?[#7e ۖHd̩]Wñ ?kܶ*wW;7=CT*̢^\gȣa)=yvc4)a,S_~/x_|_Jw~o>p 2&(& fw <0<:>;<o ǧX[Ki [1&r~O~.?Qw7Ǘ3F^ߓ|_$~ k-Z2A`C?k 456m֚JX,b,35gw0hSXEXMٸja곙9Kx'_}?ᒤ5_}0O}W@lmz Jᤛi9m/8- <8,]wmaVW^m5=lxGg}.e]͛v׳9eZtL9Mb q׃\eǰ*RF(aәTo>sEv7$`돾m,,ᗭ0o[^󮛃H£h-o9-O7x~ͅ`7[w>KYhjŰg7GJz7_-~d?c7^_(?EcdqwE,7]?=$wB3w?@ ^ϟyws7϶~~śxWN,ֈ<}c  1T Lxg?=z?M #?]f[k7}]iR٧'\!p4 m938Wj˴<7W8:Zirr_}y p? jf~ f6v\b(ÛxbWh֭m4 2d {7c^Gw=ņm82XL ]z2fƎ|l/oDnc9$`ڵ>/|^?T|7j]'x\w;{dqcqWW]L׿Q~pȀغq;us^~R. &d{@[:.S. wHUV, [9;o c;_95 \&o| O#x.'1a[ F=^{>~=/` /ouVF`0k |v"#C```u2 4o _׿}` <4³\|b~j3vs$t^r [-XmAst+EI V} #}w_~|<4/ ;_}(rUٹ\7_q;OPfq7?ޛ&]>yzJMմ{O<8rW$sw5,g? ~˿};vd{̯0\ōxj2 Pa?Ϸ~vxus*q)[WMǪWﻞkW`w$z7$u[߇NwƮꆑWjnc"g$ޮ%˘_35Xry]O ն%]ed8dX̫?Q.dp(KϏhY`Ebӱh5X>M'=+OEs//\6F #{ -Io+2PU IDAT::ر};-3gFo#Gr S?MρG~_>̜sGۧ3\:FyL\.|NkXdo?z Ԡnc; x[7ٻK{7n?t!`ګ9g-ܽnjl>W5O5wPJwn۩lYy f_曛cct~C;\*+Y4ȎM9Z<=u䔄iS{n5X:ٝd0zzk|@zNd^'k_0ٲ 0w>+f߂ry+1w@R[?ŻoQLR8Bѷ~_yſ{e ϩn$5EZ.Z"[t֬X7˹0=<IR)V~328={(!o888ȴiӪ ZFFF( W b5 GG;?NZB =z檗W>Os+_=C8oxݫۺ<Zvefo~ŝ?hb 07[iV]N [0ܽ:9nBYѺp gla~r JErFN?'7 fj=qq#@\SXP [w7HbŊw!qՙ M{}]\\sٙs~;GBH= ]үߎoۇiZ7ć_ K%K)XjN^h\`L9Ů8ޛKU+jVAqQgHK8ye9,bEjp gYMF}ȸ RxMv:wfxP -ACvmhK I (9g9yBd,N^)HVvE>pd2J+d,Ğ:WQjKċi4VYWr^Lop .+N' /{xo?6@żlk&f7ZwL,ekZ׾=}=C+^Wd&eo'% ~Ea5-S]}~VgtRKdݮ/ #**pJAR\Qe0L%BAZZ,PP\١x uA W[H^xMnXÜy;ߡ_%hk" J؆ DĵWQ&9mofo%#Fm|({plpi9<(yUN `Y=RJ4ʑ=$ڇim.,50Z[∍[LIag1e.[nHvi72K(.W[+*I#F^eE[宇2nQ&n-"ѸVp֖3mImyAEA`5>`Ҡٗ ~;-B]vD8`C UNgy"r][dKخEj&idpLRI̯g4?ltҝoѾRj&t /Yo5k8,c,z fԃdM,u+Y%'\JYl}ywRk};வh)gXFL?~?~DHLVaO1I;`OZGrh +d_̆̊62s|&0xle$;;l%PN3O9ѓSx _p+kEIjL9%6S aӼuN-ZH?}3&tхռV%|=ֹ*Ɖ6h"wڥzg%sD)\eXJ!jDq3ӡC._̈; ХKt:]I\F#fĄۣիqYD^Cp#櫴h΂E+8t0;u?Vwd0][E:AYhU@I&>Bꪍ{BjiWQI%^GIExiLWs Q`(⎭L^tCB"Dil$4 Qr䈞>QQس=`,tׅ6!Zv$fk\QXŧT1U .p*:}kX| ~]%ڥg%%P)*&G悱@ !y 0`ggW0yd233si4aaa<% C~~>&6!IVVK/{dw|un4HXtp_\g&!>0!-@SKM qևvXpr-T]; e;R1&Xm׬l_Uu4&5)iCЕOeV{,y$X7 .bS(鷑)8|W\7)Gk LiC‚WV]h38 |M@$><3LylE%mzs`4j&':,by3s&.CQi%2BL6FDPݫ$TQ ñdZ*6M r*@Ө-nVےN{'fL?TSmd1eK֛2127-Wa+K9[pkk)mY\qգ"mڃ~UT#; s:R+F 7V٧>J]|g'GHAAtiF6~]tBTS<,'`K3L~`([.QCZ\CU$PɌPXƍ'Y:| J3ipNh[pkoY6s2{f9@NDvƥJA T \))hZX, ͛7g̚5z=&Wh4jm۷ٵ5aݹNd2(1]:v#?I'kdׁn+qw L5LwovE>TZ8t1M2 ObW5"QQҢc h:oUy{77X1%7s(QTumd{RW(k\Zu wV諈E}ިJcN, @y6e3K AT%'2Tܰ nh$ Q\8[x$5 qi:%q= æ$P_eb抑qiu/-Ȋ4Rᴩ3oRW!P(dgrmL!%TRw)9w%V˙gi^Gє _(Z(FcccXdI";'o#ZPM;b2ikJVv&[a=u@rtŵ‡M=L*,<N} '(,9IR_VRINS~zAhBH+qʕ":]wd}3Q4~^r/;+ >O}}T554 _^dKk۪*Ա8ƶNnd Ǝ+ʬnâ.a1q0']}wr6æ[ٲzwǙ[H)r;6 e˱L֭#֢~+J=.mߤn3kl.?3 2К>w(?f ̕{e#ݯ8бg6o!r'FQXw&F8QhCl qJ釙ѱ/K`iPϵhhSǽQ)z=Rhp(TK{L2 *k쎧J2br E$M0MU'6v)87`c ;>`J[JEwñ_ym^3Hvg_3q+^K jn.֞^4 sC*_@rc EּyBbR"WLz=\8v F`()'Y[ Flmm5н; '(|lll0͕Vs0.'V9ZHr1 End6WƏ0 'mqLUâ5j@_TAW.n:{쵀U }beWfd.dҮS>F FPQJF4r E:]lam[U g}mCl\)~BYf|B-7S ɉv> 7PT:зe2C5 [I&6Ktj3 ע%ۖoUfd:A>{Hİ 77td@`|"[{Lc<6ĥ+\I`R,߉ ;?䪠,+g%{a+rͤ$ N(/"ӘT0_'6׬bնĒDv#Rᆮxu|go`fwjUP>z7îV"{㧕h)\%?w,A]y)D\ "![ok3zǖ,EGt*찷+*U Ȫ"WSk7 ^ _':H8;InobTRB^n n7~AYVhh6v=MKXv?m Z4;]/}nd|#"ԜEŜ](I oTbӜ6ʹoD!ڦWi5ZZEo9w~॒᩻tl%3>Y Hv{3Z's&TIq/NM l%=闏rсj2n͛*J$myEZB{ YaMV%)`c{ԃ h9fU bK&diQ jf9+¥Y[&Kra0:Rβ֮roԇ]UN%EZ+[-䪹|@+G%ҫ3:;wG 5ɌlٺYяv G2ovNAA!}ǧL~lwt dhD{K\"4 ШHkflaL??WҎg?^xgd">;Bxr$}hJ.N.*i ك&\v{**&ł^J\!2(((͍,2{(MmFsj- !2D8z81ݪp2qb25 ]&=؎/_=ȸbۍƷq<]mᲅ+Xp+מ)(L6U}I+1"?<w!CHf.\Œ_\41bcPNPPM^'ծ _gM,]hU"5w(F=A Є%.]S%]m+кu0ѥʍc]Uo叅G5u ş5h!ۃAO<1фjNfr6h/MZHG_`̙7YxAI;Eg͎F^!>Ȩ\&hѸ1 k,+椓n6U\/VۦPTs׳OrْdFUS:c182dY IDAT.֞ cs_8bH8_?*Ӗ֓[9?#K\rb.^oX,7..`0ˣ7ȲVuEΆd3INI"9%D2jk]B`!Xr\Ō&F440q1+h }du5h!\1sz^R˙,m/R}ܹ/ao"t7=zDVs·B8qm!# -.oS`y.Z@Zn\L⶿Xbq[]W’_?+Z%ޒ2eڵmXO⪎ao1/HIΫC~ !R5ǔpl~"Cq>tMBLQO\m~"ҩ [W^ VĝkU8~~9OFM'yypZxc~̧,Oo(If}ި6:d3w/ӅP%t. y J,\;7n4r-$ޤ'-sQTB̹VF$om sޖ@]\Cq+̙JyM[bVg+AJ+օ}&7/Яj@ $h &++ ggg.Ƣ%`Ʉ x{{ RɟH_?ZƧ}מ߰x:v섋K _UC0h`;\;NfE2u~?=-\J) &kT>r8<^3G kIs|ҽ5/}, _@ 6%zCzgrV֎Z>ѿmSke6M\Y8^BBv&J#~#08?SB %(2YN)O;W8*fsgؐB6e*Q{>ɚPz/b,羠O٩dYx`_c2KI?Vqڶ*E d+l̝tzs/`mnxrxZiqQ0d$r9!<'O\˖IT9$%$c,pr! z[-KҲ (6.pI)'+h\ cO}̄$fW48yݍIJRf&^7q![(aNGYka&a#e2187#$/vF -K xTvAL&RSSy1 6  pppd2UOZZqii$&&rkUBQ!X6o-Ͽ`Ofk 0r'(dsAIAJ{$OMҹ\G̤!ńukw Oo!=t(6Ȋ9 "^}?+2ۘ cO-?do35ln Z'|CjW?a-Fۧ>7Pjzzj#HZtq7E]=? MkP G''o,f}7x{yaoo$I(b)pd2aggG^^ccAUpԍkՂFa@ ?N:R ^Hf8Ypf..Z޷=#?+/>Hgu &sJȘ)]^6%qx%lo;,@ @: BP 4N6Ƹ?--@ [-\J@ @oE @ @ j̿"atVwC 'IHH]@ $BP8rQ@  @ AA @ A@ @ #@ @ 5F @ @ j@ @ ԘEc$;5 . p*R)$-׈'.ۚIˤVݔ IJH"=Z)R 0K:\o@ Ԍ~:wL.nq@ }wAAQd:C( _zwLbx3ۊ7>?+7q.[űa/FjpKU._CMwlWKǕz$Lu։K9H;ϒW|qM; un&A`&79,='Ah Ħ[p jJTjHNIˇVC `0cNΞ?M~ؾm[ۊ@ /sSEQjÖ7s.[!w lGi å2s=vd ,=>M[4 ;UOsDEpzV2W׆+*9g7bK yҠIKy@>)UXwu+t -@kީdFa1#͆4!ISߪOLL IIIhu2]DrAك5ѱjOBL뽴$dY ޞSd 4D</f lBV\ yʍ7EB@?GՂΜ8L $;<. ,Ɲ%R:&{Z4Ǿj1SO"J?a~N A1*(L&FvvLQ/TdphWq] Em%HFbxGj֢%.gTN:J1]cx0^ĩt6tڏ:nt3HJJK.'BHxxo߾'kʮ|2$imޅwO`8,DO!.MgmKzz:69@𶸹x^\$3Vcc H 7 Y-~R<ùٗ÷ϺO_ȗd$UErky"* t6ole'󂟕+ me0ApppDncm[Ǯ8n>+#G५`;#j/**^%K_];] "8gH!1BDh 1*[K{\wlk߉Ƨq6 ;(*uZO4> i\b4qusC+k)0ord Ʉ '9i3_3t)ޣc63d7CP#yy9ۉa!?;b Kvłh$ۜhl1#K8W5Ð8bο ûj4bTA`SW<<kEip`.Ih&JF$Cc(&Z^( J*JxY F #, j%51<ޮDz7ErSI1c{_k`@B"b;zkb42.?Pr/7dk"eհ_O&5ӧPAjd$-5;;;ӑ0 :TBr#ڔ ̙ܽ48+>Z"9x0/O.fE -tϦNwä;m㑑+VJe3èY&Ҽ)qQf/3mn_7,턒q= T+@rCP\Jl=/;M;uO%dpfIRĐ&;gp>Pa Sc?v/w~aS4Q&(NjB~yNH|z>&n> iќvUK(5zbr:[G ޖx*.P/2R㧈N̤@ͷmg_ s.>|i9$;Feא֡X qL֓t2& GPT߂%h"IjurJ*î,{:>8CKbUva[&pJ_@m]^i 1iҔ&%;x7mg8w a%j6T/~ݬVTyh0^u{-x(]a#'Ef+QZkTw`;17@#%og=δ׋ k?Ir&=|%h|1^~ Õ$[MrgdVŵ>T_x#66#7P T N"aN!.5FhM[Mӌ\=r83h}ӬA+W#7_G:&y)PX [(֮:Lwg GY Wnu0ZnPn"*KF.?!QNg+)GC~WnV*a؏ܺxhxq@&hg</VW1U N/]+ee NzǴ.Af::،)I~{LT3-w0K}_c[}KJ^C_S2ŭV,+SQPT2wa,lZ>8~8O4h؎6ߖthIYEvv@vvERF"AA%{<>_wxQb=*d Q~3Gҿ <̪>ΓbUrE߯#zO\TV2a1֡ |>0>?+6$7zNB}Y)l||39zv^)@ l jF$bzUcr>ٙIf&{, OrT: '2Pt>t◮j`T-* ]їqq+dkprvvFΛP}gOrΤ"ٶ טu(]@W7ӈ޳`z^nHhi< ^ s6q?4ήXw`5ó_م0:4b#.W5ku6z ߤdWA'@K8ա%=;ɬڶ}ؚއ?c͠k1AMD~2sU"u2F'-6?%X=*ZBPeOƁh] Erj."IeŁj)AO=Ĭ֒yN8DZ(į6t|z*ҒJ\b6'U*9?l:}=q|>3jm*iiNVU>)8!6<% eKPa=}.Ùp*3ދG/buw7mՂ¥K\#--[[[ Czlmm H^~NNҰF}?w]-BF- 8c/ fcQ1 /f ;$u ºXmʡt6o.ںU7rr#w^$WreAt*u)T eBed@QnC@ 0 U[K.:bd_rv5h1vۮSgMd,=F%D `_m7% ~0]s9n޺a2uu~}e-@ƀQ|5a//aDWV@Ve9qP2+EGǻBmWn  ..ʫ*'3utSjPɄR'2XRHQ'KN9ήN%ہy_2oM+W\H3aFc-{ ߻o/ :uB~QTd"yE3`7Xz < Hh{*>ߢ0H>Yw%&}uao1!}>Q0c,3tOc+YRCpBDGd<(q e>'F~U֦!ΡKvD|g@ ͰZPG``}.i09Z900K.XPvKli M*߈ A?u[q՟0^n#ZYN#-nwj(XE%ݯU%]d &7ILKEBbRŢb  @SQq.:gsK=T39d\B0Iry]䆣dnҳm i|ĞN-_D\VVg'7E3Grn22l#ߟ; U4@0~'0ٔpb&.=nW0vUQQU~Ԭ,F`j>9*`}v6Ea9䩀ma2;\$LQ(U""yGU yzrי~!]M|.ξ-tr{SٱV5CeډPGw7 w10-FJ{C6pc1CٷG&Oe;$I8Bӗ IDAT̫c.|9PS6G0䕩] }W~U4urŭ{Sܼ|T'0N7U+.>DӤmcw{p!L ѕ-u?]ɣ+*꧒hłww2=<Sh|ooO/01́y߲:Y%}Cƽ0Aȸ7B>=XR򀅘53ypFɿ掛CFVD%'# Evj[6;yIw o+*l:ӷowUTǿ3^i@BG E~{UԵ "tHPB 涙 MHBhz>ϳi;gy5 7///ZZZhXVڎNM&1ͶSDn>6ƤSrՙ|ړ_-yQfvC][j[1]̙F[M/ Z47[PqJKS  67,I}G/iOOxoyMl<9?Rob9PFmҽ6(ߔāhl@EZ[CQ"$.*WoM\LRVM)^w0uԡةtf 85ϖasx4ZtZ Nl4VGtIsjK嚟OB15S9t/+YyJ9/:\4ѝ+&_q2;Q=P%Hs]+9ҾF}&/9Y,6puDنxӵB]AK YO Ls-b}H_u;7.&FrplؾsGcTٱ#5bFxAWy~ Kcpp}ԭx8^渮!4*%W#F> pFp@Th4JI=wL>#zH'{.֞jf:VqlH~받fryLƺ{1?@[EՊaA]:z J*DU ! 8fޠCɡVJA*Λ9jwΊF48)).ɑs^ʃ7epӿ 갛xxm0l}<4UGddFim-Z8wp/xY^Hd.j+^'9g1ܞŒFe>CZ6KkgwsUd1npO f!nXOI`NP 3BTT+V~"8pdɲLи0M) |ڬ\ȼ>G^*5d| 5'^Fy;b@  .#,=UvL͢LPt$杻i-Σe j~U+ }; g& P%VsUlT p:Uh偾u;d'OesԲs&Y>yVY< ށg) 1u:"/,z8z.s×N|IM]G%vlD( PSN [nI 89` T[l@!ER'ƐPC56ZҺ~ݚ;|xw6AF7cEfDRh\qnsJF`j{:څN j6tYa5s.of@ 9β36HWcɼk5۶1k<:d"<Tk X0F^b9.(F C9ZĄI(,U}ހv#s BoPHLH"1!i04>9_$s!FvΫ}s#̵delPײNm#K3X !L܇`Bm0#Cڪ\*)6a>" 'oD8ޱ ao,aBaqQTpnBt:P__RwbX;;cB6W%Wfvѭ| 4jJTWopI&p8>|=k2hu(a\0;JA%$Q]O@5ʴLRF"2V{)V#2>V{euoSOՖL^y~iq΀Ѡkg-F]EdlnYJA8"ЕCY}2~ZE&>I ᓱm{K+ɣs;IGHƍcdHe::v{ȫtkFsB6Ʋ4;ɓSaI^Q.>1:fקXhlj.r\:l>wv%:Z|ݐ!C2d6>K 9>~Q0C:[r28ؗC#sG5_~Oi6R߮~4\:ja39l)>3WuX/Sc0W&ٿѲ\8 2fո3jT8݁UE9IyPNg?è\[ϯ&O394Gmg>$&5GGQ_6gF%Xdf7dFE+FGIY (M1uB0}ɒ\ڥ#d3<=E\9x4\?I˟[:O~1/DK*އ|>}WEu X $_fNLpݠV[ ,NᱻtE#m, n2G%V*`P(Q̺f]y\}'i|&imlb,F HfN /i @}A85&|{Te'?7譣ސhjlݩ$cGH69֞L*_Ĭxwl[ <4V,QҒ4m8~DȤWUտulxkgsOl\9৵RSkQb1NKDkɓLoR*+ۑg gPU3S\XJIU#456Ѐ Q ?=0+3E5L`b6o $CԓrM\$?'l5}歏Pq%7ZK`35kᆎ}$\[z)>H}by`♞亣nJkGmQt\Ez6Ӓ/Yϊ9ƱٴfK-;-(5u o'bUT)kX8F85H g_F@aI:]Z7oNA\Rio˟$ YkNL~f`_?>{9H x⏿`W.g;+sǥgpt\yL<Ņ?U!5zL1ˋdoy+9˘3ƛ*0˜xD%xƊ9hgB[[rjxxzֆ`d`J[s * ܎! yW^|$ IA̴sdQ|/yܿ )>&n+>+-+/WѨMD/rfvƑz e<O͎ޛ)7ݗlpRn!q.{}2>oC^ r'Y'8=+7x,yiV$t^1L{$\hhg<~~;ˑyqd(f7$5y²wؐ&0' TJmNwNW__|ʏ+p۶yI#賓Xj(*YI[7//SeW!A}yT,ueT4bQxAOm+|u0^{[>@}}=6o֖VEӃ&N P8*e7. })lr`Ž7o8ydQwR^Hd?8coQ4VcwCiZkj"| qQ  z/̮|.z;) "bb pJɦbz\c+[9Σb 7_;Fm." g+ooo jaZ z |Fp}OQd^VKWs'Y7\7*bq'"wtd+(i11dO ښÇmx>7 AHnA~SiygcCٮ= gq|}8{?8祾̾Ƌ$܂ AA#z ~l\J#I:h2!Yk~uSiz'd~H(KA],c8%dS\xy(]~{Z-80gi$sc8. /Qp  &j^~w6MN=Ax%|5}#'8K 1 Qz D@AAJ6>AA3YjetJ>AAAA8+ ۳N)   YPAAA"    (   o"    Be#AAm{{+Dh xr8ݧ( gPAN?[3M7||'tw&8Kʶ` E)]˱z=hu4Y15|yp<| #4i&ær/AETD;2f/_̺}> RAfvEIn>lfo EI#i>؀ũ_T*֦4h>OboF IWa}~RmMTUTQlA!AxlAehZ+}"hDjK9ye(/"b16* Jip1 _=MGb޾'d"? ):)q\%Cb[==emNhqOM7 ˰;kT,-vF]\r6Q ߿ ܏_8Wc$ȶ}!\͙v 1OSS477p8tx` ҿYFN 舞q5&g#xK S:@ve'K/@v5*UOzIlaލS.JO4n6I1Cidzn/~TDNDTfUAVmo[ЧϞBqqh/qm B)_ʣJӓǦVQd@;N_XҤe\?~Or+0TWZc[hQe|=ȒʨE2wѯYw!}PzP'$[2:ES~{&L)LNA<Īk\/k[[AWjw*4:; r%ښ$dnIG\"Aᠰd4 l۶Ǔ󩯯g0907~^M˾'Ǿ746`;sfŪrg!AדPiQ$f ˾gSqNXؼ S|%$ #vW|]? Y {qJϺCݏt %O~,!'IqչgB{x2־YVN1-qsH^x6'c;- fܶ}laƺC8X,F$NՁͮ%TIס=?JޡOm"' }&?oABEE>>>zTUe$$ű}v,˸՝ހnGi%7 =LS/f دجA}r4UV`ܙ[Y PЦ"{cbQpzBRӈYNn^%(\6}1¡`˨R{F4!)Lu?d|(e')bl ɁiLհ-m{MIjێIϚǸM,{+;,9e]laAt 042}eͨz_0{s9xb IDATʛ-^8*wq~M LD׸b"7{+(z<*"ٟj`^+h<Fb;=f0X?T=ضŀwh7m8 D$0::Ǻ͈7 h|zVSu:9(Ho-JS9yDl7ZG#vf֝J TG)-NVa-mM Ԡ- Ux:lVD{{`xD W5y{)O $UkZނ60RŞl6l*H:=~`ƥLTT`C#2u>}ѡZr-Ԣ{SVa;g0Pfgτ.%(0f!%.ѕƈAxI)A8!Lsp0s+yTw;dgcA0 ݼ{mڽqnekLL;2%ž[]$x;^=޽6TEDOKG ( l9Ώ)$dp?l̕Om%x5ovҭf ^}$W>.>|t QJ'rϓsil&)⟿y%1 {! ChkBB#w5Z GUCq:MMT >4W-tL}Bej+gxlmݟޟ [!B`'oysj?>x"q |x7p9ذ>$` bt8NTUneoNZCUnN 1 ̠% >6rm)Ga;%uGA .;=i7q0Br~-DT|4\ڋ)2Bm~>u h|'Lxx|kSAT?3>wPRɖ]j"G[5h1zD #kč|I]޲;9u hc11#$6K_df͌-P[? [ёxl]ƻo`+*Ttˡt$j6q8f.SO+Q˙52 o)^{dۧ7-'-inIVQDnq^̱aO9{}Ce 6ߒs~ԛ7^Xmyum/o>Oηc:oq&X<c -7p8*(8PXYSQ%/BRYJBڣ6Xm!_ 6ɝpt\{ٰ $ZlcgNX:7/†0"e1ރ T[iiSA2`6p3!jk 붏]%/2舂RK^~ Z"0e۳(u68xH~ q%5EdlÝgVk4Q 6z4a2Q$sJ[Kq!r\q;Z4R›73zOx(.y~3#Ҙ7<.fyOr%*o@ʖ6QhIq~udxG壚t6_Cbfۜb 8 V>q;?3eZj &*ݧF̴F|ejBDsrO3Jʄ8=7JWƇ qGieUÈpQ>|̋U0$qOsC졍sDJ㵥veDǽ[dnķ]T2A5k938 qe[JO_F̡f 3~D0cM4V*M>>Ňq#.ݦ28K{ ܠTC6vvA` 28,g@@@=6(=XKx1)d8{nl6TYɷ+6EYPEM,sV7RD_66Т%>P=!Xw$pt+oP@Nbl_Sh]ʝ( NA/y-A9'2)YUByf&%NT52T灭l!a[;@g} u6W,0+I6wtZ=`JG]IxOqZX{xShjv"պu2~on>6*3]:LF1 ^JYɊ>ȋ[w=80ɓJuF̠i;@[]nZYRQ Rz W6vZG"OYȕn.=R/f~Yu;V[w}vv'-!7̣KX"8<ֵAǣ(:splcXaƕ &:O҂H\=!-K+F*6GEL3PhooGe0j(vȑ#q:;kV RFtOc41& rٛQ^J]^.UNxezh|ج֎ƓشLN̵k,cU-` 9wNFKH;"늪txNCi>E֡P_@*aL tSxɛa306@$c_#)Fb fL7@m`[~cģc(7Zۛd7Ж.3IQj3ܽ1T+ #{ȑ 1r/rn c_󴇁Oe`4Y%~ox3WH!H @i"Hw1:N끇Ik{g- Or#Bk t߾ Ƙ)G]'h52H*n  Ό3_1/ٯ2U*mDA&|0D%y!"/&5qAw}=x]E&jyvn@&b-?ݾ+3PEp:;daKd?T984Ӡ¿%XJ%EhU͔ZO,z$pGhZ∋CU# n恟R[F z"bq߷} (5աhH卽wBnE}a,| vW<9dٺ@˃Cq:; 8|8Xb+qHg% Q%1 +V)q9 dy39臷 ?wnmZˆ,2sڲز4Ғ$FRǰ*͝+XT$S)8#|ZC\u؀&z0iIܴagg MɘԱL81v;lszHFw]d<\,y^6H$7x}jDGxZ(tF#N+aje#z$o8٘_BƮU΃T-= -Aԫ@@~]rwe~Ӹhb2VrnnJLh8 Yٟ秹\-A 'L5JK#* ( ,F;e>  Zvl6ϡU;5S:hCQwVנ K x d-:`Bpz"RUEˉd╌ q|% áӒꏌkJ8+ߢ"!17J2}w;kpz aLAE`W#2rd;p{f͌̚?-_?j֭kY5e s˨ձ̝.L9(D 9~9, b(ʡ @xk&%" `Pu~~~L4޽vf3Jvɘ1c  "7$Daܬ, )憪:\KfQ=|?eG@F :ֆ 9@^hU~UN SL \dbrI RѦj Isq^f00uF2~w2A$!LL @VR%Ga1s ej"8.tM31n/x<ڎh O>nv^ʶ)ux׿>{{-vܬضUұ9ILO\Q5{ɧ_=TS7UUEUAhBaگ_;xi8C s1((WW5R/Dz*c< fisHMG~gJ{0oy3(<2׎UoY1N8p Hm,~ae3&4FV 9.\aӧ|S@F1mjTyei e8J-%Դ`FcͽVp!$c }_R0m+|u0^{[mx^%r^6kXNQiZw"# 4\x/灕zp#02`#/]=P A4W3O7n g<oq:Vz IDATkQl# ш` ͆fQUu  :^ hq!)[h́x}_hq}S([+kT,cȺvhPW(q^}VA8"   lXpJȦ&_;6V &  φ&j^~w6MN=Ax1q.f^{r,'AA~>d#~as3DllI9%A+FAAA~AAAAS=Qv2Σc,<-A8۽:twU_ܽw$%\Ժ+jm]9Zjպj@EQ6` $dܛ;B$Css=y!Bq ,GB!BB(!B!"<!B!G(GHzF B!B!a2:A`B!Bq4t]'SRT@uyP(h`4(꾀BO b00Tlv'8=?$ B!BN($NˈOaN,V>owe%ETXnPAuPM9J@ᄢR:5}8 i!B!D4*KIBR*h4YtpOL! k{E{HLeNPAQ F#d6pB梨ʊRe`{t'H@A!9HeA*@ft+D |AvյӚndD~.Q]dKB_琔`$C䕅乛",)oC4`C64t6QUdKItu d53(ڀ 霄B!]L(ޛOiQ#LuH&AbJ_M|b2FO]LTL^INKDdtրBRvrLnB%7Iq(w}_\6eۍOX^?ՄpiQ~8/.%BmlӢm8Á/i ߯%^Zn`ciCuZŧ[B~".'GV׃!֬E^n Eqk{_楴v QQ ƇKwϯI3 yD4Ϟg#n> Rʠ.hga~'s;\#bBuBr+G[buJ||5(*Y#\6D+|R4%&^ԉڝKg3X~q]|%ICȜڽ*CQnAgr&ijπ\ר-Ia1"Ɏ}h5*WT&3tU{[f.TcĬjn1ͼ2BQ.fNMKY_Ef98unKI✕>>]]g/Oƚfeb&.ci5ƙ5y~^PP6%L7 ) Pb00l)lǶ &45,X&Fs~Z+:+YXq-=xArwmfl:kư( RSD+*m`Qeۛ^V78nbhMXT  w7rM)VZ.YBC䭯gT g[ ORxFF@Evkٶ)fM KP7KίY@:!TaΚm\^˷c97΅}qqyz^崓Y&nR7c{V,m <~U$U,F37Q6L>oQC[LjNOg0D٘[EV0ll(v2Tb&12q.B禪$={ٴ}6-cgq}W3cTCyֱ3&1"=H+ݕٶo`ƹ׮dA _׵l*m.dv1*S#/c6#xԮhb7ϼҒN -xYCW]L&zL d TfÏxX)olݺ5_f|1.Ȧեú%_?㋥ȫlnC7Gή2ZNCRe9~Ȼ})Okd*|9sXʝUMb6{W!섪2B Ia7.!P0@۷p2-N}}V%CAiz|B!4-D0,7#!oDcݤT Fڈe 6‚]& v{Xǰ yi?qDž.gu8b\{Ξ5|Z=Y懄..eelxA5*7K*6yaaBVcM{uX(d(9q6nISy_;ڥU%ѥQ^׆0apS}•-vf#鑠UN)Tdi1&Rvj7[QTt+V :ێmG5BS ؂Z2Bj+XHF`Kc!8v?1l 됇ffڪ*C/ K0tRYY7졦ϐߟnaz]>jPqn_ü{!-'0-FxVGQ+|g<S\#IF ~?劣M;3#8Ů020΀ŵ<{Є^.]8nQT\ctdXvwT?n#xTBTw]5UbYLWu(4N)jr&DI4= ->ʃos.r2Ω+:yZt3iG~s!|mภwer-*`\n<؝’>ֲr⍀^ͲϋТq3 ;Y;ogqۊٌIn>|i>#qF,U@dwRt@.JfL%:lZog{pGx垓(FF[|?Lbp}=/^sO~06/? `Nȕ??}~}{P0OL!G4xIq[Eđs+z7x<Ōk} 5TV9:~m WiZϒԱO㭦imLa漡F KwfgAgr0Ϋقn$=ҀӨCՆp:FHxN/DUiLnq5ϭQ$c󢹦.fWY9o{0;:vMmvEaI1!i\6>ۄBStMd2q LfBnLmwTiX !o-n'n&wF o+\o㼔:QcNsf xs=|qR=9"~[ ,r7U$%'pG׳lQcGt:y{}|IFR$)~uPc- k!vQlf8[U7U|Pr(:Y~+d=>k>=of=aٺFwoXt6n]OaJ W^Ē0/BdsB+ǭhH<%Ty38]1nKF+&{k *}݉2dDZAY% D dĠv4]l ;h15wD_f PF!\/A9e(F4-t>Ù( Zk%op( dyFwL܁6`tg0bq#$* h}=πNN) kwLc&F PvHNE]c7լbW-dPGl'L:f%)qA 7]Lg``+T|5vHϯz""UT N($E0 y@`Ue|?x`|dZfH#)V)lј [{͚O2qA%5Sd_tE$zCÆɿ{tǹu}.Ѩkv8?ߊA.-Du_,LrF5=ɺ]Sp޵L8ZмnMYnKWPZ﷬0/v UPZFb"KR,ķB*(kf\B\# ƠRVYA (X8۽#QMkJc=n5)T"!G\0l|nn[UX>]QK@1^ףccqWGf8v66{<%l+9n,M`}%ŤjP0:nb**M*c͘kvu4}`[=mb7JVs\7;;;ULocKe敵N]բT71.=<.5r54RLX Ki n '8n .*`CΥ̌IPXѪGl_ U}ђ:I*ulxbmmk5)(6b޺-N; \*ؙ[3q*@㮸q,`Y^YrJ.Jq$nj< IDATKl>|QYgܸO=z1](ʹM 2:ަx`5F¢ g5 BY !"TUa2`jN^ P ] mlE!..f8N9w^8v6Zu(. 1e9Vv&uӆ':?Έ kCTjYdolM36V^cV4|~V]`EeY1ܐP;?>H0"vGig`pRSԅ;۽]y~ݦv/:89'jRP;^1`bx -+67P9}Y'1Ϣ-RXe߲FYdXl-V~Zrbf!|zC. YSج:ox>kS 0kՊX(z4>_~7uBpir( j4n}SH bhAg=?o{Q]]MBB& 7koS 'M^0rz?!]\p:jkiY[à( ŵr ΈbV<?mL)VZdž.TŞ]# nֳPjhj~qQ6*uF!}T$7ol[}iԽ]{~=bn4*3לd9v.#FBu V;5bF\9c\X~[2q($'d${i5V؉ m*&:PIuruž- ljPQŒs 4V?u|{l騑S8Jׇ9~*sgẊIk4vR @v}]D& E{; R\T)1c᩿M\ hTߞ];;|5Bv-V?UU̝I례Ϗֵ Ö/N&;TapĢ t_1RµaƅNP/:ΰi瑫#ޢfk>!G{ yi8nOp5 Ln`ui*IfdYH6AiN f7glYV͛\dk9;ˆr~VFD)(Z%TNS13B._w]TN=N-$ndi*G[]=W$2} e!8pncE4[(|QsLu?72zFv\䰹 >M?0rd* y]ˮpbkp{L-u&1k5Q#R0lMڵ #u?U<芉C)x pn%oTZu,HCBqD((DQ^RxZUW㈌%>)Kubs,#oj׎NM:YT;CO# kޝvQ榑&wە2LƱVZ9'u 5~ln+ڥ q!~}Vpi=3BE\C;Q/˘@%)Ҁ5Q\ j'xCQaUSȜڽz7/w{b"e}Hd '.W|E_;rއl˘vx%qӸpsm`Y2ظci<~I_gB>yQtssyefJ;>J4TD.:=[}"Mxm)9,Sc7*㹄BKE!:6*KK+J1L-ÒPۭǒ^b&}J>Z[P˪XWTZ'G 7|@!>HMFyubo( (J::0~G}0 KVtBD]zp~=O'H(SZuhݡ<"9W[\7f(8_y\?ăr ^;ғudGS]O-`ܱ BVD"l*Jyk!P#Gp)-2/Դƕ:V>k]>)C0T`wߓ[cW.W׳[i1"gke{9[8)G䫸q<.w&Mbdܵ,ۯaK14B㛢(f%&6 wpE`6Kve'P8fl|GUhw}4 |u؎&ښmtzUz֔R3F̶h20r: Ƨ؎ 7A3d:=VlaOj [,1s'+慸yYzoPPmItuRuJ3W<‹M\mi>3YgrM71k\GJ%\Xo|şZ8YyEO#B!EcⓨԃyhJ Qþ *4xTq](q7MGiL/;]8{:n^п[){\{C/+OC8NH&%zCPX&hOd=R_O~Y=~JLJc+=!hr4k~w_XMmC!: ([@YQCGfZ<7⌌a (~jI0 >m1PB!4ń3/=҈+)pgRCºCVg k ?V=rڇ~LBq@'gj^Y[6l=;Q_SMrZ!Z;<!B>4WTSq=B!DUU, DDŒi-u]\_[Cu8#PTf5kkHN " ;! B!UYqߗ|x/șr Ea˦KY׋5[~y*RCA!Ҝ9*]O`4'-=.x($?o'Ue8"c@iܖχ]9k73I@A!ʹ=}- EPP ~97̞>r#j N'h[_ˈ%&6ؙ݁xn*J "*͉djvnh!-DDL7 0G~iB0^& ./&'q qz\R!辖Aݎljhsױ;w;@]8vc z}Av;PO]3E`0`0'%hUw$ B^̈#.}B!~-?L&p\444z hv2V֘"+!SD[ \B!uCB!8!5t]GQTUh4t:! ˵\7u;C[ !]y?pQO=B!=2` -e(FUn|8qऍB! %КPB!B!@hM= B!BG B!B!2 (!B!$ B!B.B!B!L B!B!2 (!B!$ B!B.3B!B#MQRTLe(UP_Y;wM &A'K'X_J~q% $.9D %'$Q !B!ĉCsgbMUkF}GLD+h{Y:ߴ=˯Hb/g˒XXz22h陉^H.[!B!ĉASPMP8(AQXuU{e[9{LKs@ȏ/`#>=`C 3k?*g,bp0rx&I.#=lټ- Dii'D/!!B! A+-μ8:!Bג3&QCcg`!l5z^| Ad9xMTa˨TFI;ѻH@A!Bq(ەGQLtP=t8[NQYQ0Z0P16T 2q0K>7#8I@A!BqP{1eiPD7`nuɌ B%փ +F| (!B!Nيl뺷JF?AŌQ_Z0&$М``12Ã$ B!u( 19CQ~:6eOLHR3|PңjPtW` OЍ0tDN!e"!B! NfBPMȔĩ_Q |nrٱ W]۶1쌙6C׏ynȔ@Z쪟 X3\tRD#!B!ĉKPy,ˣ^dg1>e7I'"Ȝ8)R4)z7k.dJ| $ШؖC\u"GR^~'(Zu.cRfB!'&ܥ_rƜuS9"f`dh9*AûKuJ2*8rJSJ+Z2GB!'[y2rBz&HQ̦V[R6 pR !.S;+.D<xv& B!BސO糾,-e,si˂83rьa RLxP-fLL !"_e?QPZۯa0ۉK&cPv<S䡅R~C:Gb[c^ia=Pl//WA5ZbHw5Pϱ>9;$~xE(7pp[;ɺv;GZ35+ _WfW3ulNwPr> +O6== ,";!gqCh,] FFND>g쫜XDQdwRt@.JfL%:lZog{pGx垓:ȥkkWso||r#F0 "YD-9SFTC?-lnv/mf%,)!'" (!88C.zs^HWL? Oءf>]\|~\1f9y\|uc\ߴMUABrxKz<u'o_Ί+HOV7+YAfw|U-B%t)"*EQU}kY F" IDATV"Ke RMnn|?c̙sf̙gΜ!~xiˠa}h2M ǻV>11̟cehſP-fvFncJ 8xq]QȸVX˜b*%l{`Hő'1fXϿ= *] qċ*Fƶ3mc*Ou%+')wX̷EtQNRjI*\: FQx&Ϝ_gp`BX1HT=+6Wcrtd&_==&\c M;׋+l=c (Vth}2cR5 mUvۖ YS0w~U1ݙpyHPcxU@BuxzL~_T(_M@vØFNa~uʯl]|]hFTЙ[ru} -ڌ8|xI>BmCA!|i3!f Md6l?@J~{דw=>ݸ}eGK4v@@n.Pc9 u:w~m$sT|ѵ*`-Z.FN`/NnܝgnU!cf f|ˉYwK3mg$j*A g@M7l=?q&ŗ^5._!JǖLJx4QnƓ,YsLSIٲe?iE:JhO=|mɫ(Pi֥avC3I>@_`%juTWߋpj: EݙƩc9_De~-'-cEkWБp81B2i$Z!8BQ+_,}H+'VDܱ-[6z>Ҷ#o2L2M)nfq::֌tr4hRn6@˫JiḽeҞ}__inٛ~ӯS$a0C[#[X>Co.m{X2M }6L6.=߽y+P#4b)GA "ژz멊)v2exG{L$=SDDF5a "< LLu< (F}>48#؛% Q.pа`TNC:iO!A..jhaJ`P*:\ tʾ!쑀B"m/^7N7^I=oFdJvlvinw Fkwwgo`!iwaΗ3n;;V]O 6-<2`2MC^n XJ H-_s8dQ'IK:6 P>THqOpgyPP&,ĵlN&c/n2ݖTDE_/xUfu`yzekw: t[L/]dFt[1BePBQo !cqp׿Ȧ)hbQ ~@[ׄ!xbtk ;7a-\þ yӘ>zkdxܼ9Cleے?I 44վR9Kl6O?ȗWpL3f0:Ee fs̺݆yU\L (,%1kWuş:T,|:^=<+?ՊM Vr7X|jަ3Srۭ6tPUI9j4 Ptn9_ (6oeo̪IM iiDArRǃ''qgKˆ_9O^Rc2 7lC.zvF3`7v`v<-HKWD3ud+*qπZP2R RP0ETx8'k=g9iځN23P07!BzN %"DW@K>Ir CCA Y:iZ&_B~gZ}:Ll JZ`@#iz]#cz̫ 5mE.&+A;ZjTTpЋn%kj֜1cD+ᅠz*͇7-ge,K1a}IZf*)v6zjݻ6ŀFsmذ]1߭A[r=+P/Y}lN{o.*۵..[om:5vQHܵ*'M9QmIMDG!<:z#KIzjwoe]y FzjN, _Bf' ;[S3:OG DJc<3o|wix)Nwy/V aˮ,tH\/P>3w?ե$첡\VdfZ\=YqW]Ro-!PvUZYAx6o+bQp$U䟕C?ʼT 5/ z\_ܗ=^yV.oi~={+lΩ?0؂J{x_o>:Վ cZuc(8Op]1ӪMsBgz%dgmjSnF~4^.'2:$mXMvVV2BOO+8v&o:< _giNddwgqYOѣx L}m [?z[7fxVe[8Qh"iֵn9v.a7w\J14 Nc2w`˭CPjNP )~9{GAped@V-YɏK薾"V7խkJHHI)B ŕ}hָNtoe}Ilߕ־v 5jimekpQ C#K8(?ᵍBzRqRcqL&L%;a DnIuϨz_oζc| G佬Z0d/,]дHPٛs Q3]q:$;Lzj2]z!4IYde{FNۢ5AW4 (!ӎ_Yv7n$qoY&8h478/1F|2}/;l>ַIg1uS*zMoD|?`גٱl"o]y;w9|ԕ񓇰9~t *l1ڟɲ^6!`5/'c$#b:уFл捫Je6|W#s 06^y{_ {Kb&<~.rYzRmL}g1?77 p#qT_OMz_/Zp^B{ >]yF?7?⟼8 lU̇F?mC\T:܊WC撁C1jEDrغa5;LmPUU ₥DR>μwz^2llBze*:vY 'KUC 42akf%r"4XE6HvӓILϡAH;8~D`915`/O|ٙ-ֻZ\&tnkѐuryT aM w2kޯgx-Nj̶EF=l$̢@TDT׻t$%fS͛_q)cje$Dm搕Α{7p8Fǟnn"ZDHXHPA\0.1 iT|hW[ &i_j#iզik$:~޿UF߱F/ 6݊ ռ_ZNϳ׷Miܴ)ͯwLAMi:PLOuGK>Z`4|޶c0% .HR !=_;(TrmÈޗ^dp.ǠB>t2VzxZ(흐?!9/~BÛ`'+3Mi, B|3{ɩYr7)v3m=̝E%t Zv=MNzh!V^NFσCd0F t]';=qXpGzJMa7,!B󔞽߾#N7Koy'nN@e)]v?pg UowG0O.%7~;{q Ѹ:N^aNݩkp[PUUQ 6#84!!PUvfh:F'rhL=ÏoD3wA+'67ߏGoE_HXtK|, ш٧ί)|,FDTYgHM:)!BZmN5՟rcuPh&\oF0ܴ%gimT3P^/Q\H$ B!D{F2}1_^$ZX%Z Pd |>RUf  (!BQ-wts4 Ɂ=/N 4 :v`0+#GB!BJZI?Y|s!B!^I tt]'+# kQ򪃸`I@A!B!J(1q-ٿ{;vʁ=;i??yA\B!BPPUpB#ٵm#v<޾аʂ CA\$ B!%J ~~DD+9Q ٺ~%~AD狪L,ʃB!B(WU`EbX!04/֢Br3IOIZTHtTLPI@A!B!)h [ai8z0XJXT3z&HqB!BTRTP?,) .ft:0 f,_|}- L: (!B! _PU_W| & (!B!]1 eWPB!BT*HAUI@A!B!jABS!B!#!B!BԚB!BQkPB!B!DI@A!B!&_yB!!w9B4>ˆRU:L B!D#Qڠ/߰FWi#\ϧ9BG&!u]=MvF:Eh\ojGhD# u^I@A!8Ji&YsC12-Dct88}*)*'"2hAsZI!BCeO sNs:6 lsyB( !tʁ=۰X . R!*c&!uLc[pf!'CP$( Bc&B!!]4a/yJu|iZ=Bm&!)?º9M!DEY!n֥BTǛ:L^yB! ];[ @9Br$ B4۽&u!:h !,Wa}g(L>4IC>ݚfAfRiՌO3WPf ›df{8q$aT|i"6Y n5:Ƕm؛OumYc魏ӄn6LgRTpA X}ԛ✸_B!ۮ/84~!Ms 1Qj)*FUESU.Oײy'=o9(^ c??ǤYq`r7_y=MhVֿ?ϭ7%˯EC}:*8#>-!Sw+\Acփ+?:au73aPK*m~j5a} U-7U:r'o0>2wb {{z2yq(-{}9_/FbV(WT Q] W7OS1fZg!c4C,_J#s hDUj=*z-&CQQFz7B+ 1S#$&b[iB B!Rߖ7P92tuzI·7)͗?ŒO?JqXQP(bUv ƉVYA)TVW؟gNkY)I8?s\y#OfT2%ur>ۻu铺Tl+jɹTE.Wi(Q9sdʥ0Nޱml?YԒ][iQQ!mܒ;y36f: zv4 E-!.6lM?sox'!Bg~oJeez`PugXAc-պWLM9II&}=Ƹ ;i$  z*fF\wXt*[17Ӳ>z{&ѾڈXj8s _m-%,:<qKnݺߍt ^xMQPt+gƻ_,x,>. Sp9nNwY7*O]i[@Nd !.;=ڄZN~J'2wv]/"~ U_K@b.9-8};s<-"ŇtڑUoCM۸` z){P y!MӳG;"<mY޳, l*>A4o׉؀(uj8}s.>I\زd%)1֬?~uWn˖6֢7>\z˳\|IGy=6KowgVx\˩.U"믣gecl''c^w{M뫐i<{#` [QVqgZsgbi5f8xvETw41ѷ?aw37ՙ3G0b|\v}=ٗ ͪ]-PyLE7ѢeU:iyܵ2(lم^]_SEڪ IDAT^jΓxVƁ{HH=ECHT+toO!gyX%.oZNR}ij -Ądru"Z -{p$%|%(дWf󩓟z{Xwr{O05#ҙDiqTؾc(4+noքd4 !1袶nBԎŪsٰK 02Pr{t>6U3_?m'Vv-NF;?y40Ϳ7L댶y& hKb/;*P{Ou9q.sϿR|lkxE>ۖ,9Mfo/PbO!ǵ< h4?qITi>.]̎[UC$o VeC0uqfzM[w;1߷/Ԛ@&wU 5-i b4Pې]N\uTy~ܜ0t4ZqT P.Ɖfǝr) ƍxC[1t_ǣw2\h]%s_طbW`o7ޛugIy07!4v4=٫ ]}m:*۾׾Zñ (~͸yo v%lN׷p.z*M@3g+BLz޹UV;[q3Ժ$Lot^d뛙#,u)<׶&Nv~(,(mk䛧T{{O]N{/X|#]]_rS(:/j{/WjoD/<TA B!jh<w\ƕwI9bMHy97C\SCdΌ7~BT~y;~ੇ} 4f=aϞ|;=UZ?s Yk|tU0};{kr#](>BQ)7p j˹_4怈ԙ1cd_{OzT8gm2A^`x{i_^^ccS:N@8 %l1't aMDz?M玞֙Oa \ջ%z ӯçHwƛ۸I=͊7E殛#r8u!}1?̃&>z3׷re\)?f˙pj;<6}Y\1w#An>V߷Ϛy[PzֹW]Y|i{|:Z=1yx)װ6u-cT ג^y|eа>4ߦo`uki}ft"6;Ī8wPŧ?Z&+m@d̈nYk\WЫ?%x0ʹ[?xH4NFuPy) o6#NDܾ ݳuвkcdg!!SGTrut]Gwe+kYLV|AJxvi˖j7EѭjluNurVȏm1cÝ85Ek]ܝ&vNY[t~͟`5n>hN 8tEsIA Z9tFTx&ϜGxgr*|'9UZg&qX vv'y+kT׵)5j.>/B1+C ,2Qˮd0y^ۼ\AP1jٹYAa=lA޳Gwzeǔm뷐+͐+74p7g֎%R6k8ɀvcN㸶BݿEk2]kGUV6/[Y|}u7W}e,Wˌ}\1T:o=Mtt +)l3ru5zӻ|N;Q9m[U3Q.֤D3Q8KFg$[HNsGF5:wBxh[;[`0m߆A 緽^g&:'ʵQŰ)<4&}3Iah4҉0WW95NQq|0JYŧ]ۻxgHa-}OR sh _nxnŎ_FA))^ 8l){ B-=P|Zx9i$`5 1GI[)i$NV=?mH^'ΒaMjd,rU&\tI]>zt҅ɓ&Uكɓ&ѣ{Z./-#NJ1мm UvM<+qMv;ػ? U=t-.Qu;v.׵T(uiOP#R|};tVPtKoVNT9qX)/8sSyf=ŧ]Zzӟ@?b߉ B!_Θqy maRN2I#!a mxg&%뉈p57J&ZV&:-XpyuT^=sy7q£L43c4Ѝ^={ӯ_u:j;v?.]3{D-݋q[hcYNSFT]-ոs5uCmTLfA}LcAM{G޸g)uyYk?mu0i5u.]R/~%])/UH/(@?,cSРX>9Ȝ_3dtZui':;n`M@qC;n[8ݎ$yh u<4$v*a)U>uy۽#?R C%-;SVzRw8<[3';#v]գ'}/%MБ>ʫMY|P|IUqf, BoGCyyvt4Vc> /}ITc+g4.{IVz ˟pP5_S;s7~Yۏ{ 0]0ߥt3>;1 ,9a3M,FC%0#gs9.-~W~5ݫ< A-p3t259a(VKQQbFrdi=pYNbYd 4h3~n\_Û_λ6ck3 r]w^z@un/{UJSMv;%]cw9&jZײr`/9$MFG7GzԎo|eKY0{1w:~mqS6VOUz쩧Q}0 (!ŲV)F(JͬU9:6bSTXF@üsX<Zquc-,BoB:'N@fs36a5;9A%P#F᫙Xx':vd˒U џ*w5uOtZUZ:ub{}$˩8oj{5>j\ßyټΰ 9=UܝV'#/WV|uIk]z`N_ɕh6lE6+JkƶL|*? xCrKӜ:uh]O[xNG .q|ńOWlV:UrB|]{vX0m{L6JQ$u=oEEVt\B5],'9N݁f7ؐ qYO,XCy_1WE_^z+;U&w[IΝڹ}cW?v*WCU'8]l3$IzJn; 46 Qr(ȎPeF4r8]ߢPGӉCwQ&98UZ_mv:X ):qSӯ^c\K#zV,O,.z//?ͺ峺y})PI@A!D vt -re:h;|8{JsP-)7sʝMea ~lY.h]wmZc" }lQ֑ڎ_5mE.&+A; u[emA_dCD^1^&5d|7g[1m͎.)B>-^9F]֗`UǶWݙW~%.䣟Ъm:w]MoټcBlgI.;uY)~]- s8F/]+μn`lڂfNob eHY;F3T,-ko亮!6DW4oٴi*M5ԛ@1ݶ5O~\hd !97fj(=3ăb5A2c V)}]Ós{[X?f v}?p.FW"TeCj _}Q܇)D]ͣ2~E5b #m;_|9BZռQ˱&t\]ҕQQβ}=S5( B@Xuߐ~HEUGtzg_Ò-=ԃR oϠ7p-hg)V}4z՘Vwb_rg ;7P:b?+_Äqi[X*noaw"ݳmk uۿ\«hƬG<6)啩q1'V}ҹ4IyYm>o-6Uw?速YYf׾̈y󣹬]n#s| O?IڋoΣ}U(1Uˋl)+L2v.JE㪻6y |lgu.ԛ^ؙ c 9~t=sJuЫ$4[]Q"xUG)*ry-d.!`˺Ut\o rSN'fc1vq<%B q$A^ NaS-5#6@]'CS7(fWuMp:$EjN#@[^>JwK f(M~YDAͺ/Q{>y;qen@4'ÁnqYZN:|&/–O~'#g״|]5' Y}Q5b2`29HrV>vBPx "Q6V[ =0cp*j2c6QINL%3fB#c 5mmv7y8@߂At;Eyut^m>6]0 rοyFsJY)+8blT%fR?2;(:1cVuXuLg%z;LjTǓOQ%.nj|s3O>{ IDATDz.6՗q4 6Z9\$ _3̓Kn &E,"9c<.ق $i(5WbsSx_^UUzӫ|jdj>nYdCaW苿EQ[bi#>T GA.]v'uX{ߥ4 !5ހWzD*Rچkhn-RZ|](c8|`yP8OE-.Kttx=nRs|*L}t&iKjq{Oҝ*ZFnZuu'uX Bq^()r9Eq!jEМ^mkLoySx[jsU^^Sw7A BF|!8_I#+ (!g9B:BhH^q6I#Z!B!4 (!Pc^QXKѸxZW+}Qlزeٸb1!@ps&BK# 7$!$@hp !؎ ]ظK.dխ3$[%7/lsf̞g=sΈ ql[~{M ;GDZwADDD@0D!" &w5:D9"RWÔP'M -"-3ʊr\w`<\D:u]*I%}H[H?, ۶ %LIdB*+pDWMD:qTUVPt!TB)ض= M#"u;Lk( <>?KYvwDCJJ*E#HMɄ&&SBADD5xH"%-c <͇۶mx<,I}HSBADD5muuQ/ioɄ&.Au/2p5Ձc[#"=s"?[T9"݋{%DDD ]ԋH_wt)"""""""t """""""iZCADD """"}* ""{ԙTDDDDd)"""""""i """"""]f*H7t籱J(H4%'Xػ4%1; """"""a1TQYH8:]-DJjй """"""!MɄ-eVWQP8l<^u-FN"Am_G$\GNnbM*SvP*F?c x bte ,O "%- J(Hcܶ!E$q3bYCغԴPc#EDDDDD]\%' )1 % n?[%DDDDDDd?uL75(eY8͒ I*hʃtw ueĉ """""") )""""""Z3aZ#DDDDDD\SʫOӑ(EYz Kb,-g "Vad=%\p_ȣO]ғ }l\歛c$q|^$rs+5ODDdo}\'>Odqr㡾=n}[}58{~5d0/^z?MP^8!%9F&ɟ!RWWem)吉x<]]iW?wi-qA@ 6Dzi`c[~K>NpD#136P^zj3s9b۷Ey<SH zݗ^%ꊈH{?nZ|YDWbwJ(HAGzdHiSxl\R{0%k"""1e@( gު2jtXs.MedaDk/dJ'rȘ\mάp oYɢek(' [Ą08{z_Çh\2ԔRRVKpJc8ᄱdDw) "".o!)bpJ17 0@xG X~WUDDD:-~q#8}N|\=u&f>Ő-~&ۆ37ŵ ;6M_#vfbs_!o|CƸsWr̠]j1{?S?@ۼ^v/MY2 6I=/O^ƞ|lT]Wrϰ`cǐ=~%DDZ'y_XI`9` GJ0慠Hp_xNAfP6scpKH6O_>2m*W}ċϼƂ/sǝDiʃtsgX!΂EX Cͷ1mӦrϰ`qcp֭[SgkֶwϠKI8 胘d-83)HD'$8xFV+7F\ȽO?Ɖqģ@2ׯczw9`ns]º3f,[˷og"""Wrׯ'|yXK~|R7N9oAނN9g%c.'8Cb͚^4#hqɤ, J4{e{r&Qqپ~#Z gʼnLjF?IL;}>~zSMeeS[^vHXdBXbKeOLf3nm5. ;p3{,[Sʶ(sl,ף7) ""}r*r c4:ADDdd6gvnlmY;K\cذa'N W\NO'$Gv쮼6"xJgoS5bΎFy<|˝2I9| Gz6}ߦ/'jX^_tP>d NU+ش:/YQcz"""A]o]aP{1'7~ź2d|:1'Bm!D.=X`T&0tm5۩6LJhg[䞎Ehi!`H  {WF No|O㢌 ?K3 >e=v}VA ""}&5%OgF]]5b1JV~a٫981.۳[j[`p1H쾳! ˵bZ٢_.\J vp{1z3["u*b!u|:w .^RNȌF#ĢQbK@7( ""}fڵt$''c~aY&"""a:0DU}.u]zAJKK)(( ?/6p. 1~j'?ce㛯~i}-8xzqޑcjk$3wId2F.ŦhT1 `uZ5,Ƹztn]$%DD},[?=xӦMcylX1GUN|ojj1 ᧞ux^[[5jɡ ˓X!5=l_p)eٸuuЩ\1A>4ع_ FbwG`*y,7/ ,p? vy6`ł+jnlg{@=t75DDό7E#uױr՗0r]5.&t޹l>,uP7(y0Dַv>g.SULrGfd~In=[ח*;h ?ݏ9:iֺ{:VO{g]ӿz(Y6U_fl2^y%aH;OēK>qLN< ض5̛50~x>/> Cr1HiQS{ ""ҧB>DWCDDD+ #[)^Fyi~Jx ń|Ăx Yܛo.*j\sy$Nթŷlu&){4'Mߧ0*!洨xk:zӟV!u!O=u|uvdBnbygoؾ􀹟|&w5DDDd/gLBjbFץ}~6s.+ b1uJaԯۀE,o{? ' UePĈGc% c06$꫉4^ν-C./ ԕf6"T#%kƛėdMԸId.0 b “ϩf{44/DJnpmdg=ۭ|Yc'xmglr """"""}"Q7x6Wra$R$WNd[7LD8\睺 q,2c\bu{.SWDka &kFaq׭%i$u$E#(`(;8ZCЛ RטHSZN.%DDDDDDS3>\O"쳩:ߧnHg1Kh%ZʸNՙq:mkLJ(HuwcvnYAa8Nx,mcL߯8@( """"""}u]$i RBADDDDDD}+ߤ+_/ {CbJQް3\g@0Dѿ88H2 #LT%DDDDDDd[EZfn_7%DDDDDD˔H鱑"""""""i92/LIes!"""tBG#$1ˋtIu}O""""ҷ:P0`Da\魺DJj œ/"DDDDu8)RFmuHպq ot:":rr )d$""""CwRS[]c!A,u{EJjc&dRt>@Ɨ R<~IDDDDOF(TnF"y˾eY)b2SztIO""""?:Hc   1`(hu{+d$""""݄BU]莒g,iN'H$'__u{"WvxOQADDDd鄂.EzIDDDDDV^CADz&O"  """"{.<^:L8cPxR=:""""2utoĶPYoA9z Ix;%>B}J&hoXòkٰe;uq/DZNG@džtPlCocُ x2,P.# 3E ŝHb r)o<;=n`*\r :_x6׋mzr6ns(%S˷ˣ2?N"wi\q12#c{cX` 1oG` vډjjۭhoj;Oy0,}_>϶9+@IL9d3ؑj6^̧/t4Z>-{_!n3%|x'q;wXϹ\7\7,, !9ԣ8E+>?3>䋅۟_="1m]gz>6GJOxrNZ-mCq'""""ғ)Ά7֧/2$rM"?p=?j?0/w?.4쪺/6b).&ia2MiԖQRZe1|xPKo['% UyHJqN`ä˙<93jb-7Cʡ\{,!6㐷[!QEJXD8Yu oYɢek(' [Ą08^UʙRJj!$6/9+ ƒ aW\6=!tzJ %OǴL_q>Dg žTrOt-v>w-ta ޻Ekm|> ^oC]!qI'se{8cgppljnF!39{Oy ^~u>h2Ij?y /-óygSw}G4$"b;#>LX8&nwMYMUiZaN[=)?`6IśMb}<"v<-6|jnX v|-ZZsԏ6[na 9񲛸фZt:R.p*+jgXZoecRd=OVu,cN_;HdeD;^N;6mdƌy|gai:˶w>[";̲Iu7q5'yv-ؕxtw,ŝusfꨯ'Űm{nAcEBeOY=8~D>e(u-?ai|2|`5y{ {r rmf̟gBNL\\bF*{=AD[{;܊I9W7MJլ7`Ku>2ڻ]؜:|Gq%gr,rJ>|'_Z[܁w#vL;],g%H +O괃9dƒՓJDFJHєw|t fs3Ã`Obϳ|xX;_fe)\#jGk,(y_jCt#u;;ŝ7oIDATB("8pzm^q*뒒B0jQ(Wn?z"c,ӱz~DTVf6IyVۄ# `l?3rL֐Oᬂ;N++yȫSsI'rW&1i0ڿ.|GS%wp]EC2:K2g>FDb/ͯۿW0L|8EKͼ,dwnx#A1XNCxƜ6E3NsBu5|t:b3N/97+į jcɤz \֤9nB2߽o,y??[4sr[t<*w""""ԔRRR, ۶b|>{%? ,#~, ޵^v+,2[ * +LYY۰ܢy3g8s ׉w_?塟i,X[/P0rtO I,c;SZ;_v'٣_y*.=O`{>* Ěαwn"~]]-g,ED;|-+gȁeRxٲO ;.n}> F1M5+33̟6kϼ-#83(p"Xb39{ܷ>/!>@]nK^}-DDDDg m B! `p/)o6y\m!'֚ $, O/=#?o3)R38Jλ&T5nfEI.6C'&i 0v|6e+WS[ C3~`vʼnLjF?I|xW>ڝɲ7P_!g'YCΥv˻:o`~|cOԢ]_?t @|L/ e({uLFeaR~U^iIqHq'"""" )mc6ǃu_u&e(crZBPao}]S>L7ĺ54{!nǬ|H %{$n{GeLIL9x Z)(vd .es +=_ǔfܚjj\vz-6]kFZ+q'""""=b@-4NaH*ضM9Ƕw=WƢM&y_r2:<>a'v NT; sH zX>oӗrM,o5>*ٴn7YC&1{r?-!{ؙ:_`gs?sϙ2pY\|©apx&~0NNom=Mŝ6mO(۸ 5`(۶UU179b*;ÉMeeU, ֛pq}Ls>::K.&lS<<3͆yB[cg0& 8P&^ٺ 6m%Lj(Dq ҹq g$5?X2]pw>x{ykX/V^A1.rG_Օ#(y)(:,O->"vC-2 !YHi=j6Tt-l/Wlx-xvR; %jZ;"C$0U $5.ģNq'""""=i@lj>:xZPj&Qm;l gt2do)'|P?sI=^z3j5˲,,8N+Wnv6qŨXVD>)0CdH [ hCG;ؔt4e[qA:>/\x)'d<öԵl89K  "V'a<·81;nu_s%ϫmOt#o׸ypy;^X`*+J,R1.plIƒΊ螷7ۗh!y8!xp4g>v; a,GҞōP_gb_u{LkͶI]'ϐe|cmv*RںYwP~)C=cO"Ҧ91Tz O: ~H|ibXƼ%ldɲ\lFf[xTs܉> ~qM?QGmHfl'=V66\t&/c371A;~Fim |?z#ͮ$sϮ([9.4+} lWǃMٱso}~|Kmc?<άs_jSƁėHR6ʪWef;=!\=;<HbRͷMlGᥚA`"tZWz6|]\!\_Kәxg8JDxxd KAeiR[Îśx8(e߲]@9rqLN< ض5̛50~x>/> Cr1\.uwN8DRUlmӎw߼ ;*fdeqJ1IWu+8ReSl͘&eWrPv>umS܉Hp ^@ eժU-?wߦd[# ZqH$?NijɬEX3V5'PN;k'0!|?Ek5|,ȋzx] Lh̴Qv3>{&5I˞nj$\NgƝ Ͻ̴nJW4?y61կsɷNc|(FDs{Oyo73tU\41H"l"zّ3o7[~oҙϱt&Xvw?##]|0Z@'ӑ8ƴK@Dq7~޵ B*&'2֌wJk~iFm!tδrsIDcq\z7gWl1Ʉηj[j<*w""""ң~o<{60.jbFkګQQQcŊ$2pScvJtX<}q{}I}>,->~_5 +#ulH &۶R4L;J$j]u5DoB}F Fpv]_ k!9N$֡tK궖RVQC}JJ%+~L"F<kuѺdSy=dtN4B,bU;oPkbM KTv&j$2QHkIJT=DFx]AC~,#\/K1r{4o[X=4:lPI"C _($/x=xd|681˗Lr Nzo eY7EFVb"t-Q x7Gh! W}AAA!ԛ1aZiǗ̡t4%   '=(UѮSpt{ h}cy   }TBvfc{"   /c4ky,mW{Ո< ~šKeƭ.N S”f8/Ie(0FC Reسy'SO"6rdLL7&viܹH=Įo)kBtDAAPC[u[y=otF\tRG-K5Her؁;qT;R_F:_'v"9GZSic9X.Ke`Y%GQItuԘղ)̪Zgaҍ@yƧCAAA$ozcH|{X5h,-biÝ-Hx蒾8FKIe(/ޣPvqTO(F~Ε_015&b}OO٠jtgC7Gv` Ch-,Ealk]rIHȮ   Zמ38ܵʍ۴, SMij9BW? 0ugeU ߑ{PӍ"dz#߁n_B,Gzf\../`|Px6n>PnI _+yuK#VaSؼִBv5+  sHm-Se r!iٗ0n]fݫI<9뉚2MCvOAx2h>ARKw݇=׿ob t_ A} FRqF1%.TI5Ʀ>DLJ{#a{2#C5XMҸn8FvڣvlqQY:hǂh[1jϳBъ/YNEokfY]e(w:ibY;JkITd87cZ:ս /TR-SyKyI39}j*,/cn1yGc}HA~34}+JV7׏Hk)ec8B>"7DTG5qa5&䱜Ĩ:F3x VL*N4IXX=7Ų~t>MQ*ߤpE5ۋ+C$,^qĩ-/`,f Sb\ZC &E҆񗽵l=Q}Tx+N SY- 9/Jm}# IB%~1×TNsM|ƻy3f( piVHQViuLW$ "1~ʀv|fE>w&3hAah 97(0^2!픮P͸N|g-ٌ' T_n8o%J, OdJh~e"<(JeQdej&ŤTx6 0 a}q0tiD1|M׵n> uiPVL4"ciRR6N0 {PhC:AIK",gTΙ֕! |W!20( 9p;>HX` pܑ3|*b]&] ,枹?"A*о֡3bsaZְD2g++S^_Yr[C'PQX[2!$%aY) i٤#5Ֆ~7sy}t(+YU=E WvimP[=9|i;n^Ӓ ԜB1r{64gh''(X^fɗ5CM:aw 8/X@YVL2^e1uktcZKBvA8hlk5X}mԹ>􋽤t~!)F-&ΙϘڙ7!Mn.ϞD>/l@"AI6&6}yz bv4?A$52//Hvٕ13Dx 栲Y`&'q#<.S+n)ɑA>v$dW㎁}]4sX $>d[/b7sUu Nnwz4LKxv&[y.I+HS|/S,п'?{p'ҫm'rYKlZԄ|}/ ˞*ˀ-|_9x]Q^O10 w^ᅅ[[>گVHǷxhnn;E^Z48`Cg[Z$mÀH ƥ)n0|Xzv1qQ<гf$9,=Ů~ӒCIR zCt{t{ {Fn_y qTaF֋|^21|U`,=ynIdPOdXYrw$l;0]p$*%dW&~s렜V1·][yI1L$Fx­]q?>y1{|Yu5gk-og٧ɡ,<(y,v 7ݵ5|V\>vn`qe`RUeLb޵ABŗ8 <;,[4[YXr[YvؾZWI .\xH^!vUI-i3>A}2CK]d7O{VƄMҜ4A1Д"00jxd=J"4N\1p?۶=',eU`iƸ9f5X`C>p˒tliŽ.9m{.meϭI(ϼqTcIX,T?C'1kb-YT8ז3h'.`]..#! w8&J4r%qgٸvlWjΠI&˯vU1}||my~6<_3:/JlZ=9'ܵǪ۷򏋗2YY;a~W1_v$@·l(F{r.Vq R4'i|fE S&Fs7CϤۏ;2pP-$'H+;Ҡ$RӠFiWBvA8.)][Gx.]HZR Μ3gq׮=e6~Z>g\.ֳj Ym RlNZ>Ic4pMajS&;`42MgVr~ .)i:| oǽ;N']j'&BPH &a<Ac- z;1.jjPb,}~xvlҎʹ:t}z"\3@o[`ȮhtG˲ZcQV^/٨W*PbW-!nKn(/?K{HK9"w1{npY]fhɐQh>PAMEQ:4$YvA(4ߞ;7WLĖv4B)SlT.;4;z/i;[+2 4͚LJ_6٢r i,cʱt1P\wڶH [oD^IY-ok '/I$vlac=4vI &"!IִM( S"$gt2Mk49muPj;A+"fὬ9AUbVĒ 'Wo MY &`-|}S)8sTZ0LĶTI|%'KOnt?[-f5FBְwXqcm2 L@)&h(i3svOldrCc[8gh_-㱫3̓D ^SXmQB( #=)!Q$)$XiJe~$Tyhv(zH ycE,ObTI2z-Rzˈ=s>aOEwD܀Q4'5S36SLkHP,˦.MsC #ifZCh34; iKc6ː-X/¨cu,'zN+ڎ'؄BrR7d̒۩ A|yˡ/UY7p2NHZ4ׅ=pr D2+8x&\,!+J&ֵ(f? (-7$SRLjKbI%: ,kh6I)uNН-kErNC4;ۃo9Qf5ZIiѢŌ'0/D8BE}Aьfu#z`$- 3*Sfsp+P CAXu;{ο 2W+¹R!oa LQb/?)` uYOW'h@S+WpJ\i+F< q0_ ) RPϤ:)E]&5n~pmZmUP!>-^RrDo<6XޮiDi2k;.aZ"jʄ5pe))y`4&ml|x@_t1i3 > (FE}?~yTcD[ΪtoMft\h|pnno:tDw=L ܩS6o ?8K8o$rb2*A?e|.͉d{~: !"Ӗ P1#EvHUW LiH0Ω!C"%< Ev73ڏoaֺXrVed] FŒg7&hH\;_J`TJTp(QCXZqĠ;@*1iQ(2$Γ d݀]Y4i+xƤE1}sNzZ({HB41eռIo T"wv)"VKYyR&&}+ p-&JSmq(!b{6իW>ѼV4tg]{lvtw}۲\gR!&mG ݡ}(?7t CT!OjiR e'cB[F7O%WÙ.kuD/ 4?o^wUlyId)2I()g~F; FɦQ-Bj1{JsBi^ yߺ`:ˆ ?[ B*xBnR/hR!f梩|~y*c xyRQ&O!lUUusd(;RVY/@Rr&{%[WJR:?da h~x9Og֣͇^*Ϣ Q*U_?[1+^<`aB"I{ܙC?\+DMq`i<ZmrGi5rIRtůuw;+Δ͔GYK/:i&"1}vQ]߉륳h,:sd0Ha"log__CY@8uemTòjB. ~ϖhHe賢xwo9CWɊ~!WSV P]R:QBK[C*z)Е͗0vTP lֺ0O!0΅l}v{I,/B ЃBb*{0LcJR8CaȴBGkݷWa' RUHir^{I>jZ+fQ9 W˙Kbn3Kc?/! w]k~>QI,[Ylĺ:ɏ~k7^5ӻL,^s7@gA\Ok2Zxl/\}dS{OO i5p_U@@!CsBφ{wbv[ٌxUo<; p\PEKA%PU>|9F{4?.-_S+B'ko_e܉ %[۷pMOsTɌ)p/~[g[S&@n{ӯ0Gp_OS̘D{Y% xx:(;(蘷((Am.%3X]%㜝TSU&*˙ qVZ[v>Jx>.8uV!j]vXX>nR1A}-굒(WR]ʯf|R6+Ľϣ/Z^E58AO"Mi%'c魇a)L#,tSLEP\ˠ4ڎy*O[Ԅ`6$A/l] Q uw=- O:A^(њqH(yg^ N1{Z+Zن\6Ύ.hcNc[?9SꙒtdyG[ ;^|/vŚeq\LCT)\:DCv`z[fĞje z0yB*jָ{Bmڌ;<86;wus8ڌa[f%9%pS!ʎ+N b#9¦*vBx Q%.AaPb$c+ Hb3VtlUΧ Rۣ ?)1UEY?[ع4, Ѹ-cU)+L8iWtV_qagu"A)t\/W w"Y !+Wv0QxdAA6f,^FBtrf[ ԭ&fcd7.pb6D+T1ьmąeQ0=^J!ZUl>^/yp *?.8ReM++ %oWGp>kgy)`&/ vຢa薦DFedhT4Xvo'0Ӗ;r#+ ^vrktobve]AAAO70^<I=r1CVc±Rhne *NX6-ֽߵDH_lbxQ:lRhtP_HF[n;ϸOAAgF*YnL&RtTZZ6'~ %T+ Uu5VO|~0_Y ٩>CE]qZnv{Z_՞S~e Sjq雞GHȮ    ώgjD~C90GBgF^"QW}S1j    { dAAA8xAR AAA8xAR AAA8xAR AAA8xAR AAA8xAR AAA8xAR AAA8xAR AAA8p,?xQ֣AAA>-P6(hwFAAA8>A*   D   G   QA   pTA*   D   Gg,7~  xX/Յu#as|}?`%Rmo|޹VleWe3|~.v hk*x?Mex9+GwP42irsy7@wKdgЎ"Cg k2<{. ص-k75 ͰVSGκ,evջ|˯anW|ӷ~# hrgo?GHw??wkvkL\#}gnC9e'09̚/> >ªu_⬸2ɮ'Q 4=#^ymˎ[j|*}6=OF1<A8FwIt4ok6{\~F]wfݡukv:/nX`mё5DVN\`J}qG"v-_r (E9z3xAwGbTyEx<h4}OOW龇X$F-V^{pw[ro-\X&5,ˢz ?qvUG}|K/go Nnxx%oo8Z֫U|x3l&-TIQ<{"'"orڻ"3y|qz1fXx ̉۸+g@~.3旄aD@/}Zty?mےB,WBLn?[& N3qlڣo/[sy/UarR)⩵شI4q6N^̆#=2}7f%Ec4.[΢Ia 8zc$ϟc/n$RNyu|-K3{ӽk;;Xژ3!S)йs'_Eh("ϘfcEr SLT.,g͖u!@y,[:I!߹5붲yXŒwrxɫtNa^6l ?of];Zk un{_w_׿yU&5R O^0wZ\c3?o̳_ٳ*? ~0{6>۟v[[!1GrN]pͮ1i~zֲ!KY۸7k6a]}h3G/ UW.% o_j$ĝd r_S0YŐ%b OcTzg_-2Gh3|n';y}zVyqZ5.L?#|=Hib;o^R[^(~Wpҧ|W7=p{Ƚ9,xۍTYXz?w \ŖN xʖpNkG?AٽTXiˌ ߁>$?{f@!{ˇ?o< G0Sӷ_{-yc+/Ck<ߋ9kJi!ۊҟQwẺRMnq`/X('͋bS9/7eg{#&xKu|f1a]t(;أ:m*/ ׳9U"N;~9a*]GZAրyo YL0Jݏ~o=i`ܙb~5t;7psJ?FGw;?ηQa%i7o#_NDsyg:eլՍlbe,]šʋV^Tk Im{ٺ18sfy̙iv4{$|%(GC}DLT:]o]v t|_=tt#|7UlUL6sXwr;8o%d%NX6ze$O~hwGbYNhœj~vi,3w =uM^y4ymXCi?4^)n!fnN;mhAN[kw/0qΉә8zu{fKG/^f O Npw֐md_^0s'ԚlzSt'px+o= Mv?Ag3$Ăp c3p5MpBL \[3Xl1'>ՌZdeӰر~s)5dNͼ[ͯz_I 4+̗~T >|H_F˳W_z#]<#s/P(*3+΍׿E})l5߿5e#7FՏ?2pڻ[~ IDAT;`Vd꿯㳿XOt=\X:?+[=뺫hc~#g^Gw{|H*I#_+XX}[~&t0h4stKCiA(hMPu],K@6Z^ )> -cnY,~?̳5ogWso~3ݵ<ʭ7' "u1-Knr'/7x=xݥqWES?~Go6o<ܧs myχ;CkT9/7}~SmpxgRL:l:cnv`ZOL䞯~l-OIn5k?ϡy;.vcT}o:^=G5y8.eݴR9# Xu^}yRg{O=> .xOqO~0oBYj ɱwk$by/qg('/0ֿPIcOꆌ\GUzSTR1d0*Ե A#QXV,3H I2$!KihHJ^~c%+NN*G攮X<$sޘ\H21H[ܒq2V%sbt˙<~s(]oys_ps;o>wDN%o9o©eciL. L mܟY~>0 15t PssY$*rpF?R'&=afDکXƿ|AxvaR2{LA9Dӣ~߭8v@?̑0A¥C'u%GP4p"{/Q%ײA+*sS5K f7K^磿Ju0o#KA#g&R5en,w) Zxj;;H#wW\0{n3u̟;J[aⱯOau[gSu¹H>>{!OC׈j:l gnjƛ]mX6ɑASou,U_{4$bu*GpZ GHTtuꀫ4}e'98|!==;7 ET-J:)ie7EMFII$uu6x2_πEِڼ7/IL}4l cT~I|ˏ֐hxhT40~x˾Gظ,غ>#[^ُꙇier4IlrY"TÊ2IDZiFR$E Xt[r6#+V EU*y9&="{2P^o ۿ+8{Uo8wuY{/̼/ X@ \=n$AU}]:8 MIxlYe+»рsKM4Vo5k!Vsn#)w/@ZEOaI`W['Ү&]4HxJ:OPi>`c}\+{,@GԨQD/'721\qmJ2xdwj('9`2 4+fӾ!Fs(#z @%.J*\ps7 pCQ  p5\2`jliCgLD ń1|{|V@+9TA-gW.jO %eީ!"aQ\l sqIyzY WsZN?/+/Ammj}48 ݸi= <<'N#6 FxXWK7)ɡ}p25x*AqEōiE5e&_ɪ ^Upt"Ze|xߋ? ԫi-BCO]qcoМ +nph3 bجȊHDAOǁΟ# __t{αo>,㠜adٝc3jhY&r3?*fU>IŽEKSdFʵemSЃܛ) -ICWŜUl8r,91Qeѝ"ѯ;U,' C2 9GzO*9Y9VZ>,U=_+?L<KDDEnn)iDDN^~.NN=vNѝ]qc6tV.a7!ЩS]79:t_odgcI~v+/d0lYV]ĭfjӅ%\/]N>!T_$.T]}!.U)5sIڑ#ۚ[Nh%#S 5sev|}/geձsCL/ZâUL\ѼKϠ~uTE+iAr fWAt6b4cד^L;N$5]G6mfB]2r ]pi̩I)ԉAAtJQ~zu؝mv tjv}YhCS|>tt=l챐R(W5shN!A>l@%Is tALx9uUӱ񔭮"B֡I$&Ng`k2ͩd5KO. 4gHVrSNרF5Y+gc$䐝k&xTVPIMNeOF:aMi0*D3W[ Բ/m<)N"CR ٵg}Ѯ]$))h:χ[Ѿ]vޠ`قdBQd`@]Pшfcou\*K>2tvi\T.Ëq3o$Ppv7)?lk1KGLfTzkfJJ%˰Vc^ ~c'cc<Ɉq*۟n.Y`؇h63%%%5\ h8jf{ `cвu@k^~!-ٲetuh>&d4LyAhi 1"JI5k8TUt}N:&ItnLr =*1а*Jox瓯ꧥ-ڧ%]/ŗeG.^Lس<۩fUM ݮ$~<:XsXR)(j'7k<_W[c}ڣY*xJnkE"9%/Qkߡ |cJy&N]B+U+X睕aEُ**>N,ĮZ"\doƇswź*\' :Gnߞ(M$2≗F0ݷgi:V-ۻFl)wŀ> 8٠AVF&mڄCPOΟeEU󦸸*jUz6qUQvA=hO>L+99$Is *dTBGY?Ng2%K8+aM.XcSogSR䀣o/).Wʜtć~a7zqu}goe=\JP),(D'цU'&MK{sb~ϼ>l\ I2n:!Ck!N+<{DU/tigN jA>mv??hxb~}EZ>|K~9?7WɁnf\sSo毥swŠ&9hsС&pWi`#Ѿ=u횀c|I'Y^wnC_Sg̲Πf Sa @*J{<<~l=6r 8rHir>qSyc/t|_}@LsAm{Yz%~\?؇-+QQ3Bq)>ˮ [8.i,Q:в $ }Ss=$!yY爵ƭulޝ 8͗3oCD)ƙ3"8x H5k1%8X֌в.@[?˟ײdeSn)NLңjTA>ta]ә]'!.ճIntngl:vn}?IWTr3PN:|..I_)a79^d#q'<.ou5mO횄1xsX5#}^RS_=mGlޡ@WyUZC&6&w/B Ǩyp|E?ɭ/0Y_'[Ŗ`n-u>ʭkk*%./kͳx~@,w;C^eEg0FOyfu*QZYg@æ(UB3}~=ςS%hcXxn$=3cj~YjjI \ХGoacrHÜ\a5ɝ>hfϭ|&εl񕃆2]0GB"{u˼{`]K:®ߘN*3$o^ѝ pU)C9}'%ܽ#I29i~̼ a9q=u}LYһ1ho8C|D?b(xУUtړݕ;Ӯ]GF'"-mB# nCpPZ@|}[ @Cn݆6m""*=u"ctJx{ a_>$ojH@wҽ@V^IJJ={E4rrsػwA1N= 7/i>KRm%mW<>Q;\z2?ύBw:j.iw{\FT-7l8x1sE"_d7-8[I'x'!3xV_|LLj-KhJb~<ɂ{mm4޹n%j|2<&RVcI!G ?v-13Z40:ݳƋv"o[F-C*=g>gt_YNJW\|neC[:V9=Ƙ  IDAToF}P>z[&ɒdG}|UgkhY若9_LyxF6k><ײ:\Eݾ]t5.1sv Y*:\ pC}%2$.h hUN2{xyEO^U^K.gϑms+(qLfF "58IHhU,gf9C JxYI)jp/֞Z@3q&94i3 KEE\G<u/'Š :uײn*:99Ҿ]4Cmբ8 [vϿ:EpL/b}qTH<=)$ A#yϘC7s"K_0cFYzw+<3beт5]_1!S^>ZV=ؗnqХE+8=EޞY⼡e]3)c|e Soy 檃 ¥#c $3)]uſ5tK!B%c|wtG nNp}ѻUc)a g-HF\}qOjsiEDVK3jWKV !T +I6u/@kI2[:F0|؈$~5oۻeO8w)}J-yLnNhObXh6rp, s][֙wŘD|: \{i% r:>_ mвPA9dW<7ҟbTA.&{NET\].!֊R9q(25^U$(ڇ[/ߎn깝hz^PA@h3{sFA[r+FWF u<쭄\kл\ NH8x ^  4 #n$w3AiP;n >~Jف.ㅁΗ^ƭt| {O`pGt9q|)⊐|Gk7SGAA.+zG/Z BM@Mo>Y\ŇV\7w?\ʎCZmxբ   " mOT2 %1oė%%Uj$0$OEGAAˈH:g#pkAAAH&      -Cv{0;O/y #99;A+@@@EC,RAV  S!   B   "D@*      -B   B   "e_O" r-z'=2VJ*S'YfSOW(9٤cpwhY9rJBVw :ڇi,+{dLAEl߱wлgo\շk$Rb5l.v:wLZG-SxJM!Éx0C"d/V=z3ơ|fZAYU4f2cJ[tñx{q6ī(qa:sjAZLa>}@-,sQf!yʅjyLQQ"{smןd\;1^7sتYBu(fW1r(&Nh.ZĭS6MA%q[|Y 沧hl,;>`sk)oAh:rRD\Ks$d'zIBbu;&  P,K+oC+Ǩ)%G[7_ 3:ƕ1,(:{#>^#;B6 ~& saŗy %`2qc7&OfyFep*J9ʱ#!N>5fh'_Y,Sk}s¿Ua*S܈NԢtexU Ϗ"9w6iEVVVrz񛼵6ك>wC.9%hƑ,wlj^MdI^Lȍ%BUcd:Z>;Z)@#~_[`Ă\{G3\@,hvc]WoF81>$#E Il%JTȸ\NSƁl`қ,Lg.eeKCj87gH9lϸ7M*)f߽ǿ|N'779h܁b% ݛ-j2=ui p)5[@fd_6J0Ziؓ\֋`䞛/# 8fQE͢RX{[әp@TvJV Dp#@j\ jQ0[ؚ1at-\ xiEI#_E%.^F 3¹j uʹ_|EzGmjxu cM<ǛR8hǽǿ @8z4{Njؗdzxi" T@O`8JWDDz9WhEUBS1Bٛs%7y3=HlY:J2΀f(G9E%%K%:wT2K'GBB#.ZKfCioOQ/A#wJCޚR:4}o鬱T^6#' гd:ҼW,?=IF.\XCZc9kTROב$ea9Ѫ 1 rjlai%hWk$\0aUf8Cj?]x~'q") vǖr-cuhC0'_B= 5 dY%[!_)4UC$iSǞ)L>ٜdҊ4@(-c3d7ZG|4B9U9DoP|Dת f;k@~?a)[ Ln`;D01GNkʍ1toE6lg!3z}C%=aYJFASP1s#CvSAMeT8-'q/Ed j/)I^wYdtP\ )!w<7+k׏I,?>s?">?mQPsط]v+E<1j~n֘|^~iȭ1fZY,ya0n5 Iy82yأ*\*if.Fk_>$}^e;_ۆ9oM#z[Iv޿56+a>o.wF\I747msI8ddMeBִhh52t@d2^Fxɉ_gƱsEk ;{2— 9Svvd\E !(7L-dA**$U6\t 8oj/i*Ԫ!NX5$SbV>gB A}>yC<1Z23]k>z<^ =c鰝|?w-gb9"HKm!hqpF91Bpײ-O"N,F Xϰtj:3lhNKf_I۱ Y}e7o F02R WUPVTZ*Mj'j܃<5ͺ$wg$}D' \bKtS'qsw#/7g.nnetrs?Z4UޝW5gWNW{Y@"s(/gfur-wl~S1Xˁc=bk=9uMmO/ wO|yV%7k8k*(^jA7jȮR+9aLFUQ`T]8S^]QC^Mu&7q߀2b3._ t2剋zl'8P&uھS/EŌgWw_jī-V֣sY4.eٙl! ^7 }} n _&~ l9r"EU4MCՔ6!:]Α%vq^e,'•it6222###Hgر|ׄ}At<7s乍6z^3m5ѓsfGEݑ=Q\(Oeq7׻?,ԔkyBN ?ĝ*XU@'a#VsY BʆgEӱ?R0(ϣ*vSfa8VRn37 +DhDTϞ&AFsV6p7]'a;ىڒ ~R"j12ΕS3-?|~V S TO&֝U䋈G,jV) J_*R/ PK bb!""LddDH4U#22BTT$C(.*hʿ4okJHX<4dwGfMtK5Ur~0Z|_P #54KF= s]WF텑[H.ɥtZRuAZzȮb-ҿ3 h ٺ~ݗ~^EhDu ǫ*(V b̲ ͘{mP8t4mT#ZNKfHmŹZ +<|4vj Jddeԃ-\M4j+mRACZ\\/!b2<ڴ %%%''' MII185qrVb%N`@+]k㮥E$6hFf}' *7xl z.Nbe,߲#3cU^WQ$-~_,SVּW[ntJ&o|]?Ʋ5֝c>>Tgd%r$\TUEjR 5< -b41 'fl.| 0e2=Or!oQg9N¨irNrjd2rM`4xRȩr,EE hBqH2с:d4⒬\D^&A˂&IλIxʗlxfAI.^.dſCYSG㧓%`]~n퍛^Vofb+Vk-ANwןO״J1a9iV;u !kpR5b)%`4U P=xPa6Zi|<~ǐtx_bE~|Og(z{rv:U ; >݇$M{,uF'pHNf8‹AsyxY!l7N2z:D?VHe/'FJhM5GᲤSdKĄp,0+Z0mX G9|*f2aLGM%%K!_dʯG }c-b n%stp+sױ/}!ƉG)ڽ"G--ʢ<$̹dh`_Y$>ӖEF ڟi3r=~zKywޞU%/r^}C_]Mĵ}d׆]|#i0x@.kUAUl48PK%H'sw@!{Udx%jR$ET+63$ʊ5X+륧6lX܈">t!ieH+EEEL&"rf=Iki6.9F$dL~&;F 4 fhˁ3M >(჻|у{gmWw^ƼMbl 2>G.[U霉O#_5BJRCلceW4R ɓV8\ig%r69bMw]16;h7Oe>w.wFo&py!;;m۷NaA!抯/}ӳY{H+%!5x< e1!A+KXZ9z]C'ُZRq WpZ7ĕ?:]éu\.+bXM&LF#&ӥ\WV7:" \&D@* B (<:8HȤiXW]][* \D@* %eIN=̖MI1L A˖HAACVfOyoEA  .j }7<ňW+?Mb ?HAAdBnz -YAQ:عz]rA`  -]cd8UAAA   ?" AAAZHAAAwxUm齒B %t }u {y]]PԵ! DAR j@z}#nB\@y{̹gT   HAAGQnb BbRAA_aƄ?/\Pnڲܼ\6;t"$$aiI vNtd$St@p= 5e5 SwG33\G!'eV$O"u!̭ eWV dhwACʢĢNnx ;dW(Jvg><6l`%/:f=tNgK97;z<J eP?:; Bۙ(9Ją5;p EKT|-~EQPUZ=v;bJ-9yV™grڔ͒(jHz>m%ԳӇH/qb<<^KBE[>v 'ͰfKշ Ÿڲ?kw='2f3F'z#?W0Qyn{ }ӣDJOYK۞t GO窿-H#q]|UwB(fOhzfNiF޹*mID-症35!L)Wt&@-gzFDWz _=/ʕVz3osI:9};sq_!u?bz^ɔ^'/P*H[0.I'Ow߇餖Ou(fx1N6\ Z~Q|!3g$11Ç;HaC*[I(o7Oێ`bKZSLR=LHI Jj|/U9? ꯚkׯ%:=W qd[L h$Xћ( >H7;z[oqcBFsqI@j2Xv-SLA2gz!LBvX/:txW9Nt͞?{yko3M0K!S'ԜR]I2էaЂkƿ:knVR~'P2Hwuvߘx}=nIsh?$G3 ̟}y7CGVqppl5ᒥ"m`XNj?u:3~#e9B/ Y>خ)8PЇjt>yf4`l_*%?<ʝ/f/zeddC.ǐ+.'CwfϞ}R$zn+]lifePٜiCŻ[S7a>Ů l߶*aHO/Q3(\V>I}juXjcEBskgZHe2(r`עְo_> .C. H҈ EQXLv,|2nFChHH0ʇwtZJ [Y\?ee $7|Zhu(»v>]mT~E Nw'Q$=,}r3HNޞ$A[Yw_YE4A2fqݕ}O0y\/i-ކ-XpJU34h?-JM k#(?jɞX^vgB(O]{ؔiCUMggF! `fR}]ٙSFNhl"}z;BQZ璀_sDmm-fݎPUUݝ6Rh"_oAv *}h|M4C61^em~N5r,ɎnC{_|Wvb_)_Wwd/_w5)ϳSɋۘ8t[(b>?S>g _>3F6xXdBǃQ-LJv*iz 4񛖄}y%{lxYӋZz/l ǃsQSS; l6=37г{~޼-\~ʍߖm h:0 D^kr0u\k,Ǟ9t"1C*/ſANl#>㓺~șz-~W?YZ{0E2k"vimżϘOx cK0oG*p+yY'{`y9PU#Iv 59'l.xwp|oa+fÜy㻝't&NdIeknw5x}(Kqd(,[?7`vgĹKNGyڦdSon׾- o>7֡H8Mш7Ap%w纺:<<<0(BMu K_׻bw~"7~[eo1t)Q)]!) jjuS I4t~gسٵ-ݒy-vr$c7cq};_aEuw662=6 iLi¬ !S-=Բ-R@O:*9J>_<,mNǷerM1D^q8 FlZڻ&^^JA?)9>nU'ypsv"`X\VhB-LcKB<%Eە^CH,E1'w9m(ˀ6䉤ףb< ۧï#PI<8W${2,L&SZ7f-˧NdxH 6 3}bo_3[H9ͅ%iuh%P/Xm]OscNExHyԺ|+,SGE9*lM3:)7#EqC F^HtOg8Y^-*(O\ߍk&a@?TbX 歛"dSN\9=l9k߇CJm؟~Aߒ[zJ5&x=g ѫ}䧥sTrNJRmy7  aeDޞ{'''c|Y۞ɯCN<0/Z0_zO|",gҫgMT(ZkSO̩CG%4M#!A6,!|/uI3ԧ/)Hсl*DLkΘ2/BȐG^iSJ\2 ԉe`$$dڹӿe"f//foɭ6pq[G?]Kʔ25gfT;kw})fm6 9mˍǟIw3Idӟ˻yr_qhw,:g4q2gG1\}Rs./Uj6|?õ@>z58MNbRWWGee%& шޘff3UUֺbwH&[տ㻂G*_`M)L:q*5U5(*XIOɚlH5}pwG}ؾRCUm^-.H'ĉx ѨUWN]ƺk;`C"P,Zp7ͳ/O̾b0Akd/(KpRJ{Ji1% h"TS*#jVϸOGylLdB7D~賬.sOXyU?x5 Tddʾ۩>~ iieр&2VzYV жRcJNGg+~ S&sKS+R%L暎ZT~]v2=Z*ZCwY%qҙm.U c-N[.ͽkO$RPJڋ1QD ?m$5j]0hUɥW~jƀҳϽoaeʵ#e5H ^5_hvdN谲U9>`ތ"3ٟaS>;.E J5[2l^پ1zU£FZaZ^CYN歪*dɄf[oe…L>8sj!9t}_;s,|G;4]v.\@EKMSIֵ4BMUuEicÉڬvx}nv3]svEZy: 6[qx$o VNƀcw [lL>]Ceof?m}SEw]AkQ)[SKO0.}C56`TA208H 6~OʰZNnT%h+jY}Tur768^q@?Ry:7< '2ޝ&zʒq`κLGj)wf2cWW{ +J&OiZԼ?]z?z-7Zp1Z(Ÿ^B^xv>wR ql^ʭ|y f*?1.~@鰷Yv[ o$?$ C׿UǦG0܉luZk|HG/lhɖfklcRzԧUda`<15*܏gH@O}V))kl8UWb.+F{:.玽3s?g>ƒ^H> 0]CpkvlsqHn^x9|뒏ruٻY>0}"FImLd1 ilUT֩H4VK{ͩy8Snjz_wQ>M·go:iUwlc u{5d׀8zt@E[Z-ADD#Wp?W'yvsLO=qlo*|Y6soj2ޞ:Tnd=eNq>4Z]g\(e%"C Huע~լU\rIy=Brl3V{k@fИ! %ke[C?۳׳:ǎ&b[q2+Mmzlu6/U fFF6.((4ܼa IDAT|-| iCAjj5ķ@nG );Gs#OQCSbW}ddݢ%۵t<kccn^4_}5y;GRHU*LQQdz/9NE\PNl6 swP1M(xl^y|Ks`:(ZGf3n{HQrkaɜKEnjrjdPM _ $\\rM%&&b60L? d2ѭ[7WE4tz ?,ZZWs]kRRTbxr$h+5uJ>G{^jiԮ_*P"mWLÚZ&LF"'ǚ_^ )]'C9Gz1jF:P~ {lZz icL{hyzcHxxy" 447ʞx{9ws:d{.vWsw )#?5IIF#ҝ;9b%eNфEFaAq뭟8=@>#;5oEMUy-N6fr8^ LgH5~2[s!I'*Km2jIBlڐ$ğٟ!Dh;Eːj3c9|i;3fTGtjl֚uy(.Ѿŗt)+-GABxr9?A~2`$gJabF. Ho2I"((O*gYf}6&Oݹ3[ywoNJrnfĩKʖKd/`|%rZGфk~YȲoBE,L"铸z\\.r$tԱvj}}iL~U-eCSx%ol~AbΖVnhiKɟdٛSx[JFzg&LԹn&~3I¿sj6͈kf(a Y*[H#G՚Ɏd#oܩ9%w>;ضVUC)F'g{4(n!`}[ؑWwVg3shm<'}P,l| vfK-LGxrd8i?[k/~ $I(jc,htq. Wڟznswmuװ$&FA(-ç%h'k= >aZW'Q$Aߺ5 F2|ն]JǞ}(Dws8Ps6/P:z"{]X G+R\8ԩ?o"$$NΝ;/ILLt\G [S"7QzƹݸjhvW͕/pK.Kؐ7gqA2Uŀу[HRYk5;qMc I۞!OJM8V{e͢Gg@e}U,;c-yyC(b F.~0l0X|9~! 8$^t\]+s ׄ~:I71O[j4y񼞾g.ėC.jؾW6ԡj#?s.u=cʐ|P_?MOOܔ #& 3i{Oatzn1Wv'ЖǶ_~akm)3F 1,[]hEôEUuyj`O?&mpgᒠTUPOkoxXϳT$!z(DimFgi3+[gZyྏclSջ]OO'Z=; eAEQH®=qb@X`0rTju5f3H:vj暉v4M+\]^ϸ ܩm]Э)^FAuo&)If{6dSԝUD}9A_X35}$ɸwcwpϵIm\$Ozf}߭cXy,= =co;![r~6SWQQWuX.BDCy9k [>xy]fr{ \UUl6V@@kX7ĸZ4Haa&JAʡC_8DHH;bZe,z)^⃠̞3[E*40\+c8qNƾF12b9窄 I3 tx{?}Ƀ$}'|9]u%[Xќ,Α0$id^js㋯̚gsBΏʽg1`t~:)H(Gge8f>xιCz(8xܲTwu$|mP|4DvJyݟ _[ tIL؇q(+-ƨj 2̻e&OzVK /.DdDl# ͆d2)//#2};VV[]Ѡߟ"o/MHPg*Nuwuy=Eyc3߹aw,.o}:8nlx3Va鍊,ɫ"4:h%-Cp;奵#4AHt4abL̢/bPqv:] C N tv( eȸD) "38Ngg΅F]H~DGGc20Il2Qd6oWhdz=nn a0t{:W3CBSKVx&6s؈o;:k6b[syiG 8"RAAIFAgZ@hN~8'܎7 쒟yKΞHAAQJp =Ytw&qA$RAAA@"%dpMONOK"ETAA.Mǩ`5v=!OW|'}2EOx,  F`D4gJ||y9$AiAAA   pQ\i}   ];/!   .vI      HAAAB   E!RAAA_S}=u fPTD  ŠpaX)Ƥ CӆͬuU5 sT5TTx9kJJ7cGw!%GCmyFzZO<j/BE@N쉯t~EP\XLE+Zܼ z=|3cxxHvg]ΰQRXHi7vAm;'AAA8\PXXHmm-6 NrWgG)$e3aSqT*2hK) ku3kN]ڝ+2A)709%gkǾBOit>D'fhJ{ONonednܟvRG"ݞC@QET ϩh`ult/Tř;ٖ33)1)>x#qȕ $()-&$8;]A$IXX,cZx{{ӡCWX-؋ȪRq*,}C:7;HClaƦ_I=jDՆNuF>ؓ13fC5|$OXZDn Kԩ̈́.{v6.yiu"ڃПM@O 8~wҕ͋Y^?v0TpD5RsʛbW#C< R?bz⬭d wy6 e;~懭F"{/eϖ8Z7Iѝ7cښO}Fׯe%W>=A!lٲE p$ -,,^޳]:}vu,xyzRQQqqR&ḖD玻VUĞ/|q+g6 s Y@AL;-Fr5]NVe|~\ɫNt0x0 LdpRR!voLDpXpu!VJqɥDΦ d*\6(sGl6( VLCC RAl$ X,Hnfa6ٿVEQPTݝcAf1dcDi^%"c8U5aaxIR`"R `ƒO.jSK76ZrP"# At]:w m!x2URѠK xz渥N(Ģ&ϩrP^adZu"P[^z27gP‰J9CbڝzEJEG'(-&#&In04n|t32mWvXIA.6tٵX, l6 b4(TTɗNRGL1u"yѥs$:cK@֓~Y+qHPT!mv}ۼԪ;ή}UcI<| {RwbD.|r$"Pb!Nl KsP[CT,cNt_ʗ)j=u *H<=[ %<<=z8RGSٔc! O*8**d_<]d_|%F2xrUEAxs.6~(wDRax?Z՜֧j?RAY. HM&6 EQxT'H$h)C9Xzb ٯ#4pջ"a4Iݗ=<ЫfVdP:KaPf1U^Ͽ*x TSULNV&݆KyX r)It}BuVDхwpwZ#H)gך-m*)Ǯ @.On^rՂ٢Zt><,msK+ԀQwG׷ wj4b$//e0G⫓?!mZ *سzwmNQ^!VDE|R{%?~:ɏF'#1Qwf ,pT$Um*$Ȁ*b fdM&ԞvvaT g*;k< 䧲nPi`ʔD|bVn gꐨfm Fam[ *. HfscǎtUUl8=P9ϖrl_=ѝMqf)4%yEJB'-xCZuz"zh|~ZIv*Vfl\7Xw k ]7= _,Fk0~3{j ٹ[.RQ5D6T!_ZvbՅ l= 9㮧͆XKYa6{`݁]< C;#_s*vZ"퍁[p*1jdYoJKc$,ruxPM99y;'F7I IDATJ!߃12wF)z7t'H/ Iqi,ΦhpQN\3fkAA.3XV,)r|jTUtmmי2JEK_셙REt㹎sNAFj 1`@?p4(5lu짲NwJuC$]}P+.زjUy*r`$g>'拹e߳|W)v8\3;0p"c:s(&_;nv2ױf_݉l/|9s䆛Auבף|wwUՇ?ܑ2+,A@n{_jUkmֽ{:p֭8Y {C y  {19<5",s9- ukG>Z|Y~a;$gM眳~1D3aJA~}?LI*!ntYq1J*wDDD#:u_8]^0n3f Ç&`լYULQc1pX]Ok;Eq20hV?(5Rc4M6ETX1ĵ&tty =m)',~tcsď`H o(7?QF@[Z,CMņ[.2v㾓'8]SEu UvM!0c~fObc'Meo4F66N$ly}\QDDD:Mԝr[A($993vI 'aSi%bll,FduxX4sv?j4?v(@plÇT壸p+!#Zy;K,"*{gh2U iUaw|y܍#r|^^:x|mfDMxWulRIbJrAƮL"#}/d}l.nS $_ 1H^ڹ*bȲ).܌ۭ~"""=Sibb"~8KYl^H,]'D C `2tX%FY9i8]=m{6a.f&?L`@Ft,ز[°j,葩@b3?g?/rU g31$9LddXl__om@+f30Fyiuh$;5T5O2|-!챌;"߳모 &ޞIL~ZXT mጊKݶUr]7̔! ./,\%nP̲,N:OnjcG^DDDWelXf ϧ9ӈ]u*O0rױ>4aɭ|_ọtg 64>lgW~ǀ#qa;z*mWE|+ D(VȚu D :#A/z[X1~ᘁ:PZ6#>h7–So [΋uKVQm||9Gqq`;)xy %F}TS\@#8b+祽G;gȸ4k|eHcRVPf1Ʀكʈeqb~%sV.ˍդdq$SsSZƢ/Qti#Iss0OCRB8 %.ʽvv*jKtky^RA=S[SI{^y|d{uAMTl| EDeٌȝ9 !vd..)a@JtW3\D%<ħ#9&<yiu3"}nj """"""#HEDDDDDG(HP @*""""""=BTDDDDDDzg{Wl#";u{""Eξͷ'"""݁@]t`dWDDDDDDzR """"""#HEDDDDDG(HP @*""""""=BTDDDDDDzR """"""#HEDDDDDG(HP @*""""""=BTDDDDDDzR """"""#HEDDDDDG(HP @*""""""=BTDDDDDDzR """"""#HEDDDDDG(HptDDDDDDwm{7 K@*"""""rk@w;~^/f؀{0<w9cQa;h0U ,Fxo)A "bS!>% ۃa:ڷal"!B,^U1sʔLd/)X]T;s6k~ERR^j.g q# N 7ѬJ.]Gd+!6ҽǢ1l@?:,:?e!!;+O?*B/.Mz@qrX 7IZ56Li8RT5J5틈H `5յܣOװmVn#ez~v)7=51D'uE`͚[F087o=Zm-?!6,_Jw1Id]O"*}q?0𮙂zf}'~ 7o`Ɬws-8Ǟ(k_W͟KRVn5хa4eђdŴ{s91p1~fqLn2жR*V6u?扇aւ=CazHs<NiZ%ض+o/-3pTTDDxWpیa9?36q$KObEl@5H$qj;_96nË{ UQZ"IO'-kX|+ 8<ĥf1jp2"LVf֔@D* VaI wF=qSiaN^zl3Ac6e)wY۩M]: +K(\ Ìt"j$gͩUC2IjZHcX YZ+~C0~IHM筤`Xef٦tvUe*VT;K@vr84ǹVjF)1fY{wqȮ_TN nT;9n…u& 䦧g@jIHEDDM` s_ -OG2޿2Xy[f<ŷ|F-az sJ.WfJqUNSFEMc 'R:+.p |2NJ-SbĎH`m[ou2'0a`l}xl$3a8i]Wn{OGpS즡$}9]o0#ڕ|}Q~I+7]㮇h'N xOD\xcD:d]w]aG G1QCA'<‡,|6~['pMꢭj3}NaZn?^rc*tGg|os-~K ӏ!ӧL`L=zbw2lx.[(FcF,C,fe`*v$0ŋ72™ph<@hջˑOo >4 jV%y*;fbOu.;\:d FEr/ny3N<3H82;i)kxx6oNr%1ow&qqjnU>çMé;kmj[LhpS8e*]E}'8I7UX<}ϛlv8!H_,5 9Qayˎ`ǟSIqfή+89S94Oݥ/( cOgƘQ1p& qblqȾ|~x IvCcNJ {kP H gЉW3y[Mi]-+Rb.(a)G#Or0>E˼65[ꠛF3*lEX9ɘ@`";IqO sP͋f[A>3  >>H7\?~f2Æc.)rS8Z=Rd?N~d+S+9) Qg2xp^"Wc{ͧF mAMt>/72*aCٳeI汗r,UʊUUX=N)Yj{8Ӧ}BALG'Ggm`(wՐDvq%U fDOOX^_ƒ,\8[IYYw,b Cp[F~޺~^,ёDrepQDb|YN. 5lfλoWXm>֮){ sFt22Lb1)êƦ _WIEݾ2|o"ǞM-7#1puWL%ٓ"'/sG?)+o~&.! i?I)I{0)Ǫ(gͮ-%"d:]Jwl颪Gr\tyRfzH<9_pu ^f6 /s&'X†~Zbe\aa@Xˑd vf^^ㅰ$;q:sxUxVԖ8]v DMIa;R&=*X=*ZNfc۹M-߿y؆#ᚊ=? e]e Di^ e7֚vm|Wl@*""M"8qC`+pˋoqI_\1qaѻ4ȝDǢt,>^0qͪ"̈́ьo]9`y0e\a&bAV篠΃e|VD -Ԅv>*+vY٦ט>d.а%$spx1gWpkOoWL2`U糼]_yaڳ:|܉6Ƀ v9`'<^!܌>l"qM5`[Ekk =dF{ mmk_X IDATчrѩzxB'OHoMyl$ÅE> wi>"""&lUE\}}e Ą(+\U%Ԇ G683)\|?6$`,Yf 82.7ޚJW$&4ۆQǍ (u{O嘡Qg_??1gE&8#טy*N:nLwߛO rNNBuk{n.ÛF;WC{J+~TF&hز/?YHis1Ӹ¹|x!sﻌ_|3#F$ؾ?K8h $3dޖRǟ;$b'seo=r]3xpL:Q -PHԁT]x(}o%&vaaMzzsM輍#G6@*"".<]]cq{l77M䂟@ n{6g'/㦗 7Ï/fĽM #"w?OWS䁝Qh4`C]4a~?_ό9?[7hq)9;6Ih_s 0ej ,""җ,3:vڭ%TP0¢OI_.[jʦeXn d@|.ȇ*r͟Аq?+F9_MExQHJ[,R+$zԔQ\^G0,Ӊۼ;@:'=5/Hmi![k #ޣ *m>!1s QqmY$lZp4C2b:՜{2c'Op7!vdPՐET@\dG$uGx{U*ݱۯ J[D&gDe1;.1x;fhrp?g六ծM/9Gdr츌6׎5uf_/.ryR6QP]^k|vylwQ^dJDTۃaa䁦ipGQX$6-wAn'2!1%6VZ^Ϫ*>XP9*Kx[otoHEDD\.g"DDUdq)Y|WK_lFx^,+Ԏ-MmB 6`[Yn1a0 b#8:7Kjb QQ8v׌?%"""jFd*t1DDZ'M3m;," Ѐ%`Y*mDFATDDDDD䠴#Lڶa$*q]i8yhATDDDDD ιs呧.ft/I"""eQ-[ .aa$iG~=Gʯ{L?'|hxًl4F0{ѷ~'Nr:1?;(PEKAC sp8}ղeK %e7z+"= i@Ӵ_ä4Z|/Gf}+X|^?ecR>bC3핕>\N'."P =+"o=ݾ_yvVA/ .ĞHEDDBU"]9IJl GV>ocVK7}w̯y,>m0NbڨH~X ^4n 1\ ec3&?o}ťBrKr;qVg|q={1ml;D⾲}kbXZj >,\3=+PRs`ɮ~v;"5'#hyĽG7`&%xF #ToaKm-ޖm)<0@0$b eT2JB{nL?=@0@0  H'XU|7sӏĽ7sF@BI7lZ-c0l 7hq?K@*""G\nSҨ,'3361{2 6l8[m%)1."JVa! w߃r5gE /v8qv>HDLӉQ /oa8]=HEDDe+яcƳf*JP[[eK>dxOODUЗGd@5 ۶w綿B222ͥ3mshÑ7?}z^[L?[ᅱM`C`VAc uj˱ cԇTDDf飼öl~?W&&*f+aYmaY`c:5a6֎el;sca L1fFZX SmEfdM#cWoR Qu$M TeX8s OCC>χe[f;UvHi&rǎ!224pdfd㦍=]4ֳ5`U]`YEѻnfiX&_V{ݯU%Ͽ_HMcޖJ.N+j0,/u6`m@{I`F*Gkޯ6R c8V,_Ž{(.̑Ei)kj#ixiPIvv6z 2Ȉp,+DÿĈ PSK#w۞sgZU1ny vMxl_ncoDYsA gӏ쏁 f&dUro+X7k5VRZQΎ]&R!cFEV .93xP=.H23NgwNIX3/e_}-`S#-NL[]Mڌ{Ưpm%T|?]!)2(-0YSX6p&w\Qfj)X53`:W\ GO$;j\`!~']YhxLeyrwwj8G/Ȣ"/p89~}_6,'$8q8SAneؽ*WR ""<<=] kO΁IF/br>4y\t!1c-]JÓO[3= I:̦>$lx B6c˚ ġu'2ڭ-U92xGkp'3᜙t"SOnWY!f8igJԟeSy~ w6Dfa$]@}+2a(HplFNbi8P(bʥ Ѯ]nN;UDd #23&Wv6{0N\0\ %ET0=$?%3'{NdP@he6/N'4d#_Șp+acQ9^g45F8qiS)5V2~eF\@+pX>jߗsoW0hhib0GbGU wߡ(HIƌę+o[^AuבxՕ44{V7H4rGm ] 6PWݰVPga`cY48EWG𐖙C:6壾*ϐ [Զ[@*"""""ݮ#-CꉹWGT< _~I<>='8W9'7έ|ӗlZEel3~ :0:{xqέw.ooA7Ov-זPeۘ) _{CHg:(/- C꘣9g?㬉ɸ:iÉ4NLcsgo".=6]Gc=a2x."N)A61L 7ѿ?Y`ŭ{;okSx3|W6< ϡ/>[M醥̛y/$uB5{`y|?ju _d3wqnZw]<, Ϙitb=mlZ5kgmqvgDl{/KsYw}fET96~pJ>j{CHos@7 -7>ͼ*wyӕ#daۀڵm:Vv{zisU+sjm_eiN~ٮ e7qio7||#Iv4`\܇Z/;v̚s(]Γ|?{7t߉:n$w|yU6fq{]U/p{ιJB@"0t댎Ogtqęall2>TAI H%$GH%$$Y+ksnx 10Az:ph뽏?)/ѐ!]+p[%@X{o~;a$XqSw<:%UngOf8N۳X߁Dﱧ6zSG᢫ 5/2 =M^ɵ fn֭fw^ A%NZLSFvl[Yq'9~ Kl4zFc)!{)'sVj lIDAT,Yb_7N\s/>z6~0F$EʞWqg,fǺl]H@M±AcbQDI !4a i}E?|>sFSUۍ*/mY#j[g25wFh=>/#So髹P5|r)Y7֫ѥf{-tn~en\ޑW]-h+^}6ztNC_T-|ԫ;zXXVЮcXvu,ɃUЍ*Ӆ=o?KבW{*ι.u0*YV;F-pggw{kSPSv<֦NG`wn+e&_N݈=žbix2{<0݆ۗ ̭̚ɐQ=89cΜU:g T>*gc1o)*]/fĻUOؔx$N0wB!h=ZU33lܹ2~?`]QUg4ͅ@ǡ<R~`j.n;ݓ#;0]/[/}ܠTE{V|VmUIW.G.W粓TF:jD6;7l+f0Ɍ{h±BuE=yy8Q\v1Ob9̮UsVM}ܼ@bslң wr8'O=u _+0HT)a^0B{Dڀ>vsv[q8gpϜ¾EsYrk?GBglɽ7[{&{&{^.4.6>%$BZ5x^,¶4Mr.MJkDFF(W[bȢ_tv.vORsv^dz̡msYe'd]4SGT:vF7 c[3{n!Ç1>HBX-)U.OnlA!9 >cܷ2\ȨTyFk.ֺp?m锯#IoiY8}1w$ҡ{淿W޽,XICiV`wm,pq6%,byJ1)-c$c?˘(a$ہrVϽL~*:(J wٟӀq&FǣĝĝB!ZV5e 22HEAUUt]vh!( y謞(e"A!69rVېBUH~UIԚ6W+|se`[G-8!cKTofǠ#I҃޽Ρ J%JZg͂oo+x]5 aeɺ3xy>Y[32y<ݴK5Ыfg)fٽAlB\CW-@0tܯix<ϾGw:y|i)։3Мʒ2jl&_ճf׸Ty5׏% rde@qϜskY"cٸ\&ţ݉;N!Gl*Gxx8>GD g|o] \nqM~b^ߚӟc*xS?•f.RM˙۹ /v, %*9BgBɴk9Xܟ7-$vÏظ8vCjdǎee/MDtQ1ByV|GTw*FE\N"sxp죕Y̕~5k_zsWoo9 }577:|A5[(IPZOvVwme˾btGAō~9g<q ;+`'/fq>=}szoi3MNU뭜Ni`S;|  ot&联{_FcE420!c (\<ŷXqPq~ǫ޾Ys~ݸ۹Gwnb]ljmrUK֏#.W 'H)o%w |n'Nɷ(]^BS~wdsٽsTƍ66m?{=1k:vo^I/O%M7Rv4:zՅvt|o&oʴ{;,Qe!^=q__S~d0G۾Wğob`Lzӿא=MYs/ A&Vo/ Lg=D'>O`5c(UbfpT=ci+(ᗶ 1=i/SUO}u/ޗps.t8TTzq0-P(@ )5f#XNk D;'6YF*-V) ? lOH6Q z@ԈxR#w6 m F!=:"N-6M?|PmZgRpp~щVKLeƉ3 3M}_düd@gU8fe(5Xl@}[6.B;;!B6j i.)(`oLc6W\~e#`Γ|[=N|_rAX(YϛqlQ&rzFRʼnٞDZcT'4ќϦ_b['k ]Lؤ+qwǝB!Z&!mf.PP!:9nbil2'] I\q̃h&Oiw쵣B!B"jn_vnfD;_,ۮgGz&qjΨB!8Wtp1=\?cؔD#Nx$BѨRUӰ, U=}:эJm-ROx!-OB!8T찋cZp'sZ@0PJ<3QKœB!NM vH+T)Bt\WZeTl:H)1 Qr`CE YpCPp, u0D|x|_w19O(_r‡(;Pjb %! 󦂨Б ݃ D QKA66Cw% AaT+>b]%Fh}x;=^-Pha"stpPC)[37<"㈩cدh7~:sQѻ03|G\Q)̟[A`+_\n_FyL e'o^J#%N؊h4FRp$)a4 aZ(ϭR-0!=JH)QRH_8k3<6r´;mP0XH<3YxeFj,5GlqTt{.JQuICjMae8$Y__w_IbSsp8'U0 p;Vɣd{I;*Q<1hUϘ$<{9_|i4!PR EV-1I۸zwG˩Tj8Ba.Vp_j2'tq] (?tcLap;6\ƌA'Rp"`'W!&w)n!]gZgr.0⻐@zvܩ@ ]}X;xdٳm/(%KK8QJe\Kkm!p,;;?GQ/ ߜkl__52y5s"J!FőW& Q"B?W:|Q7`BB=#{> $iHl;[yU0-d,)+L=*íIJ  c돻Fdq 20]JYg(̂p13NFG'žl]ҎgL3e*M) e"ުhFTx.]:7,ikHǼSnvVvuhÇp#w%.j47a )@`s}Afz1b0_RRI r4\8ꎊǡW&I/a%Ca(P(ӥDFt*E!Ia"\guiB$M) ^4-s&WϜ[ˍ{oƾFь49<'8Ue='Hr@El\R6n4Fx!YQk複yO: +P3X_x]/5^r=l TiEE\cHe a(:m0=2EP(!2IP>((_h{?G*W.aL%n 1gܼ{ [FRa]25Xp't»ܥ\P.%QA03wFnxd+˶@'Hg2L;3&2)k<:~/wqh4M)! nE!-]s87D1ڈD˄B_)/v~.Y_DBID`o/">#QfPɘ4L7ؗS԰ ?xzLaS@dy)['2q]vkkE\QhǗ.^}7awF{Bar$f(0ĜaZ̭ *ډn:ϞcBb/ aKpb>'wNA0 j-kٌϤM%X^PNUKwUʾSyS}j /I {.yƳ*AMP.#]PRh4G鼤tv.S2zw,˲wf0~|^=ܩUF9N󜢠v"00t\pp?.܂,<"e8\t "pRE+,rLXO3$(ƖI֓ Vg,X~TkB_؇o\osg%HKpm\N$9=H󒦤ߧƤ Eo51@.y#v X^⛇(AV4Nٶo2u̘?73Iۘ+bfShXW B@IZ!Cs+18⎅iCaȻo sD1X$EM8ڸ <]@Z"ID"Ay)}wW #Ǟ<]}rS;{()Q_(o̝ێ'DO'D̚fy/3ڵJK+UqL^yMMc|섭|jug6rŕ[ k0x}m5E'֓[noz O\P! Qy1og }#xb_ni: H0t; dZSJ +I9nȺ=@Nw\I+]B8Hkqyl9 \p=00<@v.3< 5ؓy~H -P-] I\^E|ny{_=ʷc0`Y@p;ۣR~'w=ߜq!yL&5gwrsۙziu/ ۟~'J>6pýkx5KR [eVKI9-è+yu D44\DѭؘdI0Gy0o<}5 <&/GTJ&p 8m2YĨ;h{fIL+be{dϝXa0c$}$BQ}-9y/_2r@/m%@.tks|1iamadh͌+%bXAuIa?5-IToњUSw% 4IfKDg]ҶK#Xy*ss`9'g3љhsL2$t 8r.te}ݕ1°i>DYi,'OR08BEp|ڷ0[HٞMU}x%B Jb8 !%BڽD9Jv-o_pK?-e2`owf3b=Mw/d z Ƞa ,Pl/QfwңVS1q⾝?W56 ]GTr.1`?[z/x 0HG/h[3x㙳yŢؔ \uWySJz|[T6Ӥahf{xbl]ANg `žCCc|x/\4H 2|${q!h4G@ABڐfz@Z^X$=IB7LbF.V"I^ 6rC&9pueCMDĹEm;."IBu 3\y1(= >\dҷtgX؅=== GyaYs::SDgTqu)ԙEm%AB>k%Iq5MdlW O(fSq.dms 1zVAH&Q3GN:'M9Z(Fdd9n)(0>C߹H'Bٿ R?^O(gyi~tp &^}!X$^XcW`Y/o,c>^ڳ.yOz_*΅ɺ"D#h4!]?hS `یeLŸ&:^Y?v(؆tL*3,~?9z];:X%a|}LDx'Sɱ-ώRA} n\_}><) ]K:H#Qr>b=HuV8LKQkAG}ä'Oz:h(,PpIH!zQmdWmmN7I_lr,nrM`;Ht&2 bWde}0\[h4+fS33j0DsOC@j0JTňrѕR4nZ s;y]{h$PA(d]@LbZF E*Ø*[O({-Ms +6ѭ9H,8p(IR=@A RKu~+O4*Q.<}z ez#{WW]',{:690BOXڽD9;Wsމ) wb|o}v!ZՅ|zﯭ;7~h7lW׭eG79~!=ο9˳WB=tc̙1&E{Z66 4M%>/#YF9.\K J`F"/\FznB5i-PRҐn*w]8US*f i|)p#}hY9[2fwT(\-/<n+tmR2^A4_hO |ڳsL,@:aО\),x?.4b[ I-`8Y$Ì^"Y$|rҒrBzا\LBr˓rrI;rb2O N О[FsQpyU0OM\z7?Gwv@UX8qd^~l^4&!@,i7vD#w=-'_G F/:ߑ fE| 'sF P.?7ak)q |=s#ٳx+Nm3M_) "IG`WmĪ:0z  :ɒnkfgFYͧޞ> ZAhAs_ib"iq_Å*p#]1!ewq};G&7b^w圹 w$:1h4#HXH)H+rē&َw%3" v]m;/~_VGm>_ t(a~J[l3FIGeAL@w9* /\$$"+6 KQr?LHmXvtn)-ZG"z )iEmcطӯ(h4GM|=ϖggO{wn7Å⊓۞濾r;'[rA}C=Gђ\lr3ްmC@Ln oV6 0)ƍɂ[yٍد$y/a4KxJ>d0}4KvӌYm&zba|LG!-IӣZFۆE]^oTeѺ+ yhp(62\,+DLTD#m˔Y#\w(CuةmFH><% [ISx.;Ho'2)Ȇ:#CC!z4,ۂ|:" {&7gTHXem f3giϣ}k4DX61EB(\ǡmٛG3 hJSc*r, 0kR f߁vV,+H2.)iDk4ͱw |,_$j ?[LEx7msPc)o'H3jߊmT23UF oh18KyT4G-a[6#.8\>W>})a!aX|Qa07"^hwN8r DJdo0kT$@~RYW_)Tm$̖[R@X3R9 K4cri;Fہ+m:#('dž-;P='^41LЖ|.s/_hifY&إg#ĉ!%*l~-=50""(# EQ9Ŷ*c*U*#f#<.w(F$-fGFIRJ+/S?p*C` QH;m .d}%B`ϟ;T&#"=v *peK4FREnH }ĹVT'#XoTći7re}Z#ېxEq^n.a zh7}؈yj)Deڨp‡GAE׏&i"k+G/ Ȭ\Y5ҖM 'KX lhkBŊSQk8Fh4aSIQIhΘv\Ŵ ^V>)v<<zxD YܲZ5ʰ;XU"CRL[<N- 1/~xxu#z8 m^h4fxxNЄ7< jO -II<1#${pVxYHt/U$2%#O[8X`BVltv~z=TKv/h4F3\aƹ~aYd(2uX&JRP"v>w8BVʰ\r/}=腐II Ch4F6C܃ CXVRCqmƥ Eė~6 DS)ȟF,ܽDID 7 󱤇?V?ر~7za"%FhG(ʬgZ+ STWeTj@T>&,cPח<%=(_ aHTxJZ+=(T{+wIҰU<t"eT VP ־^Gh4pHVW 8!9%rq+Mh:@2TaxO^,oO&k*K#<^]V.Z, '6>,7CV=qNm$Vl^h4f892Z#$jp#+ 0g/)qqsBtkFh4sBt%Fh4%Fh4%Fh4%Fh4%Fh4%Fh4%Fh4%Fh4 pe:Ǵ3#[u4Fh4Z&XwFh4FyEFh4FsѢ[h4F9hѭh4Fah4Fh0Ztk4Fh4G-5Fh4#!-fGFE'.h48W[9(yOR(*=Ap7.%NyW۽4k6WǼcwr$Ϋ|潿bS>9\sBácm|[?㞭9h=<5_cm}AyT(9Dm؄  =ϝE8>EYWN6&ꒃ?N;c,'-jXn̦eʵQ54/o42owGEaja49z{Uf\6|txCc_WӾ ^GNcBa]ErKM=mx΋nWl[%=gS|Xf9Q: wY#l78357>7s۾a9h; 7TuS7_ &3,;8(A9XGĴÊU))Z ]7d;B.~£15B@rOscݼ%eBJB Gy}}7E#bnQôI䎿vPu^U.꼟~d ZNTƺ[ǯ~_6P.nf^qۘlQ=Nؽg_E)SwoѾSz/tq՝۸53jq&\:L@ab&i J=V}\'p,淜İ D9p?_BM3~< fφyސSwpoNax,iջ[|+Q@m_7=R}j:ՌfVm l Tnw;=m*@fnwVXML1^Z(:-w1')Wl\u;Ȫ ` Lxxuz\ˊ[чM ^9c2,N1rcL̄;y'|enLfGr+ppjVlɁnRuN3Sc;[h`ҬE۲vGQOzH|7F:^PtybO-ګ9ͦA ew`{jԟ?w%I<5W z]y9sh^Ԏby#ڗaYsܾq݊*$Iwt;x@}+ɴ~"2G񧽌n]k-Oo_ɝeqLWgsU j^Z6~\:"ɋY_=QY#8Mo9Czfmo_ϴE~rF S~qj1"冂♻ٱ%2; "C06yAL}p;*?Ƚ[{)h y[aRV%7U89FmY:nGiL^~wռްϑnGJzoDm^c4eP~92?:8g˘5=6l@3裔"/yQw߷XV̑W|ʩCΟ3~8M[d7]9.&1?78_r7ss[ئl?F`obh4`d&p֯bؾj )Y.O[3b5Y|N=Hא2)߬2Nx =Jd3{1/NKRy$=Cf^rmH52atfLM%P2~G>sPQN3N D6rTX*)/NǮU n!Luud,@!sx_Nj=?G~k_o_{mQ^Nxj[q2j~uCRr7o'O6hNbd3ڼHsxH)WB! 1y7s-m|[.'f̟?J06{H<꧱hN+e}lӫaY/R㘷`Bѵ6^HzwwcV-c*¨^v\Ǿai]DxxNdSO^cIgUZ^ĴH3%熯4wle׿QS?ʃr>yi4:7f>k(2Lc*sY̶ΓN`B+&p{|+kK$ s=0?\{})?\ie7a5zX6}%4Ki⻼/X<8m9>Y^{3_m}G3W$۹GŪ5-{׻GssOGS.W! IDAT{dk DAˢ%Lʻv^t5'<׻7w]*'~w=:íS'HĚ&Fwt EьX_&QJa;ӛo6It΀xV;D&QAf|Er>ռ(W3I޶J~FpµeC|LKA*Y>bA2oQ}TsT'J!6-oZ)Ξ^||+ȏ~\aH7/%iC<<:vϓvkFn-RIjHy1&ODkk vQXzUEz/{ 5H#f"MHٙ9)!iSkqәFYFjfHte_EȌXtx?ħbԽ(:˦7凩 ԂcO~s7Y:\Ä=q1O/fSWiD_>!?E2-ƛ}*[;7%7ѢG0'A?y +ɇhƁBaAN8{d@CdxJ w$6P@{) ]/*>vm@$GLu 3{yL7p%̙ nS刉0kTy Ijث#*,7F^%M+`QA0o0{]Htj0Ț%t (餤;ecL 87\yE"b4vXƍ+8~-yPQٴ 1wZtT*Mv6Nԋh$e*iTH=~$i-DQJUU=*p8PΓ Un/WAg0$pĩFOrcew?_)_eFpǍ-<u WSJxy;T~ҶvGW=8Hݷ_)2G_>nvp/\E"8_EZH.9cWE0HރyǸZ\оj4[o@lRrL}\~=@δ~F4RC}Bp_ȴlE[I`~tRriXr9Oqe4Q]i$jP\XṂѦ8?xZPvRbʹ=L~WWkqإW{ʀB֎m)ŨHhg@H-OՆ~-A:D+Xll oy 87jQ󊁘a2iLm*_W9[ ׍X>jq'4h({UQbUCj).J˞ɦ"pşKV 2VKE9%r&pKw j$088\HnMJ狯3fKcĨ),?|Y~$_k  ׸'8oFEK]\xh;/mew6[TEAQ| xXΓlޒ-u]WC>hv$k.l<8ҶG7O WM}YS+|оjz3џ忰4fn)wڷHp]3k~ۦ;CW- sIy 8%t$d{ڠ]j'q- EZq^6b2`S/]kIe!ld#xaܗ|mwso<hr>6UNY}}y =^2KW䣤cJ3;Jaח~وl^T,E.vḠ(]bk4ǹQs"F:Rl rAJt*f!N- #^-8_V(20;X ѹyVNE!;?xCiVQպUκ*+,#2 a5Q.:"{jPXz#=9G黿y8yk¸Qx5׸Lҳ@GPHKk&-QZ ~"7S)(?:{CURIJ)yXhq߹^fdbNN"K0%Ԓg`p`VW-OfRym ~Yäɥ Ir㧍 qA2ꢬ Dߑn ,hAU8}5kmX:RtHfNkؒZOK6M[ MBx KusnG+MN  ޛޞ:8m;IwGaC+&z Ǫ;2gRmd9 '*BJŚ@ҟJppXbyz;IATPjރTg69 P]1Ge4ǹQ# W0KNSѪZL꙳/2/ Y-*Cm{d*gWpbY~O1FjB֠ N YS;M%RgUUeIJ9ĖP'c4@].7+HyeEi>uǾyzЁ6z2w g)[R\CW-"czǎqߑo$'V*CAhXWA\R$q оRu':y UL.iJtيPwmQGܶ=+ް??7 mӖ¶ſ3xRTShOЃ2ïjwX.z\1@Ol@ُAx]8'Vʳ`ڴEN}-2~>8ʺg-j,,^OsyU{_wUTQrÂ#+X:4ckǡU,;]ֺJަel-4&zUY?U%0hXrjYF I9M$ZHNΑHșU>M{~LgJWuO~X؛<3W9uNFW ci5bK1rט'+LW܇ȏ9C KxzH>ͲB&Q"|}jh_Il\9Y259f4WfE&8.p8 K^$b*|[m(RӶV=HvU-|W@@3{dƻ?3Oɍb\s/UҲa;ѾS{sk2>ڕJ r;#HÂg TiÙxrc],mumSAcRp#Ü87ʴ 9Qr|ykM?W_& ` ׿71g+<,ٶf3K+iili" ﺔr{ǫja/hk$oz0A2oQ|3J@1V/ZWbGwio6GJS1xhT`UdiV"Kq?6S9w@S m[pm{ܧ%;o"GMx':4f駱s], Q>6Cr ᔓ)8֟n6gW??_=(qq*eBF͓yu?͇^bbTA֡5=T}7=scZ9]VDpl=No@+h5 jCpPKww:իzHVJNS֝hsUl:>vdr}>fַ^oM[Nm?pc3ضa[#,͏ jt_'R$#ݯOoy"99rbZ$:ʜ;o4bnLmGYiu.Ab-* 9{%i߹ۅyFƩl6;A- hpgp#O͉ > &%%PZfjǎ>w|xStvs"׿$0ٰCnEbt|h;;ePJFsCEpK~!~ɩD3rtS]p7!xt>Ӊc7BO!HsOs۪?2-k?健G~ k[Mg?Y@VNfXB 9+8V'xeQME r%KC׿nd\04YBgyMо΅0)cyɎ17\.j1/2Pb|C/xޓV׳wAz*cyӡaQ9΍f@vïe4~-[#h22[5lN'f3koa2yM:Jo૬5YogsWsh*N,,c )S+Y=9V=ivmǃV`zi6cy7yfydz;H"?zuG>Wg@G]s6O1WI2-rk E?X*O%˗~ŻKdi5C733NR׹aԔ36 ӘTS5@ ueʍٹ{/>/,,bA,[qO? -CCt, v/9(fx&l746~@20YxiN^rmT|lo|/$?04tw޹Ҏr$1"ŀOH41[9y< EvWSr1Ap>2KtxWԺoN|4IM^rޑM#ͅȹp/IcMj>Ñ種U;Hڵ?{Ut opV|Ք\ cD0m=g pTq7jLz 7jM1fiE%oZ4mxiD-HWq_92/y>z{N;e|F\07.@  yi1}?k֡( 72ynӋx5$&nbsF'uipӷl|;_ǐhrظ7$GL@ 1a<<_!%oy:$dh=)tK-:{>->a ! =u1T@ D&@YVJHV/`RWmßO}E~)(_B#{@ !/ܮ:BӢ @ \ \u@  !@ f%^Onqg'_@ hk|oD JHHݫV^>qBt +@p#K@ fFn@ [ @ hf@ !@ fFn@ $R6Z|d PHpQz tjsp9Iw_Ǎp}Try}m1)5V @Pʕ!+"#HBRkSݲѝth8ʸkU1kSwxcz3\C FQ8_9ȴq^~n&(umd ZtE,; ֭ML젣g?>gq+Kp i=̓wsfpSF"$ҝI]/cTURXYi W%vVGp";dzE~^ YjbE5D%dY?^z)(I^R)E.oUz&ĝkت%Cq4)d IDAT˶tk~ sd6vc bڷ+3;JLG1.}Tt+E=e݌3 ]h›ABp &]-,*e8mG6KQ0e1ʗj$771H Wf-vʺ\5CWu=XU>aօyxW-z?2x}wRzqdeN^\FH2U.Z#Ƭr-ѱ_awr(~y,$v}TĽ Ԋ9,ߐxjM hP YüV%GrVK[HX t)+Kcgxpe1Ha;ғ%IU|:s端qW\ bZfջ%MvT2_ 7A$?%+(X^c?Avld[BDO=~W FQXuNodq@+˟C"\3ǯpmĹqg8|.b٘Ϫ.\`b|DO@P-X$դ:dBZh0i Ĵu B>wVqwQIU r)XbNޥhB490=v{*Lƒgq*sPj6yoƍbZ=7Q7V[)vkt|ۃYV!pB2#fo!1bn-4埨K^ǎ$j9 o=M$ѝ-!r V:Vvq0U0M~{ٸfȵ_r8qlO-ѷk]dMF=Cܹ6[Qzbb[m}Jet; ="uhN8< )<-8Lr^`gt3noIT.MXu@uAԄ\>rݭ;md%79`#7g.'p8^|2F΍N20J.r_jѾGW]jn]ag/4% &j>(6]{6Y[vmFO}졜Y՛{&w.Fjf7YyoR$mnjc1\ξ:@Ia{ {‡r?Xo*yJ{{)eKI&{{=r[prT]5 )4O:ǎUo&caRKn2jU)N-δDHH$)R!!݁ -^ظv&vE1D`K2iSHVHi8&Qxr ΁ap/z6>Z^tַZѫnaѩbzǴ˂(ʱ sb|d*X8Pf, dG(˚&+v36j4m$'ڸ7F,ik:Y׊dṛK$Plt'7ƹѭ?N0kF5L O TšwC-Ɇr̚By)2gx{Tv췜R+x] Ifi\xF{qĉMy̫hvki JS9R OGl W %IBFE)=w%72a<(DC!9_%3Pq9snwүMC|%@F#Wcyvil gW<~q{ .S.}Ot,y(o7fl gO͝0TU{ xr"zuCח⦬,{?gҊɽ>UAsPѼdѨnp7ZfeﶽU*Wz]gvl;{{\*-eOo$ܜ|(*[gޒ GC}[eN%IP8dfPM 4{kc %̢$VOd$@;׃PHSTdBӯ?d֡~Dԗ\K,Vh RICN&лxKL##C@ZYtɹc%ZJ-@)Eq #El+e|%P,Nr]G\H&? 8P%=e$Jt|qr@dz a[KGx{HJB^-}%jo\Í~U$C{VxLWE®ɣޖűM}/yyZ<ij[+9ID/o մ-DE/s)LfL/9Y3d8f6CIG'r߾XFW,ÇEm2 IexjN6$_KtG2˶;Dy_ZM#⥣r,-ѭp}FnfqҎ@mZjKZ>\L0޸oP%vZR"ԁlkYni7c-rDG)5OTq8X.;$ZL~n1֗"tL֗22랊e*TGc<ŃDnIs.DHǻ#R,X7d6*OnljzA vWbuDʶ,]f2T^m5[yTWKrbXYPV":>n:E:"i1۟5Gp`ǨK;v1q5n?x8֬hd/NN%& δUn8IT@ 07H#s gV {⡑P )T\T (T%<=_N?S}} rzt;dzbLH UKKFtV$/>OOMlk!;;ږ1\Iac~{[2pX|K>w3nz-bn(`WF[d}9R, âd(]N:Y\ rw&~u h$-czc2sXTƵIT(v13~~vzobqd58jf~df;#ˬlmSM[240P;vϼ_Vx2^9<77*H\rl`rN8ww)gcV,ݐɸJDTRK@)EØkC[O%8XuތXt˶;jݞzr*-)l^8vqD2VJRX`%y q LYUG^.** X%?.dA3mXw+w&j rCYҤ6noo5nüGK-5}V[-N$[/u}#L }& Ř{">yCtN{0 ln(]KwOv3|66F,h2m~KJRV,c3&~3yӹ&1(2h݊i R\CF4Yc,H¡4%\CP}R%dTR1Pr-,=EF,9gxYtAA7ù8#ټ) @̩ɤ+:'88YItpyd[O0o}?ڋtz5v&WtkD/ );͜Z3DtYF6%A)62lrh1= 2X*[312TBuXwxT'Xy3K2\~AczDމ<_zU\httj!se@a~ 9Wws[,Nz&УX@}_n7ESA,L G$Zj ͜8G.@6p7l" dTXeaߖ˛HtKnlw~ćխhF\wIxK!}9f-'-#F'\˯Xؾf LX>'%t+grQr P*0B9n$Zy0GFP\f.FQ%Ln I=CE$-\h@J=":Y[#FF:>[.Pl9pwN|=)o+X4 ar vﮩ|ݱ,4ydC $$ۮroL [10v~rs"x"VE_P(ȞZ´d9ɨ% Hwo#WO7FX}O_%~]:sirXXO-$ǃi$Np\2Zc)[~s&yp'#ªZTR173]L -RԔ38oڇ*ƌ/y6 jmcFMQ QiW!E15r WCޛX C(hjS}{%o?p'PGB!NGu݁xld4ͬ)22Cy:S9w.?Ds[nkbvg*8 jF8Y"9Da?ӼTXߺ?ŝܞ˴E%$g]:P':ȓ52S™gfeUs1K) h䦎|e0~ E$ æTTOXɗ.5  ګbۚmH: f~FtQs-ܥH Yٯ%JVe,a֑u̜vOlH5~"&=FE"m9ʓ%ʮ6䶀{D,؛J˅+Ct;yd^sQe4aUv(ަZ,\•~ZZ%p(d:I+vmR͆1y3ki#㦪g;HJa5՗:5xAP )MRɥtΎOoXlf )v ɸnj'C22o pxukeɲaes>g{ꈿW|eIcۯPԣxim[5mH^3V c{{APLĮp6fMh"l~+v?n>"vfu+v4Cy!T+Z?*n@ύB7w il%put WnnT,;.c"/}%!Jq^:iӸH wߖDCpݰNTZ1 4?ޜKd#]'Sw20A%>,vħ:⯋e N%nd~[+Oj&i ~fYs!ߔI2~_`봱hF-*CFѣW *BT@6]-/}_ڻ i<t+0i܃Z/~k($/e*6Jn@ͷ%m}[6k/7T\ڈpo@ \"g"2%Ag@p ,@ Qҷݤ9uHisݹ.^@pq D@ .jԜN8%dcg!\^%@ La#wkAK!@pq#@ 4zχ58jeml}—ss…@ .2.5]{\!@  @ @ A3#D@ @-@ 43Bt @ ͌}Eᤸh2ùh"B_1LSυ[<`#?3bdܽp+亴GS]\3 ?c-LŚE^?sOӒCZZ:9EV0z"$ov r8պf-deu]w^T[>gRϐ]`7_ZЊScEp ^ɼUm3셤g!0yP$@xL+}# rC-Lhrƛ`kLjBp lMۦ5&Af~"ض|ĤWb9OU#$p- 7_s+\lضWXV䏿z^hWeECpm;9R޻9[2PMH?L͙OլٓL"&&wǞMk;>"gM΋ҳp&y"Ykۍ&]MPUVdhyQ8skv%k"`hH=ڴ.ś]0{4Uvm`ä+hLt4bDuyeg㫨ƏfnMr;4JB ( <`}ֲv,+6:H $@(I{H!i7yz3g;;gNѧG?BǏqcش sM_T)GoyL$,2@lXe~3L> Fiv;+umh4gwRIQwp qYJQN. qBTsE,UYl#YUVtU8loEv'eDM6J7#ouWKzv  G/#0STX]r%4Q6\F/`-vمĆxaTۨ->¡cϭKX>7 t8l*h775SUty Krg[8>PiYhƀ߸ćJ '8t}}/f?9%:c"iw| J9c+*{ɱ\?aoxxd"8$C_յro&YvrǍL}%EGbƬr ,${Tke6$Caݫ{\{zd"Y0t*?"N%#hKcGa+>Y.f_R&*P:G&3*gq9;ӌYqIɓ=.a<;?㳌* ת2^S3^of9Ḵ!{{iZb {ƢJg0$V3;jZjkxE|Z~#xs̜13ǃ6UBޓ-njj>d$͝d'r+1Lõ9'JRfjJё\10n/uuQi(+=sϟˤ%m_dHzܺ>d,,P^e(bnzNuWRlVFܣpB+ NbF[:2 ]s%evܵn㦐}O@hz핔Uҳ\?3h=qk<3't;-9.J`*HHeF&2vrNJ7 -p"wx{׺[Ck_+cŒ)'=M%)nDK%)i ƅyb+;ȶ=Ǩw t}TZ<̱:Z=^Ac49`IOԐw8ܲZ,2?cb3 ^˾βרm XM1$7 {}1ʚPp! *VP oܼ<︃$d{KxEddc|TTT=67^{y2CAEmπp&%z\Ka!vP6rj` #e`/*y0哹?l/Hq`db+vk@$޽0T(?^@U-0p/-JC GK50/zϡZ,ZAQɬF)MU.F%2uz_c@-޲u''1%%wyoٺ-9I=b,~+8MIjc sy-`!:X%$ܣqH|tmYn?$]Ҡ8. T Nπ۝FfU&8t ˆ\d,$# D7Loî39>(u.h%=;9w(-,ł{xv>1Zrr[)*B mG9x-8{8jOaiǟ̴LZ35y1k?ؙmanJ[+SOX3ujn{d֤X{C-*reDK[ (u}9֏hQ8uJȮaw}<|#Τ6 <ύks@Pe:#ײ|;;xѱ|䮧5 S="eG$OwgyI}.'2S0uj{ o&NmsHej)aêTXN(H^v'<0={m8Cw.2aWO?r<Rll]4xt&^ļ.B׆aVVol9l|#+_%SW`Xv?m>a%o>͚G^ໂSy4x?v+s䁗([Fy98tv,ɸ\CHAEs__Ps~=õ!P=q)/#p&=ZOD&饝Wh /IRs""=V4yԎ]Io/:هG|\d ^./mԴ {鲞7]7Txxss8S2>>:|}=shL(!jz*c4] "ʨGю'ybUQh!}w..vɠBm4fJI`i׿'ck<26\,c,NS%oZּJq:J Ù{e\01/}}6+svա@m'ae6+78=Y<)/~~N o<$_.||Ž ߠ"#"ڦeְ{y TS p$E#y izKNJדOx<5E_y|{yR ,[QPOgm<|g^MA{6gN̗bl9K}Un& Y8*%ң8ⳐKeL]N>dG(ggQlV?4Z1Fu_´PG;<A[d5jG H&Bѵ h&3=ض.ykK}>RxOxk*H:ʣVLLHDk1U )PrPw?E67+?nO"518S?4 psQD PhV`GV{O%Gz#a?JMD S$'ɢ8=%rv)ŽiiZ4Ru!H2-QfmW=<$zhʬrr`*).u<ʩ8^3F?û59o4o5U-Wk\'&KȀ={Mןq*ӃXqΎDy|oXw/7@Vv6/Z8rHF,SQ4xYJr=y\MHaF^eӸ-NK`.nUǛs;9w8?yRlny\i:sc\}_]Nꊰsk-Ea_f^XWqlp>Zxna3ˉeO nt{f̻/.seҰ=>ȳza.L36i{/NO-9bw1RI):= 9|{!W qA#pL_1ٚU׿V;kI,vZbA|Nף]qӫՕsc66iRf0 uԠ}F\s6Z] SL~J5֯˯b{?pѝiӂ޳\?Т룦Nea3+Y{/'?t 2))Dj@36o=H S^r8nԫnrD^3bYmmg('SZhh":?2ijrZ6}7uάsRKKed&$WRRGxqO^็vj;g,vJz;8g!m{peja͔*etOcb6m>F`igM5u)=}%m2W^˖^t? SAbawﶃJ63Sw'\ ZPIە2lYٻ=fUubvXGr{ aΊ!BCyUCLEFnm1(rJ>ɞ?J21;d36`:WLnT vkmfj*n trfIAtfM S'WwMH/І.eWQa+Z‚G\T\ێ$y1~B'mTs3՚ 49[esvCOpDkU\Ff$FѺ]ǰL@?]lvmE^H@tZTd)NiLIt;Tp0  ٖCHSIΡcX/ 轉>Lf!<_7?wlMv8"bMw muz<1v :5% r%.6{Kֱ?'iNo{PeSLODΩh. j|IjppN5 b9LĂ[݂^ѵf6r͑_r{|UĦx9^=3(I@F>PA]~udɲToCECXL&.q n"'"^2J>,wz19HySikh &X2 9@N)K"%_A֒uſ8( eEgz(f^9XCeL^TRP܊ !lLG\F~Vb|E~v#0+Hٰ6RU|cy1뜥29I;>. l˽tFה$lbk~v4s4g! SIy24uide0;VUƔ2I $/J5Ui'>zu|PQ1WUҠ>CIOK¯Ïj6={hbL\"SR2s,fx;Ι!Mޡe: ܭ^^8}"$:˯5ޒu k#Tz52WSYZ}jjjUtTj*:9x}qUP 6l>M㼋R"DF~n'RL=Hhnnv]ss3z1qx(5GȮhq9SЅ3v)ΑFClN;pH:og-`z!>tnePG{ T^NSǙQԨ"\eoTbw˼eK9䙋9DlfcVө X.ɟ3$#F0Tfs.L${ tHJ={ҎḃL9Ssb*p؎E2'o0: ٲT-9. sj99D`WYD!a_Z5Gs>ֲj`l!RKQI3*CoCr'$2ZT?}+2'CRi*.F ۜ\Ԗ}*mOeTF@ʸ 6Q| 8*8?$4YillE,M M(cǘpF>L6 *4G!nd|Tɤid ; T(HN5:&wh5=/餥m﷓}{OkLg9K{zo?:I۲QjǛ5<{>|?~~~u5k>iwR2b`cn5z5m'Cjs1}^ (t-o7u G7m&_`r3wLv瀥#ٲT:ꇭf *=+ նZ矮2kLOzFf32ؿ?9M  x4*)m+Vp$!r]za&ضs|nf=ÀjbB7tf +K.zև^.2Ҿ}vLQsi}J=f;Ȯ-}n'/!o$nHTW:t6S^ވo?+驌Հ֕`f S C 5^¾ *'.δ=]&-6JKʇylxӓ >QVgТ6^-8h&\̟rRw‡^3of٥Ï>A;8٠wHHQZRqi ]`pMbϫh;euflsfctj;7z Pt ,^=WmTUV ߁Ϝ-K?/SYRZD~a jW?^b;rFٓ1Pjs2s)h踑-5rm IDATp^+55#mhMJR+zǯ=*P0\ZK)(~ ao;ri]n%l^8~Wg?{r*{)6*+Ec2ed&b.8J{Zw# ~DE:n&r4fhP9KOq;O2v?~OICq>8U0nFj# M4("SBU.5s6=s5vqSFI7 3mt.jd#e'^gwo4S&N~٤Z6W-;YH4 e;:-٤eJ:pyهĐ;#A;G޼,`oR3CG&Ey&b,j&3S)PV# 21c/ggR$& Y#S(ؓNhL&/Zĭ+o;֕7sѢ2nظ4?Vɓظ1# N296-*MGcK>[@2>CX+j,}ݢ(?nb_&o灡W4Ηh7[r%9';V%6- 7c~SZA2u혉Lנ㖣wHX*HߒA]%*N~2LGVWdI&Oa0i IFh=%ej˜9m0^e"^W Oxt[œ%O={nV_[\Sj6vvk|5Y&yqݿQ:l}h@Ș .|''YޤϨY0 0(aW+̱Or}:KC_h;kfw}^q^DgE]{mvL9e`0\.aBjͪSֳadqE I9(gg@9U,?vXᕏrQFC1".n"7C-9|ie'g[k{dSoeJGv<7 03 SM = 9[BM=ѝ4MGrפWw c Q^^]TL*2ΕKrhPk ;>y},{WEOFJ8w2 =SR_TBKc# nn#ƗK{.Q“_J܃IHI!ì{'EsEI{pM#1ĭc FQdOB>r\T;VKGFG/pdr#\9_KiH u_ΆrB>2,saǶ4s ـofN!BeK++Ip#]4ᵜHOrcm97x\ :ӓ]e$uLa:bi ^MXFnOqvէx3Yn%wwHĿxLF7z?k~oٵ]:IofVL7H7+g_Qpb;0Dpïg>_y#iN ALz d|!vJy _ ?]ޏ_ƃ<}>ɲD?]v΋ۣ mN W#ɡ[\8+3s7 :[bpˤR\_OcF<6YGe&,1axnۛ+((kWo0;Ul4Q\݌Mhx0^CXun|une\zZC⍷na,%Ц5NK9;Wֆkipܜ{?~trF}pj7/?ܼZȈwZdԒ1`ٖ} 8I'X37pbg{i1E454nDE;>=(Ao +U\,S;*c3u/gR1qۇ]  0Qiz?ʤUå. HDAAHŻ۶̪g)~vD-  Z{{v %W^$fsʺ])`3xLWo.YwbG{[A8)BuU9Q1ÞvN7pk\MTW[A^ZhνN sx 58 Ɠke`jƇnmYddQe^yIπo&w޺#oRk@CudϦ$}N56p\扭 %'%CAEmπp&%9v#ōz""qNs4je38VFbkL,)1uX'8x&Ս$c:m|vC~"ܵՕ6|`;=}Ōlh?񇳶[7Ь6Ԁi舠[A +yC\m>:zƍۈ ]$${X}NoɔuTXx࡛tVs#&Ś<~ߛjQ/#r^ZR†UO3OO~$/oKXoXWAGJk[%ǟ}5K+]O=•ѧ?}JzN6 ϸ?65opϣ0TU)QFloZ[ #2M`V݂ H; ElޒqwdC5?lL0 sץ(X|13.y>3ϼr7;I`i׿'ck<2ְ{y TS p$E#y iz5:M;lff篧t?!;vg>%_6ÒŪ\+\%iyfС׊ZnwDHD԰rwDn3ikm:"ANԉ|XL45U4c욹|&[μ3oF )#5pr}^![y;3wN<ƕQ7央kLnN)ȺxaM$*$Q4xYJr=َ>nB 3bp}:.m6A{|r;?9Q'kSĊvv/'2XT근ˇyp<ЎǭS7+n [jU$1 Ыӽm]\Qj wmFb n|t zVҞE/sӰ2#0&jjT밃6 mZ.8BiQhBe`(Go}`v%&ug}t&ƌd͜ɬIarp& :k)=6+A? V (0LrYط~3>y+&v*r9Ǭ啜b6m>ʸyJA,.w;`2s%% @{wEp%&nsuUQNtzJȨvA ¨bmmLFpޮWۧ­&|<3=s\2uąe_g:oK#mʚ(Eq.~5 ,v%,I3W*8SXzLXp+[˦LIt;=VʩGA&tx#3>9ң4#6A20)Aq;.0w7W;aFECT8zLDh/C BgmUmh#VӭjQinIP4F݂ #l?>ͥZBC}c ͭ}DKb\ZPOL8mfg-:jJ Ȯ!L[rӖ\Jy8y4Φb?7=85O PV-^>4G2=RWSّ__/04 SRSMF&Qܱc'C چrrP!I€٬VzC SڲyI^\‰^݅ڷZXWjzz'bxF~{Vm6#H`x:h 2~CbiC۱: ܵI €)٘eIObrB1F\+ڋٳ @2bPikmcX*p%#Ǝͭ^^[Ph0:Jz ?I;K3{B~:wH,kikii2ۚ[hms8zt 6gV=gs7Xˆ>fc~tr!Niq4TmHHQZRnX6J+Q?Բ7~^2P]ZG݂ BlMW[^NF|ox~p &.6Aº\uR>>70b_e[;KgwkwEL4J؏K_5vk g)FC%L4J>J8ṟOr}:KLCV1ț{4QeЄGIG#k%Օht:㈼XH݉ `%囸_]nQv X:?Rw:FZ&^Wu32uO1'{LюUܳ=$£)$nӥ_[}7W2\}Ǎ;kKy^=s'7_1jMڝhm\3zXYWs/ZIZƽ ?-L$z=&Oops@ӍH-wsS^O݂ V_AYO$Yśq$NͅK'u(nݝŏy||Y)겈$˼?W]Doۢ[kv4 %W\xcգTO'x#]%k]_7̊) )xh¹g0u-_-ΊWrM z#?];6T_Cyi1A!Ò~yi *9ٴd8֖f4o?(H~7EEjIX-}.Aʌ9N [IHL9ӻ!tZi,d0g|XVDqu367xz3TVHaeوϘ0B+vD܂Mб-?p?ōo#1spj*x;e%ET1>i .]KK3Y1y,t[-8NwLg-jAS@81&=()-(5͑R_@q+Sc֞r$6D[i~,#~b2Fsh^>.^mi#8,jwwaZA3~#'xcOc~B1MC*,,c0˗t_zf`B5A֖ٗf tDM  _no 䡛Ȟ}S"4}%mH^v˹#2mY-2nnnj#OXdt3J6 OR[UHiTm1iinD5DDatAAو7xm9T IM+a 慊BΠX`2h4Z=mM #^Zm47S[]A]M5^>h]Lt:FUUE۱+vf sH~A8iqf 00?xi+l4WUPh ? 7 IDATܩTo5vuuEhtAktH~QlV+jo[jm663 WWWVZ$FFPAA$-n~c;!tmjݝVڰZ(r2F.#1.pA  pFu̪"I,j1L@{.6۝D-  ]j8l\f"AAFul A  0jAvwbrAAAa"AA&nAAAa"AA&nAAAa"AA&nAAAa"AA&&AAF6*j[$;>^.hV_CY@:O|S[AA9mb +*wP-棬h N3jVmu]Dv "AAF*뷓Uee-;k{IH}tUQ`Co;#C#nAAa)lǨW;<ױp M@RiʶmyhorJS6_pDGJAAa4>.%A ҘeWm8ު'llM?ukv"AAG<|&kY4pp.ͮ;g6\IEN~kEœg,A  0d܆eXSwjBD6S~V-5z˳,Fs۟@guosm<6qKnoUc❇r)>ψv2JTx9ctBO'Lo&?oH&_+t).I\;bTU4UE)Sy cёWl{>ފ5ZP"U_r*HPL~z9v9uyZyD%zģLuLZɃ_O;|5U\X V8w#3l K b !pp_rop}Z2ggE][-Sv Єt< sQ@$9še_yg|K.rkU·~1ŏޓu0d?*?t{}:O}Nq+3w>\?%usD5 P-:BXi)[C@ɼҵ6YfzJ^yp?~6uzvGy}۾2Nn\.$\/]&q{6~n$$hZϪç+wLğ-\9IAnnq ȵhu.qS6PL^xz AB4V]lmCWteb~wq b$?I_tkbԚm?8K^:YKDK+ӭ(.C+?5 &\SHdSI߻?gRQ3[HZVśtҁ9귑k]Q{M"1t])Y~>zq+;uϴ$&%ֱAܶGLJoֺmr]KJF<>_v/ t[t5eh=)b)~7p_:t[!-'N((j7<dӬs@+5Zbz#<6AcB]囩ܜqKp]=]sO/uMTx;u0Igܨ`˧{o͔Nhe\l(,oT{B;=M^ 52)E.waPܜ-[8c&UtNX^0ӾݺtG2;tmObWxcVg-*^a>k>?>ů׸T}^Y{<,~dGy-w h̾C&=NX0_<&b U{Q&`kSy| N}q*u]Qo;!gc^ځ4Z/#uDn!hic9,;tG[=>?gniȻn`LYX_E?A ޾ %b4aT@Ϭb"&=ї^&og:e:^OK@yd[0覫9 o'y{X\oO{Pac|"ӜsaQrX+~яe ^yWv!.KpFU1T6+LI[C<\4񿖱 #զŠqbE})ȩ3o|3Y<0 ݯ-9]D.G.˯q <,3OPMmq{¤0?~]>,@KU:gUM;u!mirj~Wg7Һ$p=.g͌g^qC˸0[Z"y?=WՍ")[!+C+e?=?[V2x0Iώq9?s:Wlb:|KRTu`! zN7&goDq#?w%R2ZY*25# 7?njQjz> q!3 *F06:q`:_<˫?xhg^\zMC*b\+3lb^}4rq,Mĉ7j%z6s1tQi鬻vAmF˘ӟll[?R -׸iߩ'}zq?s ܾ6Ry1ڱ%*ޜ1MmQV3Plv7_ǩ,/ˠNLǷ߬j=+׷|w^\]r%®>|/>\BHmiGGd+*!QR |l *9]yhc^5<rWp >s[7)ϕmDd]Ɩ tN'o\$ u`4 ؼ47.+ kܕC"P|,[G^ɝ ŧ' hl˘0`8\iGx,tH:6+-WEPLŻ-]ڹ)菿6+`5N0l9ʎ/^xq7Z"U>z:l1s躥ˆS ;رt5#XUDTbGݬ'gJBTesUӒ9EdOsqh >pBɤlܸ4ܓ)\Uh)ړJL玄; j4; y~ro@,V6R:hѫshעnZ5mQqN =_OVt Ķmq|wq)[+pa"2:dNᠣ5 J!Ҝ / / 4+vmv6n\ˊuIKO6|BevhOs3$::hPn?W t8e͏1a3L߲=OoѬ}Wz~ӯczbf,Z w45A#/=\o:c;Э(sb80]9u rtPsk* {Q[]gh F ANJE{%='?H \%8$4lrut=.MVvQ pD #ç+7SوI-Mj&M/"bշ],CsQӹtvrpTpu@]QN /ǜߗbAv]53] 1 OOd5 NKXF ^ W?L_H[;EpXC. /fl>Fn/W˛K0I&( ф/ؖ-w;/Gq.dLEatut&렽*F/uհJp^hcɱuq9;6GI]LϚ\NI_D5I-M!=nOgQ3 D;+BDyrLoڵ+cJv]kfcET^LǙl۰U9ø$E:noT%\k?ԇKqX3z:Tz ^^E ;JU iݛkE1c.m6tܕױMs͜g7T{ Yl6l|XGٻz.ni8BO[vgуlTs70gѱCj`Fu ,YnŸ+ qѮFsI[Lz#בeL3"X??T\|%HG'I)')h("H8+A?+EI#8!40rj0aA*"=MZ1+]F~N _躎hv^k$BFɖW>wVznsEȢ?J0ݺa@#ij@Į5l>W ǖʃXݼte(hded(%V g`eߛܲ'uL #Cgo_KiKYn Kkň{GDJã*VN [fHٰN}7k+&vp{]];IguJ͆mnZvrPiѶUѱt=-F6wYjYX(FEPG_ ].Iٱ jBV-]ȆUسc3I\SxS"AB4J_wq`ONl)k>V騡_̓5^סy}^ ʖ= /=/ZybCUeg~VjlΨU%߂xۀ Pu[ּ3|~?Ze9ݮ=@Wo݂d:+#kWLDT f+: zDDTsSؼvYMn[rN:ï/ت<[@BH|T\٤=@ €RSsQy侵L~u%>~[6\ 5H?K6rDSKƭԨQL9{N D`(<KX}ĉ)Jn^J@ U֦0ϒA$^=RU>\ϟ-ENtsF Cg*.3eHI>僱*בnޝĖhjwyl*]F3K4~LoXoP`&3jzkg%I{37CT֓ivkUj Mro䶮+y{af=~ Gs3]d=}`ض9qBGw9S;=:y25cyr8~tq! /VdHcuEf<I{K-qc}Z]QT,E"Z ʷv{&a>|kckTWJds0qh?E`zjב0Eitwߣ$Ζn!h*~-h T|Bbi[ +-q|#o]5/:^WpY+ϳ\Ո2j$w*]+FU_ZlRjJR6Y Jz34M@u맓;~Dm6[/] i%N'wѹDžgM&:u͎vA Fc K)BoPm巎 VWUz6C u'|X7 Qlb°IÓRJF32ZiŃp>df5+e[!-m LrV,AË6abO\W|ŀwc<@Smk|{ExhpP@JdXM ZvED5%=%$B!j@μogqХDsM-oBZ33a Qz8k%B!i%xc7TIn!Bq֔b-, +#Ԍ&54 B!Y( _ ѱ-ٳc f#q6Ŷkii%%wB!9dDZUUC `u8t֏`ǖur*n!B!AaXٴf9'3OT}23Mkc ("UUu 2{B!8JdUU DQf {wll!"*P>جdeZHTLK| 8pK-B!D%&0摞C۬mLHd F(7H-B!HI( fo@v\.P///f||gM! B!D*j*(%˔oJ7H-B!XI:Tn6%t !BFtP]6nvit !BFeA#X (ὺu!B:vj;$y9ٴ ]ױ;`5t'? w &qff ~oUxK-BQt]'+#q8nt( QHOI?#2eB!DuMӰ`X$>躎[a>UJ-BQJVi.ZL3''EQp xKzB!D}Ҵc|s!AB!D=^"BQ$Qs-#B!Jʞˉ`F6z!B:v4t#DTqcgbUDƵ TWuӖh6>΍$ڕ!Bz7vr/<;U.< ^&* ҵ FhDUo;5Hb!BsUu+j(V#dTTUTx 7= 462wBqңw߶i]=D1^Uէ ]rZ)t6t%ʓ[!Q k !Oz^2I`ML JʮlڟLf&(-=!Lޔ 5 m!2 pEѩk{Y*n e0)znyLӞ!CgI-牒ZF8*K@?㷊wWھKx]&-Y|%"p V#oL~a ފb <~fgo`ch률Zbzc<~e;,fL GxD:G3w]<^| v訑Wr`{9*[!l !jC1ycR KEt is>)kxﵹ\8g셪3{ >1<4mUd?ećouIt%1ũ|!SLy  vĿ`[X޳AՒ (=xxVa;p0#G"Ȓ@YZDn!8OH SW5/JѴw Y~f} MסkO.cCsH޷5UE7rgK{BA֛A|cW3]},yhsLMiG瞽sSY 7pw7bcKX.: ݉V_HH-BQ2Ղo%W#]lm~F:ʩ%w&e^ˉҮ+lK#9ͅ>5a7!iqZ᲎x}k6ѷ+F@i5>NVf1 ˥Сw'|~Jj:a%1{R*FQ@-ik(jP) B!V %?/ݞέp3/l6lbJg{|9=mCPrqN] S@#y1:hpt ӉSӰ^x+Phcc{zv4Qͧ B!S-?zL&En=.V\@& D%-/j( 7Rꠃc}:?,( Ϝl=ʿ^uBqzxwݜ w!* غ ԈcTҬ:ʮEf 4Lak7alq<&̳0+Bm5t5p:6?m ?g_:GuY][!>5ۉwygf~m--~_QmБrǍ#hkbwY_u/_<Nzbќ/K6s\CTϫ7JMO\Û:ЮlEԆvtG޺҅wmHϾFڸ/:tB4]4 iծCי>JAV1yyc289||hb"|Q]v6;em>ZPt;//&UYY}k\" ハ'9t,џlج2Q1z`rfqp 7 ՊSS0xbrpMPGɂ 4y5s q:c0PU-8[FB!T~^NvV9qX8PU躆=?Bs +[[^6=Y]ל s@L| :Gɶ5|VDk6 N]ly>F'; sY]_t !BԣbȭnP]m:r(6Cpu;w#AB!D}c ~`l$d@!B!Ꙍt !B1w7՝L=&7gu9B!p\y\@td`M..4f[!(B@pY'д1O84M# C8U[!)X|'qV2q ]5!IvV&;bWCqܩqNոB!盒;4,/'R8zx?B A5'&5 AA#X .P$'ݟ)Ԉ\Iriױcwn: q) ~AL͘ߪ] Iw? !DSQ t:$Tt( QHOI?NG?F='!h 5eh"8o躎[a~=W8_GBʠ B qQW6'q$MIҴzWҟyFl!tG-8OIB!BM_$D Bjr"džn!8דxKҨIFc+猚 o1$w,} oFvoiz-l|zc<Į=9v|d! ,m&\1ɾky1'=}d:%1A8چ;!9/r]Y;L}3 rf"\u751vuʙN+8 ktt ,hψzy>uaBW EQTf;hqcCœ\_q+jJmCBrſd] ]1Ѿ}Y5Y~I[~m' *A毿fsz { rWUYw?m:>{rt&Ь;=s3arxc^E4cY3yN0dlX%#MmHBpױyLg}O?ȸ\((/k ~>~:MN4M/e~*^i~<YeM4j8_Y>Lˇo,eE=PSiB4s7CPKc޻3XFwdHgWeb72σdwno(ZUN{SнC Żܶr9?ݛ8ŠILE7вefE'?G3v:щr ]Cb3Wm7w@C^^v!){Qt@dJ3۶%r$=M`x,:'Fa>:DRf8:umO3KeGI$&%618Sw}dgȐY3q||墡Gg-q!\=™my$B?-;qAh*k5i5j[NBԞ9Mcu`>7䣫A <6b ݋C^qp˫'Dפ5˄VE} &O[u?c"v|G->{ *1^˻;T~Bt.W9qU}fT]Nvg<ŞĒOHy>P_>' $ƫ*KL+гko0s2 %w>WRI9֙;u ήSlb/>; t+i9=AMcdO}66ŏA#`62j` ~>΂n)EU>`1=ST^cNfH5nGZ;B4sOƑ5k95|Ar>Xe9f5;=aE-+yZZW\F\ O]/1t} T=9vfsYts?ĤlX ݽŲ?q]72G,ApxBf~M?M|p_ҒT>ܐ)?w< Ze;A?0-,zY#Sx+c?8Ѝ#[dI .8ٶ.`j@.:td=*i=̜6}#~5}p%-xpbO뒿:ŚGw5ʜk'h輪y}pծ3z֡e: JNޥ*0:">\!B."}Lc e=ѱU_l[gޟihxn~MhB4tvO;onhG~¬.vnH}tY OQ+ܳ7q۹n*KZ=֮[¢wGG]8N:Nc߿.#]Sv ݽ.a޲ ^aJĎ1kU\h:(=z3/wL}s S4^q[q~'N!9^RNINd9Q K~Urer"Ӆi(UV/;ZI-uuz ePQGx)}7vga=, ɏֵ;o eKٲd9ɚWNp~ī\}+3];  j}6lbKΥСw'|~JjYa<-+: (e|N]$f$33`Lfͪ-}?C3dv0rtGfsx8Zi@ aͣvY9r@1\qofD}`-Gw+w;!hZq&Mj8cӁ;b?潧igGI1yỸ_]:TKeOb6*&FH}ɩjˏ-ةVrر )нVi;؞MO^z Mf͢ؼ~ _(nsWZ460%\ȸ3ߍz طW(zUcЃh6֩X~'k%[>X| kt )ŌŬY7]|5DsX)ty =܎A;[le,_}1Sz,Ndh`U AZV'5.ce_uvJ"#džj+9PsכZ?))g}yt:֭ܽ➻&2L|>c*^}-܏oLt:gl??,NbCo|ܬF ~QIE!WPW}R]]-\wB퍔yVhI'SYrmIH@1E<k6\ܢCGsSy}I~F] .&i f;8ٛxi| a$@0|0()+jnuTb45{|W| kGv{vl%?/Nma8>%O>H)ذGiVٓʳ|!qݴ/ j*nSWqxVH;Bxtd;5ЦsLžuٗri&cv u0wiLJ}.Z4+j:iNp^q\?IsӏqK vPo| ZmhXEӽ)f3ŸL IDAT֯b>j( 7f݈X .z˕1aP3ك+Wr~SN]ٰf.ѣF1oGbdi  2S)`{UyzYĩܴSkٰc(f6q[M~'N!NHnG }se5lǟ36Qe)^t6Xh]˅F9ruN]lݵCGH L0u ms&42@\i.RP0GFQ`k7alt2'_yZN!33J &##%PFs_pw t;eA,X‘KǗ2'T!̴h(BUέ}v}gg#ɪYIGy}Mw<㞼 ̧36#ZznC/UޙفfPqS  P<-an^8=~~nӗ>fE6XPzX~a8(kc/Y!!*aDG=[vRXzYg+?xr TAdɱ\\:JN `…nyݯöyťxy0f4h`3e,Gv~]#k;D1dZEOANQz;^NC& ^`Ɔc,d*˿ &]k`rzx{gc !]iLlQ{k .ISGޟ& v-g M;3)]S2DemJ2?"y="!n]ɓtV7G+93=ѭӾE8f͚KѽܛJ ԠnL6>>j ݽ)oo7ɔM#)_g&7-cڣzv_HHe*63i 83 Y0,/sqJֹo:jS;oo>/Kֹu6f8ovtWΒ ŧ L]?˻kb!6uqk_;vJZ@Xv& ѭ҇ [;uj+4^uR_7P@bZ**k i1iu[TxI!$}݋$`Hc'LNw,< j-|nr۹wټeA]©\M(DZk;NDd<햁Y=Y{^_~@!v|í 9.:d5 W~!m<^kofcD'yops\VU~ҟy߱ohl,m["S cbYyj%+7b7ؽ,;µdՏ5 &?r>ֳ'Nf_k_9Zke<釖fu5^&8XSv'VG_77a\\75|;c! 9H95\7:$O(ñ nI(&I|Eq];y*PpGZfN8D0s8':MzX*'M.vI%!?h6mzOn\;BC4jxn"-%tL S4M$H@;'0R= \ F|xw_tpӷmn8;O`OβnK}NΜ餡;︃%VCiDOO++ql]TR #X:zxkNIL7e,BnyD++O""n^`.ʢ;V]]yɥ,,gt~Ob9 Gݲؖ1fܶ_s,x? xw'}=Xmmt:&p1ވrX)ݸNE̢(twgUDg70sٲg_ tsn5Ćb.[1̈esMo+\iGq^cKāU3WyJc1ܕ}DDCkKدOijhls+Os7J)%'  Lجei6v젗 j>ǭzSDDDDgDEwqLu3=v_oRW |dT$-eADIV3ݶaXR9 a/IXIDeݑ2Is8ʓ k)wj'QQDDDJڂnS-""""R`u_W3)qV`Tk[DDDDfG3[=ʪ_DDDDru &|IENDB`Solaar-1.1.20/docs/screenshots/Solaar-main-window-multiple.png000066400000000000000000004164521522022367600243340ustar00rootroot00000000000000PNG  IHDR|MsBIT|d IDATxy\u}{Ιɞ M eQ*.պUWQ[mVh\ =̜q=3wޛ&|G3s~N]Mz˧lB!B!B<}q}j}.8TuB!B!,HrLV{2-{^B!B!B<#̼;pMAOo@?=&C!B!B}6T]u&B!B!Bz˧tąs"B!B!8zde§.dߩ=\OcMB!B!sEaP?47iB!B!xHX{ϯ ="B!B!CAaTv(>5B!B!Ϻ]ƚ!B!B {筞lr-NPkE5p wB!B!80GrfyGDgűZ* e׶-Gp𠷣V69avnx1[y_hc`M´y׎u&.7[J`o#lP҂\B!B!}& R͂Ьq_cXӬƲ5 -cZS֌6kSe/%rcoꬴC8W;?GIP =hVl=׫&A)@P)m$CA! ׼o?j LE=4CØj7jƢG1]iP.!-^\cZ2T!B!UƴSk}7U٠.[Yuhu}9"@X5O_[sPѤiۿveSS |uGXEltߙ(ˬ1@R1ty&m?r6 ՆD NhRGc  ;lB!B!АIRG-&;V9YןcKh#i&kG]37AӬ0`yC;lb\$n\7Y&>{1v43y8ªQ&-6o'%_Yzt޼2N%\ -mzN+X'W7_B!%43:lL{ӺB!2|Ȧi ձ͎tFʂ&ێ4!ju';-SA4a;>ʆ\q{z~شmM9I;MxJ/h"ꮶJy݉訂2S??O\Yrg2 >E(,:NxnҩǍP&S v{B!āqE3ݣQ00RbÇWjox>RS1:=C4B[E^Y*”Q9ZXvOՅ1׍MVX!NekƸvdʊJrCپ[6Au;&n][dk{6K] hCZ~ӏKḡ=Tbak-;Ƿ訂ub7xVoz*X]we ^V2_7kёm7^qB!?cq6 M:g-klfa*(b քBQ/b MD!:~AQP;.c[1_Д4Ѭl@4kWC.]ouSiCTC.Piӆ3s^P."ң9G\nK"DؤYnB(:Z!\[Frқ WJoP:glc)њ7/U=æ HzR١wh& !bJt5LT S=yx8qK@gVDHõ)*-?BiX,x ixquFn~̓>P,pl^.8y =h$˘^U#Ow;B\G!qbf(NzUiU[fVLIJi5c%n%G!@ݻ}4M-X03@)F*!r9  G1'diwVV=h@&uA|k!LK } LO7&06Lל=0GI\܆ơ١֪(kYT ! by5TpOpc'Q6ʹ oQ|G팟C!Iç/U{'AYYr~005N1YOc8 cˣM˯]A2熗/ Fb:/T<)iwa0gN>XC[F|u[ctbL7A% Xt&p41b OQf]f5P8TBH9jZG݅V(xNuUfegiS1 l4iҡ ?S~(]Кp]pGkp7IK'X5MPP!b.E=p}\tMP淯d,;y5g|;dM!"IzqX ]05nw\7YXwLy15 =ֲIlMueǘ@jYcP\wPB3 ذ}$ֹ~2HVDNm[5K{F= Q0D&<! NCO.SwN'Ze9]0QD%)"MA?nA5qiq&cCAQBpfӷx$TXA1\1pv?I4m[Т" =>PP!bzu좷tn}~®~ں[jKu8},(BPuWw<I: t[I {XؙX*2[vۿ4A=,UsOs56׿ zyKc粤qGfB!>¶fOtl1Gp 5ʣiոb5'j:y^%\EckgZ.qZ'̔c_%s nhT?$y)3Wpbj=*1Pʑ_IB]+:}I1y܆pM5$jV)C8O@'=УDKuAXO3^Zf%wx5džUGgG9͕X}ڪ##,+zrh`M!8Pqjv]LMTmGa-'(׮1Q;KQ" *Sq*PMl]xj:֎]~ 6.((yaI=4q-x1ۇ+ 3v*GզxAdqx'K=ҏ/(۹( 4/EJ ^Dt`UY(׿.c.eh&UX?9iF#;76qCaٜ}*?,O~>_͏aWVD%\f<޵ e|:P1: Bai3Vϛ?gG^vgyv5Ω7VTL 892)<ǣ`yf K5qH5)t5C0A;J M ̓>QlXS0DhUK)2l1l & ksYVċ4Lҗ.wt!JVQL4PgM[V tS>y[oh͍9 Lc!Wq$CABim~nZ?.sQn/oW׷w{ ^B>R ݏ^Ǖٸn W <\~x+_+%W M:K^poaSw;h[?̻|=WJVsўKV`f.[hBs Pv-!1ZݖzZf|;8Daj+o*=-0 l `2fvjp ȹF9tTTBvT|KeqVlg3 hf/PƠyD+N*5˘}23m2M|ԟgFG>q=**+a+CABfr7o崿9Y/:[s#۸.OśZHlfNçNZgΡۗ֓5S뵦r9r[{忯~J,Y|rYG&g&,G>~3?}BBv((0~QWc8 d[zlc3銣ٕGkm@XTһ,"(-ׯ{L˹69X2(I< L&t(9\J5#+*alv_ Wr ft$aTbBbL{!WhЧ`) q8:d׮p 9HM&hcuny9wxAoD'Ճg'q~yve"aəe 2T!8mOםƻVuRPEy͢ż[]c['C}|M>Ԙ |Au gG5hW9dT"(btLk,o(mڵ=buLHLcLr/[dZvz_zHG/Ae2pvc:k~ͺkdi뎑B!sz>a=ìys8r|N9|B\l^?=_ /Y8Yv MR{j8tE*rآbGdnGvO:Wk\Sq:˲!JB!Zϭ4JӀ pˉC*9>N SV{ B 3Ki=Q5l|?ǚ$mpfj ]af.,>n& 1z@kx9ag`]= [l{frqܪs5|Q CAB6* Wk3Pcż7ʣ)NuWW-aen_,*:-°,K!m[QjӸ]i1åmc--^CwRnh3\-7N{UgsMԎvFmOS "(b"_ӣ#7a?f@Pu׿ IDATAmA>:5-B(0Eɣ6PK{D] _i\E1#6J%6R 3rmp-{NgNoxkGQD{67Nz)t䚸ǚ0Ab$T^ 8$g7VklqǨ(@'+Sc: ZPP!P\EC*FEWW`90JL73qh5EBw'`;F6tr+9|X(=r;vZtOMgYB!8Ī|u~q<ۋyż=+P]&6W3Å8bǐaqT rtuhFM] eՐ$n5:l+}ip4kG12BC;Tu7j%QUDuN :B*JFB6  &A@{Gk9((蘑:owVqj5TCU6n+خy2@fl|ȵL|r j4VЄؐRcP&B!ā\q,+ͥoc_b>Ebέ:1eZO8|JrdeMG'^7Jd^wur`ãnc)z_ N_YB!8 q-kkqkQ]*TKyJ5?&ǕO4R\u~|k-BZ-٤]uAR3R]!(FRcrm\3VMe\;W5e Rwԧoħ2 նV.3& yjom#!JRH߈QT^MދO7oG׮f|ȥTˡZ;v.8Oބqd%^qZAjG'A>+CABS|`;寏XESլ;y񱳒I?:5S[λ c1jmxd+q.i]oo6o?g1o81e !˛dzZ@/օ:iORg`u>m7s"%?dHȖ2jcafh5o^`M4p<@929  9\\P[\qO͏2nk|*arlzO1a:.Vxp@]zD~UCe[{|O?j(GEv {;(뼵cاT!0zܵ[?$1NLa(K2īqf/{M>qu$h]|c֟9U|=jƏ.3;`OXdPn|t%BC: Է ' K'n3qT{ITQDWa>Ic*;}ʣ]5 ew2SǗa]m_m>SBLaX>ǖ lպe%]~cKn*P8ǫLJ/UmKcr.-{V!谄a?W+n6f cQw21f1u' .&.YTwXw^ϫhSKeB1M,^֫qr{=yh=Wܳ۷ yȧY\ٽ=btJ`Nf7y!涫nG'WP^{:W%!n N5k?=x[ΩvexxGq>|cyK+nݩ/4ʝNjVݕW{YB!^45Պ#|co9d0}ݝDuGF}Q5 f0GqLD83t5- eOUBCOz 'a`f,:@:oD2mkT 6d4WTu~&m30 u[v{D*%ߓ5nrHkp77vP[v VT[N`>oxe2[-^&Z&B!4ݟy"q55kJ}|po١'K2ݧpf;+N|>#ãvzsI=[ゟ:-!>~Q|t2ݣ1"ݞBوow߰%3yߢnƫN5^G4eCB1 Ry\3BLT`rN7>Noph4oGkU+ 0Pߨvh$[SWn횙km:uB_n{W9T߷ro7 աQw&M݊ uT}mY y4@6H<{fijf(FԎWƠPxv&jYzխo?B!'nkbKcք߼ojZۑMO\ݸ eJɲ)7/_zMSZ [~3yb3oF=.m o_^Ͽ<2oA9ybV %BC+ M\7<q\۟>w.Dp&9^RN:08reaƘmZPng&[V@M[S yٲ[QR4wu5 X\d5j4^!TQ4hfSU/e =ıcA "oLች0xl<1ڊOmQ%nހYUP>%c]* ԰]'NW2t3juq6X!I̚ò6z.Daޡ6bGe?8ym\*i{?OG2RyE_8 عvμPdi,K!'!Ie'jrᔾnx\8ZsLϱuҰ=vNuۜ=7┇zֵ)B+]hQ֬gI϶f=޲XgZz53+gv{?ԹynBkc"voNG^f7nwFP8:,H-^`Na3?|9ᒀ}bۋ7B0Ryl>_ jR 2BuYA(PP!P}=e>6ogi,3Æeoɏ22CTB!Ъuc`4)# ӒmNCL-nhhn mɆWXfbt-pLV 2K0m, uno,kמʰV,cL" jƆ[6:-핖_23&B[[w^9u i`-Nզ =<۟c'+qZ06Qw!=綻(5SKPmA{#WY?i B!B!DVhbf4蠄ɵZ2^}]|;u?\ʔޏF) 6jwh`Me.$"'Q₪&MP<֔TlZZ 0bNVi*B!B:{;M%T(|`nzHek[jͶM%k<&ZkְٰOuT>OfzMf⡠֠Y${j\T)l[՞Đ)je B!B!Ę4i5YԸ?}=Z6ySoUVRVlVGblTc^!ak8&m{?Y[V!]k.^ =W&{ldeM߷!B!Boz+ B!B!A9]!B!B!$XB!B!b?H&B!B!~`M!B!B B!B!A5!B!B!kB!B!Bw_~V=Ye*!B!8)Xx׵F=YKg?tWA!B!! CAB!B! !B!B$XB!B!b?kB!Dk4̈ȷwО{&|2?H)mM~=} F3T&iG{OyB! 䓏BwqW9n?c[1Xym ɈkUd> Ɵ6~˼}sH!9N5!gx;0qG(E'7퍜d!?p9OC-{43\s׏em2)^3LxWyLJǷ楗X?0U|]o8PX!Bj#4T?`  !B& Ѯqzi-hfm9.޸6e`ݍ)~km:ث8Km,<y{#ЎzcxyU>~fgXKA_bU#z4ndxjK^wCz,)/k5X;SS;}ckv4Js19ڰ߼˧i9.|vCYj艛(xx&o{kLۼUtx:%ƯTy>6 uCdAå?x%|{'f|sn|Qi ]<ey{{'q 3{Coy/;9z*&cI%䜶u71j#:9ů; 'ʭ۫4^>t6z\>?jYgpЍ|]䆒;{^V=]h^]^zq>*ӽ#ź0іyԗ{ꮪY~-ϛ`3}?x|8?p/x7Hi~_@k?u!zk5Tℷ^§߰uORĀ*E$q>G;wZ.y = !gĮ;~}{\qSNcaY }B!aRT?n<ܽ}RXfw2{~?.pww#W s |x q8{Ň`t,[<!8\ 奯^K;1u=|m3yƿ5⛯浇R@NMH'wIq)3s8̙|<8^ g@,xG~!8tox3<AJ;B=O6t-#j)EY~c\?`y*Qz_w[\Ĩ(+Wr^Vn7M{{'?~<\Ṳk?uW?ҢVf;?-i(йz{ Oj䳟OO΍6~mcfH$g sD~KU,fޫa6g/ߖ ՒƃWF>1oOE"ykFpx;fʳ /1VX9l7] g_u?;/䟯R պ9mj./>5@ϵ7=q3wU{)H;gi0-O~#|G0(zY=_q~5lƵPM)BGE7rєr˷w|ؘ6,B1 gmaaEtw52a =Ké(ޅe㔼1o"Wo|2p/~|iP ߸5q{ćlGm̘ok638WX9ڹlk}w9vܢ ;WnLz)͜˿\Gl?ct{l;lNf0}G O8w\vn;l]LL=ȷ1Ya{0m\y.^yh #.} @9;|Y}G S?ӗxзOB\=3.7>'~e>\os:;Ϣ\/_nq Oy -cϽ7gZ{;\Ye__7.~*z%\t] 0wsw%l9,i_r>x[8cqr]?!F/q'Z>)pg!BiA֊86&J?{E`ْ'$"H+bbTD\ pQ`WDKB{$!ge9gfgg).TUEU5~7 ׿ʟwkFAf2 65:pi؝r~fn,#oK6?VT+tղL.6qRTo;MⲹoěüijN9TA2?n]X!v:@u@>j^4vDNcn )6p] ۖ5C*IOF̪٫jIDzzȝI-V7c jڃ(Yeź%%);|ĝ9WA5&854PsE =|+ۘJz dm B^L'97lҳJ}`M v}:e.z*vzo | 6Ի} *761x׭d_уZ˴3z"0 :jCRჽw_ڰU%y>ޒero&{ԚhXw11[@~)m9TLqU-w N U,/XCa?'3tcc1wmߛ {O~G4>@;fA*[7qٖvnKXMy䍑t/OaGMqhsU:΂  5n"ACN'$$I%>HА ^5nl2WX`LHJ6[q=)mA:vt]҄$+{Osi850LΪ#.N24@sǐ/ʴ1i QhF()KEC,ލѱ7=}!`64TEl7n&6âriy4YO} @ZO:4Ѷ]E6>B䴲W6o"^+v'%6dKKY{n-1+4Bq˄H |'7\G&Pd^ @oU aݷu)IycTk+UԌM::!D:W| 7lG;yЉMl?J/qN66z XvmdGQ0u .,*KɍvPT?Cɷ%mr h8rFФ|K@2& QWPY)s 3+L@ڿn=ܤx,JS[kh6&}q'q0u%<;g:~LRla{aI@I+ʾ6] !th \}*BαswrIY6JrCs}>?| *MԹVuᲾ?  ׎$IFY )5Ąs8\NE)^_7Tk+XӀ\Gc`tz'0#9oeI46T; M9/յSjfh:YS3K Ĵ`ѣO哪AY iEO_Hd_ɀȇ3%u3ʡ#,GdpR2E!G=&5E\(.T7*[(D3$Ih-1,VP%@?&JBZa.U-t" ww44K> .7'Ӱa@%!1 KV~i%\Z˅e~,c3pnI#g)E*MyhjV cWI$5(, a ¿\%Sm$*ݝ?Y6m>#aaܶ] r_e)QT6p#y]E-8$)c ƻܹ" (? JRJv??dȟPHtAtyZ%o]9hiEԛ|?M.2n-ܻ.,8ٿa+ 9ؿy'Y*M}UR"  EFs|IF_P`t1\@9*.\GAzc8wfnB+4 32]YAezɹiM%<%dz9iZAO6®-y(7^KlkM.AcZp7PW"vBb6Hz®첋=}H d$d$P Y}\*Y2UH$d$ /~($KԶ/|҂7IO MH?re^rmW9J.R;HsgS1.B2YT*[1jRbmEJ'>$5l[ <-w;l+ ۞yNxz$# c3 1؈83 c4ŸO"RGLa~4v&ؘޟ!ٺ3ҽok*$ VA\o(֜؝hׂ 1WMM PsL{ܨB&Ӿ<;#‹WZXoV7^zЋlGͮv.SU *4pA15e'Oh"m~~ƾC 8>KƄpS2: mB\ފƤh ,~u2d; [ºsK̅ i, +[ɽ]ل(ּ bw8fiUz';;"#CNn6i fu͚W9 'J:M;ҠA}дРA4 PqSuȞ5,fz7mFs*V$mӱĒCZ[DŽPVe\Jh-5B[A?FS}1ڧ9h6 Cuu7-d5e}}O 앍<39Lگ 5Ka|Nfᛣ*LT( zӴKgBJ<[AB};֯`^;FWM(ؕZ4BVwI06c\ؔ܃kL/iV3m[a-XПHW@i e<+ `2F 3dbļLu|>J*1]|$hGS,zHpԭ/ŮzWPwp-»K9S!֕yeݣrsK"j|J7edl/~yG.\՞Xj)p'췗̺y˼Kvˌ5ëYXXFrMnbݙ҉wpr^,?r=z4([a.ٻYJWJ6ɍ?W:jos|f*K ҦUѤ CX&K|6yį[ Z|DV׉Gtᴠ ߼=IWɧ١? 0[ͼ'q zիBfgueXd9<>!tEQoJW~;T65Zn͌{Ntv  Bff&hNzu̢^DE_+x&((EU jQf̌tNUNgZVjQdݝՋ ))LNN?JhwqsJI zU-;rOeEY,/77RѸȂ TLͳov.a2ħE-Ùrd=:peʴI]JC{7ձM?gfаzس@$L՜Z3מƫ@eȋOeٜfydB6~'zk*'x8X%oRńJ@d7-x 6,ׇ x{9UA-# MGPMeYxe#0*pdIuFkljhc<fԜ'YGÍ! g7.dӨD@TZwD&kOnu6 ˎR F_'-Ji2.| '|h`'vB|A`F29x k:6Z){-Zv>0&P#y|<d r>S=MBm䣉_1/+_0O8,8M$ܚWY;4 $a3Sym+a9ce-t4V23<";DƁ[e! hۙê o}|T8 E+n9U.)u8ٗq{##$/ FYY;A(72OBrUPksh{+AB4oٲz^]3Y ՚ʱ`m cHGFN˝U ]~!7砢a;w]Z[w~Gx4Tُca;^Yfvg.+Kxs0v_'лLYV>Q81;]/*$>4ʼn58upl(zo?Q4 ?zEQ r~`Yp)-n=[hRpKzAdKSMQ^\{ c_8%a9]Vd=%ا-**#m#(V8YZ}:̸cʯ85# 75Yt_~Ji~;⩇if*:]JɸHn_?_ʯ(ajx7/Lɻ#ύcw+e;ά[jixlYPw5εk{AA*GcN )1NNu:tY'ut:dYWz_*xO%tr((>}\* EQQTEQVjfv`- >?U)jNO^߹΂]zv5Û\VY&|͌Iٗ4gÙy4bj & >c^ܧLa<-JaI|7Je!+ ѝ~VhdX[#Fߏ*]"/:E>gK"3$,Ի^9Bl5wO:0.TLy{Nk.Vnd l[Hz<ÚӭmwM7W#2շ#ܜ@ZPn:7ioܲ 㬨sRzʁ)vkm2J~kX["ӪGg.WɛNi#y(qk4lɏ/~ $1[e̓-5'8[a/Kt֍.ۣ̕,ˋ 59|w:Y4+>dB4&QCuU֓7ЎIdo5916@l^JΓz7CLG{y#!Ztiٓ?-J^sZgAA/9\BZԪXBHqKRդr]ps{Vf|cIPC*ÊEW>z`Oip;,z ue$MllrpLdO^I[/ gF>~gC}_~恟%џdtMCA<&;{WKSut?=>SYf+-X3{Jzz(_.~´q{)Oq 6PϣomNbo]T[<FL7:I@&&ZlLF===O;L>s̱.ڙYNM$l IDATKg_`aҪl~L,h\sJ_h jNXwTTCg~^K呝jdSVo<獤:뛪T0Dٗ?$!USћkU ]/&~,6U+zbewjmȕJy|> %0xRŭdBiZnVGu򥒶v!?Ēt ҦjA5|,ޓ\ѻ:AA$E˲\q,6-zBC-4$R/ h=vn4oEL:krؐ 線F|S%*Y t@q1o3S* gryyf:Bp>_;'h'H52!>:lŮ/'EV$L 2FBf5Vwdm,?}9}O~Yӿnt]6  kpM>*A&Oh.Lm:%idf82~z,va˘JtPHVd q p;t$^B؍fN'  gtTOq_u(NPIbkŮp&MAAnH;-3DPMAA?*& pxEJ0m|  5AABz?u2AAѯAAAAAAAAAAD`MAAAAAAAAAD`MAAAAʳx=JC_c϶ӗ@JAAA]%[ pUB.   \+     T    B5     T    B5     T    B5u*p ҜHiM%l)84ҼqF,$>y+Ⱦhb;RtAt,qaĮ"(; l۾vоm{nHAA.Mٳgl6Lf3kCz.}GML9/U08KGMpKi;n7N 'sh [7>We{2[95?L:ZԽiԜxNW lP?õICyY0I^v 3/MR  2/Ӽox#s䒖cCәNk v6nıGޣn@^.7݄[eՂ  p}]vsNnFZl`bڵk o߾U۩$!گCPb'^z?.$$I+@scDڛ/܍ا<>v/x%4;"i\ܟ_x5n :wuՄOmY5O-e|wCO=*^ v/gK#dWjWt%RX|gǸwPT+zj8YsϾȜ/f2eZhA^KY8:,W냄,0  (ZoVYqJx֎$_ֱ}Cǭ6Y_>A8ӷo?n7Ĵ ==& r9|/Φ`=CۘP}:sa 6PXS,47ɢFZʟ[JFN# r]|&:a> pdjBnd&-OZFxT4.~9cZy,~i{]m.<y(H\O>:bmhH}Z;-6eK weYh1kcmeUYɁ}B*ݟ{^{-:? 2HE.!A50zh8ȉأtv3M3mڴ+Xsds4GtIg&I~?G >TUj㥿eeO2 Ѐ㧏PqjQsJfӼLq7mՒGݮ<٤s2Q(R]/aM!eo\u>Zy$#io FLxGZVri4q\2.  \i5ؽ{7 4^Ϸ}ǭ`y\.t:AA:u g0pϺsV{JI6882^]Y&ɏ2~S.rP>9z-\j8TvLl.mE4[79s. ~XSYʮE}#+v;д<Ů]9Od)gٹ}:˶s&thسk{g7f)ͯMKWʄ:[y[n憑O$½5@MѪ~sd&O䯓7~_S;&$ïZַi}h{c8>s*K3˞PZR^y[ۧ0 hF^^01BDӮf+S&޵K_.Т>I;O3rAk,Y ð,wbj9}3 Ci[ʬPYshIjm?̫Ƹү'ָu WD%iѯWޛ1f`[R* K&)ԽC5 tyajdn벨=`4O L2rmoG~9Xk`$#r{hAAH;L}|4m6vEAʪU>_i|;W33k_r{g+۟OtY?O_0$Hº}}g;]$;kW7Xi6/Qˍ3݄Y].\fgOo9*L } =4?p}LSF|Ա՞ʟwL݄;ޜ͌:P*zb7Oc s4tu 93OnvpL!yh^<*)\~~:odΫV 3gsa%q?0P*T:&jvvqM'βWeVOhP:Z=7;}s;=Mn{RNt8>\gtZu&ZG&ҨjN-);y6~=jl= &UeЄi_\8``OZͤm{9c =[;mcͤA'2yh}F| f?3N0 ȚUyvcCm 2=xl|廞C7Z[x[}Ͳs1L7f7¿-c 1'_FX<I+6Ӧ]+4;6hb.ٟҲ.ălK&~4jמj&m;DS:7 (Uӯdr:8 5Xl6cQU;n(JAiWq.۸vn/Wʏt!x0zy|+L}K/]Q}i;3}V#M_G.Ӭbư[: cƾ2AYϳ7c#>w+},=0?>Šglnpw:>aڏ6E#2r&c4d/ӫj٬ӛcnw_ex&)^5'/'5cnJrwM{QfY!. 9 sß_oߠM;=7E#>£&/pijjh70o RN}ô}>[hmuzr[|Cg JWOb$걱*z8q.hY:dTRۚCWxkYx!+$N Tu|.낗g.޾ /iY8i27#|Ws8=d/⊢m'Y,F&|$+. Xbï6sR9_╻Sr7ƭ. cT }{K5zSƻIS[x͖o൩|M}`]up&&TՔ?5S=@RӓIU4`jUte1"KBMM&Epd+I%c#:HB Ձqݗ݇y[NfD "Mz]ٯ۱CwjHnѴlT~D~:3<<NƌIyX= >ژUXz"bS= &}Ѕxy{]*]VgK\Z/%Uʃ3XNƥ_e5s$=Q14ofC]6/Cm1I BX1[%~g'QGL6O QOe_n2PSY9m۽*;]%y􎆨ǿg4AkFzl6.ÇxbxqֲbTk d |i}10"pdv8>Z3$x(HSq5]i6r #y9F‘+l1wZ]W]A18p[YPױ:6{En\u/|]]VHM`pmeZFԴϣM/ݙGE)'X>ErMnMr\QUMP5 X5 _OPL@#u4''FzZ W2HISA$/ZȜDIO%U_/sy?z~vmY7+wq*݉[PCnt7CZ)~ v+wɮdzYo}>0N $sX'{c D͟#~#s>Ï;W ztI^x!3)1?yHJ/2|>|wxcAC_0shh%NOCxGɛ %ŋCxlK}>Cd//ǎ_ҕV'_|2`9> %wi}3&0"<8|>[z(nm(|TɌǥ4iTbٺ#ͽ =;TC޽ eˮtJIC3VOjFAM%mLɐGꢕ:"n@e˯kHqbxAzU#?Q99deeaZlߟ^zajU;yvv|CE(n.:F7ii∏9Yy0?T3W*'Y1A^1yxRk|R.7n)3&ƝMl<Ke7rrUĺn(]0oǡ iGc妴R-\ b*7~\ҩ'\`nÀ+:T5ނ>݂|oI^Sz+HZ(M-hh.(l54šKt۶S:2'Z3jrȉ3 IDAT?e[0#[ ds)d>Mf~:@/<ݞٛ}Hm">?κQ72 rh>ؘ-u3[3N&{N^-s&2#%O1sțY_.j:~n"  ~OuعMyGQq{%!tP, RT( ( z$$v$@B iyvw62?#6~Snnd<QXt @#86t9gw ga-uyOo^!ƌ4Hv͜ȢF Ob>Is,-K}Fai~ΎV2e܎2xD) 5)wɇ \À3eސœXpOJRr d H{rKpQ @r8qE2q ?UEE>$IJ#*̘xz^u\Ѵw;W H')9dL&&)3ZKkR a 敲ZL'a/݋Xvތu^t/DQHz; \тSN`qج5h?l:+bq݉ރc;#ƿV\F%Cs}|m)Mr؟̛4S7`=Èt * yH.H"HQ EH݆؃_ohb 3A JBSwT$'Ÿn#lB7 '/wbi/W>yohT^@O{^Z#>Ϲ'lL%%5_!#ޜj&1$ׯ $+g|+QFUlLǐA|> G. {$-NEA_Pqd/8#FΞ^dwh;jZ&>Qm;L2 1z|:Z6F%6²)kFNnE"c_K.TUEULp]r mPIKC΢) 'Y4y. ^7ń6hIE9C\Ѭ?XFC|)9ɾBlR1o\oAMq1ןىɳ?e;լheWs!_Q% )ZWcY$I`H|S>Ujڴk0GsJdȯ|ۖ#>g K٪1Siv>G;ߊjߥW9}˺53ԲRJ#wBFƩl]7kL2h4Dr0}=}zѣrmI@ùLu:zƼ' gzVݟUMc/؟dCáRFUO\SG|bOxYBAcDnt9כT!з,5[Ƨ Gp3B;{yZvͰo7q)r'Xk 'Y!)6@vtQJnH%n<숫]/}%M|9^ժy!stG@ xj( \KzD~~9'%aemMJJ1,jӫg5xe Il[k]^{K8?CVWfvO|e>a8ZNzB3hIl`פ|ƿ ǫٷ,Ox9kjݨ},sEMMOC .(ARV,?a7z^JˊyɎ7;?Oaa$粔u|WLP!wL \Nm`??R>୙4;5Bjr *V<$aie&\($@ZPLkגj$o^Ef^G`rG{ ]᜺IlCKKdE|r=瞪)>Cl d](]pe@viJ8%Nүbp.+l~Jv!EZ[`Q>m]ˇټ/V2e)6FiҺ7,|VPܛ+ dx!uwUjW*.'M^iWV &s21L(*`\!3_KcwdGzΞ43PW]ZP#SBhQӊ ks}W#1I:Ki_NA[t.q x`:ώ=7%dOKU*|{({ûTq}y^{w6SfHBW50d8:hOE [|3Vb!Nqe3ղmrN&U~]\M~Ea`g6emJ\v@ x(a-((۷IK}6ŅwPJb䢡rԛ{Ξ۩,[s zZG=iGܑ|3^Ό d#~&a#h>[)ͦx 34:WܾrhA= Ɉ1ָNobΒZ?}HL\tR\LY칑B'Zܚ'gG$"0%^\P,jи-Ǎ(DIjMf+oZ)X.-KX >*`g[aOhc Tހ:e\ԡjX,% %Vj6b4q]6GpICk=;o5ڶo̗vW :XT|B[esضppb+;n)i[+K?_uʃkH-Sۧ- bcl<|vIo я8JLRr^"BRR" ~ev-MUjw0Zܬ5;b89/~Mwy+2-ECY Jף8P!N<䉛s^ps˱X@|e5%kbz(9sT<\v3l̘~tHG܏鯖OۧT6!6"YW!QF;I%66f@AD\N>h̔}0~ּ(l[LQxPƓ2(# r|( wG0w{K&r@e4s_`]UWa4(kvY|lG2yVЏy57o(VT൞k<6O>NK wn,5@ %"/_w}?7hԨ^^^sQ+C !((xI<߻=_o"29uzW XTμ)䈧oUƱXVe`zׯP"r|h H0^icy'EXbUI(+v8;e<4Kz{Q$ U _6Ik%bɍh3_"ko|[l<9_valRO}mo;FE_{f0~ɴex%s|1Uvu/GA}?όdtPc4u3 ̄Q-q$;̧40-ϙ/x*ﵭžCTޞzZ1bvEŢ+t-?|5oYL.AB/$a43666L&.]t #i ao% zkW%!:pƃ2ZQ7nbƥd^2V}r,,a[ȃ-#P,*>{2|gSTClݟHƧٴ.Ə֡峧)BUU-㊜)aaKY'5A@p0;#pVb9u& =n/3%FCVgM ф]}S17򑬬@.3N~Ի=FOpYUc:zP7[d1nѩt HhKy%ˌԴ\cNiHh-r>|% N^5=mRSIIQKDpһʭ ѩ4^V2GoEmׇ l"@`F辁$δ  II5%LcMX31"aaa; ghт`o̙qswf4>>>Α yJG% ~ 2-lo̻I_?N}g}HM䯑p=C*h&{5}2UZ%IZNEg몕0t`6,iԗ?3PZף틽M]hUgK3^g* feJ.XJækK$vL7߾o';4!xsw>cYlu? )eJC xwK83;W䏐4V[.ne[i|]  *aZ ~7r8?]W o?ḣ 1|νݿB{Q{[sxsy+=Ud:^-$Iş((QTqȞzgYj I)* Sx2]e_IWA,NWEs<7)T=}*T+c[ȉ m,ncǁRlT iC翰@h֪:߆pdOEP?T.Y5X3ǚVo=u^&,,7߽vr''-ilu3–~ða#;e"hoM'4˞#>Nyk2Y,v%sւrg'taCyLzorj@6cv'JëX=;! ˼3bӈקn` uxH8^Cfڗa \8Il!0=}0c’H>UЩmkb٩D --@P*]jՊ#F0r#FUVO1pr㷼n jh#.&;}-v͚̊WeP M"j4^;.r}LƯMGjYKeslbe` F#XewUr ;iQ3&szȦKNX/f VΟċR?3>ArEeB9jfBd*VSβ%Nph}N_ׯr֢wqePR!qoZlȡc TS<Ѩq\L*璶y b2s}3'vDg[:s /3g%lvWa8v{Fc1-8m'QYV{ H␶ Y#6{lzƋu 4ε܅-Ey*QH^ ?l*bȪ> 3Cp"9S~ PraؚO$g9]ҍޔ6T9׬14bFYh-^~ebqHW?vW-BXS)I3?\i2_q)ʲli؁ߢö1Whh?e3WFp5+*R*sOGqۉv4+\]2A?k\#.q)6I& 6^!e%_]95JQvXn 9w%dEg Q CyNVt+Ǧsy.GǑ&YHYǿXnWr{l Ky,6rhvLz IUdluñ?s.{Ҽu0?:Dԟ2$3b OLJSB8x*;SY*J4hZ{|dߌ/&\8a`8ẉi{a8u\b.f\OۼV${oDG!C݇n|'j $bOrKvܼMui,];8PUpHJC7 TTѱXhvyo5~/'7Fi׏Et?7M#?G&lď^K8c9?chul8:*хZʸWtΑޡ^ܽ-Ofw3&u^냃V}zwCf(~;5UiT>ZK^ a^2q~ zơ<̓ X6ޯ&{<ͼѹe,9i3w'cQ%^qޒ`4˔>t @x?~&<[N\,w 9ӼK3~)RSSIIIގ+W3...hbcb|2vv뱲*P4ܑi0l*9d8sg3wHo3Kq2ї/vo169q2ts[2]FzQydΊmw>e"=>?r؀0Nסf14Vٵ9fh5Lb F9RQ?>܍jVfS}d k#c/| !3{Sty'V358D" k1+V>aߓ pօǼJ,і5z>fO%c9·z=?fD9ce(T$!>q &g$ei?CZ_Ћ&Gk||w6?aՌ-iڻ ـl,>s,[3i}$u2~8~S3﫵$.9Α}^`܈k~Vm}i9d}Jj8#{գa~^@ i3p-}vg7g`ɷ NxrR.ر\IxRU-v.^uBr)Gm_͝eTtT'׿C\\weoX111$'%( vvQ^= cQ#I2cKPIɵq6{{a[sJZ<LX9:c[k̼7K?YmKijǜˍIVzx/[w1Z;l+e}^e-/X;Znc@v*_;ost5(w9=[$aW)M$f O\L͍ -,JBcNn\*:'lr!n,ݦ8G% gliicM V/X9⒋S_GW)<Edw/{ú(r˾ˮ_P//cCLS'\D"o>O jW'֨~\lpp-Sڠ 3q?҈rX|p$^PMDށyb Q"3:NŏaoR1?ͤK+ܯW [@ ^u,۷W>p;cB ]Ɵ=Nj2a%˼ժ *|㷘f4͓.$IǴ^ؘI?3in@ Q#ϧN.yfҕyWcVvl` 回l֛)SObTb% ZFFj䌾 Zpƍ-R.3.>iىn+'X "!Ӈgpp? Tjc嗝g.@0i+1Sn7ٞ2 heEmȘ3X$hiJSqqvaρy I^BEM $תCV$Ij=CI!ⶂg)%@/%6\|d3sa@79|l_9~tUeǑSϐ4y؍KŔz3G8z~՟m`_lƟ!F!^U;c8sCɝhrb0_dߡ;v%xC5dL []ql2E+պ߶c>e|iְ!2hTg@/gtjoDgG>`w? !=n\\:>0d ^|'@ EJ\yQ~;|([xC%@|h \'mϿZ~fEo<1Ҹq Ȗ++28fk@  Zulqi ;jm=&$;8u B~骄>mC.R Hh'"-iNEBӧ{k2H2wQ$o&pt$ 埴%@ %.E݈"E|d:tjODdQQ;C^\Ig+j%/;?ʻUGg-&Mפeשz*V)(b. I׏s d Kmv>UDzU5'r a[nS<$sN ^ǷfSBOѧOŵ\~9%>?bTxo,e]t?Ι9*wVU~WcN{G%`r\4 v+ gNBż\9zX0e]/E. w/~.g{]xX:حB P/PI>pF5ywI9r:PgR:v#4~D IE,RLD9սopF"X{PS v@USvS\#U@*^;\:uwHIۻS2l+0@Q)IսFM#a_&IX<5k5 90Gr!: +}p)% HM'nE]^hg[ v$Ii´Ƹ;zS0L@rϜU1$u#׸c)叏%ގhM\>~ÿ*+~U/rI5iܼt YeX+ҳ$qx8GĒZQ?Jc@ O=u=Qmݚ_mb93Vh:G>̓8zNL[uoFقاæ<8p~gmH/2v\~²n8b\n2a[nn㥕-?~?b|cPķ-JJ½^i9+f()1'Ι%˧l_?F43^q3mD|PI:<ax"~h35ݗr.-y\Jm>>C&pT'2ޥ6lONK6ؗB.6T {R7sN_7פJ6HEG~7օ!L:MjfMgr0^MYDH??YC}K Sg$ G $mz=i>@2ZW&;}UGeKPӟYX\GV4˪ ʍ5=_~!|s,xωEfõe48V i.F43Ϲ1XwYvAc}SV}4?/$ؕȘk"@ QۗýkD!#gWv"Y谐@M8ʌ^YH /K.ocާ_}s&vG<,o&?4`YyIr) olH,]/|xko+8`]}Lҋ 37-g1 t2utuI#^d[ƄR6:ӎfDIӧEE[GL[Ŋwi,UcX;/ϻRO۪bzTnmX6{n4Ea$'ʅnXƸyL[& }t) %z ɧn"N87+UqXVCJ/C컮P/K#0gxmejTu'(͞s:psJY\>x[ hPS܍\F8XWIdy.n@7Ӿ].EvĘf0G|JmΨop҇&=Ъj&βkq7-zͿ@+te– ] n:Rz ¢2k| ~j ~35*eڌaк8?DUU~O;7(Dt3i<^ $KHYwx(SZ{Ʊ{HƬBq]S .eulFPf'~*8Df?o#4s'ȝ9mJi"K` ۏsι C{(w)~]DZ#?|@?Uhկu4zeE}0笾hҷtN+ αc/L^#[ 3JZh#qn홵lH*ƯWsaaUhNkYFLK˯ؽu?*mj}mڐiXyIty`gV.7_෕0bE)uFE[%vfO|q5CffySM7UeaaϿq 2 h:vjxӪV6ї~fXsdhʨ.b˧61&Վv=?? {#ܰkՃ=3FVn㥮c̙Y &Ɋf= hhe, Ć#Ѱ.?7Qu%cr` }J[W?FA[ZZjKsCT~whH!YP"ɫ _O̓vh:/W^ ۤ `'̊UChPn*}ƭylh=Ώ:t(%o^(Z*zdw<no gi~v]IŊ"^/#KȀF|3^bu=>OaͮXp>dtܛQM(z(rAPEZ%L慄,袵/|/O-㛿0+i wQ]4X7s׾@Hw63g:\ t|3CdsSM,M3ȃ ~yӺO,]&Ul?t)ϬbɁ.oEP:z&~fD霥.Uv.etŐ½4WF \F\v^@ x)&>>Ua P*6͚`_,ZiO FTOUSoFK ׹|\ ,jлO-tػWN?*V>>/)pvJ*mͮj}{0yX-,&OD_7By˘e'Z7F [SFi.4{HB­û n_WGܻe\0mA ݷLrhƐ3୷ -XyuS^}ƕ{ ɮ^woa٩$BF@jmtiPY5r@8۟CnqY? hjPI| cj50.@vn&eSRVTM`ҍmsox5v5=%D*Zm.sD VK{YQyBzw"d`U:Ucq35G1|s0:>-OR$)[Z[ T]3!euX+kpȉM۸nx! EMu^?י-h߮<9a3W2P-M`]r1d'Zt@Tgt%`{֥=IV%봧bΏ./F@ <YX۾c+ 5" ,Ipԭ]dƿض/jרCu*]ɲk '; RR#8i(ף/ͭxl13sz e\;[G|SPRvulsC*G1uܰj>tP;xx>_oJؾHhܸAjr4FM'<,ŒkICӘWLy剞v%P 6Nٷ7.eӽ{[6XԢy#^vN^mX6H֞vk֥6ve -!(Ne!}0[\ 98)mcc֬KB+6b3PG?<8TT 1$(j"Rwj<ᏯFWVԯ߀ݱf~Qy2HﯞʩE2ˤxW`2EZqe+)$k3FcB1I B vXs'Gr}٥)6n8I [_$Ҽ]HkJvnʵQ89; s %)D\3us7>Ns5׍@ , z 4,DR xNd2gOR%f3fI~z9<3Ds?|s_ IDAT;~眻 6 6([PQPqUֺ~:[GZZQ7*doH$@Yd} InHy={y0%<[?ݮsxwӖu'&s+>M6lF8Vzf_zdL x`h*et=zj=NVps݅ө &E{:c??{x=?KUc'*bH7#N/!2J@F2|}5lr 2ϟRIwj6#uC\lFS, m4c<8ݸ:׍ʁAw}S0ㅷ[~ 7QmV0:{'}=ۜ]/KyB?K"ΌxZ];8kf?ޔz ̯Fc'z\K12PO#2'k^ ݍk4۞XkPyh"CY FwR{1čcJz+sKYVw_.Cr-70rB!٩C^/6ߤyzt]'$tx]*~) ur$QQFi)7vפgrf*.<͟N~^Ǡ?QؚvRK{5'F~x["e-^jQ*xQ#D6;]7$zϝ dD={Ob&D&/[|Nێy}r9i|1a ~SfdrL' ~Tb[ɪ*asV) 3|7$1x`$M[J)ۼ^Sϡ {!㻸GK&[=$#zzZPjls&S31p:¨akOntHiq1Q (~~( jv݃(??_'Fx*.,i=Nv$=`r&URa4S/.:RGKy)/@CyQݗj(!OZ.fP/B!Dhwbd2S^^JMMe(-;;ULe)I[x0zed֪g_;~P=3f_\# +VQ~.cɇ_3>*繲gf %Q{cK޼j1Qf.rRc1* #^-d9&Ӌ*} v=i5 DgHîE57׿elg?;j{ES8` f0OOG >⩷vaS&X}=Z&AY<\Ju+n E/+Gjl[8gһ7^O@yת͛?gXAOޙ/fϤIՈ1\4,a-oB~9]_ѫY8T{<($F7`h\[IYK 6Ň[8\syhƚB!R^re,\O??={a LM5o}ik^~o1ؼ5\e8tS$k\٫S™22bx} 3ႰSVxt~`/_З^ȈD3Yzq-c_zGgy|B%x%}.(y1MePդ\v6K<Wsy)ܲ>Ϯz3i7O#^v^yr[w gә28C&|Y:c?Q". n vzB7ߝ2k B٠8gpz#L 0tNvj`%v"s>ݹ۞[7~uqސNl` 췛Hv~_w=5f7O{>sF2$5 FC~,_Ī}L ӸnBSUlsܬ}\qM?s)sCױηغp&OԸ]18s0[meY NLx$jBzu5N'(aa& xB6^/[zrM ~_1"0OV]D=y1ф/j\841ԸR>[t S9{YGn䛸W%(P5J_ Cs_ק{hYq`٢u4 _c~VB!/Ykc|O3 a>cS꧋عa!6-f Ӯx=C"OmBWo+yNeyŬM֩6b\=_?MoIubH!4Un˥~\0vӧ,?f-xoNQ OMYɸkvy{}PPyk{D1u"!Lb"?y.Щ15:+Ke3sW3d@+pJư1*IQތ/K7|~"0eMviqGt=ş۞gf|ɖC -ooFFmN<\4Lw^ţgqլ~)3RA躎vN}m YQR[!%%&54@+*B۽>~ATTRAqݨj+U[D̤]Eo}ǚsp 4c1 ~w4LfR\>د3vNTWIǧL{ꛊ(\vv8͟W2ϾW LbƵ}iHǟF9wY (zNޞ\8|]dI!)3QR^qc;Tu,=%İW:~Ӟb jh؂"oqFtppr***('~F$Ak[̔RL"$**p1m x=%V㱄KHG;kX߮*Mj_,Y=Jx18D'&ek}̜}%Jltcھ})boA#*13Q!BP.칅PJ *tr"^7>,p;7]7 gv3x"ChEs6A3t*1=ӣۙMCfҩ˗΄,*%LG&=-g? ST!$&&p8Ыa0fVY,XV, fԦiz"Fc4 }e:~S&-k9 i]Nj)ݎKSPRN.!BٓX;+Ty^B!=|pkOL|}W΋yXf^~}{UB&^SB( jYF~<jDb B!g?I/|OsZI |)4n3.'W/`ŗ0w=Ki@_VB!C+]dz6Q|`˗nm&9%B!TĚh?v؍)~h4!:yD3KVOV 5O?%iSB!C?ŷtW ` r&tWsV({S0aLf"gBB!B!Yy"ڡk\;m>KQTTD||<111<IMMUw {75=tN+#;_ǹ3O k'!D7,\⇎#3=|Q;>`f䯦pkﳃ)>=H 䩧 {*[;jE\> B!BѩP' ؞_F݉@Ӽ'ްUz?t@E9wP B7(.I#4rx5Ě!??~a0Xf 6l`С׏@v E\CENr2([CN-$p%PP6e&G*ټh5N%`|ж/j˘O)JЈ:QسXFLBfD}clX]B􏥯yB !B!tx|ޜG(l] `4[Qv.&뀎Kw3a`Iv,VṳӅ8PuP/DdRQķuc-Vg[4 ebf1_-%kr/OWx6`j0}ǞC_{ (fŬ;먩Ί9ԡ/ꨩ.J`.ʝGO45ߏc^~7;M/gوBpKvDLcNSi)@s(alX[/u9;J\#[q4eF23Kxi"Qغha22zNYz5[Ê'jp2$>Ԇi<+|._KfY. P GjL06[Yys\6}0QJDss9H0iC`U0G7. W}xt:!Xt;w~nvzm рA [WfSaBZjVs4sɂJ]:‰ BPM{:n?uTB!`Y tߍRfIJrsw1I js3S#\=S'ז.[Bu] 2:&%)sGiW> \.]/xivq:'hJ0{bس3cuُK G<ǦZ>n)~Pg)~=^Q8 .fl;j8'Mfx$Ѝs.db@]Y;Z-ebEڵ{3Pt,V+Ç+?-iW>p8x*{ h}VK[!BΥpK ۤ4 5toTCz$y͌3Z;^{(uka0` {  -  IDAT-@QÉEzz:^ocRtv(ov0JWk2=Sl.bgn }SJxZwTN0 HFk8ꓘM~FqdM5 8pxGtTjab0v!sM3ZJԣrK &6rq:QŶ>T%1D|T0*H*=k%KɶE}%+<ÿ~"f>a,|tx?qG/rUI!B;&'j)5T7ո4?НW<0)ӛ*vn'(0?fѯ?))ΦF6Ě<"FZZ4"ڑڭj{v$F@NsHL hedV=mk9|*4 af*Ypq &'{G1ٻ#y{t#O%:sCJ粺%Zf!82SHn @KzZ[Qv{vtS_C~t$F9e:<Zʋ}&nesLF;3&skuGxvq 97S^.r^Îht?ݑ!Bq4ޚи^+TЌkCi:O/M`ͨ5Ádh "<@5˖ޯ?II)lߖukшjq`uuucQ3b*h#cogCe;K3-&JÔЃדt1۫uRFq@6}9MeX&#b}Pvcvgyl,wџ.$a11*eSv),gdaEW{;Yٻ+@>ٶ'i 3vix5t/^o+16jU9: zc0I 'w_,j{(&Lt.`{vKKFÁ\nϣJHt2}2zwDj/[6dEK =3-kBvԡa#*%4SXn#)9%[+N'{bE|JB{tEãl_φ],``f1ԗf6Vf{udƀ%,HG!B3܍`Цjng^2w&NW8> ]!Mz H^/fml5?jjjiDh2UAQ=Mk*.dXL 9-tnHj"4ݝ@°1쟽- Y01 b勘_¦r7jPw&^4Gf~i!ڈJd\Z&x(/.`~mڋؗUGa>}zӶYB>jPZ:[yg~=/q7?8v |jL_1'w*Viᕻx7ϋq]ͺ-gj?B_,{w_eUhP7$lsi)XbGҮyM]||J•/}|sB!|Ni[u5\?Џ ?qøDWUuefˍAQ+M}CBGᨨ 22BXĬ_N<^r`N_[+ ^G4:;qrDqtp1kWJl!;Q[d ݉H΀~-[ELGUTZ ?ߔ=-9{5a-`Qĩs!P,r:ݏz[׊xu}=a\d&bvUkW3k+_ycܐ_f˃a0<%vO3s,z]ovȹ]di n۰5~gnW/vy}4R&\v1 4Ok{f޼yDFFr( & 른 &;QR}ص>A.fU3#1c+VfxYQӎ\+_ SJv( (͏[O%IM?v[[`GDوZPN;T &;TT[ ́Y_ jW҆GBZZ4ѳDŽ8#yػ;~z-lJ5vް%,PfE :*穤РMQuдt8yG3ll~*,gGpLB +xQ HTY]Myn; Z7B m4kïcQjFR07kx=<^/NՂY{ƦKٞ][i4V48UC< <4vtI=Nt LmaDCI~k\kZ *p<9B!B4UOa{?gYam&Dt$_ԎA!x&gіBJQ (&2yPz uEEk #25`%+7$w ŹZ5ҥ wd!B5ط(|*`PTBzMr z+{ 8M2\8MY-dhg`, aXz2PEqL <7jXS^;^o%yn/bwfFR^ĭy<*(#!aekgWy~UEZ`Mc\!-N?Uc3(',r)i5!BqS IjhIdH Lur7"ڍJ,^xvupګpp8qq:q8p8]$w74BrYnw~be5S06Z^@ܨksT˛mwds}xF6Bԛ8-|3?çrà:éb;t ƌ ;vtG\xgUWMubDBPIFUL~y-qALNPMUnLH50n.;!cɃ&dB!8[\1*{Ui'V`n.FQl>5śjt"BI7'IkBqel񥟨]]V)T#R9[v|CO)_yj"RAwVsq(ud kU⟳|xه3}gr¨_K)ouBQCRu?ݍ͒],3gl{(CڐJ+O[!BupLpVtB &?V?l(kύk5:N]hM|YcM!xnf.yȋjWE?b}h 2Qc{^oo_O'?L|.akm0?w7ɫ_ ň}O}7W7;~hFpp?-ZŞZp#w]}ohtny:[Tbq 7_ |z Fz=Y~M{P"I*k0ٿP?(puCPB!4>{Nt? 'CQ0q85cVDc6[M\.ut7.t<;m'ita$ք⌧.?) ъh\b4i][KUO~xuW2ϾW LbƵÉB!vE|0}E&QEi@^} Gp>iEqx4~?i-%\n7eee#uU b CU0֣Zj1:k1j\v\.& .Ҳ XO9m1Yq{زiWGoron> !D{lX^OwGe/V3%Py!*`ΤP[R@aE=K(񉱄πQ߀Oi\Ïa,pnw,o})J/B!8 l̫c O-07ꍓ6uM#L`|8:lNa WG&1qGfbcc|8n ˬM&l6V+K;f4oC["22NwB!޼OIҿ}ygͧoHyOgB!B 瑷WBPdXDQT0UDjr7]ۖ۸[sX,TSDEb`6 n62dcW#Ί͛:dC!if9;[ƒ7s(2B0er69PBsozVB!Lphzv 3WRTQDX4A4jMQT fvvKᦙ+24{/GM4[[qqGduV$քBPËo|mfC>rfr5&B!DsRDf~n@_@يa9C5U``*c=2&=%DL% dT3o}K0O4ܕAPl6qU;SɝB!!W?=ԗRZSQ EB!8=%$3os%/϶|j84ێ:h^r=vtkrocQE*[wgD`@FM#$XBq2хB!gaLMP]hnp8phZcNUU1LX#Ԛ'ζHbM!B!Bt(F*.|MVXB!B!vt1֪u6OI5ڙGstif WB!B!84;:v'ҎMyTR^Uݥ"ֆ*`E- 3 PB!B!|-vNO5_L;Jپn5vPjaHj&$C+©yu*7/d2ȤxBؚ:]uT7-(˙B!B!~NIbM4fsgwuB|?w%u%v fFک.+doA9U60^w|CJh)t+bի޻(zbV4A:jH!lyHhݙ3Ιy&)*ă_\8*QgXiK?;+B!Bqt`ںr3me/ Rz`'O]sX^XCΟ3L]9N)-B#k B!B!ڥk//nПWX{0z;4o#Ɲ핬!52pTf?hl)(NR0vu\u).G1a "<2 YSN:WzգVl' %i ^tqO!B!X{#uvq9h˶*f߀@B0,xp9Ըq:=nTE??_ll277c$j(')'&1 ;ca¥|&QLRg AaDslM14NFSXD&# IDATKleJVė+SCz|c'd5(eD í !B!=j)-.? hL&&U=@xՕjhj]NFv wZ|E&wDּq[Wp]8*+N}A>Cmғ{i4`PA֢TX4р^M_X́ϐP ob"6-b*!ɣ8%KɩQ3H3 xXkXVsH B!Br:(*GuLm D0 9 6,V~AxnJ ٰh:5?.chhJ F#~~]VTWjP$CHJQQjpDA+["L1"dw5:L_[KΚ\I݇~QU/#F;7yZ/%؁I B!BulLXT!᝞rJ OۧMFb &?on.[WYAjjjvX&1p@ >xwB\JwALzA5503.IH`MDF`Z[*qۄB#F<lx'xg-5vtB!tulۚGlϤNoPUYAiI!1[ߚۇm)5MX-TUWq{ "*"͝7n}PXJ, c_;݁j˛z;_C\\|[NhV&j6t줆i}!^񦎦i蚆[iJrqz.GGm¶*~!$KRwQo8oUn`$GO"l]1} =;Jcxe>ݙs.4Wxޙv=$&Bqq:lۺ؄ 9ӳWǂj+ I^ꃗwӪ(~jEϤxTUb>CN{:QQQFKL.,FLL,@`` |w^dd:&&v8fpi{7psNi%kXj.<{ObpBEt`Әzm`Z x*~g$w0?hm[^ >!]1毟ٸF/#(ʩ5lg[F`d8- *}ro!B!EDuIP `KX:j#"*z3}hƯ3#&.0 غe+G#$,Ĥo?seWvJ˵֜Nx<'0o렠 t=^t:NB5ZU;48`@m&^鿑Q7aQ{a11*jcVBYɪױEr)#l^왋YOߞOk1!+HݑJPR:}CR)$uUKf-tseK!Sone.ܽOd쉇1oB!PT:]3nYME!0$ 2Bڽ~֚,&,YА0df˖,[cIPHV2|Cskn /7c0q8M7L{lݮCx۩aQneK"BrPZھCъy*wfY'u=ɖ~zsԃy IܻuppPo$j1qF/zɧVzzu `$&.#^h>9B!.LNO7,"m n%:6-icwiS.QvJ~;Xx4 )]cGXύ;D5;X1p1_W|\ovUȁo@^[C !}=+78U?}j;BFr x:;\npS58nF`r쑰cj rKÛȞ1-4J7n3E Vҗh}OZMk+j6gf8>D$ o8\; +k@W}Nwm4ma'eòRPULYh4ac9GLR$^Me׭!$ےMe[N8Bv.]X ^$c픬[ʜ4 QIOEx|2Wo%ؾ)%ԗRPE躝Ҽ1` %!;`_e5 a=HL&;\B!mF|| ވ? h[>5k݊Jbc)).&m}>Ĝ?gGUumÁ8f Oh{+WG718[xfsfʥrωӥ*6V8*ٰHpM&nPPZLyoyٛg'Q{FP|gqron°x[I,>",(k7+yeos G^aV;V ;Qa]]3x6O3k6cv)/0K-\:'I~oU}<ä//>-J{9{q1 = \;\@ƕ!<4lRO~})d\ 我#\W]7)k$G1<<{N0.V7wV39||JWh+r&/p1{ ]siƒOl !K!BK]u5~_jk$ϟvZcZhwJ^;X'};%]O%$DFlE lYɨ!}hLBwP(0޺ _o4îZARMN 2Mi 7'#H q԰$;~%>m<z0lp8喐:-N.֯\Cbbt.c-gk2)TӆhE|üSp=1;+Y|O.SFTUwRArCRo?̵ȤڿH™'Ր%yw3Oz5~.kxUNgیwae`B!D\NctbgN`X4O_8Ex]^{*.a2^63T v tbGaʣR`j~%?uXpR[6Jwh'˿?s OԴh|FJ6.gX*jv)IdHZ61{I5F6Fgdft'(r=r8+7;37dƪj[s,ٜIfV:N`l"E$ZVwe#RڱĽrH*֚:UQ)Se@>PM?3{=̝އf%B!:va0!rb0u] >\n&[_#;kF""" / ˆ%,ě?~Eh ocqGO]'attw#5յ4:=`e7TMsrH;vЍ5f!rUJ#n#6igd3 ni zҸT:䅟 (a#yKX>f-XٔQ~ oc _g$| bx n csFd K@K2^_/ #g(*ژz0 Pb ".1Y5XցWPlY+>Y~Wڛ/6{=6m8)^ SF/6vieX>ٷǰJܘx`!fJ+aj4Tb#F/ucśr"?*B!ǃpnx0.d4i/Cj5inhjЁ/G]o+NǖC!pS6mo(6k!(g& fMfW0+@S5\6PVo_O֭CWŏ!za\չ;Yb-Nci2 ik3xIxaKF&嚁|E,{~\ͅ8piA)7pkk0 TꪩiG`MY_EAUur%gp3ykC(C O<4\[Ze`$  t:;@Hp*h3֞r !B1 ]\3 =nL]*q՘ū?Κjp4Vcach؀E\чף:&G$Jx`“3+j=NyW٩SP dK}RaXL0~V3Fd1܈}y4hkT.vEŤ{T4?dal@WN@/nUhϥlh.\;Lߎ;u[N~B!1MMA(f0\]X8]4 5ݍ;ӜMsyh;к6B \tmlZ YQ֧)(aXćl$zq;2XYC IC#װb%t0{6WNV4/69P#Gpj*ײ tp:*4jκS5Z0xF !-~gMa_WPM̛XYtv4|1jt|Z:F;:XNB!ġ3]:lcyuIUmgPP?<P2B!2/?}ɷ++F: Zʵv ca`Z_F<3'6ׯ"ˡ0x(>yxi)F\޻NMI1 2-|{ PBZIcۓ@#b}L}zkXK|3((:V]EugE0 ’V>ċ 5pসd ))ގ)< B!C]MuGmmM_W[˻kuTh]!ܬ1&6S^ /Wo`6(fc[ ,e,lr:̌,t/҇ dTzu :o_1OFwo`EV+ٴm]_J^=@zћ(vVO bРh hX}=FN=V6RFN~kwn`ifb;B!{zGOmuU_[SMPphطI`M!%/zNVt,aoNG >G=kPY5JL,jl -AHQ˷Ѭ"n3r24_ՄূfG+-}=V֩Z(8N啒cS^A1~u]v'4ѐ9'?Amb#Zr欮#p.˟E3tV?IIcOgU{;}ep_̀ عY`heSIye[} *~E{=tR眓BC!0XmB]*Xm&j xWS`jݾ,aѴu'1քSmTn_9Q#?MXLT\M _*#X%:B| =H?,e~iPHC䤱)nӟVٌ탣<#9G[^`~c_ d9? #9N[ȸCI RّYNn$괫gMVﯼ6fn=~\y̿ }4iC^CB?S|N?v;NEsJ,\ɩ3az ]Bs%Jz3W,H>/?ֲu2 ׷XDQ '_O'%Ӻʐ!VeVև=1/^34|^bD}?O>ó_bo7PTi}n"&ƚiYd&ϭ7F|r.$)|kMOsiȃ׏ĿKc0%3_aʼ ͝7V3ۀ˹e?޺CǨ`S|f3<T[$#&<#|6O݉u!3K'KT[C.;MϖC2SXlݲ(X2X3?;dgcGck08DZ !BF>~Tn/|{)!᝞_@ e%ETo'(}6r{)=]q\a0h]oh0Rx0բZ0Zk0;:8,f75ŨeeeaXPݘ(!W=4\x}JGjhr|w~ºWP,>Edz5n'KZvia߆Q8Y4FEon!GlF2C즶d-$ O}[K+ڞ=7u%léZ %:*5!Bnt%'؞ke{9egO-%ݶ v;E,?fAQhZ5c:r}Vn IDATj7huZcC}GWݥ cbg3״b'ºnBIH#-ԧu?^Q]J;4.`7"m[FBbǶ@r#B!^& 6O-kFdLs٫fKdžSq:l&$,A5LttOn qd6cza0_ucތ !h-eB!B3ټ|I\B#:[h@P0i e.bZX,@@im?[1քB!B!l^>' r£cC0l݄_`9ynJ $<*C3d%5!B!B8B\boj).Jfٌv|f? 2' 0Նrbol*|IE=rGB.cJw7H8B!Bv>~۩b{q1n Ӊy:m;;*QBil? >P 4l]GnsBt;Ο[!BqhV+!8mB!B!8$&B!B!DH`M!B!BB!B! 5!B!B!:@fB!~ BEzRg:zNXB!B7-ofA!?]sD]>XḌWGBkB!]Dut]۱76iÝ-!o6/CB,:SxN$&Boˊ&2r%q`+zB"}%Rg hQ*Bv=uRu NΞBP||?uZRg MKw$5!B.:;**v1B!>[P8j53E[F=B!uMӰ76%7B!jcXiLN uh@Kw$5!BNrF3MwB!AQ<-$]@-uh㥻B!]IwBîM3N97 !Bt#P!8G" *BɎB!m1ք8~H`M!B!BB!]{?eB!3E4M9doڄB!44t%& 8)X|#8k) \tr=‘YKy`YGE;sBHy0r>:$ΟGu] i)i6n`?ǝءt{BJo8oUnDb{]z4 G$֫# |(ejIAU m~g ZT f+Z1$)\0i.3zC{}7PWoDObJTTG!&1 #gUQ0 $W¬]:s\\\Qҿ#WTgv0Slg[6n$g[;ܨf/¢IӛCf[ގ0ΥsɤШ-ΣVԃKggSי[S`BxA9rrs(,YgX,6FIJJ >?ǞkBʝ{w<̏5F}k&bųxY<6^{_}3Xy;b/JG^䅯p wy/DhkLK`CO9+/CAZu[z>:m&_?6fo` RSHMMc)6xV]iw[!m{_`fF[x屳I8}w:V.߈K fnT`PwuMcu 909޸v`'<~W~ͭ%j?G=;J ZDܯɜO=̚=tKAy+j~΋lԮ 8/ϼed.hoɾcul3R"*߸?~My|j^~K:)G㠹p؝Ex^: gĩ,BW,Jgdz/08wPa% P;Yŷ?{ mN} ^}m~mm\g$CJľ!Md૗XrxNѤ~Q][ņu8mbHJBB<6n$5%]iK`M!PZb^i~&`ȍع$v#f9Q|Rg_#}{[i}Gx~n _ˏg%Qpװ~}!Ye'yG{G%Ozk({/}8`5lPFߦPm̓VO~ɚzz+>w &7뀢9+/]sNL{wNzj}h4[ye۟.Y<,*QCsPL ]ѕż3oe s^yWeKv~(Y2"=ox+%ٷBHL#<3IQP7>gY韼J:iOuIWt9vL&3zBCN40֭]#5!8+xf~2? -R6f#FL@DO!BSy+.x*r ]6.'N2b0)蒦$r֍1ckt6Q#ZMkѭ!$Be[N8B,:%TeF7n4o#k XbbIݳz+ZV]փ" k+X u+YSBf! 7i䖅ƀDN %. ~)1{M7FʶPTLRi}6[I]{Kְ`yն>tr2;w$orK*ipXBѫ}}>OZi r8Î\}ܞe[[]lnšXĵR]i9+[J]*V`b;Щ/c[:gFJY_mKBXsq8^>馳0h.3QSLn&4.O JcD`l<{jR 1ۋTTn ".>Ҿʦ9}3h v}?Ǻnt}%iVzHp{zsy?5Sj{YtyYVngxi7Ʋ֬Baen8 CW+烺}W81M-n^d/RSut=rXFv:rKh,'0 7w~gQ9'=LH:B]=N~띀xu\"VcQi9?^NiNk7S{{)=1"?1 DC)"L~k s-<,G<n]V4]xb]e$h MW3a!466x0446;M !Fl'~W1婋u*2Ŗ=QPb9{+n~|?g}|3$ZPy亷wnbnd|Y~ bأNr3ВdڻAb^{w6jw9P?۪'q_g7o E̞/Is!{ ^{<-n\rC3&'bڪJ-X#Gӛ*xIgPק|$nTݻVL庖-vV|ɟ,$V 'N筽v>؋Jԉ18]֔:S8ӏX"6x)@a3:x~p'_㻍:)f"_Ƥ{RX]|R]i&@~[bߛr]n{ruֺuVy8s7O{M|pMOtsIՇdHAu߾3<>%:UpϫKc,n75 &NW`la'w0o^B?iǁ嵮#An YYyņUkiPX.֯^}ܱnثQM%`Hugn\ʦWyb\pY;rէ s73t' _}ŮQ88Lo[7|Œ>_DKYQl9`V&ؓS(]ACțVwOMb1$>쿾oV30v8 Ϙō%9LCQ߽9 KK3Ջwmד1A2w"pg _$wO|\f3ɼȘBt7+Wfժ6-BZʠ.QR =?87ޤ6ׅ9j$lZ]ȗ~̃mZvoOiQzWԝw:zX(rl/c{՗&r ϟ&r}7&zr6 9|F7UUq\ ڝ !đ1ywV2&?odtwuczvwPWN2cӜbM9bf ~S™FYk̙ CN=ؖ=IV4Ǎʐ6x_3oEⴰCVz~:|p85A>OyR4C<z[Sv>UNgیwaܸU xf[M-&:&3M YTz1]1a7CxUq=ٛ{)ќW,c:N>iBa5LĀpFdS2Ma`GۙpǷnZ’ ;d nƭ!a lzێ}`<UuhD.?\@8yf@PWra7< Mܙ|+vgQ;8g)KJ/ܽ/:;^f3cAwS|tn{i. ךnOc5O3w~WՃ-j~ûfE_^{p;9 x$wNzO4՛vB*>&bFT+ny_}Q`4pBuvL5/5!8-|ymEbAYX3w6vxH{{9 90Tc/ƝޛO@֌Y --? ^9=[5^ZøXq-+=7 54TsWi$םɪY(i2a>7| on,dwVaW,~Hc;EtL&#BPCRgcdX10]ʒy˨;=7e6m8?{^E>p;s{zz %AjwuZ@gY{îv+k_P*"@Uj [gGH@;̙̼sʨk+@w+.(*a`6lnlɳsfAzY9~^GޭVúˏ_=|1W7gxv(>\ϑ߸>:1yh6:E*kk_ס+9xl¼Ϸ0sP,AvM"%?eXxkRIH w^W:FT]s 5{ &x{κ+ 3Wk7{ʰ~Vjg8 WS.8*Y9 _NX{ lTyRb93՜Gz{ Ir咝a4Tֻ8tKt4JMr1Ͽ7C:{T4}p|3sqd'G2}lߐe/˭S.cro* 4AsAxm:~^z3-/]]*$ı?5k.30uS>I -e]¯clt5aW\@?s-\XG4|&FBGq_͍x˯^4\Ybߗ) | MN\_k^/^ [p?vdlv!22"Ʀ2ndlYIp$t@xDAr(ք#Y^PL`xC,~>H7W'aAtLtJ5>[%ÆJ¸IaKpLgES 3SênÆU81xPZvWҼNh:`hgeۏ9Rmsش7Wc%m)2غӯ\0P9VU &sy6ZI>c4_Bʅ,+͸Ñ5/ |7ڪ܃ɁՃ]j}z`ZE2(` IDATIƇ} Bbhڨ'90:)lPgOۃ`v: #^P7$-pŔ >stt2 ρ]71| coWz\~^ei[.)aۼ9ݏIW^˔I= <> keIvx:l KE/[?_Lچ|&wBֳ,)I\lCDb.k60sw4/{/u2KQ~}ܸ g75m5b㍊:.ÊMǃbI._  Esٴܯ~ FCoG>sٶis[Ti$k,9Ae~2bfUyvrs}?00ѹg',5 #C#v0:c%0枤vAmzэ:uE@0|Qy@A]ӗrK1L(l߾^YAkB3~}ˤGZ|pŏ``>x"UP#Gq𙬜fpm mħF1f<ҟ!kdam- /%`żxqg?,]Ǧ]+rpz+l*@fp}Lw"zux)+8h>޽zUL&V\T NB I UsNO(^~=W?hxLA/_FgRk]k"uZz.^j͑Os, C.1 $44UVIMڿք3yN؀d x8ڽp"'Fe3Z^oOZQ9N f䙣Z8܅sY55X]9;{|=s rtL]SNBPL_ }g䍷wLkbxV >d뜶d)F ڍ)Zq~r=;a1ua`VV.\Nc Uasuv2q`[rmUPV~VSp0b|=wKd_R2G ~&~Z ~+S`z&wxVHԨ܎a_S9Yvٛ1k2fMB[qAA:׍PèƜwog5M>S{lZǎ ogdR"7;XMQ 2~]&)a7 kM 4e@m&Dߞ]djлwMKQWyp-^0">ٌٌ]67\WQ&1 0Z%%DpKA6RA7 ^6ԛxoG>t k6on`xTk: ^Պhii9--bh?0<݃+? ]>AX 8ɻBy*aQQV*A)?}Ӌd,o>2+v| KE6>`"gߘcl[ݺƑTN5Tb )rqOA`(Bn'glw1Ň߀k\x ;Ko~ӯ BO ]-7(YbogFGZ 59;e VRE{-I9c (axpJ#UyK(7J(,hEĵ7>ԑw>r>vwbIs#9{dh^j(&s5 VcC覎ף^[M"6h|u4cy{sh4k-C̑]Or1DU3|nRʼVNf|0 Ovc/zm~m%9.Q;1hPwk78a5;w ҇='ݥ>x=LwǦ#.hd~3j{CyoK0l)FC{.̘.o ҦaŚ\t*F{(s:q]nQjnsS^nEqi e}\^՜t'O4M_h[ i6k}ohXzjﳲFA #*\t[xwogfr_3yQz8c\[[y B$ SyLvOVJ8);bBgߊ[ܴ%~*'jU Yr t&0fK!W:+Џ}nU7X:& X6L 8T+:ykWO}t(RuN]fV/P,JNjYL+N|AW\1S=qbok_^йFVylOKw$3fP$ :5XkC%w]^ 99/ª$lgǺ}#O%>/^EBt?i0}>6|^~c[/ol&}G=?AF26f6 m;W2I[g~F 076F<^ƮZypL.z}w~Lռr=):y_V=?FVKՐl  kobB=MZ伙Ҟ.Pؙ<Vmi#[QXO[s侶=p[^ ! B9_:a3 Xt^\QPR4l(hOQVc ݳx6y/P5sdK"N%FƻykK S<@OrKQ+;I]t,3^Jյy tS b2s0_ow&zL6K Ԩќ;ރHd .dD&%Aŕogۓ޷q/>6cϢ]AoIR}9 hn&6ORmbPt]LK FY=\2O۞g~K9Ԟo5#nj<R)Z]D1y꡴oC8<_-y> P&n7k6'0Y s58u$Cb)d,Òp>F7S9lꍞMYB$~)7^'7ǘ>ٹvsVd409=XG7@Ĵ-nۧjpumOEyh/L Ԋ)tPYͷ?Nɀ2)G L].;r3\خ=M]Ess sS”Gn`è%<C{YXn9 > UgS;_dI ֗U;eUx6,;"1W^"Pr%O= ?KsEnmbt!!2};CQ MvNڎQNJgۜgrz@yXd #x!̽_,r̯b)IԹE֋˝7/gڿ\v4cu$4.JcB97suϦWoF/f8: {'tLG<,o_Q/_OY>8\yv.xpCзkYŠۮ0zӘlېì' "-8s7d:\ˤACz\~ʣcZ]l @[t]g޽tp4քDgIcM4^}E:|"ULYә k~)*>}͵Z*9LdB>43A5sX6 Ǡ 1pSXl'0<0l̙\pF_j&rw+|a'"TG,.{/E =Yqug_سk)Rv+Ny3fMTJdq\5u* :-j矢:Iڍ# hdA ;?U >eꌊڪE d Bۼ"6ўg]S'Q]\9lmjSj!'/=|)V&2e\ρ[qjX{-^l %}AԀ 8g*7^w6=mU2>\:m{v-绊 0;UNA|߯[:{fߖ*(*z2s  RI- wC7*=z!C.nx/5eɪr0)>eُq4N\Ի+>rg-m٤ڈy*ο{ç9ik=<$O~?}Ɵ@QĤwB\"wױuL]w!/H"kv:OϘ,LV|8qSy= |m9u[#ۊM$X֙ěbWXw68ׯK4|0sc/Z,-ùiLNoTlZEO/U.~fViG k]^Xm&3%눈9 `.Ecvcqcxx<.,f!<^7KpcQA+Qg1XtXUBq[rX@w,qUc;ǥWA|b !{A`,vЌ-(++TŊ+$;k?%[0Q TyxDEE=<ڑg(`:;cħHLHhr{Xք(k&Xog%FIBԫv`-P00th1^QUEE(DWԊ}Ѭ@Rq\1_Zz5Tv]k EAU*D ];S|:o൅ vez f`M Hp'}ؕpUHY6Q<]9R?NnHj`0 <N ݤ3[,8vl6뱱֤+B:nGgǩv'0Cך&<ք} Łwf~Z&@7cy^.S:eP6#;eF)[ B=uV9%8h9xJb={ucxw?U lߙİ#uΗB!8#uLb1|j*fү )yamm@++Cwq7렎|:%̀䁵ҹ ϣsǎ}J`M!Nx =C7epSK(c6i=ˋx\oM߼ēYZTppjL(Lg%URP8}ʽi( ?09ydJHsћ^O_~cW^m 5 1W]%(+-߷<ʬ WnNlf:|d|t7+&p|BO7{zGym K!BFI_N3t]qfˈ6,85jVk8`IԏjJtPq4b,sbY)*.%"2abpJ>[!N *Q/b;K,{/`jT^ɷ'r0oW~|/ݰ g }k?>^y({65J#E] Gsnd\u|`Iτn/!Bqbsرۉ.]fX%ӂ?ٱbayg͚,4SGhlFI)zk[:q K{KBל'p3tÀ3nco !ڃA}<(2z^gW_~'_7GyDtXʘqCV>9%ۯW>K+A7uGG"8!{ }˂S%FoLm_Oc3seK!8O Sn[Up0G}#<" 82\zG?Orn̈́4{'=ȨHTa 4g> иeŔ(./\?R @#9s`o$ GҟG3 TӞ%a%0Іj2QV榬Ws00)&n/LE5Z!NDb__QT@l֋ffᷯ2JFt!O*?wu,A8oN~s Ut6g'A/dǎˆ~f/kN6np1wA%AZs}3V6pDr3aΙz`Ky쮑8=s1 ` g.yB!D0ZQFl8lu-V0JJ(3 kQKHw1ka<.ߧ ":pܕbq".6j&&&C " 䗟 0t$^EZZ !ICd.^_]FЧw ud|8Oۧp)$W ߽(:vc4t4VmXpݸ_1d@o?3hzvGM)!YIeæ\jj}"P`Kx/h״F_' 0{|@ӡ0nof_RnUYZ?|*c&pje[f/!Bq2|01Q@25agƎf,-+߾O'7e  `Pw)=o[~N K.[n7>6S-!ޔ qj~%&' b H@m$]yn|6w :Lf` ۆs~YKֲqgxkDFvQPTrы )2'Gۆ]_<?ߛZ7'?[_cMƻ/%_CŹgr^~R./ډOb̤J ΗB!8ZT`1+Խ=-nb1_L+2.թ$%U ^<2c(n7q>ay)*Y]ƶxXBQ '@؇k 빋Xn'2>zng00HGp53nOE#I݊lFY'?mUNm-1[˫$ë᭯7xcQ9ԌVVT|l^@C3Ŝqْt(Fk ::0<݃u7F\Z˺VB$ܥ,F$ 0z{^g{jVX?-exB,o?y*a݇/67jX?wŻ2'v#hqWl݆:^[w ^:/ ]'8 _GŚ/Xn4:G69ؤ"6h|u4cy{Svl~tj dSV_}[~4档`)vf|dߤh;G1FA+xx7t{rX-XIw+E$q渎 V /!BqR##w񷌞͹Ե1nūD08ct wn=*](XB '[>+R̩ d<;.SB3(Cy5F%eV-MIG}/6ؽ 4;pRwĄξ9i9i{K80L'NEumbeqkq:TO$xd8L`\g9_B!->hUxB~/2j-]/|(n6]xi}\Yd25>-_%}cxv,4T',/y,?گGkBq򕐙#7_[Aa!E /;Ǟ{'k6c(fc+ 5v*cMUKK.K^yϋQprO4(d~Ƭ>sGFPk-Nf`+^t[ݳx6y/~q0Nn.l-V-O5sdK"N%F\YK!BG0= {X1~Ws;l+MSʤ oϻe-i%>6!-fx kǛҷ^]= (c躎U٨> hS\.tye5!h~m [%ڢy(.,S>ebb%Ci߈3)⟟g🛯aũ#-NY~&,`}͵ZR߸)/,gO~\vT0 qs( fYYK!BYP:^DỴ ̟b ˘KH k(g,4S<#˻6n/J+ٵz!*{ALY\6e*c9>Q50xb2(+ 8l,#Q)J'K0ۋK{\xGf%};BmS!d zJ`M!8T;qDv:B!1&4MCUjs~߉J!B!Bx~Ph{0o: uuI K5!B!Bä( # kKhӀvՆMѴ kK ?UUt2ƚB!B!Ġ #Zv6&K 08tXj B!B!a L&A!a`@Wq|L&EkB!B!{*K6Zg6C5`M!B!bt}=o?0aAw !B!B Pg6\i8~F !B!B!1, !B!B1I5!B!BqD[X'B!B!݃&l0 + քB!B!|_URKӍB׵(P@5 3or,=[5``M!B!⿀n_3/xбaQTӡlY#BF͂iqL aT?;ZP+prb($ 0<5 քB!B!qy\JndaSpGHii,:)6I!1=VZϼ~-cI&B!Bq {uy w?FƏʮ:Bq]WC~o+qތq\WT;mDȄkq6l܈0 22ԔtBCB\ քXin GhB!ks+O^j-Tk`M󿧑4bG}q?CUn9k0ڲ_Բ,CB$1ㄓ\J!**}=FdIdQνg)ޘ U~ !B_aM^}a ". J!jƎ ╻hu< IDATIOaNz$0xڦM쨮3b^w?cٷ_3.[BW³7꽿XG=z;(*fUEWUpz[-U:#9vB!p1vpC 4Ur84)6.789ef!NЋHJNFQ`r֬^d"33,̞Ë/HnN.vc@eVK!GJxq<Į׌i٤ŇciI7y+ϳ鱭3W_Hs Ө Q^ɱ#BћGۂӭ;wVKKfM0Ay]2g"vPWWGxx8M͍-yqsٰq=ח@BB<l+Cij̓0ƨoy/qM w3ipz].,+NW+.W2ㄙ,[5i%̌-B cqt:_|#x4KDGfdÎf kq8p{v= qć8 vTWQS9ؠ:ݹkTӈS=It;ٰol1Vnpr=Œuq+Elog !61qal]uɳA_6׳m^\cHJK">ҭvl^ƈ8m=6g:\X@G&WЂ?#ǎ&վ:g2\Ԭ'S -QΚ@D`ߌp 5l?cx\[NEMm[Pc&$p1 B! j`G@;zymh.rb;~6xN2Eϭ%۹|v0xc麎]݈H64MjtL քbS ߾ʛOIypu L%/KZ }oD{)U|;~x~\9HGyoҦctɗ-|k o tu3 Ԩ3&s OQ&^/y R~b3)bI]~6w̌WJu9INz/y)N|ʧ%`cm؂B_krq6}R_|+vX>P9,ӹɸ)~; Ȁ[8vVֽ865k'YgsWpb\ !زxy~!#Hنa[ϙ11 o3cxuf.5fgU0 MkBjx4`M![Dt<׫zY%WM{o%isg4^ʖs!MKQ)XS07[+[W˼.d~=T/˧M1h!Bk:!Gtwff zoN3GxٖY=x2ox=N'ǝÜFUU^/&6gۀ*B+խذk?g3þfc6m#_,݆ș{1oyKJ9cy.扏ْsٸ2DWUaʅAA.|'?߅n ~]mʹ V=>'-If&M"-.bJBg-6\%|TRHD;Ch쩩Zg|}|ԑ-\x{l\w'աx2`d_N L.-ss&]o*.&1Q B!ıIXf y]Gj сޮ s=`a t ky?[q\z]dxf x;9^rltk!}PO;iʪDz,Pٜ:3dǔ 4d !/`3OC ;! I^JXv 9cL'³djo˯ 06f f mKބR姠m.q߇."oApL%G@} ۻ\*#olLoK..s$gbS@êy>Fz{}(ͽG%r3ݿs ՄBqq=,8(o?h>Vhrm_#l\'va Lc˶V,H}?~DE0wf۷S_s&wa!;^˅(T5!8f(?x>>$F Z_R/eKa4m'tD#nJաc">}񱺨1^֗mfu2O$'p&&wv}=:&b'M$BU^ïoz^úM訌NMUՑ$'QHgj :4h.*`Jjf*)Ii((H/,L* MbHC\=8~;:Ƞ[ #%9y|5k/eB!MC5 C]`8B[}}_cgrKaao)HI.䜳瓘dnd2( SQQIbbҀQP!8fmjA+Ỷ{g7 lJTN&l&J'sF"<^GavtS S'6Q }Pߎ12h˳< Ge+x'qUu-Ghd!BfЀdPuzjrк^0Plg%qTNE ([w{gsj/V71ydt/D4XH ?+|#Cq \N:HcȉN7=kߞ[jdr_dӖbJ.$nOurj&cMJ )آ1ed)y}(9L{:6̏Ss.9c(c| 7~_no8f!B ~4 t<`\ؐ+7;&6ijJ)i\E !ıhfoP6Pg{4R9SRcf W_؃nc܉}1E3z;wG f ШQআύ7EL Q6EG5rcfxב_梶Ď,LHơ׺BJ !9SHqmX#=(0OϮg_ř1x˪~ +B1\GpʷX[Vё?$XBptAEpb`? 4'.-V4XrJB^TJ(qٹzt֭C~`j1Щcµ̚3Yw~óvAFڨ2O90 IUJ 6֖&o^Dr%tfd񱡨 ?sXa0~H_z* ъ͸zYV J(#B!)0g#P C7{ UәcP ^I&Õ텟oO0r-s`B smWcXɘ{1!TOY@-]Kσwk.'?b2w5PN#ծm~ ww}_Gа:=~e&&z:v_ӝ=|KG Y;O!ݮSS_Qc36؝JhWR*F1- qcGoo/\<0uo&C1;f\ !rzv8@ ?v Wa>YS3b vc:8B/1քb9n'CsSm}/KS.EO%j SNkРFmׯڇWP𯛸$or7%lwZ=:'ࣼu!h q*wC:9 s撩?gqە|V=K.]1"0ķKVR YQN3yz;i# 5PF)Y7^˼+e2m+Ye@IwO9McLwW>)AG~oCKŧse߯`Ǔ;~$A|XbrɜH քBq̹r~ Qs~de5Muu5+m/v`at}016憧$XBA !B!׾o;"ӰNah>cTlK !:n^ɣ l`x<N'.W^cì-v;6 UحܫB!B!8F]x|0[I"cAEM-AX?o<̱ 6s͓+8ch;-_A(l6V+z~_kB!B!ǰ "ql.{;v$lA{c5EQ1Yl0~\W>Sc4,?X6/~Tqi8oz #VcC%B!B!1.+!'[eW8B dcQT *&k[GeSM 'GsX&@M}뫚$k8!%8Wm`M!B! yA<5ڲm6. ]Dcnt>'oyﻭ ( &U!ƬH^9 06H&B!B_Ԍ0NB z麎i zzlbX]=Ժj-T քB!B!a @QTUl6>KhD$XB!B!d}=HΡH&B!B!IЬg6܃$XB!B!Gı !B!B1I5!B!BhwKػM5!B!BQaZPM6ѩd='@$XB!B!caĞZ\N'j!FUM~FD pM5!B!BZjZ61rC1%|>ٹ}.g+#FC?\#Y!B!FZ61190x=qꉡFQ aRJ(b $XB!B!a{뉎ʸjoOQ8jk ҡz+ B!Bca躎نj_ يC`M!B!,a ^GbhQ[65yT!B!Gad(i=I&B!B#j$B < *B!BA7zkG !B!BqQP!B!BaCב8Mذq#>ÀRS 9r%XB!B!đ5r5O3u{&?B8ao^\o9=,[5M-{"8$ (__{KB\3N8ʕ`M!w֖ojw#Տ$LLd}ȖT=>8aI$øB!b-ۨ5$#}^/-lٌ;. >lr{4pDfų ~rR]EʑNGϠZuBGFxg mO<|\@U~Ŕ:Ns 匠vGB!b03Z3<>w-wb;- ;:o/W= 9}F~U{'%Z8Lf̪n6*?Fw:UPP`Gx6Vl )9EYz &,233{/"9-B au/qӭ/RjM| lېW}Ǧwyƭ4?'.L~5 /=OG?Ask,9Vkȉ䦏cdչkYyk>oy蚩DkB!8*rOJyμ^nۊU:)=l͌L;cVd)_[ΩN#(0?AFZ& _OfFʖ`M!*oJ[!(Z~<{,sYυ+M"ySfiQ\uv3qjg]ϲ3k!2葦O>dqZX : k(oC1lGG]|.h@=k?*˅bjj#"<@f0e˿&F IDAT=-֖J& :j3~rƁRStf>^"z/D\Nwb}Z64ԭD%-s#S(֚ חzbWz5a %! Zx@=ٽG w(V_{'=e #~!Bq (G axyk[_SL=i-.ģ9 rW‡fsBϧkYU؆XHI@G]:g3,aG`9~Qpk]!մvBFN 3s<=+Iou~O%'u<|M()f܊XS vhajV"yn`:\X']kr-0T4Pnkh؃‰ĤQhp:c2jDD$NgaXis:\kB1di?g)p#Ϟz#e~c)~|mq~* nO;k%{v]8X׊3G AL_kީ[o[sfB!0hMicxzm%yl2H/P6lL:=oBV9!k˷!&L8.'ނg O? /sq]ˋ?RQ+X˹c{lkRIޯhkh`E%p|mN=yvGyv{k8g^˝Wh+ɟų ;am5-Soywcܷ~1&;G,%hfS^RDe./儨c%>R0 MZmx<^4͇*$XBc-M,W_gaE ph~-+eU2?k *|KӿO^4fͿV?d+1̹rM9̛:۾[v Ԉ9M$٣ymԭ-~!B1 )Kl헑JԷ5MTHWe%va :NcjTkI<Ŵ5baQٶ7K|'rIamgo?oVԬ⩿''Z<#WŞ0+0z1L9\ քb2KfRW\sg3sZ6q>o㳜xɳU4tÀlr[淩޴U#.Z ~&oVz 7=č%}J7]m]؞x1O}ݔz-躎B„LNqocD3'ԖP%kyP9@hh3G$F+_Lҥ Gkz̹Sq/y.?cҤ U’ ~pdg5c}|#bPݵTWW8QMV..^|nN ل ^/a(/!Đ16c>A*rX7J,Xȹ~O Ӭ>gS.̀=B_ f W!~Swx!{@!8*9sdT4Z8 !B?^֭)|GcgA>49ĪJ\&9a*<괮2"Voɴ=jfPYpi\x|^|ׅ21nwwnM|Z >4řGh2uk9\0Q4YSrD3eT4lms %zWiZm U!i3%U< W1-͊;ini~7+>\O{;5/>ϟo?W% Ь9Y\udeۅ-0-ZYl# (ܙqlZL}ϙHlqLxn\.@H&Cqʳ;.oo ;֭{n?_GSdLM:tLħwփCzJe}&UW_;D[,7;a_!B cҦeP9Da&|j94O$7X]+i+-cNfāmcƳ?~ ؒ[]3>e\"c{^"X ԉF!hTo^05r ׹; >i$m ލ%i JjHLҗ#};>w+mmx4d^õPrS5lGؙ<£c&a&_8RRgp 9$&b2[1L(TTTXGAbPb=RrOt*().%?5|_E-l=ar{׳ E\I'_ScZpfǯZkKۀzyN'NP/B!?h`OJ+n-2'OBk1߯=)rNNJJ֔{<~x(]SL3TS:G?~PP}a^tLhH@mUFܞouED)Rg:dN¼}1㦴 yvlt-_RZXsLZTBnbRn60rB*9YL6irm4ғ0L~ -TKNBGLwI4'ΖM-ܭ]\-\mجǞa u{d͚50m_z !pډÜs.ۋʭ'‚-6t //yz= bbFlNwjPi~\;[ELANMmB!2bAћ[^WHɓ6!f`z-E>Aab'{Z*C~@ԅl}/EtL>ωL>Q1;BDf &%eT逯t2N&3΄u@h;i4reX&p/ҽWsδڨ^_}E7^ƹ=[-(\C#Sm?R i ݍ\kUs'O͛739gʀ7-=քb3$;^ԠOAU . ,¤D½{V7͙a٢Ҽ2Ʀ:?l5B!B0j䩉*}"4&s K OgE[J(#RDnŚqlf5!] n r tˉ<&G LicN6?B;!C,Z[yջ<.Y3oZ 9= J) Sжţ7sݭ]K}+&fkȉNsD+ycэ,zu5;=όM_osW3=`_!B {MD՛)|#] YY$\ԜMvIi ' ͤ0`S..L2ώ[0n|=^yxe>٥McIbKL]CPǤo %8XW2-F3=ް~s? ^<0roz ̘QC*@2 ۍa ['˅^0'c !eKnGg3jBcrh&voHjZ4PӸwqBྒྷ}]k^AnⒼYM 즶2i!}'Y%h q*wC:9 s撩DqpC`/W/1vxbZصe뫚 T.=\3y@l0/B!C >@a\;7`fN2^SLI4o=ut.SGg}{ <[+~E'9ʎovsKZC%+4 }2o rrH} l.Woif1+PGy4&Zh FlEQddM%s2YWG5|װLEEǹY݇e̹\zr!x0 0`x0Nukpr]$XB!Jy>i&.~Wƶu߳c]Eȉ?42{Z7{x&Y![‚+s2oK^=Um[V[vW\ 姆dGe撷xY^ZͦUlꪷ f>d6I!r !Bp"SR.S?&ŒȔ {)d$`[S0hν!<̳K7%O<ƾ2t}<"}[_uJDҩ_pAG`=9Sz|JQTo/U73!7P_<Ɠ#^_Bw)YU8_Yg\uW/`̓6tr5<^/&3-EEF.,F,[Qm͘Xؽn<' هǽzbbvn+WTJόj f O>ఀ7ްفlv2[?U~+&vbzgĘX>\n ?V֌{j)6Jۮlmƣ:˨`ۍ{l?R,~;,jӳՎ κjv6ѦpE0:&>:Z况KLd2~K¶җPQ@Q[xL @ЉJSXsStfi ! ;bB!āZѮҏGQۃ ]̓jBUutKUEQ:mohvRܰ`0 <N @>*łnfA=XgfB!B!k6 kqiGwR͆j%88[ϴ`M!B!GKC3 ;T !B!ȒdM$XB!Bw|U9w$7{B{qjk֪jkjZ[ZZ e$;\F}rϽ$}25 8=#׽158m`׸URb IƎ.PQ.c=j=˲JUaຮ*+QgE8˲d۶SHIՒsU[]%ljuti'*-Y0WIJJNm۝:TXc %µ|}~Uo:NGNx{*5=s$5l E<$)-#K)i2H?S>mۖmx<,Ӈkk`hٶ-u ԰CMZg$5:(!`&. Z#X{UdS'wǟ 5 #vG.b]0`G3´֡l Ԍ1l^#ckU]YP0(u:,c%4IGF@6j%jUanJȖ[i`Osb1lԆk 6*'PRmlVWZ4\E)e)%5CgjɂYJLLRJZz]7\#X UoܨŢM(溲,KEUQZԴNIc亮B&%%%1Fdr]J]@]ב#feYrZj9\c*(;c܎8t m[k~>TP?dw` SAة=b7s]WK-ђKEe!)3##.ҠuAV@)wt=ΐEUY܎7U)$[YJL,d|n}?L u9|32$#-ZHozvﮣ8&sHV.ZeU3zdE<^_U^4d:,VqWN0 %ZN}(8N6j܅ZQZM PqjގG9EN|RW#5moݪҭMZUfaW;GlԴԽ/Nպ7+!Onx wCOW^ۖʶS-ے$K(W+_`bv6Z+6Q#w-]D eI,Ӿш#5bHp zg4vX%&3O:5%w7?Q+A}FU$OZ֗3kʹWuǬɚ;S)K>hnԏ?{WH @1uu[k353unJ7Z5}6a~6]MzRv ^ Q_*QC{QjՊ[]fQ,YjQYYAF,'T{ >tzEk:7tbn~ӣ\idѥ7J.4 NѿUwа)t S UO;_E IDATVa@'7zL2dvN|.zk?zN.|  )Ȁ\I^u-Η , +jT(Ԥ#?Ӱ#Խ{O-?` FQ}VJ_{ӽu w.]+=d':mGI ttXV7AE5jDepWWMeK5wJmjRԓ<| Z^J*PtyLH fhƲR5 OFUΎUt歬PIVA;S~Eq*<(¤?ueZ6mT^Ъe+ϩoFm!knf~~Voׇc~K]{>TDV7Nh|ܼoҏ5=r=w]:E7Լ6__N-Oа=\e#q$I~"'&˶"X_M¨3N*ܥ SvpۨnU_F7W\Ͼi%ohǦ ڭѣ+:TFu˔?Q˾|]O1WE26w,O^K2 C?ׯ`l˚Ru\/POr^]pλ4隨S8Z$V iԌ\_\^:unJ7Zzѣ7"vϾ]{Tӻo?(I3.9O' +RfGz4?S%IF5ݧXUtg!]SCbMz 7ErKEW馞cit¥ilGɽ`y[LcC9SJ+dwֽK֦nͫ[GG?ܣGx]rthqeK]Zmi5tK+IPgCJ˧WOS}-pV_c*45a^\H)o59Ђ' cr5|#z\J}~D1{f͚Kǎ1\#۳ןwMFv|ܲ/HP~9pݲm[hTGMv@KW*f$OC _>=Vv kԆ%_˲sգe{g;ziV\O/]pty-#FnK˷#2ԶdKrV|GTh\aο-./˾r=ډ/)0B?v}kd+F,NbK^=P]~b)a}>w:3wEMCW?Gо~]r[ZYcueFM\\GyAVw=qrFI/Ԁ?Q6'>jejqL6/`idF1m73M⫚i:\|9țK]w3I)yq%uac+`j9glɫՕ {{w=%u\w/=>8:#Vސtu~럚Vcuə+'(VQʸgWoO#Cn:JYoLT8lx-[ՌwzQ#4z_o_v(1Hj9K=j( uU7G^'ףH4"(#_k)UU(W|Y tg ķ!T e=<~Խ4| .SpJq]*iFhp* ^H3&c)yY:g6tw*=QCIE#RYORtV YߤJ~v^;FqE5 t߾5mE4TZR}KQ͛QsAE"[zYS~ųJzfl'"׍mטȖuڬQ2_\yΝy^q=z08DO?VY:̘=`5b>iM1cF.X<sOCJHUj Ki-%KR~~N[P=PtU7OCg^T W(zfn,aմk=]WV:;闺Tj%q4l ]vɏ$ipmoj&I=z}\dd6M6F!j$Rx<,Kj0``kY J زdklTHݜ j%$+̈́޼vݙMy =i\ʺtvc%(W6֩y6icmmTye7Vr.xjb522 WVu;]'֙~t6bccQNvlm[QFʴx toEz)F:D- }iw3h&㏺e & SW (klj6_INjj[P)mig)ڶ]*ZP+_.zEZFFsZPmձ7ݑOO5AnSxn8Ɯ~qa鲋$ǣi_}-U6h $)\?SR 9A)Q(ؠPQ`¡FB GcdLm[TzzkZ65蔼/Ȗ]Վ4dmjk, I 9?0횭D^)ڑ-wM?EK?н-6)`/qI/- +}Ejd hlwiO -+}Do`g #$]>PNXP;F8[)K *wv2?8NLe_$I6h' ݋f !ݞ]褒FmY7_o|0j.;B]q~;ҔierՐo_ JKԴ>^3aKr|cAV.Ӫm_=略J6{4^e(#) ;-ҒM* w8EJRCN{=̝NwLt:v`0qܦzweuVeCxndg('Ӗ䨲RNmulΓ} kWAM;rwVǮg4赵 NIOyy4z'[9iA9ExckY%ӅUj'WttXcu[Z2vgLiRCd)35bλsi^|,[V[Ҍ TmdejՆiSTW jֺ}9>WZmz6.Js54MFS.M-_L=vZ[91yM=׮2Q3tZ>9:]6,E[/]ͫf(۠<_^gM(u+>M؝9v lI1-PMkwْ ת:ԞW$hcfͬoZS辏Akt%gkť[*)mqotCs"wAv[ܾO^d,n~!<y-IN*@G z^N(2ҵ7"'66lCX/O럟ޅv8Fo?s;D_[.w5d3uH%gzvȚwAW] =2owBHfzhJӮ\cGsu~o]//Sx1FpX)/ uwkЙ%w%~Ry߭zT8'Eh/[UVڀ[aI uIԂ*yvԋ*w@v`y.Х#ү/ќCհ|jWL糂uG6>]w4]yd|`q:ilOe{êX1C2Ck>Sp=>v/GgjWJUi5j_\Z O?>m՗hڑklB\5U՜Ihʚ|g'n Һ{iez/P!RX]qMo}RWRL'l)#$IQMmı_-WkGhDDJ鋉3<8•q[ 9A_=UWyf=u.yN8UO?u!q.UΆ|ݨ&TH\mY7׵{ם?W]?a'O.,DD%QL2ᖠ훹u֩Wb@ܵ@'g ҅w>>{MϿ&۠ KfkÒe;\'q.8er|{⪶n+Sw2KFߗ,;ҟnXGTpZΣsȑktwn[|L~8[&Ev]Y;ky[E^;==8=5.V wNR]y-[3~G3zw4p¦p~te:tΖ!VF_yy\35͙Nف|8bgixj/?^S:AkVM,%K.T6hkXS(knUNwz\l5fs?l[a}Ƚn=JMeMd24+ޕJ(5[;wz*%=WM׶3~r~Jƾ)>A&<>(垤k\Ƒ =.հ3$"Ѩ***d{jed!LmP/o^@aE"AE"!1EՊD*بeYuKbd5gkN^8gFzd\Ō)kQ{nF6!*+!E9]TGҴUwMř [4NF5LxG&|JIh>ߺS;=um}~*X^kKj4^fkA"XtV4(b'*K2aM+JNM@Z)y'9ZS(%e0?C,@c亮EԫߠvKJQ®vjnZ0’/)mI VS0}IJ $7`ChiGĀT7IV|L#orQ^m֤ 'x٠5%5 RsT$;V8mf˟"GrB FZF/II,7PcP:Kdv!m<N+,PCxdVW8VII|r{V,ɲ7%x&6,W=OTvXG)Kcg u핡z\= eOMZkV$ W)[YHKcU۞! d_VyRS Ƨn;<ε'qmU|6aG'51EiddF54jG+ͺB1ggVs[*ذ";8_5hwd9z5td5߯B|:FE#avz}>JLLTBM?&Xt Wt^Xed\p^zCRF1Y)@'e\N| ,RBB~[LkN;;I굛E3iؠ9'iƆ1'(SL f&Ӟ ELuzI雚o$ɒT%15;@ocj,Wy}X('/GcpP"Y65@'UrN:`.Φ#lEg]d;`9#f\ UI 55vtA%lTb %kieYJRuU\઀뺪TZfV۰,Km+)9ET-Y0WUrXG'*-Y0WIJJNm۝:TXc 6m ײsUYAkW/8]p=kJM/B5` lzCx$IiYJIː1F6 `i۶lۖeY>\#X 5۶.m 6j=Tء#iZ'`ֿ[Æo 7d"X`o=vG8q X@ā` 5 k@8q X 9 IDAT@ā` 5 k@8q X@ā` 5 k@8q X@ā` 5 k@8q X@ā` 5 k@8q X@ā` 5 k@8q X@ā` 5 k@8q X@ā` 5 k@8q X@ā` 5 k@8q X@ā` 5 k@8q X@ā` 5 k@8q X@ā` 5 k@8q X@ā` 5 k@8q X@ā` 5 k@8q X@ā` 5 k@8x;JJJ:8(k ?r@`*(5 k@Xc  u5(W0:]`ISJZzSeYj6Kq7%ttI8jUu /hW{5vhUuF8D^/(-#KI)iZpv\c5v(SQq7BUTUQޮvk й+llW5vp]G6kp<mW5 k@8}:7'pL)wbk7ڪzEd+7kRuMb)93CI ΚG+ YNu]LhDXT>Oe PI^*}Z4騢ݚYVfB˒m[2 ̹ӕ>) ǣp$|F %[<=%el|=e嚕ѠCFHXhD>GiݳIZfEG %Fz XKJV m<[kgIyõdUcG UY|iJOJ^ayHVœd3:mKTe'vê.۠b_)꒛,gE+UZ^`D| f*/?Gi6ipKJnm%K_/\9Ovf^)?XX8w||f:ƓMO(_5׼9l}O)Γ<߷UڨPmB*T$(c\וq]Ŭ橝Q'X{sԥ =-58eg娺ZMMJǜ>@uuuD"*R%ŲR8"+#Cqj)r]W*ci[s7jke:`iSRcckDdutiAu]c###cZƐk+Iu?7]ɖ$GjWVUIF3"3F5FۼGMM2줤in֮k;mEvi^@իWkJNNe[޽V^ѥonPXrdѴ^JIIQm]Lxl|-nfg{Y2_6_+;i\KjVzٓi[em$kVeaJR2lQEsB>udII61p?\}>Qsf=׫c:BbEfΐY!۶qc7/zW2UtLY>&lGCGZ\GWU`*XD_NA#+U{˄b\ )V*WԳ{FsMVR-UeYhsQLB Xu E%>ۅ` pVuUWiӻ{a%%%vtiA%a@5~9YdYNqcI6y%EZniL IpXiCmw>+9NhJ*W(%;G٩yMD5 ɑd'uոW}LvZ7u|F̀`-^N Vj2DJU>QA ?+/JVRMǞT͝6O*Sמy^ |j,%yLVBfbf]^Q#e7O4Μ%Tvj"J>Hy~>snY?(bN*#X _JIY4I3NEA V*,Kjf&)x` z~U~> `¯u9jki&"""""""re**}ExRR0̴4@He)Nɣ+n7cK' O Y)oz|x}PaMDDDDDDDKyI{Qd m}^I4r&"""""""rH=h7CHNSi54bMDDDDDDDDb8.цm&"""""""taY.WsHkAjzi=4TDDDDDDDD$*A58&""""""""DDDDDDDDDSAs'Qiq 8ST5(, des[t.-J+'k+""""GU+Wݓ(-,%IJF6)ڗ[y9_c9B{ٴf k2 n?ٻH/ftϿr#3EYz[w"l$ӑp|{_t ߿r.mXK')qy_mo(DDDD i Sk5;:Kƕ{Þkd:w!3vⱫ\K>z_nmb5x_-f[}9Aҫs49.7nv1]Yȶ=6ۓӷPc^z5>XJo0h?p<7~zR0tLW~ cD-:w[m0k;ico(DDDDD"?#ʝ20R0L1"l'柼=yZgxQ.iT?0O/چV>ԡlśIc>z)ˏ(edž|7,3>߻|9 A9&?4,ֽkg M3u`U^gY0.<w$g)/dw]bO'T4giv-+? .*2yV!9Hco(DDDDDb2AM$) pp kӷ)Xcg_scpND_;ڮpd>>mξ횣~VꩠWe { e8`a|->ƴiQ =4m}<[0Iu.w Ŏ/⮉۵4g Ο& ҧ?]]_*d 'E|yޱ8o=͟XϽzΝ #BQz(<|{FoNSq9-hQ`\xlܸ *++ B!L4#qWX<+X`:7Q8o.+xי2̚׍5lW/{{㵿Ng#]8W22EJ~?MЗO.>[W¬7ߧjXAU Q̬3v]ɤM3i,za~^ow"p}OS 7XȚ9Kq>ǽe83|$ng!}g}}nS MfǑ,#Ә:sGKomt\"c!"Y5ۮ=>3O\r%W  u_Ȩwӷ&ipF]1h &7n<~}E3о)/Lg# )#rQ; Cȹ ƆqnC; hd12l5-g钥,z.[KyY>uXbb ؀,kI!vq1{mȪ_V?8[7CVn,`Oip_,f¶i/v_?45}m}p";0b #D?g'"ލ()!Τc6y'2knx> LvbRfC;9b մĝD%i/3O anڋF3,B!ǒG4݅E oMi_å88RjC /'2i I9Uޒ#+3s02ɮmncuh sZK~FUOt+IƂv78e;)|˛J ΢‹2z'f*:`-(`Vv`x:(rlp+ `ygv˜᮲ռx6~o{{Ⱦ'z.Tw '!r,{୵Hʡ8K~/ncc* ڮ{Xbv?"P_{Us:ᄾ]a幤挧ÀfӘlV~WYK7C8}69笆k6Lޚ>jTkq'""""G᷾coElٛBL`/ȉ88vpAr.]፹v%hP19}S?]nw&p8MsZK~XLSRB .s1|xe Kq'}<gʗ|s979d!Y*\v bvo Q}}xmc\_fݼ{ H_34bPTU|$լOXS6q<<}7;jCw׻ywZu4yE7?VOFOͥ b^w'bVضCVHUSXH;c::s=bfNbtCeN*94ggs~׺  : C X=%UbSQqX;:J*+留|">L}N};ֻNp^oHx|_yv.YA(fO؎EaCRhP0흪Jwq/紆EV3].ExeIeJ#W<:wmv-?|yK}s/?!;pSשEѮBl ~nߜOA^tdLx|^1NR>&ݩ;]\`|e8u mVEF~9 ߿׻B ~6+^ߘ]`悑5yy DȋNm{u1Gq23y&aTld_<'-%'>j6bU9(X"r"]|I=rR&؂ex8q,wќ7̇aogƴTֻ>S§oUUu_)/&o`{pi]G]K;96ɜ+޷GpͧeXl/.8 W?ou O16=s!;C|H2LВeeghG_D/4|8X;2j zq\Dŏ>J7kUYP`(e[DA-?=?|>>{==cUsZVB1peG>zjt;soSCU̙3᣿^mF ֕&_=Hϫ&2B{6hlO?Ĵ[泪ż{DN˨Ž]AW֔#a‘0QR سMEEEyG.1'r9~f{*gLų_}J̾n82 n~;n[99l2m!;7&%]ٿf}1xT)DDDD$6-69oxx65x&NH~;y}Yylr.i{bfZրk{pbZ&W/DšIٿ07I']5CM}X74Nj*|2n;|ij"yNC?> an|>nuѥkF2}!<;R8鷿@P(pHR$3]^meݲ=%?<hzzz$_G߰'ײ|F>w+\>'T]Mt6o uG6C{/)sy V׽x&0;p"Tw]/zs/?}Un%9pM\>O$\']o.d7X1 Oa_]èyWa+KX3E<ܮO~J@H~ɖE?!=3{J>aHbsn>eI9=~[O i7 fʬ݌=`3<=ˣ T4 6qvdaƣNq'""""i }1TaHniKa)8mcYV-17n6z=^r'AJJjbeY!x _gӓصua_<F8D(WA=!ɟDh6,%ɠsdAAoj*&\QFv'T Uv'R֝吒Ev>@(dqo<|mE'ŸQqwN5e/y}> :OqlFGA[$v_ɣeZk={7^gOQ!g~ml⋾C\r5N{ϋ#|8W&WbfdȕqlQ7qrFFIJ&aX!4RwV"f{;¶rpk꿊6w""""zOмv 1@.:Lhz,c [Oۅ@L8groVXTsjA87*dG"SO2; eP%ɱIE5#'Zk]ƉTa B;|ǥKD,xbADDD=Hhc!k²,LLt00iln>Ip+DDDD$H[uR"O&p$k |W$mQsœ$iW[XfYb7x" ۶)*,$=+XIڲDǓ$iWfi?55+QR\eiti,+BIqkV,#9-TLLk'iK3DDDD$H[|%5j]N^]lٴ۲nHKe\ѥ{>iY /(-ixR"mIS󕘞 rU/ΖMjz&Su|i\. HSAO4wSU'W;Q0ϙ   VלTs4v\~4Ӫ^p䱋֏oԝuX ̼v.@Ac5QJ7V~=xWmFw4XI"# V}ˀ䭋ČRiJ_D"ش Ud}  pT#(>[D '5:dgϳFz~VZ( I{6LG^FڱX[U-L||~,+G5*Pm_Q2x,+D%"JYCW䎗&#҃Z I-{)> Ɉ`{Yw>o`r"  AFJb=6Ϟ,WmTyK]oG2϶/yeDؑ~䕻T[z\~:&[d 5KZZt8oSi/X ~{+QJ&?oVHPTY6uWP~^-R'W AABĮM j| ZZcYVbe.]_.: p9Bt}N~,Oec2oޥ"#=v؎[o,IE1_bK8W:}GxWmբ!9$kzўEi57J VX oV Xo  Gk`Te2-dbcϷ \Iަ8ineEGN /vew~!pˏ<c;X];K (=2V ޞ+5L&KC`;S*( 5W^ך;_йegAA7.^)Z$2l-YJlgYegDN>˘AxZycհ曭6GaH:*{LqNG:яI:oUR燋#/J|=͟ss?=LXN?Y4JK,)Atϛ|#VXCh) 7^A/vكK'~R ?sRys~OA)rFw>lWS,v &*\ʏG'q>^wA;Qje,ȆΣfl]0Ni~\Reϗ`צ|HȔzJM~u?vPvY!4cArZ!-?yzQ,nL?XL,l1X@s__nKa{Swomr&8v~ˏNrlyAf>} J!0p9z!: %ۯbz44yUs~䕝˩fl Q q֕~(@54@X4ǹL>Za: C[G /-^KP!nX!Gֶ e@y߃vFm=)tO4tq4Y]%!|}Om. ,o~Wrei)}N~mN>qKg/,R p~TuhJrJ')xB԰WŲN(+c'(('y;aC-̲ qXaŖ57ƽe5ki0RuQfiˊvGZI(`Ly^ e *+@{B87ÖC,0)믳eϗn[ZD-=βJ{yo=<:Y{+#w=v{c] i /u[ |KT*d)B,qaQl|v2h&Wx8'<"E\fӺ5ܾo7e#\h_%?g/w?AaQPXQXF: _ ,2KգL4O*܌r)8 EAs_ٌF%2b"Eۘ JYtm֖`{gܖUùaXtu_ՑJYǥiyR} XW.˦@)fkg JfM@O6hc)XM"E43 b*liu룂e_TDTntE3讵]we25{갦Ϡ{쾗u]J(6w>A.&~'w$-~af s<7^ 7sgK!z Th^7:(kMJCb[Sܸ)1X/3y>8ELu.cqXBY?Gd[,%mRʲhw`㳾\dm1nM88tn~5]?:?a8_,Kb=PZ%"Hz6Pr@XHeLo(;P(JD[o^,Y$MU[iAF8+F>`GqN>DmKTt^'ªQضwğ5wsT'_Yǃtk[o QQ h[( uPykz>oysc&KTaԸV'Bll&NPC͢:,1fSJ@ɦif͍ W T5r-ju/~)c4RLUjX,5MO!nSwTa]vaH-F`5C}>N䵴 fY\D,޼iO'S*أLj0( އ){v{aӶEC,SMqa/;5‘el=g( 1^m),lܼ`GoNjãrQqpM/(p& ݁i날 H-M1ŝO"tFo KTq~0xb??3won飳c@/7_z8_wܱ85>S>nJ>|O: :es? A~= 亶ð"A0"?O4~Xek,V( z#^z #QaZ(FEhLC\@A5;-#;^K˫՘4#%E%B\e&apqEVa֏15Q rKvX< ;fDZE冝q ]P mEDkw:ioihێ j̝n+[Ww^rAn>oiV͍^ y7 [?AMHXFP|~^:Y_y^)?{b0G pn{y6*rڛ&%U-Ii+Q sµ]JƧXdm޵PGY'ziX~5~U,&)0l)ACh&LDhjNjf&+#"?>o<<_؏,=i{*/@ #oea}u¨vGfP C4WQ(| ɗ/;(_Տo=yNo+*PK|g)~ q˝|;`O>:z~Xē|=ʓ nv{_W>a~"Vv~|#MbY/Um|yaPP9_gQciicsEjYDOM4($kWDYςrU`ݥD!n[|@kӯ4{s]E"s\H5PNYU#Um>m7Dk/AS?Xq` 53HxŻFX*byUtaӾeaqNLGb:]S ea>)F|'ª'_<[[xgB=~gO q3<|xG2'x&`*y_c=n̿վ_Q mLUC2[L_9?rŎ6MV, [7_|sD6 u GG+G.ZG/0l|j&=C0-k8kTgC6u: %'QE<jX m%J~&16EcXBw{nS?D(̎Sc^$}3Uގ8(d-b4)ת 6_f?Nf~o5q]_Mв(/G \=<_<'JDS4ܰuD5B{'Rד bB܅[ָ!бµ mYDɯUe㳱M:nDIuɱ)96 .gLq亸~۲t=Џ(G6Zֺ.rR|^F]Hv}G6[^~䏤˷DJ$4^+W:ݸS lo*JuLʎQ:/H pF}?s|pFq/fl׭󁷾Ό{wM+Rag?6Klb?u\gamZǜ~~l wAYhy%X N"LAHѢEdiBZM[8n+Ki t\'@(8J-#L(Val7( ܠ#,OdP(Ė(,߫Q068O-Hp lۊF$-"q_&@Gl[h캀qHE_ɦѴYA-(%M20"ڬ.G|OSV\uDI=ɛNke˞̳񁌯$}8CS dͿPG! pq8w+\-sܺkkuP\Ç?>t7~fYlLė ᴆll,Cݧa( 7\u] Ƕp, mgrF[ͲLʅA# TȤT@,d( i:F*ppJԿ'vmM5 -Q^ X@j<K$k^ p(V`iE1QGeЧ3ެw~nל )%S[pXNt- /}+$Y%*oET U`: 8jfB9e F8R5LC '% bwmyv~-{6 _'x.l+ׂVD0@[blw#>\W[Ώ,+%`fY1A.ld;qc:n8*CXM2v; (a9nlj;۞.`u7`>Eu9e"104K1lJ[nSwd\"*~Ĺ!0vc9jH0lE.,|p"alڋ;)-ZqILna^ld#Ԋ 2%.D D2nkٟeF3q}-eɦWi>Uiw7LS?rnL{qbTBC;uYzEO2I['| \z }?M{_~*^,U[\.(\3b[?A 03|9^eR4a Aq}5YVl>AUa, ~$Ŏe6\\Q?lp%U]4 &Kt6ï7 q̂)N " h:!U(22S''H%nc:y,5A@: pt܆p4 Ю6viw`Ə`e3S1ks3ɵ΂ufCƼNԜhϴ'LDmcIKR_jS*)ߎ.:}'@eaMNƎ7b%k` py1>C~g 6,UW}$>PCʥ2]UlKDO~!FDKch!|V,AK1( +7_W2k\;\w89-QAdpc&)7JFyh/Qi~Z5\O!Q2xFQL\vtO"NNA$*¨/֏:- qhDX.aRK.6GQ.tJhG0[0lk l΂E1uaH{- ({(^6{; c#”FuXqk3&Luɹw>AVEQ L&WniL1#uشƎ:cٹ广 w; IDAT\so/'$z_-ZmEQsY %%Y5Xobw( P E`ΞHp+؂j"C`L]l9;BNkfڐ/ `n%i^Ra,lʊ qd j~]T&D&*KK*`#{ p"/LvP"(Vcsf6\,&ؖź6%+b6zr\3vID^i5X/.{7bC ͮS8QŊnu]6ܹ??|j֯H({Ax0|zni4% "ώҲ6:؝,`ּ.9ڷk#{q[A. .s0^uz(lMTT~[Pv=lR]4%l g`F1 h=E/_ GJ%M"TlŬQ "_aP/j.-GsjYFs;jLyzVf3Tљ*SAvtʉ gf&+>Y9bcγ}>ߏ&dBAZ,/ ` vηA)Rt\;w>AV_kss?W}Yg@ۙѼxb?vN3 >3~rmfzsi- d"ݺ~ǶY5jj%p)x9J]  R85.X \ R @ ;sMjgnoԛ#~PsnFk01pnF5xqLЫ8ڣi1u4A+r(5?$AqЫRr-kEHzvzj>߫cUP7TˌzLW}f:(MexWjZPwca~e9.vd\L(NۺʰƎS^¿jP9FavmǛBӯmqK2D1; Axk>eϾxi}w~N~xKONx'oMwuc|Cq2[x0_><<3۠b)ƛՏ?c̀ Tp5A$|Qؙ>VGaƷPa4aHWii[r5] 9;1V̀PGPH+剏26[d)髾fhJ˶SC3U?@D Ы6|cK'Ч3|Y1n8*‹ DȎWjuGyPr-+5 ^dl+wܲcN26ba4ZD˳DiM+Mw4S:BϾ ->77ԣ7{כ k?,{);5iJhp Ep-W,FW<ȗ'#ѻk7*{_dw/ph.w>~o"`B~G g_Qo+X7]'l{|zeNlߔE|5PAX5q_^Qvwyue;L43GaڭaכWoٙ9F=EWw;}$ xG?9^}{{F WAEB|r'@iO_;#ˢv;=o>.Z!#,GcQ& Z5ò|ʭי@NЫyTjsKܳ fҷ:_M0Vߦ!Zb%)*mSDkR+^ jv^iB?_k3m \(a~__xr`Sk8{r}ٯ/?Ѫe5GU_V@lc&'?gZYo>Dgc3;[ dR4ΑpU[fצ~u8/0ސ`A3>]e:̉e \LfwOkf-fFsL(Xl;c֓ 3<$1ǰӄ=aWJ˴-Ey'2B'bgʊ)^g ,я#-O\+e9hbYNXdVVFhY/u=7FiURG3V0T` $\$۵`O CP;:i/SP>#~C={1b" QU]t | \\L(#XfqObLP啣xefg9vA4 ͓I|NpMeh Ɛ-/Ol-/YB̋)aŅ=ui_*z,^ h^Yky}P^kmLXt,RToW82̵ڔYM.kbӐ9OCBi>-.wFKa̠:I7V.՛ÛBw/p݄[=M))259aA'  ,-)Y+Qf4Z_zYQ^˸ِDnjcr9dP*rfQxJk͎,/BV<լHJ5Xϯ.E*4#iMg-& 0vQ~OE螮` d,[Bwڢ`C1PYP6AA&Z es-)J.+2.v?758Ŝ2f>]%*<[}k,›.a KT\q/Rs54zXTBH^$˸ |qAA i'`ebj+f ^kxfLľ  IT9JObm<}5WΘR![&)=2ZL"/qAAa5at5Fm1ճJ}!kzRLϣ7͚6Q(<~~4DKQJ5j"IAAA.o%|   \*"JAAAT!D  ppژ    ƺ܍AAAx=!"JAAaAAAX"AAAV(AAA "JAAaAAAX+lO]v }7\& eLO3=3CR=lۦX(R*젯˺86(n}h O=n  \V֌rifppB@ExyqInJ_o/JUmNJE   ¥&"^}0Z{8Jr֮]˞={J‘#Gd*dM   i(ԩx, K/LƑ#Gx衇<9\eϞ=LMOs91Q   fZS<{n(?rJA\cPJ>arUWq10\6; zD>/@ٸKs[^jG)^! Bk'\.ye!ZkRLMO"4Qa6NGGccc]vUMȻA tΠ ]uX!V^NvtlNaysz.F._ݗyoMO[rfi/㾢\zi/jK__\qLNVeQccxt٪Ol . П׮l+C"W?7;^p]tz9 ww]>&:߼Ygg|+(eУ޷yfJ_'~{V7J뙋;ڹC\-wfvwaKsɄ*I) XqS83o-g+͘#3I+> cKgyac#DtQQe_0&HLVg}93U3c#  ?&ܺD[aCz,aGq[yfןbD ?^r;P9q^6v q#3iГyyx ^9eY(q;hMTy8`ʊZFT\VMD?/-}_ԧyVʋF_b:f{l?yGfM-CXgx3|}_K 'Z6 X;(}ϲhl@Q޴/s"=zp7[SATgt6 ج[6̍>-;衧ƮՌ mߞVqxcM3[m%߅Yf8u~Cg+4;5\z\^ÆZ|]ú4 NّQ0f$'5(4S- ̝9#=Ir:ٴMܶ ]( sG~7nؖ Sq{ws0~3su~s1)6+W?MXTJEEE&!Ȯq,s#x}͐KAXqz-ׯ wp=ٺ YҀRUWyxO :+(Fjɀ=z,dxhx^̮dMi0 M.ʲ@Pn}Rq 퍮~ R0Q)ϝdPõ|_ԧ97< ɧWKfjZ;\Irpg?-~q?co[bB,`7 4FvL &.}붳fUNiL0¹  369.uqZ3~I@ zy~L m[bNDo7?jdԢ:hB{G[˫#G85kx3XVS+:\Õk[,'sk^sa8<|S7Qr0p>uZ-=Ǹ]g_ِ2s|珏0YZn{Ml\yW kMwpA[<8ugk{b;3űg_e4TlϮƏa0O[dA4|LUmw\G &^{~~ċq`yҳ+0}%}wG N=rhJcW+oC d>1mK<1N;Z69ުWxT@) W^nN<0o SOС ?Ys\FcVg\q 1s;+3 >Ph3 Ê}%7N!7ĄT۴ͭȎwTdzkύZ=OOg 7 8it%$<7ZuEm3q&¡Vr'mRG#鈱_E|hBԝ ()B78(^<T.vd*JʰCPVfwRKG_fATqe!2gS]]Po Y6r:! j 檚vj|k&F2ko0z_QCc>kYK>u>hBcXɔ:ִpՄ1dˆ&Ώ\ {J%Atzs,p.Ows$sKK;ȉrY%Ώ ֻżۛvtec$jOu} 9Ir  mc"wkfrvoǬsA")j)FV3PAKdJs;e/hK(gijZ>t^g0mu fC***ãqAA%D,nۜH}{{ϼUS:!^Z|3o[_~f^d+Wyg>IhE~y^y1úș+q˕쵃ǰ[Y4}e-yż]lWlںIn2)FC]Q#YП|_:>zr>䓬|x6W=QBRw71}/?.JuĬs e V"WCs"tI O |]7$\uTq0C>2%e8PJiFNDk X?_<,6YOusqL_Fk%gn\8b į1u If'noRGjŷ~ M hMQb jucl8%d,FjlMf(su%eFKk2V["ŸIiiPjsٻ?)zAALW;kGǷg)Cζg A@x4Qaxx]ҡ"#WKu9oBZY7|8bI7Hص?s ?*cxXP wY):_>Cp%U *HGYSM(.|Jo(5T>\eJޫ.)ߣ~"VFeq@@xt,=RH )u-1xfd`Q,"ST&@q[ ,I0__="eu )LU]rwcl8jswݶuP&0&)):ʾS%2ؽٌ&dXP];^)4+u_. y9Hԁ\YAeCFɅ|[~xqJ7+'!= DIZUAiC~:Osd?^95/iK8 -K,%TV$v;,p8ZD2d+ aРAPPwnK[UUը]dF_d옱D$ ѱD$ g)XzN9'_矤.E@6SDЏH/d嶻>!v0<ǟq✢SYyWr]ÎR̾{\hйL+,$1)qr2A?at"@؜`JV>[_od8wv@C`B$ۃ{2Rպg"̏GظjsϾwO;x~ó U0G*P*݂D+եZ!b0HYe ĠH"\#pfjVE;O2}g_Q)gLHŸ>a_P wWH ƱJ$r| @ L4Ys!Dd+;Ǝ˯_:><[x#K<ӳ>CQf3(b6e\,˄2o.}Tl|0tj"JCuݳ$kw>Z&1#Ѥq`l%g_!ZR>עykQr6ɘ[n$qlؽK2%Yl!n\ .*DzdU@@MC'M(4*FBXYIވ)⦄||Zts y#6> .V.03{0TJ~a!ťQn9/mùfXku ] ddYQK˴Sh )uxhe8rg8;ю鎱Ѧp]WQJDEEDD ܺ򲩘 *^^曄$ɪ$HPpn:?BfCA] FOaL s@3zT 5yg)cRh"B+XjkQhްbmZ(j(hD#q9ͣ5p8h4 j^|%*3+iSc#uK/@ٵ}N_-M:Y2/431nQ8ƪUGkb Knrtkz8 '4sz-5QL{ LIm8 +pפUCC q_GGvVSG,b 牢'(@qݗD2TbRiꑛhs!/`Zj, 4KTS~nm҅~uᠹl(̓ulFʨX:OHr@YP]] o/j**_!.~Ly#pO°ȶEr0sm-Vo̘ɐuM+v[] .*`o3 JjF$e [zqsDAN@UUN7Ծ'YUQQ(w܁Jjӓj+cS.<Ȥj#c86-Zx[ d:,JR ܿQhN`.UoqvGlzns}qd;&/ a+u:>2o-$ ӝ's}Wj<>Ʀ머y!!X~Zy,l 25< Bs Cf&U nDˆ V4,{C0U#:RPVH%$Ps#% ^^FоeJUg`TMуiH@>)8SZM\xuYddSb`X) ,2I/͓V 9SݸpׅUo/V5ӷw7JMĚj06~uQu1[d*J%7`!Vrxܔ6ŒD"p(_, @B|;.A_8TV)I3] @ d&X7؊̯io]ەˣ_\nˈFu4 HeY1bPi9{ }5Ǵp~lAe, *ECJN` ((;QPwku ( ῸTGTp;T^ZUZO`7:%,Lyӿa:snЪ՜t XݙLZ 8sP5b >˿rs-AlmT&GdfdᆺEC04vn^ź[XZkȗi1wwȠn|##KAY:"Z5m("hGeDRyjvǞэENkQ %jD mejDɉ4e@X1fpazO4Dc1W&1ZGVvc❸@@`@Ol%ݬl؎xDrrU1#L נE.4ME}fK!$G L-kW`NUc)Ԁ$= ^DG>hrm((! ‹Zʳl,>q=;QO麝ڜ|]Iсx8gRҐV o "Ȇ xA ~fvm/{0~jBq BU2ƑRMInRDyyjpu {; *dpf"qTu:2RTHtr){/:-_'p}.Al|!:x3ѓPٜGL;9a葿]g/azthWUVcE8۩>NE}{sU\&;yN  c֛u Cp2e+;.-AcR[hh} r-\VEjTUVyOGWPa'Yf)xB1f`ˣ_\oL1 v 8qݢ_ oC0h4*++ILH%IV ݎ h6<:˶/΃L&%ŔS\ZRh@¶nWM@D沗i:_pvQWR-a7 _:CB?6nj3|Nd@Dعel+rM f=onpdیyB8&ED8T)سOW6?pߝOSߜ]|\w$TmɺXgwɠn4a“~ᄷʉ"FKNisO@'e4B@pL̙5qAx9Ŝ\3:qWP'ُua1w#{=>QG@T/L˕7/FMa\c4O"~?16ڦ+ ob|X=[ץ $EBь5juMͼ C[$(ssB3@g̡-w_&r.lfWOKIP`KkFC2݀,V+T yCe4 ^bQtxBKAWj3tx6l\OQQÇ߿Ũ(O#*2g,|#5%8>)l0)>/yO .˸>~oSks:q%o|ޛYwuMݰ[χؿh/ϟwLo6tr7r* ~ڹ,1յmƯ2e~ܿnP_ˌ;WQh2`Î{Lf{Drs?,l}&3)Ӥ{yyJPyYfc?w mA\?>/ێz8)}),!|e_ۧ*b㖰Лie ]^R{.b?`'V/YWe8W]?gЋ-FaTUM#ϳ;.)٨*!#募3\b ۨ'z?/8$[FI@SŨ]*;Yo IDAT⠶DŁ,߫U y*2mֳqnP/jj ~{?/e&UV$?&&dY\SúP–_1b>>>t9c۩TSCuu5՛V4M.Mz%[nfM4xӻW?LjqL ?yOܠ J [2ri'K(2?x!]2]gVc2?;aLG7f[_~Ay/v؅Kyܳ4  Kxm:& ;2A`ϛ+rrr7,9kˣaI?e nj&h{sM 9bHԸ 6 @ZZ{fxzzvIr;G! {^lA·D<ibҏLY#f !,0̭pw .GbHdNާ#(.D]RAEEEEEEEa7Jsf߾}>rԔ8ٳXVƏOlLLs|g5=y_yϓ.oMġxpV :v]EEEEEEEEEb\ׯ/Id9ٳg1WAE#!>jnU@U܂|{_XZ;mʡ{_><śW "k7!kd[f B yF<ʣr ҠH~H͂JhDF&ݨJK~^C@Kf#o_'r \(O%WӂWCǞ{kSԯ*********'("+K@ݥ85GU܀vl>r7 z_c=LW {C@EEEEEEEEE&gJ;܇ȋ-G tG'}PQQQQQQQQQJIr*Q************p۽g/WqPQQQ),,"\DEEuB:p萪D| TTTTTTT󩨨tURQQQQQQQQQQQ T%JEEEEEEEEEEEJJ'P(NpY82ç)+ ׇHVˑl ~GӏIĦ[E~1yDea2+d7rHGf`|L ;Ԧ".fbƇތ{:WL&#UUp%RQQ_íJT^^易łQQ 0DwV9?y|P.ǜY{G$B7Ix:?]Ď?԰wgcCJϣrY;iK&kw/N]à'<3G?xj}o~sW'Ƒ^X*r+ho9r0d[EQ0kj `0" B_~6%j߾}ݻQF1h t:555ebbb1cs A@-$Hlϙ~O?=Ap(YM- h":k=<OPw|O-~K.eL6͵B; Vw 'z<5V())!88`,aǑl' K(Qm۶`<<;y2>O MME9} 9J2>]B !q擅̸kJ"}Q[o/vR #k(TC-5eW>on #Cd/|1~ʝrd,?w}϶ε/eK}\巟g7q[ԥPC-$@>|xp:'Mw+ݯDذtk;;zȀ ܹ͘jWjqo**pPSkWGp8.tAh7OwƵT'ʥ[feZZIIIjYbW_7{ÁF!"<3gθ:#:}0:!}{Jr`&Od oc9Y;$q@yb e߼WE2ڔ(>C'>PMc߾,SueKC_Ǩ hw PuUT܁n)DQwv/>q$&&4蠪$;维lnSfVtmm7!I">>}ԾREEEE? oXj[`K`0Q]s-uO6.3㯋 d&׍v% eT/jH,A\ղ󇟨V4DϘPg&̙ C8\1bcc%KG^1W|^e_s!v}t;=#@S[{9YTאμýmE.Y;vss37 ;~4Exse! e!TÐ1h=z$A4k6r|fШuɄ$ITWWc2(//zB$8M7e 3g]͂2/"ʏ^.Ji Ι1dEap8e݆,JEEŽEFV+Ě/ƌ$IH(fwT( r0䳫*'(!beٯcNZLiH6IȱjԷ L_ G$Y *6dSᕫ8ZpS2$ٹx d>4AnAӇ`c ]zGGa6m-FO`Τsl5ΜAO_6H R4Š`(BGZ7Gϵfs;$n`CFXq~ӹº6GY vP.(i.c/RݚycAJ Sˁ, 4)gي)-kRL CcX7Pq/u (4DnrDla̖/ؚĈg3(@d vf]M '~>QB cq =_|]C6%b`֤jv\[E{"$|La]_қ׍$Jl`ח;YG$nXdo9P̌**&nۉbP[[/~~~XVV+UUv~.4"1 0n-M/r >Q&ux( &d{{[3DP~z߾g>V8u CU]+_}[*ZLhWmh+X}ځ7c;0lW)<=z? FkbJ%1$Pg;0?41qĈT6Ll Ek@1q|e!>۟djB'>OZ' sͥ#(/BD67Lj15ksObt$#{3oZ$ރ@H6cF3G!m`8S` 5g.̞ b$k&6hijPN3^N$>%AAvP0fS&&$`p倀иՀ\]DQ+Ɉ)j֕L*?]1jrvî<䈡̘Ћ M˕Mزa''=H10*уS?T)LlԔPdj բis/^x.h4vi'%G> 㶫Xl;}ɝW $ {lZ޲:PdLc߾g>*3$T;no-<mCv E׎F`w8d-7ƿ8J6T J+$S ya/S8ƭ\5iSÕeʧr1P(~Haӏ07UjkU@ٰ<:1v2Sz+: }B%l&:`7}im!۹3GwY8>x|<߼H~XmaZK=m6 ەIptP Nİf FTt 6&d#{I؟oSPn{\~+p:QMCCc dP|h?IS "1L̼z Qٟ4UEj_3r$$ Sq… @:g'~ʄ)T)1lES5!9?!:Wu6q2.٧~5?hq#D;} }sUZbR(N/J!^f{q!e.YER-JdBjE$fϞͬYPT%%귿um߳$z_F296d8 4}vh m)QR~{dyl ^3_yP6td+{3<mX X" zx6->Ky&Vn9$1t˪~:nc?U~#_YѨP* 2<AG\k4&ngnc\u7üwOR:e#i ڷ~  ?/WW2ͅIn{RdD5|͊M eA I n%Зis㩵i :wFKdtSMhsCȔ9A]\Ghd_yg֐[B!R=`@ 3%#J)>yS6Y2bIIw5[p*QhHNŔ| @E80aD,W.#7߂gO1-OCSPLA92hµ9`OM@XK('8NgSTdo@NB\'4fK&Fff3Q6n6'7E/fxO{jqԹX~%|&-Fat}(P~ƒ{A"o.r]-$ֹ*HPi@Цv nV!O:OB7S}۬ZW`IWmEURH?=Vgʵ?b@ bSV8 TV+A fok<8)Kx4Xʮ;I(TfdP& 5a%)bi]I1V;#`ȲٟA]^kU 8Udn}\M`pQAx7>Yv$΍䊙D#]B#"PZ<z2-:+s⒉WB]68;w(VP pnkٜɎi:XcZMTYD} N>~~x2fSu ElmAIA r@D)է@+)*"!t**n ,ټ3BBX,չ }(Obg-unH>LM{eJJvBhXB#{n-gO.ZBdbeTmf X[˂Ü:)_7ҁZb~V(϶-'p3qlyQ(;q *Sc+ZL22gsj/;I^mJΫAaPcA6ɍm%xcDfC!w_YE{7s2 !qݗ2d`$I!z,KN?/UTUAqC&g(2!,߃z% IDAT5 FE_swEFKa$xՐk''H9?N[<[<Ɂh D(|1P\lA6RT%"2p} ٌ(9o?Qo)ӆ ~rnJc̾;Jif;Ț@Cȑt鍢cļYD_ Wq9|ǎ1&G[&ٶv E!YW1r 9J)kX3{ ?-UWKv#c@[ f,Gן_rg75Kn37j-¾fi<z5N^R>D zzVN=y}C][%?wv+<[!e2ɱ" QtxG+~ILr -{S*DfYףCjBB$̙lvdGuڒZf gzZ^gf}-:q:4e!y7nLWl/n3<P5>b)dJlW;#"R~B*lz+ n|Y=Խ8Y/խ֌7Oaʩij!XL ss)-4AX >B[Buxpfehry $lV~nH\׎KN61dHfШ5tJE^ׄP4u4xQǁݵ8d^)ˆ?.`jxa16V^n.*-zqOCu(UX((̇}ȦM\|-8Nz{{yV|\KːHe0> yi$yPnb: ,k­I*KlWp[nWJϥIY[m؜~UmhaKSY+ 'y6!?6p;Ɇz =FoM/ 1~ mpUn />u&78iLib&dmȊa׭@cv2C,dsQV^d'b߷5&(A\*WSK4S,OmEj,Y]鼓2ٷ)XTT>UZB*6 gKt-l5i]~*p8nL\v|^cXl߾˲4 t: A 9zvMcWh^0$I }ܸqcVrnngcAA P\>D*=Lo0)C% UP$[6EJ AeMT{]bAaq"AJrp'|1| ҈ JAx8I*.يS eQѵ8市 ɟ>@A"z>AAA AAA D   ,AAAAQ    (AAAeD=T$ Ų-}:놨A()#TNÒ\Zw.D Q^St$c.HM0Zmw*mzb"g-Zb*S%d,YI<zdpDD/v=n;wPy֋JTʝ2*$L&fVXٜp Yxe7r?g,SpذdL&%]I$*ܳ- d4ÒP#9 g PR0wcwj[F89҅vwoܩW/߱oܴR'nM4@Rlr -K^P{MWI(7i*Q\ *ń$iyKyUryjR5hX6F"w.w/u*n zS1BF'Dsiߟ^Fr`EbT9'P.Ab\'iv";y^ůKLX4ў9 ׋a;]/Yze%7r?g@M6*#s=Q K=LLϽ{=JQfvG[z7MfV7O%̜<=) ](hS{nMɅ+N7]_$_=Huש^$ݼX##[#y:'LHvTԻxEⵛۻl扠IFoD%*mV(46(S2֩=h뛶.>jeYvUvl _Isl>UDĴl*6RD& 9N,27&`pKQկ^%~Z?Nd(cӁC~>"B 4 Kᓭ$AA%1o݉(A,`STZ^s&*7p[]SoZ>{W6i?cDtG=4:*(1}AM En&݁]120H~K6Ԉ j%v`MM2Y([ytG+NEel==eAֱG8rv;,s6omܹs,^d)r)l + ^|$,bŧH RӧY|O)螺{NT+cxCKp 2(~ C %XR&q&>b"IV) Rb q95T3`ܪ~@e׷ϙ;XS]s{stj mlg$yj+):oO`Hp7Ї ЇBU]͝ +dt4N|"=W5d4gt2 aڮs3~}vv:W?BR_y32q7i2/<v%Bu?zA'5Gk2P37}I21+O391D"M~ʶµw {mLmqƓEqYu4=QFӃenHOeYHdfA)WJ"53Q1RmTȠkt'$#ʲHƳ֩3a^[udYƬϽK`̚%K%/HN&6,@gh`H k?jX=` kp?c,?(uaV s}<H-6Ȁ%v8_e|NVƆ="bXQy9zZ*b/ ^y޼u?tQffgo=;̶ҚV -ʡ_"c79u7RJ6/{xl3A@w}?$ukkr^Ed3'dwýOt }dgh#ٓg:@2uGD)IeZa2a`YlQU aܱVk ѕS鵱ydj^'MXTf|j1cY,JrQbա=&1 >3;4m U4bgcr5aehN)e^\3>['ȨelعU>veQgvW=%^ƹ|]f­'~ >NN+;,sݣHXCqN5i=#~lZ?J\ >I%).*qx[%Z6l skU(r<,0z{Yg?ݪs t&.qJ/>D[S}C:se-5~Tmk9w==ϫ F W t]ױ, 21MD隆Y5 dZt oQipj{ $ U**mCm j%d3qC6m61-tH\*{6;,&"` q'`[X+!Nh\*ҹeSHX9bw{X/z;"9lnC%d,O'^c ~PL qI,i s_jz4{۟[sb蔅\!UWGHBܞgMb"SVS˒FJd-~3қW\Ǝ_;MPU-ڑ ]8ͦ\<*'cɄv=;|#lh\;PH^>T6:9g_J1RK/w߾Ajv 8Wqφxy<% D6|'oQ^DIO/qyǪUx\8SJ7l@Wn8(Z: 0 ;(VUy>b $-*$@gY:l4<O8`e%b wgf躎iv2 ]/BKƍSd\$_J܄ȼ 嵮 52apCcbM:h)tɨAwO_/˸q(Xڭ $z>lnf$mzT,F”pG"d#T;/311No1&Z:9+&L`OݟJg;9=:HG[< >‰GxMGϚ7R0's䡡e`W/| T͑ZP&9FO Mln{>*998H_t>ۂ % -G&Dٳ˗/k. pcrJf@߸p_3-$YF{IcX%$s=zg *UN*Q9U&˅tE^2*w+ 3RZ0pj 1Rbw.&tgE*`ǍeTԸxYcp~\DE@~2M bIk>[ m׀2)Y?lT몱&?%mb K,:!٩T)U۸9F`ͅpK`X~=+rHxNPcB&gΓ#\BMHJ˓\Tȃ F.#u;7)47Z>*X;U]G7#Bw3z//>6 ɜ PVr>v)F:"' %UA¼%9F|$dd>ۂ %7',B: KK<Yn6+wZ}C鰝j9, k$L6v{uR-]Iٰ/e~|.CDD;` sFetԾ IDATN_fCe{DjtF6I. M,PEhlLDf\" ך:/Hl2e*v}(mwփz e׷"&@M>kYWO[j[7:kUІA&|R]SPt|s#'$յT-NiMX:q߽YkfX15Ebad+.Or` ;0H Uz^*E$EEF!ٚ$ 9[qz;REnlEAI~M=O^epcRs.V_ F 1K)JIYt&ZIWgá2jv疵p oK71bNWJH.Al _zl 8X.cf5>ZjikŦtr Q18 y*䍘roe,r[=Viz=BCH7e`` sxAp Cr?ñ#F#, ȲJRnbV!|Hdwd*wqޡq#}$FqF?B3|%K_{+Ή a.aSpߧ6ɦ'M/<4sQX\Q5ʷt͆*˟Cz91AV4<H$wAa ;-A Ma *ڒ3,$ HHXϓ 2MPPWpIE*#S-Yn/)W( 5UJT/MN7;KxJeϻh\S[#SZWf ZW2__Hf-pY Iz.<$5u(aCCLZ!ALzVXTS[&349Ic m>R@n2 U{>ͧ7oyP.jzh/}:8vϳyܡ0-ջxz!u{D4=Ʉ";@deuDx$EYMIº~;\.TFCp%3>1l;h#lSi!>^٧G$:c3Iu{.!nM7%"2/2n֋*^,_Z$dk<=FD¬'^Vy}fH ,$> |k?d|k]+g<<^#xP=.rD|!WPF2b왚]f8죾ο~|4y:s>&mO <|JDe- InBP$v;a p%ceu&Z*kI*_wPU|rw!2rVJٻ/> AVh(NN^ ea]$ՋDC~ȍgyzE8]ggO2u t䧃^Vx}+f12Vۨݽ쓰L Xz (CAM$W5sdB0dtx2v.C"2^4걘GL<=`}.8lq!aShb UO58!ΫRsGɵ>G83d۸못Gz><.1F{Nt08arO _s/r,EQK pc=d6pa1gP^yRuᗞfwG5Oȃ/A֑֟XJws$ffwr v_URqnf;X)K왐6f^6τ& k/4'c9NW( nD#6ѐz6T|Ja܉[,Fz\,w˫ ) S;ˤ3ÕFqyyaȸ잍ՋĆ;EnZZLD*ws(mƤߧRPx\V92؞nJ?;adB  +qC+>7OxHTF2l4Ԇ)چP3HB-<K[|o`#͵\xBYAvϷ$&naξцj^2hIz4Qʶiz-dǿ;P*~ZW;l9la?cG?yf$yPnEND _ݽk Sؗˏ~ά/4_"b`\h\ NiNmY?9cXȰRIrH/LSTߌ5l3~MKRL;QT;Q5ѓLT52tN]H3`OX!Pn|zWr\54K6 !Y+z$lw66f\fވB_eӌh'DV9T7/lvL9Rxݲ,9Nt_? 3D͜JuE@rS]@D]SeZsK<{XbO=85⅕gK>= w^cNꥺu/nNUd3,< L]3Jpǣ [q+v{deAX/Th~Wų6݋zHH؝2~;Xds x 0-)}T"Wpa14it@Q   Xbb( *) Ӆ?ubQگ^q#- glOum=dh#C7b;VsybN   362P,ʆ"KYRTF[v1>/9y}~6mN XA62SU'(AAxyҤ3j^AÌܺǻ/ͭ[E-UTLO)1OᡣK3 wH oO2aaE.1F2OVsK2bwq;Oē<=3H|D:fH G"DУHwF89҅g?*ywcy]!N128y5<[l#Iُ9~}n&׈@f Hb cہ> r󃣜x9ag7?d?8Ƶx;T|uy0=Gb֗!Pe$kDLL JAxH7l|WIzzWQ~3)B~]rUN씥ۼۖrUe{ uRnki‘wt^GU K`\;#L[%N Co^G ^A4831ŋĮ3D=%oȅӵq.Djp+!_BǮSOcG>긁cj*~]>;n2o )R&3EEe՚1>6J2&{ ƖAOƕd(y*J/-~s>6X_CM xK‰I /Ȟ[]C46PW'orm5;4钌 X]t ڦ^dg S84͊A޹A 7r T0U 0:9g_ˊRݷo?POٞ\7@O=vyUX_?;cA Ka*b'[6Z>}D%28]"-SCgyz228%lbԬ&Y,(Yy% ~fH7>Χ ;I<J+lںzUL4^s٪ٶg氅˭c¬Q5;w `jvT=qJkE;I{ MouO+w]VJ)=M2mO4c{ZH6q`uę;:QuIJPlk7aQ0,jaYsAYHHtڲ(ћqb,}GčY)4Y>դ}Fxqݝt308FR˓v4'Lh q{Mc+H5HUȤ+ŎC@oEDV  E (#p.|K pY]X**vREXf"Axh)=E>bfG|5 IdHv*6|v uЛ i3Q Gmnj[vahh`>~|P ktd.>3OfenaHL[')EWwdzdrvEB0*aݒбk:80 e J!&)7{06BBSrPjfmh,o5 Fh FhںDz#\?!'L8ɥM>ɭs c:c eJ֦Y[9F,X.dE4Va$ M[ k(egY2]P9q&?AVxaO5@mOb[3@,=\T ]ӗp,DE4 m֝TudSQ14rb( ke׬\.X|k9T4D Y WArZnAڮp-Y<RW4T2U SG/6k>P#LS|WT.=$9q uIOs|ԅ &LY$>OO&p{GQ }3z(GoP޹Q&Cǧ9~:Œrd ' =l>pӕ\~d,R1& LF/7?n'Ӓ܄þBA': "ICE a~%)za]+HNNY$>ٚ?99߿łE% {f~T%kz-zcy MOE9iz Rk(L*̉klLvq}3T4(kiKYH:6otSn!>p3ӗ{Oq+MVa]ȲBrre'&Qm5[*D$/j k vùMv ͢ qxOtBT^ɹf;.{4ǻI^̃&aiY҉1GMU=Ȭ6l3Gcxm!Fʎx{9Բ`G*{Azl)nfu?&{h:tfꠔluWϱWyzCՏ&x"  +++/iÃ#%-shU5o8A5Ҥf.4/ɨ [6R[˖H!4Kf"%bÆMBknWBp"'η1mbtzOm{xto] .v^۟8F2;CMٿ:酚&ٗPΝb #=mH*`-; > 1aNe>NOX(s2ǐU\^?~L)۰ru?|HUξ|}A1O֫tN[p;vm89n)R}]lhٌm'܊DO  SFg{jIeV5g$SY9g-ct1P}yE%%TfM-W<)uH]ÚP$mh^r55A1'[~71$mIk]יAC!:ڮ1N-{tk#_|xNqWKD pȡmlݛ }F\$C} $5$g-oXbBA`(h/Ԣ 34errƍN9db~dIfCfl=A  _Y\e÷R\Tl};gA srN:]˓L191A*9I֭; c'IO&G$kVDD pnjw(bݱ}m}9221M aԞ m[yT:=#{i"KٲLH%B!b1~)=M̞ƛ%qG(XYFaO+[o$9:#&~<˹fۨ @sz677tr˲ HfJBM!B!tnrs;׌ h6&5|h@h6oϵdS` BI%&F#=QB!Bg*ˏ>Á3q\x۶PKFǏG)@xg%4 }eiI%dQ6Is>5o Q]Τ53YX#NtaMbqlwr/DX'{gx+q[א9V2 F1884(L쪟X} {E4A#wP-eD !s@(J0,62X/FzGFj5|(|k~$9V۷c#@o9MCӬX.ϑºTٌ-)NhD[My[אZ*Lϲ3ji U0ݿ{}] r?즾) rr *[a:iL^1}ff?&,\ta?K !%WR>04c,͊'[P~~]h&hx@\^v}fiPm/H]MW5N<<ѩ'AX>W~ N璯gRyu<7;(X2KkZD\{cNi;i"naKlmEt+R:+}Կ]mFw8]wrkWdI -*bxAz ]q-%ypZGiގ^zO춛NZ-~y۸o:EAskfZXw}k{#淋fmM-\q |^!p])|%ڞhͫ.ζ%F0k]Sln/? eSnՇEMa., ]t(kgCrd%9AIY!G#M$4ŗ_RN(!XjT{GeՌkW`}u3@_?E[rqr;I9~C8wFN<᭕5bM"'y=޶ u%_f %"o39| 3zk{$Hf/;$[Jo)zGR ]i|vR^ŞvkhPb6l+$0H\+!Xjݴ$@sPj|n.-94׫`[y`qz":AK06@@)$v$,8S2=#LߙnzaↆƗMV}c) _{jX\{lNՓFk-{J-㻱 `,BI6M7v9X3XY]cc8Vmk]ϝ${5ll3꧳(J+ls3ɔi(I@lCy1]4fQ^s6LEKωnF?吗r!wT gAʊ'=œ& G8KYk@-WsSk|]4k(kgܞɪΪ+c" :NTFudS}&`‚ÓJZC 5 D !Ēcb6uC^rwL~5vl/~dlU\&FyǟȽw]wFGs6o1[r-ϙik@olV+ꍔfs.0xi>ޢ=a4R׼w_Fy^c][:aPKoF_1le& vT}Nl5Ta=7,$!#?` ~.^;A&lTJꨖ3Uٕʊu$=!NgyH/-MkOš"F_#۸mm٧]D#;Soy[!n NNӑ J!=J8fQv%m\wuSHq+<):TōikCMpSU )Lgj4\CZho!tk}gU~) ԻI( O^U)1N9eY1M͞_A4'ۨoQ>62{9JaEٔ)o.cyIwp~)+j V3i6Z &z洌vL=&;Ntf oa9WgXxu8-GCfQF/::N]̄S&2kh3o o^s6P*($#O8VrmmN;t#5VoU#>3\<8U :e ,0!.zܴP! M]~^]ECwsCů{{R`9L7VX͆ gQ5+3tHtP<8GRlx-+l+B S81]k˚wkφ521Zۈn!`-eesTL-ĂAg]=1M5V/Xf^GP5.B :P`]IuÞL{HM$22Wkg@ ](^ј*Vd" _ٞl%hi' lSKVN|Nf0ϼ'J!$ˮjNs^1-[='뷰ey ufNZs*rs\h=!z1WzΝl 'c;5,/zG3wuksztsNBny'ok9VEL( GQ%ӎUbyw=\fPohpUR2 43{FJ%C(М\v7Y鎢Iɞ%Z YNAz{1͂ #)#SEiPWŦƼO}C?l_JMSƙ&ncIfJl^KwI0t<*AT8LX1|"-#SŅg7wVltz{•1ArB"g{N%*NcR ' 4hL B Ѭx2ȧbzk/a=^v;[2u0&`wjݩM0oDllj9^HSg34SsulrVg-\ = uB`tRh,)Y^PCm(HDO-*wQPWfLNkؔ7zvi><myu}e覉tn̹,#{y,̸bN`F Cw 3t}R\?2T,LLˈH(!ҝ٬z+=]Q38@[{ [p+_Fn2DV:UDޮvZ[;Lwo>irvHse *ԲJE+ּe,K=@'[/JoC~S'sҰ 60z䛆3dP+ᖄO}dj3jrNjT^ghz6J"AbH3rm,wX˾aL҇&"6LsT<r*KAڣoO%=Ht<>BgG*8\IVzX;{1Rʲl+ۨoBa2sPL/b45{od—z}xxU<:m#vV4 0MRL,^/@^:l$4a _w |gNs7 O"(PpP>qQ J!/ '{="MW,z4Q 7akK1JG 6Fk"L_G#'&~ȥ",X桬2 ڛ;l9dVP6_ L @ck=&eT$fxpi` .DMAGlo?1nZ;々Eh O=n;'؉ 5Ef{LyLa:Cx\T'.o2(44CO6~Z9]s#9r5Gikj ߏR WP"AB,)6W$F0z%q/M1%,VYE cvشZ^s<9%& zv/|CFwQ|i{j4] oMURI >BXYɭ,'5f&=ģ=,,$ݥkVJ'eFdZ;tulmfw#|RR$ijh#V D$*|Π z&y2o"g:)*)Ǔ2sӅ"#+`pz¾yۥA(!Xr,-;8zu-]w͓EY2lXǷ:nwf4r}t6lU̽l`-ͽ:}ӣrjWW2AsXWNoD?Xظn&*)(Ya/4#[2*SOqȯWR0BIi9.=/,y ݇}#tן̝U͛w/kS^Y^GG=,57"}k|cm7/cE2b*o4zeWҨ{ 1CT1Jdaۙ6VbPb5MONV^yңe}ӄ?MWl;B1^bպM gXhpņ˓eE<<@ !tTRS 4$ 0%nj؜nRSV oꞏ3_G֓/ӡ1c;x b d&=QB!@Z8|`.5W)oZ2uu6--t|} o\Y>30Ї/=sed2'ŗ6#OBqP34k `ۺ\f&߄\[aݙlxQ"87%,T hkn$`!/xx6]6eЋ71lBT 1 IDAT!1=c-YATYpz<8} n!bʂn"/pMleqzI+%n_óѶ&J˗.DY:vpV{6;7C.`S,[U`ǐ J!hV\lx|i,r")6/HIc /^-H%B!.Iť4ן"-vVf%aJ"0 3Olv KD !BKۛCR[S`snATh`_Z%e1hh!B!.8)4}=sޏWV[D !BqbGp @,Ûǜ J!B iXO"H:}"܂"4mWD !Bqi:}2@*HT_/-oJp J!B TV^>n3 9۝BVny$ !BqXfǗjC˘-(&AB!8oJ*U~twPXmv.7ixS}2< J!Bid ̉B!B$H%B!I J!B! AB!B$A(!B!HQB!B B!" D !B!D$B!B$H%B!I J!B!`] !8; B!)??J%KH2_B! B!B$HOB!Z,94mi%B!bFSI)TD !B1J)Ru!cbgK̂[pܤgeMI $B!b#TwW;|iXҜښCd -4@JB!Sx*O0jzR1E;{b64 oJ+֤S{ N7TGRD !B1 }==HeN)irOJ{ds!B!4M"AnP.h8i3D !B1ѫ񙦁ò¤iƨj& B!bLss lz%B!bd 'B!Ĕd1{-=QB!B̘TUl@eq` %B!B\;J\EPYI/.'5Bn{hNrKKȰlΗث= ^p?G@ $B!.&iNNbDՆ '3|t]F !.=C{ /f۔OUV;X,DcQB wze+l> U د^z~0D#1.%Bz2ӳXN_?XՊb4MYV|+kc53‰xD !HJo_V/'5[%ԭX,:0o{(YOmCh*b*NmsYVâ"tuM'iٸaYcP6NvCeQք#8G% +rpM:j$uh$nqSʚu+(pO4Oeˠ, 05Fm{':ʾS]+/;%Bx<ޅ#`GPf*:a,T~TDcyU!wjrTk+Oaѣ^U~o KYٔW5Em 9:_>Y (v>v ÿ=p7kءė^ _TwRmi H%").3qrc\؀ft^(n-vVc7w\Tv&;n0Ix'p)́M`=گx!}jVl;_0[s洣kG0oeYN_.~G8x|4~R9mm_GB؊C-eأԾ[Af/^ƛg>HfaNģ-)~FRɟLSze>Ƚw/WWso-/\~jB)D,s.5B,.4˕|BK?tqZ@?`Ofm񎿸|#B,'a$H#hUX袽?=D'@ml_m{k*?1?;C28ƪ&͉9h 05.rKH4B4w3J3%ABlvsF^sVVXə3defb]dotBb67ַssNw_&Xqt_ןur/$4ocLtDm%Vө4/@ǤᤚK^a&F/Z ^Vͷ- Oטɿ|gdp'!{ZG;L/?g˃{( ];l\mߗC3 hB>3']9FO J|̸;}l>˿3LXHD !HjqlQLc^(!xSX6~kPJBuu5h8]DvƜYiiT&' $OTxs???w?>G+7]UEYXV]D̆f-J@#ABY9z(_u5tt 8r0+\ !Ă衃xJK}R||ijn=(Ы!ROСstD&Li' (@u OucEDDh)1=~s* ܒg^XB3 ecg7indNBYIgB!bԒM>B,!ab&iiBm(̑mB%w($GdMl1JӇ0A)3K@']B3ݘhX2=0r}zp8L4!Enm#t#9 J!Ĭ466nZ<a)--a& G%}~?m4MF~~>y4|4:ۈ8ݟ^ζ1i}avIkvO6c`kY|&46J`4F+ǎ`S!hܘƘ4&~ЂSc&yI J!Ĭ\}?Vÿ~[<w6ΚB,[+ 6D]3L#j>9>ϱ|rdH/|?'? 9UT09\f?Qa'iv4Q2չ1 Zw|͔gOɧӡUz.ݲN_۸aXO^~_.穏}7O&y%W\ ?u9C{4Eަ?k W"=G~z']땫Yɛ -C,4}/lbȗB1mZl!ĒbaPwUfNw]VThh7zj `S rxX">Nu+6%A{ !⺓|r'K}ONS3A< #-2SM Sm@4':L %44$<˞L I^i(eplג bh\p$8Q9mQB!B\F Iķ};>HpNB\o JƘ1(:eb3|'!|q5j'_T/-U֦#C[sp|`~RRI!8fb{y`O(c.t84 oJ+֤S{ N7TGskI}K'!b1$C^a)D\mKR$LM+,LG;y퉒$OB!6%ΕRI$<X2RiKٗ$OB!b6zi(yr(M0F5$OB!bI~i.@6p-h#OXb$hBq)H=Q !(OB!ωBO]$ B!.Vx)|Xl@eqžp\JrAꓔ#V! %|1b4r8X] ZAy{; ?RD{8^HKW?&5++XY7 v}y" r.K؅XTSBwPY6U6 !b̉2/䡧ߠuw$w[x>6gc*cZڹ즵$-?bs#4MwS} w{7V%8Teh?Y.Du%bz%eRt1 wB!p> (IΊlݰtz$@G=J'ݟkyxlx8Aݯ=~f~ȉ_7C W&*֗sj$4ּzM#o+PM򳘛 ZӦlxB؋\)zX.!N!g<Ɨ3狟uX  4S<89?\3NL{D+&+3f' {C-inU~]!ѳȝkP8-w vU+ k?䯾??TmcwO3/χ+؁//xݬ8gW$':Ew}_媩o20.4e&0& [?Ƀt s+]cm;~jL616v9zcJo~w+C;pih&]Ѓ|K/Xq~vws *;)۶|֧x綱ƗAO^}|3*ȟqbxStڻ,{ߙy7y! EKj-jz^Ԫ׭z룵j( *", JH.$1=B.Ι> rpE>nSt{;/>&;rM|GaB~4ɓ𼑌KJTƹrA9pg7ymwl6VǣĝĝBѱt>Xbcc4 ]1MKl}ii.u2|Z[% M2R7ݨSm0[-W{\B9'PeVn…t;カ^Kƀ!~cƎe\^&qF&}!}hu׎/ .Y䃕%'c_c/LKL nuƐ1Cu}WUZ\4@5|Z!‘npv37aSIyU I%nji9nm2dF./ /~3_zd&0 '!ǂ3~?@ߏ'&b 70v&SԢjr[7Lzj2,̦rF9.^ OfKXJ aۻnѷS^7)Car:}rkd287C;wsXzG^DX&H#JLi m-wgr4;K,`,^ER T. ha K>ӹ$4`g6*3QDLFGQ.;] ;!t]Gu 0 ^/q`'aDf7>xSճUV}AТ Dk CuԴʟ(+LDKb%gYI/ظa#~rh#󟸇 ;Fv% 𐚚~5)FqTARK΁ x lS@ɱV=ziet2) ??#E֨ڇ24ͤȟ$d^AʀtEV&3.&~~w3kT% 1%$&sUq )Ng:5wviwB!D;u%y )]IXla)yQi\o2VyIJFDWtR jT)8@ۓiA ±"8F|\wyeY N}[s >Bc4mc73Xr?O>;Q8?3xǃv#x*WKx_"Y-Ie;$I_t/\s3)}g6ݯKMFϡKjP_p)@廹~nt EQA>n:) uj]B;!n'F麎Kvs6eAO7tГ/Z_ń|b!x}c-k/dg_r#7e;JՙXNTԲ湸J _a~A>p'5pE"^Z&&c1ymg=c ū VE%Ƿt ''RթA#aŜ?w1hRzT (Ei>r'_D.Mqp=cG9 ƭ;91oLn:u$zPԼGOWO*8CR#ȡ"=98g~V9bbI]JPU wRvCbz?{)B9ԼFWP oSחNtVU낸z=2wkb۟? Kk.0qG.x_"8𦤒/vuc&6C fg,Uʈ% }u]ȍ=3k˫m'rO+zT~8#( ܲ2e?cW"MSS->?6_f?GN{t8蝙Z2Ç@qxz58ڍU!ÇucQaU.@9M n?̾xvś);AէPuq=Q^+gS ?)+먓ԗl1]K1 BUEE?A2'OgKYlp~\{+o7ls[+Pd &}#Qk;!4׆.m^?(JN9PJK8pwI9\ɍͼk(z)j9y|unT6m}b;O,2 PkYNy2fQ{fx4f\w|`~-4<)pS' 2Ek_-waT6®/Bk51SH]G~yi$rܷ\Q_ۢC(XILt򦭡Qml+R.`d{ikIN(X?jld8jnilT4:% ;?[βW`[犲O`nӏ'd/5%S !6Du|>a`_=~=|Ct-2&S\Gkd ĠiEpd˗Gt@O1s&1wġإ|K>Oɔ8K4==k8!~ɤo`+`ǒByأ3n6kpVNoie[X/~b %%:>_8*s'ﰬDGsũ4E3OQ27D uQw"=;3N9j-î^3o>X-}(q'q'Btn9T?x>6d"&Vmbul{t=1릑01&89<O<ƶkH2~:%M\ϼ-%4OQWŸ0׏n`xt<9~XnPWhcd %? i,)'kp7PE,[)J'a4.k3I)+_E+傞0YsCG/{+͗ƨ}"ik_lk4$TއՎ w<_n?lΊ'P>tO'cMt8M|c&,e w]۱D"\̰4{\U3ȟ{ _`{LzTYhT eW,E*i +HeXeh~{,̐ 1dƦP^UՀ„,z^S$\vc v [gV2pqU`ĤF'R=6*}UBٶEQd ɝ: (,p}ݵNfx2pQPS/jK_#wwB!DV9QW^+?lR+rq F.X~cƺ;\00* 6qf\4r7\TѾt 9EM?yl 6 gtMhߵ"mwg} !Նj}  7yQnm=_.桻W;=?+O!:HƓm $ɰx/NOr%BZtu&v?K0o*lpƓ% tժ'Qn8~LusB4F))}L'!Y$BtpShpWNtxOlY$Btf/j[hF|R2eŨvL%B)Eiq1I'B[I~ֿsAAA8PCvm ^8AAAaw!-ő   SeBiq!l4P\kJz2)ӾAA͡S;"qg}Z JUΖ) DGfv&Y^f{lSil;wH#60xTnEV/o_Ll7i%7A [[8)AAa< *i%Il(rl4JkGU}3[fD_'D KO'ek>'R( +( DH̛l٘q & 0a* i1OE6گ`I=T&j]YO> .6wGrDMo֎ڡ|!5{6gm   l%|5&o1uXŚ'**& QBLMLeS%m|;3f0qZ=XGxp\6>97R}hkM¶/ʄxM $ų1m aLgGu 1 (/[H񏸰6P$dWAAv4_38ѵMMZco4ΎY;Ҷa @E5O6#H1X:vncn !YTb=ӱ4B"a@gD6R> ʚO[٧҂i[C!ׯ ἦ]^Y!AAAarʁ!+W*Fac*ǝ5(֜QT5TDCUL6^J;DzcӖpaCd>;ٸ&*WGDaiL!BeEazo h!GG1G8-zIP,>mdbP^A dBvE1 "4ڙlD$EalɿK8 7xL!դFقz}GU4Ea 7wvsL'GZ J鷱ھzL[1cC̣ jRƗZ~uSND% Av(xH]&JX.V%\ rm'w pF~N+V`M}ɂ ;n6ϼVzZ=Z>#o/0zTͯ4~Fް{µA=O*R %*IdZ=U'b4~n71Nim32n [aQ{e/4$8[7{=)*Gj&sO{"ubiLKg= rcYpw pz( Lnj~13ip4~l֢2Vnto Jk=H^|NB.%ņeVY=$_{8oQaW¢Lځ=AaNi70@ljqr*" +b/xZv-պlLDFHYe雴yN ]v)uGM*̦ yxC4H)V@ +ƒ"KnZ£Z2!neV [YM8r2dO6%A#80M\SY/].g#7Z6CAah#QPBfTiH%/GX.RZ*lL_=H"2&vKF 32V"mh# *N46W?R{>ZkjY5֪&[av^*ϼY38}١\ y:0>ퟹ_y /V0 2S& k"ӄAj2*WZV\ZmSF*5 ƹrZec%YbgkG(:X6-Q*GeՊ>Rk}|< ³vX430+ai)ld"zC# mN ]Vm%Ѩ@|bq:<48 z} g`#cf}4`2? `QQ#Ѓ1ӎ1D7U[KWQ0=eWBvA8 C^wf- 7 nsxn[W?pt*E+^ƯA1-(boXU`&mB֮PͤL'ygm~ٌ?Gn[M_~e݊͡G |^xcTiM \9,3r2W;Ť`1`Ҕ& Ǯ T u39P:!MDȒ׆YiYo,<RUI8-CMa=b@)S~Е3+b42R {0D#pC& zL,HNKÞ6#&"C[@iLk76\s28[_ ^d ӆ?$dWt_ˣxh#- b"gٱrq8y^ڳKXds\ ƹmg_p <ҋOcg0Y}+Мf"5ZcDu$F%|@S7Q&ߪXl,/YMdɺXBKU8V'~k^0iweAh<ak%??09y9]uhsNDŀȡ)*2q}%?W%QQ4<&b'ه4!RVn.pQp4PG; m造f~k @#Lk9B$J'_}y7_yfL#s Fzpdf̧u6ˢSGƻ/\ç[oborXr9i G^J븖8Jʲ+ D>1v]LNԕ5k_ev0 qZ#Z F("ߠ,:BTau6&@F5&#y&WTbPY2<+I/_{``m%Gv*#brTMQdhÖGq^bWi^*hyDhR_T¶T4˰[KrdQQu\ ctS?k"U9X>|j>d]X+^gnQqO[izBrݼWanvHA筳5 ټܷ62{fpީGp,i Ollʥ瘓xϲ)vh4QyA ><*|zlvVog'u/'NeISm.\9=]-92e4TxGtd(b$3ٮ&.Jz,K\/3WD3( #â&4h7C#a,,а~;0(L֪Zfϣ\#< ^Ag8pȬ{ŘY'r9wTa)REݼ㽗[N&_¿b4غwnO' [2WלΫ7 M[gUdyC?㡺PgέOOEw,CpM[F[֎%eP-\rLm|n~U~gA8P=R4~N2umfV6N8V|FIUe/[;Oz%UL3Z=Z39t䘞-09LWc)F;DF}рňB@M&IU?p0qL-ce#(10B̟AR20\ :6|F>emI*ςyI1hw6!U5*.1l6*-m-4mAtkYѡq3ߊ #lflb$dW}gۡP^;]x,67rWOsѡ['?1`X}]\s|>wtݺw,'Te8jn 7ߺuoÎd,9| ~ް^vwR9ssM'[w,ϒ ƯY:QK)m[{죙ϝ g O Y;/'ZLr8 SQ/]~J S3rAw3W {V#O D?Ue{gIL~ULC H뀪)q/^g$xCʸˆN%LdqqLA/&[ޙt:\C$L^&C.4)Ѣ#:\Cgˆ&YcL^6 Ҕ8Ji~Zw`qDܱΖ~Lq}#! Dl^,_,C,?x!J3YyĺzPc>%RJ+ր ʘ05F{uí٩yXZUAmYZ]#Gi8KR垵TʁḬ=pP􏸔GBwUMkbuZZe&G&7 L뺄aM,GwlΣ=iqLȔ( ~,nd&}aHk(zkTd@N[ B:ƨΫX ɌUp2Y*fy,Z Md W3ͅ(dӐaa qb.,@:A-!YVDb(QsVي/諟_UVk5QRy$QHn~U\] rP*1P8jgp"Z8Z3š%C[.GNG˦c/ U1:T\"v̮r2ia;@دt!ѠF kLEL( /=z߫3W`,E>־Cr'%l_,IdN&)mBY83NLaXr#&> \wATP9DžkuN6U鮶iaսM-*~ ! HKOE.Ir.C*Cmi=}hhǮw׉LW{u)9GwU~l-\ {ǰl쯆+]2.5Hm\ mAT.GW]% 떭IQC&.3QX;+ MS^{eJ!l+Gm# diB.,abq'Y~7x~ ~Lp`R%EdX.TUMBI&i/_~/A+oooO~}?>B^7M1cY,o8j2'< ؐ'~7<հ˦X8]E籧_9tŇ̻_q o[pg\Gk;'וӖ ;'B*ba:#d0s ʠ20h Ț%BXMگR rœ[P̘E_?H̭R` Tה69lLYL\}KW2aev{2 5!*)̆<kgBԆ),[:P+J-GN,]z=yq_ğ&]A8gx"6YcqWe8)q hf^_;s:]> n84FG++::[؀w?D!c$p42^j.xUIHܨ?T&7ք(5Z;hW|,ʔ8!QکطnDueQ )r1}ލ@ NܪݥdKovS4~1MGݽ2u&[9A.  ͘P[L#V@lWƠPxk6Wj3ry?Aru[X_jҚM~|}vTY(oM|*PvhczF ], \X3dFG?-\%LZrao=_Okb VG([PNy;[ B=Q@`e%8uf]qR29^Nʘ"tjgarFio< jZ&i+EjkjbnRWV'm7#R*!Xz&pñ> bV6IY֦qph@̫v!t8TT

'"쌡P}7A8YrZE\pĬU?7z%DQ4K8 OeEi'.sF_S"W78X{:!5Jy0<$Dc'5ÚIOhZqٙdGʘ>ĹQ ] M~P:I[,wF:(шF'KjdSXz^B'b^(0mmŶ)sɯtQl6lrҢP&]AҷfTfͺ^֬ۍ6ؠȳ5)OdKဧA}n:(FEVʜR%օ )%)1aj"tMitB@cb|U_јgݸ2 j}O%ر^8&P{m羇)}s*M36.M>!r>C8;~Q  %0NZ8m/`2-Q5U4^XV@Y^4A/My0JVG# ҺL z<(j!Ȉ{Iח*Q'X*8`f lݘ+  g޵щD y"MSi}ME6I~K ?mֶTMLɘ8dTjrxعG'v#+]OM+kGAѸE_/Jm{?ٸxY@9dbbQ:aRhL;W&;O>֛S.'  ®z/ uJHI5Zl&˓'Y~DBLKSiYS0 j!';6hKT Ӵq=d><[^DHȮ   >O{j0!o|w!\yz<8 Hc+  _Rɲ+  _R AAA/   {TBvAAA= A*!    ]AAA}BJȮ   ¾~!H%dWAAaa _x^{{   B|KPzoFAAA80A*   D   {   ^A   WA*   D   {wG*?=5Aa/pIJAA(JS`ASnC=_MA!_>=AAXx?'oO   ;S+K}jַѾCwC*   O~ظq=>~wȆxHAAA`(zc/]WP*}`?XE/; ㏰mHdEk// 3RVd[h{)) Lbtt}[9s&gF)}O;3k_G&١YA%}kXP-^ {oiady,7 ^Y~LT朗N߶~//1O} Avu7;D?{\wu8{Z&&韰:cc1  ݿ[)j*t~:R^7񊣺d} ֚i]]L;DN8DJk׭eu}p܉l׿OWeVux[.۩>EjfQCREiE[VCwO<Agqf1E NP!~'kc12KW-tz;-F's_b}G\}ϭq5|U ٱ|A rhRCE~'W>՟(wu/G~wNZ`K l '9,qZ!;+c2wqkx#\q ''Eb,.O6,#M#]{}y9^ytCTΒe.꓉|,Z~L~[Lqk-p+w}qް?1Ay& lv2 l\>G6N4ŋrg?_Jn̛uq>KcحoMC=cu՚odmӫ&/rzZYlߡav|KNGyseVOhp\"?yH}O KgW8OIYkGy[ϕl~xZmx'X1c!Gw& YcjM8/DϲeKw1Tfۚ5l)5#cIP#wCϺf[_[nt,;X2$Bw\K߳x՟B]mo+Wv<O]z.S#8Xbx渚=ty|{a<a#|~Ϩr>JIɯ~(#][>}f\_nR'|> Ǟ1ZK|Wg}5w-pۮ[٤Og`?j#}]E~9Ϳr>}ptQ In)Xqm/! AjXqss_ WD7b<+m~};ʢ9N|"G ar>ݿeyaCs{B/ \ˣ ?| },^}.'ɽAvWE-COH ',cQr l8s7}o?Uh{*#o| yZmR,-tZw _[ϓO>_}Zݝans3~K^q17ۻ_q.{vwa4C7D(Y.25[&ˆ5[#@[ǼXGq0EցPȚf?/Y3] Y[ʸǺR̚mл# >=.3=8,^zpIEyh5uv:h@ke 2,hӸa^G6Gؑ?.](nl5sf9tiXScۙ%]?<ŋvƼ\\p sU櫟nX0q-[x٥'r;M S{|0[+mp{X[揷K%0$-]Ps4 szwE,Vr#;`o?W ݜ%ذ[x(([T\dA9'.K(>#>Wqpno?:sXEF~̧"F쳯_!m[WylP^{3~lBh9<-?3`箕'3&(p{];3]7_\ұřCk-o VK`[\xzan==|47ߊ\= X/9m|so~Wvݞ춆7/ܺApc>-t&a•-um'=k+>}:{:Xw#4O EOkɱ*— iڸܮ<.^;W'H>7o.sfi7|w3׬f759Wsہ UOcgYfCnc;W3hT;u4>rJu-}kGφma.;Ws_ȓOYlo^{cX/P|M34ª}K%a$K5s;syUC; +n=gTi_!m 8\x6at-x,l[Κn܌޻Xc8:rk{sKwe4{觿5|a!A^Sݥb#KhAekǽ;2h#ϼ0r ˻ X139ֻ;'P9NRM7-uGÕكf6϶DȪXgg]Cl9{w|U-@(HAPAŲZ׶kWQ)M5I~ss̼$ |v]Μsfr<<=+IQw}¢e\\]p1Mnh}5vGymaݾd޾DsYܙaqxcEst MLUU6mJ>I`@ ăis/p],["_}).brڵmOtT 8%ͤLs tsq1̞;vҳGOf4g}qN+] teeTy faKOrS5$IO6(EW'yC.Kf k,qc7tԱ$Qpmg`z4+!g;҃qج|4;w&[YsS|s4˳pEg !VT_vɞ?֢9| Օ ߟ²afc:m|>'o35º#6Jc+u1mꪊ5&Wx81̪CF:Сc gK^ҭsd_xB4jAFl^)s䁧gGQQ5N88NhVL #i&سq*CUϵOzdķR@#GK%6z,w^2(Yg0{U_~'3Γq ijҭ]Jb0"~`feD pf ~Yl\vKDGF"K81Rޱ"kHF'E[{%B JyYʼu&~QLbTֻкaLNWzYBoi[87 e}o2WUOB*aTf`h YPy)$QȗDGaÈ*`ݻ9*8y[nZgЮ6\ 񶤣Еpyp75f6k~~ց]{ ً1͆|` ݐ h`}ѵ%a`˲u ɛ7%XT7ޜ˭hm\q 41UUٶm;u`0FHHHy0!N~α4M#22}vM^^jח5'.G6mYv$[b;h:^ݯyH7F9쫪'YU)z :6V'upexOΞu me?V:ejƩ4;Umr8# ;2#|<čyq>tĄhLbh=5߮`tDfrf_- {iڅ\JXQ~s%;8*A 3 e4K#\2sTh^ufQʸ{7rBC1Fޒ V6" K\' ..P@b*dE)qHYqRK'lǟ`);>n/068 N2T@g؋~ J:]N4@n V:$-l<{;A)rnw_ وmiȲ_Aư;.6(IQIN9E-.=ψXmVf36 ^hD2۶%(׏[|Q48 Rp#v^,ݙI|DGDz%qsCntMT/HOjQ yҳ!1F:k- 0d 5{VPj$RڻkzVwpF%bVȱUX/!#k [ yڅ#l[͘SM7H/ $ R+cpкj(ܑffe;81emI#k Yoj6hvz#7yV5aԡ%]37= owuL]|/KnTZٿeNJwh@H*U+VTtθusn`}u șoGS#J [) vA@Jj I'XH,%t*ytڽ4 -U`͆jjbIBCs1}h4^ŎE&+}#|0gPJfF#l#j.|T^s#t5,#M*HWz@+aIJ[Q3ҭgMS4*РTcǪLCS3@Gsg#Q e,a]gS8c)5- m`]5Q,?hcr<`0p!B T̈m:SLz{`Y ($žƣ}*pj(L+0X'ɍW3?Cd|Sx #d?bIAԳ˦D/Kv,$ܺMyTTV}ʧ:lu D>r QTO|].r=ՑT@95c幻jeߒ糨3w]CAw{jvlIyeu48 &,_ζ \FvnĉDGt:uà,ŵiJꪪZ*lՊ||}9|]th%*[p3hٻM`mr $f%vt_쩸82AZn<#?O??;u]3;9er3N.ubb5 f%mg B8C/g@fRw=TNo]]%ʘ̗aT&{ UP3} x'v 9塞y-t_![54a{{3+' :+$#7Dg. D/բaN\ʲOYy2l wRRN1eVJs/ёѤA< kٚ+Ѿ}$#Gh(*H}+\@j[ oNil6 X/qAJ#b@d<aF<%P b[+Fd QtJuNf Bv)#^T@oN..Y衞,p@CѡC[r%MLޥ\<Y%^J]PKXIl+',;b$Y|ej \/}X4 6GK@2_%u6s`߹ 3=/۳ʐ[wm/PVr?`#FwO9 UM^ήPiKy|hW$♇v{RT {f_Xÿ<3yX3O[Γic*/N(]oh}uf}37) 'bpWhEә1sur |A?׿P.s=璌_ov#@aA 0#0gc ܂xn^@ߠn^_ڑH .mAhd&c NNFTUF$*`۱XJpQT|x'ͫJ$IBSU,ׯ;_pV>{W) 6pZ ohрޭE!݅z-.dUVaL'ĕi#s[mMN-,aqOztpfPTV\q塱W[3O:+ދv(V!Z{q s3ͻzsL=u~=}M/7]Twsquu0gV&mzl}]k<ە)JJ%Uܣ%$&8?̙Iv'|%rAn466v&" @Bq7eWO|wF|~/au5,{wOKR.+qU,}%Rx>.n.u IqaH_,j}/u3y餤RpoB`c.&s|x,J't6Ū͂ "Vo 2%ri BJ:uqQ ,-%ͳ۱6l6>ΐAC\Nǩuҝ(YZz~";hG :T722lS͌t1" ϪjFQF9nyI&3K*LLNJ:ٙ'Z[,-/'ZJl_Qс^ L9D2EtOs0OV[Z`?|3]c+3+d+N D FfVb^'=ğZؐp(H.6hf ˎrald}e/WZ!;yƛЦnHd_K,HxS=pCl}Lҳ5y}G9{w.ˮFM85T'v>!>Dx+V_㻚"p=).6V4TEp( `qwwze=!% 2;="jURC_[ǝ[;:2ȉ ]VM|́˴!JNY~̶Mn8yqgnt4r-8'<7Pr@E ۋ.w? S j"wN\k7j}ԒBKvkǶ0ȓ\ʤwծ=֚9\xNꪍʖ;⫃fVj 9Ry?o(ݾ;q]J[Ywh Xk GƔ/_@Y̌ǐW nwuvTUAHei5UEQÁRNa/w'+uP- tjj<ޝW+ѮI>zB$ FItʕqdeF4YP):Qɕwܴpq%"AAj~]^zB%39-au5 P烒۵Ḽ'RB?>{M0Gyd'J: ەEɩ%>Ok\AkysaH$ %fdY.<}nbA$ ٿooa|ʹYriɫ+.4փa`>Y̢+Wd 3U ;ynA@;H.rJzO(*smoX]jh dg;'4(A?o?ٝ^)KZ(hy0ŨH Udي6Բ\?Z:FUFIس{7Dk˖p^ fү@;L fvR(餅ĦGΐ̦Kv% fAy>ȓq>! Ϧp*=b[.~AuDzF:3g}K\Suv;E&ϲ{nн{m(dOgnD~5ogFʕMV8k}.I-z9QճWl疤K.#--Z" Ippp&֐ڳG W  T&   MB   B   $D@*   4    MB   B&}dZ!#ۮF9ᴢĦFt_$eQ2mZeH.t ŵ#ґ!.4;֯gL%wu#:]S}'ٶa OeQ+ZDңo/"| _   S5 =}4;wٳX,\\] &&&VZ5fUc+Fm 7|}w!x¡750}C랠͹1e^5ϰytN[X=# PF Hoζmݻ7:u`0`6`ժU2tƪa$ ɏЪ ]HHRA@NUkEFo7g- IDATx'vODeT!X@M츩9$2ؽk\ òx [rX6-1ƪ Ǥ{0=ZWWvcjZBC SSo2G0};y_>NY̦Cit޴< O.>Ps1=>]3v: S\7ןcy/r t ?{GdƷ_{KBBB AA% X,Y &`4㏙1cӦMc„ t2N8AxxxcT0&q`Lf~g;VgsED;BaՂ< TЛ'~um‰Yzs*}|/^W<>H|}_@kݮ-_skjRHF|pV`vA]=ɽ/Ҳ(޽{6mC OdhO.RAAZIر6m^gy:N85Ġ!18ìث=ճ~&Ye`\ JZa>HΞxV3:Z5֓j'ևPE3-]En*?|>@? |Z1`ΰPtvwu|-a 4!CokU^+{p`cӫCգ}[BA5%+>ZNÖSh]յ;%%UA6)'W#/Ml^ia_7dd.Gy.᳟%)Y:^LOu=)A -(y罱(,,DQL&\z  Q& YZX, Y((hFqqqcTw h-05f,ų=GM M}ZAz.a$qľ&4DuX<]ט; 40|ÆVgajN9+?/>YnU#o3<:Qom!7f5ZY  [%Ky@ GXݎnl6SXXy%ihpn.Z& [LNl~"9[ &9 1i 8 1}#}oF<ޔ4R* {YͨLp`d4U#a"7˨+eEmqb/qH.4|h}}V[]Lsw?<ÃצJQе$m;N%Gr:dT 94EAAlzӈPdstA32c$<#<%$ p5Z@bwww<<طƘq$^{kKdqK~D-$tUwFV%]88n% ϊ_Syy?@H[`Ĭ{#8zi IXa~l8^g*ndhC⇿A %+=CBhza.MڗҟQt *]Y9f^zFbTA_ o#X,SLaΜ9qH .if\R{y+ b,a-3J'ۖc "Ѧ't5PM0R_ Ԇ1/}ě&MT.捩͵qםt^cUjdT6I`w8D[[3q﹀֟%I~kB;BN#뻟y+hd/?>٣է{gA+1SSU g'ЊJ$>R%KI]Hyp-QGy\>h6>B338z?\)#o,qAwBAJkYEQ1bÇGӴi%%ކ7wbAaOɐɷ|g$~Dq8]w@Guv!A θ9U 6e<"FoFSf|zΈQ/݆4#щs &0{O~^ e{Z73d9V^ؒYLt'E=x||BU6K?=hvFצ0G7h_0j?S~NVqW+uocN:iƐ_$4KdjAA4ʔ‚Lp8p8(NMNIASզ_CZKV|X:Ͳqs)qU=O2V)Ow n!E򰺑}i#N- |T@\1L3 ۙb\Vе;W[7oOvaiy3wD0*(g3+ъ;xqό南|>E4$/% %{Іwt[^yOFw8rk"EzDF9Ѭ}iJt  BSh5$ztLb6ab0'Z̚U~!Eb؝7|[0 Bh]J@ #43)əT]BJR* Ni^1h?~=P/,3k.䌢'|\\eyr0Yy5f4 }i`YA(رc9rH~~~ewwwv;ogQ][cJjr89`ZNt^6ccUSm7 ?-z$S_6?r/9QeN؉GpJܜo&,^E_Ya>QU,#ֲX)~W2=Jk;OŐA(-Ǜ[.Ӗ'$L%q tqF˙S!¬cGu[[\#dzHV:ӆjcb ,MH%_ Ϛt3g%?fcKT4$]tޟփ*'Xor l~SNy859~UCBO;˂ дc$]}  4Z@ ЬY3&MĤI}ǵdt;U`9Óe n* &k"2Ibz[Ysf#.]5<3E]_)OK-O3EuO x#s%&3FTjlIIή.maW黿Au:Veʧ,30*3:p{ׇ{p*X porBB :WAH~w!LƃID.W3.mM1q1Rh.>iGPÜq#Iu^4eO[!kS7CA1a|ʹQGHg i߅WB F\ AAA]$5AAA   $D@*   4    MB   B   $D@*   4k" ҩSS7AAAAhdD@kϞn  4,q4u[QMfv.6jς BmTATsbт_wmOJz6-g?pjhm+1>ʌ#ԠQf}pgAVn  Jfͤh^~WdOfäq8y"]}rl$<̉\6'O?u$2Խ{XH(X-8$=.N/p1ϥP 7>VNڶ}W֣DMeS>zCXίMtxws/O + ;݆T V" gxU;f05C.^X#̚Zī8Vqj>[o䔹N'OƫR8w^|EM7Sh7gI\fAaboWe&SSgrLjsg&oCBC%bԳT鱨!^+Ђy/R.XDP1ՒшA[I2~$5Њſxb lm G?5w=}4Yhb[džm7YxŽsL2]fey$Bֶe$wz+(H\S?W.Tu$)eP}ik'X\3;=J/s kWFcHnS6,^]syy'l2Wk.A+QB(**p7pw'00ooƬ~4!h?Qw)sfLE=($%f͞32~=2GԱrdZ6JX%IXl?hG3lY5Z rӧUeBC|E-&u::ERwEL?{Ƴr޼MU/Zuǀna_!WQ}g9?7!rᨑ5ظAi!~9v(YҮ]DS7 ;l5m"#ʙt3GsArG6s@9;vPumKLmw>ң'ay|Q&[C߈m0YPO1|ͽVZuǻ$aշdk0{y݃>f7kˇ΍2Q'%<75$".=ciC|b[t5uK Olc6/ c&Errs4 S{E~8O}_N/%7cB frU=^ፉanG/SV"NL"2R 4Z@zYΞ=K Bev;&'OA۶mK`a5#Gsm-WmPZBXd0v;B~qvC_,\D1NHdfb&!:ˮQn[9i'G%Hz=BaA>EZ^1v 洏ӅJ;Iu·E]Bp򡹄[9hA5jDFFzL^}عk;7o&006m4Ы+_SQ\"6Pvه 7F˸tq]gzr h^yx˅N=:8ԓJiM@X_p3ɝ+eHx_7Gw|C6# ceצk=G04R#ss/۷SoTCl582d@~`m ģ‰kX}ZArŰ8Zj;\C}Fss a%Όr+*~6SCۭ),r!Wp,Aipp)bbbt$&&sNzELL ?q<|||ʆiUHNaGݧ'?%(G5\ EGhYµ)Ú̖')Ɖ}0o}oY߸q_z6J"S׿sE2D%dڷAޗI~r Zg$\|iK ڢ88QkwS]#vaУfP6*:Z^FF"ȁदs"قwlVҾmkثTbUZfC#Hݶ- .HX3g!僣frH*:Zb[ nٗAr`y^nDRY h4-- F#whwqTgǿMwK%ncc\H-JH$@PĘN=n\qެ3mYdJf]~s8c<ν̽sgҥ 6 FlL UUUMH&'E ֯/gqUQa3uDпW ;Ƣt HXSt[Ԕh-% ݕݗȵiWz W3|e;)X8䒵3+=U/&[Qc=kmICF rJs~m˨Զ:H>%l SRiMzFBjgдm [-lC9~Dr"k8ֱX2ric,\SGUJr{Y%N!eW7擟}*}#@!+Wtg@%]c:qocMX}2߁%-%˲p!W'l)j0?}|Bg;fĥnARr60k ufMEޛ5[ 362fJf%e.q7}^5XXx*ʩ5!ͬaW 8HLowR-)`nP`ǠSuo] :55|>Ӗ g+)l=9kLB5Ǵxc4c'rtmr""GIHIJ~?^6eY`őE\*l:ƥug4BeYl_޾ Hvtl6[a@ag{,E־هIUA! ^DicǃqQGaĿg!"y'^U7JgQ;<%,ƋA\><7 `& vV`dYRYW 1rOdS^C͠ߓ8SF2e`3+7Gߡ1Xe5ekID,.dn}=f>MGQx<8N@Ū[͋=O/ž9.յ$=~w8g8Un?^膈T#]8."e8\8a,@SɎG]guccQk32rکya}S`g fҧp݇eڊSm 㪇3qL,Jyǻ/=ABSpHi""GIH^ ˲=[Îi+"roB׋efEi9Re$0pP-`ճyNF,Ww3G3!?<[vK)5m$ = ,0[osLMޛ$M01bi9o^.]Nڕgݵ_.´8d ⏤z|!op՚AtL4`5еW7<;6/#&1u㫦Db;l I$௮0ueabg҃g'_^DNahEu ȶZ^p}{:XyF/?(7]*@Ey%&4Rm@`b`WrZv<ž1{˞IN?EtV-D6eP̽Pjv6DXX^vapivWݖNvP\Xn߃u3֌d{?wLO\ǁU(v9c&.yoD~sv )Al;ױ̺"? ʴ֞>|,}<];/. tLhic -OL,{qq.e[u)6g)~[Ado~+bR~܅`tFY S] \?gjaY/A/$8;ɐ-=|T3+X` Q9}7MT{mIjj 0bcOCZ&h"s,xzsҔ8K(h41,1~xrBln'̅CȪfΣٵn|AO`u,^m^[6 o`]v>pec=I-\k2p_m(ƈk`@~ ZK9j&;Y]->C-ÞqPTy7pύ|l on/gW.Lzq 9HBv1l= ѣYj#G3}񄜐Կu3J'v[SF18?-Xaw/r`Qq=)nܽ 4հO&6&e'mb`7n 4V_}*V)1zϪJ 77~G`VDrǦuʂlkhsĒջ9 D}KW"jy նy0yߥoںiǐvfu<:bao9v~1$Y,߾1I-tٰb-쯿9xkSQagwf<;ƾ /ƞ3&<:_tC-nɓ2^6"r1 NbkO|;<{s Ǐg"ͪWO1moleɪ*,AnVh_ol1xHϾ(^D#wgt 0˲L!'9hn>dw .zGU@qӱro´g0x`AYlj|zcEczr,.,W,Zξd GO[`sgizV|u #&e5߭5Q؄e&w:bֱe|3cN?IhtƞsC~|MuToa͚eܸN漓o; ђ:{ Q[ l-֭J#ICyfo7 q71M06l`Keld6o {% d̝ɜ;$h)["{pR]0be %Sr x|8ΨU̷>V.76r4ɼ-x&sOslcy|7Xzy5mO['>g,?sw:0O̚Rqq|z_ݴMqCxⰎ?P|gqM oX_ŧp|Rʷ,K$;7{b&+Feﯯg'rLv$̞ ԕoY;R32f$L,93Nh1 &"r뱄pz}VMMMbGĔf^%UXW6ə6XmĀ>׮EK污$"ogML$M_FdT˂-G'j#KDDv5\+8[^^ [Yg%*D.7뷔`spRhŔ`@cM MēP j,rCXӵ33Kccy&N66n5UF.Ea^x4,cR9[n#?7X1(29︒2d^1_Ɇ_]̓S18 ;0-U1mf-gFT0ιjn`Ծ\ܬm,?, g"ϸoZoW˰G,N$ˏو|'ݣq1T <6.آ29pSیé7gꋷXآ2+[DhcϐFP[[Kbb~w{8C`Vl ̲l\_q";>b1wQPԀ9È#;;[q%4ZĻ=B/,4ΦK=T/V| Deg# _GIC ;VSuaC*U/ |0bI3G]] \_[ 8ƎDU[Pi:="H4(ք6V] %$)}wvX@g1?!q{>;|رrßpAڽeYؒFq}qiM p;bݛvvRxylVF"Wor"Z?~/ABBT\N_ݳ3n=@N2'?NBvd9K&'3 ù"h{&3_g\D]uoaw0>W,SSI"B̘ۖK~Ϳ.b ki ؈O%';}c=` ED<=fddظgqWV!x۷Rq[D dH^yA ['~ѺFG-h~D󈗇(rrR8[>eYyiH鬥5l]?C&08H ""]ͫa3|t|A3bIK(/Z aa#%=W>lX Ta2`-^NVn+Ҥ>+`Y4ReHT29kg<ϖ$p C/pMv%b@B.}FQlD%2097O9{?k: =A\f3L(q;ICGD# :ʊ 0 q/ ra1|8\13$ljհM+6 1{Xc e}TU5\mf9xk:ﬨi:YĦg4xQW/r3LYꥉs>cQ܉|$-e.]˗Myysccz |!כ³c#ۙ)oUobC Tޗ)01IpzlmUvZ74_ Wwf2_blLf0~SIDR"1Բ:g]U5X$x_,`Z,:##V$)))L8+Vzjn7111vJJJXr%cǎ%##'׃;Ա~uV3b뛉 ߰b)ޒe,#rp͎ؒƲ,ťm2 sbY tm^!PB##۳WY *) +ĶJÁɮU31>fN?%upFٱ[;k#5`og-4`?g5P#&iˑ8w$䭏fMlX !Kd3a Kji5ǞM['xؼ|%>KVd 4MΛҟ3KOgsCU^dffqF&++;:T3QYÖ:Ai] l\o/l^ S߮ºN' a.s ѯ9)RWۈ7Iv{"|B~eg+) l1O:QIͣ4Z6s~1zqyzj-Woםl{2LA9nǻ-`aKz6RbpZj )l"IL꿻4KZ>Blg-'g)9Ov2V=e;vP7'u|Ǎ uH`,[Ƭmä4N̔*X:s)UrȤ3?u2uc"Hg:[9T``TT#Gdȑ=ۃ˙q]@t1L/uuԵ\~@]:{S'A)E$-[U[(X{hb?"{<]xHZ4% ڹ4e a̎i((%P չ;ܵOrzYm/z8b<Ǎ&u,Ct|!3v s5Eo,&ѻ㺶3!'|ko$6.Wgmʱ^˱6m8g^:o3NX߾}۷7c_\H"W/| #-{;Ӎ׮X˜|q%Ljjk#"~ͮ*$939r]WM[/!mosNh 9&aQϟdwae#".`ߘn8IL%f7IRV/]؈K&򈭗 DҢ;oIDDDDDD$,HX(!PB*""""""aTDDDDDDB R %""""""P?GEDЦ> ҞőE}>t;9RhʮR %""""""JHEDDDDD$,HX(!PB*""""""aTDDDDDDB R %""""""JHEDDDDD$,HX(!PB*""""""aTDDDDDDB R %""""""JHEDDކjv85VS?֑Hp 7P_׶?RɁ>(!Cd3-uXneѰ}1_}5%o1f)]=^y>^:ve(:v IDAT/; U%O\9it=>>u-\p=O>2m 8+ydiW2ң>8H,= <nI񌧸$?+`#0Ƀ'sŠFm`aT=h-G ȁǧS{LӋz?qU]WL"/3=yg,{Z#R9jxJ(,k–ANj4.~οq5<:WWFqeG,ii:]n말n!m-r!W:1]7VR\VOzf 1]YxjJ)ltrb:<ApSU^I &)Xg#]ˤiW %UMX ddvbF+3u5bO m'iNhn)Y'>$> \!}u)(Ìtos`n XԇO y&vR3}zIҐ3Λ8wgn>|~7 @#0'#n|/ ͟/RM 0Eg0+g?@ql5:dt7C9C\f7[>z{ ~,  9'4\T@C9~G\o&^p=x,ۯei8YYݛk=ΰ7.ym}?+qv's7s=C]ո?_EiFɃ&ƥ8Ե-|?6sⷠ\gsE?7?@X<|x?u7}>3yg5U> !]L l)]A|O=Uu)!#0}ş/9l*~;^Nu2һmCN>6ډ,AfC8g7pRX܅ 7Xx+9Xy.&%r||Mg:9]˿e6č\ʬW+/o浌8vjY<|h?^K~s_zӯaHļ,~?sN~Iz6O;_Ȉ ip}'iuמf)OH韉=rw~m1ﻖ&O>'to8( ;V!ݣTDDX ˙ȕ n=z(?3rJGCv2Mfw8 F7 ^~1L̔C!}q^Ƀ_Օr˨NFa qiDo~<v6<"}Ϋ9@ZV53_B+n+Ƿd }3<ϕ7fb^L/u퇣Zy:o_x ?ʵ4T,`lzCcR0k&ksIdh'!g]x;5j. ^}^ rù }e8lb@VGt5 ؈'9[n83edj#zp,|&[/ɣK a@e5cR_ÇSܴE&CrpbSL 4#*?S> ~c,d3ri$rڍ ߲bYZx27bX5~~7z|#J!ݥ%DDggԩ l}]hKf8z֬FH/ c]Xp )9a82-A/kk-<3~Wݿ~g=Kx1bF3yԾC'pR6?̚W 9g:uN&^X qqqXEsrC3 rFQc8/,;+f 'k5M xjs<{h" E ̲,'Ɉvwf.UcWFW_=V˖y%XTx3:tZKeaְ3hik݆d&NӪcm8qwB6 ,r6[`l_VO1>LZ; }<sWB8V!ݦR9h(fegXTW DZ[>ggg[Bia w<21`6֒}4taX0tkͧ¬Kۙ>shՕQhbdѶ0,r창4!/LgMk@q vQD l0aDY37pС8sW|$ә~tVc;Ll9Yd}ʙHn~bvAE㋇O/8n )\qf`y,ٸ>?~_ [j,pI>\ǵ9g']GHnf6lvA맽c:1mF}!X=Ĉ -Bf{8p׾N}H)!CMLe6WZF1Q_>NX+t<Fdv. W.,=@vmDϔImޟjs$|1kWn.ay<\ڷfvo5le'%^4ǦcߛFD9R>9X{PF8lj&[D>}&}!/ 3+|ˆq\pϹ^ۮ _W}.2dHv2.v;e>lرu`D׆]agvX?3Ȉ}l/0B|7q=ezvCO  \Q dɮ]XAL\L>YŃ= FdXMnc֙qb5a|s8sl/I[WnJL8I}}VuR7~M7i~]W"WzYsǔԿeӁTw~'u6Zu}g{J /AzGwATd_JEDߏ\Ieluw Vc{3t` $ %Y622CRg;ɆY5ڹzNv/J=u[SQFi鏶FzU^LIۇbl-meؾ_q11J=k 3rN>m2sb,6W"3JLK$NU—_fոװb{3tR+wʖ7]`6PЅ69];V`l_:ߦ Fg>VS=W[!g*,x^YKCk[o=[&c}Yl[_U[z ~#0!5Z|.}o!F2cŭs #D`5,c}WmƗr\&FbM`r}2d߿8>Es)68>i (X ^ltwݛ+یxD&%mj\v~fsr?XY3cp紳vr[`B'n`=}3; @S=W|lY}.GswOPc;ݸ>F\""r -#+a l5E>;ٵY>3^~5N{bԞM ~7:vm5R#lgkϝW%2^) > #L$o>_/,*D+_1ma i]u)^Fb4|͇_˕58#Ljv,=̴paw#^E|J/݀/!.řrW9cSm~Il+̝f3 3l ƒ*$}3oqfR8 >ds\ p͚Og`70bWq[O3mIG;=dftę{<>ϛ_~ɋ?koe~5WÈh0-0\ Ǟϥ0mxH8~N3L{ΓN旇q˭WL$+s_[c<ڻ<0E2ugs%Q?&sxf? a?7j˖z2w? xjzK0jF>'fEWԅy=l#Ѵ?ia#$dHD1q_|l) 8.ϸftu-\.MlS|,z G<^_o>E|'{u,!^iȁ+K,>-Sn7n~69H-tMWBGvo)AO[=7:l;;{gtzՃFs:x%9nʷdKf1#x&*Kj M!+#v" !|ŏ-xk(,`K$75*V,5eE'޺R0] gzJʩ3#I$!"4uQO,YvsF$_=S]BQeĦ`b 4URTZ;`'*)^ɑA>ԅ,/Eg"#fZFϱԖPTRw \!wWT;;8cI?9FHEDb"%'`v{q22;+.>q!5g,鹱wg8 Jn~jwEYygBn^wF"1;6EĒ컃f64-OP /`쿻7mK_DDDDDDB 슈΀so9y>)r4RoR9BHp'w"hʮR %""""""JHEDDDDD$,HX(!PB*""""""aTDDDDDDB R %""""""JHEDDDDD$,HX(!PB*""""""aTDDDDDDB R %""""""JHEDDDDD$,HX(!PB*""""""aTDDDDDDB R %""""""JHEDDDDD$,HX(!PB*""""""aTDDDDDDB R %""""""JHEDDDDD$,HX(!PB*""""""aTDDDDDDB R %""""""JHEDDDDD$,HX(!PB*""""""aTDDDDDDB R %""""""JHEDDDDD$,HX(!PB*""""""aTDDDDDDB R %""""""JHEDDDDD$,HX(!PB*""""""aw""r*..w"""rJHED$d)""""""!#eYAfFȟUB*""""GIhdTHڝZMNŲ,,ˢ]M@9"v"#IJM#6..)UB*""""GhEY15d&!1C"C罹;p75޼aWRL9"CGaY^p'rd2 b8, kMl|B˟*!#eY쪬$3~Oύl41 >REDDDDea&nw#JFEEe4af ՍPHh *JR)""""r1pG r eVR9hASvEDDD䈠gFE]!#Qz먨`٣HJȼVFLB21pp#L""""r w>j5R@{.bIK#_~^geC]C?zgo"syIDD7iRRZLiy)^߇AzJ:YlzDD.(<{ݼ_J36cN:\Ƥ̥ew0l!P-͋5p.HX\LlL $nPOii1eE>pEKw`#uDFe{m,m;X?f~|up ۋ hSGùTB*""auVRRwz=8v; qFDzuBWDG OgƱ\ j?.᩿Ÿ~@~w4ٷ '=*ΥR ]:k8?݆iZMˀ䤏böp*"+qޣyp/ױPq_lkHM6r6#ILð V}1개r` Ax5Q8$u,}rSvK7SoF5cF'^+װF3Č|FHf(TL"OR"SŶ4$)7̨6Aj(RJdؕlǬ`2Vl&#o8c"+RܔmI :7}:$-E e%'lW+7+#7ڻD #UFDe(2ф=2?%|73ܡa i5x,Ӌsh:>W̎b?VIUWzhއ-:_?Vwtnw&^װ{-fy#]S~5ɛ2ͻ%n3#nॻ*{B2peLg Ij8{yV5\~t ]<0诿`(N39r]5.|z"p}}roP~>=w;.RE_zTa#S&z7bOnkn}"~\?"\6_ƜgCo*6Iʈ7WrBFT%""VQWH3D ,5OQKT7ܡ;10g6O/n3.?cC IDATqy*0M/='{riN >ɚI>OL8$F&c3M[ya 7=!.2dO>>}ޟiw׶Ǚ_ι`Uyrck LF,ϜD⬏f 'srl۝4k[grFsUͼG}T< p.%M|_&Ãv(!JK͠W)l]w|W׽ $^ wmiֶ_mm~k[m묶~ں a6=>I dB|9qN=!8v!{ҩw*HZq9[y-pb/pVl::ry&.zr~ϭY`0l$A};\JJ}ʚV\h79W18nb)ֶY~/҅vFr_$ 2{0h`1ʊW?f7d"fa2(gѵV.؍x1|R+{}~Lo6w=$?Tl-?睽_W0ڮ, { !8RЍID-qFb@]MČ' * !iޘ={;7foidƕF?mo\Nq O/?[0 2,#odg7{ԾvPa"  FQҸlF {Pun:뾜~5t,,G\U]8fE)X;ֲ>RZV9/Y꬛< tcZAw[ꫮbtނr7翦R!s4 ҥK(.+&q80IIx-? A!ôl b"LoX:/lc)޷Up8$.+9CDK j& pl,ĴmŽ!]Go8Շanf"+> A=ǏDP !bTelb|l4%Pcwb@$Mزq;)x5śX1¥59^FK_o[u0+0!܃G2WbcWnge3jٗ9X5?nfP?B~6W,diL~ _/b7f2眰؜[F=-><0 tnA۞ HBrI VDŽq3oNzS]D!DGӊ:CiE Lh\GػSw'\w Z#=bKf|q> J#ujLc)G*D|~?@{8JչU_9U xhz;E88 ) Eb}p-^[l\DXz=C}x]va.(,v%s0+cMV)%6"!>kD\]'PzgRwxy*-+UGl5' #(N}?KUeOA7jַnk2 Bqʹ\npFrR ۶Y.]q$zcޜaZ8C|0A¹w8h.K'7-LAq$5] F)Y>ʻ[MﰱL߃B9 p맸-4v fe60F72PSdi9l_<;6V)GI$fHƲب_Wkr`kZ;fW啕\_hjo]'L؋o3,tx٨p_{uLi'kݶd@*]Xq=]~7n*'+B(lTegƖkYWr5]sX SMqpHc$d-w K\%e#5篸VEGtlǔ  G^_0nfͪ,)Iq:a[#cDU5(^|ʿa,'nK2 B.Dǰt2c;N\2 +6(UUQUؘF#rL,lLV5k7KWŐknd\N(T+S 0i.<}{vRVmCFVo 32?]srkp}M|"%kXVH0>Zc'9AQ7}0}V[R'L2kVb2&eEZMj\Q;>yIt۶3w y۶swn8pU{c7_X[QcݒӘu!Aao2v)ش5yX>1h'Xr óGD?I]b'a#jL}TzfEb.ױٳr%{iq''OP[;MfRz#J<eas`jvuXly'Lz)S~vkmd@*]8w̹lܰg{ӧr*sƞ !:ol1{޹r㣈 PVV~ΖW =N^-(LT0ٜPC|ϿAY q*8bv={<Ƥ=tt9#[k}F?wȚ8>kǧn"5Ҙ{2h$(QZ!g~ٓs[m笞=@w&耚1QsD$!˶ x PVVQS`P^Y=BSVzNV-\HqlfgVcyjH!B}Û斿u$O,i8&$ɥsxj_M^M~M857vȨn,e)$C 2;`hTc=8xϬ{m2ЁM,Z0i|qƱ[NQÙ2&_~͇_81\46ֽ?.n_=-g ?c9oZ6]e?~2%Zr !h7o.tВineKSqݒ_ԩCdì0 Oڰ_0 fݫ9x\3cѻ}6u2Di޲4w.Ͼ>VUU)*\vm}yY'B[Sfj]{ͣv!@mL4OdK!D v!qq-mZGZ-fnwkpElͅ/& Vќmkl/ ]2Ɉ=ƴ4on∗X "w!TȾ<.?Q* QwWDi]xpfY&./x/r$ӵS&?Rc3TJsT2zص Qt֍TA8bኊƣ:R"T(c Ѷee.n/^J<czFRc{FziUS{_z *3B!BǏ=׌@D#9$܌W{k1#t#GͭSmT6|ǭm Ť ;R~R-)w[B!8놑HHmı9-9urWSDxIAIݕiAAB!tt!g69E!B!)!R!BqZo!ĩxWvB!iE4,BUeEɶ&ǝDB!8|S] !8P>TȀT!Btxկ*BlB"EAT3mX+ODB!ⴠ( C5bY.-2))*$w11QU֐\u )B!P=(MJNp{vmö~NTM#::%4i0 M4B!dk@l|"ѱ8Ug:r6櫹*(+B!<5b۶ Dhch͐ !B՜1f|5e#d@*B!N;5;ѦvӒؒB!8@TK}B!BqJhTT'q: !Bԧ͖8TwiƒxOB!x4 uq(/+(0pH%U$$ ~'ZI)NF< !cj􀴺h?%tF\|KE4)).`݄C$w ^'ZII:Bq5{d&zgqtC=eSc? DU}ղNē8a< !ck iQAi陘!q0mEQHKDĶ ē8cq< !jԱ/`6p@ gqp H۶[K<3U[ēB!:Hkj[F[aEynI'ZIZ3B5}nb~iYIad*B~i[B3ēB!85z ubAIJ B!hơҁh4'!!98Ү$mȳB!i֋ּٕC pKB}J>m hޤCp'7"P1ܙpV'_㦿I_wWۤ.gON9qP(ά!q'BњΈ5V:oA:(ԁ)[龎¥.R|wDL~ s?ӕ DKS[UڎU棨(tv稯7>y6y_qaLí# ;!B4eסl;YV`(>:ʨ!G r`:._Ͼ}g ׋ 9Ŷw-5iYx[X[OapVg| fy>6/˙زS]>GCl-ޞeeӿb&:Zl13u!q'Bޜ֯Zyx71{k%Y6((o_mc{AG}fYN)7Ŧoe'>ȴe]čYnv꣠L}wg3t]_܁AΞSO.+0Vk=]gH}`;OsCVU>(&{w~J/)$W]'w_;ݓ)`sM'āl,&v%+w"X)q5S=ﺌMsJ̒ݬ]e/q)]7/q͙ٱ ڍ;W|wʤ>t W޴tN>rC 20gъ0n\_Z{!fW3X<)3=w36_υYŽٰCݙ5Ŧǣ]KI !4c iO?in|>?.WemB7t&Me^Vzk [Q3λ8/&qHcaz/->ؿ>Y5 ËCr㋱Ϻ:zO`M|[zTdUp1|r+]:k^(z[C??d\$o?²*vjhc^?OűL ݨ0c+O'k9y*$\~'<2<5}V'oꬭSP~yeou:Iy9.^~&jt?W,OF(sΣτq336Q9hb}.O2s*~oDo T@Qy2'/|fJlKx;N؜x$Zt;!Bϸⲫعs'A0*O>W4ͅV@ǡ_?:RK5|.>yw?Mo"b6~(ZsfFʏ]u} <>~y6;b3yr}IlM;}k (QirȚm&%#&>ΟŢ[3!8 ~քIs$) )9S̵Wm(q'q'Bݭ! |>,¶+4Mr.Mzֶ)W[P@i\>p.vfhRwؼq\ܩ/b' `tQٹ;W8O׹o9x;{(C=q0^?=nlA!Fr>bϬe#w=MF3`u9֓{n͢rm0l$A};\Jڲt(.4Λ˜ywp S79#lwZ23',\N aDQƢsV<"x*cF=K6?48b{xɫX(CGr~(nl_*YwwB!h+DGG( :nX@Qk菢(hZ\v1E& IxƗZID EV!NV#sks:zEtOα lcb;0b&,՗a&xzJDŦhO%mے];!Bvʮihƶ,>Ac"6z;6H +W?+BM<9AP||ʱ=+5Ы'Jba"J'aSoDStm[ǚ5XK6r>~rvUҗH;?RKg) ~c@uPC9c˸pFFچQ_@e=KD}b57lN$S\|*J)!jX.qׂtU<]N!FzW;2(UU+:og"-܎r@b%W\EtIB={)5<Ⱦ<٠HOiwG962bsAF/}cg/Gvvܕ1Mt>9zwAUE #ۄBGv(>ުEgj)<LIӪ6IX~fVvp;|2ZcjN r?:!CQ=K`xj/i l!!c, oNWq,<:E̛ E`A WSeL[pu:D"DjTmm%(q'q'B]쨪iD_tccN^JTpZEUzGAR9(č:?E=gb؊ UKQ,zzbvi1,km:;6fV; /v, %d;"UgBtI}HX_jǝ$vt!l\ F%jyPwuYs 'fǒwħv'6tal+\#;URr`>;SȞ_O&}2mEG{_ 4pW/_A̹c77*=-C(IROv~GGYƾ]&%SKpY\;zK&%˙Fz\zlzVĽ8Ep)@2{6wca!kKy/AM(U9R1k㷘yF͕53V霪&s6yyo>(K!N'\Lqon6tb`9Q)1`2zy}?1"C[>Y`eD\D(X]wYô{N DNǥ|(~f\MQ!B5?)Z!v(\06m(qמN!R3֐ջz<4Mü:V?ݬC( !l 2)-ڣy,x+=mUppV-AwAG7FwnsQe>{~ 'C$7CX4/Up|>JVJDɓQ>4bsI|K]?Oq_"okbtه>R2†-)@̭h?G_mL]ϭ`?U׿3Qf!;Vg=4&.8G.j*L+뗱Yh*ϊB/ɪ 1ѿ;̂j{c}=_T:0 ;l_J?>_;\ (\vA2'^s)\87`R8؀m\s=d%n_+926]wwB!hݱ/P9Cr|\.mFF F _>'8p8˱0׹plcel!>s8S&^GOO1[\v滿 {w5 3c{xkNDw( G?'Y޹Ow*xe#Y6~x폘h-7`O%Щ/0쀎^s]|>۹]ĭwεFx S>? f,PT?iþßu#¤^7u;es5όǠf(j[I~dϪMI̡20sg,V3bhuF^|3N57>]z TTO þw6mMsbsQNN!펒|go?qm˲ضi=zk~u=ټ9٣F]rt*ò,tC駞橿89+n/.P"?33_!{:n`7Rx^=:X#=35B8ƭeDׯD`Gjemw )׃Ks8e#7N1u^/U Þe誟.aEf]a\E6صe.dĂc 7 UJqK|Qjn: j=Ӷ=w#=8f;l'͎SݨdiIuc ƍ1ި(*X2B"+L0h!{ bHڍdwHؽy]{֊'!BtL}1heWQ<ΈQp]膁Q\t`Z&H9Wj6€Bls΄zg/ƴ)2)imHH&G `c:)1m(~RN1c!PɈNjQ T(l#L5YUiuB mi)/& LGZf:/.[r,E2 W-Kg( !j3MÞeȞ 6F]u <;FEv@%#+.'iмxDNN!MZC3'~- XmN8w|]-O>^PDK$'V=< ̤4%Xqr|;MfT/^9upkI~%B!Dь5mMCv79  78S/'&?1[_̨=W @!ZI+ƓmB!Hze78y7Mëoόr{>A|wXY$:& !BQu a mgSqkX8]IXB!DfHUMò,TyձnT"D}ln6:Ɠ'V$Bj]븧 > CQē8U< !c:ဴ*EQMH0;L QضMQ~> b$ęI!_fHEAUUQcݰB,K6P'2))*$w11QU֐J<3I[ƓB!:F!D'%q{?=hCFfbZ,$$mOB!踚ˮU.>O$:6 E &d*i(ҪJ<3A[ǓB!:&͐Be'ZUUlۖ8Uw;έ=C Oі$BIǾMw΄tfnˍW$ędœB!:& Hv, q::ZI)] !>M$ !ZēB!84!B!ɀT!B!)!R!B!D5~bsp*B!BDZ\fHB!B3-lX!B!D7f?ZxxdEIENDB`Solaar-1.1.20/docs/screenshots/Solaar-main-window-receiver.png000066400000000000000000002222011522022367600242700ustar00rootroot00000000000000PNG  IHDRQf sBIT|dtEXtSoftwaregnome-screenshot> IDATxyU?zknnDaQUpepTqAGqeA88::.(*.(*Ȏa HYvonݵVK   ٟ"~h'   K4G@cs""JAAC_D%t_+_=8'   D"*|A;_v`   ;њz([<   mbwH -=DAAΝ/AAAvƵDg9~ihԵiR@sA|  {/|i< 5mU{xPVJٹu A㨯YckT&^b8&SYvш,CCr?Pl0!(+.L*BF :D+0d B'XNLiwҼcEAA8on4NS/OQ_W }2'hU*ؙh6%gTѨF4z~s:OқkDZP!O9K-TU!NS*NKD]k-DfvC3ؙw4  pR@d~rNG3{xb`<~:'hγBuhĖ( 1MAT*d&s޼YM8 ݃u;Be faZ^`y;!bW;_ im&o]btAh-d\ S}NF Y>Y]P;'O#AS'7kwrcl3㎣Q ꟣h[OO'džckp?9fMghь]4v=TXf!/Ga`ږDO1Lmݾ[2ȵ`,p6܅ݷm3eRJ F׺f l^W#,}\yO}b"p0EN*ƙtXFTMKFL뛨:| =fҤ};j}Z+5؎S3֌=-q,n1$7zn+5F+q{P@iX0<`ve: V{hLKSeA+t!pəyAϨ )lP:@Mao A?7 :y+cg|Ih$o:##UY,cѨLc +XNf}  }]n Q4Y;)#U+,E_A@{0k+Ѐci{ #Ms[Yt?8XJa,dm¶2.Wpz5PTCQ>zvnLc> `0w2ѐAAV`ECR͜ ]%gVãS@uar`[e,`Vw c9d6߇SL,QN(b>AA(:[d')tT$ "Qfj4=▔I1acBr]'(K25Fbh{)1RXo,s63-aG0B$:6.JYGKݮՉK?Gm𙞅Y,Km(pgk6K- Dcj,ҙy(l,UѨPx1PԈQÿ+F :aX#0-]QyegIn-6X e ʪaG,@lWAADhnLluHDQښĶlz_׹qSv([c:=prHƋ6(F2j>?^/> )eY80S13F/*o;ݰWnH}m,W\l~&F K3=0-gi s*ZZZ mGl^-_^ 3%jJ6R\y3D--2 bxq=0g)xlx*("dt'|  ::&_H~j Cy6xM?VD^8\ufb^d3s.c0=oS 5ۆϵ W jZ"Y -ju/Ĉ_G ymPGXx eJy;K^Eәԑ"s6t2 ⇔C E^'0㋨ObM(&޹p2JG!d[09 ,BY5W;'aK9 ;vȺMܻ{"ӿŌŇƓp!2-O^3;{Y~|S<8"+9I;ߖ`*2[枍=Ry8.ea|kx$_3E.9N _ F['} NXV4gOΑs,bV yGQM5O^~U5`"@Oz7R*^He q|C%X 0lYC( 6ŭD(1mcE[0pDYgBPQ#XCO^D&mtL!薙;T I6; U{~.?ܘv;}8z?dMotV_OK8x}\w;wءc|3ssoko)0:w~#e6y~#GU|bbXڙQ ;BԨrtLԤ5+0zNm7F47Q؞F9*A˧B T15uWw};YU8:h\n€+rddo\2"-a 6Ta`ĥzyB&CM&D{(m"P=/9kpj#xss3cnՏl}$d݈1@R&Dۂ|~4b1U OQ- X@~r߇|K;[EdV_|9c.o[[|"px߮6Z;q㻙uvSrS=ζPލܿĠ=^.#7m3vIշ $ߤʡy"Jlzbd$[A)պ%7ʷ'G^|7I^:`#K,tdmHƣ=8ZÐx~_MMIX 3Rd'X.AYܙGROPC)ll:k p]o80~`FѵtavoQV`f\7;K8cZ)T.lڀʴg )YFE/E׮ qabZƻWD }Wv\uI'rJ/8cGE/{2k o =~̵֥{ыxnpU,zImV$zMs>n}w">,վpe]U3f2mj~G>A?t(3ٚIIc}qjYGEe 4kW񄾋U֟1(ͬ -h <ӳQ|YY26ӕiOcħR 4$`H۶ |'FShZ0l5@c2 X`C%t*uli]+>+ .}%|۶k5H nd`oZcL_X*CP k*a]݋A aW3!t,[Yd,h''Ee /p @CO@u?O?Ga7/OLN~U}wqI X-x)F,(8Uۿ[eG,eqȲ,e~ CuaO //{d`A?iw>`pX{)q{5HKDKGL, 1:AL510= .|*Xdm7|/Z\3~vܭ<,6(XcxjC GLGNX!9sB@NMċ \ǔ&c|!2AcפۖErU0ZZoC< /b#ިJ]c3ΰ}p5"ڗ)\o[֨;3jFuc:eYh"'OuمiQ@ V٦}az+Uu+O6jDMnNg0Z.UI/)g%UѿLh(;:%bAtfA* N9EѾpKc+6`;x2#5 hw}Fj"lG}\hV?q A}I,L~"PщClק%CaZ6:;9.e eqR_4)eB_+2~GTS"JH[^&Bs]tU jw ||ڦ }߭ŌN{lWek?FkjZ~G֢+ohVE&%GcSBh;`EִI -]z'Vb"w>VLxJ?X \+h *x'mb9l@ oJ;-kQ]~{ʌScgېK_;JV$\X 6#z~Q8WlZrYfjWcQnD.D6AՆMKfv1$K.VQ,,N8?~hK薮\ttsˢ|ԡQjT D"UDy XGGkӃ(g))`oqܘ@ vEKX ]n;g5-[R3frdzwkTl̯p$4RcgjŒ˯޴` =[iP 5A\Up_֕"Oa?b+?xǂhimafavAQ,L͏lY~jV9uA5M*G8 e6Wp_8[ZH l2{+lH=sl̢QZGh+S1% }t]6Iy)'oT(faX7'غ c+,_aV,g`<7/O ( '| 7܁;$Sqb!O`Gz&?&4 Qo/EOq#6zQNc]  SiL؎OUb٭:(?4dL2&V<}ً(=$8|jғvJ^@ QbEmZ§z !+ `uaG&b t8AtQV r mY(dB)0ZSB+ۋaqW*eڲט-qߓ4',Zf-Q:L 6k |LeAe1.>BL |B9N&Ϩڐ[_\g>ڲĝOg4} pC9ŧ<żNmUDk6wҁŬi-4Z"ɹuu/-ě">~qMZ㨹}Y #^Xo"w0Q 酜bvkW1̈́ߘ*:sZj ;fniA$l -A9e"+KhbR;D$b+ Q_OqVơ NUcE*עbvVCϐ IDAT%: >Cw‡$=v>W ~Νa'w$bxѲfڭl糳y,Ǖo%p`H&qqcҫyàF45P  1uS* Ѯp+@VMQ)!qBJ~:u9z_דӕ1:_aD[MJ#..%ᔀzfBP?;Tf[}KÕ %a#.ƫD cF׮ o`Ʊ$C3+A k\gO_|t #| 3.I 9Ksgv Ҏ"Ivv#y}™Ͼ5[- p1  ?ӓ\C1ש&fγҖ^úA8䴇ft 5Tq2St1.{Qr} l(S >v*MlkݨH5͎ Ō #>;*]Se;Eq+hMqX0R8C`9,'Cg,vVr왁rvqCC)P+32Xj%TͿ}ȥT֘8ܼhQ&t狘rzhMzR>`"l^8pvT2}e]A!KteGf톋úRS/w3߹oׯ>^8Tsrk?7ak_68t?V.xټo.@wT>OgᾬKFaZJ&: j6lT&_C.J!p5m4²zF# AϖLXgB}6ddۖAmC.eG7>[6:\9rKdTZ$i 74b!WTvZv E_BW1vsCr4.˶>l'~6\b%4{A.٧{hP֨ ]t)V$عiq;prVvaEAg=|[ ikYB~jODAQ]33NS'eȽ#?9<^7M1c\wiQnCyXxN&`w nF r8Tam+˒]<-O/>.Pl=2Lveߗu X27t,>h 5NP{A,@CK^WN ߵ޺ˌRq іI0E5O#3B^p/Nexh^WBW6픘w,dR=mj5Gͳ6`& `˘x:njt"ކbFvV0`Oln{[A=Ͼ}/΃ܰDJ/n (od77z;W|y߰^n†eƒL*06<ɿ::6N?qǿͿ~lӊt:0һo|g\xFvo=>k@9n;A8P>~ړ*NEIuɹm5Z Vm7cgz)Ī;5,tﰚЯ$=-t2*Ml?]w3BR"0k,m#9@ղJOʤEQ?FL}uTG(Wݗ^iYo.Ti7ҊC5IR% F2X#%!rZa葧,Ιh}ݳ*(2^4t{K˫(|pOx0.?m׏;`Akijɒ 벣'v(Mαhnt)چrm}<578)p,n`k=}<C|k 2A*a] ÇM+-uE>_*OcQIhv4{d˃kaŊ˴F-E,NNU#KVZ-NI[6Sgqy εRX4~,JRB˲Ft=uX~:z,dAUq))tk+^!OF~38*86FDCA3h}B"dTPSUPQJ'~<.7oc}ѫ̓9 T ³F@O (ͮFC` zkI%-TQ1CScS<%2zlzZ ϊA_m{21b'e gjQhkSnd-E:2\}|;/Bl؁xmtk(ˆbTۢ|1ˀcoFI68<*uOAAH+ZVͮPkEN|XJ0UM$-gƪׯJb(9i U^<%M?'`UKYUȮN?*vD  洓R0515Q) 3ZSO\kbwF*b_2`  =;Q=k' ⤷$7 6<)4JU)8;9: '  :njWԫ$kTC.?T ׫L}<J7!rƼz&l71Y!W LлW6LuOh S e4*T8~L3ˤ7AA'iŘOhMUMᗱijךjfɈAAat^gHLOjj6A n>-1c5cƱ?(m2>Ou![f, gOfo"ј7++&6~as Zj L'?ѽԯ I_  F&&֍shtذ|:z>k{:n>C}{:[ٻ(LEC}?0uj,O I=أ}bqLZ^~4W3h,W"c&x!|  BncRkL e*x= ܯ)X8;uhA!ĝOAAgBD;   w>AAA- %|   <["JAAAx8(D  lqP(qAAQ'  ³A!ĝOAAg  *lһhg o=AA c ec   <%  0DD   LQ   S@D   %  0DD   L)mتW?A*^AA&R~ O> MܔD/Og-yͅRbAAy{xO/y'>_S)WsϞRbA kLh4İȵҒ݋&ݦp@#sappzz{9s&gF)հ̕E^t 9ao/?ږ=A(nF6`TK0-$sϦv0e&?}!K{򶿻 Kx׿;^M_ -?3Bh@Yyq٭fx+O<3nCar-Ә5sw(~A8xQJA[[b O;og``p.\L[[~B^D>L>}mAxNW; syÜ;WYW_=uWx:0}?4¿ͧn5TX.;e\tyNAXŴN{{JM7yfӽ 㷿÷o}%#MQ{GȴuҚs.焽,% `ulFzw)P*|}zCj6tek1Ws+w\v%}"@r9xߏ GָnR)Qq+ 300aY{^a0 Z4s䟠|Ͻg>UC%ag}Įg5"d/߾ߞ1őӏ#1Nna6͌C6_ B<6~bf :hOɜqϏC+X`5I_Wcˏ} Z'pذàgS͡WlV~qLz6l׋eqKt6dڸҗv>;~?%RA[nݜxI8R;^.yz,YrG|bY{ٰE"0rɫ? [<*0ؗrDzdV;NyO>p+70OE;.kE1ujW}q5%֬wM_):<]9_/9cAIZcn&VZE>7u5_Xbg=4P{>q3#Tl7c@q$' <~-Dڏ : 1/6*+xU"6ﺂ;9mWw4izy/CNW޿b}QEGdMH(=Ȫ~ psy%o>_-̺^Ϳ_VF>7pb}=h YO7 S۹Wp[)z.׼s7ORXѿ돖0d9e>+Yկx}>#Wa6&>g.Gx.f{݇&1+e9z5 [5䷮#.#Og/}u׽bG>vzԀ#ş}o`c7|k(-~HbUV|Ǟ|'7</=eŗJ/y#T@^Y?!6eWM89C8k.O~S?f>5Z>?Vy>㕯WEBec8Qr,&~O͇J rad˭\Iъ2~?)ڎ=L~#3=A?4*[[sj/kD u*-}t}ܾ CrEsDdE JL0̺w+ZP(ʕ++eקP(ޖDz,lۉU֛yG.F3gM\fzg[CTy|y z\X|!p\|ć^Y`v}1+x'C_dF]5Û{W<si'sH{ ) G"o~+b]gx1l~d%+wg/c?(Uml ^O]L tc ,ZcwݻwcL4%KQW=?9g٣֘` |#`9ן`ߕ\Nνx8G\I{N>{{8G~Bf[\+е2ȅs'W\3Q؋Q<,N=?ՠ{]k?zT4;q,j/xtkdϟ߂ Lc o8.JRǛ;6<Pdzw7_R?Ou?moy+gqlۧKŭP*- Q-ŋ2o&4{Ъi n"Ǿ=9 \^5{~3x4ȿ80wpջA]<վwN]x3xFR9sZ__{>ݾC|էl;xSQЈ屇3T]w57'-ŬGw*f/?G6=CL_Bu588jX=x ߹+|E[xqԙ=^tܵշ|_~>i~lz`{ca 0K?>ȚK>e牵O0{ܨq.fztF+WϘAp/~mpW]}⎕sy3k2pRa Hp"k92OzN:j$ٶNPR[V/~@jfuם='oG:w? K_˻^K`j.=8կxh^֋[gKA8yb\⺕6n uHkG;= {_𲗾 ο>>iooo1x75s,*n(eQuO 4~w eMK'Iq4ϓS<wYuQLo?9o|5xS?٥};g`&6W?jy݋fp;OW|R+XCX,>7|g-|mWLn Y_൧!^e^+7nsS_3/]\> zת<~Op+?`Vc~|>krsPRp!w?< {z[z5~]q髬{cb/g*c Sn^a'sJWZ)ŝoSbw? Y-~?gMGn5+"7o}JZBhy8`O2$N!ĢP,P{޸B7g{c;9V}X3fEr_E99V hٻ(ߙ-ɦ'BH ;ĮX7'R, R-(]RCB'[g?@ {>σ읹g s{?cx9DHp;ƃ::˯ҪEs/ZHffƌNvv6 -v4l˺uԩFcSJvw[-tx"NxdǦ8t06hGr׾iL~ߧ9M`T4c͘[_2փXzKd֘|x#TSU":v{9BބkQ-4>whIauTFHI`ǚ @ "2H9BqihS[7uɇq6,[}ٲj=inhiI;_xB@gRp*oZb I;޴+4•BRrA*#n^^y\I9h8Tp%$"j񡊥 e2kSNWŤ#.pg ֯H ;͖G.t(Xj׮" ͎]2{ }^'Pź ;~!=@V-tL&)Sϴ?ӦM CGdcǏ(FuPp ZypZ&t"}/|p pꬅ{Ӷ[+<!gJ !JK38ȌvM"ʴ]b|?z`fرf%Ag0kA`((J59UrkY_UJz2rQTEA/ уƱծcۺgzq^V?7ѩm4ʼnqFQ1P/#BT_L6t͆gL;-[Mwdv ^y%ܲw]:EăpqdjESӶesotmɹ䊊EKFO4sC0LL:I׮%P:0f`6p8W umL|r)"""J6W,e5ݻ_23NׇHNI/ahޒ:b݉`^[dy&tj{JFԽ Á$Er+I!u`$JQ\.?m۶[z۠.6˂lܴYiS+GUKxP35q31c"$_ѧ⿤tIdd8deeQ,>DDÄᆵ(T#9_6KWs~VѵiMJ[kؗ@ @T.\9j28&ؙU oxO>=f%? aTZezzUO F0Vzvu/ɴAl EUU|}}ɵG !$$:u~<6lhۆV ._ƽˈ/xF vtmx7*Q^.AKuJ#wb؏P1tj 0lX;3 ZgY6e..1e< bxU )aLF'*M9} VNsUI+&3e ?#ҵU*͛1aQ\k m|w3Nm߃OzvY_q;`X3 O s\02cBk߸аa,X,ֵ<12H9̟sQд~zޗ'J:5j@wqK5ITnn.ADFFt 77722qww'##5"IJ:Ԭs@@KDVUq|n L=3\6V;Q1ԼI /<%zr:/ߨ lyI(8].l6Úk2Ȫ!9q*g3]:Qx`@,z{xic8:wgwoa4-Qi=4W[,}UGpen`]Rj۽'ggM4 A48v>s6ᡒS Q>Du2C-?GCاhFFŦ=k-_-#$HԖⓟ٘Nǚ#17FF6[Gf_4bЊ[1}4s] aVZurc?}33=b~3Z!nBba UUif0!'>DDdԭC:ys F 7J˥\(@Vv&۵c݆o$Qv̠/_>6qLOƸX:}g2Cq/1{ ۙ0&ڎx&_:g&CB q )݊ӹ>De L+ծeZ5k5~gVBFj*x7;@VNZ]VL!&/_'S1RGOO6{F4>OCdz[@=ƪ߰ @$fV7gjp7MB%:2qčx*ecxHqioKM$6yKX ȧsܡsOgT497YViCf?.W޿SHD3X!] 7pqiN32pmfN'ww,?Z)ua]Q(Ϙ^9ء#O=Gg~g02!odkH>0cxl՛2ٗF+gFC/~Cvdw+頔rd~H%Dy{ٵ>1~9TݩqzUUlپ{3?gddyRhԗLj+?Lmp~wQ0z۪+.GAjsDWKzeorqnPu:Zƃ)EHt#o{f_[£So8.΄6tűGMDZ<0u fɮ >z>a7!xyC䛐f:"G;gX:4ooBe (~+@1գ{|I P%"Wٽg7 fFӌxb6( i.Vk.F`@ <4gSKݗ(FmP}=S淢~7Bn)a&oxkQG^\ļ'>~Zbr}G<}…&+SÉ 71V!Μ8ʜgéLiRu]XUb4-xuD d(!Hn]` W׋;5R/-(8:OOON'3?JPv'^pܮ} xZ͋4NOLp[Y%Eӫǚi~ vS)/Tە7ԥ,}c!@08WOi45-} Zh7Kl`$ihz7#Nx,.^UR=R񏽙n$&[sûJ(B/Q]9P&uLt Ӆy{?ip8p8vv;N:؝uV> F#n{~ciij̓չj0=CS-7+ي4{og|J+ʠC4߁w.{=;c=%f֛Is3Q}֭%=noE%NT!&ăv2DB`O~%wB {oc{rh?F`r#+֧.6;Y_/HEa7h((!,3n}^բQ-ʏjWNJq}$ob䦛qos?p2)؟*:la[Au>ՓRjRۯj4y]/WXWۙ3g!;; AGsi8N\.pEzF:ޥ0(1p='Qגl/}~m\ơBoCRT JЉLMQKX;mNE$21Ox~5lD=kZoaҧYrK+.jng?wۡ`\W^r CgYmڽ'Nd `.և 76C8?ץKt)󢿿ӿcW7M#rXIZ~ksg񣤭eA1'>Ջ*YGV2_P;+-T$8N ÁpNvrp8N\ NGnnWG:J\ї.ϻkFnljU,-'CINXM2{W"ij_8ÙLB`7+LGw$ C Cf 5+wby u Ayt L~9gN0p[Er۟LfK!Sݳ99l[Pᰓk( l߶nғP7]#QED]5xӠyx .9թW !Ҩ>IoZ "cϋٜ((Ȥ< + y1ޒ@ !5S' qw:iihZ4Mst:u0L(–͛UT+¿*BoEצt&NKPB}>1Q}ixS12N$r8)lɫ zǢB\Kt*ICb4982HJ:͛ LӦM˼B!D0Zp(Bq]QU]{nY \.g^9y///7jBlm[Q$B!B\7TU~ԮUl233 $y* IB!c2Zr'B! IB!B$QB!BQ &J!C'O!!]q[I:T7r!B\]2O!B!JA(!B!(IB!$B!BR$J!B!JA(!B!(Ĺ8A$A=BݯDMƮAú]կjUpiir3.0!6, SvzU{b3zـmRGB!W_&Qǎcƍ8qՊÃaaRF+?c%oug4P^<zDŮѷ:|ʮOZ"ݞXLGk[7/ƹxc Hh˗q-bc?pL~q/|6 W4c^NU;mJl8zsB!5Ta6l`Μ9T^={rw9>o,XP* Y hiO?mrQb2Pռ)4=_1d"GwW0T)1_mN}ڋq2eJNg8Ic?s9o ȼֱ?mbȇyy-^%FbmҎM*E{, ^[{ѣБ<99g}ŋ~R!BUHeZYt)7t|w4k֌ٳgAN8p (݉ uhP׈u$UmLݫXUKH;5 X7}ă-۾Q’4)2դa}Z霙*,LAiǻ60gԋ<2> }OfZ≍]ce Osrwck&0%=Y{Mz<Iu4nÿ(#8ӃZXJOI9mKۺu+O<:gߡ]t؞#FuֲT!jD%$$Fiӧ|w8N !OP:ws ?NވĆ,<#z:ҋoSdgxaA*`^_Z.7ehd%n`ጉr|?p0ɃnMb۲ߙ:q}Y{ I?`G׳8m6$X6Erea~엌Ùǡ` l{'{d%3&^G%o.+,⬦z<س#t(֍ZP(78sH.b(uUOJc[snO굟z[ ёSu;nsR/|Apr5:YYYD֨AZu @jj*~B!*$JMMEj%33B5HKK+5"z &{Nbfi$҃[ۘ(Z{Lz)_&|w֒c _r{XYY?nk@~<2Ixl0iM d}4ZFqC6l0IU)H|3Z=Ƭ86Y4kւ[Fm=n'6<##xbӧ U)}thތVC[tkϝC~d% =~G,ŴP<P@.pnaOi6-fʲq ?OռF1$'ҵw\GBƭVN`ݎB/( 0)~|V(oFF3,9Oϼ3gΔB!p IDATת$*++ UQlXV23m\rrq\\.t]';I7D;юdҢV̙̟g oxjfKx/zd32H5SGr|s]1u~{ތ7WltF?ގL{RhH'e#[yWLŋ2s[ l?[%`"Qcmz<&;,}+=_!ذj޸nf<׋>LQqwM={_1qאGzgs/S0yYq\4 - *Q8Z 3^_s+BlO>En4PGwj^"o*m)kgR8{p>V g FCuRd\~wW*-'1xkQ!X5͆B\8 #o#''ҝX =&L{.\?gEd IMAÿ-Y{([lXķS?3axn,j|yn=k׸Dfɇ7?B5 ES~fK1^r=Tkٞ/-ФiB˻bp0⋭/߹$Il nF2?y8XZS'n${T<S0Lk8"bVWSDt>!BTHAZZXVz?@.]%PVk(WmG~[KO`ÏqVﳾY?i޸ܱxzxD.4mxC0Jz=҅ [WǬ5G)M pst?5lHË4)h?ƙ"1)aT/Dž/[Ǻ5+kΏ|6>eDF48CR`2cp:/;q8@11+ehowqu{zeTwGǒ._>P׺v<[F#C9,V__'BDeMvv6Nә6*jHb"#K]ˈsO.*Ⱥj2x?w,]юc`I 1|}EmZޘӋx]cz=93֜JӾ |)%^~u^brŷ5,ڟ1fph|:&j]׎vlvѝsEՈ& bw\V7qMh֢ }ɏf~hWa 6M\դ᫂FzqO3,?A!׬YOv"كYG*o0Q9֤Nt4X#QujޔZd:B! TXa;F5.==+ 9915bLaoj3I|e]E}'݌s~*oMxŝ?fxZտ\{˔?f+ƨMjˤEpO- O0 he=7m+ho$l=S0o/|*x{)Vobᰰ?.=f0g/ 9=d0C`)UE#9섘"ì#9dct]oG% V\5#yDt,x[nqG3.PyY4p|B!8l#RJ ы?33mN2Ā>W^ܺcJ$ƌڼ΄go͹q? cvngD\h9m.@ΙÇ/#%̧Yc..f=)kiDɑG.WrcΧo2a{I=CAǕZHLi ?,VtiK4ۡMںҶ/N:{ [WjOUDs9XP'';]۫lo{B!*$;سk( xzz\Upi[N{\QXxFm}'\bu!oPDƃ/O&#?8^eY =CTC/~; l:PxFI-l+vJҞۺ|2ip`v׺hX7R+(fwx ؊kfCPp Npiĉʡk{2/ahwKvJ"V6|l_?Y^ ׍!L?Ȯ l޾-6dwjoWBQ!IT:ux̘141LlٲS0tPב{QIVc=7϶07tZ&+91s&HMeŬnlZ·92p&%;/ђf0m g(ϼxSvuT10*' k sǗ< crw}h_ϭ,~4T)1Ǹ0wf#xkaO[)U9ka6lÆ g;), cW9\!7yg ?@~/0z4oⳝn4cG픶l>я1 ̥Y+_>ŚSZNN{iQʽEpF׉|ZFC !UXx5jļy7n#(87#""zQ5Cf,ᾦEO*i7q߾Mvuk ߼1Vϯ`3f>-TƘ vYKPl6̚r[o&Զ{RF=Zm o<2^_ Yt-tY'w\w8/~.Vb`qA?|;]"!'oܓg8wԞFXr9/fL[l3΋mN;, F]xM|yS 6DpTmXm1R'|tWuNGǶg>Qd4_'5's:d zD}.ʋZOqi6Oûdq+ǯzAU>EQB!?Q#Gd][7rcv}.VxJUx2O#~ ־`m]1WOٵiTpKߖ1o.g٫7rK}FuoK2l>+z]yUp3 cּ 8E$216jXNn~k0E?͒]FMb٨yy,uOIۻwmH(\2Pt !\sdGʗ)9oa^@x⎧-o %xf^x]I[XA+N35w>&oZ)Gաp)rLUe(m+ iA{]M6YAZB!| o>(qW1vJ$MC`#/dR1]0$c'A?CӀg@5kUI3(wx\4J#;i{MoZQt;gPRVB@X4"|/}:!IBS8z_͊23xӠyx +P|NhZơu奘 рWK!B!JR!%B! IB!$B!BR$J!B!JA(!B!(IB!$B!BR(>Q=^q!:!BT&B!BR$J!B!JA(!B!(IB!$B!BR$J!B!J%_0YtR54QT/7]#UyH!Bq$JU6褝qp~&QtS)NΖ5g,f1x\:۵}Ïa[}2y~ a!FTu4NV,r>ԿB!Z&Q']QU튧LuSH ?ț=6SKv\F58FMc~r^:2s01*\-Y|*Ze!BqUXuyC-ߌclش×]nQiaxذUJ?ק'l͸80UM3Ѿ?bydzMAÿB!FO;~('Ow61dPN%'V5 6UtWs촓8:RUq(o^ɛI´lNɠB!$䉓ty?~}{qIN4t-#CJ !BkҒ?~!K>XKǩb6qw?o:s;ʞL3ӯ|ӓu(9ٻ9w jCg?8-:dkn1G{+ߋҮVB}80T%m4H?٬=漦J*x)}HJ@>oilspiv:w $Ȑ£8 .Yv~Y D^{>U?ۭxe{.Y/nU(rXx|1Nf=XIY)Bq](WUEIjB{/7m1ԫ˔鿑͚3Y4yEk*`tsɃ5i*?$,Bk,_Bґ~O4ț~r,6:x2(Ѝ NLٰ-.c~1ƗS0`K9x?]M~_ռyة> p+}(;<8h89rX-nt"kT3]0=z/z^x5B,FL`KNB!^I<Ȣe lOhTShNHTS:vg)xzxQ&|'N+ %"yrTU}J hvjǁBl5JbK6*`UX kgG3ƬWB`;w l>BÆD{0G..N/_t_ ZBlf%_xn1}y$d.SF}agn*-UfcX.xnC~~a?YlJ!S摨ܾQQkpU3M[^5]Vde;lK*fEȰFwS8=vfZ{3,NfZ*fyN%ŭ TP9|Aq4%r:e cubo:.Vj|sh1kdq -@j&\ K4o[ 'lyŪz&%[md_D*nEߒW9lV]Z1s8c/ұQ%[E][MlH/W {en5UaJlCEܽ *[`Ej&e'wai/ vsʢt1.oQ1) B!ʾ&͍,mur;ͣltXoGKܒJFM FB.ԧX%< +`\H,}W|WfYRMʔD$3E2ylP  k)@(`RTsI:vp00OWDK,ig`ّC:`nhVJR1.B!D K\.6l\ Igt]Ye:бꀢv52d@;=UVҨc[;+"-nƼћTʼ ;/̦=^Toˣ{|xH8t-O+Ktk".YA˯'+k_3հ#y9 7w'Hc;sRxY% B!庤;lD׬MZ1ԩCdWj+<$L:UM#(e89TL FV;Ee}7 {9e8lZk 4QS.%H&ouFu{c:: a+]O!*}]˂:Rۀ9XHJA!B\ʜDiӧH#9)8՚KzzRJ TkE% Q},<Ȉ8'x39ltWM ,t #,D ؟P#܌E nlh!EKteߓ;bb'.j;%ꂪsCz>]l=w\hMwjs\vA,YwyP.Gwd\nFwZ{dS6B!uI1-E-h4JHHx{dJiKgLVde;| V'GO2r~בh_CwsgHwz3޳VupŠyE ( ٕdٹ|J71B|жll`&<KwVa FT?m9ls7F#B<(omer! IDATe <#ܹ;ΖB{CVY1!Bkk6:J@ʼ \(8+0-#g͛FUxw'NٗaSbV73}8IEU}|{ĝk`YٚAntTذ0|f"y{|[{t_Zz-vgl/ww 9,~}m&֕h+3F+>SUB!ޔ~ ?Q쓗}s#<޴kQץW$IЫ]fl=?/_^2TLXl<%׷.HSŪ@@=]*;$;݁+n4ɶBFA\;ϭp.C!!q67]הzreɅC5ov*&)6U~=NVhN0cv'@ S{- !B^5ϑηVZj5D5|O㇣4uLڊwStx/LFSfbb2U H7xlhs[0u7Gwp5]]d-Xɪ9]`^T.@q"*+1o X1[!hr?Ɋը@@ T!B|$D|>L&`]zVb4д3ݺiOĵCx&qimRR2.)][Al9˯0$H5,mzlۑFNl=B[@ԜUܔv=\^N= ^Tg Ⲣ=&*wjۦYj;od"k,X:6jNMoyĩ*1Yh%8Y40s,>hrczJ$MRԞ jr߼  9NK9Q8|Q5Y,cuMl,!B!eX躎khZ8~?^;r:$7/- Tہ>@Ui|L+VuQf^BuFWGcBn]tQYk?ɱ&/ͼ`IKK9U\Ų̙8/+kU.=4쯼zj;Heztc#!eڏXŜ&.!-V͇Q{3gUTT@賓9u,%N.>-T"g#or.B!DB!ifyxߙ`xn'}oU85Lfjdy ^Xio9W'.$g8454F\HJMTҏxe(=48<Kr40eeAČ`o{#Ly&NNlO;wA(-yi\MB!K|ǃBԜ9s(..`p^UzJagZ.os3,5Kʄf3Y9^{F,$BQɆ+Ɔku4MC4p9 q=~m%N"򂅟q(@痢uĄ$1>@{KL8we:=; AbZ:)qc=+!BqC Iz#BTNN999N 8u5!<>:6j#LZ^bZ+,\H:K]hxs]LhP][өY6-GHo!!9!K]i15v2xߩ_͢FM$%%Lǘa4 gK+@`p^6W8 ~w?՜HŎmձėWsm?{RxpYU>=ٵ~E~0ډK ib2bPUhylN`=!$U4uZKok-%w7S7dtЃclv4rqb0^cK&£c  \fᵝԕogkB"XB!!{&fGdd%{|>& 0;8s8zF$$ [7 wSU`[EtžYz5]'9HFr=u+=xzQBj%.g>sC`NPQYC[G9;tcxHΈ` a;60u exƚz<3cfB!($ f#BD!*7`] ͞F*k`͠ cS ;yo=_q39gփ2%.d Z/vS}`ftTf-"ybSK֎FLj.soG>r7]*ʞKz.F W =s6iZ/5Gg^ʺjQfFt,%B!5}t:;:@QlÃEl64751sP42JD6)&zN(XmKi]6F$MՔvCDV !CV\9V QZvǎq}/᡽}8&6B!Ͳe8q8%%%x<v;O`LJ1-/0)?V[q41l355KRd1+`ֲq)YqnWVF ٵV7C&}w;GF袡eHWĹpsKCBT R̠ugv.k-6#τbDx;. g~Jwr i)!yM!By 3Q$$$PYYɡCfȽt:CTH1Z!?jLyɕ:;rÂՋH35 xiO3u{rfX _?3NC4ל[ł &cs*x&|)ΒiX XqS=nq_CQ A;j皊"vM/ *r(l.ꨄG~ B!6B!BL(!B! QB!B1B!b$D !B!HB!B %B!QsgϾ%!B!> QEǎ]B!x(!B!QHB!B %B! !J!B!&@BB!BL׮w!B!BwMMW[+=}ь$&.p tRc+* \Z2C)B!BMyҴ.U1:tw+%p^~T3)_8 ʵc[^NP+,uC`p`1NB!X$Dilꦮ~K$CՌtH]C=x[S䦩 3nb4:n#fn{qmbB!btS~?>0T63.@7vPGh 楆_uڲC}694t@銼⡵A!B1.S^( 6E޽'nNMBKt!x wQRt98K{DG7S{)8GE@;ͭ] *&Q'4<z<} nIk =B]jͣ`E`'B!Ք@0/Ћ# * NjvTzokIYcsL_Hw$Q=-i9]ehETV*iȶmGkX=+AΟ*|rO`f*y/Ss}|lYL(B!u0e!nAz!JgtA\&0L# 9NK9Q'{9F4xQ,OˊJanfTb &i#8է9fCQ "(_I7Nr%5L3cB!Bޔ>e4 ᆵNF#Ωlr:]]hHbUJQQJL= NrɋF3;Xs(95 ֗se3q( yLOSzS} \fL.|%H.`L,!B!uuM։#>>u`А08± ~H7oDP]i y/lg")5SI?.ztB!BL:D@gg{_Nj%̂fgphRHOϘP{=dK LA%*sGrҟlblFPG4tM#  !B!u0@JJ*1443CQsߧRWW7gd*%li}2pRWZLiM#] z/`jB!B\7Q>p QQQ r {(t;#|+72*vzz: *S ђ4oF;qI:mXLF u-o^(!B!&?6 ׋8odM&ӈV+?p*qX^4+z̃*ٺk l\|:bY/}I뢬 HB!}k!*?P2TbdIivֽ H]t/EuӃʽV( h7It5m]9q`Uzq!v9FLl IDAT:޾Nz=:F(8~%ښiuPL؜$'ŌB!W%!f M&㉌ eS5s`FJ=:`"cܓoF:]h!&w6NF2#K/Gl;Ƞ!Y}A?eؼ66-kX.݋B_?}t r7YAuAnQ9n,W655DJJ g``UsWOCy}yP.6N^Flz"ch,(eݔvru_olf 1O6QzP!Ơ3R-ٷ6ɏ_?]o?OۼI+:M>'Zv: ɟ@ @mm-f`0p9ŋ5kᜪ&'G$Ggs=3Y;[*ѱFFBo^}^:3w[NozKV.{O_aX}{kBʲgvԹ_^^tG:(}/QBI7~n Ӽ#FUMUQuCJLg!!$͸\.f3R\RB^AEEEh8vBUA뤼}^&4TTяh;fzIwx+{%a$C:s:KgE]~$[HUA.dSp" !yZ+o=_ޖg!υN9B1RHB@ #u殂Nx4bT2zʪ ,D2A11O讜Bx zVfōw]P|9$#nNw\& !X4Z6=˳{K =B!ĨB2a2躎㥬рu~( :gwjN/%%'tݵzVC|@R=Ȕ GgP#vVuمxkE5o?O H{oh}MT6gԜ8FI}7>̙ɏ6-HsU#=AGbB:+tK$uɜ{E2ZX"bH˙N~$B\+! Qf@ 躆!^a*)+Nr@uY-Fey)ïbR9‰j:zqL6'Ә9{&!FId`Hł>ǑnC5B5~9V'Ip.~v|b r}㷟bEz ~ծ GRYy/~'F|[}|ה z(z~i:no'Wy~V3[y[ϰފJx7\Lz: '~w|c 5o.5i/'~M/{:]Ʃlf^UQ@!/5lx[C7XaWiڊrfYW9#d2ә !93ѫ߮F1=f#"~TD?i*=x7pH?BZ?Ã4f_'Ư^?:-WT11)s/ޡ:bkdR}#wUlӸ3ιi8nm/佟?<͹׾2~F|(ŊS59}NKn]s;ƾq\ :`[ӯ/\3;Y邞*v"׿g 'suz?WBWXF~ BL(;"D䐓s_W/Pc6R;;,ŴWYᵡ* .sS!r<1SՈi L~Ȁ(oXI nJXmLx҂/3 ' …O#8ϲ2K?LbG?|>@a- ^''z995~tYHϺӱيt WQ|?Qf]8?KMs. (D.}{s6\Q=šhuۢ;="wb~ub>Q$!VƱVo?U_al[(ܱ" YOmm:6rbC|@FT?`l}tQ.],ܮ$wrۢ=TI[ڣtF >U>}pxAR^!-`e,Ǩ !Igph$lx:; Es!O]4 RYk.aWЃ~ 㨟:!7:X,W384N`KimGG%:.ׅBS~ _2~ \y,13?zt/?TzCޜe,J&"̌hDi)g *F3Ƌ:zЏ?Fm`t $?`=4  s`ƭk ܶ5kq*-ʎ6 5n9ww98uu+<_lx:VCSKw?ř| V(.3w&5(ago08gxҋo+ܞ<N^{w!jTJveb֧'ًbr,ׇtnm IsJLu'`opz9n5)ncx@T°x4W)C$k?~ EՉ8wCY;z1=]]lBXHDGGl2N?NII ݎ`'?>h.lLK'~ʏUV%]9YQ& -qgjvoh8fNmLɬX;*p(;Tffz B uΓm6\:_{z_m Ct>ZhE>ŭ_43t5ϖ^Ȑ@JM\fT1*cYPǽëdl+}܇zs{ vkq$ ͍-/L \ʺ/X^p+o|ӍyV1B"]ff& TVVr!l$&&ru8P5RayLVqrȏC|00s6pT\VT U;7ffM_x{cM 7󑕙ChXD}4Unm'IO"L jx+ns#ssǷxfc߾4mD&Sq;]4U@Ii?*i9ӆ'RЇGyM@63l/='ÑJ3ɽP 3T''ӎZGcq)]ZEj: E9}Nεʵsi.+{ztLr{8jr054Hů?cDzoyw5ٺT|(2xQȪѼRNwg7iK,cJaa!=O>$Yv 0\{FI,[wkgeu9^ZEs?8 X~σ95K| kGɂ ~O{3fعs* h}T<ZFq}kȑ%Z7Wę Xb"OඛtŸ}c:+$ĩ@㥌$ƮW^!BK=\:z2n6`qYf߼Uw5~|`D5_B3Qd,ײCGw-PȾC SּOsḌ@{jc=A\:&Wֵ|;?weKÿ#]芑Դ .nw7ͷ_*e'bBuCSYg,7+>g7`>O;kLj<~K%XHIc4wme_}S||[>f~?Va&QSM1a~Sh$v BPRy$'OLcړK/R]%wZ_>JɦP Ju|▄ DOPCl ']Ի 1?_xG6?֮bݏ}?{t>C{~b$廟Ͼc6}Nqx2"l˯~󭬸P0wx.7< 8PP ,xͧ2rIԭؙL[ͬmQ}b#6=1zT{4]vgCq7AJ|tr# =t7VPrEo.%N=Z b!kK)m7 +RGB!BЛYձO9٣aM]ƇoEEgFyܵ7Ll-bnܵsk|Y?n0esS~&yطA,,{fGeL'k~Gx1%B!51e^Mװ 7;vox]A)K5>ьy܇QSK,r\tU\0vZڃ^qמzHGuNg鬨Үy ɲ*hel "B!6'* ""эA8\F{pNeݘqi48Y%xa9 -:[6j“"{f(TΆ%"""""ObbbB$&& HHHubb"EbgRG_z#,ƧWTYn^œAVX ,)XƗʏebzr%0~0 `jnkY>χuDD`=訯x;u<# +Wj}q=8jCͪˢgJ+Ӧ22V1ٵd>kMp0(+},R=pj.+(ɒtN_ʅDJV"ȱrL~O7c0,{ % 9- 8Iq&eْޕ. 8ؿ#aPe-Z@ /}/ \9. #agfK ol"3=p{ɯÙzM#"""""G(DH`I lzFօ\FŠ*}(W>ML͐gB5sy|XU׮Nqѥ|x9r:=$qԌ"""""ǔG->uC o^c&4) PqeUpGHUaD%tDV-]Ā!O#"""nܤ+QaO&*mOcAi.gؠ%"""""bB Q"""m(`)V⃇64dm_Dz/VKl{9) [KXzsͿ`D#g/bә"PunF~IuŏytMg~8m:7c?w^qUQ$W>w z٪uNb{wE5հ*1'ȯ4o<߿-+D۴X:1߾{>G^|dn|~2g=Ÿ\=|o@d <ٞS׷r~sN55}^,˕ΑzwJiQyu6;,iGy:W\u߆ڳ}GGzugIm5'V(ߩO}vc,~Ab·o'Sʶyo># i697î_ǿ̣3;vTpwS,[nDV×x|:FV6!:!<`y)ڷ%A"S3%ŷTz2Ng_^Lv|Λ/<Tx·U7p`rMQ>.y pZ4Xww<|'o߇<|!ǴfkYfly9Vs:߻;ISqbutozzAVFq==3o/GiM?m{<:gm5'2'""bz'Æe^x5/͌?bȄ>2v2pT3WY^Ÿ_==Sj=^zʿGƛ审M\2؟ˆD䎷X<2~qLlU‚g%?kF׌n3{3(=57^ovCkޘΧEL7{gUx=O|]q? aܹZƢEzcpN;,4||EJ` 1՟Έ֗|lQ,Zv#uE=<8ѽcAZc췴[ _b*g_knPs{zՓdN ÀP.k]KO]E43cլ5е,f{GSs* ҏkټG a6^1g}t:ϒ]372a~ V}4gw/=}z\<0C-͜?mx<:Ű}~_i=NlrCDDZII[{,Hd,V9UbX1ebRTL8Y{ir"e* y&W=/.w[zz#jֽ{"gu4Ch.<4&M;/1L϶j13m#1ƒsZDg_xofMax,X:_YMtZZz\ ~u:jN8 7CL{HGWy%-IIѫ9l$2vV7>_ ۲0" T/}1 ;*k+H[z Ο xtTo~jŶmDHdvO77NK%ư(Cc_ibdRgid9aO+[dBd.^yMƕC8 oo!Rp,\p|$vd&7\UbGFipӵG|r6kr ?ŧ/癟^ŇF3vpFɰIҺ3|ږMAI@`;Yj`F1AǍAPK3Nf :.>i kok۴{6]cxԩ'OכkQ[}*khB)nLfz#0o&viض.ˬ. #'ț '^È$s89n1mz-;l!Swv }vr`z޶qD{ k)j=Nt Q""$J˛R, *&ާ# kJFxXU^| $<Ë+<b8!৛OUMI#, ߏVV;\/\3M fʙ)bmwX/dmU=jtԄN.ap]VO̥?ii]vc<7c\cK-ď糖z9ܺkJ N{Q/f_~k}ߨǣCZ}Mf#YODDڍG/:M7ak[Yٍ&0+ؿ^˧rҹU1tKp`a&Qn:I@ʈ#+ ^?e_aŦABRb? NtXrɫW(]uшȠ{gfnUc|dy,Zφ'Fdݒ(*O`~[SS1lw7IӍ4vڴMk W\wƯYܖ!y dn1mz-;֡me/خdo48VO{_9)DhP8fLzc3cf _NwM_d8GV KDЈgfCQc Pkӓ0|@3g"Lg]!9Uʂ7gWe2wr*DFݗ!LQss CQzE}&e9M&tmތQVŚxaj~ ޕ" p8- k~Q fm`Pe -dsp+iSmQqtp/} Y;hshA>՞}%P X%O'giw?Ʌ sI<u|cfSJ.yF }ű}V|1Wbrlcҏy}<0~#bIgB${ҷ٘}˽׎ ɱQ%oAai%tO'g _}/XMAD2aAJcCt|]ҝfn0{Y}619w53} Gmj=F]sz֮J .0o![>wZ F&;')|/Y3b<0q#H3;Xǟf/?N[59<%,}>Z#pTs^y-,ą7\ønlY]cӣ)\!˷dJz-Kyy'cMN=#>'A&>sFjSl%0g̨]|>@RС,IM9cv#,f88G&~ #)u7xtPo{MfO݇,l)'gpn:aȱz!?/̌>յ|jʈˮ_~zE0_6og0-0H|iQ#/A˘<ۙM}CSpNǝL{>|~7c~?XZri7F?oc Gx2C.a*OF|_ y/y Wlo.Ln$PR#lEߧ1V k+|#2>Wc]I#~|V jG2kpm?-\7g>ZVWW[gX XLq> +ɦ9 c}E,B#s~~3w<9ܺcژo%И ?v})vϳT+'.#'ZթoS74u>f±KDkE 2xa_|BHK'A|};grPKkr{- L$1&=PG$ZPt$iS2rs ('[QdCxSSIhQہ$*LהF?)SS!' tI ,@ IljR^S#6sXJ9m9cʾs~/~3ofo}(Y[o4~[Y::־qPDDDDDSA󉈈MN\saѽͿuT#rP$}w"4;"' '"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(D}R IDATؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bB Q""""""6(Dؠ%"""""bx ""G=%RZB 7r9t; %"""r,x r0 U9JaJOe@%"""rXeY`*Px%rs8G܉X^R9‚o'"""r(SKDW Tu(,a9#HH`AT\"QQ{,O}93UI* #*&Nb/;'< huyG`/=/nxd>'ѮH}iK|?`MCΤpnDP7&&0 ]9 /bxr +- Ár0NFpP=q|d> Q""'P(+wx<8N|~[ðAq:ǻ\S؉9tehj!hv˸n6o7loX0~4wa{8|,DDNR")!^=p߇tKtd4dޝx+rjlOI}TW˟P= âtLtߓ|]F==z3+:P'T9I,bHV"݋q:YXNFl]qK6zфB&OeCT-Vȏ祡G3}wnf>`<>]?ETlgWp9C%ۉ\a= [e9l-zgȒ9яOBf^:-/yװzG.f8i}8mXofVe&vPizO}Ip9s9d93$Oٽ*$tNjDCޙO %#u kX +1 Q-ykK} ";u#3F*ۙALjO2b%{X~+*ӵLk-)DaxB9X Xf,pǂĚRENiV;\"L$2eV5>8,Grٛ@:7{?ϦҬGdgn~ui_"k _uenx8g/ ;![ƴȧKY~/|k'^L\4;qV1;}Ko@X?ѿ¨×"mLBISr E儅î9,'Ytv %$'RENmh[yeVeNI’J?.yƷy[8:lhlf)tGӴˠ_mgsҩ T{ۆ '`f+ Av4vexj0- r=r/gYL.&uO؅՝?4`&]ogG|Ć_dΤ?pq#GL_Ov(yV-Ȝ̔O`za:ڵ8}aM᝝~ŝKBL 'CF%TU۲>lg%_YY놢v-K0rDRֱoz%ǙBƏOĵ??0KqeW#i^[TjJ* >Vm;5_ & |x*++IM|"ǓՊ^sGuhB_ʦ]E(dnL?)0fhoM+в 2s7 <&sNq5wy2pYF @0$  5"o/̆mݶaaf^Ig^?@c\x>C1 T@nɧ BW,g>^"F~kBBXdxAhz֗G*XϪl EY]rFV` kf|Y 87އ?$d .D`teqٝqX,_%eVj[1_#r$Oņ / `(@0^]Ƙ 63%t%JD$v9"23|2JJ(-)cҹqII>%[@e>VnCmt`͎#,`-5e{oRիqga$䶻/+&5= 3Ts˟e  &^+ U~?~;cXW?gZqcPOj;H|>|lR.̠I8*`.3yxWqEٺl z3?=z{!k68u-Q ?vz|vb[Z{ 3ֆjs+ =3`,/ȹo#EKo:;c2c-[0qҭoO`YSV(@ h%"rKJLࡃTVVǹgMs)--%x("~k0Ͽv>09 ٷsUPs;-WaG w}Ͻ[n bfײywE>;r^j7I ?[ˆ"6,bռdVЮÏ?jx@A,,E#Fõt}sQcpcÚ]}5,lX ${֮topP)%LEB|tq$.p;zy6uwfr9f{f-t$0DZiXJ8PTS{Bõڏ%"rs`ܽN|8{&bդw[WDh7_1}.{(bcU8'oqpÈZf`D$ӵG7Âۨwy:gy{L9##0\.F>+6+2\ `[ `,3@ Ȉ< Fù7mfuCdY tb6duka:33"jxyJ +NKoa/)=l m\ĢGʙ?qVs8uw (IOms)/`x'rk~a&Qb"(//msU$ʈQfW01 *LA~ֽooHO͔Tg5Tg'}"ڍc aH9INp! x lrB-iYĻ"`zh ~_ܰ[~ƵK3΃;b vAk}J"PU[ng Q""p^{a.CD)%H.xMs`\0G1q^Ro(^\Y+x݆R݋ͽC&/vO=7kf s s 6#{\͐qYAxo>>qBUKvJ>_ya.V~gwM]u o>V8#'#ӏ( 80qQVuiWȼ3x~&KW/ uډt!^u3#'ZO}&޼nLDDV-]Ā!Ïw"r, 4 Bj=8%5ExL.BUTZ3$uDdϞ_Nu7e?Ӊ0&m8t%JDDDD -;u}pR3{Xo㤪Ə33ޤ QTTQh|GI4QP{y b J/eʝ²;[l_y`vνΜ={ι[KCpق*PRodYi2my 4~|A?#Q,ݚrei5teѽe&+'SSkyExLmoYh$e`ail;%BцhLnY<)mX F ODSqcI1Yfo|K'J!-5%!D{%K !B!D%BBMZBƦ B!h#a*׵h+iB!mtTu6| H'J!UR4 0v]i䓔vBl6E:QB!LQTU+![6PRTaN,a))*dǖ p'4PžΤB!8QuG*=# A\ۅͦj dDbrjX(5d䘰3(B!NTX4 4Reɳh1h(:B!D{V#*iJIVT݉<؝(!B;nIgJS;YL6D !BڍΓ-%˲p{+0M3&^bItE-KBؓ:JtEQMvjb\A](* EbIte%!DI%Hg fIZ'$4 H%H.H, !hq͞%DW9QBtuBOB4?"X_,2KXH~ӑ:i,I.mQt`R|'o~g{ 1ovDPf4[tOwxznXCPMJsE\ܿ{XeŽmK>'91gSUݎVdl0:ϛ̥\gmf[ouUQd1?:=Vgi6li*'~Ǧ'C&)= jJEE}s;X|W ??A-ʎCc;s4AMČlj _JHeBK!Bkf^a6ȅڏSϝƍמϐfViR{G뷞n=^ETtvh[j|sᲩw^***x<|>t]GUU~h U,eQ^^o~K/վfQ}7XU``)N SNgW=nfr煍_m?"#q8||>c߿덿rU؜bjߢi_y[9_ TU$ײQQyKxIF#*;JBsd 9Q7[ )G],4+Z̓O?ȅ=bq_uǚ>`C5n;^N,oVMm;m2vt:1 j MMӰlfG* i$$$r[^ G$zfT((;cu.zO qFo_}a2>g'{~iyl „_'i0EAZkeދ]|CLL0~ћݝwq{g_U4cť/'z*Ю^U!'W`|=?ޕ<S,3'U6rnjzz l: ZVu?w*W=Ӈ4YlOeƉߚ i{ Ug+z._}o~w89;xvUMA2ҡbu/y+]eԃ40dEm=O vg78=ͤQo|s1 cchԦwP/oe,d)L^)N~+4Id60rײy]V L ѧrx~y;fWϊ w_]"iqΜ$ &5k8#^~4}V<;{~[_~LWuNMAc0g;yB+enO垿>̴trNbQd{{,j*(ڡk>i|pnKJ3FԏϏfiLi /Z~Y_|`_B!%+SҺBQH~ջ vo2ON Ќ 4WM \׾NE!`Z_g*8*mAç1u\^vA;rUC %Z".VDq'K2𦏂}Xii^s|,g`_'3/j' #kU\>p/m_s _p/Ȇ7dM3?r1~֭ K="MdA^9W7|IYo"vhZ'OlaPW Q[˰w5UTc`,4^LpKGnE 60tJ\Ě9s}X6kk>-H窙E=ް~HcSnCpJk-ۙC}13vƟ6gw²*ETQHKMC%bR^V9-̊\0OYֽ,]U.i;.Gޗ!_ɸ3ѽ{qďo-.^#"52>_cME)гn>N 2E! %Ǚ̲RLH7Z z/%:w| !V)Yd]N/%/ S`ϧi n4pPbW֫{f懾(-WOэ ?UfLjh-j 8:(IMIy Jrimٜvh Pٝͫ_O`ft?&2BY811Q=bCs#=Q{wz5˲g%sKZ+us{Ws  6%<4{eqAMzd-/>8A\ǜɄ ]l6oxg6|1JWMۮP 4z<$ti]eWw̕(Qr7@3gmݏh_5 Yg&߾k߯c+:&_-b/q^ZZ(`Tؗ"%θ;yvo /zo.@q<}SL!oǐ$`m@w&nВaN7 f,f`ji6[贊֬3}6Ԭp84ײcVEI~47^0|ߢX⋹89KgZ#uC5ZCBE~7躝s`1֯b|l9K2 J>PkV.S8U w >Ϭ~0coV9*1{aec͊:]݋(o$w(2 K+i0%yJI8< 5","SA u4U!Xy_enPGiWƲ⌫,KhN3Ǘ":#k<ujV{=p`L^:yP3wv ?^_fO޺Ԋ+_U/5#nƨ^IBQ͆vsQܽzrν|ѿx<t]oz#[ˇswќmfRf/-T >lz+R #ӣb[hn}fЏ[7 {9\u;᪾6]rfѫ=\!;={㷥|QLlF&x^~G}ʥwi.0 N J1A(/4ƃ v >Y~/\*=_ˠh>& Zz*Q;/!hsc,0|GOĴк݆B{)(ثYPSHUArc1+Z!YѽEvˣ]ęgNڨ7=y\PRZLYE~?}|%F%+6b޳m5[dCS**$X7IU]noy l8.ªcU1&׬cvVoR 9nTXBjӨ~>]rtuN.#-%|ه#;7(Eآ[hgJ?ee #a*Bˤ`*6,X})YM;({L|=b^VCA3E/.p8((g߁}?SƌB(..‚Wqq%,?\GG_ENdyfeV\?zh:?gCfjl,Ǫ>xx13ی՝pR4 T-ޜ1 >Ɵ<U }zM_Bp\^< m1QNc4"O͠G پ~ ڟ e$`)#MQR˗ {L*J =`3y_ `E3'r'g )V my{(sFvUY Fw_m_B#ui獆nbO U184%^YWq9YCɗ2.^3?y }'XȞKYZwλN&%Z5C w5s.ͫW7I^Ĥ v2Ng_~;+:f9!KȣiFsޢdŧ,.0Q\czNMTN)w)\e7creYε}=wm_J_=-=/+p|Rle0Ҹ%N{d$O;>[+ oqsgq+{~~v"!ݦsl:.]AS#|-\rsfD{0lXg8j9ϭǬŮIgrJO'XZ|?c&Vkߑ 0G7fT,`Pnxl3m<ȮUEu3 λkH%zpǟÍ{|k⑫4^W껬]za\3}2+\+W!nL74|r0bb6.M=m*8dɳ˷gd2\-ӧs@Auу߳eѻlYC7rZkGX [M j Ў a`O85PX;L pVbSW31νAi<)')TTAGc~z7:z3;Pu5^AօTņa*C_8wƅEb$:JF߉j c7f'K~w>͈$"B!DWӬNTWnICQ\U/ O,⠫ǒ q D{%)D ;QaYFu6D+3ME{W%5T, !bO(4ZThi(!9n|H&DVt|KiXBĞQjU}KKQR(*,4+2M|RNHI,*ֱ$=DWiլ;Q*\ زB #tb!:RRTȎ-p%$O3m$DWҒ$=DWm9QՁ H.Œ"BHvI$&Ƽ'$%!DI%h먰VӴTI)i$$<>OԾ*itu>%ٵt, !bO(UĢ NT)$:꠪X߉%5d, !bO(ѕDSGy^[Hp΢vn KkXBĞQEV'I*FٴFX]i!:&DWM !XB^I%D}(j!B!tB!" ҉B!B00'/K&B!@-r'J!B!Py'*IQB!UE9jIENDB`Solaar-1.1.20/docs/screenshots/Solaar-menu.png000066400000000000000000001613471522022367600212160ustar00rootroot00000000000000PNG  IHDR= zTXtRaw profile type exifxڭu8Ê1zN{0w!)S E 𖻀tuUz.k=8x??wkO^x8x:a~ߵ' ?_IWu_׋ڛ}|>SyϟzFvB)ƓBAz/ sO%y|`ȷ}kO? yw{ku#W"Z;| Áʷڞw/Rzd庐#p+,WL{-YqÍFvjrR9\?[q84 o>;^5[ f}ŊyE5PHH5'/R078| 1KU[sW g=!څɄD| h!F3)IB)q3ɘSYlQ ϱ6D"J =Er.ԏF J.b˨ZjVrÒe+VͬYR˭ڬ'0ڭ .4kpgyY6YyU;]'R|ʩN;Kt-^;>F;kɔϬ3"NrFbdܔ :*g2idQn)'rg~e?ʛ+?[9#s1o-[O^]D]+c1c)na3X8_cRU-s:n,ZYt[kt`RvlҚwO)f)233ruM(y#4nܑ\;v$&0`fo /(wV-BЗ"ׂXę)s( ޥHxeusE:1lt:uNaEjN"-3.'Z kSxkJ9 sܹ"-SLvrp+"5nfc\bw>3"Q@LJfMMD\li| c!Lل^* pdshgo̼GۥSW}u2 RQ1#ܗr!b뒞Z@Řaΰix㲔?WY^9"KkziCDAa`ͷ)M(l!ۥhHes@(xc TƘիRl k{Zxi}GȯɗUȞ@6f*FąZa8쌷oVi>>@oӶ˹\ONz''=d.3{c0@Xz ݣبC5'%3䦋: tJT#D10Po.Kǁ`)O4zMAP#۷#K7E`8ė:1֙iZJx4m]]45*@avd\"3jM@!Kh 4h'>gHه(Z D&yKViHd :#kڮ qmCx ZUrHL"*D%6ejʴ\Ӏ{-tj ~L :Qo 2#Ă8[aDҙŢ@;UE,@]Is6wa(|T[Oo3>IE" aCNF|&pKDLIje*CanzP_HXYqREMо0BC 56n>6bF؎|DçAM:/"`%b5 < ADʦDRsJY߄R<p? tD rf4Wdt++΀*KTl-|áNG5~zT%:CDngX9hA#Vgj$jzptY< .~S PY -}3#Av©:ѸHI)(UX%J}?~..2_C<ҭ9BHዲ6"F*zl*-a0L4&R>1ߙG."?E` ơ#Mzd0hQ62b;SۅwYU˱81W-dQҐ6NRew.aq`1]XrcFq|&SD$ɡ" ![x J +em՝]%P`wBP{_N 夳RG3GF+!mVrl!jҀؓ~dSA& ? *5pѺn1 xYOucEɌ9@Vf]:C^AwYRN@[cVs(CTn.RگJgZ8`V+DZ%D7V2W_2#fß.qՆ\n?LF 7 Jp;>πeqB#rw76`ڃi#k cP ,1ջqr@@)hVbp8O"*끿$ 3J&&5mEfF~Y)BGg+9GQʑtcԖ]_KT#@[^扆0I`/H{a)*M䛘fI8۰'mP D>rQ@قxHumK83 DszmQɐ;nec6}K4<6>5-78  #|x\?:"ľWFW0͆6k0;-b|^o)_pJr \s# & `\m SX.9W9+u.O"39ֶ?+|F1ؚU] 6١8wqgܘdcyt+J^Ǖ , ɛW.F$Z̏Z41RhVb{Y )n#ekrHfߙB[I'=w~vs`\ .v<DƉXJ&(HYPĪuk&UVa3:E^Է p`XPU?5pBޓT0P|FsqUAvypBal ^R Wn4=돃4y Y2ʜkQPF`#ΐsY<$^Qi4 P 2 mqq~'=BQ`%"(:Rc6R oPMh!j ]|7@Q久@zEmDT'>_C ^DYSG%Eu뭺fL ~Z#!Q:|uw 97(~w!©k\ڄ" A2#_~Ӳ"5B\Yh+2״1X^~҅k>"aFR=.ݍ ޥEQU6VNet +U2v`mtK'ٮz nqA5 mLem6)a[}I*kӚ4\6#yŌ\D(R-(-ѫ".g3cp/3ۮQT'wt<@ $M$-vJ(g&(t$@:ݐ,G#3gb6Wp٧%vQ-lp1* #dɻ@}$V`!4þSWմ)7f jh롚}r_*hyHPIHA!nK.V8( ^`"=N8ZU1\f1QO]㘭Ce{gEVP"ƨmTZHhR" Ց ̴3:ՈK ȫ=ʐ˳,R?@quO:K+QN5tFBbX[6+ԦLjE '&b_A!=_|T])lF R$-t}5;E%3⻓hh442*[+@L 囃vʳӈA{ݯ7 ñtˈWRV ?4;')icX!*ʖv64-0UZ KɉD -d4,.l'C+$Qa`o|C<,ဈsb̴uq5n15 nuzv1t2Ün!c@E4"9.RaJzL$C;_E sᵋC. /.+XU&@lv:h0%F"f3A] `Xڏ2$GsAd@ -Bgne-rȭT |;@zφh]Ԥh?Ѽ;D%FsbnwǺp:u$A W85|FE\ˍSG! i'butkyrBo;z :DJ [Ɍ?>6tD=OVOMt 047 D\pz %!Klsni{_%ſ>Ax)|JQ9?1os ~N3ϓ-~*eq~I]lO#L4kXVtOZ}E:Bhs?z*jVBmC(59s\Rp{h_J7EZ uHD=6.a/YtczΖUs,b=UcфBN=҄Ra-?Ez{cI )ߞ|ɓy-֑C%ڮvob4L@ktkCjE]i.VOriCCPICC profilex}=H@_SKEZA!C "Z"TB&~A$Qp-8XupqU?@]%ݽ;@hTfndBV+ Q29IJwqܟ#-DYf6mp'tAG+q.,̨IGb+JFWfY]t)((Xz޽{ |A9p d?rhΜEp]NL웞>x`OJT^^-,0V8D@Fd 6 d yj[|TxlDhLbNT&\-A#tE:>/G""" >.N0 HTL'JN%j Wb5+D%%Җ*FqQ4RGh 7__jZ? DvԅPxi(p Q]tE @'n"2 rGyw狸ȰFy__{@k~S~6x|2Zd&$kݔDFZY*xхnS&CA؅#H TjX""E,٢mzޢ,H!" V^PP^B%ìlUCP1HI#8$r>7_{-m@ˊ>\Ꚛ<8|A,<@,tמ:SAԔ(??Q#_K{_6!wWܕů,ZXSjogm_j=g?UHû2@nҫ1HɁ" :+KŖa).{|C!VڕE]Ђ%ݭYBS'Rk-xtTIU q0:1&><6gmyaeюUn[㋞?O` յE]KX "(=˧L5q&4XfWMvl&!N*CYyEUmmSϽۯV|a I u Ŀ| Uuh`JvD)N3WDn !ʕ`4*r4K{(Sf@7 $Bzg!8c"ߑTf??UmÆ8#2]tѡ巐 6:S.A WW4655;mQQ!a&{/\kӦO]ч#;>n!/C W5niᘸ*,QSD>C5:U6ScTjj QGɒƊjPgUF=:uQԔ* ʛ!*GI.Xi((kkBZr8c*lޭС}b -qW:r>0"7+ޢ Vzr!T&WJx/@ΐ0⦧o?~ ;[v_reUUpɪF'1F$88=|39n1Ӈ N啡XO9GQGɲF t띬 [yAM^|EM gD&8]th9Ų@7S#B{UƕuG~/~T[m OZxqTsyr욾3W޿`{=.N8N|Zb-DVuuӏ ޹OˣfCCY >:"g&3;NH5bi{CN37RCKdc Q@VٿkqeM>W+ 0Ta]K]5+vﮞ#%&8~K5bK0K)k>>vkՌ Ed6ub2Rn# j1*~c;M?[],yݯ$Wm0`P"y+ťQlpiۿvG9;``7~ V-Z]a{~~}CC22GmO;ɲS^<˷~ wM `6ne?/[3kܙĒ5¨MeooxB֥JȐ5eB"=fReSҕ>ЪtKP D(躔IBȶ[,}|ﱦ~ŧe`0+JF]RZGR{E_zeӽ)spT]QEhVg>o:C|FJvl)2lE&k=_w'Ԕ9T]Q)9砃@+.MbK_!> r0 v6]WWyfm%!ïjN#6<|_h`kR;k|ƭXF\qe/4u_nW BL\#{[]31֭2gMP^\ .Ⱥ:KWZ:2iti&Ok~%97|{~bJؑ+&qʏ>Se㕕) ]AԿd1R3}Lhx%/ba%o'Oޓ\;/@۶TꌵyoHnVƆZx`0ZΦOr6O?i{z]9#.tȺxY E yr!G˞=;Ǐr9K |¾aƆGn޼#an4X`/ _^7X Ix=R> Rlk2 z%lzۺw R%&q⌁էj*fHm?xaܜXvQJ3[X P+e٬;4=gs{j(Ew3!ޣYRd&|G˰m)T^S&dy{'NG9uG e~%k`udE.4!E+S@x",g@/Gap4n lf5d€wQi)q$wojnj5/׷7u KewM21X~#TX1 dZڏKw>bsg{ʔ NR^ (ۣ<}Rz6IyGj>/S/޻1龙R[K.`NSl5;`fFIGLf#&Ҫ'>*<}Oiឃw&⩃ ]u ]tѡarxs')1-浆aM9$Ighr4UU@؊0MF:XeBbqB`":j#cSSCP0 ԼM5'=i?_Vw>۸ĭK ͨm5.)‘1VB-%p IRM26䷠)_2xtMOo,]#UbT&UWKg%3P!UUB =<2/]'ᢩ>8umcw7&u>g.jF1{&taΤ$I=H<$v-\1Hنꃰ+ReR qe*sB2+*(N2#ˈ2b:mr7EopvX)6JQ\Zc6nQIR/J!ϯ12{& bs:2@ʳZ{|o^kYQ&1@*sgI3e)~E+>ݍ;yZO/.:+(F3,)]LACs9jp_FbRl>ԸĆ? Nsv.aPOJDR=*^B|Q A}T!J) 0زbH@F3ԓ'c C~}gVg5Gi@1OSSE1H*r;8ueY$UŞ3?ēt<!5>fۚ*GO1*L[ZjR\u VQ;rYK7|5. ̛{Y(Ppܔ{ܜŽ8a5{翶nΙ8yxh)ٺ%̸ͦp\=ݿ$g˷|،w٦[m>tx]vpSiCݼ}fFN(C9z,/lep$xbeG aHΜbY(%(@DDdiyIxDĹL%l7_|ܪels+o3_DGg`"+~ԔR0dj/A„i> iQ?Zr"?b_+xckbCT>wϼ>F0`!V+r"-!P 8X+Ì IDAT zDmgGSs'q˒1$[q)L)*N 1b流Ks,7C)AXF҆,f/ zՊU";ܪ4A55 s袋.t`.3,y&WmH4@+d^J_H/?XQut׎#!m÷4sۭy6Q7wL3>ylj gc[E<1ﮋ#8RR˥ T,[뢋.]-(5pa XbH[c}}[, G|i as荃S6 h4TE\_M]w;V sn\" ^CȿlxT%.ҵEJ)Ew!NMM"6WޛlxjEŅQ%1b(htaPJG$gmuC!111!~ȂE]t*hQ7j يf3H`qWBϯ&(!6EwKV1W-/g%!W@1!Tx欱13Z:oMt|E]k{bwu(&9cQKn|!vGUVMe(ˮPKuᙕXd]tE⊷#RK ,p <lr70!o?thOY+y$j|gk *Šb{P^)X?S`~/ZfpGE']t3B~-Dz=. jwZ`(PS?;`EEvd|>UXQ5yEޱ&87!W:]tѡ#a)!DoIo1E_Xڪ 3:G WrOeBԗ51BH$EJ CQMg,h$m"3p#" .:!{x7%H#,Ypl f0fbbe07 F\DPYa1˘aRB(`0#"IrC7ȊIFp"Xk0 xܢܠ3 @]th 領lsۛ#C j1YrBS`]1,GCMp+c 5'KIIx<<F}ŗ@TQkU%Z>Jj)gRB+)>/H HZJJ)#T颋-d2ǎN&YB- H"oknnl+),1pQu>{Fv˸@K=|!"0f!@(}e` ͋2D۫cPs b`̰Do.ҚZჇ >{lYYy/(8g`z~aaa,7WXH됣:Q|BF!ޣF@ƈaX D(JfLdM con|W\SIkeFBa颋-q}AA$Q%IbeYa83~|AFS0Egy#J( *>IWw!|.{mFeW qQ—&U9x7( 1]tѡC$`0rNBՀb?tQDUTPRɒ20|^+`1@֤SmFZŋ]H+QIR+T%jd,`آgGꢋ-jlnX"#lPBF0J = 75z?zkmX͘<+^,MG>NXC KT#T.fd`}otE?kNٽW)Hb{EaY3aSz'X,.mi,>qҕ2ٖqSJiU@K,<߸v&wҵ~xg6,*@TFyQ"ZA"Ie8VEBfY0:葉gEdדs8uU4 dNM1y*N5SStԘViġ²F2G'28-ZGg~9zH~SLp+3c;o"͸÷nHnvOܽ4_ OE10emQAPm1I0^*uS5zK@[r9LANRŸ$MDyGCOL/xGkșOAH07IsppOu-=رW:T'>s,lT˿_xwfՌyyc.:hٺuk^^O?|333'Lp/Ggq\>-T H {}Hrs D@ 2R o\hwQ|B0 #W IRT0֚ҼQ`tMW "U\2H-[D1JGZDK4!pXjꬄ!X,!3'.:<wԀn-TQ[/椫pqRi.>"5 wa/}GŚtn׮]9%gK ԞC 9rd޼yyGr7O~K.=PD֨~\὇<ƚ_]ڔ@DᡎcٹS΃wֿ@2 #卽||4nR [# rrY>cH;i:V=*N^䊛zQэKhxr2-+q/}ĉnlk|fk/>2?G2$;x]:k+{i\vѲwVx NWghnظ>qqq={2LReMF 70iZ܅Rp8{/~ze8n^Lq{nQ~l(G1fy)>i}y.]RSce U75!-9\<@Q9cQJ*8|!R P ) '8L}WUAѓUNbk3%Wd/>vdEKư}C2dA^qES` ާ5|䛢u #ҝ~[%N3$yl~nd K7_p?-H +lӳGʊmC}g-Ot e<wڝX*?r!aCYχZ5\0ɗΛӷ2Nugxt.Z,^8..ni&B8cY611133v+V|=l!.KV8yHFLy>w !34|ރ{5#8D;fP&,B%+B., wYyIqfDa1o>}IdD6\|>{@bRe4\wP)nz+|߽[bCz[%b: ~x aJQ}BZ]|?j| oZ挬e4awMˍ}対ݡj^1 b_lP,Bޞ6=oR[.7#{7"7k]XF@آ,cRqiw@K(tJ5#M?8~jbr K2]qr>Rbʭ۶N6h4v.Exٜv_ɓ&|M現 V sONT+!SX/ Xד]x`~3Ss =G֪z@!g{oЁ' ĞG*SvPR_/wlZҢ7O361P%+j6w#rKv _{@ŏΐ2Y2" `jBoߔ!Ҵ}c`1k' H0ugw/>m~;7>Zx^ f a>+߾Yc6Z"_<ܼ!%O!irAI`+96~ƒvGDtF/~㯿R;w޾k~˸f:P7NvdZ۟N6Wqӳwa;~fD]Z$Izm櫮jeYE~ለ)Sٳgaq압Z,l]uuL=2fuI57@!ޟ]KCb!C99%$ =R #Rxb e[6%]hB0J_.Z]a{y{X(!J?ltWn{jOmq V4R*XS74Da?|TWXȧ7LyX `߻aga2n|~ &L3U{wЯKw:w3/ܚ.0d1Xp0K r R8iڠerv4zoOjm8DLjjZ0ϡPM9x} |RYҌwMp:9(!c0LٹMT8P"ءY 0fEb5J}'EѣFnUE]JNuu9[GιaʩP9WaຝiJG7m:-u䜹0g1sR;k P &H)bYƌ;_wn`!L]6D(0ƌ;_wP""A =kW\m4k8e%۴ +U(4k֥}X*m^Թl( aϛ}V/lpJ@,cMi{uD"ژ8u؝xǟ]Sn`e'vwN-NAP F@v^N.ohԨQǎs8DI_,3<x$IR1Nȑ#?zp>lwo5=uNi#" Z?,FD>lΜ늊<ֳJ\0LO ][L@ǨOG6TԈpz7.RbS#pU7d\ zl߽oXfe =LXU|ز/z䇷Bff=́qbWq 6ptÆܜ7~&wj]&:: Ck jʪV㛵ZUW]GnZxq?v?>LN뮠L}lI癦;?jN|01D# c],YtԨѵ6MR$& bb٪}wɒg-j)Qh,"M Eϑyn0u!ԑ0 f6d2j98Jz!50"n[{x^x^yؤ0NRIՐ3* @Q%(Ss\\ Ț9-u^Q؞?yk7^=oN8QTZ3羺'n9w,?{]Z}N)b[j8h$F:xoXJLCL O("e[r PQ:KY8ӡ/: 8 h24~]6!>zS" n'8,<jSc-%%'uU x~ESO⋧OfF{ <33_w%''uAXS05rd_6pu~~)p5!X5?xZ!0 ޠ.McJ$IR6" o-"ZɅl%WLlU]43ڛ4#uE*ᘈIgL<'޻k' 7h33UDdL!;dnݸa m7ΈڹiSz}M?3w*j5u|tP!֛lnlj}w7,|//ceyu:3qy[  ,ᘾNj`Hc5^}Lԥ+e󦔔ZNǼpIڱc '|2--MFl޼k#HtVk$Ss\͔#*_yevIFHӛ48ZoWh}y  '~]K)酺AEB)niM 04RS3 ZҡFZu}{V᠁ 9g<<;IwP?Yr664IrsNj_ԝݧ6),#)S,ĚGK+b%W>,p-{%2b:0~cF`b߷y+AJn<."׸1tҢKAKcc !á‰(MMM2@ !d6m|Ӊr:MMM]2 o?<7nY(魬 X! vp2dXja:|'eRFUEa1XeYHDEHZ#Kt_:5=g~|'y/ItS/ϲ wo~UR!O,x޹{D??.=(bRRҰM>쓣ϩ#A{u7bq,"؎ E>:%N:>}F߿B=E߬Q3/㸤I [}0a|ҰGQf0zũS/˲ o*ԺAg>8c( G>}oPV:frŬ>mΛ˕{.eFwYt>#>s&&&VUUW]W_L&o(C+VIX}G_z饃j ;}}&k֬q*B}|=Q#]S~id<"ݑ{N>:M (N AE1Ppeޜs=98g떜72(N v-{`nI{Fs|mqΖ-9%nwߐi8aWyc>x{q{LJqέ{J\ʕ nm3G?xSHKHcEQέ{Ίl'rF@b$zaLISrng=-{L8 *ؼvY'w}wOQjԋ^to:Yu͹ƙ7OuTnvOt'OU_"OKIޘ/y翶nΙ#8NU1,{ߴ~A9g͉#UnČ5JKZW.!u5pg~x0=}h/G׌.wVd'>4h=_.ښZrd>C 4(++W_㌵B)]l(ӦM8嗳UtquL&L0n851=ӳgϞ9s&B袋.JOOע!$++G5Lpĉ{f0trF]*Z1R~?gB߼ ʮ(FMNBTHv'FHޒl@˟Zc|Inn٭[7-M4{+ݻW_ ɞV]^yn6ԜM Bs0rba͇> B)PO(U8e0FAZ3E]w~9 T|\ߠ|+??]w|U?kBM $TA* ڕ]*.6쿵,V]t-+(J@HBHHWʊO%/wfysy<$noY@3 0-.+#b }\ijJ  R_{ RB c.htZLVbXkuZ5**V4IZZZ/^gqf 07x#>>mSG bc נ\m„hs=TP!OCf4I"zڇrP^!aqػ!WEUHLnݺBQ֭A]LC<9\u\O]g pʚBOO({0d4/2 >RAV "]{lAGR}UTQZUBBB䟤ݜ".+{>$ $^XT<Dr/^ZI(eYPM<qIN"ϙT޷a\^P5YUThƏ#00꧞zjܯ'O~뭷ΐ_sDޓafBˎ:wcv*ͬC)!%IbT=N%JI]_..2D}KUQ'^յ`Qٳgϟ?S{N+VDDD= pY/$n m)!_E+ղ\+J #χ(rYLt4_y!.B5c,<DY#.ryZ-fjN~{tu.]j;վ{8\!,_\ޠau\bQfRe.^ZJ>Y ?r TB _BC` h"WEUp*~j{I&qj*2Ϟ=;޽r8m]Pa=o]GTs^/A,sF0MAiHh)÷QF"ynN7h&WEU%O~f[t]U؂ p8o^e4xV d0B-ɕB ʚF!\Ldl+S[Eۈv 0T(Xn}KL*ğΕ[Y{/IUQE?x ???FhZ[[,Yظx⬬, zGD\ڴhC ҳ!hqU I\G|@\TTY=K ; A*JX,[ b A2@zP^UTQ'r{p"DzԩS9>v| 'Fq괩eYu‹zע 09"'.F^%trH'م &>1(DGdHt+TUQEj)STUUFEEiZdZK.-((иV lnjra o6HЧbbh w;Xi ;σ9rL(n*;%!!~ذaэ=eٲeEEE g^xEK{,Q4)eLמ1- blP7$īHi~_i!oHF|r)xlf)+:qzaE0"Uz -x&Y,K}fʜ9j~~~%%EBMsLu*,g Ii@r6~ cmIus5՝js!"))Tg;-wcox-KNpyﶗvcoY|*r7HE ga>}oᤳ$=VQPplSG%뇝TwS >,))t|8taVDLuEy'O׷:4%!@݊;oHTRA1zg&4TOX]JH h{֭_>ٳ\3cg]]]^EE[{cZXx )++mljW8O7t3sf~_{s>YX8 Zvgo]^O\mhI"lsOel>~ş5OQ4da~+b|oU%lu#;>FX֕?tZO[}r^f$sɬyi{{q+ڰa7-zvhZ`ΜŧN0ֵ@nnqEջ\@)//̌yGLJJri.;!@~c˲ft͞% GV-;r{|Ӟ57izM|~{ә{PNAb]t@||X2'awly 7DNJ7vR'y]1o#||ЗUO wtmpH1.h=ZlG飒_8xֆO5uDB(ݹң߾ȼ2 U?.~r6Ipq}-Gw[ok^2|%"R~nffRyo^~ζE-Ģʉ' b2/M&[\~~~111삯!##)!1!,$ѣ"1Iz҆Գ~JHBcRGs-[g\a#m:倀¬\i˚=iH?l(5HEkc[OBVr53k}lU3xfs̝ܹJ-8o\홂m+yz?XF]1!ǖ|%i#mT?^=75ly4wib4)f/, KDV LўE߼SEN{'Gڹ TϷhOWmƼCk_<'+6|asb?Mj<7w޺//quW~c_np\^yƍ=x`Sl6k>ODDF͙4i⫯|ylFFDt/zפzRΚEf5;zI#ͷ&#K~qq3EԱAB`TqqF)...jglq/EbA=T_tͽy3[,sw,wlݼa[9YźE aFЌ(N.fK=u>/gs۱a߮߼u^EIM:j*.SEM?x6~FoppD^kOk뷧b6KQQ+q=1aT {i2( w{N{cF?ef6)mkԉRZ 8t7J !j/9Y^+<}}-v }9"aw 8}Sûk˖- 8pdl\J.0Nt&d2=̳#F xlaN{ 1tؤm18f̘U-٥lzZX@>bֈu[;\h. 02=0?}_iyO(>iRJ@[s}/5 la߄+S "m־Wa߄]E_K;YY.z ѯK~au0fXsvmϻ+%]zfM&cZ!pUCÛ]|ͯl9u|CdݫW5z7}Yb~6+™IO\0&mn;@c@:fAL.׫='{:X# 5\91-_{MKq&(ap8Ǹ!(؀FƦz1O5?;Dq豃Ltf=͎Ja#>_f]_ (Vd2TWWy7 9`mNB%HXQL1_We/5 #9D:bX죧tg43&{2VE`]r逡S(Юc+}rmͧߴn&=1?e5߽c䒮ܕ;0mٳɿoo)iqAY뫯v~dQqyjocI7 ?j7fQH\Tw&hܑSmi$h@~~1RS2##Xf^qi an,׊F\+<}Bۿ x-?}?,p zԩӦNmnn>p@Nnn~~g.&&&\u &y-/m?̏vc{gͯ-ddw!cu x̎ X1qjN[5}nyu## w}c>vJNҴgG E\3c}}$ڲiZS ƪ΄ IDAT+ Wsw16znދvM߽doM7dDS&uB>k2)`~{ł}BcGNs jܬ21*_Qӱoϱ!uƌ|bo|WF>3]yzwnih"|]Gb(e 3dh%,,l<lO5pvIn.T˃S-DpIؐR q#ok/m6;#ӓ`LѣqCr9G۟qm(J3q0;vvP7yDl(hsZqM^D'v8qyU[P VJü_G\9}ʜ}Yw7O)6M׼<N!:tv|מZt#ueC7\;pG{ޟ@7V, ;nޫjj?--cop/lmj’w_o3HhD)8|eBQQaaږUEvܒ&IQS^  1p_٫J֐|#sVhv6VVGxyzoQ:M@cp|Gh4]I=]gr\#1ĉE'#5[RH5H }^|$JcG&C3(=O8QROC K QrHp8thO|r*;Y&dwE^}3mx̑q \r+a{R]. #²*\fbn Y'|[x.K"|GaؽtHz0?ř+aHHo9Yy&ǦyG2inhY};(PKSC@=I{ _xwacǍ;8 4yWNJ7,z{ 2ֻ}"wn,ض0SwagLug0a.eB14V !lrUsX;Ntl+uԴǗloqk+ {WϞ^SW#Ӳms)0:N&L/l/~?-Giֱ69]K6|o}Յc°g5ٺJ=cVW==C4l펕X| 2-,G]wU4pd= 9>s9vG^9q;K+|J7Q}$nGgc-;V@،83}2Gcx~cc iGg#rfW DE1Gf1DE1غX, ȝr2K$ ٰJB}hhgΆ]U^nH6}6-#w6wxNM>p5@ڨh.Quevҝ+r|{pM]T/1PE%6MY mmi'8 0Pb; IՏ+[GVr#lggWyz O-+*=O6` 4 .,յ:[zdMY goHE}ߗ˩K/qB8O;Б/&y@()8_b̀ISJ0B0 Ph$H͑çJCǪ;=f"jRv8qF" -ZNvԑSJG]yH=mÞ#1iz7-mX,.;i&4i7Mk`ж6Vz)bmarF-/:,^lI^xoQT$VTi }dT(`K>{hkje/{58%X wX݅\mWYG-x<*Hm_gkr^m/9UK""C~y=ضǍ x P"Uw9b -V!׎wnja7NNj'Qԍ qu,Bx."T(r ("ecyMV?bT:*M悍?(N)#ARL&4>Qݵ%f" C<_cBcIe,~e_grNR'`ҲgӋ_{a=vB'ۿ׊bN9с4s=rްj{L[K׭@p9m\8Hǥn׿, ,λU{<O_H+9~`" ;>T2S8fH`R/}qR!7ҵ,'ϙ5X`/¥zJ 5ҢIyC=8z /\zǏߕsԌa[SydˏQ3Sߟ\G"s@t[*>t֡M~ޔ { O熵Ֆ:덊 3}Rޟ-L)ӓ`sx\K;G-}xA̫9ZNg&&r#M" 8郾.=YZ_3՟\Xo8Ft7ۅwm>;^m([~TG>{rpG?>|t+ң;wpK ;ԱeȌ 87D2W,|>->D^ ^س~{0|ΕN; Gi'iYOSwK1_kOn1az1O&'>'wqcEYG LO,vit{|%)))>>ң`ww:6mi3FQILnpDAmWt@#A4׾"?]/g˚ D^{}0g}קqDKҰ=0ǽ9۾9ʧ`!o!#7eY[sʳvͼ{oɭfޕ{"j3Y z_~v$N|v+*:P|DCgpGF0z3^.R]YiW7 c?c]eUц 1 !/15Vmkcćy]=6^bשׂn1ڑ> 46>5UW6[. ,6.ZL;*Ni!A}&z;k.y?ޜZ}͊";aԩ~~~E34+P0_2.|~SʧlI)Z8:c U?r$)K 9KJegGuqg )55XТ*\*Т4/4q[~GXNA#9}U kO BLLFXd`[ۈRÒwYv*)7o*߆ڊFc럌Ʈ.SW҆j&X..Ε#,J;uW,Y6TA #,g BBga"z.r0q;E}'UQEIE_~-466Μ1oߤ/TֹEHR^` \-=0Mqz^CS Tnk#-g8s:zdFބn_*ި -݉YUU]YY**+ϖIMum~NCBz"'|n>w)D!$ȍA{nR @fKHUTQN"1PMOס %ć2HL_b 5bn,w<HgGs2[C~ C)͝ < %|V 1!9yUUTQ@|9}Ѕ*PSIl.ı 0̩!TJn!3@ !Bȁ?2f*u!'ݪ*Zvdr*k`Yeݎ0Fs||p^3q@]9,j|AE% I҄cP ?"9[4z^@D**Ehijj?s\e쪫ojn Gc1YzuidRzxxOJ}D|;HmEtRˬ bS=~C vQw` HLjɤ*rСKe䮧>h P)A=l0սf FL" l\a'ǥQƩ*`L [Au&rfB*rb-OHecV pA`zEBP RVjjpd$fTk&i"b%8(UYZt~ C'PRWvx9hHùȂz!"Xl. };eu>Y uƔTfHIbőka$$(+L.%!9(Hۭ$lsw(QCUTQ[Lchq8Bf dc+* \Җ_JqVx.`TIR!GM"N >:>d5r YxDAngEƋ^H.{TQEB::+k 6H1#w]0B<+؉kDB$ M@bn$No!IKdqj<)Š'.[Y/~8_hNƖ}_-|i#@"j)≧>1#2fXRt9͕%ٿ8TEq[_wA:=_UTQE¢. #UZV)zYuI#,BZ:;lPA8BNz۴3ݲ`s+bTvF-11=ÈjFͭ\a.,@;$_tAvSO3λ'^Xy|G'~mx:< fՑ?-EE\_XX0LTTCx)F*1!VKuᲘy686ޚ^񅼅$KsƖB)[;3Jܿd7?4X#m)PG)Rh?{l;,#5S@g 6vA0x>AZYekJ:>o001VVwRCxRp=5֕U8TRji8}BFܤ#kanI7V 0c଒/h :m谀!(2a}m \__xdg|%FΚS5= >QW?')!j*~Iznr IDATߠNlgY)jM%Pp1f#pW;:'xeSH9oXMUz/߬D2?&`8+QB 2mBf훫nɽ{p(P`o^򇻪,B@f?xUьt{,}ǼF;BP] ?)AGtodܽzżXvS{}l{Vߟ}3: PժgQܔ{obz;ݱc?rB 5-{=u:%-.: /2j?~z\h"9Ԭhc!:ks?ߐ5+_#zZ-O ru G/8\il`7JVZX1J JN3.t F1![z mBb Va7g10j/ M>|_fGH#0V?nLk}RMOZÛ4M)gH&[{D+ys;q}[vYX}MTte |1 Ji}xvc|0YKԙ6h;+!d̎e9d]?oҦv7wrڔG sI9wּ?@^[07S + YdOm0}o_|^щ{ԸǽM#8 U~'0UT?tlUMMm}K0M(P~pqķBSs76f' lcj:X[ZmibčM~͙VYd`2\\ 3c=77sz~)HV\|s%2ٳR4g)vPߵ m[JHa@ aw)a9vSL]034مO~jפbGKo?\sn[*;Y~ X!Vnº:TB7\;PC={KYd_\oߍ9>iC|WW^J̐~CU\QE Z,k2:;;;;;FgV kmJx5I(73!IKĕsC)='k&Rp$$t6-0CnOh))bǖc6:c# 2c[*t JbP~&"y`0FuE%,EqCRB1ȝE$b`Nwp\PFz-39 6䪯?]_fKcn.://gOSQ;NC:}ApLjJt;u7dM2bd?XJXjXN+H,r8x"x(8e"<Opo B b,.DNRěUF O:Jktq^ K+B (bxzbe̼:kPî훳MÃh#(6@8<*@3mmi%€|Pm,d {7.s]b?} 7ↆ-:~Blsc3`"]LhX(fJ!кnڥ^RshF }h,r@ ~$@p(QB;u֡@9wUsIz0'4aN{ݞrւ }%7gyWa#3D;)Pw~O0,R Fۺӂ6Pt:-P[De8C!FzVE)huHiLm!`CԀQQFM݋ڊ/g6|U&38^i4Ee(uTGV=_#gƅF~"^gKߖXz/MӨȢ*G>"Y-@3,K9!,:R[-G<{KE_Q>u{ cy.)/ t_ZA5ԚH0n<)G##v`9z } :bp&J+;ۋw !8[vϾ/(o--6<" #(5~Bu>2*#`) N@1+n Y%ZN8^X3enzv˴DMf@8*6z0&l`d4p[~(fȍLɪ멩|x :>i:2RUTWa|*tKB'[ JXa u8JXFb;CgHh1575fGo5ryBj@E )Bx KXu `A.Xg;J/EQX^I͹U:fi7FSI]'|]YJkh=3v3D H3p!z8aՎZ?Hp9WEq> %G}^]0[hmin!iN?O}~Y6( ֺßbEa1fb鍫8YK׭@pWE!'GDG`$滑 Y(Ǒ?x@,*\ƚN@:N.j Z4^WLX{W w6ivČIF;mZsd6Lz1rszSV8qBN|n24u:!D2qJ~m&HqhD"9N9Ym%8aU>Ptʹ~Qm5ԏT.?w?3sXGK][TpGLNϕ~]VXӫN3՟K~_xS3'gmxkqרh|`ޘ\[SxS:y'2ЀYKGcũ%u,࠴_ޱ<=#93h9#&&r#vWj k7-y2AC q/ѷ:vRȦ9{};y{ڏ~ gRbrBKK}y[[f0d2jBBCͧM,%ZAg#D: ;OJt5t4S?_А_qƈ%%-y])3sN(l/[)<"GʌPนW9_eLА_Swg͇FBR~à CH՛pBwͿF^CN Qco_ C@6;A@vrOONҢJ%%\')vC'eUKF tյ23-L#vwӫn6,y]:*rpg^կې'8!7>Լo ˷߿"ug^?;?'†s_}꞉ю۷|jGMa[ $Ej,,eqM0$R2A( )hٖ):ɳRj]qٍ +pgm<y$-+=Ph5| h5@-WE\.VJ#yi(@hI1b% A0*9kqʢ$cO$.Re+=P)/_GQ|ȃOYYP4z-ȋ&WEUThdnwXϵAy]D Q|4" \]4G 9P#I.'>a ad{$)%BWwQђ:,+?+)7IA 0 Q6Eo#HD @lin*ިρt9İ)y$Z #,6R87y³,9/q=?͆tZAtNNUThC K@X&05b2Rň! y0y QH*FAr~HxJW r)"qb8 .6O!ɧ- ۨT~= kv1*BKObۋ򋊊jjkTDG OK4HA,CK, (6R*C bC1 >J eK r7s!:Elu !DVB)1=**x#eee6t$++<,,0((44o~~~~¢̜=~] !DT,!J7_XN9`C~(ƘB m̀De%_J2Hb1?I 1`ڔ#,_`TN|ࡤ_UTQ̙3>>66+dl6[Ɔ^GDDӇRQQkKbcc??))-\T"H.Cs1O@,$\]t!8D_#R"e.ϛbTΑ"8J/PSֵK" Bsn07i'FUTQgkߟa2!\[kkX,и_ n<]d!tvFHQw`ӃT(6TzIBDm.*SD0ʊV0 *ZFR>xE,4(EIjZQ$$%PYJ3[`AXi(kj _U.oEqMM 6ϯpor}y]Ut[SLUI2"(b;tO(Ldpj6bmk=j+ 8" c2TR$JMw>q}nUB"_~7nuaJrgϞb|^xW\kC_K,Th8Q!Hs+ȴ $$r)Hؑ?e"e8",*&*w'~:H,B*4^)oiY@hٹk׍7aYVVq")( J*P=FGתl%#/׭[~{{Ǎ7|+c$/D5$H 8DEBb{Ie-vd#!HT)-T "'\݁:s>AP^R˪bz 1b6q7 FZXS-uPe {+BS2-݅l,,fxBu[n%_(X8_xģPS:%1`.x Xuom~ NExSE@}e&M9"%AAQK'1=LpūmIsmy-\za sxAMG' ,]WԈXNn߸iΑZ%G2go/%CNSI !ozw^AJK(ȐIi&:yٴc>ϔIIԓLA*Su_@u䟛~''u -?HGG<]K!D6hz ,lX]Yo.OL/37oZ:]]]Cgw?яU︮44c 8w=췜sy0h. QLtQOq05,w}ZխRi%eN{SB8%h9&Z.jրcƎROǡ2.d IDAT^Rɸ̎ +2G3ia@$7n4%,-G'"e},%E#¤@:)5R量뮚 Y>Ckܼ]N>>8z]Ӵo~[7xた~|y`^~u?)eiqZ?L|t. n5c\1k1',?|v~yW拗Ej'MOQӮ/r5Nһn9S';4H~ wLIS[ OT2HXB B1eE3hٲeB9\`,0S>r6| zb훏͍'\—՗ݎʒk(73*SLLU HJĞO$r{wh%[o`~V򮍛6n>"$Deh㆗6li^޸im{m!% ~b/=uӆFm ",y$jhDݔ=( EIOH`LSDD$*ΠuNxˁeNw]aqYh<5k(RJ } =9sl~lZj3Nx㧍Ӯ~1.Yvi/V9m)i|וfݍWw,' ۘ[ .z{'z=!eYVr>6 A+` PoIRzMɜ.닯sw~`k/K. P͑KG:$e1 MvbX1鷩)Sɢ,>>9vQ/"kAlƍ1ƘK:ѡ'rwn=ot-?C9~]ꏁ)).N پgK;v,L 0c6 ,]zāC !RJ)4uY

[u&D%,$[ˮ[ }Gs|+-3;}Ru2t85Aؘ/&ZwULL˒]CAWY&,^;188is{뺔E脂1b H![2 *s޺I`:Ubؒӕٮ UB{%kuڅƿ7~ooYpd{Eھؤ6FcP[H@WL@[qtc9dZUR 0dΊ_=Gm&Ʋ*Fs" ӟF[ɰW3qF*m Z)ӗcTgU,BÛr1,Eo*>:U_ Rk."=9jZ>xX8F΅F|he/3uc+d1GQ*%x,N$_ҩF/Y4u22]9\jba  4+#?(h%0D Q,S2`MS^M,b&CKVS~ f yٽKK"_R'/j;$L~ef}lHOxy'XwN!Xn!j`+'x7~,ou^0{wY 4 _N]9Rq"i@֪u (1?F̙Q >cJ+_O, Ćbq5J81dpLT3F%eY$TԤY,?4Mgdz"-癦"n6Cd> A_RH!#uVSJeeZy|ʷ#R@Eo\l)?/Ν7YWBRhyPqAE^=vwj]x#٫W:]]Yiwvb644O!H vI݂lvHB-HLH0-` S 㴃q-ioȨ)PySʘyQkӳ⛉ʿW }Ld8*r(S#fȾYd1b .'7pκIBDpcR< =_Ii76[w!5FmB^7T0!iYxE/[*"y㸎1/9zB}#vm.Cl^tU&ru(`tnoCic1SĹ8H*`l`Ч޿۸?wkD4@-xs{uR!ksΥrs>>.qδɄB4;\@XS*n;#@ 3EQDYB*E(I )B:=Q :ƞ@rMRrPqYdk%^Nyއ?A%[FI^֑Nl]mi/75tG^E;s5)b-4JGQJNxQ[W yA 4ޮL@ϾYd񚈋. ^4M[}=|X H^$Gl~FJ)>$bYw;ɥюKYs|k_tlnipg]І|h@U^:&M2A, zq3y{vϯy9]D5&N'4׵pir D2ƬZ\W"ɸ&cI<R%A SyQ$%N$,%YnWlɁ.C"Vs}Sk }`5bPB֚'{tgy͹B=dّo7zXe3],mjdYU.;dʼJUM|"LG_7&PakGA2PDC/xbsDF 2"}2%8$C2M)K O5e6:Ȍ(䮥,UrJ tвDž}m&5k׮=c8: H@(VhZ$msݏ]isZp6bÈ .yzkVhQ__!5!wdk:\Sܿe'}_oLXa=t!jl8TԠTcHĎP`\TMR ѽgRth2)QuT;Wɟ",Ƃ |~>v\{ݬYZ "P}Ód{q#a.h,w%\]B W#aji\}g5GxlJ]BH>eu'/쁇nKeEԊEb&&UcMZDE g@8$ZDWT  RH ÿ*CFs̛e]Ԣ",`7gyvSDG$‰][6rLl#(\r. KN(twwܹ3N?d) GQ5S-}YU|d;h պP_[`)1,AM N]bP'b8A@%B!ȒI i#NM\LMT," /P*:;;'&&%}SB1]!ӺU_ !ݓ}<j=j/MG 4Q߾!%$V&N$Jc1ԧ IeIDxZ!IO[hvbdtWR.RQ,Y˴^O|gœN3s&C럽W_}eY1bLdN̙c޽{/Zj>& ihجQ0W TkџlLa: IM4Q7d2S[bvbʻm8NOODS=1M/*Qk_2S@,\smֿ9sfOWw/US=Mw)*yJԏևF6kA_:E^}=;IUSR&"J/'ȉgbEfU0BE TiR33f0+eفߥ:ŋM&&&Mӌ#xJ@ ^?j|>GLNN 7va:Cީ^N*JiIlFE,?Ty98.\:x SdhҌ-[eH 21Z, BD%VEh4J+fRLW7~nϞ 6̟?'.T\ "GJI)߰q75=#oYLǒB1JAg_N8+"DW?NdTTU⍑bc "]93 \,e{h&9a]J ]dB+˖E׷^{5"JFٶh+d}vO/]0 uhJMTDBX5o޼E ?0J u]}!d\ NWkն&N`%%1`C3(I Tb>qvnPA'D$$+ѤT`d <8}p'$U;KʅkL ^eǙr̻VC|d];cS?X 繞g7zplvlvmw?hZ[|NT*Fh;Բ,˲bkk+|``ॗ^ji)t)sniajniYiwtժ%'(ՁWH2X,B(GeYR<&Ju "1 bM NI0ebjcʱXś/XH9+S52b&|fZ4!h !4i[CKL;:8:8خh3{/5k|>;FGGΛwQGϞ3[t4t0 0t0 ]_z`)}h uvv5 UAS5L/TkXCbR8LA-dɩ=ϑǑ ( fvoQCeR?`BM" Zfu}_ݽ{w+- I)\}Ƕ|s]7wlwh#åRq7P40Z[fYط`^<)et]5Mui1z@-n9;91Q.mVƚ'~&5&# }?SB}}Mp@ B D DJMm)i28(!-j^T 0J A4UռeYnQ—RZD:ezj $h=2J)!PB 8pרΨy!t7麞븦e6}dV.WȷH)+Պ{Bc;B0Ds Q}Nq)ו(" )g{V7n866vGLLL[*k*ZZޑC@266r} B 4MB>RrZ,YTvT :1(c£l:{BHPjubjkQ&89( PJ P| %"uyyvJiZӨTʮIH'E%9g^lo#Ia%Bv=B7L H]nxohPRRks|6l|8IDATs)$r-yS3$B M ;w#c;:c]VjU+Wu lT*\!~R\a1Jj&e B9jCc\ >\.32(RBxM_`.k4{e?0M} "erOR:K5HOI3T̾0 B( H: [Hb}$hksiORF %(\@ ƨn;G)RF덪ieD!xZ s@Zۋ\nȮx.+Uˣ\KFhQm+Z9zógu'F/r1SZE{GixgRuv6PMqtZ|^V˄Fݙ,kLQ^C3LӲz<4䄩Xk[h^5 rBybе\*+*r9's%H"n4* /! 5h:CœP7= vuWstǛHScLC\7ssm(3M].<ϣQB4](= )4@IIA3RHe _li)j5ϩZ F8 I1jaXl-5]qV^|G;FXli@lޒ/+Lk-\#`h\4굜i!A.넀yncttDbKqΜF0Ɖ#GJ) Ӳ9'֭.89+)m߱G{),?/Xo?EWJexa݋/;[[[{FF~Wj}ui&c%i ap)4 ) !@x@J9E"$f69^4(]۩V\FuMl44 Ւe(|Z,ZzBH ne4quE.8=(GY |.Dž,5B<88>!H1u۾<>HBKAt sA"$\'(J)EiR"!솃@f4l{bbL40}_p-JYVO9W>p}Mue\}ay'P>s獟`ޓ,i'+׽m|erŗ~G\\O]w|9NޑSw 4?m/f6D+!9"RJu|Tiz`v5L2 (ck:"}y>Hb&%Lpx^Z}2sQ,kZlC ݠZ 8b>FolM!h4!: Q羇 )AB\!8AB[ A s+vöm[cTcTӂ"uMB:S)%ȘfBzQ%}M9JhPJ$qPB"a4PBP&\)W+>NNJ2dt[0#7z2ւ3/_`sg>{Ao]9>tc?r' ޣ,|/Ǿt_ɏ|;Wc.xĽۺ؍/P֞#;4ҦݻCIENDB`Solaar-1.1.20/docs/screenshots/Solaar-rule-editor.png000066400000000000000000004312601522022367600224770ustar00rootroot00000000000000PNG  IHDRobjsBIT|d IDATxwW}>3sVz/lYmlcc!`C3 @Hw|EzS/]AAAA9C6I~E oAAAA$ԉ-x$ݦWDAAAaz9ܦÞ    L/M-o6/m|[m<4(kpd݊HAAA81X} ضU)'hV 9w7ő!qӨڱ+L/ ׎d7Tڮw>Y40ТP/_k77YKbTmI-   GJ3!&}ZaX Pں 08-S hŘTQzԛ9Qz鍵bޘ`|e[~5 T36KWPmArc Eɉ*-YBiK06TaTetjH   tQ GCxCys,x4S{,o&BL+VQo?n= xs㘈sG=ƼQԞMػ~J+\bE^8e$VĩƎ[CCd7(R*ωGAAA8q(!\Totxnhx.E2r?i'k9J_%4G:kD&G7s8/P1M$(ԞMx``j,nꅦ_aM9&qIegy^RT%ݦ=&AAAAxí=7:@YvU~X>Q}jѨK:DyrR؁;qԫwLy2Mmة4&a2 2(c,Џ/8,PiT4 &8cٔ>ܽwt-dۀզڞg AAA#dBoc5j{X h,1z|fE)o]&3RGge*9 Š ^T0y%R˘ Lbp}ROIFձѭ-d)mFB~)'=J+)AAA(u'͎%Oo$Šdeam=?i95BQ`32:V}ߑ{LT*15ݾ`l+ ݴM0k/k3BDŽ"MfVytNJxƥJwa 5] )mn   tQ4+|xW?R $U3H 2Zk6x:|Q/YɔSCu^?`L$IY 0& IPc$4QOtG=& |e 4OjW/.7~Qt[KGyh?{R oBpjD#NWMu>q[> 9ژL@Ġ8FW--,ȅ/__=_,A〲V$xڭ_.a<6[?}7ZZ@r>NhQd ʔ]8vuڭMۺcs<ަϡ:㨥ib,n&:It/</OR2yUFA l+oٽԯpJGJ0m P'cQW =-)r\F >\`h[U--k,|SS{|X|`sū.LLoM牄:ZyӍU}|S{O{UVǢ%9ԾO/GƶAIB˘q4[_U ƯRs,6d7>Q5bo8h TףL4$ASg\z9_zdTN+W+y/7LYp8no kGyJBɼ#Ȑ{!m|o=bqŢ杫}1)uANDZ.Kx%jSX5Ay○X8,ԫY{')S&UYB&}L9YWe/śF.mY\J{yeq3b7\@jYU8o;)DX e ¤Q,*n~JY طYn}]eFE6c\~57^E)hxoO#zzd?}p?WC[k RJѾp5N~|r{J O;HTy4C3fJFU_~xţ#OgT*pp"h߫T(Y:IJ#cۘsEĈڱT2`1Q3V)͜,,o h_4q~ٮF\Q=6/ALHS~?N"C37Mfގ#_b׷xA{b+IEVlr\׸$z$:E:ρ炓G_T_>ɢ, O*(L*<,:.sdth0m7ZcЕ CO2ǎke)Rhꤳ@8X@6-nS $l9/ z7;755{urz7[yqRs̓NAĞ=sZccAD@>Z3AU\ԺEE鶓|Řkkn+X$AubuVHeFCw֦hzGܨ|!\EەXDgԵ `j8G^\:)ڠ|߷pS)v(Ҹ%Mg:SG H÷#B>%/-*hz~*Is&>i2T=f=W P ΅4L~aXӈ۔ hn}퓴J^yj!m(X^7]WbV6b=xhs|ytCX9oX^:YԖ!gJ"'٤Xr5ڹ?}I6x+/4ƠT\`'/(8{S-\ xe=w(w7ts%E"w+n=x$‚mx*^j6s\(o7s߷S%grڹ!m<᧶廷 _s1kanZIu- @䜋9˛g3^ߏk8 4tMo6Ork;) XVhU,ԦE`g8mfT YGM%OVNUS[*zݱ+^ /tR'kQ?JP l~r)ɪ`\[-TL8-o*~l5}34o?8nL?Y_v&^Rl90{)A<מMAy2andy뛮o.ꢭvKy=8oi||C7mNz*>+qQN;{`~ڝ|w/52=u٬X{:o=/}ϟv +;Xp}?X bVk_Xe٧<}c=hlVq:o]od.X/_9Zr̟཯xǭ%GO5qӻ++qkV3)N? zZTw>탊YKK{7ee+y˲S}|^F㪧Z;޾ '( jrOj T5*3zm߫p6ޱ8 (lW %?RGH 5fu׶VacHD*J ZYE4Y|R^AT.컧ӖRx0-SLLdɼ8`q#6`q\&}[k{m]i[ >x 򿾍`$y3%hJc |۔ $N k6gg#O_ò|]V_<[Y\ ~n]ͯ]҃.}F[ i\}\ CfR>/?WO)kP=gϣt SG%]l e9׾7ed?InOoIMgZ_:>q|YZ]gw˅-g{2&WqK_w_c96[^nưo|p`! Ik .RH0mzr2(% t|~~x>Ցټ{`DK\$gx_My~&nr"sy'~'s^Ţ|y&aǞKmkr o]7pӊU.Mxji֎  J. ]'n6\TlRm8o6oӾZRRi )I$xT.URi-)Z:YacYXv8B&0*)AI=erW+T^v9vr<_m;IuzxUhW)[n"4;\_jY^Js} |jM3y'O Mf*@$',m !'➍ُsP0qA8 HM}N=[?_8oJV6sƥd+ˉ{Oʏ-Wm5iXГUd>$CH%_E<,i||q )+[-3Xnk[رT8uw1su ]f, <\HK6M*LчB( ]x J,W"m|h2M%`bmk&xqp:UԡUJ( Icy:% JkvmJa nm;‡tFYJx 7,^ ׄo7[p>w,ڹX0|X,0#=|( 3gNF]0envS(.c%Xm,P}a'iQ|Vl+]gvUqOѝ!6 MMD$3-/W/Ed=)F8 x }|4d!/ʅ};Fְ-<\=ѫ#d&A8]2Ɵ|og;NP'[i'&ː!Кet *ݶ,-@θ,liaQk up>%?ul͂RU0Z߆5:@-Z4 HF`"YmJQ>O+xŧ 93{(e=w>͝u/>O Gw=ħ~jۗfeTˣL VmH- d4CdW lT? 4OlN#ƞ?gP KU]桟cHxyI0\/'0Sz9؊%sXjfq%t"2di9zaE^83wMV!DOƢ'ki|;;.-T&'RRUv;%Ҕ:lzҩq'3vyLᑪ5磢X2NaXV:lHC,\"mlPayYL1|¶~g du&햰Ё""EG+[;]YvG(r*&[ Ny+t nYSVX*mvtג:ˆ#xS,L׍YZpz $,>ѭgo MUqA8?xn+6=pѪ\j>YݙPnk_ ^~}-1 IDATӼ_־ŢH1j*٣l\+ sTC lekf15MK±-I~'.㱫0%)*ڄE6o~/e 7Xӆ}/΅nrnW-^xwbT ]d9Z-m? #6[ASXJE;(9Dx(RTmRQa,]xz1ux Ojɣ!T+j9D 8_{J[tà8c?FL ?;\?pLXCX-$'HjDS\Fndk0ծ7N:K|'E&e|7'iI:@^@/n÷8x. [gM%wR\~QlR0*icW]H;)\jkq5P yWk㌾pe*eF}韸M L7qA1, t=~~$ *ǽ騋&0m39,_2~̳c:=F/h<{tzim:J\g#|5!:ʝo[F_zwOdqP)K<;dY((xnep4ZrܼؖMg>Mk6K ʗuuP6A,WER=.}^P(;1yTbs1^)Uܩ|JhYq_ܧAжrwg0JϘ\کu^|p`ObeQ&pN/W3Bbt^GO3{wkBl25}ux{7wl@ٝΛ/+jxΕMk5sL8pUmE9,fރC@1&ӡrl3S=Zך 0b\*Nq镼_%4nbRJ՝O05c+,{Z5tê Ug\U[H "mňP[^־Wc.GjXGm?=(J-7R1n .2y<HD_o2-VuE돣0/(m*6LϺDY :~.swv {]@:On*$~?A>E79szG5ڸvCONh~~l jw"z[+5iI  'mÎI%Ɵͻ^?ge::>x{bo8ܳuHl\JшVjp\TP5CQhqxŨʶΝ̣reNxVP_HMxŬT؎.sd7\晃R>eÃɆ4lg~V \.' dN/OW'6մ[5o<<4My_}p(owWfq_u|Vlxb?W^1)V+lCfÕfr/SX+udSdRxTϚW\gן#5$ `u7lFox?o^?nDNnb*?xl5Be/.б՚vT5AaJD.Se* q|r1t0}#&-: oWFJS嗱'R&^*+R=m܊^tEU9 ܲ%{K/jG/@3zѫ $0#| ]v">o憧rcyp|0V.7.:Ic)0߽oNky,s;2|֧/Zr|rQ0>O~u;Ͼw_ycËU#^fiW]o[ï/-P<^6-EY_\W/ivx<4`\.4敳H[ sͫ ȶH)yyݻX=w`<[(>3~}Y0z??Xw ׷+2 wε'~jG- 7׼QS.t[_//{`)zfU79D1Xnb/DeJ/"þ#zH٭\uݵ\Jh'/e\|:*X4nks`w>ε& L:@ƸU:L@ڦpxj;! fltXoxv; b$`Yvq4j+ږJeJ@WqXotU6mTy8Y/돻vj(4`G-nFe; DF^MC+㨚懩\E`UY =fP#*(ôFSm2۔ )G\.5bǾ5?a|=yAY|v+iƧ|7sMϲsSMCvd00oVNFv7}뾹FO6DA|3,NOz:0Cح;7Y{Y͋`~敲fSd6_na-G0ͼ듷?ơ 22A~;waT]=_o6HSdԿ/}Vn~q\zjGnzMSuMO90ך 05OU:=AP9۶]%Tvm;SOV`P)qhez"IqF'#XFWzbQ\6ΟR k1k#ޯ^*e>I1qD2maǟT;>cYx-]( YTCٱ0L h 5*dܡ,.~sJ#xB*PaEkx>zM, Ԡzz:Yޕ+R8<4C+Ya.Vtfۆb~nR9V-fY[ [?S7Pd[[묒8u86.łp}ӉFւ6_"Ool;al'Զ٣.w.]#Vƣ5fS†RBNR|IZmUL 1|sJaYZXSA(V쥔x7S9n"{Y,PG-7\s]0s7qg=ƧHAiQYk#FXBnG P:-&NX\EuZj(9066%   F/@]QU$OHPh(zl7rߪ:DHkCWP\w}2E2͸L՟!m!]#nS   ±$bXn^5&Vѩ꾔g?Pǿ&u>RdMq磖 4WZD!(AVU#6*8QZQRuYly1<ѡ'ך CfA#  `C[$pb (p)>7r 1`mAY*\a1ƠT,aR\rq\r ±g&mAa<" L#o0R*(w/!O5A8~k[AM $ZqÌ>ɵ& kAfoAf\k  ̈۔ 4#o ab+ r] by#0M ;¡Zen%}#'7T9fj9n\(aEKjk[A898uAyPdF=3ea՜_Fxm}Op$h_k@=  +M;x IDAT,T̞Ş uނ7_~N;3A>wΜ˵- ,* DŽc Ηk^ z|/aκky+V81΁ ' )Q?g| 5,mYsż s,`Sg.P̍[mAdAA?*6&6r / ӑU#}|'uw2ූ17KXq R=϶{t-l|ȇ…=ն8_W=%Ss,9rm '." $GYAGm | ,Ohm0P uͫ<΍%%sOe*S/x'Gm*q8u/o`S zpkwן#.J\l~t~\p9AoAN&ʛylscup_bSuk3yz}ϫ]_h1Lm>Mlzz{ xv9K9sā8)򳗰tVf}r8P[jH̦ؾ9w9gmXͼܑJN6m®CNWt5Z?}~GcDg`5lXIwÂ^\V-6%=neDg眽Fē1b[W"ˡ;9Ph9td}_?"͒UgfA1]NLK3 -‹o<'||)=tbٲ5O?^T'g̭+OgV<='c>FMq޺L4 ׶\ۂ " }l NI4Pd.#b xWar9 +C|5 |]^|/.`._cZɻϼ=Wط?g~?; :1ca[WOΤc*yn䀛J}֫ '|/}S|~JQEf%?{^Uq}- ޑ*EA,ꊂ}YvW׮+bwmułi3?B IB <rΙ3w&ޙ9Uu5eO^ھyK 9ϗ';KӮ䑻;1d=6w\M&SoMaU^xlysEys?NTefej 0faplkԢaJUEy^?O|3]˽=83POPi.Ǭm _ߗdٕxXPTqO~P/F>t:mk{)2_.ݛ_`o>4ߝW_i]+>< !:ĽETφ_Fq˓ɌnBY"o2~1oG2*Z&-]SOFreBAvW-8l'Db?sNi|eL[V-g6ػ\˵O0#Gچ c`mb':95bPy瘰:Ht~\;B5şɟÓa+R}[}[DKt+篧yJ'Vj1Kj#H{A~}uɜܮ1)|"Z(3Vܕ&aE:?9Gm>?}j}g;~*&O@؊┳zSg׽Vk2Q1 '`hD]*?-wO9xK>jM/cYJLvu}ʛ.ޕ_gNew~U]r~5m/>88Oy`e)h김~IՏ8apƀӨ}w\~]=֭" k$ p8H(" 0PW2udV^ [V, bt[ټr5N˽KʤYG#on\i2 w}0@iugZaФ9v"yw о-""7""UPMEVRy?e.gҸL'z-ҩIt=4oHB9ɭPa@ppآmrE71`з6_|su%-7bsn]wdXC@1\ŭi^ʁ7AOʦ0 (? ~E5`bX8o@ꅋسUP8ղm?go;[1u}yלKoZuĨ//(4=.!H:~ƴ1K{!)H)"/?m{> !2I6 H#"#,xo&\rwӺu[z>#))R*HMNVPcGwQG#nÖBW]FKW>+,5c{@ؽ)ap{IQoH?EDDK&dѰ_yg َ.eo|q7)lbOMiwb}$(4^, `6';F.Ci/a?Ȓ_ٱI~=v/rt\4ІrƩMka +!XR,_[5'tР{\߿yeXVt:45<9 Q"*_Tk7H-κ > .nE&LӺѬt@.zib=\V羚Ƽp„ ׵5+ g>1>Y僨tI)$xqX~.W&Fߒ_[oSۧ/I_;uܓ<{GūX  '*JXp'y>>6!vMrQ9gG\WdŶ{m{v>S%W.=kغt&_-W!E_F.nA]y`xkv=i - eYЬNdplZ7^;YMBś@>¶mr{~,V]nwel\v%Z7""U{޼1n:bj?oTv|YO%q'Tۄﻋ勌KFU}l,%ƭc;Fu]6,2Ń;U48'1+ $X0W2 ]1V41:/uABO/x*"0Z䇃kψ"/~>yLM@Cޗs_+y ߘ5s:} KWȿʣ_Į̞7YCR}M ,u~UMGfд48'c&ɭpb߫JRٺWQ.V1eڜ)*1K,?lbm,"R}aP h_ʛA kb~f9{-(LJAJN=o;n:\:>}~;6o`#Ԟ :'_Y8ӯپ WmucbuSx7vmuanVnP@:Ժ,)63#{ _ ( zi .0oY.m竟7Ui sn - ko)װsxO`ɟWt6C%>_UmxO[ cǎrobKMS}[}[DMd*IΜ̼ә=_Mڲl=뺽C,q} FehS,/ 6㣰x`.u]p}xr_ߙ7s! g03i{:$:vj -cmv. Wt-`]9aVg<{VCHмY<6al5>4Nt#g&k~9}EܭsJ Y98mUyްll6}w&{=Ry10.ϥU^dǓr@:EicrUW# DRjVZ4adN8l7\-""e(x#"R*02,夳zQO\)qu_0U!~Qn~;,.^J٬@:D[N`786H(:XQX"]/*~[ciܴ>vzU9x` mk`os$?6W>ueQٶ˷ҪSr i_emxo[ }Gx8v鶪-""=oDDPe>ΦJ|}cr [  p[@0…I`7¿ĦYo׹REkZ7N%!}JL'? vNxl$b(V֮g̛֯΢=!u̟6`{T&|E 7 B]Эg۽uL>)9=)ws,n|j ޺k)MvS,`Co㚎LE=o-0>'=•Scm2ή 8rR?J$? ѧeD<7gHz*kY; }r0Z~ KA=[A㥼ב֣;Cǡ(k~Cx/}kZtT$=o,n[]F֖̙ ]z߱rO{ε,p=S^ 2gM\vPۗIԡȰH YRP>⺯lۯ6l:u:M3Yݷ''Տh2O ,2SߕȾ-""Jě0 PPG8qx=QnRSkrٶ-Cu={mذ|.[8Dzݚg^CΦ]l@xobu۟X=Xv u;-G+:4V|;mK<7SN3hث?Ǚv=aJ| &-u|AkbUgzʻW̜cu'@R:؈HuS"o[g,ٲ;WF괒G諭DRK^dTǓY,PvlN>:nmrqDDm!zbj[k_;@QQ!t|>>gAjjmbbby1q~Ӳl(/n c [3(􇱢YP6<,֯FEr&4LGȱpE 폢F`{ZXbc=X RXt͆mw(lؚMҀF㰃> nETMA&6'UhK 1zL"&*>m۳qhuq~m(( '&ȝ]4S녢[؜CaELb* cJ ֬XJ6q\ضNI_{'x((Dz]yw.]nQQ}v]8+18Nx`JOQ~~iWWY,7(;)$ڍS㎋k{Dշ-""{5vk4FDH߶7 7d5kAn={'H(Hj0Vq()jM&ͩ8~ ~\ĸC eR0B r+t9B~,o2*oOd}JX88|;=TDAnGv??ْGVWglܰ-9҂dQ݈Ƶ]eR|=B`֤QӔ]LQ^uY%%&I͚yYObcpc`!e}%~a偝@&Iw (pDԷSo(x#"R*3[$==nmݧB.Tdc8PTygrD* v86Z p{Ľe?ݱI8']bF"vT}\=O/1aL!9&. u+ ,툓U.[oH1oDDyRIdW4rsΙėn9q,5[Xɧq`t8*Z4k:61|{J""r0 ވT A91t˼t ?_aɴ;7^N A}^F($?OԷED 9̴ITC(M?ء۹y~7Z>11;D}M""r` ވTv/0!_!XQF'QC;)TIP_~ˊTX|Gr38c{krQ9vO,deiO"qܱHToH9,¶mb≉O`Œdevw IDATDp8DNV&+,$6!ظxl>l{ިUٷED,y#"Rv*k#} ׯ vD(">>M|wk"GGUmٗ7"". 5O1;SxWr mض²)5#H(9mxI $jopϼ5*K*Rɒh5?*72U_9TB%?H6&r䩏9 ވ!+rdF yw!""""""""jLjL,??%K_昂7""""""""Gٺ¬[1oDDDDDDDD2q$oDDDDDDDD1OG斝 uIM{-eeg!Ɲ@:Ix_VYIrԙTg̛`+a4癃_aμexG[ni݈FMz9ÓfB^ӕ JIb0oFrBF48sSVU*$ҢL6BHvglظ33s'RSkӣ4lיc$u~z 8W,̒i}pq*/^%J~u26`p\]6u)""""""qD7LBFvPPϷQ&tHGJ3;>e1먾>6r҉!eS6g붭\x bb vj6o|#qru7-ڵ&hGDDDDDDD̛3ӷϙft֝33xХG<Ʒųfxm:~Z hur7qJ g޴,^A^8Ԧ޻+MLj΂ĦtlBp4~;NeeRˏ٬ӗl&$Ш}O<*rikYƪ5l7CE̙U'5#)g WgbĆ*.9 6S֑%ygۙUNGvV*fcbЮ]vs2Y` Y&Q #m""""""rJi2929S^|ycv|bmfmrs֭ҥƍ̓O=ͯ~Q/8A΍<2T"lWNncˊ1k\(`x`6.ueĶgξ>b--W[dQ'0U.yt8342ZJR/L,[`!31O/513DVb- fՖIu7&Q KRǁ'd}910TN 1;PPNE|].2Ƙ#E}~~>EEb6~ߏ_"/?Ka?o6\\ڣ%!FOp12Qӳi;2vfj:7xg StI{YDY/Xʫ⚽J<Yi~b^ʃ@ 6QO0b"JIGzML|a>]c'wkcMūS߅8A4kf?gQ~/m\}=¶+""""""Ǽo>C> %4tP.Tm[qyyx<`01M6R;N%~`鎇.~ƸڲT2shЭ7/niѥkq=m/.ӎ?}k{Sgsux޿F45wqqn8=k ? }8gvsWzӛzO|Iy {`_u\ߧ66Z.<'Nۨf9xΫ?o7Mf!iur@| YH""ڮ8`Ȑ! 2䠉,pp DGo{˯朳Vܑq bx t)>G ~|}!{xORY?Ƀɟ_ns*ΖO n$rEYڬT4"DOM{ݔDX\>mAx#k7ATY{DmWDDDDDD]rΐ!CHMM%&&WǼ֭x<^</[n7ޠn:Ԫ\+ˮ˩5mn=Y}NqJ9k3aܴՃZ]-ѽ..`JtoϤ e| nZܑrNIr'^t8+ ;G=I&9SHa92FZ'TY{$mWDDDDDD ~wY"/ m|՗|9+Yf|ԫWHN'Ѧun"|~[&{إFdң &:rߧޟJ]9.Tjs/Hۉu&233x=K<0P@OJJ]4/'|Ge'+;q:d:+T  h'dw sf6Xq$[N ^"QV 0f"Lp ~`U5ZGi#C'#2!mdQE#i""""""r\ 4d…ԨQ$&&+1II5INIZZM4-'(z0նq}ʍ'KsK Rl=kWK]Y`l- fx<ʄ3m8m_noEQ|^JqUt[1:94m9 ΎlJ>y~]ONqW4=+""""""ǃJ/oo bCB_S1_xs.8f;IN]bVFeN% =)fz 3dG>ح3{튈q ڧMḺ¿'ȳMZ^c?e&^gsoYC_j~~jszxJ\e\ M#m~V޵~0um}ktfk_ou;ddbIv[{򴼵MoEٿ۹ v=s3|yMhy4(V8Rca\-13G$3mS̵2R"'(ef2nsw}>$Sg{mWDDDDDD1dJiU"c]G2ntBYɺ\v,qBDUhKMkXy'!q5F$tCXAEz-hٴyF.s Vo#nKN::孜yD*Lަ,ߐI *m[Q7PDr].Z;'u37CDDDDDDDSFDDDDDDDSFDDDDDDDSFDDDDDDDSFDDDDDDDSFDDDDDDDSFDDDDDDDtfߪ""""""""RJo槥UE9DDDDDDDDZ6%"""""""R)x#"""""""R)x#"""""""R)x#"""""""R)x#"""""""Rv|lYq'PN]9"(˺+4,\8RӸ^aX[i3 Oi?M7EoDDDDDDD4|<&NG FZZ&"""""""S\'z4.=*b]G0;\\,"""""""9f ̯5wasItm]Uqk?ǭ #h 8"""""""c&xqw}>|>?>χ">*L4zM%fpn_ \ElLlڵGL˝LV/XC~v/s3o  /Cjtݕ M'pՐ+uyWE9z-›X) ݀!;1kc*uisX ENSɍβ\[*[؉mz*TcLf] srq;Ą*~z6 _bG<~lCMg;O>;q dsp ?+T#+tСCaAe9^sM&uap5KO9|RiO5ӥٜ{t63x JDDDDDDXrDFC aȐ!=o}>}{ӻO/u?;D4lԀx=8N p[MduZv2g{=*3{%A;S&O`'9ȃEDDDDDDhǖOi]{lwf!C7pТy >R嬚=o|3<O9zg,o}>yU6&6M^=Ւ⚻3NC" x݈2_"3sFc"pMiLR|;ٰ%U}3[q⇉M[ na"F͛@9f w,zYRy?=^Yi}j.N70S8S:.'7 `B~|De<ǂk?'+E@Dy:hh;M߭dM3hI,>jw COyx^l"6&L&|4葏q{1vSSvkyos>؛N0ԋ*bIGVxi9?] 9(xS\eWAz7 lG-s,fs޺7[`ٹCkQ|1i,eװ8 h7ccr@ػzruG Nǵ0y|C&mEC]Pm%+ePDQ^ Z6I6"""""""Ecͼ9j\$4l) g6qu[srև3Q9aH5H5H5H5H5H5H5.S| NmvQ;x:Q^?tDDDDDDDDexY(cϿ;vxgtju:鈈ϴlJDDDDDDDSFDDDDDDDSFDDDDDDDSFDDDDDDDSFDDDDDDDs9&mRc,p Y])"1&C~C9:q.m(* ("""r9,qSÆ ; I6 [|G"{Uc pؑ& N-X31&ΡxlfѪ[-^=3¥’G=.4*E ;3dArYԭ&Uܗ Yrxce`XT{ꨖN!ŁUYkQc{CeE]upMhɁ+ѸFgavŏU7VL4!!M.{̨?P UK fM,lZQ  ]o?6qn8,[gK:!Jff4({[1m^!ӷ)r۴l=]Q;GEMf|.Co [+b8DE|2ph(x L&Z?ImxD+WSx8gvO y"\~ |.LDDD$"g~{:A~J  /9G>cFqe5j. cM#/-\چ 7(yEnj07}4Ed*n'ֵ C7 xcD{btlq;f|YX7-l,gJE7j vG2j4&rؑ"#I{^²,6(r`?}KX7yihe 9!l mV6CtcQA'q`[$ufpIn\w6 IDATNgSXqB^,U7_sZXuDY`ZFhΥy|tV-ӽv{8g%d-1\mwf߈m8m}02xce3pEL^N?۶)0qfn#8V/ɐr|hdfٜܭC};p!&œ OOv6K3"rIN& []XI"RSpŊr\M\bISxr.zIS># xjB.Sr*6㣹ʚ\P \$ƞ_cUzdzAƾ-:Nk^P`;&ϔ{,&xqקwvvL(9<>% y%KcޘexQ2ÚO2q|^C |ͽk|,+o ? sڸ8ሯko 87_a6`97AAe>g8A&c8͊ވwHېIӀ|p+kUZ1qf a ry6>u=_܉Px>ߦԬe%M\$MM3Sx?? 9b-~SHuepӯA8L_PDF8jO|˦ڠu \\ȷUu"ǟ^Z,(=3r3Z&&q8ŀ5 me%g%<'fd8Xg<1⮷u'Y^n8piaw|#0xfXQ\7&?{^E>p;sKF$zޢHtk@bmWWQ, # wiv˼? P({f=̜w9e +`8pyobUɥgqfd0^ʢOsHavzv`GXGE>kk` l3[xv_NnM&J%z,R3+^m0ƃO? ! N+TJ)=>G݆<ŞwzUJẳbrn+&B/3]|7܌u3 X˙nD&NF&ylVg =!z8yj\*m?aWGɕ.5i .jkE>J݊v)d,YN}2&\ ??n~ k%iy!/Xz9]Cxsc.phqޗ||plts,WEs{kmwp1 op`}:O*DyL08XYG: q6LqycѩTv2PdB[깸`[&7!&_ %!>"sHDrҹayYLk 7@,Rs©DY>,Ěne冺¹c0]ىv@^)|(*nY!sILa&9/V>AD/3SJ):>!%U=teLY}y86|> 08K"?/IGn3ԉ) @j!xc <{LLX$Uygᢩ d{7eL6&.aYn~^solHhƄ00ހ~Z﫸#iɠԯ?BL\6!@!kWJ)RrR76V,dѶJ2#,[:2f=MÀǯ&3͡Br Iqq9-xu $~Y}bH5ͱ 3{~4CRx1:&&¶R_!>VlLt`_R5y,$ rx{$ rk(Unb(oD^BIf\;c~nf`3! 1lK(;3ˑk2yi12l$6rwd෹lF9? mLL`.ac힢1!qdJёVeYdv`G㌰kW`r [u&ˬ9?+:Eqc#^Ŷ|gÆI& /ޏꭅ,%ٗCiQJ)=WD&1i+P'FOBr <~{27+%$Ƹxxu ɅVY?g{vY,Otr۠(v}vF^Rƶqۈ,~oezs^{'=7Ɓ]|ZUX:= r6tУ]}<9'VxKwNv%hg'6QvRQ7cm8=1uU}5U)<ŴW|Ҳ+A(XX0CM H/qPȩ aU ) $Y9eZ'q1%EA@;2`) ;TҩsK EqM'㧻|n>.{0jիӹ|;BVMK 6qIRJ)SSqՒɫrym n=%Uu/Jn kgrxLΗ32E!d854p1>v{ٝW2Bg1we>Z,W[Ή_z>?n$):,/~l]cr ,,^V7ذ y}~[&0PPtLA1aS Gvz gH.(_4 2+aZG)RJ:m7ݒ)syu nؒW-g꼓9,;%ǧ.-zD|W;rPi0*)yٱ&eK OGC\WշJ)$,/)GV5 +rc.c΢zLzުw>ȯ׸.W+EwU:/a9tiݏ1զ:?z/`Ti\q;vرb;>+v%)IMmG#aNw&`و/ 6*Y!ӛ.Ϋo >s6~|L7ߔxOs;6`7aY~QQBZmӝOz6 -hJ)RN Dbz<S{kqJbnscxYmu.K ʃ./ʓW08mXcef 涿HbXD)ul#B4 og(>eiY6z!>2 IQMX)ww4с aBwmzњZ0u~ͮsS"euBH>p/bJH. +{IɅv.(6/ҏ١\^̻}a&2c\ӤiqY>fNdw )ppv};0,c'esutj]Kih4<;=tp=ty|ÍAqc=RT oVi_|6#KRgnXmwO!+3LRܘɛ_K̰ :F<,߭RJ@hTH1b:,9%;7)JĞ4;r1%=aߺ<8v)e#̿Y>!7n廵n^ӑQ<=3:sFY΄]DN<8;7Ia4nˎ.z6sq}z|,Yf籥lIŐ=|@{$7pЯ~24/\0 + KhqPyL Ӹ5;HnNvG2C<>5/>g;;9e01Ks˭S/~^4 E؈euC6oLa~VMN y`H hڴ3Y|W:0H.I ģg8rImL;[ٕRJ)T^1II7}"=ϝdviib@vp\჏0jȩR/iIpCH3WJ@9MBFy#Ax0bwzmT-rEiaYN9~[&}˴JX):o "exٝ+'tav3"oCw>8ȧ78N:&"8eo%_P: .N>Ujv!dfؕeU3M7X0i?,?A3RJm[r8^#oslyr񿯔c랊F]L2k%k'L^U }ގY$7++OWѽA=s5^6[0J'47!+CV%ixpX9|V7J)R:,>~AX^~GfN(a,f@\B^~;_q1+e:=W)R*`:4aFsm "n$.l^;4rRIr9%\\wa8}JLs3h߻5ؿ*ׅRJ)CGޜ&P'$qU;'u-/sq߬BOvƔR4phcOss}3Tb[d=<5]X)Rzti·/#e.vR^ª9i\Fa5᯻gMv:]J)R4xs!-K·RJb_~Bf?J)R6RJ)RJ)u RJ)RJ))ԴEO%7EYTj︦RJ)RJ)Wwț+WH&jj;J)RJ)Re:mJ)RJ)RRJ)RJSoRJ)RJ)NaQJ)RJ):iF)RJ)Rf?WRJtؓé[/',7:9?I#Ol t3MNᘕRJ)t͉&Y\ʒ%K*g+NŸyÆ4K˶yԏ'&67cICdnՐ%i\nZƒ%KY'fC~SDMp'RJ)Ut {.f2gԴvs.4ixsLn׏YYؼ"i#: UX v (5׆1ӥxlqtpmc:o.N\&?ԋ'J>iXׅS㚡C)RO7sfѵsWOod@jպj^tKݦ=MBN)^Vn ?I{tSqb,YH*f-65ٟC0aav ,C9ZSDZ 5CˇRJ)jo\ANrP> @-pt+~7zInGN@ꀕEfiֶәQ)Xu┹fhPJ)T p9w voiؚi[r"A뵐m%neW9;WdL3`{nFgЪn+J;ldAD)ǚXm?Y&DZvNk:ǼTV24M9Ws"+}Er#pWŝsO*k ȵ)_rS"*Ccc/\}ma5nG=hQ3xS7j{&4h׆F*y5{]hEfTQ:X$2WRJ)U^؛GJ#25R;Q۩)67~]lBɖ}S6lX_;*Yn1OU]cS0cRWs+gM0T2{ ia‘?mΕTrAF4S<$mśb qt^{0kGD 䧡eM?zW*@o(q~U6)/t jwysE^d܃Apzb'Jɦ#{:n(#2]#AAձbbJU_IZ·yr6&݆mϭupDeGŒYh?ɞVAb!rIŏ \3T/2|(RJUWxoJ8!joD, Іu1Jm[2}xsl(u!(.1#;ȵ/|!/%)1 IDAT&˧^)m ^Z.vw@&\!^rۈ۟'w$CEil9\7>1{~ZieGk^%G(_/_ 4JUC[8:I>^N"F=.4r!=Ge+nl,v34K6,?]1TDᐤa?K2u|'g}*zAljK+F>EOS#`+,Ng_]-ǒKa'ޖ/'N'ɐqb3 ,)ه | 6W _͔Kߑ4:ϖ.aU ވ]'KYG@Fsܖ.w-)?SήgC\=^U%w,oC],O?AMfOF޸ /M8PQ<=\ )͇=暾.암^3ZZRJ:7ϟ'1R~?׭_+1Rm|$[ʺukk&ވ*?ݒ$vÔ}ɞqb^(;v/yZ; 1m_ג? 6?_F<*ie7p% +KE2M0 -Voҋv)γխpmE Ôɺ2Os)m^e6{ 19iwJsbUoy7ʈ ޣr/'#X(=npad;;W|qy8ۋ\[$OvW\OyvѺh [R@s2m.-R?`Hxߑl?&`֓~9:?%Soo,6 W= xJ^;/NL. X6UaH`\;ׅc}h +RJՆnEp=hoӺ-Zf8N{&3q5?3I>><4)tR# _-l!_=.{}%\p͂1=ꔙow#Ɂb5vph_z1#J Dc>_VRJ)jI]T{;ug~ɼ=׬]ͪ+{صk3ɥ^^fe {A"PӨ3?YWt|Yp7>}1:-uf,j=L}nz'}_W|B@RSJ):Nj[o9<3,L>W^ŏ?}܏dr,rڟѡsĀWɕ{AHt'E{I? IxAė]{,@XƥtO B۽4 [ = WjI1ٰ#8^vi_6` VFdO2M|<ޯ((]k4oD&|%,3ŬO?;Gc:vtW!ɜ?|-ԣab&=㇛J?i4B 3g>;iw$Tp2ܜ\,ˇU@pyyF yE!]aG0Wa\CF#OࢫX9~|_Jwbow欜2e {]?ŨO!X=wӧW_qq.[_`BA~BdIn69*HA>l ([߹/s%]ǧ?f'yo"|:ֻl&^z]8S*RJX.VFH<νF~â94Xfk#nќm|6QvpQuHv x퉽Ѐx]Ôi; Gs i))d\@q oekiL{^Z_*sm\TJ)R5@7@^^ٰa.W_} ]t=:!c$x3c&O:k%nmc۔Y{MMGotx:v|9u,;Av/7a `žY3X}^K +u2ݻyq`!3~B ']{'YTWjp= -F_dǏSbfpMR/{Y=;>bFL LEo:֘gDbc/it6`C!E̚SF,:ΒEfԍ["YǧFRNFgԐ$lyyǘ^Vc FRJ)ԩE7 fŗcru۫N-))47FpQxߞWSp$7N72r{[/>m)ta.y% mcy xq|@Ѣ `Nٙn=a?7Ϟ}%ͿR'NCn!eYOY|݇}!^HC\q3*n0 #k ;mC//qXqz8|g: ~߇e8hѺi]C}ĘB7R,1OVkAƿ?:0O#hі.+d6qao J N&>p=oj@ ù=\'FpSQ/U:fjpJ)R*]hѼ%O=W7kMc@5KFa{]yأs]<}L ^Ν=\E[h<7~Ȝ'q`מ|vɸg9I[^^GZ[7dg+{"+4gbÎ^ŵFr~ ́.{b09W\71>ka01i]ƌ[udl S`:l-ۓ9k0>}%l{#z .A9V8ˈaH0)y zatמ1 ;g0ة 9{70o<~m#mBO".~'zOx|Fùjޛ|tTeZ b5t[̐LRWM䣏rt ӡrkkƠ g ̽FQ{5&gNE|:o|y0~y dοLh+bDɼ>O3;w'&73t]8gARJSV찗%{HwGNvt b̢WT) wLB/&bKN [~{8 C=,sJ\R&=/t+AF6%C_Uze:G}\в؏3 MQW1 m*>/X$ |wT^eI2n L$DzB_)w73VM.Y#raˈ]nׁWKZ<"F!$+ZΗGZltl)씈xʯ#HFab+y\b o"}oy]~*oUgH8/YWX,O f>߆+'$kl7GF j# N>~%˞]cH5CGm"%?G: CG~]+ʍxd{Ha=ywRׅ]3^WRJ)jVx;e(|yoq-jz;*6GAr $4EhC?غ'OH]nF\EGlqwP,M۴$u̝lڞhܸ.N7iW:0%Y=1GF gw_Nx&sVN#k#4:fyWNʆ-&8vma{sDѨUkW/{ !ikZ )5ʛ}!Ɔ(w~מsm\__^RJ)Nq7xoR/ַΥ㽿wX;iU)RJSZ޾+_E<=\[sny (RJ)' +_s{WRR3y( CywΚRJ)R4xRAցy|+͸wxѵRJ)RJ;= ד ^FĆ\)RJ)4xRE0⛵!dC)RJ)Um(V)RJ)RRJ)RJSX7;vvRJ)RJ)ʪYre츦RJ)RJ)WӦRJ)RJ)NaQJ)RJ):iF)RJ)RRJ)RJSoRJ)RJ)NaR@!!p֋qsO.Ynus(u,M2ÈMCϟ7R5D7'dj|3[= C+ƒ}d'o ~1;i~7k7i)~[)#|IVqވ3ZYt^`iB1؎!+m+gݖoqܝta?o)wShnvՍ+(O59Q;Fq`rʗWR {.f2gԴvs.4ixsLn׿ZYؼ"i#󪔪q>־6.chә80lm6|6QeCq/dm*%F8}o?N7?p9p3qλoxq/<ޡ>=O%ݕsfѡQ%|rםy`{ K>10ÚqoS; ʓRUp=ryUJ)(mJ_̢kԯW|ɀiժu-FNX+=MBj'qztO$lI`RT^[l4k~?G)\1,`Xt98&j =⋕x10#:C^Փ繛^ߞf's:hJ N뎐6<= ћC~|p{a 9{s{q˓RP=* os)p}`#kvd#hvlJT렐s Bp<5 ̝/{;gdBkҞ3tI/7g1f Kn}1{ͬܖi6rClYtqQm[)+ټw,u#KXf#85 4JJ f"lOV+&ڬ1QOz~ ;7\4lM^ɴ d}m #q;zAc)Pt[! Z> 5AJ +^T%[Y.9l_ Ws״ }w茐m%neWYd-f,ڦo;rٹ7EA.}۱AoZvىmс&;IFQ'&4Um3xs9Y#/u ˬ1.\ٷ=oqwoyE Y98J@pV+W_kxgL~Jvp;~,XU^nOtrtUQa^ ͻѷ_'*LXŶury ZR@˫RJ{Ybo)7xwJ}Dmlذ^|uٖE %''[~Mٰa}g)?9$VvL>H_ϭ6GRM)GbF;WReȝ"?<_% NI|]2r蓋%߲g{c)F6ird_@Yc|4BрGwtd܃Ap:b'JɦI V _f92a^QٿŒf ξoEim_j9Uj/q&(Ql3S?^Fc jwysE~,ZB* [b3J&G/ f5P#+ v3VM*go9E4x_fXS̸OK8{-e/ ""'.!FCE?|ޒgLnZkRm92_Z{ם%4ٓI,rs)Q2dTPupmo"w]3甯sI;*uġZ_k>:-V_WRUQ2^NHK}{4!f+d̮wVL\)u!o$} -apv:o|cʎ`o!׼4JFP^(@h % x1ZO-_N&3O񣟓!fYRڝߨ IDAT+nl,v34K6,?]1TDᐤa?ʁ @fy{`by 2mo2{7=}$nf}nI '$1&v7LBknKzxWK)H]qt*w3o|L>E&z\.hC{^l|0M.mo=Z>I#x@\ QɌYSeܛIw rQ)ؒ*)ػN,Y)۳J@ș 30ɐl9٥a0gI:y:bbJUcegySF{hqF*UwݩV:I;b׶=i~ӱPW!ͮ!c^'e{y \uXmQx@k-g|jkTCEU)Tϓ)ϟ֯)۶m>yWeݺ5joDJnIaJȾdyP8 1c/[K!<-ζr/ekɁJcb/w!ie7p% +K߮X3dxs`I[ov)γ2@TC!^\'r"yCzʳ_6pd+)mao#O,>q(xYXMu粲Oճdņ!ޑ@NUꙈ-<&s3*JU|)Q_ueLS%F!$[j720#dT\ē\o0[3yp^7~>8Mn@qԹO};s]bؚ=K~qޏo'rY=S0Sλ۳Cn`6;թCIڱr{P1 sUcՒu._9\lEd - ^_k>#XX 05[^RtV2^s-hA-^mZUL:Ird&|;saqfr2Ӈ&eNjp+gͰ_*܄ۢɗ1 $,GJ} BP'psl,WsA]# <ω>*cHhF&'+:۵)qtNOC:X?W.dž)Q wlUU ;,wEFf9{۳%ע?ϰ6(* +cY^OzhQGDbhiCq`,^ 1,6l܀.]Yp:dJvd~jt>/o#[VΜ ZD:@7v p6' fEAF S}?w}m𶱷E\-Sq*l>]x 5wju]ͧ{O2,P#$*h ՞ 8/$"I @ͬ/tL&wR&/w!4*h/Ź!62ӧ T4I@FS{?5V|FξX>o3f| 8p.b x`0ZZjd L%Qg]{5?8C9YqpJL).?OuD5x쭐vť![xV2 = k1gn[JDDXwKdM#42?ހNù,Xt:upMSR6/@Am.~7õ}?wԨYSBrJDD0`媕\/hZ$$ƌ3ѣ۳_~IW-orkGoYPc(]Ū/bz|4Q LH8PQ)ꊨZQ 3'ڔl'A _Ĕs$27'r0%u[~E̡?jrm=[x,#>ԕ2cƄ3'䱯#t"PX2ega?Fj9/DWϜqfo°pt{q+~.N\\ ۯn/_|;N P~5-qwof]Nn∝uvxOIwL}҇;.[ qӞy:۩"{[)GwD]BA~k%"{kׯØ6[<\O4hаAoc̓1'x U)8u8 WDՋ .Ĭ$ռ:40u8l¾"餁Srɗ4 `I\g\ڴ=3[Ha"kC \UɌ8jmgoA2fr 7oY`eØ0—+ { GWQPWF^P#w"9Ws`eH(UPTÀ`K,ګ ?6}#HѠڋ30i, W4LnK/8 ,lkOfp)8NO"} +m\s4YRg;]ⵔw4uݯ\O{V&戇;8p|6FӾ ulKEki;ϰot|W_g$4I 3 R^(@'/?/_y;jED̉Z-+O6J)ԗvQ"jEIvS>1K"Pr):d⣂(*/q块:KjQrm&I>KaU@TeH'|:N<.\WP 4ҳ_jE*Y^z5y?3?'l|3LRQE.LBL$F'CD(T5Y{-N&&?.TE+z#͐S>מODVSReCƻ7S(j?i22]*@q&l f{ˉ־(*1hvvi U \e1D+~Q~sωK@DҏԶeET^" xY!KɈmY A)św{ , +߾[w]Ir|U$.)Aj\OHoY P 2m嫣,[uk5Ep)T Idk*7ؤ=U\G֊r {OH~ͼ|k{%oD/_"jI~v\rk%t,(nʗmDE<}#VsOxR%?.boeS8u*RLj B >\:n$C|uR]|;G |Bu?ln_A>>zG "ګȈ 7o2.7ܚ.TA`k{q-!/݀^qAR vlfOڮD"㵴g7EW"Gp7_ Q>$2>^O\sK7?eJDx%"ɛR&Qic]A.9$wbՇsqάB֫jID㕈8(ڈ71"<1|9{;wE0oߗSP|q㟃o$*QW""b򆈈(??؂?kSboK@Gcе:gw *qW""b򆈈A4Ah7jڍ.k8y#b9[:Qx%"!&o L j ,zу1^߃bLb6'oꇇE= 7bcDDDDDDDDdo""""""""*Ř!"""""""*Ř!"""""""*Ř!"""""""*Ř!"""""""*4%]* 5\ x ڒ &o Jńx$j|Qְ5!#DD2{CDDDDDDH`ҷ⣶OǫSTp_{@D9ޙFDDDDDDDcfjx Qs}Ү=+&k0k|f&"""""""<4Yfc}6y:8`kAW~¢Pٵ~cExh`T"""""""C3g.Fyf zzzzzҰp"H<3*v>A!pkXyifzWO k%gq5 aa̵@̷KHm |+AӖIT~m*өS"̓(Bu*cNęD3 Ǜ,>=:GbD~Xa Ya:n޲ TvD3>TJ&y3FYnM ;<=0k"7xX="z;x-\uϗ`'q>}36碫 bI,\ .,в{F^r>gluΛu0m \PGY՜k'1Hӆe1M{s4?~ŽO`^ߕ[&r%.WΙmZelCXPT A;:-,3ܹSØ՘:=zf75͝jF;#1u(+}j$^l‚Q)cț3gY}~p4M^ <7Pj58hM,9oX9Z+zbE2fQtAhyX0,E6Ɯ ZDYF]QMޛ};J:EDDDDDD+y3t`6W #sOӧgvէ"s Z_ 7`QGajytRӱ1"=Y~F|;l݁$6r4my%A˸eP\jԏDje|gbqvkH ܃=WpnPBrxTF>.a߆9q& n-{ݛ=n߰o U(Tv;ғ`=8z*WP "QGkY=}pɹN?0X`An=y՞9 n][Zi@aK[n[ó6=cFǚUDEs"g$4I 3 \q%yXMryY,ٖ 6%HJZLy|2oW$]bF &"+yBw T) g?*ɖe,7d}$[ Tl;J1f.?ͽv1#D@t-'ɐܲŊ v-3RIiW/F`g%GA i"g-`k?u'@>s3:,Emﱡ=O\ }N:h3YMFNq?IZ@шo䛲/1k ϏztnQ(nl{>]PSrkt{<1z )!z|ݱ{QrF>C @z 3?BF83:{E`ިg`l5>iVͽ8ƚ~τ| N}=>:VcgcŚX6]7YKf0&/MQrj' I1[5kH%}3DDDDD6yFL-V]~@ne2c2smڛ(""SW+ˍKYjVE&ol.ȕxt̙_]DV}rXr.#uW&26S]s9g)Ga(Z*]"r%@QDTWg[+gu* n=RU Q-7s$D Q42fv?yv_#^R Jjtdr~Ⱦ6~YoP2Fy\rs;RGT2~"""""[<#onGt=t3 &tlAo4똻8v Dz-%P[#vΝ[ӺLg>5/BR6a6]O@V=_K>'wGD6u( zZ("4sĨ{(b<#1`E8cVU3W\{?>k!x,]?ldW`gcQUF")nM[¶Z^BhiqkK2֯QQ+ѫ3gz녣IDD",Ű.u1wINKFT#2ʹ1mY,^z̜ ZD9O4꽉طsT*Tf1cDV}Vy3-׺ht3# Gax@Hgޓc>{a!Q>WWE_\}&wp{{87DNzCRV6r]NvIG!I 3Q*!u{Þ 1<úc~ۀ;tLy)>  +iBQ/?^X."W]>zŐ q>c7wa2Ұ%o;uM*D{Õ wOܣZTS1_KfZcN)5X._ gwՎ޾GpxI1G凎GQܼॵՙ\6s@[+e+R_pɸn'""""H6 "X P[z f%[F}LE1&b2@[ TV U W޺81[8sf{ =b&: =*#CɝnFsrrS&.>I7X@?\%s0{sb %6+|[+.pΣN}у<Q=/qmTu˖SRPТmX; ;4h_̈/$n]uW˱w " =`HG.GTrW*wR!9 B(%g1ÒW޾G~ATJ ;j,ġ6s@[> iy>͐Y=7d$""""^cz#U7j4P`B©BMZnxkү9'^ Bw D(JZQ 3'H83*VμG9ZEL0YI( 3??QʡnѣGQ9doճHc-{cƥaM`)""""*blm ib|l?T@'n@CVaӫW"L, _av7/Ü"j˵Dv塂W.^TDEE-Bg7Qk.DѡaHgX.#)WՂK`{۬m}wc֔DZ3,PF:9#25Np.9nrQ#ST1伄H;<CU\ZW;vǀ/ݷs'bi%X-תzoOEF` 8hhP`D*H?0 91i80u,$*$tVE$7v6X4G{K$,7{5 i$|06 <dDDDDDHyτ@Ui _Z-DhDwNi8`'P},A1vwxcXLWWe-HxۖGӡ*FwZj?{ 3v'/s ruaK05K8~!f<ۚxvdD>uسhbw̞-p3rFJNy}>sX26ܽ՞dmtwZ$X8VQ""""*>'ϠI3hķ3fhZ-]bF &29ҳ8)x PTV /54.eڣFF!,D{ ڞCdM|dજ f/- DSclO$t /r$-,lDDDDDȚQ|N(fx`gY:dpFZϣÂ;qLXP|5T\N1f):)&5ZHxi.ay 7TG: x;D5jc8vN>Z T =Eɞ6>!#D48r4ePf-{f)""""*Z>| {?4Tp 5m(Wp-.x W֭`sZ"*RF 04!3d)J$s 'jS֍9UbLbLb󆈈( ]OmHXt~ 7DDDY(^P' o""""""""*Ř!"""""""*lNwȆ"""""""37bcaGߌMbLbLbLbLb.\Mh<E$n2BQ:ثڅH5<|+ɛ&p@<ӥeTT|]#rBƐzh""0A{qY/W_vy.:;XuK7*Z;Uň-G0)A p50 a./g7E7/+0 j-""" 7ΟOh42 UT-oGmƏW-/k'6 ,qU+›*zf JA5fr ^j_?a];XV3< b׻PsO}gG7*<GM1mn/]DmsF塿`6ێ ww X;(Ps!k_וbG!W!"""0y tpw@~0{OP  "+{.ݻaF Qɰ',pjw͔̋t[U@S8m6}=cF H1*z?ؖ< ل?VNOuB`bLxކˢjxUxURr..Ԫ gL g%$iT10mٍZUxrNazo@\V>(5 aat<c^#DRp칪s@-~gol%7NakGeԭ aFl=fWVmVm 0IzlߺG]Erj!Y$jd1̺ݸ<I",H9w{\JgiFCMpp} @8>u#GXPMs!AʢG:XwX[-ؾQmw]gd[$:KzjFM=d2UR.{PmXWarBѲ׽GߍMWp]jyE6okM[l}nݍ7xVBЦ'ֿs2ҴިXsw# (>&ϠIRΘYk=r9} II-|;U= V T8Ily@UNuF9޷\])*4p@ _KJ@pOg;)Y@|˷dicȵ: zݘ+!ԑ^P6G ZB.noyV _ȉ 6U{N: ˢ%9G8I7IޖωQW);eRw *~gG\OLf:E-M/XXnH&v,?c]Ύ-LO,//#+jn0L]$=fF#ҫu-dҚONuWdcZX}T+?ώk6INmwDw-3_--ʊ *)kcӦر@[O>: ?H<断 v-3R9qX]S+T,~+G+wga7qD9A$}kgoE#oւkɢX8LވX~RA QuYT-7d+!QTRWr4뵘l. DUWz/kw=V=$](5~d; \߇NQDL^,],i hTVT"k{",g7ϕ{KM s 9{e'*⧂hȫm}%`dͺղdhyN(dɺ[.ʒ+FQDU0aݱGX' ' **WHAb w, fˬGU ̘9SfQ& uZOc#E(k2A"WEK גc3?Vqzfgy/Y(y#&o 2Kw[7"eϞXI-%GgMDQKݜ.⩨ZK8ؼd۾M~*%myk⧂{ʯ׭U4]y1@% +g5(<" Dqi&MyA4Ȋ5e٬wJ{\]\V~Z[[rMhG.^IVȚäF(9P.ȪG:y}6I}6>rHj>}^端Ç9Nވ%Y\E4JK+ejQ<)3Oe?{7yOhQta9k+H%5D(3귏EH4rc(NdQ1e_gJkũ?[0}1VQw/p__S Jjtdr}6RU Q-7s5DфʘɛLc2V'i7mw˘P@q_5JOLGZQWi;l.󻺉4 y61c-rua)@2-G_(8F^ R oyMDcdt-@qvYZ*]rxQr%@QDTWg_3ʺu7^es-_[<@-?9xGh_R6!"G[|#w7nxٽPF-^ $K.rpMұh|Z?og/Ój\-{2,;1qgd߈sp:a$ƴ.sOċMX<,w-'L{W0Nx7Ⱇ>PtPOEh)0ܚFcg0W غpΘ8z119 IDATo6̵z?>k!x,]>h 1gm>1G0 t0Ƶ(tpGΕ L)_? [؀ c[xqSr*x7ƒ ?ԯ% ż`%ؖл{E.^9bW,7Ah~ƍqu( ]#UO EFCz7B- 8{_V4u@cK1-[Bc-`IU1Ex~DDDxY^GzhQGDbhiCq`,^ 1,6l܀.]Yp:dN*2ɬױ${-|_ƙ=q!fN]aa-Pڙ:@7v p6' fEAF \Q6ֿEŹ!62y Դ̫<)ޓ *GB5Ó jT3m>0QqwW3,Y*<=} qU7c5./LjPDΑ,q@w8 ׯ w+hb?ѿ/ی?B2b3/u{A-']{) J-9T6aЫiĮEKqʬB'q}ܵ.<%s3# G_Y&,zoEX\KMq@(T_mt -""*M 2ط42 M#c9^ t:KL L箨X] m؋z<' \,G9*ȹ"/Xs?zՐ qY."b#}| |Qܸ2**>F u~vR|/hQ>8V5X._  쁁ź_p6|:"t!]{u&)o*?t|wkS[q99o3.>xwGo3g^{1oALNMѯw(+PN|Srmmqz,Zs=f ;U~ܯ#ﶋ#qwOX{@RPa)g$_VDu@)e+BGzTM` x""z6SXlIf Ν9s.Sщ .ށ FpA(5k*PYꕵa0f<ALzժj9]ɰa0djzgD(6Fʭ(qn4f[<S2^6\%MTƀYmK`,+ + וֹî!᯹;W`ָ#**6ob߿rDneV PkQMҲ?zh`:4sb% \ſY@%֖:UZEp-u_[ ѯm1]iqkq[Q)>89:]FIƢ㉈R#oJ#Gc_1ztN8{4~G<ۣ'+InnMIB $9 Cl僪dIqwU]{KzB i@QDE:]AAHGi{{Kz(&C̖{̬X&V-En`',4 ;'gaOԖ_paz awCyh|65A22.ɳ<7{GmV*uBs5X?Ń|]XQ#DsU\kdl~]”p񏌈D@~B-5ҹ?&ZX7e6g9y6|z^;kw̲ci\{bWf鵎t``P}7(}GedԞtoAb#"#=CxRXt1^j%0 Ə6VZV-qr1;>ct0Dh`K?5d13`#dADL_.'fC5,S[~n%T@"k gcXNoNp,c7w3kql~7A7/_׶N7t?~h W"W1vƱ;mFf_S 3&*E d|f|;uNyl?^|r˼N~n:Yq,0hG:.9ˮ#sa0v/oDOvNe,=V˵s|!E!SJ)ҁop߸ѣeh^z=?^=E;R+iN'>`L8\͇Fr(:F~g-ع})OɛOWr_6&9-x>+~qpb%cr Sns!Np;zȍq{93^xpr$؃gzt v$sf[)~rn L;j7`y(e$Þ븛~ }pO7/p4b) ^YQe?M+Q9 W3U sLT沯WKh<`5߬EE/Ҳfa̗db^6=QcmGCٷ%rKkjv!4p+ fťt><7|3Ξf\ i^Ke E4Q{u^z9b9j!Rv#G':!%Km)8g7?l 4Z<.vBi,;SԱ6!Ojh]ȀluA)4{vF1b6(iѕf[P(*orĥIMh?sjAy?s6?͈т/uleyww&zk\93FШv~ ![AJyVJ)Nz|+^=WǍOzGNbtU3I+%Jl87!d% Bd《d\ql/s X<8qoĽh: qTP9mBLz(G+~LKz,MB9PZ[o )DgD?@@eJjsL>ipcQp[d`YYJ^"SwHDbdR=K^#ecpۯla~^S~79ps|kL啕6Ls ;p#e[%l8Ka健wϧSrʖ`aTx>cyOȢRt{8wr- &kP?Y)rFΒv.Jބ;3[RɗmnE(b)l>V 9qR͙r,"@)+n`ye»I{ƌ@޽R$ʨ԰|W;`)σsObxo㚟RK/wd`#1N%+-zq"&x'?&Im 1 <Ɖ72{PD9Jp{դ@*o Ɏ|^5Ou4/\#4֌{”*]֤env "*Qz k'r>S/521ɼdH*=OW'Nxl:-OZ ) &’SSK)J|=q/^׳q&Tys4CRLg\$J)RJhF),U?`\qO1\uRJCشg vnRJe (ԿRr(ԿӇJU|J)cQRJ)RJ)T6RJ)RJl,jUKRJ)RJoۗ.vRJ)RJ)tؔRJ)RJ)T6RJ)RJlL7J)RJ)R٘oRJ)RJ)1 (RJ)RJec.R*:#Oȉ53s"~D.|x3#XB._V{BvɌN)ݰpcE^h=aq(n"O33U>O+'4S&o?@?݀Jl)MPJ)RYLouRZu=Ӝ˗pь00eYړX7fӦ#C ĹtnuԐ2ނ;1|†a24:NxרwpӪt– g'1069588`RJ)TaSw+W&h".ju!T l& qpe_ݞp`sq-;ٵe%3vkHWr%͓7W7?4? Wjf5yohNpo:zHV$t-_ gl'ck#gm{&"I]t#6=ck=K&wt_o ܟxOX~UKw:Kb*NOR!j.ql~b^ϟeմjB]44 0gLa{;Aiv켊3k9cG/` IDAT.5ay=.5ɟڙ7MzҀ=m:Kuj=`mknr{NxDQQOr47#m= RH!A )R,yʨsT8՘!bmL}?e?mϝd](V &NphX^)RJeg7˒|kզ_m8oĹL1Ͽ@*gPrдk[LS9gT1: εұf q!X)X;.T3_ Ƨ^;ۯ1ExQlE'v@8c+Lvyԕ+/ ?N ;`!wy}Znç?$ҥ_>5=3*yQ+0@>L~*P0;$)='9F%̘^qC7٠#އasߍmC2cv['|"Z7 r%C*guj G# ,%z0zd[3$hf"w\bo Dp&:RJ)2nTbysn9w.ɓ'sT)7wJbMc;Fn'l5K%v0pvv0.ӝEt'-6r6+z7Bml]obޕfO"22xdR.&*J՚1Mc_&nA"7dzs> W\D=$::u+uҡ 0\ߑl=f;f|OYçim'bOOQ1TSB%c} G:T͓Qg;CV%*,9J)RJi#G3kwNNΜ={_'FmRjZ.+a PLY{N0 7 @ qwWmY䣺G[.w{5򛸾g2o6Ą3w#;D6qIN>Ȭ^$ueo;aaɘ#'Ҙ!V ANcq7$޸GFD`OJl=DfMx1РAirlf.%Yy ]zȱ[k_1E [=N,vgPUDpx<6 ]bcupdzɈXTnҙd%^\_PYSǂA,6a=bwF7nADLӘho/g?EN纕,nRIBrgNso+ #|̎O2=:]c>9.-Kvnݸs\xjF)RJe \qG˘ѼR{zT%==h9sFa5m_IRs TXqv*:ƪÉu;W.qȄ൫8p{AX?  +#o=6. ;.bXqfb:zbl+RIkΞxX`BU*:HA:@ɥ$(Bl^pN%'B6~Ja{HNlG{(1X|RJ)(PtY>,.A]i]`6bґ5]s~"XW/=U1Z5Ib)F#3Aٷk eߖƷ.Y؅,~iLch0g-|^1V~UNhQ6eGcp*::^O~,`Ç(*N9Q ЗW%4o֢ڢiY0{Y2k1 y~YM<rTcGϺUYطVn˵AXW/VhR`ިϙLM0|3Ξf\ i^{زTctQ|1֬Yޮ*ۜ۳?fo 0L6W&C6rc8ɦA/O_`o0<2a٦~ }pO7/p@&_Sc'МHvm{2/nf)2j̘ѕRJ)JߊWs$q㓵JDb$Xd؉ #e[%l8Ka健_Wq3\Nn8T(-W;`H':Е%QqN)o)$br/)-X#"ˠr\Zl:6H *uy9n^b,.{ K/"%K珓au$(&4T(oCL%[S@=1Q< \fݥ{61rbLs2!ǓDԹdh lܿv R͙r,[+un4~s ZGM@oYD~LKz,MBY2vStF.>!5jx_6 JsN2A888W nMyv4pF%zۦMH\ CO|#G5iJ)RJeëǷq &3~}{J1 <Ɖ72{PD9JpKW8v:^PoUӜpX3y StQr&>]v‚Op< avgr,Ey&n[ĸTR{i*v#ǃX|؄.[q"&x'?q`j*CRJ)RJGIznLBנ.~UXRKRhayFXXXFFЁe݄fSa4[Lp 7 r%D ߳SՊe $sRJ)RJe3y3fWZ'kt^&R%Kѧg3c漉fq|Xtܙqp*L㏦2_i2ߍ-.`Âoڎ1v-o̻γg_+CRJ)RJGH7}{JUfp3g̘36U77bx{$yob7q ӎKy$:tbE; U&;E]!y١RJ)RJ)>a?NNNL4R5yݷ7Ey=V1ztO])qN4]s̄o =xS7V27MTFHTƳ0%=׉،.RJ)RJ)Umm۶DGG$+p0eTv _T4QQQ""#1cfoLJ#2(NL|dt)m%00+>Ċ3>`RJ)RJLe On枒%J2R&cIg|p__6;i00!Av2W;8|,'c <H,~(RJ)R*xdf?;vR7a¯[H#7}!yJqpb%nNUH&#?Oł3s0Jv.n_7tȔRJ)RJ)zd7 # y[{k J殿oY̙&~2+4a{hoMh5"s-W&L)RJ)R*eٰ/E:yu Ƽ m@w}ѱPm `|\Қ] ʂkq9τ ;d 2Ki6|X*oZ\ؿYdez}Ϡ:pRJ)RQ`.A'h^ F\o3v:< L%h&s ("3s2=dsoyQzW]dp/Rn_S@g3VJ)RJ)9Wo &` IopWr^'ًQlv‚Op< avgr,Eyp֠RJ)RJ)Hs5&˘,K"M].=7RJ)RJ,+RJ)RJecQJ)RJ)4xRJ)RJ)iF)RJ)R*RJ)RJ)T6RJ)RJlL7J)RJ)RX7ժTImߑ.QJ)RJ)7KqfϾ}qzmG)RJ)RLM)RJ)RJecQJ)RJ)4xRJ)RJ)iF)RJ)R*RJ)RJ)T6f(RD^pI9fT>Q7 W\8gT>˖2HH_F8;.Of%%n-Dn.RJ)& 1JTt\9΁c!oʔȥT)r {O]fTR~d|;rnG0 oTx&Uzdhͨ(OKCAwf㸜7KώSCeg|;kNJ8,ie{D-{mkƍ9}ݎk!_| Tւm㥩l8K%<6nd˄xNzUN"Бt*a8+a HŏoQ<"}éRJ)4xyNB2kU9c3/=kVZĂ1vf:ucF-bU3\J%c귙Nsf\CL+i<9d'_bNͭpT>V".a(T7 ]+{>xab6c30d}|1՜m#4,9)Ӹ;?ȣ3Ci֓~#cgi4nK';5Ex88- kqοS1z_*P*o쎋ipS [ҾG?7+GR7YLv*ΐA)ooܙu+&-;ӶA'Lss۬3h苯vEVJ%K,79vt=oH~H lW4k[n %YjW![AߵY/lxc%ĹtnLw$#}F W Vm@zU)Q Kؽeq|Z;e,arhSQ[{LBzEbI*N.4/妏K]+n`K͈\QM)Njc`g]^hG8x$fϦ9?mu'4-g` FCoEF)R*Mz|+^=WǍOz2uL\vX"cd݁ !Se\Q{^,-Ws0K˞Ќ+R* Iׄ"uOȪkϸ|ږt32$O?j1HrNb`Hfl H["Vob*[GCNb`3p^Eqb6ù_;Weʖ䦊K)WC sdHSgTlĤ4i`&)y\@o̗ S.yz ~KTM\ C?QiO*^U;4 q.[%G=VvG{)`2l OtERJ@xӶS('//CÚ}AlQ dIQm KM$]B^!J╨U&=) ,H(d53.XO÷n{bo,Z2th/9Av.GAQsJa__ &""_$'D1%ʉ">v=#ޔXw#e}06Dmb\SF}WN|_ø^B ܺis#oP)-X0̻hzV 7ZY\85dÌ'<Փq;lvBf󶃜Eك|>y`%yYeH([װ~_7mn+8>N!IWƙ\ܟ&D6&nHV%OXKe^y_lRC^xUAA$ IDAT׶L=26]Ґ.y?h;^h6~k;3+/ W9 >1a\''l_$zRj+o ].&>fl&}C>` iyXQ 4˕/J7ޔfİVn=Pg'͈a L{] Rc{po+['<% R9T%Mb[Jeb/, '˹-_34E٥v~cFp/ԦKM~}r7K9=VH&Oz.Ml"\8X**{cҐN$m?| q~f\J,AJUVX,´IB)Ry3w :5)ϲNTȨX"bixa8GdX`mUyNdHy$;?{_ps!CX{[lZQtR]vjWڱXA"*L+$<>'s|0p]86; C'Ϯ8nR-=-VE#*j#s\`D*ab9Hrzj!!G%kH$5c~򿹐Gښ6w>!V zP;#\ ߣG%#rE ޻ n˭3 ݣsG8(bfTw=a#_.|$pl:E֎5_^ ˱-l{X>f6^=]j䇰l> (j6Hg33pi.}%DY>୾$_\ PKҩikQ_HMM ׮@! ,ٻkfneLiZ'Ma>u}_Tsz1cv<=C,'r4NٗR$?[$*МqqN霮D K;| Cv 2JS=ȁքB~zo)S3Gm-oѷuyfnk~Qfv {Eݯߕ bhRl eX1Pn$&VMo.q`=~]z`VŬFʉ9lr xB${ij=Ǟ7XI.3/J5Ot{r?^:oU/a)v;#C=zgBv guBpAl?bމ\Jdq |ݫ@Ӛͻb3eN4WN5pia+w/Io#"N3yJ0(^29FO?0k5*ԪZhCLl*%+ f˽rMkl7%*xS"Q\6,f3XDƐܐ4*Ԭ,\r 6cv>=;LC.oa &͂զ3k`X'l.]4 {Pe`Mw() ɋs\ B!DM޽Z}[W`k":Oo7a);1cE}MSj,ƭ22l\K3mo|ΤO _la\bnMog dž"%n tѣP;_އa1,8Y^"׾ﲧ,[uǧßMGkZ#y Ka= ka\ވ1lk ߼I}3=7T a&4k՚}pʂ\.{q`]ضX:K73O}?5[KU0`_vIjb@U? ޸t 'NlB@ŗ$^e)-la\ <=\995ٳRVsY|1 3IWlg}<@ѳ\M-V)#9'9cv8=ncU[Ҹv40c2܋%t/ibgo%K7n}7Y,tvwXD$B4yf> =-CT!z7-Jq32.7b%nt(vEf6iMbt>IّR-oAe1rOQ__=2Uv[`:a9-|;t_!耽1&)@>۟3ݫPf_ZZ|(gCfcLџvRJ7~ H]b!.TlL~hCvK ;FM(mriVX~6l7>:<[@ԎM1+ltMjrdxL&hƴ/Oi_.,`9uS/x(kn%Z#wt{wχUy\&Osih3Aa3=mPwR]nr2.=la>r +:\OTxFi;8ܻG\9 !/rI!][3]w (q3$7:,;l+IC3.`7Mg)Vݦe{-XIr]ߠj+!MsI11(e%q5*>erOQW9mk%;0cw9<\:ơ-z46Z=/фƅб-l17#>e %6i+YhTdhK2p:ۈƆ[6]0R?9uʣ#Q)R܌;76?F_ No>\aҬdpOXӰwl7aJķ.p!:6|y>Zp"V4 R@hl KXl͙ g')t/wpB!z*oҥR C:TFI/vo| z1cqwi3xˀ [ȀsiiB`JةV ""~?վov)+B Qr6$׬󗰢aW0Rx!jޔoҕE+eDZǤHJ)2qLÄaOֱ0"ncE6ʃf f(l 3T?_z-[/{#;5<Dx* ?VB΄${,9Xs֗4lO X3eb?qMCUd|vn9JК X1R㽯4In3IFcN-'z [Izě3~At-{4r5pǎtxD5/++Vp4٥l\ ^|(Sk9uYOܠҕ{!^(ԩ[ V._侩fHfG5p Xv$* =$\Tn2md{:5>~kL`vfOQ hZ*ٰ~ԯp&-bg-ش*&m7l&ΛZu[8?9 kYق+ٸ{t-syNGq=UEr'zo>mW)1Xs)WBH͈ɫq;h.+o=zcnͮ*z5vi\zL RgU."!a]'+zB[ҵ:\0l\pԲ[3ݿ :A0 (<=|94,~1Y8Dz܊R tӳE. 7*_[  T֋Nlƹ$r݂ lWӯY[;{>=WiFJ>4z+WS[RO߱£!i n+ѳeDW]Dm'+'0o[4`B wm/o9թ깦*ʔf=a9' as'DV(TvU7Qu57|kGy@Z+ޛ)?Xl*غ+|e@ <:8?e7CPqG9LFֱ;䀺Ar.*r/bIt/p'2?Je~5eG^̽rP=e*drB! NΫ;VȢIM {*~A'إ#Wa{%B=u*꿽-C#},guï׏  ;z%)\A-r?gso ,{:0_غL_qi,gQu1$Zɗʲh\0lDd:iƮD?w.o&^s/IN9P( ^*ojK*>iK4]EASzª^֫Թ]Uq'mWI;OMEK}fUNi$' M\|>^y$* -0#[ʡ}wR%A܂ו T;IcROx5s.?S?hZ֡tyzզdbZ)tǪ6xmudת[}?˨%:QZ U]NtfV|PpYn7Rfo乜ëTjQǿOP}}Ti.Ð[mZu!ݿUIcμx*p$lTe;VVuq|#iX;u2 [!i0_y]z =LO^ieQ?FNЧt &=a tzӗ"0]SyP ϝK8w&Q=ι[̇r"Cok )&*ۗr58 :7|P%K6<63p̎^.[uΟ;&4G7P[z'O\Ϣ%rLTa{8G<<\1f{yD:cvqJe]|U=RIs4]'FKR-5]6l=/M:Ί|!"<{~σ|$o^HVڄ m9ԇ K!HlW2OnNgSg{2gwB! @/B!B!aB!B!x9ySBLqf#B!B_́C2eǙՏB!B!6%B!B B!B!3L7B!B!0I!B!B<$y#B!B 3dwB̊ŕ1(+^yrbxB!/$yT$!w~ܴ>vSs?߷r)(xԟ>>;}F!B p%ϜƜ8\̼U߿GR ZXW !B!/"lJ*4{9Vԩ"4y#h^J;dkHB!B!&bŕno~lKخ]vڂgXW?9fK(B!B!xȄ@ÆYV-[/oPDɧA-;2 F߱rTt~|PopZ,\vMSQ\Fˍ g )T !B!|/|I,_<8*b+܎ǐ7X1U\ΖgbNF̝Q_La‡qEs83d<7NU?f4>מٴjwZ(Mv}2a,]ϩv.?s_H`J1{pw|#,)ذ? 2f-l)}(B\*CB!BGmS(QΕKvO*W_e䮻3eKS-G2ٕa&4SZY:͚ػ}Ӈ$QrѢg{m^5W&Ec*oҵ“zz{; Џ yì^cR nG9`{ T & wxjx1{}y6!1w֭]ɂ)_ѭ 7|OF/>#mu{_(|ߜʟGMB!Yţhs9FƄSR'vܮrwҥ ֭߰_p55VG#׮w|W*U3K5]/c@麵ޏZ5ā]uAY}xby\X8f/HiԯL̺Txy$vG\z>q?i#^\*Ly 'B!x -yӲyloYV9r˗~P̝ëF39n*j Xʰs]z/ΰ:>)$ la\bnMog dž"%n tѣP;_1,8Y^"GG9,OoOt\qK攗|I+=>#Cǰݯ1|&m(R%ЬUkW)F]GaoGs`>5B!B(^ۦRrA-^x?qcŋ!̘1W^nMr0RlӚ">}.;` ^m#2I)^ e1rOQ__ayVMȧrqtE`D`@K 4GRHpO|!iv%mi9kڣ U !2uD*J*-8"ъRi#^<ܶ--B!B(d"ĉ_ Ξ ?Ѿ]*E{?3rOۇ{G>ЗR4HsE5}>:Wc85ARfM.ZV#庾AtߡbM!ks*X28> T);}ʑmX?g/a߁<]D6Ⅲ6rʴ/'l=VdY"B!B&FVFBC3yڵiOjF^̘>m\] HDҤ0 hX=&;B`JةV ""~?վov)MzfxQ7/p):ǯ^Ê!oSyFǗM&]h_[IRFԭuL{ k&}zY - q#fwhB!B$onEDpqOwr*F߱`\sؑ.Oh/++VpԜ\?6bdzwA='n>KW3VtC:+KT6lz? =+%5w|aټˎ\m6]BUzڈ+.F=ޜnEaDOXHU!B!2$oN;uffϝC7QJW|vk&A]'h@'sEO7ߦA ˱_`~MbO˭(mqA==[B b{s8r<591Y0b,{_1ߏьyVo|׬-=w&(w\Ŧ+Y}:O{?F!BdXq>Ev񐖛ƣ0TN0KG|7 ױR NtjZ\>q&^?2C2ӪG[.…ty{"wI@2/kciTuݺD<:/cj3ROS-=h1n'_+W_qvvK"ܹezh9zlg݊,'oB!Bc9FyRc)NBuw)WlvcW^ih]-bMؑ YPKk[ N9V?nRaűS ,nPWE߬1ŝ2v\ڨmSJkƢ9WN&[6`g StS (}6hJZXzՒ69v'}ԬNOlvYB!B!gy*gT.6V/pi=pKSJ c=|JY$ e?i#B!Bd=ㅣ->L>{,B!B!YRy]\  ɲ۹`rGSV9CB!B!cHaiq՗V3zw)!B!B<yA*aɮ4;PA<3~]o~` 6;/db_!B!"KHEw۷ޙإ*B!BLU+B!B B!B!3,ɛJ*dEB!B!"iN8t(+B!B!ɐۦB!B!aB!B!xIF!B!&!B!Bg$oB!B!a@R-܌A\ʓcv#B!BB7" 9|f㚆Ι(7GXSPJCLl;6B!B!^I-6j2n1OytgCw;ӄB!B~I3=y[R`%6'rAEТL]?7aB!B!D<7Ys4)[={fr$BK8.o|͆ׄ{#8B!B!H&y3cL BɄ).8qqqa3gzXΙɛ'q`a6WOV\I|R=֛g8t6)?K1is-/?%3-KWlϭ0#4)<˗irB!Bxw ;;;O啕j?8x`ULYztO_)ev4r%NbHz:zT*ʼn)^ e1BV5?E]n%cB!B!DʖզڶmK||<@73g΢[7P(,f LLD?qq̝g$oRT?aQ_8=CeB\iOجԓ7@sE5}f}H!B!OU-ڤE&kIkW~fo4uKN4 P3d4׹Y -d@;c!\(b̄q!B!~2id^SAjh>b iXMg빓$AA[5­I;qu:&͋:Jc5'+z:BnB!B!CMyyƨY83]>Iѯ `E$z|~~!7?)4ȡa9 OT-bO˭(mqB!B!lmKG.czfY}|AqqQ}t:C;Cҡ.xDbq%]f~0T>+ԃ_ǮK24 ;ѩi9rz[Ǚh;zڲHB!BMVϙS#3wa\x\va/GXι>YʴA.Pe"ΔFgXw!MsZ~=Gf3B!B!;G M@`┩VpOr)N]EEK)wوzB‰¹B!B\=5Rym(M٧gT. !B!"ɄB!B!0I!B!B<$y#B!B B!B!3L7B!B!0I!B!B<$y#B!B KsR {2!B!B4'o:);ά~B!B!)!B!Bg$oB!B!aB!B!xIF!B!&!B!Bg!B!ŕ1(+^yrb̪nv# ng~Y-;=FqMbnޞ;׸cpvG.G!Ӧ" 9|f "(':Q}}[ru5P/"OHtݿU*џ5M 7rIӇ+ߧvf㊏(!"$yLXr[NZ}[ڝUvH?X-ZrC`@WU1kB<*7}F6sk߆7E-glx-H4}'UDb꺳ܵGtNW|G=1X:$;H؞>`|zDZIz45CNw瓯?'jn9+Y.v~/4ǂ43SnM4'k>O7]rOϿ9hN{Ӓ`vytF.LP:^eK>LM}n_6hE}^i:Vz wʌ#4m?.Ǯ_+KB 9;/7gl̐.ղ5&Eiփ!64Gм>Rd!DX82U'7AԧY켣p.ڀZRL!ܝtϾ̟[ӳ "61l旄طCyN2lOV\GiX)'k޿'h6.!hJf(guڄ^wE%ȭnrr,u߷票4$ԭ|dP&EGlxrofӲxq~g-}qvK m-UAjGa 8z 1'> K10=Jsѷ軦 Lz-3er !"C-ϫ}`)et*oEF7[f^B9b6QWRe`Sۥ*A@S%z%aǛceSuPyt2v& \!0_VތL>OW^WǮcfá0z(äeGGXǠpԄ{X*> PopZ,\vMpEZEq)4\ W WlP9{>$28m{9y&ڃy$jP# w9z&N)^U|O*G(.ޛzKR㯄fSp,J+SbԨ^4 ۸n8-Hҥɛt΋+?~*^I"B}go)YZ~[p:,~UQwʓjڽ*]|(V'U^Ml*>b+K[wK%߁c|l 4`L$‚tf#~v>ʹw׻Y VhG+{”/AEkpgkP|.T¡r֢ߴ/؎ ̹]LW8uݔX yqIt*0o%QZje։o7v@1zVͧh^k6~׻ߕ|+ ҫBFw >dyF!L/bͷvWWqbe`p{Z=DEŚ?fMN WlOkT:tʽ޷j'\^WGUr4QJaë[j~GCrSLk10Q3'/̬=X_p^hU$ml՞_ I^(4GU $AAZЫbPO,R3SBzLN݂ IDAT1̇԰Ku^t#tTnڽ_q*G WLJ-x]9&HCԒA T$XSy>R'> [>K԰ʨ%~?u(8e3cU{M9cO+K*ݿPX}TH^FboQڔT9tIk~ޣb▽ri(?3W֫/uEo7ӏTf^\QTr c4}g< {9l.ƫC~c5[S[U.o`_<bΔ0?:̔)ޗ'\T~`1쾝D hҨ zɎ-1'z0=qW瀣M;Ns6jް bl*6oƢ+YHpmX:4LlwoĘ|5oV]łicto>|WNZSs lYxܐ]SqJmxiܓ)vLslֳLԚ!N_%C&-d͖l^?WI}Кw'hQyqotdr\+7p`4ohmX\(X<|p\86Rd*Jl^ں> ?MYqX e*NarAʔ/+2.t{} /:#;V: *YB!Hf߇Ԅ%ݘx5}Y5~)5eigԪ}RJ~a5qqu;ʤFܭ~[|0Hzu,S)tm_rFMiv"\KU!=Jsm\+~RʠTs bjFiN >.ċ(*ob6Qh˝G^U(PJ *x0#&X -<+~QPy^gOo> ;oGTX ꥼu(cx`ԧe PMXN ޫy805%Gi9UyRUAuT$OQUE76UҤVΣtGS)Itf*a@a(>ߟtHBJW )r%}U!= ډv:U+x,GcB \MNwPkƳk,@wKruLó@?{^EwoIoЫz "tETDbPiRC'kH|? 7 ">r洙9;;[ՌĬdƼ3ޣn$a yl;8-ׂO*$R "(_nsIwn"kgMã KyDjfzH~ n [- Y+'V(;OM@#s!tn^By(Y =ˌUjMgGiaݻa?CvvKXaZCT (f=+#>5){ga+HJhM<^?Ҭ 1Ǩݚ; <5ɬ,k0 pVWe3V|y[6tVIɗ- {IP~uPC)pMٯ}C@;>] \l 7BB aD?;*82Uz%w4gbMP( ~1~kTZ:jeاb;de<x &MOZ\W'r=6^tiVF-gz,lK.^ů@~_AbFdb 6n: KAb*W7l߸!x`t^4ɒ]?756ϝ.'z6bPpDHqzNeK1R^UF%]+-g9}'W ԮSSA6}ЙjƔs;m9PpJ= V-bmv 9 3=&f{|Ѳ'-L8+WE߉XRr8iJ^h3N% &Ԭ)3&wu~Yz՜e'G80K+kqFw`]Ml`:ej`Z7zAl|ΚyԠnu' Wxp}P( EpO7߮ y;d.7M00 ](g2ϝh0]\ΕBؐ2"+kH߿1}_JN &e(N¾4%^!i.ۡ :,3cd&Ƀa^كXEo⿋;M=]dzTtōi9``Bb$P~> vo4Sk"638S^ xF4kނV?cuBʅZ4G{b8{#׮%bb/ׄ{$b\ɷDGk`^۱k8/@ ވn\:ᏕVe3Q_Yr{쀉|\^9y74bLX8h^x,׮'M-0 r^%xTF:Ҽw:\.~,GH:=M۸[!32MgׯDc0 $ \F7LXHL*ք䢉{ ;W/]A_N>@˺2'okUndHP( K]]ӅӏeO֣$>U̥ inK`CkP2zNh!oy 8O(v> >ʤ|*jt~)Bϱ{2.YciEܶMwoWA?L`6|{+VV4FXsj]F +8#h#&46y Zi^|-Y0 JnNlL,vѓ 3ԭW/fតvI, ̛YA?mX ?-J:^3音Ly qټSf?%޺#_n%[̚= 5=RI$-PR28yvBGOaCóXIMy:мߴNL2s O[3b 5a?4נGjUIۘ=_latRAoBc(%+Ǐ f-8vnEQ(@<ܢ+Ƚ Tތ\Yҗ3$W0gXYua=Qbn^cJ#+o^)Qq]|lX4qV>V#yF"Ƣ):qqK<_Ӭ# `Oo)\_5zEKh/Hͭ{XIl {=-$\>~<t 7x<9Y ǨD_ZZ}PzAXxg!2$6LͫfIʌfҰI- >V{<_ 0ϱf4ϥۮgpkЃ_Op+ӗgO6CB lt,?+__)B$cU'+,O 參y$>[e*_N*Mпry.o T-|6'j=1>X9##Vk9/n{v^*|ue= N*w B%T hW%(-/x?sJP;Ox#,mUNL,İܪk(WȲGҷ7C!1{L ᷉h3p6G eO(^5%ۈ52necD[Ԑg|y/6eM7%:6BTF E̿ 5zNLVf_ח䥺T*^+B`w+4ZxVyk'YxPok\2pKȑ! 5fMvJƒ1oZ"C™ݬ;ՉmilЋbW1n[ѪC]sY&XVtN؀ՌW2̺QثٟT!G8|kf>OMSiٲx1&b /u|p[$*y[%pE'ړr+Lקè8|2te?uGlh4 BP$A>c%X Lu8H@<grT]441)_t_5?fUeL 2I(_jB…xI9<1i#g eRvWdiIJ{5JV.19hb-&5iOt2^ڭc=OSw]MFwk*eD#=4Ԕ^$kΦ_C hitGD ~fIۂ9ψK Sbd߬X:]\<]vp`?/H1+K4%q/d Ak=Zv~O7)-y̚.5 F8n٬eͤ^R, X\5fM@sdmeOc7c,odLh4:/U}XS̚&ie;M!=LyeɌ[*}4Y< JڏIe,ۺɩS\c96 BPLzSAK~rWX4[z({Hݝ%)],*frTyVU9t*F /K^wX ĵG9z21V#y STbvC'ru$}(]%&#!)sϗl޹mgaOr%ɗ{w{9Ʌkh,Q> [yt>-W( "=Uy(qTyiOd~U#,BP(\+U3]S>/8B}_^ˤP( ŃO⍚?t"?KUFP( ;!X85}m4|Ϝ`:~6UFP(GMو7\#*7Tze*#zk Bx td.Xw2Yb0Em#|>ڨXP(\@oØ1L<>r>jLa) Bfw~Bs^}`'ꏁts5IqBP(½wRq0U]l@hQFo BP( BP(c\.TR%GnSQ( BP( B/rfΝ9r㜺BP( BP( ڔBP( BP( }*( BP( BPǨBP( BP( }*( BP( BPǨBP( BP( }^ P( \9{x͛'Fu)'Z{F$\8Kx%Ӵ.b%zf|ܴ{-^^r ʼn|u|`l{hҦ65eGeqVm:wne˖-Ym{-Bpc7ŊPwC7^4 "^KwHŃ)Bh孬ȞY|xK=3d۞(K`P^wܸ@֭ٹ '.`2"L%.bPBB0zK z?cTT.O\ПRm<6}`ɠ_MJGd~%d'ٵ5vqٻ-[elw=cۓu!ƲzS+oa IDAT3gNlR&3uQxܻe-?]uܚ0rkm9e'_Q%u}+jtȝGzq@@w̨G#ܰfi/4ӽ/96/{1XҺ9/E.ީ -IͦөWz*Z~шh@QefdcL\-'O4_|/{!?|g+cٿUJĬF˗U׭`Xk82Yj\U0[EsUV^Ij5(T0~I-[.j@,<ᄕc[ScgV'UV-B M8x㤽Ӫ(6\K4W6ԫJ˵(ݺٿͮt?W#3xWD..bIbX_Y?)0S#k^ћ`ƅs0||hYH x|\ [ftd`c44bX1m6'Xf]e;+Pl=~lhj{^+xGNHr>>t?M 8&j2yH8A.y!lԼג(ĭc8v3aӋڏðx eռ!TJ;67ߜfzpD}bJu]&JV(Χ&j NWŝ̟|2}ާmsjm-64\ Ȩ>kc*:u?1FT0fxh↟2eCCQNmq_8>koc2۬|OA> Th,Ex)T],N҅9y1DI7bND\"xn` 9q6ODF(h%>̡֭X[-ll#x; ʯP%iR"-vi)1ظ'yTM7JLv1\[8p2!U?rA}Go1* ”x˗y2VTl؂Fomdf3˖o5HeGLW%\inc'6osH2/"u֦i{6*21~') ='aLJ(Z΅DIڱx1Sٲ,c^\y'@0qU<)\?,%7DQ4bġ]ǹʐֈ5=#l?r}rb-Kiߩ6S69_~WZZ c3;fwKfrۏr%&%W86/N)cV/tƑ-CVn/!4YRAFI8ǞM9v SZ5Jd8hb3MkYK]Mk HgX eoU8Mׇיɨgߵp?ά_ d_s[U˩mkذ4~VoL*p'sr:DPsᱹ!-wv~ʠ9!yиK=(-| 6=:}_Lx惆Dvȵ70̄=(q(S\<.+AJVf,wS8_O'ǎD!/%.wy+bwtұA0Jo-HPJOvI$R ~K%g%yo4"Zs47)5XRj ʏ;껲zӥoEDyȄQJ^n]] Eַ*IsFn\?/H1 WfbңS2qW\Kٯ6"ib D(,̖C $&y4ĭo8L>fد֯I1xĭHzW|zYtǕs$jPِsP2哧ˉ!r-M2=ʵ|f/:oVqI7 2h>3ȧNw*-;sKnĞ$%/T Cj5_-j.huǔ5O) w<}_LfЫ\;rS7dՎҹcf5ʜc{yF<]_g&n4tdޙk}KD$lX;Hk=A(c eD72vɇRMc@YQ*:G:Đj[r$DMDDdZib,]8l-&sdEfeuo6nnRkT&w⍈]CeTY-Kf_Ɂ=ZV"^b,}alYH~Ah'>.y~A-%n&'? ?~$=j[-\/dtsRք`*->"S~'n:I_.C2`"r蛉Ti4H ~S,]$s& !nGا?uأdnP1i֐̔6lOzH<A3K^ɅT "qqo@̕JBQeɟ?vCC4Rn<2U|TK$ ='.)\G5N/ O}V R"L$ksPbJ<#,YXN&M-u>q-9{:+ vĻ3ikD9Q4/"Hu\Y SN>AfG-](sQijHْvu_lWkVD3iQK #,ZyiP$xPv.d0˨gf4c,;~meR(#*')ȲKdiyileȚT$U|v`.t3ʅiOfȥ9+⍈$/8CN](#zK"_y@sixwx~:򫱲~:޷|X9vW_!]%Oq1i)79r_fM AeQ mdDs v0ͷLM5KX?Tʚ4Ѽ䃝nm^&K]|r@)rxxi@8ʯPd{yScW$up3qkA)TcdcpI+:2ʧqH1qo*oU4b77̈́gʣiSt9qI_)`@ y@7emcuk5hhTIy9Թ%ܲ;䖜=}$@C421XDȧ RVp8o/OyYj~O "eDeWGFE:c3#[荙܊w o㱲7nxS*5P@z.}^F~|aiFO?fqޙk}.n'~6qOTQ4j̸ٟd̡$9Mxi5lhdzOd> x&i6qf(/T5#1+1Lg8W[@Ur|QoB(%5}s}R6&h6iA '/:=F|ZÚPSIݎDΜqGA Η4{^̈݁5j[ >ѫD0ibħv=*Ap2,ً;֗_aޖD3UҾ;KuOd^:}⩽hrn3~6+Q(O%봡c W3[чgGiaݻOEꐝVq0rbֳ22|f$󳂁z\[s::1}u<2yG?Dc ƤIjʎ-6Sɞ[w&{s6Ʋgzm㉻2;œ~kTZ:jeڵ7sj s={wkN 'Yb9O>Teu#[tNǹk|ˬl1_y2mw;gB"8Tl'ظ 6LThX@gYXzu1n=I[aL[)3&wuW\K0ƥk7:)ͳ9\Oqqŧt(lr ;FJ kð,Ƿr؀PLiS;@y+U!4|I H_I >,ى;Aw0eS6łj%聧qgi',92m1C0QfU'9H6mHvdɮc4(}^+2ײot"LT~ ͸E+0_Voשl\˒}u/.'xiqsY7u9ݻy<8u3fΠ]ۧHĝ_lwg޴d|w/ɇxkx/מtO8}z*Ӝo]x"v3l(NG%/_p>2-?t9e;KW]Dܛ Ii&5bL[-3lE ܓ*GqSlc|`4||i0ܖ%;q8AYD~s{pr,IiEY^k+MxQNkXK"9}*o5a?2y >ZyzGlҮfX| *4{#j"r:n ?VZMDG[8lY*H];?xqssԩL>nOJ9]I3O! ɓy:ހ"oMïb}ur5I"1)y#k&A(/ݒ$l6gClW~".g *i߹I$&%dw f*XIJ[jR˪<و,֙R^o4_lTo-J$fÉ|1} qY>T Sp: W71is6(wA 9` y *ְwP޾k1lDVMX͉7eLiٲԭGv㖶w3ۣص<6 *p9B<ֳ~e_I f:{hdHͿo/c0ylGƿv_|>ǻd@-6LYӟGwsOTHRR~ߦ ?}HIL9Hl|Ezn$ίlxxgP2a|Nv_Pג/}LY?!9'd{_赱Nci2$ś?P91ḹs1:vDUx"VNI!9 Єy;5o|}4LQ_慷؉N@άpiU^lae)ִtHͭbc_I9W:ݺrP"k?Ǹw᧥\IEIeY-aع ߞ=Ċǰ(l Ceլ42VqFфGLeiT?mVҼZVm`(ΣRzN.{yi͛Mيќk=:y|9$1 |S:ğce,]~x鱋m aJ#+o^)C"77ל&nJj5oErZ7[^Y/ңmP *; 4zۣ׾̟NTNcvih:C r67".dPy3regLzu[\.Or*$Hq64L}}nӟOc0保sN99;ؗ#_U1RKwD-W -r%azWPM>G2[3u5g_6;ܪ=:tpoEed'Ur*coxNT^^ ¥_#H1 Τ*}Ʊ71-? Wcis6rk]L`5( '/e_:/}t-M)AI\Lavέ\L#(վّ_qkY.C_ ӠaZgΚek#593GWzzr\u;` w-5 IDAT$ YYdD~ԑN/ ׾fK|旒KlͫR@6-mŜ7=;i/KD?{ȶ^sm_O>ي@1VG;vb7:f3U{z<"c6H_pkfJ+064b-\s8XXcA3W] l=2Qm9zӘ%#[H)Mܥt߿5e%Ȁh)תJF0H`ӱ3!)}i]/@ ȇ #?!#>>!FTQfIU2Q ~ʋ/[F+zȖĄY9$6'G$C$˥!`^R/>o&D3哚Ͻ)N"ƍO?3?//AW4RɹϳoGs9?_ 1*nsML? ьNwdWoRG\u9I=#%=4D3HڏJn}_~ҧ[GiU4MA edeqLS:uc=(1 K(kfOEvBj.  E,GJe A3Ѡ{rA|ћS^A!zWHw7 K d`Ɇ=rDsKƞ~e|_ W iMyd mBYB}E 1 ʚAѲbHͳ 7n|0./Mr+vcӹ-47b- |!r[+FL|0Q) Y/R)-a3];mt;fquIoeo79ZO_ ]ˈ< ƒ:I߉|)9LJo,^ (iqM9ꏕ#&gvFD*7dvs3j!|uɱqݾ씋_q-5xs5ąA [*b?<) psޙݐ5W7M7 ߓN󋻖ACK/VI#f yKHQNJ|zW(\|fL䭭ΝJJ벩c A󐂵'KO+NFĥʂ^2@k\AӲfRCu-ߐ?%uY^#oNK&kȞ&cݚJ@[3OHMyIlFGɬxs|j3t}-Dѳ:Yj| }B6Б3_5Y< JڏIe|Wל/lN/1bL-#}I>d͹m籙-4c- K; yzj}y7Gai,99UɊ]MclYFFDWwZH%"[ k يO5)F^:5kcJxHjd#ngjy[uei|]ee1ki븤ߟ'0c1+s(tS\ڹA(~q˾ވn%N&^7*Xc1cC3=C1uOëa׫p[#[^ZRd:~e7c1c#GWG=:څxBzGm:c7͏X( c1cGAőQc '6;4 >>$VZq?c1ce,W"|2߽;u/p1c1X't:u,f1c1+8xc1c1ViPENla1c1{9sENla1c1{mS1c1coc1c1J10c1cba1c1+8xc1c1VJ:1V6(s2)J]+1Ewddqg ^ 9* 鹐;F(䰒Pf^^&)|jc-}E~˭2Ye[y@y-,;o7JGܹS6HVt{i.BNJ{@n$4PLDDnI,P|ySel2&󫋐o 9?a߰vBoX:8.,wUiS\UτhEe)wQ>\BKS,3urQj]U20YmEqo-E#\Bn)y-,9;oܹ@.yTN:o:0شk~?֦MOD-*W;Y{qQ-Cpv:eNrєtp~/`R#[ 7ΰwc\iƬ8 v@Gx=NK77 ؖQ%NIQfYc\B&B#@hacb)b^Z*&E)Q/}AQD"8|0&}5.vF ШA#֡CpT^g§~40G렢˰P¬h+~+J z{+Cڐ/\^@VХTK#8Vn!E x8HJK`p>W mw"valm +B)8\?qt~;?}eO ̯uNߺ+pd fve+D'^ yo5m{Ej%S@LQږ \ 9 rA=E6Q{=}~°[X9쬞4_V<ʂ 1AXj9X9#C`@[=NK:%>=G!/ސ\W'i@Y8e,88ј4iƴK +0{36iZ2T X|SYzWU9l[< _؀K{ˋ2Ͳ,e!|Ek;)`RE)Pr!C1Ml*T=8[9q<8x}pܵݻu_s .UIˡrp/ dގ{J5j±UӇ!&>{xUmBQѹJn%׻Lƾ0lL]6|QAp E2}画G$->KO_%.*N.9hWO$P=D]r@6ڎ:@RєMS^:U niݯbr@ t,!"MD&Ayɑ4W rO]L4i~НY}]M9ʂ嚿_lWxl A-{bꗈ(4I[{Qz,U#M?Eeۯ`O:L-qzy ejVX.>]B9TpS{ omEQty"Ss,wQZڤ0ܶr1MY jf#ۈ?M-\8fo*/I{Ѫ|m*Sd:̣+Sm*Pĵ. v;EGvҊoPM' PgHgOۆW%A oK1wڴ}m^>5.GA jchCЭUd.AL}/%Kџ'MbϘ4Cs}A?xeUzvl(M_7g'mX8:!5Im"m|7d@rhuX4:D %ANla-MC[QH߻߿^JvHoIL<+i뿻hOkP^Z:-#+bp+2ߦADFUNSmݑ(߿hmW&$:cpK3er$"lrh( ZxQYJB/YBKc 92I!*SҾс$ڑl$0L%9$=x;eQC7E9c6wh=~]Rvi+=.Oir}a Ӿ^o|MEOW*<[[,D/)zv$@ ǚoҴ?щSQ4C 5hE ot}_hݶݴwڰERA _QtkR7 ejVD= TnO9Oa4Ms?f(j##94X5p-]N62ԩ!bSs,FwQ~9Ns*uI7Q@;uh!cBJ ;-/bxR՗@hbۿqDlRo;B?͛EGK/Of͛מp<.]h!"m 3 HF'TS.ij+-;FNu 6!GΫ[Q$8w%G'Pu@C3vNYjSҘR`KMg\yɣ5ڀ`ӊbĦ1siSg{>W$T.'T[}c4=מ~H A^EjwR ISN>;I{""KA ALcu|֦=sr1o "ՉI.uH :JE~j DG_ dJ}ϡu.Pbqcز!UTN-/ah<5|% yi })dޥAl,p= +35w8H@rpNͱviEN=>@L}X\-Ɩ=(H MK}O\j7M .kRVwpoњIwQi];rjJStwzƾi.kIfł$#=q7"۵4k3/C"f>5bb}1o[ZJ^ ͍Tq+u_) ~)>$Ht]5fۍO*Ķ1qĴka_TFuij׼t :y} N5BPZ w'@.AͰqz D#vBfЬȿ6#CP]XR$5@}+6gTr.ZK*LiVG^&`D}9(ngsq|ퟸ::7 mWcĨ诀%d~3_zWh8I*cQ(x+c-ܼw;8n=4ؖ.ڲ{1Ŧg.x>iM | k<#NMZ;$-E|>Ɩؽ c:5@P]I2Z2NT ~.}(9USLRg׊+$>3i$B &ap9Mޝ'^iz(ͼukcEF ]GcPUA {IiB,00zEL[ oڕ6bbᒥfN447 mgIh3x/na}uӪKGףqzqﳅW4--:ǡZ )F[Љiqh CH7KѢ/p);M< 7,؂69~+' 82)Z4pC+ܜ ;9' NRTm >hI]ĝ9B.,8t@.թ -9'i1__.:[+n }Q/OBRAV#Gj{FQ: D_mى=7vFa˷gaM ,&L]'q<_s@xgTMy&DJN|{6g.gy#k媻zW ψqvm bKQY[bbq| Pq}=cݺ!(5ʚP$ŲzE$3LZ |ԮĶ1܀ t{Kyhެ_p[ق>666NN-:9o qn5߇߭gs* [D$ \V!+!Nhm"~ %MKl_}eTg`2ȶ-/8I>nO]M"$i-Y _Hq{Qpr  N.΅)t* qaD߅mF!vlȘ1SBm$'wn8qZAlw y=tm4Q ct_B#~玺c,8߽@i>^G)A.npP +zw[ 1}ZKyg}<ֽ?"PD9BN'hT8Jڠ}ҽ'^iZ~ M}} 0nB?w6//Hpڇ)xLn(ͼ ujkKcEѦ-9KZ*5389J!JKC6b0yEԗ%+bygZ sڿ)JlX<&JW)7Ν?ތq-nߎUЫWoԩcM{w5/!\p4ʅ27դVB0P{m{\(y{~r43f&}^5LPdkgcr(<-4 ER06=f+E|(ew9g24l*:Vr9$1gU4< @ѷ~8?O`.-aZ#,&L!A";ᚦjjOcTHABK)qRKI  A,WcpU 4+/KWD3η)Jlɍ+fU1lNFL ˗/a0y֭mF_ԫ[g:|P>  ;&bmeh%Eg'ͧ~Ƶ-2e"Z \|M?cf΁b6')BvV6vrntp*MW"ElEAJ6sdAZ-سnb}VL;y5sGaXF@Vqb$LFq9-j:ڴ^TC8xZI`8:cqڂMQ*@pDvMa G-S&:?"W]9  栬 dj ,d^h3,(' Ş][ǚ-8uf%>|%6`x% ^g׌,?R@a9Jhʈ4f^:-4Y ɘ(rVrҐ2CC*ø]31FS<£7Δ@;0Z|z=|m#k`ЯEfsB|c ;ߛ ,A%4vO_j474m`X[{HYET A7RZ+Hhw-E4vh(UP+5)bĦ1s#\Y&- zܧԈg-3P#_)3ck-h GE*'iƒ8+2Jܻ‚}?ξ"H232–DG>Q8pY<^ؠaXs8iby('h]K+Kk8Cxy2sjib#~%>λ'*1>_H#nǷ1n_הŢuW>MejfXNnvyu k6iQ̧ib٘s[xLU}S/a͸_l]NA Uf\1fOICD]-]ۥ7x ݿ]PWsLx\_.oEÆJ:YE 3#\U߿&Sp\]ؤçbACFȠ]wnN}qJ|ѢU0d־=TժqPm=dvN 6 -Ņ"{@r~̑}{.BU몏^\X'P:HA>"+1"6b JGZ޾zmV]ŠAIȈAU>;b>3s(JX5 آrpVoSu +#w hٯ95C#DFH!O[jr~y?!A#8Ք/ɳ̪ ͏nmWHc'W~كkӑ rߴ m;hq?~^0MejfXNQ8p$ 8ZHQAUEQLy(bҴf1DZujB5.=Bŕ5o/5vńN"1 uxof%Z-<@ok>QwFJ'oE`ͺ@4ndW ^8y3C`&.:68pGH{h"@}q>>uK 1hS3?9R A3_0y| nw]h=^׾*){)"MgP\O?L29d=M߃Ħ1r "`4-28 W(s98?*Vk )Yqׯĕ|$ P̡yRƶo燁䬢EƎBm[ x{KSOT V[ i֝A# ,HODjRd Fހơ+ 3VUXO{4DqplPBFoͫDGtڣu.cd@;;Tw MZƠᇓ/8rbph5*C27Ŗc@0m6uNL, kxyJ6iĬKӚ{~-o;<T'48M`nh2O*F1Vg6ՆcOdvPk>Q{FJ琙9tyEXxQc(` ٺz~Twb$ihm?@JlZݏ<2j$7Od{%[ړAp賙sӔ@l)xVJ:;yJ@ĕjtJ&׃ꔓ yEQ_HHV^ iW5GV^JբOAz A}y~PQ!$K=PAg8}4_BxҐߐCg' HyQh毋ioWk$+ЛoZGѾBS?ʑ)U4oyy=AV<ϙܯ/,NB1HC '$H/hּ4m` L6ѺS' 8Q?]_"]Y&U r7ܤ3yw85ۇ4Ln2髈;L,UTN-/L"S"Ӽ0[$ t !D ۶N/b>YU!W[ y?@Mut;*Y̨Ų}O|"s% @Ce0t}ݷ4yD7 qS~SR}|qBl }5c͝;MN]kDȶJ{_b}͈|c?r@oл-ȥz7yh4TA r4l+4ņ7^O;4]F̋^7wbyP"eߡ\ޘcGGë̏z2 iA c4[2W0:ړ^ԂBABB?q*.3YsLoيPFsij>k8x=]\„? CJP=[Ek%th|-5"uNNq; Bx<8oIDLzɞ'ıuh](嵿Ö#ı2uf?SD2*}tcTAxz}_[l3?LNg7*"LZ=A#&oӌ= 0 ԯ(8 umԞ턧$T1|"}SIDt6-ϻhWGs݈ң 7ﶣ6O'%Oҡ"ʦ[㩚.J}קJQo+"ٸ} Uk tIEĶUYDDCC.w7.OCd'E["5ZVp"i4B sE 6%=.R7 օ2t_3׼FvٴEI4wMr;]S SǦNfެ5iZ~G.' AbO~ޠ\zo><. &w3{kؓ.C jDXͷic&#W.B^k ^3_Ej %=BB WBE_'#zm6Qxj>˹H%=ޗ>j ǜ}pW'!^A\%;±\T-ϐ(}SlZ˕ȖBtiMJmFNMf>R?iȵu=}Vu|}_y)!ػr05ځyaiHɭG'0c1+s(tS\ڹA(~q˾ވn%NVrz)URc1c9ټ菩{Rt^u^ :BגJ%+I/Ro1c1+!бzY~ zé]'7pt& 3MҋF8xc1c|sq$vԘ/o‰  zϧO>^6c1ce,W"|2߽;u/0i#a1c1V_.%Vz6,f1c1+8xc1c1ViP5c1c109xs9k1c1cM1c1cba1c1+8xc1c1Vq1c1c c1c1X)&+0#)l>tzc1c1\pXHQRGz VU'řhc1ccڢ'H`S|?P31c1cqdRvC 9pYy[gîMb5x23c1c1)3QUW3&Sv 4)јX?{ ~[[ Cyހc1c1LfU8e<4j5J%  %   %gukY!xS;TVw"}؞4#B=pf*!!!~/x!Gzax>| 1ëb4o9b1c1ʪ2R*?ФcwnGwiM4 !h1hS_;IwU]_F"m2"6J,@R\B1c1+LfႅX`aI'hڴTiHlŨ 9(\IԾieǍX?+"or}Y/%1c1Yn֭[g{ ~W଩ϼUC?ۦC] q@-+^c9?ß#G6G`v;iԢ3^i0wSȷP1c1ces߿?_ n`Uhۮ ڴmf͛AUUVF`xzy r9Z Y& va&4w!ZZ넹8j5.N0[( 6#AvC1c1+eDYdoP}IP@7Pr:ʤtZ7> r=zH8# X;*F'@BZLi0Z4T?1)c1c 9| 3gEXpZg:شx[wS!|4L89q &"ҼRH -w@ S1c1XXܿ`ŊOm-U+W-sgM:WZ1d@a6h7t dǔfasAe+nr%R+P83 ib1c1ƘEȯM`TVZVO RP`ݺ?1>;:G^XV-6g'ͧ~1c1c* 76hDJ1~D3o sn;smA7+trN VA'HIcf IDAT*,*A2Ԉ5 @>c1c1Vz,ZIIL[tMWB_KV @ch!YSH:IFȠ]&-FġG|c1c1VMY!x{KP1@:o5QUhS`SDkkG{!ヌv.YY9bѦf(&V9c1c=w%vԋKf۵o,~B[W!}0 ;%}[;!4DZ5Z5 Kq$h~mۊO۠޾ߩlsp>s/e xLn?c1c5i% m2-詽pbv8&>Z ?Ƣylc%tOtœ@J(޷>~I1}; ?͘1c1+s!3 bXd)FNB #*z\[OTY ŦEVR,KFn~U\l9hc1c)^Cx )1R8q%?SGh[ݒc1c,f1c1+8xc1c1Vq1c1c c1c1X)c1cR71c1coc1c1J17 ճȉOD0c1cLޜ9w"'0c1c)c1cR71c1coc1c1J10c1cba1c1+d%cL!>ɜ ΣLEbJ&'xZ4$=a2}l%EwNc1F;JP O.J2xTJnQʄ zg?FpGU^f,!'"NFd+4ڹÿj6 ENVݶ> UܪpL4zn$4cdc@qOp?=R{WxFƨ?,V(w}"[dMcGNj#(e&-9-:g8JY 8u^, ֦>| ttӱt[:;N&-++-oL>uM8 c1ƌ X&zֵTMW9/OցZ 03Uߺbfd}".e#9>Ц'Z^+ݤ[B՘:,{Z*AV1s! L*F*PiR۷_p-])\>)"VMIK Q&Hghh8Yjߋob~&|/Ư5>X{VԳ{HqO*d ؖQ%N:Ie%Eŷbe_w 1L_o7Gl+v]5QVM&2y's$Bx+2c(j\wW"q%CYQCNSq,X *-ڃk_1e<؅-%ĶZyE|ޞ/Bs'{oNm꣒ d9{ v;va`ׯ;rH PoO;ٕ 8@k@8*q~*,m_q|VMhzG,5ĸߠӃ_: I䆺ojpv2,ovbQT._TqOH -*< 9i*b\Ñ spd/a ,Ŕ-¨1LzKKc19d&yECg1,^b'](K)KywOX4CB-u_^$Ckۓ Hɻ:i0ƊQL-Hy9 u%wI 6h inv$@Bg,oSJ#ې\,[&lbfr$>_&~d(GoC!lkѸt{ǤX1Gh)7C\Z;f_sr@CGZ`Jʦ#j\ۛ(%}$n~IR܀>(3V?KׇB>$ϯ9e{%@t,c1f5/Λk7ê!=g\"vE.JًaAǎ~1]h2q1'#CcuмM(*:F2q;*)8W)>:UI` R\ѸrrxЖPR$!DbnCRTk >IH;kIGT.2qB4Ha[&<F$Hq6w̘1 gEZWƙxH _g^ťKw xW RNA8Au3\EZpL[n%"Wuel(r~&䥹B{l;or3ݸX\=)4SBi6)xمͣ8 ;Y$@J>0Ri]o' jʬıJCwVFd!i3[V>% ٶMן<c?^SZ`5D}c;xDѫE446W髣&J^pmm&.E|_Ei88#^W?m v;#;bjoT^ ]cY(@pGס} "u lN*R@ i.ޮnb( G9gc8uli:ކݓq^p Ϋ)`ݶػg6,;i8 r<g@uY0N_%KcZ*(j~ Ц`Y8o0@V;Tq4,T:/ȐFG7F ^#G.ջQ2>?D{ ;6m߁˧cP#';4};{^Eq[PCB HQª *uU\;bYw+(+Az $@HHB~3s;3o99KXKWAI~F7շ+oi,ghEG z_]R@\)RKXv_w)[ߨ-C:ffM|> XOr{/~94{]q+y vjXaJiE[w"8ԝi߯CvS^Mv*yC<)26T6;i{.\YT管UrnSt}eQ^ݣkROR;5+hB[Z|XTϮ+F[+9nvRnEt{fS 5iVI94ŗ4#?L7O]}'NMn5=:<+<0FԴ!~gE@:g>Ľ_6xfev?aT ʶ7W"hJh^wo4V78toIp,YyHtPGvPCkb>R|U;>Z3m*V)cFoѐQ~QZ =oU*k?v-;A OܵK( u󛫟RS!ŝ^O5]uNLmZDy0Q7+5x?5zEQ/:=BOeD uR!Dkx+ZΪrWɻnpVʔ-kNz󄨲Qw{yth[(}( {vjҸS7wԘIuh6C >ToRe*RzfVM5b\5W9pWRz%*pT4Mn]ߧ<)thwCn[q%'{IP罫T!ȭNmoRװոu{W=ytҭG>9r \N^=t&sVFC{ %%W$9h'ܬcٟY5fnڤ~Geu֒k.Yrmڢ̳%bTO:˪ ?Z!h`ϝ %''kՂ4JaTfъWk;]lYwlКM5~@+. .՗3{vV҂]dmRXbj%9ک} 9 8^v9њ|:Q쭧ZRn\Nǚ+c3=v9nPR^fMRDn3`2MvȻFnX*Z7ON{R2X?H듕{xlx[UGJ:%b*zxeڮ)8)GHѠ.JYLq&W&wrOn:Jʯm_r%oWrz9Ӗ鷤D+XΦ3-+~y֛qpaO)#w$5V_leoUrRMjGXSWŢ B@(˧sꞽ{ n*?N}4oLez-imӱfܯK+$2mwZTCu%3yЉuצrˢzQVRbkdٔ[)ꊈ-=zQ%NERgIs?7[,MݢC'R߸T3H!-Dʩ*ũm~YIk6w'_Ie]NoVO@k֦ݤfUJʒlJ( ܇qQeUhxFdXmVrq;LK.c P!)7 S{|^-ۣ*>暼p SySV "^X_zX/Nᯫ.!ܔHjɮ>?|<(/N`dqzVݫg&Vî7`u+]O݂%#{e+MGݒQYg XUF5Y&ϱ4HU 7ڶۨa-KRrvMJ^^f WmsquozV5Olsz OHZA_Sjpc˯įR,;+"|}23+S|.Jriٴɚh6ا\9ݝՆOJ28w_.-Bz++vY?~Qs'n/: q<8)G鬇bYY PHry$⊖Ng# ާ+eGΓ'|}bQh }J oqwܩ >peމҸpz&SdDj^{:iEVnҞE>>X^~7E 7ieӇ_#GaQoڪVUQ3EKSdtj^\6>D RPZ5O}%t4 \qixxuFwjU˸xǟ{*#+Z1mRLtb|%92Z6)6n]*N1^4n(JRޛ]fӒ5;t,RVVMz蹡=zknVLt*CY׼9O7_˹m_E?[.K"нV\ ۭwQzώamq*ew`7,܏#-J1чWІSӦJk#mjײ>>#'J1us*&:Jۏl>ߗS:kR^)OU"{yUnoۭ9w=uZԫ@_/l6Y.76)/qrB.<8-(6E^z~{=-?CRѨ[CJ u`C DJjE 8>ZqR7nnmwP(5tWEg|WyMk_EE(ͺ16 }VVIaUo@5Qp:N5>oLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLoLl,Kw|.EkF?-ԓ02sT+oK˷ ,yk|mطWg._N(j۱;Oݨ_жrTZ.g]i۔Wp)Jpn׺]UDDm3cڱv{T'"Bu|=:N-լUx27yVʧV sn׎)#m.Y{*SaJh^gÓSkv*y <ڽlKQO[έT _FxರTVCrXd=gȓXo?e.k;G'>{cx@@3T}뺵a|qG:ߥ϶Zd -E(cP]{t[U+Og?9STgr^ui7חTջFɪO;i-ƷQiR3r+IOz ڢf3Rêj[ڶ&J琦 j+=ӺS3Uqm]~cG7O{) XI=WGӑ4k4=3Cu l#<-s>h;H ώTg"UcrbwJFE~nG?7o}Du -{[OC߶9{95_zߚ ]műCtó-T|@i9o2Gn>CK}q巿,ߩ6J#&hBA=tzQsmrg--R~;Z%ިqj_19H7u%EIΤqQu ?=26:n5SӞ/.I~/cGu2ߪQ7q/{c_5I6>GMneo٠.7Z6a;3Z1\!/=)onլ+TrxLфc 6Z3ݯS_ZyZwJq*7E>+izu'"Mv׫Ͽ;wvq]W)37SQ[N$SǶ>V:/h>bRZjQtgNr>5lZ_ŵ[<(lj޺5+ʪј~稰'Uꥇ&UP6qE]R-m%ک+UMM,yj$˲R֬1OjivJX ^UU )'[9(ټLK]?Nt/CYy9:7M|:S]OQxv`RjfGq:.zA;dQpyN\\)!m.rIv:L%VY%< *RVY !nc K5 ߪm-4CӞz{ W7{k2%ߺju}/imUne 7~ד<$Cv]^lԫN9=r;#&ܥ:qV%ɐWf*q)xcoeEnD>>_s~?5$OK^{XɒѽOv9A||5|qo IneefܪyMѾjqDLi/Kr<.eG9 EExr]sfG.ٕW&>^єeOjjR<ǑG Miz8T_E!:W\`ʢHe(q: FJ;S6(7RvYڽk\2Pu*[ʄeQꜙ!Xa MdDF55k=薻HdTSXUs'.  |m#4mpeDAC0}5cNButw-.5Gi{;ȏ CVj6Wtk5UEoܧ^$7<}Us_h+泧4x0Ϳj[|;G-X͙ޙ%I, uh{]|V^vmkD+ߋ~Ip=1K,M&{7RgdC)7ȸyںal0{cF<mNՒ:/멗o kohh --help` to see the arguments to any of the commands. ## Solaar settings Solaar supports at least the following settings: Setting | Description ---------------------------------|------------ Hand Detection | Turn on backlight when your hands hover over the keyboard Scroll Wheel Smooth Scrolling | Higher-speed vertical scrolling Side Scrolling | When off, side scrolling sends custom button events Scroll Wheel High Resolution | Higher-speed vertical scrolling Scroll Wheel HID++ Scrolling | When on, vertical scrolling uses HID++ events Scroll Wheel Direction | Reverse direction of vertical scrolling Scroll Wheel Resolution | Higher-speed vertical scrolling (use with caution) Scroll Wheel Rachet | Shift wheel ratchet on and off based on wheel speed Thumb Wheel HID++ Scrolling | When on, thumb-wheel scrolling uses HID++ events Thumb Wheel Direction | Reverse direction of thumb-wheel scrolling Sensitivity (DPI) | Mouse movement sensitivity Sensitivity (Pointer Speed) | Mouse movement sensitivity Backlight | Turn on backlight Swap Fx function | Change Fn keys to normally do their special action DPI Sliding Adjustment | Change Sensitivity (DPI) by holding a button and moving the mouse Mouse Gestures | Create HID++ events by holding a button and moving the mouse Key/Button Actions | Change what a key or button does Key/Button Diversion | Divert keys and buttons to create HID++ events Divert crown events | Divert crown actions to create HID++ events Divert G Keys | Divert G keys to create HID++ events Disable keys | Disable one or more keys Set OS | Change keys to match OS Change Host | Connect to a different host Gestures | Turn on and off various (mostly touchpad) gestures Gesture params | Modify parameters for gestures HID++ events are mostly not processed by Linux input drivers. Settings that involve sending HID++ events exist so that these events can be processed by Solaar rules instead of by Linux. Different Logitech devices may implement the same functionality in different ways, thus the different settings that do the same thing. Solaar-1.1.20/docs/usb.ids.txt000066400000000000000000000276231522022367600160730ustar00rootroot00000000000000# # List of USB ID's # # Maintained by Stephen J. Gowdy # If you have any new entries, please submit them via # http://www.linux-usb.org/usb-ids.html # or send entries as patches (diff -u old new) in the # body of your email (a bot will attempt to deal with it). # The latest version can be obtained from # http://www.linux-usb.org/usb.ids # # Version: 2019.11.05 # Date: 2019-11-05 20:34:06 # # Vendors, devices and interfaces. Please keep sorted. # Syntax: # vendor vendor_name # device device_name <-- single tab # interface interface_name <-- two tabs 046d Logitech, Inc. 0082 Acer Aspire 5672 Webcam 0200 WingMan Extreme Joystick 0203 M2452 Keyboard 0242 Chillstream for Xbox 360 0301 M4848 Mouse 0401 HP PageScan 0402 NEC PageScan 040f Logitech/Storm PageScan 0430 Mic (Cordless) 0801 QuickCam Home 0802 Webcam C200 0804 Webcam C250 0805 Webcam C300 0807 Webcam B500 0808 Webcam C600 0809 Webcam Pro 9000 080a Portable Webcam C905 080f Webcam C120 0810 QuickCam Pro 0819 Webcam C210 081a Webcam C260 081b Webcam C310 081d HD Webcam C510 0820 QuickCam VC 0821 HD Webcam C910 0823 HD Webcam B910 0825 Webcam C270 0826 HD Webcam C525 0828 HD Webcam B990 082b Webcam C170 082c HD Webcam C615 082d HD Pro Webcam C920 0830 QuickClip 0836 B525 HD Webcam 0837 BCC950 ConferenceCam 0840 QuickCam Express 0843 Webcam C930e 0850 QuickCam Web 085c C922 Pro Stream Webcam 0870 QuickCam Express 0890 QuickCam Traveler 0892 OrbiCam 0894 CrystalCam 0895 QuickCam for Dell Notebooks 0896 OrbiCam 0897 QuickCam for Dell Notebooks 0899 QuickCam for Dell Notebooks 089d QuickCam E2500 series 08a0 QuickCam IM 08a1 QuickCam IM with sound 08a2 Labtec Webcam Pro 08a3 QuickCam QuickCam Chat 08a6 QuickCam IM 08a7 QuickCam Image 08a9 Notebook Deluxe 08aa Labtec Notebooks 08ac QuickCam Cool 08ad QuickCam Communicate STX 08ae QuickCam for Notebooks 08af QuickCam Easy/Cool 08b0 QuickCam 3000 Pro [pwc] 08b1 QuickCam Notebook Pro 08b2 QuickCam Pro 4000 08b3 QuickCam Zoom 08b4 QuickCam Zoom 08b5 QuickCam Sphere 08b9 QuickCam IM 08bd Microphone (Pro 4000) 08c0 QuickCam Pro 3000 08c1 QuickCam Fusion 08c2 QuickCam PTZ 08c3 Camera (Notebooks Pro) 08c5 QuickCam Pro 5000 08c6 QuickCam for DELL Notebooks 08c7 QuickCam OEM Cisco VT Camera II 08c9 QuickCam Ultra Vision 08ca Mic (Fusion) 08cb Mic (Notebooks Pro) 08cc Mic (PTZ) 08ce QuickCam Pro 5000 08cf QuickCam UpdateMe 08d0 QuickCam Express 08d7 QuickCam Communicate STX 08d8 QuickCam for Notebook Deluxe 08d9 QuickCam IM/Connect 08da QuickCam Messenger 08dd QuickCam for Notebooks 08e0 QuickCam Express 08e1 Labtec Webcam 08f0 QuickCam Messenger 08f1 QuickCam Express 08f2 Microphone (Messenger) 08f3 QuickCam Express 08f4 Labtec Webcam 08f5 QuickCam Messenger Communicate 08f6 QuickCam Messenger Plus 0900 ClickSmart 310 0901 ClickSmart 510 0903 ClickSmart 820 0905 ClickSmart 820 0910 QuickCam Cordless 0920 QuickCam Express 0921 Labtec Webcam 0922 QuickCam Live 0928 QuickCam Express 0929 Labtec Webcam Pro 092a QuickCam for Notebooks 092b Labtec Webcam Plus 092c QuickCam Chat 092d QuickCam Express / Go 092e QuickCam Chat 092f QuickCam Express Plus 0950 Pocket Camera 0960 ClickSmart 420 0970 Pocket750 0990 QuickCam Pro 9000 0991 QuickCam Pro for Notebooks 0992 QuickCam Communicate Deluxe 0994 QuickCam Orbit/Sphere AF 09a1 QuickCam Communicate MP/S5500 09a2 QuickCam Communicate Deluxe/S7500 09a4 QuickCam E 3500 09a5 Quickcam 3000 For Business 09a6 QuickCam Vision Pro 09b0 Acer OrbiCam 09b2 Fujitsu Webcam 09c0 QuickCam for Dell Notebooks Mic 09c1 QuickCam Deluxe for Notebooks 0a01 USB Headset 0a02 Premium Stereo USB Headset 350 0a03 Logitech USB Microphone 0a04 V20 portable speakers (USB powered) 0a07 Z-10 Speakers 0a0b ClearChat Pro USB 0a0c Clear Chat Comfort USB Headset 0a13 Z-5 Speakers 0a14 USB Headset 0a15 G35 Headset 0a17 G330 Headset 0a1f G930 0a29 H600 [Wireless Headset] 0a37 USB Headset H540 0a38 Headset H340 0a44 Headset H390 0a45 960 Headset 0a4d G430 Surround Sound Gaming Headset 0a5b G933 Wireless Headset Dongle 0a66 [G533 Wireless Headset Dongle] 0b02 C-UV35 [Bluetooth Mini-Receiver] (HID proxy mode) 8801 Video Camera b014 Bluetooth Mouse M336/M337/M535 b305 BT Mini-Receiver bfe4 Premium Optical Wheel Mouse c000 N43 [Pilot Mouse] c001 N48/M-BB48/M-UK96A [FirstMouse Plus] c002 M-BA47 [MouseMan Plus] c003 MouseMan c004 WingMan Gaming Mouse c005 WingMan Gaming Wheel Mouse c00b MouseMan Wheel c00c Optical Wheel Mouse c00d MouseMan Wheel+ c00e M-BJ58/M-BJ69 Optical Wheel Mouse c00f MouseMan Traveler/Mobile c011 Optical MouseMan c012 Mouseman Dual Optical c014 Corded Workstation Mouse c015 Corded Workstation Mouse c016 Optical Wheel Mouse c018 Optical Wheel Mouse c019 Optical Tilt Wheel Mouse c01a M-BQ85 Optical Wheel Mouse c01b MX310 Optical Mouse c01c Optical Mouse c01d MX510 Optical Mouse c01e MX518 Optical Mouse c024 MX300 Optical Mouse c025 MX500 Optical Mouse c030 iFeel Mouse c031 iFeel Mouse+ c032 MouseMan iFeel c033 iFeel MouseMan+ c034 MouseMan Optical c035 Mouse c036 Mouse c037 Mouse c038 Mouse c03d M-BT96a Pilot Optical Mouse c03e Premium Optical Wheel Mouse (M-BT58) c03f M-BT85 [UltraX Optical Mouse] c040 Corded Tilt-Wheel Mouse c041 G5 Laser Mouse c042 G3 Laser Mouse c043 MX320/MX400 Laser Mouse c044 LX3 Optical Mouse c045 Optical Mouse c046 RX1000 Laser Mouse c047 Laser Mouse M-UAL120 c048 G9 Laser Mouse c049 G5 Laser Mouse c050 RX 250 Optical Mouse c051 G3 (MX518) Optical Mouse c053 Laser Mouse c054 Bluetooth mini-receiver c058 M115 Mouse c05a M90/M100 Optical Mouse c05b M-U0004 810-001317 [B110 Optical USB Mouse] c05d Optical Mouse c05f M115 Optical Mouse c061 RX1500 Laser Mouse c062 M-UAS144 [LS1 Laser Mouse] c063 DELL Laser Mouse c064 M110 corded optical mouse (M-B0001) c066 G9x Laser Mouse c068 G500 Laser Mouse c069 M-U0007 [Corded Mouse M500] c06a USB Optical Mouse c06b G700 Wireless Gaming Mouse c06c Optical Mouse c077 M105 Optical Mouse c07c M-R0017 [G700s Rechargeable Gaming Mouse] c07d G502 Mouse c07e G402 Gaming Mouse c080 G303 Gaming Mouse c083 G403 Prodigy Gaming Mouse c084 G203 Gaming Mouse c101 UltraX Media Remote c110 Harmony 785/880/885 Remote c111 Harmony 525 Remote c112 Harmony 890 Remote c11f Harmony 900/1100 Remote c121 Harmony One Remote c122 Harmony 650/700 Remote c124 Harmony 300/350 Remote c125 Harmony 200 Remote c126 Harmony Link c129 Harmony Hub c12b Harmony Touch/Ultimate Remote c201 WingMan Extreme Joystick with Throttle c202 WingMan Formula c207 WingMan Extreme Digital 3D c208 WingMan Gamepad Extreme c209 WingMan Gamepad c20a WingMan RumblePad c20b WingMan Action Pad c20c WingMan Precision c20d WingMan Attack 2 c20e WingMan Formula GP c211 iTouch Cordless Receiver c212 WingMan Extreme Digital 3D c213 J-UH16 (Freedom 2.4 Cordless Joystick) c214 ATK3 (Attack III Joystick) c215 Extreme 3D Pro c216 F310 Gamepad [DirectInput Mode] c218 F510 Gamepad [DirectInput Mode] c219 F710 Gamepad [DirectInput Mode] c21a Precision Gamepad c21c G13 Advanced Gameboard c21d F310 Gamepad [XInput Mode] c21e F510 Gamepad [XInput Mode] c21f F710 Wireless Gamepad [XInput Mode] c221 G11/G15 Keyboard / Keyboard c222 G15 Keyboard / LCD c223 G11/G15 Keyboard / USB Hub c225 G11/G15 Keyboard / G keys c226 G15 Refresh Keyboard c227 G15 Refresh Keyboard c228 G19 Gaming Keyboard c229 G19 Gaming Keyboard Macro Interface c22a Gaming Keyboard G110 c22b Gaming Keyboard G110 G-keys c22d G510 Gaming Keyboard c22e G510 Gaming Keyboard onboard audio c231 G13 Virtual Mouse c245 G400 Optical Mouse c246 Gaming Mouse G300 c248 G105 Gaming Keyboard c24a G600 Gaming Mouse c24c G400s Optical Mouse c24d G710 Gaming Keyboard c24e G500s Laser Gaming Mouse c281 WingMan Force c283 WingMan Force 3D c285 WingMan Strike Force 3D c286 Force 3D Pro c287 Flight System G940 c291 WingMan Formula Force c293 WingMan Formula Force GP c294 Driving Force c295 Momo Force Steering Wheel c298 Driving Force Pro c299 G25 Racing Wheel c29b G27 Racing Wheel c29c Speed Force Wireless Wheel for Wii c2a0 Wingman Force Feedback Mouse c2a1 WingMan Force Feedback Mouse c2ab G13 Joystick c301 iTouch Keyboard c302 iTouch Pro Keyboard c303 iTouch Keyboard c305 Internet Keyboard c307 Internet Keyboard c308 Internet Navigator Keyboard c309 Y-BF37 [Internet Navigator Keyboard] c30a iTouch Composite c30b NetPlay Keyboard c30c Internet Keys (X) c30d Internet Keys c30e UltraX Keyboard (Y-BL49) c30f Logicool HID-Compliant Keyboard (106 key) c311 Y-UF49 [Internet Pro Keyboard] c312 DeLuxe 250 Keyboard c313 Internet 350 Keyboard c315 Classic Keyboard 200 c316 HID-Compliant Keyboard c317 Wave Corded Keyboard c318 Illuminated Keyboard c31a Comfort Wave 450 c31b Compact Keyboard K300 c31c Keyboard K120 c31d Media Keyboard K200 c31f Comfort Keyboard K290 c326 Washable Keyboard K310 c328 Corded Keyboard K280e c332 G502 Proteus Spectrum Optical Mouse c335 G910 Orion Spectrum Mechanical Keyboard c33a G413 Gaming Keyboard c401 TrackMan Marble Wheel c402 Marble Mouse (2-button) c403 Turbo TrackMan Marble FX c404 TrackMan Wheel c408 Marble Mouse (4-button) c501 Cordless Mouse Receiver c502 Cordless Mouse & iTouch Keys c503 Cordless Mouse+Keyboard Receiver c504 Cordless Mouse+Keyboard Receiver c505 Cordless Mouse+Keyboard Receiver c506 MX700 Cordless Mouse Receiver c508 Cordless Trackball c509 Cordless Keyboard & Mouse c50a Cordless Mouse c50b Cordless Desktop Optical c50c Cordless Desktop S510 c50d Cordless Mouse c50e Cordless Mouse Receiver c510 Cordless Mouse c512 LX-700 Cordless Desktop Receiver c513 MX3000 Cordless Desktop Receiver c514 Cordless Mouse c515 Cordless 2.4 GHz Presenter Presentation remote control c517 LX710 Cordless Desktop Laser c518 MX610 Laser Cordless Mouse c51a MX Revolution/G7 Cordless Mouse c51b V220 Cordless Optical Mouse for Notebooks c521 Cordless Mouse Receiver c525 MX Revolution Cordless Mouse c526 Nano Receiver c529 Logitech Keyboard + Mice c52b Unifying Receiver c52d R700 Remote Presenter receiver c52e MK260 Wireless Combo Receiver c52f Unifying Receiver c531 C-U0007 [Unifying Receiver] c532 Unifying Receiver c534 Unifying Receiver c603 3Dconnexion Spacemouse Plus XT c605 3Dconnexion CADman c606 3Dconnexion Spacemouse Classic c621 3Dconnexion Spaceball 5000 c623 3Dconnexion Space Traveller 3D Mouse c625 3Dconnexion Space Pilot 3D Mouse c626 3Dconnexion Space Navigator 3D Mouse c627 3Dconnexion Space Explorer 3D Mouse c628 3Dconnexion Space Navigator for Notebooks c629 3Dconnexion SpacePilot Pro 3D Mouse c62b 3Dconnexion Space Mouse Pro c640 NuLOOQ navigator c702 Cordless Presenter c703 Elite Keyboard Y-RP20 + Mouse MX900 (Bluetooth) c704 diNovo Wireless Desktop c705 MX900 Bluetooth Wireless Hub (C-UJ16A) c707 Bluetooth wireless hub c708 Bluetooth wireless hub c709 BT Mini-Receiver (HCI mode) c70a MX5000 Cordless Desktop c70b BT Mini-Receiver (HID proxy mode) c70c BT Mini-Receiver (HID proxy mode) c70d Bluetooth wireless hub c70e MX1000 Bluetooth Laser Mouse c70f Bluetooth wireless hub c712 Bluetooth wireless hub c714 diNovo Edge Keyboard c715 Bluetooth wireless hub c71a Bluetooth wireless hub c71d Bluetooth wireless hub c71f diNovo Mini Wireless Keyboard c720 Bluetooth wireless hub ca03 MOMO Racing ca04 Formula Vibration Feedback Wheel ca84 Cordless Controller for Xbox ca88 Thunderpad for Xbox ca8a Precision Vibration Feedback Wheel for Xbox caa3 DriveFX Racing Wheel cab1 Cordless Keyboard for Wii HID Receiver d001 QuickCam Pro f301 Controller Solaar-1.1.20/lib/000077500000000000000000000000001522022367600135675ustar00rootroot00000000000000Solaar-1.1.20/lib/hid_parser/000077500000000000000000000000001522022367600157075ustar00rootroot00000000000000Solaar-1.1.20/lib/hid_parser/__init__.py000066400000000000000000001031401522022367600200170ustar00rootroot00000000000000# 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})") Solaar-1.1.20/lib/hid_parser/data.py000066400000000000000000001604651522022367600172060ustar00rootroot00000000000000# 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" Solaar-1.1.20/lib/hidapi/000077500000000000000000000000001522022367600150255ustar00rootroot00000000000000Solaar-1.1.20/lib/hidapi/__init__.py000066400000000000000000000000001522022367600171240ustar00rootroot00000000000000Solaar-1.1.20/lib/hidapi/common.py000066400000000000000000000007701522022367600166730ustar00rootroot00000000000000from __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 centurion_report_id: int | None = None # 0x50 or 0x51 when centurion=True Solaar-1.1.20/lib/hidapi/hidapi_impl.py000066400000000000000000000451431522022367600176650ustar00rootroot00000000000000## 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. This provides a python interface to libusb's hidapi library which, unlike udev, is available for non-linux platforms. 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 logging import platform import typing from threading import Thread from time import sleep from typing import Any from typing import Callable from hidapi.common import DeviceInfo 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", ) for lib in _library_paths: try: _hidapi = ctypes.cdll.LoadLibrary(lib) break except OSError: pass else: 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), ] _hidapi.hid_version.argtypes = [] _hidapi.hid_version.restype = ctypes.POINTER(_cHidApiVersion) _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"} # 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)), ] if _hid_version.contents.major >= 0 and _hid_version.contents.minor >= 13: _cDeviceInfo_fields.append(("bus_type", ctypes.c_int)) _cDeviceInfo._fields_ = _cDeviceInfo_fields # Set up hidapi functions _hidapi.hid_init.argtypes = [] _hidapi.hid_init.restype = ctypes.c_int _hidapi.hid_exit.argtypes = [] _hidapi.hid_exit.restype = ctypes.c_int _hidapi.hid_enumerate.argtypes = [ctypes.c_ushort, ctypes.c_ushort] _hidapi.hid_enumerate.restype = ctypes.POINTER(_cDeviceInfo) _hidapi.hid_free_enumeration.argtypes = [ctypes.POINTER(_cDeviceInfo)] _hidapi.hid_free_enumeration.restype = None _hidapi.hid_open.argtypes = [ctypes.c_ushort, ctypes.c_ushort, ctypes.c_wchar_p] _hidapi.hid_open.restype = ctypes.c_void_p _hidapi.hid_open_path.argtypes = [ctypes.c_char_p] _hidapi.hid_open_path.restype = ctypes.c_void_p _hidapi.hid_write.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t] _hidapi.hid_write.restype = ctypes.c_int _hidapi.hid_read_timeout.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t, ctypes.c_int] _hidapi.hid_read_timeout.restype = ctypes.c_int _hidapi.hid_read.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t] _hidapi.hid_read.restype = ctypes.c_int _hidapi.hid_get_input_report.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t] _hidapi.hid_get_input_report.restype = ctypes.c_int _hidapi.hid_set_nonblocking.argtypes = [ctypes.c_void_p, ctypes.c_int] _hidapi.hid_set_nonblocking.restype = ctypes.c_int _hidapi.hid_send_feature_report.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int] _hidapi.hid_send_feature_report.restype = ctypes.c_int _hidapi.hid_get_feature_report.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t] _hidapi.hid_get_feature_report.restype = ctypes.c_int _hidapi.hid_close.argtypes = [ctypes.c_void_p] _hidapi.hid_close.restype = None _hidapi.hid_get_manufacturer_string.argtypes = [ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_size_t] _hidapi.hid_get_manufacturer_string.restype = ctypes.c_int _hidapi.hid_get_product_string.argtypes = [ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_size_t] _hidapi.hid_get_product_string.restype = ctypes.c_int _hidapi.hid_get_serial_number_string.argtypes = [ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_size_t] _hidapi.hid_get_serial_number_string.restype = ctypes.c_int _hidapi.hid_get_indexed_string.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_wchar_p, ctypes.c_size_t] _hidapi.hid_get_indexed_string.restype = ctypes.c_int _hidapi.hid_error.argtypes = [ctypes.c_void_p] _hidapi.hid_error.restype = ctypes.c_wchar_p # Initialize hidapi _hidapi.hid_init() 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": _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) class HIDError(Exception): pass def _enumerate_devices(): """Returns all HID devices which are potentially useful to us""" devices = [] c_devices = _hidapi.hid_enumerate(0, 0) p = c_devices while p: devices.append(p.contents.as_dict()) p = p.contents.next _hidapi.hid_free_enumeration(c_devices) unique_devices = {} for device in devices: # 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 unique_devices = unique_devices.values() # print("Unique devices:\n" + '\n'.join([f"{dev}" for dev in unique_devices])) return unique_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) def run(self): # Populate initial set of devices so startup doesn't cause any callbacks self.prev_devices = {tuple(dev.items()): dev for dev in _enumerate_devices()} # Continously enumerate devices and raise callback for changes while True: 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(ACTION_REMOVE, device) for key, device in current_devices.items(): if key not in self.prev_devices: self.device_callback(ACTION_ADD, device) self.prev_devices = current_devices sleep(self.polling_delay) 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: bus_id = 0x03 # USB 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_handle = None 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 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 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) 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"]: return None filtered_result = filter_func(bus_id, vid, pid, device["hidpp_short"], device["hidpp_long"]) if not filtered_result: return is_device = filtered_result.get("isDevice") if action == ACTION_ADD: d_info = DeviceInfo( path=device["path"].decode(), bus_id=bus_id, 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=is_device, hidpp_short=device["hidpp_short"], hidpp_long=device["hidpp_long"], ) return d_info elif action == ACTION_REMOVE: d_info = DeviceInfo( path=device["path"].decode(), bus_id=None, 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=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: str, index: int, timeout: int): """Find the node of a device paired with a receiver""" return None 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( glib: GLib, callback: Callable, filter_func: Callable[[int, int, int, bool, bool], dict[str, Any]], ) -> None: """Monitor GLib. 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 == ACTION_REMOVE: # Removed devices will be detected by Solaar directly pass monitor = _DeviceMonitor(device_callback=device_callback) monitor.start() def enumerate(filter_func) -> DeviceInfo: """Enumerate the HID Devices. List all the HID devices attached to the system, optionally filtering by vendor_id, product_id, and/or interface_number. :returns: a list of matching ``DeviceInfo`` tuples. """ for device in _enumerate_devices(): d_info = _match(ACTION_ADD, device, filter_func) if d_info: yield d_info def open(vendor_id, product_id, serial=None): """Open a HID device by its Vendor ID, Product ID and optional serial number. If no serial is provided, the first device with the specified IDs is opened. :returns: an opaque device handle, or ``None``. """ if serial is not None: serial = ctypes.create_unicode_buffer(serial) device_handle = _hidapi.hid_open(vendor_id, product_id, serial) if device_handle is None: raise HIDError(_hidapi.hid_error(None)) return device_handle 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(). :returns: an opaque device handle, or ``None``. """ if not isinstance(device_path, bytes): device_path = device_path.encode() device_handle = _hidapi.hid_open_path(device_path) if device_handle is None: raise HIDError(_hidapi.hid_error(None)) return 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 _hidapi.hid_close(device_handle) 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(). :param data: the data bytes to send including the report number as the first byte. The first byte of data[] must contain the Report ID. For devices which only support a single report, this must be set to 0x0. The remaining bytes contain the report data. Since the Report ID is mandatory, calls to hid_write() will always contain one more byte than the report contains. For example, if a hid report is 16 bytes long, 17 bytes must be passed to hid_write(), the Report ID (or 0x0, for devices with a single report), followed by the report data (16 bytes). In this example, the length passed in would be 17. write() will send the data on the first OUT endpoint, if one exists. If it does not, it will send the data through the Control Endpoint (Endpoint 0). """ assert device_handle assert data assert isinstance(data, bytes), (repr(data), type(data)) bytes_written = _hidapi.hid_write(device_handle, data, len(data)) if bytes_written < 0: raise HIDError(_hidapi.hid_error(device_handle)) return bytes_written def read(device_handle, bytes_count, timeout_ms=None): """Read an Input report from a HID device. :param device_handle: a device handle returned by open() or open_path(). :param bytes_count: maximum number of bytes to read. :param timeout_ms: can be -1 (default) to wait for data indefinitely, 0 to read whatever is in the device's input buffer, or a positive integer to wait that many milliseconds. Input reports are returned to the host through the INTERRUPT IN endpoint. The first byte will contain the Report number if the device uses numbered reports. :returns: the data packet read, an empty bytes string if a timeout was reached, or None if there was an error while reading. """ assert device_handle data = ctypes.create_string_buffer(bytes_count) if timeout_ms is None or timeout_ms < 0: bytes_read = _hidapi.hid_read(device_handle, data, bytes_count) else: bytes_read = _hidapi.hid_read_timeout(device_handle, data, bytes_count, timeout_ms) if bytes_read < 0: raise HIDError(_hidapi.hid_error(device_handle)) return data.raw[:bytes_read] def _get_input_report(device_handle, report_id, size): assert device_handle data = ctypes.create_string_buffer(size) 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)) return data.raw[:size] def _readstring(device_handle, func, max_length=255): assert device_handle buf = ctypes.create_unicode_buffer(max_length) ret = func(device_handle, buf, max_length) if ret < 0: raise HIDError("Error reading device property") return buf.value def get_manufacturer(device_handle): """Get the Manufacturer String from a HID device. :param device_handle: a device handle returned by open() or open_path(). """ return _readstring(device_handle, _hidapi.get_manufacturer_string) def get_product(device_handle): """Get the Product String from a HID device. :param device_handle: a device handle returned by open() or open_path(). """ return _readstring(device_handle, _hidapi.get_product_string) def get_serial(device_handle): """Get the serial number from a HID device. :param device_handle: a device handle returned by open() or open_path(). """ return _readstring(device_handle, _hidapi.get_serial_number_string) Solaar-1.1.20/lib/hidapi/hidconsole.py000066400000000000000000000203411522022367600175260ustar00rootroot00000000000000## 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 argparse import os import os.path import platform import readline import sys import time from binascii import hexlify from binascii import unhexlify from select import select from threading import Lock from threading import Thread if platform.system() == "Linux": import hidapi.udev_impl as hidapi else: import hidapi.hidapi_impl as hidapi LOGITECH_VENDOR_ID = 0x046D interactive = os.isatty(0) prompt = "?? Input: " if interactive else "" start_time = time.time() def strhex(d): return hexlify(d).decode("ascii").upper() print_lock = Lock() def _print(marker, data, scroll=False): t = time.time() - start_time if isinstance(data, str): 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)) with print_lock: # allow only one thread at a time to write to the console, otherwise # the output gets garbled, especially with ANSI codes. 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" ) # move cursor to column 1 sys.stdout.write(s) if interactive and scroll: # restore cursor position sys.stdout.write("\033[u") else: sys.stdout.write("\n") # flush stdout manually... # because trying to open stdin/out unbuffered programmatically # works much too differently in Python 2/3 sys.stdout.flush() def _error(text, scroll=False): _print("!!", text, scroll) def _continuous_read(handle, timeout=2000): while True: try: reply = hidapi.read(handle, 128, timeout) except OSError as e: _error(f"Read failed, aborting: {str(e)}", True) break assert reply is not None if reply: _print(">>", reply, True) def _validate_input(line, hidpp=False): try: data = unhexlify(line.encode("ascii")) except Exception as e: _error(f"Invalid input: {str(e)}") return None if hidpp: if len(data) < 4: _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") 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") return None if data[:1] == b"\x10": if len(data) > 7: _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": if len(data) > 20: _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] return data def _open(args): def matchfn(bid, vid, pid, _a, _b): if vid == LOGITECH_VENDOR_ID: return {"vid": vid} 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.") if not device: sys.exit("!! Device path required.") handle = hidapi.open_path(device) if not handle: sys.exit(f"!! Failed to open {device}, aborting.") print( ".. 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 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 hidapi.get_manufacturer(handle) == b"Logitech" and b"Receiver" in hidapi.get_product(handle): args.hidpp = True print(".. Logitech receiver detected, HID++ validation enabled.") return handle def _parse_arguments(): 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("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() def main(): args = _parse_arguments() handle = _open(args) 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.") if args.history is None: args.history = os.path.join(os.path.expanduser("~"), ".hidconsole-history") try: readline.read_history_file(args.history) except Exception: # file may not exist yet pass try: 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 while t.is_alive(): line = input(prompt) line = line.strip().replace(" ", "") # print ("line", line) if not line: continue data = _validate_input(line, args.hidpp) if data is None: continue _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": # the receiver will reply very fast, in a few milliseconds time.sleep(0.010) else: # the devices might reply quite slow time.sleep(0.700) except EOFError: if interactive: print("") else: time.sleep(1) finally: hidapi.close(handle) if interactive: readline.write_history_file(args.history) if __name__ == "__main__": main() Solaar-1.1.20/lib/hidapi/udev_impl.py000066400000000000000000000413221522022367600173650ustar00rootroot00000000000000## 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. It is currently a partial pure-Python implementation of the native HID API implemented by signal11 (https://github.com/signal11/hidapi), and requires ``pyudev``. The docstrings are mostly copied from the hidapi API header, with changes where necessary. """ from __future__ import annotations import errno import logging import os import typing import warnings # the tuple object we'll expose when enumerating devices from select import select from time import sleep from time import time from typing import Callable 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__) fileopen = open ACTION_ADD = "add" ACTION_REMOVE = "remove" # # exposed API # docstrings mostly copied from hidapi.h # def init(): """This function is a no-op, and exists only to match the native hidapi implementation. :returns: ``True``. """ return True def exit(): """This function is a no-op, and exists only to match the native hidapi implementation. :returns: ``True``. """ return True 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.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 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 hidpp_short = hidpp_long = centurion = False centurion_report_id = None 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 # Centurion transport: 63-byte reports on usage page 0xFFA0 (both input and output) # 0x51 = PRO X 2 LIGHTSPEED variant, 0x50 = G522 LIGHTSPEED variant (with device address byte) if 0x51 in rd.input_report_ids and 63 * 8 == int(rd.get_input_report_size(0x51)) and 0x51 in rd.output_report_ids: centurion_report_id = 0x51 elif ( 0x50 in rd.input_report_ids and 63 * 8 == int(rd.get_input_report_size(0x50)) and 0x50 in rd.output_report_ids ): centurion_report_id = 0x50 centurion = centurion_report_id is not None 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 = None hidpp_long = None centurion = False centurion_report_id = None logger.info( "Report Descriptor not processed for DEVICE %s BID %s VID %s PID %s: %s", device.device_node, bid, vid, pid, e, ) 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 interface_number = filtered_result.get("usb_interface") isDevice = filtered_result.get("isDevice") 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) 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 is not None else None d_info = DeviceInfo( path=device.device_node, bus_id=int(bid, 16), vendor_id=vid[-4:], 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.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, centurion_report_id=centurion_report_id, ) return d_info elif action == ACTION_REMOVE: d_info = DeviceInfo( path=device.device_node, bus_id=None, vendor_id=vid[-4:], product_id=pid[-4:], interface=None, driver=None, manufacturer=None, product=None, serial=None, release=None, isDevice=isDevice, hidpp_short=None, hidpp_long=None, ) return d_info def find_paired_node(receiver_path: str, index: int, timeout: int): """Find the node of a device paired with a receiver""" 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}" # 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") if dev_phys and dev_phys == phys: return dev.device_node delta = time() return None 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 = 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}" # 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") # get wpid - last 4 symbols udev_wpid = hid_id[-4:] return udev_wpid return None def monitor_glib(glib: GLib, callback: Callable, filter_func: Callable): """Monitor GLib. Parameters ---------- glib GLib instance. """ c = pyudev.Context() m = pyudev.Monitor.from_netlink(c) m.filter_by(subsystem="hidraw") 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 == ACTION_ADD: d_info = _match(action, device, filter_func) if d_info: 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, 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, filter_func) except Exception: glib.io_add_watch(m, glib.IO_IN, _process_udev_event, callback, filter_func) logger.debug("Starting dbus monitoring") m.start() 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 vendor_id, product_id, and/or interface_number. :returns: a list of matching ``DeviceInfo`` tuples. """ 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 def open(vendor_id, product_id, serial=None): """Open a HID device by its Vendor ID, Product ID and optional serial number. If no serial is provided, the first device with the specified IDs is opened. :returns: an opaque device handle, or ``None``. """ def matchfn(bid, vid, pid): return vid == vendor_id and pid == product_id for device in enumerate(matchfn): if serial is None or serial == device.serial: return open_path(device.path) def open_path(device_path): """Open a HID device by its path name. :param device_path: the path of a ``DeviceInfo`` tuple returned by enumerate(). :returns: an opaque device handle, or ``None``. """ assert device_path assert device_path.startswith("/dev/hidraw") logger.info("OPEN PATH %s", device_path) retrycount = 0 while retrycount < 3: retrycount += 1 try: return os.open(device_path, os.O_RDWR | os.O_SYNC) except OSError as e: logger.info("OPEN PATH FAILED %s ERROR %s %s", device_path, e.errno, e) if e.errno == errno.EACCES: sleep(0.1) else: raise e 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) def write(device_handle, data): """Write an Output report to a HID device. :param device_handle: a device handle returned by open() or open_path(). :param data: the data bytes to send including the report number as the first byte. The first byte of data[] must contain the Report ID. For devices which only support a single report, this must be set to 0x0. The remaining bytes contain the report data. Since the Report ID is mandatory, calls to hid_write() will always contain one more byte than the report contains. For example, if a hid report is 16 bytes long, 17 bytes must be passed to hid_write(), the Report ID (or 0x0, for devices with a single report), followed by the report data (16 bytes). In this example, the length passed in would be 17. write() will send the data on the first OUT endpoint, if one exists. If it does not, it will send the data through the Control Endpoint (Endpoint 0). """ assert device_handle assert data assert isinstance(data, bytes), (repr(data), type(data)) retrycount = 0 bytes_written = 0 while retrycount < 3: try: retrycount += 1 bytes_written = os.write(device_handle, data) except OSError as e: if e.errno == errno.EPIPE: sleep(0.1) else: break if 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): """Read an Input report from a HID device. :param device_handle: a device handle returned by open() or open_path(). :param bytes_count: maximum number of bytes to read. :param timeout_ms: can be -1 (default) to wait for data indefinitely, 0 to read whatever is in the device's input buffer, or a positive integer to wait that many milliseconds. Input reports are returned to the host through the INTERRUPT IN endpoint. The first byte will contain the Report number if the device uses numbered reports. :returns: the data packet read, an empty bytes string if a timeout was reached, or None if there was an error while reading. """ assert device_handle timeout = None if timeout_ms < 0 else timeout_ms / 1000.0 rlist, wlist, xlist = select([device_handle], [], [device_handle], timeout) if xlist: assert xlist == [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) 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"" _DEVICE_STRINGS = { 0: "manufacturer", 1: "product", 2: "serial", } def get_manufacturer(device_handle): """Get the Manufacturer String from a HID device. :param device_handle: a device handle returned by open() or open_path(). """ return get_indexed_string(device_handle, 0) def get_product(device_handle): """Get the Product String from a HID device. :param device_handle: a device handle returned by open() or open_path(). """ return get_indexed_string(device_handle, 1) def get_serial(device_handle): """Get the serial number from a HID device. :param device_handle: a device handle returned by open() or open_path(). """ serial = get_indexed_string(device_handle, 2) return serial def get_indexed_string(device_handle, index): """Get a string from a HID device, based on its string index. Note: currently not working in the ``hidraw`` native implementation. :param device_handle: a device handle returned by open() or open_path(). :param index: the index of the string to get. :returns: the value corresponding to index, or None if no value found :rtype: bytes or NoneType """ try: key = _DEVICE_STRINGS[index] except KeyError: return None assert device_handle stat = os.fstat(device_handle) try: dev = pyudev.Devices.from_device_number(pyudev.Context(), "char", stat.st_rdev) except (pyudev.DeviceNotFoundError, ValueError): return None hid_dev = dev.find_parent("hid") if hid_dev: 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") assert usb_dev return usb_dev.attributes.get(key) elif bus == "0005": # BLUETOOTH # TODO pass Solaar-1.1.20/lib/keysyms/000077500000000000000000000000001522022367600152735ustar00rootroot00000000000000Solaar-1.1.20/lib/keysyms/__init__.py000066400000000000000000000000001522022367600173720ustar00rootroot00000000000000Solaar-1.1.20/lib/keysyms/generate.py000077500000000000000000000023171522022367600174450ustar00rootroot00000000000000#!/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://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) 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__": main() Solaar-1.1.20/lib/keysyms/keysymdef.py000066400000000000000000001705701522022367600176570ustar00rootroot00000000000000# flake8: noqa 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, } Solaar-1.1.20/lib/logitech_receiver/000077500000000000000000000000001522022367600172515ustar00rootroot00000000000000Solaar-1.1.20/lib/logitech_receiver/__init__.py000066400000000000000000000017311522022367600213640ustar00rootroot00000000000000## 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. """Low-level interface for devices using Logitech HID++ protocol. Uses the HID api exposed through hidapi_impl.py, a Python thin layer over a native implementation. """ import logging logger = logging.getLogger(__name__) Solaar-1.1.20/lib/logitech_receiver/advanced_para_eq.py000066400000000000000000000411421522022367600230620ustar00rootroot00000000000000## 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. """AdvancedParaEQ (0x020D) helpers. The device handles biquad coefficient computation — we transmit only per-band filter-type + frequency + gain; the DSP does the rest. V0/V1 wire format: 3-byte band stride [freq_hi, freq_lo, gain_i8], gain is whole dB; getEQInfos returns 5 bytes [bandCount, dbRange, caps, dbMin, dbMax]. V2 wire format: 1-byte header [direction_echo], then N × 5-byte band stride [freq_hi, freq_lo, filter_type, gain_hi, gain_lo], with 0..3 trailer bytes (opaque, ignored). The parser consumes 5-byte chunks until <5 bytes remain or a freq=0 sentinel is hit. Frequency u16 BE in Hz; gain u16 BE in **offset-binary**: raw 0..(steps-1) maps linearly to gain_min..gain_max (so on G522 with steps=241 / gain=[-6..6], raw=120 = 0 dB). getEQInfos returns 13 bytes with gain bounds + step count, format enum, XY-support flag, and onboard preset counts. (The protocol spec lists a 2-byte header [dir_echo, slot_echo] for getCustomEQ, but G522 firmware via the centurion bridge omits the slot_echo and emits a 1-byte header that matches getEQDefaults. Verified against pcap traces of LGHUB ↔ G522 LIGHTSPEED traffic.) """ from __future__ import annotations import logging import struct from .hidpp20_constants import SupportedFeature logger = logging.getLogger(__name__) DIRECTION_PLAYBACK = 0 DIRECTION_CAPTURE = 1 # V2 filter-type taxonomy (byte [+0] of each band). 0x16 is observed on # G522 for every band of its factory custom slot at ISO third-octave # centers, treated as peaking. Other filter kinds (LP, shelf, notch …) # need a live probe per device firmware to enumerate. FILTER_TYPE_HP = 0x00 FILTER_TYPE_PEAKING_G522 = 0x16 FILTER_TYPE_PEAKING = 0x78 FILTER_TYPE_NAMES = { FILTER_TYPE_HP: "HP", FILTER_TYPE_PEAKING_G522: "peaking", FILTER_TYPE_PEAKING: "peaking", } def _get_version(device) -> int: return device.features.get_feature_version(SupportedFeature.HEADSET_ADVANCED_PARA_EQ) or 0 def get_advanced_eq_info(device): """Query getEQInfos (function 0). Returns a dict or None. Common fields: version int feature version (0, 1, 2) gain_min_db int signed whole-dB min gain_max_db int signed whole-dB max step_db float dB per raw LSB (1.0 on V0/V1) V0/V1 only: band_count int number of bands (from wire byte 0) db_range int raw byte 1 capabilities int raw byte 2 V2 only: gain_steps int discrete gain positions format int 0=CLASSIC, 1=STYLES supports_xy bool onboard_ro_preset_count int factory preset slots onboard_custom_preset_count int user-writable preset slots """ version = _get_version(device) result = device.feature_request(SupportedFeature.HEADSET_ADVANCED_PARA_EQ, 0x00) if result is None: logger.debug("AdvancedParaEQ getEQInfos V%d: feature_request returned None", version) return None if version >= 2: if len(result) < 13: logger.debug("AdvancedParaEQ getEQInfos V2: short response len=%d %s", len(result), result.hex()) return None gain_min = struct.unpack("b", bytes([result[2]]))[0] gain_max = struct.unpack("b", bytes([result[3]]))[0] gain_steps = struct.unpack(">H", result[4:6])[0] fmt = result[6] supports_xy = bool(result[7]) ro_presets = result[9] custom_presets = result[10] step_db = (gain_max - gain_min) / max(1, gain_steps - 1) info = { "version": 2, "gain_min_db": gain_min, "gain_max_db": gain_max, "gain_steps": gain_steps, "step_db": step_db, "format": fmt, "supports_xy": supports_xy, "onboard_ro_preset_count": ro_presets, "onboard_custom_preset_count": custom_presets, } logger.debug( "AdvancedParaEQ getEQInfos V2: gain=[%d,%d] steps=%d step_db=%.4f format=%d xy=%s " "presets_ro=%d presets_custom=%d", gain_min, gain_max, gain_steps, step_db, fmt, supports_xy, ro_presets, custom_presets, ) device._advanced_eq_info = info return info # V0 / V1 if len(result) < 5: logger.debug("AdvancedParaEQ getEQInfos V%d: short response len=%d %s", version, len(result), result.hex()) return None band_count = result[0] db_range = result[1] caps = result[2] gain_min = struct.unpack("b", bytes([result[3]]))[0] gain_max = struct.unpack("b", bytes([result[4]]))[0] info = { "version": version, "band_count": band_count, "db_range": db_range, "capabilities": caps, "gain_min_db": gain_min, "gain_max_db": gain_max, "step_db": 1.0, } logger.debug( "AdvancedParaEQ getEQInfos V%d: bands=%d dbRange=%d caps=0x%02X gain=[%d,%d]", version, band_count, db_range, caps, gain_min, gain_max, ) device._advanced_eq_info = info return info def get_advanced_eq_active_slot(device, direction=DIRECTION_PLAYBACK): """Query getActiveEQ (function 3). Returns the active slot index, or None.""" result = device.feature_request(SupportedFeature.HEADSET_ADVANCED_PARA_EQ, 0x30, direction) if result is None: logger.debug("AdvancedParaEQ getActiveEQ(dir=%d): feature_request returned None", direction) return None if len(result) < 1: logger.debug("AdvancedParaEQ getActiveEQ(dir=%d): empty response", direction) return None logger.debug("AdvancedParaEQ getActiveEQ(dir=%d): slot=%d", direction, result[0]) return result[0] def parse_v2_bands(result: bytes, info: dict | None): """Parse a V2 getCustomEQ / getEQDefaults response. Wire layout (see module docstring): [direction_echo] (1-byte header) N × [freq_hi, freq_lo, filter_type, gain_hi, gain_lo] (5 bytes) [trailer …] (0..3 bytes, ignored) Gain is offset-binary against `info`'s gain bounds: gain_db = gain_min + (gain_max - gain_min) * raw / (steps - 1) `info` is the dict returned by `get_advanced_eq_info`. If absent we fall back to step_db=1.0 (and log via the caller, not here) which is wrong but won't crash. Returns list of (filter_type_byte, freq_hz, gain_db) tuples, or None if the payload is too short to contain a header. Empty payload with valid header returns []. Bands with freq=0 are treated as the end-of-bands sentinel (matches V0/V1 behavior at lines below). """ if result is None or len(result) < 1: return None payload = result[1:] # skip [dir_echo] band_size = 5 if info: gain_min = info.get("gain_min_db", -6) gain_max = info.get("gain_max_db", 6) steps = info.get("gain_steps", 241) else: gain_min, gain_max, steps = 0, 0, 1 # produces gain_db=0 for any raw bands = [] for i in range(len(payload) // band_size): e = payload[i * band_size : (i + 1) * band_size] freq_hz = (e[0] << 8) | e[1] filter_type = e[2] gain_raw = (e[3] << 8) | e[4] if freq_hz == 0: break # disabled band — end-of-bands sentinel if steps > 1: gain_db = gain_min + (gain_max - gain_min) * gain_raw / (steps - 1) else: gain_db = 0.0 bands.append((filter_type, freq_hz, float(gain_db))) return bands def _band_label(filter_type_byte: int, freq_hz: int) -> str: kind = FILTER_TYPE_NAMES.get(filter_type_byte, f"type-0x{filter_type_byte:02X}") if filter_type_byte == FILTER_TYPE_HP: return f"HP {freq_hz} Hz" return f"{freq_hz} Hz" if kind == "peaking" else f"{kind} {freq_hz} Hz" def get_advanced_eq_defaults(device, direction=DIRECTION_PLAYBACK, slot=0): """Query getEQDefaults (function 5). Same per-band layout as getCustomEQ. Returns list of (filter_type_byte, freq_hz, gain_db) tuples, or None. V0/V1 callers receive (FILTER_TYPE_PEAKING, freq_hz, gain_db) for compatibility with the V2 tuple shape. """ version = _get_version(device) result = device.feature_request(SupportedFeature.HEADSET_ADVANCED_PARA_EQ, 0x50, direction, slot) if result is None: logger.debug( "AdvancedParaEQ getEQDefaults V%d (dir=%d slot=%d): feature_request returned None", version, direction, slot, ) return None if version >= 2: info = getattr(device, "_advanced_eq_info", None) bands = parse_v2_bands(result, info) if bands is None: logger.debug( "AdvancedParaEQ getEQDefaults V2 (dir=%d slot=%d): payload too short raw=%s", direction, slot, result.hex(), ) return None logger.debug( "AdvancedParaEQ getEQDefaults V2 (dir=%d slot=%d): %d band(s) raw=%s %s", direction, slot, len(bands), result.hex(), [_band_label(t, f) + f" {round(g, 2)}dB" for t, f, g in bands], ) return bands # V0/V1 legacy 3-byte stride. bands = [] offset = 0 while offset + 3 <= len(result): freq = struct.unpack(">H", result[offset : offset + 2])[0] if freq == 0: break gain_db = struct.unpack("b", bytes([result[offset + 2]]))[0] bands.append((FILTER_TYPE_PEAKING, freq, float(gain_db))) offset += 3 logger.debug( "AdvancedParaEQ getEQDefaults V%d (dir=%d slot=%d): %d band(s)", version, direction, slot, len(bands), ) return bands def get_advanced_eq_friendly_name(device, direction=DIRECTION_PLAYBACK, slot=0): """Query getEQFriendlyName (function 6). Returns the UTF-8 preset name or None.""" result = device.feature_request(SupportedFeature.HEADSET_ADVANCED_PARA_EQ, 0x60, direction, slot) if result is None or len(result) < 1: return None name_len = result[0] if 1 + name_len > len(result): name_len = len(result) - 1 try: name = bytes(result[1 : 1 + name_len]).decode("utf-8", errors="replace") except Exception: name = result[1 : 1 + name_len].hex() return name def probe_advanced_eq_slots(device, direction=DIRECTION_PLAYBACK, info=None): """Probe every advertised EQ slot via getCustomEQ and cache which respond. Some firmware (G522) advertises N slots via getEQInfos but only honors a subset for getCustomEQ / setActiveEQ — the rest return 0x0B NOT_SUPPORTED. This iterates 0..total-1 and records which slots actually have data. Result is cached on `device._advanced_eq_working_slots` as a list of `(slot_index, name, bands)` tuples. The HeadsetActiveEQPreset selector builds its choices from this list; the HeadsetAdvancedEQ panel uses it to skip dead slots in its diagnostic output. Logs each working slot's bands at INFO and a summary line indicating how many of the advertised slots are actually accessible. """ cached = getattr(device, "_advanced_eq_working_slots", None) if cached is not None: return cached if info is None: info = getattr(device, "_advanced_eq_info", None) or get_advanced_eq_info(device) if not info: return [] ro_count = info.get("onboard_ro_preset_count", 0) or 0 custom_count = info.get("onboard_custom_preset_count", 0) or 0 total = ro_count + custom_count if total == 0: return [] def probe(slot): bands = get_advanced_eq_params(device, direction=direction, slot=slot) if bands is None: return None name = get_advanced_eq_friendly_name(device, direction=direction, slot=slot) kind = "factory" if slot < ro_count else "custom" logger.debug( "AdvancedParaEQ %s preset slot=%d (dir=%d) name=%r: %s", kind, slot, direction, name, [f"{_band_label(t, f)} {round(g, 2)}dB" for t, f, g in bands], ) return (slot, name, bands) working = [] # Slot 0 is canonical. If it fails the device is unusable; bail. entry = probe(0) if entry is None: device._advanced_eq_working_slots = working return working working.append(entry) # Slot 1 acts as a "multi-slot capable?" canary. G522 firmware # advertises 16 slots but only honors slot 0; LGHUB itself never # touches slots > 0 on this device. When the canary fails, skip the # remaining 14 NOT_SUPPORTED probes. if total > 1: entry = probe(1) if entry is None: logger.debug( "AdvancedParaEQ: slot 1 returned NOT_SUPPORTED; " "firmware advertises %d slots but only honors slot 0", total, ) device._advanced_eq_working_slots = working return working working.append(entry) for slot in range(2, total): entry = probe(slot) if entry is not None: working.append(entry) device._advanced_eq_working_slots = working logger.debug( "AdvancedParaEQ working slots on dir=%d: %d of %d advertised %s", direction, len(working), total, [w[0] for w in working], ) return working # Backward-compat alias kept until external callers are migrated. probe_all_presets = probe_advanced_eq_slots def get_advanced_eq_params(device, direction=DIRECTION_PLAYBACK, slot=0): """Query getCustomEQ (function 1). Returns list of (filter_type, freq_hz, gain_db) or None. V0/V1: filter_type is always FILTER_TYPE_PEAKING (synthesized), freq is raw Hz from wire, gain is whole dB. V2: filter_type comes from the wire (0x00=HP, 0x78=peaking), freq is raw Hz, gain is int16 × step_db. step_db for V2 is cached on the device by get_advanced_eq_info. """ version = _get_version(device) result = device.feature_request(SupportedFeature.HEADSET_ADVANCED_PARA_EQ, 0x10, direction, slot) if result is None: logger.debug( "AdvancedParaEQ getCustomEQ V%d (dir=%d slot=%d): feature_request returned None", version, direction, slot, ) return None if version >= 2: info = getattr(device, "_advanced_eq_info", None) if not info: logger.warning("AdvancedParaEQ getCustomEQ V2: no cached getEQInfos — gain values will be wrong") bands = parse_v2_bands(result, info) if bands is None: logger.debug( "AdvancedParaEQ getCustomEQ V2 (dir=%d slot=%d): payload too short raw=%s", direction, slot, result.hex(), ) return None step_db = info["step_db"] if info and "step_db" in info else 1.0 # Log raw=... too so we can compare wire shapes across firmware # variants and across get-fns (getCustomEQ vs getEQDefaults). logger.debug( "AdvancedParaEQ getCustomEQ V2 (dir=%d slot=%d): %d band(s) step_db=%.4f raw=%s %s", direction, slot, len(bands), step_db, result.hex(), [f"{_band_label(t, f)} {round(g, 2)}dB" for t, f, g in bands], ) return bands # V0 / V1 bands = [] offset = 0 while offset + 3 <= len(result): freq = struct.unpack(">H", result[offset : offset + 2])[0] if freq == 0: break gain_db = struct.unpack("b", bytes([result[offset + 2]]))[0] bands.append((FILTER_TYPE_PEAKING, freq, float(gain_db))) offset += 3 logger.debug( "AdvancedParaEQ getCustomEQ V%d (dir=%d slot=%d): parsed %d band(s) %s", version, direction, slot, len(bands), bands, ) return bands Solaar-1.1.20/lib/logitech_receiver/base.py000066400000000000000000001052671522022367600205500ustar00rootroot00000000000000## 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. """Base low-level functions as API for upper layers.""" from __future__ import annotations import dataclasses import logging import platform import struct import threading import typing from contextlib import contextmanager from random import getrandbits from time import time from typing import Any from typing import Callable 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 if typing.TYPE_CHECKING: import gi from hidapi.common import DeviceInfo gi.require_version("Gdk", "3.0") from gi.repository import GLib # NOQA: E402 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 HIDPP_SHORT_MESSAGE_ID = 0x10 HIDPP_LONG_MESSAGE_ID = 0x11 DJ_MESSAGE_ID = 0x20 # Centurion transport (used by PRO X 2 LIGHTSPEED headset and similar) # Two variants exist, distinguished by report ID: # 0x51 (PRO X 2): [0x51, cpl_length, flags, feat_idx, func_sw, params..., pad] # 0x50 (G522): [0x50, device_addr, cpl_length, flags, feat_idx, func_sw, params..., pad] # The 0x50 variant adds a device_addr byte at position [1], shifting all CPL fields by +1. # 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_ADDRESSED_REPORT_ID = 0x50 # addressed variant with device_addr byte at frame[1] (G522 etc.) 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) @dataclasses.dataclass class CenturionHandleState: """Per-handle state for Centurion devices.""" report_id: int = CENTURION_REPORT_ID # 0x50 or 0x51 device_addr: int | None = None # learned from first RX (0x50 only) protocol_version: tuple[int, int] | None = None # from ping response # All centurion per-handle state in a single dict. # Membership test (ihandle in _centurion_handles) gates centurion-specific code paths. _centurion_handles: dict[int, CenturionHandleState] = {} """Default timeout on read (in seconds).""" DEFAULT_TIMEOUT = 4 # the receiver itself should reply very fast, within 500ms _RECEIVER_REQUEST_TIMEOUT = 0.9 # devices may reply a lot slower, as the call has to go wireless to them and come back _DEVICE_REQUEST_TIMEOUT = DEFAULT_TIMEOUT # when pinging, be extra patient (no longer) _PING_TIMEOUT = DEFAULT_TIMEOUT hidapi = typing.cast(HIDProtocol, hidapi) request_lock = threading.Lock() # serialize all requests handles_lock = {} @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})" 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, } 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 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 hidapi.enumerate(get_known_receiver_info) 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""" 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 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 hidapi.enumerate(filter_products_of_interest) 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) -> int: """Checks if the given Linux device path points to the right UR device. :param path: the Linux device path. The UR physical device may expose multiple linux devices with the same interface, so we have to check for the right one. At this moment the only way to distinguish betheen them is to do a test ping on an invalid (attached) device number (i.e., 0), expecting a 'ping failed' reply. :returns: an open receiver handle if this is the right Linux device, or ``None``. """ return hidapi.open_path(path) def open(): """Opens the first Logitech Unifying Receiver found attached to the machine. :returns: An open file handle for the found receiver, or ``None``. """ for rawdevice in receivers(): handle = open_path(rawdevice.path) if handle: return handle def close(handle): """Closes a HID device handle.""" if handle: try: if isinstance(handle, int): _centurion_handles.pop(handle, None) hidapi.close(handle) else: handle.close() return True except Exception: pass return False def _centurion_frame_header(state: CenturionHandleState, cpl_length: int, flags: int) -> bytes: """Build the fixed prefix of a centurion frame. 0x51: [0x51, cpl_length, flags] (3 bytes) 0x50: [0x50, device_addr, cpl_length, flags] (4 bytes) """ if state.report_id == CENTURION_ADDRESSED_REPORT_ID: device_addr = state.device_addr if state.device_addr is not None else 0x00 return struct.pack("!BBBB", CENTURION_ADDRESSED_REPORT_ID, device_addr, cpl_length, flags) return struct.pack("!BBB", CENTURION_REPORT_ID, cpl_length, flags) _CENTURION_REPORT_IDS = (CENTURION_REPORT_ID, CENTURION_ADDRESSED_REPORT_ID) # Per-candidate read timeout (ms) for the device_addr probe. # USB round-trip is <1ms; 5ms gives 5x margin. _CENTURION_PROBE_PER_ADDR_TIMEOUT_MS = 5 def probe_centurion_device_addr(handle, state: CenturionHandleState) -> bool: """Brute-force probe the device address byte for a 0x50-variant Centurion handle. Sends a ROOT.GetProtocolVersion request for each candidate device_addr (0x00–0xFF), reading briefly after each write. The dongle silently ignores wrong addresses and responds only to the correct one. Stops on first hit. Worst case (no response): 256 × 5ms = ~1.3s. Typical G522 (addr=0x23): 36 × 5ms = ~180ms. No-op for 0x51 (no device_addr byte) or when an address is already known. Returns True if the address was learned. """ if state.report_id != CENTURION_ADDRESSED_REPORT_ID or state.device_addr is not None: return False ihandle = int(handle) logger.debug("(%s) probing centurion device_addr: scanning 0x00-0xFF", handle) # ROOT.GetProtocolVersion: feat_idx=0x00, func=0x10, 3 zero param bytes payload = bytes([0x00, 0x10, 0x00, 0x00, 0x00]) cpl_length = len(payload) + 1 # +1 for flags byte write_errors = 0 for addr in range(256): frame = struct.pack("!BBBB", CENTURION_ADDRESSED_REPORT_ID, addr, cpl_length, 0x00) + payload frame = frame + b"\x00" * (CENTURION_FRAME_SIZE - len(frame)) try: hidapi.write(ihandle, frame) except Exception: write_errors += 1 if write_errors > 3: logger.debug("(%s) centurion device_addr probe: too many write failures, aborting", handle) return False continue try: data = hidapi.read(ihandle, CENTURION_FRAME_SIZE, _CENTURION_PROBE_PER_ADDR_TIMEOUT_MS) except Exception as reason: logger.debug("(%s) centurion device_addr probe read failed at addr 0x%02X: %s", handle, addr, reason) return False if data and len(data) >= 2 and ord(data[:1]) == state.report_id: state.device_addr = ord(data[1:2]) logger.debug( "(%s) probed centurion device addr 0x%02X (after %d candidates)", handle, state.device_addr, addr + 1, ) return True logger.debug("(%s) centurion device_addr probe: no response from any of 256 candidates", handle) return False def _unwrap_centurion_frame(data: bytes, ihandle: int, handle) -> bytes: """Unwrap a Centurion CPL frame (0x50 or 0x51) into a standard HID++ long message. Auto-detects the variant from the raw report ID byte (self-describing), matching how _read() handles 0x10 vs 0x11. For 0x50, learns the device address from byte[1] on first receive. """ raw_report_id = ord(data[:1]) if raw_report_id == CENTURION_ADDRESSED_REPORT_ID: # 0x50: [report_id, device_addr, cpl_length, flags, feat_idx, func_sw, data...] device_addr = ord(data[1:2]) state = _centurion_handles.get(ihandle) if state is not None and state.device_addr is None: state.device_addr = device_addr if logger.isEnabledFor(logging.DEBUG): logger.debug("(%s) learned centurion device addr 0x%02X", handle, device_addr) cpl_length = ord(data[2:3]) inner_payload = data[4 : 3 + cpl_length] # cpl_length - 1 bytes (skip flags) elif raw_report_id == CENTURION_REPORT_ID: # 0x51: [report_id, cpl_length, flags, feat_idx, func_sw, data...] cpl_length = ord(data[1:2]) inner_payload = data[3 : 2 + cpl_length] # cpl_length - 1 bytes (skip flags) else: return data # not a centurion frame 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] return data def write(handle, devnumber, data, long_message=False): """Writes some data to the receiver, addressed to a certain device. :param handle: an open UR handle. :param devnumber: attached device number. :param data: data to send, up to 5 bytes. The first two (required) bytes of data must be the SubId and address. :raises NoReceiver: if the receiver is no longer available, i.e. has been physically removed from the machine, or the kernel driver has been unloaded. The handle will be closed automatically. """ # the data is padded to either 5 or 18 bytes 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 = struct.pack("!BB18s", HIDPP_LONG_MESSAGE_ID, devnumber, data) else: wdata = struct.pack("!BB5s", HIDPP_SHORT_MESSAGE_ID, devnumber, data) ihandle = int(handle) if ihandle in _centurion_handles: # Centurion CPL framing — strip device_index from HID++ and wrap in CPL header. # cpl_length = len(meaningful_payload) + 1 (the +1 counts the flags byte). state = _centurion_handles[ihandle] 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 = _centurion_frame_header(state, 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: 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 write_centurion_cpl(handle, layer3_payload, flags=0x00): """Send a Centurion CPL frame with the given Layer 3+ payload. Builds the appropriate header for the handle's report ID variant: 0x51: [0x51, cpl_length, flags, layer3_payload..., pad to 64] 0x50: [0x50, device_addr, cpl_length, flags, layer3_payload..., pad to 64] 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") state = _centurion_handles[ihandle] cpl_length = len(layer3_payload) + 1 # +1 for flags byte header = _centurion_frame_header(state, cpl_length, flags) wdata = header + 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[: len(header) + cpl_length - 1])) 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): """Read some data from the receiver. Usually called after a write (feature call), to get the reply. :param: handle open handle to the receiver :param: timeout how long to wait for a reply, in seconds :returns: a tuple of (devnumber, message data), or `None` :raises NoReceiver: if the receiver is no longer available, i.e. has been physically removed from the machine, or the kernel driver has been unloaded. The handle will be closed automatically. """ reply = _read(handle, timeout) if reply: return reply 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: if len(data) in report_lengths[report_id]: return True else: logger.warning(f"unexpected message size: report_id {report_id:02X} message {common.strhex(data)}") return False 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`. :raises NoReceiver: if the receiver is no longer available, i.e. has 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 = hidapi.read(ihandle, read_size, timeout) except Exception as reason: logger.warning("read failed, assuming handle %r no longer available", handle) close(handle) raise exceptions.NoReceiver(reason=reason) from reason if data and is_centurion and ord(data[:1]) in _CENTURION_REPORT_IDS: data = _unwrap_centurion_frame(data, ihandle, handle) if data and _is_relevant_message(data): # ignore messages that fail check report_id = ord(data[:1]) devnumber = ord(data[1:2]) 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 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 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 None # DJ input records are not notifications if report_id == DJ_MESSAGE_ID and (sub_id < 0x10): 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 None if ( # standard HID++ 1.0 notification, SubId may be 0x40 - 0x7F (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 # custom HID++1.0 illumination event, where SubId is 0x17 (sub_id == 0x17 and len(data) == 5) or # HID++ 2.0 feature notifications have the SoftwareID 0 (address & 0x0F == 0x00) ): # noqa: E129 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 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] # context manager for locks with a timeout @contextmanager def acquire_timeout(lock, handle, timeout): result = lock.acquire(timeout=timeout) try: if not result: 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: 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. :param request_id: a 16-bit integer. :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 """ 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: # 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. 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 if request_id & 0xFF00 == 0x8300: timeout *= 2 if params: params = b"".join(struct.pack("B", p) if isinstance(p, int) else p for p in params) else: params = b"" request_data = struct.pack("!H", request_id) + params ihandle = int(handle) notifications_hook = getattr(handle, "notifications_hook", None) try: _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) if no_reply: return None # we consider timeout from this point 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] ): error = ord(reply_data[3:4]) 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 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]) 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, ) if reply_data[:2] == request_data[:2]: if devnumber == 0xFF: if request_id == 0x83B5 or request_id == 0x81F1: # these replies have to match the first parameter as well if reply_data[2:3] == params[:1]: return reply_data[2:] else: # hm, not matching my request, and certainly not a notification continue else: return reply_data[2:] else: return reply_data[2:] else: # a reply was received, but did not match our request in any way # reset the timeout starting point request_started = time() if notifications_hook: n = make_notification(report_id, reply_devnumber, reply_data) if n: notifications_hook(n) delta = time() - request_started 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: 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 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: _read_input_buffer(handle, int(handle), notifications_hook) except exceptions.NoReceiver: logger.warning("device or receiver disconnected") return # 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 = 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 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 major = ord(reply_data[2:3]) minor = ord(reply_data[3:4]) if is_centurion: _centurion_handles[int(handle)].protocol_version = (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 error = ord(reply_data[3:4]) if error == Hidpp10ErrorCode.INVALID_SUB_ID_COMMAND: # a valid reply from a HID++ 1.0 device return 1.0 if error in [Hidpp10ErrorCode.RESOURCE_ERROR, Hidpp10ErrorCode.CONNECTION_REQUEST_FAILED]: return # device unreachable 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) delta = time() - request_started 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]) in _CENTURION_REPORT_IDS: data = _unwrap_centurion_frame(data, ihandle, handle) 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 # HID++ Software ID claimed by Solaar. Fixed (not rotated) so cooperative # userspace HID++ clients sharing the same device can pick a different value # and reliably filter Solaar's traffic out of their reply stream. # # Known values in use by other tools at the time of writing: # # 0x07 OpenRGB # 0x0A LGSTrayEx # 0x0D Logitech G HUB (host-side) # 0x0F Logitech firmware (sub-device self-enumeration on wired transports) # # 0x0B avoids those and keeps the high bit set so notifications (sw_id=0) # remain trivially distinguishable from replies. SOLAAR_SOFTWARE_ID = 0x0B def _get_next_sw_id() -> int: """Return Solaar's HID++ Software ID (fixed, see SOLAAR_SOFTWARE_ID).""" return SOLAAR_SOFTWARE_ID Solaar-1.1.20/lib/logitech_receiver/base_usb.py000066400000000000000000000166631522022367600214220ustar00rootroot00000000000000## 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. """Collection of known Logitech product IDs. 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 USB ids of Logitech wireless receivers. Only receivers supporting the HID++ protocol can go in here. """ from __future__ import annotations from typing import Any from solaar.i18n import _ # 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 LOGITECH_VENDOR_ID = 0x046D 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, } 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, } 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.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) # standard Unifying receivers (marked with the orange Unifying logo) 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_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_C54D = _lightspeed_receiver(0xC54D) # EX100 old style receiver pre-unifying protocol EX100_27MHZ_RECEIVER_C517 = _ex100_receiver(0xC517) 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, } def get_receiver_info(product_id: int) -> dict[str, Any]: """Returns hardcoded information about a Logitech receiver. 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 Raises ------ ValueError If the product ID is unknown. """ try: return KNOWN_RECEIVERS[product_id] except KeyError: pass raise ValueError(f"Unknown product ID '0x{product_id:02X}'") Solaar-1.1.20/lib/logitech_receiver/centurion.py000066400000000000000000000541221522022367600216350ustar00rootroot00000000000000## 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 . import hidpp10_constants 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._pending = False # True when device_addr unknown; deferred init completes on first RX 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) # User-facing name: "Centurion" is Logitech's internal codename for this # headset-dongle transport, kept in code/logs but not shown to users. self.name = "Lightspeed Headset 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=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 and not self._pending: self._firmware = get_firmware_centurion(self) return self._firmware or () def _complete_deferred_init(self): """Re-run feature discovery after device_addr has been learned. Called once from the notification handler when the first 0x50 frame arrives on a pending CenturionReceiver. """ if not self._pending: return False self._pending = False ihandle = int(self.handle) state = base._centurion_handles.get(ihandle) learned_addr = state.device_addr if state else None logger.debug( "CenturionReceiver %s: completing deferred init (device_addr=0x%02X)", self.path, learned_addr or 0, ) self._dongle_features = None self._discover_dongle_features() logger.debug( "CenturionReceiver %s: deferred discovery found %d feature(s): %s", self.path, len(self._dongle_features or []), [(f"{feat_id:#06x}", idx) for _, feat_id, idx in (self._dongle_features or [])], ) 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 has_bridge = any(feat_id == CenturionCoreFeature.CENT_PP_BRIDGE for _, feat_id, _ in (self._dongle_features or [])) if has_bridge: self.notify_devices() return True logger.warning( "CenturionReceiver %s: deferred init completed but no bridge found " "(features: %s)", self.path, [f"{feat_id:#06x}" for _, feat_id, _ in (self._dongle_features or [])], ) return False 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 if self._pending: # Don't create children yet — feature discovery hasn't succeeded. # Signal receiver to UI so the tray entry exists. self.changed(alert=Alert.NONE) return # 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": hidpp10_constants.DEVICE_KIND.headset, # every Centurion-transport device so far is a headset "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() logger.debug( "CenturionReceiver %s: child device created, bridge_idx=%s, online=%s, protocol=%s", self.path, getattr(dev, "_centurion_bridge_index", None), online, dev._protocol, ) 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, or a Bluetooth-paired Centurion headset where there is no separate dongle. 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: report_id = getattr(device_info, "centurion_report_id", None) or base.CENTURION_REPORT_ID state = base.CenturionHandleState(report_id=report_id) base._centurion_handles[int(handle)] = state base.probe_centurion_device_addr(handle, state) 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 has_bridge: return cr # No bridge found. Distinguish "silent 0x50 dongle" (device_addr # unknown, headset not yet powered on) from "wired 0x50 device" # (responded to probe, features found, but no bridge). is_0x50 = state.report_id == base.CENTURION_ADDRESSED_REPORT_ID if is_0x50 and state.device_addr is None and not cr.dongle_features: logger.debug( "Centurion 0x50 device %s: probe and discovery failed, " "deferring init until first RX frame", device_info.path, ) cr._pending = True return cr logger.info("Centurion device %s has no bridge, treating as direct device", device_info.path) base._centurion_handles.pop(int(handle), None) cr.handle = None # prevent __del__ from double-closing low_level.close(handle) return None 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 Solaar-1.1.20/lib/logitech_receiver/centurion_constants.py000066400000000000000000000020341522022367600237240ustar00rootroot00000000000000"""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 Solaar-1.1.20/lib/logitech_receiver/common.py000066400000000000000000000456211522022367600211230ustar00rootroot00000000000000## 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 binascii import dataclasses import typing from enum import Flag from enum import IntEnum from typing import Generator from typing import Iterable from typing import Optional from typing import Union 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): """A regular Python integer with an attached name. Caution: comparison with strings will also match this NamedInt's name (case-insensitive).""" def __new__(cls, value, name): assert isinstance(name, str) obj = int.__new__(cls, value) obj.name = str(name) return obj def bytes(self, count=2): return int2bytes(self, count) def __eq__(self, other): if other is None: return False if isinstance(other, NamedInt): return int(self) == int(other) and self.name == other.name if isinstance(other, int): return int(self) == int(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(f"Unsupported type {str(type(other))}") def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return int(self) def __str__(self): return self.name def __repr__(self): 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 ColorInt(int): """A 24-bit RGB color (``0x000000``-``0xFFFFFF``) as an int subclass. Renders as ``0xrrggbb`` in ``str()`` / ``repr()`` and as a YAML hex int literal in dumped configs (e.g. ``color: 0xfc3300``), which loads back natively as a plain int via YAML 1.1's hex int parsing — so the value round-trips cleanly with no special loader registration. The constructor accepts both ints and hex strings (``0xfc3300`` or ``#fc3300``) so configs saved before this type existed continue to load unchanged. Negative or out-of-range values fall back to plain decimal formatting so sentinels like ``COLORSPLUS["No change"] = -1`` keep their natural display. """ def __new__(cls, value): if isinstance(value, str): s = value.strip().lower() if s.startswith("#"): value = int(s[1:], 16) elif s.startswith(("0x", "0X")): value = int(s, 16) else: value = int(s) else: value = int(value) return super().__new__(cls, value) def __str__(self): v = int(self) if 0 <= v <= 0xFFFFFF: return "0x%06x" % v return str(v) def __repr__(self): return self.__str__() def color_int_representer(dumper, data): v = int(data) if 0 <= v <= 0xFFFFFF: return dumper.represent_scalar("tag:yaml.org,2002:int", "0x%06x" % v) return dumper.represent_scalar("tag:yaml.org,2002:int", str(v)) yaml.add_representer(ColorInt, color_int_representer) class NamedInts: """An ordered set of NamedInt values. Indexing can be made by int or string, and will return the corresponding NamedInt if it exists in this set, or `None`. Extracting slices will return all present NamedInts in the given interval (extended slices are not supported). Assigning a string to an indexed int will create a new NamedInt in this set; 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): def _readable_name(n): return n.replace("__", "/").replace("_", " ") # print (repr(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 self._values = list(values.values()) self._sort_values() self._indexed = {int(v): v for v in self._values} # assert len(values) == len(self._indexed) # "(%d) %r\n=> (%d) %r" % (len(values), values, len(self._indexed), self._indexed) self._fallback = None @classmethod def list(cls, items, name_generator=lambda x: str(x)): # noqa: B008 values = {name_generator(x): x for x in items} return NamedInts(**values) @classmethod def range(cls, from_value, to_value, name_generator=lambda x: str(x), step=1): # noqa: B008 values = {name_generator(x): x for x in range(from_value, to_value + 1, step)} return NamedInts(**values) def flag_names(self, value): unknown_bits = value for k in self._indexed: assert bin(k).count("1") == 1 if k & value == k: unknown_bits &= ~k yield str(self._indexed[k]) if unknown_bits: yield f"unknown:{unknown_bits:06X}" def _sort_values(self): self._values = sorted(self._values) self._is_sorted = True def __getitem__(self, index): if isinstance(index, int): if index in self._indexed: return self._indexed[int(index)] if self._fallback: value = NamedInt(index, self._fallback(index)) self._indexed[index] = value self._values.append(value) self._is_sorted = False self._sort_values() return value 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) elif isinstance(index, slice): values = self._values if self._is_sorted else sorted(self._values) if index.start is None and index.stop is None: return values[:] v_start = int(values[0]) if index.start is None else int(index.start) v_stop = (values[-1] + 1) if index.stop is None else int(index.stop) if v_start > v_stop or v_start > values[-1] or v_stop <= values[0]: return [] if v_start <= values[0] and v_stop > values[-1]: return values[:] start_index = 0 stop_index = len(values) for i, value in enumerate(values): if value < v_start: start_index = i + 1 elif index.stop is None: break if value >= v_stop: stop_index = i break return values[start_index:stop_index] def __setitem__(self, index, name): assert isinstance(index, int), type(index) if isinstance(name, NamedInt): assert int(index) == int(name), f"{repr(index)} {repr(name)}" value = name elif isinstance(name, str): value = NamedInt(index, name) else: raise TypeError("name must be a string") if str(value) in self.__dict__: raise ValueError(f"{value} ({int(value)}) already known") if int(value) in self._indexed: raise ValueError(f"{int(value)} ({value}) already known") self._values.append(value) self._is_sorted = False self._sort_values() self.__dict__[str(value)] = value self._indexed[int(value)] = value def __contains__(self, value): if isinstance(value, NamedInt): return self[value] == value elif isinstance(value, int): return value in self._indexed elif isinstance(value, str): return value in self.__dict__ or value in self._values def __iter__(self): yield from self._values def __len__(self): return len(self._values) def __repr__(self): 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 def __or__(self, other): c = UnsortedNamedInts if isinstance(other, UnsortedNamedInts) else NamedInts return c(**self.__dict__, **other.__dict__) def strhex(x): assert x is not None """Produce a hex-string representation of a sequence of bytes.""" return binascii.hexlify(x).decode("ascii").upper() def bytes2int(x, signed=False): 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) else: return x.to_bytes(length=8, byteorder="big", signed=signed).lstrip(b"\x00") class KwException(Exception): """An exception that remembers all arguments passed to the constructor. They can be later accessed by simple member access. """ def __init__(self, **kwargs): super().__init__(kwargs) def __getattr__(self, k): try: return super().__getattr__(k) except AttributeError: return self.args[0].get(k) # was self.args[0][k] class FirmwareKind(IntEnum): Firmware = 0x00 Bootloader = 0x01 Hardware = 0x02 Other = 0x03 @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 # Solaar internal — not a HID++ protocol value OFFLINE = 0xFF 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 Solaar-1.1.20/lib/logitech_receiver/descriptors.py000066400000000000000000000434041522022367600221710ustar00rootroot00000000000000## 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. """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 .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 DEVICES_WPID = {} DEVICES = {} def _D( name, codename=None, kind=None, wpid=None, protocol=None, registers=None, settings=None, usbid=None, interface=None, btid=None, ): if kind is None: kind = ( 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, 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,): if protocol > 1.0: assert w[0:1] == "4", f"{name} has protocol {protocol:0.1f}, wpid {w}" else: 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, kind=kind, wpid=wpid, codename=codename, protocol=protocol, registers=registers, usbid=usbid, interface=interface, btid=btid, ) if usbid: found = get_usbid(usbid) assert found is None, f"duplicate usbid in device descriptors: {found}" if btid: found = get_btid(btid) assert found is None, f"duplicate btid in device descriptors: {found}" 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, f"duplicate wpid in device descriptors: {DEVICES_WPID[w]}" DEVICES_WPID[w] = device_descriptor def get_wpid(wpid): return DEVICES_WPID.get(wpid) def get_codename(codename): return DEVICES.get(codename) def get_usbid(usbid): if isinstance(usbid, str): usbid = int(usbid, 16) found = next((x for x in DEVICES.values() if x.usbid == usbid), None) return found def get_btid(btid): if isinstance(btid, str): btid = int(btid, 16) found = next((x for x in DEVICES.values() if x.btid == btid), None) return found # Some HID++1.0 registers and HID++2.0 features can be discovered at run-time, # so they are not specified here. # # State registers (battery, leds, some features, etc) are only used by # HID++ 1.0 devices, while HID++ 2.0 devices use features for the same # functionalities. # Well-known registers (in hex): # * 00 - notification flags (all devices) # 01 - mice: smooth scrolling # 07 - battery status # 09 - keyboards: FN swap (if it has the FN key) # 0D - battery charge # a device may have either the 07 or 0D register available; # no known device uses both # 51 - leds # 63 - mice: DPI # * F1 - firmware info # Some registers appear to be universally supported, no matter the HID++ version # (marked with *). The rest may or may not be supported, and their values may or # may not mean the same thing across different devices. # The 'codename' and 'kind' fields are usually guessed from the device name, # but in some cases (like the Logitech Cube) that heuristic fails and they have # to be specified. # # 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. # # 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. # 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 # Keyboards _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=(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) _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("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) # Trackballs _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( "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=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 # G522 LIGHTSPEED Gaming Headset (0x0B18 dongle, 0x0B19 wired) — Centurion 0x50 variant, no static descriptor needed Solaar-1.1.20/lib/logitech_receiver/desktop_notifications.py000066400000000000000000000110561522022367600242300ustar00rootroot00000000000000## 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 Solaar-1.1.20/lib/logitech_receiver/device.py000066400000000000000000001445261522022367600210760ustar00rootroot00000000000000## 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 errno import logging import struct import threading import time import typing from typing import Callable from typing import Optional from typing import Protocol from solaar import configuration 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 BatteryStatus from .common import _read_usb_product_string from .hidpp10_constants import NotificationFlag from .hidpp20_constants import SupportedFeature if typing.TYPE_CHECKING: from logitech_receiver import common logger = logging.getLogger(__name__) _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): report_id = getattr(device_info, "centurion_report_id", None) or base.CENTURION_REPORT_ID state = base.CenturionHandleState(report_id=report_id) base._centurion_handles[int(handle)] = state base.probe_centurion_device_addr(handle, state) # 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: Callable = hidpp10.read_register write_register: Callable = hidpp10.write_register def __init__( self, low_level: LowLevelInterface, receiver, number, online, pairing_info=None, handle=None, device_info=None, setting_callback=None, ): 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.handle = handle 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) if self._kind is None and self.centurion: self._kind = hidpp10_constants.DEVICE_KIND.headset # every Centurion-transport device so far is a headset 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._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._feature_settings_checked = False 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 = self.low_level.find_paired_node(receiver.path, number, 1) if receiver else None if not self.handle: try: self.handle = self.low_level.open_path(self.path) if self.path else None except Exception: # maybe the device wasn't set up try: time.sleep(1) self.handle = self.low_level.open_path(self.path) if self.path else None except Exception: # give up self.handle = None # should this give up completely? if receiver: 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: 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) else: 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._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 [] # Centurion devices always use HID++ 2.0 features regardless of the # protocol version the dongle reports (e.g. G522 reports 1.1). if self._protocol is not None and not self.centurion: self.features = {} if self._protocol < 2.0 else hidpp20.FeaturesArray(self) else: self.features = hidpp20.FeaturesArray(self) # may be a 2.0 device; if not, it will fix itself later 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 == id or device.serial == id or device.name == id or device.codename == id): return device @property def protocol(self): if not self._protocol: try: self.ping() except exceptions.NoSuchDevice: logger.warning("device %s inaccessible - no protocol set", self) result = self._protocol or 0 # Centurion devices always use HID++ 2.0 features regardless of the # protocol version the dongle reports (e.g. G522 reports 1.1). # Ensure all `protocol < 2.0` gates route through the 2.0 code path. if self.centurion and result < 2.0: return 2.0 return result @property def codename(self): if not self._codename: if self.online and self.protocol >= 2.0: if not self.centurion: self._codename = _hidpp20.get_friendly_name(self) if not self._codename and self.name: # Use the full live name; only drop a leading "Logitech". # Truncating at the first space mangled good names like # "G502 X PLUS" (direct USB connection, no friendly name). names = self.name.split(" ") if not self.centurion and len(names) > 1 and names[0] == "Logitech": self._codename = " ".join(names[1:]) else: self._codename = self.name 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 f"?? ({self.wpid or self.product_id})" @property def name(self): if not self._name: 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 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 # modelId is the stable per-model disambiguator (G522 0x32, G325 # 0x44). productId is shared across the headset family and varies # by firmware (G522 0x0508 -> 0x0509), so it must NOT key modelId. self._modelId = f"{model_id:02X}" self._tid_map = {"usbid": f"{product_id:04X}"} @property def unitId(self): if not self._unitId and self.online and self.protocol >= 2.0: self.get_ids() return self._unitId @property def modelId(self): if not self._modelId and self.online and self.protocol >= 2.0: self.get_ids() return self._modelId @property def tid_map(self): if not self._tid_map and self.online and self.protocol >= 2.0: self.get_ids() return self._tid_map @property def kind(self): # Centurion devices are seeded with kind=headset at construction, so # this online lookup only runs for descriptor-less HID++ 2.0 devices. if not self._kind and self.online and self.protocol >= 2.0: self._kind = _hidpp20.get_kind(self) return self._kind or "?" @property def firmware(self) -> tuple[common.FirmwareInfo]: if self._firmware is None and self.online: 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) return self._firmware or () @property def serial(self): if not self._serial and self.online and self.centurion: self.get_ids() return self._serial or "" @property def id(self): return self.unitId or self.serial @property def power_switch_location(self): return self._power_switch @property def polling_rate(self): 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: if self.online and self.protocol >= 2.0: self._keys = _hidpp20.get_keys(self) or () return self._keys @property def remap_keys(self): if self._remap_keys is None: if self.online and self.protocol >= 2.0: self._remap_keys = _hidpp20.get_remap_keys(self) or () return self._remap_keys @property def gestures(self): if self._gestures is None: with self._gestures_lock: if self._gestures is None: if self.online and self.protocol >= 2.0: self._gestures = _hidpp20.get_gestures(self) or () return self._gestures @property 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 signal_configuration_complete(self, cookie=None): """SetComplete on ConfigChange to ack end of configuration. With no cookie, sends the host's session counter (see Hidpp20.set_configuration_complete).""" if self.online and self.protocol >= 2.0: _hidpp20.set_configuration_complete(self, cookie=cookie) def _record_config_cookie(self): """After a successful apply, SetComplete with the next session cookie and persist it so the dedup gate in apply_settings_if_needed can detect drift on follow-up reconfig notifications within this session.""" if self.protocol < 2.0: return if not (self.features and SupportedFeature.CONFIG_CHANGE in self.features): return cookie = _hidpp20.next_session_cookie() self.signal_configuration_complete(cookie=cookie) if self.persister is not None: self.persister["_config_cookie"] = [cookie[0], cookie[1]] def apply_settings_if_needed(self): """Cookie-gated dedup helper for repeat WIRELESS_DEVICE_STATUS reconfig notifications on an already-active device. Skips when the live ConfigChange cookie matches the value stored by the most recent apply, otherwise applies and re-records. Must NOT be used as the initial-activation apply path — across power cycles, devices whose firmware resets the cookie to a fixed value would falsely match a stored cookie from a prior session and skip the apply the device actually needs. Returns True if apply ran, False if it was skipped.""" if not self.online: return False if self.protocol >= 2.0 and self.features and SupportedFeature.CONFIG_CHANGE in self.features: live = _hidpp20.get_configuration_cookie(self) if live and len(live) >= 2: stored = self.persister.get("_config_cookie") if self.persister else None live_pair = [live[0], live[1]] if stored == live_pair: if logger.isEnabledFor(logging.INFO): logger.info( "%s: config cookie %02X%02X matches stored — skip apply_all_settings", self, live[0], live[1], ) return False if logger.isEnabledFor(logging.INFO): logger.info( "%s: config cookie live=%02X%02X stored=%s — apply_all_settings", self, live[0], live[1], "%02X%02X" % (stored[0], stored[1]) if stored else "None", ) settings.apply_all_settings(self) self._record_config_cookie() return True 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): if not self._settings: with self._settings_lock: if not self._settings: settings = [] if self.persister and self.descriptor and self.descriptor.settings: for sclass in self.descriptor.settings: try: setting = sclass.build(self) except Exception as e: # Do nothing if the device is offline setting = None if self.online: raise e if setting is not None: settings.append(setting) self._settings = settings if not self._feature_settings_checked: with self._settings_lock: if not self._feature_settings_checked: self._feature_settings_checked = settings_templates.check_feature_settings(self, self._settings) return self._settings def battery(self): # None or level, next, status, voltage if self.protocol < 2.0: if self.centurion: logger.debug( "%s: battery() dispatching HID++ 1.0 path for a Centurion device " "(protocol=%s, _protocol=%s) — device_addr probe likely failed, " "expect INVALID_SUB_ID_COMMAND", self, self.protocol, self._protocol, ) return _hidpp10.get_battery(self) else: 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, battery = result if self.persister and battery_feature is None: self.persister["_battery"] = feature.value return battery except Exception: 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) # Activation apply must be unconditional — across power # cycles, the device may have lost state while its cookie # reset to a value that happens to match what we stored # last session. Cookie comparison is only a valid dedup # signal for repeat reconfig notifications within an # already-active session (see apply_settings_if_needed). settings.apply_all_settings(self) self._record_config_cookie() 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() 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 not active and self.receiver and self.battery_info is not None and self.battery_info.level is not None: self.battery_info = Battery( self.battery_info.level, self.battery_info.next_level, BatteryStatus.OFFLINE, self.battery_info.voltage, self.battery_info.light_level, ) 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 receiver.""" if not bool(self.receiver) or self.protocol >= 2.0: return False if enable: 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: logger.warning("%s: failed to %s device notifications", self, "enable" if enable else "disable") flag_bits = _hidpp10.get_notification_flags(self) 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): """Adds the notification handling callback `fn` to this device under name `id`. If a callback has already been registered under this name, it's replaced with the argument. The callback will be invoked whenever the device emits an event message, and the resulting notification hasn't been handled by another handler on this device (order is not guaranteed, so handlers should not overlap in functionality). The callback should have type `(PairedDevice, Notification) -> Optional[bool]`. It should return `None` if it hasn't handled the notification, return `True` if it did so successfully and return `False` if an error should be reported (malformed notification, etc). """ self._notification_handlers[id] = fn def remove_notification_handler(self, id: str): """Unregisters the notification handler under name `id`.""" 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] def handle_notification(self, n) -> Optional[bool]: for h in self._notification_handlers.values(): ret = h(self, n) if ret is not None: return ret return None def request(self, request_id, *params, no_reply=False): if self: 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) ) # 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, ) if logger.isEnabledFor(logging.WARN): logger.warning( "%s: request failure for device %s %s %s", self, self.handle, self.receiver, self.receiver._devices if self.receiver else None, ) def feature_request(self, feature, function=0x00, *params, no_reply=False): if self.protocol >= 2.0: if self.centurion: # Ensure sub-device features are discovered before routing decision if self.features is not None: self.features._check() # Guard against Centurion/HID++ 2.0 feature ID collisions. IntEnum # members with the same int value hash equal, so a dict lookup for # SupportedFeature.DEVICE_NAME (0x0005) succeeds even when the # device actually has CenturionCoreFeature.MULTI_HOST_CONTROL at # that slot. If the type of the stored enum differs from what the # caller asked for, treat the feature as unsupported. if self.features is not None: idx = self.features.get(feature) if idx is not None: stored = self.features.inverse.get(idx) if stored is not None and type(stored) is not type(feature): return None 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) if logger.isEnabledFor(logging.WARN): logger.warning("%s: feature request failure for device with protocol %s", self, self.protocol) # Max sub-message bytes in the first bridge fragment (for 0x51): # 64 - 1 (report ID) - 1 (cpl_len) - 1 (flags) - 2 (bridge prefix) - 2 (bridge hdr) = 57; # one byte of conservative margin gives 56. For 0x50 the device_addr byte # eats one more, so first_chunk = 55 (handled dynamically below). _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; one byte of margin # gives 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: [report_id] (0x51 or 0x50) Layer 2: [device_addr (0x50 only),] 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 # Adjust bridge chunk sizes for 0x50 variant (device_addr byte takes 1 frame byte) cent_state = base._centurion_handles.get(int(handle)) addr_overhead = 1 if cent_state and cent_state.report_id == base.CENTURION_ADDRESSED_REPORT_ID else 0 first_chunk = self._BRIDGE_FIRST_CHUNK - addr_overhead cont_chunk = self._BRIDGE_CONT_CHUNK - addr_overhead 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 logger.isEnabledFor(logging.DEBUG): logger.debug( "bridge TX: sub_idx=%d func=0x%02X sw_id=%d payload=%s", sub_feat_idx, sub_function, sw_id, sub_params.hex() if sub_params else "", ) if sub_len <= 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. 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 = first_chunk chunk = sub_msg[offset : offset + chunk_size] layer3 = bridge_prefix + bridge_hdr + chunk else: chunk_size = 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 # The device echoes our exact sub-device function+swid byte in # MessageEvent responses. Match on that to reject cross-contamination # from late-arriving responses to other function calls on the same # feature (e.g. GetRGBZoneInfo response showing up on a later # GetHostModeState read). expected_sub_func_sw = (sub_function & 0xF0) | sw_id # 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, expected_sub_func_sw): 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) if logger.isEnabledFor(logging.DEBUG): logger.debug( "bridge skipping reply (pre-ACK): got sub_cpl=0x%02X sub_idx=0x%02X func_sw=0x%02X" " (expected idx=0x%02X func_sw=0x%02X) data=%s", reply_data[4] if len(reply_data) > 4 else 0, reply_data[5] if len(reply_data) > 5 else 0, reply_data[6] if len(reply_data) > 6 else 0, sub_feat_idx, expected_sub_func_sw, reply_data.hex(), ) 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, expected_sub_func_sw): 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) if logger.isEnabledFor(logging.DEBUG): logger.debug( "bridge skipping reply (post-ACK): got sub_cpl=0x%02X sub_idx=0x%02X func_sw=0x%02X" " (expected idx=0x%02X func_sw=0x%02X) data=%s", reply_data[4] if len(reply_data) > 4 else 0, reply_data[5] if len(reply_data) > 5 else 0, reply_data[6] if len(reply_data) > 6 else 0, sub_feat_idx, expected_sub_func_sw, reply_data.hex(), ) 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, expected_sub_func_sw=None): """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 `expected_sub_func_sw` is provided, also matches on the echoed sub-device function byte (`(function << 4) | sw_id`). This prevents cross-talk between different function calls on the SAME feature, which can happen when a late-arriving response for one function gets picked up by a later request on the same feature (observed on G522 where a GetRGBZoneInfo response contaminated a subsequent GetHostModeState). """ 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: if expected_sub_func_sw is not None and len(reply_data) >= 7: if reply_data[6] != expected_sub_func_sw: return False 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: if expected_sub_func_sw is not None and len(reply_data) >= 8: if reply_data[7] != expected_sub_func_sw: return False 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 frame layout after sub_cpl: [0xFF, orig_feat_idx, orig_func_sw, error_code, ...] orig_feat_idx = reply_data[6] if len(reply_data) > 6 else 0 orig_func_sw = reply_data[7] if len(reply_data) > 7 else 0 error_code = reply_data[8] if len(reply_data) > 8 else 0 logger.debug( "bridge sub-device error: orig_feat_idx=%d orig_func=0x%02X error=0x%02X", orig_feat_idx, orig_func_sw, 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_state = base._centurion_handles.get(int(handle)) if cent_state and cent_state.protocol_version: self._centurion_protocol = cent_state.protocol_version def ping(self): """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) ) 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._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): # Run device.cleanups before clearing self.handle — cleanup callbacks # typically need to issue final feature_request() writes (e.g. release # SW control, restore device-side state) and feature_request() relies # on self.handle being set. if hasattr(self, "cleanups"): for cleanup in self.cleanups: cleanup(self) handle, self.handle = self.handle, None if self in Device.instances: Device.instances.remove(self) return handle and self.low_level.close(handle) def __index__(self): return self.number __int__ = __index__ def __eq__(self, other): return other is not None and self._kind == other._kind and self.wpid == other.wpid def __ne__(self, other): return other is None or self.kind != other.kind or self.wpid != other.wpid def __hash__(self): return self.wpid.__hash__() def __bool__(self): return self.wpid is not None and self.number in self.receiver if self.receiver else self.handle is not None __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 exceptions.NoSuchDevice: name = "name not available" return f"" __repr__ = __str__ 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 Solaar-1.1.20/lib/logitech_receiver/device_quirks.py000066400000000000000000000101551522022367600224620ustar00rootroot00000000000000"""Per-device-model quirks for RGB lighting. Keyed by ``device.modelId``. For normal HID++ devices that is the string Logitech composes by concatenating every transport PID (btid + btleid + wpid + usbid) — one entry covers the model on any transport. For Centurion headsets it is the firmware-stable model byte (G522 ``"32"``, G325 ``"44"``); see ``device._get_ids_centurion``. Two postures, by feature class: * **Effect parameters that do NOT persist** (zone effects, LED directions) — default-ALLOW. Those are validated and low-harm (a wrong value is cosmetic and transient). They use blocklists elsewhere, e.g. ``LedDirectionBlocklist`` in ``hidpp20.py``. Nothing of that kind lives here. * **NVconfig-saved colors** (0x8071 RGBEffects boot effects, 0x0622 HeadsetRGB signature effects) — default-DENY. Those writes persist to non-volatile storage, so an unvalidated control can durably misconfigure a device. Every field is hidden and every slot suppressed unless the exact model is listed here as known-good. Setting ``SOLAAR_EXPERIMENTAL`` truthy bypasses the allowlists entirely — for testers / reverse-engineering on devices not yet validated. Entries are hand-curated; document the observation in a comment. """ from __future__ import annotations import os _ALL_NVCONFIG_FIELDS = {"color1", "color2", "speed"} def _experimental() -> bool: """True when SOLAAR_EXPERIMENTAL is set truthy — bypasses allowlist masking.""" return os.environ.get("SOLAAR_EXPERIMENTAL", "").strip().lower() in ("1", "true", "yes", "on") # Feature 0x8071 RGBEffects, Function 3 NvConfig — persistent boot/shutdown # effects. Default-DENY allowlist: modelId -> cap_id -> set of color fields # the firmware is KNOWN to honor. A listed cap shows the setting (its On/Off # toggle plus the listed color pickers); an empty set shows the toggle only. # An unlisted cap or unlisted model suppresses the setting entirely. RGB_EFFECTS_NVCONFIG_ALLOWED: dict[str, dict[int, set[str]]] = { # G502 X PLUS — startup (0x0001): color bytes are inert, only the enabled # flag is honored, so no color fields are allowed (toggle only). Shutdown # (0x0040) is not supported and is suppressed by build()-time probe anyway. "4099C0950000": {0x0001: set()}, # G515 LIGHTSPEED TKL — startup and shutdown both honor both colors. "B38940B4C355": {0x0001: {"color1", "color2"}, 0x0040: {"color1", "color2"}}, } # Feature 0x0622 HeadsetRGB signature effects — persistent startup / shutdown # / passive effects. Default-DENY allowlist: modelId -> effect_id -> set of # fields the firmware is KNOWN to honor. An unlisted effect_id or model # suppresses that signature-effect setting entirely. HEADSET_SIGNATURE_EFFECTS_ALLOWED: dict[str, dict[int, set[str]]] = { # G522 LIGHTSPEED (Centurion model byte 0x32, from DeviceInfo func 0; # confirmed against multiple diagnostic logs). Verified against user # reports: startup honors only the primary color (secondary unused, speed # has no effect); shutdown honors both colors (speed has no effect). The # passive slot (effect_id 2) is omitted — its behavior is not understood, # so the whole passive setting is suppressed. "32": { 0: {"color1"}, 1: {"color1", "color2"}, }, } def rgb_effects_nvconfig_allowed_fields(device, cap_id: int) -> set[str] | None: """Color fields to expose for an 0x8071 NvConfig boot effect. Returns the allowed field set (possibly empty — On/Off toggle only), or ``None`` to suppress the setting entirely. """ if _experimental(): return set(_ALL_NVCONFIG_FIELDS) model_id = getattr(device, "modelId", None) or "" return RGB_EFFECTS_NVCONFIG_ALLOWED.get(model_id, {}).get(cap_id) def headset_signature_allowed_fields(device, effect_id: int) -> set[str] | None: """Fields to expose for an 0x0622 signature effect slot. Returns the allowed field set, or ``None`` to suppress the slot entirely. """ if _experimental(): return set(_ALL_NVCONFIG_FIELDS) model_id = getattr(device, "modelId", None) or "" return HEADSET_SIGNATURE_EFFECTS_ALLOWED.get(model_id, {}).get(effect_id) Solaar-1.1.20/lib/logitech_receiver/diversion.py000066400000000000000000001566361522022367600216460ustar00rootroot00000000000000## Copyright (C) 2020 Peter Patel-Schneider ## ## 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 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"): evdev = None else: import evdev from .common import NamedInt from .hidpp20 import SupportedFeature from .special_keys import CONTROL gi.require_version("Gdk", "3.0") # isort:skip from gi.repository import Gdk, GLib # NOQA: E402 # isort:skip if typing.TYPE_CHECKING: from .base import HIDPPNotification logger = logging.getLogger(__name__) # # See docs/rules.md for documentation # # Several capabilities of rules depend on aspects of GDK, X11, or XKB # As the Solaar GUI uses GTK, Glib and GDK are always available and are obtained from gi.repository # # Process condition depends on X11 from python-xlib, and is probably not possible at all in Wayland # MouseProcess condition depends on X11 from python-xlib, and is probably not possible at all in Wayland # Modifiers condition depends only on GDK # KeyPress action determines whether a keysym is a currently-down modifier using get_modifier_mapping from python-xlib; # under Wayland no modifier keys are considered down so all modifier keys are pressed, potentially leading to problems # KeyPress action translates key names to keysysms using the local file described for GUI keyname determination # 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 uinput. # For uinput to work the user must have write access for /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 # and http://cgit.freedesktop.org/xorg/proto/x11proto/plain/XF86keysym.h # because there does not seem to be a non-X11 file for this set of key names # 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 # uinput and evdev - provides input simulation 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 _KEY_PRESS = 1 _BUTTON_RELEASE = 2 _BUTTON_PRESS = 3 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 logger.isEnabledFor(logging.INFO): logger.info("GDK Keymap %sset up", "" if gkeymap else "not ") wayland = os.getenv("WAYLAND_DISPLAY") # is this Wayland? if wayland: logger.warning( "rules cannot access modifier keys in Wayland, " "accessing process only works on GNOME with Solaar Gnome extension installed" ) try: _x11 = None # X11 might be available except Exception: _x11 = False # X11 is not 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 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 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") _x11 = True # X11 available if logger.isEnabledFor(logging.INFO): logger.info("X11 library loaded and display set up") except Exception: logger.warning("X11 not available - some rule capabilities inoperable", exc_info=sys.exc_info()) _x11 = False return _x11 def gnome_dbus_interface_setup(): global _dbus_interface 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") except dbus.exceptions.DBusException: logger.warning( "Solaar Gnome extension not installed - some rule capabilities inoperable", exc_info=sys.exc_info(), ) _dbus_interface = False return _dbus_interface def xkb_setup(): global X11Lib, Xkbdisplay 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)] Xkbdisplay = X11Lib.XOpenDisplay(None) if logger.isEnabledFor(logging.INFO): logger.info("XKB display set up") except Exception: 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"]), "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(): 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], } else: # Just mock these since they won't be useful without evdev anyway buttons = {} key_events = [] devicecap = {} def setup_uinput(): global udevice if udevice is not None: return udevice try: 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: logger.warning("cannot create uinput device: %s", e) def kbdgroup(): if xkb_setup(): state = XkbStateRec() X11Lib.XkbGetState(Xkbdisplay, XkbUseCoreKbd, ctypes.pointer(state)) return state.group else: return None def modifier_code(keycode): if wayland or not x11_setup() or keycode == 0: return None for m in range(0, len(modifier_keycodes)): if keycode in modifier_keycodes[m]: return m def signed(bytes_: bytes) -> int: return int.from_bytes(bytes_, "big", signed=True) def xy_direction(_x, _y): # normalize x and y m = math.sqrt((_x * _x) + (_y * _y)) if m == 0: return "noop" x = round(_x / m) y = round(_y / m) if x < 0 and y < 0: 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" elif x > 0: return "Mouse Right" elif x < 0: return "Mouse Left" elif y > 0: return "Mouse Down" elif y < 0: return "Mouse Up" else: return "noop" def simulate_uinput(what, code, arg): global udevice if setup_uinput(): try: udevice.write(what, code, arg) udevice.syn() if logger.isEnabledFor(logging.DEBUG): logger.debug("uinput simulated input %s %s %s", what, code, arg) return True except Exception as e: udevice = None logger.warning("uinput write failed: %s", e) def simulate_key(code, event): # X11 keycode but Solaar event code if evdev and simulate_uinput(evdev.ecodes.EV_KEY, code - 8, event): return True logger.warning("no way to simulate key input") def click_uinput(button, count): if isinstance(count, int): for _ in range(count): if not simulate_uinput(evdev.ecodes.EV_KEY, button[1], 1): return False if not simulate_uinput(evdev.ecodes.EV_KEY, button[1], 0): return False else: if count != RELEASE: if not simulate_uinput(evdev.ecodes.EV_KEY, button[1], 1): return False if count != DEPRESS: if not simulate_uinput(evdev.ecodes.EV_KEY, button[1], 0): return False return True def click(button, count): if click_uinput(button, count): return True logger.warning("no way to simulate mouse click") return False def simulate_scroll(dx, dy): if setup_uinput(): success = True if dx: success = simulate_uinput(evdev.ecodes.EV_REL, evdev.ecodes.REL_HWHEEL, dx) if dy and success: success = simulate_uinput(evdev.ecodes.EV_REL, evdev.ecodes.REL_WHEEL, dy) if success: return True logger.warning("no way to simulate scrolling") def thumb_wheel_up(f, r, d, a): global thumb_wheel_displacement 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]) elif thumb_wheel_displacement <= -a: thumb_wheel_displacement += a return 1 else: return False def thumb_wheel_down(f, r, d, a): global thumb_wheel_displacement 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]) elif thumb_wheel_displacement >= a: thumb_wheel_displacement -= a return 1 else: return False 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 == 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": [], } # COMPONENTS = {} class RuleComponent: def compile(self, c): if isinstance(c, RuleComponent): return c elif isinstance(c, dict) and len(c) == 1: k, v = next(iter(c.items())) if k in COMPONENTS: return COMPONENTS[k](v) logger.warning("illegal component in rule: %s", c) return Condition() 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 = f"({self.source})" if self.source else "" return f"Rule{source}[{', '.join([c.__str__() for c in self.components])}]" 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: 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]} class Condition(RuleComponent): def __init__(self, *args): pass def __str__(self): return "CONDITION" 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] self.op = op self.component = self.compile(op) def __str__(self): return f"Not: {str(self.component)}" 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()} 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) + "]" 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, last_result) if not isinstance(component, Action) and result is None: return None if isinstance(component, Condition) and result: return result return result def data(self): 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) + "]" 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]} def x11_focus_prog(): if not x11_setup(): return None pid = wm_class = None window = xdisplay.get_input_focus().focus while window: pid = window.get_full_property(NET_WM_PID, 0) wm_class = window.get_wm_class() if wm_class and pid: break window = window.query_tree().parent try: 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,) def x11_pointer_prog(): if not x11_setup(): return None pid = wm_class = None window = xdisplay.screen().root.query_pointer().child for child in reversed(window.query_tree().children): pid = child.get_full_property(NET_WM_PID, 0) 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,) 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 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 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: 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: logger.warning("rule Process argument not a string: %s", process) self.process = str(process) def __str__(self): return f"Process: {str(self.process)}" 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() result = any(bool(s and s.startswith(self.process)) for s in focus) if focus else None return result def data(self): 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: 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: logger.warning("rule MouseProcess argument not a string: %s", process) self.process = str(process) def __str__(self): return f"MouseProcess: {str(self.process)}" 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() result = any(bool(s and s.startswith(self.process)) for s in pointer_focus) if pointer_focus else None return result def data(self): return {"MouseProcess": str(self.process)} class Feature(Condition): def __init__(self, feature: str, warn: bool = True): try: self.feature = SupportedFeature[feature.replace(" ", "_")] except KeyError: self.feature = None if warn: logger.warning("rule Feature argument not name of a feature: %s", feature) def __str__(self): return f"Feature: {str(self.feature)}" 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)} class Report(Condition): def __init__(self, report, warn=True): if not (isinstance(report, int)): if warn: logger.warning("rule Report argument not an integer: %s", report) self.report = -1 else: self.report = report def __str__(self): return f"Report: {str(self.report)}" 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} # 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: 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]) 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 if dev is None: 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: 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: logger.warning("Setting condition: error when checking setting %s: %s", self.args, e) result = False return result def data(self): 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), } 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 self.modifiers = [] for k in modifiers: if k in MODIFIERS: self.desired += MODIFIERS.get(k, 0) self.modifiers.append(k) else: if warn: logger.warning("unknown rule Modifier value: %s", k) def __str__(self): return f"Modifiers: {str(self.desired)}" 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: logger.warning("no keymap so cannot determine modifier keys") return False def data(self): return {"Modifiers": [str(m) for m in self.modifiers]} class Key(Condition): DOWN = "pressed" UP = "released" def __init__(self, args, warn=True): default_key = 0 default_action = self.DOWN key, action = None, None if not args or not isinstance(args, (list, str)): if warn: logger.warning(f"rule Key arguments unknown: {args}") key = default_key action = default_action elif isinstance(args, str): logger.debug(f'rule Key assuming action "{default_action}" for "{args}"') key = args action = default_action elif isinstance(args, list): if len(args) == 1: 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] 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: 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: logger.warning(f"rule Key action unknown: {action}, assuming {default_action}") self.action = default_action def __str__(self): return f"Key: {str(self.key) if self.key else 'None'} ({self.action})" 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]} class KeyIsDown(Condition): def __init__(self, args, warn=True): default_key = 0 key = None if not args or not isinstance(args, str): if warn: 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] else: if warn: logger.warning(f"rule Key key name not name of a Logitech key: {key}") self.key = default_key def __str__(self): return f"KeyIsDown: {str(self.key) if self.key else 'None'}" 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)} def bit_test(start, end, 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) 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.parameter = None if isinstance(test, str): test = [test] if isinstance(test, list) and all(isinstance(t, int) for t in test): if warn: 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: 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: self.test = test[0] self.function = TESTS[test[0]][0] self.parameter = test[1] if len(test) > 1 else None else: if warn: logger.warning("rule Test name not name of a test: %s", test) self.test = "False" self.function = TESTS["False"][0] else: if warn: logger.warning("rule Test argument not valid %s", test) def __str__(self): return f"Test: {str(self.test)}" 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])} 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 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: logger.warning("rule TestBytes argument not valid %s", test) def __str__(self): return f"TestBytes: {str(self.test)}" 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[:]} class MouseGesture(Condition): MOVEMENTS = [ "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 warn: 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) 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 = 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]]): return False for m in self.movements[movement_offset:]: if data_offset >= len(data): return False if data[data_offset] == 0: direction = xy_direction(data[data_offset + 1], data[data_offset + 2]) if m != direction: return False data_offset += 3 elif 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]} class Active(Condition): def __init__(self, devID, warn=True): if not (isinstance(devID, str)): if warn: logger.warning("rule Active argument not a string: %s", devID) self.devID = "" self.devID = devID def __str__(self): return f"Active: {str(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} class Device(Condition): def __init__(self, devID, warn=True): if not (isinstance(devID, str)): if warn: logger.warning("rule Device argument not a string: %s", devID) self.devID = "" self.devID = devID def __str__(self): return f"Device: {str(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} class Host(Condition): def __init__(self, host, warn=True): if not (isinstance(host, str)): if warn: logger.warning("rule Host Name argument not a string: %s", host) self.host = "" self.host = host def __str__(self): return f"Host: {str(self.host)}" 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} class Action(RuleComponent): def __init__(self, *args): pass def evaluate(self, feature, notification: HIDPPNotification, device, last_result): return None 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: 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: logger.warning("rule KeyPress keys not key names %s", self.key_names) self.key_symbols = [] def regularize_args(self, args): action = CLICK if not isinstance(args, list): args = [args] keys = args if len(args) == 2 and args[1] in [CLICK, DEPRESS, RELEASE]: keys = [args[0]] if isinstance(args[0], str) else args[0] action = args[1] return keys, action def __str__(self): return "KeyPress: " + " ".join(self.key_names) + " " + self.action def needed(self, k, modifiers): code = modifier_code(k) return not (code is not None and modifiers & (1 << code)) def mods(self, level, modifiers, direction): if level == 2 or level == 3: (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, _) = 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) = keysym_to_keycode(k, modifiers) if keycode is None: 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) = 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: HIDPPNotification, device, last_result): if gkeymap: current = gkeymap.get_modifier_state() 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) else: 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]} # KeyDown is dangerous as the key can auto-repeat and make your system unusable # class KeyDown(KeyPress): # def evaluate(self, feature, notification: HIDPPNotification, device, last_result): # super().keyDown(self.keys, current_key_modifiers) # class KeyUp(KeyPress): # 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): 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: 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]) 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 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) return None def data(self): 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] if not isinstance(args, list): args = [args] self.button = str(args[0]) if len(args) >= 0 else None if self.button not in buttons: if warn: logger.warning("rule MouseClick action: button %s not known", self.button) self.button = None count = args[1] if len(args) >= 2 else 1 try: self.count = int(count) except (ValueError, TypeError): if count in [CLICK, DEPRESS, RELEASE]: self.count = count elif warn: logger.warning( "rule MouseClick action: argument %s should be an integer or click, depress, or release", count, ) self.count = 1 def __str__(self): return f"MouseClick: {self.button} ({str(self.count)})" 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) return None def data(self): 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: 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: HIDPPNotification, device, last_result): if len(self.args) < 3: return None 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: 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: 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: logger.warning( "Set Action: invalid args %s for setting %s of %s", self.args[2:], self.args[1], self.args[0], ) return None 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[:]} 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: 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]) 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[:]} class Later(Action): def __init__(self, args, warn=True): self.delay = 0 self.rule = Rule([]) self.components = [] if not (isinstance(args, list)): args = [args] if not (isinstance(args, list) and len(args) >= 1): if warn: 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: 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 f"Later: [{str(self.delay)}, " + ", ".join(str(c) for c in self.components) + "]" def evaluate(self, feature, notification: HIDPPNotification, device, last_result): if self.delay and self.rule: 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} 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, } 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: 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))) return key in keys_down 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) 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 notification.address == 0x00: if feature == SupportedFeature.REPROG_CONTROLS_V4: new_keys_down = struct.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 == SupportedFeature.GKEY: new_g_keys_down = struct.unpack(" Rule: loaded_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() Solaar-1.1.20/lib/logitech_receiver/exceptions.py000066400000000000000000000032761522022367600220140ustar00rootroot00000000000000## 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 Solaar-1.1.20/lib/logitech_receiver/headset_rgb.py000066400000000000000000000175001522022367600220750ustar00rootroot00000000000000## 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. """Shared helpers for devices exposing Feature 0x0620 HEADSET_RGB_HOSTMODE. The G522 is currently the only Solaar-supported device advertising this feature, but anything else presenting 0x0620 will pick the same code path automatically. The module deliberately avoids G522-specific assumptions so future RGB-capable headsets can reuse it. Two entry points the settings templates rely on: - `discover_zones(device)` — one-shot zone enumeration run at setting build time. Briefly claims Solaar host control so GetRGBZoneInfo returns a non-empty zone list, then restores the previous host-mode state. Result is cached on the device. - `write_zone_map(device, zone_color_map)` — the shared write path used by both the "LEDs Primary" and "Per-zone Lighting" settings. Groups zones by final RGB color and emits one SetRgbZonesSingleValue per unique color, then a single FrameEnd to commit. """ from __future__ import annotations import logging from typing import Iterable from .hidpp20_constants import SupportedFeature logger = logging.getLogger(__name__) # Function IDs on Feature 0x0620 we actually use. FN_GET_RGB_ZONE_INFO = 0x10 FN_SET_RGB_ZONES_SINGLE = 0x50 FN_FRAME_END = 0x60 FN_GET_HOST_MODE_STATE = 0x70 FN_SET_HOST_MODE_STATE = 0x80 # Frame type sent with FrameEnd. 0x01 = transient commit (re-applies on the # next refresh). 0x02 would be persistent, but G522 firmware rejects it # with LOGITECH_INTERNAL (0x05) unless an onboard profile precondition we # haven't mapped yet is satisfied. FRAME_TYPE_TRANSIENT = 0x01 _HOST_MODE_SOLAAR = 1 _HOST_MODE_DEVICE = 0 def _device_cache_attr() -> str: return "_headset_rgb_zone_ids" def _read_host_mode(device) -> int | None: """Read the current host-mode state byte, or None on any failure.""" try: resp = device.feature_request(SupportedFeature.HEADSET_RGB_HOSTMODE, FN_GET_HOST_MODE_STATE) except Exception as e: logger.debug("headset_rgb: GetHostModeState raised %s", e) return None if not resp or len(resp) < 1: return None return resp[0] def _set_host_mode(device, value: int) -> bool: try: device.feature_request(SupportedFeature.HEADSET_RGB_HOSTMODE, FN_SET_HOST_MODE_STATE, bytes([value & 0xFF])) except Exception as e: logger.debug("headset_rgb: SetHostModeState(%d) raised %s", value, e) return False return True def _parse_zone_info(resp: bytes) -> list[int]: """Parse a GetRGBZoneInfo response into a zone-id list. Two formats observed: "tight" ([count, zone_ids...]) on G522, and the canonical protocol-doc layout (3-byte gap + 1-byte reserved before zone IDs). Both are tried; whichever yields exactly `count` IDs wins. Zone id 0 isn't filtered — some devices may use it. """ if not resp or len(resp) < 1: return [] zone_count = resp[0] tight = list(resp[1 : 1 + zone_count]) if 1 <= zone_count <= len(resp) - 1 else [] if tight and len(tight) == zone_count: return tight gap = list(resp[5 : 5 + zone_count]) if len(resp) >= 5 + zone_count else [] if gap and len(gap) == zone_count: return gap return [] def discover_zones(device) -> list[int] | None: """Return the list of RGB zone IDs on `device`, or None on failure. Caches the result on `device._headset_rgb_zone_ids` so subsequent callers don't repeat the round-trip. Briefly claims Solaar host mode if needed — GetRGBZoneInfo has been observed to return count=0 when the device is still under firmware control — and restores the prior state afterward so user-configured onboard effects resume. """ cached = getattr(device, _device_cache_attr(), None) if cached: return cached if not getattr(device, "online", False): return None prior_mode = _read_host_mode(device) claimed = False if prior_mode != _HOST_MODE_SOLAAR: if not _set_host_mode(device, _HOST_MODE_SOLAAR): return None claimed = True try: try: resp = device.feature_request(SupportedFeature.HEADSET_RGB_HOSTMODE, FN_GET_RGB_ZONE_INFO) except Exception as e: logger.debug("headset_rgb: GetRGBZoneInfo raised %s", e) return None zones = _parse_zone_info(bytes(resp) if resp else b"") if not zones: logger.debug( "headset_rgb: GetRGBZoneInfo returned no zones (raw=%s)", resp.hex() if resp else resp, ) return None logger.debug("headset_rgb: discovered %d zone(s) %s", len(zones), [f"0x{z:02X}" for z in zones]) setattr(device, _device_cache_attr(), zones) return zones finally: if claimed and prior_mode is not None: _set_host_mode(device, prior_mode) def _split_rgb(color_int: int) -> tuple[int, int, int]: return (color_int >> 16) & 0xFF, (color_int >> 8) & 0xFF, color_int & 0xFF def write_zone_map(device, zone_color_map: dict) -> bool: """Apply a zone->RGB mapping to the device. `zone_color_map` maps zone id (int) to 24-bit RGB color (int, `(r<<16)|(g<<8)|b`). Claims host mode, groups zones by color, emits one SetRgbZonesSingleValue per unique color, then a single FrameEnd. Returns True on success, False on any transport error. """ if not zone_color_map: return False if not getattr(device, "online", False): logger.debug("headset_rgb: device offline, skipping write") return False # Group zones by color for batched writes. groups: dict[int, list[int]] = {} for zone, color in zone_color_map.items(): groups.setdefault(int(color), []).append(int(zone)) try: _set_host_mode(device, _HOST_MODE_SOLAAR) for color_int, zones in groups.items(): r, g, b = _split_rgb(color_int) # SetRgbZonesSingleValue: [R, G, B, count, zone_ids...] payload = bytes([r, g, b, len(zones)]) + bytes(zones) device.feature_request(SupportedFeature.HEADSET_RGB_HOSTMODE, FN_SET_RGB_ZONES_SINGLE, payload) logger.debug( "headset_rgb: set (%02X,%02X,%02X) on %d zone(s) %s", r, g, b, len(zones), [f"0x{z:02X}" for z in zones], ) # FrameEnd commits the pending per-zone updates. Transient commit # only — persistent (0x02) requires onboard-profile preconditions # that aren't mapped yet. device.feature_request( SupportedFeature.HEADSET_RGB_HOSTMODE, FN_FRAME_END, bytes([FRAME_TYPE_TRANSIENT, 0x00, 0x00, 0x00]), ) except Exception as e: logger.warning("headset_rgb: write_zone_map failed: %s", e) return False return True def zone_named_ints(zones: Iterable[int]): """Build a list of NamedInt keys suitable for a ChoicesMap setting. Factored out so settings code can import without pulling common.NamedInt at module-load time if preferred. """ from . import common return [common.NamedInt(int(z), f"Zone {int(z)}") for z in zones] Solaar-1.1.20/lib/logitech_receiver/hidpp10.py000066400000000000000000000246061522022367600211000ustar00rootroot00000000000000## 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 __future__ import annotations import logging from typing import Any from typing_extensions import Protocol 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 logger = logging.getLogger(__name__) 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) & 0x2FF) return device.request(request_id, *params) 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) & 0x2FF) return device.request(request_id, *value) 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]) 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 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 # 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: 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 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]) charging_byte = ord(reply[1:2]) 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 Battery(charge, None, status_text, None) Solaar-1.1.20/lib/logitech_receiver/hidpp10_constants.py000066400000000000000000000200511522022367600231620ustar00rootroot00000000000000## 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 Solaar-1.1.20/lib/logitech_receiver/hidpp20.py000066400000000000000000003026601522022367600211000ustar00rootroot00000000000000## 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 import socket import struct import threading from collections import UserDict from enum import Flag from enum import IntEnum from random import getrandbits from typing import Any from typing import Dict from typing import Generator from typing import Optional from typing import Tuple 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 .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 logger = logging.getLogger(__name__) FixedBytes5 = bytes KIND_MAP = {kind: hidpp10_constants.DEVICE_KIND[str(kind)] for kind in DEVICE_KIND} 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: ... # 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 # 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) -> 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, struct.pack("!H", SupportedFeature.FEATURE_SET)) if reply is not None: fs_index = reply[0] if fs_index: count = self.device.request(fs_index << 8) if count is None: logger.warning("FEATURE_SET found, but failed to read features count") return False else: 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 _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, version] feat_id = struct.unpack("!H", response[1:3])[0] feat_type = response[3] if len(response) > 3 else 0 feat_version = response[4] if len(response) > 4 else 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 # Record version/flags so version-gated settings (sidetone, auto-sleep) # use the correct payload format on direct USB Centurion devices too. self.version[feature] = feat_version self.flags[feature] = feat_type if logger.isEnabledFor(logging.DEBUG): logger.debug( "Centurion parent feature: %s at index %d, version=%d, flags=0x%02X", feature, index, feat_version, feat_type, ) 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 per-index queries: GetCount (func 0) returns total count, then GetFeatureId (func 1) returns one feature per call. Avoids the single-frame truncation of bulk queries — a Centurion frame is 64 bytes so a bulk reply can only fit ~13 features regardless of how many the sub-device actually has. """ # 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 # Query feature count (function 0 = GetCount). Response: [count, ...]. count_resp = self.device.centurion_bridge_request(sub_fs_index, 0x00) if count_resp is None or len(count_resp) < 1: logger.warning("Failed to read Centurion sub-device feature count") return total_count = count_resp[0] if logger.isEnabledFor(logging.DEBUG): logger.debug("Centurion sub-device: FeatureSet reports %d features", total_count) # Per-index query: GetFeatureId (function 1 = 0x10). # Response: [remaining, feat_hi, feat_lo, type, version]. # We now also record `type` (flags) and `version` for each feature so # version-gated settings (sidetone, auto-sleep, etc.) can use the # correct payload format instead of defaulting to V0. sub_feat_idx = 0 for idx in range(total_count): response = self.device.centurion_bridge_request(sub_fs_index, 0x10, idx) if response is None or len(response) < 3: logger.debug("Centurion sub-device: no response at index %d", idx) continue feat_id = struct.unpack("!H", response[1:3])[0] feat_type = response[3] if len(response) > 3 else 0 feat_version = response[4] if len(response) > 4 else 0 try: feature = SupportedFeature(feat_id) except ValueError: feature = f"unknown:{feat_id:04X}" self.device._centurion_sub_indices[feature] = sub_feat_idx if dict.get(self, feature) is None: dict.__setitem__(self, feature, sub_feat_idx) self.device._centurion_sub_features.add(feature) self.sub_inverse[sub_feat_idx] = feature # Record version/flags so downstream settings can version-gate their # payload format. get_feature_version(feature) reads self.version[feature]. self.version[feature] = feat_version self.flags[feature] = feat_type if logger.isEnabledFor(logging.DEBUG): logger.debug( "Centurion sub-device feature: %s at sub-index %d, version=%d, flags=0x%02X", feature, sub_feat_idx, feat_version, feat_type, ) sub_feat_idx += 1 self._sub_feature_count = sub_feat_idx if logger.isEnabledFor(logging.DEBUG): logger.debug("Centurion sub-device: discovered %d features total", sub_feat_idx) def get_feature(self, index: int) -> SupportedFeature | None: feature = self.inverse.get(index) if feature is not None: return feature # Sub-device index; bridge unwrap offsets by 0x100 (see listener). if index >= 0x100: return self.sub_inverse.get(index - 0x100) elif self._check(): 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: 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) 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: NamedInt) -> Optional[int]: if self[feature]: return self.version.get(feature, 0) def get_flags(self, feature: NamedInt) -> Optional[int]: if self[feature]: return self.flags.get(feature, 0) 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(): index = super().get(feature) if index is not None: return index # Centurion devices enumerate all features upfront in _check_centurion(). # If the feature isn't in the dict after _check(), it genuinely doesn't # exist — skip the raw ROOT.GetFeature query that the dongle rejects # with LOGITECH_ERROR and that creates cycling log spam during settings init. if getattr(self.device, "centurion", False): return None 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 isinstance(super().get(feature), int): self.inverse.pop(super().get(feature)) super().__setitem__(feature, index) if index is not False: self.inverse[index] = feature def __delitem__(self, feature): raise ValueError("Don't delete features from FeatureArray") def __len__(self) -> int: return self.count + getattr(self, "_sub_feature_count", 0) __bool__ = __nonzero__ = _check class ReprogrammableKey: """Information about a control present on a device with the `REPROG_CONTROLS` feature. Read-only properties: - 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: Device, index: int, cid: int, task_id: int, flags: int): self._device = device self.index = index self._cid = cid self._tid = task_id self._flags = flags @property def key(self) -> NamedInt: return special_keys.CONTROL[self._cid] @property 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`.""" 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) -> KeyFlag: return KeyFlag(self._flags) class ReprogrammableKeyV4(ReprogrammableKey): """Information about a control present on a device with the `REPROG_CONTROLS_V4` feature. Ref (v2): https://lekensteyn.nl/files/logitech/x1b04_specialkeysmsebuttons.html Ref (v4): https://drive.google.com/file/d/10imcbmoxTJ1N510poGdsviEhoFfB_Ua4/view Contains all the functionality of `ReprogrammableKey` plus remapping keys and /diverting/ them in order to handle keypresses in a custom way. Additional read-only properties: - pos {int} -- position of this control on the device; 1-16 for FN-keys, otherwise 0 - 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 - mapping_flags {List[str]} -- mapping flags set on the control """ 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 self._mapping_flags = None self._mapped_to = None @property def group_mask(self) -> Generator[str]: return common.flag_names(special_keys.CIDGroupBit, self._gmask) @property def mapped_to(self) -> NamedInt: if self._mapped_to is None: self._getCidReporting() self._device.keys._ensure_all_keys_queried() 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): self._device.keys._ensure_all_keys_queried() 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.CidGroup[str(g)] for tgt_cid in self._device.keys.group_cids[g]: 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) -> MappingFlag: if self._mapping_flags is None: self._getCidReporting() return MappingFlag(self._mapping_flags) def set_diverted(self, value: bool) -> None: """If set, the control is diverted temporarily and reports presses as HID++ events.""" flags = {MappingFlag.DIVERTED: value} self._setCidReporting(flags=flags) def set_persistently_diverted(self, value: bool) -> None: """If set, the control is diverted permanently and reports presses as HID++ events.""" flags = {MappingFlag.PERSISTENTLY_DIVERTED: value} self._setCidReporting(flags=flags) 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 = {MappingFlag.RAW_XY_DIVERTED: value} self._setCidReporting(flags=flags) def remap(self, to: NamedInt): """Temporarily remaps this control to another action.""" self._setCidReporting(remap=int(to)) def _getCidReporting(self): try: 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 = 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,) = struct.unpack("!B", mapped_data[5:6]) else: mapping_flags_2 = 0 self._mapping_flags = mapping_flags_1 | (mapping_flags_2 << 8) else: 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: 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 # The capability required to set a given reporting flag. FLAG_TO_CAPABILITY = { 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 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 |= 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 activated: self._mapping_flags |= mapping_flag.value else: self._mapping_flags &= ~mapping_flag.value if remap != 0 and remap not in self.remappable_to: 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(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 = 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: def __init__(self, device, index, cid, actionId, remapped, modifierMask, cidStatus): self._device = device self.index = index self._cid = cid self.actionId = actionId self.remapped = remapped self._modifierMask = modifierMask self.cidStatus = cidStatus @property def key(self) -> NamedInt: return special_keys.CONTROL[self._cid] @property 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 f"Key: {str(self.modifiers)}{str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Mouse: return f"Mouse Button: {str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Xdisp: return f"X Displacement {str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Ydisp: return f"Y Displacement {str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Vscroll: return f"Vertical Scroll {str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Hscroll: return f"Horizontal Scroll: {str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Consumer: return f"Consumer: {str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Internal: return f"Internal Action {str(self.remapped)}" elif self.actionId == special_keys.ACTIONID.Internal: return f"Power {str(self.remapped)}" else: return "Unknown" @property def modifiers(self): return special_keys.modifiers[self._modifierMask] @property def data_bytes(self): return ( common.int2bytes(self.actionId, 1) + common.int2bytes(self.remapped, 2) + common.int2bytes(self._modifierMask, 1) ) def remap(self, data_bytes): 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.remapped, self._modifierMask = struct.unpack("!BHB", data_bytes) self.cidStatus = 0x01 self._device.feature_request(SupportedFeature.PERSISTENT_REMAPPABLE_ACTION, 0x40, cid, 0xFF, data_bytes) return True class KeysArray: """A sequence of key mappings supported by a HID++ 2.0 device.""" def __init__(self, device, count, version): assert device is not None self.device = device 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 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 _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): if k is None: self._query_key(i) def __getitem__(self, index): if isinstance(index, int): if index < 0 or index >= len(self.keys): raise IndexError(index) if self.keys[index] is None: self._query_key(index) return self.keys[index] elif isinstance(index, slice): indices = index.indices(len(self.keys)) return [self.__getitem__(i) for i in range(*indices)] def index(self, value): self._ensure_all_keys_queried() for index, k in enumerate(self.keys): if k is not None and int(value) == int(k.key): return index def __iter__(self): for k in range(0, len(self.keys)): yield self.__getitem__(k) def __len__(self): return len(self.keys) 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". When remapping controls, we point the control we want to remap at a target Control ID rather than a target Task ID. This has the effect of performing the native task of the target control, even if the target itself is also remapped. So remapping is not recursive.""" 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.CidGroup} def _query_key(self, index: int): if index < 0 or index >= len(self.keys): raise IndexError(index) keydata = self.device.feature_request(SupportedFeature.REPROG_CONTROLS, 0x10, index) if keydata: 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(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 = self.device.feature_request(SupportedFeature.REPROG_CONTROLS_V4, 0x10, index) if keydata: 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, 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.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 @property def capabilities(self): if self._capabilities is None and self.device.online: 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 = self.device.feature_request(SupportedFeature.PERSISTENT_REMAPPABLE_ACTION, 0x20, index, 0xFF) if keydata: 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: remapped = special_keys.USB_HID_KEYCODES[remapped] elif actionId == special_keys.ACTIONID.Mouse: remapped = special_keys.MOUSE_BUTTONS[remapped] elif actionId == special_keys.ACTIONID.Hscroll: 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 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") 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" def __str__(self): return self.id def __repr__(self): return self.id 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"),), } 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 = GestureId(low) self.can_be_enabled = high & 0x01 self.can_be_diverted = high & 0x02 self.show_in_ui = high & 0x04 self.desired_software_default = high & 0x08 self.persistent = high & 0x10 self.default_enabled = high & 0x20 self.index = next_index if self.can_be_enabled or self.default_enabled else None self.diversion_index = next_diversion_index if self.can_be_diverted else None self._enabled = None self._diverted = None def _offset_mask(self, index): # offset and mask if index is not None: offset = index >> 3 # 8 gestures per byte mask = 0x1 << (index % 8) return offset, mask else: return None, None def enable_offset_mask(self): return self._offset_mask(self.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 = self._device.feature_request(SupportedFeature.GESTURE_2, 0x10, offset, 0x01, mask) self._enabled = bool(result[0] & mask) if result else None return self._enabled def set(self, enable): # enable or disable the gesture if not self.can_be_enabled: return None if self.index is not None: offset, mask = self.enable_offset_mask() 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 = self._device.feature_request(SupportedFeature.GESTURE_2, 0x30, offset, 0x01, mask) self._diverted = bool(result[0] & mask) if result else None return self._diverted def divert(self, diverted): # divert or undivert the gesture if not self.can_be_diverted: return None if self.diversion_index is not None: offset, mask = self.diversion_offset_mask() reply = self._device.feature_request( SupportedFeature.GESTURE_2, 0x40, offset, 0x01, mask, mask if diverted else 0x00, ) return reply def as_int(self): return self.gesture def __int__(self): return self.id def __repr__(self): return f"" # allow a gesture to be used as a settings reader/writer to enable and disable the gesture read = enabled write = set class Param: def __init__(self, device, low: int, high, next_param_index): self._device = device self.id = 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 = next_param_index @property def sub_params(self): return SUB_PARAM.get(self.id, None) @property def value(self): return self._value if self._value is not None else self.read() def read(self): # returns the bytes for the parameter result = self._device.feature_request(SupportedFeature.GESTURE_2, 0x70, self.index, 0xFF) if result: self._value = common.bytes2int(result[: self.size]) return self._value @property def default_value(self): if self._default_value is None: self._default_value = self._read_default() return self._default_value def _read_default(self): result = self._device.feature_request(SupportedFeature.GESTURE_2, 0x60, self.index, 0xFF) if result: self._default_value = common.bytes2int(result[: self.size]) return self._default_value def write(self, bytes): self._value = bytes return self._device.feature_request(SupportedFeature.GESTURE_2, 0x80, self.index, bytes, 0xFF) def __str__(self): return str(self.param) def __int__(self): return self.id class Spec: def __init__(self, device, low: int, high): self._device = device self.id = low try: self.spec = SpecGesture(low) except ValueError: self.spec = f"unknown:{low:04X}" self.byte_count = high & 0x0F self._value = None @property def value(self): if self._value is None: self._value = self.read() return self._value def read(self): try: 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 common.bytes2int(value[: self.byte_count]) def __repr__(self): return f"[{self.spec}={self.value}]" class Gestures: """Information about the gestures that a device supports. Right now only some information fields are supported. WARNING: Assumes that parameters are always global, which is not the case. """ def __init__(self, device): self.device = device self.gestures = {} self.params = {} self.specs = {} 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 = device.feature_request(SupportedFeature.GESTURE_2, 0x00, index >> 8, index & 0xFF) if not fields: break for offset in range(8): field_high = fields[offset * 2] field_low = fields[offset * 2 + 1] if field_high == 0x1: # end of fields break elif field_high & 0x80: gesture = Gesture(device, field_low, field_high, next_gesture_index, next_divsn_index) next_gesture_index = next_gesture_index if gesture.index is None else next_gesture_index + 1 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, next_param_index) next_param_index = next_param_index + 1 self.params[param.param] = param elif field_high == 0x04: if field_low != 0x00: 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: logger.warning(f"Unimplemented GESTURE_2 field {field_low} {field_high} found.") index += 1 def gesture(self, gesture): return self.gestures.get(gesture, None) def gesture_enabled(self, gesture): # is the gesture enabled? g = self.gestures.get(gesture, None) return g.enabled() if g else None def enable_gesture(self, gesture): g = self.gestures.get(gesture, None) return g.set(True) if g else None def disable_gesture(self, gesture): g = self.gestures.get(gesture, 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.read() if g else None def set_param(self, param, value): g = self.params.get(param, 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): if device.online and device.features: if feature in device.features: feature_index = device.features[feature] return device.request((feature_index << 8) + (function & 0xFF), *params, no_reply=no_reply) if logger.isEnabledFor(logging.WARN): logger.warning( "%s: feature request failure for device online %s and features %s", device, device.online, device.features ) class Hidpp20: # Host-side counter for SetComplete cookies (see set_configuration_complete). # Seeded to a non-zero random 16-bit value at import so successive sessions # don't trivially collide; we just need to never send 0x0000. _session_cookie = getrandbits(16) or 1 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 = device.feature_request(SupportedFeature.DEVICE_FW_VERSION) if count: count = ord(count[:1]) 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: logger.error("failed to read whole name of %s (expected %d chars)", device, name_length) return None return name.decode("utf-8") def get_name_centurion(self, device): return _centurion.get_name_centurion(device) 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]) 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 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_battery_unified(self, device: Device): report = device.feature_request(SupportedFeature.UNIFIED_BATTERY, 0x10) if report is not 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 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 get_configuration_cookie(self, device: Device): """ConfigChange (0x0020) GetCookie — read the device's current configuration cookie.""" response = device.feature_request(SupportedFeature.CONFIG_CHANGE, 0x00) return response[:2] if response else None def next_session_cookie(self): """Bump and return the host-side counter used as the SetComplete cookie.""" Hidpp20._session_cookie = (Hidpp20._session_cookie + 1) & 0xFFFF or 1 return bytes([Hidpp20._session_cookie >> 8, Hidpp20._session_cookie & 0xFF]) def set_configuration_complete(self, device: Device, cookie=None, no_reply=False): """ConfigChange (0x0020) SetComplete — acknowledge host has synced with device configuration. Sends a host-side monotonic counter, incremented per call and always non-zero. Cookie 0x0000 has been observed to release the SW effect-engine claim on at least the G515 LS TKL; we avoid it.""" if cookie is None: cookie = self.next_session_cookie() if cookie and len(cookie) >= 2: return device.feature_request(SupportedFeature.CONFIG_CHANGE, 0x10, cookie[0], cookie[1], no_reply=no_reply) def config_change(self, device: Device, configuration, no_reply=False): """Deprecated — use set_configuration_complete() instead.""" return device.feature_request(SupportedFeature.CONFIG_CHANGE, 0x10, configuration, no_reply=no_reply) battery_functions = { 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 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: 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 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 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) # --- OnboardEQ (0x0636) — re-exported from onboard_eq.py --- # --- AdvancedParaEQ (0x020D) — re-exported from advanced_para_eq.py --- from .advanced_para_eq import FILTER_TYPE_HP # noqa: E402, F401 from .advanced_para_eq import FILTER_TYPE_PEAKING # noqa: E402, F401 from .advanced_para_eq import FILTER_TYPE_PEAKING_G522 # noqa: E402, F401 from .advanced_para_eq import get_advanced_eq_active_slot # noqa: E402, F401 from .advanced_para_eq import get_advanced_eq_defaults # noqa: E402, F401 from .advanced_para_eq import get_advanced_eq_friendly_name # noqa: E402, F401 from .advanced_para_eq import get_advanced_eq_info # noqa: E402, F401 from .advanced_para_eq import get_advanced_eq_params # noqa: E402, F401 from .advanced_para_eq import parse_v2_bands # noqa: E402, F401 from .advanced_para_eq import probe_advanced_eq_slots # noqa: E402, F401 from .advanced_para_eq import probe_all_presets as probe_advanced_eq_presets # noqa: E402, F401 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 Solaar-1.1.20/lib/logitech_receiver/hidpp20_constants.py000066400000000000000000000263201522022367600231700ustar00rootroot00000000000000## 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, ) Solaar-1.1.20/lib/logitech_receiver/i18n.py000066400000000000000000000042441522022367600204060ustar00rootroot00000000000000## 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. # Translation support for the Logitech receivers library import gettext _ = 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"), # battery charging statuses _("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"), # firmware kinds _("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"), # key/button statuses _("pressed"), _("released"), ) Solaar-1.1.20/lib/logitech_receiver/listener.py000066400000000000000000000155061522022367600214570ustar00rootroot00000000000000## 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. import dataclasses import logging import queue import threading from . import base from . import exceptions 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") def __init__(self, listener, path, handle): assert listener is not None assert path is not None assert handle is not None assert isinstance(handle, int) self._listener = listener self.path = path 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) if handle is None: logger.error("%r failed to open new handle", self) else: # if logger.isEnabledFor(logging.DEBUG): # logger.debug("%r opened new handle %d", self, handle) # If original handle was centurion, copy state to new per-thread handle for h in self._handles: if h in base._centurion_handles: base._centurion_handles[handle] = dataclasses.replace(base._centurion_handles[h]) break self._local.handle = handle self._handles.append(handle) return handle def close(self): if self._local: self._local = None handles, self._handles = self._handles, [] if logger.isEnabledFor(logging.DEBUG): logger.debug("%r closing %s", self, handles) for h in handles: base.close(h) @property def notifications_hook(self): if self._listener: assert isinstance(self._listener, threading.Thread) if threading.current_thread() == self._listener: return self._listener._notifications_hook def __del__(self): self._listener = None self.close() def __index__(self): if self._local: try: return self._local.handle except Exception: return self._open() else: return -1 __int__ = __index__ def __str__(self): if self._local: return str(int(self)) else: return "None" def __repr__(self): return f"<_ThreadedHandle({self.path})>" def __bool__(self): return bool(self._local) __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.0 # in seconds 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] except IndexError: path_name = receiver.path super().__init__(name=f"{self.__class__.__name__}:{path_name}") self.daemon = True self._active = False self.receiver = receiver 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 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.changed(active=True, reason="initialization") while self._active: if self._queued_notifications.empty(): try: 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: 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: logger.exception("processing %s", n) del self._queued_notifications self.has_stopped() def stop(self): """Tells the listener to stop as soon as possible.""" self._active = False def has_started(self): """Called right after the thread has started, and before it starts reading notification packets.""" pass def has_stopped(self): """Called right before the thread stops.""" 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 logger.isEnabledFor(logging.DEBUG): # logger.debug("queueing unhandled %s", n) if not self._queued_notifications.full(): self._queued_notifications.put(n) def __bool__(self): return bool(self._active and self.receiver) __nonzero__ = __bool__ Solaar-1.1.20/lib/logitech_receiver/logivoice.py000066400000000000000000000302301522022367600216010ustar00rootroot00000000000000## 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. """LogiVoice (0x0900 + 0x0901-0x0907) read helpers. Each LogiVoice processing module exposes the same 5-function API: fn 0 SetState fn 1 GetState -> u8 state (boolean) fn 2 SetParameters fn 3 GetParameters -> module-specific payload (see PARAMETERS_FIELDS) fn 4 GetInfo -> per-field [min, max] bounds (see parse_info) All multi-byte integers on the wire are big-endian. Parameters layouts are module-specific; PARAMETERS_FIELDS encodes per-field offset / width / signedness / range / label metadata. The first field is at offset 0 — there is no leading "state" byte (the state toggle is on fn 0/1 only). Writes are NOT implemented yet. State toggles via fn 0x00/0x10 are shipping as boolean settings; per-field Parameters writes need a live round-trip verification before they're safe to expose. """ from __future__ import annotations import logging import struct from typing import Iterable from .hidpp20_constants import SupportedFeature logger = logging.getLogger(__name__) # Wire function IDs (standard across all LogiVoice modules). FN_SET_STATE = 0x00 FN_GET_STATE = 0x10 FN_SET_PARAMETERS = 0x20 FN_GET_PARAMETERS = 0x30 FN_GET_INFO = 0x40 # Human-readable names for the modules Solaar may see on a LogiVoice device. MODULE_NAMES = { SupportedFeature.LOGIVOICE: "LogiVoice", SupportedFeature.LOGIVOICE_NOISE_REDUCTION: "Noise Reduction", SupportedFeature.LOGIVOICE_NOISE_GATE: "Noise Gate", SupportedFeature.LOGIVOICE_COMPRESSOR: "Compressor", SupportedFeature.LOGIVOICE_DE_ESSER: "De-esser", SupportedFeature.LOGIVOICE_DE_POPPER: "De-popper", SupportedFeature.LOGIVOICE_LIMITER: "Limiter", SupportedFeature.LOGIVOICE_HIGH_PASS_FILTER: "High Pass Filter", } # Short slugs used in Solaar setting IDs (`logivoice--`). MODULE_SLUGS = { SupportedFeature.LOGIVOICE_NOISE_REDUCTION: "nr", SupportedFeature.LOGIVOICE_NOISE_GATE: "ng", SupportedFeature.LOGIVOICE_COMPRESSOR: "comp", SupportedFeature.LOGIVOICE_DE_ESSER: "deesser", SupportedFeature.LOGIVOICE_DE_POPPER: "depopper", SupportedFeature.LOGIVOICE_LIMITER: "limiter", SupportedFeature.LOGIVOICE_HIGH_PASS_FILTER: "hpf", } class Field: """Metadata for one decoded Parameters field. offset: byte offset within the GetParameters payload. byte_count: width (1 or 2 for fields we currently decode). signed: whether to interpret as signed int. min_value/max_value: range for the Solaar slider validator. For opaque fields, use the full representable range (0..255 or 0..65535). label: human-readable name for UI. opaque: True if the field's wire encoding isn't pinned down — label shows raw units and the caller should treat as round-trip. """ def __init__(self, name, offset, byte_count, signed, min_value, max_value, label, opaque=False): self.name = name self.offset = offset self.byte_count = byte_count self.signed = signed self.min_value = min_value self.max_value = max_value self.label = label self.opaque = opaque # Per-module field layout for GetParameters / SetParameters payload. Each # module's struct is the union of named fields below; there is no separate # "state" byte at offset 0 — that toggle is only on fn 0x00/0x10. Field # encodings (signedness, byte order, units) and value ranges come from the # device's GetInfo response (see parse_info) and are confirmed against # captured bring-up bytes; ranges hardcoded here are the bounds the device # reports and the values it ships as factory defaults. # # `opaque=True` is reserved for fields whose unit scale isn't pinned down # (currently width_q on De-esser / De-popper — the host-side scale constant # is loaded at runtime and not statically resolvable). Treat opaque values # as monotonic raw integers until a live probe anchors the units. PARAMETERS_FIELDS: dict[SupportedFeature, list[Field]] = { SupportedFeature.LOGIVOICE_NOISE_REDUCTION: [ Field("sensitivity", 0, 1, False, 0, 40, "Sensitivity"), Field("release", 1, 2, False, 1, 1000, "Release (ms)"), Field("bias", 3, 1, False, 0, 5, "Bias"), Field("attenuation", 4, 1, True, -20, 0, "Attenuation (dB)"), ], SupportedFeature.LOGIVOICE_NOISE_GATE: [ Field("threshold", 0, 1, True, -60, -35, "Threshold (dB)"), Field("attenuation", 1, 1, True, -50, -3, "Attenuation (dB)"), Field("attack", 2, 2, False, 1, 200, "Attack (ms)"), Field("hold", 4, 2, False, 1, 1000, "Hold (ms)"), Field("release", 6, 2, False, 1, 1000, "Release (ms)"), ], SupportedFeature.LOGIVOICE_COMPRESSOR: [ Field("threshold", 0, 1, True, -40, 0, "Threshold (dB)"), Field("attack", 1, 2, False, 1, 200, "Attack (ms)"), Field("release", 3, 2, False, 50, 1000, "Release (ms)"), Field("post_gain", 5, 1, True, -12, 12, "Post Gain (dB)"), Field("pre_gain", 6, 1, True, -12, 12, "Pre Gain (dB)"), # Ratio reports min=1 max=20 from GetInfo; whether the device interprets # it as a literal X:1 ratio or a curve-table index is unconfirmed. Field("ratio", 7, 1, False, 1, 20, "Ratio"), ], SupportedFeature.LOGIVOICE_DE_ESSER: [ Field("threshold", 0, 1, True, -50, 0, "Threshold (dB)"), Field("frequency", 1, 2, False, 1000, 10000, "Frequency (Hz)"), # width_q is a Q-format quantization with a device-loaded scale we # don't know; range/default come straight from GetInfo. Field("width_q", 3, 1, False, 2, 120, "Width/Q", opaque=True), Field("attack", 4, 2, False, 1, 200, "Attack (ms)"), Field("release", 6, 2, False, 20, 1000, "Release (ms)"), Field("attenuation", 8, 1, True, -40, 0, "Attenuation (dB)"), ], SupportedFeature.LOGIVOICE_DE_POPPER: [ Field("threshold", 0, 1, True, -50, 0, "Threshold (dB)"), Field("frequency", 1, 2, False, 60, 500, "Frequency (Hz)"), Field("width_q", 3, 1, False, 2, 120, "Width/Q", opaque=True), Field("attack", 4, 2, False, 1, 200, "Attack (ms)"), Field("release", 6, 2, False, 20, 1000, "Release (ms)"), Field("attenuation", 8, 1, True, -40, 0, "Attenuation (dB)"), ], SupportedFeature.LOGIVOICE_LIMITER: [ Field("boost", 0, 1, True, -128, 127, "Boost (dB)"), Field("attack", 1, 2, False, 1, 65535, "Attack (ms)"), Field("release", 3, 2, False, 1, 65535, "Release (ms)"), ], SupportedFeature.LOGIVOICE_HIGH_PASS_FILTER: [ Field("frequency", 0, 2, False, 60, 300, "Cutoff (Hz)"), ], } def expected_payload_length(feature: SupportedFeature) -> int: fields = PARAMETERS_FIELDS.get(feature) if not fields: return 0 return max(f.offset + f.byte_count for f in fields) def get_state(device, feature: SupportedFeature): """Read the module's on/off state via fn 1. Returns int 0-255 or None.""" result = device.feature_request(feature, FN_GET_STATE) if result is None or len(result) < 1: return None return result[0] def get_parameters(device, feature: SupportedFeature): """Read the module's Parameters struct via fn 3. Returns raw bytes or None.""" result = device.feature_request(feature, FN_GET_PARAMETERS) if result is None: return None return bytes(result) def get_info(device, feature: SupportedFeature): """Read module capability info via fn 4. Returns raw bytes or None. Decoded per-field bounds are available via parse_info(). """ result = device.feature_request(feature, FN_GET_INFO) if result is None: return None return bytes(result) def _decode_field(chunk: bytes, byte_count: int, signed: bool) -> int: """Decode `byte_count` bytes from `chunk` as an integer per the field's wire encoding. Multi-byte values are big-endian (matches Parameters).""" if byte_count == 1: return struct.unpack("b" if signed else "B", chunk[:1])[0] if byte_count == 2: return struct.unpack(">h" if signed else ">H", chunk[:2])[0] return int.from_bytes(chunk[:byte_count], "big", signed=signed) def parse_info(feature: SupportedFeature, payload: bytes) -> dict: """Decode a GetInfo response into per-field {min, max} bounds. Layout: for each field in PARAMETERS_FIELDS in order, the payload carries [min_value, max_value] back-to-back using the field's wire encoding (so a u16 field contributes 4 bytes — 2 for min, 2 for max). Trailing bytes in the response are pad/zero. Returns a dict mapping field name to {"min": int, "max": int}. Fields that don't fit in the payload are omitted. """ fields = PARAMETERS_FIELDS.get(feature) if not fields or not payload: return {} out = {} offset = 0 for f in fields: end = offset + 2 * f.byte_count if end > len(payload): break min_val = _decode_field(payload[offset : offset + f.byte_count], f.byte_count, f.signed) max_val = _decode_field(payload[offset + f.byte_count : end], f.byte_count, f.signed) out[f.name] = {"min": min_val, "max": max_val} offset = end return out def parse_parameters(feature: SupportedFeature, payload: bytes) -> dict: """Decode Parameters bytes into a dict per the per-module field table. Returns {} on unknown feature or short payload — caller still has the raw hex via get_parameters() for corpus logging. """ fields = PARAMETERS_FIELDS.get(feature) if not fields or payload is None: return {} parsed = {} for f in fields: end = f.offset + f.byte_count if end > len(payload): continue chunk = payload[f.offset : end] if f.byte_count == 1: val = struct.unpack("b" if f.signed else "B", chunk)[0] elif f.byte_count == 2: val = struct.unpack(">h" if f.signed else ">H", chunk)[0] else: val = int.from_bytes(chunk, "big", signed=f.signed) parsed[f.name] = val return parsed def probe_module(device, feature: SupportedFeature) -> None: """One-shot corpus probe. Logs state + raw parameters + parsed + raw info + decoded info bounds.""" name = MODULE_NAMES.get(feature, f"0x{int(feature):04X}") state = get_state(device, feature) params = get_parameters(device, feature) info = get_info(device, feature) logger.debug( "LogiVoice %s [0x%04X]: state=%s parameters=%s info=%s", name, int(feature), state, params.hex() if params else None, info.hex() if info else None, ) parsed = parse_parameters(feature, params) if params else {} if parsed: logger.debug("LogiVoice %s parsed: %s", name, parsed) bounds = parse_info(feature, info) if info else {} if bounds: logger.debug("LogiVoice %s info bounds: %s", name, bounds) def probe_all_modules(device, features: Iterable[SupportedFeature]) -> None: """Probe every LogiVoice module present on the device. Call once at device-bring-up so the -dd corpus has a full snapshot. Caller passes whichever subset of LogiVoice features are actually discovered (usually derived from device.features). """ for feature in features: if feature not in PARAMETERS_FIELDS and feature != SupportedFeature.LOGIVOICE: continue try: probe_module(device, feature) except Exception as e: logger.debug("LogiVoice probe_module(%s) raised %s", feature, e) Solaar-1.1.20/lib/logitech_receiver/notifications.py000066400000000000000000000654211522022367600225040ustar00rootroot00000000000000## 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. """Handles incoming events from the receiver/devices, updating the object as appropriate. """ from __future__ import annotations import logging import struct import threading import typing from solaar.i18n import _ from . import base from . import common from . import diversion from . import hidpp10 from . import hidpp10_constants from . import hidpp20 from . import rgb_power 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 if typing.TYPE_CHECKING: from .base import HIDPPNotification from .device import Device from .receiver import Receiver logger = logging.getLogger(__name__) NotificationHandler = typing.Callable[["Receiver", "HIDPPNotification"], bool] _hidpp10 = hidpp10.Hidpp10() _hidpp20 = hidpp20.Hidpp20() notification_lock = threading.Lock() def process(device: Device | Receiver, notification: HIDPPNotification): """Handle incoming events (notification) from device or receiver.""" assert device assert notification if not device.isDevice: 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_device_notification(device: Device, notification: HIDPPNotification): """Process event messages from devices.""" # incoming packets with SubId >= 0x80 are supposedly replies from HID++ 1.0 requests, should never get here assert notification.sub_id & 0x80 == 0 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(notification) if handling_ret is not None: return handling_ret # 0x40 to 0x7F appear to be HID++ 1.0 or DJ notifications 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, 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. 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, notification) # assuming 0x00 to 0x3F are feature (HID++ 2.0) notifications if not device.features: logger.warning("%s: feature notification but features not set up: %02X %s", device, notification.sub_id, notification) return False return _process_feature_notification(device, notification) def _process_dj_notification(device: Device, notification: HIDPPNotification): if logger.isEnabledFor(logging.DEBUG): logger.debug("%s (%s) DJ %s", device, device.protocol, notification) if notification.sub_id == Notification.CONNECT_DISCONNECT: # do all DJ paired notifications also show up as HID++ 1.0 notifications? if logger.isEnabledFor(logging.INFO): logger.info("%s: ignoring DJ unpaired: %s", device, notification) return True if notification.sub_id == Notification.DJ_PAIRING: # do all DJ paired notifications also show up as HID++ 1.0 notifications? if logger.isEnabledFor(logging.INFO): logger.info("%s: ignoring DJ paired: %s", device, notification) return True 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 logger.warning("%s: unrecognized DJ %s", device, notification) 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 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 logger.warning("%s: unrecognized %s", device, notification) def _process_hidpp10_notification(device: Device, notification: HIDPPNotification): if notification.sub_id == Notification.CONNECT_DISCONNECT: # device unpairing if notification.address == 0x02: # device un-paired device.wpid = None if device.number in device.receiver: del device.receiver[device.number] device.changed(active=False, alert=Alert.ALL, reason=_("unpaired")) ## device.status = None else: logger.warning("%s: disconnection with unknown type %02X: %s", device, notification.address, notification) return True 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 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 notification.address == 0x10 # Bolt protocol always encrypted else: logger.warning( "%s: connection notification with unknown protocol %02X: %s", device.number, notification.address, notification ) return True if wpid != device.wpid: 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), ) 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 notification.sub_id == Notification.RAW_INPUT: # raw input event? just ignore it # 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 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: logger.warning("%s: unknown %s", device, notification) return True logger.warning("%s: unrecognized %s", device, notification) 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 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: logger.warning("%s: unknown BATTERY %s", device, notification) elif feature == SupportedFeature.BATTERY_VOLTAGE: if notification.address == 0x00: device.set_battery_info(hidpp20.decipher_battery_voltage(notification.data)[1]) else: logger.warning("%s: unknown VOLTAGE %s", device, notification) elif feature == SupportedFeature.UNIFIED_BATTERY: if notification.address == 0x00: device.set_battery_info(hidpp20.decipher_battery_unified(notification.data)[1]) else: logger.warning("%s: unknown UNIFIED BATTERY %s", device, notification) 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: logger.warning("%s: unknown ADC MEASUREMENT %s", device, notification) 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 = 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 = 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(SupportedFeature.SOLAR_DASHBOARD) # trigger a new report chain reports_count = 15 reports_period = 2 # seconds device.feature_request(SupportedFeature.SOLAR_DASHBOARD, 0x00, reports_count, reports_period) else: logger.warning("%s: unknown SOLAR CHARGE %s", device, notification) else: logger.warning("%s: SOLAR CHARGE not GOOD? %s", device, notification) 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 alert = Alert.NONE device.changed(active=True, alert=alert, reason=reason) # changed(active=True) already runs apply_settings_if_needed on # the first transition; for follow-up reconfig notifications # on an already-active device, fire the gate here so the # cookie comparison decides whether to re-push. device.apply_settings_if_needed() else: logger.warning("%s: unknown WIRELESS %s", device, notification) 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 logger.isEnabledFor(logging.INFO): logger.info("%s: TOUCH MOUSE status: button_down=%s mouse_lifted=%s", device, button_down, mouse_lifted) else: logger.warning("%s: unknown TOUCH MOUSE %s", device, notification) # TODO: what are REPROG_CONTROLS_V{2,3}? elif feature == SupportedFeature.REPROG_CONTROLS: if notification.address == 0x00: if logger.isEnabledFor(logging.INFO): logger.info("%s: reprogrammable key: %s", device, notification) else: logger.warning("%s: unknown REPROG_CONTROLS %s", device, notification) 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 == 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 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 if device.setting_callback: device.setting_callback(device, settings_templates.ScrollRatchet, [2 if ratchet else 1]) else: if logger.isEnabledFor(logging.INFO): logger.info("%s: unknown WHEEL %s", device, notification) 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]) elif feature == SupportedFeature.RGB_EFFECTS: fn = notification.address >> 4 if fn == 1: # onUserActivity: type=0 is IDLE, type!=0 is ACTIVE activity_type = notification.data[0] if notification.data else 0xFF rgb_power.on_user_activity(device, activity_type) if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: RGB_EFFECTS notification addr=%02x: %s", device, notification.address, notification) elif feature == SupportedFeature.HEADSET_ADVANCED_PARA_EQ: # G522 emits change events with the same payload shape as the # corresponding setter request: # fn 0 — band change (3-byte header [dir, slot, pad] + bands) # fn 2 — friendly-name change (header + nameLen + name) # fn 3 — UUID change (header + 16-byte UUID) # Low nibble of `address` is the swid the firmware echoes back — # match on the function index only. fn = notification.address >> 4 if fn == 0: info = getattr(device, "_advanced_eq_info", None) payload = notification.data[3:] if notification.data else b"" if info and len(payload) >= 5: bands = hidpp20.parse_v2_bands(b"\x00" + payload, info) if bands and device.setting_callback: band_map = {i: int(round(g)) for i, (_t, _f, g) in enumerate(bands)} device.setting_callback(device, settings_templates.HeadsetAdvancedEQ, [band_map]) elif logger.isEnabledFor(logging.DEBUG): logger.debug( "%s: HEADSET_ADVANCED_PARA_EQ band-change event with no parseable payload %s", device, notification ) elif fn in (2, 3) and logger.isEnabledFor(logging.DEBUG): logger.debug("%s: HEADSET_ADVANCED_PARA_EQ fn=%d change event %s", device, fn, notification) elif logger.isEnabledFor(logging.DEBUG): logger.debug("%s: unknown HEADSET_ADVANCED_PARA_EQ %s", device, notification) elif feature == SupportedFeature.HEADSET_MIC_MUTE: # G522 emits state-change events on two function indices, both carrying # the new state in data[0] (0 = unmuted, 1 = muted): # fn 0 — physical mute switch press # fn 1 — echo following a host-driven SetState (fn 2) write # Low nibble of `address` is the swid the firmware echoes back, which # varies with the request — match on the function index only. fn = notification.address >> 4 if fn in (0, 1) and notification.data: muted = bool(notification.data[0]) if device.setting_callback: device.setting_callback(device, settings_templates.HeadsetMicMute, [muted]) elif logger.isEnabledFor(logging.DEBUG): logger.debug("%s: unknown HEADSET_MIC_MUTE %s", device, notification) 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 Solaar-1.1.20/lib/logitech_receiver/onboard_eq.py000066400000000000000000000154151522022367600217420ustar00rootroot00000000000000## 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 # Opaque bytes observed between band params and coefficient header. First # byte matches band_count; bytes 2-3 look like LE16 coeff blob size. Keep # verbatim until a device counter-example forces a re-derivation. _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 before quantization 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) Solaar-1.1.20/lib/logitech_receiver/receiver.py000066400000000000000000000600111522022367600214250ustar00rootroot00000000000000## 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 errno import logging import time import typing from dataclasses import dataclass from typing import Callable from typing import Optional from typing import Protocol 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 if typing.TYPE_CHECKING: from logitech_receiver import common 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 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, 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 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._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) if logger.isEnabledFor(logging.INFO): logger.info("%s: init receiver: handle %s, path %s, serial %s", self, self.handle, self.path, self.serial) 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): if logger.isEnabledFor(logging.INFO): logger.info("%s: closing - handle %s %s", self, type(self.handle), self.handle) handle, self.handle = self.handle, None for _n, d in self._devices.items(): if d: d.close() self._devices.clear() 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) -> tuple[common.FirmwareInfo]: if self._firmware is None and self.handle: self._firmware = _hidpp10.get_firmware(self) return self._firmware # 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(Registers.RECEIVER_CONNECTION) if ps is not None: self._remaining_pairings = extract_remaining_pairings(ps) return self._remaining_pairings def enable_connection_notifications(self, enable=True): """Enable or disable device (dis)connection notifications on this receiver.""" if not self.handle: return False if enable: 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: logger.warning("%s: failed to %s receiver notifications", self, "enable" if enable else "disable") return None flag_bits = _hidpp10.get_notification_flags(self) 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): codename = self.read_register(Registers.RECEIVER_INFO, InfoSubRegisters.DEVICE_NAME + n - 1) if codename: return extract_codename(codename) def notify_devices(self): """Scan all devices.""" if self.handle: 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(f"{self}: device number {int(number)} already registered") assert notification is None or notification.devnumber == number assert notification is None or notification.sub_id == Notification.DJ_PAIRING try: 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 exceptions.NoSuchDevice as e: logger.warning("register new device failed for %s device %d error %s", e.receiver, e.number, e.error) 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(Registers.RECEIVER_PAIRING, action, device, timeout) if reply: return True logger.warning("%s: failed to %s the receiver lock", self, "close" if lock_closed else "open") def count(self): 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 self.low_level.request(self.handle, 0xFF, request_id, *params) def reset_pairing(self): self.pairing = Pairing() def __iter__(self): connected_devices = self.count() found_devices = 0 for number in range(1, 8): # some receivers have devices past their max # devices if found_devices >= connected_devices: return if number in self._devices: dev = self._devices[number] else: dev = self.__getitem__(number) if dev is not None: found_devices += 1 yield dev def __getitem__(self, key): if not bool(self): return None dev = self._devices.get(key) if dev is not None: return dev if not isinstance(key, int): raise TypeError("key must be an integer") if key < 1 or key > 15: # some receivers have devices past their max # devices raise IndexError(key) return self.register_new_device(key) def __delitem__(self, key): self._unpair_device(key, False) def _unpair_device(self, key, force=False): key = int(key) if self._devices.get(key) is None: raise IndexError(key) dev = self._devices[key] if not dev: if key in self._devices: del self._devices[key] return if self.re_pairs and not force: # invalidate the device, but these receivers don't unpair per se dev.online = False dev.wpid = None if key in self._devices: del self._devices[key] logger.warning("%s removed device %s", self, dev) else: 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 logger.isEnabledFor(logging.INFO): logger.info("%s unpaired device %s", self, dev) else: 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]) def __contains__(self, dev): if isinstance(dev, int): return self._devices.get(dev) is not None return self.__contains__(dev.number) 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 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 "<%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 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 logger.isEnabledFor(logging.INFO): logger.info("create receiver %s %s", handle, device_info) 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) Solaar-1.1.20/lib/logitech_receiver/rgb_effects_probe.py000066400000000000000000000205431522022367600232670ustar00rootroot00000000000000"""Read-only corpus probe for the headset RGB feature pair: - HEADSET_RGB_ONBOARD_EFFECTS (0x0621) - HEADSET_RGB_SIGNATURE_EFFECTS (0x0622) Logs raw response bytes and lengths at INFO so field testers without ``-dd`` can still capture the data. All calls are strictly read-side — no setters are invoked. If a feature isn't present the probe short-circuits cleanly. Pcap analysis of G HUB's color-set traffic confirmed that on 0x0621, ``setRGBClusterEffect`` (fn 0x30) takes a 10-byte payload ``[cluster, effect_id_BE_u16, R, G, B, ...]`` where ``effect_id=0x0000`` means "Static (with RGB)" — this is also the slot-0 entry in the fn 0x10 ``getRGBClusterInfo`` reply, which we decode structurally so the test corpus shows effect-id semantics in plaintext. """ from __future__ import annotations import logging from . import exceptions from .hidpp20_constants import SupportedFeature logger = logging.getLogger(__name__) def _hex_or_none(data) -> str | None: return data.hex() if data else None def _format_feature(feat) -> str: """Render a feature for the log: 0x{id:04X}{:NAME} when known, else raw. Unknown features are stored as the string "unknown:HHHH" by the feature discovery code, so handle that shape explicitly — int(feat) on those raises ValueError. Wrap the rest in a broad except so a future unhandled feature shape can't kill the whole table dump. """ if feat is None: return "?" if isinstance(feat, str): if feat.startswith("unknown:") and len(feat) > 8: return f"0x{feat[8:].upper()}" return feat try: return f"0x{int(feat):04X}:{feat.name}" except (AttributeError, TypeError, ValueError): try: return f"0x{int(feat):04X}" except (TypeError, ValueError): return repr(feat) def _log_feature_table(device) -> None: if not device.features: return try: # Parent features live in FeaturesArray.inverse, indexed by their # parent feature-set position. On Centurion devices these are the # ones the dongle itself exposes (typically 5-6 entries). parent = [] for idx in range(len(device.features)): parent.append(f"{idx}:{_format_feature(device.features[idx])}") logger.debug("RGB probe: parent features for %s: %s", device, ", ".join(parent)) # Centurion sub-device features live in FeaturesArray.sub_inverse, # keyed by sub-device feature index. These are where the actual # headset features (0x0620/0x0621/0x0622, LogiVoice, EQ, mic mute, # …) live — without dumping them the log shows only the dongle's # parent features and gives the wrong impression that the device # has nothing else. sub_inverse = getattr(device.features, "sub_inverse", None) if sub_inverse: sub = [f"{idx}:{_format_feature(feat)}" for idx, feat in sorted(sub_inverse.items())] logger.debug("RGB probe: sub-device features for %s: %s", device, ", ".join(sub)) except Exception as e: logger.debug("RGB probe: feature-table dump failed: %s", e) def _call(device, feature: SupportedFeature, fn: int, *params): """Wrap feature_request with uniform INFO logging. Returns the raw bytes on success, None on transport/no-feature, and doesn't raise — FeatureCallError is caught and logged as an error code. """ label = f"0x{int(feature):04X}.fn{fn:02X}" if params: label += "(" + ",".join(f"{b:02X}" for b in params) + ")" try: resp = device.feature_request(feature, fn, *params) except exceptions.FeatureCallError as e: logger.debug("RGB probe: %s err=0x%02X", label, getattr(e, "error", 0) & 0xFF) return None except Exception as e: logger.debug("RGB probe: %s raised %r", label, e) return None if resp is None: logger.debug("RGB probe: %s no reply (feature unsupported or transport failure)", label) return None logger.debug("RGB probe: %s resp=%s len=%d", label, _hex_or_none(resp), len(resp)) return resp # Names for known effect_ids on the headset RGB cluster. Confirmed via # pcap analysis of G HUB color-set traffic: setRGBClusterEffect with # effect_id=0x0000 + RGB writes a static color, so 0x0000 is "Static" # rather than the "Off / Disabled" we'd guessed from cluster ordering. # Other ids haven't been observed on the wire yet — names are placeholder # until further pcap traffic confirms them. _EFFECT_ID_NAMES = { 0x0000: "Static", 0x0001: "Effect 0x0001", 0x0006: "Effect 0x0006", 0x0007: "Effect 0x0007", 0x000F: "Effect 0x000F", 0x007F: "Effect 0x007F", } def _decode_cluster_info(resp) -> str | None: """Decode a 0x0621 fn 0x10 getRGBClusterInfo reply into a readable summary. Best-effort — returns None on unexpected length/shape. Observed shape on G522: 4-byte records (effect_id LE u16, slot_idx LE u16). The effect_id at slot 0 is 0x0000 = "Static" (with RGB), confirmed by pcap of G HUB color-set traffic. Records continue until trailing-zero padding. Note: most HID++ multi-byte fields are BE, but this particular response uses LE — confirmed against captured factory-default bytes on G522 where the values 0x0001 / 0x000F / 0x007F appear at byte 0 of each record with byte 1 = 0x00 (consistent with LE u16). """ if not resp or len(resp) < 4: return None effects = [] seen_static = False for i in range(0, len(resp) - 3, 4): eid = resp[i] | (resp[i + 1] << 8) slot = resp[i + 2] | (resp[i + 3] << 8) # Skip purely-zero padding once we've seen the (effect=0, slot=0) entry. if eid == 0 and slot == 0: if seen_static: continue seen_static = True name = _EFFECT_ID_NAMES.get(eid, f"0x{eid:04X}") effects.append(f"slot={slot}:{name}") return ", ".join(effects) if effects else None def probe_onboard_effects(device) -> None: """Probe 0x0621 RGBOnboardEffects read-side functions.""" feature = SupportedFeature.HEADSET_RGB_ONBOARD_EFFECTS if not device.features or feature not in device.features: return logger.debug("RGB probe: 0x0621 HEADSET_RGB_ONBOARD_EFFECTS present on %s", device) # fn 0x00 getInfo — empty payload _call(device, feature, 0x00) # fn 0x10 getRGBClusterInfo — iterate cluster indexes 0..7, stop on error. for cluster_idx in range(8): resp = _call(device, feature, 0x10, cluster_idx) if resp is None: break decoded = _decode_cluster_info(resp) if decoded: logger.debug("RGB probe: 0x0621.fn10(%02X) decoded: %s", cluster_idx, decoded) # fn 0x20 getRGBClusterEffect — current state per cluster. for cluster_idx in range(8): resp = _call(device, feature, 0x20, cluster_idx) if resp is None: break # fn 0x40 getRGBCustomEffectName — single call, documented. _call(device, feature, 0x40) def probe_signature_effects(device) -> None: """Probe 0x0622 RGBSignatureEffects read-side functions.""" feature = SupportedFeature.HEADSET_RGB_SIGNATURE_EFFECTS if not device.features or feature not in device.features: return logger.debug("RGB probe: 0x0622 HEADSET_RGB_SIGNATURE_EFFECTS present on %s", device) # fn 0x00 getSignatureEffectsInfo. _call(device, feature, 0x00) # fn 0x10 getSignatureEffectParams — iterate effectId 0..2 (Startup/Shutdown/Passive). # effectId is u16 BE. for eid in range(3): _call(device, feature, 0x10, (eid >> 8) & 0xFF, eid & 0xFF) # fn 0x30 getSignatureEffectState — same effectId range. for eid in range(3): _call(device, feature, 0x30, (eid >> 8) & 0xFF, eid & 0xFF) def probe(device) -> None: """Run both read-only RGB-effects probes once per device. Gated via ``_rgb_effects_probed`` so re-entry on reconnect / setting rebuild doesn't spam the log with duplicate corpus dumps. """ if getattr(device, "_rgb_effects_probed", False): return device._rgb_effects_probed = True _log_feature_table(device) try: probe_onboard_effects(device) except Exception as e: logger.debug("RGB probe: onboard-effects probe raised %r", e) try: probe_signature_effects(device) except Exception as e: logger.debug("RGB probe: signature-effects probe raised %r", e) Solaar-1.1.20/lib/logitech_receiver/rgb_power.py000066400000000000000000001222251522022367600216150ustar00rootroot00000000000000## 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. """Software-driven RGB power management for devices that hand off LED control to the host (RGB_EFFECTS / 0x8071). Handles the firmware onUserActivity events, the two-stage idle effect (smooth dim ramp or animation), and the software sleep timer that fires after idle_timeout has elapsed. """ from __future__ import annotations import logging from time import sleep from . import exceptions from . import hidpp20_constants from . import settings from . import special_keys from .hidpp20_constants import SupportedFeature logger = logging.getLogger(__name__) try: from gi.repository import GLib _has_glib = True except ImportError: _has_glib = False # SetSWControl flag bits for RGB_EFFECTS (0x8071). FLAG_EFFECT = 0x01 FLAG_POWER = 0x02 FLAG_NVCONFIG = 0x04 # SetSWControl payloads: [subfn=set, mode=3, flags] SW_ACTIVE = bytes([0x01, 0x03, FLAG_NVCONFIG]) # firmware monitors idle SW_IDLE = bytes([0x01, 0x03, FLAG_POWER]) # firmware monitors activity SW_RELEASE = bytes([0x01, 0x00, 0x00]) _managers = {} # keyed by id(device) def get_manager(device): """Return the active RGBPowerManager for `device`, or None.""" return _managers.get(id(device)) def on_user_activity(device, activity_type): """Dispatch firmware onUserActivity events to the device's power manager.""" mgr = _managers.get(id(device)) if mgr: mgr.on_user_activity(activity_type) def translate_color_for_display(color, state, dim_pct, dim_step, dim_steps): """Map a saved (undimmed) color to the display color for `state`. Returns None for SLEEPING.""" if state == RGBPowerManager.ACTIVE: return color if state == RGBPowerManager.SLEEPING: return None target = RGBPowerManager._compute_dim_color(color, dim_pct) if state == RGBPowerManager.IDLE: return target # DIMMING — interpolate from saved toward dimmed target by ramp progress. t = (dim_step / dim_steps) if dim_steps else 1.0 return RGBPowerManager._interpolate_color(color, target, t) def translate_for_device(device, color): """Translate `color` through the device's RGBPowerManager state, or return it unchanged when no manager is registered. None signals SLEEPING.""" mgr = _managers.get(id(device)) if mgr is None: return color return mgr.translate_color(color) _EFFECT_STATIC = 0x01 def perkey_has_paint(device): """Return ``(perkey_setting, has_paint)``. has_paint is True when the per-key buffer has at least one real color and the user hasn't opted out via the lock icon (sensitivity == IGNORE). The locked-but-applied state (False) still counts as paint.""" perkey = None for s in getattr(device, "settings", []) or []: if s.name == "per-key-lighting": perkey = s break if perkey is None: return None, False validator = getattr(perkey, "_validator", None) choices = getattr(validator, "choices", None) if not choices: return perkey, False # Apply path runs rgb_zone_ before per-key, so _value may still be None # when this gate is consulted — fall back to the persister. value = getattr(perkey, "_value", None) persister = getattr(device, "persister", None) if value is None and persister is not None: value = persister.get("per-key-lighting") if not value: return perkey, False no_change = special_keys.COLORSPLUS["No change"] if not any(c != no_change and isinstance(c, int) and c >= 0 for c in value.values()): return perkey, False if persister is not None and persister.get_sensitivity("per-key-lighting") == settings.SENSITIVITY_IGNORE: return perkey, False return perkey, True def zone_effect_is_static(device): """True when the persisted zone effect is Static, or when no rgb_zone_* setting exists at all (per-key-only hardware).""" has_zone = False persister = getattr(device, "persister", None) for s in getattr(device, "settings", []) or []: if s.name.startswith("rgb_zone_"): has_zone = True value = getattr(s, "_value", None) if value is None and persister is not None: value = persister.get(s.name) if value is not None and int(getattr(value, "ID", 0) or 0) == _EFFECT_STATIC: return True return not has_zone def zone_effect_is_ignored(device): """True when every rgb_zone_* setting on `device` is marked SENSITIVITY_IGNORE in the persister.""" persister = getattr(device, "persister", None) if persister is None: return False zones = [s for s in getattr(device, "settings", []) or [] if s.name.startswith("rgb_zone_")] if not zones: return False return all(persister.get_sensitivity(s.name) == settings.SENSITIVITY_IGNORE for s in zones) def effective_zone_base_color(device): """Color to use for per-key unset cells: 0 (off/black) when the zone effect is ignored (or unavailable), the persisted zone color otherwise. Reads through the persister so we still get the saved color even before apply has populated _value. During an idle-Static transition the saved color is substituted with the idle effect's color so unset cells track the idle primary. Reverts on wake when state returns to ACTIVE.""" if zone_effect_is_ignored(device): return 0 mgr = _managers.get(id(device)) if mgr is not None and mgr._state == RGBPowerManager.IDLE and mgr._idle_effect_id() == 0x01: return int(getattr(mgr._idle_effect, "color", 0) or 0) persister = getattr(device, "persister", None) for s in getattr(device, "settings", []) or []: if not s.name.startswith("rgb_zone_"): continue value = getattr(s, "_value", None) if value is None and persister is not None: value = persister.get(s.name) if value is not None: color = getattr(value, "color", None) if isinstance(color, int): return int(color) return 0 _RETRY_BUSY_BACKOFF_MS = (30, 60, 90) def feature_request_acked(device, feature, function, data=b"", retries=3): """feature_request with BUSY/timeout retries. Returns reply bytes on ACK, None on hard failure (logged WARNING).""" busy_attempt = 0 max_busy = len(_RETRY_BUSY_BACKOFF_MS) for attempt in range(retries + 1): try: reply = device.feature_request(feature, function, data) except exceptions.FeatureCallError as e: if getattr(e, "error", None) == hidpp20_constants.ErrorCode.BUSY and busy_attempt < max_busy: delay_ms = _RETRY_BUSY_BACKOFF_MS[busy_attempt] busy_attempt += 1 if logger.isEnabledFor(logging.DEBUG): logger.debug( "%s: feature 0x%04x fn 0x%02x BUSY, retry %d/%d after %dms", device, int(feature), function, busy_attempt, max_busy, delay_ms, ) sleep(delay_ms / 1000.0) continue logger.warning("%s: feature 0x%04x fn 0x%02x rejected: %s", device, int(feature), function, e) return None if reply is not None: if (attempt > 0 or busy_attempt > 0) and logger.isEnabledFor(logging.DEBUG): logger.debug( "%s: feature 0x%04x fn 0x%02x succeeded after %d timeout retries, %d BUSY retries", device, int(feature), function, attempt, busy_attempt, ) return reply if logger.isEnabledFor(logging.DEBUG): logger.debug( "%s: feature 0x%04x fn 0x%02x timed out (attempt %d/%d)", device, int(feature), function, attempt + 1, retries + 1, ) logger.warning("%s: feature 0x%04x fn 0x%02x no ACK after %d attempts", device, int(feature), function, retries + 1) return None def _probe_tmpl_bytes(device): """GetEffectSpecificInfo page 1: returns (tmpl_0, tmpl_1) or (None, None) if the device has no firmware effect cards.""" try: reply = device.feature_request(SupportedFeature.RGB_EFFECTS, 0x00, 0xFF, 0x00, 0x01, 0x00, 0x01) except exceptions.FeatureCallError: return (None, None) if reply is None or len(reply) < 12: return (None, None) return (reply[10], reply[11]) def push_artanis_perkey_prep(device): """Disable the firmware effects engine on mice with firmware effect cards. Returns True if the call ACKed.""" infos = getattr(device, "led_effects", None) if not infos or not infos.zones: return False num_effects = len(infos.zones[0].effects) # SetEffectByIndex: cluster + effectIdx + 10 param bytes + persist. # Shipping with call 2 only — sufficient on tested hardware (G502 X PLUS). # Call 1 (TMPL-handshake) left commented for reactivation if broader # testing turns up a device that needs it; uncomment the _probe_tmpl_bytes # use and the call1 block together. # tmpl_0, tmpl_1 = _probe_tmpl_bytes(device) # if tmpl_0 is None: # return False # call1 = b"\xff\x02" + b"\x00" * 6 + bytes([tmpl_0, tmpl_1]) + b"\x00\x00" + b"\x01" # if feature_request_acked(device, SupportedFeature.RGB_EFFECTS, 0x10, call1) is None: # return False call2 = b"\xff" + bytes([num_effects]) + b"\x00" * 10 + b"\x01" return feature_request_acked(device, SupportedFeature.RGB_EFFECTS, 0x10, call2) is not None def start(device): """Begin software RGB power management for `device`. No-op without GLib.""" if not _has_glib: return key = id(device) if key not in _managers: mgr = RGBPowerManager(device) _managers[key] = mgr mgr.start() else: mgr = _managers[key] mgr.reset() # Push persisted settings into the manager. Settings marked ignore via the # lock icon are skipped so the manager keeps its built-in default. from . import hidpp20 persister = getattr(device, "persister", None) def _ignored(name): return persister is not None and persister.get_sensitivity(name) == settings.SENSITIVITY_IGNORE for s in device.settings: if _ignored(s.name): continue if s.name == "rgb_idle_timeout": val = s._value if s._value is not None else 60 mgr.set_idle_timeout(int(val)) elif s.name == "rgb_sleep_timeout": val = s._value if s._value is not None else 300 mgr.set_sleep_timeout(int(val)) elif s.name == "rgb_idle_effect": val = s._value if s._value is not None else hidpp20.LEDEffectSetting(ID=0x80, intensity=50) mgr.set_idle_effect(val) def stop(device): """End software RGB power management for `device`.""" key = id(device) mgr = _managers.pop(key, None) if mgr: mgr.stop() def cleanup(device): """device.cleanups handler — restore firmware control on device close. On devices that support NvConfig cap 0x0040 (shutdown effect), also fires SetRgbPowerMode(0) as the final step so the firmware plays the configured shutdown animation during the active→off transition. If the cap is disabled, the firmware powers down LEDs silently. Matches LGHUB exit. See solaar_shutdown_effect_trigger_spec.md. rgb_control is the gate: when LED Control is off, skip every wire write here. We never claimed, so there's nothing to release; firing the shutdown animation would visibly contradict the user's "leave my lighting alone". """ stop(device) if any(s.name == "rgb_control" and not s._value for s in getattr(device, "settings", []) or []): return try: device.feature_request(SupportedFeature.RGB_EFFECTS, 0x50, SW_RELEASE) if device.features and SupportedFeature.PROFILE_MANAGEMENT in device.features: device.feature_request(SupportedFeature.PROFILE_MANAGEMENT, 0x60, b"\x03") elif device.features and SupportedFeature.ONBOARD_PROFILES in device.features: device.feature_request(SupportedFeature.ONBOARD_PROFILES, 0x10, b"\x01") except Exception: pass # Device may already be offline if getattr(device, "_rgb_has_shutdown_cap", False): try: # SetRgbPowerMode(set=1, mode=0) — firmware off transition. # no_reply: device goes offline; don't block waiting for an ACK. device.feature_request(SupportedFeature.RGB_EFFECTS, 0x90, b"\x01\x00", no_reply=True) except Exception: pass class RGBPowerManager: """Two-stage idle handler driven by firmware onUserActivity events. State machine: ACTIVE → DIMMING → IDLE → SLEEPING. Stage 1 (idle) runs a host-side dim ramp or hands off to a firmware animation. Stage 2 (sleep) is a software timer that fires sleep_timeout - idle_timeout after IDLE. """ ACTIVE = 0 DIMMING = 1 IDLE = 2 SLEEPING = 3 _DIM_INTERVAL_MS = 200 _DIM_STEPS = 25 # ~5s dim ramp def __init__(self, device): self._device = device self._state = self.ACTIVE self._idle_timeout = 60 self._sleep_timeout = 300 # LEDEffectSetting with ID in {0x00 Disabled, 0x80 Dim, 0x0A # Breathe, 0x0B Ripple}. Populated by start() from the persister. self._idle_effect = None self._sleep_timer_id = None self._dim_timer_id = None self._dim_step = 0 self._dim_zones = [] self._dim_perkey = None def start(self): self._state = self.ACTIVE self._read_firmware_timers() if logger.isEnabledFor(logging.DEBUG): logger.debug( "%s: RGB power manager started (firmware idle=%ds, sleep=%ds)", self._device, self._idle_timeout, self._sleep_timeout, ) def stop(self): self._cancel_dim_timer() self._cancel_sleep_timer() if self._state != self.ACTIVE: try: self._wake() except Exception: pass # Best effort during shutdown if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: RGB power manager stopped", self._device) def reset(self): """Reset to ACTIVE on device reconnect. Re-reads firmware timers so externally-updated values (other tool wrote NV between sessions) are picked up even when our settings are ignored.""" self._cancel_dim_timer() self._cancel_sleep_timer() self._state = self.ACTIVE self._read_firmware_timers() if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: RGB power manager reset to ACTIVE", self._device) def set_idle_timeout(self, seconds): self._idle_timeout = seconds self._cancel_sleep_timer() if seconds == 0 and self._state in (self.DIMMING, self.IDLE): self._wake() self._write_firmware_idle_timeout(seconds) if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: RGB idle timeout set to %ss", self._device, seconds) def set_sleep_timeout(self, seconds): """0 disables sleep.""" self._sleep_timeout = seconds self._cancel_sleep_timer() if seconds == 0 and self._state == self.SLEEPING: self._wake() if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: RGB sleep timeout set to %ss", self._device, seconds) def set_idle_effect(self, effect): """`effect` is an LEDEffectSetting. Wake immediately if the user switched to Disabled while we're mid-idle.""" self._idle_effect = effect if self._idle_effect_id() == 0x00 and self._state in (self.DIMMING, self.IDLE): self._wake() if logger.isEnabledFor(logging.DEBUG): logger.debug( "%s: RGB idle effect set to ID=0x%02X (period=%s, intensity=%s)", self._device, self._idle_effect_id(), getattr(self._idle_effect, "period", None), getattr(self._idle_effect, "intensity", None), ) def _idle_effect_id(self): """Return the ID of the current idle effect, or 0 if unset.""" return int(getattr(self._idle_effect, "ID", 0) or 0) # --- Firmware activity events --- def on_user_activity(self, activity_type): """Handle firmware onUserActivity event from RGB_EFFECTS (0x8071). activity_type=0: IDLE — user stopped typing, firmware idle timer expired. activity_type!=0: ACTIVE — user resumed typing after being idle. The firmware sends a burst of ~8 events with exponential backoff. Only the first event matters; subsequent events for the same transition are ignored. """ if not self._device.online: return if activity_type == 0: # IDLE event — firmware detected inactivity at idle_timeout if self._state != self.ACTIVE: return # Already idle/dimming/sleeping, ignore burst if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: firmware IDLE event — starting idle sequence", self._device) # Switch to flags=3 so firmware monitors for activity during dim/idle try: self._device.feature_request(SupportedFeature.RGB_EFFECTS, 0x50, SW_IDLE) except Exception: pass idle_enabled = self._idle_effect_id() != 0 and self._idle_timeout > 0 and not self._is_ignored("rgb_idle_effect") if idle_enabled: self._start_idle_effect() else: self._state = self.IDLE # Sleep is host-driven only — schedule whenever _sleep_timeout > 0, # regardless of the setting's ignore flag (which only blocks pushing # the user value to firmware, see start()). sleep_enabled = self._sleep_timeout > 0 if sleep_enabled: delay = max(self._sleep_timeout - self._idle_timeout, 0) if delay == 0: self._start_sleep() else: self._sleep_timer_id = GLib.timeout_add_seconds(delay, self._sleep_timer_fired) if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: sleep timer scheduled in %ds", self._device, delay) else: # ACTIVE event — user resumed typing if self._state == self.ACTIVE: return # Already active, ignore burst if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: firmware ACTIVE event — waking", self._device) self._cancel_sleep_timer() self._wake() def _sleep_timer_fired(self): """GLib callback — software sleep timer expired after IDLE.""" self._sleep_timer_id = None if self._state in (self.IDLE, self.DIMMING) and self._device.online: self._start_sleep() return False # One-shot timer def _cancel_sleep_timer(self): if self._sleep_timer_id is not None: GLib.source_remove(self._sleep_timer_id) self._sleep_timer_id = None def _read_firmware_timers(self): """Read idle/sleep timeouts from firmware as the manager's defaults.""" try: resp = self._device.feature_request(SupportedFeature.RGB_EFFECTS, 0x70, b"\x00") if resp and len(resp) >= 7: idle_s = (resp[3] << 8) | resp[4] sleep_s = (resp[5] << 8) | resp[6] if idle_s > 0: self._idle_timeout = idle_s if sleep_s > 0: self._sleep_timeout = sleep_s if logger.isEnabledFor(logging.DEBUG): logger.debug( "%s: firmware timers: idle=%ds, sleep=%ds", self._device, idle_s, sleep_s, ) except Exception as e: if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: could not read firmware timers, using defaults: %s", self._device, e) def _write_firmware_idle_timeout(self, seconds): """Push idle/sleep timeouts back to firmware so it fires IDLE on time.""" try: idle_hi = (seconds >> 8) & 0xFF idle_lo = seconds & 0xFF sleep_hi = (self._sleep_timeout >> 8) & 0xFF sleep_lo = self._sleep_timeout & 0xFF payload = bytes([0x01, 0x00, 0x00, idle_hi, idle_lo, sleep_hi, sleep_lo]) self._device.feature_request(SupportedFeature.RGB_EFFECTS, 0x70, payload) except Exception as e: if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: could not write firmware idle timeout: %s", self._device, e) # --- Idle effect --- def _start_idle_effect(self): idle_id = self._idle_effect_id() if idle_id == 0x80: # Dim dim_pct = int(getattr(self._idle_effect, "intensity", 50) or 50) self._start_dim_ramp(dim_pct) elif idle_id == 0x01: # Static — snap to idle color self._start_static_idle() elif idle_id != 0x00: self._apply_animation(idle_id) def _start_static_idle(self): """Snap to the idle effect's color exactly as if the user had set the active Static zone color to it. Per-key paint continues to display; unset cells repaint to the idle primary color via effective_zone_base_color's IDLE-state substitution. No animation — instant transition. Wake reverts via _restore_colors().""" idle_color = int(getattr(self._idle_effect, "color", 0) or 0) infos = getattr(self._device, "led_effects", None) if not infos or not infos.zones: self._state = self.IDLE return self._state = self.IDLE perkey_setting, has_paint = perkey_has_paint(self._device) perkey_dominates = has_paint and zone_effect_is_static(self._device) if perkey_dominates and perkey_setting is not None: # Per-key is the visible layer — repaint unset cells with the # idle color (effective_zone_base_color now returns it because # state == IDLE and idle effect ID == Static). try: if perkey_setting._fill_unset_zones_with_base_color(): perkey_setting._send_with_retry(0x70, b"\x00") # FrameEnd except Exception as e: if logger.isEnabledFor(logging.WARNING): logger.warning("%s: static idle per-key repaint failed: %s", self._device, e) return # Zone is the visible layer — push Static at idle.color to each zone. for zone in infos.zones: if 0x01 in (e.ID for e in zone.effects): try: self._push_static_effect(zone, idle_color) except Exception as e: if logger.isEnabledFor(logging.WARNING): logger.warning("%s: static idle zone push failed: %s", self._device, e) def _start_dim_ramp(self, dim_pct): """Smooth ~5s dim ramp. Dims the per-key buffer when it's the visible layer (any real per-key paint), otherwise the zone effect.""" infos = getattr(self._device, "led_effects", None) if not infos or not infos.zones: self._state = self.IDLE return perkey_setting, has_paint = perkey_has_paint(self._device) # Per-key only dominates when zone is Static. Under animations, the # firmware engine owns the visible layer — dim the zone instead. perkey_active = has_paint and zone_effect_is_static(self._device) self._dim_perkey = None if perkey_active: self._dim_zones = [] self._dim_perkey = self._build_full_perkey_dim_map(perkey_setting, dim_pct) if self._dim_perkey: # Push base color to unset cells first so they don't start from stale. self._init_unset_perkey_zones(perkey_setting) else: self._dim_zones = [] for zone in infos.zones: if 0x01 in (e.ID for e in zone.effects): start_color = self._get_zone_color(zone) target_color = self._compute_dim_color(start_color, dim_pct) self._dim_zones.append((zone, start_color, target_color)) if not self._dim_zones and not self._dim_perkey: self._state = self.IDLE return self._dim_step = 0 self._state = self.DIMMING self._dim_timer_id = GLib.timeout_add(self._DIM_INTERVAL_MS, self._dim_ramp_step) if logger.isEnabledFor(logging.DEBUG): n_zones = len(self._dim_zones) n_perkey = len(self._dim_perkey) if self._dim_perkey else 0 logger.debug( "%s: starting dim ramp to %d%% brightness (%d zones, %d per-key%s)", self._device, dim_pct, n_zones, n_perkey, ", per-key masking zones" if perkey_active else "", ) def _dim_ramp_step(self): if self._state != self.DIMMING or not self._device.online: self._dim_timer_id = None return False self._dim_step += 1 t = self._dim_step / self._DIM_STEPS for zone, start_color, target_color in self._dim_zones: try: self._push_static_effect(zone, self._interpolate_color(start_color, target_color, t)) except Exception as e: if logger.isEnabledFor(logging.WARNING): logger.warning("%s: dim ramp step failed for zone %s: %s", self._device, zone.index, e) if self._dim_perkey: try: self._push_perkey_dimmed(t) except Exception as e: if logger.isEnabledFor(logging.WARNING): logger.warning("%s: dim ramp step failed for per-key: %s", self._device, e) if self._dim_step >= self._DIM_STEPS: self._state = self.IDLE self._dim_timer_id = None return False return True def _push_static_effect(self, zone, color): """Non-persistent Static effect, one zone.""" static_effect = next((e for e in zone.effects if e.ID == 0x01), None) if static_effect is None: return r = (color >> 16) & 0xFF g = (color >> 8) & 0xFF b = color & 0xFF params = bytes([r, g, b, 0, 0, 0, 0, 0, 0, 0]) payload = bytes([zone.index, static_effect.index]) + params + b"\x01" self._device.feature_request(SupportedFeature.RGB_EFFECTS, 0x10, payload) def _push_perkey_dimmed(self, t): """Push interpolated per-key colors for one dim ramp step. Groups keys by their interpolated color and uses SetRgbZonesSingleValue (0x8081 function 6) for efficient bulk writes — up to 13 zone IDs per HID message when multiple keys share the same dimmed color. """ # Build color -> [zone_ids] map for this interpolation step color_groups = {} for zone_id, (start_color, target_color) in self._dim_perkey.items(): color = self._interpolate_color(start_color, target_color, t) if color not in color_groups: color_groups[color] = [] color_groups[color].append(zone_id) feat = SupportedFeature.PER_KEY_LIGHTING_V2 for color, zone_ids in color_groups.items(): r = (color >> 16) & 0xFF g = (color >> 8) & 0xFF b = color & 0xFF # Function 6: SetRgbZonesSingleValue — color(3) + zone_ids (up to 13 per report) while zone_ids: batch = zone_ids[:13] zone_ids = zone_ids[13:] data = bytes([r, g, b]) + bytes(batch) self._device.feature_request(feat, 0x60, data) # Commit the frame self._device.feature_request(feat, 0x70, b"\x00\x00\x00\x00\x00") def _apply_animation(self, effect_id): """Hand off to a firmware animation. Generic over any effect in hidpp20.LEDEffects: builds the 10-byte param block from the effect's param map, sourcing color from the zone and other params from the persisted _idle_effect.""" from . import hidpp20 infos = getattr(self._device, "led_effects", None) if not infos or not infos.zones: self._state = self.IDLE return entry = hidpp20.LEDEffects.get(effect_id) if entry is None: self._state = self.IDLE return param_map = entry[1] for zone in infos.zones: effect_info = next((e for e in zone.effects if e.ID == effect_id), None) if effect_info is None: continue color = self._get_zone_color(zone) params = bytearray(10) if hidpp20.LEDParam.color in param_map: offset = param_map[hidpp20.LEDParam.color] params[offset] = (color >> 16) & 0xFF params[offset + 1] = (color >> 8) & 0xFF params[offset + 2] = color & 0xFF for pname, poff in param_map.items(): if pname == hidpp20.LEDParam.color: continue psize = hidpp20.LEDParamSize.get(pname, 1) user_val = getattr(self._idle_effect, str(pname), None) if user_val is None: user_val = effect_info.period or 3000 if pname == hidpp20.LEDParam.period else 0 params[poff : poff + psize] = int(user_val).to_bytes(psize, "big") if effect_id == 0x01: params[3] = 0x02 # Static fixed-color marker payload = bytes([zone.index, effect_info.index]) + bytes(params) + b"\x01" try: self._device.feature_request(SupportedFeature.RGB_EFFECTS, 0x10, payload) except Exception as exc: if logger.isEnabledFor(logging.WARNING): logger.warning( "%s: failed to apply animation 0x%02x to zone %d: %s", self._device, effect_id, zone.index, exc, ) self._state = self.IDLE # --- Sleep --- def _start_sleep(self): """Enter firmware-managed sleep. Firmware fades from current level.""" self._cancel_dim_timer() try: self._device.feature_request(SupportedFeature.RGB_EFFECTS, 0x80, b"\x01\x03\x00") self._state = self.SLEEPING if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: RGB entering sleep (firmware power-down)", self._device) except Exception as e: if logger.isEnabledFor(logging.WARNING): logger.warning("%s: failed to enter RGB sleep: %s", self._device, e) # --- Wake --- def _wake(self): """Restore full lighting from any non-ACTIVE state.""" if self._state == self.ACTIVE: return prev_state = self._state self._cancel_dim_timer() self._cancel_sleep_timer() # State must be ACTIVE before _restore_colors() — the paint paths # translate through it, and writes would otherwise go at the old dim. self._state = self.ACTIVE try: if prev_state == self.SLEEPING: self._set_power_mode_with_retry(1) # Re-claim full LED pipeline control self._device.feature_request(SupportedFeature.RGB_EFFECTS, 0x50, SW_ACTIVE) # Firmware engine has re-engaged during sleep — re-arm per-key # one-shots so the next write re-fires the prep + double-send. for s in self._device.settings: if s.name == "per-key-lighting": s._frame_settled = False s._prep_pushed = False break self._restore_colors() if logger.isEnabledFor(logging.DEBUG): state_names = {self.DIMMING: "dimming", self.IDLE: "idle", self.SLEEPING: "sleep"} logger.debug("%s: RGB woken from %s", self._device, state_names.get(prev_state, "unknown")) except Exception as e: if logger.isEnabledFor(logging.WARNING): logger.warning("%s: failed to wake RGB LEDs: %s", self._device, e) def _cancel_dim_timer(self): if self._dim_timer_id is not None: GLib.source_remove(self._dim_timer_id) self._dim_timer_id = None def _get_zone_color(self, zone): location = int(zone.location) setting_name = f"rgb_zone_{location}" for s in self._device.settings: if s.name == setting_name and s._value is not None: return getattr(s._value, "color", 0xFFFFFF) return 0xFFFFFF def _get_zone_base_color(self): """Color used as the base for unset per-key cells. Black when the zone effect is marked ignore, the saved zone color otherwise.""" return effective_zone_base_color(self._device) @staticmethod def _has_real_perkey_colors(perkey_setting): if not perkey_setting._value: return False no_change = special_keys.COLORSPLUS["No change"] return any(color != no_change and isinstance(color, int) and color >= 0 for color in perkey_setting._value.values()) def _build_full_perkey_dim_map(self, perkey_setting, dim_pct): """{zone_id: (start, target)} for every zone — user-set keys from their color, unset from the zone base.""" no_change = special_keys.COLORSPLUS["No change"] zone_base = self._get_zone_base_color() user_colors = {int(k): c for k, c in perkey_setting._value.items() if c != no_change and isinstance(c, int) and c >= 0} return { int(k): (start, self._compute_dim_color(start, dim_pct)) for k in perkey_setting._validator.choices for start in (user_colors.get(int(k), zone_base),) } def _init_unset_perkey_zones(self, perkey_setting): """Push the zone base color to per-key cells the user hasn't painted — avoids the white-default flash when per-key takes over the buffer.""" no_change = special_keys.COLORSPLUS["No change"] zone_base = self._get_zone_base_color() r = (zone_base >> 16) & 0xFF g = (zone_base >> 8) & 0xFF b = zone_base & 0xFF user_set = {int(k) for k, c in perkey_setting._value.items() if c != no_change and isinstance(c, int) and c >= 0} unset_zones = [int(k) for k in perkey_setting._validator.choices if int(k) not in user_set] if not unset_zones: return feat = SupportedFeature.PER_KEY_LIGHTING_V2 remaining = list(unset_zones) try: while remaining: batch = remaining[:13] remaining = remaining[13:] self._device.feature_request(feat, 0x60, bytes([r, g, b]) + bytes(batch)) self._device.feature_request(feat, 0x70, b"\x00\x00\x00\x00\x00") except exceptions.FeatureCallError as e: logger.warning("%s: per-key zone init failed (device busy?): %s", self._device, e) @staticmethod def _compute_dim_color(color, dim_pct): r = ((color >> 16) & 0xFF) * dim_pct // 100 g = ((color >> 8) & 0xFF) * dim_pct // 100 b = (color & 0xFF) * dim_pct // 100 return (r << 16) | (g << 8) | b @staticmethod def _interpolate_color(start, target, t): r_s, g_s, b_s = (start >> 16) & 0xFF, (start >> 8) & 0xFF, start & 0xFF r_t, g_t, b_t = (target >> 16) & 0xFF, (target >> 8) & 0xFF, target & 0xFF r = int(r_s + (r_t - r_s) * t) g = int(g_s + (g_t - g_s) * t) b = int(b_s + (b_t - b_s) * t) return (r << 16) | (g << 8) | b def _current_dim_pct(self): """100 unless we're in Dim mode — animations run at firmware brightness.""" if self._idle_effect_id() != 0x80: return 100 return int(getattr(self._idle_effect, "intensity", 50) or 50) def translate_color(self, color): """Map a saved (undimmed) per-key color to what should be displayed on the device right now, given the current power-management state. Returns None to signal SLEEPING — caller should persist and skip the wire write; _restore_colors on wake will re-push the saved value.""" # Static idle is a color swap, not a brightness change — user-painted # cells render their saved color unchanged, and the unset-cell # substitution happens upstream via effective_zone_base_color. if self._state == self.IDLE and self._idle_effect_id() == 0x01: return color return translate_color_for_display(color, self._state, self._current_dim_pct(), self._dim_step, self._DIM_STEPS) def notify_perkey_changed(self, zone_id, new_color): """Resync a per-key zone's dim ramp entry to a user-repainted color.""" if self._state != self.DIMMING or not self._dim_perkey or zone_id not in self._dim_perkey: return self._dim_perkey[zone_id] = ( new_color, self._compute_dim_color(new_color, self._current_dim_pct()), ) def notify_perkey_bulk_changed(self, color_map): """Bulk notify_perkey_changed, skipping 'No change' entries.""" if self._state != self.DIMMING or not self._dim_perkey: return no_change = special_keys.COLORSPLUS["No change"] for zone_id, color in color_map.items(): if color == no_change or not isinstance(color, int) or color < 0: continue self.notify_perkey_changed(int(zone_id), int(color)) def notify_zone_changed(self, cluster_index, new_color): """Resync a zone-effect dim ramp entry to a user-repainted color.""" if self._state != self.DIMMING or not self._dim_zones: return dim_pct = self._current_dim_pct() for i, (zone, _start, _target) in enumerate(self._dim_zones): if int(zone.index) == int(cluster_index): self._dim_zones[i] = (zone, new_color, self._compute_dim_color(new_color, dim_pct)) return def _set_power_mode_with_retry(self, mode): """First command after wake may fail; retry.""" params = bytes([0x01, mode, 0x00]) for attempt in range(3): try: self._device.feature_request(SupportedFeature.RGB_EFFECTS, 0x80, params) return except Exception: if attempt == 2: raise import time as _time _time.sleep(0.1) def _is_ignored(self, setting_name): """True if marked ignore via the lock icon.""" persister = getattr(self._device, "persister", None) if persister is None: return False return persister.get_sensitivity(setting_name) == settings.SENSITIVITY_IGNORE def _restore_colors(self): """Re-push lighting state after waking. Per-key dominates only when zone is Static — under animations, the zone wire push goes through and per-key is skipped.""" _perkey_setting, has_paint = perkey_has_paint(self._device) zone_static = zone_effect_is_static(self._device) perkey_dominates = has_paint and zone_static for s in self._device.settings: if s._value is None: continue if self._is_ignored(s.name): continue if s.name == "per-key-lighting": if not self._has_real_perkey_colors(s): continue if not zone_static: continue # firmware animation owns the visible layer elif s.name.startswith("rgb_zone_"): if perkey_dominates: continue else: continue try: s.write(s._value, save=False) if logger.isEnabledFor(logging.DEBUG): logger.debug("%s: restored %s after wake", self._device, s.name) except Exception as e: if logger.isEnabledFor(logging.WARNING): logger.warning("%s: failed to restore %s: %s", self._device, s.name, e) Solaar-1.1.20/lib/logitech_receiver/settings.py000066400000000000000000001102121522022367600214600ustar00rootroot00000000000000## 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 __future__ import annotations import logging import struct import time from enum import IntEnum from typing import Any from solaar.i18n import _ 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" 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 = "" feature = register = kind = None min_version = 0 persist = True rw_options = {} validator_class = None validator_options = {} display = True # display setting in UI # Set False for settings whose value cannot be read back from the device # (e.g. PerKeyLighting — the 0x8081 protocol has no GetIndividualRgbZones). # `solaar show` uses this to suppress the "(live)" output line that would # otherwise print a fabricated value misleadingly. live_readable = True # 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._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 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 assert rw.kind == RegisterRW.kind elif p >= 2.0: # HID++ 2.0 devices do not support registers assert rw.kind == FeatureRW.kind validator_class = cls.validator_class validator = validator_class.build(cls, device, **cls.validator_options) if validator: assert cls.kind is None or cls.kind & validator.kind != 0 return cls(device, rw, validator) def val_to_string(self, value): return self._validator.to_string(value) @property def choices(self): assert hasattr(self, "_value") assert hasattr(self, "_device") 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") 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): # 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 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") self._pre_read(cached) if cached and self._value is not None: 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._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 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): # Remember the value we're trying to set, even if the write fails. # This way even if the device is offline or some other error occurs, # the last value we've tried to write is remembered in the configuration. if self._device.persister and save: self._device.persister[self.name] = self._value if self.persist else None def update(self, value, save=True): self._value = value self._pre_write(save) def write(self, value, save=True): assert hasattr(self, "_value") assert hasattr(self, "_device") assert value is not None 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: self.update(value, save) current_value = None 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 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 logger.isEnabledFor(logging.DEBUG): logger.debug("%s: prepare write(%s) => %r", self.name, value, data_bytes) reply = self._rw.write(self._device, data_bytes) # HID++ 2.0 "set" operations often return an empty ACK (b""). # Treating empty bytes as failure (`not reply`) would misreport # successful writes as errors to the GUI. Only report failure # when the transport actually returned None (error or timeout). if reply is None: logger.info( "%s: write on %s returned no reply (transport error/timeout)", self.name, self._device, ) return None return value def acceptable(self, args, current): return self._validator.acceptable(args, current) if self._validator else None def compare(self, args, current): return self._validator.compare(args, current) if self._validator else None def apply(self): 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 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, ) return f"" __repr__ = __str__ class Settings(Setting): """A setting descriptor for multiple choices, being a map from keys to values. Needs to be instantiated for each specific device.""" def read(self, cached=True): 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 if self._device.online: reply_map = {} for key in self._validator.choices: reply = self._rw.read(self._device, key) 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: # 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 key is not None 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: return self._value[int(key)] if self._device.online: 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: 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 map is not None 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 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 return map def update_key_value(self, key, value, save=True): self._value[int(key)] = value self._pre_write(save) 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 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: self.read() try: data_bytes = self._validator.prepare_write(int(key), value) # always need to write to configuration because dictionary is shared and could have changed self.update_key_value(key, value, save) except ValueError: data_bytes = value = None if data_bytes is not None: 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 return value class LongSettings(Setting): """A setting descriptor for multiple choices, being a map from keys to values. Allows multiple write requests, if the options don't fit in 16 bytes. The validator must return a list. Needs to be instantiated for each specific device.""" def read(self, cached=True): 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 if self._device.online: reply_map = {} # Reading one item at a time. This can probably be optimised for item in self._validator.items: r = self._validator.prepare_read_item(item) reply = self._rw.read(self._device, r) 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: # 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 item is not None 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: return self._value[int(item)] if self._device.online: r = self._validator.prepare_read_item(item) 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: 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 map is not None 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(): data_bytes_list = self._validator.prepare_write(self._value) if data_bytes_list is not None: for data_bytes in data_bytes_list: if data_bytes is not None: 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 return map def update_key_value(self, key, value, save=True): self._value[int(key)] = value self._pre_write(save) def write_key_value(self, item, value, save=True): assert hasattr(self, "_value") assert hasattr(self, "_device") assert item is not None assert value is not None 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: self.read() data_bytes = self._validator.prepare_write_item(item, value) self.update_key_value(item, value, save) if data_bytes is not None: 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 return value class BitFieldSetting(Setting): """A setting descriptor for a set of choices represented by one bit each, being a map from options to booleans. Needs to be instantiated for each specific device.""" def read(self, cached=True): 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 if self._device.online: reply_map = {} reply = self._do_read() 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: # 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 _do_read(self): return self._rw.read(self._device) def read_key(self, key, cached=True): assert hasattr(self, "_value") assert hasattr(self, "_device") assert key is not None 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: return self._value[int(key)] if self._device.online: 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: self._device.persister[self.name] = self._value if self.persist else None return self._value[int(key)] def _do_read_key(self, key): return self._rw.read(self._device, key) def write(self, map, save=True): assert hasattr(self, "_value") assert hasattr(self, "_device") assert map is not None 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 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: reply = self._rw.write(self._device, b) if not reply: return None return map def update_key_value(self, key, value, save=True): 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") assert key is not None assert value is not None 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: self.read() value = bool(value) self.update_key_value(key, value, save) data_bytes = self._validator.prepare_write(self._value) if data_bytes is not None: 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: reply = self._rw.write(self._device, b) if not reply: return None return value class BitFieldWithOffsetAndMaskSetting(BitFieldSetting): """A setting descriptor for a set of choices represented by one bit each, each one having an offset, being a map from options to booleans. Needs to be instantiated for each specific device.""" def _do_read(self): return {r: self._rw.read(self._device, r) for r in self._validator.prepare_read()} def _do_read_key(self, key): r = self._validator.prepare_read_key(key) return {r: self._rw.read(self._device, r)} class RangeFieldSetting(Setting): """A setting descriptor for a set of choices represented by one field each, with map from option names to range(0,n). Needs to be instantiated for each specific device.""" def read(self, cached=True): 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 if self._device.online: reply_map = {} reply = self._do_read() 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: # 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 _do_read(self): return self._rw.read(self._device) def read_key(self, key, cached=True): return self.read(cached)[int(key)] def write(self, map, save=True): assert hasattr(self, "_value") assert hasattr(self, "_device") assert map is not None 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 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 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 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() map = self._value map[int(key)] = value self.write(map, save) return value # # read/write low-level operators # class RegisterRW: __slots__ = ("register",) kind = NamedInt(0x01, _("register")) def __init__(self, register: int): assert isinstance(register, int) self.register = register def read(self, device): return device.read_register(self.register) def write(self, device, data_bytes): return device.write_register(self.register, data_bytes) class FeatureRW: kind = NamedInt(0x02, _("feature")) default_read_fnid = 0x00 default_write_fnid = 0x10 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 self.no_reply = no_reply self.prefix = prefix self.suffix = suffix self.read_prefix = read_prefix def read(self, device, data_bytes=b""): assert self.feature is not None 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 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")) default_read_fnid = 0x00 default_write_fnid = 0x10 default_key_byte_count = 1 def __init__( self, feature: hidpp20_constants.SupportedFeature, read_fnid=default_read_fnid, write_fnid=default_write_fnid, key_byte_count=default_key_byte_count, no_reply=False, ): assert isinstance(feature, (hidpp20_constants.SupportedFeature, CenturionCoreFeature)) self.feature = feature self.read_fnid = read_fnid self.write_fnid = write_fnid self.key_byte_count = key_byte_count self.no_reply = no_reply def read(self, device, key): assert self.feature is not None 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 = 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 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"): self.feature = feature # not used? self.name = name self.divert_setting_name = divert_setting_name self.kind = FeatureRW.kind # pretend to be FeatureRW as required for HID++ 2.0 devices self.device = None self.key = None self.active = False self.pressed = False def activate_action(self): # action to take when setting is activated (write non-false) pass def deactivate_action(self): # action to take when setting is deactivated (write false) pass def press_action(self): # action to take when key is pressed pass def release_action(self): # action to take when key is released pass def move_action(self, dx, dy): # action to take when mouse is moved while key is down pass def key_action(self, key): # acction to take when some other diverted key is pressed pass def read(self, device): # need to return bytes, as if read from device 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_constants.SupportedFeature.REPROG_CONTROLS_V4 ): if n.address == 0x00: 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() elif self.pressed: if int(self.key.key) not in cids: # trigger key released self.pressed = False self.release_action() else: for key in cids: if key and not key == self.key.key: # some other diverted key pressed self.key_action(key) elif n.address == 0x10: if self.pressed: 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: logger.warning("setting %s not found on %s", self.divert_setting_name, device.name) return None self.device = device 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) self.activate_action() else: 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) 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 logger.isEnabledFor(logging.WARNING): logger.warning("cannot disable %s on %s", self.name, device) self.deactivate_action() 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=""): 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_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_constants.SupportedFeature.REPROG_CONTROLS_V4: if n.address == 0x00: 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: if int(k.key) in cids: # initiating key that was pressed self.initiating_key = k if self.initiating_key: self.press_action(self.initiating_key) else: if int(self.initiating_key.key) not in cids: # initiating key released self.initiating_key = None self.release_action() else: for key in cids: if key and key != self.initiating_key.key: self.key_action(key) elif n.address == 0x10: if self.initiating_key: dx, dy = struct.unpack("!hh", n.data[:4]) self.move_action(dx, dy) def start(self, key): device_key = next((k for k in self.device.keys if k.key == key), None) if device_key: self.keys.append(device_key) if not self.active: self.active = True self.activate_action() self.device.add_notification_handler(self.name, self.handler) device_key.set_rawXY_reporting(True) def stop(self, key): # only stop if this is the active key if self.active: processing_key = next((k for k in self.keys if k.key == key), None) if processing_key: processing_key.set_rawXY_reporting(False) self.keys.remove(processing_key) if not self.keys: try: self.device.remove_notification_handler(self.name) except Exception: if logger.isEnabledFor(logging.WARNING): logger.warning("cannot disable %s on %s", self.name, self.device) self.deactivate_action() self.active = False def activate_action(self): # action to take when processing is activated pass def deactivate_action(self): # action to take when processing is deactivated pass def press_action(self, key): # action to take when an initiating key is pressed pass def release_action(self): # action to take when key is released pass def move_action(self, dx, dy): # action to take when mouse is moved while key is down pass def key_action(self, key): # acction to take when some other diverted key is pressed pass def apply_all_settings(device): 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 Solaar-1.1.20/lib/logitech_receiver/settings_new.py000066400000000000000000000224461522022367600223440ustar00rootroot00000000000000## 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 Solaar-1.1.20/lib/logitech_receiver/settings_templates.py000066400000000000000000006235501522022367600235540ustar00rootroot00000000000000## 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 __future__ import annotations import enum import logging import socket import struct import traceback from time import sleep from time import time from typing import Callable from typing import Protocol import yaml from solaar.i18n import _ from . import base from . import common from . import descriptors from . import desktop_notifications from . import device_quirks from . import diversion from . import exceptions from . import headset_rgb from . import hidpp10_constants from . import hidpp20 from . import hidpp20_constants from . import logivoice from . import rgb_effects_probe from . import rgb_power 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 def halving_marks(max_value, step_count): """Halving series from max_value down by powers of two, plus 0. For max=100, count=5 → [0, 13, 25, 50, 100], matching the G515 FN-F8 cycle. """ if step_count < 2 or max_value <= 0: return [] levels = [-(-max_value // (1 << i)) for i in range(step_count - 1)] return sorted({0, *levels}) def auto_step_count(max_value, low_pct=10): """Step count for halving_marks that stops before dropping below low_pct of max.""" if max_value <= 0: return 0 threshold = max(1, max_value * low_pct // 100) n = 0 while -(-max_value // (1 << n)) >= threshold: n += 1 return n + 1 def _possible_fields_with_direction_filter(device, possible_fields, direction_field): """Filter direction_field's choices against the per-device blocklist for Wave directions the firmware accepts but doesn't render.""" blocked = hidpp20.LedDirectionBlocklist.get(device.wpid) if not blocked: return possible_fields filtered = common.NamedInts() for v in hidpp20.LedDirectionChoices: if int(v) not in blocked: filtered[int(v)] = str(v) device_direction_field = dict(direction_field, choices=filtered) return [device_direction_field if f is direction_field else f for f in possible_fields] class State(enum.Enum): IDLE = "idle" PRESSED = "pressed" MOVED = "moved" # Setting classes are used to control the settings that the Solaar GUI shows and manipulates. # Each setting class has to several class variables: # name, which is used as a key when storing information about the setting, # setting classes can have the same name, as long as devices only have one setting with the same name; # label, which is shown in the Solaar main window; # description, which is shown when the mouse hovers over the setting in the main window; # either register or feature, the register or feature that the setting uses; # rw_class, the class of the reader/writer (if it is not the standard one), # rw_options, a dictionary of options for the reader/writer. # validator_class, the class of the validator (default settings.BooleanValidator) # validator_options, a dictionary of options for the validator # 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 # 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. # 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 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 # 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, 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). # 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 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. # 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 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 ChoicesValidator should have keys_universe and choices_universe class variable of # the potential keys and potential choices or None for no limitation. # 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) # # A few settings work very differently. They divert a key, which is then used to start and stop some special action. # These settings have reader/writer classes that perform special processing instead of sending commands to the device. 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." ) ) 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(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 = 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): feature = _F.FN_INVERSION class NewFnSwap(FnSwapVirtual): feature = _F.NEW_FN_INVERSION class Backlight(settings.Setting): name = "backlight-qualitative" label = _("Backlight Timed") description = _("Set illumination time for keyboard.") feature = _F.BACKLIGHT 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} # 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 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": b"\x09"} validator_class = settings_validator.RangeValidator min_value = 0 max_value = 1000 validator_options = {"byte_count": 2} 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") ) feature = _F.HI_RES_SCROLLING 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)." ) feature = _F.LOWRES_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} 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") ) feature = _F.HIRES_WHEEL rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} validator_options = {"true_value": 0x02, "mask": 0x02} 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)." ) feature = _F.HIRES_WHEEL rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} validator_options = {"true_value": 0x01, "mask": 0x01} 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 = settings_validator.RangeValidator min_value = 0x002E max_value = 0x01FF validator_options = {"byte_count": 2} 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)." ) 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"} 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"} # 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 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 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): 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(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!" rate_list = [] rate_flags = common.bytes2int(reply[0:1]) for i in range(0, 8): if (rate_flags >> i) & 0x01: 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 ExtendedReportRate(settings.Setting): name = "report_rate_extended" label = _("Report Rate") description = ( _("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(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" 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 = common.NamedInts(**{_("Freespinning"): 1, _("Ratcheted"): 2}) validator_class = settings_validator.ChoicesValidator validator_options = {"choices": choices_universe} 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." ) feature = _F.SMART_SHIFT rw_options = {"read_fnid": 0x00, "write_fnid": 0x10} class rw_class(settings.FeatureRW): MIN_VALUE = 1 MAX_VALUE = 50 def __init__(self, feature, read_fnid, write_fnid): super().__init__(feature, read_fnid, write_fnid) def read(self, device): value = super().read(device) if common.bytes2int(value[0:1]) == 1: # Mode = Freespin, map to minimum return common.int2bytes(self.MIN_VALUE, count=1) else: # Mode = smart shift, map to the value, capped at maximum threshold = min(common.bytes2int(value[1:2]), self.MAX_VALUE) return common.int2bytes(threshold, count=1) def write(self, device, 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 = 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 = settings_validator.RangeValidator class SmartShiftEnhanced(SmartShift): feature = _F.SMART_SHIFT_ENHANCED 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.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.") ) feature = _F.REPROG_CONTROLS_V4 keys_universe = special_keys.CONTROL choices_universe = special_keys.CONTROL class rw_class: def __init__(self, feature): self.feature = feature 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" + 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[common.bytes2int(data_bytes)]) return True class validator_class(settings_validator.ChoicesMapValidator): @classmethod def build(cls, setting_class, device): choices = {} if device.keys: for k in device.keys: tgts = k.remappable_to if len(tgts) > 1: choices[k.key] = tgts return cls(choices, key_byte_count=2, byte_count=2, extra_default=0) if choices else None 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" 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 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 = State.IDLE self.dx = 0.0 self.movingDpiIdx = None def setNewDpi(self, newDpiIdx): newDpi = self.dpiChoices[newDpiIdx] self.dpiSetting.write(newDpi) if self.device.setting_callback: self.device.setting_callback(self.device, type(self.dpiSetting), [newDpi]) def displayNewDpi(self, newDpiIdx): 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 == 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 == 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.setNewDpi(newDpiIdx) self.displayNewDpi(newDpiIdx) elif self.fsmState == State.MOVED: # Set DPI according to displacement self.setNewDpi(self.movingDpiIdx) 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.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 == 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: self.movingDpiIdx = newMovingDpiIdx self.displayNewDpi(newMovingDpiIdx) class MouseGesturesXY(settings.RawXYProcessing): def activate_action(self): 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.0 self.dy = 0.0 self.lastEv = None self.data = [] def press_action(self, key): self.starting = True if self.fsmState == State.IDLE: self.fsmState = State.PRESSED self.initialize_data() self.data = [key.key] def release_action(self): if self.fsmState == State.PRESSED: # emit mouse gesture notification self.push_mouse_event() 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 == 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.0: self.push_mouse_event() dpi = self.dpiSetting.read() if self.dpiSetting else 1000 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.0 # This multiplier yields a more-or-less DPI-independent dx of about 5/cm self.dy += dy self.lastEv = now def key_action(self, key): self.push_mouse_event() self.data.append(1) self.data.append(key) 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) y = int(self.dy) if x == 0 and y == 0: return self.data.append(0) self.data.append(x) self.data.append(y) 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.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 = 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 = 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 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(common.bytes2int(data_bytes) != 0) # not regular return True 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) def prepare_write(self, key, new_value): if self.gestures and new_value != 2: # mouse gestures self.gestures.stop(key) if self.sliding and new_value != 3: # sliding DPI self.sliding.stop(key) if self.gestures and new_value == 2: # mouse gestures self.gestures.start(key) if self.sliding and new_value == 3: # sliding DPI self.sliding.start(key) return super().prepare_write(key, new_value) @classmethod def build(cls, setting_class, device): sliding = gestures = None choices = {} if device.keys: 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") if _F.ADJUSTABLE_DPI in device.features: choices[key.key] = setting_class.choices_universe if sliding is None: sliding = DpiSlidingXY( device, name="DpiSliding", show_notification=desktop_notifications.show ) else: choices[key.key] = setting_class.choices_divert if not choices: return None validator = cls(choices, key_byte_count=2, byte_count=1, mask=0x01) validator.sliding = sliding validator.gestures = gestures return validator 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 = common.NamedInts.range(100, 4000, str, 50) class validator_class(settings_validator.ChoicesValidator): @classmethod def build(cls, setting_class, device): 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 = common.bytes2int(reply_bytes[1:3]) if reply_value == 0: # use default value instead reply_value = common.bytes2int(reply_bytes[3:5]) 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 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") 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" ) choices_universe = special_keys.CONTROL choices_extra = common.NamedInt(0, _("Off")) feature = _F.POINTER_SPEED 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) 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: logger.error("cannot save sensitivity setting on %s", self.device) if self.device.persister: 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 = device.keys[key_index] if key_index is not None else None if key is not None and KeyFlag.DIVERTABLE in key.flags: keys = [setting_class.choices_extra, key.key] return cls(choices=common.NamedInts.list(keys), byte_count=2) 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 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 = [special_keys.DISABLE[1 << i] for i in range(8) if mask & (1 << i)] return cls(options) if options else None 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 = 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), ] # 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(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 "" elif version & 0xFF: return f"{str(version >> 8)}.{str(version & 0xFF)}" else: return str(version >> 8) 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 = 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 = struct.unpack("!BBHHH", descriptor[:8]) descriptors.append((platform, os_flags, low, high)) 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 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 = settings_validator.ChoicesValidator validator_options = {"choices": choices_universe} 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 = 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 = 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] == "": hostNames[currentHost] = (True, socket.gethostname().partition(".")[0]) choices = common.NamedInts() for host in range(0, numHosts): 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 = { 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 = { 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.")), } 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_constants.GestureId _labels = _GESTURE2_GESTURES_LABELS 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(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_constants.GestureId _labels = _GESTURE2_GESTURES_LABELS 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(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_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(settings_validator.MultipleRangeValidator): @classmethod def build(cls, setting_class, device): params = _hidpp20.get_gestures(device).params.values() items = [i for i in params if i.sub_params] if not items: return None sub_items = {i: i.sub_params for i in items} return cls(items, sub_items) 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.") ) feature = _F.MKEYS 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(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(settings_validator.BitFieldValidator): @classmethod def build(cls, setting_class, device): number = device.feature_request(setting_class.feature, 0x00)[0] options = [setting_class.choices_universe[1 << i] for i in range(number)] return cls(options) if options else None 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.") ) feature = _F.MR 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" ## 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.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.") ) 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 class rw_class: def __init__(self, feature): self.feature = feature 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 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(settings_validator.ChoicesMapValidator): @classmethod def build(cls, setting_class, device): remap_keys = device.remap_keys if not remap_keys: return None capabilities = device.remap_keys.capabilities if capabilities & 0x0041 == 0x0041: # Key and Consumer Codes keys = special_keys.KEYS_KEYS_CONSUMER elif capabilities & 0x0023 == 0x0023: # Key, Mouse, and HScroll Codes keys = special_keys.KEYS_KEYS_MOUSE_HSCROLL else: 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 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 = 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 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(settings.Setting): name = "sidetone" label = _("Sidetone") description = _("Set sidetone level.") feature = _F.SIDETONE validator_class = settings_validator.RangeValidator min_value = 0 max_value = 100 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"} keys_universe = [] 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 = struct.unpack("!BBBBB", data[:5]) if dbMin == 0: dbMin = -dbRange if dbMax == 0: dbMax = dbRange 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") class ADCPower(settings.Setting): name = "adc_power_management" label = _("Power Management") description = _("Power off in minutes (0 for never).") feature = _F.ADC_MEASUREMENT 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} 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 @classmethod def build(cls, device): # G522 firmware rejects no-op writes with device-specific NACK 0x0B. # BooleanValidator.prepare_write already skips writes that match the # current value when needs_current_value=True; default-mask (0xFF) # BooleanValidators get needs_current_value=False, so flip it here. rw = settings.FeatureRW(cls.feature) validator = settings_validator.BooleanValidator() validator.needs_current_value = True return cls(device, rw, validator) 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 # HEADSET_MIC_MUTE (0x0601) doesn't follow the typical fn 0 GetState / # fn 1 SetState pattern that BooleanValidator defaults to. Function # layout (confirmed via G HUB pcap on G522): # fn 0 — physical-mute-switch state-change events from the device # fn 1 — state-change events emitted as the device's echo of a # host-driven SetState; also serves as the host-callable # GetState read # fn 2 — host-callable SetState (single byte: 0=unmuted, 1=muted) # The standard fn 0/1 write path returns 0x0A UNSUPPORTED. State-change # events from both fn 0 and fn 1 are handled by _process_feature_notification # so the toggle reflects physical mute presses too. rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} 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} min_value = 0 max_value = 100 class validator_class(settings_validator.RangeValidator): """UI value is 0-100 percent; the wire level is a gain-step index 0..N-1. N (gain_steps) comes from getSidetoneLevelSettings on V2 devices, or defaults to 101 on V1 — which makes step == percent, so V1 round-trips unchanged. The firmware silently ignores out-of-range step writes, so writes are clamped to N-1.""" gain_steps = 101 def _level_bytes(self, raw): return common.bytes2int(raw[self.read_skip_byte_count : self.read_skip_byte_count + self._byte_count]) def validate_read(self, reply_bytes): level = self._level_bytes(reply_bytes) steps = self.gain_steps return int(round(level * 100 / (steps - 1))) if steps > 1 else level def prepare_write(self, new_value, current_value=None): steps = self.gain_steps level = int(round((steps - 1) * new_value / 100)) if steps > 1 else new_value level = max(0, min((steps - 1) if steps > 1 else self.max_value, level)) to_write = self.write_prefix_bytes + common.int2bytes(level, self._byte_count) if current_value is not None and self._level_bytes(current_value) == level: return None return to_write @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" # V2 getSidetoneLevelSettings (fn 2) reply byte 2 is the gain-step count N. # V1 has no such call — N stays 101 (step == percent). Raw reply still # logged at debug so a G HUB setpoint correlation can refine the layout. gain_steps = 101 if version > 1: try: reply = device.feature_request(cls.feature, 0x20) except Exception as e: reply = None logger.debug("%s: getSidetoneLevelSettings probe raised %s", cls.name, e) logger.debug("%s: getSidetoneLevelSettings raw reply: %s", cls.name, reply.hex() if reply else reply) if reply is not None and len(reply) >= 3 and reply[2] > 1: gain_steps = reply[2] 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: validator.gain_steps = gain_steps 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 # Fallback range covers int8; build() overrides with device-reported bounds # from GetInfo (fn 0) so SetMicGain doesn't get device-specific # out-of-range NACK (error 0x0B) on devices that use a small signed range # (e.g. G522 reports a narrow window like -12..+12). min_value = -128 max_value = 127 validator_options = {"byte_count": 1, "signed": True} @classmethod def build(cls, device): # GetInfo (function 0) returns [min_gain (int8), max_gain (int8)]. # Query once at build time so the slider range reflects the device's # actual supported range rather than a generic int8 window. try: info = device.feature_request(cls.feature, 0x00) except Exception as e: logger.debug("HeadsetMicGain: GetInfo raised %s, using fallback int8 range", e) info = None if info and len(info) >= 2: min_gain = struct.unpack("b", bytes([info[0]]))[0] max_gain = struct.unpack("b", bytes([info[1]]))[0] if max_gain <= min_gain: # sanity — fall back to class defaults logger.debug( "HeadsetMicGain: GetInfo returned nonsense range [%d, %d] (hex=%s), using fallback int8 range", min_gain, max_gain, info.hex(), ) min_gain, max_gain = cls.min_value, cls.max_value elif logger.isEnabledFor(logging.DEBUG): logger.debug( "HeadsetMicGain: device reports gain range [%d, %d]", min_gain, max_gain, ) else: logger.debug( "HeadsetMicGain: GetInfo returned %s, using fallback int8 range", info.hex() if info else info, ) min_gain, max_gain = cls.min_value, cls.max_value rw = settings.FeatureRW(cls.feature, **cls.rw_options) validator = settings_validator.RangeValidator(min_value=min_gain, max_value=max_gain, byte_count=1, signed=True) return cls(device, rw, validator) 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 _AutoSleepRangeValidator(settings_validator.RangeValidator): """Single-slot read-modify-write validator for HID++ 0x0108 AutoSleep. 0x0108 is not a single timer: V3 has two uint8 bytes, V4+ has three. Each byte is an independent timer slot. Solaar exposes only the user-facing slot today and preserves the others via RMW; writing zero into the other slots causes the firmware to reject the request. Wire byte layout per feature version: V<3: [timer] V3: [reserved, timer] — preserve byte[0] V4+: [timer_a, timer_b, timer_c] — preserve byte[1], byte[2] """ def __init__(self, byte_count, **kwargs): super().__init__(byte_count=byte_count, **kwargs) # V3 sources the user-controllable timer from byte[1] per LGHUB. self._slot = 1 if byte_count == 2 else 0 def validate_read(self, reply_bytes): if len(reply_bytes) <= self._slot: raise AssertionError( f"{self.__class__.__name__}: read returned {len(reply_bytes)} bytes, expected ≥ {self._slot + 1}" ) return reply_bytes[self._slot] 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}") if current_value is None: payload = bytearray(self._byte_count) else: payload = bytearray(current_value[: self._byte_count]) if len(payload) < self._byte_count: payload.extend(b"\x00" * (self._byte_count - len(payload))) if payload[self._slot] == new_value: return None payload[self._slot] = new_value return bytes(payload) 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 = _AutoSleepRangeValidator min_value = 0 max_value = 255 # uint8 slot validator_options = {"byte_count": 1} @classmethod def build(cls, device): version = device.features.get_feature_version(cls.feature) or 0 if version >= 4: byte_count = 3 elif version >= 3: byte_count = 2 else: byte_count = 1 rw = settings.FeatureRW(cls.feature, **cls.rw_options) validator = _AutoSleepRangeValidator(min_value=0, max_value=cls.max_value, byte_count=byte_count) return cls(device, rw, validator) 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: logger.debug("HeadsetOnboardEQ.build: getEQInfo failed, no panel will be built") return None _has_hw_eq, num_bands = info bands = hidpp20.get_onboard_eq_params(device, slot=0x00) if not bands: logger.debug("HeadsetOnboardEQ.build: getEQParameters returned no bands, no panel will be built") return None if len(bands) != num_bands: logger.debug( "HeadsetOnboardEQ.build: band count mismatch — EQInfo=%d getEQParameters=%d; skipping", num_bands, len(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] logger.debug("HeadsetOnboardEQ.build: panel built with %d band(s)", num_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 HeadsetAdvancedEQ(settings.RangeFieldSetting): """Per-band gain editor for the headset's active AdvancedParaEQ (0x020D) slot. V2 wire format (pcap-verified against G522 LIGHTSPEED): getCustomEQ response: [dir_echo] + N × [freq_hi, freq_lo, filter, gain_hi, gain_lo] setCustomEQ request: [dir, slot, pad=0] + N × [freq_hi, freq_lo, filter, gain_hi, gain_lo] Gain is offset-binary against gain_min..gain_max with `gain_steps` discrete positions (raw=120 ≈ 0 dB on G522's [-6, +6] / 241-step grid). Frequency and filter type are read at build time and not user-editable today — UI only exposes per-band gain. """ name = "headset-advanced-eq" label = _("Headset Advanced EQ") description = _("Per-band gain for the headset's active parametric EQ.") feature = _F.HEADSET_ADVANCED_PARA_EQ rw_options = {"read_fnid": 0x10, "write_fnid": 0x20} keys_universe = [] class rw_class(settings.FeatureRW): """get/setCustomEQ both take [direction, slot]; on writes the device additionally expects a single 0x00 padding byte before the band payload. The slot is the *active* EQ preset, which the device may have switched while we weren't looking — re-query it on every read and write instead of caching at build time. Direction is hardcoded to 0 (playback); mic-side EQ isn't exposed yet. """ def read(self, device, data_bytes=b""): active_slot = hidpp20.get_advanced_eq_active_slot(device, direction=0) self.read_prefix = bytes([0, active_slot if active_slot is not None else 0]) return super().read(device, data_bytes) def write(self, device, data_bytes): active_slot = hidpp20.get_advanced_eq_active_slot(device, direction=0) slot = active_slot if active_slot is not None else 0 write_bytes = bytes([0, slot, 0]) + data_bytes return device.feature_request(self.feature, self.write_fnid, write_bytes) class validator_class(settings_validator.PackedRangeValidator): kind = settings.Kind.GRAPHIC_EQ @classmethod def build(cls, setting_class, device): info = hidpp20.get_advanced_eq_info(device) if not info: logger.debug("HeadsetAdvancedEQ.build: getEQInfos failed, no panel will be built") return None device._advanced_eq_info = info version = info["version"] gain_min = info["gain_min_db"] gain_max = info["gain_max_db"] step_db = info["step_db"] active_slot = hidpp20.get_advanced_eq_active_slot(device, direction=0) or 0 bands = hidpp20.get_advanced_eq_params(device, direction=0, slot=active_slot) if not bands: logger.debug("HeadsetAdvancedEQ.build: getCustomEQ returned no bands, no panel will be built") return None band_count = len(bands) expected = info.get("band_count") if expected is not None and expected != band_count: logger.debug( "HeadsetAdvancedEQ.build: V%d band count mismatch — EQInfos=%d getCustomEQ=%d; trusting getCustomEQ", version, expected, band_count, ) keys = common.NamedInts() for i, (filter_type, freq_hz, _gain_db) in enumerate(bands): if filter_type == hidpp20.FILTER_TYPE_HP: keys[i] = "HP " + str(freq_hz) + _("Hz") else: keys[i] = str(freq_hz) + _("Hz") v = cls( keys, min_value=int(round(gain_min)), max_value=int(round(gain_max)), count=band_count, byte_count=1, ) v._version = version v._step_db = step_db v._gain_min = gain_min v._gain_max = gain_max v._gain_steps = info.get("gain_steps", 241) v._band_types = [band[0] for band in bands] v._band_freqs = [band[1] for band in bands] v._active_slot = active_slot logger.debug( "HeadsetAdvancedEQ.build: panel built V%d with %d band(s), slot=%d, range=[%d,%d], step_db=%.4f", version, band_count, active_slot, gain_min, gain_max, step_db, ) # One-shot per-slot probe — logs band data for each slot the # firmware actually honors and caches the working-slot list on # `device._advanced_eq_working_slots`. Cheap if HeadsetActiveEQPreset # already populated the cache (it usually has at this point); # otherwise this is the first-time probe. if version >= 2: try: hidpp20.probe_advanced_eq_slots(device, direction=0, info=info) except Exception as e: logger.debug("HeadsetAdvancedEQ.build: preset corpus probe failed: %s", e) return v def validate_read(self, reply_bytes): if reply_bytes is None: return {} version = getattr(self, "_version", 0) if version >= 2: info = { "gain_min_db": getattr(self, "_gain_min", -6), "gain_max_db": getattr(self, "_gain_max", 6), "gain_steps": getattr(self, "_gain_steps", 241), "step_db": getattr(self, "_step_db", 0.05), } bands = hidpp20.parse_v2_bands(reply_bytes, info) if bands is None: return {} result = {} for i, (filter_type, freq_hz, gain_db) in enumerate(bands): if i >= self.count: break result[i] = int(round(gain_db)) if hasattr(self, "_band_types") and i < len(self._band_types): self._band_types[i] = filter_type if hasattr(self, "_band_freqs") and i < len(self._band_freqs): self._band_freqs[i] = freq_hz return result # V0/V1: 3-byte stride. result = {} offset = 0 i = 0 while offset + 3 <= len(reply_bytes) and i < self.count: freq = struct.unpack(">H", reply_bytes[offset : offset + 2])[0] if freq == 0: break gain = struct.unpack("b", bytes([reply_bytes[offset + 2]]))[0] result[i] = gain if hasattr(self, "_band_freqs") and i < len(self._band_freqs): self._band_freqs[i] = freq offset += 3 i += 1 return result def prepare_write(self, new_values): """Encode N × [freq_hi, freq_lo, filter, gain_hi, gain_lo]. new_values is {band_idx: int_gain_dB}. freq and filter type come from the cache captured at build time; gain is mapped from integer dB back to the device's offset-binary raw u16 against the [_gain_min, _gain_max] / _gain_steps grid. """ version = getattr(self, "_version", 0) if version < 2: return None gain_min = getattr(self, "_gain_min", -6) gain_max = getattr(self, "_gain_max", 6) steps = getattr(self, "_gain_steps", 241) freqs = getattr(self, "_band_freqs", None) or [] types = getattr(self, "_band_types", None) or [] if not freqs or not types: return None span = gain_max - gain_min payload = bytearray() for i in range(self.count): freq = freqs[i] if i < len(freqs) else 0 filt = types[i] if i < len(types) else hidpp20.FILTER_TYPE_PEAKING_G522 gain_db = new_values.get(i, 0) if steps > 1 and span > 0: raw = int(round((gain_db - gain_min) / span * (steps - 1))) else: raw = 0 raw = max(0, min(steps - 1, raw)) payload += bytes([(freq >> 8) & 0xFF, freq & 0xFF, filt & 0xFF, (raw >> 8) & 0xFF, raw & 0xFF]) return bytes(payload) def write(self, map, save=True): # RangeFieldSetting.write treats an empty-bytes reply (`not reply`) # as failure, but setCustomEQ returns an empty ACK on success. # Override to treat only `reply is None` (transport error/timeout) # as failure. assert hasattr(self, "_value") assert hasattr(self, "_device") assert map is not None if self._device.online: self.update(map, save) data_bytes = self._validator.prepare_write(self._value) if data_bytes is not None: reply = self._rw.write(self._device, data_bytes) if reply is None: return None return map def _is_valid_persisted_value(self, value): """True iff `value` is a well-formed band-gain dict for the current validator: a dict with exactly `count` int keys covering [0, count), each mapped to an int within [min_value, max_value]. Used to detect stale persister entries from older Solaar builds whose V2 parser had a different stride/header (commits prior to 7c73c888) and produced partial dicts or out-of-range gain values.""" validator = getattr(self, "_validator", None) if not isinstance(value, dict) or validator is None: return False count = getattr(validator, "count", 0) if count == 0 or len(value) != count: return False mn = getattr(validator, "min_value", None) mx = getattr(validator, "max_value", None) if mn is None or mx is None: return False for i in range(count): if i not in value: return False v = value[i] if not isinstance(v, int) or v < mn or v > mx: return False return True def apply(self): """Validate the persisted EQ against the live device state before pushing. Setting.apply uses cached=True so the persister is treated as authoritative — that's wrong here because (a) the EQ can be changed externally (LGHUB, onboard preset buttons) and (b) older Solaar V2 parsers stored partial/out-of-range dicts that prepare_write silently fills with 0 dB, overwriting user EQ with zeros. Strategy: if the persisted value is well-formed, apply it normally (matches existing Solaar semantics). If it's corrupt, treat the device's live read as truth and reseed the persister from it without writing back. If both are invalid, skip this setting only — apply_all_settings keeps going.""" assert hasattr(self, "_value") assert hasattr(self, "_device") if not self._device.online: return persister = getattr(self._device, "persister", None) persisted = persister.get(self.name) if persister else None persisted_valid = self._is_valid_persisted_value(persisted) try: live = self.read(cached=False) except Exception as e: logger.warning("%s: live EQ read failed during apply (%s): %s", self.name, self._device, repr(e)) live = None live_valid = self._is_valid_persisted_value(live) if persisted_valid: try: self.write(persisted, save=False) except Exception as e: logger.warning("%s: error applying %s (%s): %s", self.name, persisted, self._device, repr(e)) elif live_valid: logger.info( "%s: rejecting stale persister value %r; reseeding from device live %r", self.name, persisted, live, ) self._value = live if persister is not None: persister[self.name] = live else: logger.warning( "%s: both persisted (%r) and live (%r) values invalid; skipping apply", self.name, persisted, live, ) class HeadsetActiveEQPreset(settings.Setting): """Choose which AdvancedParaEQ slot drives live audio. Activation works for any slot — read-only factory presets and user-custom slots alike. The "(factory)" tag in the slot label distinguishes the read-only ones; that distinction matters for band-editing (not supported yet), not for activation today. """ name = "headset-eq-active-preset" label = _("EQ Preset") description = _("Switch the active EQ preset. Factory presets are read-only.") feature = _F.HEADSET_ADVANCED_PARA_EQ rw_options = {"read_fnid": 0x30, "write_fnid": 0x40, "prefix": b"\x00"} validator_class = settings_validator.ChoicesValidator @classmethod def build(cls, device): info = getattr(device, "_advanced_eq_info", None) or hidpp20.get_advanced_eq_info(device) if not info: return None ro_count = info.get("onboard_ro_preset_count", 0) or 0 # Probe each advertised slot — getEQInfos may report capacity that # the firmware doesn't actually back (G522 advertises 16 slots but # only honors slot 0). Only include slots that responded with band # data; the result is cached on device._advanced_eq_working_slots # so HeadsetAdvancedEQ.build can reuse it without re-probing. working = hidpp20.probe_advanced_eq_slots(device, direction=0, info=info) if len(working) <= 1: # One option (or zero) is meaningless as a selector — there's # nothing for the user to choose between. The active EQ is # whatever slot 0 has, no preset switching is available. return None choices = common.NamedInts() for slot, slot_name, _bands in working: if not slot_name: slot_name = _("Slot") + " " + str(slot) if slot < ro_count: slot_name = slot_name + " " + _("(factory)") choices[slot] = slot_name rw = settings.FeatureRW(cls.feature, **cls.rw_options) validator = settings_validator.ChoicesValidator(choices=choices) return cls(device, rw, validator) def write(self, value, save=True): result = super().write(value, save) if result is not None: # After setActiveEQ, repopulate the AdvancedParaEQ band-display # cache so the panel reflects the newly-active slot. Force a # fresh read so _value is a real dict — leaving it as None # would let a UI band-click hit `_value[item]` on None and # crash (config_panel.py:589 'NoneType' is not subscriptable). # The visible widget redraw still waits for a manual refresh / # panel reopen — auto-redraw would need UI-side plumbing. eq_panel = _headset_setting_by_name(self._device, HeadsetAdvancedEQ.name) if eq_panel is not None: try: eq_panel._value = None eq_panel.read(cached=False) except Exception as e: logger.debug("HeadsetActiveEQPreset: failed to refresh EQ panel: %s", e) return result _NO_CHANGE_COLOR = int(special_keys.COLORSPLUS["No change"]) def _headset_setting_by_name(device, name): for s in getattr(device, "settings", None) or []: if getattr(s, "name", None) == name: return s return None def _headset_primary_color(device, default=0xFFFFFF): """The headset's base color — the 0x0621 onboard Fixed-effect color. Per-zone 'No change' cells resolve against this. Returns `default` when the onboard-effect setting is absent or not currently on Fixed.""" s = _headset_setting_by_name(device, HeadsetOnboardEffect.name) value = getattr(s, "_value", None) if s is not None else None if value is not None and int(getattr(value, "ID", -1)) == 0: return int(getattr(value, "color1", default)) return default def _headset_cluster_effect_is_fixed(device): """True when the 0x0621 onboard effect is Fixed (the Static analog), or when the device has no onboard-effect setting. A non-Fixed cluster animation masks the per-zone buffer, so per-zone writes are suppressed while one runs.""" s = _headset_setting_by_name(device, HeadsetOnboardEffect.name) if s is None: return True value = getattr(s, "_value", None) if value is None: persister = getattr(device, "persister", None) value = persister.get(HeadsetOnboardEffect.name) if persister else None if value is None: return True return int(getattr(value, "ID", 0)) == 0 def _headset_per_zone_overrides(device): """Return `{zone_id: color_int}` for zones with explicit (non-'No change') colors set via the Per-zone Lighting setting, or `None` if the setting isn't built/present.""" s = _headset_setting_by_name(device, HeadsetPerZoneLighting.name) if s is None: return None value = getattr(s, "_value", None) if not isinstance(value, dict): return None overrides = {} for zone, color in value.items(): try: color_int = int(color) except (TypeError, ValueError): continue if color_int != _NO_CHANGE_COLOR: overrides[int(zone)] = color_int return overrides def _headset_reassert_zone_layer(device): """Re-paint the per-zone layer: every zone to the LEDs Primary color, then the explicit per-zone overrides on top. The headset firmware drops the host-painted per-zone buffer whenever a cluster layer is (re)written — so any path that re-asserts a cluster layer (LED Control re-claim, onboard Static color change) must call this to restore the per-zone paint. No-op unless the onboard effect is Fixed; a non-Static animation owns the LEDs and masks per-zone anyway. """ if not _headset_cluster_effect_is_fixed(device): return zones = headset_rgb.discover_zones(device) if not zones: return zone_map = {int(z): _headset_primary_color(device) for z in zones} zone_map.update(_headset_per_zone_overrides(device) or {}) headset_rgb.write_zone_map(device, zone_map) def _headset_led_control_on(device): """True when the headset LED Control is on (Solaar drives the LEDs). When off, the firmware owns the LEDs and host color writes are suppressed — the value is still persisted so it re-applies on switch-on. Reads setting._value first, then the persister; accepts a bool or a legacy int 0/1 from the old ChoicesValidator era.""" s = _headset_setting_by_name(device, HeadsetLEDControl.name) v = getattr(s, "_value", None) if s is not None else None if v is None: persister = getattr(device, "persister", None) v = persister.get(HeadsetLEDControl.name) if persister else None if v is None: return True # unknown — don't suppress return bool(v) class HeadsetLEDControl(settings.Setting): """Whether Solaar holds the headset's live-coloring claim. Mirrors the `RGBControl` pattern for keyboards and mice. On = Solaar may drive the LEDs — the 0x0621 onboard effect and 0x0620 per-zone painting are both live LED control; off = Solaar releases the LEDs so another app (e.g. OpenRGB) can drive them. The 0x0622 signature effects are stored settings (startup/shutdown colors), not live coloring, and stay editable either way. """ name = "headset_led_control" label = _("LED Control") description = _("Allow Solaar to control the headset LED zones.") feature = _F.HEADSET_RGB_HOSTMODE rw_options = {"read_fnid": 0x70, "write_fnid": 0x80} # Two-state — render as a Gtk.Switch. Wire byte: 1 = Solaar (host) control, # 0 = Device (firmware) control. validator_class = settings_validator.BooleanValidator validator_options = {"true_value": 1, "false_value": 0} def _pre_read(self, cached, key=None): # Migrate legacy int values (0/1 from the old ChoicesValidator) to bool. super()._pre_read(cached, key) if isinstance(self._value, int) and not isinstance(self._value, bool): self._value = self._value != 0 @classmethod def build(cls, device): # One-shot read-only probe of 0x0621 / 0x0622 — logs the data the RE # pass needs to pin down RGB onboard/signature effect structures. # Skip cleanly if neither feature is exposed. try: rgb_effects_probe.probe(device) except Exception as e: logger.debug("RGB effects probe raised %r", e) return super().build(device) def write(self, value, save=True): # On re-claim the firmware drops our colors; reassert the dominant # layer — per-zone when the onboard effect is Static, else the effect. result = super().write(value, save) if result is not None and value and self._device.online: if _headset_cluster_effect_is_fixed(self._device): _headset_reassert_zone_layer(self._device) else: onboard = next((s for s in self._device.settings if s.name == "headset-onboard-effect"), None) if onboard is not None and onboard._value is not None: onboard.write(onboard._value, save=False) return result class HeadsetPerZoneLighting(settings.Settings): """Per-zone LED color overrides. Mirrors `PerKeyLighting` — keys are firmware zone IDs, values are 24-bit RGB ints with the `-1` sentinel meaning "inherit the current `LEDs Primary` color." Surfaces in the UI via the per-key painter. """ name = "headset_per_zone_lighting" label = _("Per-zone Lighting") description = _( "Override individual zone colors. 'No change' inherits the LEDs Primary color.\n" "LED Control needs to be set to Solaar to be effective." ) feature = _F.HEADSET_RGB_HOSTMODE persist = True editor_class = "solaar.ui.perkey.control:PerKeyControl" 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): zones = headset_rgb.discover_zones(device) if not zones: return None choices_map = {common.NamedInt(int(z), _("Zone") + " " + str(int(z))): cls._COLOR_RANGE for z in zones} return cls(choices_map) if choices_map else None def read(self, cached=True): self._pre_read(cached) if cached and self._value is not None: return self._value # Device doesn't expose current per-zone state; default every # zone to "No change" so the primary color shows through. reply_map = {int(key): _NO_CHANGE_COLOR for key in self._validator.choices} self._value = reply_map return reply_map def _resolve_zone_map(self, map_, primary): """Substitute 'No change' entries with the primary color.""" resolved = {} for key, value in map_.items(): try: v = int(value) except (TypeError, ValueError): continue resolved[int(key)] = primary if v == _NO_CHANGE_COLOR else v return resolved def write(self, map_, save=True): device = self._device if not device.online: return None self.update(map_, save) # Gate the wire on both conditions, like keyboard per-key (needs # rgb_control on + zone Static): LED Control on, cluster effect Fixed. if not _headset_led_control_on(device) or not _headset_cluster_effect_is_fixed(device): return map_ # value stored, skip the wire primary = _headset_primary_color(device) zone_map = self._resolve_zone_map(map_, primary) if not zone_map: return map_ headset_rgb.write_zone_map(device, zone_map) return map_ def write_key_value(self, key, value, save=True): result = super().write_key_value(int(key), value, save) device = self._device if not device.online: return result if not _headset_led_control_on(device) or not _headset_cluster_effect_is_fixed(device): return result # value stored, skip the wire try: v = int(value) except (TypeError, ValueError): return result effective = _headset_primary_color(device) if v == _NO_CHANGE_COLOR else v headset_rgb.write_zone_map(device, {int(key): int(effective)}) return result class _HeadsetSignatureEffect: """A 0x0622 signature-effect slot value: an enable byte, two colors and a speed. Synthetic 8-byte form [ID, R1,G1,B1, R2,G2,B2, speed] — ID is 0x01 on / 0x02 off. The rw_class splits it across get/setSignatureEffectParams (colors + speed) and get/setSignatureEffectState (the enable byte).""" def __init__(self, ID=1, color1=0, color2=0, speed=0): self.ID = int(ID) self.speed = max(0, min(100, int(speed))) for k, v in (("color1", color1), ("color2", color2)): setattr(self, k, common.ColorInt(int(v) & 0xFFFFFF)) @classmethod def from_bytes(cls, data, options=None): if data is None or len(data) < 8: return cls() c1 = (data[1] << 16) | (data[2] << 8) | data[3] c2 = (data[4] << 16) | (data[5] << 8) | data[6] return cls(ID=data[0], color1=c1, color2=c2, speed=data[7]) def to_bytes(self, options=None): return bytes( [ self.ID & 0xFF, (self.color1 >> 16) & 0xFF, (self.color1 >> 8) & 0xFF, self.color1 & 0xFF, (self.color2 >> 16) & 0xFF, (self.color2 >> 8) & 0xFF, self.color2 & 0xFF, self.speed & 0xFF, ] ) def __eq__(self, other): return isinstance(other, self.__class__) and self.to_bytes() == other.to_bytes() def __str__(self): return yaml.dump(self, width=float("inf")).rstrip("\n") @classmethod def from_yaml(cls, loader, node): return cls(**loader.construct_mapping(node)) @classmethod def to_yaml(cls, dumper, data): return dumper.represent_mapping("!HeadsetSignatureEffect", data.__dict__, flow_style=True) yaml.SafeLoader.add_constructor("!HeadsetSignatureEffect", _HeadsetSignatureEffect.from_yaml) yaml.add_representer(_HeadsetSignatureEffect, _HeadsetSignatureEffect.to_yaml) class _HeadsetSignatureEffectSetting(settings.Setting): """One firmware signature-effect slot on HEADSET_RGB_SIGNATURE_EFFECTS (0x0622). Subclasses set effect_id (0 startup, 1 shutdown, 2 passive). Build probes the slot via getSignatureEffectState and suppresses the setting if the device doesn't expose it. These run autonomously on the device firmware, so — like the keyboard boot animations — they are not gated on host LED control.""" feature = _F.HEADSET_RGB_SIGNATURE_EFFECTS effect_id: int = 0 _ENABLED_CHOICES = common.NamedInts(**{"On": 1, "Off": 2}) _COLOR1_FIELD = {"name": "color1", "kind": settings.Kind.COLOR, "label": _("Primary")} _COLOR2_FIELD = {"name": "color2", "kind": settings.Kind.COLOR, "label": _("Secondary")} _SPEED_FIELD = {"name": "speed", "kind": settings.Kind.RANGE, "label": _("Speed"), "min": 0, "max": 100} class rw_class: kind = settings.FeatureRW.kind def __init__(self, feature, effect_id): self.feature = feature self._eid = bytes([(effect_id >> 8) & 0xFF, effect_id & 0xFF]) def read(self, device): params = device.feature_request(self.feature, 0x10, self._eid) # getSignatureEffectParams state = device.feature_request(self.feature, 0x30, self._eid) # getSignatureEffectState if params is None or len(params) < 9 or state is None or len(state) < 3: return None # params: [effectId, R1,G1,B1, R2,G2,B2, speed]; state: [effectId, enabled] return bytes([state[2]]) + params[2:9] def write(self, device, data_bytes): # data_bytes: [enabled, R1,G1,B1, R2,G2,B2, speed] params = device.feature_request(self.feature, 0x20, self._eid + data_bytes[1:8]) state = device.feature_request(self.feature, 0x40, self._eid + data_bytes[0:1]) return data_bytes if params is not None and state is not None else None @classmethod def build(cls, device): eid = bytes([(cls.effect_id >> 8) & 0xFF, cls.effect_id & 0xFF]) try: state = device.feature_request(cls.feature, 0x30, eid) # probe: is this slot present? except exceptions.FeatureCallError: return None if state is None or len(state) < 3: return None # NVconfig-saved colors are default-DENY: signature effects persist to # device storage, so a slot is shown only on models explicitly known- # good. allowed is None on an unlisted model/slot (suppress the whole # setting), else the set of fields the firmware honors. The G522 # passive slot is deliberately unlisted — its behavior is unknown. # SOLAAR_EXPERIMENTAL unmasks everything. allowed = device_quirks.headset_signature_allowed_fields(device, cls.effect_id) if allowed is None: return None # Log getSignatureEffectsInfo (fn 0) once per device — its byte layout # isn't pinned down, so slot discovery uses per-slot probing for now. if not getattr(device, "_headset_sig_info_logged", False): device._headset_sig_info_logged = True try: info = device.feature_request(cls.feature, 0x00) logger.debug("%s: getSignatureEffectsInfo raw reply: %s", cls.name, info.hex() if info else info) except Exception as e: logger.debug("%s: getSignatureEffectsInfo probe raised %s", cls.name, e) rw = cls.rw_class(cls.feature, cls.effect_id) validator = settings_validator.HeteroValidator(data_class=_HeadsetSignatureEffect, options=None) setting = cls(device, rw, validator) # Enable byte as a right-aligned Gtk.Switch (on=1 / off=2); colors and # speed stay visible in both states so toggling Off keeps them. id_field = {"name": "ID", "kind": settings.Kind.TOGGLE, "label": None, "on_value": 1, "off_value": 2} setting.possible_fields = [id_field, cls._COLOR1_FIELD, cls._COLOR2_FIELD, cls._SPEED_FIELD] visible = {f: 1 for f in ("color1", "color2", "speed") if f in allowed} setting.fields_map = { int(cls._ENABLED_CHOICES["On"]): (cls._ENABLED_CHOICES["On"], visible), int(cls._ENABLED_CHOICES["Off"]): (cls._ENABLED_CHOICES["Off"], visible), } return setting class HeadsetSignatureStartupEffect(_HeadsetSignatureEffectSetting): name = "headset-signature-startup" label = _("Startup Effect") description = ( _("Firmware lighting effect played when the headset powers on or wakes.") + "\n" + _("Device default: Primary #00B8FC, Secondary #FF00AB, Speed 100.") ) effect_id = 0 class HeadsetSignatureShutdownEffect(_HeadsetSignatureEffectSetting): name = "headset-signature-shutdown" label = _("Shutdown Effect") description = ( _("Firmware lighting effect played when the headset powers off or sleeps.") + "\n" + _("Device default: Primary #00B8FC, Secondary #FF00AB, Speed 100.") ) effect_id = 1 class HeadsetSignaturePassiveEffect(_HeadsetSignatureEffectSetting): name = "headset-signature-passive" label = _("Passive Effect") description = ( _("Firmware lighting effect played while the headset is idle.") + "\n" + _("Device default: Primary #00B8FC, Secondary #FF00AB, Speed 75.") ) effect_id = 2 class _HeadsetOnboardEffect: """A 0x0621 onboard RGB effect: an effect ID plus the parameters that effect uses. Synthetic form [effectId_u16_BE, 7 param bytes]; to_bytes / from_bytes encode the per-effect parameter layout (the rw_class adds the leading clusterIndex). intensity is a 0-100 percent; saturation is a raw 0-255 byte (same as the keyboard RGB effects).""" # Per-effect default parameter values, applied only to fields left # unset (passed as None). An explicit value is always honored — passing # 0 means the caller chose 0, e.g. a black Static color1 turns the LEDs # off. Only a genuinely absent field falls back to the default (a fresh # effect-pick seeds its RANGE widgets UI-side via _apply_id_defaults). # Defaults confirmed against the LGHUB binary decode of 0x0621. _DEFAULTS = { 0: {"color1": 0xFFFFFF}, # Static / Fixed 1: {"intensity": 100, "saturation": 255, "period": 5000}, # Color Cycle 2: {"intensity": 100, "saturation": 255, "period": 5000}, # Color Wave 3: {"color1": 0xFFFFFF, "intensity": 100, "period": 5000}, # Breathing 4: {"color1": 0xFFFFFF, "color2": 0x0000FF, "intensity": 100}, # Dual Color } # speed is accepted only to load configs persisted before the 0x0621 # decode (DualColor byte 6 was mislabelled "speed"; it is intensity). def __init__(self, ID=0, color1=None, color2=None, intensity=None, saturation=None, period=None, speed=0, direction=None): self.ID = int(ID) defaults = self._DEFAULTS.get(self.ID, {}) color1 = defaults.get("color1", 0) if color1 is None else color1 color2 = defaults.get("color2", 0) if color2 is None else color2 intensity = defaults.get("intensity", 0) if intensity is None else intensity saturation = defaults.get("saturation", 0) if saturation is None else saturation period = defaults.get("period", 0) if period is None else period direction = defaults.get("direction", 0) if direction is None else direction self.intensity = max(0, min(100, int(intensity))) self.saturation = max(0, min(255, int(saturation))) self.period = max(0, min(0xFFFF, int(period))) self.direction = max(0, min(3, int(direction))) for k, v in (("color1", color1), ("color2", color2)): setattr(self, k, common.ColorInt(int(v) & 0xFFFFFF)) @classmethod def from_bytes(cls, data, options=None): if data is None or len(data) < 9: return cls() eid = (data[0] << 8) | data[1] p = data[2:9] kw = {"ID": eid} if eid == 0: # Fixed kw["color1"] = (p[0] << 16) | (p[1] << 8) | p[2] elif eid in (1, 2): # ColorCycle / ColorWave kw["intensity"] = p[0] kw["period"] = (p[1] << 8) | p[2] kw["saturation"] = p[3] if eid == 2: kw["direction"] = p[4] elif eid == 3: # Breathing: R, G, B, intensity, period u16 BE kw["color1"] = (p[0] << 16) | (p[1] << 8) | p[2] kw["intensity"] = p[3] kw["period"] = (p[4] << 8) | p[5] elif eid == 4: # DualColor: R1, G1, B1, R2, G2, B2, intensity kw["color1"] = (p[0] << 16) | (p[1] << 8) | p[2] kw["color2"] = (p[3] << 16) | (p[4] << 8) | p[5] kw["intensity"] = p[6] return cls(**kw) def to_bytes(self, options=None): eid = self.ID p = bytearray(7) c1, c2 = int(self.color1), int(self.color2) if eid == 0: # Fixed: R, G, B p[0], p[1], p[2] = (c1 >> 16) & 0xFF, (c1 >> 8) & 0xFF, c1 & 0xFF elif eid in (1, 2): # ColorCycle / ColorWave p[0] = self.intensity p[1], p[2] = (self.period >> 8) & 0xFF, self.period & 0xFF p[3] = self.saturation if eid == 2: p[4] = self.direction elif eid == 3: # Breathing: R, G, B, intensity, period u16 BE, pad p[0], p[1], p[2] = (c1 >> 16) & 0xFF, (c1 >> 8) & 0xFF, c1 & 0xFF p[3] = self.intensity p[4], p[5] = (self.period >> 8) & 0xFF, self.period & 0xFF elif eid == 4: # DualColor: R1,G1,B1, R2,G2,B2, intensity p[0], p[1], p[2] = (c1 >> 16) & 0xFF, (c1 >> 8) & 0xFF, c1 & 0xFF p[3], p[4], p[5] = (c2 >> 16) & 0xFF, (c2 >> 8) & 0xFF, c2 & 0xFF p[6] = self.intensity return bytes([(eid >> 8) & 0xFF, eid & 0xFF]) + bytes(p) def __eq__(self, other): return isinstance(other, self.__class__) and self.to_bytes() == other.to_bytes() def __str__(self): return yaml.dump(self, width=float("inf")).rstrip("\n") @classmethod def from_yaml(cls, loader, node): return cls(**loader.construct_mapping(node)) @classmethod def to_yaml(cls, dumper, data): return dumper.represent_mapping("!HeadsetOnboardEffect", data.__dict__, flow_style=True) yaml.SafeLoader.add_constructor("!HeadsetOnboardEffect", _HeadsetOnboardEffect.from_yaml) yaml.add_representer(_HeadsetOnboardEffect, _HeadsetOnboardEffect.to_yaml) class HeadsetOnboardEffect(settings.Setting): """The RGB effect the headset shows on its primary lighting cluster (HEADSET_RGB_ONBOARD_EFFECTS, 0x0621). Build reads the cluster's supported-effect set so the picker offers only those. This is live LED control, like per-zone painting — gated on Solaar holding the LED-control claim: writes are skipped and the row greys out when the claim is released. Multi-cluster devices (none seen yet) drive only cluster 0.""" name = "headset-onboard-effect" label = _("Onboard Effect") description = _("Firmware RGB effect the headset plays on its own.") feature = _F.HEADSET_RGB_ONBOARD_EFFECTS _CLUSTER = 0 # Custom (5) is intentionally absent — it is a stored card-reference # effect, not a parametric one; it cannot be set via setRGBClusterEffect. _ALL_EFFECTS = ( ("Static", 0), ("Color Cycle", 1), ("Color Wave", 2), ("Breathing", 3), ("Dual Color", 4), ) _EFFECT_FIELDS = { 0: ("color1",), 1: ("intensity", "period", "saturation"), 2: ("intensity", "period", "saturation", "direction"), 3: ("color1", "intensity", "period"), 4: ("color1", "color2", "intensity"), } _DIRECTIONS = common.NamedInts(**{"Horizontal": 0, "Vertical": 1, "Reverse Horizontal": 2, "Reverse Vertical": 3}) class rw_class: kind = settings.FeatureRW.kind def __init__(self, feature, cluster): self.feature = feature self._cluster = cluster def read(self, device): reply = device.feature_request(self.feature, 0x20, bytes([self._cluster])) # getRGBClusterEffect if reply is None or len(reply) < 10: return None return reply[1:10] # strip clusterIndex -> [effectId_u16, 7 param bytes] def write(self, device, data_bytes): # data_bytes is [effectId_u16, 7 param bytes]; prepend clusterIndex. # The onboard effect is live LED control — skip the wire when Solaar # doesn't hold the claim; the value is still persisted. if not _headset_led_control_on(device): return data_bytes reply = device.feature_request(self.feature, 0x30, bytes([self._cluster]) + bytes(data_bytes)) return data_bytes if reply is not None else None @classmethod def build(cls, device): try: info = device.feature_request(cls.feature, 0x10, bytes([cls._CLUSTER])) # getRGBClusterInfo except exceptions.FeatureCallError: return None if info is None or len(info) < 1: return None # [count, count x {effectId_u16_BE, caps_u16_BE}] — take effectId of each entry supported = [] for i in range(info[0]): off = 1 + i * 4 if off + 2 > len(info): break eid = (info[off] << 8) | info[off + 1] if 0 <= eid <= 4 and eid not in supported: supported.append(eid) if not supported: # Unparseable reply — offer the five parametric effects; the # firmware rejects any it doesn't support. Custom (5) is never # offered here. See the 0x0621 fallback note in protocol RE. supported = [0, 1, 2, 3, 4] rw = cls.rw_class(cls.feature, cls._CLUSTER) validator = settings_validator.HeteroValidator(data_class=_HeadsetOnboardEffect, options=None) setting = cls(device, rw, validator) id_choices = common.NamedInts(**{name: eid for name, eid in cls._ALL_EFFECTS if eid in supported}) id_field = {"name": "ID", "kind": settings.Kind.CHOICE, "label": None, "choices": id_choices} setting.possible_fields = [ id_field, {"name": "color1", "kind": settings.Kind.COLOR, "label": _("Primary")}, {"name": "color2", "kind": settings.Kind.COLOR, "label": _("Secondary")}, {"name": "intensity", "kind": settings.Kind.RANGE, "label": _("Intensity"), "min": 0, "max": 100}, {"name": "saturation", "kind": settings.Kind.RANGE, "label": _("Saturation"), "min": 0, "max": 255}, { "name": "period", "kind": settings.Kind.RANGE, "label": _("Period"), "min": 1000, "max": 20000, "display_seconds": True, }, {"name": "direction", "kind": settings.Kind.CHOICE, "label": _("Direction"), "choices": cls._DIRECTIONS}, ] setting.fields_map = {eid: (id_choices[eid], {field: 1 for field in cls._EFFECT_FIELDS[eid]}) for eid in supported} return setting def write(self, value, save=True): # Writing the 0x0621 cluster effect re-fills every LED uniformly; the # firmware treats that as dropping the host per-zone buffer. After a # Static write, re-overlay the per-zone paint so individually-colored # zones survive a LEDs Primary change. _headset_reassert_zone_layer is # a no-op for non-Static effects (the animation masks per-zone). result = super().write(value, save) if result is not None and self._device.online and _headset_led_control_on(self._device): _headset_reassert_zone_layer(self._device) return result # ---------------------------------------------------------------------------- # LogiVoice (0x0900 + 0x0901..0x0907) — read-only presentation pass. # # Per module we auto-generate two settings: # 1. A flat State toggle — reads GetState (fn 1), renders as a boolean. # Top-level so users see a direct on/off indicator at a glance. # 2. A collapsible "Parameters" panel — one MULTIPLE_RANGE-kind setting # that reads GetParameters (fn 3) once and distributes the bytes to # per-field sliders. The existing MultipleRangeControl widget is # collapsible by default, so the field-level clutter stays folded. # # Writes are disabled — the Parameters struct carries fields whose wire # encodings are still ambiguous (see logivoice.py) and a SetParameters # write must bundle all fields at once. A write pass can be added once # each field's encoding is confirmed live. # ---------------------------------------------------------------------------- class _LogiVoiceStateSetting(settings.Setting): """Per-module State toggle. Reads GetState (fn 1) and writes SetState (fn 0). State wire format is unambiguous (one byte: 0 = off, 1 = on), so this is the one piece of the LogiVoice surface we enable for writes. The per-module Parameters struct stays read-only until each field's encoding is confirmed. """ rw_options = {"read_fnid": logivoice.FN_GET_STATE, "write_fnid": logivoice.FN_SET_STATE} validator_class = settings_validator.BooleanValidator @classmethod def build(cls, device): # Corpus probe runs here (once per module) so -dd users get a full # snapshot of state + raw Parameters + raw Info for future decoding. try: logivoice.probe_module(device, cls.feature) except Exception as e: logger.debug("LogiVoice probe_module(%s) raised %s", cls.feature, e) return super().build(device) class _LogiVoiceModuleItem: """Top-level MULTIPLE_RANGE item representing one LogiVoice module. One `item` per setting — the module itself. `__int__` returns the feature id so the Setting's reply dict is keyed predictably. """ def __init__(self, feature: hidpp20_constants.SupportedFeature): self._feature = feature self.id = logivoice.MODULE_SLUGS.get(feature, f"0x{int(feature):04X}") self.index = 0 def __int__(self): return int(self._feature) def __str__(self): return logivoice.MODULE_NAMES.get(self._feature, f"0x{int(self._feature):04X}") class _LogiVoiceFieldSubItem: """MULTIPLE_RANGE sub-item wrapping one decoded Parameters field. MultipleRangeControl reads minimum/maximum/length/widget/str(). We pick SpinButton for wide ranges (0..65535) where a 64k-step slider is useless, and Scale for small ranges (e.g. signed int8 thresholds). """ def __init__(self, field: logivoice.Field): self._field = field self.id = field.name self.minimum = field.min_value self.maximum = field.max_value self.length = field.byte_count self.widget = "SpinButton" if (field.max_value - field.min_value) > 512 else "Scale" def __int__(self): return hash(self.id) & 0xFFFFFF def __str__(self): return self._field.label + (" (raw)" if self._field.opaque else "") class _LogiVoiceParametersValidator(settings_validator.MultipleRangeValidator): """Reads the whole GetParameters struct once and distributes bytes to fields. MULTIPLE_RANGE's default read loop fires prepare_read_item once per top- level item; we have exactly one item (the module), so this issues a single GetParameters call. validate_read_item parses the shared reply into a {field_name: value} dict. Writes are blocked. """ def __init__(self, feature: hidpp20_constants.SupportedFeature): fields = logivoice.PARAMETERS_FIELDS.get(feature, []) self._fields = list(fields) item = _LogiVoiceModuleItem(feature) sub_items = {item: [_LogiVoiceFieldSubItem(f) for f in fields]} super().__init__(items=[item], sub_items=sub_items) def prepare_read_item(self, item): return b"" # GetParameters takes no wire arguments def validate_read(self, reply_bytes): # Setting.read() calls validate_read with the raw GetParameters reply. # MultipleRangeValidator only defines validate_read_item, so wrap that # call — we have a single item (the module) so one call suffices. item = self.items[0] return {int(item): self.validate_read_item(reply_bytes, item)} def validate_read_item(self, reply_bytes, item): parsed = {} # Key by str(sub_item) so MultipleRangeControl.set_value can look up # values via v[str(sub_item)] — the UI uses the label as the dict key. for sub in self.sub_items[item]: f = sub._field end = f.offset + f.byte_count if end > len(reply_bytes): continue chunk = reply_bytes[f.offset : end] if f.byte_count == 1: v = struct.unpack("b" if f.signed else "B", chunk)[0] elif f.byte_count == 2: v = struct.unpack(">h" if f.signed else ">H", chunk)[0] else: v = int.from_bytes(chunk, "big", signed=f.signed) parsed[str(sub)] = v return parsed def prepare_write_item(self, item, value): return None def prepare_write(self, value): return None class _LogiVoiceParametersSetting(settings.Setting): """Collapsible read-only display of one module's GetParameters struct.""" rw_options = {"read_fnid": logivoice.FN_GET_PARAMETERS} persist = False kind = settings.Kind.MULTIPLE_RANGE @classmethod def build(cls, device): if not logivoice.PARAMETERS_FIELDS.get(cls.feature): return None rw = settings.FeatureRW(cls.feature, **cls.rw_options) validator = _LogiVoiceParametersValidator(cls.feature) return cls(device, rw, validator) def write(self, map, save=True): return None def _logivoice_make_state_class(feature: hidpp20_constants.SupportedFeature): slug = logivoice.MODULE_SLUGS.get(feature) if not slug: return None module_name = logivoice.MODULE_NAMES.get(feature, f"0x{int(feature):04X}") attrs = { "name": f"logivoice-{slug}-state", "label": f"LogiVoice {module_name}", "description": f"Enable the headset {module_name} processing block.", "feature": feature, } return type(f"LogiVoice_{slug}_State", (_LogiVoiceStateSetting,), attrs) def _logivoice_make_parameters_class(feature: hidpp20_constants.SupportedFeature): slug = logivoice.MODULE_SLUGS.get(feature) if not slug or not logivoice.PARAMETERS_FIELDS.get(feature): return None module_name = logivoice.MODULE_NAMES.get(feature, f"0x{int(feature):04X}") attrs = { "name": f"logivoice-{slug}-parameters", "label": f"LogiVoice {module_name}: Parameters (read-only)", "description": ( f"Decoded {module_name} GetParameters fields. " "Opaque raw values shown where the wire encoding isn't confirmed yet." ), "feature": feature, } return type(f"LogiVoice_{slug}_Parameters", (_LogiVoiceParametersSetting,), attrs) _LOGIVOICE_SETTINGS: list[type] = [] for _feature in logivoice.PARAMETERS_FIELDS: _state_cls = _logivoice_make_state_class(_feature) if _state_cls is not None: _LOGIVOICE_SETTINGS.append(_state_cls) # Parameters panels are read-only and the wire encoding is only # partially decoded — hide from the UI until we can write them back. # _params_cls = _logivoice_make_parameters_class(_feature) # if _params_cls is not None: # _LOGIVOICE_SETTINGS.append(_params_cls) 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 def write(self, value, save=True): # Snap to firmware-driven halving levels (off only at exact 0). steps = getattr(self._validator, "steps", 0) marks = halving_marks(self._validator.max_value, steps) if value is not None and marks: if value <= 0: value = 0 else: nonzero = [m for m in marks if m > 0] if nonzero: value = min(reversed(nonzero), key=lambda m: abs(m - value)) return super().write(value, save) 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") steps_and_flags = reply[2] caps = reply[3] min_value = int.from_bytes(reply[4:6], byteorder="big") on_off = bool(caps & 0x04) if logger.isEnabledFor(logging.DEBUG): logger.debug( "%s BrightnessControl getInfo: %s — max=%d min=%d steps_and_flags=0x%02x caps=0x%02x on_off=%s", device, reply[:8].hex(), max_value, min_value, steps_and_flags, caps, on_off, ) validator = cls(min_value=min_value, max_value=max_value, byte_count=2) validator.on_off = on_off validator.steps = steps_and_flags & 0x0F device._brightness_steps = validator.steps # for sibling settings (e.g. idle Dim) return validator class LEDControl(settings.Setting): name = "led_control" label = _("LED Control") description = _("Allow Solaar to control LED zones.") feature = _F.COLOR_LED_EFFECTS rw_options = {"read_fnid": 0x70, "write_fnid": 0x80} # Two-state setting — render as a Gtk.Switch rather than a 2-option combo. # true_value=1 / false_value=0 are the wire bytes for Solaar / Device mode. validator_class = settings_validator.BooleanValidator validator_options = {"true_value": 1, "false_value": 0} def _pre_read(self, cached, key=None): # Migrate legacy int values (0/1) stored under the old ChoicesValidator # to bool so the switch widget gets a value it can set_state() on. super()._pre_read(cached, key) if isinstance(self._value, int) and not isinstance(self._value, bool): self._value = self._value != 0 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 enabled.") 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": 1000, "max": 20000, "display_seconds": True, } 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} saturation_field = {"name": _LEDP.saturation, "kind": settings.Kind.RANGE, "label": _("Saturation"), "min": 0, "max": 255} form_field = {"name": _LEDP.form, "kind": settings.Kind.CHOICE, "label": _("Waveform"), "choices": hidpp20.LedFormChoices} direction_field = { "name": _LEDP.direction, "kind": settings.Kind.CHOICE, "label": _("Direction"), "choices": hidpp20.LedDirectionChoices, } # Per-widget visibility driven by LEDEffects[ID][1]; RGBEffectSetting # overrides this list to drop ramp/form on 0x8071. possible_fields = [ color_field, speed_field, period_field, intensity_field, ramp_field, saturation_field, form_field, direction_field, ] @classmethod def setup(cls, device, read_fnid, write_fnid, suffix): infos = device.led_effects possible_fields = cls._device_possible_fields(device) 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] + possible_fields setting.fields_map = hidpp20.LEDEffects settings_.append(setting) return settings_ @classmethod def _device_possible_fields(cls, device): return _possible_fields_with_direction_filter(device, cls.possible_fields, cls.direction_field) @classmethod def build(cls, device): return cls.setup(device, 0xE0, 0x30, b"") class RGBControl(settings.Setting): name = "rgb_control" label = _("LED Control") description = _("Allow Solaar to control LED zones.") feature = _F.RGB_EFFECTS rw_options = {"read_fnid": 0x50, "write_fnid": 0x50} # Two-state setting — render as a Gtk.Switch rather than a 2-option combo. # true_value=3 / false_value=0 are the wire bytes for Solaar / Device mode # returned by GetSWControl after the 1-byte sub-fn echo. Mode 3 is the # full SW takeover the claim handshake below expects. validator_class = settings_validator.BooleanValidator validator_options = {"true_value": 3, "false_value": 0, "write_prefix_bytes": b"\x01", "read_skip_byte_count": 1} def _pre_read(self, cached, key=None): # Migrate legacy int values (0/3) stored under the old ChoicesValidator # to bool so the switch widget gets a value it can set_state() on. super()._pre_read(cached, key) if isinstance(self._value, int) and not isinstance(self._value, bool): self._value = self._value != 0 def write(self, value, save=True): assert hasattr(self, "_value") assert hasattr(self, "_device") assert value is not None device = self._device if not device.online: return None if self._value != value: self.update(value, save) claiming = int(value) != 0 # any non-zero value is a Solaar-side claim if claiming: self._claim_sw_control(device) else: self._release_sw_control(device) return value def _claim_sw_control(self, device): # Disable firmware power management via profile management or onboard profiles if device.features and _F.PROFILE_MANAGEMENT in device.features: device.feature_request(_F.PROFILE_MANAGEMENT, 0x60, b"\x05") elif device.features and _F.ONBOARD_PROFILES in device.features: device.feature_request(_F.ONBOARD_PROFILES, 0x10, b"\x02") # Claim LED pipeline: SetSWControl(mode=3, flags=5) device.feature_request(_F.RGB_EFFECTS, 0x50, rgb_power.SW_ACTIVE) # Reset per-key one-shot flags so the first write after this claim # re-fires the prep + double-send. for s in device.settings: if s.name == "per-key-lighting": s._frame_settled = False s._prep_pushed = False break # Start software power management rgb_power.start(device) # Register cleanup for graceful release on device close if rgb_power.cleanup not in device.cleanups: device.cleanups.append(rgb_power.cleanup) # Repaint LEDs with Solaar's saved state. Without this the firmware's # last-active onboard profile keeps showing until the user changes # something — the takeover would look like it did nothing. self._repaint_after_claim(device) def _repaint_after_claim(self, device): """Push saved zone effects and (if opted in) per-key buffer to the device after a fresh SW claim. Best-effort: individual failures get logged but don't abort the rest of the repaint.""" for s in device.settings: if s.name.startswith("rgb_zone_") and s._value is not None: try: s.write(s._value, save=False) except Exception as e: logger.warning("%s: post-claim repaint of %s failed: %s", device, s.name, e) perkey, has_paint = rgb_power.perkey_has_paint(device) if has_paint and perkey._value is not None: try: perkey.write(perkey._value, save=False) except Exception as e: logger.warning("%s: post-claim per-key repaint failed: %s", device, e) def _release_sw_control(self, device): # If we never claimed in this session, don't touch the device at all. # The presence of an RGBPowerManager is the canonical "we claimed" signal # — _claim_sw_control creates it via rgb_power.start, and stop() pops it. had_claim = rgb_power.get_manager(device) is not None rgb_power.stop(device) if not had_claim: return # Release LED pipeline: SetSWControl(mode=0, flags=0) device.feature_request(_F.RGB_EFFECTS, 0x50, rgb_power.SW_RELEASE) # Restore firmware power management if device.features and _F.PROFILE_MANAGEMENT in device.features: device.feature_request(_F.PROFILE_MANAGEMENT, 0x60, b"\x03") elif device.features and _F.ONBOARD_PROFILES in device.features: device.feature_request(_F.ONBOARD_PROFILES, 0x10, b"\x01") # Keep cleanup registered on devices that support the shutdown effect # cap — it also fires the firmware shutdown animation trigger on exit. if not getattr(device, "_rgb_has_shutdown_cap", False): if rgb_power.cleanup in device.cleanups: device.cleanups.remove(rgb_power.cleanup) class RGBIdleTimeout(settings.Setting): name = "rgb_idle_timeout" label = _("Idle Timeout") description = _("Time without input before LED idle effect starts.") + "\n" + _("LED Control needs to be enabled.") feature = _F.RGB_EFFECTS choices_universe = common.NamedInts( **{ "Disabled": 0, "15 Seconds": 15, "30 Seconds": 30, "1 Minute": 60, "2 Minutes": 120, "5 Minutes": 300, } ) validator_class = settings_validator.ChoicesValidator validator_options = {"choices": choices_universe} class rw_class: def __init__(self, feature, **kwargs): self.feature = feature self.kind = settings.FeatureRW.kind def read(self, device): return common.int2bytes(60, 2) # default 1 minute def write(self, device, data_bytes): timeout = int.from_bytes(data_bytes, byteorder="big") mgr = rgb_power.get_manager(device) if mgr: mgr.set_idle_timeout(timeout) return True class RGBIdleEffect(settings.Setting): """Idle-effect setting with per-effect sub-widgets. Persisted value is an LEDEffectSetting; legacy bare-int values are migrated in `_pre_read`.""" name = "rgb_idle_effect" label = _("Idle Effect") description = ( _("What happens to LEDs when idle — dim to a percentage, change the base color, or play an animation.") + "\n" + _("LED Control needs to be enabled.") ) feature = _F.RGB_EFFECTS # Reuse zone fields so idle controls match the active-zone setup exactly. # Idle-specific intensity override carries the halving marks for Dim. intensity_field = {**LEDZoneSetting.intensity_field, "halving": True} period_field = LEDZoneSetting.period_field saturation_field = LEDZoneSetting.saturation_field speed_field = LEDZoneSetting.speed_field direction_field = LEDZoneSetting.direction_field color_field = LEDZoneSetting.color_field possible_fields = [color_field, speed_field, period_field, intensity_field, saturation_field, direction_field] class rw_class: def __init__(self, feature, **kwargs): self.feature = feature self.kind = settings.FeatureRW.kind def read(self, device): return hidpp20.LEDEffectSetting(ID=0x80, intensity=50).to_bytes() def write(self, device, data_bytes): value = hidpp20.LEDEffectSetting.from_bytes(data_bytes) mgr = rgb_power.get_manager(device) if mgr: mgr.set_idle_effect(value) return True def _pre_read(self, cached, key=None): """Migrate legacy bare-int values to LEDEffectSetting on first read.""" super()._pre_read(cached, key) if self._value is None or isinstance(self._value, hidpp20.LEDEffectSetting): return if not isinstance(self._value, int): return legacy = self._value if legacy == 0: migrated = hidpp20.LEDEffectSetting(ID=0x00) elif legacy in (25, 50, 75): migrated = hidpp20.LEDEffectSetting(ID=0x80, intensity=legacy) elif legacy == 0x0A: migrated = hidpp20.LEDEffectSetting(ID=0x0A, period=3000, intensity=100) elif legacy == 0x0B: migrated = hidpp20.LEDEffectSetting(ID=0x0B, period=3000) else: return # unrecognized — leave alone, write() will error informatively if logger.isEnabledFor(logging.DEBUG): logger.debug( "%s: migrating legacy bare-int %s to LEDEffectSetting on %s", self.name, legacy, self._device, ) self._value = migrated if getattr(self._device, "persister", None) is not None: self._device.persister[self.name] = self._value # Ripple needs keyboard input to animate so it can't run while idle. # Disabled (0x00) and Dim (0x80) are seeded into choice_ids directly. # Static (0x01) is also seeded so it appears right below Dim regardless # of probe-derived ID ordering. _IDLE_EXCLUDED_IDS = frozenset({0x00, 0x0B, 0x17}) @classmethod def build(cls, device): rw = cls.rw_class(cls.feature) # No change → Dim → Static, then any probed device-specific effects. choice_ids = [0x00, 0x80, 0x01] probed = set() try: infos = device.led_effects if infos and infos.zones: probed = {int(e.ID) for e in infos.zones[0].effects} except Exception: pass for eid in sorted(probed): if eid in cls._IDLE_EXCLUDED_IDS or eid in choice_ids: continue if eid not in hidpp20.LEDEffects: continue choice_ids.append(eid) idle_disabled = common.NamedInt(0x00, _("No change")) choices = [idle_disabled if i == 0x00 else hidpp20.LEDEffects[i][0] for i in choice_ids] ID_field = {"name": "ID", "kind": settings.Kind.CHOICE, "label": None, "choices": choices} fields_map = { 0x00: [idle_disabled, {}], 0x80: [ hidpp20.LEDEffects[0x80][0], {hidpp20.LEDParam.intensity: 0}, {hidpp20.LEDParam.intensity: 50}, ], } for eid in choice_ids: if eid not in fields_map: fields_map[eid] = hidpp20.LEDEffects[eid] validator = settings_validator.HeteroValidator(data_class=hidpp20.LEDEffectSetting, options=None, readable=True) setting = cls(device, rw, validator) setting.possible_fields = [ID_field] + _possible_fields_with_direction_filter( device, cls.possible_fields, cls.direction_field ) setting.fields_map = fields_map return setting class RGBSleepTimeout(settings.Setting): name = "rgb_sleep_timeout" label = _("Sleep Timeout") description = _("Time without input before LEDs fade off completely.") + "\n" + _("LED Control needs to be enabled.") feature = _F.RGB_EFFECTS choices_universe = common.NamedInts( **{ "Disabled": 0, "2 Minutes": 120, "5 Minutes": 300, "10 Minutes": 600, "15 Minutes": 900, "30 Minutes": 1800, } ) validator_class = settings_validator.ChoicesValidator validator_options = {"choices": choices_universe} class rw_class: def __init__(self, feature, **kwargs): self.feature = feature self.kind = settings.FeatureRW.kind def read(self, device): return common.int2bytes(300, 2) # default 5 minutes def write(self, device, data_bytes): timeout = int.from_bytes(data_bytes, byteorder="big") mgr = rgb_power.get_manager(device) if mgr: mgr.set_sleep_timeout(timeout) return True class _RgbBootEffect: """NvConfig persistent boot/shutdown effect payload on RGBEffects (0x8071). Wire format (7 bytes, NvConfig cap 0x0001 startup / 0x0040 shutdown): [enabled, R1, G1, B1, R2, G2, B2] enabled: 0x01 on, 0x02 off. Colors are kept editable in both states so toggling Off doesn't lose the user's chosen color. """ _COLOR_ATTRS = ("color1", "color2") def __init__(self, ID=1, color1=0, color2=0): self.ID = int(ID) for k, v in (("color1", color1), ("color2", color2)): iv = int(v) & 0xFFFFFF setattr(self, k, common.ColorInt(iv)) @classmethod def from_bytes(cls, data, options=None): if data is None or len(data) < 7: return cls() c1 = (data[1] << 16) | (data[2] << 8) | data[3] c2 = (data[4] << 16) | (data[5] << 8) | data[6] return cls(ID=data[0], color1=c1, color2=c2) def to_bytes(self, options=None): return bytes( [ self.ID & 0xFF, (self.color1 >> 16) & 0xFF, (self.color1 >> 8) & 0xFF, self.color1 & 0xFF, (self.color2 >> 16) & 0xFF, (self.color2 >> 8) & 0xFF, self.color2 & 0xFF, ] ) def __eq__(self, other): return isinstance(other, self.__class__) and self.to_bytes() == other.to_bytes() def __str__(self): return yaml.dump(self, width=float("inf")).rstrip("\n") @classmethod def from_yaml(cls, loader, node): return cls(**loader.construct_mapping(node)) @classmethod def to_yaml(cls, dumper, data): return dumper.represent_mapping("!RgbBootEffect", data.__dict__, flow_style=True) yaml.SafeLoader.add_constructor("!RgbBootEffect", _RgbBootEffect.from_yaml) yaml.add_representer(_RgbBootEffect, _RgbBootEffect.to_yaml) class _RgbBootEffectSetting(settings.Setting): """Base for NvConfig persistent effect toggles on RGBEffects (0x8071). Subclasses set cap_id (0x0001 startup, 0x0040 shutdown). Build probes the cap once and suppresses the setting if the device doesn't answer — so the setting auto-appears on any 0x8071 device that supports the cap, without needing per-model gating. """ feature = _F.RGB_EFFECTS cap_id: int = 0 _ENABLED_CHOICES = common.NamedInts(**{"On": 1, "Off": 2}) _COLOR1_FIELD = {"name": "color1", "kind": settings.Kind.COLOR, "label": _("Primary")} _COLOR2_FIELD = {"name": "color2", "kind": settings.Kind.COLOR, "label": _("Secondary")} class rw_class: kind = settings.FeatureRW.kind def __init__(self, feature, cap_id): self.feature = feature self.cap_id = cap_id self._cap_bytes = bytes([(cap_id >> 8) & 0xFF, cap_id & 0xFF]) def read(self, device): reply = device.feature_request(self.feature, 0x30, b"\x00" + self._cap_bytes) if reply is None or len(reply) < 10: return None return reply[3:10] # strip [sub-fn, capHi, capLo] echo def write(self, device, data_bytes): return device.feature_request(self.feature, 0x30, b"\x01" + self._cap_bytes + bytes(data_bytes)) @classmethod def build(cls, device): cap_bytes = bytes([(cls.cap_id >> 8) & 0xFF, cls.cap_id & 0xFF]) try: reply = device.feature_request(_F.RGB_EFFECTS, 0x30, b"\x00" + cap_bytes) except exceptions.FeatureCallError: return None # device rejects this cap — gate the setting off if reply is None or len(reply) < 10: return None # Register the firmware shutdown trigger on cap 0x0040 devices so the # animation plays on Solaar exit. This depends only on the device # having the cap (probe above), not on the UI control being shown — # so it runs before the allowlist gate below. rgb_power.cleanup fires # mode 0 at its end when _rgb_has_shutdown_cap is set. See # solaar_shutdown_effect_trigger_spec.md. if cls.cap_id == 0x0040: device._rgb_has_shutdown_cap = True if rgb_power.cleanup not in device.cleanups: device.cleanups.append(rgb_power.cleanup) # NVconfig-saved colors are default-DENY: the boot-effect setting is # shown only on models explicitly known-good. allowed is None on an # unlisted model/cap (suppress), or a (possibly empty) set of color # fields the firmware honors. SOLAAR_EXPERIMENTAL unmasks everything. allowed = device_quirks.rgb_effects_nvconfig_allowed_fields(device, cls.cap_id) if allowed is None: return None rw = cls.rw_class(cls.feature, cls.cap_id) validator = settings_validator.HeteroValidator(data_class=_RgbBootEffect, options=None) setting = cls(device, rw, validator) # Render the enabled byte as a right-aligned Gtk.Switch (like a plain # TOGGLE setting) rather than an inline Off/On combo. on_value/off_value # carry the wire-format byte (0x01 = on, 0x02 = off) through to the # data class without changing the byte semantics. id_field = {"name": "ID", "kind": settings.Kind.TOGGLE, "label": None, "on_value": 1, "off_value": 2} # Keep all color widgets in possible_fields so reads still populate # them and writes still carry their values — the firmware may store # bytes it doesn't visibly use. fields_map controls UI visibility. setting.possible_fields = [id_field, cls._COLOR1_FIELD, cls._COLOR2_FIELD] # An empty allowed set shows the On/Off toggle only (cap works, colors # don't); a non-empty set adds the listed color pickers. visible = {n: o for n, o in (("color1", 1), ("color2", 4)) if n in allowed} # Both On/Off map to the same visible field set so colors stay editable # when the effect is Off (pre-stages them for next enable). setting.fields_map = { int(cls._ENABLED_CHOICES["On"]): (cls._ENABLED_CHOICES["On"], visible), int(cls._ENABLED_CHOICES["Off"]): (cls._ENABLED_CHOICES["Off"], visible), } return setting class RgbStartupAnimation(_RgbBootEffectSetting): name = "rgb_startup_animation" label = _("Startup Animation") description = ( _( "Firmware-played animation when the keyboard wakes from deep sleep or powers on.\n" "Setting persists on the device (non-volatile)." ) + "\n" + _("Device default: Primary #FF0081, Secondary #80AAFF.") ) cap_id = 0x0001 class RgbShutdownAnimation(_RgbBootEffectSetting): name = "rgb_shutdown_animation" label = _("Shutdown Animation") description = ( _("Firmware-played animation when the keyboard powers off.\n" "Setting persists on the device (non-volatile).") + "\n" + _("Device default: Primary #FF0081, Secondary #80AAFF.") ) cap_id = 0x0040 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 enabled.") feature = _F.RGB_EFFECTS # 0x8071 firmware-fixes ramp/form bytes; drop those widgets here. possible_fields = [ LEDZoneSetting.color_field, LEDZoneSetting.speed_field, LEDZoneSetting.period_field, LEDZoneSetting.intensity_field, LEDZoneSetting.saturation_field, LEDZoneSetting.direction_field, ] @classmethod def build(cls, device): return cls.setup(device, None, 0x10, b"\x01") def write(self, value, save=True): """Push zone effect to wire unless per-key is the dominant layer. Per-key acts as a multi-color sub-mode of Static: when zone is Static and per-key is opted in with paint, per-key owns the visible layer. Non-Static zone effects (animations) always go to the wire — per-key defers to the firmware animation. Transitions into Static still push the Static wire so any running animation stops. """ assert hasattr(self, "_value") assert hasattr(self, "_device") assert value is not None device = self._device if not device.online: return None perkey, has_paint = rgb_power.perkey_has_paint(device) new_is_static = int(getattr(value, "ID", 0) or 0) == rgb_power._EFFECT_STATIC old_value = self._value old_is_static = old_value is None or int(getattr(old_value, "ID", 0) or 0) == rgb_power._EFFECT_STATIC if has_paint and new_is_static and old_is_static: if save: changed = old_value != value self.update(value, save) if changed and perkey._fill_unset_zones_with_base_color(): perkey._send_with_retry(0x70, b"\x00") # FrameEnd # Resync the dim ramp's start colors for unset cells. mgr = rgb_power.get_manager(device) if mgr is not None: new_base = int(getattr(value, "color", 0) or 0) unset_zones = perkey._unset_zone_ids() if unset_zones: mgr.notify_perkey_bulk_changed({z: new_base for z in unset_zones}) return value # Persist undimmed value first (single source of truth). if self._value != value: self.update(value, save) # rgb_control gate: skip wire when the user has LED Control off. rgb_ctrl = next((s for s in device.settings if s.name == "rgb_control"), None) if rgb_ctrl is not None and not rgb_ctrl._value: return value wire_value = self._translate_for_wire(value) if wire_value is None: # SLEEPING — _wake() will re-push at full brightness. return value current_value = None if self._validator.needs_current_value: current_value = self._rw.read(device) data_bytes = self._validator.prepare_write(wire_value, current_value) if data_bytes is None: return None reply = self._rw.write(device, data_bytes) if not reply: return None # Animation → Static transition with per-key paint: the Static wire # we just pushed stops the animation; now repaint the per-key # multi-color overlay on top so the user gets a seamless switch. if has_paint and new_is_static and perkey is not None: try: perkey.write(perkey._value, save=False) except Exception as e: logger.warning("%s: per-key repaint after Static restore failed: %s", device, e) # Resync any in-flight dim ramp to the new color. mgr = rgb_power.get_manager(device) if mgr is not None and getattr(value, "color", None) is not None and self._rw.prefix: mgr.notify_zone_changed(self._rw.prefix[0], int(value.color)) return value def _translate_for_wire(self, value): """Clone `value` with `.color` translated through rgb_power state. Returns None for SLEEPING.""" saved_color = getattr(value, "color", None) if saved_color is None: return value wire_color = rgb_power.translate_for_device(self._device, int(saved_color)) if wire_color is None: return None if int(wire_color) == int(saved_color): return value # ACTIVE or no-op translation; reuse original # Build a shallow clone with translated color so the persister and the # in-memory _value keep the undimmed source-of-truth color. wire_attrs = dict(value.__dict__) wire_attrs["color"] = int(wire_color) return hidpp20.LEDEffectSetting(**wire_attrs) class PerKeyLighting(settings.Settings): name = "per-key-lighting" label = _("Per-key Lighting") description = ( _("Control per-key lighting.") + "\n" + _("LED Control needs to be enabled and the zone effect set to Static for per-key paint to be visible.") ) feature = _F.PER_KEY_LIGHTING_V2 keys_universe = special_keys.KEYCODES editor_class = "solaar.ui.perkey.control:PerKeyControl" # 0x8081 has no GetIndividualRgbZones — there's no way to ask the device # what colors are currently on the per-key buffer. read() returns the # canonical in-memory map for callers that need a value, but `solaar show` # honors this flag and skips its live-read line to avoid misleading output. live_readable = False @staticmethod def _wrap_color(value): # Wrap raw 24-bit-range ints in ColorInt so saved configs render as # ``0xrrggbb`` hex literals and `solaar show` prints hex. Sentinels # (NamedInt "No change" = -1) and existing ColorInt values pass # through untouched. # type(value) is int — exact match excludes NamedInt sentinels like # COLORSPLUS["No change"] = -1 and avoids re-wrapping ColorInts. if type(value) is int and 0 <= value <= 0xFFFFFF: # noqa: E721 return common.ColorInt(value) return value def update(self, value, save=True): if isinstance(value, dict): value = {k: self._wrap_color(v) for k, v in value.items()} super().update(value, save) def update_key_value(self, key, value, save=True): super().update_key_value(key, self._wrap_color(value), save) def _sw_control_held(self): """Return True if it's safe to push LED bytes to the wire. rgb_control is the gate: when the user has it off, LED writes must be silent no-ops at the wire. Never auto-flip it on from here — doing so rewrites the persister and turns the user-facing toggle into a lie.""" if getattr(self, "_has_rgb_effects", None) is None: self._has_rgb_effects = bool(self._device.features and _F.RGB_EFFECTS in self._device.features) if not self._has_rgb_effects: return True # No autonomous effect engine, no gate needed for s in self._device.settings: if s.name == "rgb_control": # _value may be bool (current) or int 3/0 (legacy persister value # before BooleanValidator migration); both coerce cleanly. return bool(s._value) return True # rgb_control not on this device → no gate to enforce # BUSY-retry backoff (ms). _BUSY_BACKOFF_MS = (30, 60, 90) def _send_with_retry(self, function, data, retries=3): # Retry on BUSY and timeout (both transient). Other FeatureCallError # codes abort — they're real bugs we shouldn't paper over. busy_attempt = 0 max_busy = len(self._BUSY_BACKOFF_MS) for attempt in range(retries + 1): try: reply = self._device.feature_request(self.feature, function, data) except exceptions.FeatureCallError as e: if getattr(e, "error", None) == hidpp20_constants.ErrorCode.BUSY and busy_attempt < max_busy: delay_ms = self._BUSY_BACKOFF_MS[busy_attempt] busy_attempt += 1 if logger.isEnabledFor(logging.DEBUG): logger.debug( "%s: per-key 0x%02x BUSY, retry %d/%d after %dms", self._device, function, busy_attempt, max_busy, delay_ms, ) sleep(delay_ms / 1000.0) continue logger.warning("%s: per-key 0x%02x rejected by device", self._device, function) return False if reply is not None: if (attempt > 0 or busy_attempt > 0) and logger.isEnabledFor(logging.DEBUG): logger.debug( "%s: per-key 0x%02x succeeded after %d timeout retries, %d BUSY retries", self._device, function, attempt, busy_attempt, ) return True if logger.isEnabledFor(logging.DEBUG): logger.debug( "%s: per-key 0x%02x timed out (attempt %d/%d)", self._device, function, attempt + 1, retries + 1, ) return False def _zone_base_color(self): """Color used to fill per-key unset cells. Defers to rgb_power.effective_zone_base_color which returns black when the zone effect is ignored, otherwise the saved zone color.""" return rgb_power.effective_zone_base_color(self._device) def _send_zone_color(self, zone_id, wire_color): """Emit a single-zone color update (fn 0x10). `wire_color` is the already-translated color that should appear on the device.""" r = (wire_color >> 16) & 0xFF g = (wire_color >> 8) & 0xFF b = wire_color & 0xFF return self._send_with_retry(0x10, bytes([zone_id, r, g, b])) def _unset_zone_ids(self): """Per-key zone IDs that don't have a user-painted color.""" no_change = special_keys.COLORSPLUS["No change"] user_set = set() if self._value: for key, color in self._value.items(): if color != no_change and isinstance(color, int) and color >= 0: user_set.add(int(key)) return [int(k) for k in self._validator.choices if int(k) not in user_set] def _fill_unset_zones_with_base_color(self): """Push the zone base color (translated for current power state) to any per-key cell the user hasn't painted. Caller commits FrameEnd.""" if not self._has_rgb_effects: return True zone_base = self._zone_base_color() wire_base = rgb_power.translate_for_device(self._device, zone_base) if wire_base is None: return True # SLEEPING — caller defers wire entirely r = (wire_base >> 16) & 0xFF g = (wire_base >> 8) & 0xFF b = wire_base & 0xFF unset_zones = self._unset_zone_ids() if not unset_zones: return True remaining = list(unset_zones) ok = True while remaining: batch = remaining[:13] remaining = remaining[13:] if not self._send_with_retry(0x60, bytes([r, g, b]) + bytes(batch)): ok = False return ok def read(self, cached=True): # The 0x8081 protocol has no GetIndividualRgbZones — the device cannot # report its current per-key buffer back. So a "live" read is fictional: # we either return what we last wrote (the persisted/in-memory map, # which is the canonical truth for this setting) or, on a fresh device # with no persisted state, fabricate an all-"No change" sentinel map # as the starting point. Returning the cached value unconditionally # also fixes `solaar show` showing every key as "No change" on the # live line — it now matches what's actually on the keyboard. self._pre_read(cached) if 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"] # starting state, no per-key write yet self._value = reply_map return reply_map def _send_perkey_frame(self, map, no_change): """Send all per-key sub-packets for `map`, fill unset cells with the zone base color, and commit with FrameEnd. Returns True on success.""" # Bucket by color; single-bucket allows the range-update fast path. table = {} for key, value in map.items(): if value in table: table[value].append(key) else: table[value] = [key] ok = True if len(table) == 1: # use range update for value, keys in table.items(): # only one, of course if value != no_change: # this signals no change, so don't update at all wire = rgb_power.translate_for_device(self._device, int(value)) data_bytes = keys[0].to_bytes(1, "big") + keys[-1].to_bytes(1, "big") + wire.to_bytes(3, "big") if not self._send_with_retry(0x50, data_bytes): # range update command to update all keys ok = False else: data_bytes = b"" for value, keys in table.items(): if value != no_change: # this signals no change, so ignore it wire = rgb_power.translate_for_device(self._device, int(value)) while len(keys) > 3: # use an optimized update command that can update up to 13 keys data = wire.to_bytes(3, "big") + b"".join([key.to_bytes(1, "big") for key in keys[0:13]]) if not self._send_with_retry(0x60, data): # single-value multiple-keys update ok = False keys = keys[13:] for key in keys: data_bytes += key.to_bytes(1, "big") + wire.to_bytes(3, "big") if len(data_bytes) >= 16: # up to four values are packed into a regular update if not self._send_with_retry(0x10, data_bytes): ok = False data_bytes = b"" if len(data_bytes) > 0: # update any remaining keys if not self._send_with_retry(0x10, data_bytes): ok = False # Fill unset zones before FrameEnd so the frame commits atomically. if not self._fill_unset_zones_with_base_color(): ok = False # Suppress FrameEnd on partial failure to avoid a visibly wrong commit. if ok: if not self._send_with_retry(0x70, b"\x00"): logger.warning("%s: per-key FrameEnd failed; frame not committed", self._device) ok = False else: logger.warning( "%s: per-key frame had failed sub-packets; suppressing FrameEnd to avoid partial commit", self._device, ) return ok def write(self, map, save=True): if self._device.online: # Persist undimmed (single source of truth). self.update(map, save) if not self._sw_control_held(): return map # gate is off — keep state in memory, skip the wire # Per-key is a sub-mode of Static — when zone is animating, the # firmware engine owns the visible layer. if not rgb_power.zone_effect_is_static(self._device): return map no_change = special_keys.COLORSPLUS["No change"] # SLEEPING — defer wire to wake. for value in map.values(): if value != no_change and rgb_power.translate_for_device(self._device, int(value)) is None: return map # Mouse-only prep, one-shot per claim — keyboards don't need it. if ( getattr(self, "_has_rgb_effects", False) and not getattr(self, "_prep_pushed", False) and int(getattr(self._device, "kind", -1) or -1) == int(hidpp10_constants.DEVICE_KIND.mouse) ): if rgb_power.push_artanis_perkey_prep(self._device): self._prep_pushed = True ok = self._send_perkey_frame(map, no_change) # First frame after a claim sometimes lands before the firmware # has fully transitioned out of onboard mode — replay it once. if ok and not getattr(self, "_frame_settled", False): self._send_perkey_frame(map, no_change) self._frame_settled = True if ok: mgr = rgb_power.get_manager(self._device) if mgr is not None: mgr.notify_perkey_bulk_changed(map) return map def write_key_value(self, key, value, save=True): no_change = special_keys.COLORSPLUS["No change"] zone_id = int(key) if value != no_change: self.update_key_value(zone_id, value, save) if not self._device.online: return value if not self._sw_control_held(): return value # gate is off — state stored, no wire push # Per-key is a sub-mode of Static — defer to firmware animation. if not rgb_power.zone_effect_is_static(self._device): return value wire = rgb_power.translate_for_device(self._device, int(value)) if wire is None: return value # SLEEPING — wake re-pushes ok = True # Fill unset zones once so they don't show white-default. if not getattr(self, "_base_filled", False): if self._fill_unset_zones_with_base_color(): self._base_filled = True else: ok = False if ok and not self._send_zone_color(zone_id, wire): ok = False if ok: mgr = rgb_power.get_manager(self._device) if mgr is not None: mgr.notify_perkey_changed(zone_id, int(value)) if not self._send_with_retry(0x70, b"\x00"): logger.warning("%s: per-key FrameEnd failed; frame not committed", self._device) else: logger.warning("%s: per-key write failed; suppressing FrameEnd", self._device) return value else: # Un-set: store "No change", push the zone base color to that cell. self.update_key_value(zone_id, no_change, save) if not self._device.online: return no_change if not self._sw_control_held(): return no_change # gate is off — state stored, no wire push if not rgb_power.zone_effect_is_static(self._device): return no_change zone_base = self._zone_base_color() wire_base = rgb_power.translate_for_device(self._device, zone_base) if wire_base is None: return no_change if self._send_zone_color(zone_id, wire_base): mgr = rgb_power.get_manager(self._device) if mgr is not None: mgr.notify_perkey_changed(zone_id, zone_base) if not self._send_with_retry(0x70, b"\x00"): logger.warning("%s: per-key FrameEnd failed; frame not committed", self._device) else: logger.warning("%s: per-key un-set write failed; suppressing FrameEnd", self._device) return no_change class rw_class(settings.FeatureRWMap): pass class validator_class(settings_validator.MapRangeValidator): _COLOR_RANGE = settings_validator.Range(min=0, max=0xFFFFFF, byte_count=3, value_type=common.ColorInt) @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 RegisterDpi, RegisterFnSwap, # working HiResScroll, # simple LowresMode, # simple HiresSmoothInvert, # working 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, PerKeyLighting, BrightnessControl, RGBIdleEffect, RGBIdleTimeout, RGBSleepTimeout, RgbStartupAnimation, RgbShutdownAnimation, FnSwap, # simple NewFnSwap, # simple K375sFnSwap, # working ReprogrammableKeys, # working PersistentRemappableAction, DivertKeys, # working DisableKeyboardKeys, # working ForceSensing, CrownSmooth, # working DivertCrown, # working DivertGkeys, # working MKeyLEDs, # working MRKeyLED, # working Multiplatform, # working DualPlatform, # simple ChangeHost, # working Gesture2Gestures, # working Gesture2Divert, Gesture2Params, # working AnalogButtonTuning, HapticLevel, PlayHapticWaveForm, Sidetone, Equalizer, ADCPower, HeadsetEcoMode, HeadsetDoNotDisturb, HeadsetMicMute, HeadsetMicSNR, HeadsetAINR, HeadsetAINRLevel, HeadsetSidetone, HeadsetMicGain, HeadsetMixBalance, HeadsetAutoSleep, HeadsetOnboardEQ, HeadsetActiveEQPreset, HeadsetAdvancedEQ, HeadsetLEDControl, HeadsetOnboardEffect, HeadsetPerZoneLighting, HeadsetSignatureStartupEffect, HeadsetSignatureShutdownEffect, HeadsetSignaturePassiveEffect, *_LOGIVOICE_SETTINGS, ] 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, 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): logger.debug( "check_feature %s [%s]: min_version=%d > device feature version=%d; skipping", settings_class.name, settings_class.feature, settings_class.min_version, device.features.get_feature_version(settings_class.feature) or 0, ) return if device.features.get_hidden(settings_class.feature): flags = device.features.flags.get(settings_class.feature, 0) logger.debug( "check_feature %s [%s]: feature has INTERNAL flag set (flags=0x%02X); skipping", settings_class.name, settings_class.feature, flags, ) return try: 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: 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 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 [] new_absent = [] for sclass in SETTINGS: if sclass.feature: if device.persister: if sclass.name.endswith("_"): # Multi-setting prototype (e.g. rgb_zone_); persister stores child keys # like rgb_zone_1, never the prototype name itself. known_present = any(k.startswith(sclass.name) for k in device.persister) else: known_present = sclass.name in device.persister else: known_present = False already = any(s.name == sclass.name for s in already_known) if already: continue if not known_present and sclass.name in absent: # Silent-skip cache from an earlier run's failed build(). If the # feature is actually present on this device now, the cache is # stale (e.g. from a prior build that returned None for a # feature that currently works) — drop it and retry the probe. if sclass.feature in device.features: logger.debug( "check_feature_settings: retrying %s — cached in _absent but feature %s is present now", sclass.name, sclass.feature, ) absent.remove(sclass.name) if device.persister: device.persister["_absent"] = absent else: continue 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 new_absent: new_absent.remove(sclass.name) elif setting is None: 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: str) -> settings.Setting | None: for sclass in SETTINGS: 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 Solaar-1.1.20/lib/logitech_receiver/settings_validator.py000066400000000000000000001021751522022367600235360ustar00rootroot00000000000000from __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 not 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): # options=None for purely host-side settings — data_class handles bytes[0] as the ID. assert data_class 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. `value_type` is the int factory used to wrap values returned from `validate_read` — defaults to `int`, but settings that store RGB colors pass `common.ColorInt` so the values self-format as ``0xrrggbb`` in `solaar show` output and the YAML config file. 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 value_type: type = int 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) # Persisted dicts loaded from YAML come back as plain ints regardless # of the choice's `value_type`. Re-wrap raw ints through the configured # value_type (e.g. ColorInt) so they self-format consistently here and # in `solaar show`. Skip wrapping for NamedInt sentinels / subclasses # (`type(v) is int` is the exact-match guard). rng_by_int_key = {int(k): rng for k, rng in self.choices.items()} def _fmt(k): v = value[k] rng = rng_by_int_key.get(int(k)) if rng is not None and type(v) is int and rng.value_type is not int: # noqa: E721 try: v = rng.value_type(v) except Exception: pass return f"{k}:{v}" return "{" + ", ".join(_fmt(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 rng.value_type(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] Solaar-1.1.20/lib/logitech_receiver/special_keys.py000066400000000000000000001365261522022367600223130ustar00rootroot00000000000000## 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. # Reprogrammable keys information # Mostly from Logitech documentation, but with some edits for better Linux compatibility 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 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: 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( A=0x04, B=0x05, C=0x06, D=0x07, E=0x08, F=0x09, G=0x0A, H=0x0B, I=0x0C, J=0x0D, K=0x0E, L=0x0F, M=0x10, N=0x11, O=0x12, P=0x13, Q=0x14, R=0x15, S=0x16, T=0x17, U=0x18, V=0x19, W=0x1A, X=0x1B, Y=0x1C, Z=0x1D, ENTER=0x28, ESC=0x29, BACKSPACE=0x2A, TAB=0x2B, SPACE=0x2C, MINUS=0x2D, EQUAL=0x2E, LEFTBRACE=0x2F, RIGHTBRACE=0x30, BACKSLASH=0x31, HASHTILDE=0x32, SEMICOLON=0x33, APOSTROPHE=0x34, GRAVE=0x35, COMMA=0x36, DOT=0x37, SLASH=0x38, CAPSLOCK=0x39, F1=0x3A, F2=0x3B, F3=0x3C, F4=0x3D, F5=0x3E, F6=0x3F, F7=0x40, F8=0x41, F9=0x42, F10=0x43, F11=0x44, F12=0x45, SYSRQ=0x46, SCROLLLOCK=0x47, PAUSE=0x48, INSERT=0x49, HOME=0x4A, PAGEUP=0x4B, DELETE=0x4C, END=0x4D, PAGEDOWN=0x4E, RIGHT=0x4F, LEFT=0x50, DOWN=0x51, UP=0x52, NUMLOCK=0x53, KPSLASH=0x54, KPASTERISK=0x55, KPMINUS=0x56, KPPLUS=0x57, KPENTER=0x58, KP1=0x59, KP2=0x5A, KP3=0x5B, KP4=0x5C, KP5=0x5D, KP6=0x5E, KP7=0x5F, KP8=0x60, KP9=0x61, KP0=0x62, KPDOT=0x63, COMPOSE=0x65, POWER=0x66, KPEQUAL=0x67, F13=0x68, F14=0x69, F15=0x6A, F16=0x6B, F17=0x6C, F18=0x6D, F19=0x6E, F20=0x6F, F21=0x70, F22=0x71, F23=0x72, F24=0x73, OPEN=0x74, HELP=0x75, PROPS=0x76, FRONT=0x77, STOP=0x78, AGAIN=0x79, 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, 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, ) 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: 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) ACTIONID = NamedInts( Empty=0x00, Key=0x01, Mouse=0x02, Xdisp=0x03, Ydisp=0x04, Vscroll=0x05, Hscroll=0x06, Consumer=0x07, Internal=0x08, Power=0x09, ) MOUSE_BUTTONS = NamedInts( Mouse_Button_Left=0x0001, Mouse_Button_Right=0x0002, Mouse_Button_Middle=0x0004, Mouse_Button_Back=0x0008, Mouse_Button_Forward=0x0010, Mouse_Button_6=0x0020, Mouse_Button_Scroll_Left=0x0040, Mouse_Button_Scroll_Right=0x0080, Mouse_Button_9=0x0100, Mouse_Button_10=0x0200, Mouse_Button_11=0x0400, Mouse_Button_12=0x0800, Mouse_Button_13=0x1000, Mouse_Button_DPI=0x2000, Mouse_Button_15=0x4000, Mouse_Button_16=0x8000, ) MOUSE_BUTTONS._fallback = lambda x: f"unknown mouse button:{x:04X}" class HorizontalScroll(IntEnum): Left = 0x4000 Right = 0x8000 # Construct universe for Persistent Remappable Keys setting (only for supported values) 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 # 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+", } for val, name in modifiers.items(): for key in USB_HID_KEYCODES: KEYS[(ACTIONID.Key << 24) + (int(key) << 8) + val] = name + str(key) # Add HID Consumer Codes for code in HID_CONSUMERCODES: KEYS[(ACTIONID.Consumer << 24) + (int(code) << 8)] = str(code) # Add Mouse Buttons for code in MOUSE_BUTTONS: KEYS[(ACTIONID.Mouse << 24) + (int(code) << 8)] = str(code) # Add 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)" for key in KEYS: if (int(key) >> 24) in action_ids: keys[int(key)] = str(key) return keys 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) Solaar-1.1.20/lib/solaar/000077500000000000000000000000001522022367600150505ustar00rootroot00000000000000Solaar-1.1.20/lib/solaar/__init__.py000066400000000000000000000024371522022367600171670ustar00rootroot00000000000000## 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 pkgutil import subprocess import sys NAME = "Solaar" try: __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() except Exception: __version__ = pkgutil.get_data("solaar", "version").strip().decode() Solaar-1.1.20/lib/solaar/cli/000077500000000000000000000000001522022367600156175ustar00rootroot00000000000000Solaar-1.1.20/lib/solaar/cli/__init__.py000066400000000000000000000217251522022367600177370ustar00rootroot00000000000000## 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 argparse import logging import sys 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 logger = logging.getLogger(__name__) def _create_parser(): parser = argparse.ArgumentParser( prog=NAME.lower(), add_help=False, epilog=f"For details on individual actions, run `{NAME.lower()} --help`.", ) subparsers = parser.add_subparsers(title="actions", help="command-line action to perform") 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)', ) sp.set_defaults(action="show") 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" ) sp.set_defaults(action="probe") sp = subparsers.add_parser( "profiles", description="Print or load YAML dump of profiles.", epilog="Only works on active devices.", ) sp.add_argument( "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("profiles", nargs="?", help="file containing YAML dump of profiles to load") sp.set_defaults(action="profiles") sp = subparsers.add_parser( "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( "device", help="device to configure; 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 = 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.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 _cli_parser, actions = _create_parser() print_help = _cli_parser.print_help def _receivers(dev_path=None): for dev_info in base.receivers(): if dev_path is not None and dev_path != dev_info.path: continue try: r = receiver.create_receiver(base, dev_info) logger.debug("[%s] => %s", dev_info.path, r) if r: yield r except Exception as e: logger.exception("opening " + str(dev_info)) sys.exit(f"{NAME.lower()}: error: {str(e)}") def _receivers_and_devices(dev_path=None): for dev_info in base.receivers_and_devices(): if dev_path is not None and dev_path != dev_info.path: continue try: 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: logger.exception("opening " + str(dev_info)) sys.exit(f"{NAME.lower()}: error: {str(e)}") def _find_receiver(receivers, name): assert receivers assert name for r in receivers: if name in r.name.lower() or (r.serial is not None and name == r.serial.lower()): return r def _find_device(receivers, name): assert receivers assert name number = None if len(name) == 1: try: number = int(name) except Exception: pass else: assert not (number < 0) if number > 6: number = None for r in receivers: if not r.isDevice: # look for nth device of receiver if number: dev = r[number] if dev: yield dev count = r.count() else: # wired device, make a device list from it r.ping() r = [r] count = 1 for dev in r: if ( name == dev.serial.lower() or name == dev.codename.lower() or name == str(dev.kind).lower() or name in dev.name.lower() ): yield dev count -= 1 if not count: break def run(cli_args=None, hidraw_path=None): if cli_args: action = cli_args[0] args = _cli_parser.parse_args(cli_args) else: 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(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" 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.' ) m = import_module("." + action, package=__name__) m.run(c, args, _find_receiver, _find_device) except AssertionError: 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: sys.exit(f"{NAME.lower()}: error: {format_exc()}") Solaar-1.1.20/lib/solaar/cli/config.py000066400000000000000000000332701522022367600174430ustar00rootroot00000000000000## 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 yaml 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) 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( "# 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)") else: print(s.name, "=", s.val_to_string(value)) def _print_setting_keyed(s, key, verbose=True): print("#", s.label) if verbose: if s.description: 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)") else: 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)") else: print(s.name, s.val_to_string({k: value[str(int(k))]})) 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)") else: 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)") else: print(s.name, s.val_to_string({k: value[int(k)]})) def to_int(s): try: return int(s) except ValueError: return None def select_choice(value, choices, setting, key): lvalue = value.lower() ivalue = to_int(value) val = None for choice in choices: if value == str(choice): val = choice break if val is not None: value = val elif ivalue is not None and 1 <= ivalue <= len(choices): value = choices[ivalue - 1] 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(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 :] value = higher_values[0] if higher_values else choices[:][-1] elif lvalue in ("highest", "max", "first"): value = choices[:][-1] elif lvalue in ("lowest", "min", "last"): value = choices[:][0] else: 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", "~"): value = not (setting.read() if key is None else setting.read()[str(int(key))]) else: try: value = bool(int(value)) except Exception as exc: if value.lower() in ("true", "yes", "on", "t", "y"): value = True elif value.lower() in ("false", "no", "off", "f", "n"): value = False else: 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 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): assert receivers assert args.device device_name = args.device.lower() 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 args.setting: # print all settings, so first set them all up if not dev.settings: 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_setting(s) return setting_name = args.setting.lower() 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: try: setting = sclass.build(dev) except Exception: setting = None if setting is None: raise Exception(f"no setting '{args.setting}' for {dev.name}") if args.value_key is None: _print_setting(setting) return remote = False try: import gi 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? application = Gtk.Application.new(APP_ID, Gio.ApplicationFlags.HANDLES_COMMAND_LINE) application.register() remote = application.get_is_remote() except Exception: pass # don't save configuration here because we might want the Solaar GUI to make the change result, message, value = set(dev, setting, args, False) if message is not None: print(message) if result is None: 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.extend([a for a in [args.value_key, args.extra_subkey, args.extra2] if a is not None]) args = yaml.dump(argl) application.run([args]) else: if dev.persister and setting.persist: dev.persister[setting.name] = setting._value 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 = f"Setting {setting.name} of {dev.name} to {value}" result = setting.write(value, save=save) elif setting.kind == settings.Kind.RANGE: value = select_range(args.value_key, setting) args.value_key = value message = f"Setting {setting.name} of {dev.name} to {value}" result = setting.write(value, save=save) elif setting.kind == settings.Kind.CHOICE: value = select_choice(args.value_key, setting.choices, setting, None) args.value_key = int(value) message = f"Setting {setting.name} of {dev.name} to {value}" result = setting.write(value, save=save) elif setting.kind == settings.Kind.MAP_CHOICE: if args.extra_subkey is None: _print_setting_keyed(setting, args.value_key) 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 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: 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: if args.extra_subkey is None: _print_setting_keyed(setting, args.value_key) 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) 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) if k is not None: value = select_toggle(args.extra_subkey, setting, key=k) args.extra_subkey = value args.value_key = str(int(k)) else: 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: if args.extra_subkey is None: 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) if args.extra2 is None or to_int(args.extra2) is None: 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) if k is None and ikey is not None: k = next((k for k in setting._value if ikey == k), None) item = setting._value[k] if args.extra_subkey in item.keys(): item[args.extra_subkey] = to_int(args.extra2) args.value_key = str(int(k)) else: 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: print(f"Setting {setting.name}, with kind {setting.kind.name}, not implemented") raise Exception("NotImplemented") return result, message, value Solaar-1.1.20/lib/solaar/cli/pair.py000066400000000000000000000147401522022367600171320ustar00rootroot00000000000000## 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 time import time from logitech_receiver import base from logitech_receiver import hidpp10 from logitech_receiver import hidpp10_constants from logitech_receiver import notifications _hidpp10 = hidpp10.Hidpp10() def run(receivers, args, find_receiver, _ignore): assert receivers if args.receiver: receiver_name = args.receiver.lower() receiver = find_receiver(receivers, receiver_name) if not receiver: raise Exception(f"no receiver found matching '{receiver_name}'") else: receiver = receivers[0] assert receiver # check if it's necessary to set the notification flags 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: kd, known_devices = known_devices, None # only process one connection notification if kd is not None: if n.devnumber not in kd: 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.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 receiver.discover(timeout=timeout) 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.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 if n: receiver.handle.notifications_hook(n) 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: 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) 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.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.pairing.error if error: raise Exception(f"pairing failed: {error}") else: print("Paired device") # this is better than an error Solaar-1.1.20/lib/solaar/cli/probe.py000066400000000000000000000111731522022367600173030ustar00rootroot00000000000000## Copyright (C) 2020 ## ## 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 logitech_receiver import base from logitech_receiver.common import strhex from logitech_receiver.hidpp10_constants import ErrorCode from logitech_receiver.hidpp10_constants import Registers from solaar.cli.show import _print_device from solaar.cli.show import _print_receiver def run(receivers, args, find_receiver, _ignore): assert receivers if args.receiver: receiver_name = args.receiver.lower() receiver = find_receiver(receivers, receiver_name) if not receiver: raise Exception(f"no receiver found matching '{receiver_name}'") else: receiver = receivers[0] assert receiver is not None if receiver.isDevice: _print_device(receiver, 1) return _print_receiver(receiver) 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(Registers.RECEIVER_INFO, sub_reg) print( " 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(Registers.RECEIVER_INFO, sub_reg + device) print( " Pairing Register %#04x %#04x: %s" % (Registers.RECEIVER_INFO % 0x100, sub_reg + device, f"0x{strhex(rgst)}" if rgst else "None") ) rgst = receiver.read_register(Registers.RECEIVER_INFO, 0x40 + device) print( " 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(Registers.RECEIVER_INFO, 0x60 + device, part) print( " 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(Registers.FIRMWARE, sub_reg) print( " Firmware %#04x %#04x: %s" % (Registers.FIRMWARE % 0x100, sub_reg, f"0x{strhex(rgst)}" if rgst is not None else "None") ) print("") for reg in range(0, 0xFF): 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 Solaar-1.1.20/lib/solaar/cli/profiles.py000066400000000000000000000052141522022367600200160ustar00rootroot00000000000000## 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()) Solaar-1.1.20/lib/solaar/cli/show.py000066400000000000000000000606301522022367600171560ustar00rootroot00000000000000## 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 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 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(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)) 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.") 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(" %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}") _LED_CAPS_BITS = ((0x0001, "color"), (0x0002, "fade"), (0x0004, "period"), (0x0010, "direction"), (0xC000, "fw")) def _decode_led_caps(caps): names = [name for mask, name in _LED_CAPS_BITS if caps & mask] other = caps & ~sum(m for m, _ in _LED_CAPS_BITS) if other: names.append(f"+{other:#06x}") return f"0x{caps:04x}=" + ("+".join(names) if names else "none") def _battery_text(level) -> str: if level is None: return "N/A" elif isinstance(level, NamedInt): return str(level) else: return f"{int(level)}%" def _battery_line(dev): battery = dev.battery() if battery is not None: level, nextLevel, status, voltage = battery.level, battery.next_level, battery.status, battery.voltage text = _battery_text(level) if voltage is not None: 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.") 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: online = dev.ping() except exceptions.NoSuchDevice: print(f" {num}: Device not found" or dev.number) return if num or dev.number < 8: print(f" {int(num or dev.number)}: {dev.name}") else: 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) if dev.unitId: print(" Unit ID: ", dev.unitId) if dev.firmware: for fw in dev.firmware: print(f" {fw.kind:11}:", (fw.name + " " + fw.version).strip()) if dev.power_switch_location: print(f" The power switch is located on the {dev.power_switch_location}.") # 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_constants.NotificationFlag.flag_names(notification_flags) print(f" Notifications: {', '.join(notification_names)} (0x{notification_flags.value:06X}).") else: print(" Notifications: (none).") device_features = _hidpp10.get_device_features(dev) if device_features is not None: if 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)") if dev.online and 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) feature_num = 0 in_sub_device = False for feature, index in dev.features.enumerate(): 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(f" Multiplier: {multi}") if has_invert: 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") if res: print(" High resolution mode") else: print(" Low resolution mode") if target: print(" HID++ notification") else: print(" HID notification") elif feature == SupportedFeature.MOUSE_POINTER: mouse_pointer = _hidpp20.get_mouse_pointer_info(dev) if mouse_pointer: 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") else: 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(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") else: print(" Hi-res scrolling disabled") if scrolling_resolution: 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(f" Pointer Speed: {pointer_speed}") elif feature == SupportedFeature.LOWRES_WHEEL: wheel_status = _hidpp20.get_lowres_wheel_status(dev) if wheel_status: 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 == SupportedFeature.HOSTS_INFO: host_names = _hidpp20.get_host_names(dev) for host, (paired, name) in host_names.items(): 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(f" Firmware: {fw.kind} {fw.name} {fw.version} {extras}") ids = _hidpp20.get_ids(dev) if ids: unitId, modelId, tid_map = ids 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(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 feature == SupportedFeature.RGB_EFFECTS or feature == SupportedFeature.COLOR_LED_EFFECTS: try: infos = dev.led_effects except Exception as e: print(f" Effect enumeration failed: {e}") infos = None if infos and infos.zones: for zone in infos.zones: print(f" Zone {int(zone.index)} ({zone.location}): {len(zone.effects)} effect(s)") for e in zone.effects: entry = hidpp20.LEDEffects.get(e.ID) name = entry[0].name if entry else f"Unknown(0x{e.ID:02x})" caps = _decode_led_caps(e.capabilities) params = ", ".join(str(p) for p in entry[1]) if entry and entry[1] else "—" print( f" [{e.index}] 0x{e.ID:02x} {name:<14} " f"caps {caps:<28} default {e.period}ms params: {params}" ) 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 ): v = setting.val_to_string(setting._device.persister.get(setting.name)) print(f" {setting.label} (saved): {v}") # Settings whose value cannot be read back from the device # (e.g. PerKeyLighting — 0x8081 has no GetIndividualRgbZones) # suppress the live-read line; the saved line above is the # authoritative record. See Setting.live_readable. if getattr(setting, "live_readable", True): try: 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(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 == 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(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 "")) 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)) ) 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()) ) for k in dev.gestures.params.values(): 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)) if dev.online: _battery_line(dev) else: print(" Battery: unknown (device is offline).") def run(devices, args, find_receiver, find_device): assert devices assert args.device print(f"{NAME.lower()} version {__version__}") print("") device_name = args.device.lower() if device_name == "all": for d in devices: if isinstance(d, (receiver.Receiver, CenturionReceiver)): _print_receiver(d) count = d.count() if count: for dev in d: print("") _print_device(dev, dev.number) count -= 1 if not count: break print("") else: _print_device(d) print("") return dev = find_receiver(devices, device_name) if dev and not dev.isDevice: _print_receiver(dev) return dev = next(find_device(devices, device_name), None) if not dev: raise Exception(f"no device found matching '{device_name}'") _print_device(dev) Solaar-1.1.20/lib/solaar/cli/unpair.py000066400000000000000000000071121522022367600174700ustar00rootroot00000000000000## 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. def run(receivers, args, find_receiver, find_device): assert receivers 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(f"no device found matching '{device_name}'") if not dev.receiver.may_unpair: print( 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(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)") Solaar-1.1.20/lib/solaar/configuration.py000066400000000000000000000236271522022367600203030ustar00rootroot00000000000000## 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. import json import logging import os import threading import yaml from logitech_receiver.common import NamedInt from solaar import __version__ logger = logging.getLogger(__name__) _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" _config = [] def _load(): loaded_config = [] 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) except Exception as e: 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) except Exception as e: logger.error("failed to load from %s: %s", _json_file_path, e) loaded_config = _convert_json(loaded_config) else: path = None logger.debug("load => %s", loaded_config) global _config _config = _parse_config(loaded_config, path) def _parse_config(loaded_config, config_path): current_version = __version__ parsed_config = [current_version] try: if not loaded_config: return parsed_config loaded_version = loaded_config[0] discard_derived_properties = loaded_version != current_version if discard_derived_properties: 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: 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") if divert: 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") if gestures: # convert old-style mouse-gestures setting to divert-keys entry divert[int(gestures)] = 2 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", } if discard_derived_properties: data.pop("_absent", None) data.pop("_battery", None) return _DeviceEntry(**data) save_timer = None configuration_lock = threading.Lock() defer_saves = False # don't allow configuration saves to be deferred def save(defer=False): global save_timer if not _config: return dirname = os.path.dirname(_yaml_file_path) if not os.path.isdir(dirname): try: os.makedirs(dirname) except Exception: logger.error("failed to create %s", dirname) return if not defer or not defer_saves: do_save() else: with configuration_lock: if not save_timer: save_timer = threading.Timer(5.0, do_save) save_timer.start() def do_save(): global save_timer 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) logger.info("saved %s to %s", _config, _yaml_file_path) except Exception as 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(":") 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 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"]: 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") config.append(dev) return config class _DeviceEntry(dict): def __init__(self, **kwargs): super().__init__(**kwargs) def __setitem__(self, key, value): super().__setitem__(key, value) save(defer=True) 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 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) def set_sensitivity(self, name, value): sensitives = self.get(_KEY_SENSITIVE, {}) if sensitives.get(name) != value: sensitives[name] = value self.__setitem__(_KEY_SENSITIVE, sensitives) def device_representer(dumper, data): return dumper.represent_mapping("tag:yaml.org,2002:map", data) yaml.add_representer(_DeviceEntry, device_representer) def named_int_representer(dumper, data): return dumper.represent_scalar("tag:yaml.org,2002:int", str(int(data))) yaml.add_representer(NamedInt, named_int_representer) # A device can be identified by a combination of WPID and serial number (for receiver-connected devices) # or a combination of modelId and unitId (for direct-connected devices). # 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 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 == 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) ) ) 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): pass Solaar-1.1.20/lib/solaar/custom_logger.py000066400000000000000000000015521522022367600202760ustar00rootroot00000000000000import 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) Solaar-1.1.20/lib/solaar/dbus.py000066400000000000000000000056351522022367600163700ustar00rootroot00000000000000## 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 ) Solaar-1.1.20/lib/solaar/gtk.py000077500000000000000000000162171522022367600162210ustar00rootroot00000000000000#!/usr/bin/env python3 ## 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 argparse import faulthandler import importlib import locale import logging import os.path import platform import signal import sys import tempfile from traceback import format_exc 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 logging.setLoggerClass(CustomLogger) logger = logging.getLogger(__name__) def _require(module, os_package, gi=None, gi_package=None, gi_version=None): try: if gi is not None: gi.require_version(gi_package, gi_version) return importlib.import_module(module) except (ImportError, ValueError): sys.exit(f"{NAME.lower()}: missing required system package {os_package}") 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(): arg_parser = create_parser() args = arg_parser.parse_args() if args.help_actions: cli.print_help() return if args.window is None: 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" 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_level = logging.ERROR - 10 * args.debug 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) 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) if not args.action: 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): signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGTERM, signal.SIG_DFL) if signl == int(signal.SIGINT): if logger.isEnabledFor(logging.INFO): faulthandler.dump_traceback() 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") args = _parse_arguments() if not args: # explicit close before return temp.close() return if args.action: # if any argument, run comandline and exit 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") # 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 ( 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: listener.setup_scanner(ui.status_changed, ui.setting_changed, ui.common.error_dialog) if args.restart_on_wake_up: dbus.watch_suspend_resume(listener.start_all, listener.stop_all) else: dbus.watch_suspend_resume(lambda: listener.ping_all(True)) 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") except Exception: sys.exit(f"{NAME.lower()}: error: {format_exc()}") temp.close() if __name__ == "__main__": main() Solaar-1.1.20/lib/solaar/i18n.py000066400000000000000000000046111522022367600162030ustar00rootroot00000000000000## 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 gettext import locale import logging import os import sys from glob import glob from solaar import NAME _LOCALE_DOMAIN = NAME.lower() logger = logging.getLogger(__name__) 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(os.path.join(location, "locale", "*", "LC_MESSAGES", f"{locale_domain}.mo")) if mo_files: return os.path.join(location, "locale") raise FileNotFoundError(f"Could not find locale path for {locale_domain}") def set_locale_to_system_default() -> None: """Sets locale for translations to the system default. If locale is unsupported, fallback to standard English without translation 'C'. 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 try: path = _find_locale_path(_LOCALE_DOMAIN) except FileNotFoundError: path = None gettext.bindtextdomain(_LOCALE_DOMAIN, path) gettext.textdomain(_LOCALE_DOMAIN) gettext.install(_LOCALE_DOMAIN) set_locale_to_system_default() _ = gettext.gettext ngettext = gettext.ngettext Solaar-1.1.20/lib/solaar/listener.py000066400000000000000000000526031522022367600172550ustar00rootroot00000000000000## 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 errno import logging import subprocess import time import typing from collections import namedtuple from functools import partial from typing import Callable import gi import logitech_receiver 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 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 if typing.TYPE_CHECKING: from logitech_receiver.device import Device logger = logging.getLogger(__name__) ACTION_ADD = "add" _GHOST_DEVICE = namedtuple("_GHOST_DEVICE", ("receiver", "number", "name", "kind", "online", "path")) _GHOST_DEVICE.__bool__ = lambda self: False _GHOST_DEVICE.__nonzero__ = _GHOST_DEVICE.__bool__ def _ghost(device): return _GHOST_DEVICE( receiver=device.receiver, number=device.number, name=device.name, kind=device.kind, online=False, path=None ) 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): assert status_changed_callback super().__init__(receiver, self._notifications_handler) self.status_changed_callback = status_changed_callback receiver.status_callback = self._status_changed def has_started(self): logger.info("%s: notifications listener has started (%s)", self.receiver, self.receiver.handle) nfs = self.receiver.enable_connection_notifications() 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) def has_stopped(self): r, self.receiver = self.receiver, None assert r is not None 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 _all_listeners.pop(r.path, None) # this causes problems but what is it doing (pfps) - r.status = _('The receiver was unplugged.') if r: try: r.close() except Exception: logger.exception(f"closing receiver {r.path}") self.status_changed_callback(r) def _status_changed(self, device, alert=None, reason=None): assert device is not None 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 # the status of the receiver changed self.status_changed_callback(device, alert, reason) return # 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. 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 self.status_changed_callback(self.receiver) def _notifications_handler(self, n): assert self.receiver 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): if self.receiver._pending: ihandle = int(self.receiver.handle) state = base._centurion_handles.get(ihandle) if state and state.device_addr is not None: self.receiver._complete_deferred_init() self._status_changed(self.receiver) self._handle_centurion_notification(n) return # a receiver notification 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: 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: 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 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. device_ready = not bool(ord(n.data[0:1]) & 0x80) or n.sub_id != 0x41 if not device_ready: time.sleep(0.01) if n.sub_id == 0x40 and not already_known: return # disconnecting something that is not known - nothing to do if n.sub_id == 0x41: if not already_known: 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( 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.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.pairing.new_device = self.receiver[n.devnumber] else: dev = self.receiver[n.devnumber] else: dev = self.receiver[n.devnumber] if not 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: 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) dev.status_callback = self._status_changed # the receiver changed status as well self._status_changed(self.receiver) notifications.process(dev, n) if self.receiver.pairing.lock_open and not already_known: # this should be the first notification after a device was paired 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 f"" 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 _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 _post_attach_device(device): """Shared post-create_device wiring: hook configuration up to the new Device, and if it's BT-paired, install the bluez-dbus connect watcher so disconnect/reconnect events propagate to the UI without a restart. Both the Centurion-direct fallback (no-dongle device, e.g. BT-paired G522 / PRO X 2) and the regular non-Centurion device path call this — previously only the latter wired the bluez watcher, so a BT-paired Centurion device wouldn't see reconnect events.""" configuration.attach_to(device) if device.bluetooth and device.hid_serial: dbus.watch_bluez_connect(device.hid_serial, partial(_process_bluez_dbus, device)) device.cleanups.append(_cleanup_bluez_dbus) 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 # (wired headset, or BT-paired headset with no LIGHTSPEED dongle). receiver_ = logitech_receiver.device.create_device(base, device_info, _setting_callback) if receiver_: _post_attach_device(receiver_) else: receiver_ = logitech_receiver.device.create_device(base, device_info, _setting_callback) if receiver_: _post_attach_device(receiver_) if receiver_: rl = SolaarListener(receiver_, _status_callback) rl.start() _all_listeners[device_info.path] = rl return rl logger.warning("failed to open %s", device_info) def start_all(): 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: logger.info("stopping receiver listening threads %s", listeners) for listener_thread in listeners: listener_thread.stop() configuration.save() if listeners: for listener_thread in listeners: listener_thread.join() # 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): 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 = listener_thread.receiver.count() if count: 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 # 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: 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(GLib, _process_receiver_event) def _process_add(device_info: DeviceInfo, retry): try: _start(device_info) except OSError as e: if e.errno == errno.EACCES: try: 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(common.ErrorReason.PERMISSIONS, device_info.path) else: _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 def _process_receiver_event(action, device_info): assert action is not None assert device_info is not None assert _error_callback logger.info("receiver event %s %s", action, device_info) # whatever the action, stop any previous receivers at this path 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 Solaar-1.1.20/lib/solaar/tasks.py000066400000000000000000000033441522022367600165530ustar00rootroot00000000000000#!/usr/bin/env python3 ## 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 threading import Thread logger = logging.getLogger(__name__) try: from Queue import Queue except ImportError: from queue import Queue class TaskRunner(Thread): def __init__(self, name): super().__init__(name=name) self.daemon = True self.queue = Queue(16) self.alive = False def __call__(self, function, *args, **kwargs): task = (function, args, kwargs) self.queue.put(task) def stop(self): self.alive = False self.queue.put(None) def run(self): self.alive = True logger.debug("started") while self.alive: task = self.queue.get() if task: function, args, kwargs = task assert function try: function(*args, **kwargs) except Exception: logger.exception("calling %s", function) logger.debug("stopped") Solaar-1.1.20/lib/solaar/ui/000077500000000000000000000000001522022367600154655ustar00rootroot00000000000000Solaar-1.1.20/lib/solaar/ui/__init__.py000066400000000000000000000107701522022367600176030ustar00rootroot00000000000000## 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. import logging from enum import Enum from typing import Callable import gi import yaml from logitech_receiver.common import Alert 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 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 logger = logging.getLogger(__name__) assert Gtk.get_major_version() > 2, "Solaar requires Gtk 3 python bindings" APP_ID = "io.github.pwr_solaar.solaar" class GtkSignal(Enum): ACTIVATE = "activate" COMMAND_LINE = "command-line" SHUTDOWN = "shutdown" def _startup(app, startup_hook, use_tray, show_window): 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): logger.debug("activate") if app.get_windows(): window.popup() else: app.add_window(window._window) def _command_line(app, command_line): args = command_line.get_arguments() args = yaml.safe_load("".join(args)) if args else args if not args: _activate(app) 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) if setting: change_setting(dev, setting, args[3:]) return 0 def _shutdown(_app, shutdown_hook): logger.debug("shutdown") shutdown_hook() common.stop_async() tray.destroy() desktop_notifications.uninit() 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(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")) application.run() def _status_changed(device, alert, reason, refresh=False): 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: tray.attention(reason) need_popup = alert & Alert.SHOW_WINDOW window.update(device, need_popup, refresh) diversion_rules.update_devices() if alert & (Alert.NOTIFICATION | Alert.ATTENTION): desktop_notifications.show(device, reason) 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) Solaar-1.1.20/lib/solaar/ui/about/000077500000000000000000000000001522022367600165775ustar00rootroot00000000000000Solaar-1.1.20/lib/solaar/ui/about/__init__.py000066400000000000000000000000001522022367600206760ustar00rootroot00000000000000Solaar-1.1.20/lib/solaar/ui/about/about.py000066400000000000000000000023071522022367600202650ustar00rootroot00000000000000## 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() Solaar-1.1.20/lib/solaar/ui/about/model.py000066400000000000000000000057121522022367600202560ustar00rootroot00000000000000## 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", "Ken Sanislo"]), (_("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" Solaar-1.1.20/lib/solaar/ui/about/presenter.py000066400000000000000000000055521522022367600211670ustar00rootroot00000000000000## 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() Solaar-1.1.20/lib/solaar/ui/about/view.py000066400000000000000000000045301522022367600201250ustar00rootroot00000000000000## 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() Solaar-1.1.20/lib/solaar/ui/action.py000066400000000000000000000072161522022367600173220ustar00rootroot00000000000000## 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 Enum from gi.repository import Gdk from gi.repository import Gtk from solaar.i18n import _ from solaar.ui import common from . import pair_window 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=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(GtkSignal.ACTIVATE.value, function, *args) return action def make_toggle(name, label, function, stock_id=None, *args): 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(GtkSignal.ACTIVATE.value, function, *args) return action def pair(window, receiver): assert receiver assert receiver.kind is None pair_dialog = pair_window.create(receiver) pair_dialog.set_transient_for(window) pair_dialog.set_destroy_with_parent(True) pair_dialog.set_modal(True) pair_dialog.set_type_hint(Gdk.WindowTypeHint.DIALOG) pair_dialog.set_position(Gtk.WindowPosition.CENTER) pair_dialog.present() def unpair(window, device): assert device assert device.kind is not None qdialog = Gtk.MessageDialog( 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) choice = qdialog.run() qdialog.destroy() if choice == Gtk.ResponseType.ACCEPT: receiver = device.receiver assert receiver device_number = device.number try: # 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: common.error_dialog(common.ErrorReason.UNPAIR, device) Solaar-1.1.20/lib/solaar/ui/common.py000066400000000000000000000062701522022367600173340ustar00rootroot00000000000000## 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) Solaar-1.1.20/lib/solaar/ui/config_panel.py000066400000000000000000001420461522022367600204720ustar00rootroot00000000000000## 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. import logging from enum import Enum from threading import Timer import gi from logitech_receiver import hidpp20 from logitech_receiver import settings from logitech_receiver import settings_templates 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): 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)) null_okay = not getattr(getattr(s, "_validator", None), "readable", True) GLib.idle_add(_update_setting_item, sb, v, online, sensitive, null_okay, priority=99) 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): try: if key is None: v = setting.write(v) else: v = setting.write_key_value(key, v) v = {key: v} except Exception: v = None if sb: GLib.idle_add(_update_setting_item, sb, v, True, sensitive, priority=99) if sbox: sbox._control.set_sensitive(False) sbox._failed.set_visible(False) sbox._spinner.set_visible(True) sbox._spinner.start() 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 Scale(Gtk.Scale): def get_value(self): return int(super().get_value()) class Control: def __init__(self, **kwargs): self.sbox = None self.delegate = None def init(self, sbox, delegate): self.sbox = sbox self.delegate = delegate if delegate else self def changed(self, *args): if self.get_sensitive(): self.delegate.update() def update(self): _write_async(self.sbox.setting, self.get_value(), self.sbox) def layout(self, sbox, label, change, spinner, failed): sbox.pack_start(label, False, False, 0) sbox.pack_end(change, False, False, 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(GtkSignal.NOTIFY_ACTIVE.value, self.changed) def set_value(self, 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) self.timer = None self.set_range(*self.sbox.setting.range) self.set_round_digits(0) self.set_digits(0) self.set_increments(1, 5) # Halving tick marks are an intensity-slider feature only. if self.sbox.setting.name == "brightness_control": validator = getattr(self.sbox.setting, "_validator", None) steps = getattr(validator, "steps", 0) if validator is not None else 0 if steps: for mark in settings_templates.halving_marks(validator.max_value, steps): self.add_mark(mark, Gtk.PositionType.BOTTOM, None) 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()) def changed(self, *args): if self.get_sensitive(): if self.timer: self.timer.cancel() self.timer = Timer(0.5, lambda: GLib.idle_add(self.do_change)) self.timer.start() def do_change(self): self.timer.cancel() self.update() def _create_choice_control(sbox, delegate=None, choices=None): if 50 > len(choices if choices else sbox.setting.choices): return ChoiceControlLittle(sbox, choices=choices, delegate=delegate) else: return ChoiceControlBig(sbox, choices=choices, delegate=delegate) # 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(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): if value is not None: self.set_active_id(str(int(value))) def get_choice(self): id = self.get_value() return next((x for x in self.choices if x == id), None) def set_choices(self, choices): self.remove_all() for choice in choices: self.append(str(int(choice)), _(str(choice))) class ChoiceControlBig(Gtk.Entry, 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 self.value = None self.set_width_chars(max([len(str(x)) for x in self.choices]) + 5) liststore = Gtk.ListStore(int, str) for v in self.choices: liststore.append((int(v), str(v))) completion = Gtk.EntryCompletion() completion.set_model(liststore) 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(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): 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 "" self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) 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): if self.value is not None and self.get_sensitive(): self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "") self.delegate.update() 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.delegate.update() class MapChoiceControl(Gtk.HBox, Control): def __init__(self, sbox, delegate=None): super().__init__(homogeneous=False, spacing=6) self.init(sbox, delegate) self.keyBox = Gtk.ComboBoxText() for entry in sbox.setting.choices: self.keyBox.append(str(int(entry)), _(str(entry))) self.keyBox.set_active(0) key_choice = int(self.keyBox.get_active_id()) self.value_choices = self.sbox.setting.choices[key_choice] 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(GtkSignal.CHANGED.value, self.map_value_notify_key) def get_value(self): key_choice = int(self.keyBox.get_active_id()) if key_choice is not None and self.valueBox.get_value() is not None: 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: self.valueBox.set_value(value.get(key)) self.valueBox.set_sensitive(True) def map_populate_value_box(self, key_choice): choices = self.sbox.setting.choices[key_choice] if choices != self.value_choices: self.value_choices = choices 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): key_choice = int(self.keyBox.get_active_id()) if self.keyBox.get_sensitive(): self.map_populate_value_box(key_choice) def update(self): key_choice = int(self.keyBox.get_active_id()) value = self.get_value() if value is not None and self.valueBox.get_sensitive() and self.sbox.setting._value.get(key_choice) != value: self.sbox.setting._value[int(key_choice)] = value _write_async(self.sbox.setting, value, self.sbox, key=int(key_choice)) class MultipleControl(Gtk.ListBox, Control): 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(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) hbox.pack_end(btn, False, False, 0) self._header = hbox vbox = Gtk.VBox(homogeneous=False, spacing=6) vbox.pack_start(hbox, True, True, 0) vbox.pack_end(self, True, True, 0) self.vbox = vbox self.toggle_display() _disable_listbox_highlight_bg(self) def layout(self, sbox, label, change, spinner, failed): self._header.pack_start(label, False, False, 0) self._header.pack_end(spinner, False, False, 0) self._header.pack_end(failed, False, False, 0) sbox.pack_start(self.vbox, True, True, 0) sbox._button = self._button return True def toggle_display(self, *_args): self._showing = not self._showing if not self._showing: for c in self.get_children(): c.hide() self.hide() else: self.show() for c in self.get_children(): c.show_all() 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"): 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(label=lbl_text) h.set_tooltip_text(lbl_tooltip or " ") control = Gtk.Switch() control._setting_key = int(k) 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_margin_start(30) self.add(h) self._label_control_pairs.append((lbl, control)) def toggle_notify(self, switch, _active): if switch.get_sensitive(): key = switch._setting_key new_state = switch.get_state() if self.sbox.setting._value[key] != new_state: self.sbox.setting._value[key] = new_state _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 = [] for lbl, elem in self._label_control_pairs: v = value.get(elem._setting_key, None) if v is not None: elem.set_state(v) if elem.get_state(): active += 1 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"): 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(label=lbl_text) self.add(item_lbl) self.set_tooltip_text(lbl_tooltip or " ") item_lb = Gtk.ListBox() item_lb.set_selection_mode(Gtk.SelectionMode.NONE) item_lb._sub_items = [] for sub_item in setting._validator.sub_items[item]: h = Gtk.HBox(homogeneous=False, spacing=20) lbl_text = str(sub_item) lbl_tooltip = None 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(label=lbl_text) h.set_tooltip_text(lbl_tooltip or " ") h.pack_start(sub_item_lbl, False, False, 0) 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": 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(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 item_lb._sub_items.append(h) item_lb._setting_item = item _disable_listbox_highlight_bg(item_lb) self.add(item_lb) self._items.append(item_lb) def changed(self, control, item, sub_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, sub_item)) control._timer.start() def _write(self, control, item, sub_item): control._timer.cancel() 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): 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 += f"{str(item)}: (" to_join = [] for c in ch._sub_items: sub_item = c._setting_sub_item try: sub_item_value = v[str(sub_item)] except KeyError: sub_item_value = c._control.get_value() c._control.set_value(sub_item_value) n += 1 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): self._items = [] validator = setting._validator for item in range(validator.count): h = Gtk.HBox(homogeneous=False, spacing=0) 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(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_margin_start(30) self.add(h) self._items.append(h) 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 h in self._items: item = h._setting_item v = value.get(int(item), None) if v is not None: h.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) 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) 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()) value = self.sbox.setting._value if not isinstance(value, dict): return if value.get(int(item)) != new_state: value[int(item)] = new_state _write_async(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) stored = self.sbox.setting._value if isinstance(self.sbox.setting._value, dict) else {} for vbox in self._items: item = vbox._setting_item v = value.get(int(item)) if v is not None: vbox.control.set_value(v) else: v = stored.get(int(item), 0) 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 _HeteroToggleSwitch(Gtk.Switch): """Gtk.Switch with int-valued get/set_value for HeteroKeyControl. Maps switch True/False to the field's wire on/off integer values so the surrounding control machinery (changed handler, get_value, set_value) can stay int-based like every other field kind. """ def __init__(self, on_value: int = 1, off_value: int = 2, **kwargs): super().__init__(**kwargs) self._on = int(on_value) self._off = int(off_value) def get_value(self) -> int: return self._on if self.get_state() else self._off def set_value(self, value) -> None: self.set_state(int(value) == self._on) 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.TOGGLE: # Right-align like standard TOGGLE settings — pack_end so the # switch hugs the right edge while other fields stay left. item_box = _HeteroToggleSwitch( on_value=item.get("on_value", 1), off_value=item.get("off_value", 2), halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER, ) item_box.connect(GtkSignal.NOTIFY_ACTIVE.value, self.changed) self.pack_end(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) # Halving tick marks are an intensity-slider feature only. if item.get("halving") and str(item.get("name")) == str(hidpp20.LEDParam.intensity): steps = getattr(sbox.setting._device, "_brightness_steps", 0) or settings_templates.auto_step_count( item["max"] ) for mark in settings_templates.halving_marks(item["max"], steps): item_box.add_mark(mark, Gtk.PositionType.BOTTOM, None) if item.get("display_seconds", False): item_box.connect("format-value", lambda _s, v: f"{int(v) / 1000:.2f}s") 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() data_class = getattr(self.sbox.setting._validator, "data_class", hidpp20.LEDEffectSetting) result = data_class(**result) return result def set_value(self, value): self.set_sensitive(False) id_ = value.ID if value is not None else 0 self._apply_id_ranges(id_) 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) self.setup_visibles(id_) 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, *_args): # *_args swallows the extra GParamSpec passed by Gtk.Switch's # "notify::active" signal — other field signals pass just (widget,). if self.get_sensitive() and control.get_sensitive(): if "ID" in self._items and control == self._items["ID"][1]: new_id = int(self._items["ID"][1].get_value()) self.setup_visibles(new_id) self._apply_id_ranges(new_id) self._apply_id_defaults(new_id) if hasattr(control, "_timer"): control._timer.cancel() control._timer = Timer(0.3, lambda: GLib.idle_add(self._write, control)) control._timer.start() def _apply_id_ranges(self, id_): """Reset every RANGE widget to its field's global min/max, then apply per-effect overrides from fields_map[id_][3]. Reset-first ensures switching from an override (e.g. Ripple 2-200) to an effect without one restores the global range instead of inheriting the narrow one.""" fields_map = getattr(self.sbox.setting, "fields_map", None) entry = fields_map.get(id_) if fields_map else None ranges = entry[3] if entry and len(entry) > 3 else {} for field in self.sbox.setting.possible_fields: if field.get("kind") != settings.Kind.RANGE: continue name = str(field["name"]) if name not in self._items: continue _, box = self._items[name] lo, hi = ranges.get(field["name"], (field.get("min", 0), field.get("max", 0))) box.set_range(lo, hi) def _apply_id_defaults(self, id_): """Apply fields_map[id_][2] defaults to RANGE widgets sitting at min.""" fields_map = getattr(self.sbox.setting, "fields_map", None) if not fields_map or id_ not in fields_map: return entry = fields_map[id_] if len(entry) < 3: return defaults = entry[2] ranges = entry[3] if len(entry) > 3 else {} field_by_name = {str(f["name"]): f for f in self.sbox.setting.possible_fields} for param_name, default_value in defaults.items(): name = str(param_name) if name not in self._items: continue field = field_by_name.get(name) if field is None or field.get("kind") != settings.Kind.RANGE: continue _, box = self._items[name] effective_min = ranges[param_name][0] if param_name in ranges else field.get("min", 0) if box.get_value() == effective_min: box.set_value(default_value) 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"), settings.SENSITIVITY_IGNORE: _("Ignore this setting"), } _next_allowable = {True: False, False: settings.SENSITIVITY_IGNORE, settings.SENSITIVITY_IGNORE: True} _icons_allowables = {v: k for k, v in _allowables_icons.items()} # clicking on the lock icon changes from changeable to unchangeable to ignore # Settings whose operation depends on LED Control being set to Solaar. # Zone settings (rgb_zone_*) are matched by prefix because their name carries # the zone index (rgb_zone_1, rgb_zone_2, ...). _SW_CONTROL_DEPENDENT_NAMES = ("rgb_idle_timeout", "rgb_idle_effect", "rgb_sleep_timeout") _SW_CONTROL_DEPENDENT_PREFIXES = ("rgb_zone_",) # headset_led_control = whether Solaar holds the live-coloring claim (off lets # another app drive the LEDs). The 0x0620 per-zone painting and the 0x0621 # onboard effect are both live LED control, so both need the claim; per-zone # additionally needs the onboard effect on Static (the per-key analog of # needs-rgb_control + zone-Static). The 0x0622 signature effects are stored # settings (startup/shutdown colors) and stay ungated. _HEADSET_LED_DEPENDENT_NAMES = ("headset_per_zone_lighting", "headset-onboard-effect") def _sw_control_blocked(device): """True when LED Control is not Solaar. Reads from setting._value first, then the persister, so the gate is right at panel load before live reads have populated. Accepts either the current bool (BooleanValidator) or the legacy int 3/0 (older ChoicesValidator persister entries).""" persister = getattr(device, "persister", None) if persister is None: return False value = None for s in getattr(device, "settings", []) or []: if s.name == "rgb_control": value = s._value break if value is None: value = persister.get("rgb_control") if value is None: return False # Solaar = bool True or legacy int 3; everything else (False, 0, …) blocks. return value not in (True, 3) def _headset_led_blocked(device): """True when the headset's LED Control is off (Device/firmware mode). Reads setting._value first, then the persister; accepts the current bool or a legacy int 0/1 from the old ChoicesValidator persister entries.""" persister = getattr(device, "persister", None) if persister is None: return False value = None for s in getattr(device, "settings", []) or []: if s.name == "headset_led_control": value = s._value break if value is None: value = persister.get("headset_led_control") if value is None: return False return value not in (True, 1) def _cluster_effect_blocks_perzone(device): """True when the headset's 0x0621 onboard effect is not Fixed — a non-Fixed cluster animation masks the per-zone buffer. Mirrors `_zone_effect_blocks_perkey`. False when the device has no onboard-effect setting (nothing to mask against).""" persister = getattr(device, "persister", None) value = None for s in getattr(device, "settings", []) or []: if s.name == "headset-onboard-effect": value = s._value if s._value is not None else (persister.get(s.name) if persister else None) break else: return False if value is None: return False return int(getattr(value, "ID", 0)) != 0 def _zone_effect_blocks_perkey(device): """True when any zone effect's saved ID is not Static (0x01) — zone animations mask the per-key buffer regardless of SW control state.""" persister = getattr(device, "persister", None) if persister is None: return False for s in getattr(device, "settings", []) or []: if not s.name.startswith("rgb_zone_"): continue v = s._value if s._value is not None else persister.get(s.name) if v is None: continue if int(getattr(v, "ID", 0) or 0) != 0x01: return True return False def _set_row_sensitive(device, name, can_function): """Apply sensitivity to a single setting's control row. Combines the user's lock-icon opt-in (persister sensitivity) with the can-function gate so neither alone can override the other.""" device_id = (device.receiver.path if device.receiver else device.path, device.number) sbox = _items.get((device_id[0], device_id[1], name)) if sbox is None or not hasattr(sbox, "_control"): return persister = getattr(device, "persister", None) user_allowed = persister.get_sensitivity(name) if persister else True sbox._control.set_sensitive(user_allowed is True and can_function) def _gate_blocks(device, name): """Single source of truth for "is this setting's row gated off?". Used by `_apply_rgb_gates` to grey rows and by `_update_setting_item` so async-read completions can't undo the grey-out when their callbacks land later.""" if name in _SW_CONTROL_DEPENDENT_NAMES or any(name.startswith(p) for p in _SW_CONTROL_DEPENDENT_PREFIXES): return _sw_control_blocked(device) if name == "per-key-lighting": return _sw_control_blocked(device) or _zone_effect_blocks_perkey(device) if name in _HEADSET_LED_DEPENDENT_NAMES: if _headset_led_blocked(device): return True # Per-zone painting additionally needs the onboard effect on Static. return name == "headset_per_zone_lighting" and _cluster_effect_blocks_perzone(device) return False def _apply_rgb_gates(device): """Grey out RGB settings whose prerequisites aren't met. Visual-only: leaves persister _sensitive flags (user lock-icon opt-ins) intact. - rgb_zone_* and rgb_idle_*/rgb_sleep_timeout need LED Control = Solaar (rgb_control == 3). - per-key-lighting needs LED Control = Solaar AND every zone effect on Static (0x01), because non-Static zone animations mask per-key writes. """ for s in getattr(device, "settings", []) or []: name = s.name if ( name in _SW_CONTROL_DEPENDENT_NAMES or any(name.startswith(p) for p in _SW_CONTROL_DEPENDENT_PREFIXES) or name == "per-key-lighting" or name in _HEADSET_LED_DEPENDENT_NAMES ): _set_row_sensitive(device, name, not _gate_blocks(device, name)) def _change_click(button, sbox): icon = button.get_children()[0] icon_name, _ = icon.get_icon_name() allowed = _icons_allowables.get(icon_name, True) new_allowed = _next_allowable[allowed] sbox._control.set_sensitive(new_allowed is True) _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 == 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 if setting.persist and persisted is not None: _write_async(setting, persisted, sbox) else: _read_async(setting, True, sbox, bool(sbox.setting._device.online), sbox._control.get_sensitive()) elif new_allowed == settings.SENSITIVITY_IGNORE and sbox.setting.name == "per-key-lighting": # User just opted out of per-key lighting. The firmware effect engine # is currently in its OOR "direct mode" slot showing the per-key buffer # (entered when the prep sequence wrote effectIdx=numEffects via # SetEffectByIndex on 0x8071). Writing a regular in-range effectIdx # with persist=1 displaces that slot and the saved zone effect becomes # the visible layer again. See LOGITECH_HIDPP2_PROTOCOL.md # "Per-key prep sequence" and 0x8071 SetEffectByIndex persist=1 # requirement. device = sbox.setting._device for s in device.settings: if s.name.startswith("rgb_zone_") and s._value is not None: _write_async(s, s._value, None) break # one zone-effect write is enough to flip the engine if sbox.setting.name.startswith("rgb_zone_"): # Toggling zone-effect sensitivity changes the effective base color # for per-key unset cells (zone color ↔ black). When per-key is # opted-in, repaint it so the unset cells pick up the new base. from logitech_receiver import rgb_power device = sbox.setting._device perkey, has_paint = rgb_power.perkey_has_paint(device) if has_paint: _write_async(perkey, perkey._value, None) # The lock icon on rgb_control, any zone, per-key, or headset_led_control # can change whether a dependent row is functional — re-evaluate the gate. name = sbox.setting.name if name in ("rgb_control", "per-key-lighting", "headset_led_control", "headset-onboard-effect") or name.startswith( "rgb_zone_" ): _apply_rgb_gates(sbox.setting._device) return True def _change_icon(allowed, icon): if allowed in _allowables_icons: icon._allowed = allowed icon.set_from_icon_name(_allowables_icons[allowed], Gtk.IconSize.LARGE_TOOLBAR) icon.set_tooltip_text(_allowables_tooltips[allowed]) 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(label=s.label) label = Gtk.EventBox() label.add(lbl) spinner = Gtk.Spinner() 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.")) sbox._failed = failed 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(GtkSignal.CLICKED.value, _change_click, sbox) 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: 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 control.layout(sbox, label, change, spinner, failed) sbox._control = control sbox.show_all() spinner.start() # the first read will stop it failed.set_visible(False) return sbox def _update_setting_item(sbox, value, is_online=True, sensitive=True, null_okay=False): sbox._spinner.stop() sensitive = sbox._change_icon._allowed if sensitive is None else sensitive name = sbox.setting.name can_function = not _gate_blocks(sbox.setting._device, name) if value is None and not null_okay: sbox._control.set_sensitive(sensitive is True and can_function) _change_icon(sensitive, sbox._change_icon) sbox._failed.set_visible(is_online) return sbox._failed.set_visible(False) sbox._control.set_sensitive(False) 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 and can_function) _change_icon(sensitive, sbox._change_icon) # rgb_control / rgb_zone_* gate per-key; headset_led_control and the # headset-onboard-effect gate the per-zone row — re-evaluate on a change. if name in ("rgb_control", "headset_led_control", "headset-onboard-effect") or name.startswith("rgb_zone_"): _apply_rgb_gates(sbox.setting._device) def _disable_listbox_highlight_bg(lb): colour = Gdk.RGBA() 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 = {} def create(): global _box assert _box is None _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.NONE) # was IN config_scroll.set_size_request(0, 350) # ask for enough vertical space for about eight settings return config_scroll def update(device, is_online=None): assert _box is not None assert device device_id = (device.receiver.path if device.receiver else device.path, device.number) if is_online is None: is_online = bool(device.online) # if the device changed since last update, clear the box first if device_id != _box._last_device: _box.set_visible(False) _box._last_device = device_id # hide controls belonging to other devices for k, sbox in _items.items(): sbox = _items[k] sbox.set_visible(k[0:2] == device_id) for s in device.settings: k = (device_id[0], device_id[1], s.name) if k in _items: sbox = _items[k] sbox.setting = s # use setting from current version of device else: sbox = _create_sbox(s, device) if sbox is None: continue _items[k] = sbox _box.pack_start(sbox, False, False, 0) sensitive = device.persister.get_sensitivity(s.name) if device.persister else True _read_async(s, False, sbox, is_online, sensitive) _apply_rgb_gates(device) _box.set_visible(True) def clean(device): """Remove the controls for a given device serial. Needed after the device has been unpaired. """ assert _box is not None device_id = (device.receiver.path if device.receiver else device.path, device.number) for k in list(_items.keys()): if k[0:2] == device_id: _box.remove(_items[k]) del _items[k] def destroy(): global _box _box = None _items.clear() def change_setting(device, setting, values): """External interface to change a setting and have the GUI show the change""" assert device == setting._device GLib.idle_add(_change_setting, device, setting, values, priority=99) def _change_setting(device, setting, values): 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)] else: sbox = None _write_async(setting, values[-1], sbox, None, key=values[0] if len(values) > 1 else None) def record_setting(device, setting, values): """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_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) Solaar-1.1.20/lib/solaar/ui/desktop_notifications.py000066400000000000000000000112441522022367600224430ustar00rootroot00000000000000## 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 importlib # Optional desktop notifications. import logging from solaar import NAME from solaar.i18n import _ from . import icons 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") 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 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(): """Initialize desktop notifications.""" global available if available: if not Notify.is_initted(): logger.info("starting desktop notifications") try: return Notify.init(NAME.lower()) 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(): logger.info("stopping desktop notifications") _notifications.clear() Notify.uninit() def alert(reason, icon=None): assert reason if available and Notify.is_initted(): n = _notifications.get(NAME.lower()) if n is None: 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) n.update(NAME.lower(), reason, icon_file) n.set_urgency(Notify.Urgency.NORMAL) n.set_hint("desktop-entry", GLib.Variant("s", NAME.lower())) try: n.show() except Exception: logger.exception("showing %s", n) def show(dev, reason=None, icon=None, progress=None): """Show a notification with title and text. Optionally displays the `progress` integer value in [0, 100] as a progress bar.""" if available and Notify.is_initted(): summary = dev.name # if a notification with same name is already visible, reuse it to avoid spamming n = _notifications.get(summary) if n is None: n = _notifications[summary] = Notify.Notification() if reason: message = reason else: 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) n.update(summary, message, icon_file) 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)) try: return n.show() except Exception: logger.exception(f"showing {n}") else: def init(): return False def uninit(): return None # toggle = lambda action: False def alert(reason): return None def show(dev, reason=None): return None Solaar-1.1.20/lib/solaar/ui/diversion_rules.py000066400000000000000000002260361522022367600212640ustar00rootroot00000000000000## 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 import dataclasses import logging import string import threading from collections import defaultdict from copy import copy 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 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 logger = logging.getLogger(__name__) _diversion_dialog = None _rule_component_clipboard = None 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 self.editable = editable GObject.GObject.__init__(self) def display_left(self): if isinstance(self.component, diversion.Rule): if self.level == 0: return _("Built-in rules") if not self.editable else _("User-defined rules") if self.level == 1: return " " + _("Rule") return " " + _("Sub-rule") if self.component is None: return _("[empty]") return " " + self.__component_ui().left_label(self.component) def display_right(self): if self.component is None: return "" return self.__component_ui().right_label(self.component) def display_icon(self): if self.component is None: 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) 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 _create_selected_rule_edit_panel() -> Gtk.Grid: """Creates the edit Condition/Actions panel for a rule. 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 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) @dataclasses.dataclass class AllowedActions: c: Any copy: bool delete: bool flatten: bool insert: bool insert_only_rule: bool insert_root: bool wrap: bool 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 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, ) class ActionMenu: def __init__( self, window: Gtk.Window, tree_view: Gtk.TreeView, populate_model_func: Callable, on_update: Callable, ): self.window = window self.tree_view = tree_view self._populate_model_func = populate_model_func self._on_update = on_update def create_menu_event_button_released(self, v, e): if e.button == Gdk.BUTTON_SECONDARY: # right click menu = Gtk.Menu() 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 enabled_actions.flatten: menu.append(self._menu_flatten(m, it)) 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 enabled_actions.delete: menu.append(self._menu_cut(m, it)) if enabled_actions.copy and enabled_actions.c is not None: menu.append(self._menu_copy(m, it)) if enabled_actions.insert and _rule_component_clipboard is not None: p = self._menu_paste(m, it) menu.append(p) if enabled_actions.c is None: # just a placeholder p.set_label(_("Paste here")) else: p.set_label(_("Paste above")) p2 = self._menu_paste(m, it, below=True) p2.set_label(_("Paste below")) menu.append(p2) elif enabled_actions.insert_only_rule and isinstance(_rule_component_clipboard, diversion.Rule): p = self._menu_paste(m, it) menu.append(p) if enabled_actions.c is None: p.set_label(_("Paste rule here")) else: p.set_label(_("Paste rule above")) p2 = self._menu_paste(m, it, below=True) p2.set_label(_("Paste rule below")) menu.append(p2) 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")) menu.append(p) if menu.get_children() and enabled_actions.delete: menu.append(Gtk.SeparatorMenuItem(visible=True)) if enabled_actions.delete: menu.append(self._menu_delete(m, it)) if menu.get_children(): menu.popup_at_pointer(e) 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, 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 :]] children = [child[0].component for child in m[it].iterchildren()] m.remove(it) self._populate_model_func(m, parent_it, children, level=wrapped.level, pos=idx) new_iter = m.iter_nth_child(parent_it, idx) 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(GtkSignal.ACTIVATE.value, self.menu_do_flatten, m, it) menu_flatten.show() return menu_flatten def _menu_do_insert(self, _mitem, m, it, new_c, below=False): wrapped = m[it][0] c = wrapped.component parent_it = m.iter_parent(it) parent_c = m[parent_it][0].component if len(parent_c.components) == 0: # we had only a placeholder idx = 0 else: idx = parent_c.components.index(c) 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_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.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) return self._menu_do_insert(_mitem, m, it, new_c, below=below) def _menu_insert(self, m, it, below=False): elements = [ _("Insert"), [ (_("Sub-rule"), diversion.Rule, []), (_("Or"), diversion.Or, []), (_("And"), diversion.And, []), [ _("Condition"), [ (_("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"), [ (_("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): if isinstance(spec, list): # has sub-menu label, children = spec item = Gtk.MenuItem(label) submenu = Gtk.Menu() item.set_submenu(submenu) for child in children: submenu.append(build(child)) return item elif isinstance(spec, tuple): # has click action label, feature, *args = spec item = Gtk.MenuItem(label) args = [a.copy() if isinstance(a, list) else a for a in args] item.connect(GtkSignal.ACTIVATE.value, self._menu_do_insert_new, m, it, feature, *args, below) return item else: return None menu_insert = build(elements) menu_insert.show_all() return menu_insert 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): 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) parent_c.components.pop(idx) if len(parent_c.components) == 0: # placeholder self._populate_model_func(m, parent_it, None, level=wrapped.level) m.remove(it) 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) -> 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): wrapped = m[it][0] c = wrapped.component parent_it = m.iter_parent(it) parent_c = m[parent_it][0].component 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, 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) -> 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): wrapped = m[it][0] c = wrapped.component parent_it = m.iter_parent(it) parent_c = m[parent_it][0].component if isinstance(parent_c, diversion.Not): new_c = cls([c], warn=False) parent_c.component = new_c m.remove(it) 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() 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(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) menu_wrap.set_submenu(submenu_wrap) menu_wrap.show_all() return menu_wrap 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(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): global _rule_component_clipboard c = _rule_component_clipboard _rule_component_clipboard = None if c: _rule_component_clipboard = diversion.RuleComponent().compile(c.data()) self._menu_do_insert(_mitem, m, it, new_c=c, below=below) self._on_update() def _menu_paste(self, m, it, below=False): 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_copy(self, 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() 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 SmartComboBox(Gtk.ComboBox): """A custom ComboBox with some extra features. The constructor requires a collection of allowed values. Each element must be a single value or a non-empty tuple containing: - a value (any hashable object) - a name (optional; str(value) is used if not provided) - alternative names. Example: (some_object, 'object name', 'other name', 'also accept this'). It is assumed that the same string cannot be the name or an alternative name of more than one value. The widget displays the names, but the alternative names are also suggested and accepted as input. If `has_entry` is `True`, then the user can insert arbitrary text (possibly with auto-complete if `completion` is True). Otherwise, only a drop-down list is shown, with an extra blank item in the beginning (correspondent to `None`). The display text of the blank item is defined by the parameter `blank`. If `case_insensitive` is `True`, then upper-case and lower-case letters are treated as equal. If `replace_with_default_name`, then the field text is immediately replaced with the default name of a value as soon as the user finishes typing any accepted name. """ def __init__( self, all_values, blank="", completion=False, case_insensitive=False, replace_with_default_name=False, **kwargs ): super().__init__(**kwargs) self._name_to_idx = {} self._value_to_idx = {} self._hidden_idx = set() self._all_values = [] self._blank = blank self._model = None 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 def replace_with(value): if self.get_has_entry() and self._replace_with_default_name and value is not None: item = self._all_values[self._value_to_idx[value]] name = item[1] if len(item) > 1 else str(item[0]) if name != self.get_child().get_text(): self.get_child().set_text(name) 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(): self.set_entry_text_column(1) else: renderer = Gtk.CellRendererText() self.pack_start(renderer, True) self.add_attribute(renderer, "text", 1) self.set_all_values(all_values) self.set_active_id("") @classmethod def new_model(cls): model = Gtk.ListStore(str, str, bool) # (index: int converted to str, name: str, visible: bool) filtered_model = model.filter_new() filtered_model.set_visible_column(2) return model, filtered_model def set_all_values(self, all_values, visible_fn=(lambda value: True)): old_value = self.get_value() 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] 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)) self._model = model to_complete = [self._blank] for idx, item in enumerate(self._all_values): 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._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) else: self.set_value(self._blank) self.queue_draw() def _include(self, model, idx, value, visible, *names): name = str(names[0]) if names else str(value).strip() self._name_to_idx[self._norm(name)] = idx if isinstance(value, NamedInt): self._name_to_idx[self._norm(str(name))] = idx model.append((str(idx), name, visible)) for alt in names[1:]: self._name_to_idx[self._norm(str(alt).strip())] = idx self._value_to_idx[value] = idx if self._case_insensitive and isinstance(value, str): self._name_to_idx[self._norm(value)] = idx def get_value(self, invalid_as_str=True, accept_hidden=True): """Return the selected value or the typed text. If the typed or selected text corresponds to one of the allowed values (or their names and alternative names), then the value is returned. Otherwise, the raw text is returned as string if the widget has an entry and `invalid_as_str` is `True`; if the widget has no entry or `invalid_as_str` is `False`, then `None` is returned. """ tree_iter = self.get_active_iter() 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 elif self.get_has_entry(): text = self.get_child().get_text().strip() if text == self._blank: return None idx = self._find_idx(text) if idx is None: return text if invalid_as_str else None item = self._all_values[idx] return item[0] return None def _find_idx(self, search): if search == self._blank: return None try: return self._value_to_idx[search] except KeyError: pass try: return self._name_to_idx[self._norm(search)] except KeyError: pass return None def set_value(self, value, accept_invalid=True): """Set a specific value. Raw values, their names and alternative names are accepted. Base-10 representations of int values as strings are also accepted. The actual value is used in all cases. 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 "" 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) else: self.set_active_id("") def show_only(self, only, include_new=False): """Hide items not present in `only`. Only values are accepted (not their names and alternative names). If `include_new` is True, then the values in `only` not currently present are included with their string representation as names; otherwise, they are ignored. If `only` is new, then the visibility status is reset and all values are shown. """ values = self._all_values[:] if include_new and only is not None: values += [v for v in only if v not in self._value_to_idx] self.set_all_values(values, (lambda v: only is None or (v in only))) @dataclass class DeviceInfo: 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) -> str: return self.serial or self.unitId or "" @property def identifiers(self) -> list[str]: return [id for id in (self.serial, self.unitId) if id] @property def display_name(self) -> str: return f"{self.codename} ({self.id})" def matches(self, search: str) -> bool: return search and search in (self.serial, self.unitId, self.display_name) 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}) class DeviceInfoFactory: @classmethod def create_device_info(cls, device) -> DeviceInfo: d = DeviceInfo() d.update(device) return d class AllDevicesInfo: def __init__(self): self._devices: list[DeviceInfo] = [] self._lock = threading.Lock() def __iter__(self): return iter(self._devices) def __getitem__(self, search): if not search: return search assert isinstance(search, str) # linear search - ok because it is always a small list return next((d for d in self._devices if d.matches(search)), None) def refresh(self): updated = False 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 != "?"): existing = self[device.serial] or self[device.unitId] if not existing: updated = True device_info = DeviceInfoFactory.create_device_info(device) self._devices.append(device_info) elif not existing.settings and device.settings: updated = True existing.update(device) with self._lock: _dev_model.foreach(dev_in_row) return updated 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.") 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 = diversion.Rule def create_widgets(self): self.widgets = {} def collect_value(self): return self.component.components[:] # not editable on the bottom panel @classmethod def left_label(cls, component): return _("Rule") @classmethod def icon_name(cls): return "format-justify-fill" class AndUI(RuleComponentUI): CLASS = diversion.And def create_widgets(self): self.widgets = {} def collect_value(self): return self.component.components[:] # not editable on the bottom panel @classmethod def left_label(cls, component): return _("And") class OrUI(RuleComponentUI): CLASS = diversion.Or def create_widgets(self): self.widgets = {} def collect_value(self): return self.component.components[:] # not editable on the bottom panel @classmethod def left_label(cls, component): return _("Or") class LaterUI(RuleComponentUI): 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. 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.connect(GtkSignal.VALUE_CHANGED.value, 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.delay) def collect_value(self): return [float(int((self.field.get_value() + 0.0001) * 1000)) / 1000] + self.component.components @classmethod def left_label(cls, component): return _("Later") @classmethod def right_label(cls, component): return str(component.delay) class NotUI(RuleComponentUI): CLASS = diversion.Not def create_widgets(self): self.widgets = {} def collect_value(self): return self.component.component # not editable on the bottom panel @classmethod def left_label(cls, component): return _("Not") class ActionUI(RuleComponentUI): CLASS = diversion.Action @classmethod def icon_name(cls): return "go-next" def _from_named_ints(v, all_values): """Obtain a NamedInt from NamedInts given its numeric value (as int) or name.""" if all_values and (v in all_values): return all_values[v] return v 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(GtkSignal.CHANGED.value, self._changed) self.range_widget = Gtk.SpinButton.new_with_range(0, 0xFFFF, 1) 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(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) self.sub_key_widget.hide() for w in [self.toggle_widget, self.range_widget, self.choice_widget, self.unsupported_label]: self.pack_end(w, True, True, 0) w.hide() self.unsupp_value = 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 == SetValueControlKinds.CHOICE: value = widget.get_value() 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 == 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 ) (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) def _hide_all(self): for w in self.get_children(): w.hide() def get_value(self): if self.current_kind == SetValueControlKinds.TOGGLE: return self.toggle_widget.get_value() if self.current_kind == SetValueControlKinds.RANGE: return int(self.range_widget.get_value()) 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 == SetValueControlKinds.CHOICE: return self.choice_widget.get_value() return self.unsupp_value def set_value(self, value): 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 == 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 ) (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.range_widget.set_value(max(minimum, min(maximum, v))) 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 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("") def make_toggle(self): self.current_kind = SetValueControlKinds.TOGGLE self._hide_all() self.toggle_widget.show() def make_range(self, minimum, maximum): 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 = SetValueControlKinds.RANGE_WITH_KEY self._hide_all() self.sub_key_range_items = items or None if not labels: labels = {} self.sub_key_widget.set_all_values( map(lambda item: (item.id, labels.get(item.id, [str(item.id)])[0]), items) if items else [] ) self.sub_key_widget.show() self.range_widget.show() def make_choice(self, values, extra=None): # if extra is not in values, it is ignored 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: values = [extra] + sorted((v for v in values if v != extra), key=sort_key) else: values = sorted(values, key=sort_key) self.choice_widget.set_all_values(values) self.choice_widget._allowed_values = values self.choice_widget.show() def make_unsupported(self): self.current_kind = None self._hide_all() self.unsupported_label.show() class _DeviceUI: 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 "") 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(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.set_valign(Gtk.Align.CENTER) self.device_field.set_size_request(400, 0) self.device_field.connect(GtkSignal.CHANGED.value, self._on_update) self.widgets[self.device_field] = (1, 1, 1, 1) def update_devices(self): self._update_device_list() def _update_device_list(self): with self.ignore_changes(): self.device_field.set_all_values([(d.id, d.display_name, *d.identifiers[1:]) for d in _all_devices]) def collect_value(self): device_str = self.device_field.get_value() 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 @classmethod def right_label(cls, component): device = _all_devices[component.devID] return device.display_name if device else shlex_quote(component.devID) class ActiveUI(_DeviceUI, ConditionUI): CLASS = diversion.Active label_text = _("Device is active and its settings can be changed.") @classmethod def left_label(cls, component): return _("Active") class DeviceUI(_DeviceUI, ConditionUI): CLASS = diversion.Device label_text = _("Device that originated the current notification.") @classmethod def left_label(cls, component): return _("Device") class HostUI(ConditionUI): 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.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): super().show(component, editable) with self.ignore_changes(): self.field.set_text(component.host) def collect_value(self): return self.field.get_text() @classmethod def left_label(cls, component): return _("Host") @classmethod def right_label(cls, component): return str(component.host) class _SettingWithValueUI: MULTIPLE = [Kind.MULTIPLE_TOGGLE, Kind.MAP_CHOICE, Kind.MULTIPLE_RANGE] ACCEPT_TOGGLE = True 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(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.set_valign(Gtk.Align.CENTER) self.device_field.set_size_request(400, 0) self.device_field.set_margin_top(m) 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( 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 ALL_SETTINGS.values()]) self.setting_field.set_valign(Gtk.Align.CENTER) 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( 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) self.value_field.set_size_request(250, 35) self.widgets[self.value_field] = (3, 2, 1, 1) self.key_lbl = Gtk.Label( 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.set_margin_top(m) self.key_field.hide() self.key_field.set_valign(Gtk.Align.CENTER) 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 def _all_choices(cls, setting): # choice and map-choice """Return a NamedInts instance with the choices for a setting. If the argument `setting` is a Setting instance or subclass, then the choices are taken only from it. If instead it is a name, then the function returns the union of the choices for each setting with that name. Only one label per number is kept. The function returns a 2-tuple whose first element is a NamedInts instance with the possible choices (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): setting = type(setting) if isinstance(setting, type) and issubclass(setting, Setting): choices = UnsortedNamedInts() universe = getattr(setting, "choices_universe", None) if universe: choices |= universe extra = getattr(setting, "choices_extra", None) if extra is not None: choices |= NamedInts(**{str(extra): int(extra)}) return choices, extra settings = ALL_SETTINGS.get(setting, []) choices = UnsortedNamedInts() extra = None for s in settings: ch, ext = cls._all_choices(s) choices |= ch if ext is not None: extra = ext return choices, extra @classmethod def _setting_attributes(cls, setting_name, device=None): if device and setting_name in device.settings: setting = device.settings.get(setting_name, None) settings = [type(setting)] if setting else None else: 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 == Kind.MAP_CHOICE else "choices_universe", None) if universe: keys |= universe # only one key per number is used else: keys = None return setting, val_class, kind, keys def _changed_device(self, *args): device = _all_devices[self.device_field.get_value()] setting_name = self.setting_field.get_value() if not device or not device.settings or setting_name in device.settings: kind = self._setting_attributes(setting_name, device)[2] key = self.key_field.get_value() if kind in self.MULTIPLE else None else: setting_name = kind = key = None with self.ignore_changes(): self._update_setting_list(device) self._update_key_list(setting_name, device) self._update_value_list(setting_name, device, key) def _changed_setting(self, *args): with self.ignore_changes(): device = _all_devices[self.device_field.get_value()] setting_name = self.setting_field.get_value() self._update_key_list(setting_name, device) key = self.key_field.get_value() self._update_value_list(setting_name, device, key) def _changed_key(self, *args): with self.ignore_changes(): setting_name = self.setting_field.get_value() device = _all_devices[self.device_field.get_value()] key = self.key_field.get_value() self._update_value_list(setting_name, device, key) def update_devices(self): self._update_device_list() def _update_device_list(self): with self.ignore_changes(): self.device_field.set_all_values([(d.id, d.display_name, *d.identifiers[1:]) for d in _all_devices]) def _update_setting_list(self, device=None): supported_settings = device.settings.keys() if device else {} with self.ignore_changes(): self.setting_field.show_only(supported_settings or None) def _update_key_list(self, setting_name, device=None): setting, val_class, kind, keys = self._setting_attributes(setting_name, device) multiple = kind in self.MULTIPLE self.key_field.set_visible(multiple) self.key_lbl.set_visible(multiple) if not multiple: return labels = getattr(setting, "_labels", {}) def item(k): lbl = labels.get(k, None) 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])) ds = device.settings if device else {} device_setting = ds.get(setting_name, None) supported_keys = None if device_setting: val = device_setting._validator if device_setting.kind == Kind.MULTIPLE_TOGGLE: supported_keys = val.get_options() or None elif device_setting.kind == Kind.MAP_CHOICE: choices = val.choices or None supported_keys = choices.keys() if choices else None elif device_setting.kind == Kind.MULTIPLE_RANGE: supported_keys = val.keys self.key_field.show_only(supported_keys, include_new=True) self._update_validation() def _update_value_list(self, setting_name, device=None, key=None): 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 (Kind.TOGGLE, Kind.MULTIPLE_TOGGLE): self.value_field.make_toggle() 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 == Kind.CHOICE: supported_values = choices 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 == Kind.RANGE: self.value_field.make_range(val_class.min_value, val_class.max_value) 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, ) else: self.value_field.make_unsupported() def _on_update(self, *_args): if not self._ignore_changes and self.component: self._update_validation() def _update_validation(self): 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" ) else: 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 "" self.key_field.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) 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 "" self.value_field.choice_widget.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon) def show(self, component, editable): a = iter(component.args) with self.ignore_changes(): 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, "") setting, _v, kind, keys = self._setting_attributes(setting_name, device) 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) self.key_field.set_value(key) 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")] 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() setting, val_class, kind, keys = self._setting_attributes(setting_name, device) key_value = [] if kind in self.MULTIPLE or kind is None and len(self.component.args) > 3: key = self.key_field.get_value() key = _from_named_ints(key, keys) key_value.append(key) key_value.append(self.value_field.get_value()) return [device_value, setting_name, *key_value] @classmethod def right_label(cls, component): 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) 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 disp.append(key_label or key) value = next(a, None) 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 == Kind.CHOICE: supported_values = choices 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 == 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 (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])) class SetUI(_SettingWithValueUI, ActionUI): CLASS = diversion.Set ACCEPT_TOGGLE = True label_text = _("Change setting on device") def show(self, component, editable): ActionUI.show(self, component, editable) _SettingWithValueUI.show(self, component, editable) def _on_update(self, *_args): if not self._ignore_changes and self.component: ActionUI._on_update(self, *_args) _SettingWithValueUI._on_update(self, *_args) class SettingUI(_SettingWithValueUI, ConditionUI): CLASS = diversion.Setting ACCEPT_TOGGLE = False label_text = _("Setting on device") def show(self, component, editable): ConditionUI.show(self, component, editable) _SettingWithValueUI.show(self, component, editable) def _on_update(self, *_args): if not self._ignore_changes and self.component: ConditionUI._on_update(self, *_args) _SettingWithValueUI._on_update(self, *_args) 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() _dev_model = None def update_devices(): global _dev_model global _all_devices global _diversion_dialog if _dev_model and _all_devices.refresh() and _diversion_dialog: _diversion_dialog.update_devices() 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(ActionMenu) update_devices() _diversion_dialog.window.present() Solaar-1.1.20/lib/solaar/ui/icons.py000066400000000000000000000142531522022367600171570ustar00rootroot00000000000000## 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. import logging from gi.repository import Gtk import solaar.gtk as gtk logger = logging.getLogger(__name__) 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, _has_level_icons, _has_padded_level_icons if _default_theme: return _default_theme = Gtk.IconTheme.get_default() 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"): 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): logger.warning("icon %s not found in current theme", icon_name) return TRAY_OKAY # use Solaar icon if battery icon not available logger.debug("battery icon for %s:%s = %s", level, charging, icon_name) return icon_name # return first res where val >= guard # _first_res(val,((guard,res),...)) def _first_res(val, pairs): return next((res for guard, res in pairs if val >= guard), None) 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 f"battery-missing{suffix}" 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 f"solaar-light_{int(20 * ((level + 50) // 100)):03}" _ICON_SETS = {} def device_icon_set(name="_", kind=None): icon_set = _ICON_SETS.get(name) if icon_set is None: # names of possible icons, in reverse desirability icon_set = ["preferences-desktop-peripherals"] if kind: 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): 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): if _default_theme.has_icon(n): return n 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 theme_icon = _default_theme.lookup_icon(name, size, 0) if theme_icon: file_name = theme_icon.get_filename() return file_name logger.warning("icon %s(%d) not found in current theme", name, size) Solaar-1.1.20/lib/solaar/ui/pair_window.py000066400000000000000000000256101522022367600203650ustar00rootroot00000000000000## 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. import logging from enum import Enum from gi.repository import GLib from gi.repository import Gtk from logitech_receiver import hidpp10_constants from solaar.i18n import _ from solaar.i18n import ngettext from . import icons logger = logging.getLogger(__name__) _PAIRING_TIMEOUT = 30 # seconds _STATUS_CHECK = 500 # milliseconds class GtkSignal(Enum): CANCEL = "cancel" CLOSE = "close" 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 prepare(receiver): if receiver.receiver_kind == "bolt": if receiver.discover(timeout=_PAIRING_TIMEOUT): return True else: receiver.pairing.error = "discovery did not start" return False elif receiver.set_lock(False, timeout=_PAIRING_TIMEOUT): return True 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): 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.pairing.device_passkey, } else: 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 _create_assistant(receiver, ok, finish, title, text): assistant = Gtk.Assistant() 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 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_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 Solaar-1.1.20/lib/solaar/ui/perkey/000077500000000000000000000000001522022367600167645ustar00rootroot00000000000000Solaar-1.1.20/lib/solaar/ui/perkey/__init__.py000066400000000000000000000017311522022367600210770ustar00rootroot00000000000000## 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") Solaar-1.1.20/lib/solaar/ui/perkey/_icons.py000066400000000000000000000121451522022367600206130ustar00rootroot00000000000000## 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. """Theme-aware loader for Solaar's per-key UI icons. Loads SVG icons from ``share/solaar/icons/`` and recolors them at load time to match the active GTK theme's text foreground, by substituting ``currentColor`` in the SVG before passing it to GdkPixbuf. GTK's stock symbolic loader is bypassed because it only recolors specific palette fill stand-ins and ignores ``stroke="currentColor"``. """ from __future__ import annotations import logging from pathlib import Path import gi gi.require_version("Gtk", "3.0") from gi.repository import GdkPixbuf # NOQA: E402 from gi.repository import Gio # NOQA: E402 from gi.repository import Gtk # NOQA: E402 logger = logging.getLogger(__name__) ICON_PIXEL_SIZE = 22 _search_path_added = False def ensure_icon_path() -> None: """Register share/solaar/icons with the default GtkIconTheme so our custom symbolic tool icons resolve by name. Idempotent.""" global _search_path_added if _search_path_added: return theme = Gtk.IconTheme.get_default() existing = set(theme.get_search_path() or []) # _icons.py: lib/solaar/ui/perkey/_icons.py -> parents[4] = repo root candidates = [ Path(__file__).resolve().parents[4] / "share" / "solaar" / "icons", ] for c in candidates: if c.is_dir() and str(c) not in existing: theme.append_search_path(str(c)) _search_path_added = True def themed_icon_image(icon_name: str, style_widget: Gtk.Widget) -> Gtk.Image | None: """Load a Solaar tool icon and recolor it to match the given widget's text foreground color, so the icons follow the active GTK theme (light / dark / custom). Returns None if the icon can't be loaded. """ ensure_icon_path() theme = Gtk.IconTheme.get_default() icon_info = theme.lookup_icon(icon_name, ICON_PIXEL_SIZE, Gtk.IconLookupFlags.FORCE_SIZE) if icon_info is None: return None path = icon_info.get_filename() if not path: return None fg = style_widget.get_style_context().get_color(Gtk.StateFlags.NORMAL) color = f"#{int(fg.red * 255):02x}{int(fg.green * 255):02x}{int(fg.blue * 255):02x}" try: with open(path, "r", encoding="utf-8") as f: svg = f.read() svg = svg.replace("currentColor", color) stream = Gio.MemoryInputStream.new_from_data(svg.encode("utf-8")) pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(stream, ICON_PIXEL_SIZE, ICON_PIXEL_SIZE, True) return Gtk.Image.new_from_pixbuf(pixbuf) except Exception as e: logger.debug("recolor failed for %s: %s", icon_name, e) return None def _fg_color_key(widget: Gtk.Widget) -> tuple[float, float, float]: fg = widget.get_style_context().get_color(Gtk.StateFlags.NORMAL) return (round(fg.red, 3), round(fg.green, 3), round(fg.blue, 3)) def attach_themed_icon(button: Gtk.Container, icon_name: str) -> int | None: """Add a themed icon to `button` and re-render it whenever the active GTK theme changes the button's foreground color. Returns the style-updated signal handler ID, or None if the icon couldn't be loaded (in which case the button is left unchanged so the caller can fall back to a text label). Listening to the button's own ``style-updated`` signal — instead of ``Gtk.Settings notify::gtk-theme-name`` — means we read the foreground color *after* GTK has re-resolved CSS for the new theme. Subscribing to the Settings notify fires too early; it returns the stale (pre-switch) color and produces icons that all settle on the previous theme's tone. We guard the rebuild with a per-button color key so unrelated style updates (hover, focus, active) don't trigger needless re-renders. """ image = themed_icon_image(icon_name, button) if image is None: return None button.add(image) image.show() state = {"color_key": _fg_color_key(button)} def _refresh(_widget) -> None: new_key = _fg_color_key(button) if new_key == state["color_key"]: return state["color_key"] = new_key new_image = themed_icon_image(icon_name, button) if new_image is None: return old = button.get_child() if old is not None: button.remove(old) button.add(new_image) new_image.show() return button.connect("style-updated", _refresh) Solaar-1.1.20/lib/solaar/ui/perkey/binding.py000066400000000000000000000052201522022367600207470ustar00rootroot00000000000000## 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) Solaar-1.1.20/lib/solaar/ui/perkey/canvas.py000066400000000000000000000424131522022367600206150ustar00rootroot00000000000000## 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 .layout import Cell # 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 # Phantom anchor for gaps in the matrix grid — gives rect/gradient # drags a valid endpoint where no real cell exists. rows, cols = self._matrix_size() matrix_w = cols * CELL_PX + max(0, cols - 1) * GUTTER_PX matrix_h = rows * CELL_PX + max(0, rows - 1) * GUTTER_PX if PADDING_PX <= x < PADDING_PX + matrix_w and PADDING_PX <= y < PADDING_PX + matrix_h: col = int((x - PADDING_PX) // (CELL_PX + GUTTER_PX)) row = int((y - PADDING_PX) // (CELL_PX + GUTTER_PX)) if 0 <= col < cols and 0 <= row < rows: return BoundCell(cell=Cell(zone_id=-1, row=row, col=col), bound=False) 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. The background is the zone # base color (what these cells actually display on the keyboard). # Stripes alternate darker / lighter than base in equal measure so # the cell's perceived average stays at the base color, instead of # being uniformly biased toward black or white as a single-overlay # stripe would. 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 cr.set_source_rgba(r, g, b, 1.0) cr.rectangle(x, y, w, h) cr.fill() # Per-channel ±offset. When a channel is too close to 0 or 1 to # fit the full offset, halve the offset on the constrained side # (per spec: average drifts at the limits, but stays centered on # base elsewhere). offset = 0.22 def _shift(v: float) -> tuple[float, float]: down_off = offset if (v - offset) >= 0.0 else offset / 2.0 up_off = offset if (v + offset) <= 1.0 else offset / 2.0 return max(0.0, v - down_off), min(1.0, v + up_off) rd, ru = _shift(r) gd, gu = _shift(g) bd, bu = _shift(b) # Interleave darker and lighter stripes (period = step per set, # other-color set offset by step/2). Equal coverage of the two # colors keeps the perceived average at base. cr.set_line_width(1.5) step = 6 half = step // 2 d_max = int(w + h) cr.set_source_rgba(rd, gd, bd, 1.0) 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.set_source_rgba(ru, gu, bu, 1.0) d = -int(h) + half while d <= d_max: cr.move_to(x + d, y + h) cr.line_to(x + d + h, y) cr.stroke() d += step else: # No zone base color known — fall back to a neutral dark bg with # medium-gray stripes; "average = base" doesn't apply since there # is no expected color to preserve. cr.set_source_rgba(0.30, 0.30, 0.32, 1.0) cr.rectangle(x, y, w, h) cr.fill() 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: return False tool = TOOLS.get(self._tool_name) # Endpoint tools (rect/gradient) anchor on cell centers regardless of # bind state; brush/bucket need a real key to paint/flood. if not (tool and tool.overlay_shape) and not bc.bound: return False self._press_cell = bc self._motion_cell = bc self._dragging = True self._brush_path = [bc.cell.zone_id] 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: return False tool = TOOLS.get(self._tool_name) if not (tool and tool.overlay_shape) and not bc.bound: return False if bc is self._motion_cell: return False self._motion_cell = bc 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 Solaar-1.1.20/lib/solaar/ui/perkey/control.py000066400000000000000000000234051522022367600210220ustar00rootroot00000000000000## 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: """Color used to render per-key unset cells in the editor. Matches rgb_power.effective_zone_base_color: black when zone is ignored, the saved zone color otherwise.""" device = getattr(self._setting, "_device", None) if device is None: return None from logitech_receiver import rgb_power return int(rgb_power.effective_zone_base_color(device)) 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) # Stable per-device key so the same physical device on USB and on # the receiver shares a single dialog. unitId is read from the # device firmware (via DeviceInformation) and is the same across # transports; serial is per-pairing-slot. id(self._sink) is a # last-resort fallback that should never be hit in practice. key = ( getattr(device, "unitId", None) or getattr(device, "serial", None) or getattr(device, "hid_serial", None) or getattr(device, "codename", None) or id(self._sink) ) dlg = dialog_mod.get_dialog(key) dlg.present(self._sink, layout) Solaar-1.1.20/lib/solaar/ui/perkey/dialog.py000066400000000000000000000110461522022367600205770ustar00rootroot00000000000000## 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. """Per-device dialogs hosting a PerKeyEditor. One dialog instance is kept per device key (firmware unit-id, falling back to other stable identifiers — see ``get_dialog``). The same physical device on different transports (receiver vs direct USB) shares a key so it doesn't open two windows. """ from __future__ import annotations from enum import Enum from typing import Hashable 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" _dialogs: dict[Hashable, "PerKeyEditorDialog"] = {} class PerKeyEditorDialog: def __init__(self, key: Hashable) -> None: self._key = key self._window: Gtk.Window | None = None self._wrapper: Gtk.Box | None = None self._editor: PerKeyEditor | None = None self._sink: PerKeyColorSink | None = None def _on_delete(self, _w, _e) -> bool: self._destroy() _dialogs.pop(self._key, None) return True def _destroy(self) -> None: if self._editor is not None: self._editor.shutdown() self._editor = None if self._window is not None: self._window.destroy() self._window = None self._wrapper = None self._sink = None def present(self, sink: PerKeyColorSink, layout: Layout | None) -> None: # Re-opening for the same sink while the window is already open: # just raise it (no rebuild flicker, preserves any in-progress # interaction state). if self._window is not None and self._sink is sink: self._window.present() return # Otherwise build a fresh window. We always recreate rather than # swap content in place because Gtk.Window.resize() after first # show is unreliable across X11/Wayland WMs — the WM often keeps # the original geometry — and a new window picks up the layout's # natural size cleanly on first show. self._destroy() self._sink = sink self._window = Gtk.Window() self._window.set_title(_("Per-key Lighting") + " — " + sink.title) self._window.connect(GtkSignal.DELETE_EVENT.value, self._on_delete) self._wrapper = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) self._wrapper.set_border_width(8) self._window.add(self._wrapper) self._editor = PerKeyEditor(sink, layout) self._wrapper.pack_start(self._editor, True, True, 0) self._wrapper.show_all() # Ask GTK what the wrapper actually wants to be — the canvas's # size_request propagates up through ScrolledWindow + editor VBox # (toolbar + scrolled canvas) + the wrapper's border, so the # natural size already accounts for every layout contribution. _min, nat = self._wrapper.get_preferred_size() if nat.width > 0 and nat.height > 0: self._window.resize(nat.width, nat.height) self._window.present() def get_dialog(key: Hashable) -> PerKeyEditorDialog: """Return the dialog for `key`, creating one if none is open. `key` should be a stable per-device identifier. The caller (control.py) builds it from `device.unitId` first — that's read from the device firmware via the DeviceInformation feature and is the same regardless of whether the device is on a receiver or plugged directly via USB, so the same physical device doesn't open two windows when its transport changes. """ d = _dialogs.get(key) if d is None: d = PerKeyEditorDialog(key) _dialogs[key] = d return d Solaar-1.1.20/lib/solaar/ui/perkey/editor.py000066400000000000000000000204721522022367600206310ustar00rootroot00000000000000## 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 ._icons import attach_themed_icon # 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"), } _TOOL_ICON_NAMES = { "single": "solaar-tool-brush-symbolic", "rect": "solaar-tool-rect-symbolic", "bucket": "solaar-tool-bucket-symbolic", } 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, "")) icon_name = _TOOL_ICON_NAMES.get(name) btn = Gtk.RadioButton.new_from_widget(first) btn.set_mode(False) # render as toggle button rather than radio if icon_name and attach_themed_icon(btn, icon_name) is not None: btn.set_tooltip_text(tip or label) btn.get_accessible().set_name(label) else: btn.set_label(label) 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) # Inset frame around the keyboard so it reads as a distinct panel # rather than floating flat against the dialog background. scroll.set_shadow_type(Gtk.ShadowType.IN) 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._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 try: self._palette.shutdown() except Exception as e: logger.debug("palette shutdown failed: %s", e) 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()}) Solaar-1.1.20/lib/solaar/ui/perkey/layout.py000066400000000000000000000067521522022367600206650ustar00rootroot00000000000000## 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) Solaar-1.1.20/lib/solaar/ui/perkey/layouts/000077500000000000000000000000001522022367600204645ustar00rootroot00000000000000Solaar-1.1.20/lib/solaar/ui/perkey/layouts/__init__.py000066400000000000000000000117421522022367600226020ustar00rootroot00000000000000## 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 headset_g522 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) # HEADSET_RGB_HOSTMODE = 0x0620 register_layout(0x0620, _name_contains("G522"), headset_g522.LAYOUT) Solaar-1.1.20/lib/solaar/ui/perkey/layouts/_keyboard_base.py000066400000000000000000000261151522022367600237740ustar00rootroot00000000000000## 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. Drops the row 2 col 13 backslash (zone 46 is the # upper half of the L-shape Enter on ISO, addressed by zone 37) and adds # the two ISO-only keys: POUND (zone 47) at row 3 col 12 between ' and # Enter, and ISO_BACKSLASH (zone 97) at row 4 col 1 between Shift and Z. # Regional layouts override the labels to match local keycaps (# / < on # QWERTZ, # / \ on UK QWERTY, * / < on AZERTY). _ISO_EXTRA_KEYS: tuple[Cell, ...] = ( Cell(zone_id=47, row=3, col=12, group="main", label="#"), Cell(zone_id=97, row=4, col=1, group="main", label="\\"), ) MAIN_ISO: tuple[Cell, ...] = tuple(c for c in MAIN_ANSI if not (c.row == 2 and c.col == 13)) + _ISO_EXTRA_KEYS # --- 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, ) Solaar-1.1.20/lib/solaar/ui/perkey/layouts/headset_g522.py000066400000000000000000000043511522022367600232150ustar00rootroot00000000000000## 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 G522 LIGHTSPEED headset. Eight LEDs in two 2×2 grids — one per earcup, viewed from outside: Left earcup Right earcup ┌─────┬─────┐ ┌─────┬─────┐ │ 8 │ 7 │ │ 6 │ 5 │ ├─────┼─────┤ ├─────┼─────┤ │ 4 │ 3 │ │ 2 │ 1 │ └─────┴─────┘ └─────┴─────┘ """ from __future__ import annotations from ..layout import Cell from ..layout import Layout _CELLS: tuple[Cell, ...] = ( # Left earcup (cols 0-1, outer view): top-left=8, top-right=7, bottom-left=4, bottom-right=3 Cell(zone_id=8, row=0, col=0, group="main", label="8"), Cell(zone_id=7, row=0, col=1, group="main", label="7"), Cell(zone_id=4, row=1, col=0, group="main", label="4"), Cell(zone_id=3, row=1, col=1, group="main", label="3"), # Right earcup (cols 3-4, outer view): top-left=6, top-right=5, bottom-left=2, bottom-right=1 Cell(zone_id=6, row=0, col=3, group="main", label="6"), Cell(zone_id=5, row=0, col=4, group="main", label="5"), Cell(zone_id=2, row=1, col=3, group="main", label="2"), Cell(zone_id=1, row=1, col=4, group="main", label="1"), ) LAYOUT: Layout = Layout( cells=_CELLS, rows=2, cols=5, description="Logitech G522 LIGHTSPEED headset (8 LEDs, 4 per earcup)", ) Solaar-1.1.20/lib/solaar/ui/perkey/layouts/keyboard_ansi.py000066400000000000000000000030161522022367600236500ustar00rootroot00000000000000## 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", ) Solaar-1.1.20/lib/solaar/ui/perkey/layouts/keyboard_iso_azerty.py000066400000000000000000000047231522022367600251140ustar00rootroot00000000000000## 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 47: "*", # POUND key (row 3 col 12) — French * / µ 97: "<", # ISO_BACKSLASH (row 4 col 1), between Shift and W } 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", ) Solaar-1.1.20/lib/solaar/ui/perkey/layouts/keyboard_iso_qwerty.py000066400000000000000000000033401522022367600251230ustar00rootroot00000000000000## 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", ) Solaar-1.1.20/lib/solaar/ui/perkey/layouts/keyboard_iso_qwertz.py000066400000000000000000000036471522022367600251360ustar00rootroot00000000000000## 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 47: "#", # POUND key (row 3 col 12), between Ä and Enter 97: "<", # ISO_BACKSLASH (row 4 col 1), between Shift and Y } 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", ) Solaar-1.1.20/lib/solaar/ui/perkey/layouts/keyboard_jis.py000066400000000000000000000033371522022367600235110ustar00rootroot00000000000000## 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", ) Solaar-1.1.20/lib/solaar/ui/perkey/layouts/mouse_g502x.py000066400000000000000000000034051522022367600231150ustar00rootroot00000000000000## 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 reported as zones 1..8 by the firmware. Positions may need revision per actual hardware. Row 0: 3 . . . . . 2 Row 1: . 4 8 7 6 5 . Row 2: . . . . . . 1 """ 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="1"), Cell(zone_id=2, row=0, col=6, group="main", label="2"), Cell(zone_id=3, row=0, col=0, group="main", label="3"), Cell(zone_id=4, row=1, col=1, group="main", label="4"), Cell(zone_id=5, row=1, col=5, group="main", label="5"), Cell(zone_id=6, row=1, col=4, group="main", label="6"), Cell(zone_id=7, row=1, col=3, group="main", label="7"), Cell(zone_id=8, row=1, col=2, group="main", label="8"), ) LAYOUT: Layout = Layout( cells=_CELLS, rows=3, cols=7, description="Logitech G502 X family", ) Solaar-1.1.20/lib/solaar/ui/perkey/palette.py000066400000000000000000000215701522022367600210010ustar00rootroot00000000000000## 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 from ._icons import attach_themed_icon # NOQA: E402 _UNSET_ICON_NAME = "solaar-tool-palette-off-symbolic" 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 # 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_btn = Gtk.ToggleButton() self._unset_btn.set_tooltip_text(_("Paint as 'no change' — clears the cell to the zone base color")) unset_label = _("Unset") if attach_themed_icon(self._unset_btn, _UNSET_ICON_NAME) is not None: self._unset_btn.get_accessible().set_name(unset_label) else: self._unset_btn.set_label(unset_label) self._unset_btn.connect(GtkSignal.TOGGLED.value, self._on_unset_toggled) self.pack_start(self._unset_btn, False, False, 0) def shutdown(self) -> None: # attach_themed_icon connects to the button's own style-updated # signal; GTK disconnects it automatically when the button is # destroyed, so there is nothing to clean up here. pass 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) # Re-render when the GTK theme changes, so the rounded-square # outline (drawn in the theme foreground color) stays in sync # with the tool icons next to it. self.connect("style-updated", lambda w: w.queue_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) @staticmethod def _rounded_rect_path(cr, x: float, y: float, w: float, h: float, r: float) -> 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 _on_draw(self, _w, cr) -> None: import cairo # local: keeps the module light when GradientSwatch isn't built 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) # Render in Tabler "square" coordinates (24x24 viewBox, rounded # rect from (3,3) to (21,21), corner radius 2, stroke 2) and let # cairo scale to the swatch's pixel size. Matches the outline # style of the tool icons exactly. cr.save() cr.scale(self.SIZE / 24.0, self.SIZE / 24.0) # Build the rounded-square path once, clip+fill the gradient # inside it, then re-build and stroke the outline in the theme # foreground color. self._rounded_rect_path(cr, 3, 3, 18, 18, 2) cr.save() cr.clip() # Top-left (previous, gradient start) → bottom-right (active, end). # Matches the directional behavior of dragging the line tool TL → BR. # Endpoints are shifted inward by the arc inset (corner radius * (1 # - 1/sqrt(2)), ~0.586 for r=2) so t=0 lands on the actual visible # TL corner pixel of the rounded rect — without this, the rendered # corners sample at t≈0.033/0.967 and the displayed colors are # ~8 RGB units short of the true endpoint colors. inset = 2 * (1 - 1 / (2**0.5)) pat = cairo.LinearGradient(3 + inset, 3 + inset, 21 - inset, 21 - inset) 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(3, 3, 18, 18) cr.fill() cr.restore() # drop clip self._rounded_rect_path(cr, 3, 3, 18, 18, 2) fg = self.get_style_context().get_color(Gtk.StateFlags.NORMAL) cr.set_source_rgba(fg.red, fg.green, fg.blue, fg.alpha) cr.set_line_width(2) cr.set_line_join(cairo.LINE_JOIN_ROUND) cr.set_line_cap(cairo.LINE_CAP_ROUND) cr.stroke() cr.restore() Solaar-1.1.20/lib/solaar/ui/perkey/protocol.py000066400000000000000000000047261522022367600212100ustar00rootroot00000000000000## 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. """ ... Solaar-1.1.20/lib/solaar/ui/perkey/tools.py000066400000000000000000000170141522022367600205010ustar00rootroot00000000000000## 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(), } Solaar-1.1.20/lib/solaar/ui/rule_actions.py000066400000000000000000000324501522022367600205320ustar00rootroot00000000000000## 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]) Solaar-1.1.20/lib/solaar/ui/rule_base.py000066400000000000000000000070561522022367600200100ustar00rootroot00000000000000## 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 Solaar-1.1.20/lib/solaar/ui/rule_conditions.py000066400000000000000000000567161522022367600212560ustar00rootroot00000000000000## 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) Solaar-1.1.20/lib/solaar/ui/tray.py000066400000000000000000000403371522022367600170250ustar00rootroot00000000000000## 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. import logging import os 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 solaar import NAME from solaar.i18n import _ from . import action from . import icons from . import window from .about import about 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() 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()) 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 _last_scroll = 0 def _scroll(tray_icon, event, direction=None): if direction is None: direction = event.direction now = event.time / 1000.0 else: now = None if direction != ScrollDirection.UP and direction != ScrollDirection.DOWN: # ignore all other directions return # 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 global _last_scroll now = now or time() if now - _last_scroll < 0.33: # seconds return _last_scroll = now global _picked_device candidate = None if _picked_device is None: for info in _devices_info: # pick first peripheral found if info[1] is not None: candidate = info break else: found = False for info in _devices_info: if not info[1]: # only conside peripherals continue # compare peripherals if info[0:2] == _picked_device[0:2]: if direction == ScrollDirection.UP and candidate: # select previous device break found = True else: if found: candidate = info if direction == ScrollDirection.DOWN: break # if direction is up, but no candidate found before _picked, # let it run through all candidates, will get stuck with the last one else: if direction == ScrollDirection.DOWN: # only use the first one, in case no candidates are after _picked if candidate is None: candidate = info else: candidate = info # if the last _picked_device is gone, clear it # the candidate will be either the first or last one remaining, # depending on the scroll direction if not found: _picked_device = None _picked_device = candidate or _picked_device logger.debug("scroll: picked %s", _picked_device) _update_tray_icon() try: try: 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") from gi.repository import AppIndicator3 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 # Defense against bug that shows up in XFCE 4.16 where icons are not upscaled def _icon_file(icon_name): if gtk.tray_icon_size is None and not os.path.isfile(icon_name): return icon_name icon_info = Gtk.IconTheme.get_default().lookup_icon( icon_name, gtk.tray_icon_size or _TRAY_ICON_SIZE, Gtk.IconLookupFlags.FORCE_SVG ) return icon_info.get_filename() if icon_info else icon_name def _create(menu): icons._init_icon_paths() ind = AppIndicator3.Indicator.new( "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.lower(), NAME.lower()) ind.set_menu(menu) ind.connect(GtkSignal.SCROLL_EVENT.value, _scroll) return ind def _hide(indicator): indicator.set_status(AppIndicator3.IndicatorStatus.PASSIVE) def _show(indicator): indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE) def _update_tray_icon(): 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 description_lines = _generate_description_lines() description = "\n".join(description_lines).rstrip("\n") # icon_file = icons.icon_file(icon_name, _TRAY_ICON_SIZE) _icon.set_icon_full(_icon_file(tray_icon_name), description) 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_status(AppIndicator3.IndicatorStatus.ATTENTION) GLib.timeout_add(10 * 1000, _icon.set_status, AppIndicator3.IndicatorStatus.ACTIVE) except ImportError: logger.debug("using StatusIcon") def _create(menu): 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(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 def _hide(icon): icon.set_visible(False) def _show(icon): icon.set_visible(True) def _update_tray_icon(): tooltip_lines = _generate_tooltip_lines() 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 = _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 _icon.set_from_icon_name(tray_icon_name) _icon_before_attention = None def _blink(count): global _icon_before_attention if count % 2: _icon.set_from_icon_name(icons.TRAY_ATTENTION) else: _icon.set_from_icon_name(_icon_before_attention) if count > 0: GLib.timeout_add(1000, _blink, count - 1) else: _icon_before_attention = None def attention(reason=None): global _icon_before_attention if _icon_before_attention is None: _icon_before_attention = _icon.get_icon_name() GLib.idle_add(_blink, 9) def _generate_tooltip_lines(): if not _devices_info: yield f"{NAME}: " + _("no receiver") return yield from _generate_description_lines() def _generate_description_lines(): if not _devices_info: yield _("no receiver") return for _ignore, number, name, device in _devices_info: if number is None: # receiver continue p = device.status_string() if p: # does it have any properties to print? yield f"{name}" if device.online: yield f"\t{p}" else: yield f"\t{p} (" + _("offline") + ")" else: if device.online: yield f"{name} (" + _("no status") + ")" else: yield f"{name} (" + _("offline") + ")" def _pick_device_with_lowest_battery(): if not _devices_info: return None picked = None picked_level = 1000 for info in _devices_info: if info[1] is None: # is receiver continue 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 logger.debug("picked device with lowest battery: %s", picked) return picked def _add_device(device): assert device index = 0 receiver_path = device.receiver.path if device.receiver is not None else device.path if device.receiver is not None: # if receiver insert into devices for the receiver in device number order for idx, (path, _ignore, _ignore, _ignore) in enumerate(_devices_info): if path and path == receiver_path: index = idx + 1 # the first entry matching the receiver serial should be for the receiver itself break while index < len(_devices_info): path, number, _ignore, _ignore = _devices_info[index] if not path == receiver_path: break assert number != device.number if number > device.number: break index = index + 1 new_device_info = (receiver_path, device.number, device.name, device) _devices_info.insert(index, new_device_info) 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 def _remove_device(index): assert index is not None menu_items = _menu.get_children() _menu.remove(menu_items[index]) removed_device = _devices_info.pop(index) global _picked_device if _picked_device and _picked_device[0:2] == removed_device[0:2]: # the current pick was unpaired _picked_device = None def _add_receiver(receiver): index = len(_devices_info) new_receiver_info = (receiver.path, None, receiver.name, None) _devices_info.insert(index, new_receiver_info) 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 def _remove_receiver(receiver): index = 0 # remove all entries in devices_info that match this receiver while index < len(_devices_info): path, _ignore, _ignore, _ignore = _devices_info[index] if path == receiver.path: _remove_device(index) else: index += 1 def _update_menu_item(index, 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.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)) 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, device) _devices_info = [] _menu = None _icon = None def init(_quit_handler): global _menu, _icon assert _menu is None _menu = _create_menu(_quit_handler) assert _icon is None _icon = _create(_menu) update() def destroy(): global _icon, _menu, _devices_info if _icon is not None: i, _icon = _icon, None _hide(i) i = None _icon = None _menu = None _devices_info = None def update(device=None): if _icon is None: return if device is not None: if device.kind is None: # receiver is_alive = bool(device) receiver_path = device.path if is_alive: index = None for idx, (path, _ignore, _ignore, _ignore) in enumerate(_devices_info): if path == receiver_path: index = idx break if index is None: _add_receiver(device) else: _remove_receiver(device) else: # peripheral is_paired = bool(device) receiver_path = device.receiver.path if device.receiver is not None else device.path index = None for idx, (path, number, _ignore, _ignore) in enumerate(_devices_info): if path == receiver_path and number == device.number: index = idx if is_paired: if index is None: index = _add_device(device) _update_menu_item(index, device) else: # was just unpaired or unplugged if index is not None: _remove_device(index) menu_items = _menu.get_children() no_receivers_index = len(_devices_info) menu_items[no_receivers_index].set_visible(not _devices_info) global _picked_device if (not _picked_device or _last_scroll == 0) and device is not None and device.kind is not None: # if it's just a receiver update, it's unlikely the picked device would change _picked_device = _pick_device_with_lowest_battery() _update_tray_icon() if _icon: if not _devices_info: _hide(_icon) else: _show(_icon) Solaar-1.1.20/lib/solaar/ui/window.py000066400000000000000000000767561522022367600173730ustar00rootroot00000000000000## 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. import logging from enum import Enum from enum import IntEnum import gi from gi.repository.GObject import TYPE_PYOBJECT 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 _ from solaar.i18n import ngettext from . import action from . import config_panel from . import diversion_rules from . import icons from .about import about from .common import ui_async 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 logger = logging.getLogger(__name__) _SMALL_BUTTON_ICON_SIZE = Gtk.IconSize.MENU _NORMAL_BUTTON_ICON_SIZE = Gtk.IconSize.BUTTON _TREE_ICON_SIZE = Gtk.IconSize.BUTTON _INFO_ICON_SIZE = Gtk.IconSize.LARGE_TOOLBAR _DEVICE_ICON_SIZE = Gtk.IconSize.DND try: gi.check_version("3.7.4") _CAN_SET_ROW_NONE = None except (ValueError, AttributeError): _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 _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) 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): b = Gtk.ToggleButton() if toggle else Gtk.Button() c = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 5) 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=label), True, True, 0) b.add(c) if clicked is not None: b.connect(GtkSignal.CLICKED.value, clicked) if tooltip: b.set_tooltip_text(tooltip) if not label and icon_size < _NORMAL_BUTTON_ICON_SIZE: b.set_relief(Gtk.ReliefStyle.NONE) b.set_focus_on_click(False) return b def _create_receiver_panel(): p = Gtk.Box.new(Gtk.Orientation.VERTICAL, 4) p._count = Gtk.Label() p._count.set_margin_top(24) p.pack_start(p._count, True, True, 0) p._scanning = Gtk.Label(label=_("Scanning") + "...") p._spinner = Gtk.Spinner() bp = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 8) 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) return p def _create_device_panel(): p = Gtk.Box.new(Gtk.Orientation.VERTICAL, 4) def _status_line(label_text): b = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 8) b.set_size_request(10, 28) b._label = Gtk.Label(label=label_text) b._label.set_size_request(170, 10) b.pack_start(b._label, False, False, 0) b._icon = Gtk.Image() b.pack_start(b._icon, False, False, 0) b._text = Gtk.Label() b.pack_start(b._text, False, False, 0) return b 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.pack_start(p._secure, False, False, 0) 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._config = config_panel.create() p.pack_end(p._config, True, True, 4) return p def _create_details_panel(): p = Gtk.Frame() p.set_shadow_type(Gtk.ShadowType.NONE) p.set_size_request(240, 0) p.set_state_flags(Gtk.StateFlags.ACTIVE, True) p._text = Gtk.Label() p._text.set_margin_start(6) p._text.set_margin_end(4) p._text.set_selectable(True) p.add(p._text) return p def _create_buttons_box(): bb = Gtk.HButtonBox() bb.set_layout(Gtk.ButtonBoxStyle.END) bb._details = _new_button( None, "dialog-information", _SMALL_BUTTON_ICON_SIZE, tooltip=_("Show Technical Details"), toggle=True, clicked=_update_details, ) bb.add(bb._details) bb.set_child_secondary(bb._details, True) bb.set_child_non_homogeneous(bb._details, True) def _pair_new_device(trigger): assert _find_selected_device_id() is not None receiver = _find_selected_device() assert receiver is not None assert bool(receiver) assert receiver.kind is None action.pair(_window, receiver) 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 device.kind is not None action.unpair(_window, device) bb._unpair = _new_button(_("Unpair"), "edit-delete", clicked=_unpair_current_device) bb.add(bb._unpair) return bb def _create_empty_panel(): p = Gtk.Label() p.set_markup("" + _("Select a device") + "") p.set_sensitive(False) return p def _create_info_panel(): p = Gtk.Box.new(Gtk.Orientation.VERTICAL, 4) p._title = Gtk.Label(label=" ") p._icon = Gtk.Image() b1 = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 4) b1.pack_start(p._title, True, True, 0) b1.pack_start(p._icon, False, False, 0) p.pack_start(b1, False, False, 0) p.pack_start(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL), False, False, 0) # spacer p._receiver = _create_receiver_panel() p.pack_start(p._receiver, True, True, 0) p._device = _create_device_panel() p.pack_start(p._device, True, True, 0) p.pack_start(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL), False, False, 0) # spacer p._buttons = _create_buttons_box() p.pack_end(p._buttons, False, False, 0) return p def _create_tree(model): tree = Gtk.TreeView() tree.set_size_request(330, 0) # enough width for simple setups tree.set_headers_visible(False) tree.set_show_expanders(False) tree.set_level_indentation(20) 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 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) 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.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_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) tree.append_column(battery_column) return tree def _create_window_layout(): assert _tree is not None assert _details is not None assert _info is not None assert _empty is not None assert _tree.get_selection().get_mode() == Gtk.SelectionMode.SINGLE _tree.get_selection().connect(GtkSignal.CHANGED.value, _device_selected) tree_scroll = Gtk.ScrolledWindow() tree_scroll.add(_tree) tree_scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) tree_scroll.set_shadow_type(Gtk.ShadowType.IN) tree_panel = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) tree_panel.set_homogeneous(False) tree_panel.pack_start(tree_scroll, True, True, 0) tree_panel.pack_start(_details, False, False, 0) panel = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 16) panel.pack_start(tree_panel, False, False, 0) panel.pack_start(_info, True, True, 0) panel.pack_start(_empty, True, True, 0) 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) bottom_buttons_box.add(quit_button) 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: diversion_rules.show_window(_model) ) bottom_buttons_box.add(diversion_button) bottom_buttons_box.set_child_secondary(diversion_button, True) vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 8) vbox.set_border_width(8) vbox.pack_start(panel, True, True, 0) vbox.pack_end(bottom_buttons_box, False, False, 0) vbox.show_all() _details.set_visible(False) _info.set_visible(False) return vbox def _create(delete_action): window = Gtk.Window() window.set_title(NAME) window.set_role("status-window") window.connect(GtkSignal.DELETE_EVENT.value, delete_action) vbox = _create_window_layout() window.add(vbox) geometry = Gdk.Geometry() geometry.min_width = 600 geometry.min_height = 320 window.set_geometry_hints(vbox, geometry, Gdk.WindowHints.MIN_SIZE) window.set_position(Gtk.WindowPosition.CENTER) style = window.get_style_context() style.add_class("solaar") return window def _find_selected_device(): selection = _tree.get_selection() model, item = selection.get_selected() 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) # 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 device: _update_info_panel(device, full=True) else: # When removing a receiver, one of its children may get automatically selected # before the tree had time to remove them as well. # Rather than chase around for another device to select, just clear the selection. _tree.get_selection().unselect_all() _update_info_panel(None, full=True) def _receiver_row(receiver_path, receiver=None): assert receiver_path r = _model.get_iter_first() while r: r = _model.iter_next(r) 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: return item item = _model.iter_next(item) if not item and receiver: 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) logger.debug("new receiver row %s", row_data) item = _model.append(None, row_data) if _TREE_SEPATATOR: _model.append(None, _TREE_SEPATATOR) return item or None def _device_row(receiver_path, device_number, device=None): 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 item = None new_child_index = 0 else: item = _model.iter_children(receiver_row) new_child_index = 0 while item: 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) if item_number == device_number: return item if item_number > device_number: item = None break new_child_index += 1 item = _model.iter_next(item) if not item and device: 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, ) assert len(row_data) == len(_TREE_SEPATATOR) 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 if device_number is None: item = _receiver_row(receiver_path) else: item = _device_row(receiver_path, device_number) if item: selection = _tree.get_selection() selection.select_iter(item) else: logger.warning("select(%s, %s) failed to find an item", receiver_path, device_number) def _hide(w, _ignore=None): assert w == _window # some window managers move the window to 0,0 after hide() # so try to remember the last position position = _window.get_position() _window.hide() _window.move(*position) return True def popup(trigger=None, receiver_path=None, device_id=None): if receiver_path: select(receiver_path, device_id) _window.present() return True def toggle(trigger=None): if _window.get_visible(): _hide(_window) else: _window.present() def _update_details(button): assert button visible = button.get_active() if visible: # _details._text.set_markup('reading...') def _details_items(device, read_all=False): # If read_all is False, only return stuff that is ~100% already # cached, and involves no HID++ calls. yield _("Path"), device.path if device.kind is None: yield _("USB ID"), f"{LOGITECH_VENDOR_ID:04x}:" + device.product_id if read_all: yield _("Serial"), device.serial else: yield _("Serial"), "..." else: # yield ('Codename', device.codename) yield _("Index"), device.number if device.wpid: yield _("Wireless PID"), device.wpid if device.product_id: yield _("Product ID"), f"{LOGITECH_VENDOR_ID:04x}:" + device.product_id hid_version = device.protocol 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"), device.polling_rate if read_all or not device.online: yield _("Serial"), device.serial else: yield _("Serial"), "..." if read_all and device.unitId and device.unitId != device.serial: 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() elif device.kind is None or device.online: yield f" {_('Firmware')}", "..." flag_bits = device.notification_flags if flag_bits is not None: 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 f"{text}" def _displayable_items(items): for name, value in items: value = GLib.markup_escape_text(str(value).replace("\x00", "")).strip() if value: yield name, value def _read_slow(device): items = _details_items(selected_device, True) items = _displayable_items(items) text = _make_text(items) if device == _details._current_device: GLib.idle_add(_set_details, text) selected_device = _find_selected_device() assert selected_device _details._current_device = selected_device read_all = not (selected_device.kind is None or selected_device.online) items = _details_items(selected_device, read_all) _set_details(_make_text(items)) if read_all: _details._current_device = None else: ui_async(_read_slow, selected_device) _details.set_visible(visible) def _update_receiver_panel(receiver, panel, buttons, full=False): assert receiver 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} ) 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 += 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 ) panel._count.set_markup(paired_text) is_pairing = receiver.pairing.lock_open if is_pairing: panel._scanning.set_visible(True) if not panel._spinner.get_visible(): panel._spinner.start() panel._spinner.set_visible(True) else: panel._scanning.set_visible(False) if panel._spinner.get_visible(): panel._spinner.stop() panel._spinner.set_visible(False) panel.set_visible(True) # 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) ): buttons._pair.set_sensitive(True) else: buttons._pair.set_sensitive(False) buttons._pair.set_visible(True) def _update_device_panel(device, panel, buttons, full=False): assert device is_online = bool(device.online) panel.set_sensitive(is_online) if device.battery_info is None or device.battery_info.level is None: device.read_battery() 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.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 = 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") if battery_voltage is not None and battery_level is not None: text += ", " if battery_level is not None: 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)) + ")" else: text += " (" + _("next reported ") + f"{int(battery_next_level)}%" + ")" tooltip_text = tooltip_text + _(" and next level to be reported.") if is_online: if charging: text += f" ({_('charging')})" else: text += f" ({_('last known')})" panel._battery._text.set_markup(text) panel._battery.set_tooltip_text(tooltip_text) 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.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.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." ) ) else: panel._secure.set_visible(True) panel._secure._icon.set_visible(False) panel._secure._text.set_markup(f"{_('offline')}") panel._secure.set_tooltip_text("") if is_online: 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.set_visible(True) else: panel._lux.set_visible(False) buttons._pair.set_visible(False) buttons._unpair.set_sensitive(device.receiver.may_unpair if device.receiver else False) buttons._unpair.set_visible(True) panel.set_visible(True) if full: config_panel.update(device, is_online) def _update_info_panel(device, full=False): if device is None: # no selected device, show the 'empty' panel _details.set_visible(False) _info.set_visible(False) _empty.set_visible(True) return # a receiver must be valid # a device must be paired assert device _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: _info._device.set_visible(False) _info._icon.set_sensitive(True) _info._title.set_sensitive(True) _update_receiver_panel(device, _info._receiver, _info._buttons, full) else: _info._receiver.set_visible(False) is_online = bool(device.online) _info._icon.set_sensitive(is_online) _info._title.set_sensitive(is_online) _update_device_panel(device, _info._device, _info._buttons, full) _empty.set_visible(False) _info.set_visible(True) if full: _update_details(_info._buttons._details) # window layout: # +--------------------------------+ # | tree | receiver | empty | # | | or device | | # |------------| status | | # | details | | | # |--------------------------------| # | (about) | # +--------------------------------| # either the status or empty panel is visible at any point # the details panel can be toggle on/off _model = None _tree = None _details = None _info = None _empty = None _window = None def init(show_window, hide_on_close): Gtk.Window.set_default_icon_name(NAME.lower()) global _model, _tree, _details, _info, _empty, _window _model = Gtk.TreeStore(*_COLUMN_TYPES) _tree = _create_tree(_model) _details = _create_details_panel() _info = _create_info_panel() _empty = _create_empty_panel() _window = _create(_hide if hide_on_close else destroy) if show_window: _window.present() def destroy(_ignore1=None, _ignore2=None): global _model, _tree, _details, _info, _empty, _window w, _window = _window, None w.destroy() w = None config_panel.destroy() _empty = None _info = None _details = None _tree = None _model = None def update(device, need_popup=False, refresh=False): if _window is None: return assert device is not None if need_popup: popup() selected_device_id = _find_selected_device_id() if device.kind is None: # receiver # receiver is_alive = bool(device) 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.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 _update_info_panel(device, full=full_update) elif item: if _TREE_SEPATATOR: separator = _model.iter_next(item) if separator: _model.remove(separator) _model.remove(item) else: path = device.receiver.path if device.receiver is not None else device.path 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) # 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) is_online = bool(device.online) _model.set_value(item, Column.ACTIVE, is_online) 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) else: if battery_voltage is not None and False: # Use levels instead of voltage here status_text = f"{int(battery_voltage)}mV" elif isinstance(battery_level, NamedInt): status_text = _(str(battery_level)) else: status_text = f"{int(battery_level)}%" _model.set_value(item, Column.STATUS_TEXT, status_text) 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) if selected_device_id is None or need_popup: select(device.receiver.path if device.receiver else device.path, device.number) elif selected_device_id == (device.receiver.path if device.receiver else device.path, device.number): full_update = full or was_online != is_online _update_info_panel(device, full=full_update) def find_device(serial): 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) if device and device.kind and (device.unitId == serial or device.serial == serial): result = device return True _model.foreach(check) return result Solaar-1.1.20/lib/solaar/version000066400000000000000000000000071522022367600164550ustar00rootroot000000000000001.1.20 Solaar-1.1.20/mkdocs.yml000066400000000000000000000014071522022367600150260ustar00rootroot00000000000000site_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 Solaar-1.1.20/po/000077500000000000000000000000001522022367600134375ustar00rootroot00000000000000Solaar-1.1.20/po/README000066400000000000000000000001141522022367600143130ustar00rootroot00000000000000See docs/i18n.md for instructions about creating or updating a translation. Solaar-1.1.20/po/bg.po000066400000000000000000002175121522022367600143770ustar00rootroot00000000000000# 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 "" Solaar-1.1.20/po/ca.po000066400000000000000000001571271522022367600143770ustar00rootroot00000000000000# Catalan translations for Solaar package. # Copyright (C) 2021 THE Solaar's COPYRIGHT HOLDER. # 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: 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:46 msgid "Bolt Receiver" msgstr "" #: lib/logitech_receiver/base_usb.py:57 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" #: lib/logitech_receiver/base_usb.py:124 msgid "Lightspeed Receiver" msgstr "Receptor Lightspeed" #: 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 "discharging" msgstr "descarregant" #: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "recarregant" #: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "carregant" #: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "no carregant" #: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "quasi plena" #: lib/logitech_receiver/i18n.py:43 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 "thermal error" msgstr "error tèrmic" #: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "error" #: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "estàndard" #: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "ràpid" #: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "lent" #: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "temps exhaurit pel dispositiu" #: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "dispositiu no suportat" #: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "masses dispositius" #: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "temps de seqüència exhaurit" #: 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 d'arrencada" #: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardware" #: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Altres" #: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "Botó Esquerra" #: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "Botó Dret" #: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "Botó Central" #: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "Botó Endarrere" #: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "Botó Endavant" #: lib/logitech_receiver/i18n.py:70 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 Tilt" msgstr "Inclinació a l'Esquerra" #: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "Inclinació a la Dreta" #: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "Clic Esquerra" #: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "Clic Dreta" #: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "Clic Central Ratolí" #: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "Clic Endarrere Ratolí" #: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "Clik Endavant Ratolí" #: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "Navegació amb el Botó de Gestos" #: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "Botó Scroll a l'esquerra" #: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "Botó Scroll a la dreta" #: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "pressionat" #: lib/logitech_receiver/i18n.py:86 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 "Swap Fx function" msgstr "Intercanviar funció 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 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 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:149 msgid "Hand Detection" msgstr "Detecció de mans" #: 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:157 msgid "Scroll Wheel Smooth Scrolling" msgstr "Suavitat Scroll Rodeta" #: 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 "Mode d'alta sensibilitat per l'scroll vertical amb la rodeta." #: lib/logitech_receiver/settings_templates.py:165 msgid "Side Scrolling" msgstr "Scroll 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 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: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 "Sensibilitat del moviment del ratolí" #: 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: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 "Encendre o apagar la il·luminació del teclat." #: lib/logitech_receiver/settings_templates.py:237 msgid "Scroll Wheel High Resolution" msgstr "Rodeta d'Alta Resolució" #: 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 "Desviar Rodeta" #: 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 "Direcció Desplaçament Rodeta" #: 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:265 msgid "Scroll Wheel Resolution" msgstr "Resolució Rodeta Desplaçament" #: 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 "Sensibilitat (Velocitat del punter)" #: 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:299 msgid "Thumb Wheel Diversion" msgstr "Desviament de la Rodeta del Polze" #: 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 "Direcció de la Rodeta del Polze" #: 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: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." #: 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: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:873 msgid "Change Host" msgstr "Canviar Host" #: lib/logitech_receiver/settings_templates.py:874 msgid "Switch connection to a different host" msgstr "Canviar connexió a un host diferent" #: 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 "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 "Cap dispositiu connectat." #: 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 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" #: 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 "" #: 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 "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." #: 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 "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}." #: 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." #: 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 "Testing" msgstr "Provant" #: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Documentació de Logitech" #: 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/action.py:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "Cancel·lar" #: lib/solaar/ui/config_panel.py:212 msgid "Complete - ENTER to change" msgstr "" #: lib/solaar/ui/config_panel.py:212 msgid "Incomplete" msgstr "" #: 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" #: lib/solaar/ui/config_panel.py:518 msgid "Changes allowed" msgstr "Canvis permesos" #: lib/solaar/ui/config_panel.py:519 msgid "No changes allowed" msgstr "Canvis no permesos" #: lib/solaar/ui/config_panel.py:520 msgid "Ignore this setting" msgstr "Ignorar aquesta opció" #: lib/solaar/ui/config_panel.py:565 msgid "Working" msgstr "Funcionant" #: 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:65 msgid "Built-in rules" msgstr "Regles integrades" #: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" msgstr "Regles definides per l'usuari" #: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "Regla" #: 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:70 msgid "[empty]" msgstr "[buit]" #: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "Editor de Regles Solaar" #: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "¿Fer els canvis permanents?" #: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Sí" #: lib/solaar/ui/diversion_rules.py:148 msgid "No" msgstr "No" #: 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 below" msgstr "Inserir a baix" #: lib/solaar/ui/diversion_rules.py:382 msgid "Insert new rule here" msgstr "Inserir nova regla aquí" #: lib/solaar/ui/diversion_rules.py:384 msgid "Insert new rule above" msgstr "Inserir nova regla a dalt" #: lib/solaar/ui/diversion_rules.py:386 msgid "Insert new rule below" msgstr "Inserir nova regla a baix" #: lib/solaar/ui/diversion_rules.py:427 msgid "Paste here" msgstr "Enganxar aquí" #: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "Enganxar a dalt" #: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "Enganxar 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 above" msgstr "Enganxar regla a dalt" #: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "Enganxar regla a baix" #: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "Enganxar regla" #: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "Aplanar" #: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "Insertar" #: 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 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 "Condició" #: 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:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "Informar" #: 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:518 msgid "Mouse process" msgstr "" #: 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 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "Tecla" #: 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 "Actiu" #: 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: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 "Gest de Ratolí" #: lib/solaar/ui/diversion_rules.py:532 msgid "Action" msgstr "Acció" #: 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:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "Desplaçament rodeta del ratolí" #: 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:537 msgid "Set" msgstr "" #: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 msgid "Execute" msgstr "Executar" #: 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 "Afegir nova regla" #: 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:610 msgid "Negate" msgstr "Negar" #: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "Embolicar amb" #: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Tallar" #: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Enganxar" #: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Copiar" #: 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/diversion_rules.py:1138 msgid "Number of seconds to delay." msgstr "" #: lib/solaar/ui/diversion_rules.py:1177 msgid "Not" msgstr "No" #: 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 "ProcésRatolí" #: 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 "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 "Afegir tecla" #: 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 "Botó" #: 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 "Afegir argument" #: 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 "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 "%(receiver_name)s: pair new device" msgstr "%(receiver_name)s: vincular nou dispositiu" #: 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 "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 "Voltage reported by battery" msgstr "Voltatge informat per la bateria" #: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "Nivell de 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:705 lib/solaar/ui/window.py:707 msgid "next reported " msgstr "següent informe " #: 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:713 msgid "last known" msgstr "últim conegut" #: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "xifrat" #: 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:728 msgid "not encrypted" msgstr "no xifrat" #: 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" #~ 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_percent)d%%" #~ msgstr "%(battery_percent)d%%" #~ msgid "Add action" #~ msgstr "Afegir acció" #~ 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 "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 "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 "Battery information unknown." #~ msgstr "Informació de la bateria desconeguda." #~ msgid "Count" #~ msgstr "Conta" #~ msgid "DPI Sliding Adjustment" #~ msgstr "Ajustar DPI lliscant" #~ msgid "Effectively turns off thumb scrolling in Linux." #~ msgstr "Desactiva eficaçment el desplaçament amb el polze en Linux." #~ msgid "Effectively turns off wheel scrolling in Linux." #~ msgstr "Desactiva eficaçment l'scroll de la rodeta amb Linux." #, 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 "HID++ Scrolling" #~ msgstr "Desplaçament HID++" #~ msgid "HID++ mode for horizontal scroll with the thumb wheel." #~ msgstr "Mode HID++ per desplaçament horitzontal amb la rodeta del " #~ "polze." #~ 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" Solaar-1.1.20/po/cs.po000066400000000000000000002404651522022367600144170ustar00rootroot00000000000000# Czech translations for solaar package. # Copyright (C) 2020 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # # Automatically generated, 2020. # SPDX-FileCopyrightText: 2020, 2026 Marián Kyral msgid "" msgstr "" "Project-Id-Version: solaar 1.0.4\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-05-22 18:55+0200\n" "PO-Revision-Date: 2026-05-22 19:15+0200\n" "Last-Translator: Marián Kyral \n" "Language-Team: Czech \n" "Language: cs\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 26.04.1\n" #: lib/logitech_receiver/base_usb.py:52 msgid "Bolt Receiver" msgstr "Přijímač Bolt" #: lib/logitech_receiver/base_usb.py:64 msgid "Unifying Receiver" msgstr "Přijímač 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 "Přijímač Nano" #: lib/logitech_receiver/base_usb.py:125 msgid "Lightspeed Receiver" msgstr "Přijímač Lightspeed" #: lib/logitech_receiver/base_usb.py:136 msgid "EX100 Receiver 27 Mhz" msgstr "Přijímač EX100 27 Mhz" #: lib/logitech_receiver/common.py:698 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "Baterie: %(level)s (%(status)s)" #: lib/logitech_receiver/common.py:701 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Baterie: %(percent)d%% (%(status)s)" #: lib/logitech_receiver/hidpp20.py:1189 lib/logitech_receiver/hidpp20.py:1222 #: lib/logitech_receiver/hidpp20.py:1250 msgid "Cycle" msgstr "Cyklické" #: lib/logitech_receiver/hidpp20.py:1190 msgid "Right" msgstr "Vpravo" #: lib/logitech_receiver/hidpp20.py:1191 msgid "Down" msgstr "Dolů" #: lib/logitech_receiver/hidpp20.py:1192 msgid "Center Out" msgstr "Ze středu" #: lib/logitech_receiver/hidpp20.py:1193 msgid "In" msgstr "Dovnitř" #: lib/logitech_receiver/hidpp20.py:1194 msgid "Out" msgstr "Ven" #: lib/logitech_receiver/hidpp20.py:1195 #: lib/logitech_receiver/settings_templates.py:1398 msgid "Left" msgstr "Vlevo" #: lib/logitech_receiver/hidpp20.py:1196 msgid "Up" msgstr "Nahoru" #: lib/logitech_receiver/hidpp20.py:1197 msgid "Center In" msgstr "Do středu" #: lib/logitech_receiver/hidpp20.py:1218 #: lib/logitech_receiver/settings_templates.py:344 msgid "Disabled" msgstr "Zakázáno" #: lib/logitech_receiver/hidpp20.py:1219 msgid "Static" msgstr "Statické" #: lib/logitech_receiver/hidpp20.py:1220 msgid "Pulse" msgstr "Pulzní" #: lib/logitech_receiver/hidpp20.py:1229 lib/logitech_receiver/hidpp20.py:1255 msgid "Wave" msgstr "Vlna" #: lib/logitech_receiver/hidpp20.py:1233 msgid "Boot" msgstr "Spuštění" #: lib/logitech_receiver/hidpp20.py:1234 msgid "Demo" msgstr "Ukázka" #: lib/logitech_receiver/hidpp20.py:1236 msgid "Breathe" msgstr "Dech" #: lib/logitech_receiver/hidpp20.py:1241 lib/logitech_receiver/hidpp20.py:1262 msgid "Ripple" msgstr "Vlnění" #: lib/logitech_receiver/hidpp20.py:1246 msgid "Decomposition" msgstr "Dekompozice" #: lib/logitech_receiver/hidpp20.py:1247 msgid "Signature1" msgstr "Podpis1" #: lib/logitech_receiver/hidpp20.py:1248 msgid "Signature2" msgstr "Podpis2" #: lib/logitech_receiver/hidpp20.py:1268 msgid "Dim" msgstr "Ztlumeno" #: lib/logitech_receiver/hidpp20.py:1340 msgid "Unknown Location" msgstr "Neznámé místo" #: lib/logitech_receiver/hidpp20.py:1341 #: lib/logitech_receiver/settings_templates.py:2619 #: lib/logitech_receiver/settings_templates.py:2907 #: lib/logitech_receiver/settings_templates.py:3647 msgid "Primary" msgstr "Primární" #: lib/logitech_receiver/hidpp20.py:1342 msgid "Logo" msgstr "Logo" #: lib/logitech_receiver/hidpp20.py:1343 msgid "Left Side" msgstr "Levá strana" #: lib/logitech_receiver/hidpp20.py:1344 msgid "Right Side" msgstr "Pravá strana" #: lib/logitech_receiver/hidpp20.py:1345 msgid "Combined" msgstr "Kombinováno" #: lib/logitech_receiver/hidpp20.py:1346 msgid "Primary 1" msgstr "Primární 1" #: lib/logitech_receiver/hidpp20.py:1347 msgid "Primary 2" msgstr "Primární 2" #: lib/logitech_receiver/hidpp20.py:1348 msgid "Primary 3" msgstr "Primární 3" #: lib/logitech_receiver/hidpp20.py:1349 msgid "Primary 4" msgstr "Primární 4" #: lib/logitech_receiver/hidpp20.py:1350 msgid "Primary 5" msgstr "Primární 5" #: lib/logitech_receiver/hidpp20.py:1351 msgid "Primary 6" msgstr "Primární 6" #: lib/logitech_receiver/i18n.py:28 msgid "empty" msgstr "prázdná" #: lib/logitech_receiver/i18n.py:29 msgid "critical" msgstr "kritická" #: lib/logitech_receiver/i18n.py:30 msgid "low" msgstr "nízká" #: lib/logitech_receiver/i18n.py:31 msgid "average" msgstr "průměrná" #: lib/logitech_receiver/i18n.py:32 msgid "good" msgstr "dobrá" #: lib/logitech_receiver/i18n.py:33 msgid "full" msgstr "plně nabito" #: lib/logitech_receiver/i18n.py:35 msgid "discharging" msgstr "vybíjení" #: lib/logitech_receiver/i18n.py:36 msgid "recharging" msgstr "nabíjení" #: lib/logitech_receiver/i18n.py:37 lib/solaar/ui/window.py:706 msgid "charging" msgstr "nabíjení" #: lib/logitech_receiver/i18n.py:38 msgid "not charging" msgstr "nenabíjí" #: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "téměř nabito" #: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "nabito" #: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "pomalé nabíjení" #: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "nesprávná baterie" #: lib/logitech_receiver/i18n.py:43 msgid "thermal error" msgstr "teplotní chyba" #: lib/logitech_receiver/i18n.py:44 msgid "error" msgstr "chyba" #: lib/logitech_receiver/i18n.py:45 msgid "standard" msgstr "standard" #: lib/logitech_receiver/i18n.py:46 msgid "fast" msgstr "rychle" #: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "pomalu" #: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "vypršel časový limit zařízení" #: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "nepodporované zařízení" #: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "příliš mnoho zařízení" #: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "vypršel časový limit sekvence" #: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:561 msgid "Firmware" msgstr "Firmware" #: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Bootloader" #: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Hardware" #: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "Další" #: lib/logitech_receiver/i18n.py:59 #: lib/logitech_receiver/settings_templates.py:4336 msgid "Left Button" msgstr "Levé tlačítko" #: lib/logitech_receiver/i18n.py:60 #: lib/logitech_receiver/settings_templates.py:4336 msgid "Right Button" msgstr "Pravé tlačítko" #: lib/logitech_receiver/i18n.py:61 msgid "Middle Button" msgstr "Prostřední tlačítko" #: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "Tlačítko Zpět" #: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "Tlačítko Vpřed" #: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "Tlačítko Gesta myši" #: lib/logitech_receiver/i18n.py:65 msgid "Smart Shift" msgstr "Chytrý přepínač" #: lib/logitech_receiver/i18n.py:66 msgid "DPI Switch" msgstr "Přepínač DPI" #: lib/logitech_receiver/i18n.py:67 msgid "Left Tilt" msgstr "Levé naklonění" #: lib/logitech_receiver/i18n.py:68 msgid "Right Tilt" msgstr "Pravé naklonění" #: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "Levý klik" #: lib/logitech_receiver/i18n.py:70 msgid "Right Click" msgstr "Pravý klik" #: lib/logitech_receiver/i18n.py:71 msgid "Mouse Middle Button" msgstr "Prostřední tlačítko myši" #: lib/logitech_receiver/i18n.py:72 msgid "Mouse Back Button" msgstr "Tlačítko myši Zpět" #: lib/logitech_receiver/i18n.py:73 msgid "Mouse Forward Button" msgstr "Tlačítko myši Vpřed" #: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "Tlačítko navigace gesty" #: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "Levé Rolovací tlačítko myši" #: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "Pravé Rolovací tlačítko myši" #: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "stisknuto" #: lib/logitech_receiver/i18n.py:79 msgid "released" msgstr "uvolněno" #: lib/logitech_receiver/notifications.py:157 lib/solaar/listener.py:350 msgid "connected" msgstr "připojeno" #: lib/logitech_receiver/notifications.py:157 lib/solaar/listener.py:350 msgid "disconnected" msgstr "odpojeno" #: lib/logitech_receiver/notifications.py:183 msgid "unpaired" msgstr "nespárováno" #: lib/logitech_receiver/notifications.py:232 msgid "powered on" msgstr "zapnuto" #: lib/logitech_receiver/notifications.py:284 msgid "ADC measurement notification" msgstr "ADC měřící oznámení" #: lib/logitech_receiver/notifications.py:486 #: lib/logitech_receiver/notifications.py:542 msgid "pairing lock is closed" msgstr "zámek párování je uzamknut" #: lib/logitech_receiver/notifications.py:486 #: lib/logitech_receiver/notifications.py:542 msgid "pairing lock is open" msgstr "zámek párování je odemknut" #: lib/logitech_receiver/notifications.py:505 msgid "discovery lock is closed" msgstr "zámek vyhledávání je uzamknut" #: lib/logitech_receiver/notifications.py:505 msgid "discovery lock is open" msgstr "zámek vyhledávání je odemknut" #: lib/logitech_receiver/receiver.py:462 msgid "No paired devices." msgstr "Nespárované zařízení." #: lib/logitech_receiver/receiver.py:464 lib/solaar/ui/window.py:612 #, python-format msgid "%(count)s paired device." msgid_plural "%(count)s paired devices." msgstr[0] "%(count)s spárované zařízení." msgstr[1] "%(count)s spárovaná zařízení." msgstr[2] "%(count)s spárovaných zařízení." #: lib/logitech_receiver/settings.py:621 msgid "register" msgstr "registrace" #: lib/logitech_receiver/settings.py:635 lib/logitech_receiver/settings.py:673 msgid "feature" msgstr "vlastnost" #: lib/logitech_receiver/settings_templates.py:183 msgid "Swap Fx function" msgstr "Přepnout Fx funkci" #: lib/logitech_receiver/settings_templates.py:186 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 klávesu pro aktivaci jejich standardní funkce." #: lib/logitech_receiver/settings_templates.py:191 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 pro aktivaci jejich speciální funkce." #: lib/logitech_receiver/settings_templates.py:199 msgid "Hand Detection" msgstr "Detekce rukou" #: lib/logitech_receiver/settings_templates.py:200 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:207 msgid "Scroll Wheel Smooth Scrolling" msgstr "Plynulý posuv kolečka" #: lib/logitech_receiver/settings_templates.py:208 #: lib/logitech_receiver/settings_templates.py:455 #: lib/logitech_receiver/settings_templates.py:484 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:215 msgid "Side Scrolling" msgstr "Boční posun" #: lib/logitech_receiver/settings_templates.py:217 msgid "" "When disabled, pushing the wheel sideways sends custom button " "events\n" "instead of the standard side-scrolling events." msgstr "" "Pokud je zakázáno, boční stlačení kolečka pošle vlastní události " "tlačítek\n" "místo standardních událostí bočního posuvu." #: lib/logitech_receiver/settings_templates.py:227 msgid "Sensitivity (DPI - older mice)" msgstr "Citlivost (DPI - starší myši)" #: lib/logitech_receiver/settings_templates.py:228 #: lib/logitech_receiver/settings_templates.py:1064 #: lib/logitech_receiver/settings_templates.py:1092 msgid "Mouse movement sensitivity" msgstr "Citlivost pohybu myši" #: lib/logitech_receiver/settings_templates.py:301 msgid "Backlight Timed" msgstr "Doba podsvícení" #: lib/logitech_receiver/settings_templates.py:302 #: lib/logitech_receiver/settings_templates.py:442 msgid "Set illumination time for keyboard." msgstr "Nastavit dobu podsvícení klávesnice." #: lib/logitech_receiver/settings_templates.py:313 msgid "Backlight" msgstr "Podsvícení" #: lib/logitech_receiver/settings_templates.py:314 msgid "" "Illumination level on keyboard. Changes made are only applied in " "Manual mode." msgstr "" "Úroveň osvětlení kláves. Změny jsou aplikovány pouze v manuálním " "režimu." #: lib/logitech_receiver/settings_templates.py:346 msgid "Automatic" msgstr "Automatický" #: lib/logitech_receiver/settings_templates.py:348 msgid "Manual" msgstr "Manuální" #: lib/logitech_receiver/settings_templates.py:350 msgid "Enabled" msgstr "Zapnuto" #: lib/logitech_receiver/settings_templates.py:356 msgid "Backlight Level" msgstr "Úroveň podsvícení" #: lib/logitech_receiver/settings_templates.py:357 msgid "Illumination level on keyboard when in Manual mode." msgstr "Úroveň podsvícení klávesnice v manuálním režimu." #: lib/logitech_receiver/settings_templates.py:414 msgid "Backlight Delay Hands Out" msgstr "Zpoždění vypnutí podsvícení" #: lib/logitech_receiver/settings_templates.py:415 msgid "" "Delay in seconds until backlight fades out with hands away from " "keyboard." msgstr "" "Zpoždění v sekundách, po kterém se podsvícení zhasne, pokud ruce " "opustí prostor klávesnice." #: lib/logitech_receiver/settings_templates.py:423 msgid "Backlight Delay Hands In" msgstr "Zpoždění zapnutí podsvícení" #: lib/logitech_receiver/settings_templates.py:424 msgid "Delay in seconds until backlight fades out with hands near keyboard." msgstr "" "Zpoždění v sekundách, po kterém se podsvícení zhasne, když jsou ruce " "přiblíží klávesnice." #: lib/logitech_receiver/settings_templates.py:432 msgid "Backlight Delay Powered" msgstr "Zpoždění podsvícení při napájení" #: lib/logitech_receiver/settings_templates.py:433 msgid "Delay in seconds until backlight fades out with external power." msgstr "" "Zpoždění v sekundách, po kterém podsvícení při napájení z externího " "zdroje postupně zhasne." #: lib/logitech_receiver/settings_templates.py:441 msgid "Backlight (Seconds)" msgstr "Podsvíceni (sekundy)" #: lib/logitech_receiver/settings_templates.py:453 msgid "Scroll Wheel High Resolution" msgstr "Vysoké rozlišení posuvného kolečka" #: lib/logitech_receiver/settings_templates.py:457 #: lib/logitech_receiver/settings_templates.py:486 msgid "Set to ignore if scrolling is abnormally fast or slow" msgstr "Nastavte na ignorovat pokud je rolování nezvykle rychle nebo pomalé" #: lib/logitech_receiver/settings_templates.py:464 #: lib/logitech_receiver/settings_templates.py:495 msgid "Scroll Wheel Diversion" msgstr "Odklonění rolovacího kolečka" #: lib/logitech_receiver/settings_templates.py:466 msgid "" "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which " "trigger Solaar rules but are otherwise ignored)." msgstr "" "Nastaví rolovací kolečko aby posílalo LOWRES_WHEEL HID++ oznámení " "(které spustí Solaar pravidla, ale jinak je ignorováno)." #: lib/logitech_receiver/settings_templates.py:473 msgid "Scroll Wheel Direction" msgstr "Směr kolečka" #: lib/logitech_receiver/settings_templates.py:474 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:482 msgid "Scroll Wheel Resolution" msgstr "Rozlišení kolečka" #: lib/logitech_receiver/settings_templates.py:497 msgid "" "Make scroll wheel send HIRES_WHEEL HID++ notifications (which " "trigger Solaar rules but are otherwise ignored)." msgstr "" "Nastaví rolovací kolečko aby posílalo HIRES_WHEEL HID++ oznámení " "(které spustí Solaar pravidla, ale jinak je ignorováno)." #: lib/logitech_receiver/settings_templates.py:506 msgid "Sensitivity (Pointer Speed)" msgstr "Citlivost (rychlost kurzoru)" #: lib/logitech_receiver/settings_templates.py:507 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:517 msgid "Thumb Wheel Diversion" msgstr "Odklonění kolečka palce" #: lib/logitech_receiver/settings_templates.py:519 msgid "" "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger " "Solaar rules but are otherwise ignored)." msgstr "" "Nastaví kolečko palce aby posílalo THUMB_WHEEL HID++ oznámení (které " "spustí Solaar pravidla, ale jinak je ignorováno)." #: lib/logitech_receiver/settings_templates.py:528 msgid "Thumb Wheel Direction" msgstr "Směr kolečka palce" #: lib/logitech_receiver/settings_templates.py:529 msgid "Invert thumb wheel scroll direction." msgstr "Mění směr otáčení kolečka palce." #: lib/logitech_receiver/settings_templates.py:549 msgid "Onboard Profiles" msgstr "Vestavěné profily" #: lib/logitech_receiver/settings_templates.py:550 msgid "" "Enable an onboard profile, which controls report rate, sensitivity, " "and button actions" msgstr "" "Povolit vestavěný profil který nastavuje četnost hlášením, citlivost " "a akce tlačítek" #: lib/logitech_receiver/settings_templates.py:594 #: lib/logitech_receiver/settings_templates.py:627 msgid "Report Rate" msgstr "Četnost hlášení" #: lib/logitech_receiver/settings_templates.py:596 #: lib/logitech_receiver/settings_templates.py:629 msgid "Frequency of device movement reports" msgstr "Frekvence hlášení pohybu zařízení" #: lib/logitech_receiver/settings_templates.py:596 #: lib/logitech_receiver/settings_templates.py:629 #: lib/logitech_receiver/settings_templates.py:1064 #: lib/logitech_receiver/settings_templates.py:1092 #: lib/logitech_receiver/settings_templates.py:1471 #: lib/logitech_receiver/settings_templates.py:1502 msgid "May need Onboard Profiles set to Disable to be effective." msgstr "Může vyžadovat deaktivaci Vestavěných profilů aby se změna projevila." #: lib/logitech_receiver/settings_templates.py:657 msgid "Divert crown events" msgstr "Odklonění událostí korunky" #: lib/logitech_receiver/settings_templates.py:658 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:666 msgid "Crown smooth scroll" msgstr "Plynulý posun korunky" #: lib/logitech_receiver/settings_templates.py:667 msgid "Set crown smooth scroll" msgstr "Nastaví plynulý posun korunky" #: lib/logitech_receiver/settings_templates.py:675 msgid "Divert G and M Keys" msgstr "Odklonění G a M kláves" #: lib/logitech_receiver/settings_templates.py:676 msgid "" "Make G and M keys send HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "" "Nastaví G a M klávesy aby posílaly HID++ oznámení (která spustí " "Solaar pravidla, ale jinak budou ignorována)." #: lib/logitech_receiver/settings_templates.py:690 msgid "Scroll Wheel Ratcheted" msgstr "Krokování rolovacího kolečka" #: lib/logitech_receiver/settings_templates.py:691 msgid "" "Switch the mouse wheel between speed-controlled ratcheting and " "always freespin." msgstr "" "Přepíná kolečko myši mezi rychlostně omezeným krokováním a volnou " "rotací." #: lib/logitech_receiver/settings_templates.py:693 msgid "Freespinning" msgstr "Volná rotace" #: lib/logitech_receiver/settings_templates.py:693 msgid "Ratcheted" msgstr "Krokování" #: lib/logitech_receiver/settings_templates.py:700 msgid "Scroll Wheel Ratchet Speed" msgstr "Rychlost krokování kolečka" #: lib/logitech_receiver/settings_templates.py:702 msgid "" "Use the mouse wheel speed to switch between ratcheted and " "freespinning.\n" "The mouse wheel is always ratcheted at 50." msgstr "" "Rychlost rotace kolečka při které se přepne mezi krokováním a volným " "posunem.\n" "Hodnota 50 vždy přepne na krokování." #: lib/logitech_receiver/settings_templates.py:752 msgid "Scroll Wheel Ratchet Torque" msgstr "Moment změny krokování kolečka" #: lib/logitech_receiver/settings_templates.py:753 msgid "Change the torque needed to overcome the ratchet." msgstr "Změní moment nutný k překonání krokování." #: lib/logitech_receiver/settings_templates.py:788 msgid "Key/Button Actions" msgstr "Akce klávesy/tlačítka" #: lib/logitech_receiver/settings_templates.py:790 msgid "Change the action for the key or button." msgstr "Změní akci klávesy nebo tlačítka." #: lib/logitech_receiver/settings_templates.py:792 msgid "Overridden by diversion." msgstr "Zrušeno odklonění." #: lib/logitech_receiver/settings_templates.py:794 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:969 msgid "Key/Button Diversion" msgstr "Odklonění klávesy/tlačítka" #: lib/logitech_receiver/settings_templates.py:970 msgid "" "Make the key or button send HID++ notifications (Diverted) or " "initiate Mouse Gestures or Sliding DPI" msgstr "" "Nastaví G klávesy aby posílaly HID++ oznámení (Odkloněno) nebo " "spustí gesta myši či plynulé nastavení DPI" #: lib/logitech_receiver/settings_templates.py:973 #: lib/logitech_receiver/settings_templates.py:974 #: lib/logitech_receiver/settings_templates.py:975 msgid "Diverted" msgstr "Odkloněno" #: lib/logitech_receiver/settings_templates.py:973 #: lib/logitech_receiver/settings_templates.py:974 msgid "Mouse Gestures" msgstr "Gesta myši" #: lib/logitech_receiver/settings_templates.py:973 #: lib/logitech_receiver/settings_templates.py:974 #: lib/logitech_receiver/settings_templates.py:975 msgid "Regular" msgstr "Běžné" #: lib/logitech_receiver/settings_templates.py:973 msgid "Sliding DPI" msgstr "Plynulé nastavení DPI" #: lib/logitech_receiver/settings_templates.py:1063 #: lib/logitech_receiver/settings_templates.py:1091 msgid "Sensitivity (DPI)" msgstr "Citlivost (DPI)" #: lib/logitech_receiver/settings_templates.py:1173 msgid "Sensitivity Switching" msgstr "Přepnutí citlivosti" #: lib/logitech_receiver/settings_templates.py:1175 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 "" "Přepnutí současné a uložené citlivosti při stisku tlačítka či " "klávesy.\n" "Pokud není citlivost uložena, uloží současnou citlivost." #: lib/logitech_receiver/settings_templates.py:1179 msgid "Off" msgstr "Vypnuto" #: lib/logitech_receiver/settings_templates.py:1210 msgid "Disable keys" msgstr "Zakázat klávesy" #: lib/logitech_receiver/settings_templates.py:1211 msgid "Disable specific keyboard keys." msgstr "Zakáže specifické klávesy." #: lib/logitech_receiver/settings_templates.py:1214 #, python-format msgid "Disables the %s key." msgstr "Zakáže %s klávesu." #: lib/logitech_receiver/settings_templates.py:1227 #: lib/logitech_receiver/settings_templates.py:1284 msgid "Set OS" msgstr "Nastavit OS" #: lib/logitech_receiver/settings_templates.py:1228 #: lib/logitech_receiver/settings_templates.py:1285 msgid "Change keys to match OS." msgstr "Změní klávesy dle operačního systému." #: lib/logitech_receiver/settings_templates.py:1297 msgid "Change Host" msgstr "Změnit hostitele" #: lib/logitech_receiver/settings_templates.py:1298 msgid "Switch connection to a different host" msgstr "Přepnout spojení na jiného hostitele" #: lib/logitech_receiver/settings_templates.py:1322 msgid "Performs a left click." msgstr "Provede levý klik." #: lib/logitech_receiver/settings_templates.py:1322 msgid "Single tap" msgstr "Jednoduché klepnutí" #: lib/logitech_receiver/settings_templates.py:1323 msgid "Performs a right click." msgstr "Provede pravý klik." #: lib/logitech_receiver/settings_templates.py:1323 msgid "Single tap with two fingers" msgstr "Jedno klepnutí dvěma prsty" #: lib/logitech_receiver/settings_templates.py:1324 msgid "Single tap with three fingers" msgstr "Jedno klepnutí třemi prsty" #: lib/logitech_receiver/settings_templates.py:1328 msgid "Double tap" msgstr "Dvojklepnutí" #: lib/logitech_receiver/settings_templates.py:1328 msgid "Performs a double click." msgstr "Provede dvouklik." #: lib/logitech_receiver/settings_templates.py:1329 msgid "Double tap with two fingers" msgstr "Dvojité klepnutí dvěma prsty" #: lib/logitech_receiver/settings_templates.py:1330 msgid "Double tap with three fingers" msgstr "Dvojité klepnutí třemi prsty" #: lib/logitech_receiver/settings_templates.py:1333 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:1333 msgid "Tap and drag" msgstr "Klepni a táhni" #: lib/logitech_receiver/settings_templates.py:1335 msgid "Tap and drag with two fingers" msgstr "Klepni a táhni dvěma prsty" #: lib/logitech_receiver/settings_templates.py:1336 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:1338 msgid "Tap and drag with three fingers" msgstr "Klepnutí a táhnutí třemi prsty" #: lib/logitech_receiver/settings_templates.py:1341 msgid "Suppress tap and edge gestures" msgstr "Potlačit gesta klepnutí a hrany" #: lib/logitech_receiver/settings_templates.py:1342 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:1344 msgid "Scroll with one finger" msgstr "Posuv jedním prstem." #: lib/logitech_receiver/settings_templates.py:1344 #: lib/logitech_receiver/settings_templates.py:1345 #: lib/logitech_receiver/settings_templates.py:1348 msgid "Scrolls." msgstr "Posuvy." #: lib/logitech_receiver/settings_templates.py:1345 #: lib/logitech_receiver/settings_templates.py:1348 msgid "Scroll with two fingers" msgstr "Posuv dvěma prsty" #: lib/logitech_receiver/settings_templates.py:1346 msgid "Scroll horizontally with two fingers" msgstr "Vodorovný posuv dvěma prsty" #: lib/logitech_receiver/settings_templates.py:1346 msgid "Scrolls horizontally." msgstr "Vodorovný posuv." #: lib/logitech_receiver/settings_templates.py:1347 msgid "Scroll vertically with two fingers" msgstr "Svislý posuv dvěma prsty" #: lib/logitech_receiver/settings_templates.py:1347 msgid "Scrolls vertically." msgstr "Svislý posuv." #: lib/logitech_receiver/settings_templates.py:1349 msgid "Inverts the scrolling direction." msgstr "Mění směr otáčení." #: lib/logitech_receiver/settings_templates.py:1349 msgid "Natural scrolling" msgstr "Přirozený posuv" #: lib/logitech_receiver/settings_templates.py:1350 msgid "Enables the thumbwheel." msgstr "Povolí kolečko palce." #: lib/logitech_receiver/settings_templates.py:1350 msgid "Thumbwheel" msgstr "Kolečko palce" #: lib/logitech_receiver/settings_templates.py:1361 #: lib/logitech_receiver/settings_templates.py:1365 msgid "Swipe from the top edge" msgstr "Táhnutí z horní hrany" #: lib/logitech_receiver/settings_templates.py:1362 msgid "Swipe from the left edge" msgstr "Táhnutí z levé hrany" #: lib/logitech_receiver/settings_templates.py:1363 msgid "Swipe from the right edge" msgstr "Táhnutí z pravé hrany" #: lib/logitech_receiver/settings_templates.py:1364 msgid "Swipe from the bottom edge" msgstr "Táhnutí ze spodní hrany" #: lib/logitech_receiver/settings_templates.py:1366 msgid "Swipe two fingers from the left edge" msgstr "Táhnutí dvěma prsty z levé hrany" #: lib/logitech_receiver/settings_templates.py:1367 msgid "Swipe two fingers from the right edge" msgstr "Táhnutí dvěma prsty z pravé hrany" #: lib/logitech_receiver/settings_templates.py:1368 msgid "Swipe two fingers from the bottom edge" msgstr "Táhnutí dvěma prsty ze spodní hrany" #: lib/logitech_receiver/settings_templates.py:1369 msgid "Swipe two fingers from the top edge" msgstr "Táhnutí dvěma prsty z horní hrany" #: lib/logitech_receiver/settings_templates.py:1370 #: lib/logitech_receiver/settings_templates.py:1374 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:1370 msgid "Zoom with two fingers." msgstr "Přiblížení dvěma prsty." #: lib/logitech_receiver/settings_templates.py:1371 msgid "Pinch to zoom out." msgstr "Sevřít pro přiblížení." #: lib/logitech_receiver/settings_templates.py:1372 msgid "Spread to zoom in." msgstr "Rozevřít pro oddálení." #: lib/logitech_receiver/settings_templates.py:1373 msgid "Zoom with three fingers." msgstr "Přiblížení třemi prsty." #: lib/logitech_receiver/settings_templates.py:1374 msgid "Zoom with two fingers" msgstr "Přiblížení dvěma prsty" #: lib/logitech_receiver/settings_templates.py:1392 msgid "Pixel zone" msgstr "Pixelová zóna" #: lib/logitech_receiver/settings_templates.py:1393 msgid "Ratio zone" msgstr "Poměrová zóna" #: lib/logitech_receiver/settings_templates.py:1394 msgid "Scale factor" msgstr "Faktor škálování" #: lib/logitech_receiver/settings_templates.py:1394 msgid "Sets the cursor speed." msgstr "Nastaví rychlost kurzoru." #: lib/logitech_receiver/settings_templates.py:1398 msgid "Left-most coordinate." msgstr "Souřadnice nejvíce vlevo." #: lib/logitech_receiver/settings_templates.py:1399 msgid "Bottom" msgstr "Dole" #: lib/logitech_receiver/settings_templates.py:1399 msgid "Bottom coordinate." msgstr "Spodní souřadnice." #: lib/logitech_receiver/settings_templates.py:1400 msgid "Width" msgstr "Šířka" #: lib/logitech_receiver/settings_templates.py:1400 msgid "Width." msgstr "Šířka." #: lib/logitech_receiver/settings_templates.py:1401 msgid "Height" msgstr "Výška" #: lib/logitech_receiver/settings_templates.py:1401 msgid "Height." msgstr "Výška." #: lib/logitech_receiver/settings_templates.py:1402 msgid "Cursor speed." msgstr "Rychlost kurzoru." #: lib/logitech_receiver/settings_templates.py:1402 msgid "Scale" msgstr "Měřítko" #: lib/logitech_receiver/settings_templates.py:1408 msgid "Gestures" msgstr "Gesta" #: lib/logitech_receiver/settings_templates.py:1409 msgid "Tweak the mouse/touchpad behaviour." msgstr "Ladí chování myši/touchpadu." #: lib/logitech_receiver/settings_templates.py:1425 msgid "Gestures Diversion" msgstr "Gesto odklonění" #: lib/logitech_receiver/settings_templates.py:1426 msgid "Divert mouse/touchpad gestures." msgstr "Odkloní gesta myši/touchpadu." #: lib/logitech_receiver/settings_templates.py:1442 msgid "Gesture params" msgstr "Parametry gesta" #: lib/logitech_receiver/settings_templates.py:1443 msgid "Change numerical parameters of a mouse/touchpad." msgstr "Změňte číselné parametry myši/touchpadu." #: lib/logitech_receiver/settings_templates.py:1467 msgid "M-Key LEDs" msgstr "M-Key LEDky" #: lib/logitech_receiver/settings_templates.py:1469 msgid "Control the M-Key LEDs." msgstr "Ovládání LEDek M-Key." #: lib/logitech_receiver/settings_templates.py:1473 #: lib/logitech_receiver/settings_templates.py:1504 msgid "May need G Keys diverted to be effective." msgstr "Může vyžadovat odklonění G kláves aby se projevilo." #: lib/logitech_receiver/settings_templates.py:1479 #, python-format msgid "Lights up the %s key." msgstr "Osvětlení %s klávesy." #: lib/logitech_receiver/settings_templates.py:1498 msgid "MR-Key LED" msgstr "MR-Key LED" #: lib/logitech_receiver/settings_templates.py:1500 msgid "Control the MR-Key LED." msgstr "Ovládání MR-Key LEDky." #: lib/logitech_receiver/settings_templates.py:1521 msgid "Persistent Key/Button Mapping" msgstr "Trvalé mapování Klávesy/Tlačítka" #: lib/logitech_receiver/settings_templates.py:1523 msgid "Permanently change the mapping for the key or button." msgstr "Trvale změní mapování klávesy nebo tlačítka." #: lib/logitech_receiver/settings_templates.py:1525 msgid "" "Changing important keys or buttons (such as for the left mouse " "button) can result in an unusable system." msgstr "" "Změna důležité klávesy nebo tlačítka (například levé tlačítko myši) " "může vést k nepoužitelnému systému." #: lib/logitech_receiver/settings_templates.py:1582 msgid "Sidetone" msgstr "Zpětná vazba" #: lib/logitech_receiver/settings_templates.py:1583 msgid "Set sidetone level." msgstr "Nastavení úrovně zpětné vazby." #: lib/logitech_receiver/settings_templates.py:1592 msgid "Equalizer" msgstr "Ekvalizér" #: lib/logitech_receiver/settings_templates.py:1593 #: lib/logitech_receiver/settings_templates.py:1902 msgid "Set equalizer levels." msgstr "Nastavení úrovní ekvalizéru." #: lib/logitech_receiver/settings_templates.py:1615 #: lib/logitech_receiver/settings_templates.py:1930 #: lib/logitech_receiver/settings_templates.py:2056 #: lib/logitech_receiver/settings_templates.py:2058 msgid "Hz" msgstr "Hz" #: lib/logitech_receiver/settings_templates.py:1621 msgid "Power Management" msgstr "Správa napájení" #: lib/logitech_receiver/settings_templates.py:1622 msgid "Power off in minutes (0 for never)." msgstr "Vypnutí po minutách (0 pro nikdy)." #: lib/logitech_receiver/settings_templates.py:1634 msgid "Eco Mode" msgstr "Eko režim" #: lib/logitech_receiver/settings_templates.py:1635 msgid "Battery saver mode." msgstr "Režim šetření baterie." #: lib/logitech_receiver/settings_templates.py:1653 msgid "Do Not Disturb" msgstr "Nerušit" #: lib/logitech_receiver/settings_templates.py:1654 msgid "Suppress notification sounds." msgstr "Potlačit zvuk oznámení." #: lib/logitech_receiver/settings_templates.py:1661 msgid "Mic Mute" msgstr "Ztlumit mikrofon." #: lib/logitech_receiver/settings_templates.py:1662 msgid "Mute the microphone." msgstr "Ztlumení mikrofonu." #: lib/logitech_receiver/settings_templates.py:1681 msgid "Mic SNR" msgstr "SNR mikrofonu" #: lib/logitech_receiver/settings_templates.py:1682 msgid "Microphone signal-to-noise ratio enhancement." msgstr "Rozšíření poměru signál-šum mikrofonu." #: lib/logitech_receiver/settings_templates.py:1689 msgid "AI Noise Reduction" msgstr "AI redukce šumu" #: lib/logitech_receiver/settings_templates.py:1690 msgid "Enable AI noise reduction." msgstr "Povolí AI redukci šumu." #: lib/logitech_receiver/settings_templates.py:1697 msgid "AI Noise Reduction Level" msgstr "Úroveň AI omezení šumu." #: lib/logitech_receiver/settings_templates.py:1698 msgid "AI noise reduction intensity." msgstr "Intenzita AI omezení šumu." #: lib/logitech_receiver/settings_templates.py:1707 msgid "Headset Sidetone" msgstr "Zpětná vazba náhlavní soupravy" #: lib/logitech_receiver/settings_templates.py:1708 msgid "Sidetone level (0 = off, 100 = max)." msgstr "Úroveň zpětné vazby (0 = vypnuto, 100 = max)." #: lib/logitech_receiver/settings_templates.py:1771 msgid "Mic Gain" msgstr "Zesílení mikrofonu" #: lib/logitech_receiver/settings_templates.py:1772 msgid "Microphone gain level." msgstr "Úroveň zesílení mikrofonu." #: lib/logitech_receiver/settings_templates.py:1824 msgid "Audio Mix Balance" msgstr "Vyvážení zvuku" #: lib/logitech_receiver/settings_templates.py:1825 msgid "Balance between game and chat audio." msgstr "Vyvážení zvuku mezi hrou a mluvením." #: lib/logitech_receiver/settings_templates.py:1876 msgid "Auto Sleep Timeout" msgstr "Časový limit pro automatické uspání" #: lib/logitech_receiver/settings_templates.py:1877 msgid "" "Idle time in minutes before the headset enters sleep mode (0 = " "disabled)." msgstr "" "Doba nečinnosti v minutách, po které se sluchátka přepnou do režimu " "spánku (0 = vypnuto)." #: lib/logitech_receiver/settings_templates.py:1901 msgid "Headset Equalizer" msgstr "Ekvalizér sluchátek" #: lib/logitech_receiver/settings_templates.py:1996 msgid "Headset Advanced EQ" msgstr "Pokročilý EQ sluchátek" #: lib/logitech_receiver/settings_templates.py:1997 msgid "Per-band gain for the headset's active parametric EQ." msgstr "" "Zesílení jednotlivých pásem u aktivního parametrického ekvalizéru sluchátek." #: lib/logitech_receiver/settings_templates.py:2268 msgid "EQ Preset" msgstr "Přednastavení ekvalizéru" #: lib/logitech_receiver/settings_templates.py:2269 msgid "Switch the active EQ preset. Factory presets are read-only." msgstr "" "Přepne aktivní přednastavení ekvalizéru. Tovární přednastavení jsou pouze pro " "čtení." #: lib/logitech_receiver/settings_templates.py:2294 msgid "Slot" msgstr "Pozice" #: lib/logitech_receiver/settings_templates.py:2296 msgid "(factory)" msgstr "(tovární)" #: lib/logitech_receiver/settings_templates.py:2429 #: lib/logitech_receiver/settings_templates.py:3206 #: lib/logitech_receiver/settings_templates.py:3298 msgid "LED Control" msgstr "Ovládání LED" #: lib/logitech_receiver/settings_templates.py:2430 msgid "Allow Solaar to control the headset LED zones." msgstr "Povolit Solaaru kontrolovat LED zóny." #: lib/logitech_receiver/settings_templates.py:2478 msgid "Per-zone Lighting" msgstr "Zónové osvětlení" #: lib/logitech_receiver/settings_templates.py:2480 msgid "" "Override individual zone colors. 'No change' inherits the LEDs " "Primary color.\n" "LED Control needs to be set to Solaar to be effective." msgstr "" "Přepsání barev jednotlivých zón. Volba „Bez změny“ převezme primární barvu LED" " diod.\n" "Aby bylo ovládání LED funkční musí být nastaveno na Solaar." #: lib/logitech_receiver/settings_templates.py:2498 msgid "Zone" msgstr "Zóna" #: lib/logitech_receiver/settings_templates.py:2620 #: lib/logitech_receiver/settings_templates.py:2908 #: lib/logitech_receiver/settings_templates.py:3648 msgid "Secondary" msgstr "Sekundární" #: lib/logitech_receiver/settings_templates.py:2621 #: lib/logitech_receiver/settings_templates.py:3234 msgid "Speed" msgstr "Rychlost" #: lib/logitech_receiver/settings_templates.py:2688 msgid "Startup Effect" msgstr "Efekt při zapnutí" #: lib/logitech_receiver/settings_templates.py:2690 msgid "Firmware lighting effect played when the headset powers on or wakes." msgstr "" "Světelný efekt firmwaru, který se spustí při zapnutí nebo probuzení sluchátek." #: lib/logitech_receiver/settings_templates.py:2692 #: lib/logitech_receiver/settings_templates.py:2703 msgid "Device default: Primary #00B8FC, Secondary #FF00AB, Speed 100." msgstr "" "Výchozí v zařízení: Primární #00B8FC, Sekundární #FF00AB, Rychlost 100." #: lib/logitech_receiver/settings_templates.py:2699 msgid "Shutdown Effect" msgstr "Efekt při vypnutí" #: lib/logitech_receiver/settings_templates.py:2701 msgid "" "Firmware lighting effect played when the headset powers off or " "sleeps." msgstr "" "Světelný efekt firmwaru, který se spustí při vypnutí nebo uspání sluchátek." #: lib/logitech_receiver/settings_templates.py:2710 msgid "Passive Effect" msgstr "Efekt při nečinnosti" #: lib/logitech_receiver/settings_templates.py:2712 msgid "Firmware lighting effect played while the headset is idle." msgstr "Světelný efekt firmwaru, který se spustí při neaktivitě sluchátek." #: lib/logitech_receiver/settings_templates.py:2714 msgid "Device default: Primary #00B8FC, Secondary #FF00AB, Speed 75." msgstr "Výchozí v zařízení: Primární #00B8FC, Sekundární #FF00AB, Rychlost 75." #: lib/logitech_receiver/settings_templates.py:2833 msgid "Onboard Effect" msgstr "Vestavěný efekt" #: lib/logitech_receiver/settings_templates.py:2834 msgid "Firmware RGB effect the headset plays on its own." msgstr "RGB efekt firmware, který sluchátka přehrají samy od sebe." #: lib/logitech_receiver/settings_templates.py:2909 #: lib/logitech_receiver/settings_templates.py:3243 msgid "Intensity" msgstr "Intenzita" #: lib/logitech_receiver/settings_templates.py:2910 #: lib/logitech_receiver/settings_templates.py:3245 msgid "Saturation" msgstr "Sytost" #: lib/logitech_receiver/settings_templates.py:2914 #: lib/logitech_receiver/settings_templates.py:3238 msgid "Period" msgstr "Období" #: lib/logitech_receiver/settings_templates.py:2919 #: lib/logitech_receiver/settings_templates.py:3250 msgid "Direction" msgstr "Směr" #: lib/logitech_receiver/settings_templates.py:3134 msgid "Brightness Control" msgstr "Nastavení jasu" #: lib/logitech_receiver/settings_templates.py:3135 msgid "Control overall brightness" msgstr "Nastavení celkového jasu" #: lib/logitech_receiver/settings_templates.py:3207 #: lib/logitech_receiver/settings_templates.py:3299 msgid "Allow Solaar to control LED zones." msgstr "Povolit Sollaru kontrolovat LED zóny." #: lib/logitech_receiver/settings_templates.py:3230 #: lib/logitech_receiver/settings_templates.py:3744 msgid "LED Zone Effects" msgstr "LED zónové efekty" #: lib/logitech_receiver/settings_templates.py:3231 #: lib/logitech_receiver/settings_templates.py:3399 #: lib/logitech_receiver/settings_templates.py:3439 #: lib/logitech_receiver/settings_templates.py:3546 #: lib/logitech_receiver/settings_templates.py:3745 msgid "LED Control needs to be enabled." msgstr "Ovládání LED musí být povoleno." #: lib/logitech_receiver/settings_templates.py:3231 #: lib/logitech_receiver/settings_templates.py:3745 msgid "Set effect for LED Zone" msgstr "Nastavit efekt pro zónu LED" #: lib/logitech_receiver/settings_templates.py:3233 msgid "Color" msgstr "Barva" #: lib/logitech_receiver/settings_templates.py:3244 #, fuzzy msgid "Ramp" msgstr "Rampa" #: lib/logitech_receiver/settings_templates.py:3246 msgid "Waveform" msgstr "Forma vlny" #: lib/logitech_receiver/settings_templates.py:3279 msgid "LEDs" msgstr "LEDky" #: lib/logitech_receiver/settings_templates.py:3398 msgid "Idle Timeout" msgstr "Limit nečinnosti" #: lib/logitech_receiver/settings_templates.py:3399 msgid "Time without input before LED idle effect starts." msgstr "Doba v klidu předtím než se spustí efekt nečinnosti." #: lib/logitech_receiver/settings_templates.py:3435 msgid "Idle Effect" msgstr "Efekt při nečinnosti" #: lib/logitech_receiver/settings_templates.py:3437 msgid "" "What happens to LEDs when idle — dim to a percentage, change the " "base color, or play an animation." msgstr "" "Co se má stít s LED během nečinnosti — ztlumit na procenta, změnit " "základní barvu nebo přehrát animaci." #: lib/logitech_receiver/settings_templates.py:3520 msgid "No change" msgstr "Beze změny" #: lib/logitech_receiver/settings_templates.py:3545 msgid "Sleep Timeout" msgstr "Časový limit pro uspání" #: lib/logitech_receiver/settings_templates.py:3546 msgid "Time without input before LEDs fade off completely." msgstr "Doba v klidu předtím než LEDky kompletně zhasnou." #: lib/logitech_receiver/settings_templates.py:3719 msgid "Startup Animation" msgstr "Úvodní animace" #: lib/logitech_receiver/settings_templates.py:3722 msgid "" "Firmware-played animation when the keyboard wakes from deep sleep or " "powers on.\n" "Setting persists on the device (non-volatile)." msgstr "" "Animace přehrávaná firmwarem při probuzení klávesnice z režimu " "hlubokého spánku nebo při zapnutí.\n" "Nastavení zůstává uloženo v zařízení (trvale)." #: lib/logitech_receiver/settings_templates.py:3726 #: lib/logitech_receiver/settings_templates.py:3737 msgid "Device default: Primary #FF0081, Secondary #80AAFF." msgstr "Výchozí v zařízení: Primární #FF0081, Sekundární #80AAFF." #: lib/logitech_receiver/settings_templates.py:3733 msgid "Shutdown Animation" msgstr "Vypínací animace" #: lib/logitech_receiver/settings_templates.py:3735 msgid "" "Firmware-played animation when the keyboard powers off.\n" "Setting persists on the device (non-volatile)." msgstr "" "Animace spouštěná firmwarem při vypnutí klávesnice.\n" "Nastavení zůstává uloženo v zařízení (trvale)." #: lib/logitech_receiver/settings_templates.py:3847 #: lib/solaar/ui/perkey/dialog.py:87 msgid "Per-key Lighting" msgstr "Osvětlení jednotlivých kláves" #: lib/logitech_receiver/settings_templates.py:3849 msgid "Control per-key lighting." msgstr "Ovládání podsvícení jednotlivých kláves." #: lib/logitech_receiver/settings_templates.py:3851 msgid "" "LED Control needs to be enabled and the zone effect set to Static " "for per-key paint to be visible." msgstr "" "Aby bylo možné zobrazit barvy jednotlivých kláves, je třeba povolit " "ovládání LED a nastavit efekt zóny na „Statický“." #: lib/logitech_receiver/settings_templates.py:4182 msgid "Force Sensing Buttons" msgstr "Tlačítka se snímačem tlaku" #: lib/logitech_receiver/settings_templates.py:4183 msgid "Change the force required to activate button." msgstr "Změňte sílu potřebnou pro aktivaci tlačítka." #: lib/logitech_receiver/settings_templates.py:4200 msgid "Force Sensing Button" msgstr "Tlačítko se snímačem tlaku" #: lib/logitech_receiver/settings_templates.py:4316 msgid "Analog Button Tuning" msgstr "Nastavení analogového tlačítka" #: lib/logitech_receiver/settings_templates.py:4317 msgid "" "Configure analog button settings including actuation point, rapid " "trigger, and haptics." msgstr "" "Nastavte parametry analogových tlačítek, včetně bodu stisknutí, " "rychlého spouštění a haptické zpětné vazby." #: lib/logitech_receiver/settings_templates.py:4347 #, python-format msgid "Actuation point depth (1=shallow, %d=deep)." msgstr "Hloubka bodu aktivace (1=mělký, %d=hluboký)." #: lib/logitech_receiver/settings_templates.py:4355 #, python-format msgid "Rapid trigger sensitivity (1..%d)." msgstr "Citlivost spouště (1..%d)." #: lib/logitech_receiver/settings_templates.py:4363 #, python-format msgid "Click haptic feedback intensity (0=off, %d=max)." msgstr "Intenzita haptické odezvy na kliknutí (0=vypnuto, %d=max)." #: lib/logitech_receiver/settings_templates.py:4371 msgid "Haptic Feedback Level" msgstr "Úroveň haptické odezvy" #: lib/logitech_receiver/settings_templates.py:4372 msgid "Change power of haptic feedback. (Zero to turn off.)" msgstr "Změní sílu haptické odezvy. (Nula pro vypnutí.)" #: lib/logitech_receiver/settings_templates.py:4414 msgid "Play Haptic Waveform" msgstr "Přehrát haptickou odezvu" #: lib/logitech_receiver/settings_templates.py:4415 msgid "Tell device to play a haptic waveform." msgstr "Řekne zařízení aby přehrálo haptickou odezvu." #: lib/solaar/ui/__init__.py:120 msgid "Another Solaar process is already running so just expose its window" msgstr "Další proces Solaar již běží takže pouze zobrazíme jeho okno" #: lib/solaar/ui/about/model.py:36 msgid "" "Manages Logitech receivers,\n" "keyboards, mice, and tablets." msgstr "" "Správa Logitech přijímačů,\n" "klávesnic, myší a tabletů." #: lib/solaar/ui/about/model.py:64 msgid "Additional Programming" msgstr "Dodatečné programování" #: lib/solaar/ui/about/model.py:65 msgid "GUI design" msgstr "Vzhled aplikace" #: lib/solaar/ui/about/model.py:67 msgid "Testing" msgstr "Testování" #: lib/solaar/ui/about/model.py:75 msgid "Logitech documentation" msgstr "Dokumentace Logitechu" #: lib/solaar/ui/action.py:88 lib/solaar/ui/action.py:92 #: lib/solaar/ui/window.py:208 msgid "Unpair" msgstr "Zrušit párování" #: lib/solaar/ui/action.py:91 lib/solaar/ui/diversion_rules.py:142 msgid "Cancel" msgstr "Zrušit" #: lib/solaar/ui/common.py:42 msgid "Permissions error" msgstr "Chyba oprávnění" #: 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 "" "Nalezen přijímač Logitech nebo zařízení (%s), ale chybí oprávnění " "pro jeho otevření." #: lib/solaar/ui/common.py:46 msgid "" "If you've just installed Solaar, try disconnecting the receiver or " "device and then reconnecting it." msgstr "" "Pokud jste právě nainstalovali Solaar, zkuste přijímač nebo zařízení " "odpojit a poté jej znovu připojit." #: lib/solaar/ui/common.py:49 msgid "Cannot connect to device error" msgstr "Chyba nelze se připojit k zařízení" #: 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 "" "Nalezen přijímač Logitech nebo zařízení (%s), ale při pokusu o " "připojení se k němu se vyskytla chyba." #: lib/solaar/ui/common.py:53 msgid "" "Try disconnecting the device and then reconnecting it or turning it " "off and then on." msgstr "" "Zkuste zařízení odpojit a znovu připojit nebo ho vypnout a znovu " "zapnout." #: lib/solaar/ui/common.py:56 msgid "Unpairing failed" msgstr "Párování selhalo" #: lib/solaar/ui/common.py:58 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Selhalo odpojení %{device} od %{receiver}." #: lib/solaar/ui/common.py:63 msgid "The receiver returned an error, with no further details." msgstr "Přijímač vrátil chybu bez dalších detailů." #: lib/solaar/ui/config_panel.py:254 msgid "Complete - ENTER to change" msgstr "Kompletní - ENTER pro změnu" #: lib/solaar/ui/config_panel.py:254 msgid "Incomplete" msgstr "Nekompletní" #: lib/solaar/ui/config_panel.py:500 lib/solaar/ui/config_panel.py:552 #: lib/solaar/ui/config_panel.py:619 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "%d hodnota" msgstr[1] "%d hodnoty" msgstr[2] "%d hodnot" #: lib/solaar/ui/config_panel.py:805 msgid "Changes allowed" msgstr "Změny povoleny" #: lib/solaar/ui/config_panel.py:806 msgid "No changes allowed" msgstr "Změny nejsou povoleny" #: lib/solaar/ui/config_panel.py:807 msgid "Ignore this setting" msgstr "Ignorovat toto nastavení" #: lib/solaar/ui/config_panel.py:1022 msgid "Working" msgstr "Pracuji" #: lib/solaar/ui/config_panel.py:1025 msgid "Read/write operation failed." msgstr "Operace čtení/zápisu selhala." #: lib/solaar/ui/desktop_notifications.py:112 msgid "unspecified reason" msgstr "nespecifikovaný důvod" #: lib/solaar/ui/diversion_rules.py:104 msgid "Built-in rules" msgstr "Zabudovaná pravidla" #: lib/solaar/ui/diversion_rules.py:104 msgid "User-defined rules" msgstr "Uživatelem definovaná pravidla" #: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:1127 msgid "Rule" msgstr "Pravidlo" #: lib/solaar/ui/diversion_rules.py:107 lib/solaar/ui/diversion_rules.py:383 #: lib/solaar/ui/diversion_rules.py:510 msgid "Sub-rule" msgstr "Podpravidlo" #: lib/solaar/ui/diversion_rules.py:109 msgid "[empty]" msgstr "[prázdné]" #: lib/solaar/ui/diversion_rules.py:133 msgid "Make changes permanent?" msgstr "Nastavit změnu jako trvalou?" #: lib/solaar/ui/diversion_rules.py:138 msgid "Yes" msgstr "Ano" #: lib/solaar/ui/diversion_rules.py:140 msgid "No" msgstr "Ne" #: lib/solaar/ui/diversion_rules.py:145 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "Pokud vyberete Ne, budou změny po ukončení aplikace Solaar ztraceny." #: lib/solaar/ui/diversion_rules.py:274 msgid "Paste here" msgstr "Vložit tady" #: lib/solaar/ui/diversion_rules.py:276 msgid "Paste above" msgstr "Vložit nad" #: lib/solaar/ui/diversion_rules.py:278 msgid "Paste below" msgstr "Vložit pod" #: lib/solaar/ui/diversion_rules.py:284 msgid "Paste rule here" msgstr "Vložit pravidlo tady" #: lib/solaar/ui/diversion_rules.py:286 msgid "Paste rule above" msgstr "Vložit pravidlo nad" #: lib/solaar/ui/diversion_rules.py:288 msgid "Paste rule below" msgstr "Vložit pravidlo pod" #: lib/solaar/ui/diversion_rules.py:292 msgid "Paste rule" msgstr "Vložit pravidlo" #: lib/solaar/ui/diversion_rules.py:307 msgid "Insert here" msgstr "Vložit tady" #: lib/solaar/ui/diversion_rules.py:309 msgid "Insert above" msgstr "Vložit nad" #: lib/solaar/ui/diversion_rules.py:311 msgid "Insert below" msgstr "Vložit pod" #: lib/solaar/ui/diversion_rules.py:317 msgid "Insert new rule here" msgstr "Vložit nové pravidlo tady" #: lib/solaar/ui/diversion_rules.py:319 msgid "Insert new rule above" msgstr "Vložit nové pravidlo nad" #: lib/solaar/ui/diversion_rules.py:321 msgid "Insert new rule below" msgstr "Vložit nové pravidlo pod" #: lib/solaar/ui/diversion_rules.py:348 msgid "Flatten" msgstr "Srovnání" #: lib/solaar/ui/diversion_rules.py:381 msgid "Insert" msgstr "Vložit" #: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:512 #: lib/solaar/ui/diversion_rules.py:1159 msgid "Or" msgstr "Nebo" #: lib/solaar/ui/diversion_rules.py:385 lib/solaar/ui/diversion_rules.py:511 #: lib/solaar/ui/diversion_rules.py:1145 msgid "And" msgstr "A" #: lib/solaar/ui/diversion_rules.py:387 msgid "Condition" msgstr "Podmínka" #: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:154 msgid "Feature" msgstr "Vlastnost" #: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:188 msgid "Report" msgstr "Hlášení" #: lib/solaar/ui/diversion_rules.py:391 lib/solaar/ui/rule_conditions.py:69 msgid "Process" msgstr "Proces" #: lib/solaar/ui/diversion_rules.py:392 msgid "Mouse process" msgstr "Proces myši" #: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:225 msgid "Modifiers" msgstr "Modifikátory" #: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:277 msgid "Key" msgstr "Klávesa" #: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/rule_conditions.py:318 msgid "KeyIsDown" msgstr "Klávesa zmáčknuta" #: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1433 msgid "Active" msgstr "Aktivní" #: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1391 #: lib/solaar/ui/diversion_rules.py:1442 lib/solaar/ui/diversion_rules.py:1488 msgid "Device" msgstr "Zařízení" #: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1468 msgid "Host" msgstr "Hostitel" #: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/diversion_rules.py:1507 msgid "Setting" msgstr "Nastavení" #: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:333 #: lib/solaar/ui/rule_conditions.py:382 msgid "Test" msgstr "Test" #: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:507 msgid "Test bytes" msgstr "Test bajty" #: lib/solaar/ui/diversion_rules.py:402 lib/solaar/ui/rule_conditions.py:608 msgid "Mouse Gesture" msgstr "Gesto myši" #: lib/solaar/ui/diversion_rules.py:406 msgid "Action" msgstr "Akce" #: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:138 msgid "Key press" msgstr "Stisknutí klávesy" #: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:191 msgid "Mouse scroll" msgstr "Otočení kolečkem" #: lib/solaar/ui/diversion_rules.py:410 lib/solaar/ui/rule_actions.py:256 msgid "Mouse click" msgstr "Kliknutí myši" #: lib/solaar/ui/diversion_rules.py:411 msgid "Set" msgstr "Nastavit" #: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/rule_actions.py:328 msgid "Execute" msgstr "Vykonat" #: lib/solaar/ui/diversion_rules.py:413 lib/solaar/ui/diversion_rules.py:1190 msgid "Later" msgstr "Zpoždění" #: lib/solaar/ui/diversion_rules.py:442 msgid "Insert new rule" msgstr "Vložit nové pravidlo" #: lib/solaar/ui/diversion_rules.py:462 lib/solaar/ui/rule_actions.py:82 #: lib/solaar/ui/rule_actions.py:287 lib/solaar/ui/rule_conditions.py:556 msgid "Delete" msgstr "Smazat" #: lib/solaar/ui/diversion_rules.py:484 msgid "Negate" msgstr "Negovat" #: lib/solaar/ui/diversion_rules.py:508 msgid "Wrap with" msgstr "Obalit s" #: lib/solaar/ui/diversion_rules.py:538 msgid "Cut" msgstr "Vyjmout" #: lib/solaar/ui/diversion_rules.py:554 msgid "Paste" msgstr "Vložit" #: lib/solaar/ui/diversion_rules.py:560 msgid "Copy" msgstr "Kopírovat" #: lib/solaar/ui/diversion_rules.py:569 msgid "Solaar Rule Editor" msgstr "Editor pravidel Solaar" #: lib/solaar/ui/diversion_rules.py:669 msgid "Save changes" msgstr "Uložit změny" #: lib/solaar/ui/diversion_rules.py:674 msgid "Discard changes" msgstr "Zahodit změny" #: lib/solaar/ui/diversion_rules.py:1105 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:1170 msgid "" "Number of seconds to delay. Delay between 0 and 1 is done with " "higher precision." msgstr "" "Počet sekund pro zpoždění. Zpoždění mezi 0 a 1 bude s větší " "přesností." #: lib/solaar/ui/diversion_rules.py:1208 msgid "Not" msgstr "Opak" #: lib/solaar/ui/diversion_rules.py:1239 msgid "Toggle" msgstr "Přepnout" #: lib/solaar/ui/diversion_rules.py:1240 msgid "True" msgstr "Pravda" #: lib/solaar/ui/diversion_rules.py:1241 msgid "False" msgstr "Nepravda" #: lib/solaar/ui/diversion_rules.py:1254 msgid "Unsupported setting" msgstr "Nepodporované nastavení" #: lib/solaar/ui/diversion_rules.py:1397 lib/solaar/ui/diversion_rules.py:1416 #: lib/solaar/ui/diversion_rules.py:1494 lib/solaar/ui/diversion_rules.py:1758 #: lib/solaar/ui/diversion_rules.py:1776 msgid "Originating device" msgstr "Výchozí zařízení" #: lib/solaar/ui/diversion_rules.py:1429 msgid "Device is active and its settings can be changed." msgstr "Zařízení je aktivní a jeho nastavení může být změněno." #: lib/solaar/ui/diversion_rules.py:1438 msgid "Device that originated the current notification." msgstr "Zařízení které odeslalo aktuální oznámení. " #: lib/solaar/ui/diversion_rules.py:1451 msgid "Name of host computer." msgstr "Jméno hostitelského počítače." #: lib/solaar/ui/diversion_rules.py:1521 msgid "Value" msgstr "Hodnota" #: lib/solaar/ui/diversion_rules.py:1530 msgid "Item" msgstr "Položka" #: lib/solaar/ui/diversion_rules.py:1818 msgid "Change setting on device" msgstr "Změní nastavení na zařízení" #: lib/solaar/ui/diversion_rules.py:1834 msgid "Setting on device" msgstr "Nastavení na zařízení" #: 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: párování nového zařízení" #: lib/solaar/ui/pair_window.py:46 msgid "Bolt receivers are only compatible with Bolt devices." msgstr "Přijímače Bolt jsou kompatibilní pouze s Bolt zařízeními." #: lib/solaar/ui/pair_window.py:48 msgid "" "Press a pairing button or key until the pairing light flashes " "quickly." msgstr "" "Držte párovací tlačítko nebo klávesu dokud nezačne indikátor " "párování rychle blikat." #: lib/solaar/ui/pair_window.py:51 msgid "Unifying receivers are only compatible with Unifying devices." msgstr "Přijímače Unifying jsou kompatibilní pouze s Unifying zařízeními." #: lib/solaar/ui/pair_window.py:53 msgid "Other receivers are only compatible with a few devices." msgstr "Další přijímače jsou kompatibilní pouze s několika zařízeními." #: lib/solaar/ui/pair_window.py:55 msgid "For most devices, turn on the device you want to pair." msgstr "Pro většinu zařízení, zapněte zařízení, které chcete spárovat." #: lib/solaar/ui/pair_window.py:56 msgid "If the device is already turned on, turn it off and on again." msgstr "Pokud je zařízení již zapnuto, vypněte jej a znova zapněte." #: lib/solaar/ui/pair_window.py:58 msgid "The device must not be paired with a nearby powered-on receiver." msgstr "Zařízení nesmí být spárováno s blízkým zapnutým přijímačem." #: 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 "" "Pro zařízení s více kanály, stiskněte, podržte a pusťte tlačítko " "kanálu, který chcete napárovat\n" "nebo použijte tlačítko na přepínaní kanálů, vyberte kanál a pak " "stiskněte, podržte a pusťte tlačítko přepínání kanálu." #: lib/solaar/ui/pair_window.py:68 msgid "The channel indicator light should be blinking rapidly." msgstr "Indikátor kanálu by měl rychle blikat." #: 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" "K přijímači lze napárovat ještě %d zařízení." msgstr[1] "" "\n" "\n" "K přijímači lze napárovat ještě %d zařízení." msgstr[2] "" "\n" "\n" "K přijímači lze napárovat ještě %d zařízení." #: lib/solaar/ui/pair_window.py:78 msgid "" "\n" "Cancelling at this point will not use up a pairing." msgstr "" "\n" "Zrušení v tomto okamžiku nezvýší počet napárovaných." #: lib/solaar/ui/pair_window.py:168 #, python-format msgid "Enter passcode on %(name)s." msgstr "Vložte kód do %(name)s." #: lib/solaar/ui/pair_window.py:171 #, python-format msgid "Type %(passcode)s and then press the enter key." msgstr "Vložte %(passcode)s a pak stiskněte klávesu enter." #: lib/solaar/ui/pair_window.py:176 msgid "left" msgstr "vlevo" #: lib/solaar/ui/pair_window.py:176 msgid "right" msgstr "vpravo" #: lib/solaar/ui/pair_window.py:178 #, python-format msgid "" "Press %(code)s\n" "and then press left and right buttons simultaneously." msgstr "" "Stiskněte %(code)s\n" "a pak zároveň stiskněte levé a pravé tlačítko." #: lib/solaar/ui/pair_window.py:221 msgid "The wireless link is not encrypted" msgstr "Bezdrátové spojení není šifrováno" #: lib/solaar/ui/pair_window.py:226 msgid "Found a new device:" msgstr "Nalezeno nové zařízení:" #: lib/solaar/ui/pair_window.py:247 msgid "Pairing failed" msgstr "Připojení selhalo." #: lib/solaar/ui/pair_window.py:249 msgid "" "Make sure your device is within range, and has a decent battery " "charge." msgstr "Ujistěte se, že je zařízení v dosahu a je dostatečně nabito." #: lib/solaar/ui/pair_window.py:251 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:253 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:255 msgid "No further details are available about the error." msgstr "Další detaily o chybě nejsou dostupné." #: lib/solaar/ui/perkey/control.py:178 msgid "(not loaded)" msgstr "(nenačteno)" #: lib/solaar/ui/perkey/control.py:182 msgid "Open editor…" msgstr "Otevřít editor…" #: lib/solaar/ui/perkey/control.py:183 msgid "Paint key colors on a keyboard layout" msgstr "Vyznačení hlavních barev na rozložení klávesnice" #: lib/solaar/ui/perkey/control.py:229 msgid "(no zones)" msgstr "(bez zón)" #: lib/solaar/ui/perkey/control.py:233 #, python-brace-format msgid "{painted} / {total} keys painted" msgstr "{painted} / {total} kláves zbarveno" #: lib/solaar/ui/perkey/editor.py:54 msgid "Brush" msgstr "Štětec" #: lib/solaar/ui/perkey/editor.py:54 msgid "Click or drag to paint individual keys" msgstr "Kliknout a táhnout pro obarvení jednotlivých kláves" #: lib/solaar/ui/perkey/editor.py:55 msgid "Drag to select a rectangle of keys, painted on release" msgstr "Táhněte pro výběr obdélníku kláves, budou nabarveny po uvolnění" #: lib/solaar/ui/perkey/editor.py:55 msgid "Rect" msgstr "Obdélník" #: lib/solaar/ui/perkey/editor.py:56 msgid "Fill" msgstr "Výplň" #: lib/solaar/ui/perkey/editor.py:56 msgid "Flood-fill connected keys of the same color with the active color" msgstr "Vyplnit aktivní barvou klávesy propojené stejnou barvou" #: lib/solaar/ui/perkey/editor.py:59 msgid "Drag to fade from previous color to active color" msgstr "Táhněte pro plynulou změnu z předchozí na aktivní barvu" #: lib/solaar/ui/perkey/palette.py:90 msgid "Active color" msgstr "Aktivní barva" #: lib/solaar/ui/perkey/palette.py:95 msgid "Paint as 'no change' — clears the cell to the zone base color" msgstr "" "Překreslení výběrem „beze změny“ — nastaví buňce základní barvu " "oblasti" #: lib/solaar/ui/perkey/palette.py:96 msgid "Unset" msgstr "Nenastaveno" #: 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 "" "Simuluje kliknutí nebo zmáčknutí nebo uvolnění propojené klávesy.\n" "Na Waylandu vyžaduje právo zápisu do /dev/uinput." #: lib/solaar/ui/rule_actions.py:60 msgid "Add key" msgstr "Přidat klávesu" #: lib/solaar/ui/rule_actions.py:63 msgid "Click" msgstr "Kliknutí" #: lib/solaar/ui/rule_actions.py:66 msgid "Depress" msgstr "Stisknout" #: lib/solaar/ui/rule_actions.py:69 msgid "Release" msgstr "Uvolnit" #: lib/solaar/ui/rule_actions.py:153 msgid "" "Simulate a mouse scroll.\n" "On Wayland requires write access to /dev/uinput." msgstr "" "Simulace posunu kolečka myši.\n" "Na Waylandu vyžaduje právo zápisu do /dev/uinput." #: lib/solaar/ui/rule_actions.py:211 msgid "" "Simulate a mouse click.\n" "On Wayland requires write access to /dev/uinput." msgstr "" "Simulace kliknutí tlačítkem myši.\n" "Na Waylandu vyžaduje právo zápisu do /dev/uinput." #: lib/solaar/ui/rule_actions.py:216 msgid "Button" msgstr "Tlačítko" #: lib/solaar/ui/rule_actions.py:218 msgid "Action (and Count, if click)" msgstr "Akce (a počet, pokud bylo kliknuto)" #: lib/solaar/ui/rule_actions.py:269 msgid "Execute a command with arguments." msgstr "Vykonat příkaz s parametry." #: lib/solaar/ui/rule_actions.py:273 msgid "Add argument" msgstr "Přidat parametr" #: lib/solaar/ui/rule_conditions.py:52 msgid "X11 active process. For use in X11 only." msgstr "X11 aktivní proces. Pouze pro X11." #: lib/solaar/ui/rule_conditions.py:82 msgid "X11 mouse process. For use in X11 only." msgstr "X11 proces myši. Pouze pro X11." #: lib/solaar/ui/rule_conditions.py:99 msgid "MouseProcess" msgstr "ProcesMyši" #: lib/solaar/ui/rule_conditions.py:123 msgid "Feature name of notification triggering rule processing." msgstr "Název funkce, která spouští zpracování pravidla pro oznámení." #: lib/solaar/ui/rule_conditions.py:169 msgid "Report number of notification triggering rule processing." msgstr "Počet oznámení, která vedla ke spuštění zpracování pravidla." #: lib/solaar/ui/rule_conditions.py:201 msgid "Active keyboard modifiers. Not always available in Wayland." msgstr "Aktivní klávesové modifikátory. Ne vždy dostupné ve Waylandu." #: 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 "" "Odkloněná klávesa nebo tlačítko stištěné či uvolněné.\n" "Použijte nastavení odklonění Klávesy/Tlačítka a G kláves pro " "odklonění kláves a tlačítek." #: lib/solaar/ui/rule_conditions.py:250 msgid "Key down" msgstr "Klávesa dolů" #: lib/solaar/ui/rule_conditions.py:253 msgid "Key up" msgstr "Klávesa nahoru" #: 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 "" "Odkloněná klávesa nebo tlačítko je momentálně dole.\n" "Použijte nastavení odklonění Klávesy/Tlačítka a G kláves pro " "odklonění kláves a tlačítek." #: lib/solaar/ui/rule_conditions.py:331 msgid "Test condition on notification triggering rule processing." msgstr "Testovat podmínky oznámení spouštějící zpracování pravidel." #: lib/solaar/ui/rule_conditions.py:335 msgid "Parameter" msgstr "Parametr" #: lib/solaar/ui/rule_conditions.py:408 msgid "begin (inclusive)" msgstr "začátek (včetně)" #: lib/solaar/ui/rule_conditions.py:409 msgid "end (exclusive)" msgstr "konec (bez)" #: lib/solaar/ui/rule_conditions.py:417 msgid "range" msgstr "rozsah" #: 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 "bajty %(0)d do %(1)d, v rozmezí 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 "" "Bitový nebo rozsahový test na bajtech v oznamovací zprávě, která " "spouští zpracování pravidla." #: 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 "" "Gesto myší s volitelným spouštěcím tlačítkem, po kterém následuje " "žádný nebo více pohybů myší." #: lib/solaar/ui/rule_conditions.py:541 msgid "Add movement" msgstr "Přidat pohyb" #: lib/solaar/ui/tray.py:55 msgid "No supported device found" msgstr "Nenalezeno podporované zařízení" #: lib/solaar/ui/tray.py:60 lib/solaar/ui/window.py:328 #, python-format msgid "About %s" msgstr "O aplikaci %s" #: lib/solaar/ui/tray.py:61 lib/solaar/ui/window.py:326 #, python-format msgid "Quit %s" msgstr "Ukončit %s" #: lib/solaar/ui/tray.py:273 lib/solaar/ui/tray.py:281 msgid "no receiver" msgstr "bez přijímače" #: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:299 #: lib/solaar/ui/window.py:734 msgid "offline" msgstr "vypnuto" #: lib/solaar/ui/tray.py:297 msgid "no status" msgstr "bez statusu" #: lib/solaar/ui/window.py:110 msgid "Scanning" msgstr "Skenování" #: lib/solaar/ui/window.py:141 msgid "Battery" msgstr "Baterie" #: lib/solaar/ui/window.py:144 msgid "Wireless Link" msgstr "Bezdrátové propojení" #: lib/solaar/ui/window.py:148 msgid "Lighting" msgstr "Osvětlení" #: lib/solaar/ui/window.py:182 msgid "Show Technical Details" msgstr "Ukázat technické detaily" #: lib/solaar/ui/window.py:198 msgid "Pair new device" msgstr "Připojit nové zařízení" #: lib/solaar/ui/window.py:216 msgid "Select a device" msgstr "Vyberte zařízení" #: lib/solaar/ui/window.py:331 msgid "Rule Editor" msgstr "Editor pravidel" #: lib/solaar/ui/window.py:522 msgid "Path" msgstr "Cesta" #: 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:550 lib/solaar/ui/window.py:552 msgid "Serial" msgstr "Sériové číslo" #: lib/solaar/ui/window.py:533 msgid "Index" msgstr "Index" #: lib/solaar/ui/window.py:535 msgid "Wireless PID" msgstr "Bezdrát. PID" #: lib/solaar/ui/window.py:537 msgid "Product ID" msgstr "ID výrobku" #: lib/solaar/ui/window.py:541 lib/solaar/ui/window.py:543 #: lib/solaar/ui/window.py:545 msgid "Protocol" msgstr "Protokol" #: lib/solaar/ui/window.py:545 msgid "Unknown" msgstr "Neznámý" #: lib/solaar/ui/window.py:547 msgid "Polling rate" msgstr "Četn. dotazů" #: lib/solaar/ui/window.py:554 msgid "Unit ID" msgstr "Číslo jednotky" #: lib/solaar/ui/window.py:565 msgid "none" msgstr "žádný" #: lib/solaar/ui/window.py:566 msgid "Notifications" msgstr "Oznámení" #: lib/solaar/ui/window.py:610 msgid "No device paired." msgstr "Žádné připojené zařízení." #: lib/solaar/ui/window.py:619 #, 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 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:626 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:630 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." 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:687 msgid "Battery Voltage" msgstr "Napětí baterie" #: lib/solaar/ui/window.py:689 msgid "Voltage reported by battery" msgstr "Napětí hlášené baterií" #: lib/solaar/ui/window.py:691 msgid "Battery Level" msgstr "Úroveň baterie" #: lib/solaar/ui/window.py:693 msgid "Approximate level reported by battery" msgstr "Přibližná úroveň hlášená baterií" #: lib/solaar/ui/window.py:700 lib/solaar/ui/window.py:702 msgid "next reported " msgstr "další hlášené " #: lib/solaar/ui/window.py:703 msgid " and next level to be reported." msgstr " a další úroveň pro hlášení." #: lib/solaar/ui/window.py:708 msgid "last known" msgstr "poslední známý" #: lib/solaar/ui/window.py:719 msgid "encrypted" msgstr "šifrováno" #: lib/solaar/ui/window.py:721 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:723 msgid "not encrypted" msgstr "nešifrováno" #: lib/solaar/ui/window.py:727 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é spojení mezi zařízením a jeho přijímačem není šifrováno.\n" "To je bezpečnostní problém pro polohovací zařízení a významný " "bezpečnostní problém pro zařízení vkládající text." #: lib/solaar/ui/window.py:743 #, 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)" #~ msgid "Adjust the DPI by sliding the mouse horizontally while " #~ "holding the DPI button." #~ 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." #, python-format #~ msgid "Battery: %(level)s" #~ msgstr "Baterie: %(level)s" #, python-format #~ msgid "Battery: %(percent)d%%" #~ msgstr "Baterie: %(percent)d%%" #~ 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 "CycleS" #~ msgstr "CyklyS" #~ msgid "DPI Sliding Adjustment" #~ msgstr "Úprava DPI klouzání" #~ msgid "Divert G Keys" #~ msgstr "Odklonění G kláves" #~ 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 "Enable onboard profiles, which often control report rate and " #~ "keyboard lighting" #~ msgstr "Povolí vestavěné profily které často kontrolují frekvenci " #~ "odesílání a podsvícení klávesnice" #~ 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 "Frequency of device polling, in milliseconds" #~ msgstr "Frekvence snímání zařízení v milisekundách" #~ 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 "LED Control needs to be set to Solaar to be effective." #~ msgstr "Aby bylo ovládání LED funkční musí být nastaveno na Solaar." #, python-format #~ msgid "Lighting: %(level)s lux" #~ msgstr "Osvětlení: %(level)s lux" #~ msgid "Make G keys send GKEY HID++ notifications (which trigger " #~ "Solaar rules but are otherwise ignored)." #~ msgstr "Nastaví G klávesy aby posílaly GKEY HID++ oznámení (která " #~ "spustí Solaar pravidla, ale jinak je ignorováno)." #~ 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 "May also make M keys and MR key send HID++ notifications" #~ msgstr "Také může nastavit M a MR klávesy, aby zasílaly HID++ " #~ "oznámení" #~ msgid "No Logitech receiver found" #~ msgstr "Logitech přijímač nebyl nalezen" #~ msgid "Polling Rate (ms)" #~ msgstr "Frekvence snímání (ms)" #~ 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" #~ "přes bezdrátový přijímač Logitech." #~ msgid "Solaar depends on a udev file that is not present" #~ msgstr "Solaar závisí na udev souboru, který chybí" #~ msgid "Switch control of LED zones between device and Solaar" #~ msgstr "Přepínání ovládání zón LED mezi zařízením a Solaar" #~ 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 "Turn illumination on or off on keyboard." #~ msgstr "Vypne/zapne podsvícení klávesnice." #~ msgid "Turn on the device you want to pair." #~ msgstr "Zapněte zařízení které chcete připojit." #~ msgid "height" #~ msgstr "výška" #~ msgid "top" #~ msgstr "nahoře" #~ msgid "unknown" #~ msgstr "neznámý" #~ msgid "width" #~ msgstr "šířka" Solaar-1.1.20/po/da.po000066400000000000000000001434571522022367600144010ustar00rootroot00000000000000# Danish translations for solaar package. # Copyright (C) 2020 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # Automatically generated, 2020. # msgid "" msgstr "Project-Id-Version: solaar 1.0.3\n" "Report-Msgid-Bugs-To: \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" "Language: da\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" #: 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: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:124 msgid "Lightspeed Receiver" msgstr "" #: lib/logitech_receiver/base_usb.py:133 msgid "EX100 Receiver 27 Mhz" msgstr "" #: lib/logitech_receiver/i18n.py:30 msgid "empty" msgstr "tom" #: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "kritisk" #: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "lav" #: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "" #: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "god" #: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "fuld" #: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "afladning" #: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "genopladning" #: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "opladning" #: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "" #: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "næsten fuld" #: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "opladt" #: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "langsom opladning" #: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "batterifejl" #: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "termisk fejl" #: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "" #: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "" #: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "" #: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "" #: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "enheden svarede ikke" #: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "enhed understøttes ikke" #: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "for mange enheder" #: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "sekvens timeout" #: 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 "Opstartsindlæser" #: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardware" #: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Andet" #: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "" #: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "" #: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "" #: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "" #: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "" #: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "" #: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "Smart skift" #: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "" #: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "" #: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "" #: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "" #: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "" #: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "" #: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "" #: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "" #: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "" #: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "" #: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "" #: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "" #: lib/logitech_receiver/i18n.py:86 msgid "released" msgstr "" #: 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:75 #: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is open" msgstr "parlåsen er åben" #: 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:224 msgid "disconnected" msgstr "" #: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "uparret" #: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "tændt" #: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "" #: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 msgid "feature" msgstr "" #: 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: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: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 "Højfølsom tilstand til lodret rulling med hjulet." #: lib/logitech_receiver/settings_templates.py:165 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 "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: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 "Bagbelysning" #: 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: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:465 msgid "Change the action for the key or button." msgstr "Skift handlingen for tasten eller knappen." #: 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: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 "Følsomhed (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 "Deaktiver taster" #: lib/logitech_receiver/settings_templates.py:792 msgid "Disable specific keyboard keys." msgstr "Deaktiver specifikke tastaturtaster." #: 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 "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: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 "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: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:170 #, python-format msgid "Battery: %(level)s" msgstr "Batteri: %(level)s" #: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "Batteri: %(percent)d%%" #: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "Belysning: %(level)s lux" #: 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:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Batteri: %(percent)d%% (%(status)s)" #: lib/solaar/ui/__init__.py:52 msgid "Permissions error" msgstr "Tilladelsesfejl" #: 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 "" #: 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 "" #: 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 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 "Frakobling mislykkedes" #: lib/solaar/ui/__init__.py:66 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Mislykkedes med at bryde paret %{device} og %{receiver}." #: lib/solaar/ui/__init__.py:67 msgid "The receiver returned an error, with no further details." msgstr "Modtageren returnerede en fejl uden yderligere detaljer." #: 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 "GUI-design" #: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "Testing" #: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Logitech-dokumentation" #: 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:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "" #: lib/solaar/ui/config_panel.py:212 msgid "Complete - ENTER to change" msgstr "" #: lib/solaar/ui/config_panel.py:212 msgid "Incomplete" msgstr "" #: 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/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:65 msgid "User-defined rules" msgstr "" #: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "" #: 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:70 msgid "[empty]" msgstr "" #: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "" #: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "" #: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Ja" #: lib/solaar/ui/diversion_rules.py:148 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: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 msgid "Paste here" msgstr "" #: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "" #: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "" #: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "" #: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "" #: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "" #: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "" #: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "" #: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "" #: 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:511 lib/solaar/ui/diversion_rules.py:637 #: lib/solaar/ui/diversion_rules.py:1111 msgid "And" msgstr "" #: lib/solaar/ui/diversion_rules.py:513 msgid "Condition" msgstr "" #: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "" #: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "" #: 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:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "" #: 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: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:532 msgid "Action" msgstr "Handling" #: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 msgid "Key press" msgstr "" #: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "" #: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 msgid "Mouse click" msgstr "" #: 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: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: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:610 msgid "Negate" msgstr "" #: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "" #: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Klip" #: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Indsæt" #: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Kopiér" #: 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:1138 msgid "Number of seconds to delay." msgstr "" #: lib/solaar/ui/diversion_rules.py:1177 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 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: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: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: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:194 msgid "More paired devices than receiver can support." msgstr "Flere parrede enheder end modtageren kan understøtte." #: 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:210 msgid "Found a new device:" msgstr "Fandt en ny enhed:" #: 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: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 "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: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" "Denne modtager har %d parring tilbage." msgstr[1] "\n" "\n" "Denne modtager har %d parringer tilbage." #: 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/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 "Om %s" #: 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:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "ingen modtager" #: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "ingen status" #: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "Søger" #: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Batteri" #: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Trådløs tilslutning" #: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Belysning" #: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Vis tekniske detaljer" #: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Parre ny enhed" #: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Vælg en enhed" #: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "" #: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Sti" #: lib/solaar/ui/window.py:536 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 msgid "Serial" msgstr "Seriel" #: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Indeks" #: lib/solaar/ui/window.py:547 msgid "Wireless PID" msgstr "Trådløs-PID" #: lib/solaar/ui/window.py:549 msgid "Product ID" msgstr "Produkt-id" #: lib/solaar/ui/window.py:551 msgid "Protocol" msgstr "Protokol" #: lib/solaar/ui/window.py:551 msgid "Unknown" msgstr "Ukendt" #: 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 "Spørgerate" #: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "" #: lib/solaar/ui/window.py:576 msgid "none" msgstr "ingen" #: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Varsler" #: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "Ingen enhed parret." #: 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: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: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:692 msgid "Battery Voltage" msgstr "" #: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "" #: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "" #: lib/solaar/ui/window.py:698 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:708 msgid " and next level to be reported." msgstr "" #: lib/solaar/ui/window.py:713 msgid "last known" msgstr "sidst kendt" #: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "kryptert" #: 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: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" #~ msgid "\n" #~ "\n" #~ "This receiver has %d pairing(s) remaining." #~ msgstr "\n" #~ "\n" #~ "Denne modtager har %d parring(er) tilbage." #~ msgid "Actions" #~ msgstr "Handlinger" #~ 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 "Skift automatisk musehjulet mellem skralde og freespin-" #~ "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" #~ msgid "High Resolution Wheel Invert" #~ msgstr "Inverter høyfølsom rulling" #~ msgid "High-sensitivity wheel invert direction for vertical scroll." #~ msgstr "Inverter retning til høyfølsom lodret rulling med hjulet." #~ 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" #~ "via trådløse Logitech-modtagere." #~ msgid "Smooth Scrolling" #~ msgstr "Glat rulling" #~ msgid "Solaar depends on a udev file that is not present" #~ msgstr "Solaar er afhængig af en udev-fil, der ikke er til stede" #~ msgid "The receiver only supports %d paired device(s)." #~ msgstr "Modtageren understøtter kun %d parrede enhed(er)." #~ 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." #~ msgid "Thumb Scroll Invert" #~ msgstr "Inverter tommelrulling" #~ msgid "USB id" #~ msgstr "USB-id" #~ msgid "Unexpected device number (%s) in notification %s." #~ msgstr "Uventet enhedsnummer (%s) i varsel %s." #~ msgid "Wheel Resolution" #~ msgstr "Hjulopløsning" #~ msgid "height" #~ msgstr "højde" #~ msgid "next " #~ msgstr "næste " #~ msgid "unknown" #~ msgstr "ukendt" #~ msgid "width" #~ msgstr "bredde" Solaar-1.1.20/po/de.po000066400000000000000000001700121522022367600143700ustar00rootroot00000000000000# German translations for solaar package. # Copyright (C) 2014 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # Automatically generated, 2014. # msgid "" msgstr "Project-Id-Version: solaar 1.0.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-05-11 19:38+0200\n" "PO-Revision-Date: 2026-05-11 19:35+0200\n" "Last-Translator: Daniel Frost \n" "Language-Team: none\n" "Language: de\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-Launchpad-Export-Date: 2021-04-17 16:52+0000\n" "X-Generator: Poedit 3.4.2\n" #: lib/logitech_receiver/base_usb.py:46 msgid "Bolt Receiver" msgstr "Bolt Empfänger" #: lib/logitech_receiver/base_usb.py:57 msgid "Unifying Receiver" msgstr "Unifying Empfänger" #: 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 Empfänger" #: lib/logitech_receiver/base_usb.py:124 msgid "Lightspeed Receiver" msgstr "Lightspeed Empfänger" #: lib/logitech_receiver/base_usb.py:133 msgid "EX100 Receiver 27 Mhz" msgstr "EX100 Empfänger 27 Mhz" #: lib/logitech_receiver/i18n.py:30 msgid "empty" msgstr "entladen" #: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "kritisch" #: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "niedrig" #: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "mittel" #: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "gut" #: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "voll" #: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "entlädt" #: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "lädt wieder auf" #: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "lädt" #: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "lädt nicht" #: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "nahezu voll" #: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "geladen" #: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "langsames Aufladen" #: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "unzulässiger/defekter Akku" #: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "thermischer Fehler" #: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "Fehler" #: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "standard" #: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "schnell" #: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "langsam" #: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "Geräte Zeitüberschreitung" #: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "nicht unterstütztes Gerät" #: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "zu viele Geräte" #: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "Zeitüberschreitung im Ablauf" #: 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 "Bootloader" #: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardware" #: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Andere" #: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "Linke Taste" #: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "Rechte Taste" #: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "Mittlere Taste" #: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "Zurück Taste" #: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "Vorwärts Taste" #: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "Gestentaste" #: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "Smart Shift" #: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "DPI Schalter" #: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "Links kippen" #: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "Rechts kippen" #: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "Linksklick" #: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "Rechtsklick" #: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "Maus mittlere Taste" #: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "Maus \"Zurück\" Taste" #: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "Maus \"Vorwärts\"\n" " Taste" #: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "Gestentaste-Navigation" #: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "Maus \"Links scrollen\" Taste" #: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "Maus \"Rechts scrollen\" Taste" #: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "gedrückt" #: lib/logitech_receiver/i18n.py:86 msgid "released" msgstr "losgelassen" #: lib/logitech_receiver/notifications.py:75 #: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is closed" msgstr "Koppelsperre ist inaktiv" #: lib/logitech_receiver/notifications.py:75 #: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is open" msgstr "Koppelsperre ist aktiv" #: lib/logitech_receiver/notifications.py:92 msgid "discovery lock is closed" msgstr "Entdeckungssperre ist inaktiv" #: lib/logitech_receiver/notifications.py:92 msgid "discovery lock is open" msgstr "Entdeckungssperre ist aktiv" #: lib/logitech_receiver/notifications.py:224 lib/solaar/ui/notify.py:122 msgid "connected" msgstr "verbunden" #: lib/logitech_receiver/notifications.py:224 msgid "disconnected" msgstr "nicht verbunden" #: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "nicht gekoppelt" #: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "eingeschaltet" #: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "registrieren" #: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 msgid "feature" msgstr "Feature" #: lib/logitech_receiver/settings_templates.py:139 msgid "Swap Fx function" msgstr "Fn-Tastenbelegung umkehren" #: 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 "Wenn aktiviert, dann behalten die Tasten F1-F12 ihre " "Sonderbelegung.\n" "Die jeweilige Standardbelegung kann über die FN-Taste aktiviert " "werden." #: 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 "Wenn nicht aktiviert, dann behalten die Tasten F1-F12 ihre " "Standardbelegung.\n" "Die jeweilige Sonderbelegung kann über die FN-Taste aktiviert werden." #: lib/logitech_receiver/settings_templates.py:149 msgid "Hand Detection" msgstr "Handerkennung" #: lib/logitech_receiver/settings_templates.py:150 msgid "Turn on illumination when the hands hover over the keyboard." msgstr "Schaltet die Beleuchtung ein, wenn sich die Hand über der Tastatur " "befindet." #: lib/logitech_receiver/settings_templates.py:157 msgid "Scroll Wheel Smooth Scrolling" msgstr "Weiches Mausrad-Scrollen" #: 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 "Hohe Empfindlichkeit für vertikales Scrollen mit dem Mausrad." #: lib/logitech_receiver/settings_templates.py:165 msgid "Side Scrolling" msgstr "Seitliches Scrollen" #: 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 "Wenn ausgeschaltet, werden durch seitliches Kippen des Mausrads " "benutzer-\n" "definierte Maustastenereignisse gesendet, anstatt der vorgegebenen " "Seitlich-Scrollen-\n" "Ereignisse." #: lib/logitech_receiver/settings_templates.py:177 msgid "Sensitivity (DPI - older mice)" msgstr "Empfindlichkeit (DPI - ältere Mäuse)" #: lib/logitech_receiver/settings_templates.py:178 #: lib/logitech_receiver/settings_templates.py:712 msgid "Mouse movement sensitivity" msgstr "Mauszeigerempfindlichkeit" #: lib/logitech_receiver/settings_templates.py:208 #: lib/logitech_receiver/settings_templates.py:218 #: lib/logitech_receiver/settings_templates.py:225 msgid "Backlight" msgstr "Hintergrundbeleuchtung" #: lib/logitech_receiver/settings_templates.py:209 #: lib/logitech_receiver/settings_templates.py:226 msgid "Set illumination time for keyboard." msgstr "Die Beleuchtungszeit für die Tastatur einstellen." #: lib/logitech_receiver/settings_templates.py:219 msgid "Turn illumination on or off on keyboard." msgstr "Schaltet die Tastaturbeleuchtung an oder aus." #: lib/logitech_receiver/settings_templates.py:237 msgid "Scroll Wheel High Resolution" msgstr "Hohe Auflösung für Mausrad-Scrollen" #: 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 "Aktivieren zum Ignorieren, falls Scrollen ungewöhnlich langsam oder " "schnell ist" #: lib/logitech_receiver/settings_templates.py:247 #: lib/logitech_receiver/settings_templates.py:277 msgid "Scroll Wheel Diversion" msgstr "Mausrad Umleitung" #: 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 "Lasse das Mausrad LOWRES_WHEEL HID++ Nachrichten senden (diese lösen " "Solaar Regeln aus, werden sonst aber ignoriert)." #: lib/logitech_receiver/settings_templates.py:256 msgid "Scroll Wheel Direction" msgstr "Mausrad-Scroll-Richtung" #: lib/logitech_receiver/settings_templates.py:257 msgid "Invert direction for vertical scroll with wheel." msgstr "Kehrt die vertikale Scroll-Richtung des Mausrads um." #: lib/logitech_receiver/settings_templates.py:265 msgid "Scroll Wheel Resolution" msgstr "Auflösung für Mausrad-Scrollen" #: 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 "Lasse das Mausrad HIRES_WHEEL HID++ Nachrichten senden (diese lösen " "Solaar Regeln aus, werden sonst aber ignoriert)." #: lib/logitech_receiver/settings_templates.py:288 msgid "Sensitivity (Pointer Speed)" msgstr "Empfindlichkeit (Zeigergeschwindigkeit)" #: lib/logitech_receiver/settings_templates.py:289 msgid "Speed multiplier for mouse (256 is normal multiplier)." msgstr "Geschwindigkeits-Multiplikator für die Maus (256 ist der normale " "Multiplikator)." #: lib/logitech_receiver/settings_templates.py:299 msgid "Thumb Wheel Diversion" msgstr "Daumenrad Umleitung" #: 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 "Lasse das Daumenrad THUMB_WHEEL HID++ Nachrichten senden (diese " "lösen Solaar Regeln aus, werden sonst aber ignoriert)." #: lib/logitech_receiver/settings_templates.py:310 msgid "Thumb Wheel Direction" msgstr "Daumenrad-Scroll-Richtung" #: lib/logitech_receiver/settings_templates.py:311 msgid "Invert thumb wheel scroll direction." msgstr "Kehrt die Scroll-Richtung des Daumenrads um." #: lib/logitech_receiver/settings_templates.py:319 msgid "Onboard Profiles" msgstr "Onboard-Profile" #: lib/logitech_receiver/settings_templates.py:320 msgid "Enable onboard profiles, which often control report rate and " "keyboard lighting" msgstr "Schaltet Onboard-Profile an, welche oft die Wiederholrate und die " "Tastenbeleuchtung steuern" #: lib/logitech_receiver/settings_templates.py:330 msgid "Polling Rate (ms)" msgstr "Abtastrate (ms)" #: lib/logitech_receiver/settings_templates.py:332 msgid "Frequency of device polling, in milliseconds" msgstr "Frequenz der Geräteabtastung, in Millisekunden" #: 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 "Onboard-Profile müssen eventuell deaktivert sein damit dies " "funktioniert." #: lib/logitech_receiver/settings_templates.py:365 msgid "Divert crown events" msgstr "Drehregler-Ereignisse umleiten" #: lib/logitech_receiver/settings_templates.py:366 msgid "Make crown send CROWN HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "Das Senden von CROWN-HID++-Mitteilungen beim Bedienen des " "Drehreglers erzwingen (diese lösen Solaar Regeln aus, werden sonst " "aber ignoriert)." #: lib/logitech_receiver/settings_templates.py:374 msgid "Crown smooth scroll" msgstr "Crown weiches scrollen" #: lib/logitech_receiver/settings_templates.py:375 msgid "Set crown smooth scroll" msgstr "Aktiviere Crown weiches scrollen" #: lib/logitech_receiver/settings_templates.py:383 msgid "Divert G Keys" msgstr "G-Tasten umleiten" #: lib/logitech_receiver/settings_templates.py:385 msgid "Make G keys send GKEY HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "Das Senden von GKEY-HID++-Mitteilungen beim Drücken der G-Tasten " "erzwingen (diese lösen Solaar Regeln aus, werden sonst aber " "ignoriert)." #: lib/logitech_receiver/settings_templates.py:386 msgid "May also make M keys and MR key send HID++ notifications" msgstr "Die M und MR Tasten senden ggf. auch HID++ Benachrichtigungen" #: lib/logitech_receiver/settings_templates.py:402 msgid "Scroll Wheel Ratcheted" msgstr "Mausrad-Rastfunktion" #: lib/logitech_receiver/settings_templates.py:403 msgid "Switch the mouse wheel between speed-controlled ratcheting and " "always freespin." msgstr "Schaltet beim Mausrad um zwischen geschwindigkeitskontrolliert mit " "einrasten und immer freilaufend." #: lib/logitech_receiver/settings_templates.py:405 msgid "Freespinning" msgstr "Freilaufen" #: lib/logitech_receiver/settings_templates.py:405 msgid "Ratcheted" msgstr "Einrastend" #: lib/logitech_receiver/settings_templates.py:412 msgid "Scroll Wheel Ratchet Speed" msgstr "Mausrad-Rastgeschwindigkeit" #: 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 "Mausrad automatisch zwischen einrastendem und freilaufendem Modus " "umschalten.\n" "Das Mausrad läuft bei 0 stets frei und verhält sich bei 50 stets " "einrastend." #: lib/logitech_receiver/settings_templates.py:463 msgid "Key/Button Actions" msgstr "Tasten-/Maustastenaktionen" #: lib/logitech_receiver/settings_templates.py:465 msgid "Change the action for the key or button." msgstr "Die Aktion für die Taste oder die Maustaste ändern." #: lib/logitech_receiver/settings_templates.py:465 msgid "Overridden by diversion." msgstr "Durch Umleitung überschrieben." #: 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 "Das Ändern wichtiger Aktionen (wie z.B. für die linke Maustaste) " "kann zu einem unbenutzbaren System führen." #: lib/logitech_receiver/settings_templates.py:639 msgid "Key/Button Diversion" msgstr "Taste/Maustaste umleiten" #: 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 "Die Taste/Maustaste sendet HID++-Mitteilungen (umgeleitet) oder " "initiiert Mausgesten oder Gleiten 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 "Umgeleitet" #: lib/logitech_receiver/settings_templates.py:643 #: lib/logitech_receiver/settings_templates.py:644 msgid "Mouse Gestures" msgstr "Mausgesten" #: 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 "Gleiten DPI" #: lib/logitech_receiver/settings_templates.py:711 msgid "Sensitivity (DPI)" msgstr "Empfindlichkeit (DPI)" #: lib/logitech_receiver/settings_templates.py:752 msgid "Sensitivity Switching" msgstr "Umschaltung Empfindlichkeit" #: 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 "Tauscht die aktuelle Empfindlichkeit und die abgespeicherte " "Empfindlichkeit wenn die Taste gedrückt wird.\n" "Falls keine gespeicherte Empfindlichkeit vorhanden ist, wird die " "aktuelle Empfindlichkeit abgespeichert" #: lib/logitech_receiver/settings_templates.py:758 msgid "Off" msgstr "Aus" #: lib/logitech_receiver/settings_templates.py:791 msgid "Disable keys" msgstr "Tasten abschalten" #: lib/logitech_receiver/settings_templates.py:792 msgid "Disable specific keyboard keys." msgstr "Bestimmte Tastatur-Tasten abschalten." #: lib/logitech_receiver/settings_templates.py:795 #, python-format msgid "Disables the %s key." msgstr "Deaktiviert die %s-Taste." #: lib/logitech_receiver/settings_templates.py:809 #: lib/logitech_receiver/settings_templates.py:860 msgid "Set OS" msgstr "Betriebssystem auswählen" #: lib/logitech_receiver/settings_templates.py:810 #: lib/logitech_receiver/settings_templates.py:861 msgid "Change keys to match OS." msgstr "Ändert Tasten so, dass Sie zum BS passen." #: lib/logitech_receiver/settings_templates.py:873 msgid "Change Host" msgstr "Anderen Empfänger wählen" #: lib/logitech_receiver/settings_templates.py:874 msgid "Switch connection to a different host" msgstr "Verbindung zu einem anderen Empfänger umschalten" #: lib/logitech_receiver/settings_templates.py:900 msgid "Performs a left click." msgstr "Führt einen Linksklick aus." #: lib/logitech_receiver/settings_templates.py:900 msgid "Single tap" msgstr "Einfaches Tippen" #: lib/logitech_receiver/settings_templates.py:901 msgid "Performs a right click." msgstr "Führt einen Rechtsklick aus." #: lib/logitech_receiver/settings_templates.py:901 msgid "Single tap with two fingers" msgstr "Einfaches Tippen mit zwei Fingern" #: lib/logitech_receiver/settings_templates.py:902 msgid "Single tap with three fingers" msgstr "Einfaches Tippen mit drei Fingern" #: lib/logitech_receiver/settings_templates.py:906 msgid "Double tap" msgstr "Doppeltes Tippen" #: lib/logitech_receiver/settings_templates.py:906 msgid "Performs a double click." msgstr "Führt einen Doppelklick aus." #: lib/logitech_receiver/settings_templates.py:907 msgid "Double tap with two fingers" msgstr "Doppeltes Tippen mit zwei Fingern" #: lib/logitech_receiver/settings_templates.py:908 msgid "Double tap with three fingers" msgstr "Doppeltes Tippen mit drei Fingern" #: lib/logitech_receiver/settings_templates.py:911 msgid "Drags items by dragging the finger after double tapping." msgstr "Zieht Objekte durch Ziehen des Fingers nach doppeltem Tippen." #: lib/logitech_receiver/settings_templates.py:911 msgid "Tap and drag" msgstr "Tippen-und-Ziehen" #: lib/logitech_receiver/settings_templates.py:913 msgid "Drags items by dragging the fingers after double tapping." msgstr "Zieht Objekte durch Ziehen der Finger nach doppeltem Tippen." #: lib/logitech_receiver/settings_templates.py:913 msgid "Tap and drag with two fingers" msgstr "Tippen und mit zwei Fingern ziehen" #: lib/logitech_receiver/settings_templates.py:914 msgid "Tap and drag with three fingers" msgstr "Tippen und mit drei Fingern ziehen" #: lib/logitech_receiver/settings_templates.py:917 msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." msgstr "Schaltet Tipp- und Bildschirmrand-Gesten ab (entspricht dem Drücken " "von Fn+Linksklick)." #: lib/logitech_receiver/settings_templates.py:917 msgid "Suppress tap and edge gestures" msgstr "Tipp- und Bildschirmrand-Gesten abschalten" #: lib/logitech_receiver/settings_templates.py:918 msgid "Scroll with one finger" msgstr "Mit einem Finger scrollen" #: 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 "Mit zwei Fingern scrollen" #: lib/logitech_receiver/settings_templates.py:920 msgid "Scroll horizontally with two fingers" msgstr "Horizontales Scrollen mit zwei Fingern" #: lib/logitech_receiver/settings_templates.py:920 msgid "Scrolls horizontally." msgstr "Scrollt horizontal." #: lib/logitech_receiver/settings_templates.py:921 msgid "Scroll vertically with two fingers" msgstr "Vertikales Scrollen mit zwei Fingern" #: lib/logitech_receiver/settings_templates.py:921 msgid "Scrolls vertically." msgstr "Scrollt vertikal." #: lib/logitech_receiver/settings_templates.py:923 msgid "Inverts the scrolling direction." msgstr "Kehrt die Scroll-Richtung um." #: lib/logitech_receiver/settings_templates.py:923 msgid "Natural scrolling" msgstr "Natürlicher Bildlauf" #: lib/logitech_receiver/settings_templates.py:924 msgid "Enables the thumbwheel." msgstr "Aktiviert das Daumenrad." #: lib/logitech_receiver/settings_templates.py:924 msgid "Thumbwheel" msgstr "Daumenrad" #: lib/logitech_receiver/settings_templates.py:935 #: lib/logitech_receiver/settings_templates.py:939 msgid "Swipe from the top edge" msgstr "Vom oberen Bildschirmrand wischen" #: lib/logitech_receiver/settings_templates.py:936 msgid "Swipe from the left edge" msgstr "Vom linken Bildschirmrand wischen" #: lib/logitech_receiver/settings_templates.py:937 msgid "Swipe from the right edge" msgstr "Vom rechten Bildschirmrand wischen" #: lib/logitech_receiver/settings_templates.py:938 msgid "Swipe from the bottom edge" msgstr "Vom unteren Bildschirmrand wischen" #: lib/logitech_receiver/settings_templates.py:940 msgid "Swipe two fingers from the left edge" msgstr "Mit zwei Fingern vom linken Bildschirmrand wischen" #: lib/logitech_receiver/settings_templates.py:941 msgid "Swipe two fingers from the right edge" msgstr "Mit zwei Fingern vom rechten Bildschirmrand wischen" #: lib/logitech_receiver/settings_templates.py:942 msgid "Swipe two fingers from the bottom edge" msgstr "Mit zwei Fingern vom unteren Bildschirmrand wischen" #: lib/logitech_receiver/settings_templates.py:943 msgid "Swipe two fingers from the top edge" msgstr "Mit zwei Fingern vom oberen Bildschirmrand wischen" #: lib/logitech_receiver/settings_templates.py:944 #: lib/logitech_receiver/settings_templates.py:948 msgid "Pinch to zoom out; spread to zoom in." msgstr "Zusammenziehen-Geste zum Verkleinern; Auseinanderbewegen-Geste zum " "Vergrößern." #: lib/logitech_receiver/settings_templates.py:944 msgid "Zoom with two fingers." msgstr "Zoom mit zwei Fingern." #: lib/logitech_receiver/settings_templates.py:945 msgid "Pinch to zoom out." msgstr "Zusammenziehen-Geste zum Verkleinern." #: lib/logitech_receiver/settings_templates.py:946 msgid "Spread to zoom in." msgstr "Auseinanderbewegen-Geste zum Vergrößern." #: lib/logitech_receiver/settings_templates.py:947 msgid "Zoom with three fingers." msgstr "Zoom mit drei Fingern." #: lib/logitech_receiver/settings_templates.py:948 msgid "Zoom with two fingers" msgstr "Zoom mit zwei Fingern" #: lib/logitech_receiver/settings_templates.py:966 msgid "Pixel zone" msgstr "Pixel Zone" #: lib/logitech_receiver/settings_templates.py:967 msgid "Ratio zone" msgstr "Verhältnis Zone" #: lib/logitech_receiver/settings_templates.py:968 msgid "Scale factor" msgstr "Skalierungsfaktor" #: lib/logitech_receiver/settings_templates.py:968 msgid "Sets the cursor speed." msgstr "Stellt die Cursorgeschwindigkeit ein." #: lib/logitech_receiver/settings_templates.py:972 msgid "Left" msgstr "Links" #: lib/logitech_receiver/settings_templates.py:972 msgid "Left-most coordinate." msgstr "Äußerste linke Koordinate." #: lib/logitech_receiver/settings_templates.py:973 msgid "Bottom" msgstr "Ende" #: lib/logitech_receiver/settings_templates.py:973 msgid "Bottom coordinate." msgstr "Ende Koordinate." #: lib/logitech_receiver/settings_templates.py:974 msgid "Width" msgstr "Breite" #: lib/logitech_receiver/settings_templates.py:974 msgid "Width." msgstr "Breite." #: lib/logitech_receiver/settings_templates.py:975 msgid "Height" msgstr "Höhe" #: lib/logitech_receiver/settings_templates.py:975 msgid "Height." msgstr "Höhe." #: lib/logitech_receiver/settings_templates.py:976 msgid "Cursor speed." msgstr "Zeigergeschwindigkeit." #: lib/logitech_receiver/settings_templates.py:976 msgid "Scale" msgstr "Skalierung" #: lib/logitech_receiver/settings_templates.py:982 msgid "Gestures" msgstr "Gesten" #: lib/logitech_receiver/settings_templates.py:983 msgid "Tweak the mouse/touchpad behaviour." msgstr "Das Maus-/Tastfeldverhalten einstellen." #: lib/logitech_receiver/settings_templates.py:1000 msgid "Gestures Diversion" msgstr "Gesten Umleitung" #: lib/logitech_receiver/settings_templates.py:1001 msgid "Divert mouse/touchpad gestures." msgstr "Maus/Tastfeldgesten umleiten." #: lib/logitech_receiver/settings_templates.py:1018 msgid "Gesture params" msgstr "Gestenparameter" #: lib/logitech_receiver/settings_templates.py:1019 msgid "Change numerical parameters of a mouse/touchpad." msgstr "Numerische Parameter einer Maus/eines Tastfelds ändern." #: lib/logitech_receiver/settings_templates.py:1044 msgid "M-Key LEDs" msgstr "M-Taste LED's" #: lib/logitech_receiver/settings_templates.py:1046 msgid "Control the M-Key LEDs." msgstr "Steuert die M-Tasten LED's." #: 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 Tasten müssen evtl. umgeleitet sein um zu funktionieren." #: lib/logitech_receiver/settings_templates.py:1053 #, python-format msgid "Lights up the %s key." msgstr "Beleuchtet die %s Tasten." #: lib/logitech_receiver/settings_templates.py:1074 msgid "MR-Key LED" msgstr "MR-Taste LED" #: lib/logitech_receiver/settings_templates.py:1076 msgid "Control the MR-Key LED." msgstr "Steuert die MR-Taste LED." #: lib/logitech_receiver/settings_templates.py:1095 msgid "Persistent Key/Button Mapping" msgstr "Dauerhafte Tasten-/Maustastenzuordnung" #: lib/logitech_receiver/settings_templates.py:1097 msgid "Permanently change the mapping for the key or button." msgstr "Ändert die Zuordung für die Taste/Maustaste dauerhaft." #: 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 "Änderungen an wichtigen Tasten oder Maustasten (wie an der linken " "Maustaste) können zu einem unbenutzbaren System führen." #: lib/logitech_receiver/settings_templates.py:1157 msgid "Sidetone" msgstr "Nebenton" #: lib/logitech_receiver/settings_templates.py:1158 msgid "Set sidetone level." msgstr "Setze Nebentonlautstärke." #: lib/logitech_receiver/settings_templates.py:1167 msgid "Equalizer" msgstr "Equalizer" #: lib/logitech_receiver/settings_templates.py:1168 msgid "Set equalizer levels." msgstr "Setze Equalizerlautstärke." #: lib/logitech_receiver/settings_templates.py:1191 msgid "Hz" msgstr "Hz" #: lib/logitech_receiver/settings_templates.py:1197 msgid "Power Management" msgstr "Energieverwaltung" #: lib/logitech_receiver/settings_templates.py:1198 msgid "Power off in minutes (0 for never)." msgstr "Ausschalten in Minuten (0 für nie)" #: lib/logitech_receiver/status.py:114 msgid "No paired devices." msgstr "Keine gekoppelten Geräte." #: 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 gekoppeltes Gerät." msgstr[1] "%(count)s gekoppelte Geräte." #: lib/logitech_receiver/status.py:170 #, python-format msgid "Battery: %(level)s" msgstr "Akku: %(level)s" #: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "Akku: %(percent)d%%" #: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "Beleuchtung: %(level)s lux" #: lib/logitech_receiver/status.py:239 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "Akku: %(level)s (%(status)s)" #: lib/logitech_receiver/status.py:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Akku: %(percent)d%% (%(status)s)" #: lib/solaar/ui/__init__.py:52 msgid "Permissions error" msgstr "Zugriffsfehler" #: 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 "Es wurde ein Logitech Empfänger oder Gerät (%s) gefunden, aber es " "fehlen die Berechtigungen dieses zu öffnen." #: lib/solaar/ui/__init__.py:55 msgid "If you've just installed Solaar, try disconnecting the receiver or " "device and then reconnecting it." msgstr "Falls du Solaar gerade erst installiert hast, versuche den Empfänger " "oder das Geräte zu trennen und dann erneut zu verbinden." #: lib/solaar/ui/__init__.py:58 msgid "Cannot connect to device error" msgstr "Kann nicht mit dem Gerät verbinden Fehler" #: 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 "Ein Logitech Empfänger oder Gerät wurde auf %s gefunden, aber beim " "Verbinden ist ein Fehler aufgetreten." #: lib/solaar/ui/__init__.py:61 msgid "Try disconnecting the device and then reconnecting it or turning it " "off and then on." msgstr "Versuche die Verbindung zu dem Geräte zu trennen und neu zu " "verbinden oder dieses aus- und wieder einzuschalten." #: lib/solaar/ui/__init__.py:64 msgid "Unpairing failed" msgstr "Entkopplung fehlgeschlagen" #: lib/solaar/ui/__init__.py:66 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Kopplung zwischen Gerät %{device} und Empfänger %{receiver} konnte " "nicht gelöst werden." #: lib/solaar/ui/__init__.py:67 msgid "The receiver returned an error, with no further details." msgstr "Der Empfänger gab einen Fehler zurück, ohne weitere Details." #: lib/solaar/ui/__init__.py:177 msgid "Another Solaar process is already running so just expose its window" msgstr "Ein anderer Solaar-Prozess läuft bereits deshalb nur dessen Fenster " "in den Vordergrund holen" #: lib/solaar/ui/about.py:36 msgid "Manages Logitech receivers,\n" "keyboards, mice, and tablets." msgstr "Verwaltet Logitech Empfänger, Tastaturen, Mäuse und Grafiktablets." #: lib/solaar/ui/about.py:44 msgid "Additional Programming" msgstr "Zusätzliche Programmierung" #: lib/solaar/ui/about.py:45 msgid "GUI design" msgstr "GUI Design" #: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "Tester" #: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Logitech-Dokumentation" #: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 #: lib/solaar/ui/window.py:197 msgid "Unpair" msgstr "Entkoppeln" #: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "Abbrechen" #: lib/solaar/ui/config_panel.py:212 msgid "Complete - ENTER to change" msgstr "Abgeschlossen - ENTER zu ändern" #: lib/solaar/ui/config_panel.py:212 msgid "Incomplete" msgstr "Unvollständig" #: 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 Wert" msgstr[1] "%d Werte" #: lib/solaar/ui/config_panel.py:518 msgid "Changes allowed" msgstr "Änderungen erlaubt" #: lib/solaar/ui/config_panel.py:519 msgid "No changes allowed" msgstr "Änderungen nicht erlaubt" #: lib/solaar/ui/config_panel.py:520 msgid "Ignore this setting" msgstr "Diese Einstellung ignorieren" #: lib/solaar/ui/config_panel.py:565 msgid "Working" msgstr "Beschäftigt" #: lib/solaar/ui/config_panel.py:568 msgid "Read/write operation failed." msgstr "Lese-/Schreiboperation fehlgeschlagen." #: lib/solaar/ui/diversion_rules.py:65 msgid "Built-in rules" msgstr "Integrierte Regeln" #: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" msgstr "Benutzerdefinierte Regeln" #: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "Regel" #: 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 "Unterregel" #: lib/solaar/ui/diversion_rules.py:70 msgid "[empty]" msgstr "[leer]" #: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "Solaar-Regelbearbeitung" #: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "Änderungen dauerhaft anwenden?" #: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Ja" #: lib/solaar/ui/diversion_rules.py:148 msgid "No" msgstr "Nein" #: lib/solaar/ui/diversion_rules.py:153 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "Wenn Sie »Nein« wählen, gehen alle Änderungen beim Schließen von " "Solaar verloren." #: lib/solaar/ui/diversion_rules.py:201 msgid "Save changes" msgstr "Änderungen speichern" #: lib/solaar/ui/diversion_rules.py:206 msgid "Discard changes" msgstr "Änderungen verwerfen" #: lib/solaar/ui/diversion_rules.py:372 msgid "Insert here" msgstr "Hier hinzufügen" #: lib/solaar/ui/diversion_rules.py:374 msgid "Insert above" msgstr "Darüber hinzufügen" #: lib/solaar/ui/diversion_rules.py:376 msgid "Insert below" msgstr "Darunter hinzufügen" #: lib/solaar/ui/diversion_rules.py:382 msgid "Insert new rule here" msgstr "Neue Regel hier hinzufügen" #: lib/solaar/ui/diversion_rules.py:384 msgid "Insert new rule above" msgstr "Neue Regel darüber hinzufügen" #: lib/solaar/ui/diversion_rules.py:386 msgid "Insert new rule below" msgstr "Neue Regel darunter hinzufügen" #: lib/solaar/ui/diversion_rules.py:427 msgid "Paste here" msgstr "Hier einfügen" #: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "Darüber einfügen" #: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "Darunter einfügen" #: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "Regel hier einfügen" #: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "Regel darüber einfügen" #: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "Regel darunter einfügen" #: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "Regel einfügen" #: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "Einrückung aufheben" #: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "Hinzufügen" #: lib/solaar/ui/diversion_rules.py:510 lib/solaar/ui/diversion_rules.py:638 #: lib/solaar/ui/diversion_rules.py:1126 msgid "Or" msgstr "Oder" #: lib/solaar/ui/diversion_rules.py:511 lib/solaar/ui/diversion_rules.py:637 #: lib/solaar/ui/diversion_rules.py:1111 msgid "And" msgstr "Und" #: lib/solaar/ui/diversion_rules.py:513 msgid "Condition" msgstr "Bedingung" #: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "Funktion" #: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "Rückgabewert" #: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 msgid "Process" msgstr "Prozessname (Anwendung)" #: lib/solaar/ui/diversion_rules.py:518 msgid "Mouse process" msgstr "Maus Prozess" #: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 msgid "Modifiers" msgstr "Zusatztasten" #: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "Taste" #: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 msgid "KeyIsDown" msgstr "TasteIstGedrückt" #: 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 "Gerät" #: lib/solaar/ui/diversion_rules.py:524 lib/solaar/ui/diversion_rules.py:2297 msgid "Host" msgstr "Host" #: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 msgid "Setting" msgstr "Einstellung" #: lib/solaar/ui/diversion_rules.py:526 lib/solaar/ui/diversion_rules.py:1477 #: lib/solaar/ui/diversion_rules.py:1526 msgid "Test" msgstr "Prüfwert/Ereignis" #: lib/solaar/ui/diversion_rules.py:527 lib/solaar/ui/diversion_rules.py:1643 msgid "Test bytes" msgstr "Prüfwert Bytes" #: lib/solaar/ui/diversion_rules.py:528 lib/solaar/ui/diversion_rules.py:1736 msgid "Mouse Gesture" msgstr "Mausgeste" #: lib/solaar/ui/diversion_rules.py:532 msgid "Action" msgstr "Aktion" #: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 msgid "Key press" msgstr "Tastendruck" #: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "Mausrad-Scrollen" #: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 msgid "Mouse click" msgstr "Mausklick" #: lib/solaar/ui/diversion_rules.py:537 msgid "Set" msgstr "Aktivieren" #: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 msgid "Execute" msgstr "Ausführen" #: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 msgid "Later" msgstr "Später" #: lib/solaar/ui/diversion_rules.py:568 msgid "Insert new rule" msgstr "Neue Regel hinzufügen" #: 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 "Löschen" #: lib/solaar/ui/diversion_rules.py:610 msgid "Negate" msgstr "Negieren" #: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "Einrücken unter" #: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Ausschneiden" #: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Einfügen" #: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Kopieren" #: lib/solaar/ui/diversion_rules.py:1063 msgid "This editor does not support the selected rule component yet." msgstr "Diese Regelbearbeitung unterstützt die ausgewählte Regelkomponente " "noch nicht." #: lib/solaar/ui/diversion_rules.py:1138 msgid "Number of seconds to delay." msgstr "Angabe der Sekunden für die Verzögerung." #: lib/solaar/ui/diversion_rules.py:1177 msgid "Not" msgstr "Nicht" #: lib/solaar/ui/diversion_rules.py:1187 msgid "X11 active process. For use in X11 only." msgstr "Aktiver X11 Prozess. Nur zur Verwendung mit X11." #: lib/solaar/ui/diversion_rules.py:1218 msgid "X11 mouse process. For use in X11 only." msgstr "X11 Mausprozess. Nur zur Verwendung mit X11." #: lib/solaar/ui/diversion_rules.py:1235 msgid "MouseProcess" msgstr "Mausprozess" #: lib/solaar/ui/diversion_rules.py:1260 msgid "Feature name of notification triggering rule processing." msgstr "Funktionsname der Benachrichtigung, die die Regelverarbeitung " "auslöst." #: 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 "Aktive Keyboardanpassung. Unter Wayland nicht immer verfügbar." #: 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 "Umgeleitete Taste oder Maustaste ist gedrückt oder losgelassen.\n" "Verwende die Tasten-/Maustastenumleitungs- und G " "Tastenumleitungseinstellungen um Tasten sowie Maustasten umzuleiten." #: lib/solaar/ui/diversion_rules.py:1392 msgid "Key down" msgstr "Taste gedrückt" #: lib/solaar/ui/diversion_rules.py:1395 msgid "Key up" msgstr "Taste losgelassen" #: 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 "Umgeleitete Taste oder Maustaste ist unten. Verwende die Tasten-/" "Maustastenumleitungs- und G Tastenumleitungseinstellungen um Tasten " "sowie Maustasten umzuleiten." #: 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 "Anfang (inklusive)" #: lib/solaar/ui/diversion_rules.py:1543 msgid "end (exclusive)" msgstr "Ende (exklusive)" #: lib/solaar/ui/diversion_rules.py:1552 msgid "range" msgstr "Bereich" #: 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 "Bytes %(0)d bis %(1)d, beginnend von %(2)d bis %(3)d" #: lib/solaar/ui/diversion_rules.py:1562 msgid "mask" msgstr "Maske" #: lib/solaar/ui/diversion_rules.py:1563 #, python-format msgid "bytes %(0)d to %(1)d, mask %(2)d" msgstr "Bytes %(0)d bis %(1)d, Maske %(2)d" #: 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 "Typ" #: lib/solaar/ui/diversion_rules.py:1666 msgid "Mouse gesture with optional initiating button followed by zero or " "more mouse movements." msgstr "Mausgeste mit optionaler initiierender Maustaste gefolgt von keiner " "oder mehreren Bewegungen." #: lib/solaar/ui/diversion_rules.py:1671 msgid "Add movement" msgstr "Bewegung hinzufügen" #: 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 "Simuliert einen kombinierten Mausklick oder Tastendruck bzw. " "loslassen einer Taste.\n" "Benötigt unter Wayland Schreibzugriff auf /dev/uinput." #: lib/solaar/ui/diversion_rules.py:1769 msgid "Add key" msgstr "Weitere Taste hinzufügen" #: lib/solaar/ui/diversion_rules.py:1772 msgid "Click" msgstr "Linksklick" #: lib/solaar/ui/diversion_rules.py:1775 msgid "Depress" msgstr "Abgesenkt" #: lib/solaar/ui/diversion_rules.py:1778 msgid "Release" msgstr "Loslassen" #: lib/solaar/ui/diversion_rules.py:1861 msgid "Simulate a mouse scroll.\n" "On Wayland requires write access to /dev/uinput." msgstr "Simuliert eine Mausradbewegung. Benötigt unter Wayland " "Schreibzugriff auf /dev/uinput." #: lib/solaar/ui/diversion_rules.py:1918 msgid "Simulate a mouse click.\n" "On Wayland requires write access to /dev/uinput." msgstr "Simuliert einen Mausklick. Benötigt unter Wayland Schreibzugriff " "auf /dev/uinput." #: lib/solaar/ui/diversion_rules.py:1921 msgid "Button" msgstr "Maustaste" #: lib/solaar/ui/diversion_rules.py:1922 msgid "Count and Action" msgstr "Anzahl und Aktion" #: lib/solaar/ui/diversion_rules.py:1972 msgid "Execute a command with arguments." msgstr "Einen Befehl mit Parametern ausführen." #: lib/solaar/ui/diversion_rules.py:1975 msgid "Add argument" msgstr "Argument hinzufügen" #: lib/solaar/ui/diversion_rules.py:2050 msgid "Toggle" msgstr "Umschalten" #: lib/solaar/ui/diversion_rules.py:2050 msgid "True" msgstr "Wahr" #: lib/solaar/ui/diversion_rules.py:2051 msgid "False" msgstr "Falsch" #: lib/solaar/ui/diversion_rules.py:2065 msgid "Unsupported setting" msgstr "Nicht unterstützte Einstellung" #: 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 "Quellgerät" #: lib/solaar/ui/diversion_rules.py:2256 msgid "Device is active and its settings can be changed." 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 Benachrichtigung stammt." #: lib/solaar/ui/diversion_rules.py:2280 msgid "Name of host computer." msgstr "Name des Host-Computers." #: lib/solaar/ui/diversion_rules.py:2347 msgid "Value" msgstr "Wert" #: lib/solaar/ui/diversion_rules.py:2355 msgid "Item" msgstr "Item" #: lib/solaar/ui/diversion_rules.py:2630 msgid "Change setting on device" msgstr "Einstellung auf dem Gerät ändern" #: lib/solaar/ui/diversion_rules.py:2647 msgid "Setting on device" msgstr "Einstellung auf dem Gerät" #: 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 "%(receiver_name)s: pair new device" msgstr "%(receiver_name)s: neues Gerät koppeln" #: lib/solaar/ui/pair_window.py:123 #, python-format msgid "Enter passcode on %(name)s." msgstr "Gib das Passwort ein auf %(name)s." #: lib/solaar/ui/pair_window.py:126 #, python-format msgid "Type %(passcode)s and then press the enter key." msgstr "Gib %(passcode)s ein und drücke die ENTER Taste." #: lib/solaar/ui/pair_window.py:129 msgid "left" msgstr "links" #: lib/solaar/ui/pair_window.py:129 msgid "right" msgstr "rechts" #: lib/solaar/ui/pair_window.py:131 #, python-format msgid "Press %(code)s\n" "and then press left and right buttons simultaneously." msgstr "Drücke %(code)s\n" "und drücke dann die links und rechts Tasten gleichzeitig." #: lib/solaar/ui/pair_window.py:188 msgid "Pairing failed" msgstr "Kopplung fehlgeschlagen" #: lib/solaar/ui/pair_window.py:190 msgid "Make sure your device is within range, and has a decent battery " "charge." msgstr "Überprüfen Sie, ob sich das Gerät in Reichweite befindet und der " "Akku ausreichend geladen bzw. die Batterie voll genug ist." #: lib/solaar/ui/pair_window.py:192 msgid "A new device was detected, but it is not compatible with this " "receiver." msgstr "Es wurde ein neues Gerät festgestellt, aber es ist nicht mit diesem " "Empfänger kompatibel." #: lib/solaar/ui/pair_window.py:194 msgid "More paired devices than receiver can support." msgstr "Es sind mehr Geräte gekoppelt als der Empfänger unterstützt." #: lib/solaar/ui/pair_window.py:196 msgid "No further details are available about the error." msgstr "Es gibt keine näheren Informationen zu diesem Fehler." #: lib/solaar/ui/pair_window.py:210 msgid "Found a new device:" msgstr "Neues Gerät gefunden:" #: lib/solaar/ui/pair_window.py:235 msgid "The wireless link is not encrypted" msgstr "Die drahtlose Verbindung ist nicht verschlüsselt" #: lib/solaar/ui/pair_window.py:264 msgid "Unifying receivers are only compatible with Unifying devices." msgstr "Unifying Empfänger sind nur mit Unifying Geräten kompatibel." #: lib/solaar/ui/pair_window.py:266 msgid "Bolt receivers are only compatible with Bolt devices." msgstr "Bolt Empfänger sind nur mit Bolt Geräten kompatibel." #: lib/solaar/ui/pair_window.py:268 msgid "Other receivers are only compatible with a few devices." msgstr "Andere Empfänger sind nur mit wenigen Geräten kompatibel." #: lib/solaar/ui/pair_window.py:270 msgid "The device must not be paired with a nearby powered-on receiver." msgstr "Das Gerät darf nicht mit mit anderen, eingeschalteten Empfängern in " "der Nähe gekoppelt sein." #: lib/solaar/ui/pair_window.py:274 msgid "Press a pairing button or key until the pairing light flashes " "quickly." msgstr "Drücke die Kopplungstaste bis das Kopplungslicht schnell blinkt." #: lib/solaar/ui/pair_window.py:276 msgid "You may have to first turn the device off and on again." msgstr "Eventuell muss das Gerät dazu aus- und wieder eingeschaltet werden." #: lib/solaar/ui/pair_window.py:278 msgid "Turn on the device you want to pair." msgstr "Schalten Sie das zu koppelnde Gerät ein." #: lib/solaar/ui/pair_window.py:280 msgid "If the device is already turned on, turn it off and on again." msgstr "Falls das Gerät eingeschaltet ist, schalte es aus und wieder ein." #: 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" "Dieser Empfänger kann an %d weiteres Gerät gekoppelt werden." msgstr[1] "\n" "\n" "Dieser Empfänger kann an %d weitere Geräte gekoppelt werden." #: lib/solaar/ui/pair_window.py:286 msgid "\n" "Cancelling at this point will not use up a pairing." msgstr "\n" "Ein Abbruch zu diesem Zeitpunkt wird keine Kopplung aufbrauchen." #: lib/solaar/ui/tray.py:58 msgid "No supported device found" msgstr "Kein unterstütztes Gerät gefunden" #: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 #, python-format msgid "About %s" msgstr "Info zu %s" #: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 #, python-format msgid "Quit %s" msgstr "%s beenden" #: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "kein Empfänger" #: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "kein Status" #: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "Scanne" #: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Akku" #: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Drahtlose Verbindung" #: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Beleuchtung" #: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Technische Details anzeigen" #: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Neues Gerät koppeln" #: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Gerät auswählen" #: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "Regeln bearbeiten" #: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Pfad" #: 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 "Seriennummer" #: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Index" #: lib/solaar/ui/window.py:547 msgid "Wireless PID" msgstr "Wireless PID" #: lib/solaar/ui/window.py:549 msgid "Product ID" msgstr "Produkt-ID" #: lib/solaar/ui/window.py:551 msgid "Protocol" msgstr "Protokoll" #: lib/solaar/ui/window.py:551 msgid "Unknown" msgstr "Unbekannt" #: 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 "Abfragerate" #: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "Geräte ID" #: lib/solaar/ui/window.py:576 msgid "none" msgstr "keine" #: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Mitteilungen" #: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "Kein Gerät gekoppelt." #: 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] "Nur %(max_count)s Gerät kann an diesen Empfänger gekoppelt " "werden." msgstr[1] "Bis zu %(max_count)s Geräte können an diesen Empfänger " "gekoppelt werden." #: lib/solaar/ui/window.py:634 msgid "Only one device can be paired to this receiver." msgstr "An diesen Empfänger kann nur ein Gerät gekoppelt werden." #: 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] "Dieser Empfänger kann an %d weiteres Gerät gekoppelt werden." msgstr[1] "Dieser Empfänger kann an %d weitere Geräte gekoppelt werden." #: lib/solaar/ui/window.py:692 msgid "Battery Voltage" msgstr "Akkuspannung" #: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "Spannung, die vom Akku gemeldet wird" #: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "Akkuladung" #: lib/solaar/ui/window.py:698 msgid "Approximate level reported by battery" msgstr "Ungefährer Ladezustand des Akkus, den das Gerät meldet" #: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 msgid "next reported " msgstr "nächste Meldung bei " #: lib/solaar/ui/window.py:708 msgid " and next level to be reported." msgstr " und der nächste, der vom Gerät mitgeteilt werden wird." #: lib/solaar/ui/window.py:713 msgid "last known" msgstr "letzte bekannte" #: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "verschlüsselt" #: lib/solaar/ui/window.py:726 msgid "The wireless link between this device and its receiver is encrypted." msgstr "Die drahtlose Verbindung zwischen diesem Gerät und dem Empfänger ist " "verschlüsselt." #: lib/solaar/ui/window.py:728 msgid "not encrypted" msgstr "nicht verschlüsselt" #: 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 "Die drahtlose Verbindung zwischen diesem Gerät und dem Empfänger ist " "nicht verschlüsselt.\n" "Dies ist ein Sicherheitsproblem für Zeigegeräte, aber ein großes " "Sicherheitsrisiko für Texteingabegeräte." #: lib/solaar/ui/window.py:748 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d Lux" #~ msgid "About" #~ msgstr "Über" #~ msgid "Add action" #~ msgstr "Aktion hinzufügen" #~ msgid "Adjust the DPI by sliding the mouse horizontally while " #~ "holding the DPI button." #~ msgstr "Die Empfindlichkeit (in DPI) durch seitliches Bewegen der " #~ "Maus ändern, während der DPI-Knopf gehalten wird." #~ msgid "Adjust the DPI by sliding the mouse horizontally while " #~ "holding the button down." #~ msgstr "Passe die DPI an durch horizontale Mausbewegung bei " #~ "gleichzeitig gedrückter Taste." #~ msgid "Battery information unknown." #~ msgstr "Akkuzustand unbekannt." #~ msgid "Click to allow changes." #~ msgstr "Anklicken, um Änderungen zuzulassen." #~ msgid "Click to prevent changes." #~ msgstr "Anklicken, um Änderungen zu verhindern." #~ msgid "Count" #~ msgstr "Klickanzahl" #~ msgid "DPI Sliding Adjustment" #~ msgstr "DPI-Anpassung durch seitliches Bewegen" #~ msgid "Diverted key or button depressed or released.\n" #~ "Use the Key/Button Diversion setting to divert keys and buttons." #~ msgstr "Umgeleitete Taste oder Maustaste gedrückt bzw. losgelassen.\n" #~ "Verwende die Einstellungen für Tasten-/Maustastenumleitung um Tasten " #~ "oder Maustasten umzuleiten." #~ msgid "Effectively turns off thumb scrolling in Linux." #~ msgstr "Schaltet das Scrollen mit dem Daumen unter Linux faktisch " #~ "aus." #~ msgid "Effectively turns off wheel scrolling in Linux." #~ msgstr "Schaltet das Scrollen mit dem Mausrad unter Linux faktisch " #~ "aus." #~ msgid "For more information see the Solaar installation directions\n" #~ "at https://pwr-solaar.github.io/Solaar/installation" #~ msgstr "Weitere Informationen erhalten Sie in den Solaar-" #~ "Installationshinweisen\n" #~ "unter https://pwr-solaar.github.io/Solaar/installation" #, python-format #~ msgid "Found a Logitech Receiver (%s), but did not have permission " #~ "to open it." #~ msgstr "Ein Logitech-Empfänger (%s) wurde gefunden, wegen fehlender " #~ "Berechtigung kann aber nicht darauf zugegriffen werden." #~ msgid "HID++ mode for horizontal scroll with the thumb wheel." #~ msgstr "HID++-Modus für horizontales Scrollen mit dem Daumenrad." #~ msgid "HID++ mode for vertical scroll with the wheel." #~ msgstr "HID++ Modus für vertikales Mausrad-Scrollen." #~ msgid "If the device is already turned on, turn if off and on again." #~ msgstr "Wenn das Gerät bereits eingeschaltet ist, schalte es aus und " #~ "wieder ein." #~ msgid "If you've just installed Solaar, try removing the receiver " #~ "and plugging it back in." #~ msgstr "Wenn Sie Solaar gerade neu installiert haben, versuchen Sie " #~ "den Empfänger aus- und wieder einzustecken." #~ msgid "No Logitech receiver found" #~ msgstr "Kein Logitech-Empfänger gefunden" #~ msgid "Quit" #~ msgstr "Beenden" #~ msgid "Scroll Wheel HID++ Scrolling" #~ msgstr "Mausrad-HID++-Scrollen" #~ msgid "Send a gesture by sliding the mouse while holding the button " #~ "down." #~ msgstr "Erstelle eine Geste durch Mausbewegung bei gleichzeitig " #~ "gedrückter Taste." #~ msgid "Set to ignore if unusual device behaviour is experienced" #~ msgstr "Aktivieren zum Ignorieren, falls das Gerät sich ungewöhnlich " #~ "verhält" #~ msgid "Shows status of devices connected\n" #~ "through wireless Logitech receivers." #~ msgstr "Zeigt den Status von Geräten an, die über\n" #~ "Logitech-Empfänger drahtlos verbunden sind." #~ msgid "Solaar depends on a udev file that is not present" #~ msgstr "Solaar benötigt eine udev-Datei, die nicht gefunden wurde" #~ msgid "Thumb Wheel HID++ Scrolling" #~ msgstr "Daumenrad-HID++-Scrollen" #~ msgid "Top-most coordinate." #~ msgstr "Äußerste obere Koordinate." #~ msgid "Try removing the device and plugging it back in or turning " #~ "it off and then on." #~ msgstr "Versuche das Gerät herauszuziehen und neu einzustecken bzw. " #~ "es aus und wieder an zuschalten." #~ msgid "height" #~ msgstr "Höhe" #~ msgid "top" #~ msgstr "oben" #~ msgid "unknown" #~ msgstr "unbekannt" #~ msgid "width" #~ msgstr "Breite" Solaar-1.1.20/po/el.po000066400000000000000000002242121522022367600144020ustar00rootroot00000000000000# Greek translations for solaar package. # Copyright (C) 2014 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # 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: 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" "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.2\n" #: lib/logitech_receiver/base_usb.py:45 msgid "Bolt Receiver" msgstr "Δέκτης Bolt" #: lib/logitech_receiver/base_usb.py:58 msgid "Unifying Receiver" msgstr "Δέκτης 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 "Δέκτης Nano" #: lib/logitech_receiver/base_usb.py:138 msgid "Lightspeed Receiver" msgstr "Δέκτης Lightspeed" #: lib/logitech_receiver/base_usb.py:149 msgid "EX100 Receiver 27 Mhz" msgstr "Δέκτης EX100 27 Mhz" #: 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: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 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 msgid "Firmware" msgstr "Firmware" #: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Bootloader" #: 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:63 #: lib/logitech_receiver/notifications.py:115 msgid "pairing lock is closed" msgstr "η δικλείδα σύζευξης είναι κλειστή" #: lib/logitech_receiver/notifications.py:63 #: lib/logitech_receiver/notifications.py:115 msgid "pairing lock is open" msgstr "η δικλείδα σύζευξης είναι ανοιχτή" #: lib/logitech_receiver/notifications.py:80 msgid "discovery lock is closed" msgstr "η δικλείδα ανακάλυψης είναι κλειστή" #: lib/logitech_receiver/notifications.py:80 msgid "discovery lock is open" msgstr "η δικλείδα ανακάλυψης είναι ανοιχτή" #: lib/logitech_receiver/notifications.py:207 msgid "connected" msgstr "συνδεμένη" #: lib/logitech_receiver/notifications.py:207 msgid "disconnected" msgstr "αποσυνδεδεμένη" #: lib/logitech_receiver/notifications.py:233 msgid "unpaired" msgstr "διαχωρισμένη" #: lib/logitech_receiver/notifications.py:280 msgid "powered on" msgstr "ανοιχτή" #: 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:631 lib/logitech_receiver/settings.py:658 msgid "feature" msgstr "χαρακτηριστικό" #: lib/logitech_receiver/settings_templates.py:144 msgid "Swap Fx function" msgstr "Αντιστροφή λειτουργιών 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 "" "Όταν είναι ορισμένο, τα πλήκτρα F1..F12 θα ενεργοποιήσουν τις ειδικές τους " "λειτουργίες,\n" "και πρέπει να κρατήσετε το πλήκτρο FN για να ενεργοποιήσετε την τυπική " "λειτουργία τους." #: 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 "" "Όταν δεν είναι ορισμένο, τα πλήκτρα F1..F12 θα ενεργοποιήσουν την τυπική " "λειτουργία τους,\n" "και πρέπει να κρατήσετε το πλήκτρο FN για να ενεργοποιήσετε την ειδική " "λειτουργία τους." #: lib/logitech_receiver/settings_templates.py:160 msgid "Hand Detection" msgstr "Ανίχνευση χεριού" #: 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:168 msgid "Scroll Wheel Smooth Scrolling" msgstr "Ομαλή κύλιση του τροχού κύλισης" #: 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:176 msgid "Side Scrolling" msgstr "Πλάγια κύλιση" #: 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 "" "Όταν είναι απενεργοποιημένο, πιέζοντας τον τροχό στο πλάι στέλνει " "προσαρμοσμένα γεγονότα κουμπιού\n" "αντί των τυπικών γεγονότων πλάγιας ολίσθησης." #: lib/logitech_receiver/settings_templates.py:188 msgid "Sensitivity (DPI - older mice)" msgstr "Ευαισθησία (DPI - παλαιά ποντίκια)" #: 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:249 #: lib/logitech_receiver/settings_templates.py:261 #: lib/logitech_receiver/settings_templates.py:388 msgid "Backlight" msgstr "Οπίσθιος φωτισμός" #: 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:262 msgid "" "Illumination level on keyboard. Changes made are only applied in Manual " "mode." msgstr "" "Επίπεδο φωτισμού στο πληκτρολόγιο. Οι αλλαγές που πραγματοποιούνται " "εφαρμόζονται μόνο στη χειροκίνητη λειτουργία." #: 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: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:411 #: lib/logitech_receiver/settings_templates.py:442 msgid "Scroll Wheel Diversion" msgstr "Εκτροπή τροχού κύλισης" #: 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 "" "Κάντε τον τροχό κύλισης να στέλνει ειδοποιήσεις LOWRES_WHEEL HID++ (οι " "οποίες ενεργοποιούν τους κανόνες Solaar αλλά αγνοούνται κατά τα άλλα)." #: lib/logitech_receiver/settings_templates.py:420 msgid "Scroll Wheel Direction" msgstr "Κατεύθυνση τροχού κύλισης" #: lib/logitech_receiver/settings_templates.py:421 msgid "Invert direction for vertical scroll with wheel." msgstr "Αντιστροφή κατεύθυνσης για κάθετη κύλιση με τον τροχό." #: lib/logitech_receiver/settings_templates.py:429 msgid "Scroll Wheel Resolution" msgstr "Ανάλυση τροχού κύλισης" #: 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 "" "Κάντε τον τροχό κύλισης να στέλνει ειδοποιήσεις HIRES_WHEEL HID++ (οι οποίες " "ενεργοποιούν τους κανόνες Solaar αλλά αγνοούνται διαφορετικά)." #: lib/logitech_receiver/settings_templates.py:453 msgid "Sensitivity (Pointer Speed)" msgstr "Ευαισθησία (ταχύτητα δείκτη)" #: lib/logitech_receiver/settings_templates.py:454 msgid "Speed multiplier for mouse (256 is normal multiplier)." msgstr "" "Πολλαπλασιαστής ταχύτητας για το ποντίκι (256 είναι ο κανονικός " "πολλαπλασιαστής)." #: lib/logitech_receiver/settings_templates.py:464 msgid "Thumb Wheel Diversion" msgstr "Εκτροπή τροχού αντίχειρα" #: 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 "" "Κάντε τον τροχό αντίχειρα να στέλνει ειδοποιήσεις THUMB_WHEEL HID++ (οι " "οποίες ενεργοποιούν τους κανόνες του Solaar αλλά αγνοούνται διαφορετικά)." #: lib/logitech_receiver/settings_templates.py:475 msgid "Thumb Wheel Direction" msgstr "Κατεύθυνση τροχού αντίχειρα" #: lib/logitech_receiver/settings_templates.py:476 msgid "Invert thumb wheel scroll direction." msgstr "Αντιστροφή της κατεύθυνσης κύλισης του τροχού αντίχειρα." #: 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 "" "Ενεργοποιήστε ένα ενσωματωμένο προφίλ, το οποίο ελέγχει το ρυθμό αναφοράς, " "την ευαισθησία και τις ενέργειες των κουμπιών" #: lib/logitech_receiver/settings_templates.py:541 #: lib/logitech_receiver/settings_templates.py:581 msgid "Report Rate" 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: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:623 msgid "Divert crown events" msgstr "Εκτροπή εκδηλώσεων crown" #: lib/logitech_receiver/settings_templates.py:624 msgid "" "Make crown send CROWN HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." msgstr "" "Κάντε το crown να στέλνει ειδοποιήσεις CROWN HID++ (οι οποίες ενεργοποιούν " "τους κανόνες Solaar αλλά αγνοούνται κατά τα άλλα)." #: lib/logitech_receiver/settings_templates.py:632 msgid "Crown smooth scroll" msgstr "Ομαλή κύλιση Crown" #: lib/logitech_receiver/settings_templates.py:633 msgid "Set crown smooth scroll" msgstr "Ρύθμιση ομαλής κύλισης Crown" #: lib/logitech_receiver/settings_templates.py:641 msgid "Divert G and M Keys" msgstr "Εκτροπή πλήκτρων G και 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 "" "Κάντε τα πλήκτρα G και M να στέλνουν ειδοποιήσεις HID++ (οι οποίες " "ενεργοποιούν τους κανόνες Solaar αλλά αγνοούνται κατά τα άλλα)." #: lib/logitech_receiver/settings_templates.py:656 msgid "Scroll Wheel Ratcheted" msgstr "Τροχός κύλισης Αγκιστρωμένος" #: lib/logitech_receiver/settings_templates.py:657 msgid "" "Switch the mouse wheel between speed-controlled ratcheting and always " "freespin." msgstr "" "Αλλάξτε τον τροχό του ποντικιού μεταξύ ρυθμιζόμενης ταχύτητας Αγκιστρωμένου " "και πάντα ελεύθερης περιστροφής." #: lib/logitech_receiver/settings_templates.py:659 msgid "Freespinning" msgstr "Ελεύθερη περιστροφή" #: lib/logitech_receiver/settings_templates.py:659 msgid "Ratcheted" msgstr "Αγκιστρωμένη" #: lib/logitech_receiver/settings_templates.py:666 msgid "Scroll Wheel Ratchet Speed" msgstr "Ταχύτητα Αγκιστρωμένου Τροχού Κύλισης" #: 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 "" "Χρησιμοποιήστε την ταχύτητα του τροχού του ποντικιού για να εναλλάσσεστε " "ανάμεσα σε Αγκιστρωμένου και ελεύθερη περιστροφή.\n" "Ο τροχός του ποντικιού είναι πάντα ρυθμισμένος στο 50." #: lib/logitech_receiver/settings_templates.py:717 msgid "Key/Button Actions" msgstr "Ενέργειες πλήκτρων/κουμπιών" #: lib/logitech_receiver/settings_templates.py:719 msgid "Change the action for the key or button." msgstr "Αλλαγή της ενέργειας για το πλήκτρο ή το κουμπί." #: lib/logitech_receiver/settings_templates.py:721 msgid "Overridden by diversion." msgstr "Παρακάμπτεται από εκτροπή." #: 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 "" "Η αλλαγή σημαντικών ενεργειών (όπως για το αριστερό κουμπί του ποντικιού) " "μπορεί να οδηγήσει σε ένα σύστημα που δεν μπορεί να χρησιμοποιηθεί." #: lib/logitech_receiver/settings_templates.py:886 msgid "Key/Button Diversion" msgstr "Εκτροπή πλήκτρων/κουμπιών" #: 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 "" "Κάντε το πλήκτρο ή το κουμπί να στέλνει ειδοποιήσεις HID++ (εκτροπή) ή να " "ξεκινάει χειρονομίες ποντικιού ή συρόμενο DPI" #: 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:890 #: lib/logitech_receiver/settings_templates.py:891 msgid "Mouse Gestures" msgstr "Χειρονομίες ποντικιού" #: 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" #: lib/logitech_receiver/settings_templates.py:957 msgid "Sensitivity (DPI)" msgstr "Ευαισθησία (DPI)" #: lib/logitech_receiver/settings_templates.py:997 msgid "Sensitivity Switching" msgstr "Εναλλαγή ευαισθησίας" #: 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 "" "Εναλλαγή της τρέχουσας ευαισθησίας και της ευαισθησίας που έχει " "απομνημονευθεί όταν πατηθεί το πλήκτρο ή το κουμπί.\n" "Εάν δεν υπάρχει απομνημονευμένη ευαισθησία, απλά θυμηθείτε την τρέχουσα " "ευαισθησία" #: lib/logitech_receiver/settings_templates.py:1003 msgid "Off" msgstr "Απενεργοποίηση" #: lib/logitech_receiver/settings_templates.py:1034 msgid "Disable keys" msgstr "Απενεργοποίηση πλήκτρων" #: lib/logitech_receiver/settings_templates.py:1035 msgid "Disable specific keyboard keys." msgstr "Απενεργοποίηση συγκεκριμένων πλήκτρων πληκτρολογίου." #: lib/logitech_receiver/settings_templates.py:1038 #, python-format msgid "Disables the %s key." msgstr "Απενεργοποίηση του πλήκτρου %s." #: lib/logitech_receiver/settings_templates.py:1051 #: lib/logitech_receiver/settings_templates.py:1108 msgid "Set OS" msgstr "Ορισμός λειτουργικού συστήματος" #: 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:1121 msgid "Change Host" msgstr "Αλλαγή κεντρικού υπολογιστή" #: lib/logitech_receiver/settings_templates.py:1122 msgid "Switch connection to a different host" msgstr "Αλλαγή σύνδεσης σε διαφορετικό κεντρικό υπολογιστή" #: lib/logitech_receiver/settings_templates.py:1146 msgid "Performs a left click." msgstr "Πραγματοποιεί αριστερό κλικ." #: lib/logitech_receiver/settings_templates.py:1146 msgid "Single tap" msgstr "Απλό πάτημα" #: lib/logitech_receiver/settings_templates.py:1147 msgid "Performs a right click." msgstr "Πραγματοποιεί δεξί κλικ." #: lib/logitech_receiver/settings_templates.py:1147 msgid "Single tap with two fingers" msgstr "Απλό πάτημα με δύο δάχτυλα" #: lib/logitech_receiver/settings_templates.py:1148 msgid "Single tap with three fingers" msgstr "Απλό πάτημα με τρία δάχτυλα" #: lib/logitech_receiver/settings_templates.py:1152 msgid "Double tap" msgstr "Διπλό πάτημα" #: lib/logitech_receiver/settings_templates.py:1152 msgid "Performs a double click." msgstr "Πραγματοποιεί διπλό κλικ." #: lib/logitech_receiver/settings_templates.py:1153 msgid "Double tap with two fingers" msgstr "Διπλό πάτημα με δύο δάχτυλα" #: lib/logitech_receiver/settings_templates.py:1154 msgid "Double tap with three fingers" msgstr "Διπλό πάτημα με τρία δάχτυλα" #: lib/logitech_receiver/settings_templates.py:1157 msgid "Drags items by dragging the finger after double tapping." msgstr "Σύρετε αντικείμενα σύροντας το δάχτυλο μετά από διπλό πάτημα." #: lib/logitech_receiver/settings_templates.py:1157 msgid "Tap and drag" msgstr "Πάτημα και σύρσιμο" #: lib/logitech_receiver/settings_templates.py:1159 msgid "Tap and drag with two fingers" msgstr "Πάτημα και σύρσιμο με δύο δάχτυλα" #: 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: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:1168 msgid "Scroll with one finger" msgstr "Κύλιση με ένα δάχτυλο" #: 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:1169 #: lib/logitech_receiver/settings_templates.py:1172 msgid "Scroll with two fingers" msgstr "Κύλιση με δύο δάχτυλα" #: lib/logitech_receiver/settings_templates.py:1170 msgid "Scroll horizontally with two fingers" msgstr "Οριζόντια κύλιση με δύο δάχτυλα" #: lib/logitech_receiver/settings_templates.py:1170 msgid "Scrolls horizontally." msgstr "Κύλιση οριζόντια." #: lib/logitech_receiver/settings_templates.py:1171 msgid "Scroll vertically with two fingers" msgstr "Κατακόρυφη κύλιση με δύο δάχτυλα" #: lib/logitech_receiver/settings_templates.py:1171 msgid "Scrolls vertically." msgstr "Κατακόρυφη Κύλιση." #: lib/logitech_receiver/settings_templates.py:1173 msgid "Inverts the scrolling direction." msgstr "Αναστροφή της κατεύθυνσης κύλισης." #: lib/logitech_receiver/settings_templates.py:1173 msgid "Natural scrolling" msgstr "Φυσική κύλιση" #: lib/logitech_receiver/settings_templates.py:1174 msgid "Enables the thumbwheel." msgstr "Ενεργοποίηση του τροχού αντίχειρα." #: lib/logitech_receiver/settings_templates.py:1174 msgid "Thumbwheel" msgstr "Τροχός Αντίχειρα" #: 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:1186 msgid "Swipe from the left edge" msgstr "Σύρσιμο από την αριστερή άκρη" #: lib/logitech_receiver/settings_templates.py:1187 msgid "Swipe from the right edge" msgstr "Σύρσιμο από τη δεξιά άκρη" #: lib/logitech_receiver/settings_templates.py:1188 msgid "Swipe from the bottom edge" msgstr "Σύρσιμο από την κάτω άκρη" #: lib/logitech_receiver/settings_templates.py:1190 msgid "Swipe two fingers from the left edge" msgstr "Σύρσιμο δύο δάχτυλων από την αριστερή άκρη" #: lib/logitech_receiver/settings_templates.py:1191 msgid "Swipe two fingers from the right edge" msgstr "Σύρσιμο δύο δάχτυλων από τη δεξιά άκρη" #: lib/logitech_receiver/settings_templates.py:1192 msgid "Swipe two fingers from the bottom edge" msgstr "Σύρσιμο δύο δάχτυλων από την κάτω άκρη" #: lib/logitech_receiver/settings_templates.py:1193 msgid "Swipe two fingers from the top edge" msgstr "Σύρσιμο δύο δάχτυλων από την επάνω άκρη" #: 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:1194 msgid "Zoom with two fingers." msgstr "Ζουμ με δύο δάχτυλα." #: lib/logitech_receiver/settings_templates.py:1195 msgid "Pinch to zoom out." msgstr "Τσιμπήστε για σμίκρυνση." #: lib/logitech_receiver/settings_templates.py:1196 msgid "Spread to zoom in." msgstr "Απλώστε για μεγέθυνση." #: lib/logitech_receiver/settings_templates.py:1197 msgid "Zoom with three fingers." msgstr "Ζουμ με τρία δάχτυλα." #: lib/logitech_receiver/settings_templates.py:1198 msgid "Zoom with two fingers" msgstr "Ζουμ με δύο δάχτυλα" #: lib/logitech_receiver/settings_templates.py:1216 msgid "Pixel zone" msgstr "Ζώνη pixel" #: lib/logitech_receiver/settings_templates.py:1217 msgid "Ratio zone" msgstr "Ζώνη αναλογίας" #: lib/logitech_receiver/settings_templates.py:1218 msgid "Scale factor" msgstr "Συντελεστής κλίμακας" #: lib/logitech_receiver/settings_templates.py:1218 msgid "Sets the cursor speed." msgstr "Ορίζει την ταχύτητα του δρομέα." #: lib/logitech_receiver/settings_templates.py:1222 msgid "Left" msgstr "Αριστερά" #: lib/logitech_receiver/settings_templates.py:1222 msgid "Left-most coordinate." msgstr "Αριστερότερη συντεταγμένη." #: lib/logitech_receiver/settings_templates.py:1223 msgid "Bottom" msgstr "Κάτω" #: lib/logitech_receiver/settings_templates.py:1223 msgid "Bottom coordinate." msgstr "Κάτωθεν συντεταγμένη." #: lib/logitech_receiver/settings_templates.py:1224 msgid "Width" msgstr "Πλάτος" #: lib/logitech_receiver/settings_templates.py:1224 msgid "Width." msgstr "Πλάτος." #: lib/logitech_receiver/settings_templates.py:1225 msgid "Height" msgstr "Ύψος" #: lib/logitech_receiver/settings_templates.py:1225 msgid "Height." msgstr "Ύψος." #: lib/logitech_receiver/settings_templates.py:1226 msgid "Cursor speed." msgstr "Ταχύτητα δρομέα." #: lib/logitech_receiver/settings_templates.py:1226 msgid "Scale" msgstr "Κλίμακα" #: lib/logitech_receiver/settings_templates.py:1232 msgid "Gestures" msgstr "Χειρονομίες" #: lib/logitech_receiver/settings_templates.py:1233 msgid "Tweak the mouse/touchpad behaviour." msgstr "Ρύθμιση της συμπεριφοράς του ποντικιού/του touchpad." #: lib/logitech_receiver/settings_templates.py:1249 msgid "Gestures Diversion" msgstr "Χειρονομίες εκτροπής" #: lib/logitech_receiver/settings_templates.py:1250 msgid "Divert mouse/touchpad gestures." msgstr "Εκτροπή χειρονομιών του ποντικιού/του touchpad." #: lib/logitech_receiver/settings_templates.py:1266 msgid "Gesture params" msgstr "Παράμετροι χειρονομιών" #: lib/logitech_receiver/settings_templates.py:1267 msgid "Change numerical parameters of a mouse/touchpad." msgstr "Αλλαγή των αριθμητικών παραμέτρων ενός ποντικιού/του 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 "Έλεγχος των λυχνιών 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 "" "Μπορεί να χρειαστεί εκτροπή των πλήκτρων G για να είναι αποτελεσματική." #: lib/logitech_receiver/settings_templates.py:1303 #, python-format msgid "Lights up the %s key." msgstr "Ανάβει το πλήκτρο %s." #: lib/logitech_receiver/settings_templates.py:1322 msgid "MR-Key LED" msgstr "MR-Πλήκτρο LED" #: lib/logitech_receiver/settings_templates.py:1324 msgid "Control the MR-Key LED." msgstr "Έλεγχος της λυχνίας LED πλήκτρου MR." #: lib/logitech_receiver/settings_templates.py:1345 msgid "Persistent Key/Button Mapping" msgstr "Μόνιμη αντιστοίχιση πλήκτρων/κουμπιών" #: lib/logitech_receiver/settings_templates.py:1347 msgid "Permanently change the mapping for the key or button." msgstr "Μόνιμη αλλαγή της αντιστοίχισης για το πλήκτρο ή το κουμπί." #: 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 "" "Η αλλαγή σημαντικών πλήκτρων ή κουμπιών (όπως για παράδειγμα του αριστερού " "κουμπιού του ποντικιού) μπορεί να οδηγήσει σε ένα άχρηστο σύστημα." #: lib/logitech_receiver/settings_templates.py:1406 msgid "Sidetone" msgstr "Sidetone" #: lib/logitech_receiver/settings_templates.py:1407 msgid "Set sidetone level." msgstr "Ορισμός επιπέδου sidetone." #: lib/logitech_receiver/settings_templates.py:1416 msgid "Equalizer" msgstr "Ισοσταθμιστής" #: lib/logitech_receiver/settings_templates.py:1417 msgid "Set equalizer levels." msgstr "Ρύθμιση επιπέδων ισοσταθμιστή." #: lib/logitech_receiver/settings_templates.py:1439 msgid "Hz" msgstr "Hz" #: lib/logitech_receiver/settings_templates.py:1445 msgid "Power Management" msgstr "Διαχείριση ενέργειας" #: lib/logitech_receiver/settings_templates.py:1446 msgid "Power off in minutes (0 for never)." msgstr "Απενεργοποίηση σε λεπτά (0 για ποτέ)." #: lib/logitech_receiver/settings_templates.py:1457 msgid "LED Control" msgstr "Έλεγχος LED" #: lib/logitech_receiver/settings_templates.py:1458 msgid "Switch control of LEDs between device and Solaar" msgstr "Εναλλαγή ελέγχου των LED μεταξύ συσκευής και Solaar" #: lib/logitech_receiver/settings_templates.py:1473 msgid "LED Zone Effects" msgstr "Εφέ ζώνης LED" #: lib/logitech_receiver/settings_templates.py:1474 msgid "Set effect for LED Zone" msgstr "Ρύθμιση εφέ για τη ζώνη LED" #: lib/logitech_receiver/settings_templates.py:1477 msgid "Speed" msgstr "Ταχύτητα" #: lib/logitech_receiver/settings_templates.py:1478 msgid "Period" msgstr "Περίοδος" #: lib/logitech_receiver/settings_templates.py:1479 msgid "Intensity" msgstr "Ένταση" #: 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/common.py:37 #, 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: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 "" "Βρέθηκε ένας δέκτης ή μια συσκευή Logitech στη διεύθυνση %s, αλλά " "παρουσιάστηκε σφάλμα σύνδεσης." #: 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:231 msgid "Complete - ENTER to change" msgstr "Ολοκλήρωση - ENTER για αλλαγή" #: lib/solaar/ui/config_panel.py:231 msgid "Incomplete" msgstr "Ελλιπής" #: 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:612 msgid "Changes allowed" msgstr "Επιτρεπτές αλλαγές" #: lib/solaar/ui/config_panel.py:613 msgid "No changes allowed" msgstr "Μη επιτρεπτές αλλαγές" #: lib/solaar/ui/config_panel.py:614 msgid "Ignore this setting" msgstr "Αγνόηση αυτής της ρύθμισης" #: lib/solaar/ui/config_panel.py:659 msgid "Working" msgstr "Εργάζεται" #: lib/solaar/ui/config_panel.py:662 msgid "Read/write operation failed." msgstr "Αποτυχία λειτουργίας ανάγνωσης/εγγραφής." #: lib/solaar/ui/diversion_rules.py:66 msgid "Built-in rules" msgstr "Ενσωματωμένοι κανόνες" #: lib/solaar/ui/diversion_rules.py:66 msgid "User-defined rules" msgstr "Κανόνες που ορίζονται από το χρήστη" #: lib/solaar/ui/diversion_rules.py:68 lib/solaar/ui/diversion_rules.py:1089 msgid "Rule" msgstr "Κανόνας" #: 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:71 msgid "[empty]" msgstr "[κενό]" #: lib/solaar/ui/diversion_rules.py:95 msgid "Make changes permanent?" msgstr "Να κάνετε τις αλλαγές μόνιμες;" #: lib/solaar/ui/diversion_rules.py:100 msgid "Yes" msgstr "Ναι" #: lib/solaar/ui/diversion_rules.py:102 msgid "No" msgstr "Όχι" #: 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:138 msgid "Solaar Rule Editor" msgstr "Συντάκτης κανόνων Solaar" #: lib/solaar/ui/diversion_rules.py:231 msgid "Save changes" msgstr "Αποθήκευση αλλαγών" #: lib/solaar/ui/diversion_rules.py:236 msgid "Discard changes" msgstr "Απόρριψη αλλαγών" #: lib/solaar/ui/diversion_rules.py:397 msgid "Insert here" msgstr "Εισαγωγή εδώ" #: lib/solaar/ui/diversion_rules.py:399 msgid "Insert above" msgstr "Εισαγωγή επάνω" #: lib/solaar/ui/diversion_rules.py:401 msgid "Insert below" msgstr "Εισαγωγή από κάτω" #: lib/solaar/ui/diversion_rules.py:407 msgid "Insert new rule here" msgstr "Εισαγωγή νέου κανόνα εδώ" #: lib/solaar/ui/diversion_rules.py:409 msgid "Insert new rule above" msgstr "Εισαγωγή νέου κανόνα επάνω" #: lib/solaar/ui/diversion_rules.py:411 msgid "Insert new rule below" msgstr "Εισαγωγή νέου κανόνα από κάτω" #: lib/solaar/ui/diversion_rules.py:456 msgid "Paste here" msgstr "Επικόλληση εδώ" #: lib/solaar/ui/diversion_rules.py:458 msgid "Paste above" msgstr "Επικόλληση επάνω" #: lib/solaar/ui/diversion_rules.py:460 msgid "Paste below" msgstr "Επικόλληση από κάτω" #: lib/solaar/ui/diversion_rules.py:466 msgid "Paste rule here" msgstr "Επικόλληση κανόνα εδώ" #: lib/solaar/ui/diversion_rules.py:468 msgid "Paste rule above" msgstr "Επικόλληση κανόνα επάνω" #: lib/solaar/ui/diversion_rules.py:470 msgid "Paste rule below" msgstr "Επικόλληση κανόνα από κάτω" #: lib/solaar/ui/diversion_rules.py:474 msgid "Paste rule" msgstr "Επικόλληση κανόνα" #: lib/solaar/ui/diversion_rules.py:503 msgid "Flatten" msgstr "Ισοπέδωση" #: lib/solaar/ui/diversion_rules.py:536 msgid "Insert" msgstr "Εισαγωγή" #: 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:540 lib/solaar/ui/diversion_rules.py:666 #: lib/solaar/ui/diversion_rules.py:1115 msgid "And" msgstr "Και" #: lib/solaar/ui/diversion_rules.py:542 msgid "Condition" msgstr "Συνθήκη" #: lib/solaar/ui/diversion_rules.py:544 lib/solaar/ui/diversion_rules.py:1290 msgid "Feature" msgstr "Χαρακτηριστικό" #: lib/solaar/ui/diversion_rules.py:545 lib/solaar/ui/diversion_rules.py:1325 msgid "Report" msgstr "Αναφορά" #: lib/solaar/ui/diversion_rules.py:546 lib/solaar/ui/diversion_rules.py:1204 msgid "Process" msgstr "Διεργασία" #: lib/solaar/ui/diversion_rules.py:547 msgid "Mouse process" msgstr "Διεργασία ποντικιού" #: lib/solaar/ui/diversion_rules.py:548 lib/solaar/ui/diversion_rules.py:1362 msgid "Modifiers" msgstr "Τροποποιητικοί Παράγοντες" #: lib/solaar/ui/diversion_rules.py:549 lib/solaar/ui/diversion_rules.py:1414 msgid "Key" msgstr "Πλήκτρο" #: 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 "Ενεργό" #: 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: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: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 "Δοκιμαστικά bytes" #: lib/solaar/ui/diversion_rules.py:557 lib/solaar/ui/diversion_rules.py:1734 msgid "Mouse Gesture" msgstr "Χειρονομία ποντικιού" #: lib/solaar/ui/diversion_rules.py:561 msgid "Action" msgstr "Ενέργεια" #: lib/solaar/ui/diversion_rules.py:563 lib/solaar/ui/diversion_rules.py:1844 msgid "Key press" msgstr "Πάτημα πλήκτρου" #: lib/solaar/ui/diversion_rules.py:564 lib/solaar/ui/diversion_rules.py:1895 msgid "Mouse scroll" msgstr "Κύλιση ποντικιού" #: lib/solaar/ui/diversion_rules.py:565 lib/solaar/ui/diversion_rules.py:1956 msgid "Mouse click" msgstr "Κλικ ποντικιού" #: lib/solaar/ui/diversion_rules.py:566 msgid "Set" msgstr "Ορισμός" #: lib/solaar/ui/diversion_rules.py:567 lib/solaar/ui/diversion_rules.py:2026 msgid "Execute" msgstr "Εκτέλεση" #: lib/solaar/ui/diversion_rules.py:568 lib/solaar/ui/diversion_rules.py:1160 msgid "Later" msgstr "Αργότερα" #: lib/solaar/ui/diversion_rules.py:597 msgid "Insert new rule" msgstr "Εισαγωγή νέου κανόνα" #: 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:639 msgid "Negate" msgstr "Αρνηση" #: lib/solaar/ui/diversion_rules.py:663 msgid "Wrap with" msgstr "Τύλιγμα με" #: lib/solaar/ui/diversion_rules.py:685 msgid "Cut" msgstr "Αποκοπή" #: lib/solaar/ui/diversion_rules.py:700 msgid "Paste" msgstr "Επικόλληση" #: lib/solaar/ui/diversion_rules.py:706 msgid "Copy" msgstr "Αντιγραφή" #: 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:1140 msgid "Number of seconds to delay." msgstr "Αριθμός δευτερολέπτων καθυστέρησης." #: lib/solaar/ui/diversion_rules.py:1178 msgid "Not" msgstr "Μη" #: lib/solaar/ui/diversion_rules.py:1187 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:1258 msgid "Feature name of notification triggering rule processing." msgstr "" "Όνομα χαρακτηριστικού της ειδοποίησης που ενεργοποιεί την επεξεργασία κανόνα." #: lib/solaar/ui/diversion_rules.py:1305 msgid "Report number of notification triggering rule processing." msgstr "" "Αριθμός αναφοράς της ειδοποίησης που ενεργοποιεί την επεξεργασία του κανόνα." #: lib/solaar/ui/diversion_rules.py:1338 msgid "Active keyboard modifiers. Not always available in Wayland." msgstr "" "Ενεργοί τροποποιούμενοι παράγοντες πληκτρολογίου. Δεν είναι πάντα διαθέσιμο " "στο 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 "" "Εκτροπή πλήκτρου ή κουμπιού πατημένο ή απελευθερωμένο.\n" "Χρησιμοποιήστε τις ρυθμίσεις εκτροπής πλήκτρων/κουμπιών και εκτροπής " "πλήκτρων G για να εκτρέψετε πλήκτρα και κουμπιά." #: lib/solaar/ui/diversion_rules.py:1387 msgid "Key down" msgstr "Πλήκτρο κάτω" #: lib/solaar/ui/diversion_rules.py:1390 msgid "Key up" msgstr "Πλήκτρο πάνω" #: 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 "" "Το πλήκτρο ή το κουμπί εκτροπής είναι προς το παρόν κατεβασμένο.\n" "Χρησιμοποιήστε τις ρυθμίσεις εκτροπής πλήκτρων/κουμπιών και εκτροπής " "πλήκτρων G για να εκτρέψετε πλήκτρα και κουμπιά." #: lib/solaar/ui/diversion_rules.py:1468 msgid "Test condition on notification triggering rule processing." msgstr "" "Συνθήκη δοκιμής για την ειδοποίηση που ενεργοποιεί την επεξεργασία κανόνα." #: lib/solaar/ui/diversion_rules.py:1472 msgid "Parameter" msgstr "Παράμετρος" #: lib/solaar/ui/diversion_rules.py:1534 msgid "begin (inclusive)" msgstr "αρχή ( συμπεριλαμβανομένου)" #: lib/solaar/ui/diversion_rules.py:1535 msgid "end (exclusive)" msgstr "τέλος ( αποκλειστικό)" #: lib/solaar/ui/diversion_rules.py:1543 msgid "range" msgstr "εύρος" #: lib/solaar/ui/diversion_rules.py:1546 msgid "minimum" msgstr "ελάχιστο" #: lib/solaar/ui/diversion_rules.py:1547 msgid "maximum" msgstr "μέγιστο" #: 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: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 "bytes %(0)d έως %(1)d, μάσκα %(2)d" #: lib/solaar/ui/diversion_rules.py:1563 msgid "" "Bit or range test on bytes in notification message triggering rule " "processing." msgstr "" "Δοκιμή bit ή εύρους σε bytes στο μήνυμα ειδοποίησης που ενεργοποιεί την " "επεξεργασία κανόνα." #: lib/solaar/ui/diversion_rules.py:1573 msgid "type" msgstr "τύπος" #: lib/solaar/ui/diversion_rules.py:1661 msgid "" "Mouse gesture with optional initiating button followed by zero or more mouse " "movements." msgstr "" "Χειρονομία ποντικιού με προαιρετικό κουμπί εκκίνησης που ακολουθείται από " "μηδέν ή περισσότερες κινήσεις του ποντικιού." #: lib/solaar/ui/diversion_rules.py:1666 msgid "Add movement" msgstr "Προσθήκη κίνησης" #: 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 "" "Προσομοίωση ενός χορδισμένου πλήκτρου κλικ ή πάτημα ή απελευθέρωση.\n" "Στο Wayland απαιτεί πρόσβαση εγγραφής στο /dev/uinput." #: lib/solaar/ui/diversion_rules.py:1765 msgid "Add key" msgstr "Προσθήκη πλήκτρου" #: lib/solaar/ui/diversion_rules.py:1768 msgid "Click" msgstr "Κλικ" #: lib/solaar/ui/diversion_rules.py:1771 msgid "Depress" msgstr "Απελευθέρωση" #: lib/solaar/ui/diversion_rules.py:1774 msgid "Release" msgstr "Αποδέσμευση" #: lib/solaar/ui/diversion_rules.py:1859 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:1915 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:1918 msgid "Button" msgstr "Κουμπί" #: lib/solaar/ui/diversion_rules.py:1919 msgid "Count and Action" msgstr "Καταμέτρηση και Δράση" #: lib/solaar/ui/diversion_rules.py:1968 msgid "Execute a command with arguments." msgstr "Εκτέλεση μιας εντολής με ορίσματα." #: lib/solaar/ui/diversion_rules.py:1971 msgid "Add argument" msgstr "Προσθήκη ορίσματος" #: lib/solaar/ui/diversion_rules.py:2046 msgid "Toggle" msgstr "Εναλλαγή" #: 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 "Μη υποστηριζόμενη ρύθμιση" #: 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:2256 msgid "Device is active and its settings can be changed." msgstr "Η συσκευή είναι ενεργή και οι ρυθμίσεις της μπορούν να αλλάξουν." #: lib/solaar/ui/diversion_rules.py:2265 msgid "Device that originated the current notification." msgstr "Συσκευή από την οποία προήλθε η τρέχουσα ειδοποίηση." #: 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:2353 msgid "Item" msgstr "Στοιχείο" #: lib/solaar/ui/diversion_rules.py:2627 msgid "Change setting on device" msgstr "Αλλαγή ρύθμισης στη συσκευή" #: lib/solaar/ui/diversion_rules.py:2643 msgid "Setting on device" msgstr "Ρύθμιση στη συσκευή" #: lib/solaar/ui/notify.py:118 msgid "unspecified reason" msgstr "αδιευκρίνιστος λόγος" #: 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:127 #, python-format msgid "Enter passcode on %(name)s." msgstr "Πληκτρολογήστε τον κωδικό πρόσβασης στο %(name)s." #: 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:133 msgid "left" msgstr "αριστερά" #: lib/solaar/ui/pair_window.py:133 msgid "right" msgstr "δεξιά" #: lib/solaar/ui/pair_window.py:135 #, 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:192 msgid "Pairing failed" msgstr "Αποτυχία συνταιρίασματος" #: 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:196 msgid "A new device was detected, but it is not compatible with this receiver." msgstr "Εντοπίστηκε μια συσκευή, αλλά δεν είναι συμβατή με αυτόν τον δέκτη." #: lib/solaar/ui/pair_window.py:198 msgid "More paired devices than receiver can support." msgstr "" "Περισσότερες συζευγμένες συσκευές από όσες μπορεί να υποστηρίξει ο δέκτης." #: lib/solaar/ui/pair_window.py:200 msgid "No further details are available about the error." msgstr "Δεν υπάρχουν άλλες πληροφορίες για το σφάλμα." #: lib/solaar/ui/pair_window.py:214 msgid "Found a new device:" msgstr "Βρήκα μια νέα συσκευή:" #: lib/solaar/ui/pair_window.py:239 msgid "The wireless link is not encrypted" msgstr "Η ασύρματη σύνδεση είναι χωρίς κρυπτογράφηση" #: 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:270 msgid "Bolt receivers are only compatible with Bolt devices." msgstr "Οι δέκτες Bolt είναι συμβατοί μόνο με συσκευές Bolt." #: lib/solaar/ui/pair_window.py:272 msgid "Other receivers are only compatible with a few devices." msgstr "Άλλοι δέκτες είναι συμβατοί μόνο με λίγες συσκευές." #: 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:278 msgid "Press a pairing button or key until the pairing light flashes quickly." msgstr "" "Πιέστε ένα κουμπί ή πλήκτρο ζεύξης μέχρι να αναβοσβήσει γρήγορα η φωτεινή " "ένδειξη ζεύξης." #: 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:282 msgid "Turn on the device you want to pair." msgstr "Άνοιξε την συσκευή που θέλεις να συνταιριάξεις." #: 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: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" "Αυτός ο δέκτης έχει %d ζεύξη που απομένει." msgstr[1] "" "\n" "\n" "Αυτός ο δέκτης έχει %d ζεύξεις που απομένουν." #: lib/solaar/ui/pair_window.py:294 msgid "" "\n" "Cancelling at this point will not use up a pairing." msgstr "" "\n" "Η ακύρωση σε αυτό το σημείο δεν θα χρησιμοποιήσει μια αντιστοίχιση." #: lib/solaar/ui/tray.py:58 msgid "No supported device found" msgstr "Δεν βρέθηκε υποστηριζόμενη συσκευή" #: lib/solaar/ui/tray.py:63 lib/solaar/ui/window.py:321 #, python-format msgid "About %s" msgstr "Περί %s" #: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 #, python-format msgid "Quit %s" msgstr "Έξοδος %s" #: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:302 msgid "no receiver" msgstr "χωρίς δέκτη" #: 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:99 msgid "Scanning" msgstr "Σάρωση" #: lib/solaar/ui/window.py:132 msgid "Battery" msgstr "Μπαταρία" #: lib/solaar/ui/window.py:135 msgid "Wireless Link" msgstr "Ασύρματη ζεύξη" #: lib/solaar/ui/window.py:139 msgid "Lighting" msgstr "Φωτισμός" #: lib/solaar/ui/window.py:173 msgid "Show Technical Details" msgstr "Εμφάνιση τεχνικών λεπτομερειών" #: lib/solaar/ui/window.py:189 msgid "Pair new device" msgstr "Συνταίριασμα νέας συσκευής" #: lib/solaar/ui/window.py:207 msgid "Select a device" msgstr "Επέλεξε μια συσκευή" #: lib/solaar/ui/window.py:324 msgid "Rule Editor" msgstr "Συντάκτης κανόνων" #: lib/solaar/ui/window.py:543 msgid "Path" msgstr "Διαδρομή" #: lib/solaar/ui/window.py:546 msgid "USB ID" msgstr "USB ID" #: 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:555 msgid "Index" msgstr "Index" #: lib/solaar/ui/window.py:557 msgid "Wireless PID" msgstr "Wireless PID" #: lib/solaar/ui/window.py:559 msgid "Product ID" msgstr "Αναγνωριστικό προϊόντος" #: lib/solaar/ui/window.py:561 msgid "Protocol" msgstr "Πρωτόκολλο" #: lib/solaar/ui/window.py:561 msgid "Unknown" msgstr "Άγνωστο" #: lib/solaar/ui/window.py:563 msgid "Polling rate" msgstr "Ρυθμός ανανέωσης" #: lib/solaar/ui/window.py:570 msgid "Unit ID" msgstr "Unit ID" #: lib/solaar/ui/window.py:584 msgid "Notifications" msgstr "Ειδοποιήσεις" #: lib/solaar/ui/window.py:628 msgid "No device paired." msgstr "Δεν υπάρχει αντιστοιχισμένη συσκευή." #: 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] "" "Έως και %(max_count)s συσκευή μπορεί να συνδεθεί με αυτόν τον δέκτη." msgstr[1] "" "Έως και %(max_count)s συσκευές μπορούν να αντιστοιχιστούν σε αυτόν τον δέκτη." #: 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:705 msgid "Battery Voltage" msgstr "Τάση μπαταρίας" #: lib/solaar/ui/window.py:707 msgid "Voltage reported by battery" msgstr "Τάση που αναφέρεται από την μπαταρία" #: lib/solaar/ui/window.py:709 msgid "Battery Level" msgstr "Στάθμη μπαταρίας" #: lib/solaar/ui/window.py:711 msgid "Approximate level reported by battery" msgstr "Προσεγγιστικό επίπεδο που αναφέρεται από την μπαταρία" #: lib/solaar/ui/window.py:718 lib/solaar/ui/window.py:720 msgid "next reported " msgstr "επόμενο αναφερόμενο " #: lib/solaar/ui/window.py:721 msgid " and next level to be reported." msgstr " και το επόμενο επίπεδο που θα αναφερθεί." #: 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:741 msgid "not encrypted" msgstr "χωρίς κρυπτογράφηση" #: 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 "" "Η ασύρματη σύνδεση μεταξύ αυτής της συσκευής και του δέκτη της δεν είναι " "κρυπτογραφημένη.\n" "Αυτό είναι ένα ζήτημα ασφάλειας για τις συσκευές επισήμανσης και ένα " "σημαντικό ζήτημα ασφάλειας για τις συσκευές εισαγωγής κειμένου." #: 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." #~ msgstr "" #~ "Βρέθηκε ένας δέκτης Logitech (%s), αλλά δεν έχετε δικαίωμα να τον " #~ "ανοίξετε." #~ msgid "Found a new device" #~ msgstr "Βρέθηκε νέα συσκευή" #~ msgid "" #~ "If the device is already turned on,\n" #~ "turn if off and on again." #~ msgstr "" #~ "Αν η συσκευή είναι ήδη ανοικτή,\n" #~ "κλείσε την και μετά άνοιξε την ξανά." #~ msgid "" #~ "If you've just installed Solaar, try removing the receiver and plugging " #~ "it back in." #~ msgstr "" #~ "Εάν μόλις κάνατε εγκατάσταση του Solaar, δοκιμάστε να αφαιρέσετε τον " #~ "δέκτη και να τον ξανασυνδέσετε." #~ msgid "No Logitech device found" #~ msgstr "Δεν βρέθηκε συσκευή Logitech" #~ msgid "No Logitech receiver found" #~ msgstr "Δεν βρέθηκε δέκτης Logitech" #~ msgid "No device paired" #~ msgstr "Χωρίς συνταιριασμένες συσκευές" #~ msgid "Only one device can be paired to this receiver" #~ msgstr "Μόνο μία συσκευή μπορεί να συνταιριαστεί σε αυτόν τον δέκτη" #~ msgid "" #~ "Shows status of devices connected\n" #~ "through wireless Logitech receivers." #~ msgstr "" #~ "Δείχνει την κατάσταση των συσκευών που συνδέονται\n" #~ "ασύρματα σε δέκτες Logitech." #~ msgid "Smooth Scrolling" #~ msgstr "Ομαλή κύλιση" #, python-format #~ msgid "The receiver only supports %d paired device(s)." #~ msgstr "Ο δέκτης υποστηρίζει μέχρι %d συνταιριασμένες συσκευές." #~ msgid "The receiver was unplugged." #~ msgstr "Ο δέκτης αποσυνδέθηκε." #~ 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 "USB id" #~ msgstr "USB id" #~ msgid "Up to %d devices can be paired to this receiver" #~ msgstr "Μέχρι %d συσκευές μπορούν να συνταιριαστούν σε αυτόν τον δέκτη" #~ msgid "closed" #~ msgstr "κλειστή" #~ msgid "height" #~ msgstr "ύψος" #~ msgid "lux" #~ msgstr "lux" #~ msgid "open" #~ msgstr "ανοιχτή" #~ msgid "pair new device" #~ msgstr "συνταίριασε μια νέα συσκευή" #~ msgid "paired devices" #~ msgstr "συνταιριασμένες συσκευές" #~ msgid "pairing lock is " #~ msgstr "το κλείδωμα ταιριάσματος είναι" #~ msgid "unknown" #~ msgstr "άγνωστο" #~ msgid "width" #~ msgstr "πλάτος" Solaar-1.1.20/po/es.po000066400000000000000000001665641522022367600144300ustar00rootroot00000000000000# Spanish translations for Solaar package. # Copyright (C) 2021 THE Solaar's COPYRIGHT HOLDER. # 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: 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" #: lib/logitech_receiver/base_usb.py:57 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" #: lib/logitech_receiver/base_usb.py:124 msgid "Lightspeed Receiver" msgstr "Receptor Lightspeed" #: 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 "vacía" #: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "crítica" #: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "baja" #: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "media" #: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "buena" #: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "llena" #: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "descargando" #: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "recargando" #: 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" #: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "casi llena" #: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "cargado" #: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "recarga lenta" #: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "batería no válida" #: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "error térmico" #: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "error" #: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "estándar" #: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "rápido" #: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "lento" #: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "tiempo agotado para el dispositivo" #: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "dispositivo no soportado" #: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "demasiados dispositivos" #: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "tiempo agotado de secuencia" #: 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" #: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardware" #: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Otros" #: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "Botón Izquierdo" #: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "Botón Derecho" #: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "Botón Central" #: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "Botón Atrás" #: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "Botón Adelante" #: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "Botón Gestos Ratón" #: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "Cambio inteligente" #: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "Selector DPI" #: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "Inclinación a la Izquierda" #: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "Inclinación a la Derecha" #: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "Clic Izquierdo" #: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "Clic Derecho" #: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "Clic Central Ratón" #: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "Clic Atrás Ratón" #: lib/logitech_receiver/i18n.py:79 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" #: lib/logitech_receiver/i18n.py:81 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 de desplazamiento a la Derecha" #: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "pulsado" #: lib/logitech_receiver/i18n.py:86 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" #: 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" #: lib/logitech_receiver/notifications.py:92 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" #: 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" #: 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 "encendido" #: 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 "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." #: 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." #: lib/logitech_receiver/settings_templates.py:149 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." #: lib/logitech_receiver/settings_templates.py:157 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." #: lib/logitech_receiver/settings_templates.py:165 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." #: lib/logitech_receiver/settings_templates.py:177 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: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" #: 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." #: lib/logitech_receiver/settings_templates.py:219 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 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" #: lib/logitech_receiver/settings_templates.py:247 #: lib/logitech_receiver/settings_templates.py:277 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 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 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." #: lib/logitech_receiver/settings_templates.py:265 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 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)" #: 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)" #: lib/logitech_receiver/settings_templates.py:299 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 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" #: 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." #: lib/logitech_receiver/settings_templates.py:319 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" #: lib/logitech_receiver/settings_templates.py:330 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" #: 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 "" "Es posible que los Perfiles integrados se desactiven para que sean efectivos." #: lib/logitech_receiver/settings_templates.py:365 msgid "Divert crown events" msgstr "Desviar eventos 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 "" "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:374 msgid "Crown smooth scroll" msgstr "Desplazamiento suave de la corona" #: 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 "Divert G Keys" msgstr "Desviar Teclas 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 "" "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: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 "Scroll Wheel Ratcheted" msgstr "Rueda de desplazamiento con carraca" #: 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:405 msgid "Freespinning" msgstr "Giro libre" #: lib/logitech_receiver/settings_templates.py:405 msgid "Ratcheted" msgstr "Carraca" #: 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: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 "Key/Button Actions" msgstr "Acciones 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:465 msgid "Overridden by diversion." msgstr "Anulado por la diversión." #: 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:639 msgid "Key/Button Diversion" msgstr "Desvio de Tecla/Botón" #: 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: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:643 #: lib/logitech_receiver/settings_templates.py:644 msgid "Mouse Gestures" msgstr "Gestos de Ratón" #: 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 "DPI deslizante" #: lib/logitech_receiver/settings_templates.py:711 msgid "Sensitivity (DPI)" msgstr "Sensibilidad (PPP)" #: lib/logitech_receiver/settings_templates.py:752 msgid "Sensitivity Switching" msgstr "Cambio de sensibilidad" #: 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: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." #: 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: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:873 msgid "Change Host" msgstr "Cambiar Equipo" #: 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 "Performs a left click." msgstr "Realiza un clic izquierdo." #: lib/logitech_receiver/settings_templates.py:900 msgid "Single tap" msgstr "Un toque" #: lib/logitech_receiver/settings_templates.py:901 msgid "Performs a right click." msgstr "Realiza un click derecho." #: lib/logitech_receiver/settings_templates.py:901 msgid "Single tap with two fingers" msgstr "Un toque con dos dedos" #: lib/logitech_receiver/settings_templates.py:902 msgid "Single tap with three fingers" msgstr "Un toque con tres dedos" #: lib/logitech_receiver/settings_templates.py:906 msgid "Double tap" msgstr "Doble toque" #: lib/logitech_receiver/settings_templates.py:906 msgid "Performs a double click." msgstr "Realiza un click doble." #: lib/logitech_receiver/settings_templates.py:907 msgid "Double tap with two fingers" msgstr "Doble toque con dos dedos" #: 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 "Scrolls." msgstr "Desplazamiento." #: lib/logitech_receiver/settings_templates.py:919 #: 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 "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 "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 "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 "Enables the thumbwheel." msgstr "Activa la rueda del pulgar." #: lib/logitech_receiver/settings_templates.py:924 msgid "Thumbwheel" msgstr "Rueda del pulgar" #: 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:936 msgid "Swipe from the left edge" msgstr "Deslizar desde el borde izquierdo" #: 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:938 msgid "Swipe from the bottom edge" msgstr "Deslizar desde el borde inferior" #: 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:941 msgid "Swipe two fingers from the right edge" msgstr "Deslizar dos dedos desde el borde derecho" #: 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:943 msgid "Swipe two fingers from the top edge" msgstr "Deslizar dos dedos desde el borde 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 "Pellizcar para alejar; Extender para acercar." #: lib/logitech_receiver/settings_templates.py:944 msgid "Zoom with two fingers." msgstr "Zoom con dos dedos." #: lib/logitech_receiver/settings_templates.py:945 msgid "Pinch to zoom out." msgstr "Pellizcar para alejar." #: lib/logitech_receiver/settings_templates.py:946 msgid "Spread to zoom in." msgstr "Extender para acercar." #: lib/logitech_receiver/settings_templates.py:947 msgid "Zoom with three fingers." msgstr "Zoom con tres dedos." #: 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 "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" msgstr "Gestos" #: lib/logitech_receiver/settings_templates.py:983 msgid "Tweak the mouse/touchpad behaviour." msgstr "Modificar el comportamiento del mouse/panel táctil." #: lib/logitech_receiver/settings_templates.py:1000 msgid "Gestures Diversion" msgstr "Desvío de gestos" #: 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:1018 msgid "Gesture params" msgstr "Parámetros de gestos" #: 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: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:1074 msgid "MR-Key LED" msgstr "MR-Key LED" #: 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:1095 msgid "Persistent Key/Button Mapping" msgstr "Asignación persistente de teclas/botones" #: 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: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:1157 msgid "Sidetone" msgstr "Tono lateral" #: lib/logitech_receiver/settings_templates.py:1158 msgid "Set sidetone level." msgstr "Establezca el nivel de tono lateral." #: lib/logitech_receiver/settings_templates.py:1167 msgid "Equalizer" msgstr "Ecualizador" #: 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." #: lib/logitech_receiver/status.py:170 #, python-format msgid "Battery: %(level)s" msgstr "Batería: %(level)s" #: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "Batería: %(percent)d%%" #: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "Iluminación: %(level)s lux" #: lib/logitech_receiver/status.py:239 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "Batería: %(level)s (%(status)s)" #: lib/logitech_receiver/status.py:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Batería: %(percent)d%% (%(status)s)" #: lib/solaar/ui/__init__.py:52 msgid "Permissions error" msgstr "Error de permisos" #: 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 "" "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 #, 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: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:64 msgid "Unpairing failed" msgstr "Desvinculación fallida" #: lib/solaar/ui/__init__.py:66 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Fallo al desvincular %{device} de %{receiver}." #: 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." #: lib/solaar/ui/about.py:44 msgid "Additional Programming" msgstr "Programación adicional" #: lib/solaar/ui/about.py:45 msgid "GUI design" msgstr "Diseño de la interfaz gráfica" #: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "Prueba" #: lib/solaar/ui/about.py:54 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:197 msgid "Unpair" msgstr "Desvincular" #: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "Cancelar" #: lib/solaar/ui/config_panel.py:212 msgid "Complete - ENTER to change" msgstr "Completa - ENTER para cambiar" #: lib/solaar/ui/config_panel.py:212 msgid "Incomplete" msgstr "Incompleta" #: 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" #: lib/solaar/ui/config_panel.py:518 msgid "Changes allowed" msgstr "Cambios permitidos" #: lib/solaar/ui/config_panel.py:519 msgid "No changes allowed" msgstr "Cambios no permitidos" #: lib/solaar/ui/config_panel.py:520 msgid "Ignore this setting" msgstr "Ignorar esta opción" #: lib/solaar/ui/config_panel.py:565 msgid "Working" msgstr "Funcionando" #: 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:65 msgid "Built-in rules" msgstr "Reglas integradas" #: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" msgstr "Reglas definidas por el usuario" #: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "Regla" #: 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:70 msgid "[empty]" msgstr "[vacío]" #: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "Editor de Reglas Solaar" #: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "¿Hacer los cambios permanentes?" #: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Sí" #: lib/solaar/ui/diversion_rules.py:148 msgid "No" msgstr "No" #: 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:201 msgid "Save changes" msgstr "Guardar cambios" #: lib/solaar/ui/diversion_rules.py:206 msgid "Discard changes" msgstr "Descartar cambios" #: lib/solaar/ui/diversion_rules.py:372 msgid "Insert here" msgstr "Insertar aquí" #: lib/solaar/ui/diversion_rules.py:374 msgid "Insert above" msgstr "Insertar encima" #: lib/solaar/ui/diversion_rules.py:376 msgid "Insert below" msgstr "Insertar debajo" #: lib/solaar/ui/diversion_rules.py:382 msgid "Insert new rule here" msgstr "Insertar nueva regla aquí" #: lib/solaar/ui/diversion_rules.py:384 msgid "Insert new rule above" msgstr "Insertar nueva regla encima" #: 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 here" msgstr "Pegar aquí" #: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "Pegar encima" #: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "Pegar debajo" #: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "Pegar regla aquí" #: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "Pegar regla encima" #: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "Pegar regla debajo" #: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "Pegar regla" #: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "Aplanar" #: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "Insertar" #: 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 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 msgid "Condition" msgstr "Condición" #: 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:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "Informar" #: lib/solaar/ui/diversion_rules.py:517 lib/solaar/ui/diversion_rules.py:1204 msgid "Process" msgstr "Proceso" #: lib/solaar/ui/diversion_rules.py:518 msgid "Mouse process" msgstr "Proceso de ratón" #: lib/solaar/ui/diversion_rules.py:519 lib/solaar/ui/diversion_rules.py:1366 msgid "Modifiers" msgstr "Modificadores" #: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "Tecla" #: lib/solaar/ui/diversion_rules.py:521 lib/solaar/ui/diversion_rules.py:1461 msgid "KeyIsDown" msgstr "TeclaPulsada" #: lib/solaar/ui/diversion_rules.py:522 lib/solaar/ui/diversion_rules.py:2260 msgid "Active" msgstr "Activo" #: 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:524 lib/solaar/ui/diversion_rules.py:2297 msgid "Host" msgstr "Equipo" #: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 msgid "Setting" msgstr "Ajuste" #: 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:527 lib/solaar/ui/diversion_rules.py:1643 msgid "Test bytes" msgstr "Bytes de prueba" #: 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 "Action" msgstr "Acción" #: 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:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "Desplazamiento rueda del ratón" #: 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:537 msgid "Set" msgstr "Establecer" #: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 msgid "Execute" msgstr "Ejecutar" #: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 msgid "Later" msgstr "Luego" #: lib/solaar/ui/diversion_rules.py:568 msgid "Insert new rule" msgstr "Añadir nueva regla" #: 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:610 msgid "Negate" msgstr "Negar" #: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "Envolver con" #: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Cortar" #: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Pegar" #: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Copiar" #: 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:1138 msgid "Number of seconds to delay." msgstr "Número de segundos a retrasar." #: lib/solaar/ui/diversion_rules.py:1177 msgid "Not" msgstr "No" #: 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: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:1235 msgid "MouseProcess" msgstr "ProcesoRatón" #: 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: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: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 "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:1479 msgid "Parameter" msgstr "Parámetro" #: lib/solaar/ui/diversion_rules.py:1542 msgid "begin (inclusive)" msgstr "inicio (incluido)" #: lib/solaar/ui/diversion_rules.py:1543 msgid "end (exclusive)" msgstr "fin (excluido)" #: lib/solaar/ui/diversion_rules.py:1552 msgid "range" msgstr "rango" #: 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" #: lib/solaar/ui/diversion_rules.py:1562 msgid "mask" msgstr "máscara" #: 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" #: 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:1583 msgid "type" msgstr "tipo" #: 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:1671 msgid "Add movement" msgstr "Añadir movimiento" #: 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:1769 msgid "Add key" msgstr "Añadir tecla" #: lib/solaar/ui/diversion_rules.py:1772 msgid "Click" msgstr "Hacer clic" #: lib/solaar/ui/diversion_rules.py:1775 msgid "Depress" msgstr "Pulsar" #: lib/solaar/ui/diversion_rules.py:1778 msgid "Release" msgstr "Liberar" #: 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: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:1921 msgid "Button" msgstr "Botón" #: lib/solaar/ui/diversion_rules.py:1922 msgid "Count and Action" msgstr "Pulsaciones y Acción" #: lib/solaar/ui/diversion_rules.py:1972 msgid "Execute a command with arguments." msgstr "Ejecutar un comando con parámetros." #: lib/solaar/ui/diversion_rules.py:1975 msgid "Add argument" msgstr "Añadir parámetro" #: lib/solaar/ui/diversion_rules.py:2050 msgid "Toggle" msgstr "Invertir" #: lib/solaar/ui/diversion_rules.py:2050 msgid "True" msgstr "Verdadero" #: lib/solaar/ui/diversion_rules.py:2051 msgid "False" msgstr "Falso" #: lib/solaar/ui/diversion_rules.py:2065 msgid "Unsupported setting" msgstr "Configuración no admitida" #: 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: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:2266 msgid "Device that originated the current notification." msgstr "Dispositivo que ha causado la notificación actual." #: lib/solaar/ui/diversion_rules.py:2280 msgid "Name of host computer." msgstr "Nombre del equipo." #: lib/solaar/ui/diversion_rules.py:2347 msgid "Value" msgstr "Valor" #: 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:288 #, python-format 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 "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." #: lib/solaar/ui/pair_window.py:129 msgid "left" msgstr "izquierda" #: lib/solaar/ui/pair_window.py:129 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 "" "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" #: 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." #: 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." #: 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." #: 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." #: lib/solaar/ui/pair_window.py:210 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" #: lib/solaar/ui/pair_window.py:264 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 "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 "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 "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: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 "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 supported device found" msgstr "No se ha encontrado ningún dispositivo compatible" #: 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:65 lib/solaar/ui/window.py:317 #, python-format msgid "Quit %s" msgstr "Salir de %s" #: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "sin receptor" #: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "sin estado" #: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "Explorando" #: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Batería" #: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Enlace inalámbrico" #: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Iluminación" #: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Mostrar detalles técnicos" #: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Vincular nuevo dispositivo" #: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Seleccionar un dispositivo" #: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "Editor de Reglas" #: 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 "Serie" #: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Índice" #: 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 #, 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 "Tasa de sondeo" #: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "ID Unidad" #: lib/solaar/ui/window.py:576 msgid "none" msgstr "ninguno" #: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Notificaciones" #: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "No hay dispositivos vinculados." #: 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." #: 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: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." #: lib/solaar/ui/window.py:692 msgid "Battery Voltage" msgstr "Voltaje de la batería" #: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "Voltaje informado por la batería" #: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "Nivel de 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:705 lib/solaar/ui/window.py:707 msgid "next reported " msgstr "siguiente informe " #: 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:713 msgid "last known" msgstr "último conocido" #: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "cifrado" #: 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" #: 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." #: lib/solaar/ui/window.py:748 #, python-format 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 "%(battery_level)s" #~ msgstr "%(battery_level)s" #~ msgid "%(battery_percent)d%%" #~ msgstr "%(battery_percent)d%%" #~ 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 "" #~ "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 "Battery information unknown." #~ msgstr "Información de la batería desconocida." #~ msgid "Count" #~ msgstr "Cuenta" #~ msgid "DPI Sliding Adjustment" #~ msgstr "Ajustar DPI deslizando" #~ 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 "Effectively turns off thumb scrolling in Linux." #~ msgstr "Desactiva eficazmente el desplazamiento con el pulgar en Linux." #~ msgid "Effectively turns off wheel scrolling in Linux." #~ msgstr "Desactiva eficazmente el desplazamiento de la rueda en Linux." #, 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 "HID++ Scrolling" #~ msgstr "Desplazamiento HID++" #~ msgid "HID++ mode for horizontal scroll with the thumb wheel." #~ msgstr "Modo HID++ para desplazamientor horizontal con la rueda del pulgar." #~ msgid "HID++ mode for vertical scroll with the wheel." #~ msgstr "Modo HID++ para el desplazamiento vertical con la rueda." #~ msgid "High Resolution Scrolling" #~ msgstr "Desplazamiento de alta resolución" #~ msgid "High Resolution Wheel Invert" #~ msgstr "Invertir rueda en alta resolución " #~ msgid "High-sensitivity wheel invert mode for vertical scroll." #~ msgstr "" #~ "Modo inverso de alta resolución en la rueda para desplazamiento vertical." #~ 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 "" #~ "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 "" #~ "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 "No Logitech device found" #~ msgstr "No se encontró ningún dispositivo Logitech" #~ msgid "No Logitech receiver found" #~ msgstr "No se encontró ningún receptor Logitech" #~ msgid "Scroll Wheel Rachet" #~ msgstr "Trinquete Rueda Desplazamiento" #~ 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 "" #~ "Shows status of devices connected\n" #~ "through wireless Logitech receivers." #~ msgstr "" #~ "Mostrar el estado de dispositivos conectados\n" #~ "mediante receptores inalámbricos Logitech." #~ msgid "Smooth Scrolling" #~ msgstr "Desplazamiento suave" #~ msgid "The receiver only supports %d paired device(s)." #~ msgstr "El receptor solo soporta %d dispositivo(s) vinculado(s)." #~ msgid "The receiver was unplugged." #~ msgstr "El receptor se desconectó." #~ 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 "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" Solaar-1.1.20/po/fi.po000066400000000000000000001766771522022367600144250ustar00rootroot00000000000000# Finnish translations for solaar package. # Copyright (C) 2013 THE solaar'S COPYRIGHT HOLDER # 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: 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:52 msgid "Bolt Receiver" msgstr "Bolt-vastaanotin" #: lib/logitech_receiver/base_usb.py:64 msgid "Unifying Receiver" msgstr "Unifying-vastaanotin" #: 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:125 msgid "Lightspeed Receiver" msgstr "Lightspeed-vastaanotin" #: 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 "not charging" msgstr "ei latauksessa" #: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "lähes täysi" #: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "ladattu" #: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "hidas lataus" #: lib/logitech_receiver/i18n.py:42 #, fuzzy msgid "invalid battery" msgstr "virheellinen akku" #: lib/logitech_receiver/i18n.py:43 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 "fast" msgstr "nopea" #: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "hidas" #: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "laitteen aikakatkaisu" #: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "laite ei ole tuettu" #: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "liian monta laitetta" #: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "sarjan aikakatkaisu" #: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:555 msgid "Firmware" msgstr "Laiteohjelmisto" #: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Alkulatausohjelma" #: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Laitteisto" #: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "Muu" #: 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 "Middle Button" msgstr "Keskipainike" #: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "Edellinen-painike" #: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "Seuraava-painike" #: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "Hiiren elepainike" #: 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 "Right Tilt" msgstr "Kallistus oikealle" #: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "Vasen napsautus" #: lib/logitech_receiver/i18n.py:70 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 "Mouse Forward Button" msgstr "Hiiren seuraava-painike" #: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "Elepainikkeen navigointi" #: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "Hiiren vieritys vasemmalle -painike" #: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "Hiiren vieritys oikealle -painike" #: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "painettu" #: lib/logitech_receiver/i18n.py:79 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 "" "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 "" "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:154 msgid "Hand Detection" msgstr "Käsien tunnistus" #: 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:162 msgid "Scroll Wheel Smooth Scrolling" msgstr "Vierityspyörän sulava vieritys" #: 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 "Korkean herkkyyden tila pystyvieritykseen hiiren rullalla." #: lib/logitech_receiver/settings_templates.py:170 msgid "Side Scrolling" msgstr "Sivuttaisvieritys" #: 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:182 msgid "Sensitivity (DPI - older mice)" msgstr "Herkkyys (DPI - vanhemmat hiiret)" #: 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:256 msgid "Backlight Timed" msgstr "Ajastettu taustavalo" #: 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:268 msgid "Backlight" msgstr "Taustavalo" #: 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:301 msgid "Automatic" msgstr "Automaattinen" #: lib/logitech_receiver/settings_templates.py:303 msgid "Manual" msgstr "Manuaalinen" #: lib/logitech_receiver/settings_templates.py:305 msgid "Enabled" msgstr "Käytössä" #: lib/logitech_receiver/settings_templates.py:311 msgid "Backlight Level" msgstr "Taustavalon taso" #: 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:369 msgid "Backlight Delay Hands Out" msgstr "Taustavalon viive, kädet poissa" #: 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:378 msgid "Backlight Delay Hands In" msgstr "Taustavalon viive, kädet lähellä" #: 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 "Poistaa %s-näppäimen käytöstä." #: 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: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:1247 msgid "Change Host" msgstr "Vaihda isäntälaitetta" #: lib/logitech_receiver/settings_templates.py:1248 msgid "Switch connection to a different host" msgstr "Vaihda yhteys toiseen isäntälaitteeseen" #: 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 "Lights up the %s key." msgstr "Sytyttää %s-näppäimen." #: lib/logitech_receiver/settings_templates.py:1448 msgid "MR-Key LED" msgstr "MR-näppäimen LED" #: lib/logitech_receiver/settings_templates.py:1450 msgid "Control the MR-Key LED." msgstr "Hallitse MR-näppäimen LEDiä." #: lib/logitech_receiver/settings_templates.py:1471 msgid "Persistent Key/Button Mapping" msgstr "Pysyvä näppäin-/painikemääritys" #: 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/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/logitech_receiver/settings_templates.py:1532 msgid "Sidetone" msgstr "Sivuääni" #: lib/logitech_receiver/settings_templates.py:1533 msgid "Set sidetone level." msgstr "Aseta sivuäänen taso." #: lib/logitech_receiver/settings_templates.py:1542 msgid "Equalizer" msgstr "Taajuuskorjain" #: lib/logitech_receiver/settings_templates.py:1543 msgid "Set equalizer levels." msgstr "Aseta taajuuskorjaimen tasot." #: lib/logitech_receiver/settings_templates.py:1565 msgid "Hz" msgstr "Hz" #: lib/logitech_receiver/settings_templates.py:1571 msgid "Power Management" msgstr "Virranhallinta" #: lib/logitech_receiver/settings_templates.py:1572 msgid "Power off in minutes (0 for never)." msgstr "Virrankatkaisu minuuteissa (0 = ei koskaan)." #: lib/logitech_receiver/settings_templates.py:1584 msgid "Brightness Control" msgstr "Kirkkauden säätö" #: lib/logitech_receiver/settings_templates.py:1585 msgid "Control overall brightness" msgstr "Hallitse yleistä kirkkautta" #: lib/logitech_receiver/settings_templates.py:1628 #: lib/logitech_receiver/settings_templates.py:1681 msgid "LED Control" msgstr "LED-hallinta" #: 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/logitech_receiver/settings_templates.py:1644 #: lib/logitech_receiver/settings_templates.py:1692 msgid "LED Zone Effects" msgstr "LED-alueiden efektit" #: 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/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" #: 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:91 lib/solaar/ui/diversion_rules.py:141 msgid "Cancel" msgstr "Peruuta" #: lib/solaar/ui/common.py:42 msgid "Permissions error" msgstr "Virheelliset käyttöoikeudet" #: 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 "" "Löydettiin Logitech-vastaanotin tai -laite (%s), mutta siihen ei ollut " "avaamisoikeutta." #: 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/common.py:49 msgid "Cannot connect to device error" msgstr "Virhe yhdistettäessä laitteeseen" #: 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/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/common.py:56 msgid "Unpairing failed" msgstr "Parituksen poisto epäonnistui" #: 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/common.py:63 msgid "The receiver returned an error, with no further details." msgstr "Vastaanotin palautti virheen ilman lisätietoja." #: lib/solaar/ui/config_panel.py:245 msgid "Complete - ENTER to change" msgstr "Valmis - paina ENTER muuttaaksesi" #: lib/solaar/ui/config_panel.py:245 msgid "Incomplete" msgstr "Keskeneräinen" #: 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/config_panel.py:642 msgid "Changes allowed" msgstr "Muutokset sallittu" #: lib/solaar/ui/config_panel.py:643 msgid "No changes allowed" msgstr "Ei muutoksia sallittu" #: lib/solaar/ui/config_panel.py:644 msgid "Ignore this setting" msgstr "Ohita tämä asetus" #: lib/solaar/ui/config_panel.py:690 msgid "Working" msgstr "Työskentelee" #: lib/solaar/ui/config_panel.py:693 msgid "Read/write operation failed." msgstr "Luku/kirjoitus operaatio epäonnistui." #: lib/solaar/ui/desktop_notifications.py:112 msgid "unspecified reason" msgstr "määrittämätön syy" #: 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" msgstr "Lisää" #: 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: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:386 msgid "Condition" msgstr "Ehto" #: lib/solaar/ui/diversion_rules.py:388 lib/solaar/ui/rule_conditions.py:154 msgid "Feature" msgstr "Ominaisuus" #: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:188 msgid "Report" msgstr "Raportti" #: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:69 msgid "Process" msgstr "Prosessi" #: lib/solaar/ui/diversion_rules.py:391 msgid "Mouse process" msgstr "Hiiriprosessi" #: lib/solaar/ui/diversion_rules.py:392 lib/solaar/ui/rule_conditions.py:225 msgid "Modifiers" msgstr "Muuntajat" #: 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:394 lib/solaar/ui/rule_conditions.py:318 msgid "KeyIsDown" msgstr "Näppäin on painettu" #: 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 "Wrap with" msgstr "Kääri" #: lib/solaar/ui/diversion_rules.py:537 msgid "Cut" msgstr "Leikkaa" #: lib/solaar/ui/diversion_rules.py:553 msgid "Paste" msgstr "Liitä" #: lib/solaar/ui/diversion_rules.py:559 msgid "Copy" msgstr "Kopioi" #: lib/solaar/ui/diversion_rules.py:568 msgid "Solaar Rule Editor" msgstr "Solaarin sääntöeditori" #: lib/solaar/ui/diversion_rules.py:668 msgid "Save changes" msgstr "Tallenna muutokset" #: lib/solaar/ui/diversion_rules.py:673 msgid "Discard changes" msgstr "Hylkää muutokset" #: 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: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:1207 msgid "Not" msgstr "Ei" #: lib/solaar/ui/diversion_rules.py:1238 msgid "Toggle" msgstr "Vaihda" #: lib/solaar/ui/diversion_rules.py:1239 msgid "True" msgstr "Tosi" #: lib/solaar/ui/diversion_rules.py:1240 msgid "False" msgstr "Epätosi" #: lib/solaar/ui/diversion_rules.py:1253 msgid "Unsupported setting" msgstr "Ei tuettu asetus" #: 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: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:1437 msgid "Device that originated the current notification." msgstr "Laite, josta nykyinen ilmoitus on peräisin." #: lib/solaar/ui/diversion_rules.py:1450 msgid "Name of host computer." msgstr "Isäntätietokoneen nimi." #: lib/solaar/ui/diversion_rules.py:1520 msgid "Value" msgstr "Arvo" #: lib/solaar/ui/diversion_rules.py:1529 msgid "Item" msgstr "Kohde" #: lib/solaar/ui/diversion_rules.py:1808 msgid "Change setting on device" msgstr "Muuta asetusta laitteessa" #: lib/solaar/ui/diversion_rules.py:1824 msgid "Setting on device" msgstr "Asetus laitteessa" #: 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: parita uusi laite" #: 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: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] "" "\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: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:168 #, python-format msgid "Enter passcode on %(name)s." msgstr "Anna pääsykoodi laitteella %(name)s." #: 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/pair_window.py:176 msgid "left" msgstr "vasen" #: lib/solaar/ui/pair_window.py:176 msgid "right" msgstr "oikea" #: 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/pair_window.py:221 msgid "The wireless link is not encrypted" msgstr "Langaton yhteys ei ole salattu" #: lib/solaar/ui/pair_window.py:226 msgid "Found a new device:" msgstr "Löytyi uusi laite:" #: 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 "Wireless Link" msgstr "Langaton yhteys" #: lib/solaar/ui/window.py:148 msgid "Lighting" msgstr "Valaistus" #: lib/solaar/ui/window.py:182 msgid "Show Technical Details" msgstr "Näytä tekniset tiedot" #: 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 "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 "Polling rate" msgstr "Päivitysnopeus" #: lib/solaar/ui/window.py:548 msgid "Unit ID" msgstr "Yksikön tunnus" #: lib/solaar/ui/window.py:559 msgid "none" msgstr "ei mitään" #: lib/solaar/ui/window.py:560 msgid "Notifications" msgstr "Ilmoitukset" #: lib/solaar/ui/window.py:604 msgid "No device paired." msgstr "Ei paritettua laitetta." #: 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] "" "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: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:624 #, python-format 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:681 msgid "Battery Voltage" msgstr "Akun jännite" #: lib/solaar/ui/window.py:683 msgid "Voltage reported by battery" msgstr "Akun ilmoittama jännite" #: 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 "last known" msgstr "viimeisin tunnettu" #: lib/solaar/ui/window.py:713 msgid "encrypted" msgstr "salattu" #: 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:717 msgid "not encrypted" msgstr "salaamaton" #: 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:737 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d luksia" #~ msgid " paired devices." #~ msgstr " paritettua laitetta" #~ msgid "1 paired device." #~ msgstr "Yksi paritettu laite." #, 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." #~ 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 "" #~ "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 "No Logitech receiver found" #~ msgstr "Ei löydetty Logitechin vastaanottimia." #~ 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 "Sulava vieritys" #, 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" Solaar-1.1.20/po/fr.po000066400000000000000000002234501522022367600144140ustar00rootroot00000000000000# French translations for solaar package. # Copyright (C) 2013 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # Automatically generated, 2013. # msgid "" msgstr "" "Project-Id-Version: solaar 1.1.20\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-06-17 17:17+0200\n" "PO-Revision-Date: 2026-06-17 21:06+0200\n" "Last-Translator: David Geiger \n" "Language-Team: Language: fr\n" "Language: fr\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 "Récepteur Bolt" #: lib/logitech_receiver/base_usb.py:64 msgid "Unifying Receiver" msgstr "Récepteur 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 "Récepteur Nano" #: lib/logitech_receiver/base_usb.py:125 msgid "Lightspeed Receiver" msgstr "Récepteur Lightspeed" #: lib/logitech_receiver/base_usb.py:136 msgid "EX100 Receiver 27 Mhz" msgstr "Récepteur EX100 27 Mhz" #: lib/logitech_receiver/common.py:698 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "Batterie : %(level)s (%(status)s)" #: lib/logitech_receiver/common.py:701 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Batterie : %(percent)d%% (%(status)s)" #: lib/logitech_receiver/hidpp20.py:1189 lib/logitech_receiver/hidpp20.py:1222 #: lib/logitech_receiver/hidpp20.py:1250 msgid "Cycle" msgstr "Cycle" #: lib/logitech_receiver/hidpp20.py:1190 msgid "Right" msgstr "Droit" #: lib/logitech_receiver/hidpp20.py:1191 msgid "Down" msgstr "En bas" #: lib/logitech_receiver/hidpp20.py:1192 msgid "Center Out" msgstr "Centrer dehors" #: lib/logitech_receiver/hidpp20.py:1193 msgid "In" msgstr "Dedans" #: lib/logitech_receiver/hidpp20.py:1194 msgid "Out" msgstr "Dehors" #: lib/logitech_receiver/hidpp20.py:1195 #: lib/logitech_receiver/settings_templates.py:1398 msgid "Left" msgstr "Gauche" #: lib/logitech_receiver/hidpp20.py:1196 msgid "Up" msgstr "En haut" #: lib/logitech_receiver/hidpp20.py:1197 msgid "Center In" msgstr "Centrer dedans" #: lib/logitech_receiver/hidpp20.py:1218 #: lib/logitech_receiver/settings_templates.py:344 msgid "Disabled" msgstr "Désactivé" #: lib/logitech_receiver/hidpp20.py:1219 msgid "Static" msgstr "Statique" #: lib/logitech_receiver/hidpp20.py:1220 msgid "Pulse" msgstr "Pulsation" #: lib/logitech_receiver/hidpp20.py:1229 lib/logitech_receiver/hidpp20.py:1255 msgid "Wave" msgstr "" #: lib/logitech_receiver/hidpp20.py:1233 msgid "Boot" msgstr "Boot" #: lib/logitech_receiver/hidpp20.py:1234 msgid "Demo" msgstr "Démo" #: lib/logitech_receiver/hidpp20.py:1236 msgid "Breathe" msgstr "" #: lib/logitech_receiver/hidpp20.py:1241 lib/logitech_receiver/hidpp20.py:1262 msgid "Ripple" msgstr "" #: lib/logitech_receiver/hidpp20.py:1246 msgid "Decomposition" msgstr "Décomposition" #: lib/logitech_receiver/hidpp20.py:1247 msgid "Signature1" msgstr "Signature 1" #: lib/logitech_receiver/hidpp20.py:1248 msgid "Signature2" msgstr "Signature 2" #: lib/logitech_receiver/hidpp20.py:1268 msgid "Dim" msgstr "Sombre" #: lib/logitech_receiver/hidpp20.py:1340 msgid "Unknown Location" msgstr "Emplacement inconnu" #: lib/logitech_receiver/hidpp20.py:1341 #: lib/logitech_receiver/settings_templates.py:2619 #: lib/logitech_receiver/settings_templates.py:2907 #: lib/logitech_receiver/settings_templates.py:3647 msgid "Primary" msgstr "Primaire" #: lib/logitech_receiver/hidpp20.py:1342 msgid "Logo" msgstr "Logo" #: lib/logitech_receiver/hidpp20.py:1343 msgid "Left Side" msgstr "Côté gauche" #: lib/logitech_receiver/hidpp20.py:1344 msgid "Right Side" msgstr "Côté droit" #: lib/logitech_receiver/hidpp20.py:1345 msgid "Combined" msgstr "Combiné" #: lib/logitech_receiver/hidpp20.py:1346 msgid "Primary 1" msgstr "Primaire 1" #: lib/logitech_receiver/hidpp20.py:1347 msgid "Primary 2" msgstr "Primaire 2" #: lib/logitech_receiver/hidpp20.py:1348 msgid "Primary 3" msgstr "Primaire 3" #: lib/logitech_receiver/hidpp20.py:1349 msgid "Primary 4" msgstr "Primaire 4" #: lib/logitech_receiver/hidpp20.py:1350 msgid "Primary 5" msgstr "Primaire 5" #: lib/logitech_receiver/hidpp20.py:1351 msgid "Primary 6" msgstr "Primaire 6" #: lib/logitech_receiver/i18n.py:28 msgid "empty" msgstr "vide" #: lib/logitech_receiver/i18n.py:29 msgid "critical" msgstr "critique" #: lib/logitech_receiver/i18n.py:30 msgid "low" msgstr "faible" #: lib/logitech_receiver/i18n.py:31 msgid "average" msgstr "moyenne" #: lib/logitech_receiver/i18n.py:32 msgid "good" msgstr "bonne" #: lib/logitech_receiver/i18n.py:33 msgid "full" msgstr "pleine" #: lib/logitech_receiver/i18n.py:35 msgid "discharging" msgstr "En décharge" #: lib/logitech_receiver/i18n.py:36 msgid "recharging" msgstr "En charge" #: lib/logitech_receiver/i18n.py:37 lib/solaar/ui/window.py:706 msgid "charging" msgstr "en charge" #: lib/logitech_receiver/i18n.py:38 msgid "not charging" msgstr "pas en charge" #: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "presque pleine" #: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "chargée" #: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "recharge lente" #: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "batterie invalide" #: lib/logitech_receiver/i18n.py:43 msgid "thermal error" msgstr "erreur thermique" #: lib/logitech_receiver/i18n.py:44 msgid "error" msgstr "erreur" #: lib/logitech_receiver/i18n.py:45 msgid "standard" msgstr "standard" #: lib/logitech_receiver/i18n.py:46 msgid "fast" msgstr "rapide" #: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "lente" #: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "le périphérique ne répond pas" #: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "périphérique non pris en charge" #: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "trop de périphériques" #: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "délai dépassé" #: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:561 msgid "Firmware" msgstr "Micrologiciel" #: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Chargeur d'amorçage" #: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Matériel" #: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "Autre" #: lib/logitech_receiver/i18n.py:59 #: lib/logitech_receiver/settings_templates.py:4336 msgid "Left Button" msgstr "Bouton gauche" #: lib/logitech_receiver/i18n.py:60 #: lib/logitech_receiver/settings_templates.py:4336 msgid "Right Button" msgstr "Bouton droit" #: lib/logitech_receiver/i18n.py:61 msgid "Middle Button" msgstr "Bouton central" #: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "Bouton retour" #: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "Bouton avant" #: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "Bouton gestuel de la souris" #: lib/logitech_receiver/i18n.py:65 msgid "Smart Shift" msgstr "Changement intelligent" #: lib/logitech_receiver/i18n.py:66 msgid "DPI Switch" msgstr "Commutateur DPI" #: lib/logitech_receiver/i18n.py:67 msgid "Left Tilt" msgstr "Inclinaison à gauche" #: lib/logitech_receiver/i18n.py:68 msgid "Right Tilt" msgstr "Inclinaison à droite" #: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "Clic gauche" #: lib/logitech_receiver/i18n.py:70 msgid "Right Click" msgstr "Clic droit" #: lib/logitech_receiver/i18n.py:71 msgid "Mouse Middle Button" msgstr "Bouton central de la souris" #: lib/logitech_receiver/i18n.py:72 msgid "Mouse Back Button" msgstr "Bouton de retour de la souris" #: lib/logitech_receiver/i18n.py:73 msgid "Mouse Forward Button" msgstr "Bouton avant de la souris" #: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "Navigation par bouton de gestuelle" #: 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:76 msgid "Mouse Scroll Right Button" msgstr "Bouton de défilement vers la droite de la souris" #: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "pressé" #: lib/logitech_receiver/i18n.py:79 msgid "released" msgstr "relâché" #: lib/logitech_receiver/notifications.py:157 lib/solaar/listener.py:350 msgid "connected" msgstr "connecté" #: lib/logitech_receiver/notifications.py:157 lib/solaar/listener.py:350 msgid "disconnected" msgstr "déconnecté" #: lib/logitech_receiver/notifications.py:183 msgid "unpaired" msgstr "non jumelé" #: lib/logitech_receiver/notifications.py:232 msgid "powered on" msgstr "sous tension" #: lib/logitech_receiver/notifications.py:284 msgid "ADC measurement notification" msgstr "Notification de mesure ADC" #: lib/logitech_receiver/notifications.py:486 #: lib/logitech_receiver/notifications.py:542 msgid "pairing lock is closed" msgstr "le verrou de jumelage est fermé" #: lib/logitech_receiver/notifications.py:486 #: lib/logitech_receiver/notifications.py:542 msgid "pairing lock is open" msgstr "le verrou de jumelage est ouvert" #: lib/logitech_receiver/notifications.py:505 msgid "discovery lock is closed" msgstr "le verrou de détection est fermé" #: lib/logitech_receiver/notifications.py:505 msgid "discovery lock is open" msgstr "le verrou de détection est ouvert" #: lib/logitech_receiver/receiver.py:466 msgid "No paired devices." msgstr "Aucun périphérique jumelé." #: lib/logitech_receiver/receiver.py:468 lib/solaar/ui/window.py:612 #, 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:621 msgid "register" msgstr "registre" #: lib/logitech_receiver/settings.py:635 lib/logitech_receiver/settings.py:673 msgid "feature" msgstr "fonctionnalité" #: lib/logitech_receiver/settings_templates.py:183 msgid "Swap Fx function" msgstr "Fonction Swap Fx" #: lib/logitech_receiver/settings_templates.py:186 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 "" "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:191 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 "" "Lorsque non défini, les touches F1..F12 activeront leurs fonctions " "standards,\n" "et vous devez maintenir la touche FN pour activer leurs fonctions spéciales." #: lib/logitech_receiver/settings_templates.py:199 msgid "Hand Detection" msgstr "Détection manuelle" #: lib/logitech_receiver/settings_templates.py:200 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:207 msgid "Scroll Wheel Smooth Scrolling" msgstr "Défilement fluide à la molette" #: lib/logitech_receiver/settings_templates.py:208 #: lib/logitech_receiver/settings_templates.py:455 #: lib/logitech_receiver/settings_templates.py:484 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:215 msgid "Side Scrolling" msgstr "Défilement latéral" #: lib/logitech_receiver/settings_templates.py:217 msgid "" "When disabled, pushing the wheel sideways sends custom button events\n" "instead of the standard side-scrolling events." msgstr "" "Lorsque désactivé, l'appui sur les côtés de la roulette envoie des " "évènements\n" " de bouton personnalisés à la place des évènements standards de défilement " "latéral." #: lib/logitech_receiver/settings_templates.py:227 msgid "Sensitivity (DPI - older mice)" msgstr "Sensibilité (DPI - souris plus anciennne)" #: lib/logitech_receiver/settings_templates.py:228 #: lib/logitech_receiver/settings_templates.py:1064 #: lib/logitech_receiver/settings_templates.py:1092 msgid "Mouse movement sensitivity" msgstr "Sensibilité du mouvement de la souris" #: lib/logitech_receiver/settings_templates.py:301 msgid "Backlight Timed" msgstr "Rétroéclairage chronométré" #: lib/logitech_receiver/settings_templates.py:302 #: lib/logitech_receiver/settings_templates.py:442 msgid "Set illumination time for keyboard." msgstr "Définir le temps d'éclairage du clavier." #: lib/logitech_receiver/settings_templates.py:313 msgid "Backlight" msgstr "Rétroéclairage" #: lib/logitech_receiver/settings_templates.py:314 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:346 msgid "Automatic" msgstr "Automatique" #: lib/logitech_receiver/settings_templates.py:348 msgid "Manual" msgstr "Manuel" #: lib/logitech_receiver/settings_templates.py:350 msgid "Enabled" msgstr "Activé" #: lib/logitech_receiver/settings_templates.py:356 msgid "Backlight Level" msgstr "Niveau de rétroéclairage" #: lib/logitech_receiver/settings_templates.py:357 msgid "Illumination level on keyboard when in Manual mode." msgstr "Niveau d'éclairage du clavier en mode manuel." #: lib/logitech_receiver/settings_templates.py:414 msgid "Backlight Delay Hands Out" msgstr "Délai de rétroéclairage sans les mains" #: lib/logitech_receiver/settings_templates.py:415 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:423 msgid "Backlight Delay Hands In" msgstr "Délai de rétroéclairage avec les mains" #: lib/logitech_receiver/settings_templates.py:424 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:432 msgid "Backlight Delay Powered" msgstr "Délai de rétroéclairage alimenté" #: lib/logitech_receiver/settings_templates.py:433 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:441 msgid "Backlight (Seconds)" msgstr "Rétroéclairage (Secondes)" #: lib/logitech_receiver/settings_templates.py:453 msgid "Scroll Wheel High Resolution" msgstr "Molette haute résolution" #: lib/logitech_receiver/settings_templates.py:457 #: lib/logitech_receiver/settings_templates.py:486 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:464 #: lib/logitech_receiver/settings_templates.py:495 msgid "Scroll Wheel Diversion" msgstr "Interception de la molette" #: lib/logitech_receiver/settings_templates.py:466 msgid "" "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " "Solaar rules but are otherwise ignored)." 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:473 msgid "Scroll Wheel Direction" msgstr "Direction de la molette" #: lib/logitech_receiver/settings_templates.py:474 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:482 msgid "Scroll Wheel Resolution" msgstr "Résolution de la molette" #: lib/logitech_receiver/settings_templates.py:497 msgid "" "Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." 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:506 msgid "Sensitivity (Pointer Speed)" msgstr "Sensibilité (vitesse du pointeur)" #: lib/logitech_receiver/settings_templates.py:507 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:517 msgid "Thumb Wheel Diversion" msgstr "Interception de la molette de pouce" #: lib/logitech_receiver/settings_templates.py:519 msgid "" "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." 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:528 msgid "Thumb Wheel Direction" msgstr "Direction de la molette de pouce" #: lib/logitech_receiver/settings_templates.py:529 msgid "Invert thumb wheel scroll direction." msgstr "Inverser la direction du défilement de la molette de pouce." #: lib/logitech_receiver/settings_templates.py:549 msgid "Onboard Profiles" msgstr "Profils embarqués" #: lib/logitech_receiver/settings_templates.py:550 msgid "" "Enable an onboard profile, which controls report rate, sensitivity, and " "button actions" msgstr "" "Activer un profil embarqué, qui contrôle le taux de rapport, la sensibilité " "et les actions des boutons" #: lib/logitech_receiver/settings_templates.py:594 #: lib/logitech_receiver/settings_templates.py:627 msgid "Report Rate" msgstr "Taux de rapport" #: lib/logitech_receiver/settings_templates.py:596 #: lib/logitech_receiver/settings_templates.py:629 msgid "Frequency of device movement reports" msgstr "Fréquence des rapports de mouvement des périphériques" #: lib/logitech_receiver/settings_templates.py:596 #: lib/logitech_receiver/settings_templates.py:629 #: lib/logitech_receiver/settings_templates.py:1064 #: lib/logitech_receiver/settings_templates.py:1092 #: lib/logitech_receiver/settings_templates.py:1471 #: lib/logitech_receiver/settings_templates.py:1502 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:657 msgid "Divert crown events" msgstr "Définir les évènements de la couronne" #: lib/logitech_receiver/settings_templates.py:658 msgid "" "Make crown send CROWN HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." 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:666 msgid "Crown smooth scroll" msgstr "Défilement fluide de la couronne" #: lib/logitech_receiver/settings_templates.py:667 msgid "Set crown smooth scroll" msgstr "Définir le défilement fluide de la couronne" #: lib/logitech_receiver/settings_templates.py:675 msgid "Divert G and M Keys" msgstr "Interception des touches G et M" #: lib/logitech_receiver/settings_templates.py:676 msgid "" "Make G and M keys send HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." msgstr "" "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:690 msgid "Scroll Wheel Ratcheted" msgstr "Molette mode cliquet" #: lib/logitech_receiver/settings_templates.py:691 msgid "" "Switch the mouse wheel between speed-controlled ratcheting and always " "freespin." 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:693 msgid "Freespinning" msgstr "Roue libre" #: lib/logitech_receiver/settings_templates.py:693 msgid "Ratcheted" msgstr "Cliquet" #: lib/logitech_receiver/settings_templates.py:700 msgid "Scroll Wheel Ratchet Speed" msgstr "Vitesse de la molette mode cliquet" #: lib/logitech_receiver/settings_templates.py:702 msgid "" "Use the mouse wheel speed to switch between ratcheted and freespinning.\n" "The mouse wheel is always ratcheted at 50." msgstr "" "Utilisez la vitesse de la molette de la souris pour basculer entre le mode " "cliquet et le mode roue libre.\n" "La molette est toujours en mode cliquet à 50." #: lib/logitech_receiver/settings_templates.py:752 msgid "Scroll Wheel Ratchet Torque" msgstr "Couple de la molette mode cliquet" #: lib/logitech_receiver/settings_templates.py:753 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:788 msgid "Key/Button Actions" msgstr "Actions des boutons" #: lib/logitech_receiver/settings_templates.py:790 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:792 msgid "Overridden by diversion." msgstr "Remplacé par interception." #: lib/logitech_receiver/settings_templates.py:794 msgid "" "Changing important actions (such as for the left mouse button) can result in " "an unusable system." 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:969 msgid "Key/Button Diversion" msgstr "Interception des boutons/touches" #: lib/logitech_receiver/settings_templates.py:970 msgid "" "Make the key or button send HID++ notifications (Diverted) or initiate Mouse " "Gestures or Sliding DPI" 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:973 #: lib/logitech_receiver/settings_templates.py:974 #: lib/logitech_receiver/settings_templates.py:975 msgid "Diverted" msgstr "Interception" #: lib/logitech_receiver/settings_templates.py:973 #: lib/logitech_receiver/settings_templates.py:974 msgid "Mouse Gestures" msgstr "Gestuelle à la souris" #: lib/logitech_receiver/settings_templates.py:973 #: lib/logitech_receiver/settings_templates.py:974 #: lib/logitech_receiver/settings_templates.py:975 msgid "Regular" msgstr "Normal" #: lib/logitech_receiver/settings_templates.py:973 msgid "Sliding DPI" msgstr "Amplification du déplacement" #: lib/logitech_receiver/settings_templates.py:1063 #: lib/logitech_receiver/settings_templates.py:1091 msgid "Sensitivity (DPI)" msgstr "Sensibilité (DPI)" #: lib/logitech_receiver/settings_templates.py:1173 msgid "Sensitivity Switching" msgstr "Basculement de la sensibilité" #: lib/logitech_receiver/settings_templates.py:1175 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 "" "Bascule la sensibilité actuelle et la sensibilité enregistrée quand la " "touche ou le bouton sont appuyés.\n" "S'il n'y aucune sensibilité enregistrée, rappelle uniquement la sensibilité " "courante" #: lib/logitech_receiver/settings_templates.py:1179 msgid "Off" msgstr "Eteint" #: lib/logitech_receiver/settings_templates.py:1210 msgid "Disable keys" msgstr "Désactiver les touches" #: lib/logitech_receiver/settings_templates.py:1211 msgid "Disable specific keyboard keys." msgstr "Désactiver des touches spécifiques du clavier." #: lib/logitech_receiver/settings_templates.py:1214 #, python-format msgid "Disables the %s key." msgstr "Désactive la touche %s." #: lib/logitech_receiver/settings_templates.py:1227 #: lib/logitech_receiver/settings_templates.py:1284 msgid "Set OS" msgstr "Définir le système d'exploitation" #: lib/logitech_receiver/settings_templates.py:1228 #: lib/logitech_receiver/settings_templates.py:1285 msgid "Change keys to match OS." msgstr "Modifier les touches pour correspondre au système d'exploitation." #: lib/logitech_receiver/settings_templates.py:1297 msgid "Change Host" msgstr "Changer d'hôte" #: lib/logitech_receiver/settings_templates.py:1298 msgid "Switch connection to a different host" msgstr "Commuter la connexion vers un autre hôte" #: lib/logitech_receiver/settings_templates.py:1322 msgid "Performs a left click." msgstr "Réalise un clic gauche." #: lib/logitech_receiver/settings_templates.py:1322 msgid "Single tap" msgstr "Une tape simple" #: lib/logitech_receiver/settings_templates.py:1323 msgid "Performs a right click." msgstr "Réalise un clic droit." #: lib/logitech_receiver/settings_templates.py:1323 msgid "Single tap with two fingers" msgstr "Une tape simple avec deux doigts" #: lib/logitech_receiver/settings_templates.py:1324 msgid "Single tap with three fingers" msgstr "Une tape simple avec trois doigts" #: lib/logitech_receiver/settings_templates.py:1328 msgid "Double tap" msgstr "Une double tape" #: lib/logitech_receiver/settings_templates.py:1328 msgid "Performs a double click." msgstr "Réalise un double clic." #: lib/logitech_receiver/settings_templates.py:1329 msgid "Double tap with two fingers" msgstr "Une double tape avec deux doigts" #: lib/logitech_receiver/settings_templates.py:1330 msgid "Double tap with three fingers" msgstr "Une double tape avec trois doigts" #: lib/logitech_receiver/settings_templates.py:1333 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:1333 msgid "Tap and drag" msgstr "Taper-glisser" #: lib/logitech_receiver/settings_templates.py:1335 msgid "Tap and drag with two fingers" msgstr "Taper-glisser avec deux doigts" #: lib/logitech_receiver/settings_templates.py:1336 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:1338 msgid "Tap and drag with three fingers" msgstr "Taper-glisser avec trois doigts" #: lib/logitech_receiver/settings_templates.py:1341 msgid "Suppress tap and edge gestures" msgstr "Supprimer les tapes et les gestes" #: lib/logitech_receiver/settings_templates.py:1342 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:1344 msgid "Scroll with one finger" msgstr "Défilement à un doigt" #: lib/logitech_receiver/settings_templates.py:1344 #: lib/logitech_receiver/settings_templates.py:1345 #: lib/logitech_receiver/settings_templates.py:1348 msgid "Scrolls." msgstr "Défilements." #: lib/logitech_receiver/settings_templates.py:1345 #: lib/logitech_receiver/settings_templates.py:1348 msgid "Scroll with two fingers" msgstr "Défilement à deux doigts" #: lib/logitech_receiver/settings_templates.py:1346 msgid "Scroll horizontally with two fingers" msgstr "Défilement horizontal à deux doigts" #: lib/logitech_receiver/settings_templates.py:1346 msgid "Scrolls horizontally." msgstr "Défile horizontalement." #: lib/logitech_receiver/settings_templates.py:1347 msgid "Scroll vertically with two fingers" msgstr "Défilement vertical à deux doigts" #: lib/logitech_receiver/settings_templates.py:1347 msgid "Scrolls vertically." msgstr "Défile verticalement." #: lib/logitech_receiver/settings_templates.py:1349 msgid "Inverts the scrolling direction." msgstr "Inverse la direction de défilement." #: lib/logitech_receiver/settings_templates.py:1349 msgid "Natural scrolling" msgstr "Défilement naturel" #: lib/logitech_receiver/settings_templates.py:1350 msgid "Enables the thumbwheel." msgstr "Active la molette de pouce." #: lib/logitech_receiver/settings_templates.py:1350 msgid "Thumbwheel" msgstr "Molette de pouce" #: lib/logitech_receiver/settings_templates.py:1361 #: lib/logitech_receiver/settings_templates.py:1365 msgid "Swipe from the top edge" msgstr "Glissement depuis le bord supérieur" #: lib/logitech_receiver/settings_templates.py:1362 msgid "Swipe from the left edge" msgstr "Glissement depuis le bord gauche" #: lib/logitech_receiver/settings_templates.py:1363 msgid "Swipe from the right edge" msgstr "Glissement depuis le bord droit" #: lib/logitech_receiver/settings_templates.py:1364 msgid "Swipe from the bottom edge" msgstr "Glissement depuis le bord inférieur" #: lib/logitech_receiver/settings_templates.py:1366 msgid "Swipe two fingers from the left edge" msgstr "Glissement à deux doigts depuis le bord gauche" #: lib/logitech_receiver/settings_templates.py:1367 msgid "Swipe two fingers from the right edge" msgstr "Glissement à deux doigts depuis le bord droit" #: lib/logitech_receiver/settings_templates.py:1368 msgid "Swipe two fingers from the bottom edge" msgstr "Glissement à deux doigts depuis le bord inférieur" #: lib/logitech_receiver/settings_templates.py:1369 msgid "Swipe two fingers from the top edge" msgstr "Glissement à deux doigts depuis le bord supérieur" #: lib/logitech_receiver/settings_templates.py:1370 #: lib/logitech_receiver/settings_templates.py:1374 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:1370 msgid "Zoom with two fingers." msgstr "Zoom à deux doigts." #: lib/logitech_receiver/settings_templates.py:1371 msgid "Pinch to zoom out." msgstr "Pincer pour réduire le zoom." #: lib/logitech_receiver/settings_templates.py:1372 msgid "Spread to zoom in." msgstr "Écarter pour agrandir le zoom." #: lib/logitech_receiver/settings_templates.py:1373 msgid "Zoom with three fingers." msgstr "Zoom à trois doigts." #: lib/logitech_receiver/settings_templates.py:1374 msgid "Zoom with two fingers" msgstr "Zoom à deux doigts" #: lib/logitech_receiver/settings_templates.py:1392 msgid "Pixel zone" msgstr "Zone de pixels" #: lib/logitech_receiver/settings_templates.py:1393 msgid "Ratio zone" msgstr "Zone de rapport" #: lib/logitech_receiver/settings_templates.py:1394 msgid "Scale factor" msgstr "Facteur d’échelle" #: lib/logitech_receiver/settings_templates.py:1394 msgid "Sets the cursor speed." msgstr "Définit la vitesse du curseur." #: lib/logitech_receiver/settings_templates.py:1398 msgid "Left-most coordinate." msgstr "Coordonnée la plus à gauche." #: lib/logitech_receiver/settings_templates.py:1399 msgid "Bottom" msgstr "Bas" #: lib/logitech_receiver/settings_templates.py:1399 msgid "Bottom coordinate." msgstr "Coordonnée du bas." #: lib/logitech_receiver/settings_templates.py:1400 msgid "Width" msgstr "Largeur" #: lib/logitech_receiver/settings_templates.py:1400 msgid "Width." msgstr "Largeur." #: lib/logitech_receiver/settings_templates.py:1401 msgid "Height" msgstr "Hauteur" #: lib/logitech_receiver/settings_templates.py:1401 msgid "Height." msgstr "Hauteur." #: lib/logitech_receiver/settings_templates.py:1402 msgid "Cursor speed." msgstr "Vitesse du curseur." #: lib/logitech_receiver/settings_templates.py:1402 msgid "Scale" msgstr "Échelle" #: lib/logitech_receiver/settings_templates.py:1408 msgid "Gestures" msgstr "Gestes" #: lib/logitech_receiver/settings_templates.py:1409 msgid "Tweak the mouse/touchpad behaviour." msgstr "Personnaliser le comportement de la souris ou du pavé tactile." #: lib/logitech_receiver/settings_templates.py:1425 msgid "Gestures Diversion" msgstr "Interception de la gestuelle" #: lib/logitech_receiver/settings_templates.py:1426 msgid "Divert mouse/touchpad gestures." msgstr "Interception de la gestuelle de la souris ou du pavé tactile." #: lib/logitech_receiver/settings_templates.py:1442 msgid "Gesture params" msgstr "Paramètres de la gestuelle" #: lib/logitech_receiver/settings_templates.py:1443 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:1467 msgid "M-Key LEDs" msgstr "LEDs de touche M" #: lib/logitech_receiver/settings_templates.py:1469 msgid "Control the M-Key LEDs." msgstr "Contrôler les LEDs de touche M." #: lib/logitech_receiver/settings_templates.py:1473 #: lib/logitech_receiver/settings_templates.py:1504 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:1479 #, python-format msgid "Lights up the %s key." msgstr "Allume la touche %s." #: lib/logitech_receiver/settings_templates.py:1498 msgid "MR-Key LED" msgstr "LED de touche MR" #: lib/logitech_receiver/settings_templates.py:1500 msgid "Control the MR-Key LED." msgstr "Contrôler la LED de touche MR." #: lib/logitech_receiver/settings_templates.py:1521 msgid "Persistent Key/Button Mapping" msgstr "Mappage touche/bouton persistant" #: lib/logitech_receiver/settings_templates.py:1523 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:1525 msgid "" "Changing important keys or buttons (such as for the left mouse button) can " "result in an unusable system." 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:1582 msgid "Sidetone" msgstr "Tonalité latérale" #: lib/logitech_receiver/settings_templates.py:1583 msgid "Set sidetone level." msgstr "Régler le niveau de tonalité latérale." #: lib/logitech_receiver/settings_templates.py:1592 msgid "Equalizer" msgstr "Égaliseur" #: lib/logitech_receiver/settings_templates.py:1593 #: lib/logitech_receiver/settings_templates.py:1902 msgid "Set equalizer levels." msgstr "Définir les niveaux de l'égaliseur." #: lib/logitech_receiver/settings_templates.py:1615 #: lib/logitech_receiver/settings_templates.py:1930 #: lib/logitech_receiver/settings_templates.py:2056 #: lib/logitech_receiver/settings_templates.py:2058 msgid "Hz" msgstr "Hz" #: lib/logitech_receiver/settings_templates.py:1621 msgid "Power Management" msgstr "Gestion de l'alimentation" #: lib/logitech_receiver/settings_templates.py:1622 msgid "Power off in minutes (0 for never)." msgstr "Éteindre en quelques minutes (0 pour jamais)." #: lib/logitech_receiver/settings_templates.py:1634 msgid "Eco Mode" msgstr "Mode éco" #: lib/logitech_receiver/settings_templates.py:1635 msgid "Battery saver mode." msgstr "Mode économie d'énergie." #: lib/logitech_receiver/settings_templates.py:1653 msgid "Do Not Disturb" msgstr "Ne pas déranger" #: lib/logitech_receiver/settings_templates.py:1654 msgid "Suppress notification sounds." msgstr "Supprimer les notifications sonores." #: lib/logitech_receiver/settings_templates.py:1661 msgid "Mic Mute" msgstr "Mise en sourdine du micro" #: lib/logitech_receiver/settings_templates.py:1662 msgid "Mute the microphone." msgstr "Coupez le son du micro." #: lib/logitech_receiver/settings_templates.py:1681 msgid "Mic SNR" msgstr "Rapport signal/bruit du micro" #: lib/logitech_receiver/settings_templates.py:1682 msgid "Microphone signal-to-noise ratio enhancement." msgstr "Amélioration du rapport signal/bruit du micro." #: lib/logitech_receiver/settings_templates.py:1689 msgid "AI Noise Reduction" msgstr "Réduction du bruit par IA" #: lib/logitech_receiver/settings_templates.py:1690 msgid "Enable AI noise reduction." msgstr "Activer la réduction du bruit par IA." #: lib/logitech_receiver/settings_templates.py:1697 msgid "AI Noise Reduction Level" msgstr "Niveau de réduction du bruit par IA" #: lib/logitech_receiver/settings_templates.py:1698 msgid "AI noise reduction intensity." msgstr "Intensité de réduction du bruit par IA." #: lib/logitech_receiver/settings_templates.py:1707 msgid "Headset Sidetone" msgstr "Retour audio du casque" #: lib/logitech_receiver/settings_templates.py:1708 msgid "Sidetone level (0 = off, 100 = max)." msgstr "Niveau de retour audio (0 = désactivé, 100 = maximum)." #: lib/logitech_receiver/settings_templates.py:1771 msgid "Mic Gain" msgstr "Gain du micro" #: lib/logitech_receiver/settings_templates.py:1772 msgid "Microphone gain level." msgstr "Niveau de gain du microphone." #: lib/logitech_receiver/settings_templates.py:1824 msgid "Audio Mix Balance" msgstr "Équilibrage du mixage audio" #: lib/logitech_receiver/settings_templates.py:1825 msgid "Balance between game and chat audio." msgstr "Équilibre entre le son du jeu et celui du chat." #: lib/logitech_receiver/settings_templates.py:1876 msgid "Auto Sleep Timeout" msgstr "Délai de mise en veille automatique" #: lib/logitech_receiver/settings_templates.py:1877 msgid "" "Idle time in minutes before the headset enters sleep mode (0 = disabled)." msgstr "" "Temps d'inactivité en minutes avant que le casque ne passe en mode veille (0 " "= désactivé)." #: lib/logitech_receiver/settings_templates.py:1901 msgid "Headset Equalizer" msgstr "Égaliseur du casque" #: lib/logitech_receiver/settings_templates.py:1996 msgid "Headset Advanced EQ" msgstr "Égaliseur avancé du casque" #: lib/logitech_receiver/settings_templates.py:1997 msgid "Per-band gain for the headset's active parametric EQ." msgstr "Gain par bande pour l'égaliseur paramétrique actif du casque." #: lib/logitech_receiver/settings_templates.py:2268 msgid "EQ Preset" msgstr "Préréglage de l'égaliseur" #: lib/logitech_receiver/settings_templates.py:2269 msgid "Switch the active EQ preset. Factory presets are read-only." msgstr "" "Basculer le préréglage d'égalisation actif. Les préréglages d'usine sont en " "lecture seule." #: lib/logitech_receiver/settings_templates.py:2294 msgid "Slot" msgstr "" #: lib/logitech_receiver/settings_templates.py:2296 msgid "(factory)" msgstr "(usine)" #: lib/logitech_receiver/settings_templates.py:2429 #: lib/logitech_receiver/settings_templates.py:3206 #: lib/logitech_receiver/settings_templates.py:3298 msgid "LED Control" msgstr "Contrôle de LED" #: lib/logitech_receiver/settings_templates.py:2430 msgid "Allow Solaar to control the headset LED zones." msgstr "Autoriser Solaar à contrôler les zones de LED du casque." #: lib/logitech_receiver/settings_templates.py:2478 msgid "Per-zone Lighting" msgstr "Éclairage par zone" #: lib/logitech_receiver/settings_templates.py:2480 msgid "" "Override individual zone colors. 'No change' inherits the LEDs Primary " "color.\n" "LED Control needs to be set to Solaar to be effective." msgstr "" "Remplacer les couleurs de chaque zone individuellement. L'option « Aucun " "changement » conserve la couleur principale des LEDs.\n" "Le contrôle de LED doit être défini sur Solaar pour être effectif." #: lib/logitech_receiver/settings_templates.py:2498 msgid "Zone" msgstr "Zone" #: lib/logitech_receiver/settings_templates.py:2620 #: lib/logitech_receiver/settings_templates.py:2908 #: lib/logitech_receiver/settings_templates.py:3648 msgid "Secondary" msgstr "Dérivé" #: lib/logitech_receiver/settings_templates.py:2621 #: lib/logitech_receiver/settings_templates.py:3234 msgid "Speed" msgstr "Vitesse" #: lib/logitech_receiver/settings_templates.py:2688 msgid "Startup Effect" msgstr "Effet de démarrage" #: lib/logitech_receiver/settings_templates.py:2690 msgid "Firmware lighting effect played when the headset powers on or wakes." msgstr "" #: lib/logitech_receiver/settings_templates.py:2692 #: lib/logitech_receiver/settings_templates.py:2703 msgid "Device default: Primary #00B8FC, Secondary #FF00AB, Speed 100." msgstr "" #: lib/logitech_receiver/settings_templates.py:2699 msgid "Shutdown Effect" msgstr "Effet de fermeture" #: lib/logitech_receiver/settings_templates.py:2701 msgid "Firmware lighting effect played when the headset powers off or sleeps." msgstr "" #: lib/logitech_receiver/settings_templates.py:2710 msgid "Passive Effect" msgstr "Effet passif" #: lib/logitech_receiver/settings_templates.py:2712 msgid "Firmware lighting effect played while the headset is idle." msgstr "" #: lib/logitech_receiver/settings_templates.py:2714 msgid "Device default: Primary #00B8FC, Secondary #FF00AB, Speed 75." msgstr "" #: lib/logitech_receiver/settings_templates.py:2833 msgid "Onboard Effect" msgstr "Effet embarqué" #: lib/logitech_receiver/settings_templates.py:2834 msgid "Firmware RGB effect the headset plays on its own." msgstr "" #: lib/logitech_receiver/settings_templates.py:2909 #: lib/logitech_receiver/settings_templates.py:3243 msgid "Intensity" msgstr "Intensité" #: lib/logitech_receiver/settings_templates.py:2910 #: lib/logitech_receiver/settings_templates.py:3245 msgid "Saturation" msgstr "Saturation" #: lib/logitech_receiver/settings_templates.py:2914 #: lib/logitech_receiver/settings_templates.py:3238 msgid "Period" msgstr "Période" #: lib/logitech_receiver/settings_templates.py:2919 #: lib/logitech_receiver/settings_templates.py:3250 msgid "Direction" msgstr "Direction" #: lib/logitech_receiver/settings_templates.py:3134 msgid "Brightness Control" msgstr "Contrôle de la luminosité" #: lib/logitech_receiver/settings_templates.py:3135 msgid "Control overall brightness" msgstr "Contrôle de la luminosité globale" #: lib/logitech_receiver/settings_templates.py:3207 #: lib/logitech_receiver/settings_templates.py:3299 msgid "Allow Solaar to control LED zones." msgstr "Autoriser Solaar à contrôler les zones de LED." #: lib/logitech_receiver/settings_templates.py:3230 #: lib/logitech_receiver/settings_templates.py:3744 msgid "LED Zone Effects" msgstr "Effet de zone de LED" #: lib/logitech_receiver/settings_templates.py:3231 #: lib/logitech_receiver/settings_templates.py:3399 #: lib/logitech_receiver/settings_templates.py:3439 #: lib/logitech_receiver/settings_templates.py:3546 #: lib/logitech_receiver/settings_templates.py:3745 msgid "LED Control needs to be enabled." msgstr "Le contrôle de LED doit être activé." #: lib/logitech_receiver/settings_templates.py:3231 #: lib/logitech_receiver/settings_templates.py:3745 msgid "Set effect for LED Zone" msgstr "Définir l'effet pour la zone de LED" #: lib/logitech_receiver/settings_templates.py:3233 msgid "Color" msgstr "Couleur" #: lib/logitech_receiver/settings_templates.py:3244 msgid "Ramp" msgstr "" #: lib/logitech_receiver/settings_templates.py:3246 msgid "Waveform" msgstr "" #: lib/logitech_receiver/settings_templates.py:3279 msgid "LEDs" msgstr "LEDs" #: lib/logitech_receiver/settings_templates.py:3398 msgid "Idle Timeout" msgstr "Délai d'inactivité" #: lib/logitech_receiver/settings_templates.py:3399 msgid "Time without input before LED idle effect starts." msgstr "" "Délai d'inactivité avant le déclenchement de l'effet de veille de la LED." #: lib/logitech_receiver/settings_templates.py:3435 msgid "Idle Effect" msgstr "Effet d'inactivité" #: lib/logitech_receiver/settings_templates.py:3437 msgid "" "What happens to LEDs when idle — dim to a percentage, change the base color, " "or play an animation." msgstr "" "Ce qu'il se passe lorsque les LEDs sont inactives — elles s'assombrissent à " "un certain pourcentage, leurs couleurs de base changent ou elles lancent une " "animation." #: lib/logitech_receiver/settings_templates.py:3520 msgid "No change" msgstr "Aucun changement" #: lib/logitech_receiver/settings_templates.py:3545 msgid "Sleep Timeout" msgstr "Délai de mise en veille" #: lib/logitech_receiver/settings_templates.py:3546 msgid "Time without input before LEDs fade off completely." msgstr "Durée d'inactivité avant l'extinction complète des LEDs." #: lib/logitech_receiver/settings_templates.py:3719 msgid "Startup Animation" msgstr "Animation de démarrage" #: lib/logitech_receiver/settings_templates.py:3722 msgid "" "Firmware-played animation when the keyboard wakes from deep sleep or powers " "on.\n" "Setting persists on the device (non-volatile)." msgstr "" #: lib/logitech_receiver/settings_templates.py:3726 #: lib/logitech_receiver/settings_templates.py:3737 msgid "Device default: Primary #FF0081, Secondary #80AAFF." msgstr "" #: lib/logitech_receiver/settings_templates.py:3733 msgid "Shutdown Animation" msgstr "Animation de fermeture" #: lib/logitech_receiver/settings_templates.py:3735 msgid "" "Firmware-played animation when the keyboard powers off.\n" "Setting persists on the device (non-volatile)." msgstr "" #: lib/logitech_receiver/settings_templates.py:3847 #: lib/solaar/ui/perkey/dialog.py:87 msgid "Per-key Lighting" msgstr "Éclairage par touche" #: lib/logitech_receiver/settings_templates.py:3849 msgid "Control per-key lighting." msgstr "Contrôler l’éclairage par touche." #: lib/logitech_receiver/settings_templates.py:3851 msgid "" "LED Control needs to be enabled and the zone effect set to Static for per-" "key paint to be visible." msgstr "" #: lib/logitech_receiver/settings_templates.py:4182 msgid "Force Sensing Buttons" msgstr "Boutons à détection de pression" #: lib/logitech_receiver/settings_templates.py:4183 msgid "Change the force required to activate button." msgstr "Modifier la force nécessaire pour activer le bouton." #: lib/logitech_receiver/settings_templates.py:4200 msgid "Force Sensing Button" msgstr "Bouton à détection de pression" #: lib/logitech_receiver/settings_templates.py:4316 msgid "Analog Button Tuning" msgstr "" #: lib/logitech_receiver/settings_templates.py:4317 msgid "" "Configure analog button settings including actuation point, rapid trigger, " "and haptics." msgstr "" #: lib/logitech_receiver/settings_templates.py:4347 #, python-format msgid "Actuation point depth (1=shallow, %d=deep)." msgstr "" #: lib/logitech_receiver/settings_templates.py:4355 #, python-format msgid "Rapid trigger sensitivity (1..%d)." msgstr "Sensibilité de déclenchement rapide (1..%d)." #: lib/logitech_receiver/settings_templates.py:4363 #, python-format msgid "Click haptic feedback intensity (0=off, %d=max)." msgstr "Intensité du retour haptique par clic (0=désactivé, %d=max)." #: lib/logitech_receiver/settings_templates.py:4371 msgid "Haptic Feedback Level" msgstr "Niveau de retour haptique" #: lib/logitech_receiver/settings_templates.py:4372 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:4414 msgid "Play Haptic Waveform" msgstr "" #: lib/logitech_receiver/settings_templates.py:4415 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:64 msgid "Additional Programming" msgstr "Programmation supplémentaire" #: lib/solaar/ui/about/model.py:65 msgid "GUI design" msgstr "Interface graphique" #: lib/solaar/ui/about/model.py:67 msgid "Testing" msgstr "Testeur" #: lib/solaar/ui/about/model.py:75 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:142 msgid "Cancel" msgstr "Annuler" #: lib/solaar/ui/common.py:42 msgid "Permissions error" msgstr "Erreur de permissions" #: 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 "" "Un récepteur ou un périphérique Logitech (%s) a été trouvé, mais sans " "l'autorisation de l'ouvrir." #: lib/solaar/ui/common.py:46 msgid "" "If you've just installed Solaar, try disconnecting the receiver or device " "and then reconnecting it." 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/common.py:49 msgid "Cannot connect to device error" msgstr "Impossible de se connecter au périphérique" #: 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 "" "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/common.py:53 msgid "" "Try disconnecting the device and then reconnecting it or turning it off and " "then on." msgstr "" "Essayez de déconnecter le périphérique, puis de le reconnecter ou de " "l'éteindre puis de le rallumer." #: lib/solaar/ui/common.py:56 msgid "Unpairing failed" msgstr "La déconnexion a échouée" #: 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/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/config_panel.py:254 msgid "Complete - ENTER to change" msgstr "Complet - ENTRER pour modifier" #: lib/solaar/ui/config_panel.py:254 msgid "Incomplete" msgstr "Incomplet" #: lib/solaar/ui/config_panel.py:500 lib/solaar/ui/config_panel.py:552 #: lib/solaar/ui/config_panel.py:619 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "valeur %d" msgstr[1] "valeurs %d" #: lib/solaar/ui/config_panel.py:805 msgid "Changes allowed" msgstr "Modifications autorisées" #: lib/solaar/ui/config_panel.py:806 msgid "No changes allowed" msgstr "Aucune modification autorisée" #: lib/solaar/ui/config_panel.py:807 msgid "Ignore this setting" msgstr "Ignorer ce réglage" #: lib/solaar/ui/config_panel.py:1022 msgid "Working" msgstr "En fonctionnement" #: lib/solaar/ui/config_panel.py:1025 msgid "Read/write operation failed." msgstr "Les opérations de lecture/écriture ont échouée." #: lib/solaar/ui/desktop_notifications.py:112 msgid "unspecified reason" msgstr "raison non précisée" #: lib/solaar/ui/diversion_rules.py:104 msgid "Built-in rules" msgstr "Règles pré-définies" #: lib/solaar/ui/diversion_rules.py:104 msgid "User-defined rules" msgstr "Règles définies par l'utilisateur" #: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:1127 msgid "Rule" msgstr "Règle" #: lib/solaar/ui/diversion_rules.py:107 lib/solaar/ui/diversion_rules.py:383 #: lib/solaar/ui/diversion_rules.py:510 msgid "Sub-rule" msgstr "Sous-règle" #: lib/solaar/ui/diversion_rules.py:109 msgid "[empty]" msgstr "[vide]" #: lib/solaar/ui/diversion_rules.py:133 msgid "Make changes permanent?" msgstr "Rendre les modifications permanentes ?" #: lib/solaar/ui/diversion_rules.py:138 msgid "Yes" msgstr "Oui" #: lib/solaar/ui/diversion_rules.py:140 msgid "No" msgstr "Non" #: lib/solaar/ui/diversion_rules.py:145 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:274 msgid "Paste here" msgstr "Coller ici" #: lib/solaar/ui/diversion_rules.py:276 msgid "Paste above" msgstr "Coller ci-dessus" #: lib/solaar/ui/diversion_rules.py:278 msgid "Paste below" msgstr "Coller ci-dessous" #: lib/solaar/ui/diversion_rules.py:284 msgid "Paste rule here" msgstr "Coller la règle ici" #: lib/solaar/ui/diversion_rules.py:286 msgid "Paste rule above" msgstr "Coller la règle ci-dessus" #: lib/solaar/ui/diversion_rules.py:288 msgid "Paste rule below" msgstr "Coller la règle ci-dessous" #: lib/solaar/ui/diversion_rules.py:292 msgid "Paste rule" msgstr "Coller la règle" #: lib/solaar/ui/diversion_rules.py:307 msgid "Insert here" msgstr "Insérer ici" #: lib/solaar/ui/diversion_rules.py:309 msgid "Insert above" msgstr "Insérer ci-dessus" #: lib/solaar/ui/diversion_rules.py:311 msgid "Insert below" msgstr "Insérer ci-dessous" #: lib/solaar/ui/diversion_rules.py:317 msgid "Insert new rule here" msgstr "Insérer une nouvelle règle ici" #: lib/solaar/ui/diversion_rules.py:319 msgid "Insert new rule above" msgstr "Insérer une nouvelle règle ci-dessus" #: lib/solaar/ui/diversion_rules.py:321 msgid "Insert new rule below" msgstr "Insérer une nouvelle règle ci-dessous" #: lib/solaar/ui/diversion_rules.py:348 msgid "Flatten" msgstr "Aplanir" #: lib/solaar/ui/diversion_rules.py:381 msgid "Insert" msgstr "Insérer" #: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:512 #: lib/solaar/ui/diversion_rules.py:1159 msgid "Or" msgstr "Ou" #: lib/solaar/ui/diversion_rules.py:385 lib/solaar/ui/diversion_rules.py:511 #: lib/solaar/ui/diversion_rules.py:1145 msgid "And" msgstr "Et" #: lib/solaar/ui/diversion_rules.py:387 msgid "Condition" msgstr "Condition" #: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:154 msgid "Feature" msgstr "Fonctionnalité" #: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:188 msgid "Report" msgstr "Rapport" #: lib/solaar/ui/diversion_rules.py:391 lib/solaar/ui/rule_conditions.py:69 msgid "Process" msgstr "Processus" #: lib/solaar/ui/diversion_rules.py:392 msgid "Mouse process" msgstr "Processus de la souris" #: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:225 msgid "Modifiers" msgstr "Modificateurs" #: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:277 msgid "Key" msgstr "Touche" #: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/rule_conditions.py:318 msgid "KeyIsDown" msgstr "" #: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1433 msgid "Active" msgstr "Actif" #: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1391 #: lib/solaar/ui/diversion_rules.py:1442 lib/solaar/ui/diversion_rules.py:1488 msgid "Device" msgstr "Périphérique" #: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1468 msgid "Host" msgstr "Hôte" #: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/diversion_rules.py:1507 msgid "Setting" msgstr "Réglage" #: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:333 #: lib/solaar/ui/rule_conditions.py:382 msgid "Test" msgstr "Test" #: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:507 msgid "Test bytes" msgstr "Octets de test" #: lib/solaar/ui/diversion_rules.py:402 lib/solaar/ui/rule_conditions.py:608 msgid "Mouse Gesture" msgstr "Geste à la souris" #: lib/solaar/ui/diversion_rules.py:406 msgid "Action" msgstr "Action" #: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:138 msgid "Key press" msgstr "Appui de touche" #: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:191 msgid "Mouse scroll" msgstr "Défilement de la souris" #: lib/solaar/ui/diversion_rules.py:410 lib/solaar/ui/rule_actions.py:256 msgid "Mouse click" msgstr "Clic de la souris" #: lib/solaar/ui/diversion_rules.py:411 msgid "Set" msgstr "Définir" #: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/rule_actions.py:328 msgid "Execute" msgstr "Exécuter" #: lib/solaar/ui/diversion_rules.py:413 lib/solaar/ui/diversion_rules.py:1190 msgid "Later" msgstr "Plus tard" #: lib/solaar/ui/diversion_rules.py:442 msgid "Insert new rule" msgstr "Insérer nouvelle règle" #: lib/solaar/ui/diversion_rules.py:462 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:484 msgid "Negate" msgstr "Nier" #: lib/solaar/ui/diversion_rules.py:508 msgid "Wrap with" msgstr "Entoure" #: lib/solaar/ui/diversion_rules.py:538 msgid "Cut" msgstr "Couper" #: lib/solaar/ui/diversion_rules.py:554 msgid "Paste" msgstr "Coller" #: lib/solaar/ui/diversion_rules.py:560 msgid "Copy" msgstr "Copier" #: lib/solaar/ui/diversion_rules.py:569 msgid "Solaar Rule Editor" msgstr "Éditeur de règle Solaar" #: lib/solaar/ui/diversion_rules.py:669 msgid "Save changes" msgstr "Sauvegarder les modifications" #: lib/solaar/ui/diversion_rules.py:674 msgid "Discard changes" msgstr "Annuler les modifications" #: lib/solaar/ui/diversion_rules.py:1105 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:1170 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:1208 msgid "Not" msgstr "Non" #: lib/solaar/ui/diversion_rules.py:1239 msgid "Toggle" msgstr "Basculer" #: lib/solaar/ui/diversion_rules.py:1240 msgid "True" msgstr "Vrai" #: lib/solaar/ui/diversion_rules.py:1241 msgid "False" msgstr "Faux" #: lib/solaar/ui/diversion_rules.py:1254 msgid "Unsupported setting" msgstr "Réglage non pris en charge" #: lib/solaar/ui/diversion_rules.py:1397 lib/solaar/ui/diversion_rules.py:1416 #: lib/solaar/ui/diversion_rules.py:1494 lib/solaar/ui/diversion_rules.py:1758 #: lib/solaar/ui/diversion_rules.py:1776 msgid "Originating device" msgstr "Périphérique d'origine" #: lib/solaar/ui/diversion_rules.py:1429 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:1438 msgid "Device that originated the current notification." msgstr "Périphérique à l’origine de la notification actuelle." #: lib/solaar/ui/diversion_rules.py:1451 msgid "Name of host computer." msgstr "Nom de l'ordinateur hôte." #: lib/solaar/ui/diversion_rules.py:1521 msgid "Value" msgstr "Valeur" #: lib/solaar/ui/diversion_rules.py:1530 msgid "Item" msgstr "Item" #: lib/solaar/ui/diversion_rules.py:1818 msgid "Change setting on device" msgstr "Modifier le réglage sur le périphérique" #: lib/solaar/ui/diversion_rules.py:1834 msgid "Setting on device" msgstr "Réglage sur le périphérique" #: 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: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: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: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: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: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: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" "\n" "This receiver has %d pairing remaining." msgid_plural "" "\n" "\n" "This receiver has %d pairings remaining." msgstr[0] "" "\n" "\n" "Ce récepteur a %d jumelage restant." msgstr[1] "" "\n" "\n" "Ce récepteur a %d jumelages restants." #: lib/solaar/ui/pair_window.py:78 msgid "" "\n" "Cancelling at this point will not use up a pairing." msgstr "" "\n" "L'annulation à ce stade n'utilisera pas de jumelage." #: 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/perkey/control.py:178 msgid "(not loaded)" msgstr "(non chargé)" #: lib/solaar/ui/perkey/control.py:182 msgid "Open editor…" msgstr "Ouvrir l'éditeur…" #: lib/solaar/ui/perkey/control.py:183 msgid "Paint key colors on a keyboard layout" msgstr "" #: lib/solaar/ui/perkey/control.py:229 msgid "(no zones)" msgstr "(aucune zones)" #: lib/solaar/ui/perkey/control.py:233 #, python-brace-format msgid "{painted} / {total} keys painted" msgstr "" #: lib/solaar/ui/perkey/editor.py:54 msgid "Brush" msgstr "" #: lib/solaar/ui/perkey/editor.py:54 msgid "Click or drag to paint individual keys" msgstr "Cliquez ou faites glisser pour colorer les touches individuellement" #: lib/solaar/ui/perkey/editor.py:55 msgid "Drag to select a rectangle of keys, painted on release" msgstr "" #: lib/solaar/ui/perkey/editor.py:55 msgid "Rect" msgstr "" #: lib/solaar/ui/perkey/editor.py:56 msgid "Fill" msgstr "Remplir" #: lib/solaar/ui/perkey/editor.py:56 msgid "Flood-fill connected keys of the same color with the active color" msgstr "" #: lib/solaar/ui/perkey/editor.py:59 msgid "Drag to fade from previous color to active color" msgstr "" #: lib/solaar/ui/perkey/palette.py:90 msgid "Active color" msgstr "Couleur active" #: lib/solaar/ui/perkey/palette.py:95 msgid "Paint as 'no change' — clears the cell to the zone base color" msgstr "" #: lib/solaar/ui/perkey/palette.py:96 msgid "Unset" msgstr "Non défini" #: 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:60 lib/solaar/ui/window.py:328 #, python-format msgid "About %s" msgstr "À propos de %s" #: 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:273 lib/solaar/ui/tray.py:281 msgid "no receiver" msgstr "aucun récepteur" #: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:299 #: lib/solaar/ui/window.py:734 msgid "offline" msgstr "non connecté" #: lib/solaar/ui/tray.py:297 msgid "no status" msgstr "aucun statut" #: lib/solaar/ui/window.py:110 msgid "Scanning" msgstr "Balayage" #: lib/solaar/ui/window.py:141 msgid "Battery" msgstr "Batterie" #: lib/solaar/ui/window.py:144 msgid "Wireless Link" msgstr "Connexion sans fil" #: lib/solaar/ui/window.py:148 msgid "Lighting" msgstr "Éclairage" #: lib/solaar/ui/window.py:182 msgid "Show Technical Details" msgstr "Afficher les détails techniques" #: lib/solaar/ui/window.py:198 msgid "Pair new device" msgstr "Jumeler un nouveau périphérique" #: lib/solaar/ui/window.py:216 msgid "Select a device" msgstr "Sélectionner un périphérique" #: lib/solaar/ui/window.py:331 msgid "Rule Editor" msgstr "Éditeur de règles" #: lib/solaar/ui/window.py:522 msgid "Path" msgstr "Chemin" #: lib/solaar/ui/window.py:524 msgid "USB ID" msgstr "ID USB" #: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 #: lib/solaar/ui/window.py:550 lib/solaar/ui/window.py:552 msgid "Serial" msgstr "Numéro de série" #: lib/solaar/ui/window.py:533 msgid "Index" msgstr "Index" #: lib/solaar/ui/window.py:535 msgid "Wireless PID" msgstr "PID sans fil" #: lib/solaar/ui/window.py:537 msgid "Product ID" msgstr "Identifiant produit" #: lib/solaar/ui/window.py:541 lib/solaar/ui/window.py:543 #: lib/solaar/ui/window.py:545 msgid "Protocol" msgstr "Protocole" #: lib/solaar/ui/window.py:545 msgid "Unknown" msgstr "Inconnu" #: lib/solaar/ui/window.py:547 msgid "Polling rate" msgstr "Taux de scrutation" #: lib/solaar/ui/window.py:554 msgid "Unit ID" msgstr "ID d'unité" #: lib/solaar/ui/window.py:565 msgid "none" msgstr "aucun" #: lib/solaar/ui/window.py:566 msgid "Notifications" msgstr "Notifications" #: lib/solaar/ui/window.py:610 msgid "No device paired." msgstr "Aucun périphérique jumelé." #: lib/solaar/ui/window.py:619 #, 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] "Jusqu'à %(max_count)s périphérique peut être jumelé à ce récepteur." msgstr[1] "" "Jusqu'à %(max_count)s périphériques peuvent être jumelés à ce récepteur." #: lib/solaar/ui/window.py:626 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:630 #, 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:687 msgid "Battery Voltage" msgstr "Tension de la batterie" #: lib/solaar/ui/window.py:689 msgid "Voltage reported by battery" msgstr "Tension indiquée par la batterie" #: lib/solaar/ui/window.py:691 msgid "Battery Level" msgstr "Niveau de la batterie" #: lib/solaar/ui/window.py:693 msgid "Approximate level reported by battery" msgstr "Niveau approximatif indiqué par la batterie" #: lib/solaar/ui/window.py:700 lib/solaar/ui/window.py:702 msgid "next reported " msgstr "prochain rapport " #: lib/solaar/ui/window.py:703 msgid " and next level to be reported." msgstr " et prochain niveau à rapporter." #: lib/solaar/ui/window.py:708 msgid "last known" msgstr "dernière valeur connue" #: lib/solaar/ui/window.py:719 msgid "encrypted" msgstr "chiffrée" #: lib/solaar/ui/window.py:721 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:723 msgid "not encrypted" msgstr "non chiffrée" #: lib/solaar/ui/window.py:727 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 "" "La connexion sans fil entre ce périphérique et son récepteur n'est pas " "chiffrée.\n" "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:743 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d lux" Solaar-1.1.20/po/ga.po000066400000000000000000002256551522022367600144050ustar00rootroot00000000000000# Irish 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. # # Aindriú Mac Giolla Eoin , 2026. msgid "" msgstr "" "Project-Id-Version: solaar 1.1.20rc2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-06-11 16:38+0100\n" "PO-Revision-Date: 2026-06-11 17:04+0000\n" "Last-Translator: Aindriú Mac Giolla Eoin \n" "Language-Team: Irish \n" "Language: ga\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 ? 1 : 2;\n" "X-Generator: Weblate 2026.6.1\n" #: lib/logitech_receiver/base_usb.py:52 msgid "Bolt Receiver" msgstr "Glacadóir Bolt" #: lib/logitech_receiver/base_usb.py:64 msgid "Unifying Receiver" msgstr "Glacadóir Aontaitheach" #: 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 "Glacadóir Nano" #: lib/logitech_receiver/base_usb.py:125 msgid "Lightspeed Receiver" msgstr "Glacadóir Lightspeed" #: lib/logitech_receiver/base_usb.py:136 msgid "EX100 Receiver 27 Mhz" msgstr "Glacadóir EX100 27 Mhz" #: lib/logitech_receiver/common.py:698 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "Ceallraí: %(level)s (%(status)s)" #: lib/logitech_receiver/common.py:701 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Ceallraí: %(percent)d%% (%(status)s)" #: lib/logitech_receiver/hidpp20.py:1189 lib/logitech_receiver/hidpp20.py:1222 #: lib/logitech_receiver/hidpp20.py:1250 msgid "Cycle" msgstr "Timthriall" #: lib/logitech_receiver/hidpp20.py:1190 msgid "Right" msgstr "Ar dheis" #: lib/logitech_receiver/hidpp20.py:1191 msgid "Down" msgstr "Síos" #: lib/logitech_receiver/hidpp20.py:1192 msgid "Center Out" msgstr "Lár Amach" #: lib/logitech_receiver/hidpp20.py:1193 msgid "In" msgstr "Isteach" #: lib/logitech_receiver/hidpp20.py:1194 msgid "Out" msgstr "Amach" #: lib/logitech_receiver/hidpp20.py:1195 #: lib/logitech_receiver/settings_templates.py:1398 msgid "Left" msgstr "Ar chlé" #: lib/logitech_receiver/hidpp20.py:1196 msgid "Up" msgstr "Suas" #: lib/logitech_receiver/hidpp20.py:1197 msgid "Center In" msgstr "Lár Isteach" #: lib/logitech_receiver/hidpp20.py:1218 #: lib/logitech_receiver/settings_templates.py:344 msgid "Disabled" msgstr "Míchumasaithe" #: lib/logitech_receiver/hidpp20.py:1219 msgid "Static" msgstr "Statach" #: lib/logitech_receiver/hidpp20.py:1220 msgid "Pulse" msgstr "Bíog" #: lib/logitech_receiver/hidpp20.py:1229 lib/logitech_receiver/hidpp20.py:1255 msgid "Wave" msgstr "Tonn" #: lib/logitech_receiver/hidpp20.py:1233 msgid "Boot" msgstr "Buatais" #: lib/logitech_receiver/hidpp20.py:1234 msgid "Demo" msgstr "Taispeántas" #: lib/logitech_receiver/hidpp20.py:1236 msgid "Breathe" msgstr "Análaigh" #: lib/logitech_receiver/hidpp20.py:1241 lib/logitech_receiver/hidpp20.py:1262 msgid "Ripple" msgstr "Tonnóg" #: lib/logitech_receiver/hidpp20.py:1246 msgid "Decomposition" msgstr "Dianscaoileadh" #: lib/logitech_receiver/hidpp20.py:1247 msgid "Signature1" msgstr "Síniú1" #: lib/logitech_receiver/hidpp20.py:1248 msgid "Signature2" msgstr "Síniú2" #: lib/logitech_receiver/hidpp20.py:1268 msgid "Dim" msgstr "Lag" #: lib/logitech_receiver/hidpp20.py:1340 msgid "Unknown Location" msgstr "Suíomh Anaithnid" #: lib/logitech_receiver/hidpp20.py:1341 #: lib/logitech_receiver/settings_templates.py:2619 #: lib/logitech_receiver/settings_templates.py:2907 #: lib/logitech_receiver/settings_templates.py:3647 msgid "Primary" msgstr "Príomhúil" #: lib/logitech_receiver/hidpp20.py:1342 msgid "Logo" msgstr "Lógó" #: lib/logitech_receiver/hidpp20.py:1343 msgid "Left Side" msgstr "Taobh Clé" #: lib/logitech_receiver/hidpp20.py:1344 msgid "Right Side" msgstr "Taobh Deas" #: lib/logitech_receiver/hidpp20.py:1345 msgid "Combined" msgstr "Comhcheangailte" #: lib/logitech_receiver/hidpp20.py:1346 msgid "Primary 1" msgstr "Bunscoil 1" #: lib/logitech_receiver/hidpp20.py:1347 msgid "Primary 2" msgstr "Bunscoil 2" #: lib/logitech_receiver/hidpp20.py:1348 msgid "Primary 3" msgstr "Bunscoil 3" #: lib/logitech_receiver/hidpp20.py:1349 msgid "Primary 4" msgstr "Bunscoil 4" #: lib/logitech_receiver/hidpp20.py:1350 msgid "Primary 5" msgstr "Bunscoil 5" #: lib/logitech_receiver/hidpp20.py:1351 msgid "Primary 6" msgstr "Bunscoil 6" #: lib/logitech_receiver/i18n.py:28 msgid "empty" msgstr "folamh" #: lib/logitech_receiver/i18n.py:29 msgid "critical" msgstr "criticiúil" #: lib/logitech_receiver/i18n.py:30 msgid "low" msgstr "íseal" #: lib/logitech_receiver/i18n.py:31 msgid "average" msgstr "meán" #: lib/logitech_receiver/i18n.py:32 msgid "good" msgstr "maith" #: lib/logitech_receiver/i18n.py:33 msgid "full" msgstr "lán" #: lib/logitech_receiver/i18n.py:35 msgid "discharging" msgstr "ag scaoileadh" #: lib/logitech_receiver/i18n.py:36 msgid "recharging" msgstr "athluchtú" #: lib/logitech_receiver/i18n.py:37 msgid "charging" msgstr "muirearú" #: lib/logitech_receiver/i18n.py:38 msgid "not charging" msgstr "gan muirearú" #: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "beagnach lán" #: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "cúisithe" #: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "athluchtú mall" #: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "ceallraí neamhbhailí" #: lib/logitech_receiver/i18n.py:43 msgid "thermal error" msgstr "earráid theirmeach" #: lib/logitech_receiver/i18n.py:44 msgid "error" msgstr "earráid" #: lib/logitech_receiver/i18n.py:45 msgid "standard" msgstr "caighdeánach" #: lib/logitech_receiver/i18n.py:46 msgid "fast" msgstr "tapa" #: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "mall" #: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "am scoir gléas" #: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "ní thacaítear leis an ngléas" #: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "an iomarca gléasanna" #: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "am scoir seicheamh" #: lib/logitech_receiver/i18n.py:54 msgid "Firmware" msgstr "Dochtearraí" #: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Tosaitheoir" #: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Crua-earraí" #: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "Eile" #: lib/logitech_receiver/i18n.py:59 #: lib/logitech_receiver/settings_templates.py:4336 msgid "Left Button" msgstr "Cnaipe Clé" #: lib/logitech_receiver/i18n.py:60 #: lib/logitech_receiver/settings_templates.py:4336 msgid "Right Button" msgstr "Cnaipe Deas" #: lib/logitech_receiver/i18n.py:61 msgid "Middle Button" msgstr "Cnaipe Lárnach" #: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "Cnaipe Ar Ais" #: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "Cnaipe Ar Aghaidh" #: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "Cnaipe Gotha Luiche" #: lib/logitech_receiver/i18n.py:65 msgid "Smart Shift" msgstr "Aistriú Cliste" #: lib/logitech_receiver/i18n.py:66 msgid "DPI Switch" msgstr "Lasc DPI" #: lib/logitech_receiver/i18n.py:67 msgid "Left Tilt" msgstr "Claonadh ar Chlé" #: lib/logitech_receiver/i18n.py:68 msgid "Right Tilt" msgstr "Claonadh ar Dheis" #: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "Cliceáil ar Chlé" #: lib/logitech_receiver/i18n.py:70 msgid "Right Click" msgstr "Cliceáil ar Dheis" #: lib/logitech_receiver/i18n.py:71 msgid "Mouse Middle Button" msgstr "Cnaipe Lárnach na Luiche" #: lib/logitech_receiver/i18n.py:72 msgid "Mouse Back Button" msgstr "Cnaipe Ar Ais Luiche" #: lib/logitech_receiver/i18n.py:73 msgid "Mouse Forward Button" msgstr "Cnaipe Ar Aghaidh Luiche" #: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "Nascleanúint Cnaipe Gotha" #: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "Cnaipe Scrollaigh Clé Luiche" #: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "Cnaipe Scrollaigh ar Dheis na Luiche" #: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "brúite" #: lib/logitech_receiver/i18n.py:79 msgid "released" msgstr "scaoilte" #: lib/logitech_receiver/notifications.py:157 lib/solaar/listener.py:350 msgid "connected" msgstr "ceangailte" #: lib/logitech_receiver/notifications.py:157 lib/solaar/listener.py:350 msgid "disconnected" msgstr "dícheangailte" #: lib/logitech_receiver/notifications.py:183 msgid "unpaired" msgstr "neamhphéireáilte" #: lib/logitech_receiver/notifications.py:232 msgid "powered on" msgstr "cumhachtaithe air" #: lib/logitech_receiver/notifications.py:284 msgid "ADC measurement notification" msgstr "Fógra tomhais ADC" #: lib/logitech_receiver/notifications.py:486 #: lib/logitech_receiver/notifications.py:542 msgid "pairing lock is closed" msgstr "Tá an glas péireála dúnta" #: lib/logitech_receiver/notifications.py:486 #: lib/logitech_receiver/notifications.py:542 msgid "pairing lock is open" msgstr "Tá an glas péireála oscailte" #: lib/logitech_receiver/notifications.py:505 msgid "discovery lock is closed" msgstr "Tá an glas fionnachtana dúnta" #: lib/logitech_receiver/notifications.py:505 msgid "discovery lock is open" msgstr "Tá an glas fionnachtana oscailte" #: lib/logitech_receiver/receiver.py:466 msgid "No paired devices." msgstr "Gan aon ghléasanna péireáilte." #: lib/logitech_receiver/receiver.py:468 lib/solaar/ui/window.py:612 #, python-format msgid "%(count)s paired device." msgid_plural "%(count)s paired devices." msgstr[0] "%(count)s gléas péireáilte." msgstr[1] "%(count)s gléasanna péireáilte." msgstr[2] "%(count)s gléasanna péireáilte." #: lib/logitech_receiver/settings.py:621 msgid "register" msgstr "cláraigh" #: lib/logitech_receiver/settings.py:635 lib/logitech_receiver/settings.py:673 msgid "feature" msgstr "gné" #: lib/logitech_receiver/settings_templates.py:183 msgid "Swap Fx function" msgstr "Feidhm Malartaithe Fx" #: lib/logitech_receiver/settings_templates.py:186 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 "" "Nuair a bheidh siad socraithe, cuirfidh na heochracha F1..F12 a bhfeidhm " "speisialta i ngníomh,\n" "agus ní mór duit an eochair FN a shealbhú síos chun a bhfeidhm chaighdeánach " "a ghníomhachtú." #: lib/logitech_receiver/settings_templates.py:191 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 "" "Nuair nach mbíonn siad socraithe, cuirfidh na heochracha F1..F12 a bhfeidhm " "chaighdeánach i ngníomh,\n" "agus ní mór duit an eochair FN a shealbhú síos chun a bhfeidhm speisialta a " "ghníomhachtú." #: lib/logitech_receiver/settings_templates.py:199 msgid "Hand Detection" msgstr "Brath Láimhe" #: lib/logitech_receiver/settings_templates.py:200 msgid "Turn on illumination when the hands hover over the keyboard." msgstr "" "Cuir an soilsiú ar siúl nuair a bhíonn na lámha ag luascadh os cionn an " "mhéarchláir." #: lib/logitech_receiver/settings_templates.py:207 msgid "Scroll Wheel Smooth Scrolling" msgstr "Roth Scrollaigh Scrollú Réidh" #: lib/logitech_receiver/settings_templates.py:208 #: lib/logitech_receiver/settings_templates.py:455 #: lib/logitech_receiver/settings_templates.py:484 msgid "High-sensitivity mode for vertical scroll with the wheel." msgstr "Mód ard-íogaireachta le haghaidh scrolla ingearach leis an roth." #: lib/logitech_receiver/settings_templates.py:215 msgid "Side Scrolling" msgstr "Scrolláil Taobh" #: lib/logitech_receiver/settings_templates.py:217 msgid "" "When disabled, pushing the wheel sideways sends custom button events\n" "instead of the standard side-scrolling events." msgstr "" "Nuair a bhíonn sé díchumasaithe, seoltar imeachtaí cnaipe saincheaptha nuair " "a bhrúitear an roth\n" "ar thaobh seachas na himeachtaí scrollaigh taobh caighdeánacha." #: lib/logitech_receiver/settings_templates.py:227 msgid "Sensitivity (DPI - older mice)" msgstr "Íogaireacht (DPI - lucha níos sine)" #: lib/logitech_receiver/settings_templates.py:228 #: lib/logitech_receiver/settings_templates.py:1064 #: lib/logitech_receiver/settings_templates.py:1092 msgid "Mouse movement sensitivity" msgstr "Íogaireacht ghluaiseacht luiche" #: lib/logitech_receiver/settings_templates.py:301 msgid "Backlight Timed" msgstr "Soilsiú Cúlra Amaithe" #: lib/logitech_receiver/settings_templates.py:302 #: lib/logitech_receiver/settings_templates.py:442 msgid "Set illumination time for keyboard." msgstr "Socraigh am soilsithe don mhéarchlár." #: lib/logitech_receiver/settings_templates.py:313 msgid "Backlight" msgstr "Cúlsholas" #: lib/logitech_receiver/settings_templates.py:314 msgid "" "Illumination level on keyboard. Changes made are only applied in Manual " "mode." msgstr "" "Leibhéal soilsithe ar an méarchlár. Ní chuirtear na hathruithe a dhéantar i " "bhfeidhm ach amháin sa mhodh Láimhe." #: lib/logitech_receiver/settings_templates.py:346 msgid "Automatic" msgstr "Uathoibríoch" #: lib/logitech_receiver/settings_templates.py:348 msgid "Manual" msgstr "Lámhleabhar" #: lib/logitech_receiver/settings_templates.py:350 msgid "Enabled" msgstr "Cumasaithe" #: lib/logitech_receiver/settings_templates.py:356 msgid "Backlight Level" msgstr "Leibhéal an tSolais Chúlra" #: lib/logitech_receiver/settings_templates.py:357 msgid "Illumination level on keyboard when in Manual mode." msgstr "Leibhéal soilsithe ar an méarchlár nuair a bhíonn sé i mód Láimhe." #: lib/logitech_receiver/settings_templates.py:414 msgid "Backlight Delay Hands Out" msgstr "Moill ar Sholas Cúlra Lámha Amach" #: lib/logitech_receiver/settings_templates.py:415 msgid "" "Delay in seconds until backlight fades out with hands away from keyboard." msgstr "" "Moill i soicindí go dtí go n-imíonn an cúlsholas agus na lámha ar shiúl ón " "méarchlár." #: lib/logitech_receiver/settings_templates.py:423 msgid "Backlight Delay Hands In" msgstr "Moill ar Sholas Cúlra Lámha Isteach" #: lib/logitech_receiver/settings_templates.py:424 msgid "Delay in seconds until backlight fades out with hands near keyboard." msgstr "" "Moill i soicindí go dtí go n-imíonn an cúlra agus na lámha in aice leis an " "méarchlár." #: lib/logitech_receiver/settings_templates.py:432 msgid "Backlight Delay Powered" msgstr "Moill ar Shoilse Cúltaca Cumhachtaithe" #: lib/logitech_receiver/settings_templates.py:433 msgid "Delay in seconds until backlight fades out with external power." msgstr "" "Moill i soicindí go dtí go n-imíonn an cúlsholas le cumhacht sheachtrach." #: lib/logitech_receiver/settings_templates.py:441 msgid "Backlight (Seconds)" msgstr "Solas Cúil (Soicindí)" #: lib/logitech_receiver/settings_templates.py:453 msgid "Scroll Wheel High Resolution" msgstr "Roth Scrollaigh Ardtaifigh" #: lib/logitech_receiver/settings_templates.py:457 #: lib/logitech_receiver/settings_templates.py:486 msgid "Set to ignore if scrolling is abnormally fast or slow" msgstr "" "Socraigh chun neamhaird a dhéanamh má tá an scrollú thar a bheith gasta nó " "mall" #: lib/logitech_receiver/settings_templates.py:464 #: lib/logitech_receiver/settings_templates.py:495 msgid "Scroll Wheel Diversion" msgstr "Atreorú Roth Scrollaigh" #: lib/logitech_receiver/settings_templates.py:466 msgid "" "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " "Solaar rules but are otherwise ignored)." msgstr "" "Cuir fógraí LOWRES_WHEEL HID++ ar an roth scrollála (a spreagann rialacha " "Solaar ach a ndéantar neamhaird orthu murach sin)." #: lib/logitech_receiver/settings_templates.py:473 msgid "Scroll Wheel Direction" msgstr "Treo Roth Scrollaigh" #: lib/logitech_receiver/settings_templates.py:474 msgid "Invert direction for vertical scroll with wheel." msgstr "Inbhéartaigh an treo le haghaidh scrolla ingearach le roth." #: lib/logitech_receiver/settings_templates.py:482 msgid "Scroll Wheel Resolution" msgstr "Rún Roth Scrollaigh" #: lib/logitech_receiver/settings_templates.py:497 msgid "" "Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "" "Cuir fógraí HIRES_WHEEL HID++ ar an roth scrollála (a spreagann rialacha " "Solaar ach a ndéantar neamhaird orthu murach sin)." #: lib/logitech_receiver/settings_templates.py:506 msgid "Sensitivity (Pointer Speed)" msgstr "Íogaireacht (Luas an Phointeora)" #: lib/logitech_receiver/settings_templates.py:507 msgid "Speed multiplier for mouse (256 is normal multiplier)." msgstr "Iolraitheoir luais don luch (is gnách go mbíonn 256 ina iolraitheoir)." #: lib/logitech_receiver/settings_templates.py:517 msgid "Thumb Wheel Diversion" msgstr "Atreorú Roth Ordóige" #: lib/logitech_receiver/settings_templates.py:519 msgid "" "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "" "Cuir fógraí THUMB_WHEEL HID++ chuig roth an ordóige (a spreagann rialacha " "Solaar ach a ndéantar neamhaird orthu murach sin)." #: lib/logitech_receiver/settings_templates.py:528 msgid "Thumb Wheel Direction" msgstr "Treo Roth Ordóige" #: lib/logitech_receiver/settings_templates.py:529 msgid "Invert thumb wheel scroll direction." msgstr "Inbhéartaigh treo scrolla roth an ordóige." #: lib/logitech_receiver/settings_templates.py:549 msgid "Onboard Profiles" msgstr "Próifílí Ar Bord" #: lib/logitech_receiver/settings_templates.py:550 msgid "" "Enable an onboard profile, which controls report rate, sensitivity, and " "button actions" msgstr "" "Cumasaigh próifíl ar bord, a rialaíonn ráta tuairiscithe, íogaireacht agus " "gníomhartha cnaipe" #: lib/logitech_receiver/settings_templates.py:594 #: lib/logitech_receiver/settings_templates.py:627 msgid "Report Rate" msgstr "Ráta Tuairiscithe" #: lib/logitech_receiver/settings_templates.py:596 #: lib/logitech_receiver/settings_templates.py:629 msgid "Frequency of device movement reports" msgstr "Minicíocht tuairiscí gluaiseachta gléasanna" #: lib/logitech_receiver/settings_templates.py:596 #: lib/logitech_receiver/settings_templates.py:629 #: lib/logitech_receiver/settings_templates.py:1064 #: lib/logitech_receiver/settings_templates.py:1092 #: lib/logitech_receiver/settings_templates.py:1471 #: lib/logitech_receiver/settings_templates.py:1502 msgid "May need Onboard Profiles set to Disable to be effective." msgstr "" "B’fhéidir go mbeadh gá le Próifílí Ar Bord a shocrú go Díchumasaigh le go " "mbeidh siad éifeachtach." #: lib/logitech_receiver/settings_templates.py:657 msgid "Divert crown events" msgstr "Imeachtaí corónach a atreorú" #: lib/logitech_receiver/settings_templates.py:658 msgid "" "Make crown send CROWN HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." msgstr "" "Cuir fógraí CROWN HID++ chuig an gcoróin (a spreagann rialacha Solaar ach a " "ndéantar neamhaird orthu murach sin)." #: lib/logitech_receiver/settings_templates.py:666 msgid "Crown smooth scroll" msgstr "Scrolla réidh na Corónach" #: lib/logitech_receiver/settings_templates.py:667 msgid "Set crown smooth scroll" msgstr "Socraigh scrolla réidh an choróin" #: lib/logitech_receiver/settings_templates.py:675 msgid "Divert G and M Keys" msgstr "Eochracha G agus M a atreorú" #: lib/logitech_receiver/settings_templates.py:676 msgid "" "Make G and M keys send HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." msgstr "" "Cuir fógraí HID++ ar na heochracha G agus M (a spreagann rialacha Solaar ach " "a ndéantar neamhaird orthu murach sin)." #: lib/logitech_receiver/settings_templates.py:690 msgid "Scroll Wheel Ratcheted" msgstr "Roth Scrollaigh Céimnithe" #: lib/logitech_receiver/settings_templates.py:691 msgid "" "Switch the mouse wheel between speed-controlled ratcheting and always " "freespin." msgstr "" "Athraigh roth na luiche idir raicéadú rialaithe luais agus casadh saor in " "aisce i gcónaí." #: lib/logitech_receiver/settings_templates.py:693 msgid "Freespinning" msgstr "Sníomh saor in aisce" #: lib/logitech_receiver/settings_templates.py:693 msgid "Ratcheted" msgstr "Céimnithe" #: lib/logitech_receiver/settings_templates.py:700 msgid "Scroll Wheel Ratchet Speed" msgstr "Luas Raicéad Roth Scrollaigh" #: lib/logitech_receiver/settings_templates.py:702 msgid "" "Use the mouse wheel speed to switch between ratcheted and freespinning.\n" "The mouse wheel is always ratcheted at 50." msgstr "" "Bain úsáid as luas roth na luiche chun aistriú idir luas raicéadaithe agus " "luas saor-sníomhach.\n" "Bíonn luas raicéadaithe 50 ar roth na luiche i gcónaí." #: lib/logitech_receiver/settings_templates.py:752 msgid "Scroll Wheel Ratchet Torque" msgstr "Chasmhóiminte Raicéad Roth Scrollaigh" #: lib/logitech_receiver/settings_templates.py:753 msgid "Change the torque needed to overcome the ratchet." msgstr "Athraigh an chasmhóimint atá riachtanach chun an raicéad a shárú." #: lib/logitech_receiver/settings_templates.py:788 msgid "Key/Button Actions" msgstr "Gníomhartha Eochracha/Cnaipí" #: lib/logitech_receiver/settings_templates.py:790 msgid "Change the action for the key or button." msgstr "Athraigh an gníomh don eochair nó don chnaipe." #: lib/logitech_receiver/settings_templates.py:792 msgid "Overridden by diversion." msgstr "Sáraithe ag atreorú." #: lib/logitech_receiver/settings_templates.py:794 msgid "" "Changing important actions (such as for the left mouse button) can result in " "an unusable system." msgstr "" "Is féidir go mbeadh córas neamhúsáideach mar thoradh ar ghníomhartha " "tábhachtacha a athrú (mar shampla an cnaipe luiche ar chlé)." #: lib/logitech_receiver/settings_templates.py:969 msgid "Key/Button Diversion" msgstr "Atreorú Eochrach/Cnaipe" #: lib/logitech_receiver/settings_templates.py:970 msgid "" "Make the key or button send HID++ notifications (Diverted) or initiate Mouse " "Gestures or Sliding DPI" msgstr "" "Cuir an eochair nó an cnaipe ag seoladh fógraí HID++ (Atreoraithe) nó cuir " "tús le Gothaí Luiche nó DPI Sleamhnáin" #: lib/logitech_receiver/settings_templates.py:973 #: lib/logitech_receiver/settings_templates.py:974 #: lib/logitech_receiver/settings_templates.py:975 msgid "Diverted" msgstr "Atreoraithe" #: lib/logitech_receiver/settings_templates.py:973 #: lib/logitech_receiver/settings_templates.py:974 msgid "Mouse Gestures" msgstr "Gothaí Luiche" #: lib/logitech_receiver/settings_templates.py:973 #: lib/logitech_receiver/settings_templates.py:974 #: lib/logitech_receiver/settings_templates.py:975 msgid "Regular" msgstr "Rialta" #: lib/logitech_receiver/settings_templates.py:973 msgid "Sliding DPI" msgstr "DPI sleamhnáin" #: lib/logitech_receiver/settings_templates.py:1063 #: lib/logitech_receiver/settings_templates.py:1091 msgid "Sensitivity (DPI)" msgstr "Íogaireacht (DPI)" #: lib/logitech_receiver/settings_templates.py:1173 msgid "Sensitivity Switching" msgstr "Athrú Íogaireachta" #: lib/logitech_receiver/settings_templates.py:1175 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 "" "Athraigh an íogaireacht reatha agus an íogaireacht mheabhraithe nuair a " "bhrúitear an eochair nó an cnaipe.\n" "Mura bhfuil aon íogaireacht mheabhraithe ann, cuimhnigh ar an íogaireacht " "reatha amháin" #: lib/logitech_receiver/settings_templates.py:1179 msgid "Off" msgstr "As" #: lib/logitech_receiver/settings_templates.py:1210 msgid "Disable keys" msgstr "Díchumasaigh eochracha" #: lib/logitech_receiver/settings_templates.py:1211 msgid "Disable specific keyboard keys." msgstr "Díchumasaigh eochracha sonracha ar an méarchlár." #: lib/logitech_receiver/settings_templates.py:1214 #, python-format msgid "Disables the %s key." msgstr "Díchumasaíonn sé an eochair %s." #: lib/logitech_receiver/settings_templates.py:1227 #: lib/logitech_receiver/settings_templates.py:1284 msgid "Set OS" msgstr "Socraigh OS" #: lib/logitech_receiver/settings_templates.py:1228 #: lib/logitech_receiver/settings_templates.py:1285 msgid "Change keys to match OS." msgstr "Athraigh eochracha chun go n-oirfidh siad don chóras oibriúcháin." #: lib/logitech_receiver/settings_templates.py:1297 msgid "Change Host" msgstr "Athraigh Óstach" #: lib/logitech_receiver/settings_templates.py:1298 msgid "Switch connection to a different host" msgstr "Athraigh nasc chuig óstach difriúil" #: lib/logitech_receiver/settings_templates.py:1322 msgid "Performs a left click." msgstr "Déanann cliceáil ar chlé." #: lib/logitech_receiver/settings_templates.py:1322 msgid "Single tap" msgstr "Tapáil aonair" #: lib/logitech_receiver/settings_templates.py:1323 msgid "Performs a right click." msgstr "Déanann cliceáil ar dheis." #: lib/logitech_receiver/settings_templates.py:1323 msgid "Single tap with two fingers" msgstr "Tapáil aonair le dhá mhéar" #: lib/logitech_receiver/settings_templates.py:1324 msgid "Single tap with three fingers" msgstr "Tapáil aonair le trí mhéar" #: lib/logitech_receiver/settings_templates.py:1328 msgid "Double tap" msgstr "Tapáil faoi dhó" #: lib/logitech_receiver/settings_templates.py:1328 msgid "Performs a double click." msgstr "Déanann cliceáil dhúbailte." #: lib/logitech_receiver/settings_templates.py:1329 msgid "Double tap with two fingers" msgstr "Tapáil faoi dhó le dhá mhéar" #: lib/logitech_receiver/settings_templates.py:1330 msgid "Double tap with three fingers" msgstr "Tapáil faoi dhó le trí mhéar" #: lib/logitech_receiver/settings_templates.py:1333 msgid "Drags items by dragging the finger after double tapping." msgstr "" "Tarraingíonn sé míreanna tríd an méar a tharraingt tar éis tapáil faoi dhó." #: lib/logitech_receiver/settings_templates.py:1333 msgid "Tap and drag" msgstr "Tapáil agus tarraing" #: lib/logitech_receiver/settings_templates.py:1335 msgid "Tap and drag with two fingers" msgstr "Tapáil agus tarraing le dhá mhéar" #: lib/logitech_receiver/settings_templates.py:1336 msgid "Drags items by dragging the fingers after double tapping." msgstr "" "Tarraingíonn sé míreanna trí na méara a tharraingt tar éis dóibh tapáil faoi " "dhó." #: lib/logitech_receiver/settings_templates.py:1338 msgid "Tap and drag with three fingers" msgstr "Tapáil agus tarraing le trí mhéar" #: lib/logitech_receiver/settings_templates.py:1341 msgid "Suppress tap and edge gestures" msgstr "Coisc gothaí tapála agus imeall" #: lib/logitech_receiver/settings_templates.py:1342 msgid "Disables tap and edge gestures (equivalent to pressing Fn+LeftClick)." msgstr "" "Díchumasaíonn sé gothaí tapála agus imeall (comhionann le brúigh Fn+Cliceáil " "Ar Chlé)." #: lib/logitech_receiver/settings_templates.py:1344 msgid "Scroll with one finger" msgstr "Scrollaigh le méar amháin" #: lib/logitech_receiver/settings_templates.py:1344 #: lib/logitech_receiver/settings_templates.py:1345 #: lib/logitech_receiver/settings_templates.py:1348 msgid "Scrolls." msgstr "Scrollaí." #: lib/logitech_receiver/settings_templates.py:1345 #: lib/logitech_receiver/settings_templates.py:1348 msgid "Scroll with two fingers" msgstr "Scrollaigh le dhá mhéar" #: lib/logitech_receiver/settings_templates.py:1346 msgid "Scroll horizontally with two fingers" msgstr "Scrollaigh go cothrománach le dhá mhéar" #: lib/logitech_receiver/settings_templates.py:1346 msgid "Scrolls horizontally." msgstr "Scrollaíonn go cothrománach." #: lib/logitech_receiver/settings_templates.py:1347 msgid "Scroll vertically with two fingers" msgstr "Scrollaigh go hingearach le dhá mhéar" #: lib/logitech_receiver/settings_templates.py:1347 msgid "Scrolls vertically." msgstr "Scrollaíonn go hingearach." #: lib/logitech_receiver/settings_templates.py:1349 msgid "Inverts the scrolling direction." msgstr "Déanann sé treo an scrollaigh a aisiompú." #: lib/logitech_receiver/settings_templates.py:1349 msgid "Natural scrolling" msgstr "Scrolláil nádúrtha" #: lib/logitech_receiver/settings_templates.py:1350 msgid "Enables the thumbwheel." msgstr "Cumasaíonn sé an roth ordóige." #: lib/logitech_receiver/settings_templates.py:1350 msgid "Thumbwheel" msgstr "Roth ordóige" #: lib/logitech_receiver/settings_templates.py:1361 #: lib/logitech_receiver/settings_templates.py:1365 msgid "Swipe from the top edge" msgstr "Svaidhpeáil ón imeall uachtarach" #: lib/logitech_receiver/settings_templates.py:1362 msgid "Swipe from the left edge" msgstr "Svaidhpeáil ón imeall clé" #: lib/logitech_receiver/settings_templates.py:1363 msgid "Swipe from the right edge" msgstr "Svaidhpeáil ón imeall deas" #: lib/logitech_receiver/settings_templates.py:1364 msgid "Swipe from the bottom edge" msgstr "Svaidhpeáil ón imeall bun" #: lib/logitech_receiver/settings_templates.py:1366 msgid "Swipe two fingers from the left edge" msgstr "Svaidhpeáil dhá mhéar ón imeall clé" #: lib/logitech_receiver/settings_templates.py:1367 msgid "Swipe two fingers from the right edge" msgstr "Svaidhpeáil dhá mhéar ón imeall deas" #: lib/logitech_receiver/settings_templates.py:1368 msgid "Swipe two fingers from the bottom edge" msgstr "Svaidhpeáil dhá mhéar ón imeall bun" #: lib/logitech_receiver/settings_templates.py:1369 msgid "Swipe two fingers from the top edge" msgstr "Svaidhpeáil dhá mhéar ón imeall uachtarach" #: lib/logitech_receiver/settings_templates.py:1370 #: lib/logitech_receiver/settings_templates.py:1374 msgid "Pinch to zoom out; spread to zoom in." msgstr "Brúigh le zúmáil amach; scaip le zúmáil isteach." #: lib/logitech_receiver/settings_templates.py:1370 msgid "Zoom with two fingers." msgstr "Zúmáil le dhá mhéar." #: lib/logitech_receiver/settings_templates.py:1371 msgid "Pinch to zoom out." msgstr "Brúigh chun zúmáil amach." #: lib/logitech_receiver/settings_templates.py:1372 msgid "Spread to zoom in." msgstr "Scaip chun zúmáil isteach." #: lib/logitech_receiver/settings_templates.py:1373 msgid "Zoom with three fingers." msgstr "Zúmáil le trí mhéar." #: lib/logitech_receiver/settings_templates.py:1374 msgid "Zoom with two fingers" msgstr "Zúmáil le dhá mhéar" #: lib/logitech_receiver/settings_templates.py:1392 msgid "Pixel zone" msgstr "Crios picteilíní" #: lib/logitech_receiver/settings_templates.py:1393 msgid "Ratio zone" msgstr "Crios cóimheasa" #: lib/logitech_receiver/settings_templates.py:1394 msgid "Scale factor" msgstr "Fachtóir scála" #: lib/logitech_receiver/settings_templates.py:1394 msgid "Sets the cursor speed." msgstr "Socraíonn sé luas an chúrsóra." #: lib/logitech_receiver/settings_templates.py:1398 msgid "Left-most coordinate." msgstr "Comhordanáid is faide ar chlé." #: lib/logitech_receiver/settings_templates.py:1399 msgid "Bottom" msgstr "Bun" #: lib/logitech_receiver/settings_templates.py:1399 msgid "Bottom coordinate." msgstr "Comhordanáid bun." #: lib/logitech_receiver/settings_templates.py:1400 msgid "Width" msgstr "Leithead" #: lib/logitech_receiver/settings_templates.py:1400 msgid "Width." msgstr "Leithead." #: lib/logitech_receiver/settings_templates.py:1401 msgid "Height" msgstr "Airde" #: lib/logitech_receiver/settings_templates.py:1401 msgid "Height." msgstr "Airde." #: lib/logitech_receiver/settings_templates.py:1402 msgid "Cursor speed." msgstr "Luas an chúrsóra." #: lib/logitech_receiver/settings_templates.py:1402 msgid "Scale" msgstr "Scála" #: lib/logitech_receiver/settings_templates.py:1408 msgid "Gestures" msgstr "Gothaí" #: lib/logitech_receiver/settings_templates.py:1409 msgid "Tweak the mouse/touchpad behaviour." msgstr "Coigeartaigh iompar na luiche/an tadhaill." #: lib/logitech_receiver/settings_templates.py:1425 msgid "Gestures Diversion" msgstr "Gothaí a atreorú" #: lib/logitech_receiver/settings_templates.py:1426 msgid "Divert mouse/touchpad gestures." msgstr "Atreoraigh gothaí luiche/tadhallcheap." #: lib/logitech_receiver/settings_templates.py:1442 msgid "Gesture params" msgstr "Paraiméadair gotha" #: lib/logitech_receiver/settings_templates.py:1443 msgid "Change numerical parameters of a mouse/touchpad." msgstr "Athraigh paraiméadair uimhriúla luiche/tadhallcheap." #: lib/logitech_receiver/settings_templates.py:1467 msgid "M-Key LEDs" msgstr "LED M-Eochair" #: lib/logitech_receiver/settings_templates.py:1469 msgid "Control the M-Key LEDs." msgstr "Rialú na soilse LED M-Key." #: lib/logitech_receiver/settings_templates.py:1473 #: lib/logitech_receiver/settings_templates.py:1504 msgid "May need G Keys diverted to be effective." msgstr "" "B’fhéidir go mbeadh gá le heochracha G a atreorú le go mbeidh siad " "éifeachtach." #: lib/logitech_receiver/settings_templates.py:1479 #, python-format msgid "Lights up the %s key." msgstr "Lasann an eochair %s suas." #: lib/logitech_receiver/settings_templates.py:1498 msgid "MR-Key LED" msgstr "LED MR-Key" #: lib/logitech_receiver/settings_templates.py:1500 msgid "Control the MR-Key LED." msgstr "Rialú an LED MR-Key." #: lib/logitech_receiver/settings_templates.py:1521 msgid "Persistent Key/Button Mapping" msgstr "Mapáil Eochrach/Cnaipe Buan" #: lib/logitech_receiver/settings_templates.py:1523 msgid "Permanently change the mapping for the key or button." msgstr "Athraigh mapáil na heochrach nó an chnaipe go buan." #: lib/logitech_receiver/settings_templates.py:1525 msgid "" "Changing important keys or buttons (such as for the left mouse button) can " "result in an unusable system." msgstr "" "Is féidir go mbeadh córas neamhúsáideach mar thoradh ar eochracha nó cnaipí " "tábhachtacha a athrú (mar shampla an cnaipe luiche clé)." #: lib/logitech_receiver/settings_templates.py:1582 msgid "Sidetone" msgstr "Taobh-thon" #: lib/logitech_receiver/settings_templates.py:1583 msgid "Set sidetone level." msgstr "Socraigh leibhéal taobh-thon." #: lib/logitech_receiver/settings_templates.py:1592 msgid "Equalizer" msgstr "Cothromóir" #: lib/logitech_receiver/settings_templates.py:1593 #: lib/logitech_receiver/settings_templates.py:1902 msgid "Set equalizer levels." msgstr "Socraigh leibhéil cothromóra." #: lib/logitech_receiver/settings_templates.py:1615 #: lib/logitech_receiver/settings_templates.py:1930 #: lib/logitech_receiver/settings_templates.py:2056 #: lib/logitech_receiver/settings_templates.py:2058 msgid "Hz" msgstr "Hz" #: lib/logitech_receiver/settings_templates.py:1621 msgid "Power Management" msgstr "Bainistíocht Cumhachta" #: lib/logitech_receiver/settings_templates.py:1622 msgid "Power off in minutes (0 for never)." msgstr "Múch an chumhacht i nóiméid (0 le haghaidh riamh)." #: lib/logitech_receiver/settings_templates.py:1634 msgid "Eco Mode" msgstr "Mód Éicea" #: lib/logitech_receiver/settings_templates.py:1635 msgid "Battery saver mode." msgstr "Mód sábhála ceallraí." #: lib/logitech_receiver/settings_templates.py:1653 msgid "Do Not Disturb" msgstr "Ná Cuir Isteach" #: lib/logitech_receiver/settings_templates.py:1654 msgid "Suppress notification sounds." msgstr "Cuir fuaimeanna fógra faoi chois." #: lib/logitech_receiver/settings_templates.py:1661 msgid "Mic Mute" msgstr "Micreafón Balbhaigh" #: lib/logitech_receiver/settings_templates.py:1662 msgid "Mute the microphone." msgstr "Balbhaigh an micreafón." #: lib/logitech_receiver/settings_templates.py:1681 msgid "Mic SNR" msgstr "SNR micreafóin" #: lib/logitech_receiver/settings_templates.py:1682 msgid "Microphone signal-to-noise ratio enhancement." msgstr "Feabhsú cóimheas comhartha-go-torann micreafóin." #: lib/logitech_receiver/settings_templates.py:1689 msgid "AI Noise Reduction" msgstr "Laghdú Torainn AI" #: lib/logitech_receiver/settings_templates.py:1690 msgid "Enable AI noise reduction." msgstr "Cumasaigh laghdú torainn AI." #: lib/logitech_receiver/settings_templates.py:1697 msgid "AI Noise Reduction Level" msgstr "Leibhéal Laghdaithe Torainn AI" #: lib/logitech_receiver/settings_templates.py:1698 msgid "AI noise reduction intensity." msgstr "Déine laghdaithe torainn AI." #: lib/logitech_receiver/settings_templates.py:1707 msgid "Headset Sidetone" msgstr "Taobh-thon na gCluasán" #: lib/logitech_receiver/settings_templates.py:1708 msgid "Sidetone level (0 = off, 100 = max)." msgstr "Leibhéal taobh-thon (0 = múchta, 100 = uasmhéid)." #: lib/logitech_receiver/settings_templates.py:1771 msgid "Mic Gain" msgstr "Gnóthachan Micreafóin" #: lib/logitech_receiver/settings_templates.py:1772 msgid "Microphone gain level." msgstr "Leibhéal gnóthachain micreafóin." #: lib/logitech_receiver/settings_templates.py:1824 msgid "Audio Mix Balance" msgstr "Cothromaíocht Meascáin Fuaime" #: lib/logitech_receiver/settings_templates.py:1825 msgid "Balance between game and chat audio." msgstr "Cothromaíocht idir fuaim an chluiche agus an chomhrá." #: lib/logitech_receiver/settings_templates.py:1876 msgid "Auto Sleep Timeout" msgstr "Am Teorann Codlata Uathoibríoch" #: lib/logitech_receiver/settings_templates.py:1877 msgid "" "Idle time in minutes before the headset enters sleep mode (0 = disabled)." msgstr "" "Am díomhaoin i nóiméid sula dtéann an cluasán isteach i mód codlata (0 = " "díchumasaithe)." #: lib/logitech_receiver/settings_templates.py:1901 msgid "Headset Equalizer" msgstr "Cothromóir Cluasán" #: lib/logitech_receiver/settings_templates.py:1996 msgid "Headset Advanced EQ" msgstr "Cothromóir Ardleibhéil Cluasán" #: lib/logitech_receiver/settings_templates.py:1997 msgid "Per-band gain for the headset's active parametric EQ." msgstr "" "Gnóthachan in aghaidh an bhanda do chothromóir paraiméadrach gníomhach na " "gcluasán." #: lib/logitech_receiver/settings_templates.py:2268 msgid "EQ Preset" msgstr "Réamhshocrú Cothromóra" #: lib/logitech_receiver/settings_templates.py:2269 msgid "Switch the active EQ preset. Factory presets are read-only." msgstr "" "Athraigh an réamhshocrú cothromóra gníomhach. Is inléite amháin atá " "réamhshocruithe monarchan." #: lib/logitech_receiver/settings_templates.py:2294 msgid "Slot" msgstr "Sliotán" #: lib/logitech_receiver/settings_templates.py:2296 msgid "(factory)" msgstr "(monarcha)" #: lib/logitech_receiver/settings_templates.py:2429 #: lib/logitech_receiver/settings_templates.py:3206 #: lib/logitech_receiver/settings_templates.py:3298 msgid "LED Control" msgstr "Rialú LED" #: lib/logitech_receiver/settings_templates.py:2430 msgid "Allow Solaar to control the headset LED zones." msgstr "Lig do Solaar criosanna LED na gcluasán a rialú." #: lib/logitech_receiver/settings_templates.py:2478 msgid "Per-zone Lighting" msgstr "Soilsiú in aghaidh an chrios" #: lib/logitech_receiver/settings_templates.py:2480 msgid "" "Override individual zone colors. 'No change' inherits the LEDs Primary " "color.\n" "LED Control needs to be set to Solaar to be effective." msgstr "" "Sáraigh dathanna criosanna aonair. Faigheann 'Gan athrú' dath príomhúil na " "soilse LED mar oidhreacht.\n" "Ní mór Rialú LED a shocrú go Solaar le go mbeidh sé éifeachtach." #: lib/logitech_receiver/settings_templates.py:2498 msgid "Zone" msgstr "Crios" #: lib/logitech_receiver/settings_templates.py:2620 #: lib/logitech_receiver/settings_templates.py:2908 #: lib/logitech_receiver/settings_templates.py:3648 msgid "Secondary" msgstr "Tánaisteach" #: lib/logitech_receiver/settings_templates.py:2621 #: lib/logitech_receiver/settings_templates.py:3234 msgid "Speed" msgstr "Luas" #: lib/logitech_receiver/settings_templates.py:2688 msgid "Startup Effect" msgstr "Éifeacht Tosaithe" #: lib/logitech_receiver/settings_templates.py:2690 msgid "Firmware lighting effect played when the headset powers on or wakes." msgstr "" "Éifeacht soilsithe dochtearraí a sheinmtear nuair a chuirtear na cluasáin ar " "siúl nó nuair a dhúisítear iad." #: lib/logitech_receiver/settings_templates.py:2692 #: lib/logitech_receiver/settings_templates.py:2703 msgid "Device default: Primary #00B8FC, Secondary #FF00AB, Speed 100." msgstr "Réamhshocrú gléis: Príomhghléas #00B8FC, Meánghléas #FF00AB, Luas 100." #: lib/logitech_receiver/settings_templates.py:2699 msgid "Shutdown Effect" msgstr "Éifeacht Múchta" #: lib/logitech_receiver/settings_templates.py:2701 msgid "Firmware lighting effect played when the headset powers off or sleeps." msgstr "" "Éifeacht soilsithe dochtearraí a sheinntear nuair a mhúchtar na cluasáin nó " "nuair a théann siad i gcodladh." #: lib/logitech_receiver/settings_templates.py:2710 msgid "Passive Effect" msgstr "Éifeacht Éighníomhach" #: lib/logitech_receiver/settings_templates.py:2712 msgid "Firmware lighting effect played while the headset is idle." msgstr "Éifeacht soilsithe dochtearraí á seinnt agus an cluasán díomhaoin." #: lib/logitech_receiver/settings_templates.py:2714 msgid "Device default: Primary #00B8FC, Secondary #FF00AB, Speed 75." msgstr "Réamhshocrú gléis: Príomhghléas #00B8FC, Meánghléas #FF00AB, Luas 75." #: lib/logitech_receiver/settings_templates.py:2833 msgid "Onboard Effect" msgstr "Éifeacht Ar Bord" #: lib/logitech_receiver/settings_templates.py:2834 msgid "Firmware RGB effect the headset plays on its own." msgstr "Éifeacht RGB Dochtearraí a sheinneann an cluasán leis féin." #: lib/logitech_receiver/settings_templates.py:2909 #: lib/logitech_receiver/settings_templates.py:3243 msgid "Intensity" msgstr "Déine" #: lib/logitech_receiver/settings_templates.py:2910 #: lib/logitech_receiver/settings_templates.py:3245 msgid "Saturation" msgstr "Sáithiú" #: lib/logitech_receiver/settings_templates.py:2914 #: lib/logitech_receiver/settings_templates.py:3238 msgid "Period" msgstr "Tréimhse" #: lib/logitech_receiver/settings_templates.py:2919 #: lib/logitech_receiver/settings_templates.py:3250 msgid "Direction" msgstr "Treo" #: lib/logitech_receiver/settings_templates.py:3134 msgid "Brightness Control" msgstr "Rialú Gile" #: lib/logitech_receiver/settings_templates.py:3135 msgid "Control overall brightness" msgstr "Rialú gile iomlán" #: lib/logitech_receiver/settings_templates.py:3207 #: lib/logitech_receiver/settings_templates.py:3299 msgid "Allow Solaar to control LED zones." msgstr "Lig do Solaar criosanna LED a rialú." #: lib/logitech_receiver/settings_templates.py:3230 #: lib/logitech_receiver/settings_templates.py:3744 msgid "LED Zone Effects" msgstr "Éifeachtaí Crios LED" #: lib/logitech_receiver/settings_templates.py:3231 #: lib/logitech_receiver/settings_templates.py:3399 #: lib/logitech_receiver/settings_templates.py:3439 #: lib/logitech_receiver/settings_templates.py:3546 #: lib/logitech_receiver/settings_templates.py:3745 msgid "LED Control needs to be enabled." msgstr "Ní mór Rialú LED a chumasú." #: lib/logitech_receiver/settings_templates.py:3231 #: lib/logitech_receiver/settings_templates.py:3745 msgid "Set effect for LED Zone" msgstr "Socraigh éifeacht don Chrios LED" #: lib/logitech_receiver/settings_templates.py:3233 msgid "Color" msgstr "Dath" #: lib/logitech_receiver/settings_templates.py:3244 msgid "Ramp" msgstr "Rampa" #: lib/logitech_receiver/settings_templates.py:3246 msgid "Waveform" msgstr "Tonnfhoirm" #: lib/logitech_receiver/settings_templates.py:3279 msgid "LEDs" msgstr "LEDanna" #: lib/logitech_receiver/settings_templates.py:3398 msgid "Idle Timeout" msgstr "Am Teorann Díomhaoin" #: lib/logitech_receiver/settings_templates.py:3399 msgid "Time without input before LED idle effect starts." msgstr "Am gan ionchur sula dtosaíonn éifeacht dhíomhaoin LED." #: lib/logitech_receiver/settings_templates.py:3435 msgid "Idle Effect" msgstr "Éifeacht Díomhaoin" #: lib/logitech_receiver/settings_templates.py:3437 msgid "" "What happens to LEDs when idle — dim to a percentage, change the base color, " "or play an animation." msgstr "" "Cad a tharlaíonn do na soilse LED nuair a bhíonn siad díomhaoin — laghdaigh " "go céatadán, athraigh an dath bonn, nó seinn beochan." #: lib/logitech_receiver/settings_templates.py:3520 msgid "No change" msgstr "Gan athrú" #: lib/logitech_receiver/settings_templates.py:3545 msgid "Sleep Timeout" msgstr "Sos Codlata" #: lib/logitech_receiver/settings_templates.py:3546 msgid "Time without input before LEDs fade off completely." msgstr "Am gan ionchur sula múchann na soilse LED go hiomlán." #: lib/logitech_receiver/settings_templates.py:3719 msgid "Startup Animation" msgstr "Beochan Tosaithe" #: lib/logitech_receiver/settings_templates.py:3722 msgid "" "Firmware-played animation when the keyboard wakes from deep sleep or powers " "on.\n" "Setting persists on the device (non-volatile)." msgstr "" "Beochan a sheinnear le firmware nuair a dhúisíonn an méarchlár ó chodladh " "domhain nó nuair a chuirtear ar siúl é.\n" "Fanann an socrú ar an ngléas (neamh-luaineach)." #: lib/logitech_receiver/settings_templates.py:3726 #: lib/logitech_receiver/settings_templates.py:3737 msgid "Device default: Primary #FF0081, Secondary #80AAFF." msgstr "Réamhshocrú gléis: Príomhúil #FF0081, Tánaisteach #80AAFF." #: lib/logitech_receiver/settings_templates.py:3733 msgid "Shutdown Animation" msgstr "Beochan Múchta" #: lib/logitech_receiver/settings_templates.py:3735 msgid "" "Firmware-played animation when the keyboard powers off.\n" "Setting persists on the device (non-volatile)." msgstr "" "Beochan a sheinneadh le dochtearraí nuair a mhúchtar an méarchlár.\n" "Fanann an socrú ar an ngléas (neamh-luaineach)." #: lib/logitech_receiver/settings_templates.py:3847 #: lib/solaar/ui/perkey/dialog.py:87 msgid "Per-key Lighting" msgstr "Soilsiú in aghaidh an eochrach" #: lib/logitech_receiver/settings_templates.py:3849 msgid "Control per-key lighting." msgstr "Rialú soilsiú in aghaidh an eochrach." #: lib/logitech_receiver/settings_templates.py:3851 msgid "" "LED Control needs to be enabled and the zone effect set to Static for per-" "key paint to be visible." msgstr "" "Ní mór Rialú LED a chumasú agus éifeacht an chrios a shocrú go Statach le go " "mbeidh péint in aghaidh an eochrach le feiceáil." #: lib/logitech_receiver/settings_templates.py:4182 msgid "Force Sensing Buttons" msgstr "Cnaipí Braite Fórsa" #: lib/logitech_receiver/settings_templates.py:4183 msgid "Change the force required to activate button." msgstr "Athraigh an fórsa atá ag teastáil chun an cnaipe a ghníomhachtú." #: lib/logitech_receiver/settings_templates.py:4200 msgid "Force Sensing Button" msgstr "Cnaipe Braite Fórsa" #: lib/logitech_receiver/settings_templates.py:4316 msgid "Analog Button Tuning" msgstr "Tiúnáil Cnaipe Analógach" #: lib/logitech_receiver/settings_templates.py:4317 msgid "" "Configure analog button settings including actuation point, rapid trigger, " "and haptics." msgstr "" "Cumraigh socruithe cnaipe analógacha lena n-áirítear pointe gníomhachtaithe, " "spreagadh tapa, agus haptics." #: lib/logitech_receiver/settings_templates.py:4347 #, python-format msgid "Actuation point depth (1=shallow, %d=deep)." msgstr "Doimhneacht phointe gníomhachtaithe (1=éadomhain, %d=doimhneacht)." #: lib/logitech_receiver/settings_templates.py:4355 #, python-format msgid "Rapid trigger sensitivity (1..%d)." msgstr "Íogaireacht thapa spreagtha (1..%d)." #: lib/logitech_receiver/settings_templates.py:4363 #, python-format msgid "Click haptic feedback intensity (0=off, %d=max)." msgstr "Déine aiseolais haptic cliceáil (0=as, %d=uas)." #: lib/logitech_receiver/settings_templates.py:4371 msgid "Haptic Feedback Level" msgstr "Leibhéal Aiseolais Haptic" #: lib/logitech_receiver/settings_templates.py:4372 msgid "Change power of haptic feedback. (Zero to turn off.)" msgstr "Athraigh cumhacht an aiseolais haptic. (Nialas chun é a mhúchadh.)" #: lib/logitech_receiver/settings_templates.py:4414 msgid "Play Haptic Waveform" msgstr "Seinn Tonnfhorm Haptic" #: lib/logitech_receiver/settings_templates.py:4415 msgid "Tell device to play a haptic waveform." msgstr "Abair leis an ngléas tonnfhoirm haptic a sheinm." #: lib/solaar/ui/__init__.py:120 msgid "Another Solaar process is already running so just expose its window" msgstr "" "Tá próiseas Solaar eile ag rith cheana féin mar sin níl le déanamh ach a " "fhuinneog a nochtadh" #: lib/solaar/ui/about/model.py:36 msgid "" "Manages Logitech receivers,\n" "keyboards, mice, and tablets." msgstr "" "Bainistíonn glacadóirí, méarchláir,\n" "lucha agus táibléid Logitech." #: lib/solaar/ui/about/model.py:64 msgid "Additional Programming" msgstr "Cláreagrú Breise" #: lib/solaar/ui/about/model.py:65 msgid "GUI design" msgstr "Dearadh GUI" #: lib/solaar/ui/about/model.py:67 msgid "Testing" msgstr "Tástáil" #: lib/solaar/ui/about/model.py:75 msgid "Logitech documentation" msgstr "Doiciméadú Logitech" #: lib/solaar/ui/action.py:88 lib/solaar/ui/action.py:92 #: lib/solaar/ui/window.py:208 msgid "Unpair" msgstr "Díphéireáil" #: lib/solaar/ui/action.py:91 lib/solaar/ui/diversion_rules.py:142 msgid "Cancel" msgstr "Cealaigh" #: lib/solaar/ui/common.py:42 msgid "Permissions error" msgstr "Earráid ceadanna" #: 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 "" "Fuarthas glacadóir nó gléas Logitech (%s), ach ní raibh cead agam é a " "oscailt." #: lib/solaar/ui/common.py:46 msgid "" "If you've just installed Solaar, try disconnecting the receiver or device " "and then reconnecting it." msgstr "" "Má tá Solaar suiteáilte agat díreach, déan iarracht an glacadóir nó an gléas " "a dhícheangal agus ansin é a athcheangal." #: lib/solaar/ui/common.py:49 msgid "Cannot connect to device error" msgstr "Ní féidir ceangal leis an ngléas earráide" #: 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 "" "Fuarthas glacadóir nó gléas Logitech ag %s, ach tháinig earráid ar an " "gceangal leis." #: lib/solaar/ui/common.py:53 msgid "" "Try disconnecting the device and then reconnecting it or turning it off and " "then on." msgstr "" "Bain triail as an ngléas a dhícheangal agus ansin é a athcheangal nó é a " "mhúchadh agus a chur air arís." #: lib/solaar/ui/common.py:56 msgid "Unpairing failed" msgstr "Theip ar dhíphéireáil" #: lib/solaar/ui/common.py:58 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Theip ar %{device} a dhíphéireáil ó %{receiver}." #: lib/solaar/ui/common.py:63 msgid "The receiver returned an error, with no further details." msgstr "Thug an glacadóir earráid ar ais, gan aon sonraí breise." #: lib/solaar/ui/config_panel.py:254 msgid "Complete - ENTER to change" msgstr "Críochnaithe - ENTER le hathrú" #: lib/solaar/ui/config_panel.py:254 msgid "Incomplete" msgstr "Neamhchríochnaithe" #: lib/solaar/ui/config_panel.py:500 lib/solaar/ui/config_panel.py:552 #: lib/solaar/ui/config_panel.py:619 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "%d luach" msgstr[1] "%d luachanna" msgstr[2] "%d luachanna" #: lib/solaar/ui/config_panel.py:805 msgid "Changes allowed" msgstr "Athruithe ceadaithe" #: lib/solaar/ui/config_panel.py:806 msgid "No changes allowed" msgstr "Ní cheadaítear aon athruithe" #: lib/solaar/ui/config_panel.py:807 msgid "Ignore this setting" msgstr "Déan neamhaird den socrú seo" #: lib/solaar/ui/config_panel.py:1022 msgid "Working" msgstr "Ag obair" #: lib/solaar/ui/config_panel.py:1025 msgid "Read/write operation failed." msgstr "Theip ar an oibríocht léigh/scríbhneoireachta." #: lib/solaar/ui/desktop_notifications.py:112 msgid "unspecified reason" msgstr "cúis neamhshonraithe" #: lib/solaar/ui/diversion_rules.py:104 msgid "Built-in rules" msgstr "Rialacha ionsuite" #: lib/solaar/ui/diversion_rules.py:104 msgid "User-defined rules" msgstr "Rialacha sainmhínithe ag an úsáideoir" #: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:1127 msgid "Rule" msgstr "Riail" #: lib/solaar/ui/diversion_rules.py:107 lib/solaar/ui/diversion_rules.py:383 #: lib/solaar/ui/diversion_rules.py:510 msgid "Sub-rule" msgstr "Fo-riail" #: lib/solaar/ui/diversion_rules.py:109 msgid "[empty]" msgstr "[folamh]" #: lib/solaar/ui/diversion_rules.py:133 msgid "Make changes permanent?" msgstr "Déan athruithe buana?" #: lib/solaar/ui/diversion_rules.py:138 msgid "Yes" msgstr "Tá" #: lib/solaar/ui/diversion_rules.py:140 msgid "No" msgstr "Níl" #: lib/solaar/ui/diversion_rules.py:145 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "Mura roghnaíonn tú Níl, caillfear na hathruithe nuair a dhúnfar Solaar." #: lib/solaar/ui/diversion_rules.py:274 msgid "Paste here" msgstr "Greamaigh anseo" #: lib/solaar/ui/diversion_rules.py:276 msgid "Paste above" msgstr "Greamaigh thuas" #: lib/solaar/ui/diversion_rules.py:278 msgid "Paste below" msgstr "Greamaigh thíos" #: lib/solaar/ui/diversion_rules.py:284 msgid "Paste rule here" msgstr "Greamaigh riail anseo" #: lib/solaar/ui/diversion_rules.py:286 msgid "Paste rule above" msgstr "Greamaigh an riail thuas" #: lib/solaar/ui/diversion_rules.py:288 msgid "Paste rule below" msgstr "Greamaigh riail thíos" #: lib/solaar/ui/diversion_rules.py:292 msgid "Paste rule" msgstr "Riail ghreamaithe" #: lib/solaar/ui/diversion_rules.py:307 msgid "Insert here" msgstr "Cuir isteach anseo" #: lib/solaar/ui/diversion_rules.py:309 msgid "Insert above" msgstr "Cuir isteach thuas" #: lib/solaar/ui/diversion_rules.py:311 msgid "Insert below" msgstr "Cuir isteach thíos" #: lib/solaar/ui/diversion_rules.py:317 msgid "Insert new rule here" msgstr "Cuir riail nua isteach anseo" #: lib/solaar/ui/diversion_rules.py:319 msgid "Insert new rule above" msgstr "Cuir riail nua isteach thuas" #: lib/solaar/ui/diversion_rules.py:321 msgid "Insert new rule below" msgstr "Cuir riail nua isteach thíos" #: lib/solaar/ui/diversion_rules.py:348 msgid "Flatten" msgstr "Leacaigh" #: lib/solaar/ui/diversion_rules.py:381 msgid "Insert" msgstr "Cuir isteach" #: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:512 #: lib/solaar/ui/diversion_rules.py:1159 msgid "Or" msgstr "Nó" #: lib/solaar/ui/diversion_rules.py:385 lib/solaar/ui/diversion_rules.py:511 #: lib/solaar/ui/diversion_rules.py:1145 msgid "And" msgstr "Agus" #: lib/solaar/ui/diversion_rules.py:387 msgid "Condition" msgstr "Coinníoll" #: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:154 msgid "Feature" msgstr "Gné" #: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:188 msgid "Report" msgstr "Tuairisc" #: lib/solaar/ui/diversion_rules.py:391 lib/solaar/ui/rule_conditions.py:69 msgid "Process" msgstr "Próiseas" #: lib/solaar/ui/diversion_rules.py:392 msgid "Mouse process" msgstr "Próiseas luiche" #: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:225 msgid "Modifiers" msgstr "Modhnóirí" #: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:277 msgid "Key" msgstr "Eochair" #: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/rule_conditions.py:318 msgid "KeyIsDown" msgstr "EochairIsSíos" #: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1433 msgid "Active" msgstr "Gníomhach" #: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1391 #: lib/solaar/ui/diversion_rules.py:1442 lib/solaar/ui/diversion_rules.py:1488 msgid "Device" msgstr "Gléas" #: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1468 msgid "Host" msgstr "Óstach" #: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/diversion_rules.py:1507 msgid "Setting" msgstr "Socrú" #: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:333 #: lib/solaar/ui/rule_conditions.py:382 msgid "Test" msgstr "Tástáil" #: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:507 msgid "Test bytes" msgstr "Bheiteanna tástála" #: lib/solaar/ui/diversion_rules.py:402 lib/solaar/ui/rule_conditions.py:608 msgid "Mouse Gesture" msgstr "Gotha Luiche" #: lib/solaar/ui/diversion_rules.py:406 msgid "Action" msgstr "Gníomh" #: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:138 msgid "Key press" msgstr "Brúigh eochair" #: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:191 msgid "Mouse scroll" msgstr "Scrollaigh luiche" #: lib/solaar/ui/diversion_rules.py:410 lib/solaar/ui/rule_actions.py:256 msgid "Mouse click" msgstr "Cliceáil luiche" #: lib/solaar/ui/diversion_rules.py:411 msgid "Set" msgstr "Socraigh" #: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/rule_actions.py:328 msgid "Execute" msgstr "Forghníomhaigh" #: lib/solaar/ui/diversion_rules.py:413 lib/solaar/ui/diversion_rules.py:1190 msgid "Later" msgstr "Níos déanaí" #: lib/solaar/ui/diversion_rules.py:442 msgid "Insert new rule" msgstr "Cuir riail nua isteach" #: lib/solaar/ui/diversion_rules.py:462 lib/solaar/ui/rule_actions.py:82 #: lib/solaar/ui/rule_actions.py:287 lib/solaar/ui/rule_conditions.py:556 msgid "Delete" msgstr "Scrios" #: lib/solaar/ui/diversion_rules.py:484 msgid "Negate" msgstr "Néig" #: lib/solaar/ui/diversion_rules.py:508 msgid "Wrap with" msgstr "Fillte le" #: lib/solaar/ui/diversion_rules.py:538 msgid "Cut" msgstr "Gearr" #: lib/solaar/ui/diversion_rules.py:554 msgid "Paste" msgstr "Greamaigh" #: lib/solaar/ui/diversion_rules.py:560 msgid "Copy" msgstr "Cóipeáil" #: lib/solaar/ui/diversion_rules.py:569 msgid "Solaar Rule Editor" msgstr "Eagarthóir Rialacha Solaar" #: lib/solaar/ui/diversion_rules.py:669 msgid "Save changes" msgstr "Sábháil athruithe" #: lib/solaar/ui/diversion_rules.py:674 msgid "Discard changes" msgstr "Scrios athruithe" #: lib/solaar/ui/diversion_rules.py:1105 msgid "This editor does not support the selected rule component yet." msgstr "" "Ní thacaíonn an t-eagarthóir seo leis an gcomhpháirt riail roghnaithe go " "fóill." #: lib/solaar/ui/diversion_rules.py:1170 msgid "" "Number of seconds to delay. Delay between 0 and 1 is done with higher " "precision." msgstr "" "Líon na soicindí le moill. Déantar moill idir 0 agus 1 le cruinneas níos " "airde." #: lib/solaar/ui/diversion_rules.py:1208 msgid "Not" msgstr "Ní" #: lib/solaar/ui/diversion_rules.py:1239 msgid "Toggle" msgstr "Scoránaigh" #: lib/solaar/ui/diversion_rules.py:1240 msgid "True" msgstr "Fíor" #: lib/solaar/ui/diversion_rules.py:1241 msgid "False" msgstr "Bréagach" #: lib/solaar/ui/diversion_rules.py:1254 msgid "Unsupported setting" msgstr "Socrú gan tacaíocht" #: lib/solaar/ui/diversion_rules.py:1397 lib/solaar/ui/diversion_rules.py:1416 #: lib/solaar/ui/diversion_rules.py:1494 lib/solaar/ui/diversion_rules.py:1758 #: lib/solaar/ui/diversion_rules.py:1776 msgid "Originating device" msgstr "Gléas tionscnaimh" #: lib/solaar/ui/diversion_rules.py:1429 msgid "Device is active and its settings can be changed." msgstr "Tá an gléas gníomhach agus is féidir a shocruithe a athrú." #: lib/solaar/ui/diversion_rules.py:1438 msgid "Device that originated the current notification." msgstr "An gléas a chuir an fógra reatha ar bun." #: lib/solaar/ui/diversion_rules.py:1451 msgid "Name of host computer." msgstr "Ainm an ríomhaire óstach." #: lib/solaar/ui/diversion_rules.py:1521 msgid "Value" msgstr "Luach" #: lib/solaar/ui/diversion_rules.py:1530 msgid "Item" msgstr "Mír" #: lib/solaar/ui/diversion_rules.py:1818 msgid "Change setting on device" msgstr "Athraigh an socrú ar an ngléas" #: lib/solaar/ui/diversion_rules.py:1834 msgid "Setting on device" msgstr "Socrú ar an ngléas" #: 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: péireáil gléas nua" #: lib/solaar/ui/pair_window.py:46 msgid "Bolt receivers are only compatible with Bolt devices." msgstr "Níl glacadóirí Bolt comhoiriúnach ach le gléasanna Bolt." #: lib/solaar/ui/pair_window.py:48 msgid "Press a pairing button or key until the pairing light flashes quickly." msgstr "" "Brúigh cnaipe nó eochair péireála go dtí go splancann an solas péireála go " "tapa." #: lib/solaar/ui/pair_window.py:51 msgid "Unifying receivers are only compatible with Unifying devices." msgstr "Níl glacadóirí Unifying comhoiriúnach ach le gléasanna Unifying." #: lib/solaar/ui/pair_window.py:53 msgid "Other receivers are only compatible with a few devices." msgstr "Níl glacadóirí eile comhoiriúnach ach le cúpla gléas." #: lib/solaar/ui/pair_window.py:55 msgid "For most devices, turn on the device you want to pair." msgstr "" "I gcás fhormhór na ngléasanna, cuir an gléas ar siúl is mian leat a " "phéireáil." #: lib/solaar/ui/pair_window.py:56 msgid "If the device is already turned on, turn it off and on again." msgstr "Má tá an gléas casta air cheana féin, múch é agus cuir air arís é." #: lib/solaar/ui/pair_window.py:58 msgid "The device must not be paired with a nearby powered-on receiver." msgstr "" "Ní mór an gléas a bheith péireáilte le glacadóir atá ar siúl in aice láimhe." #: 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 "" "I gcás gléasanna a bhfuil ilchainéil acu, brúigh, coinnigh síos agus scaoil " "an cnaipe don chainéal is mian leat a phéireáil\n" "nó bain úsáid as an gcnaipe athraithe cainéal chun cainéal a roghnú agus " "ansin brúigh, coinnigh síos agus scaoil an cnaipe athraithe cainéal." #: lib/solaar/ui/pair_window.py:68 msgid "The channel indicator light should be blinking rapidly." msgstr "Ba chóir go mbeadh solas táscaire an chainéil ag splancadh go tapa." #: 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" "Tá %d péireáil fágtha ag an nglacadóir seo." msgstr[1] "" "\n" "\n" "Tá %d péirí fágtha ag an nglacadóir seo." msgstr[2] "" "\n" "\n" "Tá %d péirí fágtha ag an nglacadóir seo." #: lib/solaar/ui/pair_window.py:78 msgid "" "\n" "Cancelling at this point will not use up a pairing." msgstr "" "\n" "Ní úsáidfear péireáil má chealaítear ag an bpointe seo." #: lib/solaar/ui/pair_window.py:168 #, python-format msgid "Enter passcode on %(name)s." msgstr "Cuir isteach an pasfhocal ar %(name)s." #: lib/solaar/ui/pair_window.py:171 #, python-format msgid "Type %(passcode)s and then press the enter key." msgstr "Clóscríobh %(passcode)s agus ansin brúigh an eochair iontrála." #: lib/solaar/ui/pair_window.py:176 msgid "left" msgstr "ar chlé" #: lib/solaar/ui/pair_window.py:176 msgid "right" msgstr "ar dheis" #: lib/solaar/ui/pair_window.py:178 #, python-format msgid "" "Press %(code)s\n" "and then press left and right buttons simultaneously." msgstr "" "Brúigh %(code)s\n" "agus ansin brúigh na cnaipí clé agus deas ag an am céanna." #: lib/solaar/ui/pair_window.py:221 msgid "The wireless link is not encrypted" msgstr "Níl an nasc gan sreang criptithe" #: lib/solaar/ui/pair_window.py:226 msgid "Found a new device:" msgstr "Fuarthas gléas nua:" #: lib/solaar/ui/pair_window.py:247 msgid "Pairing failed" msgstr "Theip ar an bpéireáil" #: lib/solaar/ui/pair_window.py:249 msgid "Make sure your device is within range, and has a decent battery charge." msgstr "" "Cinntigh go bhfuil do ghléas laistigh de raon, agus go bhfuil muirear " "ceallraí réasúnta air." #: lib/solaar/ui/pair_window.py:251 msgid "A new device was detected, but it is not compatible with this receiver." msgstr "Braitheadh gléas nua, ach níl sé comhoiriúnach leis an nglacadóir seo." #: lib/solaar/ui/pair_window.py:253 msgid "More paired devices than receiver can support." msgstr "" "Níos mó gléasanna péireáilte ná mar is féidir leis an nglacadóir tacú leo." #: lib/solaar/ui/pair_window.py:255 msgid "No further details are available about the error." msgstr "Níl aon sonraí breise ar fáil faoin earráid." #: lib/solaar/ui/perkey/control.py:178 msgid "(not loaded)" msgstr "(gan luchtú)" #: lib/solaar/ui/perkey/control.py:182 msgid "Open editor…" msgstr "Oscail an t-eagarthóir…" #: lib/solaar/ui/perkey/control.py:183 msgid "Paint key colors on a keyboard layout" msgstr "Péinteáil dathanna eochracha ar leagan amach méarchláir" #: lib/solaar/ui/perkey/control.py:229 msgid "(no zones)" msgstr "(gan criosanna)" #: lib/solaar/ui/perkey/control.py:233 #, python-brace-format msgid "{painted} / {total} keys painted" msgstr "{painted} / {total} eochracha péinteáilte" #: lib/solaar/ui/perkey/editor.py:54 msgid "Brush" msgstr "Scuab" #: lib/solaar/ui/perkey/editor.py:54 msgid "Click or drag to paint individual keys" msgstr "Cliceáil nó tarraing chun eochracha aonair a phéinteáil" #: lib/solaar/ui/perkey/editor.py:55 msgid "Drag to select a rectangle of keys, painted on release" msgstr "" "Tarraing chun dronuilleog eochracha a roghnú, péinteáilte nuair a scaoiltear " "iad" #: lib/solaar/ui/perkey/editor.py:55 msgid "Rect" msgstr "Dronuilleog" #: lib/solaar/ui/perkey/editor.py:56 msgid "Fill" msgstr "Líon" #: lib/solaar/ui/perkey/editor.py:56 msgid "Flood-fill connected keys of the same color with the active color" msgstr "" "Líon isteach eochracha ceangailte den dath céanna leis an dath gníomhach" #: lib/solaar/ui/perkey/editor.py:59 msgid "Drag to fade from previous color to active color" msgstr "Tarraing chun céimniú ón dath roimhe seo go dtí an dath gníomhach" #: lib/solaar/ui/perkey/palette.py:90 msgid "Active color" msgstr "Dath gníomhach" #: lib/solaar/ui/perkey/palette.py:95 msgid "Paint as 'no change' — clears the cell to the zone base color" msgstr "" "Péinteáil mar 'gan athrú' — glanann sé an chill go dtí an dath bonn crios" #: lib/solaar/ui/perkey/palette.py:96 msgid "Unset" msgstr "Díshuiteáil" #: 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 "" "Insamhladh cliceáil nó brú nó scaoileadh eochrach corda.\n" "Ar Wayland, teastaíonn rochtain scríbhneoireachta chuig /dev/uinput." #: lib/solaar/ui/rule_actions.py:60 msgid "Add key" msgstr "Cuir eochair leis" #: lib/solaar/ui/rule_actions.py:63 msgid "Click" msgstr "Cliceáil" #: lib/solaar/ui/rule_actions.py:66 msgid "Depress" msgstr "Dúlagar" #: lib/solaar/ui/rule_actions.py:69 msgid "Release" msgstr "Scaoileadh" #: lib/solaar/ui/rule_actions.py:153 msgid "" "Simulate a mouse scroll.\n" "On Wayland requires write access to /dev/uinput." msgstr "" "Insamhladh scrolla luiche.\n" "Ar Wayland, teastaíonn rochtain scríbhneoireachta chuig /dev/uinput." #: lib/solaar/ui/rule_actions.py:211 msgid "" "Simulate a mouse click.\n" "On Wayland requires write access to /dev/uinput." msgstr "" "Insamhlaigh cliceáil luiche.\n" "Éilíonn Wayland rochtain scríbhneoireachta chuig /dev/uinput." #: lib/solaar/ui/rule_actions.py:216 msgid "Button" msgstr "Cnaipe" #: lib/solaar/ui/rule_actions.py:218 msgid "Action (and Count, if click)" msgstr "Gníomh (agus Líon, má chliceálann tú)" #: lib/solaar/ui/rule_actions.py:269 msgid "Execute a command with arguments." msgstr "Forghníomhaigh ordú le hargóintí." #: lib/solaar/ui/rule_actions.py:273 msgid "Add argument" msgstr "Cuir argóint leis" #: lib/solaar/ui/rule_conditions.py:52 msgid "X11 active process. For use in X11 only." msgstr "Próiseas gníomhach X11. Le húsáid in X11 amháin." #: lib/solaar/ui/rule_conditions.py:82 msgid "X11 mouse process. For use in X11 only." msgstr "Próiseas luiche X11. Le húsáid in X11 amháin." #: lib/solaar/ui/rule_conditions.py:99 msgid "MouseProcess" msgstr "Próiseas Luiche" #: lib/solaar/ui/rule_conditions.py:123 msgid "Feature name of notification triggering rule processing." msgstr "Ainm gné den phróiseáil rialach a spreagann fógra." #: lib/solaar/ui/rule_conditions.py:169 msgid "Report number of notification triggering rule processing." msgstr "Tuairiscigh líon na bhfógraí a spreagann próiseáil rialacha." #: lib/solaar/ui/rule_conditions.py:201 msgid "Active keyboard modifiers. Not always available in Wayland." msgstr "" "Mionathraitheoirí méarchláir ghníomhacha. Ní bhíonn siad ar fáil i gcónaí 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 "" "Eochair nó cnaipe atreoraithe brúite nó scaoilte.\n" "Úsáid na socruithe Atreorú Eochrach/Cnaipe agus Atreorú Eochracha G chun " "eochracha agus cnaipí a atreorú." #: lib/solaar/ui/rule_conditions.py:250 msgid "Key down" msgstr "Eochair síos" #: lib/solaar/ui/rule_conditions.py:253 msgid "Key up" msgstr "Eochair suas" #: 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 "" "Tá an eochair nó an cnaipe atreoraithe síos faoi láthair.\n" "Bain úsáid as na socruithe Atreorú Eochrach/Cnaipe agus Atreorú Eochracha G " "chun eochracha agus cnaipí a atreorú." #: lib/solaar/ui/rule_conditions.py:331 msgid "Test condition on notification triggering rule processing." msgstr "Coinníoll tástála ar phróiseáil rialach a spreagann fógra." #: lib/solaar/ui/rule_conditions.py:335 msgid "Parameter" msgstr "Paraiméadar" #: lib/solaar/ui/rule_conditions.py:408 msgid "begin (inclusive)" msgstr "tús (san áireamh)" #: lib/solaar/ui/rule_conditions.py:409 msgid "end (exclusive)" msgstr "deireadh (eisiach)" #: lib/solaar/ui/rule_conditions.py:417 msgid "range" msgstr "raon" #: lib/solaar/ui/rule_conditions.py:420 msgid "minimum" msgstr "íosmhéid" #: lib/solaar/ui/rule_conditions.py:421 msgid "maximum" msgstr "uasmhéid" #: lib/solaar/ui/rule_conditions.py:423 #, python-format msgid "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" msgstr "beart %(0)d go %(1)d, idir %(2)d agus %(3)d" #: lib/solaar/ui/rule_conditions.py:426 lib/solaar/ui/rule_conditions.py:427 msgid "mask" msgstr "masc" #: lib/solaar/ui/rule_conditions.py:428 #, python-format msgid "bytes %(0)d to %(1)d, mask %(2)d" msgstr "beart %(0)d go %(1)d, masc %(2)d" #: lib/solaar/ui/rule_conditions.py:437 msgid "" "Bit or range test on bytes in notification message triggering rule " "processing." msgstr "" "Tástáil giotán nó raoin ar bhearta i dteachtaireacht fógra a spreagann " "próiseáil rialacha." #: lib/solaar/ui/rule_conditions.py:447 msgid "type" msgstr "cineál" #: lib/solaar/ui/rule_conditions.py:535 msgid "" "Mouse gesture with optional initiating button followed by zero or more mouse " "movements." msgstr "" "Gotha luiche le cnaipe tosaithe roghnach agus gan aon ghluaiseacht luiche nó " "níos mó ina dhiaidh." #: lib/solaar/ui/rule_conditions.py:541 msgid "Add movement" msgstr "Cuir gluaiseacht leis" #: lib/solaar/ui/tray.py:55 msgid "No supported device found" msgstr "Níor aimsíodh aon ghléas tacaithe" #: lib/solaar/ui/tray.py:60 lib/solaar/ui/window.py:328 #, python-format msgid "About %s" msgstr "Maidir le %s" #: lib/solaar/ui/tray.py:61 lib/solaar/ui/window.py:326 #, python-format msgid "Quit %s" msgstr "Scoir %s" #: lib/solaar/ui/tray.py:273 lib/solaar/ui/tray.py:281 msgid "no receiver" msgstr "gan aon ghlacadóir" #: lib/solaar/ui/tray.py:294 lib/solaar/ui/tray.py:299 msgid "offline" msgstr "as líne" #: lib/solaar/ui/tray.py:297 msgid "no status" msgstr "gan stádas" #: lib/solaar/ui/window.py:110 msgid "Scanning" msgstr "Scanadh" #: lib/solaar/ui/window.py:141 msgid "Battery" msgstr "Ceallraí" #: lib/solaar/ui/window.py:144 msgid "Wireless Link" msgstr "Nasc Gan Sreang" #: lib/solaar/ui/window.py:148 msgid "Lighting" msgstr "Soilsiú" #: lib/solaar/ui/window.py:182 msgid "Show Technical Details" msgstr "Taispeáin Sonraí Teicniúla" #: lib/solaar/ui/window.py:198 msgid "Pair new device" msgstr "Péireáil gléas nua" #: lib/solaar/ui/window.py:216 msgid "Select a device" msgstr "Roghnaigh gléas" #: lib/solaar/ui/window.py:331 msgid "Rule Editor" msgstr "Eagarthóir Rialacha" #: lib/solaar/ui/window.py:522 msgid "Path" msgstr "Cosán" #: lib/solaar/ui/window.py:524 msgid "USB ID" msgstr "Aitheantas USB" #: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 #: lib/solaar/ui/window.py:550 lib/solaar/ui/window.py:552 msgid "Serial" msgstr "Sraitheach" #: lib/solaar/ui/window.py:533 msgid "Index" msgstr "Innéacs" #: lib/solaar/ui/window.py:535 msgid "Wireless PID" msgstr "PID Gan Sreang" #: lib/solaar/ui/window.py:537 msgid "Product ID" msgstr "Aitheantas Táirge" #: lib/solaar/ui/window.py:541 lib/solaar/ui/window.py:543 #: lib/solaar/ui/window.py:545 msgid "Protocol" msgstr "Prótacal" #: lib/solaar/ui/window.py:545 msgid "Unknown" msgstr "Anaithnid" #: lib/solaar/ui/window.py:547 msgid "Polling rate" msgstr "Ráta faireachán" #: lib/solaar/ui/window.py:554 msgid "Unit ID" msgstr "Aitheantas Aonaid" #: lib/solaar/ui/window.py:566 msgid "Notifications" msgstr "Fógraí" #: lib/solaar/ui/window.py:610 msgid "No device paired." msgstr "Níl aon ghléas péireáilte." #: lib/solaar/ui/window.py:619 #, 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] "" "Is féidir suas le %(max_count)s gléas a phéireáil leis an nglacadóir seo." msgstr[1] "" "Is féidir suas le %(max_count)s gléasanna a phéireáil leis an nglacadóir seo." msgstr[2] "" "Is féidir suas le %(max_count)s gléasanna a phéireáil leis an nglacadóir seo." #: lib/solaar/ui/window.py:630 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." msgstr[0] "Tá %d péireáil fágtha ag an nglacadóir seo." msgstr[1] "Tá %d péireáil fágtha ag an nglacadóir seo." msgstr[2] "Tá %d péireáil fágtha ag an nglacadóir seo." #: lib/solaar/ui/window.py:687 msgid "Battery Voltage" msgstr "Voltas Ceallraí" #: lib/solaar/ui/window.py:689 msgid "Voltage reported by battery" msgstr "Voltas tuairiscithe ag ceallraí" #: lib/solaar/ui/window.py:691 msgid "Battery Level" msgstr "Leibhéal Ceallraí" #: lib/solaar/ui/window.py:693 msgid "Approximate level reported by battery" msgstr "Leibhéal garbh a tuairiscíodh ag an gceallraí" #: lib/solaar/ui/window.py:700 lib/solaar/ui/window.py:702 msgid "next reported " msgstr "an chéad tuairisc eile " #: lib/solaar/ui/window.py:703 msgid " and next level to be reported." msgstr " agus an chéad leibhéal eile le tuairisciú." #: lib/solaar/ui/window.py:719 msgid "encrypted" msgstr "criptithe" #: lib/solaar/ui/window.py:721 msgid "The wireless link between this device and its receiver is encrypted." msgstr "Tá an nasc gan sreang idir an gléas seo agus a ghlacadóir criptithe." #: lib/solaar/ui/window.py:723 msgid "not encrypted" msgstr "gan criptiú" #: lib/solaar/ui/window.py:727 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íl an nasc gan sreang idir an gléas seo agus a ghlacadóir criptithe.\n" "Is fadhb slándála í seo do ghléasanna pointeála, agus fadhb mhór slándála do " "ghléasanna ionchuir téacs." #: lib/solaar/ui/window.py:743 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d lux" Solaar-1.1.20/po/hr.po000066400000000000000000001713421522022367600144200ustar00rootroot00000000000000# Croatian translations for solaar package # Copyright (C) YEAR THE solaar'S COPYRIGHT HOLDER # 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: 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" #: 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" #: lib/logitech_receiver/base_usb.py:124 msgid "Lightspeed Receiver" msgstr "Lightspeed prijemnik" #: 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" #: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "kritično" #: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "slaba" #: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "prosječna" #: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "dobra" #: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "napunjena" #: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "pražnjenje" #: lib/logitech_receiver/i18n.py:39 msgid "recharging" 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" #: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "uskoro puno" #: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "napunjena" #: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "sporo punjenje" #: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "neispravna baterija" #: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "toplinska greška" #: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "greška" #: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "standardno" #: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "brzo" #: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "sporo" #: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "istek čekanja uređaja" #: lib/logitech_receiver/i18n.py:54 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" #: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "redoslijed isteka vremena" #: 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" #: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardver" #: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Ostalo" #: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "Lijeva tipka" #: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "Desna tipka" #: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "Srednja tipka" #: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "Tipka za natrag" #: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "Tipka za naprijed" #: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "Tipka gesta miša" #: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "Pametan pomak" #: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "DPI prebacivač" #: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "Lijevi nagib" #: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "Desni nagib" #: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "Lijevi klik" #: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "Desni klik" #: lib/logitech_receiver/i18n.py:77 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" #: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "Tipka miša za naprijed" #: lib/logitech_receiver/i18n.py:80 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" #: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "Desna tipka pomicanja miša" #: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "pritisak" #: lib/logitech_receiver/i18n.py:86 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" #: 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:92 msgid "discovery lock is closed" msgstr "otkrivanje je zatvoreno" #: lib/logitech_receiver/notifications.py:92 msgid "discovery lock is open" msgstr "otkrivanje je otvoreno" #: 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" #: 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" #: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "registar" #: 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" #: 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." #: 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." #: lib/logitech_receiver/settings_templates.py:149 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." #: lib/logitech_receiver/settings_templates.py:157 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." #: lib/logitech_receiver/settings_templates.py:165 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." #: lib/logitech_receiver/settings_templates.py:177 msgid "Sensitivity (DPI - older mice)" msgstr "Osjetljivost (DPI - stariji miševi)" #: lib/logitech_receiver/settings_templates.py:178 #: 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" #: 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." #: lib/logitech_receiver/settings_templates.py:219 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" #: 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" #: lib/logitech_receiver/settings_templates.py:247 #: lib/logitech_receiver/settings_templates.py:277 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)." #: lib/logitech_receiver/settings_templates.py:256 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." #: lib/logitech_receiver/settings_templates.py:265 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)." #: lib/logitech_receiver/settings_templates.py:288 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)." #: lib/logitech_receiver/settings_templates.py:299 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)." #: lib/logitech_receiver/settings_templates.py:310 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." #: lib/logitech_receiver/settings_templates.py:319 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" #: lib/logitech_receiver/settings_templates.py:330 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" #: 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 "Možda je potrebno postaviti Onboard profile na 'Onemogućeno' kako bi " "bili učinkoviti." #: lib/logitech_receiver/settings_templates.py:365 msgid "Divert crown events" msgstr "Preusmjeri crown događaje" #: 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:374 msgid "Crown smooth scroll" msgstr "Crown glatko pomicanje" #: 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 "Divert G Keys" msgstr "Preusmjeri G tipke" #: 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: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 "Scroll Wheel Ratcheted" msgstr "Ustavljanje pomicanja kotačića" #: 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:405 msgid "Freespinning" msgstr "Slobodna vrtnja" #: lib/logitech_receiver/settings_templates.py:405 msgid "Ratcheted" msgstr "Ustavljanje" #: 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: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 "Key/Button Actions" msgstr "Radnje 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:465 msgid "Overridden by diversion." msgstr "Preusmjereno skretanjem." #: 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:639 msgid "Key/Button Diversion" msgstr "Preusmjeravanje tipki" #: 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: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:643 #: lib/logitech_receiver/settings_templates.py:644 msgid "Mouse Gestures" msgstr "Geste miša" #: 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:643 msgid "Sliding DPI" msgstr "Klizni DPI" #: lib/logitech_receiver/settings_templates.py:711 msgid "Sensitivity (DPI)" msgstr "Osjetljivost (DPI)" #: lib/logitech_receiver/settings_templates.py:752 msgid "Sensitivity Switching" msgstr "Prebacivanje osjetljivosti" #: 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: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." #: 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: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:873 msgid "Change Host" msgstr "Promijeni računalo" #: 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 "Performs a left click." msgstr "Izvodi lijevi klik." #: lib/logitech_receiver/settings_templates.py:900 msgid "Single tap" msgstr "Jednostruki 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 "Jednostruki dodir s dva prsta" #: lib/logitech_receiver/settings_templates.py:902 msgid "Single tap with three fingers" msgstr "Jednostruki dodir s 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 "Dvostruki dodir s dva prsta" #: 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 "Scrolls." msgstr "Pomicanje." #: lib/logitech_receiver/settings_templates.py:919 #: 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 "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 "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 "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 "Enables the thumbwheel." msgstr "Omogućite kotačić palca." #: 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 "Prijeđite prstom od gornjeg ruba" #: 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:937 msgid "Swipe from the right edge" msgstr "Prijeđite prstom od desnog ruba" #: 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:940 msgid "Swipe two fingers from the left edge" msgstr "Prijeđite s dva prsta od lijevog ruba" #: 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:942 msgid "Swipe two fingers from the bottom edge" msgstr "Prijeđite s dva prsta od donjeg ruba" #: 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: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:944 msgid "Zoom with two fingers." msgstr "Uvećanje/Smanjenje s dva prsta." #: lib/logitech_receiver/settings_templates.py:945 msgid "Pinch to zoom out." msgstr "Skupite prste za smanjivanje." #: 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:947 msgid "Zoom with three fingers." msgstr "Uvećanje/Smanjenje s tri prsta." #: 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 "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" msgstr "Geste" #: lib/logitech_receiver/settings_templates.py:983 msgid "Tweak the mouse/touchpad behaviour." msgstr "Prilagodite ponašanje miša/touchpada." #: 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 "Preusmjerite ponašanje miša/touchpada." #: lib/logitech_receiver/settings_templates.py:1018 msgid "Gesture params" msgstr "Parametri gesta" #: 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: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:1074 msgid "MR-Key LED" msgstr "LED osvjetljenje MR-tipke" #: 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:1095 msgid "Persistent Key/Button Mapping" msgstr "Trajno mapiranje tipka" #: 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: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:1157 msgid "Sidetone" msgstr "Bočni ton" #: lib/logitech_receiver/settings_templates.py:1158 msgid "Set sidetone level." msgstr "Postavi razinu bočnog tona." #: lib/logitech_receiver/settings_templates.py:1167 msgid "Equalizer" msgstr "Ekvalizator" #: 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." #: lib/logitech_receiver/status.py:170 #, python-format msgid "Battery: %(level)s" msgstr "Baterija: %(level)s" #: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "Baterija: %(percent)d%%" #: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "Osvjetljenje: %(level)s lux" #: lib/logitech_receiver/status.py:239 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "Baterija: %(level)s (%(status)s)" #: lib/logitech_receiver/status.py:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Baterija: %(percent)d%% (%(status)s)" #: lib/solaar/ui/__init__.py:52 msgid "Permissions error" msgstr "Greška dozvole" #: 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 "" #: 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 #, 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: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 "Uparivanje neuspjelo" #: lib/solaar/ui/__init__.py:66 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Neuspjelo uparivanje %{device} s %{receiver}." #: 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." #: lib/solaar/ui/about.py:44 msgid "Additional Programming" msgstr "Dodatno programiranje" #: lib/solaar/ui/about.py:45 msgid "GUI design" msgstr "GUI dizajn" #: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "Testiranje" #: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Logitech dokumentacija" #: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 #: lib/solaar/ui/window.py:197 msgid "Unpair" msgstr "Odpari" #: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "Odustani" #: lib/solaar/ui/config_panel.py:212 msgid "Complete - ENTER to change" msgstr "Potpuno - ENTER za promjenu" #: lib/solaar/ui/config_panel.py:212 msgid "Incomplete" msgstr "Nepotpuno" #: 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" #: lib/solaar/ui/config_panel.py:518 msgid "Changes allowed" msgstr "Promjene su dopuštene" #: lib/solaar/ui/config_panel.py:519 msgid "No changes allowed" msgstr "Promjene nisu dopuštene" #: lib/solaar/ui/config_panel.py:520 msgid "Ignore this setting" msgstr "Zanemari ovu postavku" #: lib/solaar/ui/config_panel.py:565 msgid "Working" msgstr "Izvodi se" #: lib/solaar/ui/config_panel.py:568 msgid "Read/write operation failed." msgstr "Radnja čitanja/pisanja neuspjela." #: 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 određena 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 "Učini promjene trajnima?" #: 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 odaberete 'Ne', promjene će biti izgubljene kada se Solaar " "zatvori." #: lib/solaar/ui/diversion_rules.py:201 msgid "Save changes" msgstr "Spremi promjene" #: lib/solaar/ui/diversion_rules.py:206 msgid "Discard changes" msgstr "Odbaci promjene" #: lib/solaar/ui/diversion_rules.py:372 msgid "Insert here" msgstr "Umetni ovdje" #: lib/solaar/ui/diversion_rules.py:374 msgid "Insert above" msgstr "Umetni iznad" #: lib/solaar/ui/diversion_rules.py:376 msgid "Insert below" msgstr "Umetni ispod" #: lib/solaar/ui/diversion_rules.py:382 msgid "Insert new rule here" msgstr "Umetni novo pravilo ovdje" #: lib/solaar/ui/diversion_rules.py:384 msgid "Insert new rule above" msgstr "Umetni novo pravilo iznad" #: 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 here" msgstr "Zalijepi ovdje" #: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "Zalijepi iznad" #: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "Zalijepi ispod" #: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "Zalijepi pravilo ovdje" #: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "Zalijepi pravilo iznad" #: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "Zalijepi pravilo ispod" #: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "Zalijepi pravilo" #: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "Poravnaj" #: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "Umetni" #: 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 "Prijavak" #: 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 "Tipkovnički izmjenjivači" #: lib/solaar/ui/diversion_rules.py:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "Ključ" #: 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 "Aktivan" #: 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 "Testni bajtovi" #: 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 "Radnja" #: 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 "Pomicanje kotačića" #: 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:537 msgid "Set" msgstr "Postavka" #: lib/solaar/ui/diversion_rules.py:538 lib/solaar/ui/diversion_rules.py:2030 msgid "Execute" msgstr "Pokretanje" #: lib/solaar/ui/diversion_rules.py:539 lib/solaar/ui/diversion_rules.py:1158 msgid "Later" msgstr "Odgoda" #: lib/solaar/ui/diversion_rules.py:568 msgid "Insert new rule" msgstr "Umetni 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 "Negacija" #: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "Umetni iznad" #: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Izreži" #: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Zalijepi" #: 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 uređivač još ne podržava komponentu odabira pravila." #: lib/solaar/ui/diversion_rules.py:1138 msgid "Number of seconds to delay." msgstr "Broj sekundi odgode." #: 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 "X11 aktivni proces. Samo za korištenje u X11." #: 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:1235 msgid "MouseProcess" msgstr "Proces miša" #: 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:1308 msgid "Report number of notification triggering rule processing." msgstr "Prijavi broj obrade pravila za pokretanje 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 "Test condition on notification triggering rule processing." msgstr "Uvjet testiranja za obradu pravila pokretanja obavijesti." #: 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 "raspon" #: 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" #: lib/solaar/ui/diversion_rules.py:1562 msgid "mask" msgstr "maska" #: 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" #: 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:1583 msgid "type" msgstr "vrsta" #: 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:1671 msgid "Add movement" msgstr "Dodaj pokret" #: 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:1769 msgid "Add key" msgstr "Dodaj tipku" #: lib/solaar/ui/diversion_rules.py:1772 msgid "Click" msgstr "Klik" #: lib/solaar/ui/diversion_rules.py:1775 msgid "Depress" msgstr "Pritisak" #: lib/solaar/ui/diversion_rules.py:1778 msgid "Release" msgstr "Otpuštanje" #: 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: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:1921 msgid "Button" msgstr "Tipka" #: 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 "Pokreni naredbu s argumentima." #: lib/solaar/ui/diversion_rules.py:1975 msgid "Add argument" msgstr "Dodaj argument" #: lib/solaar/ui/diversion_rules.py:2050 msgid "Toggle" msgstr "Uklj/Isklj" #: lib/solaar/ui/diversion_rules.py:2050 msgid "True" msgstr "Istina" #: 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 "Uređaj je aktivan i njegove postavke se mogu promijeniti." #: 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 "Vrijednost" #: 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:288 #, python-format 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." #: 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." #: lib/solaar/ui/pair_window.py:129 msgid "left" msgstr "lijevo" #: lib/solaar/ui/pair_window.py:129 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." #: lib/solaar/ui/pair_window.py:188 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." #: 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." #: 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." #: 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." #: lib/solaar/ui/pair_window.py:210 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" #: 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 "Pritisnite tipku uparivanja sve dok svjetlo uparivanja ne počne brzo " "treptati." #: lib/solaar/ui/pair_window.py:276 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 supported device found" msgstr "" #: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 #, python-format msgid "About %s" msgstr "O %su" #: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 #, python-format msgid "Quit %s" msgstr "Zatvori %s" #: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "nema prijemnika" #: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "bez stanja" #: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "Pretraživanje" #: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Baterija" #: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Bežično povezivanje" #: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Osvjetljenje" #: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Prikaži tehničke pojedinosti" #: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Upari novi uređaj" #: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Odaberi uređaj" #: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "Uređivač pravila" #: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Putanja" #: 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 "Serijski broj" #: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Indeks" #: 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 #, 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 "Cikličko osvježavanje" #: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "ID jedinice" #: lib/solaar/ui/window.py:576 msgid "none" msgstr "nepoznato" #: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Obavijesti" #: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "Nema uparenih uređaja." #: 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." #: 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: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." #: lib/solaar/ui/window.py:692 msgid "Battery Voltage" msgstr "Napon baterije" #: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "Napon prijavljen od strane baterije" #: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "Razina 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:705 lib/solaar/ui/window.py:707 msgid "next reported " msgstr "sljedeće poznato " #: 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:713 msgid "last known" msgstr "posljednje poznato" #: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "šifrirano" #: 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" #: 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." #: lib/solaar/ui/window.py:748 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d lux" #~ msgid " paired devices." #~ msgstr " uparena uređaja." #~ msgid "%(battery_level)s" #~ msgstr "%(battery_level)s" #~ msgid "%(battery_percent)d%%" #~ msgstr "%(battery_percent)d%%" #~ msgid "1 paired device." #~ msgstr "1 upareni uređaj." #~ msgid "Actions" #~ msgstr "Radnje" #~ 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 "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 "Battery information unknown." #~ msgstr "Informacije baterije nepoznate." #~ msgid "Count" #~ msgstr "Brojač" #~ msgid "DPI Sliding Adjustment" #~ msgstr "Prilagodba DPI pomicanjem" #~ 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 "Effectively turns off thumb scrolling in Linux." #~ msgstr "Učinkovito isključuje pomicanje palcem u Linuxu." #~ msgid "Effectively turns off wheel scrolling in Linux." #~ msgstr "Učinkovito isključuje pomicanje kotačića u Linuxu." #~ 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" #, 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 "HID++ Scrolling" #~ msgstr "HID++ Scrolling" #~ msgid "HID++ Thumb Scrolling" #~ msgstr "HID++ pomicanje palcem" #~ msgid "HID++ mode for horizontal scroll with the thumb wheel." #~ msgstr "HID++ način rada za okomito pomicanje kotačića palca." #~ msgid "HID++ mode for vertical scroll with the wheel." #~ msgstr "HID++ način rada za okomito pomicanje kotačića." #~ msgid "High Resolution Scrolling" #~ msgstr "Pomicanje kotačićem visoke razlučivosti" #~ msgid "High Resolution Wheel Invert" #~ msgstr "Obrnuto pomicanje kotačićem visoke razlučivosti" #~ msgid "High-sensitivity wheel invert direction for vertical scroll." #~ msgstr "Obrnuti visoko osjetljivi smjer kotačića za okomito " #~ "pomicanje." #~ 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 "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 "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 "Invert thumb scroll direction." #~ msgstr "Obrnuti smjer pomicanja kotačića palca." #~ 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 "No Logitech device found" #~ msgstr "Nema pronađenih Logitechovih uređaja" #~ msgid "Scroll Wheel Rachet" #~ msgstr "Ustavljanje pomicanja kotačića" #~ 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 "Shows status of devices connected\n" #~ "through wireless Logitech receivers." #~ msgstr "Prikazuje stanje uređaja povezanih\n" #~ "putem Logitech bežičnih prijemnika." #~ msgid "Solaar depends on a udev file that is not present" #~ msgstr "Solaar ovisi o udev datoteci koja nije prisutna" #~ msgid "The receiver only supports %d paired device(s)." #~ msgstr "Prijemnik samo podržava %d upareni uređaj(e)." #~ msgid "The receiver was unplugged." #~ msgstr "Prijemnik je odspojen." #~ 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 "Thumb Scroll Invert" #~ msgstr "Obrnuto pomicanje kotačića palca" #~ 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" Solaar-1.1.20/po/id.po000066400000000000000000001620101522022367600143730ustar00rootroot00000000000000# 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" Solaar-1.1.20/po/it.po000066400000000000000000001461461522022367600144270ustar00rootroot00000000000000# Italian translations for solaar package. # Copyright (C) 2013 THE solaar'S COPYRIGHT HOLDER # 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: 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" "Language: it\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-Launchpad-Export-Date: 2021-02-17 00:56+0000\n" "X-Generator: Launchpad (build " "b3a93345a124168b715ec9ae0945884caa15f58f)\n" #: 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: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:124 msgid "Lightspeed Receiver" msgstr "" #: lib/logitech_receiver/base_usb.py:133 msgid "EX100 Receiver 27 Mhz" msgstr "" #: lib/logitech_receiver/i18n.py:30 msgid "empty" msgstr "vuoto" #: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "critico" #: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "bassa" #: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "" #: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "buono" #: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "piena" #: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "in scarica" #: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "in ricarica" #: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "in ricarica" #: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "" #: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "quasi piena" #: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "carico" #: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "ricarica lenta" #: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "batteria non valida" #: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "errore di temperatura" #: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "" #: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "" #: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "" #: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "" #: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "timeout del dispositivo" #: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "dispositivo non supportato" #: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "troppe periferiche" #: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "timeout della sequenza" #: 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 "Bootloader" #: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardware" #: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Altro" #: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "" #: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "" #: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "" #: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "" #: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "" #: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "" #: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "" #: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "" #: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "" #: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "" #: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "" #: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "" #: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "" #: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "" #: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "" #: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "" #: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "" #: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "" #: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "" #: lib/logitech_receiver/i18n.py:86 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 msgid "connected" msgstr "connesso" #: lib/logitech_receiver/notifications.py:224 msgid "disconnected" msgstr "disconnesso" #: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "non associato" #: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "acceso" #: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "" #: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 msgid "feature" msgstr "" #: 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: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: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 "Modalità di alta sensibilità per lo scorrimento verticale con la " "rotella." #: lib/logitech_receiver/settings_templates.py:165 msgid "Side Scrolling" msgstr "Scorrimento Laterale" #: 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 disabilitato, spingendo la rotella lateralmente vengono " "inviati eventi personalizzati\n" "invece degli eventi standard di scorrimento laterale." #: 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 "Sensibilità al movimento del mouse" #: 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: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: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: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 "Azioni tasto/pulsante" #: 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: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: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 "Sensibilità (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 "Disabilita i pulsanti" #: 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:795 #, python-format msgid "Disables the %s key." msgstr "Disabilita il tasto %s" #: 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:917 msgid "Suppress tap and edge gestures" msgstr "" #: 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/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: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:170 #, python-format msgid "Battery: %(level)s" msgstr "Batteria: %(level)s" #: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "Batteria: %(percent)d%%" #: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "Illuminazione: %(level)s lux" #: 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:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Batteria: %(percent)d%% (%(status)s)" #: lib/solaar/ui/__init__.py:52 msgid "Permissions error" msgstr "Errore di permessi" #: 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 "" #: 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 "" #: 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 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 "Disassociazione fallita" #: lib/solaar/ui/__init__.py:66 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Disassociazione fallita di %{device} da %{receiver}." #: lib/solaar/ui/__init__.py:67 msgid "The receiver returned an error, with no further details." msgstr "Il ricevitore ha ritornato un errore senza ulteriori dettagli." #: 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 "GUI design" #: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "Testing" #: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Documentazione Logitech" #: 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:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "" #: lib/solaar/ui/config_panel.py:212 msgid "Complete - ENTER to change" msgstr "" #: lib/solaar/ui/config_panel.py:212 msgid "Incomplete" msgstr "" #: 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/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:65 msgid "User-defined rules" msgstr "Regole definite dall'utente" #: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "Regola" #: 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:70 msgid "[empty]" msgstr "[vuoto]" #: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "Editor di regole di Solaar" #: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "Rendi le modifiche permanenti?" #: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Sì" #: lib/solaar/ui/diversion_rules.py:148 msgid "No" msgstr "" #: 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: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 "Inserisci qui" #: lib/solaar/ui/diversion_rules.py:374 msgid "Insert above" msgstr "Inserisci sopra" #: lib/solaar/ui/diversion_rules.py:376 msgid "Insert below" msgstr "Inserisci sotto" #: lib/solaar/ui/diversion_rules.py:382 msgid "Insert new rule here" msgstr "Inserisci una nuova regola qui" #: lib/solaar/ui/diversion_rules.py:384 msgid "Insert new rule above" msgstr "Inserisci una nuova regola sopra" #: lib/solaar/ui/diversion_rules.py:386 msgid "Insert new rule below" msgstr "Inserisci una nuova regola sotto" #: lib/solaar/ui/diversion_rules.py:427 msgid "Paste here" msgstr "Incolla qui" #: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "Incolla sopra" #: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "Incolla sotto" #: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "Incolla la regola qui" #: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "Incolla la regola sopra" #: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "Incolla la regola sotto" #: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "Incolla la regola" #: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "Appiattisci" #: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "Inserisci" #: 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: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:513 msgid "Condition" msgstr "Condizione" #: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "Funzionalità" #: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "Segnala" #: 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:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "Chiave" #: 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: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:532 msgid "Action" msgstr "Azione" #: 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:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "Scorrimento del mouse" #: 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: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: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: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:610 msgid "Negate" msgstr "Nega" #: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "" #: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Taglia" #: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Incolla" #: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Copia" #: 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:1138 msgid "Number of seconds to delay." msgstr "" #: lib/solaar/ui/diversion_rules.py:1177 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 "Aggiungi chiave" #: 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: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 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: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: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: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: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: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:210 msgid "Found a new device:" msgstr "Trovato un nuovo dispositivo:" #: 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: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 "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: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" "Questo ricevitore ha %d accoppiamento rimanente." msgstr[1] "\n" "\n" "Questo ricevitore ha %d accoppiamenti rimanenti." #: 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/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 "Informazioni %s" #: 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:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "nessun ricevitore" #: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "nessuno stato" #: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "Ricerca" #: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Batteria" #: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Collegamento Wireless" #: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Luminosità" #: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Mostra Dettagli Tecnici" #: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Associa un nuovo dispositivo" #: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Seleziona un dispositivo" #: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "Editor regole" #: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Percorso" #: 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 "Seriale" #: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Indice" #: lib/solaar/ui/window.py:547 msgid "Wireless PID" msgstr "PID Wireless" #: lib/solaar/ui/window.py:549 msgid "Product ID" msgstr "ID Prodotto" #: lib/solaar/ui/window.py:551 msgid "Protocol" msgstr "Protocollo" #: lib/solaar/ui/window.py:551 msgid "Unknown" msgstr "Sconosciuto" #: 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 "Frequenza di aggiornamento" #: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "ID unità" #: lib/solaar/ui/window.py:576 msgid "none" msgstr "nessuno" #: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Notifiche" #: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "Nessun dispositivo accoppiato" #: 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] "Fino a %(max_count)s dispositivo può essere accoppiato con " "questo ricevitore" msgstr[1] "Fino a %(max_count)s dispositivi possono essere accoppiati " "con questo ricevitore" #: 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: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:692 msgid "Battery Voltage" msgstr "Tensione della batteria" #: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "Tensione riportata dalla batteria" #: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "Livello batteria" #: lib/solaar/ui/window.py:698 msgid "Approximate level reported by battery" msgstr "Livello approssimativo riportato dalla batteria" #: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 msgid "next reported " msgstr "Prossimo livello " #: 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:713 msgid "last known" msgstr "ultimo conosciuto" #: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "cifrato" #: 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: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" #~ msgid "Adjust the DPI by sliding the mouse horizontally while " #~ "holding the DPI button." #~ 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" #~ "attraverso i ricevitori Logitech." #~ 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" Solaar-1.1.20/po/ja.po000066400000000000000000001523731522022367600144040ustar00rootroot00000000000000# Japanese translations for solaar package # Copyright (C) 2021 SOLAAR'S COPYRIGHT HOLDER # This file is distributed under the same license as the Solaar package. # 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: 2021-09-24 00:25+0900\n" "Last-Translator: Ryunosuke Toda \n" "Language-Team: \n" "Language: ja\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:46 msgid "Bolt Receiver" msgstr "" #: lib/logitech_receiver/base_usb.py:57 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:124 msgid "Lightspeed Receiver" msgstr "Lightspeed レシーバー" #: lib/logitech_receiver/base_usb.py:133 msgid "EX100 Receiver 27 Mhz" msgstr "EX100 レシーバー 27 Mhz" #: lib/logitech_receiver/i18n.py:30 msgid "empty" msgstr "空" #: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "わずか" #: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "低" #: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "中" #: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "良好" #: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "満充電" #: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "低下" #: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "再充電中" #: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "充電中" #: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "充電していません" #: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "ほぼ満充電" #: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "充電完了" #: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "低速充電" #: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "バッテリー不正" #: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "温度エラー" #: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "エラー" #: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "標準" #: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "高速" #: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "低速" #: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "デバイス応答なし" #: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "非対応のデバイス" #: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "デバイス数超過" #: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "シーケンス応答なし" #: 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 "Bootloader" #: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardware" #: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Other" #: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "左ボタン" #: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "右ボタン" #: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "中ボタン" #: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "戻るボタン" #: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "進むボタン" #: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "マウスジェスチャーボタン" #: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "SmartShift" #: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "DPIスイッチ" #: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "左チルト" #: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "右チルト" #: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "左クリック" #: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "右クリック" #: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "マウス中ボタン" #: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "マウス戻るボタン" #: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "マウス進むボタン" #: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "" #: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "" #: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "" #: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "" #: lib/logitech_receiver/i18n.py:86 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 msgid "connected" msgstr "接続" #: lib/logitech_receiver/notifications.py:224 msgid "disconnected" msgstr "切断" #: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "ペアリング解除" #: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "電源オン" #: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "" #: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 msgid "feature" msgstr "" #: lib/logitech_receiver/settings_templates.py:139 msgid "Swap Fx function" 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." 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: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 "バックライトのON/OFFを切り替えます。" #: 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 "マウス速度の倍率(256で標準)。" #: 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 "CrownにCROWNHID++通知を送信させます(Solaarルールをトリガーしますが、" "それ以外の場合は無視されます)。" #: 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 "感度(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 "無効キー" #: 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 "キー %s を無効にする。" #: 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:810 #: lib/logitech_receiver/settings_templates.py:861 msgid "Change keys to match OS." msgstr "OSに合わせたキーに変更します。" #: 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 + 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: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:170 #, python-format msgid "Battery: %(level)s" msgstr "バッテリー残量: %(level)s" #: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "バッテリー残量: %(percent)d%%" #: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "照明: %(level)s ルクス" #: lib/logitech_receiver/status.py:239 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "バッテリー残量: %(level)s (%(status)s)" #: lib/logitech_receiver/status.py:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "バッテリー残量: %(percent)d%% (%(status)s)" #: 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 "" #: 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 "デバイスに接続できません" #: 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 で Logitech のレシーバーまたはデバイスが見つかりましたが、接続中に" "エラーが発生しました。" #: 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 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "%{receiver} からの %{device} のペアリング解除に失敗しました。" #: lib/solaar/ui/__init__.py:67 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 "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 "" #: 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 "" #: lib/solaar/ui/config_panel.py:212 msgid "Incomplete" msgstr "" #: 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/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:65 msgid "User-defined rules" msgstr "ユーザー定義のルール" #: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "ルール" #: 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:70 msgid "[empty]" msgstr "[空]" #: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "Solaarルールエディタ" #: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "変更を保存しますか?" #: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "はい" #: lib/solaar/ui/diversion_rules.py:148 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: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 msgid "Paste here" msgstr "ここに貼り付け" #: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "上に貼り付け" #: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "下に貼り付け" #: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "ここにルールを貼り付け" #: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "上にルールを貼り付け" #: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "下にルールを貼り付け" #: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "ルールを貼り付け" #: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "フラット化" #: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "挿入" #: 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:511 lib/solaar/ui/diversion_rules.py:637 #: lib/solaar/ui/diversion_rules.py:1111 msgid "And" msgstr "すべて" #: lib/solaar/ui/diversion_rules.py:513 msgid "Condition" msgstr "状態" #: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "機能" #: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "通知" #: 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:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "キー" #: 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: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:532 msgid "Action" msgstr "アクション" #: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 msgid "Key press" msgstr "キー押下" #: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "マウススクロール" #: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 msgid "Mouse click" msgstr "マウスクリック" #: 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: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: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:610 msgid "Negate" msgstr "否" #: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "次で覆う" #: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "切り取り" #: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "貼り付け" #: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "コピー" #: 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:1138 msgid "Number of seconds to delay." msgstr "" #: lib/solaar/ui/diversion_rules.py:1177 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 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: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: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 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: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" "このレシーバーはあと %d デバイスとペアリングできます。" #: lib/solaar/ui/pair_window.py:286 msgid "\n" "Cancelling at this point will not use up a pairing." msgstr "" #: 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 "%sについて" #: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 #, python-format msgid "Quit %s" msgstr "%sを終了" #: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "レシーバーなし" #: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "ステータスなし" #: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "スキャン中" #: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "バッテリー" #: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "無線通信" #: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "照明" #: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "技術的な詳細を表示" #: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "デバイスの追加" #: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "デバイスを選択して下さい" #: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "ルールエディタ" #: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Path" #: 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 "Serial" #: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Index" #: 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 "Protocol" #: lib/solaar/ui/window.py:551 msgid "Unknown" msgstr "Unknown" #: 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 "Polling rate" #: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "Unit ID" #: lib/solaar/ui/window.py:576 msgid "none" msgstr "none" #: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Notifications" #: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "ペアリングされたデバイスはありません。" #: 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:634 msgid "Only one device can be paired to this receiver." msgstr "このレシーバーは1つのデバイスのみとペアリングできます。" #: 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:692 msgid "Battery Voltage" msgstr "バッテリー電圧" #: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "バッテリーから報告された電圧" #: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "バッテリー残量" #: lib/solaar/ui/window.py:698 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:708 msgid " and next level to be reported." msgstr " と 次に報告される残量。" #: lib/solaar/ui/window.py:713 msgid "last known" msgstr "最後に認識された値" #: lib/solaar/ui/window.py:724 msgid "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: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 "幅" Solaar-1.1.20/po/ka.po000066400000000000000000002471721522022367600144070ustar00rootroot00000000000000# 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 ლუქსი" Solaar-1.1.20/po/nb.po000066400000000000000000002015211522022367600143770ustar00rootroot00000000000000# Norwegian Bokmal translations for solaar package. # 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: 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:48 msgid "Bolt Receiver" msgstr "Bolt mottaker" #: lib/logitech_receiver/base_usb.py:60 msgid "Unifying Receiver" msgstr "Unifying 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:121 msgid "Lightspeed Receiver" msgstr "Lightspeed mottaker" #: lib/logitech_receiver/base_usb.py:131 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 "low" msgstr "lav" #: 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 "not charging" msgstr "lader ikke" #: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "nesten full" #: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "ladet" #: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "langsom lading" #: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "batterifeil" #: lib/logitech_receiver/i18n.py:43 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 "fast" msgstr "rask" #: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "langsom" #: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "enheten svarte ikke" #: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "enheten er ikke støttet" #: 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 "Firmware" msgstr "Fastvare" #: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Oppstartslaster" #: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Maskinvare" #: 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 "Right Button" msgstr "Høyreknapp" #: lib/logitech_receiver/i18n.py:61 msgid "Middle Button" msgstr "Midtknapp" #: lib/logitech_receiver/i18n.py:62 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 "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 "Right Tilt" msgstr "Høyre-tilt" #: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "Venstre-klikk" #: lib/logitech_receiver/i18n.py:70 msgid "Right Click" msgstr "Høyre-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 "Mouse Forward Button" msgstr "Foroverknapp for mus" #: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "Navigering med bevegelsesknapp" #: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "Venstre rulleknapp for mus" #: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "Høyre rulleknapp for mus" #: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "nedtrykt" #: lib/logitech_receiver/i18n.py:79 msgid "released" msgstr "utløst" #: 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/notifications.py:86 #: lib/logitech_receiver/notifications.py:138 msgid "pairing lock is open" msgstr "parlåsen er åpen" #: lib/logitech_receiver/notifications.py:103 msgid "discovery lock is closed" msgstr "oppdagelseslåsen er lukket" #: lib/logitech_receiver/notifications.py:103 msgid "discovery lock is open" msgstr "oppdagelseslåsen er åpen" #: lib/logitech_receiver/notifications.py:232 lib/solaar/listener.py:247 msgid "connected" msgstr "tilkoblet" #: lib/logitech_receiver/notifications.py:232 lib/solaar/listener.py:247 msgid "disconnected" msgstr "frakoblet" #: lib/logitech_receiver/notifications.py:258 msgid "unpaired" msgstr "uparet" #: lib/logitech_receiver/notifications.py:305 msgid "powered on" msgstr "påslått" #: lib/logitech_receiver/receiver.py:383 msgid "No paired devices." msgstr "Ingen paret enhet." #: 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/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 "" "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: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" #: 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:157 msgid "Scroll Wheel Smooth Scrolling" msgstr "Myk rulling med rullehjul" #: lib/logitech_receiver/settings_templates.py:158 #: 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" #: 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 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)" #: 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 "Følsomhet for musbevegelser" #: lib/logitech_receiver/settings_templates.py:251 msgid "Backlight Timed" msgstr "Tidsbestemt bakgrunnslys" #: 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:263 msgid "Backlight" msgstr "Bakbelysning" #: 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:296 msgid "Automatic" msgstr "Automatisk" #: lib/logitech_receiver/settings_templates.py:298 msgid "Manual" msgstr "Manuell" #: lib/logitech_receiver/settings_templates.py:300 msgid "Enabled" msgstr "Påslått" #: lib/logitech_receiver/settings_templates.py:306 msgid "Backlight Level" msgstr "Bakgrunnslysnivå" #: 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 "Backlight Delay Hands Out" msgstr "Forsinkelse på bakgrunnsbelysning når hendene fjernes" #: 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 "Backlight Delay Hands In" msgstr "Forsinkelse på bakgrunnsbelysning når hendene er nære" #: 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 "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:391 msgid "Backlight (Seconds)" msgstr "Bakgrunnslys (sekunder)" #: lib/logitech_receiver/settings_templates.py:403 msgid "Scroll Wheel High Resolution" msgstr "Høyoppløselig rullehjul" #: 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: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 å sende LOWRES_WHEEL HID++-varsler (som utløser Solaar-" "regler, men som ellers ignoreres)." #: 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øsning 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 å 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 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:640 msgid "Scroll Wheel Ratcheted" msgstr "Rullehjul sperrehaket" #: 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: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 å bytte mellom sperrehake og frittløpende.\n" "Musehjulet er alltid satt 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 handling for en 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 handlinger (for eksempel venstre musknapp) kan resultere " "i et ubrukbart 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 å sende HID++-varsler (omdirigert) eller " "initier musebevegelser eller glidende 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 "Musebevegelser" #: lib/logitech_receiver/settings_templates.py:886 #: lib/logitech_receiver/settings_templates.py:887 #: lib/logitech_receiver/settings_templates.py:888 msgid "Regular" msgstr "Vanlig" #: lib/logitech_receiver/settings_templates.py:886 msgid "Sliding DPI" msgstr "Glidende DPI" #: 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:1081 msgid "Sensitivity Switching" msgstr "Følsomhetsbytte" #: 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:1087 msgid "Off" msgstr "Av" #: lib/logitech_receiver/settings_templates.py:1118 msgid "Disable keys" msgstr "Slå av taster" #: lib/logitech_receiver/settings_templates.py:1119 msgid "Disable specific keyboard keys." msgstr "Slå av spesifikke tastaturtaster." #: lib/logitech_receiver/settings_templates.py:1122 #, python-format msgid "Disables the %s key." msgstr "Slår av %s-tasten." #: lib/logitech_receiver/settings_templates.py:1135 #: lib/logitech_receiver/settings_templates.py:1192 msgid "Set OS" msgstr "Still OS" #: 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/settings_templates.py:1205 msgid "Change Host" msgstr "Endre tjener" #: 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 "Lights up the %s key." msgstr "Lyser opp %s-tasten." #: 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 "" "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/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 "" "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/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/solaar/ui/common.py:49 msgid "Unpairing failed" msgstr "Kunne ikke bryte paringen" #: lib/solaar/ui/common.py:51 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Mislyktes med å bryte paret %{device} og %{receiver}." #: 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/config_panel.py:228 msgid "Complete - ENTER to change" msgstr "Fullfør - ENTER for å endre" #: lib/solaar/ui/config_panel.py:228 msgid "Incomplete" msgstr "Ufullstendig" #: 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" #: lib/solaar/ui/config_panel.py:609 msgid "Changes allowed" msgstr "Endringer tillatt" #: lib/solaar/ui/config_panel.py:610 msgid "No changes allowed" msgstr "Ingen endringer tillatt" #: lib/solaar/ui/config_panel.py:611 msgid "Ignore this setting" msgstr "Ignorer denne innstillingen" #: lib/solaar/ui/config_panel.py:655 msgid "Working" msgstr "Suksess" #: lib/solaar/ui/config_panel.py:658 msgid "Read/write operation failed." msgstr "Lese-/skriveoperasjonen mislyktes." #: lib/solaar/ui/desktop_notifications.py:114 msgid "unspecified reason" msgstr "uspesifisert årsak" #: lib/solaar/ui/diversion_rules.py:69 msgid "Built-in rules" msgstr "Faste regler" #: lib/solaar/ui/diversion_rules.py:69 msgid "User-defined rules" msgstr "Brukerdefinerte regler" #: lib/solaar/ui/diversion_rules.py:71 lib/solaar/ui/diversion_rules.py:1089 msgid "Rule" msgstr "Regel" #: 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:74 msgid "[empty]" msgstr "[tom]" #: lib/solaar/ui/diversion_rules.py:98 msgid "Make changes permanent?" msgstr "Gjøre endringer permanente?" #: lib/solaar/ui/diversion_rules.py:103 msgid "Yes" msgstr "Ja" #: lib/solaar/ui/diversion_rules.py:105 msgid "No" msgstr "Nei" #: 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:239 msgid "Paste here" msgstr "Lim inn her" #: lib/solaar/ui/diversion_rules.py:241 msgid "Paste above" msgstr "Lim inn ovenfor" #: lib/solaar/ui/diversion_rules.py:243 msgid "Paste below" msgstr "Lim inn nedenfor" #: 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 ovenfor" #: lib/solaar/ui/diversion_rules.py:253 msgid "Paste rule below" msgstr "Lim inn regel nedenfor" #: 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 "Sett inn her" #: lib/solaar/ui/diversion_rules.py:274 msgid "Insert above" msgstr "Sett inn ovenfor" #: lib/solaar/ui/diversion_rules.py:276 msgid "Insert below" msgstr "Sett inn 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:284 msgid "Insert new rule above" msgstr "Sett inn ny regel ovenfor" #: lib/solaar/ui/diversion_rules.py:286 msgid "Insert new rule below" msgstr "Sett inn ny regel nedenfor" #: lib/solaar/ui/diversion_rules.py:313 msgid "Flatten" msgstr "Utflate" #: lib/solaar/ui/diversion_rules.py:346 msgid "Insert" msgstr "Sett 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 "Betingelse" #: 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 "Enhet" #: 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 "Musebevegelse" #: 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 "Set" msgstr "Still 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:378 lib/solaar/ui/diversion_rules.py:1152 msgid "Later" msgstr "Senere" #: lib/solaar/ui/diversion_rules.py:407 msgid "Insert new rule" msgstr "Sett inn ny regel" #: 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: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 "" "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:1170 msgid "Not" msgstr "Ikke" #: lib/solaar/ui/diversion_rules.py:1201 msgid "Toggle" msgstr "Veksle" #: lib/solaar/ui/diversion_rules.py:1202 msgid "True" msgstr "Sann" #: lib/solaar/ui/diversion_rules.py:1203 msgid "False" msgstr "Falsk" #: lib/solaar/ui/diversion_rules.py:1216 msgid "Unsupported setting" msgstr "Innstillingen støttes ikke" #: 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: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:1419 msgid "Device that originated the current notification." msgstr "Enheten som ga det gjeldende varselet." #: lib/solaar/ui/diversion_rules.py:1432 msgid "Name of host computer." msgstr "Navn på vertsmaskin." #: lib/solaar/ui/diversion_rules.py:1503 msgid "Value" msgstr "Verdi" #: lib/solaar/ui/diversion_rules.py:1512 msgid "Item" msgstr "Punkt" #: lib/solaar/ui/diversion_rules.py:1791 msgid "Change setting on device" msgstr "Endre innstilling på enhet" #: lib/solaar/ui/diversion_rules.py:1807 msgid "Setting on device" msgstr "Innstilling på enhet" #: 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 enhet" #: 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/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 "" "\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/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/pair_window.py:166 #, python-format msgid "Enter passcode on %(name)s." msgstr "Skriv inn passordet på %(name)s." #: 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/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 "" "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:219 msgid "The wireless link is not encrypted" msgstr "Den trådløse koblingen er ukryptert" #: lib/solaar/ui/pair_window.py:224 msgid "Found a new device:" msgstr "Fant ny enhet:" #: 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 "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øste 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 "" "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" #: 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 mottaker" #: 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ådløs kobling" #: lib/solaar/ui/window.py:128 msgid "Lighting" msgstr "Belysning" #: lib/solaar/ui/window.py:162 msgid "Show Technical Details" msgstr "Vis tekniske detaljer" #: lib/solaar/ui/window.py:178 msgid "Pair new device" msgstr "Par ny enhet" #: lib/solaar/ui/window.py:196 msgid "Select a device" msgstr "Velg en enhet" #: lib/solaar/ui/window.py:311 msgid "Rule Editor" msgstr "Regel editor" #: lib/solaar/ui/window.py:504 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ådløs-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 "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 "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:608 #, 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:665 msgid "Battery Voltage" msgstr "Batterispenning" #: 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 "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 "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 "not encrypted" msgstr "ukryptert" #: 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å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:721 #, python-format 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." #, python-format #~ msgid "%(rate)d ms (%(rate_hz)dHz)" #~ msgstr "%(rate)d ms (%(rate_hz)dHz)" #~ msgid "Actions" #~ msgstr "Handlinger" #~ msgid "Add action" #~ msgstr "Legg til handling" #~ 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 "" #~ "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 "" #~ "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 "Battery information unknown." #~ msgstr "Ukjent batteriinformasjon" #, python-format #~ msgid "Battery: %(level)s" #~ msgstr "Batteri: %(level)s" #, python-format #~ msgid "Battery: %(percent)d%%" #~ msgstr "Batteri: %(percent)d%%" #~ msgid "Count" #~ msgstr "Antall" #~ msgid "DPI Sliding Adjustment" #~ msgstr "DPI-glidejustering" #~ msgid "Divert G Keys" #~ msgstr "Omdiriger G-taster" #~ 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 "Effectively turns off thumb scrolling in Linux." #~ msgstr "Slår i praksis av tommelrulling i Linux." #~ msgid "Effectively turns off wheel scrolling in Linux." #~ msgstr "Slår i praksis av hjulrulling i Linux." #~ msgid "" #~ "Enable onboard profiles, which often control report rate and keyboard " #~ "lighting" #~ msgstr "" #~ "Aktiver innebygde profiler, som ofte kontrollerer rapporthastighet og " #~ "tastaturbelysning" #~ 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 "" #~ "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 "Frequency of device polling, in milliseconds" #~ msgstr "Frekvens på enhetsspørring, i millisekunder" #~ 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++ modus for horisontal rulling med tommelhjulet." #~ msgid "HID++ mode for vertical scroll with the wheel." #~ msgstr "HID++ modus for vertikal rulling med hjulet." #~ msgid "High Resolution Scrolling" #~ msgstr "Høyoppløselig rulling" #~ msgid "High Resolution Wheel Invert" #~ msgstr "Inverter høy rulleoppløsning" #~ msgid "High-sensitivity wheel invert direction for vertical scroll." #~ msgstr "Inverter retning for høy vertikal rulleoppløsning." #~ 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 "" #~ "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 "Invert thumb scroll direction." #~ msgstr "Inverter retning på tommelrulling." #, 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-taster til å sende GKEY HID++-varsler (som utløser Solaar-regler, " #~ "men som ellers ignoreres)." #~ 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 "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 "No Logitech device found" #~ msgstr "Finner ingen Logitech-enhet" #~ 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" Solaar-1.1.20/po/nl.po000066400000000000000000001536561522022367600144300ustar00rootroot00000000000000# Dutch translations for solaar package # Copyright (C) 2021 SOLAAR'S COPYRIGHT HOLDER # This file is distributed under the same license as the Solaar package. # 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: 2021-01-15 22:03+0100\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: \n" "Language: nl\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.4.2\n" #: 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: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:124 msgid "Lightspeed Receiver" msgstr "" #: lib/logitech_receiver/base_usb.py:133 msgid "EX100 Receiver 27 Mhz" msgstr "" #: lib/logitech_receiver/i18n.py:30 msgid "empty" msgstr "leeg" #: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "bijna leeg" #: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "laag" #: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "" #: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "goed" #: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "opgeladen" #: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "bezig met ontladen" #: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "bezig met opladen" #: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "bezig met opladen" #: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "" #: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "bijna opgeladen" #: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "opgeladen" #: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "langzaam opladen" #: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "verkeerde batterij" #: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "temperatuurfout" #: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "" #: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "" #: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "" #: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "" #: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "time-out opgetreden" #: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "apparaat wordt niet ondersteund" #: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "te veel apparaten" #: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "meerdere time-outs" #: 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 "Opstartlader" #: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardware" #: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Overig" #: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "" #: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "" #: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "" #: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "" #: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "" #: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "" #: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "Slim schakelen" #: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "" #: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "" #: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "" #: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "" #: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "" #: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "" #: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "" #: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "" #: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "" #: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "" #: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "" #: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "" #: lib/logitech_receiver/i18n.py:86 msgid "released" msgstr "" #: 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:75 #: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is open" msgstr "koppelvergendeling uitgeschakeld" #: 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:224 msgid "disconnected" msgstr "verbinding verbroken" #: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "niet gekoppeld" #: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "ingeschakeld" #: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "" #: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 msgid "feature" msgstr "" #: 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: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:157 msgid "Scroll Wheel Smooth Scrolling" msgstr "Vloeiend scrollen" #: 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:165 msgid "Side Scrolling" msgstr "Zijwaarts scrollen" #: 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 "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: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 "Muisgevoeligheid" #: 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: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:237 msgid "Scroll Wheel High Resolution" msgstr "Scrollen met hoge resolutie" #: 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 "Scrollrichting" #: 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:265 msgid "Scroll Wheel Resolution" msgstr "Scrollresolutie" #: 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 "Gevoeligheid (cursorsnelheid)" #: 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: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 "Duimwielrichting" #: 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: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 "Kroongebeurtenissen omleiden" #: 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: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: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: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 "Toets-/Knopacties" #: 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: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 "Let op: het aanpassen van belangrijke acties (zoals die van de " "linkermuisknop) kan leiden tot een onbruikbaar systeem." #: lib/logitech_receiver/settings_templates.py:639 msgid "Key/Button Diversion" msgstr "Toets-/Knopomleiding" #: 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 "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: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:967 msgid "Ratio zone" msgstr "" #: 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/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: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:170 #, python-format msgid "Battery: %(level)s" msgstr "Batterijniveau: %(level)s" #: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "Batterijniveau: %(percent)d%%" #: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "Verlichting: %(level)s lux" #: 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:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Batterijniveau: %(percent)d%% (%(status)s)" #: lib/solaar/ui/__init__.py:52 msgid "Permissions error" msgstr "Machtigingsfout" #: 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 "" #: 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 "" #: 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 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 "Loskoppelen mislukt" #: lib/solaar/ui/__init__.py:66 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "'%{device}' kan niet worden losgekoppeld van '%{receiver}'." #: lib/solaar/ui/__init__.py:67 msgid "The receiver returned an error, with no further details." msgstr "Er is een fout opgetreden zonder nadere informatie." #: 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 "Toepassingsontwerp" #: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "Testen" #: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Logitech-documentatie" #: 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:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "" #: lib/solaar/ui/config_panel.py:212 msgid "Complete - ENTER to change" msgstr "" #: lib/solaar/ui/config_panel.py:212 msgid "Incomplete" msgstr "" #: 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/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:65 msgid "User-defined rules" msgstr "Eigen regels" #: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "Regel" #: 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:70 msgid "[empty]" msgstr "[leeg]" #: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "Solaar-regelbewerker" #: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "Wilt u de wijzigingen permanent opslaan?" #: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Ja" #: lib/solaar/ui/diversion_rules.py:148 msgid "No" msgstr "" #: 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: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 "Hier invoegen" #: lib/solaar/ui/diversion_rules.py:374 msgid "Insert above" msgstr "Hierboven invoegen" #: lib/solaar/ui/diversion_rules.py:376 msgid "Insert below" msgstr "Hieronder invoegen" #: lib/solaar/ui/diversion_rules.py:382 msgid "Insert new rule here" msgstr "Nieuwe regel hier invoegen" #: lib/solaar/ui/diversion_rules.py:384 msgid "Insert new rule above" msgstr "Nieuwe regel hierboven invoegen" #: lib/solaar/ui/diversion_rules.py:386 msgid "Insert new rule below" msgstr "Nieuwe regel hieronder invoegen" #: lib/solaar/ui/diversion_rules.py:427 msgid "Paste here" msgstr "Hier plakken" #: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "Hierboven plakken" #: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "Hieronder plakken" #: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "Regel hier plakken" #: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "Regel hierboven plakken" #: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "Regel hieronder plakken" #: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "Regel plakken" #: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "Vlakmaken" #: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "Invoegen" #: 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: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:513 msgid "Condition" msgstr "Omstandigheid" #: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "Functie" #: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "Melden" #: 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:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "Toets" #: 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: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:532 msgid "Action" msgstr "Actie" #: 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:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "Scrollbeweging" #: 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: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: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: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:610 msgid "Negate" msgstr "Negeren" #: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "Omslaan met" #: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Knippen" #: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Plakken" #: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Kopiëren" #: 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: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: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 "Toets toevoegen" #: 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: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 "Aanvullende optie toevoegen" #: 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: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: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: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: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: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:210 msgid "Found a new device:" msgstr "Nieuw apparaat aangetroffen:" #: 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: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 "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: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" "Er kan nog %d apparaat worden gekoppeld aan deze ontvanger." msgstr[1] "\n" "\n" "Er kunnen nog %d apparaten worden gekoppeld aan deze ontvanger." #: 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/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 "Info %s" #: 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:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "geen ontvanger" #: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "geen status" #: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "Bezig met zoeken..." #: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Batterij" #: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Draadloze verbinding" #: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Verlichting" #: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Technische informatie tonen" #: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Nieuw apparaat aankoppelen" #: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Kies een apparaat" #: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "Regelbewerker" #: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Pad" #: 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 "Serie" #: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Index" #: lib/solaar/ui/window.py:547 msgid "Wireless PID" msgstr "Draadloze pid" #: lib/solaar/ui/window.py:549 msgid "Product ID" msgstr "Product-id" #: lib/solaar/ui/window.py:551 msgid "Protocol" msgstr "Protocol" #: lib/solaar/ui/window.py:551 msgid "Unknown" msgstr "Onbekend" #: 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 "Controlesnelheid" #: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "Apparaat-id" #: lib/solaar/ui/window.py:576 msgid "none" msgstr "geen" #: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Meldingen" #: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "Geen apparaat gekoppeld." #: 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] "Er kan slechts %(max_count)s apparaat worden gekoppeld aan " "deze ontvanger." msgstr[1] "Er kunnen slechts %(max_count)s apparaten worden gekoppeld " "aan deze ontvanger." #: 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:638 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." 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:692 msgid "Battery Voltage" msgstr "Batterijvoltage" #: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "Het door de batterij gemelde voltage" #: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "Batterijniveau" #: lib/solaar/ui/window.py:698 msgid "Approximate level reported by battery" msgstr "Het door de batterij geschatte niveau" #: lib/solaar/ui/window.py:705 lib/solaar/ui/window.py:707 msgid "next reported " msgstr "volgende melding " #: 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:713 msgid "last known" msgstr "laatst bekend" #: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "versleuteld" #: 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: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" #, python-format #~ msgid "%(battery_level)s" #~ msgstr "%(battery_level)s" #, python-format #~ 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" #~ msgid "High Resolution Wheel Invert" #~ msgstr "Andersom scrollen met hoge resolutie" #~ msgid "High-sensitivity wheel invert mode for vertical scroll." #~ msgstr "Hogegevoeligheidsmodus voor andersom verticaal scrollen." #~ msgid "If the device is already turned on,\n" #~ "turn if off and on again." #~ msgstr "Als het apparaat reeds is ingeschakeld,\n" #~ "schakel het dan uit en weer in." #~ msgid "If the device is already turned on, turn if off and on again." #~ 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" #~ "zijn middels draadloze Logitech-ontvangers." #~ msgid "Solaar depends on a udev file that is not present" #~ msgstr "Solaar is afhankelijk van een udev-bestand dat niet aanwezig " #~ "is" #, python-format #~ msgid "The receiver only supports %d paired device(s)." #~ msgstr "Dit apparaat ondersteunt slechts %d gekoppeld apparaat/" #~ "apparaten." #~ 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" Solaar-1.1.20/po/nn.po000066400000000000000000001753311522022367600144240ustar00rootroot00000000000000# Norwegian Nynorsk translations for solaar package. # 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: 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:48 msgid "Bolt Receiver" msgstr "Bolt mottakar" #: lib/logitech_receiver/base_usb.py:60 msgid "Unifying Receiver" msgstr "Unifying mottakar" #: 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:121 msgid "Lightspeed Receiver" msgstr "Lightspeed mottakar" #: 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 "not charging" msgstr "lader ikkje" #: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "nesten full" #: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "ladet" #: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "langsom lading" #: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "batterifeil" #: lib/logitech_receiver/i18n.py:43 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 "fast" msgstr "rask" #: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "langsom" #: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "eininga svarte ikkje" #: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "eininga er ikkje støtta" #: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "for mange einingar" #: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "sekvens timeout" #: lib/logitech_receiver/i18n.py:54 msgid "Firmware" msgstr "Fastvare" #: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Oppstartslaster" #: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Maskinvare" #: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "Anna" #: 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 "Middle Button" msgstr "Midtknapp" #: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "Tlbakeknapp" #: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "Framoverknapp" #: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "Rørsleknapp" #: 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 "Right Tilt" msgstr "Høgre-tilt" #: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "Venstre-klikk" #: lib/logitech_receiver/i18n.py:70 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 "Mouse Forward Button" msgstr "Framoverknapp for mus" #: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "Navigering med rørsleknapp" #: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "Venstre rulleknapp for mus" #: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "Høgre rulleknapp for mus" #: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "nedtrykt" #: lib/logitech_receiver/i18n.py:79 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 "" "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 "" "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:149 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." #: 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: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 "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 "" "Viss deaktivert; blir trykt hjula sidevegs så sendast ein tilpassa " "knapphending\n" "i staden for standard siderulling." #: lib/logitech_receiver/settings_templates.py:177 msgid "Sensitivity (DPI - older mice)" msgstr "Følsomhet (DPI – eldre mus)" #: 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:251 msgid "Backlight Timed" msgstr "Tidfesta bakgrunnslys" #: 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:263 msgid "Backlight" msgstr "Bakbelysning" #: 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:296 msgid "Automatic" msgstr "Automatisk" #: lib/logitech_receiver/settings_templates.py:298 msgid "Manual" msgstr "Manuell" #: lib/logitech_receiver/settings_templates.py:300 msgid "Enabled" msgstr "Påslått" #: lib/logitech_receiver/settings_templates.py:306 msgid "Backlight Level" msgstr "Bakgrunnslysnivå" #: 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 "Backlight Delay Hands Out" msgstr "Forseinking på bakgrunnsbelysning når hendene blir fjerna" #: 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:373 msgid "Backlight Delay Hands In" msgstr "Forseinking på bakgrunnsbelysning når hendene er nære" #: 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:382 msgid "Backlight Delay Powered" msgstr "Forseinking på bakgrunnsbelysning når straumforsynt" #: 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:391 msgid "Backlight (Seconds)" msgstr "Bakgrunnslys (sekund)" #: lib/logitech_receiver/settings_templates.py:403 msgid "Scroll Wheel High Resolution" msgstr "Høgopplausleg rullehjul" #: 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 "Slår av %s-tasten." #: 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: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:1205 msgid "Change Host" msgstr "Endre tjener" #: lib/logitech_receiver/settings_templates.py:1206 msgid "Switch connection to a different host" msgstr "Endre koplinga til ein annan tenar" #: 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 "Lights up the %s key." msgstr "Lyser opp %s-tasten." #: 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 "" "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/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 "" "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/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/solaar/ui/common.py:49 msgid "Unpairing failed" msgstr "Kunne ikkje bryta paringa" #: lib/solaar/ui/common.py:51 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Mislykkast med å bryta paret %{device} og %{receiver}." #: 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/config_panel.py:228 msgid "Complete - ENTER to change" msgstr "Fullfør - ENTER for å endra" #: lib/solaar/ui/config_panel.py:228 msgid "Incomplete" msgstr "Ufullstendig" #: 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 verdiar" #: lib/solaar/ui/config_panel.py:609 msgid "Changes allowed" msgstr "Endringar tillatne" #: lib/solaar/ui/config_panel.py:610 msgid "No changes allowed" msgstr "Ingen endringar tillatne" #: lib/solaar/ui/config_panel.py:611 msgid "Ignore this setting" msgstr "Ignorer denne innstillinga" #: lib/solaar/ui/config_panel.py:655 msgid "Working" msgstr "Suksess" #: lib/solaar/ui/config_panel.py:658 msgid "Read/write operation failed." msgstr "Lese-/skriveoperasjonen mislykkast." #: lib/solaar/ui/desktop_notifications.py:114 msgid "unspecified reason" msgstr "uspesifisert årsak" #: lib/solaar/ui/diversion_rules.py:69 msgid "Built-in rules" msgstr "Faste reglar" #: lib/solaar/ui/diversion_rules.py:69 msgid "User-defined rules" msgstr "Brukardefinerte reglar" #: lib/solaar/ui/diversion_rules.py:71 lib/solaar/ui/diversion_rules.py:1089 msgid "Rule" msgstr "Regel" #: 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:74 msgid "[empty]" msgstr "[tom]" #: lib/solaar/ui/diversion_rules.py:98 msgid "Make changes permanent?" msgstr "Gjera endringar permanente?" #: lib/solaar/ui/diversion_rules.py:103 msgid "Yes" msgstr "Ja" #: lib/solaar/ui/diversion_rules.py:105 msgid "No" msgstr "Nei" #: 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 "Set" msgstr "Still 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:378 lib/solaar/ui/diversion_rules.py:1152 msgid "Later" msgstr "Seinare" #: lib/solaar/ui/diversion_rules.py:407 msgid "Insert new rule" msgstr "Set inn ny regel" #: 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: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 endringar" #: lib/solaar/ui/diversion_rules.py:639 msgid "Discard changes" msgstr "Forkast endringar" #: 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: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:1170 msgid "Not" msgstr "Ikkje" #: lib/solaar/ui/diversion_rules.py:1201 msgid "Toggle" msgstr "Veksle" #: lib/solaar/ui/diversion_rules.py:1202 msgid "True" msgstr "Sann" #: lib/solaar/ui/diversion_rules.py:1203 msgid "False" msgstr "Falsk" #: lib/solaar/ui/diversion_rules.py:1216 msgid "Unsupported setting" msgstr "Innstillinga blir ikkje støtta" #: 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: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:1419 msgid "Device that originated the current notification." msgstr "Eininga som gav det gjeldande varselet." #: lib/solaar/ui/diversion_rules.py:1432 msgid "Name of host computer." msgstr "Namn på vertsmaskin." #: lib/solaar/ui/diversion_rules.py:1503 msgid "Value" msgstr "Verdi" #: lib/solaar/ui/diversion_rules.py:1512 msgid "Item" msgstr "Punkt" #: lib/solaar/ui/diversion_rules.py:1791 msgid "Change setting on device" msgstr "Endre innstilling på eining" #: lib/solaar/ui/diversion_rules.py:1807 msgid "Setting on device" msgstr "Innstilling på eining" #: 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" #: 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: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 paringar." #: 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:166 #, python-format msgid "Enter passcode on %(name)s." msgstr "Skriv inn passordet på %(name)s." #: 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/pair_window.py:174 msgid "left" msgstr "venstre" #: lib/solaar/ui/pair_window.py:174 msgid "right" msgstr "høgre" #: 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/pair_window.py:219 msgid "The wireless link is not encrypted" msgstr "Den trådlause koplinga er ukryptert" #: lib/solaar/ui/pair_window.py:224 msgid "Found a new device:" msgstr "Fant ny eining:" #: lib/solaar/ui/pair_window.py:245 msgid "Pairing failed" msgstr "Paringa feila" #: 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 "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:311 msgid "Rule Editor" msgstr "Regel editor" #: lib/solaar/ui/window.py:504 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 "Notifications" msgstr "Varslar" #: lib/solaar/ui/window.py:588 msgid "No device paired." msgstr "Inga eining para." #: lib/solaar/ui/window.py:597 #, 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." #: lib/solaar/ui/window.py:608 #, 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." #: lib/solaar/ui/window.py:665 msgid "Battery Voltage" msgstr "Batterispenning" #: 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 "%(light_level)d lux" msgstr "%(light_level)d lux" #~ msgid "" #~ "\n" #~ "\n" #~ "This receiver has %d pairing(s) remaining." #~ msgstr "" #~ "\n" #~ "\n" #~ "Denne mottakaren har %d gjenståande paringar." #, python-format #~ msgid "%(rate)d ms (%(rate_hz)dHz)" #~ msgstr "%(rate)d ms (%(rate_hz)dHz)" #~ 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" #, python-format #~ msgid "Battery: %(level)s" #~ msgstr "Batteri: %(level)s" #, python-format #~ msgid "Battery: %(percent)d%%" #~ msgstr "Batteri: %(percent)d%%" #~ msgid "Divert G Keys" #~ msgstr "Omdiriger G tastar" #~ msgid "Effectively turns off thumb scrolling in Linux." #~ msgstr "Slår i praksis av tommelrulling i Linux." #~ msgid "Effectively turns off wheel scrolling in Linux." #~ msgstr "Slår i praksis av hjulrulling i Linux." #~ 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 "" #~ "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 "" #~ "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 "Frequency of device polling, in milliseconds" #~ msgstr "Frekvensen til rapportar om einingsrørsler" #~ 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++ modus for horisontal rulling med tommelhjulet." #~ msgid "HID++ mode for vertical scroll with the wheel." #~ msgstr "HID++ modus for vertikal rulling med hjulet." #~ msgid "High Resolution Scrolling" #~ msgstr "Høgoppløyseleg rulling" #~ msgid "High Resolution Wheel Invert" #~ msgstr "Inverter høg rulleoppløysning" #~ msgid "High-sensitivity wheel invert direction for vertical scroll." #~ msgstr "Inverter retning for høg vertikal rulleoppløysning." #~ 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 "" #~ "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 "Invert thumb scroll direction." #~ msgstr "Inverter retning på tommelrulling." #, 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" Solaar-1.1.20/po/pl.po000066400000000000000000002254371522022367600144270ustar00rootroot00000000000000# Polish translations for solaar package. # Copyright (C) 2013 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # Automatically generated, 2013. # msgid "" msgstr "" "Project-Id-Version: solaar 1.1.18\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-05-24 20:04+0200\n" "PO-Revision-Date: 2026-05-24 20:43+0200\n" "Last-Translator: Matthaiks\n" "Language-Team: none\n" "Language: pl\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%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" "X-Generator: Poedit 3.9\n" #: lib/logitech_receiver/base_usb.py:52 msgid "Bolt Receiver" msgstr "Odbiornik Bolt" #: lib/logitech_receiver/base_usb.py:64 msgid "Unifying Receiver" msgstr "Odbiornik 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 "Odbiornik Nano" #: lib/logitech_receiver/base_usb.py:125 msgid "Lightspeed Receiver" msgstr "Odbiornik Lightspeed" #: lib/logitech_receiver/base_usb.py:136 msgid "EX100 Receiver 27 Mhz" msgstr "Odbiornik EX100 27 MHz" #: lib/logitech_receiver/common.py:698 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "Akumulator: %(level)s (%(status)s)" #: lib/logitech_receiver/common.py:701 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Akumulator: %(percent)d%% (%(status)s)" #: lib/logitech_receiver/hidpp20.py:1189 lib/logitech_receiver/hidpp20.py:1222 #: lib/logitech_receiver/hidpp20.py:1250 msgid "Cycle" msgstr "Cykl" #: lib/logitech_receiver/hidpp20.py:1190 msgid "Right" msgstr "W prawo" #: lib/logitech_receiver/hidpp20.py:1191 msgid "Down" msgstr "W dół" #: lib/logitech_receiver/hidpp20.py:1192 msgid "Center Out" msgstr "Ze środka" #: lib/logitech_receiver/hidpp20.py:1193 msgid "In" msgstr "Do wewnątrz" #: lib/logitech_receiver/hidpp20.py:1194 msgid "Out" msgstr "Na zewnątrz" #: lib/logitech_receiver/hidpp20.py:1195 #: lib/logitech_receiver/settings_templates.py:1398 msgid "Left" msgstr "W lewo" #: lib/logitech_receiver/hidpp20.py:1196 msgid "Up" msgstr "W górę" #: lib/logitech_receiver/hidpp20.py:1197 msgid "Center In" msgstr "Do środka" #: lib/logitech_receiver/hidpp20.py:1218 #: lib/logitech_receiver/settings_templates.py:344 msgid "Disabled" msgstr "Wyłączone" #: lib/logitech_receiver/hidpp20.py:1219 msgid "Static" msgstr "Statyczne" #: lib/logitech_receiver/hidpp20.py:1220 msgid "Pulse" msgstr "Puls" #: lib/logitech_receiver/hidpp20.py:1229 lib/logitech_receiver/hidpp20.py:1255 msgid "Wave" msgstr "Fala" #: lib/logitech_receiver/hidpp20.py:1233 msgid "Boot" msgstr "Rozruch" #: lib/logitech_receiver/hidpp20.py:1234 msgid "Demo" msgstr "Demo" #: lib/logitech_receiver/hidpp20.py:1236 msgid "Breathe" msgstr "Oddech" #: lib/logitech_receiver/hidpp20.py:1241 lib/logitech_receiver/hidpp20.py:1262 msgid "Ripple" msgstr "Plusk" #: lib/logitech_receiver/hidpp20.py:1246 msgid "Decomposition" msgstr "Dekompozycja" #: lib/logitech_receiver/hidpp20.py:1247 msgid "Signature1" msgstr "Podpis1" #: lib/logitech_receiver/hidpp20.py:1248 msgid "Signature2" msgstr "Podpis2" #: lib/logitech_receiver/hidpp20.py:1268 msgid "Dim" msgstr "Przyciemnienie" #: lib/logitech_receiver/hidpp20.py:1340 msgid "Unknown Location" msgstr "Nieznana lokalizacja" #: lib/logitech_receiver/hidpp20.py:1341 #: lib/logitech_receiver/settings_templates.py:2619 #: lib/logitech_receiver/settings_templates.py:2907 #: lib/logitech_receiver/settings_templates.py:3647 msgid "Primary" msgstr "Podstawowy" #: lib/logitech_receiver/hidpp20.py:1342 msgid "Logo" msgstr "Logo" #: lib/logitech_receiver/hidpp20.py:1343 msgid "Left Side" msgstr "Lewa strona" #: lib/logitech_receiver/hidpp20.py:1344 msgid "Right Side" msgstr "Prawa strona" #: lib/logitech_receiver/hidpp20.py:1345 msgid "Combined" msgstr "Łączony" #: lib/logitech_receiver/hidpp20.py:1346 msgid "Primary 1" msgstr "Podstawowy 1" #: lib/logitech_receiver/hidpp20.py:1347 msgid "Primary 2" msgstr "Podstawowy 2" #: lib/logitech_receiver/hidpp20.py:1348 msgid "Primary 3" msgstr "Podstawowy 3" #: lib/logitech_receiver/hidpp20.py:1349 msgid "Primary 4" msgstr "Podstawowy 4" #: lib/logitech_receiver/hidpp20.py:1350 msgid "Primary 5" msgstr "Podstawowy 5" #: lib/logitech_receiver/hidpp20.py:1351 msgid "Primary 6" msgstr "Podstawowy 6" #: lib/logitech_receiver/i18n.py:28 msgid "empty" msgstr "pusty" #: lib/logitech_receiver/i18n.py:29 msgid "critical" msgstr "krytyczny" #: lib/logitech_receiver/i18n.py:30 msgid "low" msgstr "niski" #: lib/logitech_receiver/i18n.py:31 msgid "average" msgstr "przeciętny" #: lib/logitech_receiver/i18n.py:32 msgid "good" msgstr "dobry" #: lib/logitech_receiver/i18n.py:33 msgid "full" msgstr "pełny" #: lib/logitech_receiver/i18n.py:35 msgid "discharging" msgstr "rozładowywanie" #: lib/logitech_receiver/i18n.py:36 msgid "recharging" msgstr "ładowanie" #: lib/logitech_receiver/i18n.py:37 msgid "charging" msgstr "ładowanie" #: lib/logitech_receiver/i18n.py:38 msgid "not charging" msgstr "brak ładowania" #: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "prawie pełny" #: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "naładowany" #: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "powolne ładowanie" #: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "nieodpowiedni akumulator" #: lib/logitech_receiver/i18n.py:43 msgid "thermal error" msgstr "błąd termiczny" #: lib/logitech_receiver/i18n.py:44 msgid "error" msgstr "błąd" #: lib/logitech_receiver/i18n.py:45 msgid "standard" msgstr "standardowe" #: lib/logitech_receiver/i18n.py:46 msgid "fast" msgstr "szybkie" #: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "powolne" #: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "upłynął limit czasu urządzenia" #: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "urządzenie nie jest obsługiwane" #: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "za dużo urządzeń" #: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "upłynął limit czasu sekwencji" #: lib/logitech_receiver/i18n.py:54 msgid "Firmware" msgstr "Firmware" #: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Bootloader" #: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Sprzęt" #: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "Inne" #: lib/logitech_receiver/i18n.py:59 #: lib/logitech_receiver/settings_templates.py:4336 msgid "Left Button" msgstr "Przycisk lewy" #: lib/logitech_receiver/i18n.py:60 #: lib/logitech_receiver/settings_templates.py:4336 msgid "Right Button" msgstr "Przycisk prawy" #: lib/logitech_receiver/i18n.py:61 msgid "Middle Button" msgstr "Przycisk środkowy" #: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "Przycisk wstecz" #: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "Przycisk dalej" #: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "Przycisk gestów myszy" #: lib/logitech_receiver/i18n.py:65 msgid "Smart Shift" msgstr "SmartShift" #: lib/logitech_receiver/i18n.py:66 msgid "DPI Switch" msgstr "Przełącznik DPI" #: lib/logitech_receiver/i18n.py:67 msgid "Left Tilt" msgstr "Pochylenie w lewo" #: lib/logitech_receiver/i18n.py:68 msgid "Right Tilt" msgstr "Pochylenie w prawo" #: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "Kliknięcie LPM" #: lib/logitech_receiver/i18n.py:70 msgid "Right Click" msgstr "Kliknięcie PPM" #: lib/logitech_receiver/i18n.py:71 msgid "Mouse Middle Button" msgstr "Przycisk środkowy myszy" #: lib/logitech_receiver/i18n.py:72 msgid "Mouse Back Button" msgstr "Przycisk wstecz myszy" #: lib/logitech_receiver/i18n.py:73 msgid "Mouse Forward Button" msgstr "Przycisk dalej myszy" #: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "Nawigacja przyciskiem gestów" #: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "Lewy przycisk kółka myszy" #: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "Prawy przycisk kółka myszy" #: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "wciśnięty" #: lib/logitech_receiver/i18n.py:79 msgid "released" msgstr "zwolniony" #: lib/logitech_receiver/notifications.py:157 lib/solaar/listener.py:350 msgid "connected" msgstr "podłączone" #: lib/logitech_receiver/notifications.py:157 lib/solaar/listener.py:350 msgid "disconnected" msgstr "rozłączone" #: lib/logitech_receiver/notifications.py:183 msgid "unpaired" msgstr "niesparowane" #: lib/logitech_receiver/notifications.py:232 msgid "powered on" msgstr "włączone" #: lib/logitech_receiver/notifications.py:284 msgid "ADC measurement notification" msgstr "powiadomienie o pomiarze ADC" #: lib/logitech_receiver/notifications.py:486 #: lib/logitech_receiver/notifications.py:542 msgid "pairing lock is closed" msgstr "blokada sparowania jest zamknięta" #: lib/logitech_receiver/notifications.py:486 #: lib/logitech_receiver/notifications.py:542 msgid "pairing lock is open" msgstr "blokada sparowania jest otwarta" #: lib/logitech_receiver/notifications.py:505 msgid "discovery lock is closed" msgstr "blokada wykrywania jest zamknięta" #: lib/logitech_receiver/notifications.py:505 msgid "discovery lock is open" msgstr "blokada wykrywania jest otwarta" #: lib/logitech_receiver/receiver.py:462 msgid "No paired devices." msgstr "Brak sparowanych urządzeń." #: lib/logitech_receiver/receiver.py:464 lib/solaar/ui/window.py:612 #, 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:621 msgid "register" msgstr "rejestr" #: lib/logitech_receiver/settings.py:635 lib/logitech_receiver/settings.py:673 msgid "feature" msgstr "funkcja" #: lib/logitech_receiver/settings_templates.py:183 msgid "Swap Fx function" msgstr "Zamień funkcję Fx" #: lib/logitech_receiver/settings_templates.py:186 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 "" "Gdy włączona, po wciśnięciu klawiszy F1..F12 aktywowana zostanie\n" "ich funkcja pomocnicza. Aby aktywować funkcję standardową, należy\n" "przytrzymać klawisz FN." #: lib/logitech_receiver/settings_templates.py:191 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 "" "Gdy wyłączona, po wciśnięciu klawiszy F1..F12 aktywowana zostanie\n" "ich funkcja standardowa. Aby aktywować funkcję pomocniczą, należy\n" "przytrzymać klawisz FN." #: lib/logitech_receiver/settings_templates.py:199 msgid "Hand Detection" msgstr "Wykrywanie dłoni" #: lib/logitech_receiver/settings_templates.py:200 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:207 msgid "Scroll Wheel Smooth Scrolling" msgstr "Płynne przewijanie kółkiem przewijania" #: lib/logitech_receiver/settings_templates.py:208 #: lib/logitech_receiver/settings_templates.py:455 #: lib/logitech_receiver/settings_templates.py:484 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:215 msgid "Side Scrolling" msgstr "Przewijanie na boki" #: lib/logitech_receiver/settings_templates.py:217 msgid "" "When disabled, pushing the wheel sideways sends custom button events\n" "instead of the standard side-scrolling events." msgstr "" "Gdy wyłączone, przesuwanie kółka na boki powoduje wysyłanie " "niestandardowych\n" "zdarzeń przycisków zamiast standardowych zdarzeń przewijania na boki." #: lib/logitech_receiver/settings_templates.py:227 msgid "Sensitivity (DPI - older mice)" msgstr "Czułość (DPI - starsze myszy)" #: lib/logitech_receiver/settings_templates.py:228 #: lib/logitech_receiver/settings_templates.py:1064 #: lib/logitech_receiver/settings_templates.py:1092 msgid "Mouse movement sensitivity" msgstr "Czułość ruchu myszy" #: lib/logitech_receiver/settings_templates.py:301 msgid "Backlight Timed" msgstr "Podświetlenie czasowe" #: lib/logitech_receiver/settings_templates.py:302 #: lib/logitech_receiver/settings_templates.py:442 msgid "Set illumination time for keyboard." msgstr "Ustaw czas podświetlenia klawiatury." #: lib/logitech_receiver/settings_templates.py:313 msgid "Backlight" msgstr "Podświetlenie" #: lib/logitech_receiver/settings_templates.py:314 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:346 msgid "Automatic" msgstr "Automatyczne" #: lib/logitech_receiver/settings_templates.py:348 msgid "Manual" msgstr "Ręczne" #: lib/logitech_receiver/settings_templates.py:350 msgid "Enabled" msgstr "Włączone" #: lib/logitech_receiver/settings_templates.py:356 msgid "Backlight Level" msgstr "Poziom podświetlenia" #: lib/logitech_receiver/settings_templates.py:357 msgid "Illumination level on keyboard when in Manual mode." msgstr "Poziom podświetlenia klawiatury w trybie ręcznym." #: lib/logitech_receiver/settings_templates.py:414 msgid "Backlight Delay Hands Out" msgstr "Opóźnienie podświetlenia bez rąk" #: lib/logitech_receiver/settings_templates.py:415 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:423 msgid "Backlight Delay Hands In" msgstr "Opóźnienie podświetlenia z rękoma" #: lib/logitech_receiver/settings_templates.py:424 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:432 msgid "Backlight Delay Powered" msgstr "Opóźnienie podświetlenia z zasilanem" #: lib/logitech_receiver/settings_templates.py:433 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:441 msgid "Backlight (Seconds)" msgstr "Podświetlenie (sekundy)" #: lib/logitech_receiver/settings_templates.py:453 msgid "Scroll Wheel High Resolution" msgstr "Wysoka rozdzielczość kółka przewijania" #: lib/logitech_receiver/settings_templates.py:457 #: lib/logitech_receiver/settings_templates.py:486 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:464 #: lib/logitech_receiver/settings_templates.py:495 msgid "Scroll Wheel Diversion" msgstr "Przekierowanie kółka przewijania" #: lib/logitech_receiver/settings_templates.py:466 msgid "" "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " "Solaar rules but are otherwise ignored)." 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:473 msgid "Scroll Wheel Direction" msgstr "Kierunek kółka przewijania" #: lib/logitech_receiver/settings_templates.py:474 msgid "Invert direction for vertical scroll with wheel." msgstr "Odwróć kierunek przewijania w pionie za pomocą kółka." #: lib/logitech_receiver/settings_templates.py:482 msgid "Scroll Wheel Resolution" msgstr "Rozdzielczość kółka przewijania" #: lib/logitech_receiver/settings_templates.py:497 msgid "" "Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." 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:506 msgid "Sensitivity (Pointer Speed)" msgstr "Czułość (prędkość wskaźnika)" #: lib/logitech_receiver/settings_templates.py:507 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:517 msgid "Thumb Wheel Diversion" msgstr "Przekierowanie kółka obsługiwanego kciukiem" #: lib/logitech_receiver/settings_templates.py:519 msgid "" "Make thumb wheel send THUMB_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." 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:528 msgid "Thumb Wheel Direction" msgstr "Kierunek kółka obsługiwanego kciukiem" #: lib/logitech_receiver/settings_templates.py:529 msgid "Invert thumb wheel scroll direction." msgstr "Odwróć kierunek przewijania kółkiem obsługiwanym kciukiem." #: lib/logitech_receiver/settings_templates.py:549 msgid "Onboard Profiles" msgstr "Profile wbudowane" #: lib/logitech_receiver/settings_templates.py:550 msgid "" "Enable an onboard profile, which controls report rate, sensitivity, and " "button actions" msgstr "" "Włącz wbudowany profil, który kontroluje częstotliwość raportowania, czułość " "i czynności przycisków" #: lib/logitech_receiver/settings_templates.py:594 #: lib/logitech_receiver/settings_templates.py:627 msgid "Report Rate" msgstr "Szybkość raportów" #: lib/logitech_receiver/settings_templates.py:596 #: lib/logitech_receiver/settings_templates.py:629 msgid "Frequency of device movement reports" msgstr "Częstotliwość raportów o ruchu urządzenia" #: lib/logitech_receiver/settings_templates.py:596 #: lib/logitech_receiver/settings_templates.py:629 #: lib/logitech_receiver/settings_templates.py:1064 #: lib/logitech_receiver/settings_templates.py:1092 #: lib/logitech_receiver/settings_templates.py:1471 #: lib/logitech_receiver/settings_templates.py:1502 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:657 msgid "Divert crown events" msgstr "Przekieruj zdarzenia korony" #: lib/logitech_receiver/settings_templates.py:658 msgid "" "Make crown send CROWN HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." 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:666 msgid "Crown smooth scroll" msgstr "Płynne przewijanie koroną" #: lib/logitech_receiver/settings_templates.py:667 msgid "Set crown smooth scroll" msgstr "Ustaw płynne przewijanie koroną" #: lib/logitech_receiver/settings_templates.py:675 msgid "Divert G and M Keys" msgstr "Przekieruj klawisze G i M" #: lib/logitech_receiver/settings_templates.py:676 msgid "" "Make G and M keys send HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." msgstr "" "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:690 msgid "Scroll Wheel Ratcheted" msgstr "Zapadkowe kółko przewijania" #: lib/logitech_receiver/settings_templates.py:691 msgid "" "Switch the mouse wheel between speed-controlled ratcheting and always " "freespin." msgstr "" "Przełącz kółko myszy między zapadką z kontrolowaną prędkością a zawsze " "swobodnym obrotem." #: lib/logitech_receiver/settings_templates.py:693 msgid "Freespinning" msgstr "Obrót swobodny" #: lib/logitech_receiver/settings_templates.py:693 msgid "Ratcheted" msgstr "Obrót zapadkowy" #: lib/logitech_receiver/settings_templates.py:700 msgid "Scroll Wheel Ratchet Speed" msgstr "Prędkość zapadki kółka przewijania" #: lib/logitech_receiver/settings_templates.py:702 msgid "" "Use the mouse wheel speed to switch between ratcheted and freespinning.\n" "The mouse wheel is always ratcheted at 50." msgstr "" "Użyj prędkości kółka myszy, aby przełączać się między obrotem zapadkowym a " "swobodnym.\n" "Kółko myszy jest zawsze ustawione na 50." #: lib/logitech_receiver/settings_templates.py:752 msgid "Scroll Wheel Ratchet Torque" msgstr "Moment obrotowy zapadki kółka przewijania" #: lib/logitech_receiver/settings_templates.py:753 msgid "Change the torque needed to overcome the ratchet." msgstr "Zmień moment obrotowy potrzebny do przejścia zapadki." #: lib/logitech_receiver/settings_templates.py:788 msgid "Key/Button Actions" msgstr "Czynności klawiszy/przycisków" #: lib/logitech_receiver/settings_templates.py:790 msgid "Change the action for the key or button." msgstr "Zmień czynność klawisza lub przycisku." #: lib/logitech_receiver/settings_templates.py:792 msgid "Overridden by diversion." msgstr "Omijane przez przekierowanie." #: lib/logitech_receiver/settings_templates.py:794 msgid "" "Changing important actions (such as for the left mouse button) can result in " "an unusable system." 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:969 msgid "Key/Button Diversion" msgstr "Przekierowanie klawiszy/przycisków" #: lib/logitech_receiver/settings_templates.py:970 msgid "" "Make the key or button send HID++ notifications (Diverted) or initiate Mouse " "Gestures or Sliding DPI" msgstr "" "Spraw, aby klawisz lub przycisk wysyłał powiadomienia HID++ (przekierowany) " "lub inicjował gesty myszy lub przesuwnie DPI" #: lib/logitech_receiver/settings_templates.py:973 #: lib/logitech_receiver/settings_templates.py:974 #: lib/logitech_receiver/settings_templates.py:975 msgid "Diverted" msgstr "Przekierowany" #: lib/logitech_receiver/settings_templates.py:973 #: lib/logitech_receiver/settings_templates.py:974 msgid "Mouse Gestures" msgstr "Gesty myszy" #: lib/logitech_receiver/settings_templates.py:973 #: lib/logitech_receiver/settings_templates.py:974 #: lib/logitech_receiver/settings_templates.py:975 msgid "Regular" msgstr "Zwykły" #: lib/logitech_receiver/settings_templates.py:973 msgid "Sliding DPI" msgstr "Przesuwnie DPI" #: lib/logitech_receiver/settings_templates.py:1063 #: lib/logitech_receiver/settings_templates.py:1091 msgid "Sensitivity (DPI)" msgstr "Czułość (DPI)" #: lib/logitech_receiver/settings_templates.py:1173 msgid "Sensitivity Switching" msgstr "Przełączanie czułości" #: lib/logitech_receiver/settings_templates.py:1175 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 "" "Przełącz bieżącą czułość i zapamiętaną czułość po naciśnięciu klawisza lub " "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:1179 msgid "Off" msgstr "Wyłączone" #: lib/logitech_receiver/settings_templates.py:1210 msgid "Disable keys" msgstr "Wyłącz klawisze" #: lib/logitech_receiver/settings_templates.py:1211 msgid "Disable specific keyboard keys." msgstr "Wyłącz określone klawisze klawiatury." #: lib/logitech_receiver/settings_templates.py:1214 #, python-format msgid "Disables the %s key." msgstr "Wyłącza klawisz %s." #: lib/logitech_receiver/settings_templates.py:1227 #: lib/logitech_receiver/settings_templates.py:1284 msgid "Set OS" msgstr "Ustaw system operacyjny" #: lib/logitech_receiver/settings_templates.py:1228 #: lib/logitech_receiver/settings_templates.py:1285 msgid "Change keys to match OS." msgstr "Zmień klawisze, aby pasowały do systemu operacyjnego." #: lib/logitech_receiver/settings_templates.py:1297 msgid "Change Host" msgstr "Zmiana hosta" #: lib/logitech_receiver/settings_templates.py:1298 msgid "Switch connection to a different host" msgstr "Przełącz połączenie na innego hosta" #: lib/logitech_receiver/settings_templates.py:1322 msgid "Performs a left click." msgstr "Wykonuje kliknięcie lewym przyciskiem myszy." #: lib/logitech_receiver/settings_templates.py:1322 msgid "Single tap" msgstr "Pojedyncze stuknięcie" #: lib/logitech_receiver/settings_templates.py:1323 msgid "Performs a right click." msgstr "Wykonuje kliknięcie prawym przyciskiem myszy." #: lib/logitech_receiver/settings_templates.py:1323 msgid "Single tap with two fingers" msgstr "Pojedyncze stuknięcie dwoma palcami" #: lib/logitech_receiver/settings_templates.py:1324 msgid "Single tap with three fingers" msgstr "Pojedyncze stuknięcie trzema palcami" #: lib/logitech_receiver/settings_templates.py:1328 msgid "Double tap" msgstr "Podwójne stuknięcie" #: lib/logitech_receiver/settings_templates.py:1328 msgid "Performs a double click." msgstr "Wykonuje podwójne kliknięcie." #: lib/logitech_receiver/settings_templates.py:1329 msgid "Double tap with two fingers" msgstr "Podwójne stuknięcie dwoma palcami" #: lib/logitech_receiver/settings_templates.py:1330 msgid "Double tap with three fingers" msgstr "Podwójne stuknięcie trzema palcami" #: lib/logitech_receiver/settings_templates.py:1333 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:1333 msgid "Tap and drag" msgstr "Stuknięcie i przeciągnięcie" #: lib/logitech_receiver/settings_templates.py:1335 msgid "Tap and drag with two fingers" msgstr "Stuknięcie i przeciągnięcie dwoma palcami" #: lib/logitech_receiver/settings_templates.py:1336 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:1338 msgid "Tap and drag with three fingers" msgstr "Stuknięcie i przeciągnięcie trzema palcami" #: lib/logitech_receiver/settings_templates.py:1341 msgid "Suppress tap and edge gestures" msgstr "Stłumienie gestów stukania i krawędziowych" #: lib/logitech_receiver/settings_templates.py:1342 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:1344 msgid "Scroll with one finger" msgstr "Przewijanie jednym palcem" #: lib/logitech_receiver/settings_templates.py:1344 #: lib/logitech_receiver/settings_templates.py:1345 #: lib/logitech_receiver/settings_templates.py:1348 msgid "Scrolls." msgstr "Przewija." #: lib/logitech_receiver/settings_templates.py:1345 #: lib/logitech_receiver/settings_templates.py:1348 msgid "Scroll with two fingers" msgstr "Przewijanie dwoma palcami" #: lib/logitech_receiver/settings_templates.py:1346 msgid "Scroll horizontally with two fingers" msgstr "Przewijanie w poziomie dwoma palcami" #: lib/logitech_receiver/settings_templates.py:1346 msgid "Scrolls horizontally." msgstr "Przewija w poziomie." #: lib/logitech_receiver/settings_templates.py:1347 msgid "Scroll vertically with two fingers" msgstr "Przewijanie w pionie dwoma palcami" #: lib/logitech_receiver/settings_templates.py:1347 msgid "Scrolls vertically." msgstr "Przewija w pionie." #: lib/logitech_receiver/settings_templates.py:1349 msgid "Inverts the scrolling direction." msgstr "Odwraca kierunek przewijania." #: lib/logitech_receiver/settings_templates.py:1349 msgid "Natural scrolling" msgstr "Przewijanie naturalne" #: lib/logitech_receiver/settings_templates.py:1350 msgid "Enables the thumbwheel." msgstr "Włącza kółko obsługiwane kciukiem." #: lib/logitech_receiver/settings_templates.py:1350 msgid "Thumbwheel" msgstr "Kółko obsługiwane kciukiem" #: lib/logitech_receiver/settings_templates.py:1361 #: lib/logitech_receiver/settings_templates.py:1365 msgid "Swipe from the top edge" msgstr "Przesuwanie od górnej krawędzi" #: lib/logitech_receiver/settings_templates.py:1362 msgid "Swipe from the left edge" msgstr "Przesuwanie od lewej krawędzi" #: lib/logitech_receiver/settings_templates.py:1363 msgid "Swipe from the right edge" msgstr "Przesuwanie od prawej krawędzi" #: lib/logitech_receiver/settings_templates.py:1364 msgid "Swipe from the bottom edge" msgstr "Przesuwanie od dolnej krawędzi" #: lib/logitech_receiver/settings_templates.py:1366 msgid "Swipe two fingers from the left edge" msgstr "Przesuwanie dwoma palcami od lewej krawędzi" #: lib/logitech_receiver/settings_templates.py:1367 msgid "Swipe two fingers from the right edge" msgstr "Przesuwanie dwoma palcami od prawej krawędzi" #: lib/logitech_receiver/settings_templates.py:1368 msgid "Swipe two fingers from the bottom edge" msgstr "Przesuwanie dwoma palcami od dolnej krawędzi" #: lib/logitech_receiver/settings_templates.py:1369 msgid "Swipe two fingers from the top edge" msgstr "Przesuwanie dwoma palcami od górnej krawędzi" #: lib/logitech_receiver/settings_templates.py:1370 #: lib/logitech_receiver/settings_templates.py:1374 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:1370 msgid "Zoom with two fingers." msgstr "Zoom dwoma palcami." #: lib/logitech_receiver/settings_templates.py:1371 msgid "Pinch to zoom out." msgstr "Ściśnięcie, aby pomniejszyć." #: lib/logitech_receiver/settings_templates.py:1372 msgid "Spread to zoom in." msgstr "Rozłożenie, aby powiększyć." #: lib/logitech_receiver/settings_templates.py:1373 msgid "Zoom with three fingers." msgstr "Zoom trzema palcami." #: lib/logitech_receiver/settings_templates.py:1374 msgid "Zoom with two fingers" msgstr "Zoom dwoma palcami" #: lib/logitech_receiver/settings_templates.py:1392 msgid "Pixel zone" msgstr "Strefa pikseli" #: lib/logitech_receiver/settings_templates.py:1393 msgid "Ratio zone" msgstr "Strefa współczynnika" #: lib/logitech_receiver/settings_templates.py:1394 msgid "Scale factor" msgstr "Współczynnik skalowania" #: lib/logitech_receiver/settings_templates.py:1394 msgid "Sets the cursor speed." msgstr "Ustawia szybkość kursora." #: lib/logitech_receiver/settings_templates.py:1398 msgid "Left-most coordinate." msgstr "Współrzędna najbardziej z lewej." #: lib/logitech_receiver/settings_templates.py:1399 msgid "Bottom" msgstr "Dół" #: lib/logitech_receiver/settings_templates.py:1399 msgid "Bottom coordinate." msgstr "Współrzędna dolna." #: lib/logitech_receiver/settings_templates.py:1400 msgid "Width" msgstr "Szerokość" #: lib/logitech_receiver/settings_templates.py:1400 msgid "Width." msgstr "Szerokość." #: lib/logitech_receiver/settings_templates.py:1401 msgid "Height" msgstr "Wysokość" #: lib/logitech_receiver/settings_templates.py:1401 msgid "Height." msgstr "Wysokość." #: lib/logitech_receiver/settings_templates.py:1402 msgid "Cursor speed." msgstr "Prędkość kursora." #: lib/logitech_receiver/settings_templates.py:1402 msgid "Scale" msgstr "Skala" #: lib/logitech_receiver/settings_templates.py:1408 msgid "Gestures" msgstr "Gesty" #: lib/logitech_receiver/settings_templates.py:1409 msgid "Tweak the mouse/touchpad behaviour." msgstr "Dostosuj zachowania myszy/panelu dotykowego." #: lib/logitech_receiver/settings_templates.py:1425 msgid "Gestures Diversion" msgstr "Przekierowanie gestów" #: lib/logitech_receiver/settings_templates.py:1426 msgid "Divert mouse/touchpad gestures." msgstr "Przekieruj gesty myszy/panelu dotykowego." #: lib/logitech_receiver/settings_templates.py:1442 msgid "Gesture params" msgstr "Parametry gestu" #: lib/logitech_receiver/settings_templates.py:1443 msgid "Change numerical parameters of a mouse/touchpad." msgstr "Zmień parametry numeryczne myszy/panelu dotykowego." #: lib/logitech_receiver/settings_templates.py:1467 msgid "M-Key LEDs" msgstr "Diody LED klawiszy M" #: lib/logitech_receiver/settings_templates.py:1469 msgid "Control the M-Key LEDs." msgstr "Kontroluj diody LED klawiszy M." #: lib/logitech_receiver/settings_templates.py:1473 #: lib/logitech_receiver/settings_templates.py:1504 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:1479 #, python-format msgid "Lights up the %s key." msgstr "Zapala klawisz %s." #: lib/logitech_receiver/settings_templates.py:1498 msgid "MR-Key LED" msgstr "Dioda LED klawisza MR" #: lib/logitech_receiver/settings_templates.py:1500 msgid "Control the MR-Key LED." msgstr "Kontroluj diody LED klawiszy MR." #: lib/logitech_receiver/settings_templates.py:1521 msgid "Persistent Key/Button Mapping" msgstr "Trwałe mapowanie klawiszy/przycisków" #: lib/logitech_receiver/settings_templates.py:1523 msgid "Permanently change the mapping for the key or button." msgstr "Trwale zmień mapowania klawisza lub przycisku." #: lib/logitech_receiver/settings_templates.py:1525 msgid "" "Changing important keys or buttons (such as for the left mouse button) can " "result in an unusable system." 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:1582 msgid "Sidetone" msgstr "Efekt lokalny" #: lib/logitech_receiver/settings_templates.py:1583 msgid "Set sidetone level." msgstr "Ustaw poziom efektu lokalnego." #: lib/logitech_receiver/settings_templates.py:1592 msgid "Equalizer" msgstr "Korektor" #: lib/logitech_receiver/settings_templates.py:1593 #: lib/logitech_receiver/settings_templates.py:1902 msgid "Set equalizer levels." msgstr "Ustaw poziomy korektora." #: lib/logitech_receiver/settings_templates.py:1615 #: lib/logitech_receiver/settings_templates.py:1930 #: lib/logitech_receiver/settings_templates.py:2056 #: lib/logitech_receiver/settings_templates.py:2058 msgid "Hz" msgstr "Hz" #: lib/logitech_receiver/settings_templates.py:1621 msgid "Power Management" msgstr "Zarządzanie energią" #: lib/logitech_receiver/settings_templates.py:1622 msgid "Power off in minutes (0 for never)." msgstr "Wyłączenie zasilania w minutach (0 oznacza nigdy)." #: lib/logitech_receiver/settings_templates.py:1634 msgid "Eco Mode" msgstr "Tryb eko" #: lib/logitech_receiver/settings_templates.py:1635 msgid "Battery saver mode." msgstr "Tryb oszczędzania akumulatora." #: lib/logitech_receiver/settings_templates.py:1653 msgid "Do Not Disturb" msgstr "Nie przeszkadzać" #: lib/logitech_receiver/settings_templates.py:1654 msgid "Suppress notification sounds." msgstr "Wyłącz dźwięki powiadomień." #: lib/logitech_receiver/settings_templates.py:1661 msgid "Mic Mute" msgstr "Wyciszenie mikrofonu" #: lib/logitech_receiver/settings_templates.py:1662 msgid "Mute the microphone." msgstr "Wycisz mikrofon." #: lib/logitech_receiver/settings_templates.py:1681 msgid "Mic SNR" msgstr "SNR mikrofonu" #: lib/logitech_receiver/settings_templates.py:1682 msgid "Microphone signal-to-noise ratio enhancement." msgstr "Poprawa stosunku sygnału do szumu mikrofonu." #: lib/logitech_receiver/settings_templates.py:1689 msgid "AI Noise Reduction" msgstr "Redukcja szumów AI" #: lib/logitech_receiver/settings_templates.py:1690 msgid "Enable AI noise reduction." msgstr "Włącz redukcję szumów AI." #: lib/logitech_receiver/settings_templates.py:1697 msgid "AI Noise Reduction Level" msgstr "Poziom redukcji szumów AI" #: lib/logitech_receiver/settings_templates.py:1698 msgid "AI noise reduction intensity." msgstr "Intensywność redukcji szumów AI." #: lib/logitech_receiver/settings_templates.py:1707 msgid "Headset Sidetone" msgstr "Odsłuch zestawu słuchawkowego" #: lib/logitech_receiver/settings_templates.py:1708 msgid "Sidetone level (0 = off, 100 = max)." msgstr "Poziom odsłuchu (0 = wyłączony, 100 = maks.)." #: lib/logitech_receiver/settings_templates.py:1771 msgid "Mic Gain" msgstr "Wzmocnienie mikrofonu" #: lib/logitech_receiver/settings_templates.py:1772 msgid "Microphone gain level." msgstr "Poziom wzmocnienia mikrofonu." #: lib/logitech_receiver/settings_templates.py:1824 msgid "Audio Mix Balance" msgstr "Balans miksu dźwięku" #: lib/logitech_receiver/settings_templates.py:1825 msgid "Balance between game and chat audio." msgstr "Balans między dźwiękiem gry i czatu." #: lib/logitech_receiver/settings_templates.py:1876 msgid "Auto Sleep Timeout" msgstr "Limit czasu automatycznego uśpienia" #: lib/logitech_receiver/settings_templates.py:1877 msgid "" "Idle time in minutes before the headset enters sleep mode (0 = disabled)." msgstr "" "Czas bezczynności w minutach, po którym zestaw słuchawkowy przejdzie w tryb " "uśpienia (0 = wyłączony)." #: lib/logitech_receiver/settings_templates.py:1901 msgid "Headset Equalizer" msgstr "Korektor dźwięku zestawu słuchawkowego" #: lib/logitech_receiver/settings_templates.py:1996 msgid "Headset Advanced EQ" msgstr "Zaawansowany korektor dźwięku zestawu słuchawkowego" #: lib/logitech_receiver/settings_templates.py:1997 msgid "Per-band gain for the headset's active parametric EQ." msgstr "" "Wzmocnienie na pasmo aktywnego korektora parametrycznego zestawu " "słuchawkowego." #: lib/logitech_receiver/settings_templates.py:2268 msgid "EQ Preset" msgstr "Preset korektora" #: lib/logitech_receiver/settings_templates.py:2269 msgid "Switch the active EQ preset. Factory presets are read-only." msgstr "" "Zmień aktywny preset korektora. Ustawienia fabryczne są tylko do odczytu." #: lib/logitech_receiver/settings_templates.py:2294 msgid "Slot" msgstr "Gniazdo" #: lib/logitech_receiver/settings_templates.py:2296 msgid "(factory)" msgstr "(fabryczne)" #: lib/logitech_receiver/settings_templates.py:2429 #: lib/logitech_receiver/settings_templates.py:3206 #: lib/logitech_receiver/settings_templates.py:3298 msgid "LED Control" msgstr "Sterowanie diodami LED" #: lib/logitech_receiver/settings_templates.py:2430 msgid "Allow Solaar to control the headset LED zones." msgstr "Pozwól programowi Solaar sterować strefami LED zestawu słuchawkowego." #: lib/logitech_receiver/settings_templates.py:2478 msgid "Per-zone Lighting" msgstr "Oświetlenie przedstrefowe" #: lib/logitech_receiver/settings_templates.py:2480 msgid "" "Override individual zone colors. 'No change' inherits the LEDs Primary " "color.\n" "LED Control needs to be set to Solaar to be effective." msgstr "" "Zastąp kolory poszczególnych stref. Opcja „Bez zmian” dziedziczy kolor " "podstawowy diod LED.\n" "Należy ustawić je na Solaar, aby sterowanie diodami LED działało." #: lib/logitech_receiver/settings_templates.py:2498 msgid "Zone" msgstr "Strefa" #: lib/logitech_receiver/settings_templates.py:2620 #: lib/logitech_receiver/settings_templates.py:2908 #: lib/logitech_receiver/settings_templates.py:3648 msgid "Secondary" msgstr "Drugorzędny" #: lib/logitech_receiver/settings_templates.py:2621 #: lib/logitech_receiver/settings_templates.py:3234 msgid "Speed" msgstr "Szybkość" #: lib/logitech_receiver/settings_templates.py:2688 msgid "Startup Effect" msgstr "Efekt startowy" #: lib/logitech_receiver/settings_templates.py:2690 msgid "Firmware lighting effect played when the headset powers on or wakes." msgstr "" "Efekt świetlny oprogramowania sprzętowego jest odtwarzany po włączeniu lub " "wybudzeniu zestawu słuchawkowego." #: lib/logitech_receiver/settings_templates.py:2692 #: lib/logitech_receiver/settings_templates.py:2703 msgid "Device default: Primary #00B8FC, Secondary #FF00AB, Speed 100." msgstr "" "Domyślne urządzenie: podstawowe #00B8FC, drugorzędne #FF00AB, prędkość 100." #: lib/logitech_receiver/settings_templates.py:2699 msgid "Shutdown Effect" msgstr "Efekt wyłączenia" #: lib/logitech_receiver/settings_templates.py:2701 msgid "Firmware lighting effect played when the headset powers off or sleeps." msgstr "" "Efekt świetlny oprogramowania sprzętowego jest odtwarzany, gdy zestaw " "słuchawkowy wyłącza się lub przechodzi w tryb uśpienia." #: lib/logitech_receiver/settings_templates.py:2710 msgid "Passive Effect" msgstr "Efekt pasywny" #: lib/logitech_receiver/settings_templates.py:2712 msgid "Firmware lighting effect played while the headset is idle." msgstr "" "Efekt świetlny oprogramowania sprzętowego jest odtwarzany, gdy zestaw " "słuchawkowy jest bezczynny." #: lib/logitech_receiver/settings_templates.py:2714 msgid "Device default: Primary #00B8FC, Secondary #FF00AB, Speed 75." msgstr "" "Domyślne urządzenie: podstawowe #00B8FC, drugorzędne #FF00AB, prędkość 75." #: lib/logitech_receiver/settings_templates.py:2833 msgid "Onboard Effect" msgstr "Efekt pokładowy" #: lib/logitech_receiver/settings_templates.py:2834 msgid "Firmware RGB effect the headset plays on its own." msgstr "" "Efekt RGB oprogramowania sprzętowego zestawu słuchawkowego odtwarzany jest " "automatycznie." #: lib/logitech_receiver/settings_templates.py:2909 #: lib/logitech_receiver/settings_templates.py:3243 msgid "Intensity" msgstr "Intensywność" #: lib/logitech_receiver/settings_templates.py:2910 #: lib/logitech_receiver/settings_templates.py:3245 msgid "Saturation" msgstr "Nasycenie" #: lib/logitech_receiver/settings_templates.py:2914 #: lib/logitech_receiver/settings_templates.py:3238 msgid "Period" msgstr "Okres" #: lib/logitech_receiver/settings_templates.py:2919 #: lib/logitech_receiver/settings_templates.py:3250 msgid "Direction" msgstr "Kierunek" #: lib/logitech_receiver/settings_templates.py:3134 msgid "Brightness Control" msgstr "Sterowanie jasnością" #: lib/logitech_receiver/settings_templates.py:3135 msgid "Control overall brightness" msgstr "Steruj ogólną jasnością" #: lib/logitech_receiver/settings_templates.py:3207 #: lib/logitech_receiver/settings_templates.py:3299 msgid "Allow Solaar to control LED zones." msgstr "Pozwól programowi Solaar kontrolować strefy LED." #: lib/logitech_receiver/settings_templates.py:3230 #: lib/logitech_receiver/settings_templates.py:3744 msgid "LED Zone Effects" msgstr "Efekty strefowe diod LED" #: lib/logitech_receiver/settings_templates.py:3231 #: lib/logitech_receiver/settings_templates.py:3399 #: lib/logitech_receiver/settings_templates.py:3439 #: lib/logitech_receiver/settings_templates.py:3546 #: lib/logitech_receiver/settings_templates.py:3745 msgid "LED Control needs to be enabled." msgstr "Należy włączyć sterowanie diodami LED." #: lib/logitech_receiver/settings_templates.py:3231 #: lib/logitech_receiver/settings_templates.py:3745 msgid "Set effect for LED Zone" msgstr "Ustaw efekt dla strefy diod LED" #: lib/logitech_receiver/settings_templates.py:3233 msgid "Color" msgstr "Kolor" #: lib/logitech_receiver/settings_templates.py:3244 msgid "Ramp" msgstr "Narastanie" #: lib/logitech_receiver/settings_templates.py:3246 msgid "Waveform" msgstr "Kształt fali" #: lib/logitech_receiver/settings_templates.py:3279 msgid "LEDs" msgstr "Diody LED" #: lib/logitech_receiver/settings_templates.py:3398 msgid "Idle Timeout" msgstr "Limit czasu bezczynności" #: lib/logitech_receiver/settings_templates.py:3399 msgid "Time without input before LED idle effect starts." msgstr "Czas bezczynności przed rozpoczęciem efektu bezczynności diody LED." #: lib/logitech_receiver/settings_templates.py:3435 msgid "Idle Effect" msgstr "Efekt bezczynności" #: lib/logitech_receiver/settings_templates.py:3437 msgid "" "What happens to LEDs when idle — dim to a percentage, change the base color, " "or play an animation." msgstr "" "Co dzieje się z diodami LED, gdy są bezczynne — przyciemniają się do " "określonego poziomu, zmieniają kolor bazowy lub odtwarzają animację." #: lib/logitech_receiver/settings_templates.py:3520 msgid "No change" msgstr "Bez zmian" #: lib/logitech_receiver/settings_templates.py:3545 msgid "Sleep Timeout" msgstr "Limit czasu uśpienia" #: lib/logitech_receiver/settings_templates.py:3546 msgid "Time without input before LEDs fade off completely." msgstr "Czas bezczynności, po którym diody LED całkowicie gasną." #: lib/logitech_receiver/settings_templates.py:3719 msgid "Startup Animation" msgstr "Animacja startowa" #: lib/logitech_receiver/settings_templates.py:3722 msgid "" "Firmware-played animation when the keyboard wakes from deep sleep or powers " "on.\n" "Setting persists on the device (non-volatile)." msgstr "" "Animacja odtwarzana przez oprogramowanie sprzętowe podczas wybudzania " "klawiatury z trybu głębokiego uśpienia lub włączania.\n" "Ustawienie jest zachowywane na urządzeniu (trwale)." #: lib/logitech_receiver/settings_templates.py:3726 #: lib/logitech_receiver/settings_templates.py:3737 msgid "Device default: Primary #FF0081, Secondary #80AAFF." msgstr "Domyślne urządzenie: podstawowe #FF0081, drugorzędne #80AAFF." #: lib/logitech_receiver/settings_templates.py:3733 msgid "Shutdown Animation" msgstr "Animacja wyłączania" #: lib/logitech_receiver/settings_templates.py:3735 msgid "" "Firmware-played animation when the keyboard powers off.\n" "Setting persists on the device (non-volatile)." msgstr "" "Animacja odtwarzana przez oprogramowanie sprzętowe podczas wyłączania " "klawiatury.\n" "Ustawienie jest zachowywane na urządzeniu (trwale)." #: lib/logitech_receiver/settings_templates.py:3847 #: lib/solaar/ui/perkey/dialog.py:87 msgid "Per-key Lighting" msgstr "Podświetlenie poszczególnych klawiszy" #: lib/logitech_receiver/settings_templates.py:3849 msgid "Control per-key lighting." msgstr "Steruj podświetleniem poszczególnych klawiszy." #: lib/logitech_receiver/settings_templates.py:3851 msgid "" "LED Control needs to be enabled and the zone effect set to Static for per-" "key paint to be visible." msgstr "" "Należy włączyć sterowanie LED i ustawić opcję strefy na „Statyczne”, aby " "wyświetlanie kolorów poszczególnych klawiszy było widoczne." #: lib/logitech_receiver/settings_templates.py:4182 msgid "Force Sensing Buttons" msgstr "Przyciski wykrywające siłę" #: lib/logitech_receiver/settings_templates.py:4183 msgid "Change the force required to activate button." msgstr "Zmień siłę potrzebną do aktywowania przycisku." #: lib/logitech_receiver/settings_templates.py:4200 msgid "Force Sensing Button" msgstr "Przycisk wykrywający siłę" #: lib/logitech_receiver/settings_templates.py:4316 msgid "Analog Button Tuning" msgstr "Regulacja przycisków analogowych" #: lib/logitech_receiver/settings_templates.py:4317 msgid "" "Configure analog button settings including actuation point, rapid trigger, " "and haptics." msgstr "" "Skonfiguruj ustawienia przycisków analogowych, w tym punkt aktywacji, szybki " "spust i haptykę." #: lib/logitech_receiver/settings_templates.py:4347 #, python-format msgid "Actuation point depth (1=shallow, %d=deep)." msgstr "Głębokość punktu aktywacji (1=płytko, %d=głęboko)." #: lib/logitech_receiver/settings_templates.py:4355 #, python-format msgid "Rapid trigger sensitivity (1..%d)." msgstr "Czułość szybkiego spustu (1..%d)." #: lib/logitech_receiver/settings_templates.py:4363 #, python-format msgid "Click haptic feedback intensity (0=off, %d=max)." msgstr "Intensywność reakcji dotykowej kliknięcia (0=wył., %d=maks.)." #: lib/logitech_receiver/settings_templates.py:4371 msgid "Haptic Feedback Level" msgstr "Poziom haptycznego sprzężenia zwrotnego" #: lib/logitech_receiver/settings_templates.py:4372 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:4414 msgid "Play Haptic Waveform" msgstr "Odtwórz falę haptyczną" #: lib/logitech_receiver/settings_templates.py:4415 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:64 msgid "Additional Programming" msgstr "Dodatkowe programowanie" #: lib/solaar/ui/about/model.py:65 msgid "GUI design" msgstr "Projekt GUI" #: lib/solaar/ui/about/model.py:67 msgid "Testing" msgstr "Testowanie" #: lib/solaar/ui/about/model.py:75 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:142 msgid "Cancel" msgstr "Anuluj" #: lib/solaar/ui/common.py:42 msgid "Permissions error" msgstr "Błąd uprawnień" #: 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 "" "Znaleziono odbiornik lub urządzenie firmy Logitech (%s), ale nie ma " "uprawnień do jego otwarcia." #: lib/solaar/ui/common.py:46 msgid "" "If you've just installed Solaar, try disconnecting the receiver or device " "and then reconnecting it." 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/common.py:49 msgid "Cannot connect to device error" msgstr "Błąd łączenia się z urządzeniem" #: 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 "" "Znaleziono odbiornik lub urządzenie firmy Logitech w %s, ale wystąpił błąd " "podczas łączenia się z nim." #: lib/solaar/ui/common.py:53 msgid "" "Try disconnecting the device and then reconnecting it or turning it off and " "then on." msgstr "" "Spróbuj odłączyć urządzenie, a następnie podłączyć je ponownie lub wyłączyć " "i ponownie włączyć." #: lib/solaar/ui/common.py:56 msgid "Unpairing failed" msgstr "Usunięcie sparowania nie powiodło się" #: 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/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/config_panel.py:254 msgid "Complete - ENTER to change" msgstr "Kompletne - ENTER, aby zmienić" #: lib/solaar/ui/config_panel.py:254 msgid "Incomplete" msgstr "Niekompletne" #: lib/solaar/ui/config_panel.py:500 lib/solaar/ui/config_panel.py:552 #: lib/solaar/ui/config_panel.py:619 #, python-format msgid "%d value" msgid_plural "%d values" msgstr[0] "%d wartość" msgstr[1] "%d wartości" msgstr[2] "%d wartości" #: lib/solaar/ui/config_panel.py:805 msgid "Changes allowed" msgstr "Zmiany dozwolone" #: lib/solaar/ui/config_panel.py:806 msgid "No changes allowed" msgstr "Zmiany niedozwolone" #: lib/solaar/ui/config_panel.py:807 msgid "Ignore this setting" msgstr "Ignoruj to ustawienie" #: lib/solaar/ui/config_panel.py:1022 msgid "Working" msgstr "Pracuję" #: lib/solaar/ui/config_panel.py:1025 msgid "Read/write operation failed." msgstr "Operacja odczytu/zapisu nie powiodła się." #: lib/solaar/ui/desktop_notifications.py:112 msgid "unspecified reason" msgstr "nieokreślony powód" #: lib/solaar/ui/diversion_rules.py:104 msgid "Built-in rules" msgstr "Wbudowane reguły" #: lib/solaar/ui/diversion_rules.py:104 msgid "User-defined rules" msgstr "Reguły zdefiniowane przez użytkownika" #: lib/solaar/ui/diversion_rules.py:106 lib/solaar/ui/diversion_rules.py:1127 msgid "Rule" msgstr "Reguła" #: lib/solaar/ui/diversion_rules.py:107 lib/solaar/ui/diversion_rules.py:383 #: lib/solaar/ui/diversion_rules.py:510 msgid "Sub-rule" msgstr "Reguła podrzędna" #: lib/solaar/ui/diversion_rules.py:109 msgid "[empty]" msgstr "[pusta]" #: lib/solaar/ui/diversion_rules.py:133 msgid "Make changes permanent?" msgstr "Wprowadzić zmiany na stałe?" #: lib/solaar/ui/diversion_rules.py:138 msgid "Yes" msgstr "Tak" #: lib/solaar/ui/diversion_rules.py:140 msgid "No" msgstr "Nie" #: lib/solaar/ui/diversion_rules.py:145 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:274 msgid "Paste here" msgstr "Wklej tutaj" #: lib/solaar/ui/diversion_rules.py:276 msgid "Paste above" msgstr "Wklej powyżej" #: lib/solaar/ui/diversion_rules.py:278 msgid "Paste below" msgstr "Wklej poniżej" #: lib/solaar/ui/diversion_rules.py:284 msgid "Paste rule here" msgstr "Wklej regułę tutaj" #: lib/solaar/ui/diversion_rules.py:286 msgid "Paste rule above" msgstr "Wklej regułę powyżej" #: lib/solaar/ui/diversion_rules.py:288 msgid "Paste rule below" msgstr "Wklej regułę poniżej" #: lib/solaar/ui/diversion_rules.py:292 msgid "Paste rule" msgstr "Wklej regułę" #: lib/solaar/ui/diversion_rules.py:307 msgid "Insert here" msgstr "Wstaw tutaj" #: lib/solaar/ui/diversion_rules.py:309 msgid "Insert above" msgstr "Wstaw powyżej" #: lib/solaar/ui/diversion_rules.py:311 msgid "Insert below" msgstr "Wstaw poniżej" #: lib/solaar/ui/diversion_rules.py:317 msgid "Insert new rule here" msgstr "Wstaw nową regułę tutaj" #: lib/solaar/ui/diversion_rules.py:319 msgid "Insert new rule above" msgstr "Wstaw nową regułę powyżej" #: lib/solaar/ui/diversion_rules.py:321 msgid "Insert new rule below" msgstr "Wstaw nową regułę poniżej" #: lib/solaar/ui/diversion_rules.py:348 msgid "Flatten" msgstr "Spłaszcz" #: lib/solaar/ui/diversion_rules.py:381 msgid "Insert" msgstr "Wstaw" #: lib/solaar/ui/diversion_rules.py:384 lib/solaar/ui/diversion_rules.py:512 #: lib/solaar/ui/diversion_rules.py:1159 msgid "Or" msgstr "Lub" #: lib/solaar/ui/diversion_rules.py:385 lib/solaar/ui/diversion_rules.py:511 #: lib/solaar/ui/diversion_rules.py:1145 msgid "And" msgstr "Oraz" #: lib/solaar/ui/diversion_rules.py:387 msgid "Condition" msgstr "Warunek" #: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:154 msgid "Feature" msgstr "Funkcja" #: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:188 msgid "Report" msgstr "Zgłoszenie" #: lib/solaar/ui/diversion_rules.py:391 lib/solaar/ui/rule_conditions.py:69 msgid "Process" msgstr "Proces" #: lib/solaar/ui/diversion_rules.py:392 msgid "Mouse process" msgstr "Proces myszy" #: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:225 msgid "Modifiers" msgstr "Modyfikatory" #: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:277 msgid "Key" msgstr "Klawisz" #: lib/solaar/ui/diversion_rules.py:395 lib/solaar/ui/rule_conditions.py:318 msgid "KeyIsDown" msgstr "KlawiszJestWciśnięty" #: lib/solaar/ui/diversion_rules.py:396 lib/solaar/ui/diversion_rules.py:1433 msgid "Active" msgstr "Aktywny" #: lib/solaar/ui/diversion_rules.py:397 lib/solaar/ui/diversion_rules.py:1391 #: lib/solaar/ui/diversion_rules.py:1442 lib/solaar/ui/diversion_rules.py:1488 msgid "Device" msgstr "Urządzenie" #: lib/solaar/ui/diversion_rules.py:398 lib/solaar/ui/diversion_rules.py:1468 msgid "Host" msgstr "Host" #: lib/solaar/ui/diversion_rules.py:399 lib/solaar/ui/diversion_rules.py:1507 msgid "Setting" msgstr "Ustawienie" #: lib/solaar/ui/diversion_rules.py:400 lib/solaar/ui/rule_conditions.py:333 #: lib/solaar/ui/rule_conditions.py:382 msgid "Test" msgstr "Test" #: lib/solaar/ui/diversion_rules.py:401 lib/solaar/ui/rule_conditions.py:507 msgid "Test bytes" msgstr "Bajty testowe" #: lib/solaar/ui/diversion_rules.py:402 lib/solaar/ui/rule_conditions.py:608 msgid "Mouse Gesture" msgstr "Gest myszy" #: lib/solaar/ui/diversion_rules.py:406 msgid "Action" msgstr "Czynność" #: lib/solaar/ui/diversion_rules.py:408 lib/solaar/ui/rule_actions.py:138 msgid "Key press" msgstr "Naciśnięcie klawisza" #: lib/solaar/ui/diversion_rules.py:409 lib/solaar/ui/rule_actions.py:191 msgid "Mouse scroll" msgstr "Przewijanie myszą" #: lib/solaar/ui/diversion_rules.py:410 lib/solaar/ui/rule_actions.py:256 msgid "Mouse click" msgstr "Kliknięcie myszą" #: lib/solaar/ui/diversion_rules.py:411 msgid "Set" msgstr "Ustaw" #: lib/solaar/ui/diversion_rules.py:412 lib/solaar/ui/rule_actions.py:328 msgid "Execute" msgstr "Wykonaj" #: lib/solaar/ui/diversion_rules.py:413 lib/solaar/ui/diversion_rules.py:1190 msgid "Later" msgstr "Później" #: lib/solaar/ui/diversion_rules.py:442 msgid "Insert new rule" msgstr "Wstaw nową regułę" #: lib/solaar/ui/diversion_rules.py:462 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:484 msgid "Negate" msgstr "Neguj" #: lib/solaar/ui/diversion_rules.py:508 msgid "Wrap with" msgstr "Obwiąż" #: lib/solaar/ui/diversion_rules.py:538 msgid "Cut" msgstr "Wytnij" #: lib/solaar/ui/diversion_rules.py:554 msgid "Paste" msgstr "Wklej" #: lib/solaar/ui/diversion_rules.py:560 msgid "Copy" msgstr "Skopiuj" #: lib/solaar/ui/diversion_rules.py:569 msgid "Solaar Rule Editor" msgstr "Edytor reguł Solaar" #: lib/solaar/ui/diversion_rules.py:669 msgid "Save changes" msgstr "Zapisz zmiany" #: lib/solaar/ui/diversion_rules.py:674 msgid "Discard changes" msgstr "Odrzuć zmiany" #: lib/solaar/ui/diversion_rules.py:1105 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:1170 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:1208 msgid "Not" msgstr "Nie" #: lib/solaar/ui/diversion_rules.py:1239 msgid "Toggle" msgstr "Przełącz" #: lib/solaar/ui/diversion_rules.py:1240 msgid "True" msgstr "Prawda" #: lib/solaar/ui/diversion_rules.py:1241 msgid "False" msgstr "Fałsz" #: lib/solaar/ui/diversion_rules.py:1254 msgid "Unsupported setting" msgstr "Nieobsługiwane ustawienie" #: lib/solaar/ui/diversion_rules.py:1397 lib/solaar/ui/diversion_rules.py:1416 #: lib/solaar/ui/diversion_rules.py:1494 lib/solaar/ui/diversion_rules.py:1758 #: lib/solaar/ui/diversion_rules.py:1776 msgid "Originating device" msgstr "Urządzenie inicjujące" #: lib/solaar/ui/diversion_rules.py:1429 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:1438 msgid "Device that originated the current notification." msgstr "Urządzenie, z którego pochodzi bieżące powiadomienie." #: lib/solaar/ui/diversion_rules.py:1451 msgid "Name of host computer." msgstr "Nazwa komputera hosta." #: lib/solaar/ui/diversion_rules.py:1521 msgid "Value" msgstr "Wartość" #: lib/solaar/ui/diversion_rules.py:1530 msgid "Item" msgstr "Pozycja" #: lib/solaar/ui/diversion_rules.py:1818 msgid "Change setting on device" msgstr "Zmień ustawienie w urządzeniu" #: lib/solaar/ui/diversion_rules.py:1834 msgid "Setting on device" msgstr "Ustawienie w urządzeniu" #: 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: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: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: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: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: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: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" "\n" "This receiver has %d pairing remaining." msgid_plural "" "\n" "\n" "This receiver has %d pairings remaining." msgstr[0] "" "\n" "\n" "Ten odbiornik ma jeszcze %d sparowanie." msgstr[1] "" "\n" "\n" "Ten odbiornik ma jeszcze %d sparowania." msgstr[2] "" "\n" "\n" "Ten odbiornik ma jeszcze %d sparowań." #: lib/solaar/ui/pair_window.py:78 msgid "" "\n" "Cancelling at this point will not use up a pairing." msgstr "" "\n" "Anulowanie w tym momencie nie spowoduje zużycia sparowania." #: 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/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/perkey/control.py:178 msgid "(not loaded)" msgstr "(nie wczytano)" #: lib/solaar/ui/perkey/control.py:182 msgid "Open editor…" msgstr "Otwórz edytor…" #: lib/solaar/ui/perkey/control.py:183 msgid "Paint key colors on a keyboard layout" msgstr "Malowanie kolorów klawiszy na układzie klawiatury" #: lib/solaar/ui/perkey/control.py:229 msgid "(no zones)" msgstr "(brak stref)" #: lib/solaar/ui/perkey/control.py:233 #, python-brace-format msgid "{painted} / {total} keys painted" msgstr "Pomalowane klawisze: {painted} / {total}" #: lib/solaar/ui/perkey/editor.py:54 msgid "Brush" msgstr "Pędzel" #: lib/solaar/ui/perkey/editor.py:54 msgid "Click or drag to paint individual keys" msgstr "Kliknij lub przeciągnij, aby pomalować poszczególne klawisze" #: lib/solaar/ui/perkey/editor.py:55 msgid "Drag to select a rectangle of keys, painted on release" msgstr "Przeciągnij, aby wybrać prostokąt klawiszy, namalowany po zwolnieniu" #: lib/solaar/ui/perkey/editor.py:55 msgid "Rect" msgstr "Prostokąt" #: lib/solaar/ui/perkey/editor.py:56 msgid "Fill" msgstr "Wypełnianie" #: lib/solaar/ui/perkey/editor.py:56 msgid "Flood-fill connected keys of the same color with the active color" msgstr "Wypełnij połączone klawisze tego samego koloru aktywnym kolorem" #: lib/solaar/ui/perkey/editor.py:59 msgid "Drag to fade from previous color to active color" msgstr "Przeciągnij, aby przejść z poprzedniego koloru do aktywnego koloru" #: lib/solaar/ui/perkey/palette.py:90 msgid "Active color" msgstr "Kolor aktywny" #: lib/solaar/ui/perkey/palette.py:95 msgid "Paint as 'no change' — clears the cell to the zone base color" msgstr "Maluj jako „bez zmian” — czyści komórkę do koloru bazowego strefy" #: lib/solaar/ui/perkey/palette.py:96 msgid "Unset" msgstr "Wyłącz" #: 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:61 lib/solaar/ui/window.py:326 #, python-format msgid "Quit %s" msgstr "Zakończ %s" #: lib/solaar/ui/tray.py:273 lib/solaar/ui/tray.py:281 msgid "no receiver" msgstr "brak odbiornika" #: 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:110 msgid "Scanning" msgstr "Skanowanie" #: lib/solaar/ui/window.py:141 msgid "Battery" msgstr "Akumulator" #: lib/solaar/ui/window.py:144 msgid "Wireless Link" msgstr "Połączenie bezprzewodowe" #: lib/solaar/ui/window.py:148 msgid "Lighting" msgstr "Podświetlenie" #: lib/solaar/ui/window.py:182 msgid "Show Technical Details" msgstr "Wyświetl szczegóły techniczne" #: lib/solaar/ui/window.py:198 msgid "Pair new device" msgstr "Sparuj nowe urządzenie" #: lib/solaar/ui/window.py:216 msgid "Select a device" msgstr "Wybierz urządzenie" #: lib/solaar/ui/window.py:331 msgid "Rule Editor" msgstr "Edytor reguł" #: lib/solaar/ui/window.py:522 msgid "Path" msgstr "Ścieżka" #: lib/solaar/ui/window.py:524 msgid "USB ID" msgstr "ID USB" #: lib/solaar/ui/window.py:527 lib/solaar/ui/window.py:529 #: lib/solaar/ui/window.py:550 lib/solaar/ui/window.py:552 msgid "Serial" msgstr "Nr seryjny" #: lib/solaar/ui/window.py:533 msgid "Index" msgstr "Indeks" #: lib/solaar/ui/window.py:535 msgid "Wireless PID" msgstr "Prawo" #: lib/solaar/ui/window.py:537 msgid "Product ID" msgstr "ID produktu" #: lib/solaar/ui/window.py:541 lib/solaar/ui/window.py:543 #: lib/solaar/ui/window.py:545 msgid "Protocol" msgstr "Protokół" #: lib/solaar/ui/window.py:545 msgid "Unknown" msgstr "Nieznany" #: lib/solaar/ui/window.py:547 msgid "Polling rate" msgstr "Odpytywanie" #: lib/solaar/ui/window.py:554 msgid "Unit ID" msgstr "ID jednostki" #: lib/solaar/ui/window.py:566 msgid "Notifications" msgstr "Powiadomienia" #: lib/solaar/ui/window.py:610 msgid "No device paired." msgstr "Brak sparowanych urządzeń." #: lib/solaar/ui/window.py:619 #, 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] "" "Można sparować maksymalnie %(max_count)s urządzenie z tym odbiornikiem." msgstr[1] "" "Można sparować maksymalnie %(max_count)s urządzenia z tym odbiornikiem." msgstr[2] "" "Można sparować maksymalnie %(max_count)s urządzeń z tym odbiornikiem." #: lib/solaar/ui/window.py:630 #, python-format msgid "This receiver has %d pairing remaining." msgid_plural "This receiver has %d pairings remaining." 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:687 msgid "Battery Voltage" msgstr "Napięcie akumulatora" #: lib/solaar/ui/window.py:689 msgid "Voltage reported by battery" msgstr "Napięcie zgłoszone przez akumulator" #: lib/solaar/ui/window.py:691 msgid "Battery Level" msgstr "Poziom akumulatora" #: lib/solaar/ui/window.py:693 msgid "Approximate level reported by battery" msgstr "Przybliżony poziom zgłoszony przez akumulator" #: lib/solaar/ui/window.py:700 lib/solaar/ui/window.py:702 msgid "next reported " msgstr "następny zgłoszony " #: lib/solaar/ui/window.py:703 msgid " and next level to be reported." msgstr " oraz następny poziom do zgłoszenia." #: lib/solaar/ui/window.py:719 msgid "encrypted" msgstr "szyfrowane" #: lib/solaar/ui/window.py:721 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:723 msgid "not encrypted" msgstr "nieszyfrowane" #: lib/solaar/ui/window.py:727 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 "" "Łącze bezprzewodowe między tym urządzeniem a jego odbiornikiem nie jest " "szyfrowane.\n" "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:743 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d lx" Solaar-1.1.20/po/pt.po000066400000000000000000001372101522022367600144260ustar00rootroot00000000000000# Translation of solaar's ui's messages to European Portuguese # Copyright (C) 2014 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # # Américo Monteiro , 2014. msgid "" msgstr "Project-Id-Version: solaar 0.9.2\n" "Report-Msgid-Bugs-To: \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" "Language: pt\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: Lokalize 1.4\n" #: 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: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:124 msgid "Lightspeed Receiver" msgstr "" #: lib/logitech_receiver/base_usb.py:133 msgid "EX100 Receiver 27 Mhz" msgstr "" #: lib/logitech_receiver/i18n.py:30 msgid "empty" msgstr "vazio" #: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "crítico" #: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "baixo" #: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "" #: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "bom" #: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "cheio" #: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "a descarregar" #: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "a carregar" #: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "a carregar" #: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "" #: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "quase cheiro" #: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "" #: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "carga lenta" #: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "bateria inválida" #: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "erro térmico" #: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "" #: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "" #: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "" #: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "" #: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "tempo limite do dispositivo" #: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "dispositivo não suportado" #: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "demasiados dispositivos" #: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "tempo limite da sequência" #: 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" #: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardware" #: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Outro" #: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "" #: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "" #: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "" #: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "" #: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "" #: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "" #: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "" #: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "" #: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "" #: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "" #: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "" #: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "" #: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "" #: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "" #: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "" #: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "" #: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "" #: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "" #: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "" #: lib/logitech_receiver/i18n.py:86 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 msgid "connected" msgstr "ligado" #: lib/logitech_receiver/notifications.py:224 msgid "disconnected" msgstr "" #: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "desemparelhado" #: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "com energia ligada" #: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "" #: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 msgid "feature" msgstr "" #: lib/logitech_receiver/settings_templates.py:139 msgid "Swap Fx function" msgstr "Trocar função 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 definido, as teclas F1..F12 irão activar as suas funções " "especiais,\n" "e você tem manter a tecla FN carregada para activar as suas funções " "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 não definido, as teclas F1..F12 irão activar as suas funções " "standard,\n" "e você tem manter a tecla FN carregada para activar as suas funções " "especiais." #: 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: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 "Sensibilidade (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 "" #: 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 "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: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:170 #, python-format msgid "Battery: %(level)s" msgstr "" #: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "" #: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "" #: lib/logitech_receiver/status.py:239 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "" #: lib/logitech_receiver/status.py:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "" #: lib/solaar/ui/__init__.py:52 msgid "Permissions error" msgstr "Erro de permissões" #: 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 "" #: 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 "" #: 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 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 "Desemparelhamento falhado" #: lib/solaar/ui/__init__.py:66 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "" #: lib/solaar/ui/__init__.py:67 msgid "The receiver returned an error, with no further details." msgstr "O receptor devolveu um erro, sem mais detalhes." #: 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 "Desenho da GUI" #: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "A testar" #: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Documentação da Logitech" #: 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:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "" #: lib/solaar/ui/config_panel.py:212 msgid "Complete - ENTER to change" msgstr "" #: lib/solaar/ui/config_panel.py:212 msgid "Incomplete" msgstr "" #: 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/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:65 msgid "User-defined rules" msgstr "" #: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "" #: 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:70 msgid "[empty]" msgstr "" #: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "" #: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "" #: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Sim" #: lib/solaar/ui/diversion_rules.py:148 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: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 msgid "Paste here" msgstr "" #: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "" #: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "" #: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "" #: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "" #: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "" #: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "" #: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "" #: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "" #: 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:511 lib/solaar/ui/diversion_rules.py:637 #: lib/solaar/ui/diversion_rules.py:1111 msgid "And" msgstr "" #: lib/solaar/ui/diversion_rules.py:513 msgid "Condition" msgstr "" #: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "" #: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "" #: 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:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "" #: 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: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:532 msgid "Action" msgstr "Ação" #: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 msgid "Key press" msgstr "" #: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "" #: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 msgid "Mouse click" msgstr "" #: 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: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: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:610 msgid "Negate" msgstr "" #: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "" #: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Cortar" #: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Colar" #: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Copiar" #: 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:1138 msgid "Number of seconds to delay." msgstr "" #: lib/solaar/ui/diversion_rules.py:1177 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 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: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: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: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: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 "Não há mais detalhes disponíveis acerca do erro." #: 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 "A ligação sem fios não está encriptada" #: 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 "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:283 #, 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] "" #: lib/solaar/ui/pair_window.py:286 msgid "\n" "Cancelling at this point will not use up a pairing." msgstr "" #: 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 "Acerca de %s" #: 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:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "nenhum receptor" #: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "nenhum estado" #: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "A sondar" #: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Bateria" #: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Ligação Sem Fios" #: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Iluminação" #: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Mostrar Detalhes Técnicos" #: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Emparelhar novo dispositivo" #: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Seleccionar um dispositivo" #: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "" #: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Caminho" #: lib/solaar/ui/window.py:536 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 msgid "Serial" msgstr "Série" #: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Índice" #: lib/solaar/ui/window.py:547 msgid "Wireless PID" msgstr "PID de ligação sem fios" #: lib/solaar/ui/window.py:549 msgid "Product ID" msgstr "ID do produto" #: lib/solaar/ui/window.py:551 msgid "Protocol" msgstr "Protocolo" #: lib/solaar/ui/window.py:551 msgid "Unknown" msgstr "Desconhecido" #: lib/solaar/ui/window.py:554 #, python-format msgid "%(rate)d ms (%(rate_hz)dHz)" msgstr "" #: lib/solaar/ui/window.py:554 msgid "Polling rate" msgstr "Rácio de votações" #: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "" #: lib/solaar/ui/window.py:576 msgid "none" msgstr "nenhum" #: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Notificações" #: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "" #: 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:634 msgid "Only one device can be paired to this receiver." msgstr "" #: 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:692 msgid "Battery Voltage" msgstr "" #: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "" #: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "" #: lib/solaar/ui/window.py:698 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:708 msgid " and next level to be reported." msgstr "" #: lib/solaar/ui/window.py:713 msgid "last known" msgstr "último conhecido" #: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "encriptado" #: 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: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 "" #~ msgid " paired devices." #~ msgstr " dispositivos emparelhados." #~ 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" #~ msgid "If the device is already turned on,\n" #~ "turn if off and on again." #~ 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" #~ msgid "Only one device can be paired to this receiver" #~ msgstr "Apenas um dispositivo pode ser emparelhado com este receptor" #~ msgid "Shows status of devices connected\n" #~ "through wireless Logitech receivers." #~ msgstr "Mostra o estado dos dispositivos ligados\n" #~ "através de receptores sem fio da Logitech." #~ msgid "Smooth Scrolling" #~ msgstr "Deslocamento de Ecrã Suave" #, python-format #~ msgid "The receiver only supports %d paired device(s)." #~ msgstr "O receptor apenas suporta %d dispositivo(s) emparelhados." #~ 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" #, python-format #~ msgid "Up to %d devices can be paired to this receiver" #~ msgstr "Podem ser emparelhados até %d dispositivos com este receptor" #~ msgid "closed" #~ msgstr "fechado" #~ msgid "height" #~ msgstr "altura" #~ msgid "lux" #~ msgstr "lux" #~ msgid "open" #~ msgstr "aberto" #~ msgid "pair new device" #~ msgstr "emparelhar o novo dispositivo" #~ msgid "paired devices" #~ msgstr "dispositivos emparelhados" #~ msgid "pairing lock is " #~ msgstr "tranca de emparelhamento é " #~ msgid "unknown" #~ msgstr "desconhecido" #~ msgid "width" #~ msgstr "largura" Solaar-1.1.20/po/pt_BR.po000066400000000000000000002032401522022367600150060ustar00rootroot00000000000000# Portuguese translations for solaar package. # Copyright (C) 2015 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # Automatically generated, 2015. # msgid "" msgstr "" "Project-Id-Version: solaar 0.9.2\n" "Report-Msgid-Bugs-To: \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" "Content-Type: text/plain; charset=UTF-8\n" "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.8\n" #: 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: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:125 msgid "Lightspeed Receiver" msgstr "Receptor Lightspeed" #: lib/logitech_receiver/base_usb.py:136 msgid "EX100 Receiver 27 Mhz" msgstr "Receptor EX100 27 Mhz" #: lib/logitech_receiver/common.py:649 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "Bateria: %(level)s (%(status)s)" #: 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:29 msgid "critical" msgstr "crítica" #: lib/logitech_receiver/i18n.py:30 msgid "low" msgstr "fraca" #: lib/logitech_receiver/i18n.py:31 msgid "average" msgstr "média" #: lib/logitech_receiver/i18n.py:32 msgid "good" msgstr "boa" #: lib/logitech_receiver/i18n.py:33 msgid "full" msgstr "cheia" #: lib/logitech_receiver/i18n.py:35 msgid "discharging" msgstr "descarregando" #: lib/logitech_receiver/i18n.py:36 msgid "recharging" msgstr "recarregando" #: lib/logitech_receiver/i18n.py:37 lib/solaar/ui/window.py:700 msgid "charging" msgstr "carregando" #: lib/logitech_receiver/i18n.py:38 msgid "not charging" msgstr "não carregando" #: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "quase cheia" #: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "carregada" #: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "recarga lenta" #: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "bateria inválida" #: lib/logitech_receiver/i18n.py:43 msgid "thermal error" msgstr "erro térmico" #: lib/logitech_receiver/i18n.py:44 msgid "error" msgstr "erro" #: lib/logitech_receiver/i18n.py:45 msgid "standard" msgstr "padrão" #: lib/logitech_receiver/i18n.py:46 msgid "fast" msgstr "rápido" #: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "lento" #: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "tempo esgotado do dispositivo" #: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "dispositivo não suportado" #: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "número excessivo de dispositivos" #: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "tempo esgotado da sequência" #: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:555 msgid "Firmware" msgstr "Firmware" #: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Bootloader" #: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Hardware" #: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "Outro" #: lib/logitech_receiver/i18n.py:59 msgid "Left Button" msgstr "Botão da esquerda" #: lib/logitech_receiver/i18n.py:60 msgid "Right Button" msgstr "Botão da direita" #: lib/logitech_receiver/i18n.py:61 msgid "Middle Button" msgstr "Botão do meio" #: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "Botão Voltar" #: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "Botão Avançar" #: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "Botão de gestos do mouse" #: lib/logitech_receiver/i18n.py:65 msgid "Smart Shift" msgstr "Smart Shift" #: lib/logitech_receiver/i18n.py:66 msgid "DPI Switch" msgstr "Mudança de DPI" #: lib/logitech_receiver/i18n.py:67 msgid "Left Tilt" msgstr "Inclinação para a esquerda" #: lib/logitech_receiver/i18n.py:68 msgid "Right Tilt" msgstr "Inclinação para a direita" #: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "Clique com o botão esquerdo" #: lib/logitech_receiver/i18n.py:70 msgid "Right Click" msgstr "Clique com o botão direito" #: lib/logitech_receiver/i18n.py:71 msgid "Mouse Middle Button" msgstr "Botão do meio do mouse" #: lib/logitech_receiver/i18n.py:72 msgid "Mouse Back Button" msgstr "Botão de voltar do mouse" #: lib/logitech_receiver/i18n.py:73 msgid "Mouse Forward Button" msgstr "Botão de avançar do mouse" #: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "Botão de navegação por gestos" #: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "Botão de rolagem esquerda do mouse" #: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "Botão de rolagem direita do mouse" #: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "pressionada" #: lib/logitech_receiver/i18n.py:79 msgid "released" msgstr "liberada" #: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 msgid "connected" msgstr "conectado" #: lib/logitech_receiver/notifications.py:156 lib/solaar/listener.py:243 msgid "disconnected" msgstr "desconectado" #: lib/logitech_receiver/notifications.py:182 msgid "unpaired" msgstr "desemparelhado" #: lib/logitech_receiver/notifications.py:231 msgid "powered on" msgstr "ligado" #: 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:616 lib/logitech_receiver/settings.py:654 msgid "feature" msgstr "feature" #: 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: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:162 msgid "Scroll Wheel Smooth Scrolling" msgstr "Rolagem suave com a roda" #: 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:170 msgid "Side Scrolling" msgstr "Rolagem Lateral" #: 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 "" "Quando desabilitado, pressionar a roda lateralmente envia eventos " "customizados\n" "ao invés dos eventos tradicionais de rolagem lateral." #: lib/logitech_receiver/settings_templates.py:182 msgid "Sensitivity (DPI - older mice)" msgstr "Sensibilidade (DPI - mouses antigos)" #: 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:256 msgid "Backlight Timed" msgstr "Temporizador de luz de fundo" #: 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:268 msgid "Backlight" msgstr "Luz de fundo" #: lib/logitech_receiver/settings_templates.py:269 msgid "" "Illumination level on keyboard. Changes made are only applied in Manual " "mode." msgstr "" "Nível de iluminação do teclado. As alterações feitas só se aplicam no modo " "Manual." #: lib/logitech_receiver/settings_templates.py:301 msgid "Automatic" msgstr "Automático" #: 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 "" "Delay in seconds until backlight fades out with hands away from keyboard." msgstr "" "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:378 msgid "Backlight Delay Hands In" msgstr "Atraso da luz de fundo ao colocar as mãos" #: 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:387 msgid "Backlight Delay Powered" msgstr "Atraso da luz de fundo Alimentado" #: 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:396 msgid "Backlight (Seconds)" msgstr "Luz de fundo (segundos)" #: 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: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:419 #: lib/logitech_receiver/settings_templates.py:450 msgid "Scroll Wheel Diversion" msgstr "Desvio da roda de rolagem" #: 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 "Direção de rolagem" #: 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:484 msgid "Invert thumb wheel scroll direction." msgstr "Inverter a direção da rolagem com o polegar." #: lib/logitech_receiver/settings_templates.py:504 msgid "Onboard Profiles" msgstr "Perfis Embarcados" #: 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 "Taxa de relatório" #: 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: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:613 msgid "" "Make crown send CROWN HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." 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: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 "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:1272 msgid "Single tap" msgstr "Toque único" #: 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:1273 msgid "Single tap with two fingers" msgstr "Toque único com dois dedos" #: 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:1278 msgid "Double tap" msgstr "Toque duplo" #: lib/logitech_receiver/settings_templates.py:1278 msgid "Performs a double click." msgstr "Executa um clique duplo." #: 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:1280 msgid "Double tap with three fingers" msgstr "Toque duplo com três dedos" #: 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:1283 msgid "Tap and drag" msgstr "Tocar e arrastar" #: 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: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:1288 msgid "Tap and drag with three fingers" msgstr "Tocar e arrastar com três dedos" #: 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: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:1294 msgid "Scroll with one finger" msgstr "Rolar com um dedo" #: 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:1295 #: lib/logitech_receiver/settings_templates.py:1298 msgid "Scroll with two fingers" msgstr "Rolar com dois dedos" #: 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:1296 msgid "Scrolls horizontally." msgstr "Faz rolagem horizontal." #: 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:1297 msgid "Scrolls vertically." msgstr "Faz rolagem vertical." #: 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:1299 msgid "Natural scrolling" msgstr "Rolagem natural" #: lib/logitech_receiver/settings_templates.py:1300 msgid "Enables the thumbwheel." msgstr "Ativa a roda do polegar." #: lib/logitech_receiver/settings_templates.py:1300 msgid "Thumbwheel" msgstr "Roda do polegar" #: 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:1312 msgid "Swipe from the left edge" msgstr "Deslizar da borda esquerda" #: lib/logitech_receiver/settings_templates.py:1313 msgid "Swipe from the right edge" msgstr "Deslizar da borda direita" #: lib/logitech_receiver/settings_templates.py:1314 msgid "Swipe from the bottom edge" msgstr "Deslizar da borda inferior" #: 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:1317 msgid "Swipe two fingers from the right edge" msgstr "Deslizar dois dedos da borda direita" #: 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:1319 msgid "Swipe two fingers from the top edge" msgstr "Deslizar dois dedos da borda superior" #: 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:1320 msgid "Zoom with two fingers." msgstr "Fazer zoom com dois dedos." #: 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:1322 msgid "Spread to zoom in." msgstr "Afastar os dedos para aumentar o zoom." #: 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:1324 msgid "Zoom with two fingers" msgstr "Fazer zoom com dois dedos" #: lib/logitech_receiver/settings_templates.py:1342 msgid "Pixel zone" msgstr "Região de pixels" #: lib/logitech_receiver/settings_templates.py:1343 msgid "Ratio zone" msgstr "Região de proporção" #: lib/logitech_receiver/settings_templates.py:1344 msgid "Scale factor" msgstr "Fator de escala" #: lib/logitech_receiver/settings_templates.py:1344 msgid "Sets the cursor speed." msgstr "Define a velocidade do cursor." #: lib/logitech_receiver/settings_templates.py:1348 msgid "Left" msgstr "Esquerda" #: lib/logitech_receiver/settings_templates.py:1348 msgid "Left-most coordinate." msgstr "Coordenada do ponto mais à esquerda." #: lib/logitech_receiver/settings_templates.py:1349 msgid "Bottom" msgstr "Fundo" #: lib/logitech_receiver/settings_templates.py:1349 msgid "Bottom coordinate." msgstr "Coordenada inferior." #: 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:1351 msgid "Height" msgstr "Altura" #: lib/logitech_receiver/settings_templates.py:1351 msgid "Height." msgstr "Altura." #: lib/logitech_receiver/settings_templates.py:1352 msgid "Cursor speed." msgstr "Velocidade do cursor." #: lib/logitech_receiver/settings_templates.py:1352 msgid "Scale" msgstr "Escala" #: 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 "Lights up the %s key." msgstr "Ilumina a tecla %s." #: 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 "Alterar permanentemente o mapeamento da tecla ou do botão." #: 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/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 "Equalizador" #: 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 "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/common.py:44 #, python-format msgid "" "Found a Logitech receiver or device (%s), but did not have permission to " "open it." msgstr "" "Foi encontrado um receptor ou dispositivo Logitech (%s), mas não tinha " "permissão para abri-lo." #: 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/common.py:51 #, python-format msgid "" "Found a Logitech receiver or device at %s, but encountered an error " "connecting to it." msgstr "" "Foi encontrado um receptor ou dispositivo Logitech em %s, mas houve um erro " "ao conectar-se a ele." #: lib/solaar/ui/common.py:53 msgid "" "Try disconnecting the device and then reconnecting it or turning it off and " "then on." msgstr "" "Tente desconectar o dispositivo e reconectá-lo, ou desligá-lo e ligá-lo " "novamente." #: lib/solaar/ui/common.py:56 msgid "Unpairing failed" msgstr "Desemparelhamento falhou" #: 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/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/config_panel.py:245 msgid "Complete - ENTER to change" msgstr "Completo - pressione ENTER para alterar" #: lib/solaar/ui/config_panel.py:245 msgid "Incomplete" msgstr "Incompleto" #: 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/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:103 msgid "User-defined rules" msgstr "Regras do usuário" #: lib/solaar/ui/diversion_rules.py:105 lib/solaar/ui/diversion_rules.py:1126 msgid "Rule" msgstr "Regra" #: 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:108 msgid "[empty]" msgstr "[vazio]" #: lib/solaar/ui/diversion_rules.py:132 msgid "Make changes permanent?" msgstr "Tornar as alterações permanentes?" #: lib/solaar/ui/diversion_rules.py:137 msgid "Yes" msgstr "Sim" #: lib/solaar/ui/diversion_rules.py:139 msgid "No" msgstr "Não" #: 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:273 msgid "Paste here" msgstr "Colar aqui" #: lib/solaar/ui/diversion_rules.py:275 msgid "Paste above" msgstr "Colar acima" #: lib/solaar/ui/diversion_rules.py:277 msgid "Paste below" msgstr "Colar abaixo" #: lib/solaar/ui/diversion_rules.py:283 msgid "Paste rule here" msgstr "Colar regra aqui" #: lib/solaar/ui/diversion_rules.py:285 msgid "Paste rule above" msgstr "Colar regra acima" #: lib/solaar/ui/diversion_rules.py:287 msgid "Paste rule below" msgstr "Colar regra abaixo" #: lib/solaar/ui/diversion_rules.py:291 msgid "Paste rule" msgstr "Colar regra" #: 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:380 msgid "Insert" msgstr "Inserir" #: 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: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:386 msgid "Condition" msgstr "Condição" #: lib/solaar/ui/diversion_rules.py:388 lib/solaar/ui/rule_conditions.py:154 msgid "Feature" msgstr "Feature" #: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:188 msgid "Report" msgstr "Report" #: 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:393 lib/solaar/ui/rule_conditions.py:277 msgid "Key" msgstr "Tecla" #: 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: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:405 msgid "Action" msgstr "Ação" #: 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:408 lib/solaar/ui/rule_actions.py:191 msgid "Mouse scroll" msgstr "Rolagem do mouse" #: 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: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: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: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:483 msgid "Negate" msgstr "Negar" #: lib/solaar/ui/diversion_rules.py:507 msgid "Wrap with" msgstr "Envolver com" #: lib/solaar/ui/diversion_rules.py:537 msgid "Cut" msgstr "Recortar" #: lib/solaar/ui/diversion_rules.py:553 msgid "Paste" msgstr "Colar" #: lib/solaar/ui/diversion_rules.py:559 msgid "Copy" msgstr "Copiar" #: 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: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:1238 msgid "Toggle" msgstr "Alternar" #: lib/solaar/ui/diversion_rules.py:1239 msgid "True" msgstr "Verdadeiro" #: lib/solaar/ui/diversion_rules.py:1240 msgid "False" msgstr "Falso" #: lib/solaar/ui/diversion_rules.py:1253 msgid "Unsupported setting" msgstr "Configuração não suportada" #: 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: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:1437 msgid "Device that originated the current notification." msgstr "Dispositivo que originou a notificação atual." #: lib/solaar/ui/diversion_rules.py:1450 msgid "Name of host computer." msgstr "Name of host computer." #: lib/solaar/ui/diversion_rules.py:1520 msgid "Value" msgstr "Valor" #: lib/solaar/ui/diversion_rules.py:1529 msgid "Item" msgstr "Item" #: lib/solaar/ui/diversion_rules.py:1808 msgid "Change setting on device" msgstr "Alterar configuração no dispositivo" #: lib/solaar/ui/diversion_rules.py:1824 msgid "Setting on device" msgstr "Configuração no dispositivo" #: 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: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: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" "\n" "This receiver has %d pairing remaining." msgid_plural "" "\n" "\n" "This receiver has %d pairings remaining." msgstr[0] "" "\n" "\n" "Este dispositivo tem %d pareamento disponível." msgstr[1] "" "\n" "\n" "Este dispositivo tem %d pareamentos disponíveis." #: lib/solaar/ui/pair_window.py:78 msgid "" "\n" "Cancelling at this point will not use up a pairing." msgstr "" "\n" "Cancelar neste ponto não usará um pareamento." #: 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/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/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:273 lib/solaar/ui/tray.py:281 msgid "no receiver" msgstr "nenhum receptor" #: 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:110 msgid "Scanning" msgstr "Procurando" #: lib/solaar/ui/window.py:141 msgid "Battery" msgstr "Bateria" #: lib/solaar/ui/window.py:144 msgid "Wireless Link" msgstr "Conexão sem fio" #: lib/solaar/ui/window.py:148 msgid "Lighting" msgstr "Iluminação" #: lib/solaar/ui/window.py:182 msgid "Show Technical Details" msgstr "Mostrar detalhes técnicos" #: lib/solaar/ui/window.py:198 msgid "Pair new device" msgstr "Emparelhar novo dispositivo" #: lib/solaar/ui/window.py:216 msgid "Select a device" msgstr "Selecione um dispositivo" #: lib/solaar/ui/window.py:331 msgid "Rule Editor" msgstr "Editor de regras" #: lib/solaar/ui/window.py:522 msgid "Path" msgstr "Caminho" #: lib/solaar/ui/window.py:524 msgid "USB ID" msgstr "ID 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 "Número de série" #: lib/solaar/ui/window.py:533 msgid "Index" msgstr "Índice" #: lib/solaar/ui/window.py:535 msgid "Wireless PID" msgstr "PID sem fio" #: lib/solaar/ui/window.py:537 msgid "Product ID" msgstr "ID do produto" #: lib/solaar/ui/window.py:539 msgid "Protocol" msgstr "Protocolo" #: lib/solaar/ui/window.py:539 msgid "Unknown" msgstr "Desconhecido" #: lib/solaar/ui/window.py:541 msgid "Polling rate" msgstr "Taxa de sondagem" #: lib/solaar/ui/window.py:548 msgid "Unit ID" msgstr "Unit ID" #: lib/solaar/ui/window.py:559 msgid "none" msgstr "nenhum" #: lib/solaar/ui/window.py:560 msgid "Notifications" msgstr "Notificações" #: lib/solaar/ui/window.py:604 msgid "No device paired." msgstr "Nenhum dispositivo emparelhado." #: 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] "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: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: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:681 msgid "Battery Voltage" msgstr "Tensão da Bateria" #: lib/solaar/ui/window.py:683 msgid "Voltage reported by battery" msgstr "Tensão informada pela bateria" #: lib/solaar/ui/window.py:685 msgid "Battery Level" msgstr "Nível de bateria" #: lib/solaar/ui/window.py:687 msgid "Approximate level reported by battery" msgstr "Nível aproximado informado pela bateria" #: 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:697 msgid " and next level to be reported." msgstr " e o próximo nível que será informado." #: lib/solaar/ui/window.py:702 msgid "last known" msgstr "último conhecido" #: lib/solaar/ui/window.py:713 msgid "encrypted" msgstr "criptografada" #: 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: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 " 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 " #~ "button." #~ msgstr "" #~ "Ajustar o DPI deslizando o mouse horizontalmente ao segurar o botão DPI." #~ 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." #~ 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 "" #~ "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 "" #~ "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 "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" #~ "turn if off and on again." #~ msgstr "" #~ "Se o dispositivo já encontra-se ligado,\n" #~ "desligue-o e ligue-o novamente." #~ 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 "" #~ "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." #, python-format #~ msgid "Lighting: %(level)s lux" #~ msgstr "Iluminação: %(level)s lux" #~ 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 "" #~ "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 "No Logitech receiver found" #~ msgstr "Nenhum receptor da Logitech encontrado" #~ msgid "No device paired" #~ msgstr "Nenhum dispositivo emparelhado" #~ msgid "Only one device can be paired to this receiver" #~ msgstr "Apenas um dispositivo pode ser emparelhado com este receptor" #~ 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 "open" #~ msgstr "aberto" #~ msgid "pair new device" #~ msgstr "emparelhar novo dispositivo" #~ msgid "paired devices" #~ msgstr "dispositivos emparelhados" #~ msgid "pairing lock is " #~ msgstr "trava de emparelhamento está " #~ msgid "top" #~ msgstr "topo" #~ msgid "unknown" #~ msgstr "desconhecida" #~ msgid "width" #~ msgstr "largura" Solaar-1.1.20/po/ro.po000066400000000000000000001351731522022367600144310ustar00rootroot00000000000000# Romanian translations for solaar package. # Copyright (C) 2013 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # Automatically generated, 2013. # msgid "" msgstr "Project-Id-Version: solaar 0.9.1\n" "Report-Msgid-Bugs-To: \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" "Language: ro\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" "X-Generator: Poedit 1.5.4\n" #: 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: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:124 msgid "Lightspeed Receiver" msgstr "" #: lib/logitech_receiver/base_usb.py:133 msgid "EX100 Receiver 27 Mhz" msgstr "" #: lib/logitech_receiver/i18n.py:30 msgid "empty" msgstr "descărcată" #: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "aproape descărcată" #: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "joasă" #: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "" #: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "bună" #: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "plină" #: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "în descarcare" #: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "re-încărcare" #: lib/logitech_receiver/i18n.py:40 lib/solaar/ui/window.py:711 msgid "charging" msgstr "se încarcă" #: lib/logitech_receiver/i18n.py:41 msgid "not charging" msgstr "" #: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "aproape plină" #: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "" #: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "încarcare inceată" #: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "baterie necorespunzătoare" #: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "eroare termică" #: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "" #: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "" #: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "" #: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "" #: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "" #: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "periferic incompatibil" #: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "prea multe periferice" #: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "" #: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 msgid "Firmware" msgstr "" #: lib/logitech_receiver/i18n.py:60 msgid "Bootloader" msgstr "" #: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "" #: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "" #: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "" #: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "" #: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "" #: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "" #: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "" #: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "" #: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "" #: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "" #: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "" #: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "" #: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "" #: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "" #: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "" #: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "" #: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "" #: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "" #: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "" #: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "" #: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "" #: lib/logitech_receiver/i18n.py:86 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 msgid "connected" msgstr "conectat(ă)" #: lib/logitech_receiver/notifications.py:224 msgid "disconnected" msgstr "" #: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "deconectat(ă)" #: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "a pornit" #: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "" #: lib/logitech_receiver/settings.py:764 lib/logitech_receiver/settings.py:791 msgid "feature" msgstr "" #: lib/logitech_receiver/settings_templates.py:139 msgid "Swap Fx function" msgstr "Inversează funcțiile 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 "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: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: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: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 "Sentivitivate (PPI)" #: 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 "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:115 lib/solaar/ui/window.py:622 #, python-format msgid "%(count)s paired device." msgid_plural "%(count)s paired devices." msgstr[0] "" msgstr[1] "" msgstr[2] "" #: lib/logitech_receiver/status.py:170 #, python-format msgid "Battery: %(level)s" msgstr "" #: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "" #: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "" #: lib/logitech_receiver/status.py:239 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "" #: lib/logitech_receiver/status.py:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "" #: lib/solaar/ui/__init__.py:52 msgid "Permissions error" msgstr "Eroare de permisiuni" #: 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 "" #: 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 "" #: 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 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 "Deconectare eșuată" #: lib/solaar/ui/__init__.py:66 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Deconectarea %{device} de la %{receiver} a eșuat." #: lib/solaar/ui/__init__.py:67 msgid "The receiver returned an error, with no further details." msgstr "Receptorul a semnalat o eroare, fără alte detalii." #: 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 "Interfață grafica" #: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "Testare" #: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Documentație Logitech" #: 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:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "" #: lib/solaar/ui/config_panel.py:212 msgid "Complete - ENTER to change" msgstr "" #: lib/solaar/ui/config_panel.py:212 msgid "Incomplete" msgstr "" #: 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] "" msgstr[2] "" #: 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:65 msgid "User-defined rules" msgstr "" #: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "" #: 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:70 msgid "[empty]" msgstr "" #: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "" #: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "" #: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Da" #: lib/solaar/ui/diversion_rules.py:148 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: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 msgid "Paste here" msgstr "" #: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "" #: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "" #: lib/solaar/ui/diversion_rules.py:437 msgid "Paste rule here" msgstr "" #: lib/solaar/ui/diversion_rules.py:439 msgid "Paste rule above" msgstr "" #: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "" #: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "" #: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "" #: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "" #: 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:511 lib/solaar/ui/diversion_rules.py:637 #: lib/solaar/ui/diversion_rules.py:1111 msgid "And" msgstr "" #: lib/solaar/ui/diversion_rules.py:513 msgid "Condition" msgstr "" #: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "" #: lib/solaar/ui/diversion_rules.py:516 lib/solaar/ui/diversion_rules.py:1328 msgid "Report" msgstr "" #: 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:520 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "" #: 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: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:532 msgid "Action" msgstr "Acțiune" #: lib/solaar/ui/diversion_rules.py:534 lib/solaar/ui/diversion_rules.py:1845 msgid "Key press" msgstr "" #: lib/solaar/ui/diversion_rules.py:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "" #: lib/solaar/ui/diversion_rules.py:536 lib/solaar/ui/diversion_rules.py:1959 msgid "Mouse click" msgstr "" #: 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: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: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:610 msgid "Negate" msgstr "" #: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "" #: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Taie" #: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Lipește" #: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Copiază" #: 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:1138 msgid "Number of seconds to delay." msgstr "" #: lib/solaar/ui/diversion_rules.py:1177 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 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: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: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: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: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 "Alte detalii despre eroare nu sunt disponibile." #: 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 "Legătura fără fir nu este criptată" #: 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 "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:283 #, 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] "" msgstr[2] "" #: lib/solaar/ui/pair_window.py:286 msgid "\n" "Cancelling at this point will not use up a pairing." msgstr "" #: 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 "Despre %s" #: 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:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "nici un receptor" #: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "stare necunoscută" #: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "Caut" #: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Baterie" #: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Legatură fără fir" #: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Lumină" #: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Detalii tehnice" #: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Conectează periferic" #: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Selectați un dispozitiv" #: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "" #: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Cale" #: lib/solaar/ui/window.py:536 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 msgid "Serial" msgstr "Serial" #: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Index" #: lib/solaar/ui/window.py:547 msgid "Wireless PID" msgstr "Cod WPID" #: lib/solaar/ui/window.py:549 msgid "Product ID" msgstr "ID produs" #: lib/solaar/ui/window.py:551 msgid "Protocol" msgstr "Protocol" #: lib/solaar/ui/window.py:551 msgid "Unknown" msgstr "Necunoscut" #: lib/solaar/ui/window.py:554 #, python-format msgid "%(rate)d ms (%(rate_hz)dHz)" msgstr "" #: lib/solaar/ui/window.py:554 msgid "Polling rate" msgstr "Rată acces" #: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "" #: lib/solaar/ui/window.py:576 msgid "none" msgstr "nici una" #: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Notificări" #: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "" #: 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] "" msgstr[2] "" #: lib/solaar/ui/window.py:634 msgid "Only one device can be paired to this receiver." msgstr "" #: 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] "" msgstr[2] "" #: lib/solaar/ui/window.py:692 msgid "Battery Voltage" msgstr "" #: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "" #: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "" #: lib/solaar/ui/window.py:698 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:708 msgid " and next level to be reported." msgstr "" #: lib/solaar/ui/window.py:713 msgid "last known" msgstr "ultima valoare" #: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "criptată" #: 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: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 "" #~ msgid " paired devices." #~ msgstr " periferice contectate." #~ 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" #~ msgid "If the device is already turned on,\n" #~ "turn if off and on again." #~ 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" #~ msgid "Only one device can be paired to this receiver" #~ msgstr "Acest receptor suportă un singur periferic conectat" #~ msgid "Shows status of devices connected\n" #~ "through wireless Logitech receivers." #~ msgstr "Afișează starea perifericelor conectate\n" #~ "printr-un receptor Logitech fără fir." #~ msgid "Smooth Scrolling" #~ msgstr "Derulare fină" #, python-format #~ msgid "The receiver only supports %d paired device(s)." #~ msgstr "Receptorul suportă maxim %d periferic(e) contectate." #~ 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" #~ msgid "Up to %d devices can be paired to this receiver" #~ msgstr "Acest receptor suportă maxim %d periferice conectate" #~ msgid "closed" #~ msgstr "închis" #~ msgid "height" #~ msgstr "înălțime" #~ msgid "lux" #~ msgstr "lucși" #~ msgid "open" #~ msgstr "deschis" #~ msgid "pair new device" #~ msgstr "conectează periferic nou" #~ msgid "paired devices" #~ msgstr "periferice conectate" #~ msgid "pairing lock is " #~ msgstr "lacătul de contectare este " #~ msgid "unknown" #~ msgstr "necunoscută" #~ msgid "width" #~ msgstr "lățime" Solaar-1.1.20/po/ru.po000066400000000000000000002271201522022367600144310ustar00rootroot00000000000000# Russian translations for solaar package. # Copyright (C) 2013 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # # Automatically generated, 2013. # Dmitry Ryazantcev , 2013-2016. # Vadim Illarionov , 2021. # Olesya Gerasimenko , 2021, 2022. # 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: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 "Переключатель чувствительности" #: 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 сопряжённых устройства." msgstr[2] "%(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" "вместо стандартных событий горизонтальной прокрутки\n" "будет отправлять пользовательские события нажатия." #: 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 "Заставить колесо прокрутки отправлять уведомления 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 "Скользящий тчк/дюйм" #: 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 "Переключение текущей чувствительности и сохранённой чувствительности " "при нажатии клавиши или кнопки.\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 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 "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: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 значения" msgstr[2] "%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 "Если выбрать «Нет», при закрытии программы изменения пропадут." #: 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 "Feature" #: 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 "Редактор правил" #: 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 "Приемники 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 "\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: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 и затем нажмите клавишу «Ввод»." #: 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 "Имитация щелчка мыши.В 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 "Жест мыши с дополнительной кнопкой запуска, за которым следует ноль " "или более движений мыши." #: 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 "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 "Приблизительный уровень заряда, cообщаемый батареей" #: 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 " \n" "(следующий уровень для отчёта)." #: 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 люкс" Solaar-1.1.20/po/sk.po000066400000000000000000001637071522022367600144320ustar00rootroot00000000000000# Slovak translations for solaar package. # Copyright (C) 2021 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # Automatically generated, 2021. # # 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:46 msgid "Bolt Receiver" msgstr "Prijímač Bolt" #: lib/logitech_receiver/base_usb.py:57 msgid "Unifying Receiver" msgstr "Prijímač 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 "Prijímač Nano" #: lib/logitech_receiver/base_usb.py:124 msgid "Lightspeed Receiver" msgstr "Prijímač Lightspeed" #: 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 "discharging" msgstr "vybíja sa" #: lib/logitech_receiver/i18n.py:39 msgid "recharging" msgstr "nabíja sa" #: 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 "not charging" msgstr "nenabíja sa" #: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "takmer nabitá" #: lib/logitech_receiver/i18n.py:43 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 "thermal error" msgstr "teplotná chyba" #: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "chyba" #: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "štandardné" #: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "rýchle" #: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "pomalé" #: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "vypršal časový limit zariadenia" #: lib/logitech_receiver/i18n.py:54 msgid "device not supported" msgstr "nepodporované zariadenie" #: lib/logitech_receiver/i18n.py:55 msgid "too many devices" msgstr "príliš veľa zariadení" #: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "vypršal časový limit sekvencie" #: lib/logitech_receiver/i18n.py:59 lib/solaar/ui/window.py:572 msgid "Firmware" msgstr "Firmvér" #: lib/logitech_receiver/i18n.py:60 msgid "Bootloader" msgstr "Bootloader" #: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardvér" #: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Ďalší" #: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "Ľavé tlačidlo" #: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "Pravé tlačidlo" #: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "Stredné tlačidlo" #: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "Tlačidlo Späť" #: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "Tlačidlo Dopredu" #: lib/logitech_receiver/i18n.py:70 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 Tilt" msgstr "Náklon Vľavo" #: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "Náklon Vpravo" #: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "Ľavý klik" #: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "Pravý klik" #: lib/logitech_receiver/i18n.py:77 msgid "Mouse Middle Button" msgstr "Stredné tlačidlo myši" #: lib/logitech_receiver/i18n.py:78 msgid "Mouse Back Button" msgstr "Tlačidlo Späť na myši" #: lib/logitech_receiver/i18n.py:79 msgid "Mouse Forward Button" msgstr "Tlačidlo Dopredu na myši" #: lib/logitech_receiver/i18n.py:80 msgid "Gesture Button Navigation" msgstr "Tlačidlo navigácie gestami" #: lib/logitech_receiver/i18n.py:81 msgid "Mouse Scroll Left Button" msgstr "Ľavé tlačidlo rolovania na myši" #: lib/logitech_receiver/i18n.py:82 msgid "Mouse Scroll Right Button" msgstr "Pravé tlačidlo rolovania na myši" #: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "stlačnené" #: lib/logitech_receiver/i18n.py:86 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 "Swap Fx function" msgstr "Prepnúť funkciu 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 "" "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 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:149 msgid "Hand Detection" msgstr "Detekcia rúk" #: 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:157 msgid "Scroll Wheel Smooth Scrolling" msgstr "Plynulé skrolovanie" #: 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 "Vysokocitlivý režim zvislého posunu kolieska myši." #: lib/logitech_receiver/settings_templates.py:165 msgid "Side Scrolling" msgstr "Bočné skrolovanie" #: 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 "" "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:177 msgid "Sensitivity (DPI - older mice)" msgstr "Citlivosť (DPI - staršie myši)" #: 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: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: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:219 msgid "Turn illumination on or off on keyboard." msgstr "Vypne/zapne podsvietenie klávesnice." #: 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: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:247 #: lib/logitech_receiver/settings_templates.py:277 msgid "Scroll Wheel Diversion" msgstr "Odklonenie rolovacieho kolieska" #: 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:256 msgid "Scroll Wheel Direction" msgstr "Smer skrolovania" #: 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:265 msgid "Scroll Wheel Resolution" msgstr "Rozlíšenie skrolovania myši" #: 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:288 msgid "Sensitivity (Pointer Speed)" msgstr "Citlivosť (rýchlosť kurzora)" #: 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:299 msgid "Thumb Wheel Diversion" msgstr "Odchylka palcového kolieska" #: 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:310 msgid "Thumb Wheel Direction" msgstr "Smer posuvného kolieska" #: 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: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." #: 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: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:873 msgid "Change Host" msgstr "Zmeniť hostiteľa" #: 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/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 "Lights up the %s key." msgstr "Rozsvieti kláves %s." #: lib/logitech_receiver/settings_templates.py:1074 msgid "MR-Key LED" msgstr "MR-Key LED" #: lib/logitech_receiver/settings_templates.py:1076 msgid "Control the MR-Key LED." msgstr "Ovláda LEDky klávesu MR-Key." #: lib/logitech_receiver/settings_templates.py:1095 msgid "Persistent Key/Button Mapping" msgstr "Trvalé mapovanie Klávesov/Tlačidiel" #: 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/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 "%(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" #: 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 "" "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 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" #: 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." #: lib/solaar/ui/__init__.py:61 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" #: 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}." #: 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." #: 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 "Testing" msgstr "Testovanie" #: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Dokumentácia Logitechu" #: 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/action.py:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "Zrušiť" #: lib/solaar/ui/config_panel.py:212 msgid "Complete - ENTER to change" msgstr "" #: lib/solaar/ui/config_panel.py:212 msgid "Incomplete" msgstr "" #: 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" #: lib/solaar/ui/config_panel.py:518 msgid "Changes allowed" msgstr "Zmeny sú povolené" #: lib/solaar/ui/config_panel.py:519 msgid "No changes allowed" msgstr "Zmeny nie sú povolené" #: lib/solaar/ui/config_panel.py:520 msgid "Ignore this setting" msgstr "Ignorovať toto nastavenie" #: lib/solaar/ui/config_panel.py:565 msgid "Working" msgstr "Pracujem" #: 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:65 msgid "Built-in rules" msgstr "Zabudované pravidlá" #: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" msgstr "Používateľom definované pravidlá" #: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1083 msgid "Rule" msgstr "Pravidlo" #: 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:70 msgid "[empty]" msgstr "[prázdne]" #: lib/solaar/ui/diversion_rules.py:94 msgid "Solaar Rule Editor" msgstr "Editor pravidiel Solaar" #: lib/solaar/ui/diversion_rules.py:141 msgid "Make changes permanent?" msgstr "Označiť zmeny ako trvalé?" #: lib/solaar/ui/diversion_rules.py:146 msgid "Yes" msgstr "Áno" #: lib/solaar/ui/diversion_rules.py:148 msgid "No" msgstr "Nie" #: 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 below" msgstr "Vložiť pod" #: lib/solaar/ui/diversion_rules.py:382 msgid "Insert new rule here" msgstr "Vložiť nové pravidlo sem" #: lib/solaar/ui/diversion_rules.py:384 msgid "Insert new rule above" msgstr "Vložiť nové pravidlo nad" #: lib/solaar/ui/diversion_rules.py:386 msgid "Insert new rule below" msgstr "Vložiť nové pravidlo pod" #: lib/solaar/ui/diversion_rules.py:427 msgid "Paste here" msgstr "Vložiť sem" #: lib/solaar/ui/diversion_rules.py:429 msgid "Paste above" msgstr "Vložiť nad" #: lib/solaar/ui/diversion_rules.py:431 msgid "Paste below" msgstr "Vložiť 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 above" msgstr "Vložiť pravidlo nad" #: lib/solaar/ui/diversion_rules.py:441 msgid "Paste rule below" msgstr "Vložiť pravidlo pod" #: lib/solaar/ui/diversion_rules.py:445 msgid "Paste rule" msgstr "Vložiť pravidlo" #: lib/solaar/ui/diversion_rules.py:474 msgid "Flatten" msgstr "Sploštiť" #: lib/solaar/ui/diversion_rules.py:507 msgid "Insert" msgstr "Vlož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 "Alebo" #: 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:513 msgid "Condition" msgstr "Podmienka" #: lib/solaar/ui/diversion_rules.py:515 lib/solaar/ui/diversion_rules.py:1292 msgid "Feature" msgstr "Vlastnosť" #: 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:517 lib/solaar/ui/diversion_rules.py:1204 msgid "Process" msgstr "Proces" #: lib/solaar/ui/diversion_rules.py:518 msgid "Mouse process" msgstr "Proces 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 lib/solaar/ui/diversion_rules.py:1419 msgid "Key" msgstr "Kláves" #: 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:522 lib/solaar/ui/diversion_rules.py:2260 msgid "Active" msgstr "Aktívne" #: 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:524 lib/solaar/ui/diversion_rules.py:2297 msgid "Host" msgstr "Hostiteľ" #: lib/solaar/ui/diversion_rules.py:525 lib/solaar/ui/diversion_rules.py:2339 msgid "Setting" msgstr "Nastavenie" #: 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 "Test bajtov" #: 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:532 msgid "Action" msgstr "Akcia" #: 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:535 lib/solaar/ui/diversion_rules.py:1897 msgid "Mouse scroll" msgstr "Skrolovanie kolieskom" #: 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:537 msgid "Set" msgstr "Nastaviť" #: 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:539 lib/solaar/ui/diversion_rules.py:1158 msgid "Later" msgstr "Neskôr" #: lib/solaar/ui/diversion_rules.py:568 msgid "Insert new rule" msgstr "Vložiť nové pravidlo" #: 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:610 msgid "Negate" msgstr "Negovať" #: lib/solaar/ui/diversion_rules.py:634 msgid "Wrap with" msgstr "Obaliť čím" #: lib/solaar/ui/diversion_rules.py:656 msgid "Cut" msgstr "Vystrihnúť" #: lib/solaar/ui/diversion_rules.py:671 msgid "Paste" msgstr "Vložiť" #: lib/solaar/ui/diversion_rules.py:683 msgid "Copy" msgstr "Kopírovať" #: 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/diversion_rules.py:1138 msgid "Number of seconds to delay." msgstr "Počet sekúnd oneskorenia." #: lib/solaar/ui/diversion_rules.py:1177 msgid "Not" msgstr "Opak" #: 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/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/diversion_rules.py:1235 msgid "MouseProcess" msgstr "ProcesMyši" #: 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 "bytes %(0)d to %(1)d, ranging from %(2)d to %(3)d" msgstr "" #: lib/solaar/ui/diversion_rules.py:1562 msgid "mask" msgstr "maska" #: 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 "typ" #: 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 "Pridať pohyb" #: 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 "Pridať kláves" #: lib/solaar/ui/diversion_rules.py:1772 msgid "Click" msgstr "Kliknutie" #: lib/solaar/ui/diversion_rules.py:1775 msgid "Depress" msgstr "Stlačiť" #: lib/solaar/ui/diversion_rules.py:1778 msgid "Release" msgstr "Uvoľniť" #: 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/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/diversion_rules.py:1921 msgid "Button" msgstr "Tlačidlo" #: lib/solaar/ui/diversion_rules.py:1922 msgid "Count and Action" msgstr "Počet a Akcia" #: lib/solaar/ui/diversion_rules.py:1972 msgid "Execute a command with arguments." msgstr "Vykonať príkaz s argumentami." #: lib/solaar/ui/diversion_rules.py:1975 msgid "Add argument" msgstr "Pridať argument" #: lib/solaar/ui/diversion_rules.py:2050 msgid "Toggle" msgstr "Prepínač" #: lib/solaar/ui/diversion_rules.py:2050 msgid "True" msgstr "Pravda" #: lib/solaar/ui/diversion_rules.py:2051 msgid "False" msgstr "Nepravda" #: lib/solaar/ui/diversion_rules.py:2065 msgid "Unsupported setting" msgstr "Nepodporované nastavenie" #: 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 "Zariadenie je aktívne a jeho nastavenia možno meniť." #: 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 "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 "%(receiver_name)s: pair new device" msgstr "%(receiver_name)s: spárovať nové zariadenie" #: lib/solaar/ui/pair_window.py:123 #, python-format msgid "Enter passcode on %(name)s." msgstr "Vložte tajný kód na %(name)s." #: lib/solaar/ui/pair_window.py:126 #, python-format 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 "Voltage reported by battery" msgstr "Napätie hlásené batériou" #: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "Úroveň batérie" #: 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:705 lib/solaar/ui/window.py:707 msgid "next reported " msgstr "ďalšie hlásené " #: 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:713 msgid "last known" msgstr "naposledy známa" #: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "šifrované" #: 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:728 msgid "not encrypted" msgstr "bez šifrovania" #: 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:748 #, python-format 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" #~ "pomocou bezdrôtového prijímača Logitech." #~ 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" Solaar-1.1.20/po/solaar.pot000066400000000000000000001374111522022367600154530ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "Project-Id-Version: solaar 1.1.19\n" "Report-Msgid-Bugs-To: \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" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: lib/logitech_receiver/base_usb.py:52 msgid "Bolt Receiver" msgstr "" #: lib/logitech_receiver/base_usb.py:64 msgid "Unifying 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 "" #: lib/logitech_receiver/base_usb.py:125 msgid "Lightspeed Receiver" msgstr "" #: lib/logitech_receiver/base_usb.py:136 msgid "EX100 Receiver 27 Mhz" msgstr "" #: 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: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 "" #: 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: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] "" msgstr[1] "" #: 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 "" #: 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/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/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/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/common.py:46 msgid "If you've just installed Solaar, try disconnecting the receiver or " "device and then reconnecting it." msgstr "" #: 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 "" #: 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 "" #: 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] "" msgstr[1] "" #: 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 "" #: 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 "" #: 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 "" #: 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 "" #: lib/solaar/ui/pair_window.py:46 msgid "Bolt receivers are only compatible with Bolt devices." msgstr "" #: 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 "" #: 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] "" msgstr[1] "" #: lib/solaar/ui/pair_window.py:78 msgid "\n" "Cancelling at this point will not use up a pairing." msgstr "" #: 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:60 lib/solaar/ui/window.py:328 #, python-format msgid "About %s" msgstr "" #: lib/solaar/ui/tray.py:61 lib/solaar/ui/window.py:326 #, python-format msgid "Quit %s" msgstr "" #: 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 "" #: 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 "" #: 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 "" #: 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] "" msgstr[1] "" #: 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] "" msgstr[1] "" #: 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 "" #: lib/solaar/ui/window.py:737 #, python-format msgid "%(light_level)d lux" msgstr "" Solaar-1.1.20/po/sr.po000066400000000000000000001547131522022367600144360ustar00rootroot00000000000000# Language rs translations for solaar package. # Copyright (C) 2022 THE solaar'S COPYRIGHT HOLDER # 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: 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:46 msgid "Bolt Receiver" msgstr "Bolt 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" #: lib/logitech_receiver/base_usb.py:124 msgid "Lightspeed Receiver" msgstr "Lightspeed prijemnik" #: 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" #: lib/logitech_receiver/i18n.py:31 msgid "critical" msgstr "kritično" #: lib/logitech_receiver/i18n.py:32 msgid "low" msgstr "slabo" #: lib/logitech_receiver/i18n.py:33 msgid "average" msgstr "prosečna" #: lib/logitech_receiver/i18n.py:34 msgid "good" msgstr "dobra" #: lib/logitech_receiver/i18n.py:35 msgid "full" msgstr "puna" #: lib/logitech_receiver/i18n.py:38 msgid "discharging" msgstr "pražnjenje" #: lib/logitech_receiver/i18n.py:39 msgid "recharging" 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" #: lib/logitech_receiver/i18n.py:42 msgid "almost full" msgstr "skoro puna" #: lib/logitech_receiver/i18n.py:43 msgid "charged" msgstr "napunjeno" #: lib/logitech_receiver/i18n.py:44 msgid "slow recharge" msgstr "sporo punjenje" #: lib/logitech_receiver/i18n.py:45 msgid "invalid battery" msgstr "neispravna baterija" #: lib/logitech_receiver/i18n.py:46 msgid "thermal error" msgstr "toplotna greška" #: lib/logitech_receiver/i18n.py:47 msgid "error" msgstr "greška" #: lib/logitech_receiver/i18n.py:48 msgid "standard" msgstr "standardno" #: lib/logitech_receiver/i18n.py:49 msgid "fast" msgstr "brzo" #: lib/logitech_receiver/i18n.py:50 msgid "slow" msgstr "sporo" #: lib/logitech_receiver/i18n.py:53 msgid "device timeout" msgstr "istek čekanja uređaja" #: lib/logitech_receiver/i18n.py:54 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" #: lib/logitech_receiver/i18n.py:56 msgid "sequence timeout" msgstr "istek vremenske sekvence" #: 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" #: lib/logitech_receiver/i18n.py:61 msgid "Hardware" msgstr "Hardver" #: lib/logitech_receiver/i18n.py:62 msgid "Other" msgstr "Ostalo" #: lib/logitech_receiver/i18n.py:65 msgid "Left Button" msgstr "Levo dugme" #: lib/logitech_receiver/i18n.py:66 msgid "Right Button" msgstr "Desno dugme" #: lib/logitech_receiver/i18n.py:67 msgid "Middle Button" msgstr "Srednje dugme" #: lib/logitech_receiver/i18n.py:68 msgid "Back Button" msgstr "Dugme za unazad" #: lib/logitech_receiver/i18n.py:69 msgid "Forward Button" msgstr "Dugme za unapred" #: lib/logitech_receiver/i18n.py:70 msgid "Mouse Gesture Button" msgstr "Dugme za pokrete miša" #: lib/logitech_receiver/i18n.py:71 msgid "Smart Shift" msgstr "Pametan pomak" #: lib/logitech_receiver/i18n.py:72 msgid "DPI Switch" msgstr "DPI svitch" #: lib/logitech_receiver/i18n.py:73 msgid "Left Tilt" msgstr "Levi nagib" #: lib/logitech_receiver/i18n.py:74 msgid "Right Tilt" msgstr "Desni nagib" #: lib/logitech_receiver/i18n.py:75 msgid "Left Click" msgstr "Levi lik" #: lib/logitech_receiver/i18n.py:76 msgid "Right Click" msgstr "Desni klik" #: lib/logitech_receiver/i18n.py:77 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" #: lib/logitech_receiver/i18n.py:79 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" #: lib/logitech_receiver/i18n.py:81 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" #: lib/logitech_receiver/i18n.py:85 msgid "pressed" msgstr "pritisnuto" #: lib/logitech_receiver/i18n.py:86 msgid "released" msgstr "otpušteno" #: 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:75 #: lib/logitech_receiver/notifications.py:126 msgid "pairing lock is open" msgstr "uparivanje je otvoreno" #: lib/logitech_receiver/notifications.py:92 msgid "discovery lock is closed" msgstr "otkrivanje je otvoreno" #: lib/logitech_receiver/notifications.py:92 msgid "discovery lock is open" msgstr "otkrivanje je zatvoreno" #: 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 "odspojeno" #: lib/logitech_receiver/notifications.py:262 lib/solaar/ui/notify.py:120 msgid "unpaired" msgstr "nije upareno" #: lib/logitech_receiver/notifications.py:304 msgid "powered on" msgstr "uključen" #: lib/logitech_receiver/settings.py:750 msgid "register" msgstr "registriraj se" #: 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" #: 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." #: 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." #: lib/logitech_receiver/settings_templates.py:149 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." #: lib/logitech_receiver/settings_templates.py:157 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." #: lib/logitech_receiver/settings_templates.py:165 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." #: lib/logitech_receiver/settings_templates.py:177 msgid "Sensitivity (DPI - older mice)" msgstr "Osetljivost (DPI - stariji miševi)" #: lib/logitech_receiver/settings_templates.py:178 #: 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" #: 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." #: lib/logitech_receiver/settings_templates.py:219 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." #: 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" #: 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" #: 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ć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" #: lib/logitech_receiver/settings_templates.py:257 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" #: 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ćuSolaar pravila, ali se inače ignorišu." #: lib/logitech_receiver/settings_templates.py:288 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)." #: lib/logitech_receiver/settings_templates.py:299 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)." #: lib/logitech_receiver/settings_templates.py:310 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" #: lib/logitech_receiver/settings_templates.py:319 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" #: lib/logitech_receiver/settings_templates.py:330 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." #: 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 "Možda će biti potrebni ugrađeni profili podešeni na 'Onemogućeno' da " "bi bili efikasni." #: lib/logitech_receiver/settings_templates.py:365 msgid "Divert crown events" msgstr "Preusmerite crown događaje" #: 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:374 msgid "Crown smooth scroll" msgstr "Crown glatko pomicanje" #: 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 "Divert G Keys" msgstr "Preusmeri G tastere" #: 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: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: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 "Radnje tastera" #: 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:465 msgid "Overridden by diversion." msgstr "Preusmjeravanje tipki." #: 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:639 msgid "Key/Button Diversion" msgstr "Preusmeravanje tipki" #: 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: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:643 #: lib/logitech_receiver/settings_templates.py:644 msgid "Mouse Gestures" msgstr "Pokreti miša" #: 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:643 msgid "Sliding DPI" msgstr "Klizni DPI" #: lib/logitech_receiver/settings_templates.py:711 msgid "Sensitivity (DPI)" msgstr "Osetljivost (DPI)" #: lib/logitech_receiver/settings_templates.py:752 msgid "Sensitivity Switching" msgstr "Promena osetljivosti" #: 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: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." #: 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: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: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 #: 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:1053 #, python-format msgid "Lights up the %s key." msgstr "Osvetli %s taster." #: lib/logitech_receiver/settings_templates.py:1074 msgid "MR-Key LED" msgstr "LED MR-tastera" #: 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:1095 msgid "Persistent Key/Button Mapping" msgstr "Trajno mapiranje tastera/dugmada" #: lib/logitech_receiver/settings_templates.py:1097 msgid "Permanently change the mapping for the key or button." msgstr "Trajno promenite mapiranje za taster ili dugme." #: 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:1157 msgid "Sidetone" msgstr "Bočni ton" #: lib/logitech_receiver/settings_templates.py:1158 msgid "Set sidetone level." msgstr "Podesite nivo bočnih tonova." #: lib/logitech_receiver/settings_templates.py:1167 msgid "Equalizer" msgstr "Ekvilajzer" #: lib/logitech_receiver/settings_templates.py:1168 msgid "Set equalizer levels." msgstr "Podesite nivo ekvilajzera." #: 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 uparen uređaj." msgstr[1] "%(count)s uparena uređaja." #: lib/logitech_receiver/status.py:170 #, python-format msgid "Battery: %(level)s" msgstr "Baterija: %(level)s" #: lib/logitech_receiver/status.py:172 #, python-format msgid "Battery: %(percent)d%%" msgstr "Baterija: %(percent)d%%" #: lib/logitech_receiver/status.py:184 #, python-format msgid "Lighting: %(level)s lux" msgstr "Osvetljenje %(level)s lux" #: lib/logitech_receiver/status.py:239 #, python-format msgid "Battery: %(level)s (%(status)s)" msgstr "Baterija: %(level)s (%(status)s)" #: lib/logitech_receiver/status.py:241 #, python-format msgid "Battery: %(percent)d%% (%(status)s)" msgstr "Baterija: %(percent)d%% (%(status)s)" #: lib/solaar/ui/__init__.py:52 msgid "Permissions error" msgstr "Greška u dozvolama" #: 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 "" #: 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 #, 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: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 "Uparivanje neuspelo" #: lib/solaar/ui/__init__.py:66 #, python-brace-format msgid "Failed to unpair %{device} from %{receiver}." msgstr "Odparivanje %{device} sa %{receiver} nije uspelo." #: 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." #: lib/solaar/ui/about.py:44 msgid "Additional Programming" msgstr "Dodatno programiranje" #: lib/solaar/ui/about.py:45 msgid "GUI design" msgstr "GUI dizajn" #: lib/solaar/ui/about.py:47 msgid "Testing" msgstr "Testiranje" #: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Logitech dokumentacija" #: lib/solaar/ui/action.py:85 lib/solaar/ui/action.py:89 #: lib/solaar/ui/window.py:197 msgid "Unpair" msgstr "Odspoji" #: lib/solaar/ui/action.py:88 lib/solaar/ui/diversion_rules.py:150 msgid "Cancel" msgstr "Poništi" #: 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:212 msgid "Incomplete" msgstr "Nepotpuno" #: 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: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" #: lib/solaar/ui/diversion_rules.py:1562 msgid "mask" msgstr "mask" #: 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" #: 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 "tip" #: 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 "Dodaj tipku" #: lib/solaar/ui/diversion_rules.py:1772 msgid "Click" msgstr "Kliknite" #: lib/solaar/ui/diversion_rules.py:1775 msgid "Depress" msgstr "Odpustite" #: lib/solaar/ui/diversion_rules.py:1778 msgid "Release" msgstr "Odpustite" #: 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 "Taster" #: 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 "Dodajte argument" #: lib/solaar/ui/diversion_rules.py:2050 msgid "Toggle" msgstr "Uključi/Isključi" #: lib/solaar/ui/diversion_rules.py:2050 msgid "True" msgstr "Istina" #: 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" #: lib/solaar/ui/pair_window.py:123 #, python-format msgid "Enter passcode on %(name)s." msgstr "Unesite lozinku na %(name)s." #: 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." #: lib/solaar/ui/pair_window.py:129 msgid "left" msgstr "levo" #: lib/solaar/ui/pair_window.py:129 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" "a zatim istovremeno pritisnite levo i desno dugme." #: lib/solaar/ui/pair_window.py:188 msgid "Pairing failed" msgstr "Uparivanje neuspelo" #: 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: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: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:196 msgid "No further details are available about the error." msgstr "Nisu dostupni dodatni detalji o grešci." #: lib/solaar/ui/pair_window.py:210 msgid "Found a new device:" msgstr "Pronađen novi uređaj:" #: 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: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 "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." #: 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 supported device found" msgstr "" #: lib/solaar/ui/tray.py:64 lib/solaar/ui/window.py:319 #, python-format msgid "About %s" msgstr "O %s" #: lib/solaar/ui/tray.py:65 lib/solaar/ui/window.py:317 #, python-format msgid "Quit %s" msgstr "Zatvori %s" #: lib/solaar/ui/tray.py:297 lib/solaar/ui/tray.py:305 msgid "no receiver" msgstr "nema prijemnika" #: lib/solaar/ui/tray.py:321 msgid "no status" msgstr "nema statusa" #: lib/solaar/ui/window.py:96 msgid "Scanning" msgstr "Skeniranje" #: lib/solaar/ui/window.py:129 msgid "Battery" msgstr "Baterija" #: lib/solaar/ui/window.py:132 msgid "Wireless Link" msgstr "Bežična veza" #: lib/solaar/ui/window.py:136 msgid "Lighting" msgstr "Osvetljenje" #: lib/solaar/ui/window.py:170 msgid "Show Technical Details" msgstr "Prikaži tehničke detalje" #: lib/solaar/ui/window.py:186 msgid "Pair new device" msgstr "Uparite novi uređaj" #: lib/solaar/ui/window.py:205 msgid "Select a device" msgstr "Odaberite uređaj" #: lib/solaar/ui/window.py:322 msgid "Rule Editor" msgstr "Tekstualni uređivač prava" #: lib/solaar/ui/window.py:533 msgid "Path" msgstr "Put" #: 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 "Serijski" #: lib/solaar/ui/window.py:545 msgid "Index" msgstr "Indeks" #: 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 #, 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 "Brzina pozivanja" #: lib/solaar/ui/window.py:565 msgid "Unit ID" msgstr "Unit ID" #: lib/solaar/ui/window.py:576 msgid "none" msgstr "nema" #: lib/solaar/ui/window.py:577 msgid "Notifications" msgstr "Obaveštenja" #: lib/solaar/ui/window.py:621 msgid "No device paired." msgstr "Nijedan uređaj nije uparen." #: 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." #: 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: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." #: lib/solaar/ui/window.py:692 msgid "Battery Voltage" msgstr "Voltaža baterije" #: lib/solaar/ui/window.py:694 msgid "Voltage reported by battery" msgstr "Napon koji javlja baterija" #: lib/solaar/ui/window.py:696 msgid "Battery Level" msgstr "Nivo baterije" #: lib/solaar/ui/window.py:698 msgid "Approximate level reported by battery" msgstr "Baterija prijavljuje približan nivo" #: 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:708 msgid " and next level to be reported." msgstr " i sledeći nivo koji treba izvesti." #: lib/solaar/ui/window.py:713 msgid "last known" msgstr "poslednje poznato" #: lib/solaar/ui/window.py:724 msgid "encrypted" msgstr "šifrovano" #: 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" #: 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" #~ 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" Solaar-1.1.20/po/sv.po000066400000000000000000002060171522022367600144350ustar00rootroot00000000000000# Swedish translations for solaar package. # Copyright (C) 2013 THE solaar'S COPYRIGHT HOLDER # 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: 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:52 msgid "Bolt Receiver" msgstr "Bolt-mottagare" #: lib/logitech_receiver/base_usb.py:64 msgid "Unifying Receiver" msgstr "Unifying-mottagare" #: 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:125 msgid "Lightspeed Receiver" msgstr "Lightspeed-mottagare" #: 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 "not charging" msgstr "laddar inte" #: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "nästan full" #: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "laddad" #: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "långsam laddning" #: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "batterifel" #: lib/logitech_receiver/i18n.py:43 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 "fast" msgstr "snabb" #: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "långsam" #: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "enheten svarade inte" #: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "enheten stöds inte" #: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "för många enheter" #: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "tidsgräns för sekvens överskreds" #: lib/logitech_receiver/i18n.py:54 lib/solaar/ui/window.py:555 msgid "Firmware" msgstr "Fast programvara" #: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Startladdare" #: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Hårdvara" #: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "Annan" #: 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 "Middle Button" msgstr "Mittenknapp" #: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "Tillbakaknapp" #: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "Framåtknapp" #: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "Mus gestknapp" #: 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 "Right Tilt" msgstr "Höger lutning" #: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "Vänsterklick" #: lib/logitech_receiver/i18n.py:70 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 "Mouse Forward Button" msgstr "Mus framåtknapp" #: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "Gestknapp navigering" #: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "Vänster musrullningsknapp" #: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "Höger musrullningsknapp" #: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "nedtryckt" #: lib/logitech_receiver/i18n.py:79 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 "" "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 "" "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:154 msgid "Hand Detection" msgstr "Handavkänning" #: 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:162 msgid "Scroll Wheel Smooth Scrolling" msgstr "Rullningshjul mjuk rullning" #: 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 "Högkänsligt läge för vertikal rullning med hjulet." #: lib/logitech_receiver/settings_templates.py:170 msgid "Side Scrolling" msgstr "Sidrullning" #: 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:182 msgid "Sensitivity (DPI - older mice)" msgstr "Känslighet (DPI - äldre möss)" #: 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:256 msgid "Backlight Timed" msgstr "Bakgrundsbelysning tidsinställd" #: 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:268 msgid "Backlight" msgstr "Bakbelysning" #: 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:301 msgid "Automatic" msgstr "Automatiskt" #: lib/logitech_receiver/settings_templates.py:303 msgid "Manual" msgstr "Manuellt" #: lib/logitech_receiver/settings_templates.py:305 msgid "Enabled" msgstr "Aktiverad" #: lib/logitech_receiver/settings_templates.py:311 msgid "Backlight Level" msgstr "Bakgrundsbelysningsnivå" #: 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:369 msgid "Backlight Delay Hands Out" msgstr "Fördröjning av bakgrundsbelysning händer ute" #: 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:378 msgid "Backlight Delay Hands In" msgstr "Fördröjning av bakgrundsbelysning händer inne" #: 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 "Inaktiverar tangenten %s." #: 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: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:1247 msgid "Change Host" msgstr "Byt värd" #: 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/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 "Lights up the %s key." msgstr "Lyser upp tangenten %s." #: 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 "" "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/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 "" "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/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/solaar/ui/common.py:56 msgid "Unpairing failed" msgstr "Kunde inte bryta parkoppling" #: 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}." #: 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/config_panel.py:245 msgid "Complete - ENTER to change" msgstr "Komplett - ENTER för att ändra" #: lib/solaar/ui/config_panel.py:245 msgid "Incomplete" msgstr "Ofullständig" #: 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 värde" msgstr[1] "%d värden" #: lib/solaar/ui/config_panel.py:642 msgid "Changes allowed" msgstr "Ändringar tillåtna" #: lib/solaar/ui/config_panel.py:643 msgid "No changes allowed" msgstr "Inga ändringar tillåtna" #: lib/solaar/ui/config_panel.py:644 msgid "Ignore this setting" msgstr "Ignorera denna inställning" #: lib/solaar/ui/config_panel.py:690 msgid "Working" msgstr "Fungerar" #: lib/solaar/ui/config_panel.py:693 msgid "Read/write operation failed." msgstr "Läs-/skrivoperationen misslyckades." #: lib/solaar/ui/desktop_notifications.py:112 msgid "unspecified reason" msgstr "ospecificerad anledning" #: lib/solaar/ui/diversion_rules.py:103 msgid "Built-in rules" msgstr "Inbyggda regler" #: lib/solaar/ui/diversion_rules.py:103 msgid "User-defined rules" msgstr "Användardefinierade regler" #: lib/solaar/ui/diversion_rules.py:105 lib/solaar/ui/diversion_rules.py:1126 msgid "Rule" msgstr "Regel" #: 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:108 msgid "[empty]" msgstr "[tom]" #: lib/solaar/ui/diversion_rules.py:132 msgid "Make changes permanent?" msgstr "Göra ändringarna permanenta?" #: lib/solaar/ui/diversion_rules.py:137 msgid "Yes" msgstr "Ja" #: lib/solaar/ui/diversion_rules.py:139 msgid "No" msgstr "Nej" #: 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:273 msgid "Paste here" msgstr "Klistra in här" #: 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" msgstr "Infoga" #: 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: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:386 msgid "Condition" msgstr "Villkor" #: lib/solaar/ui/diversion_rules.py:388 lib/solaar/ui/rule_conditions.py:154 msgid "Feature" msgstr "Funktion" #: lib/solaar/ui/diversion_rules.py:389 lib/solaar/ui/rule_conditions.py:188 msgid "Report" msgstr "Rapport" #: lib/solaar/ui/diversion_rules.py:390 lib/solaar/ui/rule_conditions.py:69 msgid "Process" msgstr "Process" #: lib/solaar/ui/diversion_rules.py:391 msgid "Mouse process" msgstr "Musprocess" #: lib/solaar/ui/diversion_rules.py:392 lib/solaar/ui/rule_conditions.py:225 msgid "Modifiers" msgstr "Modifierare" #: lib/solaar/ui/diversion_rules.py:393 lib/solaar/ui/rule_conditions.py:277 msgid "Key" msgstr "Tangent" #: lib/solaar/ui/diversion_rules.py:394 lib/solaar/ui/rule_conditions.py:318 msgid "KeyIsDown" msgstr "KeyIsDown" #: 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 "Wrap with" msgstr "Omslut med" #: lib/solaar/ui/diversion_rules.py:537 msgid "Cut" msgstr "Klipp ut" #: lib/solaar/ui/diversion_rules.py:553 msgid "Paste" msgstr "Klistra in" #: lib/solaar/ui/diversion_rules.py:559 msgid "Copy" msgstr "Kopiera" #: lib/solaar/ui/diversion_rules.py:568 msgid "Solaar Rule Editor" msgstr "Solaar regelredigerare" #: lib/solaar/ui/diversion_rules.py:668 msgid "Save changes" msgstr "Spara ändringar" #: lib/solaar/ui/diversion_rules.py:673 msgid "Discard changes" msgstr "Släng ändringar" #: 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: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:1207 msgid "Not" msgstr "Inte" #: lib/solaar/ui/diversion_rules.py:1238 msgid "Toggle" msgstr "Växla" #: lib/solaar/ui/diversion_rules.py:1239 msgid "True" msgstr "Sant" #: lib/solaar/ui/diversion_rules.py:1240 msgid "False" msgstr "Falskt" #: lib/solaar/ui/diversion_rules.py:1253 msgid "Unsupported setting" msgstr "Inställning som inte stöds" #: 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: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:1437 msgid "Device that originated the current notification." msgstr "Enhet som skapade det aktuella meddelandet." #: lib/solaar/ui/diversion_rules.py:1450 msgid "Name of host computer." msgstr "Namn på värddator." #: lib/solaar/ui/diversion_rules.py:1520 msgid "Value" msgstr "Värde" #: lib/solaar/ui/diversion_rules.py:1529 msgid "Item" msgstr "Objekt" #: lib/solaar/ui/diversion_rules.py:1808 msgid "Change setting on device" msgstr "Ändra inställning på enheten" #: lib/solaar/ui/diversion_rules.py:1824 msgid "Setting on device" msgstr "Inställning på enheten" #: 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: parkoppla ny enhet" #: 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: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 parkoppling kvar." msgstr[1] "" "\n" "\n" "Denna mottagare har %d parkopplingar kvar." #: 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:168 #, python-format msgid "Enter passcode on %(name)s." msgstr "Ange lösenkoden på %(name)s." #: 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/pair_window.py:176 msgid "left" msgstr "vänster" #: lib/solaar/ui/pair_window.py:176 msgid "right" msgstr "höger" #: 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/pair_window.py:221 msgid "The wireless link is not encrypted" msgstr "Den trådlösa anslutningen är inte krypterad" #: lib/solaar/ui/pair_window.py:226 msgid "Found a new device:" msgstr "Hittade en ny enhet:" #: 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 "Wireless Link" msgstr "Trådlös anslutning" #: lib/solaar/ui/window.py:148 msgid "Lighting" msgstr "Belysning" #: lib/solaar/ui/window.py:182 msgid "Show Technical Details" msgstr "Visa tekniska detaljer" #: 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 "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 "Polling rate" msgstr "Uppdateringshastighet" #: lib/solaar/ui/window.py:548 msgid "Unit ID" msgstr "Enhets-ID" #: lib/solaar/ui/window.py:559 msgid "none" msgstr "ingen" #: lib/solaar/ui/window.py:560 msgid "Notifications" msgstr "Aviseringar" #: lib/solaar/ui/window.py:604 msgid "No device paired." msgstr "Ingen enhet parkopplad." #: 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] "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: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:624 #, python-format 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:681 msgid "Battery Voltage" msgstr "Batterispänning" #: lib/solaar/ui/window.py:683 msgid "Voltage reported by battery" msgstr "Spänning rapporterad av batteri" #: 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 "last known" msgstr "senast kända" #: lib/solaar/ui/window.py:713 msgid "encrypted" msgstr "krypterad" #: 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:717 msgid "not encrypted" msgstr "inte krypterad" #: 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:737 #, python-format msgid "%(light_level)d lux" msgstr "%(light_level)d lux" #~ msgid "Turn illumination on or off on keyboard." #~ msgstr "Slå på eller stäng av belysningen på tangentbordet." #~ msgid "" #~ "Enable onboard profiles, which often control report rate and keyboard " #~ "lighting" #~ msgstr "" #~ "Aktivera inbyggda profiler, som ofta styr rapporthastighet och " #~ "tangentbordsbelysning" #~ msgid "Polling Rate (ms)" #~ msgstr "Uppdateringsfrekvens (ms)" #~ msgid "Frequency of device polling, in milliseconds" #~ msgstr "Frekvens för enhetsavfrågning, i millisekunder" #~ msgid "Divert G Keys" #~ msgstr "Avleda G-tangenter" #~ 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 "May also make M keys and MR key send HID++ notifications" #~ msgstr "" #~ "Kan också få M-tangenter och MR-tangenten att skicka HID++-aviseringar" #, python-format #~ msgid "Battery: %(level)s" #~ msgstr "Batteri: %(level)s" #, python-format #~ msgid "Battery: %(percent)d%%" #~ msgstr "Batteri: %(percent)d%%" #, python-format #~ msgid "Lighting: %(level)s lux" #~ msgstr "Belysning: %(level)s lux" #~ msgid "Number of seconds to delay." #~ msgstr "Antal sekunder att fördröja." #~ msgid "Count and Action" #~ msgstr "Antal och åtgärd" #~ 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 "Turn on the device you want to pair." #~ msgstr "Slå på enheten du vill parkoppla." #, python-format #~ 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" #~ "Denna mottagare har %d parning(er) kvar." #~ msgid " paired devices." #~ msgstr "parkopplade enheter." #~ msgid "%(battery_level)s" #~ msgstr "%(battery_level)s" #~ msgid "%(battery_percent)d%%" #~ msgstr "%(battery_percent)d%%" #~ msgid "1 paired device." #~ msgstr "1 parkopplad enhet." #~ msgid "Actions" #~ msgstr "Åtgärder" #~ 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 "Count" #~ msgstr "Antal" #~ msgid "Effectively turns off thumb scrolling in Linux." #~ msgstr "Stänger effektivt av tumrullning i Linux." #~ msgid "Effectively turns off wheel scrolling in Linux." #~ msgstr "Stänger effektivt av rullning i Linux." #~ 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 "" #~ "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 "Found a new device" #~ msgstr "Ny enhet har hittats" #~ msgid "HID++ Scrolling" #~ msgstr "HID++ rullning" #~ msgid "HID++ Thumb Scrolling" #~ msgstr "HID++ thumrullning" #~ msgid "HID++ mode for horizontal scroll with the thumb wheel." #~ msgstr "HID ++ -läge för horisontell rullning med tumhjulet." #~ msgid "HID++ mode for vertical scroll with the wheel." #~ msgstr "HID++ läge för vertikal rullning med hjulet." #~ msgid "High Resolution Scrolling" #~ msgstr "Rullning med hög upplösning" #~ msgid "High Resolution Wheel Invert" #~ msgstr "Invertera hög hjulupplösning" #~ msgid "High-sensitivity wheel invert direction for vertical scroll." #~ msgstr "Invertera riktning för vertikal rullning med hög känslighet." #~ msgid "High-sensitivity wheel invert mode for vertical scroll." #~ msgstr "Inverteringsläge med hög känslighet för vertikal rullning." #~ 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 "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" Solaar-1.1.20/po/tr.po000066400000000000000000001633021522022367600144310ustar00rootroot00000000000000# Turkish translations for solaar package. # Copyright (C) 2015 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # Automatically generated, 2015. # # Behzat Erte, , 2015. # Osman Karagöz, , 2024. # msgid "" msgstr "" "Project-Id-Version: solaar 0.9.2\n" "Report-Msgid-Bugs-To: \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" "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.2\n" "X-Poedit-SourceCharset: UTF-8\n" #: lib/logitech_receiver/base_usb.py:45 msgid "Bolt Receiver" msgstr "Bolt Alıcı" #: lib/logitech_receiver/base_usb.py:57 msgid "Unifying Receiver" msgstr "Unifying Alıcı" #: 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:131 msgid "Lightspeed Receiver" msgstr "Lightspeed Alıcı" #: lib/logitech_receiver/base_usb.py:141 msgid "EX100 Receiver 27 Mhz" msgstr "EX100 27 Mhz Alıcı" #: 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:29 msgid "critical" msgstr "kritik" #: lib/logitech_receiver/i18n.py:30 msgid "low" msgstr "az" #: lib/logitech_receiver/i18n.py:31 msgid "average" msgstr "ortalama" #: lib/logitech_receiver/i18n.py:32 msgid "good" msgstr "iyi" #: lib/logitech_receiver/i18n.py:33 msgid "full" msgstr "dolu" #: lib/logitech_receiver/i18n.py:35 msgid "discharging" msgstr "boşalıyor" #: lib/logitech_receiver/i18n.py:36 msgid "recharging" msgstr "yeniden şarj ediliyor" #: lib/logitech_receiver/i18n.py:37 msgid "charging" msgstr "şarj ediliyor" #: lib/logitech_receiver/i18n.py:38 msgid "not charging" msgstr "şarj edilmiyor" #: lib/logitech_receiver/i18n.py:39 msgid "almost full" msgstr "nerdeyse dolu" #: lib/logitech_receiver/i18n.py:40 msgid "charged" msgstr "şarj edildi" #: lib/logitech_receiver/i18n.py:41 msgid "slow recharge" msgstr "yavaş yeniden şarj edilme" #: lib/logitech_receiver/i18n.py:42 msgid "invalid battery" msgstr "geçersiz pil" #: lib/logitech_receiver/i18n.py:43 msgid "thermal error" msgstr "sıcaklık hatası" #: lib/logitech_receiver/i18n.py:44 msgid "error" msgstr "hata" #: lib/logitech_receiver/i18n.py:45 msgid "standard" msgstr "standart" #: lib/logitech_receiver/i18n.py:46 msgid "fast" msgstr "hızlı" #: lib/logitech_receiver/i18n.py:47 msgid "slow" msgstr "yavaş" #: lib/logitech_receiver/i18n.py:49 msgid "device timeout" msgstr "aygıt zaman aşımı" #: lib/logitech_receiver/i18n.py:50 msgid "device not supported" msgstr "aygıt desteklenmiyor" #: lib/logitech_receiver/i18n.py:51 msgid "too many devices" msgstr "çok fazla aygıt" #: lib/logitech_receiver/i18n.py:52 msgid "sequence timeout" msgstr "sıralama zaman aşımı" #: lib/logitech_receiver/i18n.py:54 msgid "Firmware" msgstr "Donanım Yazılımı" #: lib/logitech_receiver/i18n.py:55 msgid "Bootloader" msgstr "Önyükleme Yazılımı" #: lib/logitech_receiver/i18n.py:56 msgid "Hardware" msgstr "Donanım" #: lib/logitech_receiver/i18n.py:57 msgid "Other" msgstr "Diğer" #: lib/logitech_receiver/i18n.py:59 msgid "Left Button" msgstr "Sol Düğme" #: lib/logitech_receiver/i18n.py:60 msgid "Right Button" msgstr "Sag Düğme" #: lib/logitech_receiver/i18n.py:61 msgid "Middle Button" msgstr "Orta Düğme" #: lib/logitech_receiver/i18n.py:62 msgid "Back Button" msgstr "Geri Düğmesi" #: lib/logitech_receiver/i18n.py:63 msgid "Forward Button" msgstr "İleri Düğmesi" #: lib/logitech_receiver/i18n.py:64 msgid "Mouse Gesture Button" msgstr "Fare Hareketi Düğmesi" #: lib/logitech_receiver/i18n.py:65 msgid "Smart Shift" msgstr "Akıllı Kaydırma" #: lib/logitech_receiver/i18n.py:66 msgid "DPI Switch" msgstr "DPI Anahtarı" #: lib/logitech_receiver/i18n.py:67 msgid "Left Tilt" msgstr "Sola Eğme" #: lib/logitech_receiver/i18n.py:68 msgid "Right Tilt" msgstr "Sağa Eğme" #: lib/logitech_receiver/i18n.py:69 msgid "Left Click" msgstr "Sol Tıklama" #: lib/logitech_receiver/i18n.py:70 msgid "Right Click" msgstr "Sağ Tıklama" #: lib/logitech_receiver/i18n.py:71 msgid "Mouse Middle Button" msgstr "Fare Orta Düğmesi" #: lib/logitech_receiver/i18n.py:72 msgid "Mouse Back Button" msgstr "Fare Geri Düğmesi" #: lib/logitech_receiver/i18n.py:73 msgid "Mouse Forward Button" msgstr "Fare İleri Düğmesi" #: lib/logitech_receiver/i18n.py:74 msgid "Gesture Button Navigation" msgstr "Hareket Düğmesi Navigasyonu" #: lib/logitech_receiver/i18n.py:75 msgid "Mouse Scroll Left Button" msgstr "Fare Tekerleği Sol Düğmesi" #: lib/logitech_receiver/i18n.py:76 msgid "Mouse Scroll Right Button" msgstr "Fare Tekerleği Sağ Düğmesi" #: lib/logitech_receiver/i18n.py:78 msgid "pressed" msgstr "basıldı" #: lib/logitech_receiver/i18n.py:79 msgid "released" msgstr "bırakıldı" #: 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:64 #: lib/logitech_receiver/notifications.py:116 msgid "pairing lock is open" msgstr "eşleştirme kilidi açık" #: lib/logitech_receiver/notifications.py:81 msgid "discovery lock is closed" msgstr "keşif kilidi kapalı" #: lib/logitech_receiver/notifications.py:81 msgid "discovery lock is open" msgstr "keşif kilidi açık" #: lib/logitech_receiver/notifications.py:210 lib/solaar/listener.py:238 msgid "connected" msgstr "bağlı" #: lib/logitech_receiver/notifications.py:210 lib/solaar/listener.py:238 msgid "disconnected" msgstr "bağlantı kesildi" #: lib/logitech_receiver/notifications.py:236 msgid "unpaired" msgstr "eşleşmemiş" #: lib/logitech_receiver/notifications.py:283 msgid "powered on" msgstr "açık" #: 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:625 lib/logitech_receiver/settings.py:651 msgid "feature" msgstr "özellik" #: lib/logitech_receiver/settings_templates.py:122 msgid "Swap Fx function" msgstr "Fx işlevlerini ters çevir" #: 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." 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: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." 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:138 msgid "Hand Detection" msgstr "El Algılama" #: 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:146 msgid "Scroll Wheel Smooth Scrolling" msgstr "Kaydırma Tekerleği Yumuşak Kaydırma" #: 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:154 msgid "Side Scrolling" msgstr "Yatay Kaydırma" #: 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 yatay kaydırma komutu " "yerine\n" "özel tuş komutunu gönderir." #: lib/logitech_receiver/settings_templates.py:166 msgid "Sensitivity (DPI - older mice)" msgstr "Hassasiyet (DPI - eski fare)" #: 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: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: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:285 msgid "Automatic" msgstr "Otomatik" #: 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 "Kaydırma Tekerleği Yüksek Çözünürlük" #: 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: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:405 msgid "" "Make scroll wheel send LOWRES_WHEEL HID++ notifications (which trigger " "Solaar rules but are otherwise ignored)." msgstr "" "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:412 msgid "Scroll Wheel Direction" msgstr "Kaydırma Tekerleği Yönü" #: lib/logitech_receiver/settings_templates.py:413 msgid "Invert direction for vertical scroll with wheel." msgstr "Kaydırma tekerleği ile dikey kaydırma yönünü tersine çevirir." #: 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:436 msgid "" "Make scroll wheel send HIRES_WHEEL HID++ notifications (which trigger Solaar " "rules but are otherwise ignored)." msgstr "" "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:445 msgid "Sensitivity (Pointer Speed)" msgstr "Hassasiyet (İşaretçi Hızı)" #: 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:456 msgid "Thumb Wheel Diversion" msgstr "Başparmak Tekerleği Yönlendirme" #: 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ğ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:467 msgid "Thumb Wheel Direction" msgstr "Başparmak Tekerleği Yönü" #: lib/logitech_receiver/settings_templates.py:468 msgid "Invert thumb wheel scroll direction." msgstr "Kaydırma tekerleği ile başparmak kaydırma yönünü tersine çevirir." #: lib/logitech_receiver/settings_templates.py:488 msgid "Onboard Profiles" msgstr "Yerleşik Profiller" #: lib/logitech_receiver/settings_templates.py:489 msgid "" "Enable an onboard profile, which controls report rate, sensitivity, and " "button actions" msgstr "" "Yerleşik profilleri etkinleştir, bu rapor hızını, hassasiyeti ve düğme " "etkinliğini kontrol eder" #: 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: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: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:596 msgid "Divert crown events" msgstr "Kadran etkinliklerini yönlendir" #: lib/logitech_receiver/settings_templates.py:597 msgid "" "Make crown send CROWN HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." msgstr "" "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:605 msgid "Crown smooth scroll" msgstr "Kadran yumuşak kaydırma" #: 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:614 msgid "Divert G and M Keys" msgstr "G ve M Tuşlarını Yönlendir" #: lib/logitech_receiver/settings_templates.py:615 msgid "" "Make G and M keys send HID++ notifications (which trigger Solaar rules but " "are otherwise ignored)." msgstr "" "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:629 msgid "Scroll Wheel Ratcheted" msgstr "Kaydırma Tekerleği Dönüşü" #: lib/logitech_receiver/settings_templates.py:630 msgid "" "Switch the mouse wheel between speed-controlled ratcheting and always " "freespin." msgstr "" "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: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: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:694 msgid "Overridden by diversion." msgstr "Yönlendirmeyle geçersiz kılındı." #: 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 "" "Önemli eylemleri değiştirmek (farenin sol tuşu gibi) sistemde " "kararsızlıklara yol açabilir." #: lib/logitech_receiver/settings_templates.py:862 msgid "Key/Button Diversion" msgstr "Tuş/Düğme Yönlendirme" #: 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 "" "Tuş veya düğmenin HID++ bildirimleri (yönlendirmeler) veya Fare Hareketleri " "başlatmalar veya Kaydırma DPI'si" #: 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:866 #: lib/logitech_receiver/settings_templates.py:867 msgid "Mouse Gestures" msgstr "Fare Hareketleri" #: 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:866 msgid "Sliding DPI" msgstr "Kaydırma DPI" #: 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:1059 msgid "Sensitivity Switching" msgstr "Hassasiyet Değiştirme" #: lib/logitech_receiver/settings_templates.py:1061 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 "" "Şuanki hassasiyeti ve hatırlanan hassasiyeti tuş veya düğmeye basıldığında " "değiştir.\n" "Eğer hatırlanan hassasiyet yoksa sadece şuanki hassasiyeti hatırla" #: lib/logitech_receiver/settings_templates.py:1065 msgid "Off" msgstr "Kapalı" #: lib/logitech_receiver/settings_templates.py:1096 msgid "Disable keys" msgstr "Tuşları devredışı bırak" #: 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:1100 #, python-format msgid "Disables the %s key." msgstr "%s tuşunu devredışı bırakır." #: 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: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:1183 msgid "Change Host" msgstr "Bağlanılan Bilgisayarı Değiştir" #: lib/logitech_receiver/settings_templates.py:1184 msgid "Switch connection to a different host" msgstr "Bağlantıyı başka bilgisayara değiştir" #: 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:1208 msgid "Single tap" msgstr "Tek dokunma" #: 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:1209 msgid "Single tap with two fingers" msgstr "İki parmakla tek dokunma" #: lib/logitech_receiver/settings_templates.py:1210 msgid "Single tap with three fingers" msgstr "Üç parmakla tek dokunma" #: lib/logitech_receiver/settings_templates.py:1214 msgid "Double tap" msgstr "Çift dokunma" #: 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:1215 msgid "Double tap with two fingers" msgstr "İki parmakla çift dokunma" #: lib/logitech_receiver/settings_templates.py:1216 msgid "Double tap with three fingers" msgstr "Üç parmakla çift dokunma" #: 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:1219 msgid "Tap and drag" msgstr "Dokun ve taşı" #: 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: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:1227 msgid "Suppress tap and edge gestures" msgstr "Dokunma ve kenar hareketlerini baskıla" #: 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: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:1231 #: lib/logitech_receiver/settings_templates.py:1234 msgid "Scroll with two fingers" msgstr "İki parmakla kaydır" #: 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:1232 msgid "Scrolls horizontally." msgstr "Yatay kaydırır." #: 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:1233 msgid "Scrolls vertically." msgstr "Dikey kaydırır." #: 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:1235 msgid "Natural scrolling" msgstr "Doğal kaydırma" #: lib/logitech_receiver/settings_templates.py:1236 msgid "Enables the thumbwheel." msgstr "Başparmak tekerleğini etkinleştirir." #: lib/logitech_receiver/settings_templates.py:1236 msgid "Thumbwheel" msgstr "Başparmak Tekerleği" #: 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:1248 msgid "Swipe from the left edge" msgstr "Sol kenardan vuruş" #: lib/logitech_receiver/settings_templates.py:1249 msgid "Swipe from the right edge" msgstr "Sağ kenardan vuruş" #: lib/logitech_receiver/settings_templates.py:1250 msgid "Swipe from the bottom edge" msgstr "Alt kenardan vuruş" #: 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:1253 msgid "Swipe two fingers from the right edge" msgstr "Sağ kenardan iki parmak vuruş" #: 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:1255 msgid "Swipe two fingers from the top edge" msgstr "Üst kenardan iki parmak vuruş" #: 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:1256 msgid "Zoom with two fingers." msgstr "İki parmakla yakınlaştırma." #: 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:1258 msgid "Spread to zoom in." msgstr "Yakınlaştırmak için yayın." #: lib/logitech_receiver/settings_templates.py:1259 msgid "Zoom with three fingers." msgstr "Üç parmakla yakınlaştırma." #: 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:1278 msgid "Pixel zone" msgstr "Piksel alanı" #: lib/logitech_receiver/settings_templates.py:1279 msgid "Ratio zone" msgstr "Oran alanı" #: lib/logitech_receiver/settings_templates.py:1280 msgid "Scale factor" msgstr "Ölçek katsayısı" #: lib/logitech_receiver/settings_templates.py:1280 msgid "Sets the cursor speed." msgstr "İmleç hızını ayarla." #: lib/logitech_receiver/settings_templates.py:1284 msgid "Left" msgstr "Sol" #: lib/logitech_receiver/settings_templates.py:1284 msgid "Left-most coordinate." msgstr "En soldaki koordinat." #: lib/logitech_receiver/settings_templates.py:1285 msgid "Bottom" msgstr "Alt" #: lib/logitech_receiver/settings_templates.py:1285 msgid "Bottom coordinate." msgstr "Alt koordinat." #: lib/logitech_receiver/settings_templates.py:1286 msgid "Width" msgstr "Genişlik" #: lib/logitech_receiver/settings_templates.py:1286 msgid "Width." msgstr "Genişlik." #: lib/logitech_receiver/settings_templates.py:1287 msgid "Height" msgstr "Yükseklik" #: lib/logitech_receiver/settings_templates.py:1287 msgid "Height." msgstr "Yükseklik." #: lib/logitech_receiver/settings_templates.py:1288 msgid "Cursor speed." msgstr "İmleç hızı." #: lib/logitech_receiver/settings_templates.py:1288 msgid "Scale" msgstr "Ölçek" #: lib/logitech_receiver/settings_templates.py:1294 msgid "Gestures" msgstr "Hareketler" #: 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:1311 msgid "Gestures Diversion" msgstr "Hareketleri Yönlendir" #: 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:1328 msgid "Gesture params" msgstr "Hareket parametreleri" #: 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:1353 msgid "M-Key LEDs" msgstr "M-Tuşu LED'leri" #: 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: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:1365 #, python-format msgid "Lights up the %s key." msgstr "%s tuşunu aydınlat." #: lib/logitech_receiver/settings_templates.py:1384 msgid "MR-Key LED" msgstr "MR-Tuşu LED'i" #: 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:1407 msgid "Persistent Key/Button Mapping" msgstr "Kalıcı Tuş/Düğme Haritalama" #: 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:1411 msgid "" "Changing important keys or buttons (such as for the left mouse button) can " "result in an unusable system." 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:1468 msgid "Sidetone" msgstr "Yan ton" #: lib/logitech_receiver/settings_templates.py:1469 msgid "Set sidetone level." msgstr "Yan ton seviyesini ayarla." #: lib/logitech_receiver/settings_templates.py:1478 msgid "Equalizer" msgstr "Ekolayzer" #: lib/logitech_receiver/settings_templates.py:1479 msgid "Set equalizer levels." msgstr "Ekolayzer seviyesini ayarla." #: lib/logitech_receiver/settings_templates.py:1501 msgid "Hz" msgstr "Hz" #: lib/logitech_receiver/settings_templates.py:1507 msgid "Power Management" msgstr "Güç Yönetimi" #: 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/settings_templates.py:1519 msgid "Brightness Control" msgstr "Parlaklık Kontrolü" #: lib/logitech_receiver/settings_templates.py:1520 msgid "Control overall brightness" msgstr "Genel parlaklığı kontrol et" #: lib/logitech_receiver/settings_templates.py:1563 #: lib/logitech_receiver/settings_templates.py:1617 msgid "LED Control" msgstr "LED Kontrolü" #: 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/settings_templates.py:1579 #: lib/logitech_receiver/settings_templates.py:1628 msgid "LED Zone Effects" msgstr "LED Bölge Efektleri" #: 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/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/logitech_receiver/settings_templates.py:1583 msgid "Speed" msgstr "Hız" #: lib/logitech_receiver/settings_templates.py:1584 msgid "Period" msgstr "Sıklık" #: lib/logitech_receiver/settings_templates.py:1585 msgid "Intensity" msgstr "Yoğunluk" #: lib/logitech_receiver/settings_templates.py:1586 msgid "Ramp" msgstr "Eğim" #: lib/logitech_receiver/settings_templates.py:1602 msgid "LEDs" msgstr "LEDler" #: lib/logitech_receiver/settings_templates.py:1639 msgid "Per-key Lighting" msgstr "Tuş başına Aydınlatma" #: 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: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:34 msgid "" "Manages Logitech receivers,\n" "keyboards, mice, and tablets." msgstr "" "Logitech alıcılarını, klavyelerini,\n" "farelerini ve tabletlerini yönetir." #: lib/solaar/ui/about.py:43 msgid "Additional Programming" msgstr "Ek Programlama" #: lib/solaar/ui/about.py:44 msgid "GUI design" msgstr "GUI tasarımı" #: lib/solaar/ui/about.py:46 msgid "Testing" msgstr "Sınama" #: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "Logitech belgeleri" #: 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:86 lib/solaar/ui/diversion_rules.py:103 msgid "Cancel" msgstr "Vazgeç" #: 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:228 msgid "Incomplete" msgstr "Tamamlanmamış" #: 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ğer" #: lib/solaar/ui/config_panel.py:603 msgid "Changes allowed" msgstr "Değişiklikler kabul ediliyor" #: lib/solaar/ui/config_panel.py:604 msgid "No changes allowed" msgstr "Değişiklik kabul edilmiyor" #: lib/solaar/ui/config_panel.py:605 msgid "Ignore this setting" msgstr "Bu ayarı görmezden gel" #: lib/solaar/ui/config_panel.py:649 msgid "Working" msgstr "Çalışıyor" #: lib/solaar/ui/config_panel.py:652 msgid "Read/write operation failed." msgstr "Okuma/yazma işlemi yapılamadı." #: lib/solaar/ui/diversion_rules.py:65 msgid "Built-in rules" msgstr "Varolan kurallar" #: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" msgstr "Kullanıcı tanımlı kurallar" #: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1033 msgid "Rule" msgstr "Kural" #: 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:70 msgid "[empty]" msgstr "[boş]" #: lib/solaar/ui/diversion_rules.py:94 msgid "Make changes permanent?" msgstr "Değişiklikleri kalıcı yap?" #: lib/solaar/ui/diversion_rules.py:99 msgid "Yes" msgstr "Evet" #: lib/solaar/ui/diversion_rules.py:101 msgid "No" msgstr "Hayır" #: 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: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:265 msgid "Discard changes" msgstr "Değişiklikleri gözardı et" #: lib/solaar/ui/diversion_rules.py:404 msgid "Insert here" msgstr "Buraya ekle" #: lib/solaar/ui/diversion_rules.py:406 msgid "Insert above" msgstr "Yukarıya ekle" #: lib/solaar/ui/diversion_rules.py:408 msgid "Insert below" msgstr "Aşağıya ekle" #: lib/solaar/ui/diversion_rules.py:414 msgid "Insert new rule here" msgstr "Buraya yeni kural ekle" #: lib/solaar/ui/diversion_rules.py:416 msgid "Insert new rule above" msgstr "Yukarıya yeni kural ekle" #: 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:463 msgid "Paste here" msgstr "Buraya yapıştır" #: lib/solaar/ui/diversion_rules.py:465 msgid "Paste above" msgstr "Yukarı yapıştır" #: lib/solaar/ui/diversion_rules.py:467 msgid "Paste below" msgstr "Aşağıya yapıştır" #: lib/solaar/ui/diversion_rules.py:473 msgid "Paste rule here" msgstr "Kuralı buraya yapıştır" #: lib/solaar/ui/diversion_rules.py:475 msgid "Paste rule above" msgstr "Kuralı yukarı yapıştır" #: 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:481 msgid "Paste rule" msgstr "Kuralı yapıştır" #: lib/solaar/ui/diversion_rules.py:510 msgid "Flatten" msgstr "Kapsamdan çıkar" #: lib/solaar/ui/diversion_rules.py:543 msgid "Insert" msgstr "Ekle" #: 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: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:549 msgid "Condition" msgstr "Durum" #: lib/solaar/ui/diversion_rules.py:551 lib/solaar/ui/rule_conditions.py:146 msgid "Feature" msgstr "Özellik" #: lib/solaar/ui/diversion_rules.py:552 lib/solaar/ui/rule_conditions.py:181 msgid "Report" msgstr "Rapor" #: 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:556 lib/solaar/ui/rule_conditions.py:270 msgid "Key" msgstr "Tuş" #: 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:558 lib/solaar/ui/diversion_rules.py:1359 msgid "Active" msgstr "Etkin" #: 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: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: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:1454 msgid "Item" msgstr "Öge" #: lib/solaar/ui/diversion_rules.py:1728 msgid "Change setting on device" msgstr "Aygıtın ayarını değiştir" #: 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: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: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: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: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: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:55 #, 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" "Bu alıcı %d aygıtla daha eşleşebilir." msgstr[1] "" "\n" "\n" "Bu alıcı %d aygıtla daha eşleşebilir." #: lib/solaar/ui/pair_window.py:61 msgid "" "\n" "Cancelling at this point will not use up a pairing." msgstr "" "\n" "Bu adımda vezgeçerseniz eşleşme kullanılamayacak." #: 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/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:56 lib/solaar/ui/window.py:308 #, python-format msgid "Quit %s" msgstr "%s Çıkış" #: lib/solaar/ui/tray.py:267 lib/solaar/ui/tray.py:275 msgid "no receiver" msgstr "alıcı yok" #: 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:91 msgid "Scanning" msgstr "Taranıyor" #: lib/solaar/ui/window.py:122 msgid "Battery" msgstr "Pil" #: lib/solaar/ui/window.py:125 msgid "Wireless Link" msgstr "Kablosuz Bağlantı" #: lib/solaar/ui/window.py:129 msgid "Lighting" msgstr "Aydınlatma" #: lib/solaar/ui/window.py:163 msgid "Show Technical Details" msgstr "Teknik Ayrıntıları Göster" #: lib/solaar/ui/window.py:179 msgid "Pair new device" msgstr "Yeni aygıt eşle" #: lib/solaar/ui/window.py:197 msgid "Select a device" msgstr "Aygıtı seç" #: lib/solaar/ui/window.py:313 msgid "Rule Editor" msgstr "Kural Düzenleyici" #: lib/solaar/ui/window.py:517 msgid "Path" msgstr "Yol" #: lib/solaar/ui/window.py:520 msgid "USB ID" msgstr "USB Kimliği" #: 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:529 msgid "Index" msgstr "Dizin" #: lib/solaar/ui/window.py:531 msgid "Wireless PID" msgstr "Kablosuz bağlantı kimliği" #: lib/solaar/ui/window.py:533 msgid "Product ID" msgstr "Ürün Kimliği" #: lib/solaar/ui/window.py:535 msgid "Protocol" msgstr "İletişim kuralı" #: lib/solaar/ui/window.py:535 msgid "Unknown" msgstr "Bilinmeyen" #: lib/solaar/ui/window.py:537 msgid "Polling rate" msgstr "Bağlantı sıklığı" #: lib/solaar/ui/window.py:544 msgid "Unit ID" msgstr "Birim Kimliği" #: lib/solaar/ui/window.py:558 msgid "Notifications" msgstr "Bildirimler" #: lib/solaar/ui/window.py:602 msgid "No device paired." msgstr "Eşleştirilmiş aygıt yok." #: 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: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:679 msgid "Battery Voltage" msgstr "Pil Gerilimi" #: lib/solaar/ui/window.py:681 msgid "Voltage reported by battery" msgstr "Pil tarafından bildirilen gerilim" #: lib/solaar/ui/window.py:683 msgid "Battery Level" msgstr "Pil Düzeyi" #: lib/solaar/ui/window.py:685 msgid "Approximate level reported by battery" msgstr "Pil tarafından bildirilen yaklaşık düzeyi" #: lib/solaar/ui/window.py:692 lib/solaar/ui/window.py:694 msgid "next reported " msgstr "sonraki rapor " #: lib/solaar/ui/window.py:695 msgid " and next level to be reported." msgstr " ve sonraki seviye raporlanması." #: lib/solaar/ui/window.py:711 msgid "encrypted" msgstr "şifreli" #: 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: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" Solaar-1.1.20/po/uk.po000066400000000000000000001523771522022367600144350ustar00rootroot00000000000000# 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 люкс" Solaar-1.1.20/po/zh_CN.po000066400000000000000000001702121522022367600150030ustar00rootroot00000000000000# Chinese translations for solaar package # Copyright (C) 2014 THE solaar'S COPYRIGHT HOLDER # This file is distributed under the same license as the solaar package. # Automatically generated, 2014. # ZH , 2014. # Rongrong , 2022. msgid "" msgstr "" "Project-Id-Version: solaar 1.1.13\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-07-01 13:49+0800\n" "PO-Revision-Date: 2026-05-24 01:16+0800\n" "Last-Translator: Rongrong \n" "Language-Team: none\n" "Language: zh_CN\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_CN\n" "X-Source-Language: C\n" "X-Generator: Poedit 3.9\n" #: lib/logitech_receiver/base_usb.py:45 msgid "Bolt Receiver" msgstr "Bolt 接收器" #: lib/logitech_receiver/base_usb.py:57 msgid "Unifying Receiver" msgstr "优联接收器" #: 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:131 msgid "Lightspeed Receiver" msgstr "Lightspeed 接收器" #: lib/logitech_receiver/base_usb.py:141 msgid "EX100 Receiver 27 Mhz" msgstr "EX100 接收器 (27MHz)" #: 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: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 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 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 "SmartShift" #: 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:64 #: lib/logitech_receiver/notifications.py:116 msgid "pairing lock is closed" msgstr "配对锁已关闭" #: lib/logitech_receiver/notifications.py:64 #: lib/logitech_receiver/notifications.py:116 msgid "pairing lock is open" msgstr "配对锁已打开" #: lib/logitech_receiver/notifications.py:81 msgid "discovery lock is closed" msgstr "发现锁已关闭" #: lib/logitech_receiver/notifications.py:81 msgid "discovery lock is open" msgstr "发现锁已打开" #: lib/logitech_receiver/notifications.py:210 lib/solaar/listener.py:238 msgid "connected" msgstr "已连接" #: lib/logitech_receiver/notifications.py:210 lib/solaar/listener.py:238 msgid "disconnected" msgstr "已断开" #: lib/logitech_receiver/notifications.py:236 msgid "unpaired" msgstr "未配对" #: lib/logitech_receiver/notifications.py:283 msgid "powered on" msgstr "已上电" #: 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:625 lib/logitech_receiver/settings.py:651 msgid "feature" msgstr "特性" #: lib/logitech_receiver/settings_templates.py:122 msgid "Swap Fx function" msgstr "互换 Fx 键功能" #: 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." msgstr "" "若开启,F1 到 F12 键会激活特殊功能,\n" "使用标准功能请同时按住 FN 键。" #: 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." msgstr "" "若关闭,F1 到 F12 键会激活标准功能,\n" "使用特殊功能请同时按住 FN 键。" #: lib/logitech_receiver/settings_templates.py:138 msgid "Hand Detection" msgstr "手掌识别" #: 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:146 msgid "Scroll Wheel Smooth Scrolling" msgstr "滚轮平滑滚动" #: 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:154 msgid "Side Scrolling" msgstr "侧向滚轮" #: 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 "" "若禁用,侧向推动滚轮将发送自定义按钮事件\n" "而不是标准侧向滚动事件。" #: lib/logitech_receiver/settings_templates.py:166 msgid "Sensitivity (DPI - older mice)" msgstr "灵敏度 (DPI - 旧款鼠标)" #: 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:240 msgid "Backlight Timed" msgstr "定时背光" #: 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:252 msgid "Backlight" msgstr "背光" #: 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:396 #: lib/logitech_receiver/settings_templates.py:425 msgid "Set to ignore if scrolling is abnormally fast or slow" msgstr "如果滚动异常地快或慢,请设置为忽略" #: lib/logitech_receiver/settings_templates.py:403 #: lib/logitech_receiver/settings_templates.py:434 msgid "Scroll Wheel Diversion" msgstr "滚轮重定义" #: 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 规则,则被触发)" #: lib/logitech_receiver/settings_templates.py:412 msgid "Scroll Wheel Direction" msgstr "滚轮方向" #: lib/logitech_receiver/settings_templates.py:413 msgid "Invert direction for vertical scroll with wheel." msgstr "反转滚轮的垂直滚动方向。" #: lib/logitech_receiver/settings_templates.py:421 msgid "Scroll Wheel Resolution" msgstr "滚轮分辨率" #: 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 规则,则被触发)" #: lib/logitech_receiver/settings_templates.py:445 msgid "Sensitivity (Pointer Speed)" msgstr "灵敏度 (指针速度)" #: lib/logitech_receiver/settings_templates.py:446 msgid "Speed multiplier for mouse (256 is normal multiplier)." msgstr "鼠标速度倍率 (256 为常规倍率)。" #: lib/logitech_receiver/settings_templates.py:456 msgid "Thumb Wheel Diversion" msgstr "拇指滚轮重定义" #: 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 规则,则被触" "发)" #: lib/logitech_receiver/settings_templates.py:467 msgid "Thumb Wheel Direction" msgstr "拇指滚轮方向" #: lib/logitech_receiver/settings_templates.py:468 msgid "Invert thumb wheel scroll direction." msgstr "反转拇指滚轮的滚动方向。" #: lib/logitech_receiver/settings_templates.py:488 msgid "Onboard Profiles" msgstr "板载预设" #: lib/logitech_receiver/settings_templates.py:489 msgid "" "Enable an onboard profile, which controls report rate, sensitivity, and " "button actions" msgstr "启用板载预设,用于控制回报率、灵敏度及按钮功能" #: lib/logitech_receiver/settings_templates.py:533 #: lib/logitech_receiver/settings_templates.py:566 msgid "Report Rate" 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: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:596 msgid "Divert crown events" msgstr "重定义旋钮事件" #: 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 规则,则被触发)" #: lib/logitech_receiver/settings_templates.py:605 msgid "Crown smooth scroll" msgstr "旋钮平滑旋动" #: lib/logitech_receiver/settings_templates.py:606 msgid "Set crown smooth scroll" msgstr "设置旋钮的平滑旋动" #: lib/logitech_receiver/settings_templates.py:614 msgid "Divert G and M Keys" msgstr "重定义 G 键及 M 键" #: lib/logitech_receiver/settings_templates.py:615 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:629 msgid "Scroll Wheel Ratcheted" msgstr "滚轮棘轮" #: lib/logitech_receiver/settings_templates.py:630 msgid "" "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" "50时将始终处于棘轮模式。" #: lib/logitech_receiver/settings_templates.py:690 msgid "Key/Button Actions" msgstr "按键/按钮行为" #: lib/logitech_receiver/settings_templates.py:692 msgid "Change the action for the key or button." msgstr "更改按键或按钮的行为。" #: lib/logitech_receiver/settings_templates.py:694 msgid "Overridden by diversion." msgstr "会被重定义配置覆盖。" #: 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 "更改关键操作 (如鼠标左键) 可能导致系统无法使用。" #: lib/logitech_receiver/settings_templates.py:862 msgid "Key/Button Diversion" msgstr "按键/按钮重定义" #: 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" #: lib/logitech_receiver/settings_templates.py:866 #: lib/logitech_receiver/settings_templates.py:867 #: lib/logitech_receiver/settings_templates.py:868 msgid "Diverted" msgstr "重定义" #: lib/logitech_receiver/settings_templates.py:866 #: lib/logitech_receiver/settings_templates.py:867 msgid "Mouse Gestures" msgstr "鼠标手势" #: 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:866 msgid "Sliding DPI" msgstr "滑动调节 DPI" #: 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:1059 msgid "Sensitivity Switching" msgstr "灵敏度切换" #: lib/logitech_receiver/settings_templates.py:1061 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:1065 msgid "Off" msgstr "关" #: lib/logitech_receiver/settings_templates.py:1096 msgid "Disable keys" msgstr "禁用按键" #: lib/logitech_receiver/settings_templates.py:1097 msgid "Disable specific keyboard keys." msgstr "禁用特定的键盘按键。" #: lib/logitech_receiver/settings_templates.py:1100 #, python-format msgid "Disables the %s key." msgstr "禁用 %s 按键。" #: lib/logitech_receiver/settings_templates.py:1113 #: lib/logitech_receiver/settings_templates.py:1170 msgid "Set OS" msgstr "设置操作系统" #: 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:1183 msgid "Change Host" msgstr "切换主机" #: lib/logitech_receiver/settings_templates.py:1184 msgid "Switch connection to a different host" msgstr "切换连接的主机" #: lib/logitech_receiver/settings_templates.py:1208 msgid "Performs a left click." msgstr "单击鼠标左键。" #: lib/logitech_receiver/settings_templates.py:1208 msgid "Single tap" msgstr "单指点击" #: lib/logitech_receiver/settings_templates.py:1209 msgid "Performs a right click." msgstr "单击鼠标右键。" #: lib/logitech_receiver/settings_templates.py:1209 msgid "Single tap with two fingers" msgstr "双指点击" #: lib/logitech_receiver/settings_templates.py:1210 msgid "Single tap with three fingers" msgstr "三指点击" #: lib/logitech_receiver/settings_templates.py:1214 msgid "Double tap" msgstr "单指双击" #: lib/logitech_receiver/settings_templates.py:1214 msgid "Performs a double click." msgstr "双击。" #: lib/logitech_receiver/settings_templates.py:1215 msgid "Double tap with two fingers" msgstr "双指双击" #: lib/logitech_receiver/settings_templates.py:1216 msgid "Double tap with three fingers" msgstr "三指双击" #: lib/logitech_receiver/settings_templates.py:1219 msgid "Drags items by dragging the finger after double tapping." msgstr "单指双击后移动手指来拖动项目。" #: lib/logitech_receiver/settings_templates.py:1219 msgid "Tap and drag" msgstr "触摸并拖动" #: lib/logitech_receiver/settings_templates.py:1221 msgid "Tap and drag with two fingers" msgstr "双指触摸并拖动" #: 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:1227 msgid "Suppress tap and edge gestures" msgstr "禁用触摸和边缘手势" #: 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:1230 #: lib/logitech_receiver/settings_templates.py:1231 #: lib/logitech_receiver/settings_templates.py:1234 msgid "Scrolls." msgstr "滚动。" #: 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:1232 msgid "Scroll horizontally with two fingers" msgstr "双指水平滚动" #: lib/logitech_receiver/settings_templates.py:1232 msgid "Scrolls horizontally." msgstr "水平滚动。" #: lib/logitech_receiver/settings_templates.py:1233 msgid "Scroll vertically with two fingers" msgstr "双指垂直滚动" #: lib/logitech_receiver/settings_templates.py:1233 msgid "Scrolls vertically." msgstr "垂直滚动。" #: lib/logitech_receiver/settings_templates.py:1235 msgid "Inverts the scrolling direction." msgstr "反转滚动方向。" #: lib/logitech_receiver/settings_templates.py:1235 msgid "Natural scrolling" msgstr "自然滚动" #: lib/logitech_receiver/settings_templates.py:1236 msgid "Enables the thumbwheel." msgstr "启用拇指滚轮。" #: lib/logitech_receiver/settings_templates.py:1236 msgid "Thumbwheel" msgstr "拇指滚轮" #: 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:1248 msgid "Swipe from the left edge" msgstr "从左侧边缘滑动" #: lib/logitech_receiver/settings_templates.py:1249 msgid "Swipe from the right edge" msgstr "从右侧边缘滑动" #: lib/logitech_receiver/settings_templates.py:1250 msgid "Swipe from the bottom edge" msgstr "从底部边缘滑动" #: lib/logitech_receiver/settings_templates.py:1252 msgid "Swipe two fingers from the left edge" msgstr "从左侧边缘双指滑动" #: lib/logitech_receiver/settings_templates.py:1253 msgid "Swipe two fingers from the right edge" msgstr "从右侧边缘双指滑动" #: lib/logitech_receiver/settings_templates.py:1254 msgid "Swipe two fingers from the bottom edge" msgstr "从底部边缘双指滑动" #: lib/logitech_receiver/settings_templates.py:1255 msgid "Swipe two fingers from the top edge" msgstr "从顶部边缘双指滑动" #: 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:1256 msgid "Zoom with two fingers." msgstr "双指缩放。" #: lib/logitech_receiver/settings_templates.py:1257 msgid "Pinch to zoom out." msgstr "捏合手指以缩小。" #: lib/logitech_receiver/settings_templates.py:1258 msgid "Spread to zoom in." msgstr "伸展手指以放大。" #: lib/logitech_receiver/settings_templates.py:1259 msgid "Zoom with three fingers." msgstr "三指缩放。" #: lib/logitech_receiver/settings_templates.py:1260 msgid "Zoom with two fingers" msgstr "双指缩放" #: lib/logitech_receiver/settings_templates.py:1278 msgid "Pixel zone" msgstr "像素区" #: lib/logitech_receiver/settings_templates.py:1279 msgid "Ratio zone" msgstr "比例区" #: lib/logitech_receiver/settings_templates.py:1280 msgid "Scale factor" msgstr "缩放比例" #: lib/logitech_receiver/settings_templates.py:1280 msgid "Sets the cursor speed." msgstr "设置指针速度。" #: lib/logitech_receiver/settings_templates.py:1284 msgid "Left" msgstr "左侧" #: lib/logitech_receiver/settings_templates.py:1284 msgid "Left-most coordinate." msgstr "最左坐标。" #: lib/logitech_receiver/settings_templates.py:1285 msgid "Bottom" msgstr "底部" #: lib/logitech_receiver/settings_templates.py:1285 msgid "Bottom coordinate." msgstr "底部坐标。" #: lib/logitech_receiver/settings_templates.py:1286 msgid "Width" msgstr "宽度" #: lib/logitech_receiver/settings_templates.py:1286 msgid "Width." msgstr "宽度。" #: lib/logitech_receiver/settings_templates.py:1287 msgid "Height" msgstr "高度" #: lib/logitech_receiver/settings_templates.py:1287 msgid "Height." msgstr "高度。" #: lib/logitech_receiver/settings_templates.py:1288 msgid "Cursor speed." msgstr "指针速度。" #: lib/logitech_receiver/settings_templates.py:1288 msgid "Scale" msgstr "比例" #: lib/logitech_receiver/settings_templates.py:1294 msgid "Gestures" msgstr "手势" #: lib/logitech_receiver/settings_templates.py:1295 msgid "Tweak the mouse/touchpad behaviour." msgstr "调整鼠标或触摸板的行为。" #: lib/logitech_receiver/settings_templates.py:1311 msgid "Gestures Diversion" msgstr "手势重定义" #: lib/logitech_receiver/settings_templates.py:1312 msgid "Divert mouse/touchpad gestures." msgstr "重定义鼠标或触摸板手势。" #: lib/logitech_receiver/settings_templates.py:1328 msgid "Gesture params" msgstr "手势参数" #: lib/logitech_receiver/settings_templates.py:1329 msgid "Change numerical parameters of a mouse/touchpad." msgstr "更改鼠标或触摸板的数值参数。" #: lib/logitech_receiver/settings_templates.py:1353 msgid "M-Key LEDs" msgstr "M 键 LED" #: lib/logitech_receiver/settings_templates.py:1355 msgid "Control the M-Key LEDs." msgstr "控制 M 键的 LED。" #: 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 键重定义。" #: lib/logitech_receiver/settings_templates.py:1365 #, python-format msgid "Lights up the %s key." msgstr "点亮 %s 按键。" #: lib/logitech_receiver/settings_templates.py:1384 msgid "MR-Key LED" msgstr "MR 键 LED" #: lib/logitech_receiver/settings_templates.py:1386 msgid "Control the MR-Key LED." msgstr "控制 MR 键的 LED。" #: lib/logitech_receiver/settings_templates.py:1407 msgid "Persistent Key/Button Mapping" msgstr "持久化按键/按钮映射" #: lib/logitech_receiver/settings_templates.py:1409 msgid "Permanently change the mapping for the key or button." msgstr "永久更改按键或按钮的映射。" #: 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:1468 msgid "Sidetone" msgstr "侧音 (麦克风回馈)" #: lib/logitech_receiver/settings_templates.py:1469 msgid "Set sidetone level." msgstr "设置侧音水平。" #: lib/logitech_receiver/settings_templates.py:1478 msgid "Equalizer" msgstr "均衡器" #: lib/logitech_receiver/settings_templates.py:1479 msgid "Set equalizer levels." msgstr "设置均衡器水平。" #: lib/logitech_receiver/settings_templates.py:1501 msgid "Hz" msgstr "" #: lib/logitech_receiver/settings_templates.py:1507 msgid "Power Management" msgstr "电源管理" #: lib/logitech_receiver/settings_templates.py:1508 msgid "Power off in minutes (0 for never)." msgstr "分钟后关机(0为不关机)" #: lib/logitech_receiver/settings_templates.py:1519 msgid "Brightness Control" msgstr "亮度控制" #: lib/logitech_receiver/settings_templates.py:1520 msgid "Control overall brightness" msgstr "全局亮度控制" #: lib/logitech_receiver/settings_templates.py:1563 #: lib/logitech_receiver/settings_templates.py:1617 msgid "LED Control" msgstr "LED控制" #: 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/settings_templates.py:1579 #: lib/logitech_receiver/settings_templates.py:1628 msgid "LED Zone Effects" msgstr "LED区域效果" #: 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/logitech_receiver/settings_templates.py:1580 #: lib/logitech_receiver/settings_templates.py:1629 msgid "Set effect for LED Zone" msgstr "设置LED区域效果" #: lib/logitech_receiver/settings_templates.py:1583 msgid "Speed" msgstr "速度" #: lib/logitech_receiver/settings_templates.py:1584 msgid "Period" msgstr "周期" #: lib/logitech_receiver/settings_templates.py:1585 msgid "Intensity" msgstr "强度" #: lib/logitech_receiver/settings_templates.py:1586 msgid "Ramp" msgstr "渐变" #: lib/logitech_receiver/settings_templates.py:1602 msgid "LEDs" msgstr "LED" #: lib/logitech_receiver/settings_templates.py:1639 msgid "Per-key Lighting" msgstr "按键独立灯光" #: lib/logitech_receiver/settings_templates.py:1640 msgid "Control per-key lighting." msgstr "设置每个按键的灯光" #: lib/solaar/ui/__init__.py:103 msgid "Another Solaar process is already running so just expose its window" msgstr "另一个 Solaar 进程正在运行,已唤醒窗口" #: lib/solaar/ui/about.py:34 msgid "" "Manages Logitech receivers,\n" "keyboards, mice, and tablets." msgstr "" "管理罗技接收器、\n" "键盘、鼠标和平板电脑。" #: lib/solaar/ui/about.py:43 msgid "Additional Programming" msgstr "协作研发" #: lib/solaar/ui/about.py:44 msgid "GUI design" msgstr "界面设计" #: lib/solaar/ui/about.py:46 msgid "Testing" msgstr "软件测试" #: lib/solaar/ui/about.py:54 msgid "Logitech documentation" msgstr "罗技文档" #: 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:86 lib/solaar/ui/diversion_rules.py:103 msgid "Cancel" msgstr "取消" #: 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:228 msgid "Incomplete" msgstr "未完成" #: 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:603 msgid "Changes allowed" msgstr "允许更改" #: lib/solaar/ui/config_panel.py:604 msgid "No changes allowed" msgstr "不允许更改" #: lib/solaar/ui/config_panel.py:605 msgid "Ignore this setting" msgstr "忽略此设置" #: lib/solaar/ui/config_panel.py:649 msgid "Working" msgstr "工作中" #: lib/solaar/ui/config_panel.py:652 msgid "Read/write operation failed." msgstr "读写操作失败。" #: lib/solaar/ui/diversion_rules.py:65 msgid "Built-in rules" msgstr "内置规则" #: lib/solaar/ui/diversion_rules.py:65 msgid "User-defined rules" msgstr "自定义规则" #: lib/solaar/ui/diversion_rules.py:67 lib/solaar/ui/diversion_rules.py:1033 msgid "Rule" msgstr "规则" #: 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:70 msgid "[empty]" msgstr "[空]" #: lib/solaar/ui/diversion_rules.py:94 msgid "Make changes permanent?" msgstr "永久应用更改?" #: lib/solaar/ui/diversion_rules.py:99 msgid "Yes" msgstr "是" #: lib/solaar/ui/diversion_rules.py:101 msgid "No" msgstr "否" #: lib/solaar/ui/diversion_rules.py:106 msgid "If you choose No, changes will be lost when Solaar is closed." msgstr "如果选择否,改动会在 Solaar 关闭后丢失。" #: 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:265 msgid "Discard changes" msgstr "放弃更改" #: lib/solaar/ui/diversion_rules.py:404 msgid "Insert here" msgstr "插入" #: lib/solaar/ui/diversion_rules.py:406 msgid "Insert above" msgstr "在上方插入" #: lib/solaar/ui/diversion_rules.py:408 msgid "Insert below" msgstr "在下方插入" #: lib/solaar/ui/diversion_rules.py:414 msgid "Insert new rule here" msgstr "新建规则" #: lib/solaar/ui/diversion_rules.py:416 msgid "Insert new rule above" msgstr "在上方添加规则" #: lib/solaar/ui/diversion_rules.py:418 msgid "Insert new rule below" msgstr "在下方添加规则" #: lib/solaar/ui/diversion_rules.py:463 msgid "Paste here" msgstr "在此处粘贴" #: lib/solaar/ui/diversion_rules.py:465 msgid "Paste above" msgstr "在上方粘贴" #: lib/solaar/ui/diversion_rules.py:467 msgid "Paste below" msgstr "在下方粘贴" #: lib/solaar/ui/diversion_rules.py:473 msgid "Paste rule here" msgstr "在此处粘贴规则" #: lib/solaar/ui/diversion_rules.py:475 msgid "Paste rule above" msgstr "在上方粘贴规则" #: lib/solaar/ui/diversion_rules.py:477 msgid "Paste rule below" msgstr "在下方粘贴规则" #: lib/solaar/ui/diversion_rules.py:481 msgid "Paste rule" msgstr "粘贴规则" #: lib/solaar/ui/diversion_rules.py:510 msgid "Flatten" msgstr "展平" #: lib/solaar/ui/diversion_rules.py:543 msgid "Insert" msgstr "插入" #: 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:547 lib/solaar/ui/diversion_rules.py:673 #: lib/solaar/ui/diversion_rules.py:1051 msgid "And" msgstr "且" #: lib/solaar/ui/diversion_rules.py:549 msgid "Condition" msgstr "条件" #: lib/solaar/ui/diversion_rules.py:551 lib/solaar/ui/rule_conditions.py:146 msgid "Feature" msgstr "特性" #: lib/solaar/ui/diversion_rules.py:552 lib/solaar/ui/rule_conditions.py:181 msgid "Report" msgstr "报文" #: lib/solaar/ui/diversion_rules.py:553 lib/solaar/ui/rule_conditions.py:60 msgid "Process" msgstr "焦点所在进程" #: lib/solaar/ui/diversion_rules.py:554 msgid "Mouse process" msgstr "光标所在进程" #: lib/solaar/ui/diversion_rules.py:555 lib/solaar/ui/rule_conditions.py:218 msgid "Modifiers" msgstr "修饰键" #: lib/solaar/ui/diversion_rules.py:556 lib/solaar/ui/rule_conditions.py:270 msgid "Key" msgstr "按键" #: 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 "活跃设备" #: 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: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:1355 msgid "Device is active and its settings can be changed." msgstr "设备已激活,且可更改其设置。" #: 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:1454 msgid "Item" msgstr "项目" #: lib/solaar/ui/diversion_rules.py:1728 msgid "Change setting on device" msgstr "更改设备上保存的设置" #: lib/solaar/ui/diversion_rules.py:1744 msgid "Setting on device" msgstr "设备上保存的设置" #: lib/solaar/ui/notify.py:115 msgid "unspecified reason" msgstr "未定原因" #: 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:39 msgid "Bolt receivers are only compatible with Bolt devices." msgstr "Bolt接收器仅适用于Bolt设备" #: 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:44 msgid "Unifying receivers are only compatible with Unifying devices." msgstr "优联接收器仅适用于优联设备" #: 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: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:55 #, 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 个设备配对。" #: lib/solaar/ui/pair_window.py:61 msgid "" "\n" "Cancelling at this point will not use up a pairing." msgstr "" "\n" "此时取消将不会使配对生效。" #: lib/solaar/ui/pair_window.py:156 #, python-format msgid "Enter passcode on %(name)s." msgstr "在 %(name)s 上输入密码。" #: 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:56 lib/solaar/ui/window.py:308 #, python-format msgid "Quit %s" msgstr "退出 %s" #: lib/solaar/ui/tray.py:267 lib/solaar/ui/tray.py:275 msgid "no receiver" msgstr "无接收器" #: 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:91 msgid "Scanning" msgstr "扫描中" #: lib/solaar/ui/window.py:122 msgid "Battery" msgstr "电池" #: lib/solaar/ui/window.py:125 msgid "Wireless Link" msgstr "无线连接" #: lib/solaar/ui/window.py:129 msgid "Lighting" msgstr "照明" #: lib/solaar/ui/window.py:163 msgid "Show Technical Details" msgstr "显示技术细节" #: lib/solaar/ui/window.py:179 msgid "Pair new device" msgstr "配对新设备" #: lib/solaar/ui/window.py:197 msgid "Select a device" msgstr "选择 1 个设备" #: lib/solaar/ui/window.py:313 msgid "Rule Editor" msgstr "规则编辑器" #: lib/solaar/ui/window.py:517 msgid "Path" msgstr "路径" #: lib/solaar/ui/window.py:520 msgid "USB ID" msgstr "" #: 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:529 msgid "Index" msgstr "索引号" #: lib/solaar/ui/window.py:531 msgid "Wireless PID" msgstr "无线标示符" #: lib/solaar/ui/window.py:533 msgid "Product ID" msgstr "产品 ID" #: lib/solaar/ui/window.py:535 msgid "Protocol" msgstr "协议" #: lib/solaar/ui/window.py:535 msgid "Unknown" msgstr "未知" #: lib/solaar/ui/window.py:537 msgid "Polling rate" msgstr "轮询速率" #: lib/solaar/ui/window.py:544 msgid "Unit ID" msgstr "单元 ID" #: lib/solaar/ui/window.py:558 msgid "Notifications" msgstr "通告" #: lib/solaar/ui/window.py:602 msgid "No device paired." msgstr "没有已配对设备。" #: 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: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:679 msgid "Battery Voltage" msgstr "电池电压" #: lib/solaar/ui/window.py:681 msgid "Voltage reported by battery" msgstr "电池报告的电压" #: lib/solaar/ui/window.py:683 msgid "Battery Level" msgstr "电量" #: lib/solaar/ui/window.py:685 msgid "Approximate level reported by battery" msgstr "电池报告的大致电量" #: lib/solaar/ui/window.py:692 lib/solaar/ui/window.py:694 msgid "next reported " 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 "not encrypted" msgstr "未加密" #: 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:735 #, python-format msgid "%(light_level)d lux" msgstr "" #~ msgid " paired devices." #~ msgstr " 个已配对设备。" #~ msgid "1 paired device." #~ msgstr "1 个已配对设备。" #~ msgid "About" #~ msgstr "关于" #~ 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 "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++ 模式。" #~ msgid "HID++ mode for vertical scroll with the wheel." #~ msgstr "为滚轮的垂直滚动启用 HID++ 模式。" #~ msgid "" #~ "If the device is already turned on,\n" #~ "turn if off and on again." #~ 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 "未发现罗技接收器" #~ msgid "No device paired" #~ 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 "通过在按住按钮的同时滑动鼠标来发送手势。" #~ msgid "" #~ "Shows status of devices connected\n" #~ "through wireless Logitech receivers." #~ msgstr "" #~ "显示罗技接收器\n" #~ "已连接的设备状态。" #~ msgid "Smooth Scrolling" #~ msgstr "平滑滚动" #~ msgid "The receiver only supports %d paired device(s)." #~ msgstr "此接收器仅支持 %d 个配对设备。" #~ msgid "The receiver was unplugged." #~ msgstr "接收器被移除。" #~ 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 "Turn illumination on or off on keyboard." #~ msgstr "打开或关闭键盘背光。" #~ msgid "USB id" #~ msgstr "USB 序号" #~ 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 "开启" #~ msgid "pair new device" #~ msgstr "配对新设备" #~ msgid "paired devices" #~ msgstr "个已配对设备" #~ msgid "pairing lock is " #~ msgstr "配对锁已" #~ msgid "unknown" #~ msgstr "未知的" Solaar-1.1.20/po/zh_TW.po000066400000000000000000001615551522022367600150470ustar00rootroot00000000000000# Chinese translations for solaar package # Copyright (C) 2014 THE solaar'S COPYRIGHT HOLDER # 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: 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: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 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 "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 個已配對裝置。" #: 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 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 鍵 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 "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 "" "Found a Logitech receiver or device (%s), but did not have permission to " "open it." msgstr "找到羅技接收器或裝置 (%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 "在 %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: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 個值" #: 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 接收器僅相容於 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 次配對。" #: 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 "滑鼠手勢,可選擇性地帶有起始按鈕,後接零個或多個滑鼠移動。" #: 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 個裝置。" #: 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 次配對。" #: 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 lux" Solaar-1.1.20/pyproject.toml000066400000000000000000000004131522022367600157330ustar00rootroot00000000000000[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 Solaar-1.1.20/release.sh000077500000000000000000000121111522022367600147740ustar00rootroot00000000000000#!/usr/bin/env bash repo=pwr-Solaar/Solaar usage() { basename="$(basename $0)" cat </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##*/} commit=$(git rev-list --max-count=1 HEAD) [ $? -ne 0 ] && echo 'Error: Failed to get HEAD' && exit 1 remote=$(git config --get branch.$branch.remote) [ $? -ne 0 ] && echo 'Error: Failed to get remote' && exit 1 remote_ref=$(git config --get branch.$branch.merge) [ $? -ne 0 ] && echo 'Error: Failed to get remote HEAD' && exit 1 remote_branch=${remote_ref##*/} github_token=$(cat ~/.github-token) [ $? -ne 0 ] && echo 'Error: Failed to get github token (check ~/.github_token)' && exit 1 jq -V >/dev/null [ $? -ne 0 ] && echo 'Error: jq is not installed' && exit 1 echo -e "\n\t** You are tagging a release for version $version **\n" echo "Version: $version" echo "Commit: $commit" echo "Pre-release: $prerelease" echo "Remote: $remote" if [ "$branch" == "$remote_branch" ]; then echo "Branch: $branch" else echo "Local branch: $branch" echo "Remote branch: $remote_branch" fi echo -e '\nPlease read RELEASE.md before continuing.\n' read -p 'Are you sure you want to proceed? (y/n) ' -n 1 -r < /dev/tty echo [[ ! $REPLY =~ ^[Yy]$ ]] && echo 'Release aborted.' && exit 1 read -p 'Are you *really* sure you want to proceed? (y/n) ' -n 1 -r < /dev/tty echo [[ ! $REPLY =~ ^[Yy]$ ]] && echo 'Release aborted.' && exit 1 # Check if version is in the changelog grep '^# '"$version" CHANGELOG.md >/dev/null [ $? -ne 0 ] && echo 'Error: Version is not present in the changelog' && exit 1 # Check for uncommitted changes git diff --quiet HEAD >/dev/null [ $? -ne 0 ] && echo -e '\nError: Uncommitted changes found' && exit 1 # Check if commit is a version bump git show -s --format=%B HEAD | grep "^release $version">/dev/null #[ $? -ne 0 ] && echo -e '\nError: Commit does not look like a version bump' && exit 1 # Check if commit has been pushed to remote remote_commit=$(git rev-list --max-count=1 $remote/$branch) [ $? -ne 0 ] && echo -e '\nError: Failed to get remote HEAD' && exit 1 [ "$commit" != "$remote_commit" ] && echo -e '\nError: Commit has not been pushed to the remote' && exit 1 # Check if tag already exists git rev-list --max-count=1 $version >/dev/null 2>/dev/null [ $? -eq 0 ] && echo -e '\nError: Tag already exists' && exit 1 # Check if tag already exists on remote git ls-remote $remote | grep "refs/tags/$version$" >/dev/null [ $? -eq 0 ] && echo -e '\nError: Tag already exists on remote' && exit 1 echo # Create tag echo 'Creating tag...' { echo "release $version" echo found=no while read -r line; do if [[ "$line" == "# "* ]]; then [ "$line" == "# $version" ] && found=yes || found=no fi [ "$found" == 'yes' ] && [ "${line:0:1}" == '*' ] && echo "$line" 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 # Push tag 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 </dev/null | jq -r .html_url) [ -z "$DRY_RUN" ] && url=$(curl -H 'Authorization: $github_token' -X POST --data "$(body)" "https://api.github.com/repos/$repo/releases" 2>/dev/null | jq -r .html_url) [ -z "$DRY_RUN" ] && [ -z "$url" ] && echo -e '\nError: Failed to create a github release' && exit 1 [ -z "$DRY_RUN" ] && echo -e "\nRelease created: $url" rm /tmp/solaar-changelog Solaar-1.1.20/rules.d-uinput/000077500000000000000000000000001522022367600157175ustar00rootroot00000000000000Solaar-1.1.20/rules.d-uinput/42-logitech-unify-permissions.rules000066400000000000000000000016541522022367600245230ustar00rootroot00000000000000# This rule was added by Solaar. # # Allows non-root users to have raw access to Logitech devices. # Allowing users to write to the device is potentially dangerous # because they could perform firmware updates. KERNEL=="uinput", SUBSYSTEM=="misc", TAG+="uaccess", OPTIONS+="static_node=uinput" ACTION == "remove", GOTO="solaar_end" SUBSYSTEM != "hidraw", GOTO="solaar_end" # USB-connected Logitech receivers and devices ATTRS{idVendor}=="046d", GOTO="solaar_apply" # Lenovo nano receiver ATTRS{idVendor}=="17ef", ATTRS{idProduct}=="6042", GOTO="solaar_apply" # Bluetooth-connected Logitech devices KERNELS == "0005:046D:*", GOTO="solaar_apply" GOTO="solaar_end" LABEL="solaar_apply" # Allow any seated user to access the receiver. # uaccess: modern ACL-enabled udev TAG+="uaccess" # Grant members of the "plugdev" group access to receiver (useful for SSH users) #MODE="0660", GROUP="plugdev" LABEL="solaar_end" # vim: ft=udevrules Solaar-1.1.20/rules.d/000077500000000000000000000000001522022367600143755ustar00rootroot00000000000000Solaar-1.1.20/rules.d/42-logitech-unify-permissions.rules000066400000000000000000000016541522022367600232010ustar00rootroot00000000000000# This rule was added by Solaar. # # Allows non-root users to have raw access to Logitech devices. # Allowing users to write to the device is potentially dangerous # because they could perform firmware updates. KERNEL=="uinput", SUBSYSTEM=="misc", TAG+="uaccess", OPTIONS+="static_node=uinput" ACTION == "remove", GOTO="solaar_end" SUBSYSTEM != "hidraw", GOTO="solaar_end" # USB-connected Logitech receivers and devices ATTRS{idVendor}=="046d", GOTO="solaar_apply" # Lenovo nano receiver ATTRS{idVendor}=="17ef", ATTRS{idProduct}=="6042", GOTO="solaar_apply" # Bluetooth-connected Logitech devices KERNELS == "0005:046D:*", GOTO="solaar_apply" GOTO="solaar_end" LABEL="solaar_apply" # Allow any seated user to access the receiver. # uaccess: modern ACL-enabled udev TAG+="uaccess" # Grant members of the "plugdev" group access to receiver (useful for SSH users) #MODE="0660", GROUP="plugdev" LABEL="solaar_end" # vim: ft=udevrules Solaar-1.1.20/setup.py000077500000000000000000000066151522022367600145460ustar00rootroot00000000000000import subprocess import textwrap from glob import glob from os.path import dirname from pathlib import Path from setuptools import find_packages from setuptools import setup NAME = "Solaar" version = Path("lib/solaar/version").read_text().strip() try: # get commit from git describe commit = subprocess.check_output(["git", "describe", "--always"], stderr=subprocess.DEVNULL).strip().decode() Path("lib/solaar/commit").write_text(f"{commit}\n") except Exception: # get commit from Ubuntu dpkg-parsechangelog try: commit = ( subprocess.check_output(["dpkg-parsechangelog", "--show-field", "Version"], stderr=subprocess.DEVNULL) .strip() .decode() ) commit = commit.split("~") Path("lib/solaar/commit").write_text(f"{commit[0]}\n") except Exception as e: print("Exception using dpkg-parsechangelog", e) def _data_files(): yield "share/icons/hicolor/scalable/apps", glob("share/solaar/icons/solaar*.svg") yield "share/icons/hicolor/32x32/apps", glob("share/solaar/icons/solaar-light_*.png") for mo in glob("share/locale/*/LC_MESSAGES/solaar.mo"): yield dirname(mo), [mo] yield "share/applications", ["share/applications/solaar.desktop"] yield "lib/udev/rules.d", ["rules.d/42-logitech-unify-permissions.rules"] yield "share/metainfo", ["share/solaar/io.github.pwr_solaar.solaar.metainfo.xml"] setup( name=NAME.lower(), version=version, description="Linux device manager for Logitech receivers, keyboards, mice, and tablets.", long_description=textwrap.dedent( """ Solaar is a Linux device manager for many Logitech peripherals that connect through Unifying and other receivers or via USB or Bluetooth. Solaar is able to pair/unpair devices with receivers and show and modify some of the modifiable features of devices. For instructions on installing Solaar see https://pwr-solaar.github.io/Solaar/installation""" ), author="Daniel Pavel", license="GPLv2", url="http://pwr-solaar.github.io/Solaar/", classifiers=[ "Development Status :: 4 - Beta", "Environment :: X11 Applications :: GTK", "Environment :: Console", "Intended Audience :: End Users/Desktop", "License :: DFSG approved", "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", "Natural Language :: English", "Programming Language :: Python :: 3 :: Only", "Operating System :: POSIX :: Linux", "Topic :: Utilities", ], 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 ; platform_system=="Linux"', "PyGObject", "pycairo", "typing_extensions", ], extras_require={ "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=find_packages(where="lib"), data_files=list(_data_files()), include_package_data=True, entry_points={ "console_scripts": [ "solaar = solaar.gtk:main", ], }, ) Solaar-1.1.20/share/000077500000000000000000000000001522022367600141235ustar00rootroot00000000000000Solaar-1.1.20/share/README000066400000000000000000000002231522022367600150000ustar00rootroot00000000000000Solaar application icon drawn by me, roughly guided by Logitech's Unifying logo. All other images are from the GNOME and Oxygen free icon themes. Solaar-1.1.20/share/applications/000077500000000000000000000000001522022367600166115ustar00rootroot00000000000000Solaar-1.1.20/share/applications/solaar.desktop000066400000000000000000000017251522022367600214720ustar00rootroot00000000000000[Desktop Entry] Name=Solaar 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 Terminal=false Type=Application Keywords=logitech;unifying;receiver;mouse;keyboard; Categories=Utility;GTK; #Categories=Utility;GTK;Settings;HardwareSettings; X-GNOME-UsesNotifications=true Solaar-1.1.20/share/autostart/000077500000000000000000000000001522022367600161515ustar00rootroot00000000000000Solaar-1.1.20/share/autostart/solaar.desktop000066400000000000000000000017041522022367600210270ustar00rootroot00000000000000[Desktop Entry] Name=Solaar 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 --window=hide Icon=solaar StartupNotify=true Terminal=false Type=Application Keywords=logitech;unifying;receiver;mouse;keyboard; Categories=Utility;GTK; #Categories=Utility;GTK;Settings;HardwareSettings; Solaar-1.1.20/share/solaar-master.svg000066400000000000000000000134571522022367600174300ustar00rootroot00000000000000 Solaar master template image/svg+xml Solaar master template Daniel Pavel 2013-06-25 solaar-master Solaar-1.1.20/share/solaar/000077500000000000000000000000001522022367600154045ustar00rootroot00000000000000Solaar-1.1.20/share/solaar/icons/000077500000000000000000000000001522022367600165175ustar00rootroot00000000000000Solaar-1.1.20/share/solaar/icons/THIRD_PARTY.md000066400000000000000000000034501522022367600206740ustar00rootroot00000000000000# Third-party assets in this directory Solaar itself is licensed under GPL-2.0 (see `LICENSE.txt` at the repository root). Some icons in this directory are sourced from third-party projects under permissive licenses compatible with GPL-2.0. Their original license notices are preserved as comments inside each SVG file. ## Tabler Icons The following SVGs are derived from [Tabler Icons](https://tabler.io/icons), copyright (c) 2020-2024 Paweł Kuna, distributed under the MIT License: - `solaar-tool-brush-symbolic.svg` — from `brush` - `solaar-tool-rect-symbolic.svg` — from `paint` - `solaar-tool-bucket-symbolic.svg` — from `bucket-droplet` - `solaar-tool-palette-off-symbolic.svg` — from `palette-off` ### MIT License (Tabler Icons) ``` MIT License Copyright (c) 2020-2024 Paweł Kuna Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` Solaar-1.1.20/share/solaar/icons/solaar-attention-dark-filled.svg000066400000000000000000000331341522022367600247040ustar00rootroot00000000000000 Solaar attentionimage/svg+xmlSolaar attentionDaniel Pavel2013-06-25solaar-attention Solaar-1.1.20/share/solaar/icons/solaar-attention.svg000066400000000000000000000067341522022367600225360ustar00rootroot00000000000000 Solaar attention image/svg+xml Solaar attention Daniel Pavel 2013-06-25 solaar-attention Solaar-1.1.20/share/solaar/icons/solaar-filled.svg000066400000000000000000000241751522022367600217670ustar00rootroot00000000000000 Solaarimage/svg+xmlSolaarDaniel Pavel2013-06-25solaar Solaar-1.1.20/share/solaar/icons/solaar-init-dark-filled.svg000066400000000000000000000407631522022367600236500ustar00rootroot00000000000000 Solaar initimage/svg+xmlSolaar initDaniel Pavel2013-06-25solaar-init Solaar-1.1.20/share/solaar/icons/solaar-init-dark-rotate-filled.svg000066400000000000000000000522061522022367600251370ustar00rootroot00000000000000 Solaar initimage/svg+xmlSolaar initDaniel Pavel2013-06-25solaar-init Solaar-1.1.20/share/solaar/icons/solaar-init.svg000066400000000000000000000064011522022367600214630ustar00rootroot00000000000000 Solaar init image/svg+xml Solaar init Daniel Pavel 2013-06-25 solaar-init Solaar-1.1.20/share/solaar/icons/solaar-light_000.png000066400000000000000000000031711522022367600221740ustar00rootroot00000000000000PNG  IHDR szz@IDATxV4g=_cl?Gq[]\۶m۶m[8y޸ުG__] }_IOߟon)xw9@e~~~[n֛\z׻pر:tvض-t]? wѡV  "laB>O .x o??Uwq 'R,zϻ `PaJE K&r*h9/| v)gy*u^70M఩R2 Z>eTX*ahh'|{e/1ždff[[[KFgWmOjZT~7e5k0F:ưU@beebCn TzmbN743Ld[VZlbluUס) {#hoL "yPbeܲ_w)j666*W\踯aF0P(݃ b.RA5h l4ua֤ױXTćpJ_/=s{huR7^p*"^* ٺpP>h6ADN9Y-Q)G?LON LzTK+2k<0e`2PlۂOK̃븊lk K!Rd;6R7y_ >(,N?L޸暫_fSRK9,B-ȤRt{k[I`蚼hl#0gB^8ɪ@>y3Ҡ4~U^DL#4C¶ZG\`qz $ x'@f[>T|T,$F@ШUh<1 lV1 RlNvD^BhJtȈNde>A'PKecfpvn3#CzO)|A1<4: j&YqN"C!{0dx64]k1chZvzKC~`qiE]vw76twpȖXG0 ¨e$|v˂09GpuT*M ޑP$ؖ3ԞR~LOSGP{aIQvpu} Os9ř *b@Z&dáe[-'HPD<~k<z;߃?R\2*M*L]6*[X\Zz<=RdG\j -~P2ؖg;S/S599x.\AGmnױ/S5~uxIENDB`Solaar-1.1.20/share/solaar/icons/solaar-light_020.png000066400000000000000000000035471522022367600222050ustar00rootroot00000000000000PNG  IHDR szz.IDATx#K[]tpmm۶m۶=݇wQ2YU'ow~p+л_;ޜ>7~t::x`, `<σJ@ }. \_6M#d~4 a1X4|x [Aч~r 1Dc du18j+׮^Ώ w xhii5b`0HPP @)}xzCH  j֬%];1\]潕3Ah.S3˲gex0nkz6(Jе9u11ǵ۪y4N[ + 8NkbK~]F*$15(}nm!cw 3ItT2n+$,J煗0m{DF0RiP(ZH>?391;|.}?3-CJ&B1( V C%umsP0ԝ!Y+ c'N_ua$^rxcwel/l{q,MCDIq1@0ci  ]ʕlYT~B'O|cG;e~B5IլWBGc8$!85BЇNV)IPiX @'sssɹe'Y0I|;[n;F0 oA4eSJ|~5Lr0jwj짿|4 ,`)BVƦڲ,TDJ%m)jܶ\sXʩs, ޝ]xu.`0Z_akƾ*!Uuarz>{9tF;23_c!yJ@O5m"5]4MlۆBEiif}tk_շ^+,Q9و1s0CCRv9]˺ #gݡk-)8y/@M* XO[ٱJ =q[~`^k_:3D*M]nqGtCse_bv-0JxƎmY?,j[61k,IA0(_^}泟u7[[['O9_o~S X{s|(ViUV66iuǶm!@UYjy>ɋrLURe[z:D]rZׯ_\ `~+ORyޫQ~tT׍g&qZ-eHA (+zIoDA@p6Imq<}ѣG^A8-!#e ˲8qQg a j5loX۲;P7|6JdµMYV=u[[/N̚/&dEYJh }D ,B]4(MA2 A PKf)ǿW퍍qWl(bqrMaӈy}I˱'2"f>0 Cɋ܀e6yPʲFヲ7/k'JӤy$}pb<4  n|Ġ[24xLf3] DۛlW*{3k>0Y Ah(P|*X[]A{Rtnk$n=Murի?K]xM .=|ziVu 4!cFf ; *0t@;,UZK G^VHF)| ^SN sgt:mPw BI) N i*eQiк6s̊Tu5-@'L8f0(.nfRJn:K3aa`LȬˍi C陡0fңl9rAa+fRFӗ?){Q_Xe:YWX#a໮ym%-h_ z6_`0і9jZfXOg&hݾ!8`?OY/oܸW.xooZ.of1%0B9GN@5+ > Q nqG?&\lomylb[ѰZM6Yd6JjT\.Lbr$x -A0ujc1|ޜ׿ₙ\|9*a紴,&}-*| F˴ԲJqh SߩecBdIENDB`Solaar-1.1.20/share/solaar/icons/solaar-light_060.png000066400000000000000000000044171522022367600222060ustar00rootroot00000000000000PNG  IHDR szzIDATx+M)u'2ڶm>X}ٶڶx2YUg7Y~1/oGnH=1 Ǯ0a5uYvtBam/(U? oʅ\tv8('*2]2!^xE>+"x| S_-0/\ҵ9Zy Fp[<<%[9lm;>l]u՟QzG|;Mtifwglj~W {@``36༆ޣ$F宙rId^ )R(Y~G$?#?[4m$W-/l-|WI c %+ p tӕgji񭜋w۲{_D>9 L 0CAP1 "u[pjl / -WopnAivE|Ξ=mQT:A֚4= ßDcڃA xK-| x5X>\(/Q^ g/-.GcU6"/f;'(KlAd9H3$ ^MxjAoށ(`x0;]0zНwn1T1e)5ub8)ct}dyFKAi~Hv` fL w2tIsf/>R~XloF[ @T*avfAhJ0uaAtֿibgQ-uZE'c.UR*3j p(J 0Q.aREl! J% >} wAĀ*OG%kuFRix d'QsYyGEaރC^g#\)eypxrb)R 2`fH9~K0F0~23"17~7Oa,W0J 63PJyXN갓T4BX֑^C i:΋9]B@Q)i%s N;I@l?혮]#e硆]"@ȫ7ujv/syӡLGK^ɈoH>>`]MgCR:< مgL eX}Lwr{1HV00?gYAۣ 9Tv\-NuWiT # CP}ъ=\`(,R LH]}G*Y C_M00(Uh_)%6: IG\_T 9g=Gȷ6D ;Yѥe$-Zֽx[%xMs㝔{Gm܊8tR23kߋۖoi4Ww=Ğ*͓5be_{}w& /}ߌK`tjWjQ|AR;4#a;ȶ}p{0I,bTQp3Ҷw4^$/4L6 &8-o.\KIZƽv7$&k( cj'S1 ڟWI+yϋ\~m;o[\- E{#m]$^9e j8|?l/i+\}+o[MA17^ԯ1%p fl@L1 aaBP$ E: (x(oMDv,~z)W2!,`Xhq4JGnncv8J.\1"_z=]jW~Z~?SNOR+)(5T:DiDS2h %o]M}k_8O?(]؇N&v{hdqor.L2JFVL Izt'YC(]\y]C-Ǚ~w30yoTf+1[5R념OFqip}8$16C|>NYZIQilcY o5{, Ze5i:NS0,̛Jy.uz'JWĕ#L#">EaHlb9]\Ms]~W婎>鵫uW ? ahRuE0.^T{^ڨE;7St|aqtE?ؼd^WWZ:b`PL&9---߷o_y5d> Xf|>uMc ~%tonUW'Ǖ{ND*&6>:c_y;iHdk+> K[q?KϤ ז!a' # Y;X̷!4~xvs&>@;Ęp5÷.-֟],q+GI9"kQr*6mgR-cXYM7gIGHc,b bP0Ԙ$يx>r5ڽCx70YITڅo f"Qqb-bKP xѨBɪ<1&ƫr/D7>͝o5Zjjr|c)VTL!LY7#{=i߸ta] &[Zgm X,@EA=H@L FNk:$/F[+W%*neSM/g$hG+"&O唈FFS@@A3ړZu?X͂fѝOby*~卩,t^i*"R "C$c@KTР%c'4ֱBap<\÷]$BpA*o曹$͍$?!T5O&*M$h4A shYrM#:u@2dT Բmlw]m/&[%&m5h0K@1%I h^bAkDPnK$kO\ymשToӭn^1Im4i "f0%?2w֕Nw_2lskMo*2W<\7pƦF5{sPj[>)fq)Nc/&'Cђ잩QS=~`קӻoX^-y5U>tk;. n&,3n4Z`[}=Pަ鍕ɯ7I^G/]yYh4P|gD4tv6nw3_2O'uU*A#\ {a1wI9kԜKm0F|>LϗUS0hܶ'[kij;*UAY'l IMwdvMcu6jރ7C2{> dQοjJ!#<Ӳ&b\!63tum/8x=%Da#3z "dcYc; {8H N67>s^|oM{(yD!wȯF'Xo]Yw> |sm@;-<`VS%d_yHX4w@ͧS x񏼸dR1@sU"@V)9npdIENDB`Solaar-1.1.20/share/solaar/icons/solaar-light_unknown.png000066400000000000000000000017571522022367600234040ustar00rootroot00000000000000PNG  IHDR szzIDATxbv,@XYd ~ ˗$e:Lb>}z!..˗Ϊ +P"HEQPTRJ(@PJ(( !o0cҰv߶sΜ=G=hgZC}g|(//GFF?J!P\\l ݁x<$Y/흝aqqӘ...,.pfa`R 8NOO1991`ss///or= p"cnn%%%GNNs*|+-deeB\P"1??Vtww{Fff&AXX5@dqii)yՠJ[[[GYwtuuChBTUU|HNNVpT6PfzuuJ$ p~~OIz|̄h|{{@SSs7// F`9Z $)7Z/;"N&l((++CMMj*wcbbs$m˶RJ ~ x__Ӄ^& U4y\oXpNYcXr;NT;d6)MPTTz;٩ޣ(,,m;ZZZ@\^Ац :csn ԗm5A-_,{5fC2Q l.\E~~>(3ALr<`M/`ww;;;xzzﵵAC0j%B nID~@ u"_A / 5޼y:33SaE W@F) 6b䁘>7?3 %}(Ѿ( Solaarimage/svg+xmlSolaarDaniel Pavel2013-06-25solaar Solaar-1.1.20/share/solaar/icons/solaar-symbolic-filled.svg000066400000000000000000000325221522022367600236010ustar00rootroot00000000000000 Solaarimage/svg+xmlSolaarDaniel Pavel2013-06-25solaar Solaar-1.1.20/share/solaar/icons/solaar-symbolic.svg000066400000000000000000000063651522022367600223520ustar00rootroot00000000000000 Solaar image/svg+xml Solaar Daniel Pavel 2013-06-25 solaar Solaar-1.1.20/share/solaar/icons/solaar-tool-brush-symbolic.svg000066400000000000000000000011171522022367600244340ustar00rootroot00000000000000 Solaar-1.1.20/share/solaar/icons/solaar-tool-bucket-symbolic.svg000066400000000000000000000015671522022367600245770ustar00rootroot00000000000000 Solaar-1.1.20/share/solaar/icons/solaar-tool-palette-off-symbolic.svg000066400000000000000000000015001522022367600255130ustar00rootroot00000000000000 Solaar-1.1.20/share/solaar/icons/solaar-tool-rect-symbolic.svg000066400000000000000000000012341522022367600242460ustar00rootroot00000000000000 Solaar-1.1.20/share/solaar/icons/solaar.svg000066400000000000000000000063621522022367600205300ustar00rootroot00000000000000 Solaar image/svg+xml Solaar Daniel Pavel 2013-06-25 solaar Solaar-1.1.20/share/solaar/io.github.pwr_solaar.solaar.metainfo.xml000066400000000000000000000165411522022367600252560ustar00rootroot00000000000000 io.github.pwr_solaar.solaar Solaar Linux manager for Logitech keyboards, mice, and trackpads pwr-Solaar https://pwr-solaar.github.io/Solaar/ https://github.com/pwr-Solaar/Solaar pfpschneider_AT_gmail.com CC-BY-4.0 GPL-2.0-only pointing keyboard touch

Solaar is a Linux manager for many Logitech keyboards, mice, and trackpads that connect wirelessly to a USB, Lightspeed, or Nano receiver, connect directly via a USB cable, or connect via Bluetooth. Solaar does not work with peripherals from other companies.

Solaar can be used as a GUI application or via its command-line interface. Both interfaces are able to list the connected devices and show information about each device, often including battery status. Solaar is able to pair and unpair devices with receivers as supported by the device and receiver. Solaar can also control some changeable features of devices, such as smooth scrolling or function key behavior.

Solaar's GUI normally uses an icon in the system tray and starts with its main window visible.

solaar.desktop 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/screenshots/Solaar-main-window-receiver.png https://raw.githubusercontent.com/pwr-Solaar/Solaar/master/docs/screenshots/Solaar-menu.png solaar usb:v17EFp6042d* usb:v046DpC517d* usb:v046DpC518d* usb:v046DpC51Ad* usb:v046DpC51Bd* usb:v046DpC521d* usb:v046DpC525d* usb:v046DpC526d* usb:v046DpC52Bd* usb:v046DpC52Ed* usb:v046DpC52Fd* usb:v046DpC531d* usb:v046DpC532d* usb:v046DpC534d* usb:v046DpC537d* usb:v046DpC539d* usb:v046DpC53Ad* usb:v046DpC53Dd* usb:v046DpC53Fd* usb:v046DpC541d* usb:v046DpC545d* usb:v046DpC547d* usb:v046DpC548d* usb:v046DpC06Bd* usb:v046DpC07Cd* usb:v046DpC07Dd* usb:v046DpC07Ed* usb:v046DpC07Fd* usb:v046DpC08*d* usb:v046DpC090d* usb:v046DpC091d* usb:v046DpC092d* usb:v046DpC093d* usb:v046DpC094d* usb:v046DpC32Bd* usb:v046DpC32Cd* usb:v046DpC32Dd* usb:v046DpC32Ed* usb:v046DpC32Fd* usb:v046DpC33*d* usb:v046DpC340d* usb:v046DpC341d* usb:v046DpC342d* usb:v046DpC343d* usb:v046DpC344d* usb:v046DpB012d* usb:v046DpB013d* usb:v046DpB014d* usb:v046DpB015d* usb:v046DpB016d* usb:v046DpB017d* usb:v046DpB018d* usb:v046DpB019d* usb:v046DpB01Ad* usb:v046DpB01Bd* usb:v046DpB01Cd* usb:v046DpB01Dd* usb:v046DpB01Ed* usb:v046DpB01Fd* usb:v046DpB02*d* usb:v046DpB03*d* usb:v046DpB04*d* usb:v046DpB05*d* usb:v046DpB06*d* usb:v046DpB07*d* usb:v046DpB08*d* usb:v046DpB09*d* usb:v046DpB0A*d* usb:v046DpB0B*d* usb:v046DpB0C*d* usb:v046DpB0D*d* usb:v046DpB0E*d* usb:v046DpB0F*d* usb:v046DpB32Ad* usb:v046DpB32Bd* usb:v046DpB32Cd* usb:v046DpB32Dd* usb:v046DpB32Ed* usb:v046DpB32Fd* usb:v046DpB33*d* usb:v046DpB34*d* usb:v046DpB35*d* usb:v046DpB36*d* usb:v046DpB37*d* usb:v046DpB38*d* usb:v046DpB39*d* usb:v046DpB3A*d* usb:v046DpB3B*d* usb:v046DpB3C*d* usb:v046DpB3D*d* usb:v046DpB3E*d* usb:v046DpB3F*d*
Solaar-1.1.20/tests/000077500000000000000000000000001522022367600141635ustar00rootroot00000000000000Solaar-1.1.20/tests/__init__.py000066400000000000000000000000001522022367600162620ustar00rootroot00000000000000Solaar-1.1.20/tests/conftest.py000066400000000000000000000035151522022367600163660ustar00rootroot00000000000000import importlib from unittest import mock import pytest @pytest.fixture(autouse=True) def isolate_solaar_configuration(tmp_path, monkeypatch): """Redirect solaar.configuration at a throwaway path for every test. Tests build FakeDevices named 'TestDevice'; any that touch device.settings or device.persister without mocking call the real configuration.persister(), which loads and rewrites ~/.config/solaar/config.yaml — appending a fresh un-matchable TestDevice entry on every run. Pointing the paths at tmp_path and clearing the cached _config keeps each test off the real config and isolated from every other test.""" from solaar import configuration monkeypatch.setattr(configuration, "_yaml_file_path", str(tmp_path / "config.yaml")) monkeypatch.setattr(configuration, "_json_file_path", str(tmp_path / "config.json")) monkeypatch.setattr(configuration, "_config", []) @pytest.fixture(autouse=True) def mock_desktop_notifications(monkeypatch): """Swap the libnotify backend for a mock in both desktop_notifications modules. Tests still exercise the real init/alert/show code paths, but Notification.show() never reaches the daemon — without this the suite raises real 'MockDevice' / 'unknown' notifications on every run. Returns the Notify mock so notification tests can assert against it.""" notify = mock.MagicMock(name="Notify") notify.is_initted.return_value = True notify.init.return_value = True for modname in ("solaar.ui.desktop_notifications", "logitech_receiver.desktop_notifications"): try: module = importlib.import_module(modname) except Exception: continue monkeypatch.setattr(module, "Notify", notify, raising=False) monkeypatch.setattr(module, "_notifications", {}, raising=False) return notify Solaar-1.1.20/tests/hid_parser/000077500000000000000000000000001522022367600163035ustar00rootroot00000000000000Solaar-1.1.20/tests/hid_parser/__init__.py000066400000000000000000000000001522022367600204020ustar00rootroot00000000000000Solaar-1.1.20/tests/hid_parser/test_data.py000066400000000000000000000004221522022367600206230ustar00rootroot00000000000000from 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 Solaar-1.1.20/tests/hidapi/000077500000000000000000000000001522022367600154215ustar00rootroot00000000000000Solaar-1.1.20/tests/hidapi/__init__.py000066400000000000000000000000001522022367600175200ustar00rootroot00000000000000Solaar-1.1.20/tests/hidapi/test_hidapi.py000066400000000000000000000003421522022367600202670ustar00rootroot00000000000000import 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()) Solaar-1.1.20/tests/logitech_receiver/000077500000000000000000000000001522022367600176455ustar00rootroot00000000000000Solaar-1.1.20/tests/logitech_receiver/__init__.py000066400000000000000000000000001522022367600217440ustar00rootroot00000000000000Solaar-1.1.20/tests/logitech_receiver/fake_hidpp.py000066400000000000000000000600111522022367600223070ustar00rootroot00000000000000## 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 path = None cleanups = None 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 def __post_init__(self): self._name = self.name self._protocol = self.protocol self.persister = configuration._DeviceEntry() self.features = hidpp20.FeaturesArray(self) self.settings = [] self.cleanups = [] 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 Solaar-1.1.20/tests/logitech_receiver/test_base.py000066400000000000000000000412031522022367600221700ustar00rootroot00000000000000import 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 CENTURION_ADDRESSED_REPORT_ID from logitech_receiver.base import CENTURION_REPORT_ID from logitech_receiver.base import HIDPP_SHORT_MESSAGE_ID from logitech_receiver.base import CenturionHandleState 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(): assert base._get_next_sw_id() == base.SOLAAR_SOFTWARE_ID assert base._get_next_sw_id() == base.SOLAAR_SOFTWARE_ID @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 # --- Centurion transport tests --- class TestCenturionFrameHeader: """Test _centurion_frame_header builds correct headers for both variants.""" def test_0x51_header(self): state = CenturionHandleState(report_id=CENTURION_REPORT_ID) header = base._centurion_frame_header(state, cpl_length=5, flags=0x00) assert header == bytes([0x51, 5, 0x00]) def test_0x51_header_with_flags(self): state = CenturionHandleState(report_id=CENTURION_REPORT_ID) header = base._centurion_frame_header(state, cpl_length=10, flags=0x03) assert header == bytes([0x51, 10, 0x03]) def test_0x50_header_unknown_addr(self): state = CenturionHandleState(report_id=CENTURION_ADDRESSED_REPORT_ID, device_addr=None) header = base._centurion_frame_header(state, cpl_length=5, flags=0x00) # device_addr defaults to 0x00 when unknown assert header == bytes([0x50, 0x00, 5, 0x00]) def test_0x50_header_known_addr(self): state = CenturionHandleState(report_id=CENTURION_ADDRESSED_REPORT_ID, device_addr=0x23) header = base._centurion_frame_header(state, cpl_length=5, flags=0x00) assert header == bytes([0x50, 0x23, 5, 0x00]) def test_0x50_header_with_flags(self): state = CenturionHandleState(report_id=CENTURION_ADDRESSED_REPORT_ID, device_addr=0x23) header = base._centurion_frame_header(state, cpl_length=10, flags=0x07) assert header == bytes([0x50, 0x23, 10, 0x07]) class TestUnwrapCenturionFrame: """Test _unwrap_centurion_frame for both 0x51 and 0x50 variants.""" HANDLE = 99 def setup_method(self): """Ensure no leftover centurion state between tests.""" base._centurion_handles.pop(self.HANDLE, None) def teardown_method(self): base._centurion_handles.pop(self.HANDLE, None) def test_unwrap_0x51_frame(self): """0x51 frame with feat_idx=0x02, func_sw=0x1A, 2 data bytes.""" # cpl_length = 1(flags) + 1(feat_idx) + 1(func_sw) + 2(data) = 5 raw = bytes([0x51, 5, 0x00, 0x02, 0x1A, 0xAA, 0xBB]) + b"\x00" * 57 result = base._unwrap_centurion_frame(raw, self.HANDLE, self.HANDLE) # Should reconstruct as [0x11, 0xFF, feat_idx, func_sw, data..., pad to 20] assert result[0] == 0x11 assert result[1] == 0xFF assert result[2] == 0x02 # feat_idx assert result[3] == 0x1A # func_sw assert result[4] == 0xAA assert result[5] == 0xBB assert len(result) == 20 # padded to standard long def test_unwrap_0x50_frame(self): """0x50 frame with device_addr=0x23, same payload as above.""" # Frame: [0x50, device_addr, cpl_length, flags, feat_idx, func_sw, data...] raw = bytes([0x50, 0x23, 5, 0x00, 0x02, 0x1A, 0xAA, 0xBB]) + b"\x00" * 56 base._centurion_handles[self.HANDLE] = CenturionHandleState(report_id=CENTURION_ADDRESSED_REPORT_ID) result = base._unwrap_centurion_frame(raw, self.HANDLE, self.HANDLE) assert result[0] == 0x11 assert result[1] == 0xFF assert result[2] == 0x02 # feat_idx assert result[3] == 0x1A # func_sw assert result[4] == 0xAA assert result[5] == 0xBB assert len(result) == 20 def test_0x50_learns_device_addr(self): """First RX on a 0x50 handle should learn the device address.""" base._centurion_handles[self.HANDLE] = CenturionHandleState(report_id=CENTURION_ADDRESSED_REPORT_ID) assert base._centurion_handles[self.HANDLE].device_addr is None raw = bytes([0x50, 0x23, 3, 0x00, 0x02, 0x1A]) + b"\x00" * 58 base._unwrap_centurion_frame(raw, self.HANDLE, self.HANDLE) assert base._centurion_handles[self.HANDLE].device_addr == 0x23 def test_0x50_does_not_overwrite_addr(self): """Once learned, device address should not be overwritten.""" base._centurion_handles[self.HANDLE] = CenturionHandleState(report_id=CENTURION_ADDRESSED_REPORT_ID, device_addr=0x23) raw = bytes([0x50, 0xFF, 3, 0x00, 0x02, 0x1A]) + b"\x00" * 58 base._unwrap_centurion_frame(raw, self.HANDLE, self.HANDLE) # Should keep the original address, not overwrite with 0xFF assert base._centurion_handles[self.HANDLE].device_addr == 0x23 def test_non_centurion_frame_passthrough(self): """Non-centurion report IDs should be returned unchanged.""" raw = bytes([0x11, 0x01, 0x02, 0x1A]) + b"\x00" * 16 result = base._unwrap_centurion_frame(raw, self.HANDLE, self.HANDLE) assert result == raw def test_unwrap_0x51_large_payload(self): """0x51 frame with payload large enough to need 63-byte padding.""" # cpl_length covers all 61 payload bytes + flags = 62 payload = bytes(range(61)) raw = bytes([0x51, 62, 0x00]) + payload result = base._unwrap_centurion_frame(raw, self.HANDLE, self.HANDLE) assert len(result) == 63 # padded to centurion extended assert result[0] == 0x11 assert result[1] == 0xFF assert result[2:63] == payload class TestProbeCenturionDeviceAddr: """Test probe_centurion_device_addr: brute-force write for all 256 addrs, then read.""" HANDLE = 101 def setup_method(self): base._centurion_handles.pop(self.HANDLE, None) def teardown_method(self): base._centurion_handles.pop(self.HANDLE, None) def test_learns_addr_on_first_hit(self): """Probe finds addr=0x23 on candidate #36 (0-indexed 0x23=35) and stops.""" state = CenturionHandleState(report_id=CENTURION_ADDRESSED_REPORT_ID) reply = bytes([0x50, 0x23, 0x03, 0x00]) + b"\x00" * 60 def read_side_effect(_handle, _size, _timeout): # Return a response only after the write with addr=0x23 if mock_write.call_count == 0x24: # 0x23 is the 36th write (1-indexed) return reply return None with ( mock.patch.object(base.hidapi, "write") as mock_write, mock.patch.object(base.hidapi, "read", side_effect=read_side_effect), ): result = base.probe_centurion_device_addr(self.HANDLE, state) assert result is True assert state.device_addr == 0x23 # Short-circuit: stopped at candidate 0x23 (36 writes), not all 256 assert mock_write.call_count == 0x24 def test_skips_non_matching_read_until_match(self): """Non-0x50 frames in the read are ignored; next candidate's read succeeds.""" state = CenturionHandleState(report_id=CENTURION_ADDRESSED_REPORT_ID) noise = b"\x11\xff" + b"\x00" * 62 match = bytes([0x50, 0x42, 0x03, 0x00]) + b"\x00" * 60 # Reads cycle: noise, noise, match — so addr is found on 3rd candidate with ( mock.patch.object(base.hidapi, "write"), mock.patch.object(base.hidapi, "read", side_effect=[noise, noise, match]), ): result = base.probe_centurion_device_addr(self.HANDLE, state) assert result is True assert state.device_addr == 0x42 def test_returns_false_when_no_response(self): state = CenturionHandleState(report_id=CENTURION_ADDRESSED_REPORT_ID) with ( mock.patch.object(base.hidapi, "write"), mock.patch.object(base.hidapi, "read", return_value=None), ): result = base.probe_centurion_device_addr(self.HANDLE, state) assert result is False assert state.device_addr is None def test_noop_for_0x51_variant(self): state = CenturionHandleState(report_id=CENTURION_REPORT_ID) with ( mock.patch.object(base.hidapi, "write") as mock_write, mock.patch.object(base.hidapi, "read") as mock_read, ): result = base.probe_centurion_device_addr(self.HANDLE, state) assert result is False assert state.device_addr is None mock_write.assert_not_called() mock_read.assert_not_called() def test_noop_when_addr_already_known(self): state = CenturionHandleState(report_id=CENTURION_ADDRESSED_REPORT_ID, device_addr=0x23) with ( mock.patch.object(base.hidapi, "write") as mock_write, mock.patch.object(base.hidapi, "read") as mock_read, ): result = base.probe_centurion_device_addr(self.HANDLE, state) assert result is False assert state.device_addr == 0x23 mock_write.assert_not_called() mock_read.assert_not_called() def test_aborts_on_repeated_write_failure(self): state = CenturionHandleState(report_id=CENTURION_ADDRESSED_REPORT_ID) with ( mock.patch.object(base.hidapi, "write", side_effect=OSError("no device")), mock.patch.object(base.hidapi, "read") as mock_read, ): result = base.probe_centurion_device_addr(self.HANDLE, state) assert result is False assert state.device_addr is None mock_read.assert_not_called() def test_write_frames_have_sequential_addrs(self): """Verify each write uses a different device_addr from 0x00 to 0xFF.""" state = CenturionHandleState(report_id=CENTURION_ADDRESSED_REPORT_ID) with ( mock.patch.object(base.hidapi, "write") as mock_write, mock.patch.object(base.hidapi, "read", return_value=None), # no response → scans all 256 ): base.probe_centurion_device_addr(self.HANDLE, state) assert mock_write.call_count == 256 addrs_sent = [mock_write.call_args_list[i][0][1][1] for i in range(256)] assert addrs_sent == list(range(256)) Solaar-1.1.20/tests/logitech_receiver/test_base_usb.py000066400000000000000000000013561522022367600230460ustar00rootroot00000000000000import 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) Solaar-1.1.20/tests/logitech_receiver/test_common.py000066400000000000000000000234201522022367600225470ustar00rootroot00000000000000from 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_color_int_str_and_repr(): c = common.ColorInt(0xFC3300) assert str(c) == "0xfc3300" assert repr(c) == "0xfc3300" assert int(c) == 0xFC3300 def test_color_int_equality_with_plain_int(): assert common.ColorInt(0xFC3300) == 0xFC3300 assert isinstance(common.ColorInt(0), int) def test_color_int_from_hex_string_prefixes(): assert common.ColorInt("0xfc3300") == 0xFC3300 assert common.ColorInt("0XFC3300") == 0xFC3300 assert common.ColorInt("#fc3300") == 0xFC3300 def test_color_int_from_int_passes_through(): assert common.ColorInt(0) == 0 assert common.ColorInt(0xFFFFFF) == 0xFFFFFF def test_color_int_out_of_range_falls_back_to_decimal(): # Sentinel values like COLORSPLUS["No change"] = -1 should still render # in their natural form when wrapped (though they normally aren't). assert str(common.ColorInt(-1)) == "-1" def test_color_int_yaml_dumps_as_hex_int_literal(): c = common.ColorInt(0xFC3300) dumped = yaml.dump(c).strip() assert dumped == "0xfc3300\n..." or dumped.startswith("0xfc3300") def test_color_int_yaml_roundtrips_to_plain_int(): # yaml.safe_load returns a plain int (yaml 1.1 hex literal parsing). # The value matches; type promotion to ColorInt happens lazily at # validator/setting boundaries. c = common.ColorInt(0xFC3300) loaded = yaml.safe_load(yaml.dump(c)) assert loaded == 0xFC3300 def test_color_int_in_dict_yaml_dumps_each_value_as_hex(): data = {1: common.ColorInt(0xFC3300), 2: common.ColorInt(0x00FF00), 3: -1} dumped = yaml.dump(data, default_flow_style=None, width=150).strip() assert "0xfc3300" in dumped assert "0x00ff00" in dumped assert "-1" in dumped # sentinel preserved 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, ""), (common.BatteryStatus.OFFLINE, 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)" def test_battery_offline(): battery = common.Battery(58, None, common.BatteryStatus.OFFLINE, None) assert battery.status == common.BatteryStatus.OFFLINE assert battery.level == 58 assert battery.ok() assert not battery.charging() assert battery.to_str() == "Battery: 58% (offline)" Solaar-1.1.20/tests/logitech_receiver/test_desktop_notifications.py000066400000000000000000000015351522022367600256640ustar00rootroot00000000000000from unittest import mock from logitech_receiver import desktop_notifications # The mock_desktop_notifications autouse fixture (tests/conftest.py) swaps the # libnotify backend for a mock, so these exercise the real code paths without # raising real desktop notifications. 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(mock_desktop_notifications): result = desktop_notifications.show(MockDevice(), "unknown") if desktop_notifications.available: assert result is not None mock_desktop_notifications.Notification.return_value.show.assert_called() else: assert result is None Solaar-1.1.20/tests/logitech_receiver/test_device.py000066400000000000000000000405541522022367600225250ustar00rootroot00000000000000## 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 from unittest import mock 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 centurion_report_id: int | None = None 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 with mock.patch.object(base, "probe_centurion_device_addr", return_value=False): 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 state = base._centurion_handles[int(test_device.handle)] assert state.report_id == base.CENTURION_REPORT_ID # 0x51 default # kind is seeded at construction, so the headset icon shows even offline test_device.online = False assert test_device.kind == "headset" # Clean up base._centurion_handles.pop(int(test_device.handle), None) di_0B18 = DeviceInfoStub("11", product_id="0B18", centurion=True, centurion_report_id=0x50) def test_create_centurion_0x50_device(): """Test that a 0x50 centurion device gets the correct report ID registered.""" from logitech_receiver import base with mock.patch.object(base, "probe_centurion_device_addr", return_value=False): low_level_mock = LowLevelInterfaceFake(fake_hidpp.r_empty) test_device = device.create_device(low_level_mock, di_0B18) 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 state = base._centurion_handles[int(test_device.handle)] assert state.report_id == base.CENTURION_ADDRESSED_REPORT_ID # 0x50 assert state.device_addr is None # not yet learned # Clean up base._centurion_handles.pop(int(test_device.handle), None) @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 def test_codename_uses_full_name(): """A direct-connected HID++ 2.0 device with no friendly name uses its full live name as the codename instead of truncating it at the first space.""" test_device = device.create_device(LowLevelInterfaceFake(fake_hidpp.r_empty), di_CCCC) test_device._protocol = 2.0 test_device.online = True test_device._codename = None test_device._name = "G502 X PLUS" assert test_device.codename == "G502 X PLUS" # a leading "Logitech" is still dropped test_device._codename = None test_device._name = "Logitech MX Master 3" assert test_device.codename == "MX Master 3" @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) Solaar-1.1.20/tests/logitech_receiver/test_diversion.py000066400000000000000000000067461522022367600232750ustar00rootroot00000000000000import 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) Solaar-1.1.20/tests/logitech_receiver/test_hidpp10.py000066400000000000000000000316311522022367600225270ustar00rootroot00000000000000from 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 Solaar-1.1.20/tests/logitech_receiver/test_hidpp20_complex.py000066400000000000000000001422731522022367600242640ustar00rootroot00000000000000## 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.GetCount (func 0) -> total feature count # 3. CenturionFeatureSet.GetFeatureId (func 0x10) per-index -> one feature each 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).GetCount (func=0) -> 3 features (0x01, 0x00, ""): bytes([0x03, 0x00, 0x00]), # CenturionFeatureSet(idx=1).GetFeatureId (func=0x10, index=N) -> one feature per response. # Response format: [remaining, feat_hi, feat_lo, type, flags] (0x01, 0x10, "00"): bytes([0x02, 0x06, 0x04, 0x00, 0x00]), # HEADSET_AUDIO_SIDETONE at sub-idx 0 (0x01, 0x10, "01"): bytes([0x01, 0x06, 0x01, 0x00, 0x00]), # HEADSET_MIC_MUTE at sub-idx 1 (0x01, 0x10, "02"): bytes([0x00, 0x06, 0x11, 0x00, 0x00]), # HEADSET_MIC_GAIN 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 # --- 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 == "Lightspeed Headset 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_child_device_kind_headset(): """notify_devices() seeds the headset child with kind=headset via pairing_info, so the headphone icon shows even when the headset is powered off at startup.""" info = FakeCenturionDeviceInfo(product="PRO X 2 LIGHTSPEED") recv = CenturionReceiver(FakeLowLevel(), 0x99, info) recv._pending = False recv._dongle_features = [] recv.notify_devices() child = recv[1] child.online = False assert child.kind == "headset" 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 Solaar-1.1.20/tests/logitech_receiver/test_hidpp20_simple.py000066400000000000000000000413161522022367600241020ustar00rootroot00000000000000## 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_get_configuration_cookie(): responses = [fake_hidpp.Response("12345678", 0x0400)] device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.CONFIG_CHANGE) result = _hidpp20.get_configuration_cookie(device) assert result == bytes.fromhex("1234") def test_set_configuration_complete_explicit_cookie(): # An explicit cookie is sent unchanged. responses = [fake_hidpp.Response("00", 0x0410, "1234")] device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.CONFIG_CHANGE) result = _hidpp20.set_configuration_complete(device, cookie=bytes.fromhex("1234")) assert result == bytes.fromhex("00") def test_set_configuration_complete_monotonic_counter(): # With no cookie, sends a host-side monotonic counter, +1 per call. hidpp20.Hidpp20._session_cookie = 0x1233 responses = [ fake_hidpp.Response("00", 0x0410, "1234"), fake_hidpp.Response("00", 0x0410, "1235"), ] device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.CONFIG_CHANGE) assert _hidpp20.set_configuration_complete(device) == bytes.fromhex("00") assert _hidpp20.set_configuration_complete(device) == bytes.fromhex("00") def test_set_configuration_complete_skips_zero(): # Counter at 0xFFFF wraps to 1, never 0. hidpp20.Hidpp20._session_cookie = 0xFFFF responses = [fake_hidpp.Response("00", 0x0410, "0001")] device = fake_hidpp.Device(responses=responses, feature=SupportedFeature.CONFIG_CHANGE) assert _hidpp20.set_configuration_complete(device) == bytes.fromhex("00") 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 Solaar-1.1.20/tests/logitech_receiver/test_notifications.py000066400000000000000000000322521522022367600241330ustar00rootroot00000000000000import 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 Solaar-1.1.20/tests/logitech_receiver/test_receiver.py000066400000000000000000000274341522022367600230740ustar00rootroot00000000000000from 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() Solaar-1.1.20/tests/logitech_receiver/test_rgb_power.py000066400000000000000000000574271522022367600232630ustar00rootroot00000000000000## 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. """Tests for the display-state-aware color translation in rgb_power. These cover the pure math (`translate_color_for_display`) and the small manager hooks that PerKeyLighting calls into (`translate_color`, `notify_perkey_changed`) without requiring a GLib main loop. """ import pytest from logitech_receiver import rgb_power M = rgb_power.RGBPowerManager # --- translate_color_for_display (pure function) ---------------------------- @pytest.mark.parametrize( "color", [0x000000, 0x123456, 0xFF0000, 0xFFFFFF], ) def test_translate_active_is_identity(color): assert rgb_power.translate_color_for_display(color, M.ACTIVE, 50, 0, 25) == color def test_translate_idle_50pct(): # 255 * 50 // 100 == 127 assert rgb_power.translate_color_for_display(0xFFFFFF, M.IDLE, 50, 0, 25) == 0x7F7F7F def test_translate_idle_25pct_on_ff8800(): # 0xFF * 25 // 100 = 63 (0x3F); 0x88 * 25 // 100 = 34 (0x22); 0 stays 0 assert rgb_power.translate_color_for_display(0xFF8800, M.IDLE, 25, 0, 25) == 0x3F2200 def test_translate_dimming_start_is_saved_color(): # t = 0/25 = 0 → interpolation returns the start (saved) color assert rgb_power.translate_color_for_display(0xABCDEF, M.DIMMING, 50, 0, 25) == 0xABCDEF def test_translate_dimming_end_equals_idle(): # t = 25/25 = 1 → interpolation returns the target (fully dimmed) idle = rgb_power.translate_color_for_display(0xFFFFFF, M.IDLE, 50, 0, 25) dimming_end = rgb_power.translate_color_for_display(0xFFFFFF, M.DIMMING, 50, 25, 25) assert dimming_end == idle def test_translate_dimming_midramp_between_full_and_dim(): # At t=12/25 (≈0.48), white interpolates between 0xFF and 0x7F (50% target). # Expected r = 255 + (127 - 255) * 12/25 = 255 - 61.44 → int(193.56) = 193 (0xC1) assert rgb_power.translate_color_for_display(0xFFFFFF, M.DIMMING, 50, 12, 25) == 0xC1C1C1 def test_translate_sleeping_returns_none(): assert rgb_power.translate_color_for_display(0xFFFFFF, M.SLEEPING, 50, 0, 25) is None # --- translate_for_device (manager lookup) ---------------------------------- def test_translate_for_device_no_manager_is_identity(): # An arbitrary object with no manager registered → returns input unchanged. fake = object() assert rgb_power.translate_for_device(fake, 0xABCDEF) == 0xABCDEF def _dim(intensity): """Shortcut: build a Dim-mode LEDEffectSetting for tests that used to pass a bare dim-percent int as `_idle_effect`.""" from logitech_receiver import hidpp20 return hidpp20.LEDEffectSetting(ID=0x80, intensity=intensity) def _install_manager(monkeypatch, state, idle_effect=None, dim_step=0): """Build an RGBPowerManager with state injected and register it for a fake device id. Cleanup happens via monkeypatch's _managers swap. `idle_effect` defaults to Dim 50%. Pass a bare int (legacy) and it will be wrapped as a Dim-mode LEDEffectSetting; pass an LEDEffectSetting directly to use as-is. """ from logitech_receiver import hidpp20 fake_device = object() class _Dev: pass mgr = M.__new__(M) # bypass __init__ (no GLib needed) mgr._device = _Dev() mgr._state = state if idle_effect is None: mgr._idle_effect = _dim(50) elif isinstance(idle_effect, int): mgr._idle_effect = _dim(idle_effect) else: mgr._idle_effect = idle_effect mgr._dim_step = dim_step mgr._dim_perkey = None # Avoid circular import surprise — make sure the LEDEffectSetting is # imported here so type checks in helpers don't crash. _ = hidpp20.LEDEffectSetting saved_managers = dict(rgb_power._managers) monkeypatch.setattr(rgb_power, "_managers", {id(fake_device): mgr}) yield_data = (fake_device, mgr, saved_managers) return yield_data def test_translate_for_device_idle_routes_through_manager(monkeypatch): fake_device, mgr, _ = _install_manager(monkeypatch, M.IDLE, idle_effect=50) assert rgb_power.translate_for_device(fake_device, 0xFFFFFF) == 0x7F7F7F def test_translate_for_device_sleeping_returns_none(monkeypatch): fake_device, _, _ = _install_manager(monkeypatch, M.SLEEPING) assert rgb_power.translate_for_device(fake_device, 0xFFFFFF) is None def test_current_dim_pct_falls_back_to_100_for_non_dim_effects(): from logitech_receiver import hidpp20 mgr = M.__new__(M) # Dim is the only host-side idle effect — it's the only one Solaar can # render itself (by interpolating colors toward a dimmer target on a # Static zone color or in the per-key buffer). Disabled does nothing. # Breathe and Ripple hand off to the firmware effect engine, which # runs the animation at its own brightness; Solaar applies no dim # translation in those cases, so _current_dim_pct returns 100. for fw_or_disabled in (0x00, 0x0A, 0x0B): mgr._idle_effect = hidpp20.LEDEffectSetting(ID=fw_or_disabled) assert mgr._current_dim_pct() == 100 # Dim mode — intensity carries the dim percentage. for dim_pct in (25, 50, 75): mgr._idle_effect = hidpp20.LEDEffectSetting(ID=0x80, intensity=dim_pct) assert mgr._current_dim_pct() == dim_pct # --- notify_perkey_changed -------------------------------------------------- def test_notify_perkey_changed_updates_dim_map_during_dimming(): mgr = M.__new__(M) mgr._state = M.DIMMING mgr._idle_effect = _dim(50) mgr._dim_perkey = {5: (0xFFFFFF, 0x7F7F7F)} mgr.notify_perkey_changed(5, 0x00FF00) start, target = mgr._dim_perkey[5] assert start == 0x00FF00 # target = dim(0x00FF00, 50) = (0, 0xFF*50//100, 0) = (0, 0x7F, 0) assert target == 0x007F00 def test_notify_perkey_changed_noop_when_not_dimming(): mgr = M.__new__(M) mgr._state = M.IDLE mgr._idle_effect = _dim(50) mgr._dim_perkey = {5: (0xFFFFFF, 0x7F7F7F)} mgr.notify_perkey_changed(5, 0x00FF00) assert mgr._dim_perkey[5] == (0xFFFFFF, 0x7F7F7F) def test_notify_perkey_changed_noop_for_unknown_zone(): mgr = M.__new__(M) mgr._state = M.DIMMING mgr._idle_effect = _dim(50) mgr._dim_perkey = {5: (0xFFFFFF, 0x7F7F7F)} mgr.notify_perkey_changed(99, 0x00FF00) # not in _dim_perkey assert mgr._dim_perkey == {5: (0xFFFFFF, 0x7F7F7F)} def test_notify_perkey_bulk_changed_skips_no_change(): from logitech_receiver import special_keys no_change = special_keys.COLORSPLUS["No change"] mgr = M.__new__(M) mgr._state = M.DIMMING mgr._idle_effect = _dim(50) mgr._dim_perkey = {5: (0xFFFFFF, 0x7F7F7F), 7: (0x000000, 0x000000)} mgr.notify_perkey_bulk_changed({5: 0x00FF00, 7: no_change}) # Zone 5 updated, zone 7 (No change) left alone. assert mgr._dim_perkey[5][0] == 0x00FF00 assert mgr._dim_perkey[7] == (0x000000, 0x000000) # --- notify_zone_changed (zone-effect dim ramp) ----------------------------- class _FakeZone: """Minimal stand-in for hidpp20.LEDZoneInfo — only `.index` is consulted by RGBPowerManager.notify_zone_changed.""" def __init__(self, index): self.index = index def test_notify_zone_changed_updates_matching_cluster_during_dimming(): mgr = M.__new__(M) mgr._state = M.DIMMING mgr._idle_effect = _dim(50) zone_a = _FakeZone(0) zone_b = _FakeZone(1) mgr._dim_zones = [ (zone_a, 0xFFFFFF, 0x7F7F7F), (zone_b, 0xFF0000, 0x7F0000), ] mgr.notify_zone_changed(1, 0x00FF00) # zone_a unchanged assert mgr._dim_zones[0] == (zone_a, 0xFFFFFF, 0x7F7F7F) # zone_b updated: new start + recomputed target at 50% zone, start, target = mgr._dim_zones[1] assert zone is zone_b assert start == 0x00FF00 # _compute_dim_color(0x00FF00, 50) = (0, 0xFF*50//100, 0) = (0, 0x7F, 0) assert target == 0x007F00 def test_notify_zone_changed_noop_when_not_dimming(): mgr = M.__new__(M) mgr._state = M.IDLE mgr._idle_effect = _dim(50) zone = _FakeZone(0) mgr._dim_zones = [(zone, 0xFFFFFF, 0x7F7F7F)] mgr.notify_zone_changed(0, 0x00FF00) assert mgr._dim_zones[0] == (zone, 0xFFFFFF, 0x7F7F7F) def test_notify_zone_changed_noop_for_unknown_cluster(): mgr = M.__new__(M) mgr._state = M.DIMMING mgr._idle_effect = _dim(50) zone = _FakeZone(0) mgr._dim_zones = [(zone, 0xFFFFFF, 0x7F7F7F)] mgr.notify_zone_changed(99, 0x00FF00) # cluster 99 not in _dim_zones assert mgr._dim_zones[0] == (zone, 0xFFFFFF, 0x7F7F7F) def test_notify_zone_changed_noop_when_dim_zones_empty(): mgr = M.__new__(M) mgr._state = M.DIMMING mgr._idle_effect = _dim(50) mgr._dim_zones = [] # e.g. per-key active, zone path skipped mgr.notify_zone_changed(0, 0x00FF00) # should not raise assert mgr._dim_zones == [] # --- perkey_has_paint / zone_effect_is_static (module-level predicates) ----- class _FakePerKey: """Minimal stand-in for PerKeyLighting: holds a _value map and a _validator with .choices that perkey_has_paint inspects.""" name = "per-key-lighting" def __init__(self, value, choices=(1, 2, 3)): self._value = value class _V: pass self._validator = _V() self._validator.choices = list(choices) class _FakeZoneSetting: """Minimal stand-in for an RGBEffectSetting child instance: holds a name starting with "rgb_zone_" and a _value with an .ID attribute.""" def __init__(self, name, value): self.name = name self._value = value class _ValueWithID: """Stand-in for hidpp20.LEDEffectSetting — only .ID is consulted by zone_effect_is_static.""" def __init__(self, ID): self.ID = ID class _FakePersister: def __init__(self, sensitivities=None): self._s = sensitivities or {} def get_sensitivity(self, name): return self._s.get(name, False) class _FakeDevice: def __init__(self, settings_list, persister=None): self.settings = settings_list self.persister = persister def test_perkey_has_paint_with_real_colors(): pk = _FakePerKey({1: 0xFF0000, 2: -1, 3: -1}) # one real color, rest "No change" dev = _FakeDevice([pk], _FakePersister({"per-key-lighting": True})) found, has_paint = rgb_power.perkey_has_paint(dev) assert found is pk assert has_paint is True def test_perkey_has_paint_with_locked_sensitivity(): # False (locked) still counts as paint — only IGNORE opts out. pk = _FakePerKey({1: 0xFF0000}) dev = _FakeDevice([pk], _FakePersister()) # sensitivity defaults to False _, has_paint = rgb_power.perkey_has_paint(dev) assert has_paint is True def test_perkey_has_paint_only_no_change(): pk = _FakePerKey({1: -1, 2: -1, 3: -1}) # all "No change" dev = _FakeDevice([pk]) found, has_paint = rgb_power.perkey_has_paint(dev) assert found is pk assert has_paint is False def test_perkey_has_paint_no_perkey_setting(): dev = _FakeDevice([]) found, has_paint = rgb_power.perkey_has_paint(dev) assert found is None assert has_paint is False def test_perkey_has_paint_validator_choices_empty(): pk = _FakePerKey({1: 0xFF0000}, choices=()) dev = _FakeDevice([pk]) found, has_paint = rgb_power.perkey_has_paint(dev) assert found is pk assert has_paint is False def test_perkey_has_paint_user_ignores_perkey(): from logitech_receiver import settings as _settings pk = _FakePerKey({1: 0xFF0000}) dev = _FakeDevice([pk], _FakePersister({"per-key-lighting": _settings.SENSITIVITY_IGNORE})) found, has_paint = rgb_power.perkey_has_paint(dev) assert found is pk assert has_paint is False def test_perkey_has_paint_with_no_persister(): # No persister, no IGNORE flag — treat as paint present. pk = _FakePerKey({1: 0xFF0000}) dev = _FakeDevice([pk], persister=None) _, has_paint = rgb_power.perkey_has_paint(dev) assert has_paint is True def test_zone_effect_is_static_true_for_static(): z = _FakeZoneSetting("rgb_zone_1", _ValueWithID(0x01)) assert rgb_power.zone_effect_is_static(_FakeDevice([z])) is True def test_zone_effect_is_static_false_for_animated(): for eff_id in (0x02, 0x03, 0x0A, 0x0B, 0x0E, 0x15): z = _FakeZoneSetting("rgb_zone_1", _ValueWithID(eff_id)) assert rgb_power.zone_effect_is_static(_FakeDevice([z])) is False, eff_id def test_zone_effect_is_static_false_for_disabled(): # Disabled (0x00) means the user wants no zone effect — including # suppressing the per-key paint, since per-key push would re-light # the device. z = _FakeZoneSetting("rgb_zone_1", _ValueWithID(0x00)) assert rgb_power.zone_effect_is_static(_FakeDevice([z])) is False def test_zone_effect_is_static_true_when_no_zone_setting(): # Devices that only enumerate PER_KEY_LIGHTING_V2 (no RGB_EFFECTS) have # no zone-effect setting at all — per-key paint should be free to drive. assert rgb_power.zone_effect_is_static(_FakeDevice([])) is True def test_zone_effect_is_static_true_when_any_zone_is_static(): # Multi-zone device with one Static and one Cycle zone — at least one # Static slot is enough for per-key to overlay on. a = _FakeZoneSetting("rgb_zone_1", _ValueWithID(0x01)) # Static b = _FakeZoneSetting("rgb_zone_2", _ValueWithID(0x03)) # Cycle assert rgb_power.zone_effect_is_static(_FakeDevice([a, b])) is True # --- RGBEffectSetting.write divert / push -------------------------------- def _make_rgb_effect_setting(device, current_value): """Build a partial RGBEffectSetting bound to `device` without running the setup classmethod (which requires a real led_effects descriptor). Only the fields touched by RGBEffectSetting.write are populated.""" from unittest.mock import MagicMock from logitech_receiver import settings_templates s = settings_templates.RGBEffectSetting.__new__(settings_templates.RGBEffectSetting) s._device = device s._value = current_value s._rw = MagicMock() s._rw.prefix = b"\x01" s._validator = MagicMock() s._validator.needs_current_value = False # update() persists onto _value; mimic with a simple assignment. s.update = lambda v, save=True: setattr(s, "_value", v) return s def test_rgb_effect_write_static_to_static_repaints_unset_zones(monkeypatch): """User tweaks the Static base color while per-key is the visible layer: persist, repaint per-key's unset zones, send FrameEnd. No SetEffectByIndex (would reclaim the engine and overwrite per-key). Also notifies any in-flight dim ramp so unset cells' start_color tracks the new base.""" from unittest.mock import MagicMock from logitech_receiver import hidpp20 perkey = MagicMock() perkey._fill_unset_zones_with_base_color.return_value = True perkey._send_with_retry.return_value = True perkey._unset_zone_ids.return_value = [5, 7, 9] device = MagicMock() device.online = True mgr = MagicMock() monkeypatch.setattr(rgb_power, "perkey_has_paint", lambda d: (perkey, True)) monkeypatch.setattr(rgb_power, "get_manager", lambda d: mgr) old = hidpp20.LEDEffectSetting(ID=1, color=0xFF0000) new = hidpp20.LEDEffectSetting(ID=1, color=0x00FF00) s = _make_rgb_effect_setting(device, old) result = s.write(new) assert result is new assert s._value is new perkey._fill_unset_zones_with_base_color.assert_called_once() perkey._send_with_retry.assert_called_once_with(0x70, b"\x00") s._rw.write.assert_not_called() # No follow-up per-key push — we were already in Static. perkey.write.assert_not_called() # In-flight dim ramp gets notified that unset cells now interpolate # from the new base (0x00FF00) rather than the old. mgr.notify_perkey_bulk_changed.assert_called_once_with({5: 0x00FF00, 7: 0x00FF00, 9: 0x00FF00}) def test_rgb_effect_write_static_to_static_no_notify_when_no_unset_zones(monkeypatch): """If every per-key cell is user-painted, _unset_zone_ids returns [] and the dim-ramp notification is skipped (nothing to update).""" from unittest.mock import MagicMock from logitech_receiver import hidpp20 perkey = MagicMock() perkey._fill_unset_zones_with_base_color.return_value = True perkey._send_with_retry.return_value = True perkey._unset_zone_ids.return_value = [] device = MagicMock() device.online = True mgr = MagicMock() monkeypatch.setattr(rgb_power, "perkey_has_paint", lambda d: (perkey, True)) monkeypatch.setattr(rgb_power, "get_manager", lambda d: mgr) old = hidpp20.LEDEffectSetting(ID=1, color=0xFF0000) new = hidpp20.LEDEffectSetting(ID=1, color=0x00FF00) s = _make_rgb_effect_setting(device, old) s.write(new) mgr.notify_perkey_bulk_changed.assert_not_called() def test_rgb_effect_write_static_to_static_unchanged_is_noop(monkeypatch): """Same value persisted: no repaint, no FrameEnd, no wire write.""" from unittest.mock import MagicMock from logitech_receiver import hidpp20 perkey = MagicMock() device = MagicMock() device.online = True monkeypatch.setattr(rgb_power, "perkey_has_paint", lambda d: (perkey, True)) same = hidpp20.LEDEffectSetting(ID=1, color=0xFF0000) s = _make_rgb_effect_setting(device, same) s.write(same) perkey._fill_unset_zones_with_base_color.assert_not_called() perkey._send_with_retry.assert_not_called() s._rw.write.assert_not_called() def test_rgb_effect_write_static_to_animation_pushes_wire(monkeypatch): """User switches the zone effect dropdown from Static to an animation while per-key has paint: push the new value to wire so the animation starts. Per-key is a sub-mode of Static — animations take over the visible layer when selected.""" from unittest.mock import MagicMock from logitech_receiver import hidpp20 perkey = MagicMock() perkey._value = {1: 0xFF0000} device = MagicMock() device.online = True monkeypatch.setattr(rgb_power, "perkey_has_paint", lambda d: (perkey, True)) monkeypatch.setattr(rgb_power, "translate_for_device", lambda d, c: c) monkeypatch.setattr(rgb_power, "get_manager", lambda d: None) old = hidpp20.LEDEffectSetting(ID=0x01, color=0xFF0000) new = hidpp20.LEDEffectSetting(ID=0x0A, color=0x00FF00) s = _make_rgb_effect_setting(device, old) s._validator.prepare_write.return_value = b"prepared" s._rw.write.return_value = b"ack" s.write(new) s._rw.write.assert_called_once() perkey._fill_unset_zones_with_base_color.assert_not_called() def test_rgb_effect_write_repaints_perkey_unset_on_color_change(monkeypatch): """When per-key has paint and the zone's color changes, repaint the per-key unset cells with the new base so the visible result tracks the user's color choice.""" from unittest.mock import MagicMock from logitech_receiver import hidpp20 perkey = MagicMock() perkey._value = {1: 0xFF0000, 2: -1} perkey._fill_unset_zones_with_base_color.return_value = True perkey._unset_zone_ids.return_value = [2] device = MagicMock() device.online = True monkeypatch.setattr(rgb_power, "perkey_has_paint", lambda d: (perkey, True)) monkeypatch.setattr(rgb_power, "translate_for_device", lambda d, c: c) monkeypatch.setattr(rgb_power, "get_manager", lambda d: None) old = hidpp20.LEDEffectSetting(ID=0x01, color=0xFF0000) new = hidpp20.LEDEffectSetting(ID=0x01, color=0x00FF00) s = _make_rgb_effect_setting(device, old) s._validator.prepare_write.return_value = b"prepared" s._rw.write.return_value = b"ack" s.write(new) s._rw.write.assert_not_called() perkey._fill_unset_zones_with_base_color.assert_called_once() perkey._send_with_retry.assert_called_once_with(0x70, b"\x00") def test_rgb_effect_write_apply_path_suppressed_when_perkey_has_paint(monkeypatch): """save=False is the apply_all_settings path. When per-key has paint and is opted in, per-key fully owns the visible layer — the zone wire push is suppressed here too, not just on the save path.""" from unittest.mock import MagicMock from logitech_receiver import hidpp20 perkey = MagicMock() device = MagicMock() device.online = True monkeypatch.setattr(rgb_power, "perkey_has_paint", lambda d: (perkey, True)) monkeypatch.setattr(rgb_power, "translate_for_device", lambda d, c: c) monkeypatch.setattr(rgb_power, "get_manager", lambda d: None) new = hidpp20.LEDEffectSetting(ID=0x01, color=0x00FF00) s = _make_rgb_effect_setting(device, None) s._validator.prepare_write.return_value = b"prepared" s._rw.write.return_value = b"ack" s.write(new, save=False) s._rw.write.assert_not_called() # Apply path doesn't repaint either — that's reserved for explicit user # color changes via save=True. perkey._fill_unset_zones_with_base_color.assert_not_called() def test_rgb_effect_write_inactive_perkey_falls_through(monkeypatch): """No per-key paint (or per-key feature absent): existing translate-through-power-state wire path runs unchanged.""" from unittest.mock import MagicMock from logitech_receiver import hidpp20 perkey = MagicMock() device = MagicMock() device.online = True monkeypatch.setattr(rgb_power, "perkey_has_paint", lambda d: (perkey, False)) monkeypatch.setattr(rgb_power, "translate_for_device", lambda d, c: c) monkeypatch.setattr(rgb_power, "get_manager", lambda d: None) new = hidpp20.LEDEffectSetting(ID=0x01, color=0x00FF00) s = _make_rgb_effect_setting(device, None) s._validator.prepare_write.return_value = b"prepared" s._rw.write.return_value = b"ack" s.write(new) s._rw.write.assert_called_once() perkey._fill_unset_zones_with_base_color.assert_not_called() perkey.write.assert_not_called() # --- PerKeyLighting.write defers to firmware animations ------------------- def test_perkey_write_skipped_when_zone_is_animation(monkeypatch): """Per-key is a sub-mode of Static. When the saved zone effect is an animation (Breathe etc.), the firmware engine owns the visible layer and per-key writes do not go to the wire.""" from unittest.mock import MagicMock from logitech_receiver import settings_templates breathe_zone = _FakeZoneSetting("rgb_zone_1", _ValueWithID(0x0A)) s = settings_templates.PerKeyLighting.__new__(settings_templates.PerKeyLighting) device = MagicMock() device.online = True device.settings = [breathe_zone] s._device = device s._value = {} s._has_rgb_effects = True s._send_with_retry = MagicMock(return_value=True) s._fill_unset_zones_with_base_color = MagicMock(return_value=True) monkeypatch.setattr(rgb_power, "translate_for_device", lambda d, c: c) monkeypatch.setattr(rgb_power, "get_manager", lambda d: None) s.update = lambda m, save=True: None s.write({1: 0xFF0000}) s._send_with_retry.assert_not_called() def test_perkey_write_key_value_skipped_when_zone_is_animation(monkeypatch): """Same: write_key_value defers to firmware animations.""" from unittest.mock import MagicMock from logitech_receiver import settings_templates breathe_zone = _FakeZoneSetting("rgb_zone_1", _ValueWithID(0x0A)) s = settings_templates.PerKeyLighting.__new__(settings_templates.PerKeyLighting) device = MagicMock() device.online = True device.settings = [breathe_zone] s._device = device s._value = {} s._has_rgb_effects = True s._send_with_retry = MagicMock(return_value=True) s._send_zone_color = MagicMock(return_value=True) s._fill_unset_zones_with_base_color = MagicMock(return_value=True) monkeypatch.setattr(rgb_power, "translate_for_device", lambda d, c: c) monkeypatch.setattr(rgb_power, "get_manager", lambda d: None) s.update_key_value = lambda k, v, save=True: None s.write_key_value(7, 0xFF0000) s._send_zone_color.assert_not_called() Solaar-1.1.20/tests/logitech_receiver/test_setting_templates.py000066400000000000000000001342761522022367600250260ustar00rootroot00000000000000## 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, value_type=common.ColorInt) # 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, False, True), 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, False, True), fake_hidpp.Response("0000", 0x0450), fake_hidpp.Response("010304", 0x0450, "010304"), fake_hidpp.Response("00003C012C", 0x0470, "00"), # GetRgbPowerModeConfig: idle=60s, sleep=300s ), Setup( # RGBIdleEffect — software-only, no feature requests for read/write # The setting is a HeteroValidator carrying a LEDEffectSetting. # Default (read with empty persister) = Dim 50%. FeatureTest( settings_templates.RGBIdleEffect, hidpp20.LEDEffectSetting(ID=0x80, intensity=50), hidpp20.LEDEffectSetting(ID=0x80, intensity=75), 0, ), fake_hidpp.Response("00", 0xFFFF), # placeholder — no device requests needed ), Setup( # RGBIdleTimeout — software-only, no feature requests for read/write FeatureTest(settings_templates.RGBIdleTimeout, 60, 300, 0), fake_hidpp.Response("00", 0xFFFF), # placeholder — no device requests needed ), Setup( # RGBSleepTimeout — software-only, no feature requests for read/write FeatureTest(settings_templates.RGBSleepTimeout, 300, 600, 0), fake_hidpp.Response("00", 0xFFFF), # placeholder — no device requests needed ), 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 # --- RGBIdleEffect._pre_read legacy bare-int migration --------------------- # Solaar versions before the HeteroValidator refactor stored # `rgb_idle_effect` as a bare int (0 / 25 / 50 / 75 / 0x0A / 0x0B). # On first read after upgrade, RGBIdleEffect._pre_read should map # each to the equivalent LEDEffectSetting and write the upgraded form # back to the persister so subsequent reads return it directly. def _idle_setting_with_persisted(value): """Build a minimally-scaffolded RGBIdleEffect with `value` already in the persister and `_value` unread. Returns (setting, device). Avoids RGBIdleEffect.build's led_effects probe (which needs a full device fixture); _pre_read is fully exercised regardless. """ from solaar import configuration class _Dev: pass device = _Dev() device.persister = configuration._DeviceEntry() if value is not None: device.persister[settings_templates.RGBIdleEffect.name] = value setting = settings_templates.RGBIdleEffect.__new__(settings_templates.RGBIdleEffect) setting._device = device setting._value = None return setting, device @pytest.mark.parametrize( "legacy, expected_id, expected_attrs", [ (0, 0x00, {}), # Disabled (25, 0x80, {"intensity": 25}), # Dim 25% (50, 0x80, {"intensity": 50}), # Dim 50% (75, 0x80, {"intensity": 75}), # Dim 75% (0x0A, 0x0A, {"period": 3000, "intensity": 100}), # Breathe (0x0B, 0x0B, {"period": 3000}), # Ripple ], ) def test_RGBIdleEffect_legacy_int_migration(legacy, expected_id, expected_attrs): setting, device = _idle_setting_with_persisted(legacy) setting._pre_read(cached=True) assert isinstance(setting._value, hidpp20.LEDEffectSetting) assert int(setting._value.ID) == expected_id for attr, val in expected_attrs.items(): assert getattr(setting._value, attr) == val # Persister was rewritten so subsequent reads return the migrated form. persisted = device.persister[setting.name] assert isinstance(persisted, hidpp20.LEDEffectSetting) assert int(persisted.ID) == expected_id def test_RGBIdleEffect_already_migrated_is_unchanged(): """A persisted LEDEffectSetting passes through _pre_read untouched — no double-migration on subsequent reads.""" pre_migrated = hidpp20.LEDEffectSetting(ID=0x80, intensity=42) setting, _ = _idle_setting_with_persisted(pre_migrated) setting._pre_read(cached=True) assert setting._value is pre_migrated # same instance, no rewrap def test_RGBIdleEffect_none_value_passes_through(): """Fresh install / nothing in persister: _pre_read leaves _value as None instead of crashing in the migration branch.""" setting, _ = _idle_setting_with_persisted(None) setting._pre_read(cached=True) assert setting._value is None def test_RGBIdleEffect_unrecognized_int_passes_through(): """An int outside the legacy mapping (corrupt config, future value) falls through _pre_read without touching _value or the persister.""" setting, device = _idle_setting_with_persisted(999) setting._pre_read(cached=True) assert setting._value == 999 assert device.persister[setting.name] == 999 def test_HeadsetOnboardEffect_explicit_black_is_honored(): """An explicit Static color1 of 0 (black) must survive: 0 is a real choice — it turns the LEDs off — not an absent field to seed white.""" effect = settings_templates._HeadsetOnboardEffect(ID=0, color1=0) assert int(effect.color1) == 0 assert effect.to_bytes()[2:5] == b"\x00\x00\x00" def test_HeadsetOnboardEffect_absent_color_seeds_default(): """A genuinely absent field (None) still falls back to the per-effect default — Static with no color1 given is white.""" effect = settings_templates._HeadsetOnboardEffect(ID=0) assert int(effect.color1) == 0xFFFFFF def test_HeadsetOnboardEffect_from_bytes_black_round_trips(): """A black Static frame read off the device round-trips to black rather than being rewritten to the white default.""" effect = settings_templates._HeadsetOnboardEffect.from_bytes(bytes(9)) assert effect.ID == 0 assert int(effect.color1) == 0 def test_HeadsetOnboardEffect_absent_animated_fields_seed_defaults(): """Animated effects with no params given still get sane defaults so the picker never emits a zero-intensity (LEDs-off) frame.""" effect = settings_templates._HeadsetOnboardEffect(ID=1) # Color Cycle assert effect.intensity == 100 assert effect.period == 5000 Solaar-1.1.20/tests/logitech_receiver/test_settings_validator.py000066400000000000000000000052311522022367600251640ustar00rootroot00000000000000import pytest from logitech_receiver import common 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 def _color_validator(): rng = settings_validator.Range(min=0, max=0xFFFFFF, byte_count=3, value_type=common.ColorInt) choices = { common.NamedInt(1, "A"): rng, common.NamedInt(2, "B"): rng, common.NamedInt(3, "C"): rng, } return settings_validator.MapRangeValidator(choices) def test_map_range_to_string_formats_plain_int_through_value_type(): """Configs loaded from YAML come back as plain ints; to_string should re-wrap them via the choice's value_type so `solaar show` renders hex regardless of whether the dict came from a fresh read or a stale load.""" v = _color_validator() plain = {1: 12590120, 2: 12922150, 3: 16106001} # what YAML load produces rendered = v.to_string(plain) assert "0xc01c28" in rendered assert "0xc52d26" in rendered assert "0xf5c211" in rendered def test_map_range_to_string_passes_color_int_through(): v = _color_validator() wrapped = {1: common.ColorInt(0xFC3300), 2: common.ColorInt(0x00FF00)} rendered = v.to_string(wrapped) assert "0xfc3300" in rendered assert "0x00ff00" in rendered def test_map_range_to_string_preserves_sentinel_subclass(): """NamedInt 'No change' = -1 must not be re-wrapped (its name would be lost). The exact-type guard `type(v) is int` excludes it.""" v = _color_validator() mixed = {1: common.ColorInt(0xFC3300), 2: -1} rendered = v.to_string(mixed) assert "0xfc3300" in rendered assert "2:-1" in rendered def test_map_range_to_string_int_value_type_unchanged(): """When value_type is the default int, to_string emits decimal as before (no behavior change for non-color settings).""" rng = settings_validator.Range(min=0, max=255, byte_count=1) choices = {common.NamedInt(1, "A"): rng, common.NamedInt(2, "B"): rng} v = settings_validator.MapRangeValidator(choices) rendered = v.to_string({1: 42, 2: 200}) assert "1:42" in rendered assert "2:200" in rendered Solaar-1.1.20/tests/solaar/000077500000000000000000000000001522022367600154445ustar00rootroot00000000000000Solaar-1.1.20/tests/solaar/test_gtk.py000066400000000000000000000011011522022367600176330ustar00rootroot00000000000000from 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 Solaar-1.1.20/tests/solaar/ui/000077500000000000000000000000001522022367600160615ustar00rootroot00000000000000Solaar-1.1.20/tests/solaar/ui/test_about_dialog.py000066400000000000000000000014421522022367600221240ustar00rootroot00000000000000from 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 Solaar-1.1.20/tests/solaar/ui/test_common.py000066400000000000000000000014341522022367600207640ustar00rootroot00000000000000from 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 Solaar-1.1.20/tests/solaar/ui/test_desktop_notifications.py000066400000000000000000000020701522022367600240730ustar00rootroot00000000000000from unittest import mock from solaar.ui import desktop_notifications # The mock_desktop_notifications autouse fixture (tests/conftest.py) swaps the # libnotify backend for a mock, so these exercise the real code paths without # raising real desktop notifications. def test_init(): result = desktop_notifications.init() assert result == desktop_notifications.available def test_uninit(): assert desktop_notifications.uninit() is None def test_alert(mock_desktop_notifications): assert desktop_notifications.alert("unknown") is None if desktop_notifications.available: mock_desktop_notifications.Notification.return_value.show.assert_called() class MockDevice(mock.Mock): name = "MockDevice" def close(): return True def test_show(mock_desktop_notifications): result = desktop_notifications.show(MockDevice(), "unknown") if desktop_notifications.available: assert result is not None mock_desktop_notifications.Notification.return_value.show.assert_called() else: assert result is None Solaar-1.1.20/tests/solaar/ui/test_i18n.py000066400000000000000000000011471522022367600202540ustar00rootroot00000000000000import 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" Solaar-1.1.20/tests/solaar/ui/test_pair_window.py000066400000000000000000000200461522022367600220160ustar00rootroot00000000000000from 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 Solaar-1.1.20/tests/solaar/ui/test_probe.py000066400000000000000000000036771522022367600206160ustar00rootroot00000000000000from 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}" Solaar-1.1.20/tests/test_keysyms/000077500000000000000000000000001522022367600167265ustar00rootroot00000000000000Solaar-1.1.20/tests/test_keysyms/__init__.py000066400000000000000000000000001522022367600210250ustar00rootroot00000000000000Solaar-1.1.20/tests/test_keysyms/test_keysymdef.py000066400000000000000000000003051522022367600223350ustar00rootroot00000000000000from 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 Solaar-1.1.20/tools/000077500000000000000000000000001522022367600141615ustar00rootroot00000000000000Solaar-1.1.20/tools/clean.sh000077500000000000000000000002661522022367600156060ustar00rootroot00000000000000#!/usr/bin/env sh cd "$(dirname "$0")/.." find . -type f -name '*.py[co]' -delete find . -type d -name '__pycache__' -delete rm --force po/*~ rm --force --recursive share/locale/ Solaar-1.1.20/tools/create-macos-app.sh000077500000000000000000000074021522022367600176440ustar00rootroot00000000000000#!/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}" <