pax_global_header00006660000000000000000000000064136201132470014511gustar00rootroot0000000000000052 comment=42787ee4225cbc412c1757326c3f723056012f54 libratbag-0.13/000077500000000000000000000000001362011324700133635ustar00rootroot00000000000000libratbag-0.13/.circleci/000077500000000000000000000000001362011324700152165ustar00rootroot00000000000000libratbag-0.13/.circleci/config.yml000066400000000000000000000150061362011324700172100ustar00rootroot00000000000000libratbag_references: build_dependencies: &build_dependencies FEDORA_DEP_BUILD: gcc gcc-c++ meson dbus-daemon glib2-devel json-glib-devel libevdev-devel libudev-devel libunistring-devel python3-devel python3-evdev swig FEDORA_DEP_TEST: check-devel python3-gobject python3-lxml valgrind FEDORA_DEP_DOC: python3-sphinx python3-sphinx_rtd_theme UBUNTU_DEP_BUILD: gcc g++ meson pkg-config systemd libevdev-dev libglib2.0-dev libjson-glib-dev libsystemd-dev libudev-dev libunistring-dev python3-dev python3-evdev swig UBUNTU_DEP_TEST: check python3-gi python3-lxml valgrind default_settings: &default_settings working_directory: ~/libratbag environment: LANG: C.UTF-8 build_default: &build_default name: Build command: | rm -rf build meson build ${MESON_PARAMS} meson configure build ninja -v -C build ${NINJA_ARGS} environment: MESON_PARAMS: --prefix=/usr build_buildtype_plain: &build_buildtype_plain run: <<: *build_default name: Build with buildtype plain environment: MESON_PARAMS: --prefix=/usr -Dbuildtype=plain build_buildtype_release: &build_buildtype_release run: <<: *build_default name: Build with buildtype release environment: MESON_PARAMS: --prefix=/usr -Dbuildtype=release build_and_test: &build_and_test run: <<: *build_default name: Build and test environment: NINJA_ARGS: test build_with_docs: &build_with_docs run: <<: *build_default name: Build with documentation environment: MESON_PARAMS: -Ddocumentation=true install: &install run: name: Installing command: ninja -C build install check_install: &check_install run: name: Checking installation command: | PREFIX=/usr/local/ diff -u <(cd data/devices; ls *.device) <(cd $PREFIX/share/libratbag; ls *.device) check_uninstall: &check_uninstall run: name: Checking if any files are left after uninstall command: | PREFIX=/root/test_install meson build_install --prefix=$PREFIX ninja -C build_install install ninja -C build_install uninstall if [ -d $PREFIX ] then tree $PREFIX exit 1 fi export_logs: &export_logs store_artifacts: path: ~/libratbag/build/meson-logs start_dbus: &start_dbus run: name: Start dbus daemon command: | mkdir /run/dbus /usr/bin/dbus-daemon --system --fork fedora_prep_cache: &fedora_prep_cache <<: *default_settings steps: - run: name: Initializing Fedora dnf cache command: dnf install -y --downloadonly git ${FEDORA_DEP_BUILD} ${FEDORA_DEP_TEST} ${FEDORA_DEP_DOC} - persist_to_workspace: root: /var/cache/ paths: - dnf/* environment: *build_dependencies fedora_fetch_cache: &fedora_fetch_cache attach_workspace: at: /var/cache/ fedora_install: &fedora_install run: name: Install prerequisites command: | echo keepcache=1 >> /etc/dnf/dnf.conf dnf install -y git ${FEDORA_DEP_BUILD} ${FEDORA_DEP_TEST} sed -i 's/systemd//' /etc/nsswitch.conf fedora_settings: &fedora_settings <<: *default_settings steps: - *fedora_fetch_cache - *fedora_install - checkout - *start_dbus - *build_and_test - *install - *check_install - *check_uninstall - *build_buildtype_plain - *build_buildtype_release - *export_logs environment: *build_dependencies ubuntu_settings: &ubuntu_settings <<: *default_settings steps: - run: name: Install prerequisites command: | apt-get update apt-get install -y software-properties-common apt-get remove -y libnss-systemd add-apt-repository universe apt-get update apt-get install -y git ${UBUNTU_DEP_BUILD} ${UBUNTU_DEP_TEST} - checkout - *start_dbus - *build_and_test - *install - *check_install - *check_uninstall - *build_buildtype_plain - *build_buildtype_release - *export_logs environment: *build_dependencies doc_build: &doc_build <<: *default_settings steps: - *fedora_fetch_cache - *fedora_install - run: name: Install documentation build-deps command: dnf install -y ${FEDORA_DEP_DOC} - checkout - *build_with_docs - *export_logs - store_artifacts: path: ~/libratbag/build/doc/html - persist_to_workspace: root: build paths: - doc/html/* environment: *build_dependencies docs_deploy: &docs_deploy <<: *default_settings steps: - *fedora_fetch_cache - run: name: Install prerequisites command: dnf install -y git tree - checkout - attach_workspace: at: build - run: name: Setup the deploy SSH key command: | ssh-add -L | tail -n1 > ~/.ssh/deploy.pub cat < ~/.ssh/config Host github.com IdentityFile /root/.ssh/deploy.pub IdentitiesOnly yes EOF - run: name: Clone doc repository command: | cd ~ git clone ssh://git@github.com/${CIRCLE_PROJECT_REPONAME}/${CIRCLE_PROJECT_REPONAME}.github.io - run: name: Copy the doc, commit and push command: | set +e cd ~/${CIRCLE_PROJECT_REPONAME}.github.io \cp -r ~/${CIRCLE_PROJECT_REPONAME}/build/doc/html/* . if ! git diff-index --quiet HEAD --; then git config --global user.email "libratbag@librabtag.github.io" git config --global user.name "The libratbag crew" git add . git commit -m "update docs from ${CIRCLE_SHA1}" git push fi version: 2 jobs: fedora_cache: <<: *fedora_prep_cache docker: - image: fedora:31 fedora_31: <<: *fedora_settings docker: - image: fedora:31 ubuntu_19_04: <<: *ubuntu_settings docker: - image: ubuntu:19.04 doc_build: <<: *doc_build docker: - image: fedora:31 doc_deploy: <<: *docs_deploy docker: - image: fedora:31 workflows: version: 2 compile_and_test: jobs: - fedora_cache - ubuntu_19_04 - fedora_31: requires: - fedora_cache - doc_build: requires: - fedora_cache - doc_deploy: requires: - fedora_cache - doc_build filters: branches: only: master libratbag-0.13/.editorconfig000066400000000000000000000004721362011324700160430ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true [*.{c,h}] indent_style = tab tab_width = 8 trim_trailing_whitespace = true [{*.{py,py.in},tools/ratbagctl.*.in}] indent_style = space indent_size = 4 [*.{yml,xsl}] indent_style = space indent_size = 2 libratbag-0.13/.flake8000066400000000000000000000001211362011324700145300ustar00rootroot00000000000000[flake8] ignore = E402,E501 exclude = .git,__pycache__,build,data,piper/piper.py libratbag-0.13/.github/000077500000000000000000000000001362011324700147235ustar00rootroot00000000000000libratbag-0.13/.github/ISSUE_TEMPLATE/000077500000000000000000000000001362011324700171065ustar00rootroot00000000000000libratbag-0.13/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000014101362011324700215740ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Information** - `ratbagd` version (`ratbagd --version`): - Distribution: - Kernel version (ex. `uname -srmo`): `KERNEL VERSION HERE` **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 **Logs** Please start the daemon with verbosity and reproduce the issue. First make sure it isn't running already. ``` $ ratbagd --verbose=raw OUTPUT HERE ``` **Additional context** Add any other context about the problem here. libratbag-0.13/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011341362011324700226320ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **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. libratbag-0.13/COPYING000066400000000000000000000021761362011324700144240ustar00rootroot00000000000000Copyright © 2015-2017 Red Hat, Inc. Copyright © 2015 David Herrmann 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 (including the next paragraph) 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. libratbag-0.13/README.md000066400000000000000000000145301362011324700146450ustar00rootroot00000000000000libratbag ========= libratbag provides **ratbagd**, a DBus daemon to configure input devices, mainly gaming mice. The daemon provides a generic way to access the various features exposed by these mice and abstracts away hardware-specific and kernel-specific quirks. libratbag currently supports devices from Logitech, Etekcity, GSkill, Roccat, Steelseries. See [the device files](https://github.com/libratbag/libratbag/tree/master/data/devices) for a complete list of supported devices. Users interact through a GUI like [Piper](https://github.com/libratbag/piper/). For developers, the `ratbagctl` tool is the prime tool for debugging. Installing libratbag from system packages ----------------------------------------- libratbag is packaged for some distributions, you can use your system's package manager to install it. See [the wiki](https://github.com/libratbag/libratbag/wiki/Installation) for details. Compiling libratbag ------------------- libratbag uses the [meson build system](http://mesonbuild.com) which in turn uses ninja to invoke the compiler. Run the following commands to clone libratbag and initialize the build: git clone https://github.com/libratbag/libratbag.git cd libratbag meson builddir ninja -C builddir sudo ninja -C builddir install The default prefix is `/usr/local`, i.e. it will not overwrite the system installation. For more information, see [the wiki](https://github.com/libratbag/libratbag/wiki/Installation). And to build or re-build after code-changes, run: ninja -C builddir sudo ninja -C builddir install Note: `builddir` is the build output directory and can be changed to any other directory name. To set configure-time options, use e.g. meson configure builddir -Ddocumentation=false Run `meson configure builddir` to list the options. Running ratbagd as DBus-activated systemd service ------------------------------------------------- To run ratbagd, simply run it as root `sudo ratbagd`. However, ratbagd is intended to run as dbus-activated systemd service and installs the following files: /usr/share/dbus-1/system.d/org.freedesktop.ratbag1.conf /usr/share/dbus-1/system-services/org.freedesktop.ratbag1.conf /usr/share/systemd/system/ratbagd.service These files are installed into the prefix by `ninja install`, see also the configure-time options `-Dsystemd-unit-dir` and `-Ddbus-root-dir`. Developers are encouraged to simply symlink to the files in the git repository. For the files to take effect, you should run sudo systemctl daemon-reload sudo systemctl reload dbus.service And finally, to enable the service: sudo systemctl enable ratbagd.service This places the required symlink into the systemd directory so that dbus activation is possible. The DBus Interface ------------------- Full documentation of the DBus interface to interact with devices is available here: [ratbagd DBus Interface description](https://libratbag.github.io/). libratbag Internal Architecture ------------------------------- libratbag has two main components, libratbag and ratbagd. Applications like Piper talk over DBus to ratbagd. ratbagd uses libratbag to access the actual devices. +-------+ +------+ +---------+ +-----------+ | Piper | -> | DBus | -> | ratbagd | -> | libratbag | -> device +-------+ +------+ +---------+ +-----------+ Inside libratbag, we have the general frontend and API. Each device is handled by a HW-specific backend. That HW backend is responsible for the device-specific communication (usually some vendor-specific HID protocol). +---------+ +-----+ +------------+ +----------+ | ratbagd | -> | API | -> | hw backend | -> | protocol | -> device +---------+ +-----+ +------------+ +----------+ The API layer is HW agnostic. Depend on the HW, the protocol may be part of the driver implementation (e.g. etekcity) or a separate set of files (HID++). Where the protocol is separate, the whole known protocol should be implemented. The HW driver then only accesses the bits required for libratbag. This allows us to optionally export the protocol as separate library in the future, if other projects require it. Adding Devices to libratbag --------------------------- libratbag relies on a device database to match a device with the drivers. See the [data/devices/](https://github.com/libratbag/libratbag/tree/master/data/devices) directory for the set of known devices. These files are usually installed into `$prefix/$datadir` (e.g. `/usr/share/libratbag/`). Adding a new device can be as simple as adding a new `.device` file. This is the case for many devices with a shared protocol (e.g. Logitech's HID++). See the [data/devices/device.example](https://github.com/libratbag/libratbag/tree/master/data/devices/device.example) file for guidance on what information must be set. Look for existing devices from the same vendor as guidance too. If the device has a different protocol and doesn't work after adding the device file, you'll have to start reverse-engineering the device-specific protocol. Good luck :) Source ------ git clone https://github.com/libratbag/libratbag.git Bugs ---- Bugs can be reported in [our issue tracker](https://github.com/libratbag/libratbag/issues) Mailing list ------------ libratbag discussions happen on the [input-tools mailing list](http://lists.freedesktop.org/archives/input-tools/) hosted on freedesktop.org Device-specific notes --------------------- A number of device-specific notes and observations can be found in our wiki: https://github.com/libratbag/libratbag/wiki/Devices License ------- libratbag is licensed under the MIT license. > 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: [...] See the COPYING file for the full license information. [![Build Status](https://circleci.com/gh/libratbag/libratbag.svg?style=shield&circle-token=d7c782e10d2d934b176da754f11b5105ea074f4a)](https://circleci.com/gh/libratbag/libratbag) libratbag-0.13/TODO000066400000000000000000000044661362011324700140650ustar00rootroot00000000000000some mice have a per axis "sensitivity" control which adapts the hardware resolution without changing the DPI (looking at you Etekcity and Roccat). buttons need a couple of has_capability() functions to check which action types and actions are possible profiles should be allowed to be enabled/disabled light support: - on/off - color - patterns some epollfd-like thing for the caller to check if events are available. Needed for notification when the mouse changes through someone else manipulating settings. This is on backburner for now, it'll make the library more complicated, require HID parsing in libratbag for relatively little benefit. The only advantage we really get out of it is that a configuration UI would be able to update itself if a user presses a button to e.g. change the profile while the UI is running. We should actually drop the "key" functionality in favor of macros: - either a device supports real hardware macro, then there is no point in having a special set of keys exported to the user space while the driver can just figure out which way is most efficient - if the device does not support macros, then all of the supported keys should be RATBAG_SPECIAL, so the UI can enumerate them and get a capability on the current button if it supports this particular action (example: the UI is interested in sending "Volume Up", not KEY_VOLUMEUP. Macro support: - enhance doctext - add API support for name/groups - revamp the API? decide on a stable set of arguments, build the installed version of the command with that set, add a set of debug arguments (e.g. the etekcity-specific ones) only available in the build. interactive shell mode/batch mode for ratbag-command? dbus proxy - allows parallel access to the devices without interference, and provides a single instance for root permissions. this should be a separate project. change the API to return enum ratbag_error with more meaningful error messages. errno's are fine internally between the driver, but for the public-facing API we need better options ratbag-command: - add checks for user input: negative indices, unterminated macro sequences, excessive timeouts, etc - add man page - extend test cases, have a list of test cases that should work on a specific device, as per-device separate scripts - add default set/get to profile, resolution libratbag-0.13/data/000077500000000000000000000000001362011324700142745ustar00rootroot00000000000000libratbag-0.13/data/devices/000077500000000000000000000000001362011324700157165ustar00rootroot00000000000000libratbag-0.13/data/devices/README.md000066400000000000000000000006511362011324700171770ustar00rootroot00000000000000libratbag data files ==================== This directory contains the data files for each supported device. libratbag reads these files to identify the backend driver for any given device. If a device is not listed, libratbag will not support it. See the device.example file for instructions on the various options. See the libratbag wiki for more information: https://github.com/libratbag/libratbag/wiki/Adding-a-device/ libratbag-0.13/data/devices/data-parse-test.py000077500000000000000000000145531362011324700213010ustar00rootroot00000000000000#!/usr/bin/env python3 # # Copyright © 2017 Red Hat, Inc. # # 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 (including the next # paragraph) 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. # # Device data verification script # import argparse import configparser import re def assertIn(element, l): if element not in l: raise AssertionError('{} must be in {}'.format(element, l)) def assertNotIn(element, l): if element in l: raise AssertionError('{} must not be in {}'.format(element, l)) def check_match_str(string): bustypes = ['usb', 'bluetooth'] matches = string.split(';') for match in matches: if not match: # empty string if trailing ; continue parts = match.split(':') assert(len(parts) == 3) assertIn(parts[0], bustypes) vid = parts[1] assert(vid == '{:04x}'.format(int(vid, 16))) pid = parts[2] assert(pid == '{:04x}'.format(int(pid, 16))) def check_ledtypes_str(string): permitted_types = ['logo', 'side', 'battery', 'dpi', 'switches'] types = string.split(';') for t in types: if not t: # emtpy string if trailing ; continue assertIn(t, permitted_types) def check_section_device(section): required_keys = ['Name', 'Driver', 'DeviceMatch'] permitted_keys = required_keys + ['LedTypes'] for key in section.keys(): assertIn(key, permitted_keys) for r in required_keys: assertIn(r, section) try: check_ledtypes_str(section['LedTypes']) except KeyError: pass check_match_str(section['DeviceMatch']) def check_dpi_range_str(string): import re m = re.search('^([0-9]+):([0-9]+)@([0-9.]+)$', string) assert(m is not None) min = int(m.group(1)) max = int(m.group(2)) steps = float(m.group(3)) assert(min >= 0 and min <= 400) assert(max >= 2000 and max <= 12000) assert(steps > 0 and steps <= 100) if int(steps) == steps: steps = int(steps) assert(string == '{}:{}@{}'.format(min, max, steps)) def check_dpi_list_str(string): entries = string.split(';') # Remove possible empty last entry if trailing with a ; if not entries[len(entries) - 1]: entries = entries[:-1] for idx, entry in enumerate(entries): dpi = int(entry) assert(dpi >= 0 and dpi <= 12000) if idx > 0: prev = entries[idx - 1] prev_dpi = int(prev) assert(dpi > prev_dpi) def check_profile_type_str(string): types = ['G9', 'G500', 'G700'] assertIn(string, types) def check_section_hidpp10(section): permitted = ['Profiles', 'ProfileType', 'DpiRange', 'DpiList', 'DeviceIndex', 'Leds'] for key in section.keys(): assertIn(key, permitted) try: nprofiles = int(section['Profiles']) # 10 is arbitrarily chosen assert(nprofiles > 0 and nprofiles < 10) except KeyError: pass try: index = int(section['DeviceIndex'], 16) assert(index > 0 and index <= 0xff) except KeyError: pass try: check_dpi_range_str(section['DpiRange']) assertNotIn('DpiList', section.keys()) except KeyError: pass try: check_dpi_list_str(section['DpiList']) assertNotIn('DpiRange', section.keys()) except KeyError: pass try: check_profile_type_str(section['ProfileType']) except KeyError: pass try: leds = int(section['Leds']) # 10 is arbitrarily chosen assert(leds > 0 and leds < 10) except KeyError: pass def check_section_hidpp20(section): permitted = ['DeviceIndex', 'Quirk'] for key in section.keys(): assertIn(key, permitted) try: index = int(section['DeviceIndex'], 16) assert(index > 0 and index <= 0xff) except KeyError: pass def check_section_driver(driver, section): if driver == 'hidpp10': check_section_hidpp10(section) elif driver == 'hidpp20': check_section_hidpp20(section) else: assert('Unsupported driver section {}'.format(driver)) def validate_data_file_name(path): # Matching any of the characters in the regular expression will throw an # error. Currently only tests the square brackets [], parentheses, and curly # braces. illegal_characters_regex = '([\[\]\{\}\(\)])' found_characters = re.findall(illegal_characters_regex, path) if found_characters: raise AssertionError("data file name '{}' contains illegal characters: '{}'".format(path, ''.join(found_characters))) def parse_data_file(path): print('Parsing file {}'.format(path)) data = configparser.ConfigParser(strict=True) # Don't convert to lowercase data.optionxform = lambda option: option data.read(path) assertIn('Device', data.sections()) check_section_device(data['Device']) driver = data['Device']['Driver'] driver_section = 'Driver/{}'.format(driver) permitted_sections = ['Device', driver_section] for s in data.sections(): assertIn(s, permitted_sections) if data.has_section(driver_section): check_section_driver(driver, data[driver_section]) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Device data-file checker") parser.add_argument('file', nargs='+') args = parser.parse_args() for path in args.file: validate_data_file_name(path) parse_data_file(path) libratbag-0.13/data/devices/device.example000066400000000000000000000026651362011324700205430ustar00rootroot00000000000000# Example description file for a libratbag device # # Make sure the file has a recognizable name with a '.device' suffix [Device] # The kernel name of the device. This name is not used by libratbag, it is # used by debugging tools. Name=Logitech Example Device # DeviceMatch is a triplet of bus:vid:pid, where bus is 'usb' or bluetooth' # and pid/vid are lowercase 4-digit hexadecimal numbers with preceding # zeroes. DeviceMatch=usb:12ab:00f2 # The libratbag driver backend Driver=hidpp10 # The types of the LEDs, in the indexed order they appear in, separated by a # semicolon. # Allowed values: logo, side, battery, dpi LedTypes=logo;side # Only one group of driver-specific properties is permitted and it must be # [Driver/]. It is a bug do have any other [Driver/foo] group in # a device file, but this example file lists all. [Driver/hidpp10] # The number of profiles, must be greater than 0 Profiles=1 # The type of the profile ProfileType=G9 # The range of available resolutions in the form min:max@step # Mutually exclusive with DpiList DpiRange=50:5700@50 # The list of available resolutions, separated by semicolons. # Mutually exclusive with DpiRange # Where possible, DpiRange should be preferred DpiList=50;200;400;800 # The HID++ 1.0 device index # Should be unset unless required # DeviceIndex=1 # The number of LEDs Leds=1 [Driver/hidpp20] # The HID++ 2.0 device index # Should be unset unless required # DeviceIndex=1 libratbag-0.13/data/devices/duplicate-check.py000077500000000000000000000040321362011324700213170ustar00rootroot00000000000000#!/usr/bin/env python3 # # Copyright © 2018 Red Hat, Inc. # # 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 (including the next # paragraph) 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. # import argparse import os import sys import configparser def parse_data_file(path): data = configparser.ConfigParser(strict=True) # Don't convert to lowercase data.optionxform = lambda option: option data.read(path) matches = data['Device']['DeviceMatch'] return matches.split(';') if __name__ == "__main__": parser = argparse.ArgumentParser(description='Device duplicate match checker') parser.add_argument('file', nargs='+') args = parser.parse_args() device_matches = {} duplicates = False for path in args.file: matches = parse_data_file(path) fname = os.path.basename(path) for m in matches: if m in device_matches: print('Duplicate DeviceMatch={} in {} and {}'.format(m, fname, device_matches[m])) duplicates = True device_matches[m] = fname if duplicates: sys.exit(1) libratbag-0.13/data/devices/etekcity-scroll-alpha.device000066400000000000000000000001161362011324700232750ustar00rootroot00000000000000[Device] Name=Etekcity Scroll Alpha DeviceMatch=usb:1ea7:4011 Driver=etekcity libratbag-0.13/data/devices/gskill-MX-780.device000066400000000000000000000001261362011324700212210ustar00rootroot00000000000000# G.Skill MX-780 [Device] Name=G.Skill MX-780 DeviceMatch=usb:28da:3101 Driver=gskill libratbag-0.13/data/devices/logitech-M325.device000066400000000000000000000001431362011324700213170ustar00rootroot00000000000000# Logitech M325 over unifying [Device] Name=Logitech M325 DeviceMatch=usb:046d:400a Driver=hidpp20 libratbag-0.13/data/devices/logitech-M570.device000066400000000000000000000001251362011324700213210ustar00rootroot00000000000000# Logitech M570 [Device] Name=Logitech M570 DeviceMatch=usb:046d:1028 Driver=hidpp10 libratbag-0.13/data/devices/logitech-M585-M590.device000066400000000000000000000001631362011324700217610ustar00rootroot00000000000000# Logitech M585/M590 [Device] Name=Logitech M585/M590 DeviceMatch=usb:046d:406b;bluetooth:046d:b01b Driver=hidpp20 libratbag-0.13/data/devices/logitech-M705.device000066400000000000000000000001251362011324700213210ustar00rootroot00000000000000# Logitech M705 [Device] Name=Logitech M705 DeviceMatch=usb:046d:101b Driver=hidpp10 libratbag-0.13/data/devices/logitech-MX-Anywhere2.device000066400000000000000000000001661362011324700230640ustar00rootroot00000000000000[Device] Name=Logitech MX Anywhere 2 DeviceMatch=usb:046d:404a;bluetooth:046d:b013;bluetooth:046d:b018 Driver=hidpp20 libratbag-0.13/data/devices/logitech-MX-Anywhere2S.device000066400000000000000000000001431362011324700232020ustar00rootroot00000000000000[Device] Name=Logitech MX Anywhere 2S DeviceMatch=bluetooth:046d:b01a;usb:046d:406a Driver=hidpp20 libratbag-0.13/data/devices/logitech-MX-Ergo.device000066400000000000000000000001571362011324700221140ustar00rootroot00000000000000# Logitech MX Ergo [Device] Name=Logitech MX Ergo DeviceMatch=bluetooth:046d:b01d;usb:046d:406f Driver=hidpp20 libratbag-0.13/data/devices/logitech-MX-Master-2S.device000066400000000000000000000001711362011324700227310ustar00rootroot00000000000000# Logitech MX Master 2S [Device] Name=Logitech MX Master 2S DeviceMatch=bluetooth:046d:b019;usb:046d:4069 Driver=hidpp20 libratbag-0.13/data/devices/logitech-MX-Master-3.device000066400000000000000000000001671362011324700226140ustar00rootroot00000000000000# Logitech MX Master 3 [Device] Name=Logitech MX Master 3 DeviceMatch=bluetooth:046d:b023;usb:046d:4082 Driver=hidpp20 libratbag-0.13/data/devices/logitech-MX-Master.device000066400000000000000000000002671362011324700224550ustar00rootroot00000000000000# Logitech MX Master [Device] Name=Logitech MX Master DeviceMatch=usb:046d:4041;bluetooth:046d:b012;usb:046d:4060;bluetooth:046d:b017;bluetooth:046d:b01e;usb:046d:4071 Driver=hidpp20 libratbag-0.13/data/devices/logitech-MX-Vertical.device000066400000000000000000000001141362011324700227620ustar00rootroot00000000000000[Device] Name=Logitech MX Vertical DeviceMatch=usb:046d:407b Driver=hidpp20 libratbag-0.13/data/devices/logitech-MX518.device000066400000000000000000000000751362011324700214570ustar00rootroot00000000000000[Device] Name=MX518 DeviceMatch=usb:046d:c08e Driver=hidpp20 libratbag-0.13/data/devices/logitech-Marathon-M705.device000066400000000000000000000001161362011324700230700ustar00rootroot00000000000000[Device] Name=Logitech Marathon M705 DeviceMatch=usb:046d:406d Driver=hidpp20 libratbag-0.13/data/devices/logitech-T650.device000066400000000000000000000001431362011324700213270ustar00rootroot00000000000000# Logitech T650 over unifying [Device] Name=Logitech T650 DeviceMatch=usb:046d:4101 Driver=hidpp20 libratbag-0.13/data/devices/logitech-Wireless-Touchpad.device000066400000000000000000000001721362011324700242350ustar00rootroot00000000000000# Logitech Wireless Touchpad (Unifying) [Device] Name=Logitech Wireless Touchpad DeviceMatch=usb:046d:4011 Driver=hidpp20 libratbag-0.13/data/devices/logitech-g-powerplay.device000066400000000000000000000001721362011324700231410ustar00rootroot00000000000000[Device] Name=Logitech G Powerplay DeviceMatch=usb:046d:405f LedTypes=logo Driver=hidpp20 [Driver/hidpp20] DeviceIndex=7 libratbag-0.13/data/devices/logitech-g-pro-wireless.device000066400000000000000000000002301362011324700235450ustar00rootroot00000000000000[Device] Name=Logitech G Pro Wireless DeviceMatch=usb:046d:4079;usb:046d:c088 LedTypes=logo;battery;dpi; Driver=hidpp20 [Driver/hidpp20] DeviceIndex=1 libratbag-0.13/data/devices/logitech-g-pro.device000066400000000000000000000001641362011324700217200ustar00rootroot00000000000000[Device] Name=Logitech Gaming Mouse G Pro DeviceMatch=usb:046d:c085;usb:046d:c08c Driver=hidpp20 LedTypes=logo;side libratbag-0.13/data/devices/logitech-g102-g203.device000066400000000000000000000002011362011324700220060ustar00rootroot00000000000000# G102 and G203 (USB) [Device] Name=Logitech Gaming Mouse G102/G103 DeviceMatch=usb:046d:c084 Driver=hidpp20 LedTypes=logo;side; libratbag-0.13/data/devices/logitech-g300.device000066400000000000000000000002301362011324700213370ustar00rootroot00000000000000# WARNING: this file was automatically converted and has not yet been vetted [Device] Name=Logitech G300 DeviceMatch=usb:046d:c246 Driver=logitech_g300 libratbag-0.13/data/devices/logitech-g302.device000066400000000000000000000001661362011324700213510ustar00rootroot00000000000000# G302 over USB [Device] Name=Logitech Gaming Mouse G302 DeviceMatch=usb:046d:c07f Driver=hidpp20 LedTypes=logo;side; libratbag-0.13/data/devices/logitech-g303.device000066400000000000000000000001661362011324700213520ustar00rootroot00000000000000# G303 over USB [Device] Name=Logitech Gaming Mouse G303 DeviceMatch=usb:046d:c080 Driver=hidpp20 LedTypes=logo;side; libratbag-0.13/data/devices/logitech-g305.device000066400000000000000000000001571362011324700213540ustar00rootroot00000000000000[Device] Name=Logitech Gaming Mouse G305 DeviceMatch=usb:046d:4074 Driver=hidpp20 [Driver/hidpp20] Quirk=G305 libratbag-0.13/data/devices/logitech-g402.device000066400000000000000000000001631362011324700213470ustar00rootroot00000000000000# Logitech G402 Gaming Mouse USB [Device] Name=Logitech G402 Gaming Mouse DeviceMatch=usb:046d:c07e Driver=hidpp20 libratbag-0.13/data/devices/logitech-g403-hero.device000066400000000000000000000001121362011324700222750ustar00rootroot00000000000000[Device] Name=Logitech G403 Hero DeviceMatch=usb:046d:c08f Driver=hidpp20 libratbag-0.13/data/devices/logitech-g403-wireless.device000066400000000000000000000001341362011324700232010ustar00rootroot00000000000000[Device] Name=Logitech G403 Wireless DeviceMatch=usb:046d:405d;usb:046d:c082 Driver=hidpp20 libratbag-0.13/data/devices/logitech-g403.device000066400000000000000000000001051362011324700213440ustar00rootroot00000000000000[Device] Name=Logitech G403 DeviceMatch=usb:046d:c083 Driver=hidpp20 libratbag-0.13/data/devices/logitech-g5-2007.device000066400000000000000000000002221362011324700215700ustar00rootroot00000000000000# Name=Logitech G5 2007 [Device] Name=Logitech G5 DeviceMatch=usb:046d:c049 Driver=hidpp10 [Driver/hidpp10] DpiList=400;800;1600;2000 Profiles=1 libratbag-0.13/data/devices/logitech-g5.device000066400000000000000000000001461362011324700212070ustar00rootroot00000000000000[Device] Name=G5 DeviceMatch=usb:046d:c041 Driver=hidpp10 [Driver/hidpp10] DpiList=400;800;1600;2000 libratbag-0.13/data/devices/logitech-g500.device000066400000000000000000000002111362011324700213400ustar00rootroot00000000000000[Device] Name=Logitech G500 DeviceMatch=usb:046d:c068 Driver=hidpp10 [Driver/hidpp10] DpiRange=0:5700@23.53 ProfileType=G500 Profiles=1 libratbag-0.13/data/devices/logitech-g500s.device000066400000000000000000000002071362011324700215300ustar00rootroot00000000000000[Device] Name=Logitech G500s DeviceMatch=usb:046d:c24e Driver=hidpp10 [Driver/hidpp10] DpiRange=0:8200@50 ProfileType=G500 Profiles=1 libratbag-0.13/data/devices/logitech-g502-hero-wireless.device000066400000000000000000000001411362011324700241320ustar00rootroot00000000000000[Device] Name=Logitech G502 Hero Wireless DeviceMatch=usb:046d:407f;usb:046d:c08d Driver=hidpp20 libratbag-0.13/data/devices/logitech-g502-hero.device000066400000000000000000000001121362011324700222750ustar00rootroot00000000000000[Device] Name=Logitech G502 Hero DeviceMatch=usb:046d:c08b Driver=hidpp20 libratbag-0.13/data/devices/logitech-g502-proteus-core.device000066400000000000000000000001221362011324700237700ustar00rootroot00000000000000[Device] Name=Logitech G502 Proteus Core DeviceMatch=usb:046d:c07d Driver=hidpp20 libratbag-0.13/data/devices/logitech-g502-proteus-spectrum.device000066400000000000000000000001261362011324700247060ustar00rootroot00000000000000[Device] Name=Logitech G502 Proteus Spectrum DeviceMatch=usb:046d:c332 Driver=hidpp20 libratbag-0.13/data/devices/logitech-g513.device000066400000000000000000000001271362011324700213520ustar00rootroot00000000000000[Device] Name=Logitech G513 DeviceMatch=usb:046d:c33c LedTypes=switches Driver=hidpp20 libratbag-0.13/data/devices/logitech-g600.device000066400000000000000000000002301362011324700213420ustar00rootroot00000000000000# WARNING: this file was automatically converted and has not yet been vetted [Device] Name=Logitech G600 DeviceMatch=usb:046d:c24a Driver=logitech_g600 libratbag-0.13/data/devices/logitech-g602.device000066400000000000000000000002221362011324700213450ustar00rootroot00000000000000# Logitech G602 over wireless USB [Device] Name=Logitech G602 DeviceMatch=usb:046d:402c Driver=hidpp20 [Driver/hidpp20] DeviceIndex=1 Quirk=G602 libratbag-0.13/data/devices/logitech-g603.device000066400000000000000000000001511362011324700213470ustar00rootroot00000000000000# Logitech G603 [Device] Name=Logitech G603 DeviceMatch=usb:046d:406c;bluetooth:046d:b01c Driver=hidpp20 libratbag-0.13/data/devices/logitech-g604.device000066400000000000000000000001311362011324700213460ustar00rootroot00000000000000[Device] Name=Logitech G604 DeviceMatch=usb:046d:4085;bluetooth:046d:b024 Driver=hidpp20 libratbag-0.13/data/devices/logitech-g7.device000066400000000000000000000002701362011324700212070ustar00rootroot00000000000000# Logitech G7 # http://support.logitech.com/en_us/product/g7-laser-cordless-mouse/specs [Device] Name=Logitech G7 DeviceMatch=usb:046d:c51a Driver=hidpp10 [Driver/hidpp10] Profiles=1 libratbag-0.13/data/devices/logitech-g700-wireless.device000066400000000000000000000004061362011324700232030ustar00rootroot00000000000000# Logitech G700 over wireless USB # http://support.logitech.com/en_us/product/wireless-gaming-mouse-g700/specs [Device] Name=Logitech G700 DeviceMatch=usb:046d:c531 Driver=hidpp10 [Driver/hidpp10] DpiRange=0:5700@23.53 ProfileType=G700 DeviceIndex=1 Profiles=5 libratbag-0.13/data/devices/logitech-g700.device000066400000000000000000000003571362011324700213550ustar00rootroot00000000000000# Logitech G700 over USB # http://support.logitech.com/en_us/product/wireless-gaming-mouse-g700/specs [Device] Name=Logitech G700 DeviceMatch=usb:046d:c06b Driver=hidpp10 [Driver/hidpp10] DpiRange=0:5700@23.53 ProfileType=G700 Profiles=5 libratbag-0.13/data/devices/logitech-g700s.device000066400000000000000000000003731362011324700215360ustar00rootroot00000000000000# Logitech G700s over USB # http://support.logitech.com/en_us/product/g700s-rechargable-wireless-gaming-mouse/specs [Device] Name=Logitech G700s DeviceMatch=usb:046d:c07c Driver=hidpp10 [Driver/hidpp10] DpiRange=0:8200@50 ProfileType=G700 Profiles=5 libratbag-0.13/data/devices/logitech-g703-hero.device000066400000000000000000000001311362011324700223010ustar00rootroot00000000000000[Device] Name=Logitech G703 Hero DeviceMatch=usb:046d:4086;usb:046d:c090 Driver=hidpp20 libratbag-0.13/data/devices/logitech-g703.device000066400000000000000000000001241362011324700213500ustar00rootroot00000000000000[Device] Name=Logitech G703 DeviceMatch=usb:046d:c087;usb:046d:4070 Driver=hidpp20 libratbag-0.13/data/devices/logitech-g815.device000066400000000000000000000001341362011324700213550ustar00rootroot00000000000000[Device] Name=Logitech G815 DeviceMatch=usb:046d:c33f LedTypes=logo;switches Driver=hidpp20 libratbag-0.13/data/devices/logitech-g9.device000066400000000000000000000004461362011324700212160ustar00rootroot00000000000000# Logitech G9 # http://support.logitech.com/en_us/product/g9-laser-mouse/specs [Device] Name=Logitech G9 DeviceMatch=usb:046d:c048 LedTypes=dpi Driver=hidpp10 [Driver/hidpp10] DpiList=200;400;600;800;1000;1200;1400;1600;1800;2000;2200;2400;2600;2800;3000;3200 ProfileType=G9 Profiles=5 Leds=1 libratbag-0.13/data/devices/logitech-g900.device000066400000000000000000000001431362011324700213500ustar00rootroot00000000000000# Logitech G900 [Device] Name=Logitech G900 DeviceMatch=usb:046d:c081;usb:046d:4053 Driver=hidpp20 libratbag-0.13/data/devices/logitech-g903-hero.device000066400000000000000000000001301362011324700223020ustar00rootroot00000000000000[Device] Name=Logitech G903 Hero DeviceMatch=usb:046d:c091;usb:046d:4087 Driver=hidpp20 libratbag-0.13/data/devices/logitech-g903.device000066400000000000000000000001231362011324700213510ustar00rootroot00000000000000[Device] Name=Logitech G903 DeviceMatch=usb:046d:c086;usb:046d:4067 Driver=hidpp20 libratbag-0.13/data/devices/logitech-g910.device000066400000000000000000000001751362011324700213560ustar00rootroot00000000000000[Device] Name=Logitech G910 DeviceMatch=usb:046d:c335 LedTypes=logo;switches Driver=hidpp20 [Driver/hidpp20] DeviceIndex=ff libratbag-0.13/data/devices/logitech-g915.device000066400000000000000000000001741362011324700213620ustar00rootroot00000000000000[Device] Name=Logitech G915 DeviceMatch=usb:046d:c33e LedTypes=logo;switches Driver=hidpp20 [Driver/hidpp20] DeviceIndex=1 libratbag-0.13/data/devices/logitech-g9x-Call-of-Duty-MW3-Edition.device000066400000000000000000000004171362011324700255770ustar00rootroot00000000000000# Logitech G9x [Call of Duty MW3 Edition] # http://support.logitech.com/en_us/product/laser-mouse-g9x/specs [Device] Name=Logitech G9x [Call of Duty MW3 Edition] DeviceMatch=usb:046d:c249 Driver=hidpp10 [Driver/hidpp10] DpiRange=0:5700@23.53 ProfileType=G500 Profiles=5 libratbag-0.13/data/devices/logitech-g9x-Original.device000066400000000000000000000003461362011324700231470ustar00rootroot00000000000000# Logitech G9x [Original] # http://support.logitech.com/en_us/product/g9x-laser-mouse/specs [Device] Name=G9x [Original] DeviceMatch=usb:046d:c066 Driver=hidpp10 [Driver/hidpp10] DpiRange=0:5700@23.53 ProfileType=G500 Profiles=5 libratbag-0.13/data/devices/receiver-check.py000077500000000000000000000056171362011324700211630ustar00rootroot00000000000000#!/usr/bin/env python3 # # Copyright © 2018 Red Hat, Inc. # # 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 (including the next # paragraph) 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. # import argparse import os import sys import configparser # see the IDs from # https://github.com/torvalds/linux/blob/master/drivers/hid/hid-ids.h#L772 # https://github.com/torvalds/linux/blob/master/drivers/hid/hid-logitech-dj.c#L1826 logitech_receivers = [ 0xc50c, # USB_DEVICE_ID_S510_RECEIVER 0xc517, # USB_DEVICE_ID_S510_RECEIVER_2 0xc512, # USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500 0xc513, # USB_DEVICE_ID_MX3000_RECEIVER 0xc51b, # USB_DEVICE_ID_LOGITECH_27MHZ_MOUSE_RECEIVER 0xc52b, # USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER 0xc52f, # USB_DEVICE_ID_LOGITECH_NANO_RECEIVER 0xc532, # USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2 0xc534, # USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_2 0xc539, # USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1 0xc53f, # USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1 0xc53a, # USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY ] RECEIVERS = ['usb:046d:{}'.format(r) for r in logitech_receivers] def parse_data_file(path): data = configparser.ConfigParser(strict=True) # Don't convert to lowercase data.optionxform = lambda option: option data.read(path) matches = data['Device']['DeviceMatch'] return matches.split(';') if __name__ == "__main__": parser = argparse.ArgumentParser(description='Checks that no unifying receiver ID is in the device files') parser.add_argument('file', nargs='+') args = parser.parse_args() receiver_found = False for path in args.file: matches = parse_data_file(path) fname = os.path.basename(path) for m in matches: if m in RECEIVERS: print('Receiver ID {} found in file {}'.format(m, fname)) receiver_found = True if receiver_found: sys.exit(1) libratbag-0.13/data/devices/roccat-kone-pure.device000066400000000000000000000001211362011324700222470ustar00rootroot00000000000000[Device] Name=Roccat Kone Pure DeviceMatch=usb:1e7d:2dc2 Driver=roccat-kone-pure libratbag-0.13/data/devices/roccat-kone-xtd.device000066400000000000000000000001061362011324700220760ustar00rootroot00000000000000[Device] Name=Roccat Kone XTD DeviceMatch=usb:1e7d:2e22 Driver=roccat libratbag-0.13/data/devices/steelseries-kinzu-v2.device000066400000000000000000000002571362011324700231150ustar00rootroot00000000000000[Device] Name=SteelSeries Kinzu V2 DeviceMatch=usb:1038:1378 Driver=steelseries [Driver/steelseries] DeviceVersion=1 Buttons=3 Leds=0 DpiList=400;800;1600;3200 MacroLength=0 libratbag-0.13/data/devices/steelseries-kinzu-v3.device000066400000000000000000000002571362011324700231160ustar00rootroot00000000000000[Device] Name=SteelSeries Kinzu V3 DeviceMatch=usb:1038:1388 Driver=steelseries [Driver/steelseries] DeviceVersion=1 Buttons=3 Leds=0 DpiList=400;800;1600;2000 MacroLength=0 libratbag-0.13/data/devices/steelseries-rival-310.device000066400000000000000000000002551362011324700230440ustar00rootroot00000000000000[Device] Name=SteelSeries Rival 310 DeviceMatch=usb:1038:1720 Driver=steelseries [Driver/steelseries] DeviceVersion=2 Buttons=6 Leds=2 DpiRange=100:12000@100 MacroLength=1 libratbag-0.13/data/devices/steelseries-rival-600.device000066400000000000000000000002551362011324700230460ustar00rootroot00000000000000[Device] Name=SteelSeries Rival 600 DeviceMatch=usb:1038:1724 Driver=steelseries [Driver/steelseries] DeviceVersion=3 Buttons=7 Leds=8 DpiRange=100:12000@100 MacroLength=1 libratbag-0.13/data/devices/steelseries-rival.device000066400000000000000000000002461362011324700225430ustar00rootroot00000000000000[Device] Name=SteelSeries Rival DeviceMatch=usb:1038:1384 Driver=steelseries [Driver/steelseries] DeviceVersion=1 Buttons=6 Leds=2 DpiRange=50:6500@50 MacroLength=1 libratbag-0.13/data/devices/steelseries-sensei-310.device000066400000000000000000000002561362011324700232160ustar00rootroot00000000000000[Device] Name=SteelSeries Sensei 310 DeviceMatch=usb:1038:1722 Driver=steelseries [Driver/steelseries] DeviceVersion=2 Buttons=8 Leds=2 DpiRange=100:12000@100 MacroLength=1 libratbag-0.13/data/devices/steelseries-sensei-raw.device000066400000000000000000000003251362011324700235010ustar00rootroot00000000000000[Device] Name=SteelSeries Sensei Raw DeviceMatch=usb:1038:1369 Driver=steelseries LedTypes=logo;dpi [Driver/steelseries] DeviceVersion=1 Buttons=8 Leds=1 DpiRange=90:5670@90 MacroLength=1 MonoLed=1 ShortButton=1 libratbag-0.13/data/logo.svg000066400000000000000000000057571362011324700157730ustar00rootroot00000000000000 libratbag-0.13/dbus/000077500000000000000000000000001362011324700143205ustar00rootroot00000000000000libratbag-0.13/dbus/org.freedesktop.ratbag1.conf.in000066400000000000000000000012011362011324700222070ustar00rootroot00000000000000 libratbag-0.13/dbus/org.freedesktop.ratbag1.service.in000066400000000000000000000001541362011324700227300ustar00rootroot00000000000000[D-BUS Service] Name=org.freedesktop.ratbag1 Exec=@bindir@/ratbagd User=root SystemdService=ratbagd.service libratbag-0.13/dbus/org.freedesktop.ratbag_devel1.conf.in000066400000000000000000000015701362011324700233770ustar00rootroot00000000000000 libratbag-0.13/doc/000077500000000000000000000000001362011324700141305ustar00rootroot00000000000000libratbag-0.13/doc/conf.py.in000066400000000000000000000112501362011324700160330ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # @PACKAGE_NAME@ documentation build configuration file, created by # sphinx-quickstart on Wed Oct 11 13:45:08 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. import os import sys sys.path.insert(0, os.path.abspath('..')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = '@PACKAGE_NAME@' copyright = '2017, The ratbaggers' author = 'The ratbaggers' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '' # The full version, including alpha/beta/rc tags. release = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # # html_theme = 'alabaster' html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = '@PACKAGE_NAME@doc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, '@PACKAGE_NAME@.tex', '@PACKAGE_NAME@ Documentation', 'The ratbaggers', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, '@PACKAGE_NAME@', '@PACKAGE_NAME@ Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, '@PACKAGE_NAME@', '@PACKAGE_NAME@ Documentation', author, '@PACKAGE_NAME@', 'One line description of project.', 'Miscellaneous'), ] libratbag-0.13/doc/dbus.rst000066400000000000000000000475361362011324700156360ustar00rootroot00000000000000******** DBus API ******** Note: **the DBus interface is subject to change** Interfaces: * :ref:`manager` * :ref:`device` * :ref:`profile` * :ref:`resolution` * :ref:`button` * :ref:`led` Changing settings on a device is a three-step process: #. Change the various properties to the desired value #. Invoke :func:`org.freedesktop.ratbag1.Device.Commit() ` #. Optional: if an error occured writing the new data to the device, a :func:`org.freedesktop.ratbag1.Resync ` signal is emitted on the device and all properties are updated accordingly. The time it takes to write settings to a device varies greatly, any caller must be prepared to receive a :func:`org.freedesktop.ratbag1.Device.Resync ` signal several seconds after :func:`org.freedesktop.ratbag1.Device.Commit() `. Notes on the DBus API --------------------- For easier debugging, objects paths are constructed from the device. e.g. ``/org/freedesktop/ratbag/button/event5/p0/b10`` is the button interface for button 10 on profile 0 on event5. The naming is subject to change. Do not rely on a constructed object path in your application. Types ..... Types used by these interfaces: +----------+-----------------------------------+ | Type | Description | +==========+===================================+ | ``b`` | Bool | +----------+-----------------------------------+ | ``o`` | Object path | +----------+-----------------------------------+ | ``s`` | String | +----------+-----------------------------------+ | ``u`` | Unsigned 32-bit integer | +----------+-----------------------------------+ |``(uuu)`` | A triplet of 32-bit integers | +----------+-----------------------------------+ | ``ao`` | Array of object paths | +----------+-----------------------------------+ | ``as`` | Array of strings | +----------+-----------------------------------+ | ``au`` | Array of 32-bit integers | +----------+-----------------------------------+ | ``a(uu)``| Array of 2 32-bit integer tuples | +----------+-----------------------------------+ For details on each type, see the `DBus Specification `_. Flags ..... Properties marked as **constant** do not change for the lifetime of the object. Properties marked as **mutable** may change, and a ``org.freedesktop.DBus.Properties.PropertyChanged`` signal is sent for those unless otherwise specified. .. _manager: org.freedesktop.ratbag1.Manager ------------------------------- The **org.freedesktop.ratbag1.Manager** interface is the entry point to interact with ratbagd. .. attribute:: APIVersion :type: i :flags: read-only,constant The DBus API version. This is a stopgap feature while the DBus API is still in flux. The version has no backwards or forward-compatibilty guarantees - your client must understand **exactly** the same version, it is not enough to support an older or a newer version. This API will be removed once the DBus API is declared stable. .. attribute:: Devices :type: ao :flags: read-only, mutable An array of read-only object paths referencing the available devices. The devices implement the :ref:`device` interface. .. _device: org.freedesktop.ratbag1.Device ------------------------------- The **org.freedesktop.ratbag1.Device** interface describes a single device known to ratbagd. .. attribute:: Model :type: s :flags: read-only, constant An ID identifying the physical device model. This string is guaranteed to be unique for a specific model and always identical for devices of that model. This is a string of one of the following formats: - ``usb:1234:abcd:0`` - ``bluetooth:5678:ef01:0`` - ``unknown`` In the future, other formats may get added. Clients must ignore unknown string formats. For a string starting with ``usb:``, the format is the bus type (USB) followed by a 4-digit lowercase hex USB vendor ID, followed by a 4-digit lowercase hex USB product ID, followed by an decimal version number of unspecified length. These four elements are separated by a colon (``:``). For a string starting with ``bluetooth:``, the format is the bus type (Bluetooth) followed by a 4-digit lowercase hex Bluetooth vendor ID, followed by a 4-digit lowercase hex Bluetooth product ID, followed by an decimal version number of unspecified length. These four elements are separated by a colon (``:``). For the string ``unknown``, the model of the device cannot be determined. This is usually a bug in libratbag. For a ``Model`` of type ``usb`` and ``bluetooth``, the version number is reserved for use by libratbag. Device with identical vendor and product IDs but different versions must be considered different devices. For example, the version may increase when a manufacturer re-uses USB Ids. Vendor or product IDs of 0 are valid IDs (e.g. used used by test devices). .. attribute:: Name :type: s :flags: read-only, constant The device's name, suitable for presentation to the user. .. attribute:: Profiles :type: ao :flags: read-only, mutable This property is mutable if the device supports adding and removing profiles. Provides the list of profile paths for all profiles on this device, see :ref:`profile` .. function:: Commit() → () Commits the changes to the device. This call always succeeds, the data is written to the device asynchronously. Where an error occurs, the :func:`Resync` signal is emitted and all properties are updated to the current state. .. function:: Resync() :type: Signal Emitted when an internal error occurs, usually on writing values to the device after :func:`Commit()`. Upon receiving this signal, clients are expected to resync their property values with ratbagd. .. _profile: org.freedesktop.ratbag1.Profile ------------------------------- .. attribute:: Index :type: u :flags: read-only, constant The zero-based index of this profile .. attribute:: Name :type: s :flags: read-write, mutable The name of this profile. If the name is the empty string, the profile name cannot be changed. .. attribute:: Enabled :type: b :flags: read-write, mutable True if this is the profile is enabled, false otherwise. Note that a disabled profile might not have correct bindings, so it's a good thing to rebind everything before calling :func:`Commit`. .. attribute:: IsActive :type: b :flags: read-only, mutable True if this is the currently active profile, false otherwise. Profiles can only be set to active, but never to not active - at least one profile must be active at all times. This property is read-only, use the :func:`SetActive` method to activate a profile. .. attribute:: Resolutions :type: ao :flags: read-only, mutable This property is mutable if the device supports adding and removing resolutions. Provides the object paths of all resolutions in this profile, see :ref:`resolution`. .. attribute:: Buttons :type: ao :flags: read-only, constant Provides the object paths of all buttons in this profile, see :ref:`button`. .. attribute:: Leds :type: ao :flags: read-only, constant Provides the object paths of all LEDs in this profile, see :ref:`led`. .. attribute:: ReportRate :type: u :flags: read-write, mutable uint for the report rate in Hz assigned to this profile. This rate must be one of those listed in :attr:`ReportRates`. .. attribute:: ReportRates :type: au :flags: read-write, constant A list of permitted report rates. Values in this list may be used in the :attr:`ReportRate` property. This list is always sorted ascending, the lowest report rate is the first item in the list. This list may be empty if the device does not support reading and/or writing to resolutions. .. function:: SetActive() → () Set this profile to be the active profile .. _resolution: org.freedesktop.ratbag1.Resolution ---------------------------------- .. attribute:: Index :type: u :flags: read-only, constant Index of the resolution .. attribute:: IsActive :type: b :flags: read-only, mutable True if this is the currently active resolution, false otherwise. Resolutions can only be set to active, but never to not active - at least one resoultion must be active at all times. This property is read-only, use the :func:`SetActive` method to set a resolution as the active resolution. .. attribute:: IsDefault :type: b :flags: read-only, mutable True if this is the currently default resolution, false otherwise. If the device does not have the default resolution capability, this property is always false. Resolutions can only be set to default, but never to not default - at least one resolution must be default at all times. This property is read-only, use the :func:`SetDefault` method to set a resolution as the default resolution. .. attribute:: Resolution :type: v :flags: read-write, mutable The resolution for this entry in dpi. If the variant is a single unsigned integer (``u``), the value is the resolution for both the x- and the y- axis. If the variant is a unsigned integer tuple (``(uu)``), the value is the resolution for the x- and y- axis separately. A client must leave the type intact, assigning a single ``u`` to a resolution object previously exporting ``(uu)`` is invalid. The value for the resolution must be equal to one of the values in :attr:`Resolutions`. .. attribute:: Resolutions :type: au :flags: read-only, constant A list of permitted resolutions. Values in this list may be used in the :attr:`Resolution` property. This list is always sorted ascending, the lowest resolution is the first item in the list. This list may be empty if the device does not support reading and/or writing to resolutions. .. function:: SetDefault() → () Set this resolution to be the default .. function:: SetActive() → () Set this resolution to be the active one .. _button: org.freedesktop.ratbag1.Button ------------------------------ .. attribute:: Index :type: u :flags: read-only, constant Index of the button .. attribute:: Mapping :type: (uv) :flags: read-write, mutable The current button mapping. The first element is the ``ActionType`` for this button and must be one of those in :attr:`ActionTypes`. If the ActionType is *Button*, the variant is an unsigned integer (``u``) denoting the button number to map to. If the ActionType is *Special*, the variant is an unsigned integer (``u``) denoting the special value to map to. If the ActionType is *Macro*, the variant is an array of integer tuples (``a(uu)``) where each tuple is ``(type, keycode)`` and the type is one of the following: +---------+--------------------------------------+ | Value | Description | +=========+======================================+ | 0 | Key release event | +---------+--------------------------------------+ | 1 | Key press event | +---------+--------------------------------------+ If the ActionType is *Unknown*, the variant is an unsigned integer (``u``) of value 0. .. attribute:: ActionTypes :type: au :flags: read-only, constant +---------+---------+--------------------------------------+ | Value | Name | Description | +=========+=========+======================================+ | 0 | None | No mapping configured | +---------+---------+--------------------------------------+ | 1 | Button | Mapping to a logical button number | +---------+---------+--------------------------------------+ | 2 | Special | Mapping to a special function | +---------+---------+--------------------------------------+ | 3 | Macro | Mapping to a macro key sequence | +---------+---------+--------------------------------------+ | 1000 | Unknown | An unknown or unreadable mapping type| +---------+---------+--------------------------------------+ See :ref:`button_special` for a list of supported special functions. Clients must ignore :attr:`ActionTypes` unknown to them. .. function:: Disable() → () Disable this button .. _led: org.freedesktop.ratbag1.Led --------------------------- .. attribute:: Index :type: u :flags: read-only, constant Index of the LED .. attribute:: Mode :type: u :flags: read-write, mutable Enum describing the current mode, see :attr:`Modes`. .. attribute:: Modes :type: au :flags: read-only, constant A list of modes supported by this LED. +-------+-------------------------------------+ | Value | Definition | +=======+=====================================+ | 0 | LED is off | +-------+-------------------------------------+ | 1 | LED is on with constant brightness | +-------+-------------------------------------+ | 2 | LED cycles through a set of colors. | | | This mode ignores the :attr:`Color` | | | values. | +-------+-------------------------------------+ | 3 | LED uses a breathing-style animation| +-------+-------------------------------------+ In the future, extra values may get added. Clients must ignore unknown Modes. .. attribute:: Color :type: (uuu) :flags: read-write, mutable 32-bit unsigned int triplet (RGB) of the LED's color. Only the least significant bits are valid, the :attr:`ColorDepth` property defines the number of bits for each color. When writing to this property, all bits outside the color depth must be 0. .. attribute:: ColorDepth :type: u :flags: read-only, constant An enum specifying the color depth of this LED. Permitted values are: +-------+-------------------------------+ | Value | Definition | +=======+===============================+ | 0 | 0 bits per color (monochrome) | +-------+-------------------------------+ | 1 | 8 bits per color | +-------+-------------------------------+ | 2 | 1 bit per color | +-------+-------------------------------+ In the future, extra values may get added. Clients must ignore unknown ``ColorDepths`` and not manipulate the LED color where the ``ColorDepth`` is unknown. .. attribute:: EffectDuration :type: u :flags: read-write, mutable The effect duration in ms, possible values are in the range 0 - 10000 .. attribute:: Brightness :type: u :flags: read-write, mutable The brightness of the LED, normalized to the range 0-255, inclusive. Where the LED supports less than 8-bit of brightness, libratbag maps the value to a device-supported value in an implementation-defined manner. .. _button_special: Special button functions ------------------------ All special button function values are based on the value ``0x40000000`` (``1 << 30``). +------------+------------+---------------------------------------------------+ | Offset | Value |Description | +============+============+===================================================+ | 0 | 0x40000000 | Unknown | +------------+------------+---------------------------------------------------+ | 1 | 0x40000001 | Doublelick | +------------+------------+---------------------------------------------------+ | 2 | 0x40000002 | Wheel left | +------------+------------+---------------------------------------------------+ | 3 | 0x40000003 | Wheel right | +------------+------------+---------------------------------------------------+ | 4 | 0x40000004 | Wheel up | +------------+------------+---------------------------------------------------+ | 5 | 0x40000005 | Wheel down | +------------+------------+---------------------------------------------------+ | 6 | 0x40000006 | Ratchet mode switch | +------------+------------+---------------------------------------------------+ | 7 | 0x40000007 | Resolution cycle up | +------------+------------+---------------------------------------------------+ | 8 | 0x40000008 | Resolution cycle down | +------------+------------+---------------------------------------------------+ | 9 | 0x40000009 | Resolution up | +------------+------------+---------------------------------------------------+ | 10 | 0x4000000a | Resolution down | +------------+------------+---------------------------------------------------+ | 11 | 0x4000000b | Resolution alternate | +------------+------------+---------------------------------------------------+ | 12 | 0x4000000c | Resolution default | +------------+------------+---------------------------------------------------+ | 13 | 0x4000000d | Profile cycle up | +------------+------------+---------------------------------------------------+ | 14 | 0x4000000e | Profile cycle down | +------------+------------+---------------------------------------------------+ | 15 | 0x4000000f | Profile up | +------------+------------+---------------------------------------------------+ | 16 | 0x40000010 | Profile down | +------------+------------+---------------------------------------------------+ | 17 | 0x40000011 | Second mode | +------------+------------+---------------------------------------------------+ | 18 | 0x40000012 | Battery level | +------------+------------+---------------------------------------------------+ libratbag-0.13/doc/doxygen-enums-to-rst.xsl000066400000000000000000000031051362011324700207070ustar00rootroot00000000000000 ************ Enumerations ************ This document contains the enums used by libratbag's DBus interface. --------------------------------------- .. cpp:enum:: .. cpp:enumerator:: libratbag-0.13/doc/index.rst000066400000000000000000000010561362011324700157730ustar00rootroot00000000000000.. libratbag documentation master file, created by sphinx-quickstart on Wed Oct 11 13:45:08 2017. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. ********************** The libratbag DBus API ********************** .. image:: ../../data/logo.svg :width: 400 .. toctree:: :maxdepth: 2 :caption: Contents: The DBus Interfaces This document describes the DBus API that is the public interface for libratbag. Note: **the DBus interface is subject to change** libratbag-0.13/doc/libratbag.doxygen.in000066400000000000000000000010161362011324700200610ustar00rootroot00000000000000PROJECT_NAME = @PACKAGE_NAME@ PROJECT_NUMBER = @PACKAGE_VERSION@ PROJECT_BRIEF = "A mouse configuration library" JAVADOC_AUTOBRIEF = YES TAB_SIZE = 8 OPTIMIZE_OUTPUT_FOR_C = YES EXTRACT_ALL = YES EXTRACT_STATIC = YES MAX_INITIALIZER_LINES = 0 QUIET = YES # Keep this list in sync with meson.build INPUT = @top_srcdir@/src/libratbag-enums.h GENERATE_HTML = NO GENERATE_XML = YES GENERATE_LATEX = NO libratbag-0.13/doc/meson.build000066400000000000000000000025161362011324700162760ustar00rootroot00000000000000if get_option('documentation') cmd_sphinx = find_program('sphinx-build-3', 'sphinx-build3', 'sphinx-build') doc_config = configuration_data() doc_config.set('PACKAGE_NAME', meson.project_name()) doc_config.set('PACKAGE_VERSION', meson.project_version()) doc_config.set('top_srcdir', meson.source_root()) # Sphinx really really wants a _static directory in its source tree. Let's # make it happy run_command('mkdir', '-p', join_paths(meson.build_root(), 'doc', '_static')) # Effectively a noop copy. We *must* generate enums.rst so that will end up # in the build directory. sphinx doesn't allow for multiple source # directories, so any .rst and the conf.py must be copied over to the # build directory too src_rsts = ['index.rst', 'dbus.rst'] dst_rsts = [] foreach rst : src_rsts f = configure_file(input : rst, output : rst, configuration : doc_config, install : false) dst_rsts += f endforeach sphinx_conf = configure_file(input : 'conf.py.in', output : 'conf.py', configuration : doc_config, install : false) custom_target('sphinx', input : [ sphinx_conf, dst_rsts ], output : 'html', command : [ cmd_sphinx, '-b', 'html', '-q', '-n', join_paths(meson.build_root(), 'doc'), join_paths(meson.build_root(), 'doc', 'html') ], build_by_default : true, install : false) endif libratbag-0.13/meson.build000066400000000000000000000506741362011324700155410ustar00rootroot00000000000000project('libratbag', 'c', version : '0.13', license : 'MIT/Expat', default_options : [ 'c_std=gnu99', 'warning_level=2' ], meson_version : '>= 0.40.0') # The DBus API version. Increase this every time the DBus API changes. # No backwards/forwards guarantee, clients are expected to understand # whatever ratbagd speaks or bail out. This should be removed if we ever # finish the API and declare it stable. ratbagd_api_version = 1 # We use libtool-version numbers because it's easier to understand. # Before making a release, the libratbag_so_* and liblur_so_* # numbers should be modified. The components are of the form C:R:A. # a) If binary compatibility has been broken (eg removed or changed interfaces) # change to C+1:0:0. # b) If interfaces have been changed or added, but binary compatibility has # been preserved, change to C+1:0:A+1 # c) If the interface is the same as the previous version, change to C:R+1:A liblur_so_c=3 liblur_so_r=3 liblur_so_a=0 # convert to sonames liblur_so_version = '@0@.@1@.@2@'.format((liblur_so_c-liblur_so_a), liblur_so_a, liblur_so_r) # Compiler setup cc = meson.get_compiler('c') cflags = ['-Wno-unused-parameter', '-fvisibility=hidden', '-Wmissing-prototypes', '-Wformat', # required by format-security '-Werror=format-security', '-Wstrict-prototypes'] add_project_arguments(cflags, language: 'c') # Initialize config.h, to be added to in the various options below, config.h # is generated at the end of this file config_h = configuration_data() config_h.set('_GNU_SOURCE', '1') config_h.set_quoted('RATBAG_VERSION', meson.project_version()) config_h.set('RATBAGD_API_VERSION', ratbagd_api_version) libratbag_data_dir = join_paths(get_option('prefix'), get_option('datadir'), 'libratbag') libratbag_data_dir_devel = join_paths(meson.source_root(), 'data', 'devices') config_h.set_quoted('LIBRATBAG_DATA_DIR', libratbag_data_dir) # Coverity breaks because it doesn't define _Float128 correctly, you'll end # up with a bunch of messages in the form: # "/usr/include/stdlib.h", line 133: error #20: identifier "_Float128" is # undefined # extern _Float128 strtof128 (const char *__restrict __nptr, # ^ # We don't use float128 ourselves, it gets pulled in from math.h or # something, so let's just define it as uint128 and move on. # Unfortunately we can't detect the coverity build at meson configure # time, we only know it fails at runtime. So make this an option instead, to # be removed when coverity fixes this again. if get_option('coverity') config_h.set('_Float128', '__uint128_t') config_h.set('_Float32', 'int') config_h.set('_Float32x', 'int') config_h.set('_Float64', 'long') config_h.set('_Float64x', 'long') endif # dependencies pkgconfig = import('pkgconfig') dep_udev = dependency('libudev') dep_libevdev = dependency('libevdev') dep_glib = dependency('glib-2.0') dep_json_glib = dependency('json-glib-1.0') dep_lm = cc.find_library('m') dep_unistring = cc.find_library('unistring') if get_option('logind-provider') == 'elogind' dep_logind = dependency('libelogind', version : '>=227') else dep_logind = dependency('libsystemd', version : '>=227') endif enable_systemd = get_option('systemd') if enable_systemd dep_systemd = dependency('systemd') endif #### libutil.a #### src_libutil = [ 'src/libratbag-util.c', 'src/libratbag-util.h' ] deps_libutil = [ dep_udev, ] lib_libutil = static_library('util', src_libutil, dependencies : deps_libutil ) dep_libutil = declare_dependency(link_with: lib_libutil) ### libhidpp.a #### src_libhidpp = [ 'src/hidpp-generic.h', 'src/hidpp-generic.c', 'src/hidpp10.h', 'src/hidpp10.c', 'src/hidpp20.h', 'src/hidpp20.c', 'src/usb-ids.h' ] deps_libhidpp = [ dep_lm ] lib_libhidpp = static_library('hidpp', src_libhidpp, dependencies : deps_libhidpp) dep_libhidpp = declare_dependency(link_with: lib_libhidpp) ### liblur #### # # liblur is the library to handle logitech unifying receivers. # install_headers('src/liblur.h') src_liblur = [ 'src/liblur.c', 'src/liblur.h' ] deps_liblur = [ dep_libutil, dep_libhidpp, ] lur_mapfile = 'src/liblur.sym' lur_version_flag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), lur_mapfile) lib_liblur = shared_library('lur', src_liblur, include_directories : include_directories('.'), dependencies : deps_liblur, version : liblur_so_version, link_args : lur_version_flag, link_depends : lur_mapfile, install : true, ) dep_liblur = declare_dependency(link_with: lib_liblur) pkgconfig.generate ( filebase: 'liblur', name: 'Liblur', description: 'Logitech Unifying Receiver configuration library', version: meson.project_version(), libraries: lib_liblur ) #### libratbag.so #### # # libratbag is an internal-only library and the bit that does the actual # work talking to the mouse. it's used by ratbagd. src_libratbag = [ 'src/libratbag-enums.h', 'src/libratbag.h', 'src/driver-etekcity.c', 'src/driver-hidpp20.c', 'src/driver-hidpp10.c', 'src/driver-logitech-g300.c', 'src/driver-logitech-g600.c', 'src/driver-roccat.c', 'src/driver-roccat-kone-pure.c', 'src/driver-gskill.c', 'src/driver-steelseries.c', 'src/driver-test.c', 'src/libratbag.c', 'src/libratbag.h', 'src/libratbag-data.c', 'src/libratbag-data.h', 'src/libratbag-hidraw.c', 'src/libratbag-hidraw.h', 'src/libratbag-private.h', 'src/libratbag-test.c', 'src/libratbag-test.h', 'src/usb-ids.h' ] deps_libratbag = [ dep_udev, dep_libevdev, dep_glib, dep_json_glib, dep_libutil, dep_libhidpp, ] lib_libratbag = static_library('ratbag', src_libratbag, include_directories : include_directories('.'), dependencies : deps_libratbag, ) dep_libratbag = declare_dependency( link_with : lib_libratbag, dependencies : deps_libratbag ) #### libshared.a #### src_libshared = [ 'tools/shared.c', 'tools/shared.h' ] deps_libshared = [ dep_udev, dep_libevdev, ] lib_libshared = static_library('shared', src_libshared, dependencies : deps_libshared, include_directories : include_directories('src') ) dep_libshared = declare_dependency(link_with: lib_libshared) #### hidpp10-dump-page #### src_hidpp10_dump_page = [ 'tools/hidpp10-dump-page.c' ] executable('hidpp10-dump-page', src_hidpp10_dump_page, dependencies : [ dep_libhidpp ], include_directories : include_directories('src'), install : false, ) #### hidpp20-dump-page #### src_hidpp20_dump_page = [ 'tools/hidpp20-dump-page.c' ] executable('hidpp20-dump-page', src_hidpp20_dump_page, dependencies : [ dep_libhidpp ], include_directories : include_directories('src'), install : false, ) #### hidpp20-reset #### src_hidpp20_reset = [ 'tools/hidpp20-reset.c' ] executable('hidpp20-reset', src_hidpp20_reset, dependencies : [ dep_libhidpp ], include_directories : include_directories('src'), install : false, ) #### lur-command #### # # A tool to access and manipulate logitech unifying receivers. src_lur_command = [ 'tools/lur-command.c' ] executable('lur-command', src_lur_command, dependencies : [ dep_libshared, dep_liblur ], include_directories : include_directories('src'), install : true, ) man_config = configuration_data() man_config.set('version', meson.project_version()) man_lur_command = configure_file ( input: 'tools/lur-command.man', output: 'lur-command.1', configuration: man_config, install : true, install_dir : join_paths(get_option('mandir'), 'man1') ) #### data files #### # list ordered by git ls-files data/devices/*.device data_files = files( 'data/devices/etekcity-scroll-alpha.device', 'data/devices/gskill-MX-780.device', 'data/devices/logitech-M325.device', 'data/devices/logitech-M570.device', 'data/devices/logitech-M585-M590.device', 'data/devices/logitech-M705.device', 'data/devices/logitech-MX-Ergo.device', 'data/devices/logitech-MX-Master.device', 'data/devices/logitech-MX-Master-2S.device', 'data/devices/logitech-MX-Master-3.device', 'data/devices/logitech-MX518.device', 'data/devices/logitech-T650.device', 'data/devices/logitech-Wireless-Touchpad.device', 'data/devices/logitech-g-powerplay.device', 'data/devices/logitech-g-pro-wireless.device', 'data/devices/logitech-g-pro.device', 'data/devices/logitech-g102-g203.device', 'data/devices/logitech-g300.device', 'data/devices/logitech-g302.device', 'data/devices/logitech-g303.device', 'data/devices/logitech-g305.device', 'data/devices/logitech-g402.device', 'data/devices/logitech-g403-hero.device', 'data/devices/logitech-g403-wireless.device', 'data/devices/logitech-g403.device', 'data/devices/logitech-g5-2007.device', 'data/devices/logitech-g5.device', 'data/devices/logitech-g500.device', 'data/devices/logitech-g500s.device', 'data/devices/logitech-g502-hero-wireless.device', 'data/devices/logitech-g502-hero.device', 'data/devices/logitech-g502-proteus-core.device', 'data/devices/logitech-g502-proteus-spectrum.device', 'data/devices/logitech-g513.device', 'data/devices/logitech-g600.device', 'data/devices/logitech-g602.device', 'data/devices/logitech-g603.device', 'data/devices/logitech-g604.device', 'data/devices/logitech-g7.device', 'data/devices/logitech-g700-wireless.device', 'data/devices/logitech-g700.device', 'data/devices/logitech-g700s.device', 'data/devices/logitech-g703-hero.device', 'data/devices/logitech-g703.device', 'data/devices/logitech-g9.device', 'data/devices/logitech-g900.device', 'data/devices/logitech-g903-hero.device', 'data/devices/logitech-g903.device', 'data/devices/logitech-g815.device', 'data/devices/logitech-g910.device', 'data/devices/logitech-g915.device', 'data/devices/logitech-g9x-Call-of-Duty-MW3-Edition.device', 'data/devices/logitech-g9x-Original.device', 'data/devices/logitech-Marathon-M705.device', 'data/devices/logitech-MX-Anywhere2.device', 'data/devices/logitech-MX-Anywhere2S.device', 'data/devices/logitech-MX-Vertical.device', 'data/devices/roccat-kone-pure.device', 'data/devices/roccat-kone-xtd.device', 'data/devices/steelseries-kinzu-v2.device', 'data/devices/steelseries-kinzu-v3.device', 'data/devices/steelseries-rival-310.device', 'data/devices/steelseries-rival-600.device', 'data/devices/steelseries-rival.device', 'data/devices/steelseries-sensei-310.device', 'data/devices/steelseries-sensei-raw.device', ) install_data(data_files, install_dir : join_paths(get_option('datadir'), 'libratbag')) data_parse_test = find_program(join_paths(meson.source_root(), 'data/devices/data-parse-test.py')) test('data-parse-test', data_parse_test, args: data_files) duplicate_test = find_program(join_paths(meson.source_root(), 'data/devices/duplicate-check.py')) test('duplicate-test', duplicate_test, args : data_files) reciever_id_test = find_program(join_paths(meson.source_root(), 'data/devices/receiver-check.py')) test('receiver-id-test', reciever_id_test, args : data_files) #### tests #### enable_tests = get_option('tests') if enable_tests dep_check = dependency('check', version: '>= 0.9.10') config_h.set('BUILD_TESTS', '1') test_context = executable('test-context', ['test/test-context.c'], dependencies : [ dep_libratbag, dep_check ], include_directories : include_directories('src'), install : false) test_device = executable('test-device', ['test/test-device.c'], dependencies : [ dep_libratbag, dep_check ], include_directories : include_directories('src'), install : false) test_util = executable('test-util', ['test/test-util.c'], dependencies : [ dep_libratbag, dep_check ], include_directories : include_directories('src'), install : false) test_iconv_helper = executable('test-iconv-helper', ['test/test-iconv-helper.c'], dependencies : [ dep_libratbag, dep_check, dep_libutil], include_directories : include_directories('src'), install : false) test('test-context', test_context) test('test-device', test_device) test('test-util', test_util) test('test-iconv-helper', test_iconv_helper) valgrind = find_program('valgrind') valgrind_suppressions_file = join_paths(meson.source_root(), 'test', 'valgrind.suppressions') add_test_setup('valgrind', exe_wrapper : [ valgrind, '--leak-check=full', '--quiet', '--error-exitcode=3', '--suppressions=' + valgrind_suppressions_file ], timeout_multiplier: 5) endif #### ratbagd #### # # The main item of this repo, a DBus server that uses libratbag to talk to # the mice. The DBus API is the public-facing API. # src_ratbagd = [ 'src/shared-macro.h', 'src/shared-rbtree.h', 'src/shared-rbtree.c', 'ratbagd/ratbagd.h', 'ratbagd/ratbagd.c', 'ratbagd/ratbagd-led.c', 'ratbagd/ratbagd-button.c', 'ratbagd/ratbagd-device.c', 'ratbagd/ratbagd-profile.c', 'ratbagd/ratbagd-resolution.c', 'ratbagd/ratbagd-test.c', 'ratbagd/ratbagd-json.c', 'ratbagd/ratbagd-json.h', 'src/libratbag-util.h', 'src/libratbag-util.c', ] deps_ratbagd = [ dep_udev, dep_logind, dep_libratbag, dep_unistring, ] executable('ratbagd', src_ratbagd, dependencies : deps_ratbagd, include_directories : include_directories('src'), install : true, ) install_man('ratbagd/ratbagd.8') #### ratbagd_devel #### # # A development ratbagd server that owns a different name on the bus # (org.freedesktop.ratbag_devel1). This server is used by ratbagdctl.devel. # config_ratbagd_devel = configuration_data() dbus_devel_policy = configure_file(input : 'dbus/org.freedesktop.ratbag_devel1.conf.in', output : 'org.freedesktop.ratbag_devel1.conf', configuration : config_ratbagd_devel) # This is a hack. We always install the devel policy file into # /etc/dbus-1/system.d, independent of any prefixes we use otherwise. # This should never be used outside of developer machines anyway, but # installations on those may use different prefixes for building. # This is not set in stone, suggest something better if you can. dbussystemdir = join_paths('/', get_option('sysconfdir'), 'dbus-1', 'system.d') executable('ratbagd.devel', src_ratbagd, dependencies : deps_ratbagd, include_directories : include_directories('src'), install : false, c_args : ['-DRATBAG_DBUS_INTERFACE="ratbag_devel1"', '-DDBUS_POLICY_SRC="@0@/@1@"'.format(meson.build_root(), dbus_devel_policy), '-DDBUS_POLICY_DST="@0@/@1@"'.format(dbussystemdir, dbus_devel_policy), '-DDISABLE_COREDUMP=1'], ) #### unit file #### if enable_systemd unitdir = get_option('systemd-unit-dir') if unitdir == '' libdir = get_option('libdir') default_unitdir = dep_systemd.get_pkgconfig_variable('systemdsystemunitdir') # Fedora uses lib64 but systemd is in lib. Hack around this so it # works out of the box. intended_unitdir = join_paths(get_option('prefix'), get_option('libdir'), 'systemd') if get_option('prefix') == '/usr' and intended_unitdir != default_unitdir message(''' systemd unitdir libdir mismatch detected, changing unitdir to @0@ or specify with mesonconf -Dsystemd-unit-dir= See https://github.com/libratbag/libratbag/issues/188 '''.format(default_unitdir)) unitdir = default_unitdir else unitdir = intended_unitdir endif endif endif config_bindir = configuration_data() config_bindir.set('bindir', join_paths(get_option('prefix'), get_option('bindir'))) if enable_systemd configure_file(input : 'ratbagd/ratbagd.service.in', output : 'ratbagd.service', configuration : config_bindir, install_dir : unitdir) endif dbusdir = get_option('dbus-root-dir') if dbusdir == '' dbusdir = join_paths(get_option('prefix'), get_option('datadir'), 'dbus-1') endif configure_file(input : 'dbus/org.freedesktop.ratbag1.service.in', output : 'org.freedesktop.ratbag1.service', configuration : config_bindir, install_dir : join_paths(dbusdir, 'system-services')) dbusgroup = get_option('dbus-group') if dbusgroup == '' # grant everybody access by default access = 'context="default"' else # grant access to members of the specified group only access = 'group="@0@"'.format(dbusgroup) endif config_dbusaccess = configuration_data() config_dbusaccess.set('access', access) configure_file(input : 'dbus/org.freedesktop.ratbag1.conf.in', output : 'org.freedesktop.ratbag1.conf', configuration : config_dbusaccess, install_dir : join_paths(dbusdir, 'system.d')) #### tools #### if meson.version().version_compare('<0.48.0') python = import('python3') py3 = python.find_python() else pymod = import('python') py3 = pymod.find_installation() endif config_ratbagctl = configuration_data() config_ratbagctl.set('RATBAGD_API_VERSION', ratbagd_api_version) config_ratbagctl.set('version', meson.project_version()) ratbagctl_in = configure_file(input : 'tools/ratbagctl.in.in', output : 'ratbagctl.in', configuration: config_ratbagctl) # ratbagctl is the commandline tool to interact with ratbagd over DBus. custom_target('ratbagctl', output : 'ratbagctl', input : [ratbagctl_in, 'tools/ratbagd.py'], build_by_default : true, command : [py3, join_paths(meson.source_root(), 'tools', 'merge_ratbagd.py'), '@INPUT@', '--output', '@OUTPUT@'], install: true, install_dir: get_option('bindir')) man_ratbagctl = configure_file ( input: 'tools/ratbagctl.1', output: 'ratbagctl.1', configuration: man_config, install : true, install_dir : join_paths(get_option('mandir'), 'man1') ) config_ratbagctl_devel = configuration_data() config_ratbagctl_devel.set('MESON_BUILD_ROOT', meson.build_root()) config_ratbagctl_devel.set('LIBRATBAG_DATA_DIR', libratbag_data_dir_devel) config_ratbagctl_devel.set('RATBAGD_API_VERSION', ratbagd_api_version) configure_file(input : 'tools/toolbox.py', output : 'toolbox.py', configuration : config_ratbagctl_devel) # ratbagctl.devel starts a custom ratbagd and interacts with that over DBus configure_file(input : 'tools/ratbagctl.devel.in', output : 'ratbagctl.devel', configuration : config_ratbagctl_devel) # ratbagctl.test starts a custom ratbagd and interacts with that over DBus configure_file(input : 'tools/ratbagctl.test.in', output : 'ratbagctl.test', configuration : config_ratbagctl_devel) env_test = environment() env_test.set('LIBRATBAG_DATA_DIR', libratbag_data_dir_devel) ratbagctl_test = find_program(join_paths(meson.build_root(), 'ratbagctl.test')) test('ratbagctl-test', ratbagctl_test, args: ['-v'], env : env_test) # ratbag-command uses Swig bindings to call libratbag directly swig = find_program('swig') swig_gen = generator( swig, output: ['@BASENAME@.c'], arguments: ['-python', '-py3', '-o', './@OUTPUT@', '-outdir', '.', '-I' + join_paths(meson.source_root(), 'src'), '-I' + join_paths(meson.source_root(), 'tools'), '@INPUT@'], ) # From python 3.8 we neeed python3-embed dep_python3 = dependency('python3-embed', required: false) if not dep_python3.found() dep_python3 = dependency('python3') endif wrapper_deps = [ dep_python3, dep_libratbag, dep_libshared, ] i_source = join_paths(meson.source_root(), 'src', 'libratbag.i') c_source = swig_gen.process(i_source) shared_library( 'libratbag', c_source, name_prefix: '_', c_args : ['-Wno-missing-prototypes', '-Wno-format-nonliteral'], extra_files: [ i_source ] + src_libratbag , dependencies: deps_libratbag + wrapper_deps, include_directories : include_directories('src', 'tools'), install: false, ) i_source = join_paths(meson.source_root(), 'src', 'hidpp.i') c_source = swig_gen.process(i_source) shared_library( 'hidpp', c_source, name_prefix: '_', c_args : ['-Wno-missing-prototypes', '-Wno-format-nonliteral'], extra_files: [ i_source ] + src_libratbag , dependencies: deps_libratbag + wrapper_deps, include_directories : include_directories('src', 'tools'), install: false, ) # ratbagc is the layer that maps ratbagctl to the swig bindings ratbagc_py_conf = configuration_data() ratbagc_py_conf.set('LIBRATBAG_DATA_DIR', libratbag_data_dir_devel) ratbagc_py = configure_file(input: 'tools/ratbagc.py.in', output: 'ratbagc.py', configuration: ratbagc_py_conf) # ratbag-command is a commandline tool with the UI as ratbagctl but instead # of connecting to ratbagd over DBus, it uses libratbag directly. This is a # development tool for protocol debugging etc. custom_target('ratbag-command', output : 'ratbag-command', input : [ratbagctl_in, ratbagc_py], build_by_default : true, command : [py3, join_paths(meson.source_root(), 'tools', 'merge_ratbagd.py'), '@INPUT@', '--output', '@OUTPUT@'], install: false) #### output files #### configure_file(output: 'config.h', install: false, configuration: config_h) subdir('doc') libratbag-0.13/meson_options.txt000066400000000000000000000022101362011324700170130ustar00rootroot00000000000000option('udev-dir', type: 'string', value: '', description: 'udev base directory [default=$prefix/lib/udev]') option('tests', type: 'boolean', value: true, description: 'Build the tests (default=yes)') option('systemd-unit-dir', type : 'string', value : '', description : 'systemd unit directory [default=$libdir/systemd/system]') option('dbus-root-dir', type : 'string', value : '', description : 'dbus service directory [default=$datadir/dbus-1]') option('documentation', type: 'boolean', value: false, description: 'Enable documentation build (default=no)') option('dbus-group', type: 'string', value : '', description : 'The UNIX group that is granted access to the ratbagd D-Bus service. By default all users may access it.') option('systemd', type : 'boolean', value : true, description : 'Build systemd unit files') option('logind-provider', type : 'combo', choices: [ 'elogind', 'systemd'], value : 'systemd', description : 'Which logind provider to use') option('coverity', type: 'boolean', value: false, description: 'Enable coverity build fixes, see meson.build for details [default=false]') libratbag-0.13/ratbagd/000077500000000000000000000000001362011324700147675ustar00rootroot00000000000000libratbag-0.13/ratbagd/org.freedesktop.ratbag1.conf000066400000000000000000000007161362011324700222630ustar00rootroot00000000000000 libratbag-0.13/ratbagd/ratbagd-button.c000066400000000000000000000302011362011324700200440ustar00rootroot00000000000000/*** This file is part of ratbagd. Copyright 2016 Red Hat, Inc. 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 (including the next paragraph) 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. ***/ #include "config.h" #include #include #include #include #include #include #include #include #include #include "ratbagd.h" #include "shared-macro.h" #include "libratbag-util.h" struct ratbagd_button { struct ratbagd_device *device; struct ratbag_button *lib_button; unsigned int index; char *path; }; static int ratbagd_button_get_button(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_button *button = userdata; unsigned int b; b = ratbag_button_get_button(button->lib_button); verify_unsigned_int(b); return sd_bus_message_append(reply, "(uv)", RATBAG_BUTTON_ACTION_TYPE_BUTTON, "u", b); } static int ratbagd_button_set_button(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_button *button = userdata; unsigned int map; int r; r = sd_bus_message_read(m, "v", "u", &map); if (r < 0) return r; if (map == 0 || map > 30) return 0; r = ratbag_button_set_button(button->lib_button, map); if (r == 0) { sd_bus_emit_properties_changed(bus, button->path, RATBAGD_NAME_ROOT ".Button", "Mapping", NULL); } return 0; } static int ratbagd_button_get_special(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_button *button = userdata; enum ratbag_button_action_special special; special = ratbag_button_get_special(button->lib_button); if (special == RATBAG_BUTTON_ACTION_SPECIAL_INVALID) special = RATBAG_BUTTON_ACTION_SPECIAL_UNKNOWN; verify_unsigned_int(special); CHECK_CALL(sd_bus_message_append(reply, "(uv)", RATBAG_BUTTON_ACTION_TYPE_SPECIAL, "u", special)); return 0; } static int ratbagd_button_set_special(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_button *button = userdata; enum ratbag_button_action_special special; int r; CHECK_CALL(sd_bus_message_read(m, "v", "u", &special)); r = ratbag_button_set_special(button->lib_button, special); if (r == 0) { sd_bus_emit_properties_changed(bus, button->path, RATBAGD_NAME_ROOT ".Button", "Mapping", NULL); } return 0; } DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbag_button_macro *, ratbag_button_macro_unref); static int ratbagd_button_get_macro(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_button *button = userdata; _cleanup_(ratbag_button_macro_unrefp) struct ratbag_button_macro *macro = NULL; unsigned int idx; CHECK_CALL(sd_bus_message_open_container(reply, SD_BUS_TYPE_STRUCT, "uv")); CHECK_CALL(sd_bus_message_append(reply, "u", RATBAG_BUTTON_ACTION_TYPE_MACRO)); CHECK_CALL(sd_bus_message_open_container(reply, 'v', "a(uu)")); CHECK_CALL(sd_bus_message_open_container(reply, 'a', "(uu)")); macro = ratbag_button_get_macro(button->lib_button); if (!macro) goto out; for (idx = 0; idx < ratbag_button_macro_get_num_events(macro); idx++) { enum ratbag_macro_event_type type; int value; type = ratbag_button_macro_get_event_type(macro, idx); switch (type) { case RATBAG_MACRO_EVENT_INVALID: abort(); break; case RATBAG_MACRO_EVENT_NONE: goto out; case RATBAG_MACRO_EVENT_KEY_PRESSED: case RATBAG_MACRO_EVENT_KEY_RELEASED: value = ratbag_button_macro_get_event_key(macro, idx); break; case RATBAG_MACRO_EVENT_WAIT: value = ratbag_button_macro_get_event_timeout(macro, idx); break; default: abort(); } verify_unsigned_int(type); verify_unsigned_int(value); CHECK_CALL(sd_bus_message_append(reply, "(uu)", type, value)); } out: CHECK_CALL(sd_bus_message_close_container(reply)); /* a(uu) */ CHECK_CALL(sd_bus_message_close_container(reply)); /* v */ CHECK_CALL(sd_bus_message_close_container(reply)); /* ) */ return 0; } static int ratbagd_button_set_macro(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_button *button = userdata; unsigned int type, value; int r, idx = 0; _cleanup_(ratbag_button_macro_unrefp) struct ratbag_button_macro *macro = NULL; CHECK_CALL(sd_bus_message_enter_container(m, 'v', "a(uu)")); CHECK_CALL(sd_bus_message_enter_container(m, 'a', "(uu)")); macro = ratbag_button_macro_new("macro"); while ((r = sd_bus_message_read(m, "(uu)", &type, &value)) > 0) { r = ratbag_button_macro_set_event(macro, idx++, type, value); if (r < 0) { r = ratbagd_device_resync(button->device, bus); if (r < 0) return r; } } if (r < 0) return r; CHECK_CALL(sd_bus_message_exit_container(m)); /* (uu) */ CHECK_CALL(sd_bus_message_exit_container(m)); /* a(uu) */ r = ratbag_button_set_macro(button->lib_button, macro); if (r < 0) { r = ratbagd_device_resync(button->device, bus); if (r < 0) return r; } if (r == 0) { sd_bus_emit_properties_changed(bus, button->path, RATBAGD_NAME_ROOT ".Button", "Mapping", NULL); } return 0; } static int ratbagd_button_get_mapping(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_button *button = userdata; enum ratbag_button_action_type type; type = ratbag_button_get_action_type(button->lib_button); if (type == RATBAG_BUTTON_ACTION_TYPE_KEY) type = RATBAG_BUTTON_ACTION_TYPE_UNKNOWN; verify_unsigned_int(type); switch (type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: return ratbagd_button_get_button(bus, path, interface, property, reply, userdata, error); case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: return ratbagd_button_get_special(bus, path, interface, property, reply, userdata, error); case RATBAG_BUTTON_ACTION_TYPE_MACRO: return ratbagd_button_get_macro(bus, path, interface, property, reply, userdata, error); default: return sd_bus_message_append(reply, "(uv)", RATBAG_BUTTON_ACTION_TYPE_UNKNOWN, "u", 0); } return 0; } static int ratbagd_button_set_mapping(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *m, void *userdata, sd_bus_error *error) { enum ratbag_button_action_type type; CHECK_CALL(sd_bus_message_enter_container(m, SD_BUS_TYPE_STRUCT, "uv")); CHECK_CALL(sd_bus_message_read(m, "u", &type)); switch (type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: CHECK_CALL(ratbagd_button_set_button(bus, path, interface, property, m, userdata, error)); break; case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: CHECK_CALL(ratbagd_button_set_special(bus, path, interface, property, m, userdata, error)); break; case RATBAG_BUTTON_ACTION_TYPE_MACRO: CHECK_CALL(ratbagd_button_set_macro(bus, path, interface, property, m, userdata, error)); break; default: /* FIXME */ return 1; } CHECK_CALL(sd_bus_message_exit_container(m)); return 0; } static int ratbagd_button_get_action_types(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_button *button = userdata; enum ratbag_button_action_type types[] = { RATBAG_BUTTON_ACTION_TYPE_BUTTON, RATBAG_BUTTON_ACTION_TYPE_SPECIAL, RATBAG_BUTTON_ACTION_TYPE_MACRO }; enum ratbag_button_action_type *t; CHECK_CALL(sd_bus_message_open_container(reply, 'a', "u")); ARRAY_FOR_EACH(types, t) { if (!ratbag_button_has_action_type(button->lib_button, *t)) continue; verify_unsigned_int(*t); CHECK_CALL(sd_bus_message_append(reply, "u", *t)); } CHECK_CALL(sd_bus_message_close_container(reply)); return 0; } static int ratbagd_button_disable(sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_button *button = userdata; int r; CHECK_CALL(sd_bus_message_read(m, "")); r = ratbag_button_disable(button->lib_button); if (r < 0) { sd_bus *bus = sd_bus_message_get_bus(m); r = ratbagd_device_resync(button->device, bus); if (r < 0) return r; } CHECK_CALL(sd_bus_reply_method_return(m, "u", 0)); return 0; } const sd_bus_vtable ratbagd_button_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Index", "u", NULL, offsetof(struct ratbagd_button, index), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_WRITABLE_PROPERTY("Mapping", "(uv)", ratbagd_button_get_mapping, ratbagd_button_set_mapping, 0, SD_BUS_VTABLE_UNPRIVILEGED | SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("ActionTypes", "au", ratbagd_button_get_action_types, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_METHOD("Disable", "", "u", ratbagd_button_disable, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END, }; int ratbagd_button_new(struct ratbagd_button **out, struct ratbagd_device *device, struct ratbagd_profile *profile, struct ratbag_button *lib_button, unsigned int index) { _cleanup_(ratbagd_button_freep) struct ratbagd_button *button = NULL; char profile_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1], button_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1]; int r; assert(out); assert(lib_button); button = zalloc(sizeof(*button)); button->device = device; button->lib_button = lib_button; button->index = index; sprintf(profile_buffer, "p%u", ratbagd_profile_get_index(profile)); sprintf(button_buffer, "b%u", index); r = sd_bus_path_encode_many(&button->path, RATBAGD_OBJ_ROOT "/button/%/%/%", ratbagd_device_get_sysname(device), profile_buffer, button_buffer); if (r < 0) return r; *out = button; button = NULL; return 0; } const char *ratbagd_button_get_path(struct ratbagd_button *button) { assert(button); return button->path; } struct ratbagd_button *ratbagd_button_free(struct ratbagd_button *button) { if (!button) return NULL; button->path = mfree(button->path); button->lib_button = ratbag_button_unref(button->lib_button); return mfree(button); } int ratbagd_button_resync(sd_bus *bus, struct ratbagd_button *button) { return sd_bus_emit_properties_changed(bus, button->path, RATBAGD_NAME_ROOT ".Button", "ButtonMapping", "SpecialMapping", "Macro", "ActionType", NULL); } libratbag-0.13/ratbagd/ratbagd-device.c000066400000000000000000000313031362011324700177740ustar00rootroot00000000000000/*** This file is part of ratbagd. Copyright 2015 David Herrmann 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 (including the next paragraph) 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. ***/ #include "config.h" #include #include #include #include #include #include #include #include #include #include "ratbagd.h" #include "shared-macro.h" #include "shared-rbtree.h" #include "libratbag-util.h" struct ratbagd_device { unsigned int refcount; struct ratbagd *ctx; RBNode node; char *sysname; char *path; struct ratbag_device *lib_device; sd_bus_slot *profile_vtable_slot; sd_bus_slot *profile_enum_slot; unsigned int n_profiles; struct ratbagd_profile **profiles; }; #define ratbagd_device_from_node(_ptr) \ rbnode_of((_ptr), struct ratbagd_device, node) static int ratbagd_device_find_profile(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { _cleanup_(freep) char *name = NULL; struct ratbagd_device *device = userdata; unsigned int index = 0; int r; r = sd_bus_path_decode_many(path, RATBAGD_OBJ_ROOT "/profile/%/p%", NULL, &name); if (r <= 0) return r; r = safe_atou(name, &index); if (r < 0) return 0; if (index >= device->n_profiles || !device->profiles[index]) return 0; *found = device->profiles[index]; return 1; } static int ratbagd_device_list_profiles(sd_bus *bus, const char *path, void *userdata, char ***paths, sd_bus_error *error) { struct ratbagd_device *device = userdata; struct ratbagd_profile *profile; char **profiles; unsigned int i; profiles = zalloc((device->n_profiles + 1) * sizeof(char *)); for (i = 0; i < device->n_profiles; ++i) { profile = device->profiles[i]; if (!profile) continue; profiles[i] = strdup_safe(ratbagd_profile_get_path(profile)); } profiles[i] = NULL; *paths = profiles; return 1; } static int ratbagd_device_get_device_name(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_device *device = userdata; const char *name; name = ratbag_device_get_name(device->lib_device); if (!name) { log_error("%s: failed to fetch name\n", ratbagd_device_get_sysname(device)); name = ""; } CHECK_CALL(sd_bus_message_append(reply, "s", name)); return 0; } static int ratbagd_device_get_profiles(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_device *device = userdata; struct ratbagd_profile *profile; unsigned int i; CHECK_CALL(sd_bus_message_open_container(reply, 'a', "o")); for (i = 0; i < device->n_profiles; ++i) { profile = device->profiles[i]; if (!profile) continue; CHECK_CALL(sd_bus_message_append(reply, "o", ratbagd_profile_get_path(profile))); } CHECK_CALL(sd_bus_message_close_container(reply)); return 0; } static void ratbagd_device_commit_pending(void *data) { struct ratbagd_device *device = data; int r; r = ratbag_device_commit(device->lib_device); if (r) log_error("error committing device (%d)\n", r); if (r < 0) ratbagd_device_resync(device, device->ctx->bus); ratbagd_device_unref(device); } static int ratbagd_device_commit(sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_device *device = userdata; ratbagd_schedule_task(device->ctx, ratbagd_device_commit_pending, ratbagd_device_ref(device)); CHECK_CALL(sd_bus_reply_method_return(m, "u", 0)); return 0; } static int ratbag_device_get_model(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_device *device = userdata; struct ratbag_device *lib_device = device->lib_device; const char *bustype = ratbag_device_get_bustype(lib_device); uint32_t vid = ratbag_device_get_vendor_id(lib_device), pid = ratbag_device_get_product_id(lib_device), version = ratbag_device_get_product_version(lib_device); char model[64]; if (!bustype) return sd_bus_message_append(reply, "s", "unknown"); snprintf(model, sizeof(model), "%s:%04x:%04x:%d", bustype, vid, pid, version); return sd_bus_message_append(reply, "s", model); } const sd_bus_vtable ratbagd_device_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Model", "s", ratbag_device_get_model, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Name", "s", ratbagd_device_get_device_name, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Profiles", "ao", ratbagd_device_get_profiles, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_METHOD("Commit", "", "u", ratbagd_device_commit, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_SIGNAL("Resync", "", 0), SD_BUS_VTABLE_END, }; int ratbagd_device_new(struct ratbagd_device **out, struct ratbagd *ctx, const char *sysname, struct ratbag_device *lib_device) { _cleanup_(ratbagd_device_unrefp) struct ratbagd_device *device = NULL; struct ratbag_profile *profile; unsigned int i; int r; assert(out); assert(ctx); assert(sysname); device = zalloc(sizeof(*device)); device->refcount = 1; device->ctx = ctx; rbnode_init(&device->node); device->lib_device = ratbag_device_ref(lib_device); device->sysname = strdup_safe(sysname); r = sd_bus_path_encode(RATBAGD_OBJ_ROOT "/device", device->sysname, &device->path); if (r < 0) return r; device->n_profiles = ratbag_device_get_num_profiles(device->lib_device); device->profiles = zalloc(device->n_profiles * sizeof(*device->profiles)); log_info("%s: \"%s\", %d profiles\n", sysname, ratbag_device_get_name(lib_device), device->n_profiles); for (i = 0; i < device->n_profiles; ++i) { profile = ratbag_device_get_profile(device->lib_device, i); if (!profile) continue; r = ratbagd_profile_new(&device->profiles[i], device, profile, i); if (r < 0) { errno = -r; log_error("%s: failed to allocate profile: %m\n", device->sysname); } } *out = device; device = NULL; return 0; } static void ratbagd_device_free(struct ratbagd_device *device) { unsigned int i; if (!device) return; assert(!ratbagd_device_linked(device)); for (i = 0; i < device->n_profiles; ++i) device->profiles[i] = ratbagd_profile_free(device->profiles[i]); device->profiles = mfree(device->profiles); device->lib_device = ratbag_device_unref(device->lib_device); device->path = mfree(device->path); device->sysname = mfree(device->sysname); assert(!device->lib_device); /* ratbag yields !NULL if still pinned */ mfree(device); } struct ratbagd_device *ratbagd_device_ref(struct ratbagd_device *device) { assert(device->refcount > 0); ++device->refcount; return device; } struct ratbagd_device *ratbagd_device_unref(struct ratbagd_device *device) { assert(device->refcount > 0); --device->refcount; if (device->refcount == 0) ratbagd_device_free(device); return NULL; } const char *ratbagd_device_get_sysname(struct ratbagd_device *device) { assert(device); return device->sysname; } const char *ratbagd_device_get_path(struct ratbagd_device *device) { assert(device); return device->path; } unsigned int ratbagd_device_get_num_buttons(struct ratbagd_device *device) { assert(device); return ratbag_device_get_num_buttons(device->lib_device); } unsigned int ratbagd_device_get_num_leds(struct ratbagd_device *device) { assert(device); return ratbag_device_get_num_leds(device->lib_device); } int ratbagd_device_resync(struct ratbagd_device *device, sd_bus *bus) { assert(device); assert(bus); ratbagd_for_each_profile_signal(bus, device, ratbagd_profile_resync); return sd_bus_emit_signal(bus, device->path, RATBAGD_NAME_ROOT ".Device", "Resync", NULL); } bool ratbagd_device_linked(struct ratbagd_device *device) { return device && rbnode_linked(&device->node); } void ratbagd_device_link(struct ratbagd_device *device) { _cleanup_(freep) char *prefix = NULL; struct ratbagd_device *iter; RBNode **node, *parent; int r, v; unsigned int i; assert(device); assert(!ratbagd_device_linked(device)); /* find place to link it to */ parent = NULL; node = &device->ctx->device_map.root; while (*node) { parent = *node; iter = ratbagd_device_from_node(parent); v = strcmp(device->sysname, iter->sysname); /* if there's a duplicate, the caller screwed up */ assert(v != 0); if (v < 0) node = &parent->left; else /* if (v > 0) */ node = &parent->right; } /* link into context */ rbtree_add(&device->ctx->device_map, parent, node, &device->node); ++device->ctx->n_devices; /* register profile interfaces */ r = sd_bus_path_encode_many(&prefix, RATBAGD_OBJ_ROOT "/profile/%", device->sysname); if (r >= 0) { r = sd_bus_add_fallback_vtable(device->ctx->bus, &device->profile_vtable_slot, prefix, RATBAGD_NAME_ROOT ".Profile", ratbagd_profile_vtable, ratbagd_device_find_profile, device); if (r >= 0) r = sd_bus_add_node_enumerator(device->ctx->bus, &device->profile_enum_slot, prefix, ratbagd_device_list_profiles, device); } if (r < 0) { errno = -r; log_error("%s: failed to register profile interfaces: %m\n", device->sysname); return; } for (i = 0; i < device->n_profiles; i++) { r = ratbagd_profile_register_resolutions(device->ctx->bus, device, device->profiles[i]); if (r < 0) { log_error("%s: failed to register resolutions: %m\n", device->sysname); } r = ratbagd_profile_register_buttons(device->ctx->bus, device, device->profiles[i]); if (r < 0) { log_error("%s: failed to register buttons: %m\n", device->sysname); } r = ratbagd_profile_register_leds(device->ctx->bus, device, device->profiles[i]); if (r < 0) { log_error("%s: failed to register leds: %m\n", device->sysname); } } } void ratbagd_device_unlink(struct ratbagd_device *device) { if (!ratbagd_device_linked(device)) return; device->profile_enum_slot = sd_bus_slot_unref(device->profile_enum_slot); device->profile_vtable_slot = sd_bus_slot_unref(device->profile_vtable_slot); /* unlink from context */ --device->ctx->n_devices; rbtree_remove(&device->ctx->device_map, &device->node); rbnode_init(&device->node); } struct ratbagd_device *ratbagd_device_lookup(struct ratbagd *ctx, const char *name) { struct ratbagd_device *device; RBNode *node; int v; assert(ctx); assert(name); node = ctx->device_map.root; while (node) { device = ratbagd_device_from_node(node); v = strcmp(name, device->sysname); if (!v) return device; else if (v < 0) node = node->left; else /* if (v > 0) */ node = node->right; } return NULL; } struct ratbagd_device *ratbagd_device_first(struct ratbagd *ctx) { struct RBNode *node; assert(ctx); node = rbtree_first(&ctx->device_map); return node ? ratbagd_device_from_node(node) : NULL; } struct ratbagd_device *ratbagd_device_next(struct ratbagd_device *device) { struct RBNode *node; assert(device); node = rbnode_next(&device->node); return node ? ratbagd_device_from_node(node) : NULL; } int ratbagd_for_each_profile_signal(sd_bus *bus, struct ratbagd_device *device, int (*func)(sd_bus *bus, struct ratbagd_profile *profile)) { int rc = 0; for (size_t i = 0; rc == 0 && i < device->n_profiles; i++) rc = func(bus, device->profiles[i]); return rc; } libratbag-0.13/ratbagd/ratbagd-json.c000066400000000000000000000404531362011324700175140ustar00rootroot00000000000000/* * Copyright © 2019 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ /* JSON parser for ratbag test devices. The JSON format is not fixed and may change at any time, but basically looks like this: { "profiles": [ { "is_active": bool, "is_default": bool, "is_disabled": bool, "rate": int, "report_rates": [ int, ...], "capabilities": [int, ...] # profile capabilities "resolutions": [ { "xres": int, "yres": int, "dpi_min", int, "dpi_max", int, "is_active": bool, "is_default": bool, "capabilities": [int, ...] # resolution capabilities } ] "buttons": [ "action_type": "" # none, button, key, special, macro, unknown "button": int, # if button "key": int, # if key "special": "", # if special, for values see see special_lookup() "macro": ["+B", "-B", "t400"] # if macro. uppercase only ] "leds": [ ] }, { ... next profile ... } ] } ratbagd uses a minimum sane device (1 profile, 1 resolution, etc.) and any JSON instructions get merged into that device. Which means you usually only need to set those bits you care about being checked. */ #include "libratbag-util.h" #include "ratbagd-json.h" #include "ratbagd-test.h" #include #include /* Hack because we don't do proper data passing */ static int num_resolutions; static int num_buttons; static int num_leds; static int parse_error = 0; #define parser_error(element_) { \ log_error("json: parser error: %s:%d: element '%s'\n", __func__, __LINE__, (element_)); \ parse_error = -EINVAL; \ goto out; \ } static void parse_resolution_member(JsonObject *obj, const gchar *name, JsonNode *node, void *data) { struct ratbag_test_resolution *resolution = data; if (streq(name, "xres")) { gboolean v = json_object_get_int_member(obj, name); if (v < 0 || v > 20000) parser_error("xres"); resolution->xres = v; log_verbose("json: xres: %d\n", v); } else if (streq(name, "yres")) { gboolean v = json_object_get_int_member(obj, name); if (v < 0 || v > 20000) parser_error("yres"); resolution->yres = v; log_verbose("json: yres: %d\n", v); } else if (streq(name, "dpi_min")) { gboolean v = json_object_get_int_member(obj, name); if (v < 0 || v > 20000) parser_error("dpi_min"); resolution->dpi_min = v; log_verbose("json: dpi_min: %d\n", v); } else if (streq(name, "dpi_max")) { gboolean v = json_object_get_int_member(obj, name); if (v < 0 || v > 20000) parser_error("dpi_min"); resolution->dpi_max = v; log_verbose("json: dpi_max: %d\n", v); } else if (streq(name, "is_active")) { gboolean v = json_object_get_boolean_member(obj, name); resolution->active = v; log_verbose("json: is_active: %d\n", v); } else if (streq(name, "is_default")) { gboolean v = json_object_get_boolean_member(obj, name); resolution->dflt = v; log_verbose("json: is_default: %d\n", v); } else if (streq(name, "capabilities")) { JsonArray *a = json_object_get_array_member(obj, name); assert(json_array_get_length(a) < ARRAY_LENGTH(resolution->caps)); for (size_t s = 0; s < json_array_get_length(a); s++) { int v = json_array_get_int_element(a, s); if (v > RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION) parser_error("capabilities"); resolution->caps[s] = v; } log_verbose("json: caps: %d %d %d %d %d...\n", resolution->caps[0], resolution->caps[1], resolution->caps[2], resolution->caps[3], resolution->caps[4]); } else { log_error("json: unknown resolution key '%s'\n", name); parse_error = -EINVAL; } out: return; } static void parse_resolution(JsonNode *node, struct ratbag_test_resolution *resolution) { JsonObject *obj = json_node_get_object(node); json_object_foreach_member(obj, parse_resolution_member, resolution); } static void parse_led_member(JsonObject *obj, const gchar *name, JsonNode *node, void *data) { struct ratbag_test_led *led = data; if (streq(name, "type")) { gboolean v = json_object_get_int_member(obj, name); if (v < 0 || v > RATBAG_LED_TYPE_SWITCHES) parser_error("type"); led->type = v; log_verbose("json: type: %d\n", v); } else if (streq(name, "mode")) { gboolean v = json_object_get_int_member(obj, name); if (v < 0 || v > RATBAG_LED_BREATHING) parser_error("mode"); led->mode = v; log_verbose("json: mode: %d\n", v); } else if (streq(name, "duration")) { gboolean v = json_object_get_int_member(obj, name); if (v < 0 || v > 10000) parser_error("duration"); led->ms = v; log_verbose("json: duration: %d\n", v); } else if (streq(name, "brightness")) { gboolean v = json_object_get_int_member(obj, name); if (v < 0 || v > 100) parser_error("brightness"); led->brightness = v; log_verbose("json: brightness: %d\n", v); } else if (streq(name, "color")) { JsonArray *a = json_object_get_array_member(obj, name); assert(json_array_get_length(a) == 3); led->color.red = json_array_get_int_element(a, 0); led->color.green = json_array_get_int_element(a, 1); led->color.blue = json_array_get_int_element(a, 2); log_verbose("json: color: %02x%02x%02x\n", led->color.red, led->color.green, led->color.blue); } else { log_error("json: unknown led key '%s'\n", name); parse_error = -EINVAL; } out: return; } static void parse_led(JsonNode *node, struct ratbag_test_led *led) { JsonObject *obj = json_node_get_object(node); json_object_foreach_member(obj, parse_led_member, led); } static enum ratbag_button_action_special special_lookup(const char *string) { const struct { const char *key; enum ratbag_button_action_special special; } lut[] = { { "invalid", RATBAG_BUTTON_ACTION_SPECIAL_INVALID}, { "unknown", RATBAG_BUTTON_ACTION_SPECIAL_UNKNOWN}, { "doubleclick", RATBAG_BUTTON_ACTION_SPECIAL_DOUBLECLICK}, { "wheel-left", RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT}, { "wheel-right", RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT}, { "wheel-up", RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP}, { "wheel-down", RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN}, { "ratchet-mode-switch", RATBAG_BUTTON_ACTION_SPECIAL_RATCHET_MODE_SWITCH}, { "resolution-cycle-up", RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP}, { "resolution-cycledown", RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_DOWN}, { "resolution-up", RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP}, { "resolution-down", RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN}, { "resolution-alternate", RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE}, { "resolution-default", RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT}, { "profile-cycle-up", RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP}, { "profile-cycle-down", RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_DOWN}, { "profile-up", RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP}, { "profile-down", RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN}, { "second-mode", RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE}, { "battery-level", RATBAG_BUTTON_ACTION_SPECIAL_BATTERY_LEVEL}, }; for (size_t i = 0; i < ARRAY_LENGTH(lut); i++) { if (streq(string, lut[i].key)) return lut[i].special; } parser_error("special"); out: return RATBAG_BUTTON_ACTION_SPECIAL_INVALID; } static enum ratbag_button_action_type action_type_lookup(const char *string) { const struct { const char *key; enum ratbag_button_action_type type; } lut[] = { { "none", RATBAG_BUTTON_ACTION_TYPE_NONE}, { "button", RATBAG_BUTTON_ACTION_TYPE_BUTTON}, { "special", RATBAG_BUTTON_ACTION_TYPE_SPECIAL}, { "key", RATBAG_BUTTON_ACTION_TYPE_KEY}, { "macro", RATBAG_BUTTON_ACTION_TYPE_MACRO}, { "unknown", RATBAG_BUTTON_ACTION_TYPE_UNKNOWN}, }; for (size_t i = 0; i < ARRAY_LENGTH(lut); i++) { if (streq(string, lut[i].key)) return lut[i].type; } parser_error("action_type"); out: return RATBAG_BUTTON_ACTION_TYPE_UNKNOWN; } static inline struct ratbag_test_macro_event parse_macro(const char *m) { struct ratbag_test_macro_event event = { .type = RATBAG_MACRO_EVENT_INVALID, .value = 0, }; char keyname[32]; if (strlen(m) < 2) goto out; switch(m[0]) { case '+': event.type = RATBAG_MACRO_EVENT_KEY_PRESSED; snprintf(keyname, sizeof(keyname), "KEY_%s", m + 1); event.value = libevdev_event_code_from_name(EV_KEY, keyname); log_verbose("json: macro: +%s\n", keyname); break; case '-': event.type = RATBAG_MACRO_EVENT_KEY_RELEASED; snprintf(keyname, sizeof(keyname), "KEY_%s", m + 1); event.value = libevdev_event_code_from_name(EV_KEY, keyname); log_verbose("json: macro: -%s\n", keyname); break; case 't': event.type = RATBAG_MACRO_EVENT_WAIT; event.value = atoi(m+1); log_verbose("json: macro: t%d\n", event.value); break; default: parser_error("macro"); break; } out: return event; } static void parse_button_member(JsonObject *obj, const gchar *name, JsonNode *node, void *data) { struct ratbag_test_button *button = data; if (streq(name, "action_type")) { const gchar* v = json_object_get_string_member(obj, name); button->action_type = action_type_lookup(v); log_verbose("json: action_type: %s\n", v); } else if (streq(name, "button")) { gint v = json_object_get_int_member(obj, name); if (v < 0 || v > 32) parser_error("button"); button->button = v; log_verbose("json: button: %d\n", v); } else if (streq(name, "key")) { gint v = json_object_get_int_member(obj, name); if (v < 0 || v > KEY_MAX) parser_error("key"); button->key = v; log_verbose("json: key: %d\n", v); } else if (streq(name, "special")) { const gchar *v = json_object_get_string_member(obj, name); button->special = special_lookup(v); log_verbose("json: special: %s\n", v); } else if (streq(name, "macro")) { JsonArray *a = json_object_get_array_member(obj, name); assert(json_array_get_length(a) < ARRAY_LENGTH(button->macro)); for (size_t s = 0; s < json_array_get_length(a); s++) { const gchar *v = json_array_get_string_element(a, s); button->macro[s] = parse_macro(v); } } else { log_error("json: unknown button key '%s'\n", name); parse_error = -EINVAL; } out: return; } static void parse_button(JsonNode *node, struct ratbag_test_button *button) { JsonObject *obj = json_node_get_object(node); json_object_foreach_member(obj, parse_button_member, button); } static void parse_profile_member(JsonObject *obj, const gchar *name, JsonNode *node, void *data) { struct ratbag_test_profile *profile = data; if (streq(name, "name")) { const char *v = json_object_get_string_member(obj, name); if (v == NULL) parser_error("name"); free(profile->name); profile->name = strdup_safe(v); log_verbose("name: %s\n", v); } else if (streq(name, "is_default")) { gboolean v = json_object_get_boolean_member(obj, name); profile->dflt = v; log_verbose("json: is_default: %d\n", v); } else if (streq(name, "is_active")) { gboolean v = json_object_get_boolean_member(obj, name); profile->active = v; log_verbose("json: is_active: %d\n", v); } else if (streq(name, "is_disabled")) { gboolean v = json_object_get_boolean_member(obj, name); profile->disabled = v; log_verbose("json: is_disabled: %d\n", v); } else if (streq(name, "rate")) { gboolean v = json_object_get_int_member(obj, name); if (v < 0 || v > 20000) parser_error("rate"); profile->hz = v; log_verbose("json: rate: %d\n", v); } else if (streq(name, "report_rates")) { JsonArray *a = json_object_get_array_member(obj, name); assert(json_array_get_length(a) < ARRAY_LENGTH(profile->report_rates)); for (size_t s = 0; s < json_array_get_length(a); s++) { int v = json_array_get_int_element(a, s); if (v < 0 || v > 20000) parser_error("report_rate"); profile->report_rates[s] = v; } log_verbose("json: report rates: %d %d %d %d %d\n", profile->report_rates[0], profile->report_rates[1], profile->report_rates[2], profile->report_rates[3], profile->report_rates[4]); } else if (streq(name, "capabilities")) { JsonArray *a = json_object_get_array_member(obj, name); assert(json_array_get_length(a) < ARRAY_LENGTH(profile->caps)); for (size_t s = 0; s < json_array_get_length(a); s++) { int v = json_array_get_int_element(a, s); profile->caps[s] = v; } log_verbose("json: caps: %d %d %d %d %d...\n", profile->caps[0], profile->caps[1], profile->caps[2], profile->caps[3], profile->caps[4]); } else if (streq(name, "resolutions")) { JsonArray *a = json_object_get_array_member(obj, name); GList *list = json_array_get_elements(a); GList *l = list; int idx = 0; while (l != NULL) { log_verbose("json: processing resolution %d\n", idx); parse_resolution(l->data, &profile->resolutions[idx]); l = g_list_next(l); idx++; } g_list_free(list); num_resolutions = max(idx, num_resolutions); } else if (streq(name, "leds")) { JsonArray *a = json_object_get_array_member(obj, name); GList *list = json_array_get_elements(a); GList *l = list; int idx = 0; while (l != NULL) { log_verbose("json: processing LED %d\n", idx); parse_led(l->data, &profile->leds[idx]); l = g_list_next(l); idx++; } g_list_free(list); num_leds = max(idx, num_leds); } else if (streq(name, "buttons")) { JsonArray *a = json_object_get_array_member(obj, name); GList *list = json_array_get_elements(a); GList *l = list; int idx = 0; while (l != NULL) { log_verbose("json: processing button %d\n", idx); parse_button(l->data, &profile->buttons[idx]); l = g_list_next(l); idx++; } g_list_free(list); num_buttons = max(idx, num_buttons); } else { log_error("json: unknown profile key '%s'\n", name); parse_error = -EINVAL; } out: return; } static void parse_profile(JsonNode *node, struct ratbag_test_profile *profile) { JsonObject *obj = json_node_get_object(node); json_object_foreach_member(obj, parse_profile_member, profile); } /* declared here because this isn't really public API, we just need to * access it from the tests */ int ratbagd_parse_json(const char *data, struct ratbag_test_device *device) { g_autoptr(JsonParser) parser = NULL; JsonNode *root; JsonObject *obj; JsonArray *arr; int r = -EINVAL; g_autoptr(GError) error = NULL; error = 0; parser = json_parser_new(); if (!json_parser_load_from_data(parser, data, strlen(data), &error)) { log_error("Failed to load JSON: %s\n", error->message); r = -EINVAL; goto out; } log_verbose("json: data: %s\n", data); root = json_parser_get_root(parser); if (JSON_NODE_TYPE(root) != JSON_NODE_OBJECT) parser_error("root"); obj = json_node_get_object(root); arr = json_object_get_array_member(obj, "profiles"); if (!arr) parser_error("profiles"); /* Our test device is preloaded with sane defaults, let's keep those */ num_resolutions = device->num_resolutions; num_buttons = device->num_buttons; num_leds = device->num_leds; g_autoptr(GList) list = json_array_get_elements(arr); GList *l = list; int idx = 0; while (l != NULL) { log_verbose("json: processing profile %d\n", idx); parse_profile(l->data, &device->profiles[idx]); if (parse_error != 0) goto out;; l = g_list_next(l); idx++; } device->num_profiles = idx; device->num_resolutions = num_resolutions; device->num_buttons = num_buttons; device->num_leds = num_leds; r = 0; out: return r; } libratbag-0.13/ratbagd/ratbagd-json.h000066400000000000000000000023711362011324700175160ustar00rootroot00000000000000/* * Copyright © 2019 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #pragma once #include "libratbag-test.h" int ratbagd_parse_json(const char *data, struct ratbag_test_device *device); libratbag-0.13/ratbagd/ratbagd-led.c000066400000000000000000000213401362011324700173010ustar00rootroot00000000000000/*** This file is part of ratbagd. Copyright 2017 Jente Hidskes . 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 (including the next paragraph) 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. ***/ #include "config.h" #include #include #include #include #include "ratbagd.h" #include "shared-macro.h" #include "libratbag-util.h" struct ratbagd_led { struct ratbag_led *lib_led; unsigned int index; char *path; enum ratbag_led_colordepth colordepth; }; static int ratbagd_led_get_modes(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_led *led = userdata; enum ratbag_led_mode mode = 0; CHECK_CALL(sd_bus_message_open_container(reply, 'a', "u")); while (mode <= RATBAG_LED_BREATHING) { if (ratbag_led_has_mode(led->lib_led, mode)) CHECK_CALL(sd_bus_message_append(reply, "u", mode)); mode++; } CHECK_CALL(sd_bus_message_close_container(reply)); return 0; } static int ratbagd_led_get_mode(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_led *led = userdata; enum ratbag_led_mode mode; mode = ratbag_led_get_mode(led->lib_led); verify_unsigned_int(mode); CHECK_CALL(sd_bus_message_append(reply, "u", mode)); return 0; } static int ratbagd_led_set_mode(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_led *led = userdata; enum ratbag_led_mode mode; int r; CHECK_CALL(sd_bus_message_read(m, "u", &mode)); r = ratbag_led_set_mode(led->lib_led, mode); if (r == 0) { sd_bus *bus = sd_bus_message_get_bus(m); sd_bus_emit_properties_changed(bus, led->path, RATBAGD_NAME_ROOT ".Led", "Mode", NULL); } return 0; } static int ratbagd_led_get_color(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_led *led = userdata; struct ratbag_color c; c = ratbag_led_get_color(led->lib_led); CHECK_CALL(sd_bus_message_append(reply, "(uuu)", c.red, c.green, c.blue)); return 0; } static int ratbagd_led_set_color(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_led *led = userdata; struct ratbag_color c; int r; CHECK_CALL(sd_bus_message_read(m, "(uuu)", &c.red, &c.green, &c.blue)); if (c.red > 255) c.red = 255; if (c.green > 255) c.green = 255; if (c.blue > 255) c.blue = 255; r = ratbag_led_set_color(led->lib_led, c); if (r == 0) { sd_bus *bus = sd_bus_message_get_bus(m); sd_bus_emit_properties_changed(bus, led->path, RATBAGD_NAME_ROOT ".Led", "Color", NULL); } return 0; } static int ratbagd_led_get_effect_duration(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_led *led = userdata; int rate; rate = ratbag_led_get_effect_duration(led->lib_led); verify_unsigned_int(rate); CHECK_CALL(sd_bus_message_append(reply, "u", rate)); return 0; } static int ratbagd_led_set_effect_duration(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_led *led = userdata; unsigned int rate; int r; CHECK_CALL(sd_bus_message_read(m, "u", &rate)); if (rate > 10000) rate = 10000; r = ratbag_led_set_effect_duration(led->lib_led, rate); if (r == 0) { sd_bus *bus = sd_bus_message_get_bus(m); sd_bus_emit_properties_changed(bus, led->path, RATBAGD_NAME_ROOT ".Led", "EffectDuration", NULL); } return 0; } static int ratbagd_led_get_brightness(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_led *led = userdata; unsigned int brightness; brightness = ratbag_led_get_brightness(led->lib_led); verify_unsigned_int(brightness); CHECK_CALL(sd_bus_message_append(reply, "u", brightness)); return 0; } static int ratbagd_led_set_brightness(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_led *led = userdata; unsigned int brightness; int r; CHECK_CALL(sd_bus_message_read(m, "u", &brightness)); if (brightness > 255) brightness = 255; r = ratbag_led_set_brightness(led->lib_led, brightness); if (r == 0) { sd_bus *bus = sd_bus_message_get_bus(m); sd_bus_emit_properties_changed(bus, led->path, RATBAGD_NAME_ROOT ".Led", "Brightness", NULL); } return 0; } const sd_bus_vtable ratbagd_led_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Index", "u", NULL, offsetof(struct ratbagd_led, index), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Modes", "au", ratbagd_led_get_modes, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_WRITABLE_PROPERTY("Mode", "u", ratbagd_led_get_mode, ratbagd_led_set_mode, 0, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_WRITABLE_PROPERTY("Color", "(uuu)", ratbagd_led_get_color, ratbagd_led_set_color, 0, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("ColorDepth", "u", NULL, offsetof(struct ratbagd_led, colordepth), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_WRITABLE_PROPERTY("EffectDuration", "u", ratbagd_led_get_effect_duration, ratbagd_led_set_effect_duration, 0, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_WRITABLE_PROPERTY("Brightness", "u", ratbagd_led_get_brightness, ratbagd_led_set_brightness, 0, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_VTABLE_END, }; int ratbagd_led_new(struct ratbagd_led **out, struct ratbagd_device *device, struct ratbagd_profile *profile, struct ratbag_led *lib_led, unsigned int index) { _cleanup_(ratbagd_led_freep) struct ratbagd_led *led = NULL; char profile_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1], led_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1]; int r; assert(out); assert(lib_led); led = zalloc(sizeof(*led)); led->lib_led = lib_led; led->index = index; led->colordepth = ratbag_led_get_colordepth(lib_led); sprintf(profile_buffer, "p%u", ratbagd_profile_get_index(profile)); sprintf(led_buffer, "l%u", index); r = sd_bus_path_encode_many(&led->path, RATBAGD_OBJ_ROOT "/led/%/%/%", ratbagd_device_get_sysname(device), profile_buffer, led_buffer); if (r < 0) return r; *out = led; led = NULL; return 0; } const char *ratbagd_led_get_path(struct ratbagd_led *led) { assert(led); return led->path; } struct ratbagd_led *ratbagd_led_free(struct ratbagd_led *led) { if (!led) return NULL; led->path = mfree(led->path); led->lib_led = ratbag_led_unref(led->lib_led); return mfree(led); } int ratbagd_led_resync(sd_bus *bus, struct ratbagd_led *led) { return sd_bus_emit_properties_changed(bus, led->path, RATBAGD_NAME_ROOT ".Led", "Mode", "Color", "EffectDuration", "Brightness", NULL); } libratbag-0.13/ratbagd/ratbagd-profile.c000066400000000000000000000563611362011324700202100ustar00rootroot00000000000000/*** This file is part of ratbagd. Copyright 2015 David Herrmann 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 (including the next paragraph) 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. ***/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "ratbagd.h" #include "libratbag-util.h" #include "shared-macro.h" #include "libratbag-util.h" struct ratbagd_profile { struct ratbagd_device *device; struct ratbag_profile *lib_profile; unsigned int index; char *path; sd_bus_slot *resolution_vtable_slot; sd_bus_slot *resolution_enum_slot; unsigned int n_resolutions; struct ratbagd_resolution **resolutions; sd_bus_slot *button_vtable_slot; sd_bus_slot *button_enum_slot; unsigned int n_buttons; struct ratbagd_button **buttons; sd_bus_slot *led_vtable_slot; sd_bus_slot *led_enum_slot; unsigned int n_leds; struct ratbagd_led **leds; }; static int ratbagd_profile_find_resolution(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { _cleanup_(freep) char *name = NULL; struct ratbagd_profile *profile = userdata; unsigned int index = 0; int r; r = sd_bus_path_decode_many(path, RATBAGD_OBJ_ROOT "/resolution/%/p%/r%", NULL, NULL, &name); if (r <= 0) return r; r = safe_atou(name, &index); if (r < 0) return 0; if (index >= profile->n_resolutions || !profile->resolutions[index]) return 0; *found = profile->resolutions[index]; return 1; } static int ratbagd_profile_get_resolutions(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; struct ratbagd_resolution *resolution; unsigned int i; CHECK_CALL(sd_bus_message_open_container(reply, 'a', "o")); for (i = 0; i < profile->n_resolutions; ++i) { resolution = profile->resolutions[i]; if (!resolution) continue; CHECK_CALL(sd_bus_message_append(reply, "o", ratbagd_resolution_get_path(resolution))); } CHECK_CALL(sd_bus_message_close_container(reply)); return 0; } static int ratbagd_profile_get_buttons(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; struct ratbagd_button *button; unsigned int i; CHECK_CALL(sd_bus_message_open_container(reply, 'a', "o")); for (i = 0; i < profile->n_buttons; ++i) { button = profile->buttons[i]; if (!button) continue; CHECK_CALL(sd_bus_message_append(reply, "o", ratbagd_button_get_path(button))); } CHECK_CALL(sd_bus_message_close_container(reply)); return 0; } static int ratbagd_profile_get_leds(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; struct ratbagd_led *led; unsigned int i; CHECK_CALL(sd_bus_message_open_container(reply, 'a', "o")); for (i = 0; i < profile->n_leds; ++i) { led = profile->leds[i]; if (!led) continue; CHECK_CALL(sd_bus_message_append(reply, "o", ratbagd_led_get_path(led))); } CHECK_CALL(sd_bus_message_close_container(reply)); return 0; } static int ratbagd_profile_is_active(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; int is_active; is_active = ratbag_profile_is_active(profile->lib_profile); CHECK_CALL(sd_bus_message_append(reply, "b", is_active)); return 0; } static int ratbagd_profile_find_button(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { _cleanup_(freep) char *name = NULL; struct ratbagd_profile *profile = userdata; unsigned int index = 0; int r; r = sd_bus_path_decode_many(path, RATBAGD_OBJ_ROOT "/button/%/p%/b%", NULL, NULL, &name); if (r <= 0) return r; r = safe_atou(name, &index); if (r < 0) return 0; if (index >= profile->n_buttons || !profile->buttons[index]) return 0; *found = profile->buttons[index]; return 1; } static int ratbagd_profile_find_led(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { _cleanup_(freep) char *name = NULL; struct ratbagd_profile *profile = userdata; unsigned int index = 0; int r; r = sd_bus_path_decode_many(path, RATBAGD_OBJ_ROOT "/led/%/p%/l%", NULL, NULL, &name); if (r <= 0) return r; r = safe_atou(name, &index); if (r < 0) return 0; if (index >= profile->n_leds || !profile->leds[index]) return 0; *found = profile->leds[index]; return 1; } static int ratbagd_profile_active_signal_cb(sd_bus *bus, struct ratbagd_profile *profile) { /* FIXME: we should cache is active and only send the signal for * those profiles where it changed */ (void) sd_bus_emit_properties_changed(bus, profile->path, RATBAGD_NAME_ROOT ".Profile", "IsActive", NULL); return 0; } static int ratbagd_profile_set_active(sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; int r; CHECK_CALL(sd_bus_message_read(m, "")); r = ratbag_profile_set_active(profile->lib_profile); if (r < 0) { sd_bus *bus = sd_bus_message_get_bus(m); r = ratbagd_device_resync(profile->device, bus); if (r < 0) return r; } ratbagd_for_each_profile_signal(sd_bus_message_get_bus(m), profile->device, ratbagd_profile_active_signal_cb); CHECK_CALL(sd_bus_reply_method_return(m, "u", 0)); return 0; } static int ratbagd_profile_set_enabled(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; int enabled; int r; CHECK_CALL(sd_bus_message_read(m, "b", &enabled)); r = ratbag_profile_set_enabled(profile->lib_profile, enabled); if (r == 0) { sd_bus *bus = sd_bus_message_get_bus(m); sd_bus_emit_properties_changed(bus, profile->path, RATBAGD_NAME_ROOT ".Profile", "Enabled", NULL); } return 0; } static int ratbagd_profile_is_enabled(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; int enabled = ratbag_profile_is_enabled(profile->lib_profile) != 0; CHECK_CALL(sd_bus_message_append(reply, "b", enabled)); return 0; } static int ratbagd_profile_set_name(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; char *name; int r; CHECK_CALL(sd_bus_message_read(m, "s", &name)); r = ratbag_profile_set_name(profile->lib_profile, name); if (r == 0) { sd_bus_emit_properties_changed(bus, profile->path, RATBAGD_NAME_ROOT ".Profile", "Name", NULL); } return 0; } static int ratbagd_profile_get_name(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; const char *name = ratbag_profile_get_name(profile->lib_profile); _cleanup_free_ char *utf8 = NULL; if (name) { if (u8_check((const uint8_t*)name, strlen(name)) == NULL) utf8 = strdup(name); else utf8 = (char*)u8_strconv_from_encoding(name, "ISO-8859-1", iconveh_question_mark); if (!utf8) utf8 = (char*)strdup_ascii_only(name); } else { utf8 = strdup(""); } CHECK_CALL(sd_bus_message_append(reply, "s", utf8)); return 0; } static int ratbagd_profile_get_capabilities(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; struct ratbag_profile *lib_profile = profile->lib_profile; enum ratbag_profile_capability cap; enum ratbag_profile_capability caps[] = { RATBAG_PROFILE_CAP_SET_DEFAULT, RATBAG_PROFILE_CAP_DISABLE, }; size_t i; CHECK_CALL(sd_bus_message_open_container(reply, 'a', "u")); for (i = 0; i < ELEMENTSOF(caps); i++) { cap = caps[i]; if (ratbag_profile_has_capability(lib_profile, cap)) { CHECK_CALL(sd_bus_message_append(reply, "u", cap)); } } CHECK_CALL(sd_bus_message_close_container(reply)); return 0; } static int ratbagd_profile_get_report_rate(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; struct ratbag_profile *lib_profile = profile->lib_profile; int rate; rate = ratbag_profile_get_report_rate(lib_profile); verify_unsigned_int(rate); return sd_bus_message_append(reply, "u", rate); } static int ratbagd_profile_get_report_rates(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; struct ratbag_profile *lib_profile = profile->lib_profile; unsigned int rates[8]; unsigned int nrates = ARRAY_LENGTH(rates); int r; r = sd_bus_message_open_container(reply, 'a', "u"); if (r < 0) return r; nrates = ratbag_profile_get_report_rate_list(lib_profile, rates, nrates); assert(nrates <= ARRAY_LENGTH(rates)); for (unsigned int i = 0; i < nrates; i++) { verify_unsigned_int(rates[i]); r = sd_bus_message_append(reply, "u", rates[i]); if (r < 0) return r; } return sd_bus_message_close_container(reply); } static int ratbagd_profile_set_report_rate(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; unsigned int rate; int r; r = sd_bus_message_read(m, "u", &rate); if (r < 0) return r; /* basic sanity check */ if (rate > 5000) rate = 5000; else if (rate % 100) rate = rate - (rate % 100); r = ratbag_profile_set_report_rate(profile->lib_profile, rate); if (r == 0) { sd_bus *bus = sd_bus_message_get_bus(m); sd_bus_emit_properties_changed(bus, profile->path, RATBAGD_NAME_ROOT ".Profile", "ReportRate", NULL); } return 0; } const sd_bus_vtable ratbagd_profile_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_WRITABLE_PROPERTY("Name", "s", ratbagd_profile_get_name, ratbagd_profile_set_name, 0, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_WRITABLE_PROPERTY("Enabled", "b", ratbagd_profile_is_enabled, ratbagd_profile_set_enabled, 0, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Index", "u", NULL, offsetof(struct ratbagd_profile, index), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Capabilities", "au", ratbagd_profile_get_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Resolutions", "ao", ratbagd_profile_get_resolutions, 0, 0), SD_BUS_PROPERTY("Buttons", "ao", ratbagd_profile_get_buttons, 0, 0), SD_BUS_PROPERTY("Leds", "ao", ratbagd_profile_get_leds, 0, 0), SD_BUS_PROPERTY("IsActive", "b", ratbagd_profile_is_active, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_WRITABLE_PROPERTY("ReportRate", "u", ratbagd_profile_get_report_rate, ratbagd_profile_set_report_rate, 0, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("ReportRates", "au", ratbagd_profile_get_report_rates, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_METHOD("SetActive", "", "u", ratbagd_profile_set_active, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END, }; int ratbagd_profile_new(struct ratbagd_profile **out, struct ratbagd_device *device, struct ratbag_profile *lib_profile, unsigned int index) { _cleanup_(ratbagd_profile_freep) struct ratbagd_profile *profile = NULL; struct ratbag_resolution *resolution; struct ratbag_button *button; struct ratbag_led *led; char index_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1]; unsigned int i; int r; assert(out); assert(lib_profile); profile = zalloc(sizeof(*profile)); profile->device = device; profile->lib_profile = lib_profile; profile->index = index; sprintf(index_buffer, "p%u", index); r = sd_bus_path_encode_many(&profile->path, RATBAGD_OBJ_ROOT "/profile/%/%", ratbagd_device_get_sysname(device), index_buffer); if (r < 0) return r; profile->n_resolutions = ratbag_profile_get_num_resolutions(profile->lib_profile); profile->resolutions = zalloc(profile->n_resolutions * sizeof(*profile->resolutions)); profile->n_buttons = ratbagd_device_get_num_buttons(device); profile->buttons = zalloc(profile->n_buttons * sizeof(*profile->buttons)); profile->n_leds = ratbagd_device_get_num_leds(device); profile->leds = zalloc(profile->n_leds * sizeof(*profile->leds)); for (i = 0; i < profile->n_resolutions; ++i) { resolution = ratbag_profile_get_resolution(profile->lib_profile, i); if (!resolution) continue; r = ratbagd_resolution_new(&profile->resolutions[i], device, profile, resolution, i); if (r < 0) { errno = -r; log_error("%s: failed to allocate resolution: %m\n", ratbagd_device_get_sysname(device)); } } for (i = 0; i < profile->n_buttons; ++i) { button = ratbag_profile_get_button(profile->lib_profile, i); if (!button) continue; r = ratbagd_button_new(&profile->buttons[i], device, profile, button, i); if (r < 0) { errno = -r; log_error("%s: failed to allocate button: %m\n", ratbagd_device_get_sysname(device)); } } for (i = 0; i < profile->n_leds; ++i) { led = ratbag_profile_get_led(profile->lib_profile, i); if (!led) continue; r = ratbagd_led_new(&profile->leds[i], device, profile, led, i); if (r < 0) { errno = -r; log_error("%s: failed to allocate led: %m\n", ratbagd_device_get_sysname(device)); } } *out = profile; profile = NULL; return 0; } struct ratbagd_profile *ratbagd_profile_free(struct ratbagd_profile *profile) { unsigned int i; if (!profile) return NULL; profile->resolution_vtable_slot = sd_bus_slot_unref(profile->resolution_vtable_slot); profile->resolution_enum_slot = sd_bus_slot_unref(profile->resolution_enum_slot); profile->button_vtable_slot = sd_bus_slot_unref(profile->button_vtable_slot); profile->button_enum_slot = sd_bus_slot_unref(profile->button_enum_slot); profile->led_vtable_slot = sd_bus_slot_unref(profile->led_vtable_slot); profile->led_enum_slot = sd_bus_slot_unref(profile->led_enum_slot); for (i = 0; i< profile->n_leds; ++i) ratbagd_led_free(profile->leds[i]); for (i = 0; i< profile->n_buttons; ++i) ratbagd_button_free(profile->buttons[i]); for (i = 0; i< profile->n_resolutions; ++i) ratbagd_resolution_free(profile->resolutions[i]); mfree(profile->leds); mfree(profile->buttons); mfree(profile->resolutions); profile->path = mfree(profile->path); profile->lib_profile = ratbag_profile_unref(profile->lib_profile); return mfree(profile); } const char *ratbagd_profile_get_path(struct ratbagd_profile *profile) { assert(profile); return profile->path; } unsigned int ratbagd_profile_get_index(struct ratbagd_profile *profile) { assert(profile); return profile->index; } static int ratbagd_profile_list_resolutions(sd_bus *bus, const char *path, void *userdata, char ***paths, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; struct ratbagd_resolution *resolution; char **resolutions; unsigned int i; resolutions = zalloc((profile->n_resolutions + 1) * sizeof(char *)); for (i = 0; i < profile->n_resolutions; ++i) { resolution = profile->resolutions[i]; if (!resolution) continue; resolutions[i] = strdup_safe(ratbagd_resolution_get_path(resolution)); } resolutions[i] = NULL; *paths = resolutions; return 1; } int ratbagd_profile_register_resolutions(struct sd_bus *bus, struct ratbagd_device *device, struct ratbagd_profile *profile) { _cleanup_(freep) char *prefix = NULL; char index_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1]; int r; sprintf(index_buffer, "p%u", profile->index); /* register resolution interfaces */ r = sd_bus_path_encode_many(&prefix, RATBAGD_OBJ_ROOT "/resolution/%/%", ratbagd_device_get_sysname(device), index_buffer); if (r >= 0) { r = sd_bus_add_fallback_vtable(bus, &profile->resolution_vtable_slot, prefix, RATBAGD_NAME_ROOT ".Resolution", ratbagd_resolution_vtable, ratbagd_profile_find_resolution, profile); if (r >= 0) r = sd_bus_add_node_enumerator(bus, &profile->resolution_enum_slot, prefix, ratbagd_profile_list_resolutions, profile); } if (r < 0) { errno = -r; log_error("%s: failed to register resolutions: %m\n", ratbagd_device_get_sysname(device)); } return 0; } static int ratbagd_profile_list_buttons(sd_bus *bus, const char *path, void *userdata, char ***paths, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; struct ratbagd_button *button; char **buttons; unsigned int i; buttons = zalloc((profile->n_buttons + 1) * sizeof(char *)); for (i = 0; i < profile->n_buttons; ++i) { button = profile->buttons[i]; if (!button) continue; buttons[i] = strdup_safe(ratbagd_button_get_path(button)); } buttons[i] = NULL; *paths = buttons; return 1; } int ratbagd_profile_register_buttons(struct sd_bus *bus, struct ratbagd_device *device, struct ratbagd_profile *profile) { _cleanup_(freep) char *prefix = NULL; char index_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1]; int r; sprintf(index_buffer, "p%u", profile->index); /* register button interfaces */ r = sd_bus_path_encode_many(&prefix, RATBAGD_OBJ_ROOT "/button/%/%", ratbagd_device_get_sysname(device), index_buffer); if (r >= 0) { r = sd_bus_add_fallback_vtable(bus, &profile->button_vtable_slot, prefix, RATBAGD_NAME_ROOT ".Button", ratbagd_button_vtable, ratbagd_profile_find_button, profile); if (r >= 0) r = sd_bus_add_node_enumerator(bus, &profile->button_enum_slot, prefix, ratbagd_profile_list_buttons, profile); } if (r < 0) { errno = -r; log_error("%s: failed to register buttons: %m\n", ratbagd_device_get_sysname(device)); } return 0; } static int ratbagd_profile_list_leds(sd_bus *bus, const char *path, void *userdata, char ***paths, sd_bus_error *error) { struct ratbagd_profile *profile = userdata; struct ratbagd_led *led; char **leds; unsigned int i; leds = zalloc((profile->n_leds + 1) * sizeof(char *)); for (i = 0; i < profile->n_leds; ++i) { led = profile->leds[i]; if (!led) continue; leds[i] = strdup_safe(ratbagd_led_get_path(led)); } leds[i] = NULL; *paths = leds; return 1; } int ratbagd_profile_register_leds(struct sd_bus *bus, struct ratbagd_device *device, struct ratbagd_profile *profile) { _cleanup_(freep) char *prefix = NULL; char index_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1]; int r; sprintf(index_buffer, "p%u", profile->index); /* register led interfaces */ r = sd_bus_path_encode_many(&prefix, RATBAGD_OBJ_ROOT "/led/%/%", ratbagd_device_get_sysname(device), index_buffer); if (r >= 0) { r = sd_bus_add_fallback_vtable(bus, &profile->led_vtable_slot, prefix, RATBAGD_NAME_ROOT ".Led", ratbagd_led_vtable, ratbagd_profile_find_led, profile); if (r >= 0) r = sd_bus_add_node_enumerator(bus, &profile->led_enum_slot, prefix, ratbagd_profile_list_leds, profile); } if (r < 0) { errno = -r; log_error("%s: failed to register leds: %m\n", ratbagd_device_get_sysname(device)); } return 0; } int ratbagd_for_each_resolution_signal(sd_bus *bus, struct ratbagd_profile *profile, int (*func)(sd_bus *bus, struct ratbagd_resolution *resolution)) { int rc = 0; for (size_t i = 0; rc == 0 && i < profile->n_resolutions; i++) rc = func(bus, profile->resolutions[i]); return rc; } int ratbagd_for_each_button_signal(sd_bus *bus, struct ratbagd_profile *profile, int (*func)(sd_bus *bus, struct ratbagd_button *button)) { int rc = 0; for (size_t i = 0; rc == 0 && i < profile->n_buttons; i++) rc = func(bus, profile->buttons[i]); return rc; } int ratbagd_for_each_led_signal(sd_bus *bus, struct ratbagd_profile *profile, int (*func)(sd_bus *bus, struct ratbagd_led *button)) { int rc = 0; for (size_t i = 0; rc == 0 && i < profile->n_leds; i++) rc = func(bus, profile->leds[i]); return rc; } int ratbagd_profile_resync(sd_bus *bus, struct ratbagd_profile *profile) { ratbagd_for_each_resolution_signal(bus, profile, ratbagd_resolution_resync); ratbagd_for_each_button_signal(bus, profile, ratbagd_button_resync); ratbagd_for_each_led_signal(bus, profile, ratbagd_led_resync); return sd_bus_emit_properties_changed(bus, profile->path, RATBAGD_NAME_ROOT ".Profile", "Resolutions", "Buttons", "Leds", "IsActive", NULL); } libratbag-0.13/ratbagd/ratbagd-resolution.c000066400000000000000000000232031362011324700207400ustar00rootroot00000000000000/*** This file is part of ratbagd. Copyright 2016 Red Hat, Inc. 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 (including the next paragraph) 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. ***/ #include "config.h" #include #include #include #include #include #include #include #include #include "ratbagd.h" #include "shared-macro.h" #include "libratbag-util.h" struct ratbagd_resolution { struct ratbagd_device *device; struct ratbagd_profile *profile; struct ratbag_resolution *lib_resolution; unsigned int index; char *path; }; int ratbagd_resolution_resync(sd_bus *bus, struct ratbagd_resolution *resolution) { return sd_bus_emit_properties_changed(bus, resolution->path, RATBAGD_NAME_ROOT ".Resolution", "IsDefault", "Resolution", "ReportRate", "IsActive", NULL); } static int ratbagd_resolution_active_signal_cb(sd_bus *bus, struct ratbagd_resolution *resolution) { /* FIXME: we should cache is_active and only send the signal for * those resolutions where it changed */ (void) sd_bus_emit_properties_changed(bus, resolution->path, RATBAGD_NAME_ROOT ".Resolution", "IsActive", NULL); return 0; } static int ratbagd_resolution_set_active(sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_resolution *resolution = userdata; int r; r = ratbag_resolution_set_active(resolution->lib_resolution); if (r < 0) { sd_bus *bus = sd_bus_message_get_bus(m); r = ratbagd_device_resync(resolution->device, bus); if (r < 0) return r; } ratbagd_for_each_resolution_signal(sd_bus_message_get_bus(m), resolution->profile, ratbagd_resolution_active_signal_cb); return sd_bus_reply_method_return(m, "u", 0); } static int ratbagd_resolution_default_signal_cb(sd_bus *bus, struct ratbagd_resolution *resolution) { /* FIXME: we should cache is default and only send the signal for * those resolutions where it changed */ (void) sd_bus_emit_properties_changed(bus, resolution->path, RATBAGD_NAME_ROOT ".Resolution", "IsDefault", NULL); return 0; } static int ratbagd_resolution_set_default(sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_resolution *resolution = userdata; int r; r = ratbag_resolution_set_default(resolution->lib_resolution); if (r < 0) { sd_bus *bus = sd_bus_message_get_bus(m); r = ratbagd_device_resync(resolution->device, bus); if (r < 0) return r; } ratbagd_for_each_resolution_signal(sd_bus_message_get_bus(m), resolution->profile, ratbagd_resolution_default_signal_cb); return sd_bus_reply_method_return(m, "u", 0); } static int ratbagd_resolution_is_active(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_resolution *resolution = userdata; struct ratbag_resolution *lib_resolution = resolution->lib_resolution; int is_active; is_active = ratbag_resolution_is_active(lib_resolution); return sd_bus_message_append(reply, "b", is_active); } static int ratbagd_resolution_is_default(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_resolution *resolution = userdata; struct ratbag_resolution *lib_resolution = resolution->lib_resolution; int is_default; is_default = ratbag_resolution_is_default(lib_resolution); return sd_bus_message_append(reply, "b", is_default); } static int ratbagd_resolution_get_resolutions(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_resolution *resolution = userdata; struct ratbag_resolution *lib_resolution = resolution->lib_resolution; unsigned int dpis[300]; unsigned int ndpis = ARRAY_LENGTH(dpis); int r; r = sd_bus_message_open_container(reply, 'a', "u"); if (r < 0) return r; ndpis = ratbag_resolution_get_dpi_list(lib_resolution, dpis, ndpis); assert(ndpis <= ARRAY_LENGTH(dpis)); for (unsigned int i = 0; i < ndpis; i++) { verify_unsigned_int(dpis[i]); r = sd_bus_message_append(reply, "u", dpis[i]); if (r < 0) return r; } return sd_bus_message_close_container(reply); } static int ratbagd_resolution_get_resolution(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd_resolution *resolution = userdata; struct ratbag_resolution *lib_resolution = resolution->lib_resolution; int xres, yres; xres = ratbag_resolution_get_dpi_x(lib_resolution); yres = ratbag_resolution_get_dpi_y(lib_resolution); verify_unsigned_int(xres); verify_unsigned_int(yres); if (ratbag_resolution_has_capability(lib_resolution, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION)) return sd_bus_message_append(reply, "v", "(uu)", xres, yres); else return sd_bus_message_append(reply, "v", "u", xres); } static int ratbagd_resolution_set_resolution(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd_resolution *resolution = userdata; struct ratbag_resolution *lib_resolution = resolution->lib_resolution; const enum ratbag_resolution_capability cap = RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION; int xres, yres; int r; if (ratbag_resolution_has_capability(lib_resolution, cap)) { CHECK_CALL(sd_bus_message_read(m, "v", "(uu)", &xres, &yres)); r = ratbag_resolution_set_dpi_xy(resolution->lib_resolution, xres, yres); } else { CHECK_CALL(sd_bus_message_read(m, "v", "u", &xres)); r = ratbag_resolution_set_dpi(resolution->lib_resolution, xres); } if (r == 0) { sd_bus *bus = sd_bus_message_get_bus(m); sd_bus_emit_properties_changed(bus, resolution->path, RATBAGD_NAME_ROOT ".Resolution", "Resolution", NULL); } return 0; } const sd_bus_vtable ratbagd_resolution_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Index", "u", NULL, offsetof(struct ratbagd_resolution, index), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("IsActive", "b", ratbagd_resolution_is_active, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("IsDefault", "b", ratbagd_resolution_is_default, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_WRITABLE_PROPERTY("Resolution", "v", ratbagd_resolution_get_resolution, ratbagd_resolution_set_resolution, 0, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Resolutions", "au", ratbagd_resolution_get_resolutions, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_METHOD("SetActive", "", "u", ratbagd_resolution_set_active, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("SetDefault", "", "u", ratbagd_resolution_set_default, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END, }; int ratbagd_resolution_new(struct ratbagd_resolution **out, struct ratbagd_device *device, struct ratbagd_profile *profile, struct ratbag_resolution *lib_resolution, unsigned int index) { _cleanup_(ratbagd_resolution_freep) struct ratbagd_resolution *resolution = NULL; char profile_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1], resolution_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1]; int r; assert(out); assert(lib_resolution); resolution = calloc(1, sizeof(*resolution)); if (!resolution) return -ENOMEM; resolution->device = device; resolution->profile = profile; resolution->lib_resolution = lib_resolution; resolution->index = index; sprintf(profile_buffer, "p%u", ratbagd_profile_get_index(profile)); sprintf(resolution_buffer, "r%u", index); r = sd_bus_path_encode_many(&resolution->path, RATBAGD_OBJ_ROOT "/resolution/%/%/%", ratbagd_device_get_sysname(device), profile_buffer, resolution_buffer); if (r < 0) return r; *out = resolution; resolution = NULL; return 0; } const char *ratbagd_resolution_get_path(struct ratbagd_resolution *resolution) { assert(resolution); return resolution->path; } struct ratbagd_resolution *ratbagd_resolution_free(struct ratbagd_resolution *resolution) { if (!resolution) return NULL; resolution->path = mfree(resolution->path); resolution->lib_resolution = ratbag_resolution_unref(resolution->lib_resolution); return mfree(resolution); } libratbag-0.13/ratbagd/ratbagd-test.c000066400000000000000000000073071362011324700175230ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include "ratbagd-test.h" #ifdef RATBAG_DEVELOPER_EDITION #include #include #include #include "libratbag-test.h" #include "ratbagd-json.h" static int load_test_device(sd_bus_message *m, struct ratbagd *ctx, const struct ratbag_test_device *source) { static int count; static struct ratbagd_device *ratbagd_test_device = NULL; struct ratbag_device *device; int r; char devicename[64]; if (ratbagd_test_device) { ratbagd_device_unlink(ratbagd_test_device); ratbagd_device_unref(ratbagd_test_device); (void) sd_bus_emit_properties_changed(ctx->bus, RATBAGD_OBJ_ROOT, RATBAGD_NAME_ROOT ".Manager", "Devices", NULL); } device = ratbag_device_new_test_device(ctx->lib_ctx, source); snprintf(devicename, sizeof(devicename), "testdevice%d", count++); r = ratbagd_device_new(&ratbagd_test_device, ctx, devicename, device); /* the ratbagd_device takes its own reference, drop ours */ ratbag_device_unref(device); if (r < 0) { log_error("Cannot track test device\n"); return r; } ratbagd_device_link(ratbagd_test_device); if (m) { sd_bus_reply_method_return(m, "u", r); (void) sd_bus_emit_properties_changed(ctx->bus, RATBAGD_OBJ_ROOT, RATBAGD_NAME_ROOT ".Manager", "Devices", NULL); } return 0; } static const struct ratbag_test_device default_device_descr = { .num_profiles = 1, .num_resolutions = 1, .num_buttons = 1, .num_leds = 0, .profiles = { { .name = NULL, .buttons = { { .button_type = RATBAG_BUTTON_TYPE_LEFT, .action_type = RATBAG_BUTTON_ACTION_TYPE_BUTTON, .button = 0 }, }, .resolutions = { { .xres = 1000, .yres = 1000, .dpi_min = 1000, .dpi_max = 1000}, }, .disabled = false, .active = true, .dflt = true, .hz = 1000, .report_rates = {1000}, }, }, .destroyed = NULL, .destroyed_data = NULL, }; int ratbagd_load_test_device(sd_bus_message *m, void *userdata, sd_bus_error *error) { struct ratbagd *ctx = userdata; struct ratbag_test_device td = default_device_descr; char *data; int r = 0; CHECK_CALL(sd_bus_message_read(m, "s", &data)); r = ratbagd_parse_json(data, &td); if (r != 0) { log_error("Failed to parse JSON data\n"); } else { r = load_test_device(m, ctx, &td); } return sd_bus_reply_method_return(m, "i", r); } #endif void ratbagd_init_test_device(struct ratbagd *ctx) { #ifdef RATBAG_DEVELOPER_EDITION setenv("RATBAG_TEST", "1", 0); load_test_device(NULL, ctx, &default_device_descr); #endif } libratbag-0.13/ratbagd/ratbagd-test.h000066400000000000000000000030131362011324700175160ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #pragma once #include "libratbag-test.h" #include "ratbagd.h" void ratbagd_init_test_device(struct ratbagd *ctx); #ifdef RATBAG_DEVELOPER_EDITION int ratbagd_reset_test_device(sd_bus_message *m, void *userdata, sd_bus_error *error); int ratbagd_load_test_device(sd_bus_message *m, void *userdata, sd_bus_error *error); #endif /* RATBAG_DEVELOPER_EDITION */ libratbag-0.13/ratbagd/ratbagd.8000066400000000000000000000016421362011324700164670ustar00rootroot00000000000000.TH ratbagd 8 "Apr 13, 2016" ratbagd .SH NAME ratbagd \- system daemon to introspect and modify configurable mice .SH SYNOPSIS .B ratbagd .RB [ \-\-verbose[=debug]|\-\-quiet|\-\-version|\-\-help] .SH DESCRIPTION .B ratbagd starts the daemon. It shouldn't be invoked directly; .B ratbagd is normally started through DBus activation. .SH OPTIONS .TP 8 .B \-\-help Print help and exit .TP 8 .B \-\-verbose, \-\-verbose=debug Enable verbose output, optionally including raw messages as sent and received from the device. This should be use to debug any issues with the device. .TP 8 .B \-\-quiet Disable any output but error messages. .TP 8 .B \-\-version Show the version number. .SH SEE ALSO .BR ratbagctl (1) .SH AUTHORS .B ratbagd was written by David Herrmann, Peter Hutterer and Benjamin Tissoires. .PP This manual page was written by Stephen Kitt for the Debian GNU/Linux system (but may be used by others). libratbag-0.13/ratbagd/ratbagd.c000066400000000000000000000367441362011324700165550ustar00rootroot00000000000000/*** This file is part of ratbagd. Copyright 2015 David Herrmann 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 (including the next paragraph) 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. ***/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "ratbagd.h" #include "shared-macro.h" #include "libratbag-util.h" #include "ratbagd-test.h" enum log_level { LL_QUIET = 1, LL_INFO, LL_VERBOSE, LL_RAW, } log_level = LL_INFO; void log_info(const char *fmt, ...) { va_list args; va_start(args, fmt); if (log_level >= LL_INFO) vprintf(fmt, args); va_end(args); } void log_verbose(const char *fmt, ...) { va_list args; va_start(args, fmt); if (log_level >= LL_VERBOSE) vprintf(fmt, args); va_end(args); } void log_error(const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "%s error: ", program_invocation_short_name); vfprintf(stderr, fmt, args); va_end(args); } static int ratbagd_find_device(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { _cleanup_(freep) char *name = NULL; struct ratbagd *ctx = userdata; struct ratbagd_device *device; int r; r = sd_bus_path_decode_many(path, RATBAGD_OBJ_ROOT "/device/%", &name); if (r <= 0) return r; device = ratbagd_device_lookup(ctx, name); if (!device) return 0; *found = device; return 1; } static int ratbagd_list_devices(sd_bus *bus, const char *path, void *userdata, char ***paths, sd_bus_error *error) { struct ratbagd *ctx = userdata; struct ratbagd_device *device; char **devices, **pos; devices = calloc(ctx->n_devices + 1, sizeof(char *)); if (!devices) return -ENOMEM; pos = devices; RATBAGD_DEVICE_FOREACH(device, ctx) { *pos = strdup(ratbagd_device_get_path(device)); if (!*pos) goto error; ++pos; } *pos = NULL; *paths = devices; return 1; error: for (pos = devices; *pos; ++pos) free(*pos); free(devices); return -ENOMEM; } static int ratbagd_get_devices(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { struct ratbagd *ctx = userdata; struct ratbagd_device *device; CHECK_CALL(sd_bus_message_open_container(reply, 'a', "o")); RATBAGD_DEVICE_FOREACH(device, ctx) { CHECK_CALL(sd_bus_message_append(reply, "o", ratbagd_device_get_path(device))); } CHECK_CALL(sd_bus_message_close_container(reply)); return 0; } static const sd_bus_vtable ratbagd_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("APIVersion", "i", 0, offsetof(struct ratbagd, api_version), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Devices", "ao", ratbagd_get_devices, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), #ifdef RATBAG_DEVELOPER_EDITION SD_BUS_METHOD("LoadTestDevice", "s", "i", ratbagd_load_test_device, SD_BUS_VTABLE_UNPRIVILEGED), #endif /* RATBAG_DEVELOPER_EDITION */ SD_BUS_VTABLE_END, }; static void ratbagd_process_device(struct ratbagd *ctx, struct udev_device *udevice) { struct ratbag_device *lib_device; struct ratbagd_device *device; const char *sysname; int r; /* * TODO: libratbag should provide some mechanism to allow * device-grouping, just like libinput does. If multiple input * devices belong to the same virtual device, we should not add * it multiple times. Instead, libratbag should group them and * provide *us* a unique sysname that identifies the group, rather * than taking a random input-device as tag. */ sysname = udev_device_get_sysname(udevice); if (!sysname || !startswith(sysname, "hidraw")) return; device = ratbagd_device_lookup(ctx, sysname); if (streq_ptr("remove", udev_device_get_action(udevice))) { /* device was removed, unlink it and destroy our context */ if (device) { ratbagd_device_unlink(device); ratbagd_device_unref(device); (void) sd_bus_emit_properties_changed(ctx->bus, RATBAGD_OBJ_ROOT, RATBAGD_NAME_ROOT ".Manager", "Devices", NULL); } } else if (device) { /* device already known, refresh our view of the device */ } else { enum ratbag_error_code error; /* device unknown, create new one and link it */ error = ratbag_device_new_from_udev_device(ctx->lib_ctx, udevice, &lib_device); if (error != RATBAG_SUCCESS) return; /* unsupported device */ r = ratbagd_device_new(&device, ctx, sysname, lib_device); /* the ratbagd_device takes its own reference, drop ours */ ratbag_device_unref(lib_device); if (r < 0) { log_error("%s: cannot track device\n", sysname); return; } ratbagd_device_link(device); (void) sd_bus_emit_properties_changed(ctx->bus, RATBAGD_OBJ_ROOT, RATBAGD_NAME_ROOT ".Manager", "Devices", NULL); } } static int ratbagd_monitor_event(sd_event_source *source, int fd, uint32_t mask, void *userdata) { struct ratbagd *ctx = userdata; struct udev_device *udevice; udevice = udev_monitor_receive_device(ctx->monitor); if (!udevice) return 0; ratbagd_process_device(ctx, udevice); udev_device_unref(udevice); return 0; } static int ratbagd_lib_open_restricted(const char *path, int flags, void *userdata) { return open(path, flags, 0); } static void ratbagd_lib_close_restricted(int fd, void *userdata) { safe_close(fd); } static const struct ratbag_interface ratbagd_lib_interface = { .open_restricted = ratbagd_lib_open_restricted, .close_restricted = ratbagd_lib_close_restricted, }; static struct ratbagd *ratbagd_free(struct ratbagd *ctx) { struct ratbagd_device *device, *tmp; if (!ctx) return NULL; RATBAGD_DEVICE_FOREACH_SAFE(device, tmp, ctx) { ratbagd_device_unlink(device); ratbagd_device_unref(device); } ctx->bus = sd_bus_flush_close_unref(ctx->bus); ctx->monitor_source = sd_event_source_unref(ctx->monitor_source); ctx->monitor = udev_monitor_unref(ctx->monitor); ctx->lib_ctx = ratbag_unref(ctx->lib_ctx); ctx->event = sd_event_unref(ctx->event); assert(!ctx->device_map.root); assert(!ctx->lib_ctx); /* ratbag returns non-NULL if still pinned */ return mfree(ctx); } DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd *, ratbagd_free); static int ratbagd_init_monitor(struct ratbagd *ctx) { struct udev *udev; int r; udev = udev_new(); if (!udev) return -ENOMEM; ctx->monitor = udev_monitor_new_from_netlink(udev, "udev"); /* we don't need the context to stay around; drop it */ udev_unref(udev); if (!ctx->monitor) return -ENOMEM; r = udev_monitor_filter_add_match_subsystem_devtype(ctx->monitor, "hidraw", NULL); if (r < 0) return r; r = udev_monitor_enable_receiving(ctx->monitor); if (r < 0) return r; r = sd_event_add_io(ctx->event, &ctx->monitor_source, udev_monitor_get_fd(ctx->monitor), EPOLLIN, ratbagd_monitor_event, ctx); if (r < 0) return r; return 0; } static int ratbagd_new(struct ratbagd **out) { _cleanup_(ratbagd_freep) struct ratbagd *ctx = NULL; int r; ctx = zalloc(sizeof(*ctx)); ctx->api_version = RATBAGD_API_VERSION; r = sd_event_default(&ctx->event); if (r < 0) return r; r = sd_event_set_watchdog(ctx->event, true); if (r < 0) return r; log_verbose("Initializing libratbag\n"); ctx->lib_ctx = ratbag_create_context(&ratbagd_lib_interface, ctx); if (!ctx->lib_ctx) return -ENOMEM; if (log_level >= LL_RAW) ratbag_log_set_priority(ctx->lib_ctx, RATBAG_LOG_PRIORITY_RAW); else if (log_level >= LL_VERBOSE) ratbag_log_set_priority(ctx->lib_ctx, RATBAG_LOG_PRIORITY_DEBUG); r = ratbagd_init_monitor(ctx); if (r < 0) return r; r = sd_bus_open_system(&ctx->bus); if (r < 0) return r; r = sd_bus_add_object_vtable(ctx->bus, NULL, RATBAGD_OBJ_ROOT, RATBAGD_NAME_ROOT ".Manager", ratbagd_vtable, ctx); if (r < 0) return r; r = sd_bus_add_fallback_vtable(ctx->bus, NULL, RATBAGD_OBJ_ROOT "/device", RATBAGD_NAME_ROOT ".Device", ratbagd_device_vtable, ratbagd_find_device, ctx); if (r < 0) return r; r = sd_bus_add_node_enumerator(ctx->bus, NULL, RATBAGD_OBJ_ROOT "/device", ratbagd_list_devices, ctx); if (r < 0) return r; r = sd_bus_request_name(ctx->bus, RATBAGD_NAME_ROOT, 0); if (r < 0) return r; r = sd_bus_attach_event(ctx->bus, ctx->event, 0); if (r < 0) return r; *out = ctx; ctx = NULL; return 0; } static int ratbagd_run_enumerate(struct ratbagd *ctx) { struct udev_list_entry *list, *iter; struct udev_enumerate *e; struct udev *udev; int r; udev = udev_monitor_get_udev(ctx->monitor); e = udev_enumerate_new(udev); if (!e) return -ENOMEM; r = udev_enumerate_add_match_subsystem(e, "hidraw"); if (r < 0) goto exit; r = udev_enumerate_add_match_is_initialized(e); if (r < 0) goto exit; r = udev_enumerate_scan_devices(e); if (r < 0) goto exit; list = udev_enumerate_get_list_entry(e); udev_list_entry_foreach(iter, list) { struct udev_device *udevice; const char *p; p = udev_list_entry_get_name(iter); udevice = udev_device_new_from_syspath(udev, p); if (udevice) ratbagd_process_device(ctx, udevice); udev_device_unref(udevice); } r = 0; exit: udev_enumerate_unref(e); return r; } static int on_timeout_cb(sd_event_source *s, uint64_t usec, void *userdata) { log_info("Exiting after idle\n"); sd_event_exit(sd_event_source_get_event(s), 0); return 0; } static int before_idle_cb(sd_event_source *s, void *userdata) { struct ratbagd *ctx = userdata; uint64_t usec; sd_event_now(sd_event_source_get_event(s), CLOCK_MONOTONIC, &usec); #define min2us(us_) (us_ * 1000000 * 60) usec += min2us(20); sd_event_source_set_time(ctx->timeout_source, usec); return 0; } static int sighandler(sd_event_source *source, const struct signalfd_siginfo *si, void *userdata) { sd_event *event = sd_event_source_get_event(source); sd_event_exit(event, 0); return 0; } static int ratbagd_run(struct ratbagd *ctx) { int r; r = ratbagd_run_enumerate(ctx); if (r < 0) return r; sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGINT); sigprocmask(SIG_BLOCK, &sigset, NULL); sd_event_add_signal(ctx->event, NULL, SIGINT, sighandler, NULL); /* exit-on-idle: we set up a timer to simply exit. Since we don't * store anything, it doesn't matter and we can just restart next * time someone wants us. * * since we don't want to monitor every single dbus call, we just * set up a post source that gets called before we go idle. That * resets the timer, so as long as something is happening, ratbagd * won't exit. */ sd_event_add_time(ctx->event, &ctx->timeout_source, CLOCK_MONOTONIC, -1, /* infinite, see before_idle_cb */ min2us(1), /* accuracy doesn't matter */ on_timeout_cb, ctx); sd_event_add_post(ctx->event, NULL, before_idle_cb, ctx); log_verbose("DBus server ready\n"); return sd_event_loop(ctx->event); } static bool install_ratbagd_devel_dbus_policy(void) { bool rc = true; #ifdef RATBAG_DEVELOPER_EDITION _cleanup_close_ int in = -1, out = -1; int nread, nwrite; char buf[40960]; int r; sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus_message *m = NULL; sd_bus *bus = NULL; rc = false; log_verbose("Installing DBus policy file to %s\n", DBUS_POLICY_DST); in = open(DBUS_POLICY_SRC, O_RDONLY); if (in == -1) { log_error("Failed to source policy file: %m\n"); goto out; } out = open(DBUS_POLICY_DST, O_CREAT|O_WRONLY, 0644); if (out == -1) { log_error("Failed to open destination: %m\n"); goto out; } nread = read(in, buf, sizeof(buf)); if (nread < 0) { log_error("Failed to read policy file: %m\n"); goto out; } nwrite = write(out, buf, nread); if (nread != nwrite) { log_error("Failed to write policy file: %m\n"); goto out; } /* Now poke DBus to reload itself */ r = sd_bus_open_system(&bus); if (r < 0) { log_error("Unable to open system bus: %s\n", strerror(-r)); goto out; } r = sd_bus_call_method(bus, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ReloadConfig", &error, &m, ""); if (r < 0) { log_error("Failed to call DBus ReloadConfig: %s\n", error.message); goto out; } rc = true; out: sd_bus_error_free(&error); sd_bus_message_unref(m); sd_bus_unref(bus); #endif return rc; } static void remove_ratbagd_devel_dbus_policy(void) { #ifdef RATBAG_DEVELOPER_EDITION unlink(DBUS_POLICY_DST); #endif } int main(int argc, char *argv[]) { struct ratbagd *ctx = NULL; int r; #if DISABLE_COREDUMP const struct rlimit corelimit = { 0, 0 }; setrlimit(RLIMIT_CORE, &corelimit); #endif if (argc > 1) { if (streq(argv[1], "--version")) { printf("%s\n", RATBAG_VERSION); return 0; } else if (streq(argv[1], "--quiet")) { log_level = LL_QUIET; } else if (streq(argv[1], "--verbose") || streq(argv[1], "--verbose=raw")) { log_level = LL_RAW; } else if (streq(argv[1], "--verbose=debug")) { log_level = LL_VERBOSE; } else { fprintf(stderr, "Usage: %s [--version | --quiet | --verbose[=debug]]\n", program_invocation_short_name); r = -EINVAL; goto exit; } } if (!install_ratbagd_devel_dbus_policy()) goto exit; r = ratbagd_new(&ctx); if (r < 0) goto exit; ratbagd_init_test_device(ctx); r = ratbagd_run(ctx); remove_ratbagd_devel_dbus_policy(); exit: ratbagd_free(ctx); if (r < 0) { if (r == -EEXIST) { log_error("Bus name is taken, another instance of ratbagd is already running.\n"); } else { errno = -r; log_error("Failed to start ratbagd: %m\n"); } return EXIT_FAILURE; } return EXIT_SUCCESS; } struct ratbagd_callback { ratbagd_callback_t callback; void *userdata; }; static int ratbagd_callback_handler(sd_event_source *s, void *userdata) { struct ratbagd_callback *cb = userdata; cb->callback(cb->userdata); sd_event_source_set_enabled(s, SD_EVENT_OFF); sd_event_source_unref(s); free(cb); return 0; } void ratbagd_schedule_task(struct ratbagd *ctx, ratbagd_callback_t callback, void *userdata) { struct ratbagd_callback *cb = zalloc(sizeof *cb); sd_event_source *source; cb->callback = callback; cb->userdata = userdata; sd_event_add_post(ctx->event, &source, ratbagd_callback_handler, cb); } libratbag-0.13/ratbagd/ratbagd.h000066400000000000000000000200471362011324700165470ustar00rootroot00000000000000#pragma once /*** This file is part of ratbagd. Copyright 2015 David Herrmann 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 (including the next paragraph) 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. ***/ #include "config.h" #include #include #include #include #include #include #include #include #include "shared-macro.h" #include "shared-rbtree.h" #ifndef RATBAG_DBUS_INTERFACE #define RATBAG_DBUS_INTERFACE "ratbag1" #else #define RATBAG_DEVELOPER_EDITION #endif #define RATBAGD_OBJ_ROOT "/org/freedesktop/" RATBAG_DBUS_INTERFACE #define RATBAGD_NAME_ROOT "org.freedesktop." RATBAG_DBUS_INTERFACE struct ratbagd; struct ratbagd_device; struct ratbagd_profile; struct ratbagd_resolution; struct ratbagd_button; struct ratbagd_led; void log_info(const char *fmt, ...) _printf_(1, 2); void log_verbose(const char *fmt, ...) _printf_(1, 2); void log_error(const char *fmt, ...) _printf_(1, 2); #define CHECK_CALL(_call) \ do { \ int _r = _call; \ if (_r < 0) { \ log_error("%s: '%s' failed with: %s\n", __func__, #_call, strerror(-_r)); \ return _r; \ } \ } while (0) /* * Profiles */ extern const sd_bus_vtable ratbagd_profile_vtable[]; int ratbagd_profile_new(struct ratbagd_profile **out, struct ratbagd_device *device, struct ratbag_profile *lib_profile, unsigned int index); struct ratbagd_profile *ratbagd_profile_free(struct ratbagd_profile *profile); const char *ratbagd_profile_get_path(struct ratbagd_profile *profile); bool ratbagd_profile_is_default(struct ratbagd_profile *profile); unsigned int ratbagd_profile_get_index(struct ratbagd_profile *profile); int ratbagd_profile_register_resolutions(struct sd_bus *bus, struct ratbagd_device *device, struct ratbagd_profile *profile); int ratbagd_profile_register_buttons(struct sd_bus *bus, struct ratbagd_device *device, struct ratbagd_profile *profile); int ratbagd_profile_register_leds(struct sd_bus *bus, struct ratbagd_device *device, struct ratbagd_profile *profile); int ratbagd_for_each_profile_signal(sd_bus *bus, struct ratbagd_device *device, int (*func)(sd_bus *bus, struct ratbagd_profile *profile)); int ratbagd_for_each_resolution_signal(sd_bus *bus, struct ratbagd_profile *profile, int (*func)(sd_bus *bus, struct ratbagd_resolution *resolution)); int ratbagd_for_each_button_signal(sd_bus *bus, struct ratbagd_profile *profile, int (*func)(sd_bus *bus, struct ratbagd_button *button)); int ratbagd_for_each_led_signal(sd_bus *bus, struct ratbagd_profile *profile, int (*func)(sd_bus *bus, struct ratbagd_led *led)); int ratbagd_profile_resync(sd_bus *bus, struct ratbagd_profile *profile); DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd_profile *, ratbagd_profile_free); /* * Resolutions */ extern const sd_bus_vtable ratbagd_resolution_vtable[]; int ratbagd_resolution_new(struct ratbagd_resolution **out, struct ratbagd_device *device, struct ratbagd_profile *profile, struct ratbag_resolution *lib_resolution, unsigned int index); struct ratbagd_resolution *ratbagd_resolution_free(struct ratbagd_resolution *resolution); const char *ratbagd_resolution_get_path(struct ratbagd_resolution *resolution); int ratbagd_resolution_resync(sd_bus *bus, struct ratbagd_resolution *resolution); DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd_resolution *, ratbagd_resolution_free); /* * Buttons */ extern const sd_bus_vtable ratbagd_button_vtable[]; int ratbagd_button_new(struct ratbagd_button **out, struct ratbagd_device *device, struct ratbagd_profile *profile, struct ratbag_button *lib_button, unsigned int index); struct ratbagd_button *ratbagd_button_free(struct ratbagd_button *button); const char *ratbagd_button_get_path(struct ratbagd_button *button); int ratbagd_button_resync(sd_bus *bus, struct ratbagd_button *button); DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd_button *, ratbagd_button_free); /* * Leds */ extern const sd_bus_vtable ratbagd_led_vtable[]; int ratbagd_led_new(struct ratbagd_led **out, struct ratbagd_device *device, struct ratbagd_profile *profile, struct ratbag_led *lib_led, unsigned int index); struct ratbagd_led *ratbagd_led_free(struct ratbagd_led *led); const char *ratbagd_led_get_path(struct ratbagd_led *led); int ratbagd_led_resync(sd_bus *bus, struct ratbagd_led *led); DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd_led *, ratbagd_led_free); /* * Devices */ extern const sd_bus_vtable ratbagd_device_vtable[]; int ratbagd_device_new(struct ratbagd_device **out, struct ratbagd *ctx, const char *sysname, struct ratbag_device *lib_device); struct ratbagd_device *ratbagd_device_ref(struct ratbagd_device *device); struct ratbagd_device *ratbagd_device_unref(struct ratbagd_device *device); const char *ratbagd_device_get_sysname(struct ratbagd_device *device); const char *ratbagd_device_get_path(struct ratbagd_device *device); unsigned int ratbagd_device_get_num_buttons(struct ratbagd_device *device); unsigned int ratbagd_device_get_num_leds(struct ratbagd_device *device); int ratbagd_device_resync(struct ratbagd_device *device, sd_bus *bus); bool ratbagd_device_linked(struct ratbagd_device *device); void ratbagd_device_link(struct ratbagd_device *device); void ratbagd_device_unlink(struct ratbagd_device *device); DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd_device *, ratbagd_device_unref); struct ratbagd_device *ratbagd_device_lookup(struct ratbagd *ctx, const char *name); struct ratbagd_device *ratbagd_device_first(struct ratbagd *ctx); struct ratbagd_device *ratbagd_device_next(struct ratbagd_device *device); #define RATBAGD_DEVICE_FOREACH(_device, _ctx) \ for ((_device) = ratbagd_device_first(_ctx); \ (_device); \ (_device) = ratbagd_device_next(_device)) #define RATBAGD_DEVICE_FOREACH_SAFE(_device, _safe, _ctx) \ for (_device = ratbagd_device_first(_ctx), \ _safe = (_device) ? ratbagd_device_next(_device) : NULL; \ (_device); \ _device = (_safe), \ _safe = (_safe) ? ratbagd_device_next(_safe) : NULL) /* Verify that _val is not -1. This traps DBus API errors where we end up * sending a valid-looking index across and then fail on the other side. * * do {} while(0) so we can terminate with a ; without the compiler * complaining about an empty statement; * */ #define verify_unsigned_int(_val) \ do { if ((int)_val == -1) { \ log_error("%s:%d - %s: expected unsigned int, got -1\n", __FILE__, __LINE__, __func__); \ return -EINVAL; \ } } while(0) /* * Context */ struct ratbagd { int api_version; sd_event *event; struct ratbag *lib_ctx; struct udev_monitor *monitor; sd_event_source *timeout_source; sd_event_source *monitor_source; sd_bus *bus; RBTree device_map; size_t n_devices; const char **themes; /* NULL-terminated */ }; typedef void (*ratbagd_callback_t)(void *userdata); void ratbagd_schedule_task(struct ratbagd *ctx, ratbagd_callback_t callback, void *userdata); libratbag-0.13/ratbagd/ratbagd.service.in000066400000000000000000000003341362011324700203620ustar00rootroot00000000000000[Unit] Description=Daemon to introspect and modify configurable mice [Service] Type=dbus BusName=org.freedesktop.ratbag1 ExecStart=@bindir@/ratbagd Restart=on-abort [Install] Alias=dbus-org.freedesktop.ratbag1.service libratbag-0.13/release.sh000077500000000000000000000337571362011324700153610ustar00rootroot00000000000000#!/bin/bash # # Generate the announce template # # Completely copy/paste of Xorg/util/modular/release.sh: # # Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved. # # 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 (including the next # paragraph) 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. export LC_ALL=C #------------------------------------------------------------------------------ # Function: check_local_changes #------------------------------------------------------------------------------ # check_local_changes() { git diff --quiet HEAD > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "" echo "Uncommitted changes found. Did you forget to commit? Aborting." echo "" echo "You can perform a 'git stash' to save your local changes and" echo "a 'git stash apply' to recover them after the tarball release." echo "Make sure to rebuild and run 'make distcheck' again." echo "" echo "Alternatively, you can clone the module in another directory" echo "and run ./configure. No need to build if testing was finished." echo "" return 1 fi return 0 } #------------------------------------------------------------------------------ # Function: check_option_args #------------------------------------------------------------------------------ # # perform sanity checks on cmdline args which require arguments # arguments: # $1 - the option being examined # $2 - the argument to the option # returns: # if it returns, everything is good # otherwise it exit's check_option_args() { option=$1 arg=$2 # check for an argument if [ x"$arg" = x ]; then echo "" echo "Error: the '$option' option is missing its required argument." echo "" usage exit 1 fi # does the argument look like an option? echo $arg | $GREP "^-" > /dev/null if [ $? -eq 0 ]; then echo "" echo "Error: the argument '$arg' of option '$option' looks like an option itself." echo "" usage exit 1 fi } #------------------------------------------------------------------------------ # Function: check_modules_specification #------------------------------------------------------------------------------ # check_modules_specification() { if [ x"${INPUT_MODULES}" = x ]; then echo "" echo "Error: no modules specified (blank command line)." usage exit 1 fi } #------------------------------------------------------------------------------ # Function: generate_announce #------------------------------------------------------------------------------ # generate_announce() { cat < $targz # Obtain the top commit SHA which should be the version bump # It should not have been tagged yet (the script will do it later) local_top_commit_sha=`git rev-list --max-count=1 HEAD` if [ $? -ne 0 ]; then echo "Error: unable to obtain the local top commit id." cd $top_src return 1 fi # Check that the top commit looks like a version bump git diff --unified=0 HEAD^ | $GREP -F $pkg_version >/dev/null 2>&1 if [ $? -ne 0 ]; then # Wayland repos use m4_define([wayland_major_version], [0]) git diff --unified=0 HEAD^ | $GREP -E "(major|minor|micro)_version" >/dev/null 2>&1 if [ $? -ne 0 ]; then echo "Error: the local top commit does not look like a version bump." echo " the diff does not contain the string \"$pkg_version\"." local_top_commit_descr=`git log --oneline --max-count=1 $local_top_commit_sha` echo " the local top commit is: \"$local_top_commit_descr\"" cd $top_src return 1 fi fi # Check that the top commit has been pushed to remote remote_top_commit_sha=`git rev-list --max-count=1 $remote_name/$remote_branch` if [ $? -ne 0 ]; then echo "Error: unable to obtain top commit from the remote repository." cd $top_src return 1 fi if [ x"$remote_top_commit_sha" != x"$local_top_commit_sha" ]; then echo "Error: the local top commit has not been pushed to the remote." local_top_commit_descr=`git log --oneline --max-count=1 $local_top_commit_sha` echo " the local top commit is: \"$local_top_commit_descr\"" cd $top_src return 1 fi # If a tag exists with the the tar name, ensure it is tagging the top commit # It may happen if the version set in configure.ac has been previously released tagged_commit_sha=`git rev-list --max-count=1 $tag_name 2>/dev/null` if [ $? -eq 0 ]; then # Check if the tag is pointing to the top commit if [ x"$tagged_commit_sha" != x"$remote_top_commit_sha" ]; then echo "Error: the \"$tag_name\" already exists." echo " this tag is not tagging the top commit." remote_top_commit_descr=`git log --oneline --max-count=1 $remote_top_commit_sha` echo " the top commit is: \"$remote_top_commit_descr\"" local_tag_commit_descr=`git log --oneline --max-count=1 $tagged_commit_sha` echo " tag \"$tag_name\" is tagging some other commit: \"$local_tag_commit_descr\"" cd $top_src return 1 else echo "Info: module already tagged with \"$tag_name\"." fi else # Tag the top commit with the tar name if [ x"$DRY_RUN" = x ]; then git tag -s -m $tag_name $tag_name if [ $? -ne 0 ]; then echo "Error: unable to tag module with \"$tag_name\"." cd $top_src return 1 else echo "Info: module tagged with \"$tag_name\"." fi else echo "Info: skipping the commit tagging in dry-run mode." fi fi # Mailing lists where to post the all [Announce] e-mails list_to="input-tools@lists.freedesktop.org" # Pushing the top commit tag to the remote repository if [ x$DRY_RUN = x ]; then echo "Info: pushing tag \"$tag_name\" to remote \"$remote_name\":" git push $remote_name $tag_name if [ $? -ne 0 ]; then echo "Error: unable to push tag \"$tag_name\" to the remote repository." echo " it is recommended you fix this manually and not run the script again" cd $top_src return 1 fi else echo "Info: skipped pushing tag \"$tag_name\" to the remote repository in dry-run mode." fi # --------- Generate the announce e-mail ------------------ # Failing to generate the announce is not considered a fatal error # Git-describe returns only "the most recent tag", it may not be the expected one # However, we only use it for the commit history which will be the same anyway. tag_previous=`git describe --abbrev=0 HEAD^ 2>/dev/null` # Git fails with rc=128 if no tags can be found prior to HEAD^ if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then echo "Warning: unable to find a previous tag." echo " perhaps a first release on this branch." echo " Please check the commit history in the announce." fi fi if [ x"$tag_previous" != x ]; then # The top commit may not have been tagged in dry-run mode. Use commit. tag_range=$tag_previous..$local_top_commit_sha else tag_range=$tag_name fi generate_announce > "$tar_name.announce" echo "Info: [ANNOUNCE] template generated in \"$tar_name.announce\" file." echo " Please pgp sign and send it." # --------- Successful completion -------------------------- cd $top_src return 0 } #------------------------------------------------------------------------------ # Function: usage #------------------------------------------------------------------------------ # Displays the script usage and exits successfully # usage() { basename="`expr "//$0" : '.*/\([^/]*\)'`" cat < #include #include #include #include #include #include #include "libratbag-private.h" #include "libratbag-hidraw.h" #define ETEKCITY_PROFILE_MAX 4 #define ETEKCITY_BUTTON_MAX 10 #define ETEKCITY_NUM_DPI 6 #define ETEKCITY_LED 0 #define ETEKCITY_REPORT_ID_CONFIGURE_PROFILE 4 #define ETEKCITY_REPORT_ID_PROFILE 5 #define ETEKCITY_REPORT_ID_SETTINGS 6 #define ETEKCITY_REPORT_ID_KEY_MAPPING 7 #define ETEKCITY_REPORT_ID_SPEED_SETTING 8 #define ETEKCITY_REPORT_ID_MACRO 9 #define ETEKCITY_REPORT_SIZE_PROFILE 50 #define ETEKCITY_REPORT_SIZE_SETTINGS 40 #define ETEKCITY_REPORT_SIZE_SPEED_SETTING 6 #define ETEKCITY_REPORT_SIZE_MACRO 130 #define ETEKCITY_CONFIG_SETTINGS 0x10 #define ETEKCITY_CONFIG_KEY_MAPPING 0x20 #define ETEKCITY_MAX_MACRO_LENGTH 50 struct etekcity_settings_report { uint8_t reportID; uint8_t twentyHeight; uint8_t profileID; uint8_t x_sensitivity; /* 0x0a means 0 */ uint8_t y_sensitivity; /* 0x0a means 0 */ uint8_t dpi_mask; uint8_t xres[6]; uint8_t yres[6]; uint8_t current_dpi; uint8_t padding1[7]; uint8_t report_rate; uint8_t padding2[4]; uint8_t light; uint8_t light_heartbit; uint8_t padding3[5]; } __attribute__((packed)); struct etekcity_macro { uint8_t reportID; uint8_t heightytwo; uint8_t profile; uint8_t button_index; uint8_t active; char name[24]; uint8_t length; struct { uint8_t keycode; uint8_t flag; } keys[ETEKCITY_MAX_MACRO_LENGTH]; } __attribute__((packed)); struct etekcity_data { uint8_t profiles[(ETEKCITY_PROFILE_MAX + 1)][ETEKCITY_REPORT_SIZE_PROFILE]; struct etekcity_settings_report settings[(ETEKCITY_PROFILE_MAX + 1)]; struct etekcity_macro macros[(ETEKCITY_PROFILE_MAX + 1)][(ETEKCITY_BUTTON_MAX + 1)]; uint8_t speed_setting[ETEKCITY_REPORT_SIZE_SPEED_SETTING]; }; static char * print_key(uint8_t key) { switch (key) { case 1: return "BTN_LEFT"; case 2: return "BTN_RIGHT"; case 3: return "BTN_MIDDLE"; case 4: return "2 x BTN_LEFT"; case 7: return "BTN_EXTRA"; case 6: return "NONE"; case 8: return "BTN_SIDE"; case 9: return "REL_WHEEL 1"; case 10: return "REL_WHEEL -1"; case 11: return "REL_HWHEEL -1"; case 12: return "REL_HWHEEL 1"; /* DPI switch */ case 13: return "DPI cycle"; case 14: return "DPI++"; case 15: return "DPI--"; case 16: return "Macro"; /* Profile */ case 18: return "profile cycle"; case 19: return "profile++"; case 20: return "profile--"; case 21: return "HOLD BTN_LEFT ON/OFF"; /* multimedia */ case 25: return "KEY_CONFIG"; case 26: return "KEY_PREVIOUSSONG"; case 27: return "KEY_NEXTSONG"; case 28: return "KEY_PLAYPAUSE"; case 29: return "KEY_STOPCD"; case 30: return "KEY_MUTE"; case 31: return "KEY_VOLUMEUP"; case 32: return "KEY_VOLUMEDOWN"; /* windows */ case 33: return "KEY_CALC"; case 34: return "KEY_MAIL"; case 35: return "KEY_BOOKMARKS"; case 36: return "KEY_FORWARD"; case 37: return "KEY_BACK"; case 38: return "KEY_STOP"; case 39: return "KEY_FILE"; case 40: return "KEY_REFRESH"; case 41: return "KEY_HOMEPAGE"; case 42: return "KEY_SEARCH"; } return "UNKNOWN"; } struct etekcity_button_type_mapping { uint8_t raw; enum ratbag_button_type type; }; static const struct etekcity_button_type_mapping etekcity_button_type_mapping[] = { { 0, RATBAG_BUTTON_TYPE_LEFT }, { 1, RATBAG_BUTTON_TYPE_RIGHT }, { 2, RATBAG_BUTTON_TYPE_MIDDLE }, { 3, RATBAG_BUTTON_TYPE_EXTRA }, { 4, RATBAG_BUTTON_TYPE_SIDE }, { 5, RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP }, { 6, RATBAG_BUTTON_TYPE_PINKIE }, { 7, RATBAG_BUTTON_TYPE_PINKIE2 }, { 8, RATBAG_BUTTON_TYPE_WHEEL_UP }, { 9, RATBAG_BUTTON_TYPE_WHEEL_DOWN }, }; static enum ratbag_button_type etekcity_raw_to_button_type(uint8_t data) { const struct etekcity_button_type_mapping *mapping; ARRAY_FOR_EACH(etekcity_button_type_mapping, mapping) { if (mapping->raw == data) return mapping->type; } return RATBAG_BUTTON_TYPE_UNKNOWN; } struct etekcity_button_mapping { uint8_t raw; struct ratbag_button_action action; }; static struct etekcity_button_mapping etekcity_button_mapping[] = { { 1, BUTTON_ACTION_BUTTON(1) }, { 2, BUTTON_ACTION_BUTTON(2) }, { 3, BUTTON_ACTION_BUTTON(3) }, { 4, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_DOUBLECLICK) }, { 6, BUTTON_ACTION_NONE }, { 7, BUTTON_ACTION_BUTTON(4) }, { 8, BUTTON_ACTION_BUTTON(5) }, { 9, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP) }, { 10, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN) }, { 11, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT) }, { 12, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT) }, { 13, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) }, { 14, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) }, { 15, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) }, { 16, BUTTON_ACTION_MACRO }, { 18, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) }, { 19, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP) }, { 20, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN) }, { 25, BUTTON_ACTION_KEY(KEY_CONFIG) }, { 26, BUTTON_ACTION_KEY(KEY_PREVIOUSSONG) }, { 27, BUTTON_ACTION_KEY(KEY_NEXTSONG) }, { 28, BUTTON_ACTION_KEY(KEY_PLAYPAUSE) }, { 29, BUTTON_ACTION_KEY(KEY_STOPCD) }, { 30, BUTTON_ACTION_KEY(KEY_MUTE) }, { 31, BUTTON_ACTION_KEY(KEY_VOLUMEUP) }, { 32, BUTTON_ACTION_KEY(KEY_VOLUMEDOWN) }, { 33, BUTTON_ACTION_KEY(KEY_CALC) }, { 34, BUTTON_ACTION_KEY(KEY_MAIL) }, { 35, BUTTON_ACTION_KEY(KEY_BOOKMARKS) }, { 36, BUTTON_ACTION_KEY(KEY_FORWARD) }, { 37, BUTTON_ACTION_KEY(KEY_BACK) }, { 38, BUTTON_ACTION_KEY(KEY_STOP) }, { 39, BUTTON_ACTION_KEY(KEY_FILE) }, { 40, BUTTON_ACTION_KEY(KEY_REFRESH) }, { 41, BUTTON_ACTION_KEY(KEY_HOMEPAGE) }, { 42, BUTTON_ACTION_KEY(KEY_SEARCH) }, }; static const struct ratbag_button_action* etekcity_raw_to_button_action(uint8_t data) { struct etekcity_button_mapping *mapping; ARRAY_FOR_EACH(etekcity_button_mapping, mapping) { if (mapping->raw == data) return &mapping->action; } return NULL; } static uint8_t etekcity_button_action_to_raw(const struct ratbag_button_action *action) { struct etekcity_button_mapping *mapping; ARRAY_FOR_EACH(etekcity_button_mapping, mapping) { if (ratbag_button_action_match(&mapping->action, action)) return mapping->raw; } return 0; } static int etekcity_current_profile(struct ratbag_device *device) { uint8_t buf[3]; int ret; ret = ratbag_hidraw_raw_request(device, ETEKCITY_REPORT_ID_PROFILE, buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (ret < 0) return ret; if (ret != 3) return -EIO; return buf[2]; } static int etekcity_set_current_profile(struct ratbag_device *device, unsigned int index) { uint8_t buf[] = {ETEKCITY_REPORT_ID_PROFILE, 0x03, index}; int ret; if (index > ETEKCITY_PROFILE_MAX) return -EINVAL; ret = ratbag_hidraw_raw_request(device, buf[0], buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); msleep(100); return ret == sizeof(buf) ? 0 : ret; } static int etekcity_set_config_profile(struct ratbag_device *device, uint8_t profile, uint8_t type) { uint8_t buf[] = {ETEKCITY_REPORT_ID_CONFIGURE_PROFILE, profile, type}; int ret; if (profile > ETEKCITY_PROFILE_MAX) return -EINVAL; ret = ratbag_hidraw_raw_request(device, buf[0], buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); msleep(100); return ret == sizeof(buf) ? 0 : ret; } static inline unsigned etekcity_button_to_index(unsigned button) { return button < 8 ? button : button + 5; } static const struct ratbag_button_action * etekcity_button_to_action(struct ratbag_profile *profile, unsigned int button_index) { struct ratbag_device *device = profile->device; struct etekcity_data *drv_data = ratbag_get_drv_data(device); uint8_t data; unsigned raw_index = etekcity_button_to_index(button_index); data = drv_data->profiles[profile->index][3 + raw_index * 3]; log_raw(device->ratbag, " - button%d: %s (%02x) %s:%d\n", button_index, print_key(data), data, __FILE__, __LINE__); return etekcity_raw_to_button_action(data); } static int etekcity_write_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; unsigned int index = profile->index; struct etekcity_data *drv_data; int rc; uint8_t *buf; assert(index <= ETEKCITY_PROFILE_MAX); drv_data = ratbag_get_drv_data(device); buf = drv_data->profiles[index]; etekcity_set_config_profile(device, index, ETEKCITY_CONFIG_KEY_MAPPING); rc = ratbag_hidraw_raw_request(device, ETEKCITY_REPORT_ID_KEY_MAPPING, buf, ETEKCITY_REPORT_SIZE_PROFILE, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); msleep(100); if (rc < ETEKCITY_REPORT_SIZE_PROFILE) return -EIO; log_raw(device->ratbag, "profile: %d written %s:%d\n", buf[2], __FILE__, __LINE__); return 0; } static void etekcity_read_button(struct ratbag_button *button) { const struct ratbag_button_action *action; struct ratbag_device *device; struct ratbag_button_macro *m; struct etekcity_macro *macro; struct etekcity_data *drv_data; uint8_t *buf; unsigned j; int rc; action = etekcity_button_to_action(button->profile, button->index); if (action) ratbag_button_set_action(button, action); button->type = etekcity_raw_to_button_type(button->index); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); if (action && action->type == RATBAG_BUTTON_ACTION_TYPE_MACRO) { device = button->profile->device; drv_data = ratbag_get_drv_data(device); etekcity_set_config_profile(device, button->profile->index, button->index); macro = &drv_data->macros[button->profile->index][button->index]; buf = (uint8_t*)macro; rc = ratbag_hidraw_raw_request(device, ETEKCITY_REPORT_ID_MACRO, buf, ETEKCITY_REPORT_SIZE_MACRO, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (rc != ETEKCITY_REPORT_SIZE_MACRO) { log_error(device->ratbag, "Unable to retrieve the macro for button %d of profile %d: %s (%d)\n", button->index, button->profile->index, rc < 0 ? strerror(-rc) : "not read enough", rc); } else { m = ratbag_button_macro_new(macro->name); log_raw(device->ratbag, "macro on button %d of profile %d is named '%s', and contains %d events:\n", button->index, button->profile->index, macro->name, macro->length); for (j = 0; j < macro->length; j++) { unsigned int keycode = ratbag_hidraw_get_keycode_from_keyboard_usage(device, macro->keys[j].keycode); ratbag_button_macro_set_event(m, j, macro->keys[j].flag & 0x80 ? RATBAG_MACRO_EVENT_KEY_RELEASED : RATBAG_MACRO_EVENT_KEY_PRESSED, keycode); log_raw(device->ratbag, " - %s %s\n", libevdev_event_code_get_name(EV_KEY, keycode), macro->keys[j].flag & 0x80 ? "released" : "pressed"); } ratbag_button_copy_macro(button, m); ratbag_button_macro_unref(m); } msleep(10); } } static void etekcity_read_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct etekcity_data *drv_data; struct ratbag_resolution *resolution; struct ratbag_button *button; struct etekcity_settings_report *setting_report; uint8_t *buf; unsigned int report_rate; int dpi_x, dpi_y; int rc; unsigned int report_rates[] = { 125, 250, 500, 1000 }; assert(profile->index <= ETEKCITY_PROFILE_MAX); drv_data = ratbag_get_drv_data(device); setting_report = &drv_data->settings[profile->index]; buf = (uint8_t*)setting_report; etekcity_set_config_profile(device, profile->index, ETEKCITY_CONFIG_SETTINGS); rc = ratbag_hidraw_raw_request(device, ETEKCITY_REPORT_ID_SETTINGS, buf, ETEKCITY_REPORT_SIZE_SETTINGS, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (rc < ETEKCITY_REPORT_SIZE_SETTINGS) return; /* first retrieve the report rate, it is set per profile */ if (setting_report->report_rate < ARRAY_LENGTH(report_rates)) { report_rate = report_rates[setting_report->report_rate]; } else { log_error(device->ratbag, "error while reading the report rate of the mouse (0x%02x)\n", buf[26]); report_rate = 0; } ratbag_profile_set_report_rate_list(profile, report_rates, ARRAY_LENGTH(report_rates)); ratbag_profile_set_report_rate(profile, report_rate); ratbag_profile_for_each_resolution(profile, resolution) { dpi_x = setting_report->xres[resolution->index] * 50; dpi_y = setting_report->yres[resolution->index] * 50; if (!(setting_report->dpi_mask & (1 << resolution->index))) { /* the profile is disabled, overwrite it */ dpi_x = 0; dpi_y = 0; } ratbag_resolution_set_resolution(resolution, dpi_x, dpi_y); ratbag_resolution_set_cap(resolution, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION); resolution->is_active = (resolution->index == setting_report->current_dpi); } ratbag_profile_for_each_button(profile, button) etekcity_read_button(button); buf = drv_data->profiles[profile->index]; etekcity_set_config_profile(device, profile->index, ETEKCITY_CONFIG_KEY_MAPPING); rc = ratbag_hidraw_raw_request(device, ETEKCITY_REPORT_ID_KEY_MAPPING, buf, ETEKCITY_REPORT_SIZE_PROFILE, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); msleep(10); if (rc < ETEKCITY_REPORT_SIZE_PROFILE) return; log_raw(device->ratbag, "profile: %d %s:%d\n", buf[2], __FILE__, __LINE__); } static int etekcity_write_macro(struct ratbag_button *button, const struct ratbag_button_action *action) { struct ratbag_device *device; struct etekcity_macro *macro; struct etekcity_data *drv_data; uint8_t *buf; unsigned i, count = 0; int rc; if (action->type != RATBAG_BUTTON_ACTION_TYPE_MACRO) return 0; device = button->profile->device; drv_data = ratbag_get_drv_data(device); macro = &drv_data->macros[button->profile->index][button->index]; buf = (uint8_t*)macro; for (i = 0; i < MAX_MACRO_EVENTS && count < ETEKCITY_MAX_MACRO_LENGTH; i++) { if (action->macro->events[i].type == RATBAG_MACRO_EVENT_INVALID) return -EINVAL; /* should not happen, ever */ if (action->macro->events[i].type == RATBAG_MACRO_EVENT_NONE) break; /* ignore timeout events */ if (action->macro->events[i].type == RATBAG_MACRO_EVENT_WAIT) continue; macro->keys[count].keycode = ratbag_hidraw_get_keyboard_usage_from_keycode(device, action->macro->events[i].event.key); if (action->macro->events[i].type == RATBAG_MACRO_EVENT_KEY_PRESSED) macro->keys[count].flag = 0x00; else macro->keys[count].flag = 0x80; count++; } macro->reportID = ETEKCITY_REPORT_ID_MACRO; macro->heightytwo = 0x82; macro->profile = button->profile->index; macro->button_index = button->index; macro->active = 0x01; strncpy(macro->name, action->macro->name, 23); macro->length = count; etekcity_set_config_profile(device, button->profile->index, button->index); rc = ratbag_hidraw_raw_request(device, ETEKCITY_REPORT_ID_MACRO, buf, ETEKCITY_REPORT_SIZE_MACRO, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc < 0) return rc; return rc == ETEKCITY_REPORT_SIZE_MACRO ? 0 : -EIO; } static int etekcity_write_button(struct ratbag_button *button, const struct ratbag_button_action *action) { struct ratbag_profile *profile = button->profile; struct ratbag_device *device = profile->device; struct etekcity_data *drv_data = ratbag_get_drv_data(device); uint8_t rc, *data; unsigned index = etekcity_button_to_index(button->index); data = &drv_data->profiles[profile->index][3 + index * 3]; rc = etekcity_button_action_to_raw(action); if (!rc) return -EINVAL; *data = rc; rc = etekcity_write_profile(button->profile); if (rc) { log_error(device->ratbag, "unable to write the profile to the device: '%s' (%d)\n", strerror(-rc), rc); return rc; } rc = etekcity_write_macro(button, action); if (rc) { log_error(device->ratbag, "unable to write the macro to the device: '%s' (%d)\n", strerror(-rc), rc); return rc; } return rc; } static int etekcity_write_resolution_dpi(struct ratbag_resolution *resolution, int dpi_x, int dpi_y) { struct ratbag_profile *profile = resolution->profile; struct ratbag_device *device = profile->device; struct etekcity_data *drv_data = ratbag_get_drv_data(device); struct etekcity_settings_report *settings_report; uint8_t *buf; int rc; if (dpi_x < 50 || dpi_x > 8200 || dpi_x % 50) return -EINVAL; if (dpi_y < 50 || dpi_y > 8200 || dpi_y % 50) return -EINVAL; settings_report = &drv_data->settings[profile->index]; settings_report->x_sensitivity = 0x0a; settings_report->y_sensitivity = 0x0a; settings_report->xres[resolution->index] = dpi_x / 50; settings_report->yres[resolution->index] = dpi_y / 50; buf = (uint8_t*)settings_report; etekcity_set_config_profile(device, profile->index, ETEKCITY_CONFIG_SETTINGS); rc = ratbag_hidraw_raw_request(device, ETEKCITY_REPORT_ID_SETTINGS, buf, ETEKCITY_REPORT_SIZE_SETTINGS, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc < 0) return rc; if (rc != ETEKCITY_REPORT_SIZE_SETTINGS) return -EIO; return 0; } static int etekcity_probe(struct ratbag_device *device) { int rc; struct ratbag_profile *profile; struct etekcity_data *drv_data; int active_idx; rc = ratbag_open_hidraw(device); if (rc) return rc; if (!ratbag_hidraw_has_report(device, ETEKCITY_REPORT_ID_KEY_MAPPING)) { ratbag_close_hidraw(device); return -ENODEV; } drv_data = zalloc(sizeof(*drv_data)); ratbag_set_drv_data(device, drv_data); /* retrieve the "on-to-go" speed setting */ rc = ratbag_hidraw_raw_request(device, ETEKCITY_REPORT_ID_SPEED_SETTING, drv_data->speed_setting, ETEKCITY_REPORT_SIZE_SPEED_SETTING, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (rc) return rc; log_debug(device->ratbag, "device is at %d ms of latency\n", drv_data->speed_setting[2]); /* profiles are 0-indexed */ ratbag_device_init_profiles(device, ETEKCITY_PROFILE_MAX + 1, ETEKCITY_NUM_DPI, ETEKCITY_BUTTON_MAX + 1, ETEKCITY_LED); ratbag_device_for_each_profile(device, profile) etekcity_read_profile(profile); active_idx = etekcity_current_profile(device); if (active_idx < 0) { log_error(device->ratbag, "Can't talk to the mouse: '%s' (%d)\n", strerror(-active_idx), active_idx); rc = -ENODEV; goto err; } list_for_each(profile, &device->profiles, link) { if (profile->index == (unsigned int)active_idx) { profile->is_active = true; break; } } log_raw(device->ratbag, "'%s' is in profile %d\n", ratbag_device_get_name(device), profile->index); return 0; err: free(drv_data); ratbag_set_drv_data(device, NULL); return rc; } static void etekcity_remove(struct ratbag_device *device) { ratbag_close_hidraw(device); free(ratbag_get_drv_data(device)); } struct ratbag_driver etekcity_driver = { .name = "EtekCity", .id = "etekcity", .probe = etekcity_probe, .remove = etekcity_remove, .write_profile = etekcity_write_profile, .set_active_profile = etekcity_set_current_profile, .write_button = etekcity_write_button, .write_resolution_dpi = etekcity_write_resolution_dpi, }; libratbag-0.13/src/driver-gskill.c000066400000000000000000001204051362011324700170760ustar00rootroot00000000000000/* * Copyright © 2016 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include "config.h" #include "libratbag-private.h" #include "libratbag-hidraw.h" #include #include #define GSKILL_PROFILE_MAX 5 #define GSKILL_NUM_DPI 5 #define GSKILL_BUTTON_MAX 10 #define GSKILL_NUM_LED 0 #define GSKILL_MAX_POLLING_RATE 1000 #define GSKILL_MIN_DPI 100 #define GSKILL_MAX_DPI 8200 #define GSKILL_DPI_UNIT 50 /* Commands */ #define GSKILL_GET_CURRENT_PROFILE_NUM 0x03 #define GSKILL_GET_SET_MACRO 0x04 #define GSKILL_GET_SET_PROFILE 0x05 #define GSKILL_GENERAL_CMD 0x0c #define GSKILL_REPORT_SIZE_PROFILE 644 #define GSKILL_REPORT_SIZE_CMD 9 #define GSKILL_REPORT_SIZE_MACRO 2052 #define GSKILL_CHECKSUM_OFFSET 3 /* Command status codes */ #define GSKILL_CMD_SUCCESS 0xb0 #define GSKILL_CMD_IN_PROGRESS 0xb1 #define GSKILL_CMD_FAILURE 0xb2 #define GSKILL_CMD_IDLE 0xb3 /* LED groups. DPI is omitted here since it's handled specially */ #define GSKILL_LED_TYPE_LOGO 0 #define GSKILL_LED_TYPE_WHEEL 1 #define GSKILL_LED_TYPE_TAIL 2 #define GSKILL_LED_TYPE_COUNT 3 #define GSKILL_KBD_MOD_CTRL_LEFT AS_MASK(0) #define GSKILL_KBD_MOD_SHIFT_LEFT AS_MASK(1) #define GSKILL_KBD_MOD_ALT_LEFT AS_MASK(2) #define GSKILL_KBD_MOD_SUPER_LEFT AS_MASK(3) #define GSKILL_KBD_MOD_CTRL_RIGHT AS_MASK(4) #define GSKILL_KBD_MOD_SHIFT_RIGHT AS_MASK(5) #define GSKILL_KBD_MOD_ALT_RIGHT AS_MASK(6) #define GSKILL_KBD_MOD_SUPER_RIGHT AS_MASK(7) struct gskill_raw_dpi_level { uint8_t x; uint8_t y; } __attribute__((packed)); struct gskill_led_color { uint8_t red; uint8_t green; uint8_t blue; } __attribute__((packed)); struct gskill_led_values { uint8_t brightness; struct gskill_led_color color; } __attribute__((packed)); enum gskill_led_control_type { GSKILL_LED_ALL_OFF = 0x0, GSKILL_LED_ALL_ON = 0x1, GSKILL_LED_BREATHING = 0x2, GSKILL_DPI_LED_RIGHT_CYCLE = 0x3, GSKILL_DPI_LED_LEFT_CYCLE = 0x4 }; struct gskill_background_led_cfg { uint8_t brightness; struct gskill_led_color dpi[4]; struct gskill_led_color leds[GSKILL_LED_TYPE_COUNT]; } __attribute__((packed)); struct gskill_dpi_led_group_cfg { uint8_t duration_step; uint8_t duration_high; uint8_t duration_low; uint8_t cycle_num; struct gskill_led_values steps[12]; }; struct gskill_led_group_cfg { enum gskill_led_control_type type :3; uint8_t :5; /* unused */ uint8_t duration_step; uint8_t duration_high; uint8_t duration_low; uint8_t cycle_num; struct gskill_led_values steps[12]; } __attribute__((packed)); struct gskill_dpi_led_cycle_cfg { enum gskill_led_control_type type :3; uint8_t :5; /* unused */ /* Don't worry, the low/high flip-flop here is intentional */ uint8_t duration_low; uint8_t duration_high; uint8_t cycle_num; struct gskill_led_values cycles[12]; } __attribute__((packed)); /* * We may occasionally run into codes outside this, however those codes * indicate functionalities that aren't too useful for us */ enum gskill_button_function_type { GSKILL_BUTTON_FUNCTION_WHEEL = 0x00, GSKILL_BUTTON_FUNCTION_MOUSE = 0x01, GSKILL_BUTTON_FUNCTION_KBD = 0x02, GSKILL_BUTTON_FUNCTION_CONSUMER = 0x03, GSKILL_BUTTON_FUNCTION_MACRO = 0x06, GSKILL_BUTTON_FUNCTION_DPI_UP = 0x09, GSKILL_BUTTON_FUNCTION_DPI_DOWN = 0x0a, GSKILL_BUTTON_FUNCTION_CYCLE_DPI_UP = 0x0b, GSKILL_BUTTON_FUNCTION_CYCLE_DPI_DOWN = 0x0c, GSKILL_BUTTON_FUNCTION_PROFILE_SWITCH = 0x0d, GSKILL_BUTTON_FUNCTION_TEMPORARY_CPI_ADJUST = 0x15, GSKILL_BUTTON_FUNCTION_DIRECT_DPI_CHANGE = 0x16, GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_UP = 0x18, GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_DOWN = 0x19, GSKILL_BUTTON_FUNCTION_DISABLE = 0xff }; struct gskill_button_cfg { enum gskill_button_function_type type :8; union { struct { enum { GSKILL_WHEEL_SCROLL_UP = 0, GSKILL_WHEEL_SCROLL_DOWN = 1, } direction :8; } wheel; struct { enum { GSKILL_BTN_MASK_LEFT = AS_MASK(0), GSKILL_BTN_MASK_RIGHT = AS_MASK(1), GSKILL_BTN_MASK_MIDDLE = AS_MASK(2), GSKILL_BTN_MASK_SIDE = AS_MASK(3), GSKILL_BTN_MASK_EXTRA = AS_MASK(4) } button_mask :8; } mouse; struct { uint16_t code; } consumer; struct { uint8_t modifier_mask; uint8_t hid_code; /* * XXX: Supposedly this is supposed to have additional * parts of the kbd code, however that doesn't seem to * be the case in practice… */ uint16_t :16; } kbd; struct { uint8_t level; } dpi; } params; } __attribute__((packed)); struct gskill_action_mapping { struct gskill_button_cfg config; struct ratbag_button_action action; }; struct gskill_profile_report { uint16_t :16; uint8_t profile_num; uint8_t checksum; uint8_t polling_rate :4; uint8_t angle_snap_ratio :4; uint8_t liftoff_value :5; bool liftoff_enabled :1; bool disable_leds_in_sleep:1; enum { GSKILL_LED_PROFILE_MODE_BACKGROUND = 0, GSKILL_LED_PROFILE_MODE_OTHER = 1, } led_profile_mode :1; uint8_t :8; /* unused */ uint8_t current_dpi_level :4; uint8_t dpi_num :4; struct gskill_raw_dpi_level dpi_levels[GSKILL_NUM_DPI]; /* LEDs */ struct gskill_background_led_cfg background_lighting; struct gskill_dpi_led_cycle_cfg led_dpi_cycle; struct gskill_dpi_led_group_cfg dpi_led; struct gskill_led_group_cfg leds[GSKILL_LED_TYPE_COUNT]; /* Button assignments */ uint8_t button_function_redirections[8]; struct gskill_button_cfg btn_cfgs[GSKILL_BUTTON_MAX]; /* A mystery */ uint8_t _unused1[27]; char name[256]; } __attribute__((packed)); _Static_assert(sizeof(struct gskill_profile_report) == GSKILL_REPORT_SIZE_PROFILE, "Size of gskill_profile_report is wrong"); struct gskill_macro_delay { uint8_t tag; /* should be 0x1 to indicate delay */ uint16_t count; } __attribute__((packed)); struct gskill_macro_report { /* yes, the report can be both at offset 0 and 1 :( */ union { struct { uint8_t result; uint8_t report_id; } read; struct { uint8_t report_id; uint8_t :8; /* unused */ } write; } header; uint8_t macro_num; uint8_t checksum; enum { GSKILL_MACRO_METHOD_BUTTON_PRESS = 0x5, GSKILL_MACRO_METHOD_BUTTON_RELEASE = 0x1, GSKILL_MACRO_METHOD_BUTTON_LOOP_START = 0x7, GSKILL_MACRO_METHOD_BUTTON_LOOP_END = 0x0 } macro_exec_method :8; uint8_t loop_count; uint8_t please_set_me_to_1_thank_you; uint16_t macro_length; uint8_t macro_name_length; char macro_name[256]; uint8_t macro_content[1786]; } __attribute__((packed)); _Static_assert(sizeof(struct gskill_macro_report) == GSKILL_REPORT_SIZE_MACRO, "Size of gskill_macro_report is wrong"); enum ratbag_button_type gskill_button_type_mapping[] = { RATBAG_BUTTON_TYPE_LEFT, RATBAG_BUTTON_TYPE_RIGHT, RATBAG_BUTTON_TYPE_MIDDLE, RATBAG_BUTTON_TYPE_THUMB, RATBAG_BUTTON_TYPE_THUMB2, RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP, RATBAG_BUTTON_TYPE_THUMB3, RATBAG_BUTTON_TYPE_THUMB4, RATBAG_BUTTON_TYPE_WHEEL_UP, RATBAG_BUTTON_TYPE_WHEEL_DOWN, }; struct gskill_button_function_mapping { enum gskill_button_function_type type; struct ratbag_button_action action; }; static const struct gskill_button_function_mapping gskill_button_function_mapping[] = { { GSKILL_BUTTON_FUNCTION_MACRO, BUTTON_ACTION_MACRO }, { GSKILL_BUTTON_FUNCTION_DPI_UP, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) }, { GSKILL_BUTTON_FUNCTION_DPI_DOWN, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) }, { GSKILL_BUTTON_FUNCTION_CYCLE_DPI_UP, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) }, { GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_UP, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) }, { GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_DOWN, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN) }, { GSKILL_BUTTON_FUNCTION_DISABLE, BUTTON_ACTION_NONE }, }; struct gskill_profile_data { struct gskill_profile_report report; uint8_t res_idx_to_dev_idx[GSKILL_NUM_DPI]; struct gskill_macro_report macros[GSKILL_BUTTON_MAX]; }; struct gskill_data { uint8_t profile_count; struct gskill_profile_data profile_data[GSKILL_PROFILE_MAX]; }; static inline struct gskill_profile_data * profile_to_pdata(struct ratbag_profile *profile) { struct gskill_data *drv_data = profile->device->drv_data; return &drv_data->profile_data[profile->index]; } static const struct ratbag_button_action * gskill_button_function_to_action(enum gskill_button_function_type type) { const struct gskill_button_function_mapping *mapping; ARRAY_FOR_EACH(gskill_button_function_mapping, mapping) { if (mapping->type == type) return &mapping->action; } return NULL; } static enum gskill_button_function_type gskill_button_function_from_action(const struct ratbag_button_action *action) { const struct gskill_button_function_mapping *mapping; ARRAY_FOR_EACH(gskill_button_function_mapping, mapping) { if (ratbag_button_action_match(&mapping->action, action)) return mapping->type; } return GSKILL_BUTTON_FUNCTION_DISABLE; } static uint8_t gskill_calculate_checksum(const uint8_t *buf, size_t len) { uint8_t checksum = 0; unsigned i; for (i = GSKILL_CHECKSUM_OFFSET + 1; i < len; i++) checksum += buf[i]; checksum = ~checksum + 1; return checksum; } static int gskill_general_cmd(struct ratbag_device *device, uint8_t buf[GSKILL_REPORT_SIZE_CMD]) { int rc; int retries; assert(buf[0] == GSKILL_GENERAL_CMD); rc = ratbag_hidraw_raw_request(device, GSKILL_GENERAL_CMD, buf, GSKILL_REPORT_SIZE_CMD, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc != GSKILL_REPORT_SIZE_CMD) { log_error(device->ratbag, "Error while sending command to mouse: %d\n", rc); return rc < 0 ? rc : -EPROTO; } rc = -EAGAIN; for (retries = 0; retries < 10 && rc == -EAGAIN; retries++) { /* * Wait for the device to be ready * Spec says this should be 10ms, but 20ms seems to get the * mouse to return slightly less nonsense responses */ msleep(20); rc = ratbag_hidraw_raw_request(device, 0, buf, GSKILL_REPORT_SIZE_CMD, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); /* * Sometimes the mouse just doesn't send anything when it wants * to tell us it's ready. In this case rc will be 0 and this * function will succeed. */ if (rc < GSKILL_REPORT_SIZE_CMD) break; /* Check the command status bit */ switch (buf[1]) { case 0: /* sometimes the mouse gets lazy and just returns a blank buffer on success */ case GSKILL_CMD_SUCCESS: rc = 0; break; case GSKILL_CMD_IN_PROGRESS: rc = -EAGAIN; continue; case GSKILL_CMD_IDLE: log_error(device->ratbag, "Command response indicates idle status? Uh huh.\n"); rc = -EPROTO; break; case GSKILL_CMD_FAILURE: log_error(device->ratbag, "Command failed\n"); rc = -EIO; break; default: log_error(device->ratbag, "Received unknown command status from mouse: 0x%x\n", buf[1]); rc = -EPROTO; break; } } if (rc == -EAGAIN) { log_error(device->ratbag, "Failed to get command response from mouse after %d tries, giving up\n", retries); rc = -ETIMEDOUT; } else if (rc) { log_error(device->ratbag, "Failed to perform command on mouse: %d\n", rc); if (rc > 0) rc = -EPROTO; } return rc; } static int gskill_get_active_profile_idx(struct ratbag_device *device) { uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x7, 0x0, 0x1 }; int rc; rc = gskill_general_cmd(device, buf); if (rc) { log_error(device->ratbag, "Error while getting active profile number from mouse: %d\n", rc); return rc; } return buf[3]; } static int gskill_set_active_profile(struct ratbag_device *device, unsigned index) { uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x7, index, 0x0 }; int rc; rc = gskill_general_cmd(device, buf); if (rc) { log_error(device->ratbag, "Error while changing active profile on mouse: %d\n", rc); return rc; } return 0; } static int gskill_get_profile_count(struct ratbag_device *device) { uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x12, 0x0, 0x1 }; int rc; rc = gskill_general_cmd(device, buf); if (rc) { log_error(device->ratbag, "Error while getting the number of profiles: %d\n", rc); return rc; } log_debug(device->ratbag, "Profile count: %d\n", buf[3]); return buf[3]; } static int gskill_set_profile_count(struct ratbag_device *device, unsigned int count) { uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x12, count, 0x0 }; int rc; log_debug(device->ratbag, "Setting profile count to %d\n", count); rc = gskill_general_cmd(device, buf); if (rc) { log_error(device->ratbag, "Error while setting the number of profiles: %d\n", rc); return rc; } return 0; } /* * This is used for setting the profile index argument on the mouse for both * reading and writing profiles */ static int gskill_select_profile(struct ratbag_device *device, unsigned index, bool write) { uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x0c, index, write }; int rc; /* * While this looks like a normal command and should have the same * behavior, trying to receive the command return status from the mouse * breaks reading the profile */ rc = ratbag_hidraw_raw_request(device, GSKILL_GENERAL_CMD, buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc != sizeof(buf)) { log_error(device->ratbag, "Error while setting profile number to read/write: %d\n", rc); return rc < 0 ? rc : -EPROTO; } return 0; } /* * Instructs the mouse to reload the data from a profile we've just written to * it. */ static int gskill_reload_profile_data(struct ratbag_device *device) { uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x0 }; int rc; log_debug(device->ratbag, "Asking mouse to reload profile data\n"); rc = gskill_general_cmd(device, buf); if (rc) { log_error(device->ratbag, "Failed to get mouse to reload profile data: %d\n", rc); return rc; } return 0; } static int gskill_write_profile(struct ratbag_device *device, struct gskill_profile_report *report) { uint8_t *buf = (uint8_t*)report; int rc; /* * The G.Skill configuration software doesn't take kindly to blank * profile names, so ensure we have one */ if (report->name[0] == '\0') { log_debug(device->ratbag, "Setting profile name to \"Ratbag profile %d\"\n", report->profile_num); rc = ratbag_utf8_to_enc(report->name, sizeof(report->name), "UTF-16LE", "Ratbag profile %d", report->profile_num); if (rc < 0) return rc; } report->checksum = gskill_calculate_checksum(buf, sizeof(*report)); rc = gskill_select_profile(device, report->profile_num, true); if (rc) return rc; /* Wait for the device to be ready */ msleep(200); rc = ratbag_hidraw_raw_request(device, GSKILL_GET_SET_PROFILE, buf, sizeof(*report), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc != sizeof(*report)) { log_error(device->ratbag, "Error while writing profile: %d\n", rc); return rc < 0 ? rc : -EPROTO; } return 0; } static int gskill_get_firmware_version(struct ratbag_device *device) { uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x08 }; int rc; rc = gskill_general_cmd(device, buf); if (rc) { log_error(device->ratbag, "Failed to read the firmware version of the mouse: %d\n", rc); return rc; } return buf[4]; } static unsigned int gskill_mouse_button_macro_code_to_keycode(uint8_t code) { unsigned int keycode; switch (code & 0x0f) { case 0x8: keycode = BTN_LEFT; break; case 0x9: keycode = BTN_RIGHT; break; case 0xa: keycode = BTN_MIDDLE; break; case 0xb: keycode = BTN_SIDE; break; case 0xc: keycode = BTN_EXTRA; break; default: keycode = 0; break; } return keycode; } static uint8_t gskill_macro_code_from_event(struct ratbag_device *device, struct ratbag_macro_event *event) { uint8_t macro_code; /* * The miscellanious keycodes are ORd with 0x70 to indicate press, 0xF0 * to indicate release */ if (event->type == RATBAG_MACRO_EVENT_KEY_PRESSED) macro_code = 0x70; else macro_code = 0xF0; switch (event->event.key) { case KEY_LEFTCTRL: macro_code |= 0x00; break; case KEY_LEFTSHIFT: macro_code |= 0x01; break; case KEY_LEFTALT: macro_code |= 0x02; break; case KEY_LEFTMETA: macro_code |= 0x03; break; case KEY_RIGHTCTRL: macro_code |= 0x04; break; case KEY_RIGHTSHIFT: macro_code |= 0x05; break; case KEY_RIGHTALT: macro_code |= 0x06; break; case KEY_RIGHTMETA: macro_code |= 0x07; break; case BTN_LEFT: macro_code |= 0x08; break; case BTN_RIGHT: macro_code |= 0x09; break; case BTN_MIDDLE: macro_code |= 0x0a; break; case BTN_SIDE: macro_code |= 0x0b; break; case BTN_EXTRA: macro_code |= 0x0c; break; case KEY_SCROLLDOWN: macro_code = 0x7e; break; case KEY_SCROLLUP: macro_code = 0xfe; break; default: macro_code = ratbag_hidraw_get_keyboard_usage_from_keycode( device, event->event.key); if (event->type == RATBAG_MACRO_EVENT_KEY_RELEASED) macro_code += 0x80; break; } return macro_code; } static struct ratbag_button_macro * gskill_macro_from_report(struct ratbag_device *device, struct gskill_macro_report *report) { struct ratbag_button_macro *macro; enum ratbag_macro_event_type type; const uint8_t *data = (uint8_t*)&report->macro_content; unsigned int event_data; int ret, i, event_idx, increment; /* The macro is empty */ if (report->macro_length == 0xff) { return NULL; } else if (report->macro_length > sizeof(report->macro_content)) { log_error(device->ratbag, "Macro length too large (max should be %ld, we got %d)\n", sizeof(report->macro_content), report->macro_length); return NULL; } /* * Since the length is only 8 bits long, it's impossible to specify a * length that's too large for the macro name */ macro = ratbag_button_macro_new(NULL); ret = ratbag_utf8_from_enc(report->macro_name, report->macro_name_length, "UTF-16LE", ¯o->macro.name); if (ret < 0) { ratbag_button_macro_unref(macro); return NULL; } for (i = 0, event_idx = 0, increment = 1; i < report->macro_length; i += increment, increment = 1, event_idx++) { const struct gskill_macro_delay *delay; switch (data[i]) { case 0x01: /* delay */ delay = (struct gskill_macro_delay*)&data[i]; increment = sizeof(struct gskill_macro_delay); type = RATBAG_MACRO_EVENT_WAIT; event_data = delay->count; break; case 0x04 ... 0x6a: /* HID KBD code, press */ type = RATBAG_MACRO_EVENT_KEY_PRESSED; event_data = ratbag_hidraw_get_keycode_from_keyboard_usage( device, data[i]); break; case 0x70 ... 0x77: /* KBD modifier, press */ type = RATBAG_MACRO_EVENT_KEY_PRESSED; event_data = ratbag_hidraw_get_keycode_from_keyboard_usage( device, data[i] + 0x70); break; case 0x78 ... 0x7c: /* Mouse button, press */ type = RATBAG_MACRO_EVENT_KEY_PRESSED; event_data = gskill_mouse_button_macro_code_to_keycode( data[i]); break; case 0x7e: /* Scroll down */ type = RATBAG_MACRO_EVENT_KEY_PRESSED; event_data = KEY_SCROLLDOWN; break; case 0x84 ... 0xef: /* HID KBD code, release */ type = RATBAG_MACRO_EVENT_KEY_RELEASED; event_data = ratbag_hidraw_get_keycode_from_keyboard_usage( device, data[i] - 0x80); break; case 0xf0 ... 0xf7: /* KBD modifier, release */ type = RATBAG_MACRO_EVENT_KEY_RELEASED; event_data = ratbag_hidraw_get_keycode_from_keyboard_usage( device, data[i] - 0x10); break; case 0xf8 ... 0xfc: /* Mouse button, release */ type = RATBAG_MACRO_EVENT_KEY_RELEASED; event_data = gskill_mouse_button_macro_code_to_keycode( data[i]); break; case 0xfe: /* Scroll up */ type = RATBAG_MACRO_EVENT_KEY_PRESSED; event_data = KEY_SCROLLUP; break; default: /* should never get there */ type = RATBAG_MACRO_EVENT_INVALID; event_data = 0; } ratbag_button_macro_set_event(macro, event_idx, type, event_data); } return macro; } /* FIXME: the macro struct here should be a const, but it looks like there's a * couple of functions in ratbag that need to have a const qualifier added to * their function declarations */ static struct gskill_macro_report * gskill_macro_to_report(struct ratbag_device *device, struct ratbag_button_macro *macro, unsigned int profile, unsigned int button) { struct gskill_data *drv_data = ratbag_get_drv_data(device); struct gskill_macro_report *report = &drv_data->profile_data[profile].macros[button]; struct gskill_macro_delay *delay; unsigned int event_num = ratbag_button_macro_get_num_events(macro); struct ratbag_macro_event *event; uint8_t *buf = report->macro_content; int profile_pos, increment, event_idx; ssize_t ret; memset(report, 0, sizeof(*report)); /* * G.Skill's configuration software will cry if we don't have a name, * so make sure we assign one */ if (!macro->macro.name || macro->macro.name[0] == '\0') { ret = ratbag_utf8_to_enc(report->macro_name, sizeof(report->macro_name), "UTF-16LE", "Ratbag macro for profile %d button %d", profile, button); } else { ret = ratbag_utf8_to_enc(report->macro_name, sizeof(report->macro_name), "UTF-16LE", "%s", macro->macro.name); } if (ret < 0) return NULL; report->macro_name_length = ret; report->macro_num = (profile * 10) + button; report->macro_exec_method = GSKILL_MACRO_METHOD_BUTTON_PRESS; report->loop_count = 0; report->please_set_me_to_1_thank_you = 1; /* No prob! Happy to help :) */ for (profile_pos = 0, increment = 1, event_idx = 0; event_idx < (signed)event_num; event_idx++, profile_pos += increment, increment = 1) { event = ¯o->macro.events[event_idx]; switch (event->type) { case RATBAG_MACRO_EVENT_WAIT: delay = (struct gskill_macro_delay*)&buf[profile_pos]; increment = sizeof(*delay); delay->tag = 1; delay->count = event->event.timeout; break; case RATBAG_MACRO_EVENT_KEY_PRESSED: case RATBAG_MACRO_EVENT_KEY_RELEASED: buf[profile_pos] = gskill_macro_code_from_event(device, event); break; case RATBAG_MACRO_EVENT_INVALID: case RATBAG_MACRO_EVENT_NONE: goto out; } } out: report->macro_length = profile_pos; return report; } static int gskill_select_macro(struct ratbag_device *device, unsigned profile, unsigned button, bool write) { uint8_t macro_num = (profile * 10) + button; uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x0b, macro_num, write }; int rc; /* * Just like in gskill_select_profile(), we can't use the normal * command handler for this */ rc = ratbag_hidraw_raw_request(device, GSKILL_GENERAL_CMD, buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc != sizeof(buf)) { log_error(device->ratbag, "Error while setting macro number to read/write: %d\n", rc); return rc < 0 ? rc : -EPROTO; } return 0; } static struct gskill_macro_report * gskill_read_button_macro(struct ratbag_device *device, unsigned int profile, unsigned int button) { struct gskill_data *drv_data = ratbag_get_drv_data(device); struct gskill_macro_report *report = &drv_data->profile_data[profile].macros[button]; uint8_t checksum; int rc; rc = gskill_select_macro(device, profile, button, false); if (rc) return NULL; /* Wait for the device to be ready */ msleep(100); rc = ratbag_hidraw_raw_request(device, GSKILL_GET_SET_MACRO, (uint8_t*)report, sizeof(*report), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (rc < (signed)sizeof(*report)) { log_error(device->ratbag, "Failed to retrieve macro for profile %d for button %d: %d\n", profile, button, rc); return NULL; } checksum = gskill_calculate_checksum((uint8_t*)report, sizeof(*report)); if (checksum != report->checksum) { log_error(device->ratbag, "Invalid checksum on macro for profile %d button %d\n", profile, button); return NULL; } return report; } static int gskill_write_button_macro(struct ratbag_device *device, struct gskill_macro_report *report) { unsigned int profile = report->macro_num / 10; unsigned int button = report->macro_num % 10; int rc; rc = gskill_select_macro(device, profile, button, true); if (rc) return rc; /* Wait for the device to be ready */ msleep(200); memset(&report->header, 0, sizeof(report->header)); report->header.write.report_id = 0x4; report->checksum = gskill_calculate_checksum((uint8_t*)report, sizeof(*report)); rc = ratbag_hidraw_raw_request(device, GSKILL_GET_SET_MACRO, (uint8_t*)report, sizeof(*report), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc < 0) { log_error(device->ratbag, "Failed to write macro for profile %d button %d to mouse: %d\n", profile, button, rc); return rc; } return 0; } static void gskill_read_resolutions(struct ratbag_profile *profile, struct gskill_profile_report *report) { struct gskill_profile_data *pdata = profile_to_pdata(profile); unsigned int rates[] = { 500, 1000 }; /* let's assume that is true */ int dpi_x, dpi_y, hz, i; log_debug(profile->device->ratbag, "Profile %d: DPI count is %d\n", profile->index, report->dpi_num); hz = GSKILL_MAX_POLLING_RATE / (report->polling_rate + 1); ratbag_profile_set_report_rate_list(profile, rates, ARRAY_LENGTH(rates)); ratbag_profile_set_report_rate(profile, hz); for (i = 0; i < report->dpi_num; i++) { _cleanup_resolution_ struct ratbag_resolution *resolution = NULL; dpi_x = report->dpi_levels[i].x * GSKILL_DPI_UNIT; dpi_y = report->dpi_levels[i].y * GSKILL_DPI_UNIT; resolution = ratbag_profile_get_resolution(profile, i); ratbag_resolution_set_resolution(resolution, dpi_x, dpi_y); resolution->is_active = (i == report->current_dpi_level); pdata->res_idx_to_dev_idx[i] = i; ratbag_resolution_set_cap(resolution, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION); ratbag_resolution_set_dpi_list_from_range(resolution, GSKILL_MIN_DPI, GSKILL_MAX_DPI); } } static void gskill_read_profile_name(struct ratbag_device *device, struct gskill_profile_report *report) { char *name; int ret; ret = ratbag_utf8_from_enc(report->name, sizeof(report->name), "UTF-16LE", &name); if (ret < 0) { log_debug(device->ratbag, "Couldn't read profile name? Error %d\n", ret); return; } log_debug(device->ratbag, "Profile %d name: \"%s\"\n", report->profile_num, name); free(name); } static void gskill_read_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct gskill_data *drv_data = ratbag_get_drv_data(device); struct gskill_profile_data *pdata = profile_to_pdata(profile); struct gskill_profile_report *report = &pdata->report; uint8_t checksum; int rc, retries; if (profile->index >= drv_data->profile_count) { profile->is_enabled = false; return; } /* * There's a couple of situations where after various commands, the * mouse will get confused and send the wrong profile. Keep trying * until we get what we want. * * As well, getting the wrong profile is sometimes a sign from the * mouse we're doing something wrong. */ for (retries = 0; retries < 3; retries++) { rc = gskill_select_profile(device, profile->index, false); if (rc < 0) return; /* Wait for the device to be ready */ msleep(100); rc = ratbag_hidraw_raw_request(device, GSKILL_GET_SET_PROFILE, (uint8_t*)report, sizeof(*report), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (rc < (signed)sizeof(*report)) { log_error(device->ratbag, "Error while requesting profile: %d\n", rc); return; } if (report->profile_num == profile->index) break; log_debug(device->ratbag, "Mouse send wrong profile, retrying...\n"); } checksum = gskill_calculate_checksum((uint8_t*)report, sizeof(*report)); if (checksum != report->checksum) { log_error(device->ratbag, "Warning: profile %d invalid checksum (expected %x, got %x)\n", profile->index, report->checksum, checksum); } gskill_read_resolutions(profile, report); gskill_read_profile_name(device, report); } static int gskill_update_resolutions(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct gskill_profile_data *pdata = profile_to_pdata(profile); struct gskill_profile_report *report = &pdata->report; int i; report->dpi_num = 0; memset(&report->dpi_levels, 0, sizeof(report->dpi_levels)); memset(&pdata->res_idx_to_dev_idx, 0, sizeof(pdata->res_idx_to_dev_idx)); /* * These mice start acting strange if we leave holes in the DPI levels. * So only write and map the enabled DPIs, disabled DPIs will just be * lost on exit */ for (i = 0; i < GSKILL_NUM_DPI; i++) { _cleanup_resolution_ struct ratbag_resolution *res = NULL; struct gskill_raw_dpi_level *level = &report->dpi_levels[report->dpi_num]; res = ratbag_profile_get_resolution(profile, i); if (!res->dpi_x || !res->dpi_y) continue; level->x = res->dpi_x / GSKILL_DPI_UNIT; level->y = res->dpi_y / GSKILL_DPI_UNIT; pdata->res_idx_to_dev_idx[i] = report->dpi_num; log_debug(device->ratbag, "Profile %d res %d mapped to %d\n", profile->index, res->index, report->dpi_num); report->dpi_num++; } return 0; } #if 0 static int gskill_reset_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x0a, profile->index }; int rc; rc = gskill_general_cmd(device, buf); if (rc < 0) return rc; log_debug(device->ratbag, "reset profile %d to factory defaults\n", profile->index); return 0; } #endif static void gskill_read_button(struct ratbag_button *button) { struct ratbag_profile *profile = button->profile; struct ratbag_device *device = profile->device; struct gskill_profile_report *report = &profile_to_pdata(profile)->report; struct gskill_macro_report *macro_report; struct ratbag_button_macro *macro; struct gskill_button_cfg *bcfg = &report->btn_cfgs[button->index]; struct ratbag_button_action *act = &button->action; button->type = gskill_button_type_mapping[button->index]; ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); /* * G.Skill mice can't save disabled profiles, so buttons from disabled * profiles shouldn't be set to anything */ if (!profile->is_enabled) { act->type = RATBAG_BUTTON_ACTION_TYPE_NONE; return; } /* Parse any parameters that might accompany the action type */ switch (bcfg->type) { case GSKILL_BUTTON_FUNCTION_WHEEL: act->type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; if (bcfg->params.wheel.direction == GSKILL_WHEEL_SCROLL_UP) act->action.special = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP; else act->action.special = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN; break; case GSKILL_BUTTON_FUNCTION_MOUSE: act->type = RATBAG_BUTTON_ACTION_TYPE_BUTTON; switch (bcfg->params.mouse.button_mask) { case GSKILL_BTN_MASK_LEFT: act->action.button = RATBAG_BUTTON_TYPE_LEFT; break; case GSKILL_BTN_MASK_RIGHT: act->action.button = RATBAG_BUTTON_TYPE_RIGHT; break; case GSKILL_BTN_MASK_MIDDLE: act->action.button = RATBAG_BUTTON_TYPE_MIDDLE; break; case GSKILL_BTN_MASK_SIDE: act->action.button = RATBAG_BUTTON_TYPE_SIDE; break; case GSKILL_BTN_MASK_EXTRA: act->action.button = RATBAG_BUTTON_TYPE_EXTRA; break; } break; case GSKILL_BUTTON_FUNCTION_KBD: act->type = RATBAG_BUTTON_ACTION_TYPE_KEY; act->action.key.key = ratbag_hidraw_get_keycode_from_keyboard_usage( device, bcfg->params.kbd.hid_code); break; case GSKILL_BUTTON_FUNCTION_CONSUMER: act->type = RATBAG_BUTTON_ACTION_TYPE_KEY; act->action.key.key = ratbag_hidraw_get_keycode_from_consumer_usage( device, bcfg->params.consumer.code); break; case GSKILL_BUTTON_FUNCTION_DPI_UP: case GSKILL_BUTTON_FUNCTION_DPI_DOWN: case GSKILL_BUTTON_FUNCTION_CYCLE_DPI_UP: case GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_UP: case GSKILL_BUTTON_FUNCTION_DISABLE: *act = *gskill_button_function_to_action(bcfg->type); break; case GSKILL_BUTTON_FUNCTION_MACRO: macro_report = gskill_read_button_macro(device, button->profile->index, button->index); if (!macro_report) goto err; macro = gskill_macro_from_report(device, macro_report); if (!macro) goto err; act->type = RATBAG_BUTTON_ACTION_TYPE_MACRO; ratbag_button_copy_macro(button, macro); ratbag_button_macro_unref(macro); break; default: break; } return; err: act->type = RATBAG_BUTTON_ACTION_TYPE_NONE; } static int gskill_update_button(struct ratbag_button *button) { struct ratbag_profile *profile = button->profile; struct ratbag_device *device = profile->device; struct ratbag_button_action *action = &button->action; struct ratbag_button_macro *macro = NULL; struct gskill_profile_data *pdata = profile_to_pdata(profile); struct gskill_button_cfg *bcfg = &pdata->report.btn_cfgs[button->index]; uint16_t code = 0; macro = container_of(action->macro, macro, macro); memset(&bcfg->params, 0, sizeof(bcfg->params)); switch (action->type) { case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: switch (action->action.special) { case RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP: bcfg->type = GSKILL_BUTTON_FUNCTION_WHEEL; bcfg->params.wheel.direction = GSKILL_WHEEL_SCROLL_UP; break; case RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN: bcfg->type = GSKILL_BUTTON_FUNCTION_WHEEL; bcfg->params.wheel.direction = GSKILL_WHEEL_SCROLL_DOWN; break; case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP: case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP: case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN: case RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP: case RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP: case RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN: bcfg->type = gskill_button_function_from_action(action); break; default: return -EINVAL; } break; case RATBAG_BUTTON_ACTION_TYPE_BUTTON: bcfg->type = GSKILL_BUTTON_FUNCTION_MOUSE; switch (action->action.button) { case RATBAG_BUTTON_TYPE_LEFT: bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_LEFT; break; case RATBAG_BUTTON_TYPE_RIGHT: bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_RIGHT; break; case RATBAG_BUTTON_TYPE_MIDDLE: bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_MIDDLE; break; case RATBAG_BUTTON_TYPE_SIDE: bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_SIDE; break; case RATBAG_BUTTON_TYPE_EXTRA: bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_EXTRA; break; default: return -EINVAL; } break; case RATBAG_BUTTON_ACTION_TYPE_KEY: code = ratbag_hidraw_get_keyboard_usage_from_keycode( device, action->action.key.key); if (code) { bcfg->type = GSKILL_BUTTON_FUNCTION_KBD; bcfg->params.kbd.hid_code = code; } else { code = ratbag_hidraw_get_consumer_usage_from_keycode( device, action->action.key.key); bcfg->type = GSKILL_BUTTON_FUNCTION_CONSUMER; bcfg->params.consumer.code = code; } break; case RATBAG_BUTTON_ACTION_TYPE_MACRO: bcfg->type = GSKILL_BUTTON_FUNCTION_MACRO; gskill_write_button_macro( device, gskill_macro_to_report(device, macro, profile->index, button->index)); break; case RATBAG_BUTTON_ACTION_TYPE_NONE: bcfg->type = GSKILL_BUTTON_FUNCTION_DISABLE; break; default: break; } return 0; } static int gskill_update_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct ratbag_button *button; struct gskill_profile_data *pdata = profile_to_pdata(profile); struct gskill_profile_report *report = &pdata->report; int rc; gskill_update_resolutions(profile); list_for_each(button, &profile->buttons, link) { if (!button->dirty) continue; rc = gskill_update_button(button); if (rc) return rc; } rc = gskill_write_profile(device, report); if (rc) return rc; return 0; } static int gskill_probe(struct ratbag_device *device) { struct gskill_data *drv_data = NULL; struct ratbag_profile *profile; unsigned int active_idx; int ret; ret = ratbag_open_hidraw(device); if (ret) return ret; drv_data = zalloc(sizeof(*drv_data)); ratbag_set_drv_data(device, drv_data); ret = gskill_get_firmware_version(device); if (ret < 0) goto err; log_debug(device->ratbag, "Firmware version: %d\n", ret); ret = gskill_get_profile_count(device); if (ret < 0) goto err; drv_data->profile_count = ret; ratbag_device_init_profiles(device, GSKILL_PROFILE_MAX, GSKILL_NUM_DPI, GSKILL_BUTTON_MAX, GSKILL_NUM_LED); ret = gskill_get_active_profile_idx(device); if (ret < 0) goto err; active_idx = ret; ratbag_device_for_each_profile(device, profile) { struct ratbag_button *button; gskill_read_profile(profile); ratbag_profile_for_each_button(profile, button) gskill_read_button(button); ratbag_profile_set_cap(profile, RATBAG_PROFILE_CAP_DISABLE); if (profile->index == active_idx) profile->is_active = true; } return 0; err: if (drv_data) { ratbag_set_drv_data(device, NULL); free(drv_data); } ratbag_close_hidraw(device); return ret; } static int gskill_commit(struct ratbag_device *device) { struct ratbag_profile *profile; struct gskill_data *drv_data = ratbag_get_drv_data(device); struct gskill_profile_report *report; uint8_t profile_count = 0, new_idx, i; bool reload = false; int rc; /* * G.Skill mice only have a concept of how many profiles are enabled, * not which ones are and aren't enabled. So in order to provide the * ability to disable individual profiles we need to only write the * enabled profiles and make sure no holes are left inbetween profiles */ for (i = 0; i < GSKILL_PROFILE_MAX; i++) { profile = ratbag_device_get_profile(device, i); if (!profile->is_enabled) continue; report = &drv_data->profile_data[profile->index].report; new_idx = profile_count++; if (new_idx == report->profile_num) continue; log_debug(device->ratbag, "Profile %d remapped to %d\n", profile->index, new_idx); profile->dirty = true; report->profile_num = new_idx; } if (profile_count != drv_data->profile_count) { rc = gskill_set_profile_count(device, profile_count); if (rc < 0) return rc; drv_data->profile_count = profile_count; } list_for_each(profile, &device->profiles, link) { if (!profile->is_enabled || !profile->dirty) continue; log_debug(device->ratbag, "Profile %d changed, rewriting\n", profile->index); reload = true; rc = gskill_update_profile(profile); if (rc) return rc; } if (reload) { rc = gskill_reload_profile_data(device); if (rc) return rc; } return 0; } static void gskill_remove(struct ratbag_device *device) { ratbag_close_hidraw(device); free(ratbag_get_drv_data(device)); } struct ratbag_driver gskill_driver = { .name = "G.Skill Ripjaws MX780", .id = "gskill", .probe = gskill_probe, .remove = gskill_remove, .commit = gskill_commit, .set_active_profile = gskill_set_active_profile, }; libratbag-0.13/src/driver-hidpp10.c000066400000000000000000000524761362011324700170720ustar00rootroot00000000000000/* * Copyright 2013-2015 Benjamin Tissoires * Copyright 2013-2015 Red Hat, Inc * * 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 (including the next * paragraph) 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. */ /* * Based on the HID++ 1.0 documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ /* * for this driver to work, you need a kernel >= v3.19 or one which contains * 925f0f3ed24f98b40c28627e74ff3e7f9d1e28bc ("HID: logitech-dj: allow transfer * of HID++ reports from/to the correct dj device") */ #include "config.h" #include #include #include #include #include #include #include "hidpp10.h" #include "usb-ids.h" #include "libratbag-private.h" #include "libratbag-hidraw.h" #include "libratbag-data.h" struct hidpp10drv_data { struct hidpp10_device *dev; }; static unsigned int hidpp10drv_read_macro_modifier(struct ratbag_device *device, union hidpp10_macro_data *macro) { switch (macro->key.key) { case 0x01: return KEY_LEFTCTRL; case 0x02: return KEY_LEFTSHIFT; case 0x04: return KEY_LEFTALT; case 0x08: return KEY_LEFTMETA; case 0x10: return KEY_RIGHTCTRL; case 0x20: return KEY_RIGHTSHIFT; case 0x40: return KEY_RIGHTALT; case 0x80: return KEY_RIGHTMETA; } return KEY_RESERVED; } static void hidpp10drv_read_macro(struct ratbag_button *button, struct hidpp10_profile *profile, union hidpp10_button *binding) { struct ratbag_device *device = button->profile->device; struct ratbag_button_macro *m; const char *name; union hidpp10_macro_data *macro; unsigned int i, keycode; bool delay = true; macro = profile->macros[binding->macro.address]; name = binding->macro.address > 1 ? (const char *)profile->macro_names[binding->macro.address - 2] : ""; i = 0; m = ratbag_button_macro_new(name); while (macro && macro->any.type != HIDPP10_MACRO_END && i < MAX_MACRO_EVENTS) { switch (macro->any.type) { case HIDPP10_MACRO_DELAY: ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, macro->delay.time); delay = true; break; case HIDPP10_MACRO_KEY_PRESS: keycode = ratbag_hidraw_get_keycode_from_keyboard_usage(device, macro->key.key); if (!delay) ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, 1); ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_KEY_PRESSED, keycode); delay = false; break; case HIDPP10_MACRO_KEY_RELEASE: keycode = ratbag_hidraw_get_keycode_from_keyboard_usage(device, macro->key.key); if (!delay) ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, 1); ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_KEY_RELEASED, keycode); delay = false; break; case HIDPP10_MACRO_MOD_PRESS: keycode = hidpp10drv_read_macro_modifier(device, macro); if (!delay) ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, 1); ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_KEY_PRESSED, keycode); delay = false; break; case HIDPP10_MACRO_MOD_RELEASE: keycode = hidpp10drv_read_macro_modifier(device, macro); if (!delay) ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, 1); ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_KEY_RELEASED, keycode); delay = false; break; } macro++; } ratbag_button_copy_macro(button, m); ratbag_button_macro_unref(m); } static void hidpp10drv_map_button(struct ratbag_device *device, struct hidpp10_device *hidpp10, struct ratbag_button *button) { struct hidpp10_profile profile; int ret; ret = hidpp10_get_profile(hidpp10, button->profile->index, &profile); if (ret) return; switch (profile.buttons[button->index].any.type) { case PROFILE_BUTTON_TYPE_BUTTON: button->action.type = RATBAG_BUTTON_ACTION_TYPE_BUTTON; button->action.action.button = profile.buttons[button->index].button.button; break; case PROFILE_BUTTON_TYPE_KEYS: button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; button->action.action.key.key = ratbag_hidraw_get_keycode_from_keyboard_usage(device, profile.buttons[button->index].keys.key); break; case PROFILE_BUTTON_TYPE_CONSUMER_CONTROL: button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; button->action.action.key.key = ratbag_hidraw_get_keycode_from_consumer_usage(device, profile.buttons[button->index].consumer_control.consumer_control); break; case PROFILE_BUTTON_TYPE_SPECIAL: button->action.type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; button->action.action.special = hidpp10_onboard_profiles_get_special(profile.buttons[button->index].special.special); break; case PROFILE_BUTTON_TYPE_DISABLED: button->action.type = RATBAG_BUTTON_ACTION_TYPE_NONE; break; default: if (profile.buttons[button->index].any.type & 0x80) { button->action.type = RATBAG_BUTTON_ACTION_TYPE_UNKNOWN; } else { hidpp10drv_read_macro(button, &profile, &profile.buttons[button->index]); } } ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); } static void hidpp10drv_read_button(struct ratbag_button *button) { enum ratbag_button_type type = RATBAG_BUTTON_TYPE_UNKNOWN; struct ratbag_device *device = button->profile->device; struct hidpp10drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp10_device *hidpp10 = drv_data->dev; switch (hidpp10->profile_type) { case HIDPP10_PROFILE_G500: switch (button->index) { case 0: type = RATBAG_BUTTON_TYPE_LEFT; break; case 1: type = RATBAG_BUTTON_TYPE_MIDDLE; break; case 2: type = RATBAG_BUTTON_TYPE_RIGHT; break; case 3: type = RATBAG_BUTTON_TYPE_THUMB; break; case 4: type = RATBAG_BUTTON_TYPE_THUMB2; break; case 5: type = RATBAG_BUTTON_TYPE_THUMB3; break; case 6: type = RATBAG_BUTTON_TYPE_WHEEL_LEFT; break; case 7: type = RATBAG_BUTTON_TYPE_WHEEL_RIGHT; break; case 8: type = RATBAG_BUTTON_TYPE_RESOLUTION_UP; break; case 9: type = RATBAG_BUTTON_TYPE_RESOLUTION_DOWN; break; case 10: case 11: case 12: /* these don't actually exist on the device */ type = RATBAG_BUTTON_TYPE_UNKNOWN; break; default: break; } hidpp10drv_map_button(device, hidpp10, button); break; case HIDPP10_PROFILE_G700: switch (button->index) { case 0: type = RATBAG_BUTTON_TYPE_LEFT; break; case 1: type = RATBAG_BUTTON_TYPE_MIDDLE; break; case 2: type = RATBAG_BUTTON_TYPE_RIGHT; break; case 3: type = RATBAG_BUTTON_TYPE_THUMB; break; case 4: type = RATBAG_BUTTON_TYPE_THUMB2; break; case 5: type = RATBAG_BUTTON_TYPE_THUMB3; break; case 6: type = RATBAG_BUTTON_TYPE_THUMB4; break; case 7: type = RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP; break; case 8: type = RATBAG_BUTTON_TYPE_RESOLUTION_DOWN; break; case 9: type = RATBAG_BUTTON_TYPE_RESOLUTION_UP; break; case 10: type = RATBAG_BUTTON_TYPE_PROFILE_CYCLE_UP; break; case 11: type = RATBAG_BUTTON_TYPE_WHEEL_LEFT; break; case 12: type = RATBAG_BUTTON_TYPE_WHEEL_RIGHT; break; default: break; } hidpp10drv_map_button(device, hidpp10, button); break; case HIDPP10_PROFILE_G9: switch (button->index) { case 0: type = RATBAG_BUTTON_TYPE_LEFT; break; case 1: type = RATBAG_BUTTON_TYPE_RIGHT; break; case 2: type = RATBAG_BUTTON_TYPE_MIDDLE; break; case 3: type = RATBAG_BUTTON_TYPE_THUMB; break; case 4: type = RATBAG_BUTTON_TYPE_THUMB2; break; case 5: type = RATBAG_BUTTON_TYPE_UNKNOWN; break; case 6: type = RATBAG_BUTTON_TYPE_WHEEL_LEFT; break; case 7: type = RATBAG_BUTTON_TYPE_WHEEL_RIGHT; break; case 8: type = RATBAG_BUTTON_TYPE_RESOLUTION_UP; break; case 9: type = RATBAG_BUTTON_TYPE_RESOLUTION_DOWN; break; case 10: case 11: case 12: /* these don't actually exist on the device */ type = RATBAG_BUTTON_TYPE_UNKNOWN; break; default: break; } hidpp10drv_map_button(device, hidpp10, button); break; default: switch (button->index) { case 0: type = RATBAG_BUTTON_TYPE_LEFT; break; case 1: type = RATBAG_BUTTON_TYPE_MIDDLE; break; case 2: type = RATBAG_BUTTON_TYPE_RIGHT; break; default: break; } } button->type = type; ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); } static int hidpp10drv_write_button(struct hidpp10_profile *profile, struct ratbag_button *button, const struct ratbag_button_action *action) { struct ratbag_device *device = button->profile->device; struct hidpp10drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp10_device *hidpp10 = drv_data->dev; uint8_t code; if (hidpp10->profile_type == HIDPP10_PROFILE_UNKNOWN) return -ENOTSUP; switch (action->type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: profile->buttons[button->index].button.type = PROFILE_BUTTON_TYPE_BUTTON; profile->buttons[button->index].button.button = action->action.button; break; case RATBAG_BUTTON_ACTION_TYPE_KEY: code = ratbag_hidraw_get_keyboard_usage_from_keycode(device, action->action.key.key); if (code == 0) { code = ratbag_hidraw_get_consumer_usage_from_keycode(device, action->action.key.key); if (code == 0) return -EINVAL; profile->buttons[button->index].consumer_control.type = PROFILE_BUTTON_TYPE_CONSUMER_CONTROL; profile->buttons[button->index].consumer_control.consumer_control = code; } else { profile->buttons[button->index].keys.type = PROFILE_BUTTON_TYPE_KEYS; profile->buttons[button->index].keys.key = code; } break; case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: code = hidpp10_onboard_profiles_get_code_from_special(action->action.special); if (code == 0) return -EINVAL; profile->buttons[button->index].special.type = PROFILE_BUTTON_TYPE_SPECIAL; profile->buttons[button->index].special.special = code; break; case RATBAG_BUTTON_ACTION_TYPE_MACRO: default: return -ENOTSUP; } return 0; } static void hidpp10drv_read_led(struct ratbag_led *led) { struct ratbag_device *device = led->profile->device; struct hidpp10drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp10_device *hidpp10 = drv_data->dev; struct hidpp10_profile profile; int ret; ret = hidpp10_get_profile(hidpp10, led->profile->index, &profile); if (ret) return; switch (hidpp10->profile_type) { case HIDPP10_PROFILE_G500: led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888; break; default: led->colordepth = RATBAG_LED_COLORDEPTH_MONOCHROME; break; } led->mode = RATBAG_LED_ON; led->color.red = profile.red; led->color.green = profile.green; led->color.blue = profile.blue; } static void hidpp10drv_write_led(struct hidpp10_profile *profile, struct ratbag_led *led) { profile->red = led->color.red; profile->green = led->color.green; profile->blue = led->color.blue; } static int hidpp10drv_set_current_profile(struct ratbag_device *device, unsigned int index) { struct hidpp10drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp10_device *hidpp10 = drv_data->dev; return hidpp10_set_current_profile(hidpp10, index); } static void hidpp10drv_read_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct ratbag_button *button; struct ratbag_led *led; struct hidpp10drv_data *drv_data; struct hidpp10_device *hidpp10; struct hidpp10_profile p = {0}; struct ratbag_resolution *res; int rc; uint16_t xres, yres; uint8_t idx; /* 0x64 USB_REFRESH_RATE has time between reports in ms. so let's * assume the 1000/500/250 rates exist on the devices */ unsigned int rates[] = {250, 500, 1000}; drv_data = ratbag_get_drv_data(device); hidpp10 = drv_data->dev; rc = hidpp10_get_profile(hidpp10, profile->index, &p); if (rc) return; if (hidpp10->profile_type != HIDPP10_PROFILE_UNKNOWN) ratbag_profile_set_cap(profile, RATBAG_PROFILE_CAP_DISABLE); profile->is_enabled = p.enabled; if (profile->name) { free(profile->name); profile->name = NULL; } if (p.name && p.name[0] != '\0') profile->name = strdup_safe((char*)p.name); rc = hidpp10_get_current_profile(hidpp10, &idx); if (rc == 0 && (unsigned int)idx == profile->index) profile->is_active = true; rc = hidpp10_get_current_resolution(hidpp10, &xres, &yres); if (rc) xres = 0xffff; ratbag_profile_for_each_resolution(profile, res) { unsigned int dpis[hidpp10->dpi_count]; ratbag_resolution_set_resolution(res, p.dpi_modes[res->index].xres, p.dpi_modes[res->index].yres); ratbag_resolution_set_cap(res, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION); if (profile->is_active && res->dpi_x == xres && res->dpi_y == yres) res->is_active = true; if (res->index == p.default_dpi_mode) { res->is_default = true; if (!profile->is_active) res->is_active = true; } if (hidpp10->dpi_table_is_range) { unsigned int min, max; min = hidpp10_dpi_table_get_min_dpi(hidpp10); max = hidpp10_dpi_table_get_max_dpi(hidpp10); /* FIXME: this relies on libratbag using the * same steps that we support */ ratbag_resolution_set_dpi_list_from_range(res, min, max); } else if (hidpp10->dpi_count > 0) { for (uint8_t i = 0; i < hidpp10->dpi_count; i++) dpis[i] = hidpp10->dpi_table[i].dpi; ratbag_resolution_set_dpi_list(res, dpis, hidpp10->dpi_count); } } ratbag_profile_set_report_rate_list(profile, rates, ARRAY_LENGTH(rates)); ratbag_profile_set_report_rate(profile, p.refresh_rate); ratbag_profile_for_each_button(profile, button) hidpp10drv_read_button(button); ratbag_profile_for_each_led(profile, led) hidpp10drv_read_led(led); } static int hidpp10drv_fill_from_profile(struct ratbag_device *device, struct hidpp10_device *dev) { int rc, num_leds; struct hidpp10_profile profile = {0}; unsigned int i; /* We don't know the HID++1.0 requests to query for buttons, etc. * Simply get the first enabled profile and fill in the device * information from that. */ for (i = 0; i < dev->profile_count; i++) { rc = hidpp10_get_profile(dev, i, &profile); if (rc) return rc; if (profile.enabled) break; } /* let the .device file override LED count from the profile */ num_leds = ratbag_device_data_hidpp10_get_led_count(device->data); if (num_leds >= 0) profile.num_leds = num_leds; ratbag_device_init_profiles(device, dev->profile_count, profile.num_dpi_modes, profile.num_buttons, profile.num_leds); return 0; } static int hidpp10drv_test_hidraw(struct ratbag_device *device) { return ratbag_hidraw_has_report(device, REPORT_ID_SHORT); } static void hidpp10_log(void *userdata, enum hidpp_log_priority priority, const char *format, va_list args) { struct ratbag_device *device = userdata; log_msg_va(device->ratbag, priority, format, args); } static int hidpp10drv_commit(struct ratbag_device *device) { struct hidpp10drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp10_device *dev = drv_data->dev; struct ratbag_profile *profile; struct ratbag_button *button; struct ratbag_led *led; struct ratbag_resolution *resolution; struct ratbag_resolution *active_resolution = NULL; struct hidpp10_profile p; int rc; list_for_each(profile, &device->profiles, link) { if (!profile->dirty) continue; rc = hidpp10_get_profile(dev, profile->index, &p); if (rc) return rc; p.enabled = profile->is_enabled; strncpy_safe((char*)p.name, profile->name, 24); ratbag_profile_for_each_resolution(profile, resolution) { p.dpi_modes[resolution->index].xres = resolution->dpi_x; p.dpi_modes[resolution->index].yres = resolution->dpi_y; if (profile->is_active && resolution->is_active) active_resolution = resolution; } list_for_each(button, &profile->buttons, link) { struct ratbag_button_action action = button->action; if (!button->dirty) continue; rc = hidpp10drv_write_button(&p, button, &action); if (rc) return RATBAG_ERROR_DEVICE; } ratbag_profile_for_each_led(profile, led) hidpp10drv_write_led(&p, led); if (dev->profile_type != HIDPP10_PROFILE_UNKNOWN) { rc = hidpp10_set_profile(dev, profile->index, &p); if (rc) return RATBAG_ERROR_DEVICE; } /* Update the current resolution in case it changed */ if (active_resolution) { rc = hidpp10_set_current_resolution(dev, active_resolution->dpi_x, active_resolution->dpi_y); if (rc) return RATBAG_ERROR_DEVICE; active_resolution = NULL; } } return RATBAG_SUCCESS; } static int hidpp10drv_probe(struct ratbag_device *device) { int rc; struct hidpp10drv_data *drv_data = NULL; struct hidpp10_device *dev = NULL; struct hidpp_device base; enum hidpp10_profile_type type = HIDPP10_PROFILE_UNKNOWN; const char *typestr; int device_idx = HIDPP_WIRED_DEVICE_IDX; unsigned int profile_count = 1; struct ratbag_profile *profile; rc = ratbag_find_hidraw(device, hidpp10drv_test_hidraw); if (rc) goto err; drv_data = zalloc(sizeof(*drv_data)); hidpp_device_init(&base, device->hidraw[0].fd); hidpp_device_set_log_handler(&base, hidpp10_log, HIDPP_LOG_PRIORITY_RAW, device); typestr = ratbag_device_data_hidpp10_get_profile_type(device->data); if (typestr) { if (strcasecmp("G500", typestr) == 0) type = HIDPP10_PROFILE_G500; else if (strcasecmp("G700", typestr) == 0) type = HIDPP10_PROFILE_G700; else if (strcasecmp("G9", typestr) == 0) type = HIDPP10_PROFILE_G9; profile_count = ratbag_device_data_hidpp10_get_profile_count(device->data); } device_idx = ratbag_device_data_hidpp10_get_index(device->data); if (device_idx == -1) device_idx = HIDPP_WIRED_DEVICE_IDX; /* In the general case, we can treat all devices as wired devices * here. If we talk to the correct hidraw device the kernel adjusts * the device index for us, so even for unifying receiver devices * we can just use 0x00 as device index. * * If there is a special need like for G700(s), then add a DeviceIndex * entry to the .device file. */ dev = hidpp10_device_new(&base, device_idx, type, profile_count); if (!dev) { log_error(device->ratbag, "Failed to get HID++1.0 device for %s\n", device->name); goto err; } if (type != HIDPP10_PROFILE_UNKNOWN) { _cleanup_(dpi_list_freep) struct dpi_list *list = NULL; _cleanup_(freep) struct dpi_range *range = NULL; range = ratbag_device_data_hidpp10_get_dpi_range(device->data); if (range) { rc = hidpp10_build_dpi_table_from_dpi_info(dev, range); if (rc) log_error(device->ratbag, "Error parsing DpiRange for %s\n", device->name); } list = ratbag_device_data_hidpp10_get_dpi_list(device->data); if (list) { rc = hidpp10_build_dpi_table_from_list(dev, list); if (rc) log_error(device->ratbag, "Error parsing DpiList for %s\n", device->name); } if (!dev->dpi_count) log_info(device->ratbag, "Device %s might have wrong dpi settings. " "Please adjust the .device file.\n", device->name); } rc = hidpp10_device_read_profiles(dev); if (rc) goto err; drv_data->dev = dev; ratbag_set_drv_data(device, drv_data); if (hidpp10drv_fill_from_profile(device, dev)) { /* Fall back to something that every mouse has */ _cleanup_profile_ struct ratbag_profile *profile; ratbag_device_init_profiles(device, 1, 1, 3, 0); profile = ratbag_device_get_profile(device, 0); profile->is_active = true; } ratbag_device_for_each_profile(device, profile) hidpp10drv_read_profile(profile); if (device->num_profiles == 1) { _cleanup_profile_ struct ratbag_profile *profile; profile = ratbag_device_get_profile(device, 0); if (!profile->is_active) { log_debug(device->ratbag, "%s: forcing profile 0 to active.\n", device->name); profile->is_active = true; } } return 0; err: free(drv_data); ratbag_set_drv_data(device, NULL); if (dev) hidpp10_device_destroy(dev); return rc; } static void hidpp10drv_remove(struct ratbag_device *device) { struct hidpp10drv_data *drv_data; struct hidpp10_device *dev; ratbag_close_hidraw(device); drv_data = ratbag_get_drv_data(device); dev = drv_data->dev; hidpp10_device_destroy(dev); free(drv_data); } struct ratbag_driver hidpp10_driver = { .name = "Logitech HID++1.0", .id = "hidpp10", .probe = hidpp10drv_probe, .remove = hidpp10drv_remove, .set_active_profile = hidpp10drv_set_current_profile, .commit = hidpp10drv_commit, }; libratbag-0.13/src/driver-hidpp20.c000066400000000000000000001407031362011324700170620ustar00rootroot00000000000000/* * Copyright 2013-2015 Benjamin Tissoires * Copyright 2013-2015 Red Hat, Inc * * 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 (including the next * paragraph) 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. */ /* * Based on the HID++ 1.0 documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ /* * for this driver to work, you need a kernel >= v3.19 or one which contains * 925f0f3ed24f98b40c28627e74ff3e7f9d1e28bc ("HID: logitech-dj: allow transfer * of HID++ reports from/to the correct dj device") */ #include "config.h" #include #include #include #include #include #include "hidpp20.h" #include "libratbag-private.h" #include "libratbag-hidraw.h" #include "libratbag-data.h" #define HIDPP_CAP_RESOLUTION_2200 (1 << 0) #define HIDPP_CAP_SWITCHABLE_RESOLUTION_2201 (1 << 1) #define HIDPP_CAP_BUTTON_KEY_1b04 (1 << 2) #define HIDPP_CAP_BATTERY_LEVEL_1000 (1 << 3) #define HIDPP_CAP_KBD_REPROGRAMMABLE_KEYS_1b00 (1 << 4) #define HIDPP_CAP_COLOR_LED_EFFECTS_8070 (1 << 5) #define HIDPP_CAP_ONBOARD_PROFILES_8100 (1 << 6) #define HIDPP_CAP_LED_SW_CONTROL_1300 (1 << 7) #define HIDPP_CAP_ADJUSTIBLE_REPORT_RATE_8060 (1 << 8) #define HIDPP_CAP_BATTERY_VOLTAGE_1001 (1 << 9) #define HIDPP_CAP_RGB_EFFECTS_8071 (1 << 10) #define HIDPP_HIDDEN_FEATURE (1 << 6) struct hidpp20drv_data { struct hidpp20_device *dev; unsigned long capabilities; unsigned num_sensors; struct hidpp20_sensor *sensors; unsigned num_controls; struct hidpp20_control_id *controls; struct hidpp20_profiles *profiles; struct hidpp20_led *leds; union hidpp20_generic_led_zone_info led_infos; unsigned int report_rates[4]; unsigned int num_report_rates; unsigned int num_profiles; unsigned int num_resolutions; unsigned int num_buttons; unsigned int num_leds; }; static void hidpp20drv_read_button_1b04(struct ratbag_button *button) { struct ratbag_device *device = button->profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp20_control_id *control; const struct ratbag_button_action *action; uint16_t mapping; if (!(drv_data->capabilities & HIDPP_CAP_BUTTON_KEY_1b04)) return; control = &drv_data->controls[button->index]; mapping = control->control_id; if (control->reporting.remapped) mapping = control->reporting.remapped; log_raw(device->ratbag, " - button%d: %s (%02x) %s%s:%d\n", button->index, hidpp20_1b04_get_logical_mapping_name(mapping), mapping, control->reporting.divert || control->reporting.persist ? "(redirected) " : "", __FILE__, __LINE__); button->type = hidpp20_1b04_get_physical_mapping(control->task_id); action = hidpp20_1b04_get_logical_mapping(mapping); if (action) ratbag_button_set_action(button, action); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); } static unsigned int hidpp20drv_read_macro_key_8100(struct ratbag_device *device, union hidpp20_macro_data *macro) { switch (macro->key.modifier) { case 0x01: return KEY_LEFTCTRL; case 0x02: return KEY_LEFTSHIFT; case 0x04: return KEY_LEFTALT; case 0x08: return KEY_LEFTMETA; case 0x10: return KEY_RIGHTCTRL; case 0x20: return KEY_RIGHTSHIFT; case 0x40: return KEY_RIGHTALT; case 0x80: return KEY_RIGHTMETA; } return ratbag_hidraw_get_keycode_from_keyboard_usage(device, macro->key.key); } static int hidpp20drv_read_macro_8100(struct ratbag_button *button, struct hidpp20_profile *profile, union hidpp20_button_binding *binding) { struct ratbag_device *device = button->profile->device; struct ratbag_button_macro *m; union hidpp20_macro_data *macro; unsigned int i, keycode; bool delay = true; macro = profile->macros[binding->macro.page]; if (!macro) return -EINVAL; i = 0; m = ratbag_button_macro_new("macro"); while (macro && macro->any.type != HIDPP20_MACRO_END && i < MAX_MACRO_EVENTS) { switch (macro->any.type) { case HIDPP20_MACRO_DELAY: ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, macro->delay.time); delay = true; break; case HIDPP20_MACRO_KEY_PRESS: keycode = hidpp20drv_read_macro_key_8100(device, macro); if (!delay) ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, 1); ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_KEY_PRESSED, keycode); delay = false; break; case HIDPP20_MACRO_KEY_RELEASE: keycode = hidpp20drv_read_macro_key_8100(device, macro); if (!delay) ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, 1); ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_KEY_RELEASED, keycode); delay = false; break; } macro++; } ratbag_button_copy_macro(button, m); ratbag_button_macro_unref(m); return 0; } static void hidpp20drv_read_button_8100(struct ratbag_button *button) { struct ratbag_device *device = button->profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp20_profile *profile; unsigned int modifiers = 0; int rc; if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)) return; profile = &drv_data->profiles->profiles[button->profile->index]; switch (profile->buttons[button->index].any.type) { case HIDPP20_BUTTON_HID_TYPE: switch (profile->buttons[button->index].subany.subtype) { case HIDPP20_BUTTON_HID_TYPE_MOUSE: button->action.type = RATBAG_BUTTON_ACTION_TYPE_BUTTON; button->action.action.button = profile->buttons[button->index].button.buttons; break; case HIDPP20_BUTTON_HID_TYPE_KEYBOARD: button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; button->action.action.key.key = ratbag_hidraw_get_keycode_from_keyboard_usage(device, profile->buttons[button->index].keyboard_keys.key); modifiers = profile->buttons[button->index].keyboard_keys.modifier_flags; break; case HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL: button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; button->action.action.key.key = ratbag_hidraw_get_keycode_from_consumer_usage(device, profile->buttons[button->index].consumer_control.consumer_control); break; } break; case HIDPP20_BUTTON_SPECIAL: button->action.type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; button->action.action.special = hidpp20_onboard_profiles_get_special(profile->buttons[button->index].special.special); break; case HIDPP20_BUTTON_MACRO: rc = hidpp20drv_read_macro_8100(button, profile, &profile->buttons[button->index]); if (rc) button->action.type = RATBAG_BUTTON_ACTION_TYPE_NONE; break; default: button->action.type = RATBAG_BUTTON_ACTION_TYPE_UNKNOWN; break; } if (button->action.type == RATBAG_BUTTON_ACTION_TYPE_KEY) { int rc; rc = ratbag_button_macro_new_from_keycode(button, button->action.action.key.key, modifiers); if (rc < 0) { log_error(device->ratbag, "Error while reading button %d\n", button->index); button->action.type = RATBAG_BUTTON_ACTION_TYPE_NONE; } } ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); } static void hidpp20drv_read_button(struct ratbag_button *button) { hidpp20drv_read_button_1b04(button); hidpp20drv_read_button_8100(button); } static void hidpp20drv_read_led_1300(struct ratbag_led *led, struct hidpp20drv_data* data) { struct hidpp20_led_sw_ctrl_led_state state; struct hidpp20_led_sw_ctrl_led_info *info; int rc; info = &data->led_infos.leds[led->index]; rc = hidpp20_led_sw_control_get_led_state(data->dev, led->index, &state); if (rc != 0) return; led->colordepth = RATBAG_LED_COLORDEPTH_MONOCHROME; switch (info->type) { case HIDPP20_LED_TYPE_LOGO: led->type = RATBAG_LED_TYPE_LOGO; break; case HIDPP20_LED_TYPE_BATTERY: led->type = RATBAG_LED_TYPE_BATTERY; break; case HIDPP20_LED_TYPE_DPI: led->type = RATBAG_LED_TYPE_DPI; break; case HIDPP20_LED_TYPE_PROFILE: case HIDPP20_LED_TYPE_COSMETIC: led->type = RATBAG_LED_TYPE_SIDE; break; } switch (state.mode) { case HIDPP20_LED_MODE_OFF: led->mode = RATBAG_LED_OFF; break; case HIDPP20_LED_MODE_ON: led->mode = RATBAG_LED_ON; break; case HIDPP20_LED_MODE_BREATHING: led->mode = RATBAG_LED_BREATHING; led->ms = state.breathing.period; led->brightness = state.breathing.brightness; break; default: led->mode = RATBAG_LED_ON; } if (info->caps & HIDPP20_LED_MODE_ON) ratbag_led_set_mode_capability(led, RATBAG_LED_ON); if (info->caps & HIDPP20_LED_MODE_OFF) ratbag_led_set_mode_capability(led, RATBAG_LED_OFF); if (info->caps & (HIDPP20_LED_MODE_BLINK | HIDPP20_LED_MODE_TRAVEL | HIDPP20_LED_MODE_RAMP_UP | HIDPP20_LED_MODE_RAMP_DOWN | HIDPP20_LED_MODE_HEARTBEAT | HIDPP20_LED_MODE_BREATHING)) ratbag_led_set_mode_capability(led, RATBAG_LED_BREATHING); } static void hidpp20drv_read_led_8070(struct ratbag_led *led, struct hidpp20drv_data* drv_data) { struct hidpp20_profile *profile; struct hidpp20_led h_led_val; struct hidpp20_led *h_led = &h_led_val; struct hidpp20_color_led_zone_info* led_info; struct hidpp20_color_led_info info; int rc; led_info = &drv_data->led_infos.color_leds_8070[led->index]; if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100) { profile = &drv_data->profiles->profiles[led->profile->index]; h_led = &profile->leds[led->index]; } else { rc = hidpp20_color_led_effects_get_zone_effect(drv_data->dev, led->index, h_led); if (rc) { log_debug(led->profile->device->ratbag, "Failed to read led settings\n"); } } switch (h_led->mode) { case HIDPP20_LED_ON: led->mode = RATBAG_LED_ON; break; case HIDPP20_LED_CYCLE: led->mode = RATBAG_LED_CYCLE; break; case HIDPP20_LED_BREATHING: led->mode = RATBAG_LED_BREATHING; break; default: led->mode = RATBAG_LED_OFF; break; } /* pre-filled, only override if unknown */ if (led->type == RATBAG_LED_TYPE_UNKNOWN) led->type = hidpp20_led_get_location_mapping(led_info->location); led->color.red = h_led->color.red; led->color.green = h_led->color.green; led->color.blue = h_led->color.blue; led->ms = h_led->period; led->brightness = h_led->brightness * 255 / 100; rc = hidpp20_color_led_effects_get_info(drv_data->dev, &info); if (rc == 0 && info.ext_caps & HIDPP20_COLOR_LED_INFO_EXT_CAP_MONOCHROME_ONLY) led->colordepth = RATBAG_LED_COLORDEPTH_MONOCHROME; else led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888; for (int i = 0; i < led_info->num_effects; i++) { struct hidpp20_color_led_zone_effect_info ei; rc = hidpp20_color_led_effect_get_zone_effect_info(drv_data->dev, led_info->index, i, &ei); if (rc < 0) break; switch (ei.effect_id) { case HIDPP20_COLOR_LED_ZONE_EFFECT_DISABLED: ratbag_led_set_mode_capability(led, RATBAG_LED_OFF); break; case HIDPP20_COLOR_LED_ZONE_EFFECT_FIXED: ratbag_led_set_mode_capability(led, RATBAG_LED_ON); break; case HIDPP20_COLOR_LED_ZONE_EFFECT_CYCLING: ratbag_led_set_mode_capability(led, RATBAG_LED_CYCLE); break; case HIDPP20_COLOR_LED_ZONE_EFFECT_WAVE: case HIDPP20_COLOR_LED_ZONE_EFFECT_BREATHING: ratbag_led_set_mode_capability(led, RATBAG_LED_BREATHING); break; default: log_debug(led->profile->device->ratbag, "%s: Unsupported effect (%d)\n", led->profile->device->name, ei.effect_id); break; } } } static void hidpp20drv_read_led_8071(struct ratbag_led *led, struct hidpp20drv_data* drv_data) { struct hidpp20_profile *profile; struct hidpp20_led *h_led; struct hidpp20_rgb_device_info device_info; struct hidpp20_rgb_cluster_info cluster_info; int rc; hidpp20_rgb_effects_get_device_info(drv_data->dev, &device_info); cluster_info = drv_data->led_infos.color_leds_8071[led->index]; profile = &drv_data->profiles->profiles[led->profile->index]; h_led = &profile->leds[led->index]; switch (h_led->mode) { case HIDPP20_LED_ON: led->mode = RATBAG_LED_ON; break; case HIDPP20_LED_CYCLE: led->mode = RATBAG_LED_CYCLE; break; case HIDPP20_LED_BREATHING: led->mode = RATBAG_LED_BREATHING; break; default: led->mode = RATBAG_LED_OFF; break; } /* pre-filled, only override if unknown */ if (led->type == RATBAG_LED_TYPE_UNKNOWN) led->type = hidpp20_led_get_location_mapping(cluster_info.location); led->color.red = h_led->color.red; led->color.green = h_led->color.green; led->color.blue = h_led->color.blue; led->ms = h_led->period; led->brightness = h_led->brightness * 255 / 100; if (device_info.ext_caps & HIDPP20_COLOR_LED_INFO_EXT_CAP_MONOCHROME_ONLY) led->colordepth = RATBAG_LED_COLORDEPTH_MONOCHROME; else led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888; for (int i = 0; i < cluster_info.num_effects; i++) { struct hidpp20_rgb_effect_info ei; rc = hidpp20_rgb_effects_get_effect_info(drv_data->dev, cluster_info.index, i, &ei); if (rc < 0) break; switch (ei.effect_id) { case HIDPP20_COLOR_LED_ZONE_EFFECT_DISABLED: ratbag_led_set_mode_capability(led, RATBAG_LED_OFF); break; case HIDPP20_COLOR_LED_ZONE_EFFECT_FIXED: ratbag_led_set_mode_capability(led, RATBAG_LED_ON); break; case HIDPP20_COLOR_LED_ZONE_EFFECT_CYCLING: ratbag_led_set_mode_capability(led, RATBAG_LED_CYCLE); break; case HIDPP20_COLOR_LED_ZONE_EFFECT_WAVE: case HIDPP20_COLOR_LED_ZONE_EFFECT_BREATHING: ratbag_led_set_mode_capability(led, RATBAG_LED_BREATHING); break; default: log_debug(led->profile->device->ratbag, "%s: Unsupported effect (%d)\n", led->profile->device->name, ei.effect_id); break; } } } static void hidpp20drv_read_led(struct ratbag_led *led) { struct ratbag_device *device = led->profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); if (drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070) hidpp20drv_read_led_8070(led, drv_data); else if (drv_data->capabilities & HIDPP_CAP_RGB_EFFECTS_8071) hidpp20drv_read_led_8071(led, drv_data); else if (drv_data->capabilities & HIDPP_CAP_LED_SW_CONTROL_1300) hidpp20drv_read_led_1300(led, drv_data); } static int hidpp20drv_update_button_1b04(struct ratbag_button *button) { struct ratbag_device *device = button->profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp20_control_id *control; struct ratbag_button_action *action = &button->action; uint16_t mapping; int rc; if (!(drv_data->capabilities & HIDPP_CAP_BUTTON_KEY_1b04)) return -ENOTSUP; control = &drv_data->controls[button->index]; mapping = hidpp20_1b04_get_logical_control_id(action); if (!mapping) return -EINVAL; control->reporting.divert = 0; control->reporting.remapped = mapping; control->reporting.updated = 1; rc = hidpp20_special_key_mouse_set_control(drv_data->dev, control); if (rc) log_error(device->ratbag, "Error while writing profile: '%s' (%d)\n", strerror(-rc), rc); return rc; } static int hidpp20drv_update_button_8100(struct ratbag_button *button) { struct ratbag_device *device = button->profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp20_profile *profile; struct ratbag_button_action *action = &button->action; unsigned int modifiers, key; int rc; uint8_t code, type, subtype; if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)) return -ENOTSUP; profile = &drv_data->profiles->profiles[button->profile->index]; switch (action->type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: profile->buttons[button->index].button.type = HIDPP20_BUTTON_HID_TYPE; profile->buttons[button->index].button.subtype = HIDPP20_BUTTON_HID_TYPE_MOUSE; profile->buttons[button->index].button.buttons = action->action.button; break; case RATBAG_BUTTON_ACTION_TYPE_MACRO: type = HIDPP20_BUTTON_HID_TYPE; subtype = HIDPP20_BUTTON_HID_TYPE_KEYBOARD; rc = ratbag_action_keycode_from_macro(action, &key, &modifiers); if (rc < 0) { log_error(device->ratbag, "Error while writing macro for button %d\n", button->index); } code = ratbag_hidraw_get_keyboard_usage_from_keycode(device, key); if (code == 0) { subtype = HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL; code = ratbag_hidraw_get_consumer_usage_from_keycode(device, action->action.key.key); if (code == 0) return -EINVAL; } profile->buttons[button->index].subany.type = type; profile->buttons[button->index].subany.subtype = subtype; if (subtype == HIDPP20_BUTTON_HID_TYPE_KEYBOARD) { profile->buttons[button->index].keyboard_keys.key = code; profile->buttons[button->index].keyboard_keys.modifier_flags = modifiers; } else { profile->buttons[button->index].consumer_control.consumer_control = code; } break; case RATBAG_BUTTON_ACTION_TYPE_KEY: type = HIDPP20_BUTTON_HID_TYPE; subtype = HIDPP20_BUTTON_HID_TYPE_KEYBOARD; code = ratbag_hidraw_get_keyboard_usage_from_keycode(device, action->action.key.key); if (code == 0) { subtype = HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL; code = ratbag_hidraw_get_consumer_usage_from_keycode(device, action->action.key.key); if (code == 0) return -EINVAL; } profile->buttons[button->index].subany.type = type; profile->buttons[button->index].subany.subtype = subtype; if (subtype == HIDPP20_BUTTON_HID_TYPE_KEYBOARD) profile->buttons[button->index].keyboard_keys.key = code; else profile->buttons[button->index].consumer_control.consumer_control = code; break; case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: code = hidpp20_onboard_profiles_get_code_from_special(action->action.special); if (code == 0) return -EINVAL; profile->buttons[button->index].special.type = HIDPP20_BUTTON_SPECIAL; profile->buttons[button->index].special.special = code; break; default: return -ENOTSUP; } return 0; } static int hidpp20drv_update_button(struct ratbag_button *button) { struct ratbag_device *device = button->profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100) return hidpp20drv_update_button_8100(button); if (drv_data->capabilities & HIDPP_CAP_BUTTON_KEY_1b04) return hidpp20drv_update_button_1b04(button); return -ENOTSUP; } static int hidpp20drv_update_led_1300(struct ratbag_led *led, struct hidpp20drv_data *data) { const uint16_t led_caps = data->led_infos.leds[led->index].caps; struct hidpp20_led_sw_ctrl_led_state h_led; int rc; h_led.index = led->index; switch(led->mode) { case RATBAG_LED_BREATHING: h_led.mode = HIDPP20_LED_MODE_BREATHING; h_led.breathing.brightness = led->brightness; h_led.breathing.period = led->ms; h_led.breathing.timeout = 300; break; case RATBAG_LED_OFF: h_led.mode = HIDPP20_LED_MODE_OFF; h_led.on.index = HIDPP20_LED_SW_CONTROL_LED_INDEX_ALL; break; case RATBAG_LED_ON: h_led.mode = HIDPP20_LED_MODE_ON; h_led.on.index = HIDPP20_LED_SW_CONTROL_LED_INDEX_ALL; break; case RATBAG_LED_CYCLE: return -ENOTSUP; } if (!(h_led.mode & led_caps)) { hidpp_log_error(&data->dev->base, "LED %d does not support effect %s(%04x), supports %04x\n", led->index, hidpp20_sw_led_control_get_mode_string(h_led.mode), h_led.mode, led_caps); return -ENOTSUP; } if (!hidpp20_led_sw_control_get_sw_ctrl(data->dev)) { rc = hidpp20_led_sw_control_set_sw_ctrl(data->dev, true); if (rc) return rc; } rc = hidpp20_led_sw_control_set_led_state(data->dev, &h_led); if (rc) return rc; /* * We revert to HW control of the LEDs. * This effectively disable the capability of changing the DPI/profile * LEDs, but this allows to make them work with the internal DPI/profile * switches. * * The net result is: * - DPI/profiles can't be controlled (but who wants this?) * - The logo can properly controlled */ rc = hidpp20_led_sw_control_set_sw_ctrl(data->dev, false); if (rc) return rc; return RATBAG_SUCCESS; } static int hidpp20drv_update_led_8070_8071(struct ratbag_led *led, struct ratbag_profile* profile, struct hidpp20drv_data *drv_data) { struct hidpp20_profile *h_profile; struct hidpp20_led h_led_val; struct hidpp20_led *h_led = &h_led_val; if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100) { h_profile = &drv_data->profiles->profiles[profile->index]; h_led = &(h_profile->leds[led->index]); } if (!h_led) return -EINVAL; switch (led->mode) { case RATBAG_LED_ON: h_led->mode = HIDPP20_LED_ON; break; case RATBAG_LED_CYCLE: h_led->mode = HIDPP20_LED_CYCLE; break; case RATBAG_LED_BREATHING: h_led->mode = HIDPP20_LED_BREATHING; break; default: h_led->mode = HIDPP20_LED_OFF; break; } h_led->color.red = led->color.red; h_led->color.green = led->color.green; h_led->color.blue = led->color.blue; h_led->period = led->ms; h_led->brightness = led->brightness * 100 / 255; if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)) { if (drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070) hidpp20_color_led_effects_set_zone_effect(drv_data->dev, led->index, h_led_val); } return RATBAG_SUCCESS; } static int hidpp20drv_update_led(struct ratbag_led *led) { struct ratbag_profile *profile = led->profile; struct ratbag_device *device = profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); if ((drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070) | (drv_data->capabilities & HIDPP_CAP_RGB_EFFECTS_8071)) return hidpp20drv_update_led_8070_8071(led, profile, drv_data); if (drv_data->capabilities & HIDPP_CAP_LED_SW_CONTROL_1300) return hidpp20drv_update_led_1300(led, drv_data); return RATBAG_ERROR_CAPABILITY; } static int hidpp20drv_current_profile(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data((struct ratbag_device *)device); int rc; if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)) return 0; rc = hidpp20_onboard_profiles_get_current_profile(drv_data->dev); if (rc < 0) return rc; return rc - 1; } static int hidpp20drv_set_current_profile(struct ratbag_device *device, unsigned int index) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data((struct ratbag_device *)device); struct hidpp20_profile *h_profile; int rc; if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)) return 0; if (index >= drv_data->num_profiles) return -EINVAL; h_profile = &drv_data->profiles->profiles[index]; if (!h_profile->enabled) { h_profile->enabled = 1; rc = hidpp20_onboard_profiles_commit(drv_data->dev, drv_data->profiles); if (rc) return rc; } return hidpp20_onboard_profiles_set_current_profile(drv_data->dev, index); } static int hidpp20drv_read_resolution_dpi_2201(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct ratbag *ratbag = device->ratbag; int rc; free(drv_data->sensors); drv_data->sensors = NULL; drv_data->num_sensors = 0; rc = hidpp20_adjustable_dpi_get_sensors(drv_data->dev, &drv_data->sensors); if (rc < 0) { log_error(ratbag, "Error while requesting resolution: %s (%d)\n", strerror(-rc), rc); return rc; } else if (rc == 0) { log_error(ratbag, "Error, no compatible sensors found.\n"); return -ENODEV; } log_debug(ratbag, "device is at %d dpi (variable between %d and %d).\n", drv_data->sensors[0].dpi, drv_data->sensors[0].dpi_min, drv_data->sensors[0].dpi_max); drv_data->num_sensors = rc; /* if 0x8100 has already been enumerated we already have the supported * number of resolutions and shouldn't overwrite it */ if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)) drv_data->num_resolutions = drv_data->num_sensors; return 0; } static int hidpp20drv_read_report_rate_8060(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct ratbag *ratbag = device->ratbag; struct ratbag_profile *profile; uint8_t bitflags_ms; int nrates = 0; int rc; uint8_t rate_ms; unsigned rate_hz; rc = hidpp20_adjustable_report_rate_get_report_rate_list(drv_data->dev, &bitflags_ms); if (rc < 0) return rc; /* We only care about 'standard' rates */ if (bitflags_ms & 0x80) drv_data->report_rates[nrates++] = 125; if (bitflags_ms & 0x8) drv_data->report_rates[nrates++] = 250; if (bitflags_ms & 0x2) drv_data->report_rates[nrates++] = 500; if (bitflags_ms & 0x1) drv_data->report_rates[nrates++] = 1000; drv_data->num_report_rates = nrates; if (!hidpp20_adjustable_report_rate_get_report_rate(drv_data->dev, &rate_ms)) { switch (rate_ms) { case 1: rate_hz = 1000; break; case 2: case 3: /* 3ms = 333.(3)Hz, we round to 500Hz */ rate_hz = 500; break; case 4: case 5: /* 5ms = 200Hz, we round to 250Hz */ rate_hz = 250; break; case 6: /* 6ms = 166.(6)Hz, we round to 125Hz */ case 7: /* 7ms = 142Hz, we round to 125Hz */ case 8: rate_hz = 125; break; default: rate_hz = 0; break; } if (rate_hz) { log_debug(ratbag, "report rate is %u\n", rate_hz); ratbag_device_for_each_profile(device, profile) profile->hz = rate_hz; } } log_debug(ratbag, "device has %d report rates\n", nrates); return 0; } static int hidpp20drv_read_resolution_dpi(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct ratbag *ratbag = device->ratbag; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct ratbag_resolution *res; unsigned int default_dpi = 1000; unsigned int default_rate = 500; int rc; if ((drv_data->capabilities & HIDPP_CAP_RESOLUTION_2200) && (drv_data->capabilities & HIDPP_CAP_SWITCHABLE_RESOLUTION_2201) == 0) { uint16_t resolution; uint8_t flags; rc = hidpp20_mousepointer_get_mousepointer_info(drv_data->dev, &resolution, &flags); if (rc) { log_error(ratbag, "Error while requesting resolution: %s (%d)\n", strerror(-rc), rc); return rc; } return 0; } if (drv_data->capabilities & HIDPP_CAP_SWITCHABLE_RESOLUTION_2201) { rc = hidpp20drv_read_resolution_dpi_2201(device); if (rc < 0) return rc; ratbag_profile_for_each_resolution(profile, res) { struct hidpp20_sensor *sensor; /* We only look at the first sensor. Multiple * sensors is too niche to care about right now */ sensor = &drv_data->sensors[0]; /* FIXME: retrieve the refresh rate */ ratbag_resolution_set_resolution(res, sensor->dpi, sensor->dpi); ratbag_resolution_set_dpi_list_from_range(res, sensor->dpi_min, sensor->dpi_max); /* FIXME: we mark all resolutions as active because * they are from different sensors */ res->is_active = true; } } else { ratbag_profile_for_each_resolution(profile, res) ratbag_resolution_set_dpi_list(res, &default_dpi, 1); } if (drv_data->capabilities & HIDPP_CAP_ADJUSTIBLE_REPORT_RATE_8060) { rc = hidpp20drv_read_report_rate_8060(device); if (rc < 0) return rc; ratbag_profile_set_report_rate_list(profile, drv_data->report_rates, drv_data->num_report_rates); } else { ratbag_profile_set_report_rate_list(profile, &default_rate, 1); } return 0; } static int hidpp20drv_update_resolution_dpi_8100(struct ratbag_resolution *resolution, int dpi_x, int dpi_y) { struct ratbag_profile *profile = resolution->profile; struct ratbag_device *device = profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp20_profile *h_profile; int dpi = dpi_x; /* dpi_x == dpi_y if we don't have the individual resolution cap */ h_profile = &drv_data->profiles->profiles[profile->index]; h_profile->dpi[resolution->index] = dpi; if (resolution->is_default) h_profile->default_dpi = resolution->index; return RATBAG_SUCCESS; } static int hidpp20drv_update_resolution_dpi(struct ratbag_resolution *resolution, int dpi_x, int dpi_y) { struct ratbag_profile *profile = resolution->profile; struct ratbag_device *device = profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp20_sensor *sensor; int i; int dpi = dpi_x; /* dpi_x == dpi_y if we don't have the individual resolution cap */ if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100) return hidpp20drv_update_resolution_dpi_8100(resolution, dpi_x, dpi_y); if (!(drv_data->capabilities & HIDPP_CAP_SWITCHABLE_RESOLUTION_2201)) return -ENOTSUP; if (!drv_data->num_sensors) return -ENOTSUP; /* just for clarity, we use the first available sensor only */ sensor = &drv_data->sensors[0]; /* validate that the sensor accepts the given DPI */ if (dpi < sensor->dpi_min || dpi > sensor->dpi_max) return -EINVAL; if (sensor->dpi_steps) { for (i = sensor->dpi_min; i < dpi; i += sensor->dpi_steps) { } if (i != dpi) return -EINVAL; } else { i = 0; while (sensor->dpi_list[i]) { if (sensor->dpi_list[i] == dpi) break; } if (sensor->dpi_list[i] != dpi) return -EINVAL; } return hidpp20_adjustable_dpi_set_sensor_dpi(drv_data->dev, sensor, dpi); } static int hidpp20drv_update_report_rate_8060(struct ratbag_profile *profile, int hz) { struct ratbag_device *device = profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); int rc; rc = hidpp20_adjustable_report_rate_set_report_rate(drv_data->dev, 1000/hz); if (rc) return rc; return RATBAG_SUCCESS; } static int hidpp20drv_update_report_rate_8100(struct ratbag_profile *profile, int hz) { struct ratbag_device *device = profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp20_profile *h_profile; h_profile = &drv_data->profiles->profiles[profile->index]; h_profile->report_rate = hz; return RATBAG_SUCCESS; } static int hidpp20drv_update_report_rate(struct ratbag_profile *profile, int hz) { struct ratbag_device *device = profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); int rc; if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100) return hidpp20drv_update_report_rate_8100(profile, hz); if (drv_data->capabilities & HIDPP_CAP_ADJUSTIBLE_REPORT_RATE_8060) { rc = hidpp20drv_update_report_rate_8060(profile, hz); /* re-populate the profile with the correct value if we fail */ if (rc) hidpp20drv_read_report_rate_8060(profile->device); return rc; } return -ENOTSUP; } static int hidpp20drv_read_special_key_mouse(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); int rc; if (!(drv_data->capabilities & HIDPP_CAP_BUTTON_KEY_1b04)) return 0; free(drv_data->controls); drv_data->controls = NULL; drv_data->num_controls = 0; rc = hidpp20_special_key_mouse_get_controls(drv_data->dev, &drv_data->controls); if (rc > 0) { drv_data->num_controls = rc; rc = 0; } return rc; } static int hidpp20drv_read_kbd_reprogrammable_key(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); int rc; if (!(drv_data->capabilities & HIDPP_CAP_KBD_REPROGRAMMABLE_KEYS_1b00)) return 0; free(drv_data->controls); drv_data->controls = NULL; drv_data->num_controls = 0; rc = hidpp20_kbd_reprogrammable_keys_get_controls(drv_data->dev, &drv_data->controls); if (rc > 0) { drv_data->num_controls = rc; rc = 0; } return rc; } static int hidpp20drv_read_color_leds_8070(struct hidpp20drv_data *drv_data) { int rc; free(drv_data->led_infos.color_leds_8070); drv_data->led_infos.color_leds_8070 = NULL; drv_data->num_leds = 0; rc = hidpp20_color_led_effects_get_zone_infos(drv_data->dev, &drv_data->led_infos.color_leds_8070); if (rc > 0) { drv_data->num_leds = rc; rc = 0; } return rc; } static int hidpp20drv_read_color_leds_8071(struct hidpp20drv_data *drv_data) { int rc; free(drv_data->led_infos.color_leds_8071); drv_data->led_infos.color_leds_8071 = NULL; drv_data->num_leds = 0; rc = hidpp20_rgb_effects_get_cluster_infos(drv_data->dev, &drv_data->led_infos.color_leds_8071); if (rc > 0) { drv_data->num_leds = rc; rc = 0; } return rc; } static int hidpp20drv_read_color_leds(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); if(drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070) return hidpp20drv_read_color_leds_8070(drv_data); else if (drv_data->capabilities & HIDPP_CAP_RGB_EFFECTS_8071) return hidpp20drv_read_color_leds_8071(drv_data); return 0; } static int hidpp20drv_read_leds(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); int rc; if (!(drv_data->capabilities & HIDPP_CAP_LED_SW_CONTROL_1300)) return 0; free(drv_data->led_infos.leds); drv_data->led_infos.leds = NULL; rc = hidpp20_led_sw_control_read_leds(drv_data->dev, &drv_data->led_infos.leds); if (rc > 0) { drv_data->num_leds = rc; rc = 0; } return rc; } static void hidpp20drv_read_profile_8100(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct ratbag_resolution *res; struct hidpp20_profile *p; int dpi_index = 0xff; profile->is_enabled = drv_data->profiles->profiles[profile->index].enabled; profile->is_active = false; if ((int)profile->index == hidpp20drv_current_profile(device)) profile->is_active = true; if (profile->is_active) dpi_index = hidpp20_onboard_profiles_get_current_dpi_index(drv_data->dev); if (dpi_index < 0) dpi_index = 0xff; p = &drv_data->profiles->profiles[profile->index]; ratbag_profile_for_each_resolution(profile, res) { struct hidpp20_sensor *sensor; /* We only look at the first sensor. Multiple * sensors is too niche to care about right now */ sensor = &drv_data->sensors[0]; ratbag_resolution_set_resolution(res, p->dpi[res->index], p->dpi[res->index]); if (profile->is_active && res->index == (unsigned int)dpi_index) res->is_active = true; if (res->index == p->default_dpi) { res->is_default = true; if (!profile->is_active || dpi_index < 0 || dpi_index > 4) res->is_active = true; } ratbag_resolution_set_dpi_list_from_range(res, sensor->dpi_min, sensor->dpi_max); } ratbag_profile_set_report_rate_list(profile, drv_data->report_rates, drv_data->num_report_rates); ratbag_profile_set_report_rate(profile, p->report_rate); } static int hidpp20drv_init_leds_8070_8071(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct ratbag *ratbag = device->ratbag; /* we only support 0x8071 via 0x8100 */ if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100) && drv_data->capabilities & HIDPP_CAP_RGB_EFFECTS_8071) { log_debug(ratbag, "disabling 0x8071 (RGB Effects) feature because the device doesn't have 0x8100 (Onboard Memory Profiles)\n"); drv_data->capabilities &= ~HIDPP_CAP_RGB_EFFECTS_8071; } if (drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070 || drv_data->capabilities & HIDPP_CAP_RGB_EFFECTS_8071) { /* we read the profile once to get the correct number of * supported leds. */ if (hidpp20drv_read_color_leds(device)) return 0; device->num_leds = drv_data->num_leds; } return 0; } static int hidpp20drv_init_profile_8100(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct ratbag *ratbag = device->ratbag; int rc; log_debug(ratbag, "initializing onboard profiles\n"); rc = hidpp20_onboard_profiles_allocate(drv_data->dev, &drv_data->profiles); if (rc < 0) return rc; rc = hidpp20_onboard_profiles_initialize(drv_data->dev, drv_data->profiles); if (rc < 0) return rc; drv_data->num_profiles = drv_data->profiles->num_profiles; drv_data->num_buttons = drv_data->profiles->num_buttons; if (drv_data->capabilities & HIDPP_CAP_SWITCHABLE_RESOLUTION_2201) drv_data->num_resolutions = drv_data->profiles->num_modes; /* We ignore the profile's num_leds and require * HIDPP_PAGE_COLOR_LED_EFFECTS to succeed instead */ return 0; } static void hidpp20drv_read_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct ratbag_led *led; struct ratbag_button *button; if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100) { hidpp20drv_read_profile_8100(profile); ratbag_profile_set_cap(profile, RATBAG_PROFILE_CAP_DISABLE); } else { hidpp20drv_read_resolution_dpi(profile); hidpp20drv_read_special_key_mouse(device); profile->is_active = (profile->index == 0); } ratbag_profile_for_each_led(profile, led) hidpp20drv_read_led(led); ratbag_profile_for_each_button(profile, button) hidpp20drv_read_button(button); } static int hidpp20drv_init_feature(struct ratbag_device *device, uint16_t feature) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct ratbag *ratbag = device->ratbag; int rc; uint8_t feature_index, feature_type, feature_version; rc = hidpp_root_get_feature(drv_data->dev, feature, &feature_index, &feature_type, &feature_version); if (rc < 0) return rc; /* check if it is a hidden feature */ if (feature_type & HIDPP_HIDDEN_FEATURE) return 0; switch (feature) { case HIDPP_PAGE_ROOT: case HIDPP_PAGE_FEATURE_SET: /* these features are mandatory and already handled */ break; case HIDPP_PAGE_MOUSE_POINTER_BASIC: { drv_data->capabilities |= HIDPP_CAP_RESOLUTION_2200; break; } case HIDPP_PAGE_ADJUSTABLE_DPI: { log_debug(ratbag, "device has adjustable dpi\n"); /* we read the profile once to get the correct number of * supported resolutions. */ rc = hidpp20drv_read_resolution_dpi_2201(device); if (rc < 0) return 0; /* this is not a hard failure */ drv_data->capabilities |= HIDPP_CAP_SWITCHABLE_RESOLUTION_2201; break; } case HIDPP_PAGE_SPECIAL_KEYS_BUTTONS: { log_debug(ratbag, "device has programmable keys/buttons\n"); drv_data->capabilities |= HIDPP_CAP_BUTTON_KEY_1b04; /* we read the profile once to get the correct number of * supported buttons. */ if (!hidpp20drv_read_special_key_mouse(device)) drv_data->num_buttons = drv_data->num_controls; break; } case HIDPP_PAGE_BATTERY_LEVEL_STATUS: { uint16_t level, next_level; enum hidpp20_battery_status status; rc = hidpp20_batterylevel_get_battery_level(drv_data->dev, &level, &next_level); if (rc < 0) return rc; status = rc; log_debug(ratbag, "device battery level is %d%% (next %d%%), status %d \n", level, next_level, status); drv_data->capabilities |= HIDPP_CAP_BATTERY_LEVEL_1000; break; } case HIDPP_PAGE_BATTERY_VOLTAGE: { uint16_t voltage; enum hidpp20_battery_voltage_status status; rc = hidpp20_batteryvoltage_get_battery_voltage(drv_data->dev, &voltage); if (rc < 0) return rc; status = rc; log_debug(ratbag, "device battery voltage is %dmv, status %02x \n", voltage, status); drv_data->capabilities |= HIDPP_CAP_BATTERY_VOLTAGE_1001; break; } case HIDPP_PAGE_KBD_REPROGRAMMABLE_KEYS: { log_debug(ratbag, "device has programmable keys/buttons\n"); drv_data->capabilities |= HIDPP_CAP_KBD_REPROGRAMMABLE_KEYS_1b00; /* we read the profile once to get the correct number of * supported buttons. */ if (!hidpp20drv_read_kbd_reprogrammable_key(device)) drv_data->num_buttons = drv_data->num_controls; break; } case HIDPP_PAGE_ADJUSTABLE_REPORT_RATE: { log_debug(ratbag, "device has adjustable report rate\n"); /* we read the profile once to get the correct number of * supported report rates. */ rc = hidpp20drv_read_report_rate_8060(device); if (rc < 0) return 0; /* this is not a hard failure */ drv_data->capabilities |= HIDPP_CAP_ADJUSTIBLE_REPORT_RATE_8060; break; } case HIDPP_PAGE_COLOR_LED_EFFECTS: { /* The 8070 feauture implemented in the G602 doesn't follow the spec, * so we ignore it */ if (ratbag_device_data_hidpp20_get_quirk(device->data) == HIDPP20_QUIRK_G602) break; log_debug(ratbag, "device has color effects\n"); drv_data->capabilities |= HIDPP_CAP_COLOR_LED_EFFECTS_8070; break; } case HIDPP_PAGE_RGB_EFFECTS: { log_debug(ratbag, "device has color effects\n"); drv_data->capabilities |= HIDPP_CAP_RGB_EFFECTS_8071; break; } case HIDPP_PAGE_LED_SW_CONTROL: { log_debug(ratbag, "device has non-rgb leds\n"); drv_data->capabilities |= HIDPP_CAP_LED_SW_CONTROL_1300; if (drv_data->capabilities & HIDPP_PAGE_COLOR_LED_EFFECTS) return 0; if (hidpp20drv_read_leds(device)) return 0; break; } case HIDPP_PAGE_ONBOARD_PROFILES: { log_debug(ratbag, "device has onboard profiles\n"); drv_data->capabilities |= HIDPP_CAP_ONBOARD_PROFILES_8100; break; } case HIDPP_PAGE_MOUSE_BUTTON_SPY: { log_debug(ratbag, "device has configurable mouse button spy\n"); break; } default: log_raw(device->ratbag, "unknown feature 0x%04x\n", feature); } return 0; } static int hidpp20drv_commit(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct ratbag_profile *profile; struct ratbag_button *button; struct ratbag_led *led; struct ratbag_resolution *resolution; int rc; list_for_each(profile, &device->profiles, link) { if (!profile->dirty) continue; if (profile->rate_dirty) { rc = hidpp20drv_update_report_rate(profile, profile->hz); if (rc) { log_error(device->ratbag, "hidpp20: failed to update report rate\n"); return RATBAG_ERROR_DEVICE; } } ratbag_profile_for_each_resolution(profile, resolution) { rc = hidpp20drv_update_resolution_dpi(resolution, resolution->dpi_x, resolution->dpi_y); if (rc) { log_error(device->ratbag, "hidpp20: failed to update resolution\n"); return RATBAG_ERROR_DEVICE; } } list_for_each(button, &profile->buttons, link) { if (!button->dirty) continue; rc = hidpp20drv_update_button(button); if (rc) { log_error(device->ratbag, "hidpp20: failed to update button\n"); return RATBAG_ERROR_DEVICE; } } list_for_each(led, &profile->leds, link) { if (!led->dirty) continue; rc = hidpp20drv_update_led(led); if (rc) { log_error(device->ratbag, "hidpp20: failed to update led\n"); return RATBAG_ERROR_DEVICE; } } } if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100) { list_for_each(profile, &device->profiles, link) drv_data->profiles->profiles[profile->index].enabled = profile->is_enabled; rc = hidpp20_onboard_profiles_commit(drv_data->dev, drv_data->profiles); if (rc) { log_error(device->ratbag, "hidpp20: failed to commit profile\n"); return RATBAG_ERROR_DEVICE; } list_for_each(profile, &device->profiles, link) { if (profile->is_active) { ratbag_profile_for_each_resolution(profile, resolution) { if (resolution->is_active) hidpp20_onboard_profiles_set_current_dpi_index(drv_data->dev, resolution->index); } } } } return RATBAG_SUCCESS; } static int hidpp20drv_20_probe(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp20_device *dev = drv_data->dev; struct hidpp20_feature *feature_list = dev->feature_list; unsigned int i; int rc; log_raw(device->ratbag, "'%s' has %d features\n", ratbag_device_get_name(device), dev->feature_count); for (i = 0; i < dev->feature_count; i++) { log_raw(device->ratbag, "Init feature %s (0x%04x) \n", hidpp20_feature_get_name(feature_list[i].feature), feature_list[i].feature); rc = hidpp20drv_init_feature(device, feature_list[i].feature); if (rc < 0) return rc; } /* initializations that depend on other features */ if (drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070 || drv_data->capabilities & HIDPP_CAP_RGB_EFFECTS_8071) { rc = hidpp20drv_init_leds_8070_8071(device); if (rc < 0) return rc; } if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100) { rc = hidpp20drv_init_profile_8100(device); if (rc < 0) return rc; } return 0; } static int hidpp20drv_test_hidraw(struct ratbag_device *device) { return ratbag_hidraw_has_report(device, REPORT_ID_LONG); } static void hidpp20_log(void *userdata, enum hidpp_log_priority priority, const char *format, va_list args) { struct ratbag_device *device = userdata; log_msg_va(device->ratbag, priority, format, args); } static void hidpp20drv_remove(struct ratbag_device *device) { struct hidpp20drv_data *drv_data; if (!device) return; drv_data = ratbag_get_drv_data(device); ratbag_close_hidraw(device); if (drv_data->profiles) hidpp20_onboard_profiles_destroy(drv_data->profiles); free(drv_data->led_infos.color_leds_8070); free(drv_data->controls); free(drv_data->sensors); free(drv_data->leds); if (drv_data->dev) hidpp20_device_destroy(drv_data->dev); free(drv_data); } static void hidpp20drv_init_device(struct ratbag_device *device, struct hidpp20drv_data *drv_data) { struct ratbag_profile *profile; bool active_profile = false; ratbag_device_init_profiles(device, drv_data->num_profiles, drv_data->num_resolutions, drv_data->num_buttons, drv_data->num_leds); ratbag_device_for_each_profile(device, profile) hidpp20drv_read_profile(profile); if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100) { /* Fallback to the first profile if no profile is active */ list_for_each(profile, &device->profiles, link) if (profile->is_active) active_profile = true; if (!active_profile && device->num_profiles >= 1) { profile = ratbag_device_get_profile(device, 0); profile->is_active = true; } } } static int hidpp20drv_probe(struct ratbag_device *device) { int rc; struct hidpp20drv_data *drv_data; struct hidpp_device base; struct hidpp20_device *dev; int device_idx = HIDPP_RECEIVER_IDX; rc = ratbag_find_hidraw(device, hidpp20drv_test_hidraw); if (rc) return rc; drv_data = zalloc(sizeof(*drv_data)); ratbag_set_drv_data(device, drv_data); hidpp_device_init(&base, device->hidraw[0].fd); hidpp_device_set_log_handler(&base, hidpp20_log, HIDPP_LOG_PRIORITY_RAW, device); device_idx = ratbag_device_data_hidpp20_get_index(device->data); if (device_idx == -1) device_idx = HIDPP_RECEIVER_IDX; /* In the general case, we can treat all devices as wired devices * here. If we talk to the correct hidraw device the kernel adjusts * the device index for us, so even for unifying receiver devices * we can just use 0xff as device index. * * If there is a special need like for G900, we can add this in the * device data file. */ dev = hidpp20_device_new(&base, device_idx, (struct hidpp_hid_report*) device->hidraw[0].reports, device->hidraw[0].num_reports); if (!dev) { rc = -ENODEV; goto err; } dev->quirk = ratbag_device_data_hidpp20_get_quirk(device->data); drv_data->dev = dev; log_debug(device->ratbag, "'%s' is using protocol v%d.%d\n", ratbag_device_get_name(device), dev->proto_major, dev->proto_minor); if(dev->quirk != HIDPP20_QUIRK_NONE) log_debug(device->ratbag, "'%s' is quirked (%s)\n", ratbag_device_get_name(device), hidpp20_get_quirk_string(dev->quirk)); /* add some defaults that will be overwritten by the device */ drv_data->num_profiles = 1; drv_data->num_resolutions = 0; drv_data->num_buttons = 8; drv_data->num_leds = 0; rc = hidpp20drv_20_probe(device); if (rc) goto err; hidpp20drv_init_device(device, drv_data); return rc; err: hidpp20drv_remove(device); return rc; } struct ratbag_driver hidpp20_driver = { .name = "Logitech HID++2.0", .id = "hidpp20", .probe = hidpp20drv_probe, .remove = hidpp20drv_remove, .commit = hidpp20drv_commit, .set_active_profile = hidpp20drv_set_current_profile, }; libratbag-0.13/src/driver-logitech-g300.c000066400000000000000000000431321362011324700200570ustar00rootroot00000000000000/* * Copyright © 2016 Thomas Hindoe Paaboel Andersen. * * 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 (including the next * paragraph) 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. */ #include "config.h" #include #include #include #include #include #include #include #include "libratbag-private.h" #include "libratbag-hidraw.h" #define LOGITECH_G300_PROFILE_MAX 2 #define LOGITECH_G300_BUTTON_MAX 8 #define LOGITECH_G300_NUM_DPI 4 #define LOGITECH_G300_NUM_LED 1 #define LOGITECH_G300_DPI_MIN 250 #define LOGITECH_G300_DPI_MAX 2500 #define LOGITECH_G300_REPORT_ID_GET_ACTIVE 0xF0 #define LOGITECH_G300_REPORT_ID_SET_ACTIVE 0xF0 #define LOGITECH_G300_REPORT_ID_GET_ACTIVE_LED 0xF1 #define LOGITECH_G300_REPORT_ID_PROFILE_0 0xF3 #define LOGITECH_G300_REPORT_ID_PROFILE_1 0xF4 #define LOGITECH_G300_REPORT_ID_PROFILE_2 0xF5 #define LOGITECH_G300_REPORT_SIZE_ACTIVE 4 #define LOGITECH_G300_REPORT_SIZE_PROFILE 35 struct logitech_g300_resolution { uint8_t dpi :7; /* Range 1-10. dpi = 250*value */ uint8_t is_default :1; } __attribute__((packed)); struct logitech_g300_button { uint8_t code; uint8_t modifier; uint8_t key; } __attribute__((packed)); struct logitech_g300_profile_report { uint8_t id; /* F3, F4, F5 */ uint8_t led_red :1; uint8_t led_green :1; uint8_t led_blue :1; uint8_t unknown1 :5; uint8_t frequency; /* 00=1000, 01=125, 02=250, 03=500 */ struct logitech_g300_resolution dpi_levels[LOGITECH_G300_NUM_DPI]; uint8_t unknown2; /* dpi index for shift, but something else too */ struct logitech_g300_button buttons[LOGITECH_G300_BUTTON_MAX + 1]; } __attribute__((packed)); struct logitech_g300_profile_data { struct logitech_g300_profile_report report; }; struct logitech_g300_data { struct logitech_g300_profile_data profile_data[LOGITECH_G300_PROFILE_MAX + 1]; }; _Static_assert(sizeof(struct logitech_g300_profile_report) == LOGITECH_G300_REPORT_SIZE_PROFILE, "Size of logitech_g300_profile_report is wrong"); struct logitech_g300_button_type_mapping { uint8_t raw; enum ratbag_button_type type; }; static const struct logitech_g300_button_type_mapping logitech_g300_button_type_mapping[] = { { 0, RATBAG_BUTTON_TYPE_LEFT }, { 1, RATBAG_BUTTON_TYPE_RIGHT }, { 2, RATBAG_BUTTON_TYPE_MIDDLE }, { 3, RATBAG_BUTTON_TYPE_THUMB }, { 4, RATBAG_BUTTON_TYPE_THUMB2 }, { 5, RATBAG_BUTTON_TYPE_PINKIE }, { 6, RATBAG_BUTTON_TYPE_PINKIE2 }, { 7, RATBAG_BUTTON_TYPE_PROFILE_CYCLE_UP }, { 8, RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP }, }; static enum ratbag_button_type logitech_g300_raw_to_button_type(uint8_t data) { const struct logitech_g300_button_type_mapping *mapping; ARRAY_FOR_EACH(logitech_g300_button_type_mapping, mapping) { if (mapping->raw == data) return mapping->type; } return RATBAG_BUTTON_TYPE_UNKNOWN; } struct logitech_g300_button_mapping { uint8_t raw; struct ratbag_button_action action; }; static struct logitech_g300_button_mapping logitech_g300_button_mapping[] = { /* 0x00 is either key or unassigned. Must be handled separatly */ { 0x01, BUTTON_ACTION_BUTTON(1) }, { 0x02, BUTTON_ACTION_BUTTON(2) }, { 0x03, BUTTON_ACTION_BUTTON(3) }, { 0x04, BUTTON_ACTION_BUTTON(4) }, { 0x05, BUTTON_ACTION_BUTTON(5) }, { 0x06, BUTTON_ACTION_BUTTON(6) }, { 0x07, BUTTON_ACTION_BUTTON(7) }, { 0x08, BUTTON_ACTION_BUTTON(8) }, { 0x09, BUTTON_ACTION_BUTTON(9) }, { 0x0A, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) }, { 0x0B, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) }, { 0x0C, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) }, { 0x0D, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) }, { 0x0E, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE) }, { 0x0F, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT) }, }; static const struct ratbag_button_action* logitech_g300_raw_to_button_action(uint8_t data) { struct logitech_g300_button_mapping *mapping; ARRAY_FOR_EACH(logitech_g300_button_mapping, mapping) { if (mapping->raw == data) return &mapping->action; } return NULL; } static uint8_t logitech_g300_modifier_to_raw(int modifier_flags) { uint8_t modifiers = 0x00; if (modifier_flags & MODIFIER_LEFTCTRL) modifiers |= 0x01; if (modifier_flags & MODIFIER_LEFTSHIFT) modifiers |= 0x02; if (modifier_flags & MODIFIER_LEFTALT) modifiers |= 0x04; if (modifier_flags & MODIFIER_LEFTMETA) modifiers |= 0x08; if (modifier_flags & MODIFIER_RIGHTCTRL) modifiers |= 0x10; if (modifier_flags & MODIFIER_RIGHTSHIFT) modifiers |= 0x20; if (modifier_flags & MODIFIER_RIGHTALT) modifiers |= 0x40; if (modifier_flags & MODIFIER_RIGHTMETA) modifiers |= 0x80; return modifiers; } static int logitech_g300_raw_to_modifiers(uint8_t data) { int modifiers = 0; if (data & 0x01) modifiers |= MODIFIER_LEFTCTRL; if (data & 0x02) modifiers |= MODIFIER_LEFTSHIFT; if (data & 0x04) modifiers |= MODIFIER_LEFTALT; if (data & 0x08) modifiers |= MODIFIER_LEFTMETA; if (data & 0x10) modifiers |= MODIFIER_RIGHTCTRL; if (data & 0x20) modifiers |= MODIFIER_RIGHTSHIFT; if (data & 0x40) modifiers |= MODIFIER_RIGHTALT; if (data & 0x80) modifiers |= MODIFIER_RIGHTMETA; return modifiers; } static uint8_t logitech_g300_button_action_to_raw(const struct ratbag_button_action *action) { struct logitech_g300_button_mapping *mapping; ARRAY_FOR_EACH(logitech_g300_button_mapping, mapping) { if (ratbag_button_action_match(&mapping->action, action)) return mapping->raw; } return 0; } struct logitech_g300_frequency_mapping { uint8_t raw; unsigned int frequency; }; static struct logitech_g300_frequency_mapping logitech_g300_frequency_mapping[] = { { 0, 1000 }, { 1, 125 }, { 2, 250 }, { 3, 500 }, }; static unsigned int logitech_g300_raw_to_frequency(uint8_t data) { struct logitech_g300_frequency_mapping *mapping; ARRAY_FOR_EACH(logitech_g300_frequency_mapping, mapping) { if (mapping->raw == data) return mapping->frequency; } return 0; } static uint8_t logitech_g300_frequency_to_raw(unsigned int frequency) { struct logitech_g300_frequency_mapping *mapping; ARRAY_FOR_EACH(logitech_g300_frequency_mapping, mapping) { if (mapping->frequency == frequency) return mapping->raw; } return 0; } struct logitech_g300_F0_report { uint8_t id; uint8_t unknown1 :1; uint8_t resolution :3; uint8_t profile :4; uint8_t unknown2; uint8_t unknown3; } __attribute__((packed)); static int logitech_g300_get_active_profile_and_resolution(struct ratbag_device *device) { struct ratbag_profile *profile; struct logitech_g300_F0_report buf; int ret; ret = ratbag_hidraw_raw_request(device, 0xF0, (uint8_t*)&buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (ret < 0) return ret; if (ret != sizeof(buf)) return -EIO; list_for_each(profile, &device->profiles, link) { struct ratbag_resolution *resolution; if (profile->index != buf.profile) continue; profile->is_active = true; ratbag_profile_for_each_resolution(profile, resolution) { resolution->is_active = resolution->index == buf.resolution; } } return 0; } static int logitech_g300_set_active_profile(struct ratbag_device *device, unsigned int index) { struct ratbag_profile *profile; uint8_t buf[] = {LOGITECH_G300_REPORT_ID_SET_ACTIVE, 0x80 | (index << 4), 0x00, 0x00}; int ret; if (index > LOGITECH_G300_PROFILE_MAX) return -EINVAL; ret = ratbag_hidraw_raw_request(device, buf[0], buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (ret != sizeof(buf)) return ret; /* Update the active resolution. After profile change the default is used. */ list_for_each(profile, &device->profiles, link) { struct ratbag_resolution *resolution; if (profile->index != index) continue; ratbag_profile_for_each_resolution(profile, resolution) { resolution->is_active = resolution->is_default; } } return 0; } static int logitech_g300_set_current_resolution(struct ratbag_device *device, unsigned int index) { uint8_t buf[] = {LOGITECH_G300_REPORT_ID_SET_ACTIVE, 0x40 | (index << 1), 0x00, 0x00}; int ret; if (index >= LOGITECH_G300_NUM_DPI) return -EINVAL; ret = ratbag_hidraw_raw_request(device, buf[0], buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); return ret == sizeof(buf) ? 0 : ret; } static void logitech_g300_read_button(struct ratbag_button *button) { const struct ratbag_button_action *action; struct ratbag_profile *profile = button->profile; struct ratbag_device *device = profile->device; struct logitech_g300_data *drv_data = device->drv_data; struct logitech_g300_profile_data *pdata; struct logitech_g300_profile_report *profile_report; struct logitech_g300_button *button_report; pdata = &drv_data->profile_data[profile->index]; profile_report = &pdata->report; button_report = &profile_report->buttons[button->index]; ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); button->type = logitech_g300_raw_to_button_type(button->index); action = logitech_g300_raw_to_button_action(button_report->code); if (action) { ratbag_button_set_action(button, action); } else if (button_report->code == 0x00 && (button_report->modifier > 0x00 || button_report->key > 0x00)) { unsigned int key, modifiers; int rc; key = ratbag_hidraw_get_keycode_from_keyboard_usage(device, button_report->key); modifiers = logitech_g300_raw_to_modifiers(button_report->modifier); rc = ratbag_button_macro_new_from_keycode(button, key, modifiers); if (rc < 0) { log_error(device->ratbag, "Error while reading button %d\n", button->index); button->action.type = RATBAG_BUTTON_ACTION_TYPE_NONE; } } } static void logitech_g300_read_led(struct ratbag_led *led) { struct ratbag_profile *profile = led->profile; struct ratbag_device *device = profile->device; struct logitech_g300_data *drv_data = device->drv_data; struct logitech_g300_profile_data *pdata; struct logitech_g300_profile_report *profile_report; pdata = &drv_data->profile_data[profile->index]; profile_report = &pdata->report; led->type = RATBAG_LED_TYPE_SIDE; led->mode = RATBAG_LED_ON; led->colordepth = RATBAG_LED_COLORDEPTH_RGB_111; led->color.red = profile_report->led_red * 255; led->color.green = profile_report->led_green * 255; led->color.blue = profile_report->led_blue * 255; ratbag_led_set_mode_capability(led, RATBAG_LED_OFF); ratbag_led_set_mode_capability(led, RATBAG_LED_ON); } static void logitech_g300_read_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct logitech_g300_data *drv_data = device->drv_data; struct logitech_g300_profile_data *pdata; struct logitech_g300_profile_report *report; struct ratbag_resolution *resolution; unsigned int hz; uint8_t report_id; int rc; struct ratbag_button *button; struct ratbag_led *led; assert(profile->index <= LOGITECH_G300_PROFILE_MAX); pdata = &drv_data->profile_data[profile->index]; report = &pdata->report; switch (profile->index) { case 0: report_id = LOGITECH_G300_REPORT_ID_PROFILE_0; break; case 1: report_id = LOGITECH_G300_REPORT_ID_PROFILE_1; break; case 2: report_id = LOGITECH_G300_REPORT_ID_PROFILE_2; break; } rc = ratbag_hidraw_raw_request(device, report_id, (uint8_t*)report, sizeof(*report), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (rc < (int)sizeof(*report)) { log_error(device->ratbag, "Error while requesting profile: %d\n", rc); return; } hz = logitech_g300_raw_to_frequency(report->frequency); ratbag_profile_set_report_rate_list(profile, &hz, 1); ratbag_profile_set_report_rate(profile, hz); ratbag_profile_for_each_resolution(profile, resolution) { struct logitech_g300_resolution *res = &report->dpi_levels[resolution->index]; unsigned int dpis[10] = { 250, 500, 750, 1000, 1250, 1500, 1750, 2000, 2250, 2500 }; resolution->dpi_x = res->dpi * 250; resolution->dpi_y = res->dpi * 250; resolution->is_default = res->is_default; resolution->is_active = res->is_default; ratbag_resolution_set_dpi_list(resolution, dpis, ARRAY_LENGTH(dpis)); } ratbag_profile_for_each_button(profile, button) logitech_g300_read_button(button); ratbag_profile_for_each_led(profile, led) logitech_g300_read_led(led); } static int logitech_g300_test_hidraw(struct ratbag_device *device) { return ratbag_hidraw_has_report(device, LOGITECH_G300_REPORT_ID_GET_ACTIVE); } static int logitech_g300_probe(struct ratbag_device *device) { int rc; struct logitech_g300_data *drv_data = NULL; int active_idx; struct ratbag_profile *profile; rc = ratbag_find_hidraw(device, logitech_g300_test_hidraw); if (rc) goto err; drv_data = zalloc(sizeof(*drv_data)); ratbag_set_drv_data(device, drv_data); /* profiles are 0-indexed */ ratbag_device_init_profiles(device, LOGITECH_G300_PROFILE_MAX + 1, LOGITECH_G300_NUM_DPI, LOGITECH_G300_BUTTON_MAX + 1, LOGITECH_G300_NUM_LED); ratbag_device_for_each_profile(device, profile) logitech_g300_read_profile(profile); active_idx = logitech_g300_get_active_profile_and_resolution(device); if (active_idx < 0) { log_error(device->ratbag, "Can't talk to the mouse: '%s' (%d)\n", strerror(-active_idx), active_idx); rc = -ENODEV; goto err; } log_raw(device->ratbag, "'%s' is in profile %d\n", ratbag_device_get_name(device), active_idx); return 0; err: free(drv_data); ratbag_set_drv_data(device, NULL); return rc; } static int logitech_g300_write_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct logitech_g300_data *drv_data = device->drv_data; struct logitech_g300_profile_data *pdata; struct logitech_g300_profile_report *report; struct ratbag_resolution *resolution; struct ratbag_button *button; struct ratbag_led *led; uint8_t *buf; int rc; pdata = &drv_data->profile_data[profile->index]; report = &pdata->report; report->frequency = logitech_g300_frequency_to_raw(profile->hz); ratbag_profile_for_each_resolution(profile, resolution) { struct logitech_g300_resolution *res = &report->dpi_levels[resolution->index]; res->dpi = resolution->dpi_x / 250; res->is_default = resolution->is_default; if (profile->is_active && resolution->is_active) logitech_g300_set_current_resolution(device, resolution->index); } list_for_each(button, &profile->buttons, link) { struct ratbag_button_action *action = &button->action; struct logitech_g300_button *raw_button; if (!button->dirty) continue; raw_button = &report->buttons[button->index]; raw_button->code = logitech_g300_button_action_to_raw(action); raw_button->modifier = 0x00; raw_button->key = 0x00; if (action->type == RATBAG_BUTTON_ACTION_TYPE_MACRO) { unsigned int key, modifiers; rc = ratbag_action_keycode_from_macro(action, &key, &modifiers); if (rc < 0) { log_error(device->ratbag, "Error while writing macro for button %d\n", button->index); } raw_button->key = ratbag_hidraw_get_keyboard_usage_from_keycode( device, key); raw_button->modifier = logitech_g300_modifier_to_raw(modifiers); } } list_for_each(led, &profile->leds, link) { if (!led->dirty) continue; /* Clamp the 8 bit colors to 1 bit */ report->led_red = led->color.red > 127; report->led_green = led->color.green > 127; report->led_blue = led->color.blue > 127; } buf = (uint8_t*)report; rc = ratbag_hidraw_raw_request(device, report->id, buf, sizeof(*report), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc < (int)sizeof(*report)) { log_error(device->ratbag, "Error while writing profile: %d\n", rc); return rc; } return 0; } static int logitech_g300_commit(struct ratbag_device *device) { struct ratbag_profile *profile; int rc = 0; list_for_each(profile, &device->profiles, link) { if (!profile->dirty) continue; log_debug(device->ratbag, "Profile %d changed, rewriting\n", profile->index); rc = logitech_g300_write_profile(profile); if (rc) return rc; } return 0; } static void logitech_g300_remove(struct ratbag_device *device) { ratbag_close_hidraw(device); free(ratbag_get_drv_data(device)); } struct ratbag_driver logitech_g300_driver = { .name = "Logitech G300", .id = "logitech_g300", .probe = logitech_g300_probe, .remove = logitech_g300_remove, .commit = logitech_g300_commit, .set_active_profile = logitech_g300_set_active_profile, }; libratbag-0.13/src/driver-logitech-g600.c000066400000000000000000000452241362011324700200660ustar00rootroot00000000000000/* * Copyright © 2018 Thomas Hindoe Paaboel Andersen. * * 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 (including the next * paragraph) 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. */ #include "config.h" #include #include #include #include #include #include #include #include "libratbag-private.h" #include "libratbag-hidraw.h" #define LOGITECH_G600_NUM_PROFILES 3 #define LOGITECH_G600_NUM_BUTTONS 20 #define LOGITECH_G600_NUM_DPI 4 #define LOGITECH_G600_NUM_LED 1 #define LOGITECH_G600_DPI_MIN 200 #define LOGITECH_G600_DPI_MAX 8200 #define LOGITECH_G600_REPORT_ID_GET_ACTIVE 0xF0 #define LOGITECH_G600_REPORT_ID_SET_ACTIVE 0xF0 #define LOGITECH_G600_REPORT_ID_PROFILE_0 0xF3 #define LOGITECH_G600_REPORT_ID_PROFILE_1 0xF4 #define LOGITECH_G600_REPORT_ID_PROFILE_2 0xF5 #define LOGITECH_G600_REPORT_SIZE_PROFILE 154 #define LOGITECH_G600_LED_SOLID 0x00 #define LOGITECH_G600_LED_BREATHE 0x01 #define LOGITECH_G600_LED_CYCLE 0x02 struct logitech_g600_button { uint8_t code; uint8_t modifier; uint8_t key; } __attribute__((packed)); struct logitech_g600_profile_report { uint8_t id; uint8_t led_red; uint8_t led_green; uint8_t led_blue; uint8_t led_effect; uint8_t led_duration; uint8_t unknown1[5]; uint8_t frequency; /* frequency = 1000 / (value + 1) */ uint8_t dpi_shift; /* value is a linear range between 200->0x04, 8200->0xa4, so value * 50, 0x00 is disabled */ uint8_t dpi_default; /* between 1 and 4*/ uint8_t dpi[4]; /* value is a linear range between 200->0x04, 8200->0xa4, so value * 50, 0x00 is disabled */ uint8_t unknown2[13]; struct logitech_g600_button buttons[20]; uint8_t g_shift_color[3]; /* can't assign it in LGS, but the 3rd profile has one that shows the feature :) */ struct logitech_g600_button g_shift_buttons[20]; } __attribute__((packed)); struct logitech_g600_active_profile_report { uint8_t id; uint8_t unknown1 :1; uint8_t resolution :2; uint8_t unknown2 :1; uint8_t profile :4; uint8_t unknown3; uint8_t unknown4; } __attribute__((packed)); struct logitech_g600_profile_data { struct logitech_g600_profile_report report; }; struct logitech_g600_data { struct logitech_g600_profile_data profile_data[LOGITECH_G600_NUM_PROFILES]; }; _Static_assert(sizeof(struct logitech_g600_profile_report) == LOGITECH_G600_REPORT_SIZE_PROFILE, "Size of logitech_g600_profile_report is wrong"); struct logitech_g600_button_type_mapping { uint8_t raw; enum ratbag_button_type type; }; static const struct logitech_g600_button_type_mapping logitech_g600_button_type_mapping[] = { { 0, RATBAG_BUTTON_TYPE_LEFT }, { 1, RATBAG_BUTTON_TYPE_RIGHT }, { 2, RATBAG_BUTTON_TYPE_MIDDLE }, { 3, RATBAG_BUTTON_TYPE_WHEEL_LEFT }, { 4, RATBAG_BUTTON_TYPE_WHEEL_RIGHT }, { 5, RATBAG_BUTTON_TYPE_PINKIE }, { 6, RATBAG_BUTTON_TYPE_PROFILE_CYCLE_UP }, { 7, RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP }, { 8, RATBAG_BUTTON_TYPE_SIDE }, // 8 = G9 ... 19 = G20 { 9, RATBAG_BUTTON_TYPE_SIDE }, { 10, RATBAG_BUTTON_TYPE_SIDE }, { 11, RATBAG_BUTTON_TYPE_SIDE }, { 12, RATBAG_BUTTON_TYPE_SIDE }, { 13, RATBAG_BUTTON_TYPE_SIDE }, { 14, RATBAG_BUTTON_TYPE_SIDE }, { 15, RATBAG_BUTTON_TYPE_SIDE }, { 16, RATBAG_BUTTON_TYPE_SIDE }, { 17, RATBAG_BUTTON_TYPE_SIDE }, { 18, RATBAG_BUTTON_TYPE_SIDE }, { 19, RATBAG_BUTTON_TYPE_SIDE }, }; static enum ratbag_button_type logitech_g600_raw_to_button_type(uint8_t data) { const struct logitech_g600_button_type_mapping *mapping; ARRAY_FOR_EACH(logitech_g600_button_type_mapping, mapping) { if (mapping->raw == data) return mapping->type; } return RATBAG_BUTTON_TYPE_UNKNOWN; } struct logitech_g600_button_mapping { uint8_t raw; struct ratbag_button_action action; }; static struct logitech_g600_button_mapping logitech_g600_button_mapping[] = { /* 0x00 is either key or unassigned. Must be handled separatly */ { 0x01, BUTTON_ACTION_BUTTON(1) }, { 0x02, BUTTON_ACTION_BUTTON(2) }, { 0x03, BUTTON_ACTION_BUTTON(3) }, { 0x04, BUTTON_ACTION_BUTTON(4) }, { 0x05, BUTTON_ACTION_BUTTON(5) }, { 0x11, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) }, { 0x12, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) }, { 0x13, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) }, { 0x14, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) }, { 0x15, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE) }, { 0x17, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE) }, }; static const struct ratbag_button_action* logitech_g600_raw_to_button_action(uint8_t data) { struct logitech_g600_button_mapping *mapping; ARRAY_FOR_EACH(logitech_g600_button_mapping, mapping) { if (mapping->raw == data) return &mapping->action; } return NULL; } static uint8_t logitech_g600_modifier_to_raw(int modifier_flags) { uint8_t modifiers = 0x00; if (modifier_flags & MODIFIER_LEFTCTRL) modifiers |= 0x01; if (modifier_flags & MODIFIER_LEFTSHIFT) modifiers |= 0x02; if (modifier_flags & MODIFIER_LEFTALT) modifiers |= 0x04; if (modifier_flags & MODIFIER_LEFTMETA) modifiers |= 0x08; if (modifier_flags & MODIFIER_RIGHTCTRL) modifiers |= 0x10; if (modifier_flags & MODIFIER_RIGHTSHIFT) modifiers |= 0x20; if (modifier_flags & MODIFIER_RIGHTALT) modifiers |= 0x40; if (modifier_flags & MODIFIER_RIGHTMETA) modifiers |= 0x80; return modifiers; } static int logitech_g600_raw_to_modifiers(uint8_t data) { int modifiers = 0; if (data & 0x01) modifiers |= MODIFIER_LEFTCTRL; if (data & 0x02) modifiers |= MODIFIER_LEFTSHIFT; if (data & 0x04) modifiers |= MODIFIER_LEFTALT; if (data & 0x08) modifiers |= MODIFIER_LEFTMETA; if (data & 0x10) modifiers |= MODIFIER_RIGHTCTRL; if (data & 0x20) modifiers |= MODIFIER_RIGHTSHIFT; if (data & 0x40) modifiers |= MODIFIER_RIGHTALT; if (data & 0x80) modifiers |= MODIFIER_RIGHTMETA; return modifiers; } static uint8_t logitech_g600_button_action_to_raw(const struct ratbag_button_action *action) { struct logitech_g600_button_mapping *mapping; ARRAY_FOR_EACH(logitech_g600_button_mapping, mapping) { if (ratbag_button_action_match(&mapping->action, action)) return mapping->raw; } return 0; } static int logitech_g600_get_active_profile_and_resolution(struct ratbag_device *device) { struct ratbag_profile *profile; struct logitech_g600_active_profile_report buf; int ret; ret = ratbag_hidraw_raw_request(device, LOGITECH_G600_REPORT_ID_GET_ACTIVE, (uint8_t*)&buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (ret < 0) return ret; if (ret != sizeof(buf)) return -EIO; list_for_each(profile, &device->profiles, link) { struct ratbag_resolution *resolution; if (profile->index != buf.profile) continue; profile->is_active = true; ratbag_profile_for_each_resolution(profile, resolution) { resolution->is_active = resolution->index == buf.resolution; } } return 0; } static int logitech_g600_set_current_resolution(struct ratbag_device *device, unsigned int index) { uint8_t buf[] = {LOGITECH_G600_REPORT_ID_SET_ACTIVE, 0x40 | (index << 1), 0x00, 0x00}; int ret; log_debug(device->ratbag, "Setting active resolution to %d\n", index); if (index >= LOGITECH_G600_NUM_DPI) return -EINVAL; ret = ratbag_hidraw_raw_request(device, buf[0], buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); return ret == sizeof(buf) ? 0 : ret; } static int logitech_g600_set_active_profile(struct ratbag_device *device, unsigned int index) { struct ratbag_profile *profile; uint8_t buf[] = {LOGITECH_G600_REPORT_ID_SET_ACTIVE, 0x80 | (index << 4), 0x00, 0x00}; int ret, active_resolution = 0; if (index >= LOGITECH_G600_NUM_PROFILES) return -EINVAL; ret = ratbag_hidraw_raw_request(device, buf[0], buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (ret != sizeof(buf)) return ret; /* Update the active resolution. After profile change the default is used. */ list_for_each(profile, &device->profiles, link) { struct ratbag_resolution *resolution; if (profile->index != index) continue; ratbag_profile_for_each_resolution(profile, resolution) { resolution->is_active = resolution->is_default; if (resolution->is_active) active_resolution = resolution->index; } } ret = logitech_g600_set_current_resolution(device, active_resolution); if (ret < 0) return ret; return 0; } static void logitech_g600_read_button(struct ratbag_button *button) { const struct ratbag_button_action *action; struct ratbag_profile *profile = button->profile; struct ratbag_device *device = profile->device; struct logitech_g600_data *drv_data = device->drv_data; struct logitech_g600_profile_data *pdata; struct logitech_g600_profile_report *profile_report; struct logitech_g600_button *button_report; pdata = &drv_data->profile_data[profile->index]; profile_report = &pdata->report; button_report = &profile_report->buttons[button->index]; ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); button->type = logitech_g600_raw_to_button_type(button->index); action = logitech_g600_raw_to_button_action(button_report->code); if (action) { ratbag_button_set_action(button, action); } else if (button_report->code == 0x00 && (button_report->modifier > 0x00 || button_report->key > 0x00)) { unsigned int key, modifiers; int rc; key = ratbag_hidraw_get_keycode_from_keyboard_usage(device, button_report->key); modifiers = logitech_g600_raw_to_modifiers(button_report->modifier); rc = ratbag_button_macro_new_from_keycode(button, key, modifiers); if (rc < 0) { log_error(device->ratbag, "Error while reading button %d\n", button->index); button->action.type = RATBAG_BUTTON_ACTION_TYPE_NONE; } } } static void logitech_g600_read_led(struct ratbag_led *led) { struct ratbag_profile *profile = led->profile; struct ratbag_device *device = profile->device; struct logitech_g600_data *drv_data = device->drv_data; struct logitech_g600_profile_data *pdata; struct logitech_g600_profile_report *report; pdata = &drv_data->profile_data[profile->index]; report = &pdata->report; led->type = RATBAG_LED_TYPE_SIDE; led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888; ratbag_led_set_mode_capability(led, RATBAG_LED_OFF); ratbag_led_set_mode_capability(led, RATBAG_LED_ON); ratbag_led_set_mode_capability(led, RATBAG_LED_BREATHING); ratbag_led_set_mode_capability(led, RATBAG_LED_CYCLE); switch (report->led_effect) { case LOGITECH_G600_LED_SOLID: led->mode = RATBAG_LED_ON; break; case LOGITECH_G600_LED_BREATHE: led->mode = RATBAG_LED_BREATHING; led->ms = report->led_duration * 1000; break; case LOGITECH_G600_LED_CYCLE: led->mode = RATBAG_LED_CYCLE; led->ms = report->led_duration * 1000; break; } led->color.red = report->led_red; led->color.green = report->led_green; led->color.blue = report->led_blue; } static void logitech_g600_read_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct logitech_g600_data *drv_data = device->drv_data; struct logitech_g600_profile_data *pdata; struct logitech_g600_profile_report *report; struct ratbag_resolution *resolution; unsigned int report_rates[] = { 125, 142, 166, 200, 250, 333, 500, 1000 }; uint8_t report_id; int rc; struct ratbag_button *button; struct ratbag_led *led; assert(profile->index < LOGITECH_G600_NUM_PROFILES); pdata = &drv_data->profile_data[profile->index]; report = &pdata->report; switch (profile->index) { case 0: report_id = LOGITECH_G600_REPORT_ID_PROFILE_0; break; case 1: report_id = LOGITECH_G600_REPORT_ID_PROFILE_1; break; case 2: report_id = LOGITECH_G600_REPORT_ID_PROFILE_2; break; } rc = ratbag_hidraw_raw_request(device, report_id, (uint8_t*)report, sizeof(*report), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (rc < (int)sizeof(*report)) { log_error(device->ratbag, "Error while requesting profile: %d\n", rc); return; } ratbag_profile_set_report_rate_list(profile, report_rates, ARRAY_LENGTH(report_rates)); ratbag_profile_set_report_rate(profile, 1000 / (report->frequency + 1)); ratbag_profile_for_each_resolution(profile, resolution) { resolution->dpi_x = report->dpi[resolution->index] * 50; resolution->dpi_y = report->dpi[resolution->index] * 50; resolution->is_default = report->dpi_default - 1U == resolution->index; resolution->is_active = resolution->is_default; ratbag_resolution_set_dpi_list_from_range(resolution, LOGITECH_G600_DPI_MIN, LOGITECH_G600_DPI_MAX); } ratbag_profile_for_each_button(profile, button) logitech_g600_read_button(button); ratbag_profile_for_each_led(profile, led) logitech_g600_read_led(led); log_debug(device->ratbag, "Unknown data in profile %d\n", profile->index); log_buf_debug(device->ratbag, " profile->unknown1: ", report->unknown1, 5); log_buf_debug(device->ratbag, " profile->unknown2: ", report->unknown2, 13); } static int logitech_g600_test_hidraw(struct ratbag_device *device) { return ratbag_hidraw_has_report(device, LOGITECH_G600_REPORT_ID_GET_ACTIVE); } static int logitech_g600_probe(struct ratbag_device *device) { int rc; struct logitech_g600_data *drv_data = NULL; struct ratbag_profile *profile; rc = ratbag_find_hidraw(device, logitech_g600_test_hidraw); if (rc) goto err; drv_data = zalloc(sizeof(*drv_data)); ratbag_set_drv_data(device, drv_data); ratbag_device_init_profiles(device, LOGITECH_G600_NUM_PROFILES, LOGITECH_G600_NUM_DPI, LOGITECH_G600_NUM_BUTTONS, LOGITECH_G600_NUM_LED); ratbag_device_for_each_profile(device, profile) logitech_g600_read_profile(profile); rc = logitech_g600_get_active_profile_and_resolution(device); if (rc < 0) { log_error(device->ratbag, "Can't talk to the mouse: '%s' (%d)\n", strerror(-rc), rc); rc = -ENODEV; goto err; } return 0; err: free(drv_data); ratbag_set_drv_data(device, NULL); return rc; } static int logitech_g600_write_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct logitech_g600_data *drv_data = device->drv_data; struct logitech_g600_profile_data *pdata; struct logitech_g600_profile_report *report; struct ratbag_resolution *resolution; struct ratbag_button *button; struct ratbag_led *led; uint8_t *buf; int rc, active_resolution = 0; pdata = &drv_data->profile_data[profile->index]; report = &pdata->report; report->frequency = (1000 / profile->hz) - 1; ratbag_profile_for_each_resolution(profile, resolution) { report->dpi[resolution->index] = resolution->dpi_x / 50; if (resolution->is_default) report->dpi_default = resolution->index + 1; if (profile->is_active && resolution->is_active) active_resolution = resolution->index; } list_for_each(button, &profile->buttons, link) { struct ratbag_button_action *action = &button->action; struct logitech_g600_button *raw_button; raw_button = &report->buttons[button->index]; raw_button->code = logitech_g600_button_action_to_raw(action); raw_button->modifier = 0x00; raw_button->key = 0x00; if (action->type == RATBAG_BUTTON_ACTION_TYPE_MACRO) { unsigned int key, modifiers; rc = ratbag_action_keycode_from_macro(action, &key, &modifiers); if (rc < 0) { log_error(device->ratbag, "Error while writing macro for button %d\n", button->index); } raw_button->key = ratbag_hidraw_get_keyboard_usage_from_keycode( device, key); raw_button->modifier = logitech_g600_modifier_to_raw(modifiers); } } list_for_each(led, &profile->leds, link) { report->led_red = led->color.red; report->led_green = led->color.green; report->led_blue = led->color.blue; switch (led->mode) { case RATBAG_LED_ON: report->led_effect = LOGITECH_G600_LED_SOLID; break; case RATBAG_LED_OFF: report->led_effect = LOGITECH_G600_LED_SOLID; report->led_red = 0x00; report->led_green = 0x00; report->led_blue = 0x00; break; case RATBAG_LED_BREATHING: report->led_effect = LOGITECH_G600_LED_BREATHE; report->led_duration = led->ms / 1000; break; case RATBAG_LED_CYCLE: report->led_effect = LOGITECH_G600_LED_CYCLE; report->led_duration = led->ms / 1000; break; } if (report->led_duration > 0x0f) report->led_duration = 0x0f; } buf = (uint8_t*)report; rc = ratbag_hidraw_raw_request(device, report->id, buf, sizeof(*report), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc < (int)sizeof(*report)) { log_error(device->ratbag, "Error while writing profile: %d\n", rc); return rc; } if (profile->is_active) { rc = logitech_g600_set_current_resolution(device, active_resolution); if (rc < 0) return rc; } return 0; } static int logitech_g600_commit(struct ratbag_device *device) { struct ratbag_profile *profile; int rc = 0; list_for_each(profile, &device->profiles, link) { if (!profile->dirty) continue; log_debug(device->ratbag, "Profile %d changed, rewriting\n", profile->index); rc = logitech_g600_write_profile(profile); if (rc) return rc; } return 0; } static void logitech_g600_remove(struct ratbag_device *device) { ratbag_close_hidraw(device); free(ratbag_get_drv_data(device)); } struct ratbag_driver logitech_g600_driver = { .name = "Logitech G600", .id = "logitech_g600", .probe = logitech_g600_probe, .remove = logitech_g600_remove, .commit = logitech_g600_commit, .set_active_profile = logitech_g600_set_active_profile, }; libratbag-0.13/src/driver-roccat-kone-pure.c000066400000000000000000000554721362011324700210020ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include "config.h" #include #include #include #include #include #include #include #include "libratbag-private.h" #include "libratbag-hidraw.h" #define ROCCAT_PROFILE_MAX 4 #define ROCCAT_BUTTON_MAX 17 #define ROCCAT_NUM_DPI 5 #define ROCCAT_LED_MAX 0 #define ROCCAT_MAX_RETRY_READY 10 #define ROCCAT_REPORT_ID_CONFIGURE_PROFILE 4 #define ROCCAT_REPORT_ID_PROFILE 5 #define ROCCAT_REPORT_ID_SETTINGS 6 #define ROCCAT_REPORT_ID_KEY_MAPPING 7 #define ROCCAT_REPORT_ID_MACRO 8 #define ROCCAT_REPORT_SIZE_PROFILE 59 #define ROCCAT_REPORT_SIZE_SETTINGS 31 #define ROCCAT_REPORT_SIZE_MACRO 2082 #define ROCCAT_CONFIG_SETTINGS 0x80 #define ROCCAT_CONFIG_KEY_MAPPING 0x90 #define ROCCAT_MAX_MACRO_LENGTH 500 struct roccat_settings_report { uint8_t reportID; uint8_t twoB; uint8_t profileID; uint8_t x_y_linked; uint8_t x_sensitivity; /* 0x06 means 0 */ uint8_t y_sensitivity; /* 0x06 means 0 */ uint8_t dpi_mask; uint8_t xres[5]; uint8_t current_dpi; uint8_t yres[5]; uint8_t padding1; uint8_t report_rate; uint8_t padding2[11]; // uint16_t checksum; // uint8_t padding2[4]; // uint8_t light; // uint8_t light_heartbit; // uint8_t padding3[5]; } __attribute__((packed)); struct roccat_macro { uint8_t reportID; uint8_t twentytwo; uint8_t height; uint8_t profile; uint8_t button_index; uint8_t active; uint8_t padding[24]; char group[24]; char name[24]; uint16_t length; struct { uint8_t keycode; uint8_t flag; uint16_t time; } keys[ROCCAT_MAX_MACRO_LENGTH]; uint16_t checksum; } __attribute__((packed)); struct roccat_data { uint8_t profiles[(ROCCAT_PROFILE_MAX + 1)][ROCCAT_REPORT_SIZE_PROFILE]; struct roccat_settings_report settings[(ROCCAT_PROFILE_MAX + 1)]; struct roccat_macro macros[(ROCCAT_PROFILE_MAX + 1)][(ROCCAT_BUTTON_MAX + 1)]; }; struct roccat_button_type_mapping { uint8_t raw; enum ratbag_button_type type; }; static const struct roccat_button_type_mapping roccat_button_type_mapping[] = { { 0, RATBAG_BUTTON_TYPE_LEFT }, { 1, RATBAG_BUTTON_TYPE_RIGHT }, { 2, RATBAG_BUTTON_TYPE_MIDDLE }, { 3, RATBAG_BUTTON_TYPE_EXTRA }, { 4, RATBAG_BUTTON_TYPE_SIDE }, { 5, RATBAG_BUTTON_TYPE_WHEEL_UP }, { 6, RATBAG_BUTTON_TYPE_WHEEL_DOWN }, { 7, RATBAG_BUTTON_TYPE_RESOLUTION_UP }, { 8, RATBAG_BUTTON_TYPE_RESOLUTION_DOWN }, { 9, RATBAG_BUTTON_TYPE_LEFT }, { 10, RATBAG_BUTTON_TYPE_RIGHT }, { 11, RATBAG_BUTTON_TYPE_MIDDLE }, { 12, RATBAG_BUTTON_TYPE_EXTRA }, { 13, RATBAG_BUTTON_TYPE_SIDE }, { 14, RATBAG_BUTTON_TYPE_WHEEL_UP }, { 15, RATBAG_BUTTON_TYPE_WHEEL_DOWN }, { 16, RATBAG_BUTTON_TYPE_RESOLUTION_UP }, { 17, RATBAG_BUTTON_TYPE_RESOLUTION_DOWN }, }; static enum ratbag_button_type roccat_raw_to_button_type(uint8_t data) { const struct roccat_button_type_mapping *mapping; ARRAY_FOR_EACH(roccat_button_type_mapping, mapping) { if (mapping->raw == data) return mapping->type; } return RATBAG_BUTTON_TYPE_UNKNOWN; } struct roccat_button_mapping { uint8_t raw; struct ratbag_button_action action; }; static struct roccat_button_mapping roccat_button_mapping[] = { /* FIXME: { 0, Disabled }, */ { 1, BUTTON_ACTION_BUTTON(1) }, { 2, BUTTON_ACTION_BUTTON(2) }, { 3, BUTTON_ACTION_BUTTON(3) }, { 4, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_DOUBLECLICK) }, /* FIXME: { 5, Shortcut (modifier + key) }, */ { 6, BUTTON_ACTION_NONE }, { 7, BUTTON_ACTION_BUTTON(4) }, { 8, BUTTON_ACTION_BUTTON(5) }, //{ 9, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT) }, //{ 10, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT) }, { 13, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP) }, { 14, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN) }, /* FIXME: { 15, quicklaunch }, -> hidraw report 03 00 60 07 01 00 00 00 */ { 16, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) }, { 17, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP) }, { 18, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN) }, { 20, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) }, { 21, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) }, { 22, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) }, { 26, BUTTON_ACTION_KEY(KEY_LEFTMETA) }, /* FIXME: { 27, open driver }, -> hidraw report 02 83 01 00 00 00 00 00 */ //{ 32, BUTTON_ACTION_KEY(KEY_CONFIG) }, { 33, BUTTON_ACTION_KEY(KEY_PREVIOUSSONG) }, { 34, BUTTON_ACTION_KEY(KEY_NEXTSONG) }, { 35, BUTTON_ACTION_KEY(KEY_PLAYPAUSE) }, { 36, BUTTON_ACTION_KEY(KEY_STOPCD) }, { 37, BUTTON_ACTION_KEY(KEY_MUTE) }, { 38, BUTTON_ACTION_KEY(KEY_VOLUMEUP) }, { 39, BUTTON_ACTION_KEY(KEY_VOLUMEDOWN) }, { 48, BUTTON_ACTION_MACRO }, { 65, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE) }, /* FIXME: { 66, Easywheel sensitivity }, */ /* FIXME: { 67, Easywheel profile }, */ /* FIXME: { 68, Easywheel CPI }, */ /* FIXME: { 81, Other Easyshift }, -> hidraw report 03 00 ff 05 01 00 00 00 */ /* FIXME: { 82, Other Easyshift Lock }, -> hidraw report 03 00 ff 05 01 00 00 00 */ /* FIXME: { 83, Both Easyshift }, -> hidraw report 03 00 ff 04 01 00 00 00 */ }; static const struct ratbag_button_action* roccat_raw_to_button_action(uint8_t data) { struct roccat_button_mapping *mapping; ARRAY_FOR_EACH(roccat_button_mapping, mapping) { if (mapping->raw == data) return &mapping->action; } return NULL; } static uint8_t roccat_button_action_to_raw(const struct ratbag_button_action *action) { struct roccat_button_mapping *mapping; ARRAY_FOR_EACH(roccat_button_mapping, mapping) { if (ratbag_button_action_match(&mapping->action, action)) return mapping->raw; } return 0; } static inline uint16_t roccat_get_unaligned_u16(uint8_t *buf) { return (buf[1] << 8) | buf[0]; } static inline uint16_t roccat_compute_crc(uint8_t *buf, unsigned int len) { unsigned i; uint16_t crc = 0; if (len < 3) return 0; for (i = 0; i < len - 2; i++) { crc += buf[i]; } return crc; } static inline int roccat_crc_is_valid(struct ratbag_device *device, uint8_t *buf, unsigned int len) { uint16_t crc; uint16_t given_crc; if (len < 3) return 0; crc = roccat_compute_crc(buf, len); given_crc = roccat_get_unaligned_u16(&buf[len - 2]); log_raw(device->ratbag, "checksum computed: 0x%04x, checksum given: 0x%04x\n", crc, given_crc); return crc == given_crc; } static int roccat_is_ready(struct ratbag_device *device) { uint8_t buf[3] = { 0 }; int rc; rc = ratbag_hidraw_get_feature_report(device, ROCCAT_REPORT_ID_CONFIGURE_PROFILE, buf, sizeof(buf)); if (rc < 0) return rc; if (rc != sizeof(buf)) return -EIO; if (buf[1] == 0x03) msleep(100); if (buf[1] == 0x02) return 2; return buf[1] == 0x01; } static int roccat_wait_ready(struct ratbag_device *device) { unsigned count = 0; int rc; msleep(10); while (count < ROCCAT_MAX_RETRY_READY) { rc = roccat_is_ready(device); if (rc < 0) return rc; if (rc == 1) return 0; if (rc == 2) return 2; msleep(10); count++; } return -ETIMEDOUT; } static int roccat_current_profile(struct ratbag_device *device) { uint8_t buf[3]; int ret; ret = ratbag_hidraw_get_feature_report(device, ROCCAT_REPORT_ID_PROFILE, buf, sizeof(buf)); if (ret < 0) return ret; if (ret != 3) return -EIO; return buf[2]; } static int roccat_set_current_profile(struct ratbag_device *device, unsigned int index) { uint8_t buf[] = {ROCCAT_REPORT_ID_PROFILE, 0x03, index}; int ret; if (index > ROCCAT_PROFILE_MAX) return -EINVAL; ret = ratbag_hidraw_set_feature_report(device, buf[0], buf, sizeof(buf)); if (ret < 0) return ret; if (ret != sizeof(buf)) return -EIO; ret = roccat_wait_ready(device); if (ret) log_error(device->ratbag, "Error while waiting for the device to be ready: %s (%d)\n", strerror(-ret), ret); return ret; } static int roccat_set_config_profile(struct ratbag_device *device, uint8_t profile, uint8_t type) { uint8_t buf[] = {ROCCAT_REPORT_ID_CONFIGURE_PROFILE, profile, type}; int ret; if (profile > ROCCAT_PROFILE_MAX) return -EINVAL; ret = ratbag_hidraw_set_feature_report(device, buf[0], buf, sizeof(buf)); if (ret < 0) return ret; if (ret != sizeof(buf)) return -EIO; ret = roccat_wait_ready(device); if (ret < 0) log_error(device->ratbag, "Error while waiting for the device to be ready: %s (%d)\n", strerror(-ret), ret); return ret; } static const struct ratbag_button_action * roccat_button_to_action(struct ratbag_profile *profile, unsigned int button_index) { struct ratbag_device *device = profile->device; struct roccat_data *drv_data = ratbag_get_drv_data(device); uint8_t data; data = drv_data->profiles[profile->index][3 + button_index * 3]; return roccat_raw_to_button_action(data); } static int roccat_write_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; unsigned int index = profile->index; struct roccat_data *drv_data; int rc; uint8_t *buf; uint16_t *crc; assert(index <= ROCCAT_PROFILE_MAX); drv_data = ratbag_get_drv_data(device); buf = drv_data->profiles[index]; crc = (uint16_t *)&buf[ROCCAT_REPORT_SIZE_PROFILE - 2]; *crc = roccat_compute_crc(buf, ROCCAT_REPORT_SIZE_PROFILE); roccat_set_config_profile(device, index, ROCCAT_CONFIG_KEY_MAPPING); rc = ratbag_hidraw_set_feature_report(device, ROCCAT_REPORT_ID_KEY_MAPPING, buf, ROCCAT_REPORT_SIZE_PROFILE); if (rc < ROCCAT_REPORT_SIZE_PROFILE) return -EIO; log_raw(device->ratbag, "profile: %d written %s:%d\n", buf[2], __FILE__, __LINE__); rc = roccat_wait_ready(device); if (rc) log_error(device->ratbag, "Error while waiting for the device to be ready: %s (%d)\n", strerror(-rc), rc); return rc; } static void roccat_read_button(struct ratbag_button *button) { const struct ratbag_button_action *action; struct ratbag_device *device; struct roccat_macro *macro; struct roccat_data *drv_data; uint8_t *buf; unsigned j, time; int rc; device = button->profile->device; drv_data = ratbag_get_drv_data(device); action = roccat_button_to_action(button->profile, button->index); if (action) ratbag_button_set_action(button, action); button->type = roccat_raw_to_button_type(button->index); // if (action == NULL) // log_error(device->ratbag, "button: %d -> %d %s:%d\n", // button->index, drv_data->profiles[button->profile->index][3 + button->index * 3], // __FILE__, __LINE__); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); if (action && action->type == RATBAG_BUTTON_ACTION_TYPE_MACRO) { struct ratbag_button_macro *m = NULL; roccat_set_config_profile(device, button->profile->index, 0); roccat_set_config_profile(device, button->profile->index, button->index); macro = &drv_data->macros[button->profile->index][button->index]; buf = (uint8_t*)macro; buf[0] = ROCCAT_REPORT_ID_MACRO; rc = ratbag_hidraw_get_feature_report(device, ROCCAT_REPORT_ID_MACRO, buf, ROCCAT_REPORT_SIZE_MACRO); if (rc != ROCCAT_REPORT_SIZE_MACRO) { log_error(device->ratbag, "Unable to retrieve the macro for button %d of profile %d: %s (%d)\n", button->index, button->profile->index, rc < 0 ? strerror(-rc) : "not read enough", rc); } else { if (buf[0] != ROCCAT_REPORT_ID_MACRO) { log_error(device->ratbag, "Error while reading the macro of button %d of profile %d.\n", button->index, button->profile->index); goto out_macro; } if (!roccat_crc_is_valid(device, buf, ROCCAT_REPORT_SIZE_MACRO)) { log_error(device->ratbag, "wrong checksum while reading the macro of button %d of profile %d.\n", button->index, button->profile->index); goto out_macro; } m = ratbag_button_macro_new(macro->name); log_raw(device->ratbag, "macro on button %d of profile %d is named '%s', and contains %d events:\n", button->index, button->profile->index, macro->name, macro->length); for (j = 0; j < macro->length; j++) { unsigned int keycode = ratbag_hidraw_get_keycode_from_keyboard_usage(device, macro->keys[j].keycode); ratbag_button_macro_set_event(m, j * 2, macro->keys[j].flag & 0x01 ? RATBAG_MACRO_EVENT_KEY_PRESSED : RATBAG_MACRO_EVENT_KEY_RELEASED, keycode); if (macro->keys[j].time) time = macro->keys[j].time; else time = macro->keys[j].flag & 0x01 ? 10 : 50; ratbag_button_macro_set_event(m, j * 2 + 1, RATBAG_MACRO_EVENT_WAIT, time); log_raw(device->ratbag, " - %s %s\n", libevdev_event_code_get_name(EV_KEY, keycode), macro->keys[j].flag & 0x80 ? "released" : "pressed"); } ratbag_button_copy_macro(button, m); } out_macro: msleep(10); ratbag_button_macro_unref(m); } } static int roccat_write_macro(struct ratbag_button *button, const struct ratbag_button_action *action) { struct ratbag_device *device; struct roccat_macro *macro; struct roccat_data *drv_data; uint8_t *buf; unsigned i, count = 0; int rc; if (action->type != RATBAG_BUTTON_ACTION_TYPE_MACRO) return 0; device = button->profile->device; drv_data = ratbag_get_drv_data(device); macro = &drv_data->macros[button->profile->index][button->index]; buf = (uint8_t*)macro; memset(buf, 0, ROCCAT_REPORT_SIZE_MACRO); for (i = 0; i < MAX_MACRO_EVENTS && count < ROCCAT_MAX_MACRO_LENGTH; i++) { if (action->macro->events[i].type == RATBAG_MACRO_EVENT_INVALID) return -EINVAL; /* should not happen, ever */ if (action->macro->events[i].type == RATBAG_MACRO_EVENT_NONE) break; /* ignore the first wait */ if (action->macro->events[i].type == RATBAG_MACRO_EVENT_WAIT && !count) continue; if (action->macro->events[i].type == RATBAG_MACRO_EVENT_KEY_PRESSED || action->macro->events[i].type == RATBAG_MACRO_EVENT_KEY_RELEASED) { macro->keys[count].keycode = ratbag_hidraw_get_keyboard_usage_from_keycode(device, action->macro->events[i].event.key); } switch (action->macro->events[i].type) { case RATBAG_MACRO_EVENT_KEY_PRESSED: macro->keys[count].flag = 0x01; break; case RATBAG_MACRO_EVENT_KEY_RELEASED: macro->keys[count].flag = 0x02; break; case RATBAG_MACRO_EVENT_WAIT: macro->keys[--count].time = action->macro->events[i].event.timeout; break; case RATBAG_MACRO_EVENT_INVALID: case RATBAG_MACRO_EVENT_NONE: /* should not happen */ log_error(device->ratbag, "something went wrong while writing a macro.\n"); } count++; } macro->reportID = ROCCAT_REPORT_ID_MACRO; macro->twentytwo = 0x22; macro->height = 0x08; macro->profile = button->profile->index; macro->button_index = button->index; macro->active = 0x01; strcpy(macro->group, "g0"); strncpy(macro->name, action->macro->name, 23); macro->length = count; macro->checksum = roccat_compute_crc(buf, ROCCAT_REPORT_SIZE_MACRO); rc = ratbag_hidraw_set_feature_report(device, ROCCAT_REPORT_ID_MACRO, buf, ROCCAT_REPORT_SIZE_MACRO); if (rc < 0) return rc; if (rc != ROCCAT_REPORT_SIZE_MACRO) return -EIO; rc = roccat_wait_ready(device); if (rc) log_error(device->ratbag, "Error while waiting for the device to be ready: %s (%d)\n", strerror(-rc), rc); return rc; } static int roccat_write_button(struct ratbag_button *button, const struct ratbag_button_action *action) { struct ratbag_profile *profile = button->profile; struct ratbag_device *device = profile->device; struct roccat_data *drv_data = ratbag_get_drv_data(device); uint8_t rc, *data; data = &drv_data->profiles[profile->index][3 + button->index * 3]; rc = roccat_button_action_to_raw(action); if (!rc) return -EINVAL; *data = rc; rc = roccat_write_profile(button->profile); if (rc) { log_error(device->ratbag, "unable to write the profile to the device: '%s' (%d)\n", strerror(-rc), rc); return rc; } rc = roccat_write_macro(button, action); if (rc) { log_error(device->ratbag, "unable to write the macro to the device: '%s' (%d)\n", strerror(-rc), rc); return rc; } return rc; } static int roccat_write_resolution_dpi(struct ratbag_resolution *resolution, int dpi_x, int dpi_y) { struct ratbag_profile *profile = resolution->profile; struct ratbag_device *device = profile->device; struct roccat_data *drv_data = ratbag_get_drv_data(device); struct roccat_settings_report *settings_report; uint8_t *buf; int rc; if (dpi_x < 100 || dpi_x > 5000 || dpi_x % 50) return -EINVAL; if (dpi_y < 100 || dpi_y > 5000 || dpi_y % 50) return -EINVAL; settings_report = &drv_data->settings[profile->index]; settings_report->xres[resolution->index] = dpi_x / 50; settings_report->yres[resolution->index] = dpi_y / 50; if (resolution->is_active) settings_report->current_dpi = resolution->index; buf = (uint8_t*)settings_report; // No checksum for settings report on Kone Pure. //settings_report->checksum = roccat_compute_crc(buf, ROCCAT_REPORT_SIZE_SETTINGS); rc = ratbag_hidraw_set_feature_report(device, ROCCAT_REPORT_ID_SETTINGS, buf, ROCCAT_REPORT_SIZE_SETTINGS); if (rc < 0) return rc; if (rc != ROCCAT_REPORT_SIZE_SETTINGS) return -EIO; rc = roccat_wait_ready(device); if (rc) log_error(device->ratbag, "Error while waiting for the device to be ready: %s (%d)\n", strerror(-rc), rc); return rc; } static void roccat_read_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct roccat_data *drv_data; struct ratbag_resolution *resolution; struct ratbag_button *button; struct roccat_settings_report *setting_report; uint8_t *buf; unsigned int report_rate; int dpi_x, dpi_y; int rc; unsigned int report_rates[] = { 125, 250, 500, 1000 }; assert(profile->index <= ROCCAT_PROFILE_MAX); drv_data = ratbag_get_drv_data(device); setting_report = &drv_data->settings[profile->index]; buf = (uint8_t*)setting_report; roccat_set_config_profile(device, profile->index, ROCCAT_CONFIG_SETTINGS); rc = ratbag_hidraw_get_feature_report(device, ROCCAT_REPORT_ID_SETTINGS, buf, ROCCAT_REPORT_SIZE_SETTINGS); if (rc < ROCCAT_REPORT_SIZE_SETTINGS) return; /* first retrieve the report rate, it is set per profile */ if (setting_report->report_rate < ARRAY_LENGTH(report_rates)) { report_rate = report_rates[setting_report->report_rate]; } else { log_error(device->ratbag, "error while reading the report rate of the mouse (0x%02x)\n", buf[26]); report_rate = 0; } ratbag_profile_set_report_rate_list(profile, report_rates, ARRAY_LENGTH(report_rates)); ratbag_profile_set_report_rate(profile, report_rate); ratbag_profile_for_each_resolution(profile, resolution) { dpi_x = setting_report->xres[resolution->index] * 50; dpi_y = setting_report->yres[resolution->index] * 50; if (!(setting_report->dpi_mask & (1 << resolution->index))) { /* the profile is disabled, overwrite it */ dpi_x = 0; dpi_y = 0; } ratbag_resolution_set_resolution(resolution, dpi_x, dpi_y); ratbag_resolution_set_cap(resolution, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION); resolution->is_active = (resolution->index == setting_report->current_dpi); ratbag_resolution_set_dpi_list_from_range(resolution, 100, 5000); } buf = drv_data->profiles[profile->index]; roccat_set_config_profile(device, profile->index, ROCCAT_CONFIG_KEY_MAPPING); rc = ratbag_hidraw_get_feature_report(device, ROCCAT_REPORT_ID_KEY_MAPPING, buf, ROCCAT_REPORT_SIZE_PROFILE); msleep(10); if (rc < ROCCAT_REPORT_SIZE_PROFILE) return; // Buttons were read from the buffer that was yet un-initialized with the device data. ratbag_profile_for_each_button(profile, button) roccat_read_button(button); if (!roccat_crc_is_valid(device, buf, ROCCAT_REPORT_SIZE_PROFILE)) log_error(device->ratbag, "Error while reading profile %d, continuing...\n", profile->index); log_raw(device->ratbag, "profile: %d %s:%d\n", buf[2], __FILE__, __LINE__); } static int roccat_probe(struct ratbag_device *device) { int rc; struct ratbag_profile *profile; struct roccat_data *drv_data; int active_idx; rc = ratbag_open_hidraw(device); if (rc) return rc; if (!ratbag_hidraw_has_report(device, ROCCAT_REPORT_ID_KEY_MAPPING)) { ratbag_close_hidraw(device); return -ENODEV; } drv_data = zalloc(sizeof(*drv_data)); ratbag_set_drv_data(device, drv_data); /* profiles are 0-indexed */ ratbag_device_init_profiles(device, ROCCAT_PROFILE_MAX + 1, ROCCAT_NUM_DPI, ROCCAT_BUTTON_MAX + 1, ROCCAT_LED_MAX); ratbag_device_for_each_profile(device, profile) roccat_read_profile(profile); active_idx = roccat_current_profile(device); if (active_idx < 0) { log_error(device->ratbag, "Can't talk to the mouse: '%s' (%d)\n", strerror(-active_idx), active_idx); rc = -ENODEV; goto err; } list_for_each(profile, &device->profiles, link) { if (profile->index == (unsigned int)active_idx) { profile->is_active = true; break; } } log_raw(device->ratbag, "'%s' is in profile %d\n", ratbag_device_get_name(device), profile->index); return 0; err: free(drv_data); ratbag_set_drv_data(device, NULL); return rc; } static void roccat_remove(struct ratbag_device *device) { ratbag_close_hidraw(device); free(ratbag_get_drv_data(device)); } struct ratbag_driver roccat_driver = { .name = "Roccat Kone Pure", .id = "roccat", .probe = roccat_probe, .remove = roccat_remove, .write_profile = roccat_write_profile, .set_active_profile = roccat_set_current_profile, .write_button = roccat_write_button, .write_resolution_dpi = roccat_write_resolution_dpi, }; libratbag-0.13/src/driver-roccat.c000066400000000000000000000555711362011324700170770ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include "config.h" #include #include #include #include #include #include #include #include "libratbag-private.h" #include "libratbag-hidraw.h" #define ROCCAT_PROFILE_MAX 4 #define ROCCAT_BUTTON_MAX 23 #define ROCCAT_NUM_DPI 5 #define ROCCAT_LED_MAX 0 #define ROCCAT_MAX_RETRY_READY 10 #define ROCCAT_REPORT_ID_CONFIGURE_PROFILE 4 #define ROCCAT_REPORT_ID_PROFILE 5 #define ROCCAT_REPORT_ID_SETTINGS 6 #define ROCCAT_REPORT_ID_KEY_MAPPING 7 #define ROCCAT_REPORT_ID_MACRO 8 #define ROCCAT_REPORT_SIZE_PROFILE 77 #define ROCCAT_REPORT_SIZE_SETTINGS 43 #define ROCCAT_REPORT_SIZE_MACRO 2082 #define ROCCAT_CONFIG_SETTINGS 0x80 #define ROCCAT_CONFIG_KEY_MAPPING 0x90 #define ROCCAT_MAX_MACRO_LENGTH 500 struct roccat_settings_report { uint8_t reportID; uint8_t twoB; uint8_t profileID; uint8_t x_y_linked; uint8_t x_sensitivity; /* 0x06 means 0 */ uint8_t y_sensitivity; /* 0x06 means 0 */ uint8_t dpi_mask; uint8_t xres[5]; uint8_t current_dpi; uint8_t yres[5]; uint8_t padding1; uint8_t report_rate; uint8_t padding2[21]; uint16_t checksum; // uint8_t padding2[4]; // uint8_t light; // uint8_t light_heartbit; // uint8_t padding3[5]; } __attribute__((packed)); struct roccat_macro { uint8_t reportID; uint8_t twentytwo; uint8_t height; uint8_t profile; uint8_t button_index; uint8_t active; uint8_t padding[24]; char group[24]; char name[24]; uint16_t length; struct { uint8_t keycode; uint8_t flag; uint16_t time; } keys[ROCCAT_MAX_MACRO_LENGTH]; uint16_t checksum; } __attribute__((packed)); struct roccat_data { uint8_t profiles[(ROCCAT_PROFILE_MAX + 1)][ROCCAT_REPORT_SIZE_PROFILE]; struct roccat_settings_report settings[(ROCCAT_PROFILE_MAX + 1)]; struct roccat_macro macros[(ROCCAT_PROFILE_MAX + 1)][(ROCCAT_BUTTON_MAX + 1)]; }; struct roccat_button_type_mapping { uint8_t raw; enum ratbag_button_type type; }; static const struct roccat_button_type_mapping roccat_button_type_mapping[] = { { 0, RATBAG_BUTTON_TYPE_LEFT }, { 1, RATBAG_BUTTON_TYPE_RIGHT }, { 2, RATBAG_BUTTON_TYPE_MIDDLE }, { 3, RATBAG_BUTTON_TYPE_EXTRA }, { 4, RATBAG_BUTTON_TYPE_SIDE }, { 5, RATBAG_BUTTON_TYPE_WHEEL_LEFT }, { 6, RATBAG_BUTTON_TYPE_WHEEL_RIGHT }, { 7, RATBAG_BUTTON_TYPE_WHEEL_UP }, { 8, RATBAG_BUTTON_TYPE_WHEEL_DOWN }, { 9, RATBAG_BUTTON_TYPE_RESOLUTION_UP }, { 10, RATBAG_BUTTON_TYPE_RESOLUTION_DOWN }, // { 11, RATBAG_BUTTON_TYPE_ }, /* top button above the wheel */ { 12, RATBAG_BUTTON_TYPE_LEFT }, { 13, RATBAG_BUTTON_TYPE_RIGHT }, { 14, RATBAG_BUTTON_TYPE_MIDDLE }, { 15, RATBAG_BUTTON_TYPE_EXTRA }, { 16, RATBAG_BUTTON_TYPE_SIDE }, { 17, RATBAG_BUTTON_TYPE_WHEEL_LEFT }, { 18, RATBAG_BUTTON_TYPE_WHEEL_RIGHT }, { 19, RATBAG_BUTTON_TYPE_WHEEL_UP }, { 20, RATBAG_BUTTON_TYPE_WHEEL_DOWN }, { 21, RATBAG_BUTTON_TYPE_RESOLUTION_UP }, { 22, RATBAG_BUTTON_TYPE_RESOLUTION_DOWN }, // { 23, RATBAG_BUTTON_TYPE_ }, /* top button above the wheel */ }; static enum ratbag_button_type roccat_raw_to_button_type(uint8_t data) { const struct roccat_button_type_mapping *mapping; ARRAY_FOR_EACH(roccat_button_type_mapping, mapping) { if (mapping->raw == data) return mapping->type; } return RATBAG_BUTTON_TYPE_UNKNOWN; } struct roccat_button_mapping { uint8_t raw; struct ratbag_button_action action; }; static struct roccat_button_mapping roccat_button_mapping[] = { /* FIXME: { 0, Disabled }, */ { 1, BUTTON_ACTION_BUTTON(1) }, { 2, BUTTON_ACTION_BUTTON(2) }, { 3, BUTTON_ACTION_BUTTON(3) }, { 4, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_DOUBLECLICK) }, /* FIXME: { 5, Shortcut (modifier + key) }, */ { 6, BUTTON_ACTION_NONE }, { 7, BUTTON_ACTION_BUTTON(4) }, { 8, BUTTON_ACTION_BUTTON(5) }, { 9, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT) }, { 10, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT) }, { 13, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP) }, { 14, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN) }, /* FIXME: { 15, quicklaunch }, -> hidraw report 03 00 60 07 01 00 00 00 */ { 16, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) }, { 17, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP) }, { 18, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN) }, { 20, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) }, { 21, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) }, { 22, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) }, { 26, BUTTON_ACTION_KEY(KEY_LEFTMETA) }, /* FIXME: { 27, open driver }, -> hidraw report 02 83 01 00 00 00 00 00 */ { 32, BUTTON_ACTION_KEY(KEY_CONFIG) }, { 33, BUTTON_ACTION_KEY(KEY_PREVIOUSSONG) }, { 34, BUTTON_ACTION_KEY(KEY_NEXTSONG) }, { 35, BUTTON_ACTION_KEY(KEY_PLAYPAUSE) }, { 36, BUTTON_ACTION_KEY(KEY_STOPCD) }, { 37, BUTTON_ACTION_KEY(KEY_MUTE) }, { 38, BUTTON_ACTION_KEY(KEY_VOLUMEUP) }, { 39, BUTTON_ACTION_KEY(KEY_VOLUMEDOWN) }, { 48, BUTTON_ACTION_MACRO }, { 65, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE) }, /* FIXME: { 66, Easywheel sensitivity }, */ /* FIXME: { 67, Easywheel profile }, */ /* FIXME: { 68, Easywheel CPI }, */ /* FIXME: { 81, Other Easyshift }, -> hidraw report 03 00 ff 05 01 00 00 00 */ /* FIXME: { 82, Other Easyshift Lock }, -> hidraw report 03 00 ff 05 01 00 00 00 */ /* FIXME: { 83, Both Easyshift }, -> hidraw report 03 00 ff 04 01 00 00 00 */ }; static const struct ratbag_button_action* roccat_raw_to_button_action(uint8_t data) { struct roccat_button_mapping *mapping; ARRAY_FOR_EACH(roccat_button_mapping, mapping) { if (mapping->raw == data) return &mapping->action; } return NULL; } static uint8_t roccat_button_action_to_raw(const struct ratbag_button_action *action) { struct roccat_button_mapping *mapping; ARRAY_FOR_EACH(roccat_button_mapping, mapping) { if (ratbag_button_action_match(&mapping->action, action)) return mapping->raw; } return 0; } static inline uint16_t roccat_get_unaligned_u16(uint8_t *buf) { return (buf[1] << 8) | buf[0]; } static inline uint16_t roccat_compute_crc(uint8_t *buf, unsigned int len) { unsigned i; uint16_t crc = 0; if (len < 3) return 0; for (i = 0; i < len - 2; i++) { crc += buf[i]; } return crc; } static inline int roccat_crc_is_valid(struct ratbag_device *device, uint8_t *buf, unsigned int len) { uint16_t crc; uint16_t given_crc; if (len < 3) return 0; crc = roccat_compute_crc(buf, len); given_crc = roccat_get_unaligned_u16(&buf[len - 2]); log_raw(device->ratbag, "checksum computed: 0x%04x, checksum given: 0x%04x\n", crc, given_crc); return crc == given_crc; } static int roccat_is_ready(struct ratbag_device *device) { uint8_t buf[3] = { 0 }; int rc; rc = ratbag_hidraw_get_feature_report(device, ROCCAT_REPORT_ID_CONFIGURE_PROFILE, buf, sizeof(buf)); if (rc < 0) return rc; if (rc != sizeof(buf)) return -EIO; if (buf[1] == 0x03) msleep(100); if (buf[1] == 0x02) return 2; return buf[1] == 0x01; } static int roccat_wait_ready(struct ratbag_device *device) { unsigned count = 0; int rc; msleep(10); while (count < ROCCAT_MAX_RETRY_READY) { rc = roccat_is_ready(device); if (rc < 0) return rc; if (rc == 1) return 0; if (rc == 2) return 2; msleep(10); count++; } return -ETIMEDOUT; } static int roccat_current_profile(struct ratbag_device *device) { uint8_t buf[3]; int ret; ret = ratbag_hidraw_get_feature_report(device, ROCCAT_REPORT_ID_PROFILE, buf, sizeof(buf)); if (ret < 0) return ret; if (ret != 3) return -EIO; return buf[2]; } static int roccat_set_current_profile(struct ratbag_device *device, unsigned int index) { uint8_t buf[] = {ROCCAT_REPORT_ID_PROFILE, 0x03, index}; int ret; if (index > ROCCAT_PROFILE_MAX) return -EINVAL; ret = ratbag_hidraw_set_feature_report(device, buf[0], buf, sizeof(buf)); if (ret < 0) return ret; if (ret != sizeof(buf)) return -EIO; ret = roccat_wait_ready(device); if (ret) log_error(device->ratbag, "Error while waiting for the device to be ready: %s (%d)\n", strerror(-ret), ret); return ret; } static int roccat_set_config_profile(struct ratbag_device *device, uint8_t profile, uint8_t type) { uint8_t buf[] = {ROCCAT_REPORT_ID_CONFIGURE_PROFILE, profile, type}; int ret; if (profile > ROCCAT_PROFILE_MAX) return -EINVAL; ret = ratbag_hidraw_set_feature_report(device, buf[0], buf, sizeof(buf)); if (ret < 0) return ret; if (ret != sizeof(buf)) return -EIO; ret = roccat_wait_ready(device); if (ret < 0) log_error(device->ratbag, "Error while waiting for the device to be ready: %s (%d)\n", strerror(-ret), ret); return ret; } static const struct ratbag_button_action * roccat_button_to_action(struct ratbag_profile *profile, unsigned int button_index) { struct ratbag_device *device = profile->device; struct roccat_data *drv_data = ratbag_get_drv_data(device); uint8_t data; data = drv_data->profiles[profile->index][3 + button_index * 3]; return roccat_raw_to_button_action(data); } static int roccat_write_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; unsigned int index = profile->index; struct roccat_data *drv_data; int rc; uint8_t *buf; uint16_t *crc; assert(index <= ROCCAT_PROFILE_MAX); drv_data = ratbag_get_drv_data(device); buf = drv_data->profiles[index]; crc = (uint16_t *)&buf[ROCCAT_REPORT_SIZE_PROFILE - 2]; *crc = roccat_compute_crc(buf, ROCCAT_REPORT_SIZE_PROFILE); roccat_set_config_profile(device, index, ROCCAT_CONFIG_KEY_MAPPING); rc = ratbag_hidraw_set_feature_report(device, ROCCAT_REPORT_ID_KEY_MAPPING, buf, ROCCAT_REPORT_SIZE_PROFILE); if (rc < ROCCAT_REPORT_SIZE_PROFILE) return -EIO; log_raw(device->ratbag, "profile: %d written %s:%d\n", buf[2], __FILE__, __LINE__); rc = roccat_wait_ready(device); if (rc) log_error(device->ratbag, "Error while waiting for the device to be ready: %s (%d)\n", strerror(-rc), rc); return rc; } static void roccat_read_button(struct ratbag_button *button) { const struct ratbag_button_action *action; struct ratbag_device *device; struct roccat_macro *macro; struct roccat_data *drv_data; uint8_t *buf; unsigned j, time; int rc; device = button->profile->device; drv_data = ratbag_get_drv_data(device); action = roccat_button_to_action(button->profile, button->index); if (action) ratbag_button_set_action(button, action); button->type = roccat_raw_to_button_type(button->index); // if (action == NULL) // log_error(device->ratbag, "button: %d -> %d %s:%d\n", // button->index, drv_data->profiles[button->profile->index][3 + button->index * 3], // __FILE__, __LINE__); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); if (action && action->type == RATBAG_BUTTON_ACTION_TYPE_MACRO) { struct ratbag_button_macro *m = NULL; roccat_set_config_profile(device, button->profile->index, 0); roccat_set_config_profile(device, button->profile->index, button->index); macro = &drv_data->macros[button->profile->index][button->index]; buf = (uint8_t*)macro; buf[0] = ROCCAT_REPORT_ID_MACRO; rc = ratbag_hidraw_get_feature_report(device, ROCCAT_REPORT_ID_MACRO, buf, ROCCAT_REPORT_SIZE_MACRO); if (rc != ROCCAT_REPORT_SIZE_MACRO) { log_error(device->ratbag, "Unable to retrieve the macro for button %d of profile %d: %s (%d)\n", button->index, button->profile->index, rc < 0 ? strerror(-rc) : "not read enough", rc); } else { if (buf[0] != ROCCAT_REPORT_ID_MACRO) { log_error(device->ratbag, "Error while reading the macro of button %d of profile %d.\n", button->index, button->profile->index); goto out_macro; } if (!roccat_crc_is_valid(device, buf, ROCCAT_REPORT_SIZE_MACRO)) { log_error(device->ratbag, "wrong checksum while reading the macro of button %d of profile %d.\n", button->index, button->profile->index); goto out_macro; } m = ratbag_button_macro_new(macro->name); log_raw(device->ratbag, "macro on button %d of profile %d is named '%s', and contains %d events:\n", button->index, button->profile->index, macro->name, macro->length); for (j = 0; j < macro->length; j++) { unsigned int keycode = ratbag_hidraw_get_keycode_from_keyboard_usage(device, macro->keys[j].keycode); ratbag_button_macro_set_event(m, j * 2, macro->keys[j].flag & 0x01 ? RATBAG_MACRO_EVENT_KEY_PRESSED : RATBAG_MACRO_EVENT_KEY_RELEASED, keycode); if (macro->keys[j].time) time = macro->keys[j].time; else time = macro->keys[j].flag & 0x01 ? 10 : 50; ratbag_button_macro_set_event(m, j * 2 + 1, RATBAG_MACRO_EVENT_WAIT, time); log_raw(device->ratbag, " - %s %s\n", libevdev_event_code_get_name(EV_KEY, keycode), macro->keys[j].flag & 0x80 ? "released" : "pressed"); } ratbag_button_copy_macro(button, m); } out_macro: msleep(10); ratbag_button_macro_unref(m); } } static int roccat_write_macro(struct ratbag_button *button, const struct ratbag_button_action *action) { struct ratbag_device *device; struct roccat_macro *macro; struct roccat_data *drv_data; uint8_t *buf; unsigned i, count = 0; int rc; if (action->type != RATBAG_BUTTON_ACTION_TYPE_MACRO) return 0; device = button->profile->device; drv_data = ratbag_get_drv_data(device); macro = &drv_data->macros[button->profile->index][button->index]; buf = (uint8_t*)macro; memset(buf, 0, ROCCAT_REPORT_SIZE_MACRO); for (i = 0; i < MAX_MACRO_EVENTS && count < ROCCAT_MAX_MACRO_LENGTH; i++) { if (action->macro->events[i].type == RATBAG_MACRO_EVENT_INVALID) return -EINVAL; /* should not happen, ever */ if (action->macro->events[i].type == RATBAG_MACRO_EVENT_NONE) break; /* ignore the first wait */ if (action->macro->events[i].type == RATBAG_MACRO_EVENT_WAIT && !count) continue; if (action->macro->events[i].type == RATBAG_MACRO_EVENT_KEY_PRESSED || action->macro->events[i].type == RATBAG_MACRO_EVENT_KEY_RELEASED) { macro->keys[count].keycode = ratbag_hidraw_get_keyboard_usage_from_keycode(device, action->macro->events[i].event.key); } switch (action->macro->events[i].type) { case RATBAG_MACRO_EVENT_KEY_PRESSED: macro->keys[count].flag = 0x01; break; case RATBAG_MACRO_EVENT_KEY_RELEASED: macro->keys[count].flag = 0x02; break; case RATBAG_MACRO_EVENT_WAIT: macro->keys[--count].time = action->macro->events[i].event.timeout; break; case RATBAG_MACRO_EVENT_INVALID: case RATBAG_MACRO_EVENT_NONE: /* should not happen */ log_error(device->ratbag, "something went wrong while writing a macro.\n"); } count++; } macro->reportID = ROCCAT_REPORT_ID_MACRO; macro->twentytwo = 0x22; macro->height = 0x08; macro->profile = button->profile->index; macro->button_index = button->index; macro->active = 0x01; strcpy(macro->group, "g0"); strncpy(macro->name, action->macro->name, 23); macro->length = count; macro->checksum = roccat_compute_crc(buf, ROCCAT_REPORT_SIZE_MACRO); rc = ratbag_hidraw_set_feature_report(device, ROCCAT_REPORT_ID_MACRO, buf, ROCCAT_REPORT_SIZE_MACRO); if (rc < 0) return rc; if (rc != ROCCAT_REPORT_SIZE_MACRO) return -EIO; rc = roccat_wait_ready(device); if (rc) log_error(device->ratbag, "Error while waiting for the device to be ready: %s (%d)\n", strerror(-rc), rc); return rc; } static int roccat_write_button(struct ratbag_button *button, const struct ratbag_button_action *action) { struct ratbag_profile *profile = button->profile; struct ratbag_device *device = profile->device; struct roccat_data *drv_data = ratbag_get_drv_data(device); uint8_t rc, *data; data = &drv_data->profiles[profile->index][3 + button->index * 3]; rc = roccat_button_action_to_raw(action); if (!rc) return -EINVAL; *data = rc; rc = roccat_write_profile(button->profile); if (rc) { log_error(device->ratbag, "unable to write the profile to the device: '%s' (%d)\n", strerror(-rc), rc); return rc; } rc = roccat_write_macro(button, action); if (rc) { log_error(device->ratbag, "unable to write the macro to the device: '%s' (%d)\n", strerror(-rc), rc); return rc; } return rc; } static int roccat_write_resolution_dpi(struct ratbag_resolution *resolution, int dpi_x, int dpi_y) { struct ratbag_profile *profile = resolution->profile; struct ratbag_device *device = profile->device; struct roccat_data *drv_data = ratbag_get_drv_data(device); struct roccat_settings_report *settings_report; uint8_t *buf; int rc; if (dpi_x < 200 || dpi_x > 8200 || dpi_x % 50) return -EINVAL; if (dpi_y < 200 || dpi_y > 8200 || dpi_y % 50) return -EINVAL; settings_report = &drv_data->settings[profile->index]; settings_report->xres[resolution->index] = dpi_x / 50; settings_report->yres[resolution->index] = dpi_y / 50; buf = (uint8_t*)settings_report; settings_report->checksum = roccat_compute_crc(buf, ROCCAT_REPORT_SIZE_SETTINGS); rc = ratbag_hidraw_set_feature_report(device, ROCCAT_REPORT_ID_SETTINGS, buf, ROCCAT_REPORT_SIZE_SETTINGS); if (rc < 0) return rc; if (rc != ROCCAT_REPORT_SIZE_SETTINGS) return -EIO; rc = roccat_wait_ready(device); if (rc) log_error(device->ratbag, "Error while waiting for the device to be ready: %s (%d)\n", strerror(-rc), rc); return rc; } static void roccat_read_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct roccat_data *drv_data; struct ratbag_resolution *resolution; struct ratbag_button *button; struct roccat_settings_report *setting_report; uint8_t *buf; unsigned int report_rate; int dpi_x, dpi_y; int rc; unsigned int report_rates[] = { 125, 250, 500, 1000 }; assert(profile->index <= ROCCAT_PROFILE_MAX); drv_data = ratbag_get_drv_data(device); setting_report = &drv_data->settings[profile->index]; buf = (uint8_t*)setting_report; roccat_set_config_profile(device, profile->index, ROCCAT_CONFIG_SETTINGS); rc = ratbag_hidraw_get_feature_report(device, ROCCAT_REPORT_ID_SETTINGS, buf, ROCCAT_REPORT_SIZE_SETTINGS); if (rc < ROCCAT_REPORT_SIZE_SETTINGS) return; /* first retrieve the report rate, it is set per profile */ if (setting_report->report_rate < ARRAY_LENGTH(report_rates)) { report_rate = report_rates[setting_report->report_rate]; } else { log_error(device->ratbag, "error while reading the report rate of the mouse (0x%02x)\n", buf[26]); report_rate = 0; } ratbag_profile_set_report_rate_list(profile, report_rates, ARRAY_LENGTH(report_rates)); ratbag_profile_set_report_rate(profile, report_rate); ratbag_profile_for_each_resolution(profile, resolution) { dpi_x = setting_report->xres[resolution->index] * 50; dpi_y = setting_report->yres[resolution->index] * 50; if (!(setting_report->dpi_mask & (1 << resolution->index))) { /* the profile is disabled, overwrite it */ dpi_x = 0; dpi_y = 0; } ratbag_resolution_set_resolution(resolution, dpi_x, dpi_y); ratbag_resolution_set_cap(resolution, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION); resolution->is_active = (resolution->index == setting_report->current_dpi); ratbag_resolution_set_dpi_list_from_range(resolution, 200, 8200); } ratbag_profile_for_each_button(profile, button) roccat_read_button(button); buf = drv_data->profiles[profile->index]; roccat_set_config_profile(device, profile->index, ROCCAT_CONFIG_KEY_MAPPING); rc = ratbag_hidraw_get_feature_report(device, ROCCAT_REPORT_ID_KEY_MAPPING, buf, ROCCAT_REPORT_SIZE_PROFILE); msleep(10); if (rc < ROCCAT_REPORT_SIZE_PROFILE) return; if (!roccat_crc_is_valid(device, buf, ROCCAT_REPORT_SIZE_PROFILE)) log_error(device->ratbag, "Error while reading profile %d, continuing...\n", profile->index); log_raw(device->ratbag, "profile: %d %s:%d\n", buf[2], __FILE__, __LINE__); } static int roccat_probe(struct ratbag_device *device) { int rc; struct ratbag_profile *profile; struct roccat_data *drv_data; int active_idx; rc = ratbag_open_hidraw(device); if (rc) return rc; if (!ratbag_hidraw_has_report(device, ROCCAT_REPORT_ID_KEY_MAPPING)) { ratbag_close_hidraw(device); return -ENODEV; } drv_data = zalloc(sizeof(*drv_data)); ratbag_set_drv_data(device, drv_data); /* profiles are 0-indexed */ ratbag_device_init_profiles(device, ROCCAT_PROFILE_MAX + 1, ROCCAT_NUM_DPI, ROCCAT_BUTTON_MAX + 1, ROCCAT_LED_MAX); ratbag_device_for_each_profile(device, profile) roccat_read_profile(profile); active_idx = roccat_current_profile(device); if (active_idx < 0) { log_error(device->ratbag, "Can't talk to the mouse: '%s' (%d)\n", strerror(-active_idx), active_idx); rc = -ENODEV; goto err; } list_for_each(profile, &device->profiles, link) { if (profile->index == (unsigned int)active_idx) { profile->is_active = true; break; } } log_raw(device->ratbag, "'%s' is in profile %d\n", ratbag_device_get_name(device), profile->index); return 0; err: free(drv_data); ratbag_set_drv_data(device, NULL); return rc; } static void roccat_remove(struct ratbag_device *device) { ratbag_close_hidraw(device); free(ratbag_get_drv_data(device)); } struct ratbag_driver roccat_driver = { .name = "Roccat Kone XTD", .id = "roccat", .probe = roccat_probe, .remove = roccat_remove, .write_profile = roccat_write_profile, .set_active_profile = roccat_set_current_profile, .write_button = roccat_write_button, .write_resolution_dpi = roccat_write_resolution_dpi, }; libratbag-0.13/src/driver-steelseries.c000066400000000000000000000646661362011324700201600ustar00rootroot00000000000000/* * Copyright © 2017 Thomas Hindoe Paaboel Andersen. * * 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 (including the next * paragraph) 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. */ #include "config.h" #include #include #include #include #include #include #include #include "libratbag-private.h" #include "libratbag-hidraw.h" #include "libratbag-data.h" #include "hidpp-generic.h" #define STEELSERIES_NUM_PROFILES 1 #define STEELSERIES_NUM_DPI 2 #define STEELSERIES_INPUT_ENDPOINT 0 #define STEELSERIES_INPUT_HIDRAW 1 /* not sure these two are used for */ #define STEELSERIES_REPORT_ID_1 0x01 #define STEELSERIES_REPORT_ID_2 0x02 #define STEELSERIES_REPORT_SIZE_SHORT 32 #define STEELSERIES_REPORT_SIZE 64 #define STEELSERIES_REPORT_LONG_SIZE 262 #define STEELSERIES_ID_DPI_SHORT 0x03 #define STEELSERIES_ID_REPORT_RATE_SHORT 0x04 #define STEELSERIES_ID_LED_INTENSITY_SHORT 0x05 #define STEELSERIES_ID_LED_EFFECT_SHORT 0x07 #define STEELSERIES_ID_LED_COLOR_SHORT 0x08 #define STEELSERIES_ID_SAVE_SHORT 0x09 #define STEELSERIES_ID_BUTTONS 0x31 #define STEELSERIES_ID_DPI 0x53 #define STEELSERIES_ID_REPORT_RATE 0x54 #define STEELSERIES_ID_LED 0x5b #define STEELSERIES_ID_SAVE 0x59 #define STEELSERIES_ID_FIRMWARE 0x90 #define STEELSERIES_ID_SETTTINGS 0x92 #define STEELSERIES_ID_DPI_PROTOCOL3 0x03 #define STEELSERIES_ID_REPORT_RATE_PROTOCOL3 0x04 #define STEELSERIES_ID_LED_PROTOCOL3 0x05 #define STEELSERIES_ID_SAVE_PROTOCOL3 0x09 #define STEELSERIES_ID_FIRMWARE_PROTOCOL3 0x10 #define STEELSERIES_ID_SETTTINGS_PROTOCOL3 0x16 #define STEELSERIES_BUTTON_OFF 0x00 #define STEELSERIES_BUTTON_RES_CYCLE 0x30 #define STEELSERIES_BUTTON_WHEEL_UP 0x31 #define STEELSERIES_BUTTON_WHEEL_DOWN 0x32 #define STEELSERIES_BUTTON_KEY 0x10 #define STEELSERIES_BUTTON_KBD 0x51 #define STEELSERIES_BUTTON_CONSUMER 0x61 struct steelseries_data { int firmware_major; int firmware_minor; }; struct steelseries_point { struct list link; struct ratbag_color color; /* point color */ uint8_t pos; /* relative position in the cycle */ }; struct steelseries_led_cycle { uint8_t led_id; /* led id */ uint16_t duration; /* cycle duration */ bool repeat; /* if the cycle restarts automatically */ uint8_t trigger_buttons; /* trigger button combination */ struct list points; /* colors in the cycle */ }; struct steelseries_led_cycle_spec { int hid_report_type; /* either HID_OUTPUT_REPORT or HID_FEATURE_REPORT */ int header_len; /* number of bytes in the header */ uint8_t cmd_val; /* command value for the color command */ bool has_2_led_ids; /* some mice have 2 fields for led id */ int led_id_idx; /* index of the led id field */ int led_id2_idx; /* 2nd led id index (if required by protocol) */ int duration_idx; /* index of the cycle duration field */ int repeat_idx; /* index of the repeat field */ int trigger_idx; /* index of the trigger mask field */ int point_count_idx; /* index of the point counter field */ }; static int steelseries_test_hidraw(struct ratbag_device *device) { int device_version = ratbag_device_data_steelseries_get_device_version(device->data); if (device_version > 1) return ratbag_hidraw_has_report(device, STEELSERIES_REPORT_ID_1); else return true; } static void button_defaults_for_layout(struct ratbag_button *button, int button_count) { /* The default button mapping vary depending on the number of buttons * on the device. */ enum ratbag_button_type button_types[8] = {RATBAG_BUTTON_TYPE_UNKNOWN}; struct ratbag_button_action button_actions[8] = { BUTTON_ACTION_BUTTON(1), BUTTON_ACTION_BUTTON(2), BUTTON_ACTION_BUTTON(3), BUTTON_ACTION_BUTTON(4), BUTTON_ACTION_BUTTON(5), BUTTON_ACTION_BUTTON(6), BUTTON_ACTION_BUTTON(7), BUTTON_ACTION_BUTTON(8), }; if (button_count <= 6) { button_actions[5].type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; button_actions[5].action.special = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP; button_actions[6].type = RATBAG_BUTTON_ACTION_TYPE_NONE; button_actions[7].type = RATBAG_BUTTON_ACTION_TYPE_NONE; } else if (button_count == 7) { button_actions[6].type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; button_actions[6].action.special = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP; button_actions[7].type = RATBAG_BUTTON_ACTION_TYPE_NONE; } else { button_actions[7].type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; button_actions[7].action.special = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP; } button_types[0] = RATBAG_BUTTON_TYPE_LEFT; button_types[1] = RATBAG_BUTTON_TYPE_RIGHT; button_types[2] = RATBAG_BUTTON_TYPE_MIDDLE; button_types[3] = RATBAG_BUTTON_TYPE_THUMB; button_types[4] = RATBAG_BUTTON_TYPE_THUMB2; if (button_count <= 6) { button_types[5] = RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP; } else if (button_count == 7) { button_types[5] = RATBAG_BUTTON_TYPE_THUMB3; button_types[6] = RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP; } else { button_types[5] = RATBAG_BUTTON_TYPE_PINKIE; button_types[6] = RATBAG_BUTTON_TYPE_PINKIE2; button_types[7] = RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP; } button->type = button_types[button->index]; ratbag_button_set_action(button, &button_actions[button->index]); } static int steelseries_get_firmware_version(struct ratbag_device *device) { struct steelseries_data *drv_data = device->drv_data; int device_version = ratbag_device_data_steelseries_get_device_version(device->data); size_t buf_len = STEELSERIES_REPORT_SIZE; uint8_t buf[STEELSERIES_REPORT_SIZE] = {0}; int ret; if (device_version == 2) buf[0] = STEELSERIES_ID_FIRMWARE; else if (device_version == 3) buf[0] = STEELSERIES_ID_FIRMWARE_PROTOCOL3; else return -ENOTSUP; msleep(10); ret = ratbag_hidraw_output_report(device, buf, buf_len); if (ret < 0) return ret; ret = ratbag_hidraw_read_input_report_index(device, buf, buf_len, STEELSERIES_INPUT_HIDRAW); if (ret < 0) return ret; drv_data->firmware_major = buf[1]; drv_data->firmware_minor = buf[0]; return 0; } static int steelseries_read_settings(struct ratbag_device *device) { int device_version = ratbag_device_data_steelseries_get_device_version(device->data); struct ratbag_profile *profile = NULL; struct ratbag_resolution *resolution; struct ratbag_led *led; size_t buf_len = STEELSERIES_REPORT_SIZE; uint8_t buf[STEELSERIES_REPORT_SIZE] = {0}; int ret; unsigned int active_resolution; if (device_version == 2) buf[0] = STEELSERIES_ID_SETTTINGS; else if (device_version == 3) buf[0] = STEELSERIES_ID_SETTTINGS_PROTOCOL3; else return -ENOTSUP; msleep(10); ret = ratbag_hidraw_output_report(device, buf, buf_len); if (ret < 0) return ret; ret = ratbag_hidraw_read_input_report_index(device, buf, buf_len, STEELSERIES_INPUT_HIDRAW); if (ret < 0) return ret; if (device_version == 2) { active_resolution = buf[1] - 1; ratbag_device_for_each_profile(device, profile) { ratbag_profile_for_each_resolution(profile, resolution) { resolution->is_active = resolution->index == active_resolution; resolution->dpi_x = 100 * (1 + buf[2 + resolution->index*2]); resolution->dpi_y = resolution->dpi_x; } ratbag_profile_for_each_led(profile, led) { led->color.red = buf[6 + led->index * 3]; led->color.green = buf[7 + led->index * 3]; led->color.blue = buf[8 + led->index * 3]; } } } else if (device_version == 3) { active_resolution = buf[0] - 1; ratbag_device_for_each_profile(device, profile) { ratbag_profile_for_each_resolution(profile, resolution) { resolution->is_active = resolution->index == active_resolution; } } } return 0; } static int steelseries_probe(struct ratbag_device *device) { struct steelseries_data *drv_data = NULL; struct ratbag_profile *profile = NULL; struct ratbag_resolution *resolution; struct ratbag_button *button; struct ratbag_led *led; int rc, button_count, led_count, device_version, mono_led, short_button; struct dpi_list *dpilist = NULL; struct dpi_range *dpirange = NULL; unsigned int report_rates[] = { 125, 250, 500, 1000 }; rc = ratbag_find_hidraw(device, steelseries_test_hidraw); if (rc) return rc; rc = ratbag_open_hidraw_index(device, STEELSERIES_INPUT_ENDPOINT, STEELSERIES_INPUT_HIDRAW); if (rc) return rc; device_version = ratbag_device_data_steelseries_get_device_version(device->data); button_count = ratbag_device_data_steelseries_get_button_count(device->data); led_count = ratbag_device_data_steelseries_get_led_count(device->data); dpirange = ratbag_device_data_steelseries_get_dpi_range(device->data); dpilist = ratbag_device_data_steelseries_get_dpi_list(device->data); mono_led = ratbag_device_data_steelseries_get_mono_led(device->data); short_button = ratbag_device_data_steelseries_get_short_button(device->data); ratbag_device_init_profiles(device, STEELSERIES_NUM_PROFILES, STEELSERIES_NUM_DPI, button_count, led_count); /* The device does not support reading the current settings. Fall back to some sensible defaults */ ratbag_device_for_each_profile(device, profile) { profile->is_active = true; ratbag_profile_set_cap(profile, RATBAG_PROFILE_CAP_WRITE_ONLY); ratbag_profile_set_report_rate_list(profile, report_rates, ARRAY_LENGTH(report_rates)); ratbag_profile_set_report_rate(profile, 1000); ratbag_profile_for_each_resolution(profile, resolution) { if (resolution->index == 0) { resolution->is_active = true; resolution->is_default = true; } if (dpirange) ratbag_resolution_set_dpi_list_from_range(resolution, dpirange->min, dpirange->max); if (dpilist) ratbag_resolution_set_dpi_list(resolution, (unsigned int *)dpilist->entries, dpilist->nentries); /* 800 and 1600 seem as reasonable defaults supported * by all known devices. */ resolution->dpi_x = 800 * (resolution->index + 1); resolution->dpi_y = 800 * (resolution->index + 1); } ratbag_profile_for_each_button(profile, button) { ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); if (short_button == 0) { ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); } button_defaults_for_layout(button, button_count); } ratbag_profile_for_each_led(profile, led) { led->type = led->index == 0 ? RATBAG_LED_TYPE_LOGO : RATBAG_LED_TYPE_WHEEL; led->mode = RATBAG_LED_ON; if (mono_led) { led->colordepth = RATBAG_LED_COLORDEPTH_MONOCHROME; led->brightness = 255; } else { led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888; led->color.red = 0; led->color.green = 0; led->color.blue = 255; } ratbag_led_set_mode_capability(led, RATBAG_LED_OFF); ratbag_led_set_mode_capability(led, RATBAG_LED_ON); ratbag_led_set_mode_capability(led, RATBAG_LED_BREATHING); if (device_version >= 2) ratbag_led_set_mode_capability(led, RATBAG_LED_CYCLE); } } drv_data = zalloc(sizeof(*drv_data)); ratbag_set_drv_data(device, drv_data); rc = steelseries_get_firmware_version(device); if(rc == 0) log_debug(device->ratbag, "SteelSeries firmware version %d.%d\n", drv_data->firmware_major, drv_data->firmware_minor); steelseries_read_settings(device); return 0; } static int steelseries_write_dpi(struct ratbag_resolution *resolution) { struct ratbag_device *device = resolution->profile->device; int device_version = ratbag_device_data_steelseries_get_device_version(device->data); struct dpi_list *dpilist = NULL; struct dpi_range *dpirange = NULL; int ret; size_t buf_len; uint8_t buf[STEELSERIES_REPORT_SIZE] = {0}; dpirange = ratbag_device_data_steelseries_get_dpi_range(device->data); dpilist = ratbag_device_data_steelseries_get_dpi_list(device->data); if (device_version == 1) { int i = 0; /* when using lists the entries are enumerated in reverse */ if (dpilist) { for (i = 0; i < (int)resolution->ndpis; i++) { if (resolution->dpis[i] == resolution->dpi_x) break; } i = resolution->ndpis - i; } else { i = resolution->dpi_x / dpirange->step - 1; } buf_len = STEELSERIES_REPORT_SIZE_SHORT; buf[0] = STEELSERIES_ID_DPI_SHORT; buf[1] = resolution->index + 1; buf[2] = i; } else if (device_version == 2) { buf_len = STEELSERIES_REPORT_SIZE; buf[0] = STEELSERIES_ID_DPI; buf[2] = resolution->index + 1; buf[3] = resolution->dpi_x / dpirange->step - 1; buf[6] = 0x42; /* not sure if needed */ } else if (device_version == 3) { buf_len = STEELSERIES_REPORT_SIZE; buf[0] = STEELSERIES_ID_DPI_PROTOCOL3; buf[2] = resolution->index + 1; buf[3] = resolution->dpi_x / dpirange->step - 1; buf[5] = 0x42; /* not sure if needed */ } else { return -ENOTSUP; } msleep(10); ret = ratbag_hidraw_output_report(device, buf, buf_len); if (ret < 0) return ret; return 0; } static int steelseries_write_report_rate(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; int device_version = ratbag_device_data_steelseries_get_device_version(device->data); int ret; size_t buf_len; uint8_t buf[STEELSERIES_REPORT_SIZE] = {0}; if (device_version == 1) { buf_len = STEELSERIES_REPORT_SIZE_SHORT; buf[0] = STEELSERIES_ID_REPORT_RATE_SHORT; buf[2] = 1000 / profile->hz; } else if (device_version == 2) { buf_len = STEELSERIES_REPORT_SIZE; buf[0] = STEELSERIES_ID_REPORT_RATE; buf[2] = 1000 / profile->hz; } else if (device_version == 3) { buf_len = STEELSERIES_REPORT_SIZE; buf[0] = STEELSERIES_ID_REPORT_RATE_PROTOCOL3; buf[2] = 1000 / profile->hz; } else { return -ENOTSUP; } msleep(10); ret = ratbag_hidraw_output_report(device, buf, buf_len); if (ret < 0) return ret; return 0; } static int steelseries_write_buttons(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct ratbag_button *button; int device_version = ratbag_device_data_steelseries_get_device_version(device->data); int ret; if (ratbag_device_data_steelseries_get_macro_length(device->data) == 0) return 0; int short_button = ratbag_device_data_steelseries_get_short_button(device->data); int button_size = (short_button == 0) ? 5 : 3; int report_size = (short_button == 0) ? STEELSERIES_REPORT_LONG_SIZE : STEELSERIES_REPORT_SIZE_SHORT; int max_modifiers = (short_button == 0) ? 3 : 0; uint8_t buf[report_size]; memset(buf, 0, report_size); buf[0] = STEELSERIES_ID_BUTTONS; ratbag_profile_for_each_button(profile, button) { struct ratbag_button_action *action = &button->action; uint16_t code; unsigned int key, modifiers; int idx; /* Each button takes up 3 or 5 bytes starting from index 2 */ idx = 2 + button->index * button_size; switch (action->type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: buf[idx] = action->action.button; break; case RATBAG_BUTTON_ACTION_TYPE_MACRO: ratbag_action_keycode_from_macro(action, &key, &modifiers); /* There is only space for 3 modifiers */ if (__builtin_popcount(modifiers) > max_modifiers) { log_error(device->ratbag, "Too many modifiers in macro for button %d (maximum %d)\n", button->index, max_modifiers); break; } code = ratbag_hidraw_get_keyboard_usage_from_keycode( device, key); if (code) { if (short_button) { buf[idx] = STEELSERIES_BUTTON_KEY; } else { buf[idx] = STEELSERIES_BUTTON_KBD; if (modifiers & MODIFIER_LEFTCTRL) buf[++idx] = 0xE0; if (modifiers & MODIFIER_LEFTSHIFT) buf[++idx] = 0xE1; if (modifiers & MODIFIER_LEFTALT) buf[++idx] = 0xE2; if (modifiers & MODIFIER_LEFTMETA) buf[++idx] = 0xE3; if (modifiers & MODIFIER_RIGHTCTRL) buf[++idx] = 0xE4; if (modifiers & MODIFIER_RIGHTSHIFT) buf[++idx] = 0xE5; if (modifiers & MODIFIER_RIGHTALT) buf[++idx] = 0xE6; if (modifiers & MODIFIER_RIGHTMETA) buf[++idx] = 0xE7; } buf[idx + 1] = code; } else { code = ratbag_hidraw_get_consumer_usage_from_keycode( device, action->action.key.key); buf[idx] = STEELSERIES_BUTTON_CONSUMER; buf[idx + 1] = code; } break; case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: switch (action->action.special) { case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP: buf[idx] = STEELSERIES_BUTTON_RES_CYCLE; break; case RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP: buf[idx] = STEELSERIES_BUTTON_WHEEL_UP; break; case RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN: buf[idx] = STEELSERIES_BUTTON_WHEEL_DOWN; break; default: break; } break; case RATBAG_BUTTON_ACTION_TYPE_KEY: case RATBAG_BUTTON_ACTION_TYPE_NONE: default: buf[idx] = STEELSERIES_BUTTON_OFF; break; } } msleep(10); if (device_version == 3) ret = ratbag_hidraw_raw_request(device, STEELSERIES_ID_BUTTONS, buf,sizeof(buf),HID_FEATURE_REPORT,HID_REQ_SET_REPORT); else ret = ratbag_hidraw_output_report(device, buf, sizeof(buf)); if (ret < 0) return ret; return 0; } static int steelseries_write_led_v1(struct ratbag_led *led) { struct ratbag_device *device = led->profile->device; uint8_t buf[STEELSERIES_REPORT_SIZE_SHORT] = {0}; int ret; buf[0] = STEELSERIES_ID_LED_EFFECT_SHORT; buf[1] = led->index + 1; switch(led->mode) { case RATBAG_LED_OFF: case RATBAG_LED_ON: buf[2] = 0x01; break; case RATBAG_LED_BREATHING: buf[2] = 0x03; /* 0x02: slow, 0x03: medium, 0x04: fast */ break; case RATBAG_LED_CYCLE: /* Cycle mode is not supported on this version */ default: return -EINVAL; } msleep(10); ret = ratbag_hidraw_output_report(device, buf, sizeof(buf)); if (ret < 0) return ret; int mono_led = ratbag_device_data_steelseries_get_mono_led(device->data); if (mono_led) { buf[0] = STEELSERIES_ID_LED_INTENSITY_SHORT; buf[1] = led->index + 1; if (led->mode == RATBAG_LED_OFF || led->brightness == 0) { buf[2] = 1; } else { //split the brightness into roughly 3 equal intensities buf[2] = (led->brightness / 86) + 2; } } else { buf[0] = STEELSERIES_ID_LED_COLOR_SHORT; buf[1] = led->index + 1; buf[2] = led->color.red; buf[3] = led->color.green; buf[4] = led->color.blue; } msleep(10); ret = ratbag_hidraw_output_report(device, buf, sizeof(buf)); if (ret < 0) return ret; return 0; } static void create_cycle(struct steelseries_led_cycle *cycle) { cycle->led_id = 0x00; cycle->duration = 5000; cycle->repeat = true; cycle->trigger_buttons = 0x00; list_init(&cycle->points); } static void construct_cycle_buffer(struct steelseries_led_cycle *cycle, struct steelseries_led_cycle_spec *spec, uint8_t *buf, uint8_t buf_size) { struct steelseries_point *point; uint16_t duration; uint8_t npoints = 0; uint16_t cycle_size = 0; buf[0] = spec->cmd_val; buf[spec->led_id_idx] = cycle->led_id; if (spec->has_2_led_ids) buf[spec->led_id2_idx] = cycle->led_id; if (!cycle->repeat) buf[spec->repeat_idx] = 0x01; buf[spec->trigger_idx] = cycle->trigger_buttons; int color_idx = spec->header_len; list_for_each(point, &cycle->points, link) { if(npoints == 0){ /* this is not a mistake, we need to write the first point as the first data after the header */ buf[color_idx++] = point->color.red; buf[color_idx++] = point->color.green; buf[color_idx++] = point->color.blue; } cycle_size += point->pos; assert(cycle_size < 256); assert(34 + npoints*4 <= buf_size); buf[(color_idx ) + npoints*4] = point->color.red; buf[(color_idx+1) + npoints*4] = point->color.green; buf[(color_idx+2) + npoints*4] = point->color.blue; buf[(color_idx+3) + npoints*4] = point->pos; npoints++; } buf[spec->point_count_idx] = npoints; /* this seems to be the minimum allowed */ duration = max(buf[spec->point_count_idx] * 330, cycle->duration); set_unaligned_le_u16(&buf[spec->duration_idx], duration); } static int steelseries_write_led_cycle(struct ratbag_led *led, struct steelseries_led_cycle_spec *cycle_spec) { struct ratbag_device *device = led->profile->device; uint8_t buf[STEELSERIES_REPORT_SIZE] = {0}; int device_version = ratbag_device_data_steelseries_get_device_version(device->data); int ret; struct steelseries_led_cycle cycle; struct steelseries_point point[4]; const struct ratbag_color black = { 0x00 }; const struct ratbag_color red = { 0xFF, 0x00, 0x00 }; const struct ratbag_color green = { 0x00, 0xFF, 0x00 }; const struct ratbag_color blue = { 0x00, 0x00, 0xFF }; create_cycle(&cycle); cycle.led_id = led->index; switch(led->mode) { case RATBAG_LED_OFF: cycle.repeat = false; point[0].color = black; point[0].pos = 0x00; list_append(&cycle.points, &point[0].link); break; case RATBAG_LED_ON: cycle.repeat = false; point[0].color = led->color; point[0].pos = 0x00; list_append(&cycle.points, &point[0].link); break; case RATBAG_LED_CYCLE: point[0].color = red; point[0].pos = 0x00; point[1].color = green; point[1].pos = 0x55; point[2].color = blue; point[2].pos = 0x55; point[3].color = red; point[3].pos = 0x55; list_append(&cycle.points, &point[0].link); list_append(&cycle.points, &point[1].link); list_append(&cycle.points, &point[2].link); list_append(&cycle.points, &point[3].link); cycle.duration = led->ms; break; case RATBAG_LED_BREATHING: point[0].color = black; point[0].pos = 0x00; point[1].color = led->color; point[1].pos = 0x7F; point[2].color = black; point[2].pos = 0x7F; list_append(&cycle.points, &point[0].link); list_append(&cycle.points, &point[1].link); list_append(&cycle.points, &point[2].link); cycle.duration = led->ms; break; default: return -EINVAL; } construct_cycle_buffer(&cycle, cycle_spec, buf, sizeof(buf)); msleep(10); if (device_version == 3) ret = ratbag_hidraw_raw_request(device, cycle_spec->cmd_val, buf, sizeof(buf), cycle_spec->hid_report_type, HID_REQ_SET_REPORT); else ret = ratbag_hidraw_output_report(device, buf, sizeof(buf)); if (ret < 0) return ret; return 0; } static int steelseries_write_led_v2(struct ratbag_led *led) { struct steelseries_led_cycle_spec spec; spec.hid_report_type = HID_OUTPUT_REPORT; spec.header_len = 28; spec.cmd_val = STEELSERIES_ID_LED; spec.has_2_led_ids = false; spec.led_id_idx = 2; spec.duration_idx = 3; spec.repeat_idx = 19; spec.trigger_idx = 23; spec.point_count_idx = 27; return steelseries_write_led_cycle(led, &spec); } static int steelseries_write_led_v3(struct ratbag_led *led) { struct steelseries_led_cycle_spec spec; spec.hid_report_type = HID_FEATURE_REPORT; spec.header_len = 30; spec.cmd_val = STEELSERIES_ID_LED_PROTOCOL3; spec.has_2_led_ids = true; spec.led_id_idx = 2; spec.led_id2_idx = 7; spec.duration_idx = 8; spec.repeat_idx = 24; spec.trigger_idx = 25; spec.point_count_idx = 29; return steelseries_write_led_cycle(led, &spec); } static int steelseries_write_led(struct ratbag_led *led) { struct ratbag_device *device = led->profile->device; int device_version = ratbag_device_data_steelseries_get_device_version(device->data); if (device_version == 1) return steelseries_write_led_v1(led); else if (device_version == 2) return steelseries_write_led_v2(led); else if (device_version == 3) return steelseries_write_led_v3(led); return -ENOTSUP; } static int steelseries_write_save(struct ratbag_device *device) { int device_version = ratbag_device_data_steelseries_get_device_version(device->data); int ret; size_t buf_len; uint8_t buf[STEELSERIES_REPORT_SIZE] = {0}; if (device_version == 1) { buf_len = STEELSERIES_REPORT_SIZE_SHORT; buf[0] = STEELSERIES_ID_SAVE_SHORT; } else if (device_version == 2) { buf_len = STEELSERIES_REPORT_SIZE; buf[0] = STEELSERIES_ID_SAVE; } else if (device_version == 3) { buf_len = STEELSERIES_REPORT_SIZE; buf[0] = STEELSERIES_ID_SAVE_PROTOCOL3; } else { return -ENOTSUP; } msleep(20); ret = ratbag_hidraw_output_report(device, buf, buf_len); if (ret < 0) return ret; return 0; } static int steelseries_write_profile(struct ratbag_profile *profile) { struct ratbag_resolution *resolution; struct ratbag_button *button; struct ratbag_led *led; int rc; bool buttons_dirty = false; rc = steelseries_write_report_rate(profile); if (rc != 0) return rc; ratbag_profile_for_each_resolution(profile, resolution) { if (!resolution->dirty) continue; rc = steelseries_write_dpi(resolution); if (rc != 0) return rc; /* The same hz is used for all resolutions. Only write once. */ if (resolution->index > 0) continue; } ratbag_profile_for_each_button(profile, button) { if (button->dirty) buttons_dirty = true; } if (buttons_dirty) { rc = steelseries_write_buttons(profile); if (rc != 0) return rc; } ratbag_profile_for_each_led(profile, led) { if (!led->dirty) continue; rc = steelseries_write_led(led); if (rc != 0) return rc; } return 0; } static int steelseries_commit(struct ratbag_device *device) { struct ratbag_profile *profile; int rc = 0; list_for_each(profile, &device->profiles, link) { if (!profile->dirty) continue; log_debug(device->ratbag, "Profile %d changed, rewriting\n", profile->index); rc = steelseries_write_profile(profile); if (rc) return rc; /* persist the current settings on the device */ rc = steelseries_write_save(device); if (rc) return rc; } return 0; } static void steelseries_remove(struct ratbag_device *device) { ratbag_close_hidraw_index(device, 0); ratbag_close_hidraw_index(device, 1); free(ratbag_get_drv_data(device)); } struct ratbag_driver steelseries_driver = { .name = "SteelSeries", .id = "steelseries", .probe = steelseries_probe, .remove = steelseries_remove, .commit = steelseries_commit, }; libratbag-0.13/src/driver-test.c000066400000000000000000000207471362011324700166000ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include "config.h" #include #include #include "libratbag-private.h" #include "libratbag-test.h" static int test_write_profile(struct ratbag_profile *profile) { /* check if the device is still valid */ assert(ratbag_get_drv_data(profile->device) != NULL); return 0; } static int test_set_active_profile(struct ratbag_device *device, unsigned int index) { struct ratbag_test_device *d = ratbag_get_drv_data(device); /* check if the device is still valid */ assert(d != NULL); assert(index < d->num_profiles); return 0; } static void test_read_button(struct ratbag_button *button) { struct ratbag_device *device = button->profile->device; struct ratbag_test_device *d = ratbag_get_drv_data(device); struct ratbag_test_profile *p = &d->profiles[button->profile->index]; struct ratbag_test_button *b = &p->buttons[button->index]; struct ratbag_button_macro *m; struct ratbag_test_macro_event *e; int idx; /* Only take the button types from the first profile */ button->type = d->profiles[0].buttons[button->index].button_type; switch (b->action_type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: button->action.type = RATBAG_BUTTON_ACTION_TYPE_BUTTON; button->action.action.button = p->buttons[button->index].button; break; case RATBAG_BUTTON_ACTION_TYPE_KEY: button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; button->action.action.key.key = p->buttons[button->index].key; break; case RATBAG_BUTTON_ACTION_TYPE_MACRO: button->action.type = RATBAG_BUTTON_ACTION_TYPE_MACRO; m = ratbag_button_macro_new("test macro"); idx = 0; ARRAY_FOR_EACH(b->macro, e) { if (e->type == RATBAG_MACRO_EVENT_NONE) break; ratbag_button_macro_set_event(m, idx++, e->type, e->value); } ratbag_button_copy_macro(button, m); ratbag_button_macro_unref(m); break; case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: button->action.type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; button->action.action.special = p->buttons[button->index].special; break; default: button->action.type = RATBAG_BUTTON_ACTION_TYPE_UNKNOWN; } ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); } static void test_read_led(struct ratbag_led *led) { struct ratbag_device *device = led->profile->device; struct ratbag_test_device *d = ratbag_get_drv_data(device); struct ratbag_test_profile *p = &d->profiles[led->profile->index]; struct ratbag_test_led t_led = p->leds[led->index]; ratbag_led_set_mode_capability(led, RATBAG_LED_ON); ratbag_led_set_mode_capability(led, RATBAG_LED_CYCLE); ratbag_led_set_mode_capability(led, RATBAG_LED_BREATHING); ratbag_led_set_mode_capability(led, RATBAG_LED_OFF); switch (t_led.mode) { case RATBAG_LED_ON: led->mode = RATBAG_LED_ON; break; case RATBAG_LED_CYCLE: led->mode = RATBAG_LED_CYCLE; break; case RATBAG_LED_BREATHING: led->mode = RATBAG_LED_BREATHING; break; default: led->mode = RATBAG_LED_OFF; } led->color.red = t_led.color.red; led->color.green = t_led.color.green; led->color.blue = t_led.color.blue; led->ms = t_led.ms; led->brightness = t_led.brightness; /* The led type has to be the same anyway so make writing test * devices easier by always getting the first profile's LED type. */ led->type = d->profiles[0].leds[led->index].type; } static int test_write_button(struct ratbag_button *button, const struct ratbag_button_action *action) { /* check if the device is still valid */ assert(ratbag_get_drv_data(button->profile->device) != NULL); return 0; } static int test_write_led(struct ratbag_led *led, enum ratbag_led_mode mode, struct ratbag_color color, unsigned int ms, unsigned int brightness) { /* check if the device is still valid */ assert(ratbag_get_drv_data(led->profile->device) != NULL); return 0; } static int test_fake_probe(struct ratbag_device *device) { return -ENODEV; } static void test_read_profile(struct ratbag_profile *profile) { struct ratbag_test_device *d = ratbag_get_drv_data(profile->device); struct ratbag_test_profile *p; struct ratbag_test_profile *p0; struct ratbag_test_resolution *r; struct ratbag_test_resolution *r0; struct ratbag_button *button; struct ratbag_led *led; unsigned int i; bool active_set = false; bool default_set = false; size_t nrates = 0; assert(profile->index < d->num_profiles); p = &d->profiles[profile->index]; p0 = &d->profiles[0]; r0 = &p0->resolutions[0]; for (size_t r = 0; r < ARRAY_LENGTH(p0->report_rates); r++) { if (p0->report_rates[r] > 0) nrates++; } if (nrates > 0) ratbag_profile_set_report_rate_list(profile, p0->report_rates, nrates); ratbag_profile_set_report_rate(profile, p->hz); for (i = 0; i < d->num_resolutions; i++) { _cleanup_resolution_ struct ratbag_resolution *res = NULL; r = &p->resolutions[i]; res = ratbag_profile_get_resolution(profile, i); assert(res); ratbag_resolution_set_resolution(res, r->xres, r->yres); if (r0->dpi_min != 0 && r0->dpi_max != 0) ratbag_resolution_set_dpi_list_from_range(res, r0->dpi_min, r0->dpi_max); res->is_active = r->active; if (r->active) active_set = true; res->is_default = r->dflt; if (r->dflt) default_set = true; for (size_t j = 0; j < ARRAY_LENGTH(r->caps) && r->caps[j]; j++) { ratbag_resolution_set_cap(res, r->caps[j]); } } /* special case triggered by the test suite when num_resolutions is 0 */ if (d->num_resolutions) { _cleanup_resolution_ struct ratbag_resolution *res = NULL; res = ratbag_profile_get_resolution(profile, 0); assert(res); if (!active_set) res->is_active = true; if (!default_set) res->is_default = true; } ratbag_profile_for_each_button(profile, button) test_read_button(button); ratbag_profile_for_each_led(profile, led) test_read_led(led); profile->is_active = p->active; profile->is_enabled = !p->disabled; if (p->name) { free(profile->name); profile->name = strdup(p->name); } for (i = 0; i < ARRAY_LENGTH(p->caps) && p->caps[i]; i++) { ratbag_profile_set_cap(profile, p->caps[i]); } } static int test_probe(struct ratbag_device *device, const void *data) { struct ratbag_test_device *test_device; struct ratbag_profile *profile; test_device = zalloc(sizeof(*test_device)); memcpy(test_device, data, sizeof(*test_device)); ratbag_set_drv_data(device, test_device); ratbag_device_init_profiles(device, test_device->num_profiles, test_device->num_resolutions, test_device->num_buttons, test_device->num_leds); ratbag_device_for_each_profile(device, profile) test_read_profile(profile); return 0; } static void test_remove(struct ratbag_device *device) { struct ratbag_test_device *d = ratbag_get_drv_data(device); /* remove must be called only once */ assert(d != NULL); if (d->destroyed) d->destroyed(device, d->destroyed_data); ratbag_set_drv_data(device, NULL); free(d); } struct ratbag_driver test_driver = { .name = "Test driver", .id = "test_driver", .probe = test_fake_probe, .test_probe = test_probe, .remove = test_remove, .write_profile = test_write_profile, .set_active_profile = test_set_active_profile, .write_button = test_write_button, .write_resolution_dpi = NULL, .write_led = test_write_led, }; libratbag-0.13/src/hidpp-generic.c000066400000000000000000000316071362011324700170430ustar00rootroot00000000000000/* * HID++ generic definitions * * 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 (including the next * paragraph) 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. */ /* * Based on the HID++ documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #include "config.h" #include "hidpp-generic.h" #include #include #include #include "libratbag-private.h" const char *hidpp10_errors[0x100] = { [0x00] = "ERR_SUCCESS", [0x01] = "ERR_INVALID_SUBID", [0x02] = "ERR_INVALID_ADDRESS", [0x03] = "ERR_INVALID_VALUE", [0x04] = "ERR_CONNECT_FAIL", [0x05] = "ERR_TOO_MANY_DEVICES", [0x06] = "ERR_ALREADY_EXISTS", [0x07] = "ERR_BUSY", [0x08] = "ERR_UNKNOWN_DEVICE", [0x09] = "ERR_RESOURCE_ERROR", [0x0A] = "ERR_REQUEST_UNAVAILABLE", [0x0B] = "ERR_INVALID_PARAM_VALUE", [0x0C] = "ERR_WRONG_PIN_CODE", [0x0D ... 0xFF] = NULL, }; const char *hidpp20_errors[0x100] = { [0x00] = "ERR_NO_ERROR", [0x01] = "ERR_UNKNOWN", [0x02] = "ERR_INVALID_ARGUMENT", [0x03] = "ERR_OUT_OF_RANGE", [0x04] = "ERR_HARDWARE_ERROR", [0x05] = "ERR_LOGITECH_INTERNAL", [0x06] = "ERR_INVALID_FEATURE_INDEX", [0x07] = "ERR_INVALID_FUNCTION_ID", [0x08] = "ERR_BUSY", [0x09] = "ERR_UNSUPPORTED", [0x0A ... 0xFF] = NULL, }; struct hidpp20_1b04_action_mapping { uint16_t value; const char *name; struct ratbag_button_action action; }; #define BUTTON_ACTION_MAPPING(v_, n_, act_) \ { v_, n_, act_ } static const struct hidpp20_1b04_action_mapping hidpp20_1b04_logical_mapping[] = { BUTTON_ACTION_MAPPING(0, "None" , BUTTON_ACTION_NONE), BUTTON_ACTION_MAPPING(1, "Volume Up" , BUTTON_ACTION_KEY(KEY_VOLUMEUP)), BUTTON_ACTION_MAPPING(2, "Volume Down" , BUTTON_ACTION_KEY(KEY_VOLUMEDOWN)), BUTTON_ACTION_MAPPING(3, "Mute" , BUTTON_ACTION_KEY(KEY_MUTE)), BUTTON_ACTION_MAPPING(4, "Play/Pause" , BUTTON_ACTION_KEY(KEY_PLAYPAUSE)), BUTTON_ACTION_MAPPING(5, "Next" , BUTTON_ACTION_KEY(KEY_NEXTSONG)), BUTTON_ACTION_MAPPING(6, "Previous" , BUTTON_ACTION_KEY(KEY_PREVIOUSSONG)), BUTTON_ACTION_MAPPING(7, "Stop" , BUTTON_ACTION_KEY(KEY_STOPCD)), BUTTON_ACTION_MAPPING(80, "Left" , BUTTON_ACTION_BUTTON(1)), BUTTON_ACTION_MAPPING(81, "Right" , BUTTON_ACTION_BUTTON(2)), BUTTON_ACTION_MAPPING(82, "Middle" , BUTTON_ACTION_BUTTON(3)), BUTTON_ACTION_MAPPING(83, "Back" , BUTTON_ACTION_BUTTON(4)), BUTTON_ACTION_MAPPING(86, "Forward" , BUTTON_ACTION_BUTTON(5)), BUTTON_ACTION_MAPPING(89, "Button 6" , BUTTON_ACTION_BUTTON(6)), BUTTON_ACTION_MAPPING(90, "Button 7" , BUTTON_ACTION_BUTTON(7)), BUTTON_ACTION_MAPPING(91, "Left Scroll" , BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT)), BUTTON_ACTION_MAPPING(92, "Button 8" , BUTTON_ACTION_BUTTON(8)), BUTTON_ACTION_MAPPING(93, "Right Scroll" , BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT)), BUTTON_ACTION_MAPPING(94, "Button 9" , BUTTON_ACTION_BUTTON(9)), BUTTON_ACTION_MAPPING(95, "Button 10" , BUTTON_ACTION_BUTTON(10)), BUTTON_ACTION_MAPPING(96, "Button 11" , BUTTON_ACTION_BUTTON(11)), BUTTON_ACTION_MAPPING(97, "Button 12" , BUTTON_ACTION_BUTTON(12)), BUTTON_ACTION_MAPPING(98, "Button 13" , BUTTON_ACTION_BUTTON(13)), BUTTON_ACTION_MAPPING(99, "Button 14" , BUTTON_ACTION_BUTTON(14)), BUTTON_ACTION_MAPPING(100, "Button 15" , BUTTON_ACTION_BUTTON(15)), BUTTON_ACTION_MAPPING(101, "Button 16" , BUTTON_ACTION_BUTTON(16)), BUTTON_ACTION_MAPPING(102, "Button 17" , BUTTON_ACTION_BUTTON(17)), BUTTON_ACTION_MAPPING(103, "Button 18" , BUTTON_ACTION_BUTTON(18)), BUTTON_ACTION_MAPPING(104, "Button 19" , BUTTON_ACTION_BUTTON(19)), BUTTON_ACTION_MAPPING(105, "Button 20" , BUTTON_ACTION_BUTTON(20)), BUTTON_ACTION_MAPPING(106, "Button 21" , BUTTON_ACTION_BUTTON(21)), BUTTON_ACTION_MAPPING(107, "Button 22" , BUTTON_ACTION_BUTTON(22)), BUTTON_ACTION_MAPPING(108, "Button 23" , BUTTON_ACTION_BUTTON(23)), BUTTON_ACTION_MAPPING(109, "Button 24" , BUTTON_ACTION_BUTTON(24)), BUTTON_ACTION_MAPPING(184, "Second Left" , BUTTON_ACTION_BUTTON(1)), BUTTON_ACTION_MAPPING(195, "AppSwitchGesture" , BUTTON_ACTION_NONE), BUTTON_ACTION_MAPPING(196, "SmartShift" , BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RATCHET_MODE_SWITCH)), BUTTON_ACTION_MAPPING(315, "LedToggle" , BUTTON_ACTION_NONE), }; struct hidpp20_1b04_physical_mapping { uint16_t value; const char *name; enum ratbag_button_type type; }; #define BUTTON_PHYS_MAPPING(v_, n_, t_) \ { v_, n_, t_ } static const struct hidpp20_1b04_physical_mapping hidpp20_1b04_physical_mapping[] = { BUTTON_PHYS_MAPPING(0, "None" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(1, "Volume Up" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(2, "Volume Down" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(3, "Mute" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(4, "Play/Pause" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(5, "Next" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(6, "Previous" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(7, "Stop" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(56, "Left Click" , RATBAG_BUTTON_TYPE_LEFT), BUTTON_PHYS_MAPPING(57, "Right Click" , RATBAG_BUTTON_TYPE_RIGHT), BUTTON_PHYS_MAPPING(58, "Middle Click" , RATBAG_BUTTON_TYPE_MIDDLE), BUTTON_PHYS_MAPPING(59, "Wheel Side Click Left" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(60, "Back Click" , RATBAG_BUTTON_TYPE_SIDE), BUTTON_PHYS_MAPPING(61, "Wheel Side Click Right", RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(62, "Forward Click" , RATBAG_BUTTON_TYPE_EXTRA), BUTTON_PHYS_MAPPING(63, "Left Scroll" , RATBAG_BUTTON_TYPE_WHEEL_LEFT), BUTTON_PHYS_MAPPING(64, "Right Scroll" , RATBAG_BUTTON_TYPE_WHEEL_RIGHT), BUTTON_PHYS_MAPPING(98, "Do Nothing" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(156, "Gesture Button" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(157, "SmartShift" , RATBAG_BUTTON_TYPE_WHEEL_RATCHET_MODE_SHIFT), BUTTON_PHYS_MAPPING(169, "Gesture Button" , RATBAG_BUTTON_TYPE_MIDDLE), BUTTON_PHYS_MAPPING(221, "LedToggle" , RATBAG_BUTTON_TYPE_UNKNOWN), }; struct hidpp20_8070_location_mapping { uint16_t value; const char *name; enum ratbag_led_type type; }; #define LED_LOC_MAPPING(v_, n_, t_) \ { v_, n_, t_ } static const struct hidpp20_8070_location_mapping hidpp20_8070_location_mapping[] = { LED_LOC_MAPPING(0, "None", RATBAG_LED_TYPE_UNKNOWN), LED_LOC_MAPPING(1, "Logo LED", RATBAG_LED_TYPE_LOGO), LED_LOC_MAPPING(2, "Side LED", RATBAG_LED_TYPE_SIDE), }; const struct ratbag_button_action * hidpp20_1b04_get_logical_mapping(uint16_t value) { const struct hidpp20_1b04_action_mapping *map; ARRAY_FOR_EACH(hidpp20_1b04_logical_mapping, map) { if (map->value == value) return &map->action; } return NULL; } uint16_t hidpp20_1b04_get_logical_control_id(const struct ratbag_button_action *action) { const struct hidpp20_1b04_action_mapping *mapping; ARRAY_FOR_EACH(hidpp20_1b04_logical_mapping, mapping) { if (ratbag_button_action_match(&mapping->action, action)) return mapping->value; } return 0; } const char * hidpp20_1b04_get_logical_mapping_name(uint16_t value) { const struct hidpp20_1b04_action_mapping *mapping; ARRAY_FOR_EACH(hidpp20_1b04_logical_mapping, mapping) { if (mapping->value == value) return mapping->name; } return "UNKNOWN"; } enum ratbag_button_type hidpp20_1b04_get_physical_mapping(uint16_t value) { const struct hidpp20_1b04_physical_mapping *map; ARRAY_FOR_EACH(hidpp20_1b04_physical_mapping, map) { if (map->value == value) return map->type; } return RATBAG_BUTTON_TYPE_UNKNOWN; } const char * hidpp20_1b04_get_physical_mapping_name(uint16_t value) { const struct hidpp20_1b04_physical_mapping *map; ARRAY_FOR_EACH(hidpp20_1b04_physical_mapping, map) { if (map->value == value) return map->name; } return "UNKNOWN"; } enum ratbag_led_type hidpp20_led_get_location_mapping(uint16_t value) { const struct hidpp20_8070_location_mapping *map; ARRAY_FOR_EACH(hidpp20_8070_location_mapping, map) { if (map->value == value) return map->type; } return RATBAG_LED_TYPE_UNKNOWN; } const char * hidpp20_led_get_location_mapping_name(uint16_t value) { const struct hidpp20_8070_location_mapping *map; ARRAY_FOR_EACH(hidpp20_8070_location_mapping, map) { if (map->value == value) return map->name; } return "UNKNOWN"; } int hidpp_write_command(struct hidpp_device *dev, uint8_t *cmd, int size) { int fd = dev->hidraw_fd; int res; if (size < 1 || !cmd || fd < 0) return -EINVAL; hidpp_log_buf_raw(dev, "hidpp write: ", cmd, size); res = write(fd, cmd, size); if (res < 0) { res = -errno; hidpp_log_error(dev, "Error: %s (%d)\n", strerror(-res), -res); } return res < 0 ? res : 0; } int hidpp_read_response(struct hidpp_device *dev, uint8_t *buf, size_t size) { int fd = dev->hidraw_fd; struct pollfd fds; int rc; if (size < 1 || !buf || fd < 0) return -EINVAL; fds.fd = fd; fds.events = POLLIN; rc = poll(&fds, 1, 1000); if (rc == -1) return -errno; if (rc == 0) return -ETIMEDOUT; rc = read(fd, buf, size); if (rc > 0) hidpp_log_buf_raw(dev, "hidpp read: ", buf, rc); return rc >= 0 ? rc : -errno; } void hidpp_get_supported_report_types(struct hidpp_device *dev, struct hidpp_hid_report *reports, unsigned int num_reports) { if (!reports) { hidpp_log_debug(dev, "hidpp: we don't have information about the hid reports, ignoring checks\n"); dev->supported_report_types = 0xffff; return; } /* reset the bits we are gonna check */ dev->supported_report_types &= (0xff & ~HIDPP_REPORT_SHORT); dev->supported_report_types &= (0xff & ~HIDPP_REPORT_LONG); for (unsigned i = 0; i < num_reports; i++) if ((reports[i].usage_page & 0xff00) == 0xff00) /* vendor defined usage page (0xff00-0xffff) */ switch (reports[i].report_id) { case REPORT_ID_SHORT: hidpp_log_debug(dev, "hidpp: device supports short reports\n"); dev->supported_report_types |= HIDPP_REPORT_SHORT; break; case REPORT_ID_LONG: hidpp_log_debug(dev, "hidpp: device supports long reports\n"); dev->supported_report_types |= HIDPP_REPORT_LONG; break; } } void hidpp_log(struct hidpp_device *dev, enum hidpp_log_priority priority, const char *format, ...) { va_list args; if (dev->log_priority > priority) return; va_start(args, format); dev->log_handler(dev->userdata, priority, format, args); va_end(args); } char * hidpp_buffer_to_string(const uint8_t *buf, size_t len) { size_t dstlen = len * 6; char *dst = zalloc(dstlen); unsigned int n = 0; char *sep = ""; for (unsigned i = 0; i < len; ++i) { n += snprintf_safe(&dst[n], dstlen - n, "%s%02x", sep, buf[i] & 0xFF); sep = " "; } return dst; } void hidpp_log_buffer(struct hidpp_device *dev, enum hidpp_log_priority priority, const char *header, uint8_t *buf, size_t len) { _cleanup_free_ char *output_buf = NULL; _cleanup_free_ char *bytes = NULL; bytes = hidpp_buffer_to_string(buf, len); asprintf(&output_buf, "%s %s", header ? header : "", bytes); hidpp_log(dev, priority, "%s\n", output_buf); } static void simple_log(void *userdata, enum hidpp_log_priority priority, const char *format, va_list args) { vprintf(format, args); } void hidpp_device_init(struct hidpp_device *dev, int fd) { dev->hidraw_fd = fd; hidpp_device_set_log_handler(dev, simple_log, HIDPP_LOG_PRIORITY_INFO, NULL); dev->supported_report_types = 0; } void hidpp_device_set_log_handler(struct hidpp_device *dev, hidpp_log_handler log_handler, enum hidpp_log_priority priority, void *userdata) { dev->log_handler = log_handler; dev->log_priority = priority; dev->userdata = userdata; } /* * The following crc computation has been provided by Logitech */ #define CRC_CCITT_SEED 0xFFFF uint16_t hidpp_crc_ccitt(uint8_t *data, unsigned int length) { uint16_t crc, temp, quick; unsigned int i; crc = CRC_CCITT_SEED; for (i = 0; i < length; i++) { temp = (crc >> 8) ^ (*data++); crc <<= 8; quick = temp ^ (temp >> 4); crc ^= quick; quick <<= 5; crc ^= quick; quick <<= 7; crc ^= quick; } return crc; } libratbag-0.13/src/hidpp-generic.h000066400000000000000000000147701362011324700170520ustar00rootroot00000000000000/* * HID++ generic definitions * * 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 (including the next * paragraph) 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. */ /* * Based on the HID++ documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #pragma once #include #include #include #include "libratbag-util.h" #define HIDPP_RECEIVER_IDX 0xFF #define HIDPP_WIRED_DEVICE_IDX 0x00 #define REPORT_ID_SHORT 0x10 #define REPORT_ID_LONG 0x11 #define SHORT_MESSAGE_LENGTH 7 #define LONG_MESSAGE_LENGTH 20 #define SET_REGISTER_REQ 0x80 #define SET_REGISTER_RSP 0x80 #define GET_REGISTER_REQ 0x81 #define GET_REGISTER_RSP 0x81 #define SET_LONG_REGISTER_REQ 0x82 #define SET_LONG_REGISTER_RSP 0x82 #define GET_LONG_REGISTER_REQ 0x83 #define GET_LONG_REGISTER_RSP 0x83 #define __ERROR_MSG 0x8F #define HIDPP10_ERR_SUCCESS 0x00 #define HIDPP10_ERR_INVALID_SUBID 0x01 #define HIDPP10_ERR_INVALID_ADDRESS 0x02 #define HIDPP10_ERR_INVALID_VALUE 0x03 #define HIDPP10_ERR_CONNECT_FAIL 0x04 #define HIDPP10_ERR_TOO_MANY_DEVICES 0x05 #define HIDPP10_ERR_ALREADY_EXISTS 0x06 #define HIDPP10_ERR_BUSY 0x07 #define HIDPP10_ERR_UNKNOWN_DEVICE 0x08 #define HIDPP10_ERR_RESOURCE_ERROR 0x09 #define HIDPP10_ERR_REQUEST_UNAVAILABLE 0x0A #define HIDPP10_ERR_INVALID_PARAM_VALUE 0x0B #define HIDPP10_ERR_WRONG_PIN_CODE 0x0C #define HIDPP20_ERR_NO_ERROR 0x00 #define HIDPP20_ERR_UNKNOWN 0x01 #define HIDPP20_ERR_INVALID_ARGUMENT 0x02 #define HIDPP20_ERR_OUT_OF_RANGE 0x03 #define HIDPP20_ERR_HARDWARE_ERROR 0x04 #define HIDPP20_ERR_LOGITECH_INTERNAL 0x05 #define HIDPP20_ERR_INVALID_FEATURE_INDEX 0x06 #define HIDPP20_ERR_INVALID_FUNCTION_ID 0x07 #define HIDPP20_ERR_BUSY 0x08 #define HIDPP20_ERR_UNSUPPORTED 0x09 /* Keep this in sync with ratbag_log_priority */ enum hidpp_log_priority { /** * Raw protocol messages. Using this log level results in *a lot* of * output. */ HIDPP_LOG_PRIORITY_RAW = 10, HIDPP_LOG_PRIORITY_DEBUG = 20, HIDPP_LOG_PRIORITY_INFO = 30, HIDPP_LOG_PRIORITY_ERROR = 40, }; typedef void (*hidpp_log_handler)(void *userdata, enum hidpp_log_priority priority, const char *format, va_list args) __attribute__ ((format (printf, 3, 0))); struct hidpp_hid_report { unsigned int report_id; unsigned int usage_page; unsigned int usage; }; struct hidpp_device { int hidraw_fd; void *userdata; hidpp_log_handler log_handler; enum hidpp_log_priority log_priority; unsigned supported_report_types; }; #define HIDPP_REPORT_SHORT (1 << 0) #define HIDPP_REPORT_LONG (1 << 1) void hidpp_device_init(struct hidpp_device *dev, int fd); void hidpp_device_set_log_handler(struct hidpp_device *dev, hidpp_log_handler log_handler, enum hidpp_log_priority priority, void *userdata); extern const char *hidpp10_errors[0x100]; extern const char *hidpp20_errors[0x100]; const char * hidpp20_1b04_get_physical_mapping_name(uint16_t value); enum ratbag_button_type hidpp20_1b04_get_physical_mapping(uint16_t value); enum ratbag_led_type hidpp20_led_get_location_mapping(uint16_t value); const char * hidpp20_led_get_location_mapping_name(uint16_t value); const char * hidpp20_1b04_get_logical_mapping_name(uint16_t value); const struct ratbag_button_action * hidpp20_1b04_get_logical_mapping(uint16_t value); uint16_t hidpp20_1b04_get_logical_control_id(const struct ratbag_button_action *action); int hidpp_write_command(struct hidpp_device *dev, uint8_t *cmd, int size); int hidpp_read_response(struct hidpp_device *dev, uint8_t *buf, size_t size); void hidpp_get_supported_report_types(struct hidpp_device *dev, struct hidpp_hid_report *reports, unsigned int num_reports); void hidpp_log(struct hidpp_device *dev, enum hidpp_log_priority priority, const char *format, ...) __attribute__ ((format (printf, 3, 4))); char * hidpp_buffer_to_string(const uint8_t *buf, size_t len); void hidpp_log_buffer(struct hidpp_device *dev, enum hidpp_log_priority priority, const char *header, uint8_t *buf, size_t len); #define hidpp_log_raw(li_, ...) hidpp_log((li_), HIDPP_LOG_PRIORITY_RAW, __VA_ARGS__) #define hidpp_log_debug(li_, ...) hidpp_log((li_), HIDPP_LOG_PRIORITY_DEBUG, __VA_ARGS__) #define hidpp_log_info(li_, ...) hidpp_log((li_), HIDPP_LOG_PRIORITY_INFO, __VA_ARGS__) #define hidpp_log_error(li_, ...) hidpp_log((li_), HIDPP_LOG_PRIORITY_ERROR, __VA_ARGS__) #define hidpp_log_bug_kernel(li_, ...) hidpp((li_), HIDPP_LOG_PRIORITY_ERROR, "kernel bug: " __VA_ARGS__) #define hidpp_log_buf_raw(li_, h_, buf_, len_) hidpp_log_buffer(li_, HIDPP_LOG_PRIORITY_RAW, h_, buf_, len_) #define hidpp_log_buf_debug(li_, h_, buf_, len_) hidpp_log_buffer(li_, HIDPP_LOG_PRIORITY_DEBUG, h_, buf_, len_) #define hidpp_log_buf_info(li_, h_, buf_, len_) hidpp_log_buffer(li_, HIDPP_LOG_PRIORITY_INFO, h_, buf_, len_) #define hidpp_log_buf_error(li_, h_, buf_, len_) hidpp_log_buffer(li_, HIDPP_LOG_PRIORITY_ERROR, h_, buf_, len_) uint16_t hidpp_crc_ccitt(uint8_t *data, unsigned int length); static inline uint16_t hidpp_be_u16_to_cpu(uint16_t data) { return get_unaligned_be_u16((uint8_t *)&data); } static inline uint16_t hidpp_cpu_to_be_u16(uint16_t data) { uint16_t result; set_unaligned_be_u16((uint8_t *)&result, data); return result; } static inline uint16_t hidpp_le_u16_to_cpu(uint16_t data) { return get_unaligned_le_u16((uint8_t *)&data); } static inline uint16_t hidpp_cpu_to_le_u16(uint16_t data) { uint16_t result; set_unaligned_le_u16((uint8_t *)&result, data); return result; } libratbag-0.13/src/hidpp.i000066400000000000000000000011321362011324700154250ustar00rootroot00000000000000/* wrapping libratbag functions from hidpp*.h using SWIG. */ %module hidpp %{ /* the resulting C file should be built as a python extension */ #define SWIG_FILE_WITH_INIT /* Includes the header in the wrapper code */ #include #include #include %} #define __attribute__(x) #define _Static_assert(a,b) /* Parse the header file to generate wrappers */ %include "stdint.i" %include "cpointer.i" %pointer_functions(int, intp); %pointer_functions(uint8_t, uint8_tp); %pointer_functions(uint16_t, uint16_tp); %include "hidpp-generic.h" %include "hidpp20.h" libratbag-0.13/src/hidpp10.c000066400000000000000000002312371362011324700155730ustar00rootroot00000000000000/* * HID++ 1.0 library. * * Copyright 2013-2015 Benjamin Tissoires * Copyright 2013-2015 Red Hat, Inc * * 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 (including the next * paragraph) 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. */ /* * Based on the HID++ 1.0 documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "hidpp10.h" #include "libratbag-util.h" #include "libratbag-hidraw.h" struct _hidpp10_message { uint8_t report_id; uint8_t device_idx; uint8_t sub_id; uint8_t address; union { uint8_t parameters[3]; uint8_t string[16]; }; } __attribute__((packed)); union hidpp10_message { struct _hidpp10_message msg; uint8_t data[LONG_MESSAGE_LENGTH]; }; #define ERROR_MSG(__hidpp_msg, idx) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = __ERROR_MSG, \ .address = __hidpp_msg->msg.sub_id, \ .parameters = {__hidpp_msg->msg.address, 0x00, 0x00 }, \ } \ } const char *device_types[0xFF] = { [0x00] = "Unknown", [0x01] = "Keyboard", [0x02] = "Mouse", [0x03] = "Numpad", [0x04] = "Presenter", [0x05] = "Reserved for future", [0x06] = "Reserved for future", [0x07] = "Reserved for future", [0x08] = "Trackball", [0x09] = "Touchpad", [0x0A ... 0xFE] = NULL, }; unsigned int hidpp10_dpi_table_get_max_dpi(struct hidpp10_device *dev) { struct hidpp10_dpi_mapping *dpi; assert(dev->dpi_count > 0); /* We assume a sorted list */ dpi = &dev->dpi_table[dev->dpi_count - 1]; return dpi->dpi; } unsigned int hidpp10_dpi_table_get_min_dpi(struct hidpp10_device *dev) { struct hidpp10_dpi_mapping *dpi; assert(dev->dpi_count > 0); /* We assume a sorted list, index 0 is always 0 */ dpi = &dev->dpi_table[1]; return dpi->dpi; } int hidpp10_build_dpi_table_from_list(struct hidpp10_device *dev, const struct dpi_list *list) { size_t i; if (list->nentries + 0x80 - 1> 0xff) goto err; dev->dpi_count = list->nentries; dev->dpi_table = zalloc(list->nentries * sizeof(*dev->dpi_table)); dev->dpi_table_is_range = false; for (i = 0; i < list->nentries; i++) { dev->dpi_table[i].raw_value = i + 0x80; dev->dpi_table[i].dpi = list->entries[i]; } return 0; err: dev->dpi_count = 0; free(dev->dpi_table); dev->dpi_table = NULL; return -EINVAL; } int hidpp10_build_dpi_table_from_dpi_info(struct hidpp10_device *dev, const struct dpi_range *range) { unsigned raw_max, i; raw_max = (range->max - range->min) / range->step; if (raw_max > 0xff) return -EINVAL; dev->dpi_count = raw_max + 1; dev->dpi_table = zalloc((raw_max + 1) * sizeof(*dev->dpi_table)); dev->dpi_table_is_range = true; for (i = 1; i <= raw_max; i++) { dev->dpi_table[i].raw_value = i; dev->dpi_table[i].dpi = round((range->min + range->step * i) / 25.0f) * 25; } return 0; } static int hidpp10_request_command(struct hidpp10_device *dev, union hidpp10_message *msg) { union hidpp10_message read_buffer; union hidpp10_message expected_header; union hidpp10_message expected_error_dev = ERROR_MSG(msg, msg->msg.device_idx); int ret; uint8_t hidpp_err = 0; int command_size; _cleanup_free_ char *rxdata = NULL, *txdata = NULL; switch (msg->msg.report_id) { case REPORT_ID_SHORT: command_size = SHORT_MESSAGE_LENGTH; break; case REPORT_ID_LONG: command_size = LONG_MESSAGE_LENGTH; break; default: abort(); } /* create the expected header */ expected_header = *msg; txdata = hidpp_buffer_to_string(&msg->data[4], command_size - 4); hidpp_log_raw(&dev->base, "hidpp10 tx: %02x | %02x | %02x | %02x | %s\n", msg->msg.report_id, msg->msg.device_idx, msg->msg.sub_id, msg->msg.address, txdata); /* response message length doesn't depend on request length */ #if 0 hidpp_log_buf_raw(&dev->base, " expected_header: ?? ", &expected_header.data[1], 3); hidpp_log_buf_raw(&dev->base, " expected_error_dev: ", expected_error_dev.data, SHORT_MESSAGE_LENGTH); #endif /* Send the message to the Device */ ret = hidpp_write_command(&dev->base, msg->data, command_size); if (ret) goto out_err; /* * Now read the answers from the device: * loop until we get the actual answer or an error code. */ do { ret = hidpp_read_response(&dev->base, read_buffer.data, LONG_MESSAGE_LENGTH); /* Wait and retry if the USB timed out */ if (ret == -ETIMEDOUT) { msleep(10); ret = hidpp_read_response(&dev->base, read_buffer.data, LONG_MESSAGE_LENGTH); } /* Overwrite the return device index with ours. The kernel * sets our device index on write, but gives us the real * device index on reply. Overwrite it with our index so the * messages are easier to check and compare. */ read_buffer.msg.device_idx = msg->msg.device_idx; /* actual answer */ if (!memcmp(&read_buffer.data[1], &expected_header.data[1], 3)) break; /* error */ if (!memcmp(read_buffer.data, expected_error_dev.data, 5)) { hidpp_err = read_buffer.msg.parameters[1]; hidpp_log_raw(&dev->base, " HID++ error from the %s (%d): %s (%02x)\n", read_buffer.msg.device_idx == HIDPP_RECEIVER_IDX ? "receiver" : "device", read_buffer.msg.device_idx, hidpp10_errors[hidpp_err] ? hidpp10_errors[hidpp_err] : "Undocumented error code", hidpp_err); break; } } while (ret > 0); if (ret < 0) { hidpp_log_error(&dev->base, " USB error: %s (%d)\n", strerror(-ret), -ret); perror("write"); goto out_err; } rxdata = hidpp_buffer_to_string(&read_buffer.data[4], ret - 4); hidpp_log_raw(&dev->base, "hidpp10 rx: %02x | %02x | %02x | %02x | %s\n", read_buffer.msg.report_id, read_buffer.msg.device_idx, read_buffer.msg.sub_id, read_buffer.msg.address, rxdata); if (!hidpp_err) { /* copy the answer for the caller */ *msg = read_buffer; } ret = hidpp_err; out_err: return ret; } /* -------------------------------------------------------------------------- */ /* HID++ 1.0 commands 10 */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* 0x00: Enable HID++ Notifications */ /* -------------------------------------------------------------------------- */ #define __CMD_HIDPP_NOTIFICATIONS 0x00 #define CMD_HIDPP_NOTIFICATIONS(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_HIDPP_NOTIFICATIONS, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_hidpp_notifications(struct hidpp10_device *dev, uint32_t *reporting_flags) { unsigned idx = dev->index; union hidpp10_message notifications = CMD_HIDPP_NOTIFICATIONS(idx, GET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Fetching HID++ notifications (%#02x)\n", __CMD_HIDPP_NOTIFICATIONS); res = hidpp10_request_command(dev, ¬ifications); if (res) return res; *reporting_flags = notifications.msg.parameters[0]; *reporting_flags |= (notifications.msg.parameters[1] & 0x1F) << 8; *reporting_flags |= (notifications.msg.parameters[2] & 0x7 ) << 16; return res; } int hidpp10_set_hidpp_notifications(struct hidpp10_device *dev, uint32_t reporting_flags) { unsigned idx = dev->index; union hidpp10_message notifications = CMD_HIDPP_NOTIFICATIONS(idx, SET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Setting HID++ notifications (%#02x\n", __CMD_HIDPP_NOTIFICATIONS); notifications.msg.parameters[0] = reporting_flags & 0xFF; notifications.msg.parameters[1] = (reporting_flags >> 8) & 0x1F; notifications.msg.parameters[2] = (reporting_flags >> 16) & 0x7; res = hidpp10_request_command(dev, ¬ifications); return res; } /* -------------------------------------------------------------------------- */ /* 0x01: Enable Individual Features */ /* -------------------------------------------------------------------------- */ #define __CMD_ENABLE_INDIVIDUAL_FEATURES 0x01 #define CMD_ENABLE_INDIVIDUAL_FEATURES(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_ENABLE_INDIVIDUAL_FEATURES, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_individual_features(struct hidpp10_device *dev, uint32_t *feature_mask) { unsigned idx = dev->index; union hidpp10_message features = CMD_ENABLE_INDIVIDUAL_FEATURES(idx, GET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Fetching individual features (%#02x)\n", __CMD_ENABLE_INDIVIDUAL_FEATURES); res = hidpp10_request_command(dev, &features); if (res) return res; *feature_mask = features.msg.parameters[0]; /* bits 0 and 4-7 are reserved */ *feature_mask |= (features.msg.parameters[1] & 0x0E) << 8; /* bits 6-7 are reserved */ *feature_mask |= (features.msg.parameters[2] & 0x3F) << 16; return 0; } int hidpp10_set_individual_features(struct hidpp10_device *dev, uint32_t feature_mask) { unsigned idx = HIDPP_RECEIVER_IDX; union hidpp10_message mode = CMD_ENABLE_INDIVIDUAL_FEATURES(idx, SET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Setting individual features (%#02x)\n", __CMD_ENABLE_INDIVIDUAL_FEATURES); mode.msg.device_idx = dev->index; mode.msg.parameters[0] = feature_mask & 0xFF; mode.msg.parameters[1] = (feature_mask >> 8) & 0x0E; mode.msg.parameters[2] = (feature_mask >> 16) & 0x3F; res = hidpp10_request_command(dev, &mode); return res; } /* -------------------------------------------------------------------------- */ /* 0x07: Battery status */ /* -------------------------------------------------------------------------- */ #define __CMD_BATTERY_STATUS 0x07 #define CMD_BATTERY_STATUS(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_BATTERY_STATUS, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_battery_status(struct hidpp10_device *dev, enum hidpp10_battery_level *level, enum hidpp10_battery_charge_state *charge_state, uint8_t *low_threshold_in_percent) { unsigned idx = dev->index; union hidpp10_message battery = CMD_BATTERY_STATUS(idx, GET_REGISTER_REQ); int res; res = hidpp10_request_command(dev, &battery); *level = battery.msg.parameters[0]; *charge_state = battery.msg.parameters[1]; *low_threshold_in_percent = battery.msg.parameters[2]; if (*low_threshold_in_percent >= 7) { /* reserved value, we just silently truncate it to 0 */ *low_threshold_in_percent = 0; } *low_threshold_in_percent *= 5; /* in 5% increments */ return res; } /* -------------------------------------------------------------------------- */ /* 0x0D: Battery mileage */ /* -------------------------------------------------------------------------- */ #define __CMD_BATTERY_MILEAGE 0x0D #define CMD_BATTERY_MILEAGE(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_BATTERY_MILEAGE, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_battery_mileage(struct hidpp10_device *dev, uint8_t *level_in_percent, uint32_t *max_seconds, enum hidpp10_battery_charge_state *state) { unsigned idx = dev->index; union hidpp10_message battery = CMD_BATTERY_MILEAGE(idx, GET_REGISTER_REQ); int res; int max; res = hidpp10_request_command(dev, &battery); *level_in_percent = battery.msg.parameters[0] & 0x7F; max = battery.msg.parameters[1]; max |= (battery.msg.parameters[2] & 0xF) << 8; switch((battery.msg.parameters[2] & 0x30) >> 4) { case 0x03: /* days */ max *= 24; /* fallthrough */ case 0x02: /* hours */ max *= 60; /* fallthrough */ case 0x01: /* min */ max *= 60; break; case 0x00: /* seconds */ break; } *max_seconds = max; switch(battery.msg.parameters[2] >> 6) { case 0x00: *state = HIDPP10_BATTERY_CHARGE_STATE_NOT_CHARGING; break; case 0x01: *state = HIDPP10_BATTERY_CHARGE_STATE_CHARGING; break; case 0x02: *state = HIDPP10_BATTERY_CHARGE_STATE_CHARGING_COMPLETE; break; case 0x03: *state = HIDPP10_BATTERY_CHARGE_STATE_CHARGING_ERROR; break; } return res; } /* -------------------------------------------------------------------------- */ /* 0x0F: Profile queries */ /* -------------------------------------------------------------------------- */ #define __CMD_PROFILE 0x0F #define PROFILE_TYPE_INDEX 0x00 #define PROFILE_TYPE_ADDRESS 0x01 #define PROFILE_TYPE_EEPROM 0xEE #define PROFILE_TYPE_FACTORY 0xFF #define CMD_PROFILE(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_PROFILE, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } struct _hidpp10_dpi_mode_8 { uint8_t res; uint8_t led1:4; uint8_t led2:4; uint8_t led3:4; uint8_t led4:4; } __attribute__((packed)); _Static_assert(sizeof(struct _hidpp10_dpi_mode_8) == 3, "Invalid size"); struct _hidpp10_dpi_mode_8_dual { uint8_t xres; uint8_t yres; uint8_t led1:4; uint8_t led2:4; uint8_t led3:4; uint8_t led4:4; } __attribute__((packed)); _Static_assert(sizeof(struct _hidpp10_dpi_mode_8_dual) == 4, "Invalid size"); struct _hidpp10_dpi_mode_16 { uint16_t xres; uint16_t yres; uint8_t led1:4; uint8_t led2:4; uint8_t led3:4; uint8_t led4:4; } __attribute__((packed)); _Static_assert(sizeof(struct _hidpp10_dpi_mode_16) == 6, "Invalid size"); union _hidpp10_button_binding { struct { uint8_t type; uint8_t pad; uint8_t pad1; } any; struct { uint8_t type; /* 0x81 */ uint16_t button_flags; } __attribute__((packed)) button; struct { uint8_t type; /* 0x82 */ uint8_t modifier_flags; uint8_t key; } keyboard_keys; struct { uint8_t type; /* 0x83 */ uint16_t flags; } __attribute__((packed)) special; struct { uint8_t type; /* 0x84 */ uint16_t consumer_control; } __attribute__((packed)) consumer_control; struct { uint8_t type; /* 0x8F */ uint8_t zero0; uint8_t zero1; } disabled; struct { uint8_t page; uint8_t offset; uint8_t zero; } __attribute__((packed)) macro; } __attribute__((packed)); _Static_assert(sizeof(union _hidpp10_button_binding) == 3, "Invalid size"); union _hidpp10_profile_metadata { struct { uint8_t marker[5]; uint8_t padding[420]; } __attribute__((packed)) any; struct { uint8_t marker[5]; /* { 'L', 'G', 'S', '0', '2' } */ uint16_t name[23]; uint16_t macro_names[11][17]; } __attribute__((packed)) lgs02; } __attribute__((packed)); struct _hidpp10_profile_500 { uint8_t red; uint8_t green; uint8_t blue; uint8_t unknown; struct _hidpp10_dpi_mode_16 dpi_modes[PROFILE_NUM_DPI_MODES]; uint8_t angle_correction; uint8_t default_dpi_mode; uint8_t unknown2[2]; uint8_t usb_refresh_rate; union _hidpp10_button_binding buttons[PROFILE_NUM_BUTTONS]; union _hidpp10_profile_metadata metadata; } __attribute__((packed)); _Static_assert(sizeof(struct _hidpp10_profile_500) == 503, "Invalid size"); static const uint8_t _hidpp10_profile_700_unknown1[3] = { 0x80, 0x01, 0x10 }; static const uint8_t _hidpp10_profile_700_unknown2[10] = { 0x01, 0x2c, 0x02, 0x58, 0x64, 0xff, 0xbc, 0x00, 0x09, 0x31 }; struct _hidpp10_profile_700 { struct _hidpp10_dpi_mode_8_dual dpi_modes[PROFILE_NUM_DPI_MODES]; uint8_t default_dpi_mode; uint8_t unknown1[3]; uint8_t usb_refresh_rate; uint8_t unknown2[10]; union _hidpp10_button_binding buttons[PROFILE_NUM_BUTTONS]; union _hidpp10_profile_metadata metadata; } __attribute__((packed)); _Static_assert(sizeof(struct _hidpp10_profile_700) == 499, "Invalid size"); struct _hidpp10_profile_9 { uint8_t red; uint8_t green; uint8_t blue; uint8_t unknown1; struct _hidpp10_dpi_mode_8 dpi_modes[PROFILE_NUM_DPI_MODES]; uint8_t default_dpi_mode; uint8_t unknown2[2]; uint8_t usb_refresh_rate; union _hidpp10_button_binding buttons[PROFILE_NUM_BUTTONS_G9]; uint8_t unknown3[3]; union _hidpp10_profile_metadata metadata; } __attribute__((packed)); _Static_assert(sizeof(struct _hidpp10_profile_500) == 503, "Invalid size"); union _hidpp10_profile_data { struct _hidpp10_profile_500 profile_500; struct _hidpp10_profile_700 profile_700; struct _hidpp10_profile_9 profile_9; uint8_t data[HIDPP10_PAGE_SIZE]; }; _Static_assert((sizeof(union _hidpp10_profile_data) % 16) == 0, "Invalid size"); static uint8_t hidpp10_get_dpi_mapping(struct hidpp10_device *dev, unsigned int value) { struct hidpp10_dpi_mapping *m; unsigned int i, delta, min_delta; uint8_t result = 0; if (!dev->dpi_table) return value / 50; m = dev->dpi_table; min_delta = INT_MAX; for (i = 0; i < dev->dpi_count; i++) { delta = abs((int)value - (int)m[i].dpi); if (delta < min_delta) { result = m[i].raw_value; min_delta = delta; } } return result; } static unsigned int hidpp10_get_dpi_value(struct hidpp10_device *dev, uint8_t raw_value) { struct hidpp10_dpi_mapping *m; unsigned int i; if (!dev->dpi_table) return raw_value * 50; m = dev->dpi_table; for (i = 0; i < dev->dpi_count; i++) { if (raw_value == m[i].raw_value) return m[i].dpi; } return 0; } static int hidpp10_write_profile_directory(struct hidpp10_device *dev) { unsigned int i, index; int res; uint8_t bytes[HIDPP10_PAGE_SIZE]; struct hidpp10_directory *directory = (struct hidpp10_directory *)bytes; uint16_t crc; if (dev->profile_type == HIDPP10_PROFILE_UNKNOWN) { hidpp_log_debug(&dev->base, "no profile type given\n"); return 0; } memset(bytes, 0xff, sizeof(bytes)); index = 0; for (i = 0; i < dev->profile_count; i++) { if (!dev->profiles[i].enabled) continue; directory[index].page = dev->profiles[i].page; directory[index].offset = dev->profiles[i].offset; directory[index].led_mask = ((0b111 << index) >> 2) & 0b111; ++index; } crc = hidpp_crc_ccitt(bytes, HIDPP10_PAGE_SIZE - 2); set_unaligned_be_u16(&bytes[HIDPP10_PAGE_SIZE - 2], crc); res = hidpp10_send_hot_payload(dev, 0x00, 0x0000, /* destination: RAM */ bytes, HIDPP10_PAGE_SIZE / 2); if (res < 0) return res; res = hidpp10_erase_memory(dev, 0x01); if (res < 0) return res; res = hidpp10_write_flash(dev, 0x00, 0x0000, 0x01, 0x0000, HIDPP10_PAGE_SIZE / 2); if (res < 0) return res; res = hidpp10_send_hot_payload(dev, 0x00, 0x0000, /* destination: RAM */ bytes + HIDPP10_PAGE_SIZE / 2, HIDPP10_PAGE_SIZE / 2); if (res < 0) return res; res = hidpp10_write_flash(dev, 0x00, 0x0000, 0x01, HIDPP10_PAGE_SIZE / 2, HIDPP10_PAGE_SIZE / 2); if (res < 0) return res; return 0; } static int hidpp10_read_profile_directory(struct hidpp10_device *dev) { unsigned int i; int res; uint8_t bytes[HIDPP10_PAGE_SIZE] = { 0 }; struct hidpp10_directory *directory = (struct hidpp10_directory *)bytes; unsigned int count; if (dev->profile_type == HIDPP10_PROFILE_UNKNOWN) { hidpp_log_debug(&dev->base, "no profile type given\n"); return 0; } hidpp_log_raw(&dev->base, "Fetching the profiles' directory\n"); res = hidpp10_read_page(dev, 0x01, bytes); if (res) return res; count = 0; for (i = 0; i < dev->profile_count; i++) { if (directory[i].page == 0xFF) break; dev->profiles[i].page = directory[i].page; dev->profiles[i].offset = directory[i].offset; dev->profiles[i].enabled = true; count++; } for (i = count; i < dev->profile_count; i++) dev->profiles[i].enabled = false; return count; } int hidpp10_get_current_profile(struct hidpp10_device *dev, uint8_t *current_profile) { unsigned idx = dev->index; union hidpp10_message profile = CMD_PROFILE(idx, GET_REGISTER_REQ); int res; unsigned i; uint8_t type, page, offset; hidpp_log_raw(&dev->base, "Fetching current profile (%#02x)\n", __CMD_PROFILE); res = hidpp10_request_command(dev, &profile); if (res) return res; type = profile.msg.parameters[0]; page = profile.msg.parameters[1]; switch (type) { case PROFILE_TYPE_INDEX: *current_profile = page; /* If the profile exceeds the directory length, default to * the first */ if (*current_profile > dev->profile_count) *current_profile = 0; return 0; case PROFILE_TYPE_ADDRESS: offset = profile.msg.parameters[2]; for (i = 0; i < dev->profile_count; i++) { if (page == dev->profiles[i].page && offset == dev->profiles[i].offset) { *current_profile = i; return 0; } } hidpp_log_error(&dev->base, "unable to find the profile at (%d,%d) in the directory\n", page, offset); break; case PROFILE_TYPE_FACTORY: /* Factory profile is selected and profile switching is * disabled. Let's switch to the first profile because the * factory profile doesn't help anywone */ res = hidpp10_set_current_profile(dev, 0); if (res == 0) { hidpp_log_info(&dev->base, "switched from factory profile to 0\n"); *current_profile = 0; return 0; } hidpp_log_error(&dev->base, "current profile is factory profile but switching to 0 failed.\n"); break; default: hidpp_log_error(&dev->base, "Unexpected value: %02x\n", type); } return -ENAVAIL; } static int hidpp10_set_internal_current_profile(struct hidpp10_device *dev, uint16_t current_profile, uint8_t profile_type) { unsigned idx = dev->index; union hidpp10_message profile = CMD_PROFILE(idx, SET_REGISTER_REQ); int8_t page, offset; hidpp_log_raw(&dev->base, "Setting current profile (%#02x)\n", __CMD_PROFILE); profile.msg.parameters[0] = profile_type; switch (profile_type) { case PROFILE_TYPE_INDEX: if (current_profile > dev->profile_count) return -EINVAL; profile.msg.parameters[1] = current_profile & 0xFF; break; case PROFILE_TYPE_ADDRESS: page = current_profile >> 8; offset = current_profile & 0xFF; profile.msg.parameters[1] = page; profile.msg.parameters[2] = offset; break; case PROFILE_TYPE_FACTORY: break; default: hidpp_log_error(&dev->base, "Unexpected value: %02x\n", profile_type); return -EINVAL; } return hidpp10_request_command(dev, &profile); } int hidpp10_set_current_profile(struct hidpp10_device *dev, uint16_t current_profile) { return hidpp10_set_internal_current_profile(dev, current_profile, PROFILE_TYPE_INDEX); } static void hidpp10_fill_dpi_modes_8(struct hidpp10_device *dev, struct hidpp10_profile *profile, struct _hidpp10_dpi_mode_8 *dpi_list, unsigned int count) { unsigned int i; profile->num_dpi_modes = count; for (i = 0; i < count; i++) { struct _hidpp10_dpi_mode_8 *dpi = &dpi_list[i]; profile->dpi_modes[i].xres = hidpp10_get_dpi_value(dev, dpi->res); profile->dpi_modes[i].yres = hidpp10_get_dpi_value(dev, dpi->res); profile->dpi_modes[i].led[0] = dpi->led1 == 0x2; profile->dpi_modes[i].led[1] = dpi->led2 == 0x2; profile->dpi_modes[i].led[2] = dpi->led3 == 0x2; profile->dpi_modes[i].led[3] = dpi->led4 == 0x2; } } static void hidpp10_write_dpi_modes_8(struct hidpp10_device *dev, struct hidpp10_profile *profile, struct _hidpp10_dpi_mode_8 *dpi_list, unsigned int count) { unsigned int i; for (i = 0; i < count; i++) { struct _hidpp10_dpi_mode_8 *dpi = &dpi_list[i]; dpi->res = hidpp10_get_dpi_mapping(dev, profile->dpi_modes[i].xres); dpi->led1 = profile->dpi_modes[i].led[0] ? 0x02 : 0x01; dpi->led2 = profile->dpi_modes[i].led[1] ? 0x02 : 0x01; dpi->led3 = profile->dpi_modes[i].led[2] ? 0x02 : 0x01; dpi->led4 = profile->dpi_modes[i].led[3] ? 0x02 : 0x01; } } static void hidpp10_fill_dpi_modes_8_dual(struct hidpp10_device *dev, struct hidpp10_profile *profile, struct _hidpp10_dpi_mode_8_dual *dpi_list, unsigned int count) { unsigned int i; profile->num_dpi_modes = count; for (i = 0; i < count; i++) { struct _hidpp10_dpi_mode_8_dual *dpi = &dpi_list[i]; profile->dpi_modes[i].xres = hidpp10_get_dpi_value(dev, dpi->xres); profile->dpi_modes[i].yres = hidpp10_get_dpi_value(dev, dpi->yres); profile->dpi_modes[i].led[0] = dpi->led1 == 0x2; profile->dpi_modes[i].led[1] = dpi->led2 == 0x2; profile->dpi_modes[i].led[2] = dpi->led3 == 0x2; profile->dpi_modes[i].led[3] = dpi->led4 == 0x2; } } static void hidpp10_write_dpi_modes_8_dual(struct hidpp10_device *dev, struct hidpp10_profile *profile, struct _hidpp10_dpi_mode_8_dual *dpi_list, unsigned int count) { unsigned int i; for (i = 0; i < count; i++) { struct _hidpp10_dpi_mode_8_dual *dpi = &dpi_list[i]; dpi->xres = hidpp10_get_dpi_mapping(dev, profile->dpi_modes[i].xres); dpi->yres = hidpp10_get_dpi_mapping(dev, profile->dpi_modes[i].yres); dpi->led1 = profile->dpi_modes[i].led[0] ? 0x02 : 0x01; dpi->led2 = profile->dpi_modes[i].led[1] ? 0x02 : 0x01; dpi->led3 = profile->dpi_modes[i].led[2] ? 0x02 : 0x01; dpi->led4 = profile->dpi_modes[i].led[3] ? 0x02 : 0x01; } } static void hidpp10_fill_dpi_modes_16(struct hidpp10_device *dev, struct hidpp10_profile *profile, struct _hidpp10_dpi_mode_16 *dpi_list, unsigned int count) { unsigned int i; profile->num_dpi_modes = count; for (i = 0; i < count; i++) { uint8_t *be; /* in big endian */ struct _hidpp10_dpi_mode_16 *dpi = &dpi_list[i]; be = (uint8_t*)&dpi->xres; profile->dpi_modes[i].xres = hidpp10_get_dpi_value(dev, get_unaligned_be_u16(be)); be = (uint8_t*)&dpi->yres; profile->dpi_modes[i].yres = hidpp10_get_dpi_value(dev, get_unaligned_be_u16(be)); profile->dpi_modes[i].led[0] = dpi->led1 == 0x2; profile->dpi_modes[i].led[1] = dpi->led2 == 0x2; profile->dpi_modes[i].led[2] = dpi->led3 == 0x2; profile->dpi_modes[i].led[3] = dpi->led4 == 0x2; } } static void hidpp10_write_dpi_modes_16(struct hidpp10_device *dev, struct hidpp10_profile *profile, struct _hidpp10_dpi_mode_16 *dpi_list, unsigned int count) { unsigned int i; for (i = 0; i < count; i++) { uint8_t *be; /* in big endian */ struct _hidpp10_dpi_mode_16 *dpi = &dpi_list[i]; be = (uint8_t*)&dpi->xres; set_unaligned_be_u16(be, hidpp10_get_dpi_mapping(dev, profile->dpi_modes[i].xres)); be = (uint8_t*)&dpi->yres; set_unaligned_be_u16(be, hidpp10_get_dpi_mapping(dev, profile->dpi_modes[i].yres)); dpi->led1 = profile->dpi_modes[i].led[0] ? 0x02 : 0x01; dpi->led2 = profile->dpi_modes[i].led[1] ? 0x02 : 0x01; dpi->led3 = profile->dpi_modes[i].led[2] ? 0x02 : 0x01; dpi->led4 = profile->dpi_modes[i].led[3] ? 0x02 : 0x01; } } static int hidpp10_onboard_profiles_macro_next(struct hidpp10_device *device, uint8_t memory[32], uint16_t *index, union hidpp10_macro_data *macro) { int rc = 0; unsigned int step = 0; if (*index >= 32 - sizeof(union hidpp10_macro_data)) { hidpp_log_error(&device->base, "error while parsing macro.\n"); return -EFAULT; } memcpy(macro, &memory[*index], sizeof(union hidpp10_macro_data)); switch (macro->any.type) { case HIDPP10_MACRO_NOOP: /* fallthrough */ case HIDPP10_MACRO_WAIT_FOR_BUTTON_RELEASE: /* fallthrough */ case HIDPP10_MACRO_REPEAT_UNTIL_BUTTON_RELEASE: /* fallthrough */ case HIDPP10_MACRO_REPEAT: step = 1; rc = -EAGAIN; break; case HIDPP10_MACRO_KEY_PRESS: /* fallthrough */ case HIDPP10_MACRO_KEY_RELEASE: /* fallthrough */ case HIDPP10_MACRO_MOD_PRESS: /* fallthrough */ case HIDPP10_MACRO_MOD_RELEASE: /* fallthrough */ case HIDPP10_MACRO_MOUSE_WHEEL: step = 2; rc = -EAGAIN; break; case HIDPP10_MACRO_MOUSE_BUTTON_PRESS: /* fallthrough */ case HIDPP10_MACRO_MOUSE_BUTTON_RELEASE: /* fallthrough */ case HIDPP10_MACRO_KEY_CONSUMER_CONTROL: /* fallthrough */ case HIDPP10_MACRO_DELAY: /* fallthrough */ case HIDPP10_MACRO_JUMP: /* fallthrough */ case HIDPP10_MACRO_JUMP_IF_PRESSED: step = 3; rc = -EAGAIN; break; case HIDPP10_MACRO_MOUSE_POINTER_MOVE: /* fallthrough */ case HIDPP10_MACRO_JUMP_IF_RELEASED_TIMEOUT: step = 5; rc = -EAGAIN; break; case HIDPP10_MACRO_END: return 0; default: if (macro->any.type >= 0x80 && macro->any.type <= 0xFE) { step = 1; rc = -EAGAIN; } else { hidpp_log_error(&device->base, "unknown tag: 0x%02x\n", macro->any.type); return -EFAULT; } } if ((*index + step) & 0xF0) /* the next item will be on the following chunk */ return -ENOMEM; *index += step; return rc; } static int hidpp10_onboard_profiles_read_macro(struct hidpp10_device *device, uint8_t page, uint8_t offset, union hidpp10_macro_data **return_macro) { uint8_t memory[HIDPP10_PAGE_SIZE]; union hidpp10_macro_data *macro = NULL; unsigned count = 0; unsigned index = 0; uint16_t mem_index = 0; int rc = -ENOMEM; do { if (count == index) { union hidpp10_macro_data *tmp; count += 32; /* manual realloc to have the memory zero-initialized */ tmp = zalloc(count * sizeof(union hidpp10_macro_data)); if (macro) { memcpy(tmp, macro, (count - 32) * sizeof(union hidpp10_macro_data)); free(macro); } macro = tmp; } if (rc == -ENOMEM) { offset += mem_index; if (offset & 0x01) offset--; rc = hidpp10_read_memory(device, page, offset, memory); if (rc) goto out_err; mem_index &= 0x01; hidpp_log_buf_raw(&device->base, "-> ", memory + mem_index, 16 - mem_index); } rc = hidpp10_onboard_profiles_macro_next(device, memory, &mem_index, ¯o[index]); if (rc == -EFAULT) goto out_err; if (rc != -ENOMEM) { if (macro[index].any.type == HIDPP10_MACRO_JUMP) { page = macro[index].jump.page; offset = macro[index].jump.offset; mem_index = 0; /* no need to store the jump in memory */ index--; /* force memory fetching */ rc = -ENOMEM; } index++; } } while (rc); *return_macro = macro; return index; out_err: free(macro); return rc; } static int hidpp10_onboard_profiles_parse_macro(struct hidpp10_device *device, uint8_t page, uint8_t offset, union hidpp10_macro_data **return_macro) { union hidpp10_macro_data *m, *macro = NULL; unsigned i, count = 0; int rc; hidpp_log_raw(&device->base, "*** macro starts at (0x%02x, 0x%04x) ***\n", page, offset); rc = hidpp10_onboard_profiles_read_macro(device, page, offset, ¯o); if (rc) return rc; count = rc; for (i = 0; i < count; i++) { m = ¯o[i]; switch (m->any.type) { case HIDPP10_MACRO_NOOP: hidpp_log_raw(&device->base, "noop\n"); break; case HIDPP10_MACRO_WAIT_FOR_BUTTON_RELEASE: hidpp_log_raw(&device->base, "wait for button release\n"); break; case HIDPP10_MACRO_REPEAT_UNTIL_BUTTON_RELEASE: hidpp_log_raw(&device->base, "repeat from beginning until button release\n"); break; case HIDPP10_MACRO_REPEAT: hidpp_log_raw(&device->base, "repeat from beginning\n"); break; case HIDPP10_MACRO_KEY_PRESS: hidpp_log_raw(&device->base, "key press: %02x\n", m->key.key); break; case HIDPP10_MACRO_KEY_RELEASE: hidpp_log_raw(&device->base, "key release: %02x\n", m->key.key); break; case HIDPP10_MACRO_MOD_PRESS: hidpp_log_raw(&device->base, "modifier press: %02x\n", m->modifier.key); break; case HIDPP10_MACRO_MOD_RELEASE: hidpp_log_raw(&device->base, "modifier release: %02x\n", m->modifier.key); break; case HIDPP10_MACRO_MOUSE_WHEEL: hidpp_log_raw(&device->base, "mouse wheel: %+d\n", m->wheel.value); break; case HIDPP10_MACRO_MOUSE_BUTTON_PRESS: m->button.flags = ffs(hidpp_le_u16_to_cpu(m->button.flags)); hidpp_log_raw(&device->base, "mouse button press: %d\n", m->button.flags); break; case HIDPP10_MACRO_MOUSE_BUTTON_RELEASE: m->button.flags = ffs(hidpp_le_u16_to_cpu(m->button.flags)); hidpp_log_raw(&device->base, "mouse button release: %d\n", m->button.flags); break; case HIDPP10_MACRO_KEY_CONSUMER_CONTROL: m->consumer_control.key = hidpp_be_u16_to_cpu(m->consumer_control.key); hidpp_log_raw(&device->base, "switched to consumer control: 0x%04x\n", m->consumer_control.key); break; case HIDPP10_MACRO_DELAY: m->delay.time = hidpp_be_u16_to_cpu(m->delay.time); hidpp_log_raw(&device->base, "delay: %0.03f\n", m->delay.time/1000.0); break; case HIDPP10_MACRO_JUMP: /* should be skipped by hidpp10_onboard_profiles_read_macro */ hidpp_log_raw(&device->base, "jump to: (0x%02x, 0x%02x)\n", m->jump.page, m->jump.offset); break; case HIDPP10_MACRO_JUMP_IF_PRESSED: hidpp_log_raw(&device->base, "conditional jump to: (0x%02x, 0x%02x)\n", m->jump.page, m->jump.offset); break; case HIDPP10_MACRO_MOUSE_POINTER_MOVE: break; case HIDPP10_MACRO_JUMP_IF_RELEASED_TIMEOUT: m->jump_timeout.timeout = hidpp_be_u16_to_cpu(m->jump_timeout.timeout); hidpp_log_raw(&device->base, "conditional jump to: (0x%02x, 0x%02x) if released whithin %0.03f msecs.\n", m->jump_timeout.page, m->jump_timeout.offset, m->jump_timeout.timeout / 1000.0); break; case HIDPP10_MACRO_END: break; default: if (m->any.type >= 0x80 && m->any.type <= 0x9F) { m->delay.time = 8 + (m->any.type - 0x80) * 4; m->any.type = HIDPP10_MACRO_DELAY; hidpp_log_raw(&device->base, "short delay: %0.03f\n", m->delay.time/1000.0); } else if (m->any.type >= 0xA0 && m->any.type <= 0xBF) { m->delay.time = 132 + (m->any.type - 0x9F) * 8; m->any.type = HIDPP10_MACRO_DELAY; hidpp_log_raw(&device->base, "short delay: %0.03f\n", m->delay.time/1000.0); } else if (m->any.type >= 0xC0 && m->any.type <= 0xDF) { m->delay.time = 388 + (m->any.type - 0xBF) * 16; m->any.type = HIDPP10_MACRO_DELAY; hidpp_log_raw(&device->base, "short delay: %0.03f\n", m->delay.time/1000.0); } else if (m->any.type >= 0xE0 && m->any.type <= 0xFE) { m->delay.time = 900 + (m->any.type - 0xDF) * 32; m->any.type = HIDPP10_MACRO_DELAY; hidpp_log_raw(&device->base, "short delay: %0.03f\n", m->delay.time/1000.0); } else { hidpp_log_error(&device->base, "unknown tag: 0x%02x\n", m->any.type); } } } hidpp_log_raw(&device->base, "*** end of macro ***\n"); *return_macro = macro; return 0; } static void hidpp10_fill_buttons(struct hidpp10_device *dev, struct hidpp10_profile *profile, union _hidpp10_button_binding *buttons, unsigned int count) { unsigned int i; profile->num_buttons = count; for (i = 0; i < count; i++) { union _hidpp10_button_binding *b = &buttons[i]; union hidpp10_button *button = &profile->buttons[i]; button->any.type = b->any.type; switch (b->any.type) { case PROFILE_BUTTON_TYPE_BUTTON: button->button.button = ffs(hidpp_le_u16_to_cpu(b->button.button_flags)); break; case PROFILE_BUTTON_TYPE_KEYS: button->keys.modifier_flags = b->keyboard_keys.modifier_flags; button->keys.key = b->keyboard_keys.key; break; case PROFILE_BUTTON_TYPE_SPECIAL: button->special.special = hidpp_le_u16_to_cpu(b->special.flags); break; case PROFILE_BUTTON_TYPE_CONSUMER_CONTROL: button->consumer_control.consumer_control = hidpp_be_u16_to_cpu(b->consumer_control.consumer_control); break; case PROFILE_BUTTON_TYPE_DISABLED: break; default: /* macros */ button->macro.page = b->macro.page; button->macro.offset = b->macro.offset; button->macro.address = i; if (profile->macros[i]) { free(profile->macros[i]); profile->macros[i] = NULL; } hidpp10_onboard_profiles_parse_macro(dev, b->macro.page, b->macro.offset * 2, &profile->macros[i]); } } } static void hidpp10_write_buttons(struct hidpp10_device *dev, struct hidpp10_profile *profile, union _hidpp10_button_binding *buttons, unsigned int count) { unsigned int i; for (i = 0; i < count; i++) { union _hidpp10_button_binding *button = &buttons[i]; union hidpp10_button *b = &profile->buttons[i]; button->any.type = b->any.type; switch (b->any.type) { case PROFILE_BUTTON_TYPE_BUTTON: button->button.button_flags = hidpp_cpu_to_le_u16(1U << (b->button.button - 1)); break; case PROFILE_BUTTON_TYPE_KEYS: button->keyboard_keys.modifier_flags = b->keys.modifier_flags; button->keyboard_keys.key = b->keys.key; break; case PROFILE_BUTTON_TYPE_SPECIAL: button->special.flags = hidpp_cpu_to_le_u16(b->special.special); break; case PROFILE_BUTTON_TYPE_CONSUMER_CONTROL: button->consumer_control.consumer_control = hidpp_cpu_to_be_u16(b->consumer_control.consumer_control); break; case PROFILE_BUTTON_TYPE_DISABLED: break; default: /* macros */ button->macro.page = b->macro.page; button->macro.offset = b->macro.offset; button->macro.zero = 0; } } } static void hidpp10_uchar16_to_uchar8(uint8_t *dst, uint16_t *src, size_t len) { unsigned i; for (i = 0; i < len; i++) dst[i] = hidpp_le_u16_to_cpu(src[i]) & 0xFF; } static void hidpp10_uchar8_to_uchar16(uint16_t *dst, uint8_t *src, size_t len) { unsigned i; for (i = 0; i < len; i++) dst[i] = hidpp_cpu_to_le_u16(src[i]); } static void hidpp10_profile_parse_names(struct hidpp10_device *dev, struct hidpp10_profile *profile, uint8_t number, union _hidpp10_profile_metadata *metadata) { unsigned i; if (strneq((char *)metadata->any.marker, "LGS02", 5)) { hidpp10_uchar16_to_uchar8(profile->name, metadata->lgs02.name, ARRAY_LENGTH(metadata->lgs02.name)); hidpp_log_raw(&dev->base, "profile %d is named '%s'\n", number, profile->name); for (i = 0; i < ARRAY_LENGTH(metadata->lgs02.macro_names); i++) { hidpp10_uchar16_to_uchar8(profile->macro_names[i], metadata->lgs02.macro_names[i], ARRAY_LENGTH(metadata->lgs02.macro_names[i])); if (profile->macro_names[i][0]) hidpp_log_raw(&dev->base, "macro %d of profile %d is named: '%s'\n", (unsigned)i, number, profile->macro_names[i]); } } else { snprintf((char *)profile->name, sizeof(profile->name) - 1, "Profile %d", number + 1); } } static void hidpp10_profile_set_names(struct hidpp10_device *dev, struct hidpp10_profile *profile, uint8_t number, union _hidpp10_profile_metadata *metadata) { unsigned i; memcpy(metadata->lgs02.marker, "LGS02", sizeof(metadata->lgs02.marker)); hidpp10_uchar8_to_uchar16(metadata->lgs02.name, profile->name, ARRAY_LENGTH(metadata->lgs02.name)); for (i = 0; i < ARRAY_LENGTH(metadata->lgs02.macro_names); i++) { hidpp10_uchar8_to_uchar16(metadata->lgs02.macro_names[i], profile->macro_names[i], ARRAY_LENGTH(metadata->lgs02.macro_names[i])); } } static int hidpp10_read_profile(struct hidpp10_device *dev, uint8_t number) { uint8_t page_data[HIDPP10_PAGE_SIZE]; union _hidpp10_profile_data *data = (union _hidpp10_profile_data *)page_data; struct _hidpp10_profile_500 *p500 = &data->profile_500; struct _hidpp10_profile_700 *p700 = &data->profile_700; struct _hidpp10_profile_9 *p9 = &data->profile_9; size_t i; int res; struct hidpp10_profile *profile; union _hidpp10_button_binding *buttons; uint8_t page; /* Page 0 is RAM * Page 1 is the profile directory * Page 2-31 are Flash * -> profiles are stored in the Flash * * For now we assume that number refers to the index in the profile * directory. */ hidpp_log_raw(&dev->base, "Fetching profile %d\n", number); if (dev->profile_type == HIDPP10_PROFILE_UNKNOWN) return -ENOTSUP; if (number >= dev->profile_count) { hidpp_log_error(&dev->base, "Profile number %d is not supported.\n", number); return -EINVAL; } profile = &dev->profiles[number]; if (!profile->page) { unsigned long pages = 0xffff; unsigned int i; /* pages 0 and 1 are ROM and directory so they are reserved */ long_clear_bit(&pages, 0); long_clear_bit(&pages, 1); for (i = 0; i < dev->profile_count; i++) { uint8_t page = dev->profiles[i].page; assert(page < sizeof(pages) * 8); long_clear_bit(&pages, page); } page = ffsl(pages) - 1; profile->page = page; } switch (dev->profile_type) { case HIDPP10_PROFILE_G500: buttons = p500->buttons; break; case HIDPP10_PROFILE_G700: buttons = p700->buttons; break; case HIDPP10_PROFILE_G9: buttons = p9->buttons; break; default: hidpp_log_error(&dev->base, "This should never happen, complain to your maintainer.\n"); return -EINVAL; } if (!profile->initialized) { page = profile->page; res = hidpp10_read_page(dev, page, page_data); if (res == -EILSEQ) { /* * if the CRC is wrong, the mouse still handles the * profile. Warn the user. */ if (profile->enabled) hidpp_log_info(&dev->base, "Profile %d has a wrong CRC, assuming valid.\n", number); res = 0; } if (res) return res; switch (dev->profile_type) { case HIDPP10_PROFILE_G500: profile->red = p500->red; profile->green = p500->green; profile->blue = p500->blue; profile->angle_correction = p500->angle_correction; profile->default_dpi_mode = p500->default_dpi_mode; profile->refresh_rate = p500->usb_refresh_rate ? 1000/p500->usb_refresh_rate : 0; hidpp10_fill_dpi_modes_16(dev, profile, p500->dpi_modes, PROFILE_NUM_DPI_MODES); hidpp10_profile_parse_names(dev, profile, number, &p500->metadata); hidpp10_fill_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS); break; case HIDPP10_PROFILE_G700: profile->default_dpi_mode = p700->default_dpi_mode; profile->refresh_rate = p700->usb_refresh_rate ? 1000/p700->usb_refresh_rate : 0; hidpp10_fill_dpi_modes_8_dual(dev, profile, p700->dpi_modes, PROFILE_NUM_DPI_MODES); hidpp10_profile_parse_names(dev, profile, number, &p700->metadata); hidpp10_fill_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS); break; case HIDPP10_PROFILE_G9: profile->red = p9->red; profile->green = p9->green; profile->blue = p9->blue; profile->default_dpi_mode = p9->default_dpi_mode; profile->refresh_rate = p9->usb_refresh_rate ? 1000/p9->usb_refresh_rate : 0; hidpp10_fill_dpi_modes_8(dev, profile, p9->dpi_modes, PROFILE_NUM_DPI_MODES); hidpp10_profile_parse_names(dev, profile, number, &p9->metadata); hidpp10_fill_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS_G9); break; default: hidpp_log_error(&dev->base, "This should never happen, complain to your maintainer.\n"); } profile->initialized = 1; hidpp_log_raw(&dev->base, "+++++++++++++++++++ Profile data: +++++++++++++++++ \n"); for (size_t x = 0; x < 78; x += 8) { hidpp_log_buf_raw(&dev->base, NULL, &data->data[x], min(8, 78 - x)); } hidpp_log_raw(&dev->base, "+++++++++++++++++++ Profile data end +++++++++++++++++ \n"); } hidpp_log_raw(&dev->base, "Profile %d:\n", number); for (i = 0; i < 5; i++) { hidpp_log_raw(&dev->base, " DPI mode: %dx%d dpi\n", profile->dpi_modes[i].xres, profile->dpi_modes[i].yres); hidpp_log_raw(&dev->base, " LED status: 1:%s 2:%s 3:%s 4:%s\n", profile->dpi_modes[i].led[0] ? "on" : "off", profile->dpi_modes[i].led[1] ? "on" : "off", profile->dpi_modes[i].led[2] ? "on" : "off", profile->dpi_modes[i].led[3] ? "on" : "off"); } hidpp_log_raw(&dev->base, " Angle correction: %d\n", profile->angle_correction); hidpp_log_raw(&dev->base, " Default DPI mode: %d\n", profile->default_dpi_mode); hidpp_log_raw(&dev->base, " Refresh rate: %d\n", profile->refresh_rate); for (i = 0; i < 13; i++) { union hidpp10_button *button = &profile->buttons[i]; switch (button->any.type) { case PROFILE_BUTTON_TYPE_BUTTON: hidpp_log_raw(&dev->base, " Button %zd: button %d\n", i, button->button.button); break; case PROFILE_BUTTON_TYPE_KEYS: hidpp_log_raw(&dev->base, " Button %zd: key %d modifier %x\n", i, button->keys.key, button->keys.modifier_flags); break; case PROFILE_BUTTON_TYPE_SPECIAL: hidpp_log_raw(&dev->base, " Button %zd: special %x\n", i, button->special.special); break; case PROFILE_BUTTON_TYPE_CONSUMER_CONTROL: hidpp_log_raw(&dev->base, " Button %zd: consumer: %x\n", i, button->consumer_control.consumer_control); break; case PROFILE_BUTTON_TYPE_DISABLED: hidpp_log_raw(&dev->base, " Button %zd: disabled\n", i); break; default: /* FIXME: this is the page number for the macro, * followed by a 1-byte offset */ break ; } } return 0; } int hidpp10_get_profile(struct hidpp10_device *dev, uint8_t number, struct hidpp10_profile *profile_return) { if (dev->profile_type == HIDPP10_PROFILE_UNKNOWN) return -ENOTSUP; if (number >= dev->profile_count) { hidpp_log_error(&dev->base, "Profile number %d is not supported.\n", number); return -EINVAL; } *profile_return = dev->profiles[number]; return 0; } static const enum ratbag_button_action_special hidpp10_profiles_specials[] = { [0x00] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, [0x01] = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT, [0x02] = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT, [0x03] = RATBAG_BUTTON_ACTION_SPECIAL_BATTERY_LEVEL, [0x04] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP, [0x05] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP, [0x06 ... 0x07] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, [0x08] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN, [0x09] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_DOWN, [0x0a ... 0x0f] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, [0x10] = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP, [0x11] = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP, [0x12 ... 0x1f] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, [0x20] = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN, [0x21] = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_DOWN, [0x22 ... 0xff] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, }; enum ratbag_button_action_special hidpp10_onboard_profiles_get_special(uint8_t code) { return hidpp10_profiles_specials[code]; } uint8_t hidpp10_onboard_profiles_get_code_from_special(enum ratbag_button_action_special special) { uint8_t i = 0; while (++i) { if (hidpp10_profiles_specials[i] == special) return i; } return RATBAG_BUTTON_ACTION_SPECIAL_INVALID; } int hidpp10_set_profile(struct hidpp10_device *dev, uint8_t number, struct hidpp10_profile *profile) { uint8_t page_data[HIDPP10_PAGE_SIZE]; union _hidpp10_profile_data *data = (union _hidpp10_profile_data *)page_data; struct _hidpp10_profile_500 *p500 = &data->profile_500; struct _hidpp10_profile_700 *p700 = &data->profile_700; struct _hidpp10_profile_9 *p9 = &data->profile_9; int res; union _hidpp10_button_binding *buttons; uint16_t crc; uint8_t page; hidpp_log_raw(&dev->base, "Fetching profile %d\n", number); if (dev->profile_type == HIDPP10_PROFILE_UNKNOWN) return -ENOTSUP; if (number >= dev->profile_count) { hidpp_log_error(&dev->base, "Profile number %d is incorrect.\n", number); return -EINVAL; } /* something went wrong */ if (!profile->page) return -ENOTSUP; memset(page_data, 0xff, sizeof(page_data)); switch (dev->profile_type) { case HIDPP10_PROFILE_G500: buttons = p500->buttons; break; case HIDPP10_PROFILE_G700: buttons = p700->buttons; break; case HIDPP10_PROFILE_G9: buttons = p9->buttons; break; default: hidpp_log_error(&dev->base, "This should never happen, complain to your maintainer.\n"); return -ENOTSUP; } /* First, fill out the unknown fields with the constants or the current * values when we are not sure. */ switch (dev->profile_type) { case HIDPP10_PROFILE_G500: case HIDPP10_PROFILE_G9: /* we do not know the actual values of the remaining field right now * so pre-fill with the current data */ res = hidpp10_read_page(dev, profile->page, page_data); if (res) return res; break; case HIDPP10_PROFILE_G700: memcpy(p700->unknown1, _hidpp10_profile_700_unknown1, sizeof(p700->unknown1)); memcpy(p700->unknown2, _hidpp10_profile_700_unknown2, sizeof(p700->unknown2)); break; default: hidpp_log_error(&dev->base, "This should never happen, complain to your maintainer.\n"); return -ENOTSUP; } switch (dev->profile_type) { case HIDPP10_PROFILE_G500: p500->red = profile->red; p500->green = profile->green; p500->blue = profile->blue; p500->angle_correction = profile->angle_correction; p500->default_dpi_mode = profile->default_dpi_mode; p500->usb_refresh_rate = 1000/profile->refresh_rate; hidpp10_write_dpi_modes_16(dev, profile, p500->dpi_modes, PROFILE_NUM_DPI_MODES); hidpp10_write_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS); hidpp10_profile_set_names(dev, profile, number, &p500->metadata); break; case HIDPP10_PROFILE_G700: p700->default_dpi_mode = profile->default_dpi_mode; p700->usb_refresh_rate = profile->refresh_rate ? 1000 / profile->refresh_rate : 0; hidpp10_write_dpi_modes_8_dual(dev, profile, p700->dpi_modes, PROFILE_NUM_DPI_MODES); hidpp10_write_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS); hidpp10_profile_set_names(dev, profile, number, &p700->metadata); break; case HIDPP10_PROFILE_G9: p9->red = profile->red; p9->green = profile->green; p9->blue = profile->blue; p9->default_dpi_mode = profile->default_dpi_mode; p9->usb_refresh_rate = 1000 / profile->refresh_rate; hidpp10_write_dpi_modes_8(dev, profile, p9->dpi_modes, PROFILE_NUM_DPI_MODES); hidpp10_write_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS); hidpp10_profile_set_names(dev, profile, number, &p9->metadata); break; default: hidpp_log_error(&dev->base, "This should never happen, complain to your maintainer.\n"); return -ENOTSUP; } crc = hidpp_crc_ccitt(page_data, HIDPP10_PAGE_SIZE - 2); set_unaligned_be_u16(&page_data[HIDPP10_PAGE_SIZE - 2], crc); /* * writing the data in several steps to prevent shroedinger state * if the device is unplugged while uploading the data: * - first disable the current profile by using the factory one * (this prevents the user to change the current profile by pressing * a button) * - then upload in RAM half of the data * - erase the portion of the flash we are overwriting * - write the uploaded data to the flash * - upload the rest * - write the uploaded data to the flash * - switch to the new profile */ res = hidpp10_set_internal_current_profile(dev, 0, PROFILE_TYPE_FACTORY); if (res < 0) return res; if (profile->enabled != dev->profiles[number].enabled) { dev->profiles[number].enabled = profile->enabled; res = hidpp10_write_profile_directory(dev); if (res < 0) return res; } res = hidpp10_send_hot_payload(dev, 0x00, 0x0000, /* destination: RAM */ page_data, HIDPP10_PAGE_SIZE / 2); if (res < 0) return res; page = profile->page; /* according to the spec, a profile can have an offset. * For all the devices we know, they all start at 0x0000 */ res = hidpp10_erase_memory(dev, page); if (res < 0) return res; res = hidpp10_write_flash(dev, 0x00, 0x0000, page, 0x0000, HIDPP10_PAGE_SIZE / 2); if (res < 0) return res; res = hidpp10_send_hot_payload(dev, 0x00, 0x0000, /* destination: RAM */ page_data + HIDPP10_PAGE_SIZE / 2, HIDPP10_PAGE_SIZE / 2); if (res < 0) return res; res = hidpp10_write_flash(dev, 0x00, 0x0000, page, HIDPP10_PAGE_SIZE / 2, HIDPP10_PAGE_SIZE / 2); if (res < 0) return res; res = hidpp10_set_internal_current_profile(dev, number, PROFILE_TYPE_INDEX); if (res < 0) return res; dev->profiles[number] = *profile; return res; } /* -------------------------------------------------------------------------- */ /* 0x51: LED Status */ /* -------------------------------------------------------------------------- */ #define __CMD_LED_STATUS 0x51 #define CMD_LED_STATUS(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_LED_STATUS, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_led_status(struct hidpp10_device *dev, enum hidpp10_led_status led[6]) { unsigned idx = dev->index; union hidpp10_message led_status = CMD_LED_STATUS(idx, GET_REGISTER_REQ); uint8_t *status = led_status.msg.parameters; int res; hidpp_log_raw(&dev->base, "Fetching LED status (%#02x)\n", __CMD_LED_STATUS); res = hidpp10_request_command(dev, &led_status); if (res) return res; led[0] = status[0] & 0xF; led[1] = (status[0] >> 4) & 0xF; led[2] = status[1] & 0xF; led[3] = (status[1] >> 4) & 0xF; led[4] = status[2] & 0xF; led[5] = (status[2] >> 4) & 0xF; return 0; } int hidpp10_set_led_status(struct hidpp10_device *dev, const enum hidpp10_led_status led[6]) { unsigned idx = dev->index; union hidpp10_message led_status = CMD_LED_STATUS(idx, SET_REGISTER_REQ); uint8_t *status = led_status.msg.parameters; int res; int i; hidpp_log_raw(&dev->base, "Setting LED status (%#02x)\n", __CMD_LED_STATUS); for (i = 0; i < 6; i++) { switch (led[i]) { case HIDPP10_LED_STATUS_NO_CHANGE: case HIDPP10_LED_STATUS_OFF: case HIDPP10_LED_STATUS_ON: case HIDPP10_LED_STATUS_BLINK: case HIDPP10_LED_STATUS_HEARTBEAT: case HIDPP10_LED_STATUS_SLOW_ON: case HIDPP10_LED_STATUS_SLOW_OFF: break; default: abort(); } } /* each led is 4-bits, 0x1 == off, 0x2 == on */ status[0] = led[0] | (led[1] << 4); status[1] = led[2] | (led[3] << 4); status[2] = led[4] | (led[5] << 4); res = hidpp10_request_command(dev, &led_status); return res; } /* -------------------------------------------------------------------------- */ /* 0x54: LED Intensity */ /* -------------------------------------------------------------------------- */ #define __CMD_LED_INTENSITY 0x54 #define CMD_LED_INTENSITY(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_LED_INTENSITY, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_led_intensity(struct hidpp10_device *dev, uint8_t led_intensity_in_percent[6]) { unsigned idx = dev->index; union hidpp10_message led_intensity = CMD_LED_INTENSITY(idx, GET_REGISTER_REQ); uint8_t *intensity = led_intensity.msg.parameters; int res; hidpp_log_raw(&dev->base, "Fetching LED intensity (%#02x)\n", __CMD_LED_INTENSITY); res = hidpp10_request_command(dev, &led_intensity); if (res) return res; led_intensity_in_percent[0] = 10 * ((intensity[0] ) & 0xF); led_intensity_in_percent[1] = 10 * ((intensity[0] >> 4) & 0xF); led_intensity_in_percent[2] = 10 * ((intensity[1] ) & 0xF); led_intensity_in_percent[3] = 10 * ((intensity[1] >> 4) & 0xF); led_intensity_in_percent[4] = 10 * ((intensity[2] ) & 0xF); led_intensity_in_percent[5] = 10 * ((intensity[2] >> 4) & 0xF); return 0; } int hidpp10_set_led_intensity(struct hidpp10_device *dev, const uint8_t led_intensity_in_percent[6]) { unsigned idx = dev->index; union hidpp10_message led_intensity = CMD_LED_INTENSITY(idx, SET_REGISTER_REQ); uint8_t *intensity = led_intensity.msg.parameters; int res; hidpp_log_raw(&dev->base, "Setting LED intensity (%#02x)\n", __CMD_LED_INTENSITY); intensity[0] = led_intensity_in_percent[0]/10 & 0xF; intensity[0] |= (led_intensity_in_percent[1]/10 & 0xF) << 4; intensity[1] = led_intensity_in_percent[2]/10 & 0xF; intensity[1] |= (led_intensity_in_percent[3]/10 & 0xF) << 4; intensity[2] = led_intensity_in_percent[4]/10 & 0xF; intensity[2] |= (led_intensity_in_percent[5]/10 & 0xF) << 4; res = hidpp10_request_command(dev, &led_intensity); return res; } /* -------------------------------------------------------------------------- */ /* 0x57: LED Color */ /* -------------------------------------------------------------------------- */ #define __CMD_LED_COLOR 0x57 #define CMD_LED_COLOR(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_LED_COLOR, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_led_color(struct hidpp10_device *dev, uint8_t *red, uint8_t *green, uint8_t *blue) { unsigned idx = dev->index; union hidpp10_message led_color = CMD_LED_COLOR(idx, GET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Fetching LED color (%#02x)\n", __CMD_LED_COLOR); res = hidpp10_request_command(dev, &led_color); if (res) return res; *red = led_color.msg.parameters[0]; *green = led_color.msg.parameters[1]; *blue = led_color.msg.parameters[2]; return 0; } int hidpp10_set_led_color(struct hidpp10_device *dev, uint8_t red, uint8_t green, uint8_t blue) { unsigned idx = dev->index; union hidpp10_message led_color = CMD_LED_COLOR(idx, SET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Setting LED color (%#02x)\n", __CMD_LED_COLOR); led_color.msg.parameters[0] = red; led_color.msg.parameters[1] = green; led_color.msg.parameters[2] = blue; res = hidpp10_request_command(dev, &led_color); return res; } /* -------------------------------------------------------------------------- */ /* 0x61: Optical Sensor Settings */ /* -------------------------------------------------------------------------- */ #define __CMD_OPTICAL_SENSOR_SETTINGS 0x61 #define CMD_OPTICAL_SENSOR_SETTINGS(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_OPTICAL_SENSOR_SETTINGS, \ .parameters = {0x00, 0x00, 0x00}, \ } \ } int hidpp10_get_optical_sensor_settings(struct hidpp10_device *dev, uint8_t *surface_reflectivity) { unsigned idx = dev->index; union hidpp10_message sensor = CMD_OPTICAL_SENSOR_SETTINGS(idx, GET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Fetching optical sensor settings (%#02x)\n", __CMD_OPTICAL_SENSOR_SETTINGS); res = hidpp10_request_command(dev, &sensor); if (res) return res; *surface_reflectivity = sensor.msg.parameters[0]; /* Don't know what the other values are */ return 0; } /* -------------------------------------------------------------------------- */ /* 0x63: Current Resolution */ /* -------------------------------------------------------------------------- */ #define __CMD_CURRENT_RESOLUTION 0x63 #define CMD_CURRENT_RESOLUTION(id, idx, sub) { \ .msg = { \ .report_id = id, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_CURRENT_RESOLUTION, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_current_resolution(struct hidpp10_device *dev, uint16_t *xres, uint16_t *yres) { unsigned idx = dev->index; union hidpp10_message resolution = CMD_CURRENT_RESOLUTION(REPORT_ID_SHORT, idx, GET_REGISTER_REQ); union hidpp10_message resolution_long = CMD_CURRENT_RESOLUTION(REPORT_ID_SHORT, idx, GET_LONG_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Fetching current resolution (%#02x)\n", __CMD_CURRENT_RESOLUTION); switch (dev->profile_type) { case HIDPP10_PROFILE_G9: res = hidpp10_request_command(dev, &resolution); if (res) return res; /* resolution is in 50dpi multiples */ *xres = *yres = hidpp10_get_dpi_value(dev, get_unaligned_le_u16(&resolution.data[4])); break; default: res = hidpp10_request_command(dev, &resolution_long); if (res) return res; /* resolution is in 50dpi multiples */ *xres = hidpp10_get_dpi_value(dev, get_unaligned_le_u16(&resolution_long.data[4])); *yres = hidpp10_get_dpi_value(dev, get_unaligned_le_u16(&resolution_long.data[6])); } return 0; } int hidpp10_set_current_resolution(struct hidpp10_device *dev, uint16_t xres, uint16_t yres) { unsigned idx = dev->index; union hidpp10_message resolution = CMD_CURRENT_RESOLUTION(REPORT_ID_SHORT, idx, SET_REGISTER_REQ); union hidpp10_message resolution_long = CMD_CURRENT_RESOLUTION(REPORT_ID_LONG, idx, SET_LONG_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Setting current resolution (%#02x)\n", __CMD_CURRENT_RESOLUTION); switch (dev->profile_type) { case HIDPP10_PROFILE_G9: resolution.data[4] = hidpp10_get_dpi_mapping(dev, xres); res = hidpp10_request_command(dev, &resolution); break; default: set_unaligned_le_u16(&resolution_long.data[4], hidpp10_get_dpi_mapping(dev, xres)); set_unaligned_le_u16(&resolution_long.data[6], hidpp10_get_dpi_mapping(dev, yres)); res = hidpp10_request_command(dev, &resolution_long); break; } return res; } /* -------------------------------------------------------------------------- */ /* 0x64: USB Refresh Rate */ /* -------------------------------------------------------------------------- */ #define __CMD_USB_REFRESH_RATE 0x64 #define CMD_USB_REFRESH_RATE(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_USB_REFRESH_RATE, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_usb_refresh_rate(struct hidpp10_device *dev, uint16_t *rate) { unsigned idx = dev->index; union hidpp10_message refresh = CMD_USB_REFRESH_RATE(idx, GET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Fetching USB refresh rate (%#02x)\n", __CMD_USB_REFRESH_RATE); res = hidpp10_request_command(dev, &refresh); if (res) return res; if (!refresh.msg.parameters[0]) return -EINVAL; *rate = 1000/refresh.msg.parameters[0]; return 0; } int hidpp10_set_usb_refresh_rate(struct hidpp10_device *dev, uint16_t rate) { unsigned idx = dev->index; union hidpp10_message refresh = CMD_USB_REFRESH_RATE(idx, GET_REGISTER_REQ); hidpp_log_raw(&dev->base, "Setting USB refresh rate (%#02x)\n", __CMD_USB_REFRESH_RATE); refresh.msg.parameters[0] = 1000/rate; return hidpp10_request_command(dev, &refresh); } /* -------------------------------------------------------------------------- */ /* 0xA0: Generic Memory Management */ /* -------------------------------------------------------------------------- */ #define __CMD_GENERIC_MEMORY_MANAGEMENT 0xA0 #define CMD_ERASE_MEMORY(idx, page) { \ .msg = { \ .report_id = REPORT_ID_LONG, \ .device_idx = idx, \ .sub_id = SET_LONG_REGISTER_REQ, \ .address = __CMD_GENERIC_MEMORY_MANAGEMENT, \ .string = {0x02, 0x00, \ 0x00, 0x00, 0x00, 0x00, \ page, 0x00, 0x00, 0x00,\ 0x00, 0x00, 0x00, 0x00}, \ } \ } #define CMD_WRITE_FLASH(idx, src_page, src_woffset, dst_page, dst_woffset, size) { \ .msg = { \ .report_id = REPORT_ID_LONG, \ .device_idx = idx, \ .sub_id = SET_LONG_REGISTER_REQ, \ .address = __CMD_GENERIC_MEMORY_MANAGEMENT, \ .string = {0x03, 0x00, \ src_page, src_woffset, 0x00, 0x00, \ dst_page, dst_woffset, 0x00, 0x00,\ size >> 8, size & 0xFF}, \ } \ } int hidpp10_erase_memory(struct hidpp10_device *dev, uint8_t page) { unsigned idx = dev->index; union hidpp10_message erase = CMD_ERASE_MEMORY(idx, page); hidpp_log_raw(&dev->base, "Erasing page 0x%02x\n", page); return hidpp10_request_command(dev, &erase); } int hidpp10_write_flash(struct hidpp10_device *dev, uint8_t src_page, uint16_t src_offset, uint8_t dst_page, uint16_t dst_offset, uint16_t size) { unsigned idx = dev->index; union hidpp10_message copy = CMD_WRITE_FLASH(idx, src_page, src_offset / 2, dst_page, dst_offset / 2, size); if ((src_offset % 2 != 0) || (dst_offset % 2 != 0)) { hidpp_log_error(&dev->base, "Accessing memory with odd offset is not supported.\n"); return -EINVAL; } hidpp_log_raw(&dev->base, "Copying %d bytes from (0x%02x,0x%04x) to (0x%02x,0x%04x)\n", size, src_page, src_offset, dst_page, dst_offset); return hidpp10_request_command(dev, ©); } /* -------------------------------------------------------------------------- */ /* 0x9x: HOT payload */ /* 0xA1: HOT Control Register */ /* -------------------------------------------------------------------------- */ #define __CMD_HOT_CONTROL 0xA1 #define CMD_HOT_RESET(idx) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = SET_REGISTER_REQ, \ .address = __CMD_HOT_CONTROL, \ .parameters = {0x01, 0x00, 0x00 }, \ } \ } #define HOT_NOTIFICATION 0x50 #define HOT_WRITE 0x92 #define HOT_CONTINUE 0x93 static int hidpp10_hot_ctrl_reset(struct hidpp10_device *dev) { unsigned idx = dev->index; union hidpp10_message ctrl_reset = CMD_HOT_RESET(idx); return hidpp10_request_command(dev, &ctrl_reset); } static int hidpp10_hot_request_command(struct hidpp10_device *dev, uint8_t data[LONG_MESSAGE_LENGTH]) { uint8_t read_buffer[LONG_MESSAGE_LENGTH] = {0}; int ret; uint8_t id = data[3]; if ((data[0] != REPORT_ID_LONG) || ((data[2] != HOT_WRITE) && (data[2] != HOT_CONTINUE))) return -EINVAL; /* Send the message to the Device */ ret = hidpp_write_command(&dev->base, data, LONG_MESSAGE_LENGTH); if (ret) goto out_err; /* * Now read the answers from the device: * loop until we get the actual answer or an error code. */ do { ret = hidpp_read_response(&dev->base, read_buffer, LONG_MESSAGE_LENGTH); /* Wait and retry if the USB timed out */ if (ret == -ETIMEDOUT) { msleep(10); ret = hidpp_read_response(&dev->base, read_buffer, LONG_MESSAGE_LENGTH); } /* actual answer */ if (read_buffer[2] == HOT_NOTIFICATION) break; } while (ret > 0); if (ret < 0) { hidpp_log_error(&dev->base, " USB error: %s (%d)\n", strerror(-ret), -ret); perror("write"); goto out_err; } if (read_buffer[4] != id) { ret = -EPROTO; hidpp_log_error(&dev->base, " Protocol error: ids do not match.\n"); perror("write"); goto out_err; } ret = 0; out_err: return ret; } struct hot_header { uint8_t id; uint8_t page; uint8_t offset; uint16_t zero; uint16_t size; uint16_t zero1; } __attribute__ ((__packed__)); static int hidpp10_send_hot_chunk(struct hidpp10_device *dev, uint8_t index, bool first, uint8_t dst_page, uint16_t dst_offset, uint8_t *data, unsigned size) { struct hot_header header = {0}; uint8_t buffer[LONG_MESSAGE_LENGTH] = {0}; unsigned offset = 0; unsigned count; int res; buffer[offset++] = REPORT_ID_LONG; buffer[offset++] = dev->index; if (first) { if (dst_offset % 2 != 0) { hidpp_log_error(&dev->base, "Writing memory with odd offset is not supported.\n"); return -EINVAL; } buffer[offset++] = HOT_WRITE; buffer[offset++] = index; header.id = 0x01; header.page = dst_page; header.offset = dst_offset / 2; header.size = hidpp_cpu_to_be_u16(size); memcpy(&buffer[offset], &header, sizeof(header)); offset += sizeof(header); } else { buffer[offset++] = HOT_CONTINUE; buffer[offset++] = index; } count = min(LONG_MESSAGE_LENGTH - offset, size); if (count <= 0) return -EINVAL; memcpy(&buffer[offset], data, count); res = hidpp10_hot_request_command(dev, buffer); if (res < 0) return res; return count; } int hidpp10_send_hot_payload(struct hidpp10_device *dev, uint8_t dst_page, uint16_t dst_offset, uint8_t *data, unsigned size) { bool first = true; unsigned int count = 0; unsigned int index = 0; int res; res = hidpp10_hot_ctrl_reset(dev); if (res < 0) return res; do { res = hidpp10_send_hot_chunk(dev, index, first, dst_page, dst_offset, data + count, size - count); if (res < 0) return res; first = false; count += res; index++; } while (size > count); return 0; } /* -------------------------------------------------------------------------- */ /* 0xA2: Read Sector */ /* -------------------------------------------------------------------------- */ #define __CMD_READ_MEMORY 0xA2 #define CMD_READ_MEMORY(idx, page, offset) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = GET_LONG_REGISTER_REQ, \ .address = __CMD_READ_MEMORY, \ .parameters = {page, offset, 0x00 }, \ } \ } int hidpp10_read_memory(struct hidpp10_device *dev, uint8_t page, uint16_t offset, uint8_t bytes[16]) { unsigned idx = dev->index; union hidpp10_message readmem = CMD_READ_MEMORY(idx, page, offset / 2); int res; if (offset % 2 != 0) { hidpp_log_error(&dev->base, "Reading memory with odd offset is not supported.\n"); return -EINVAL; } if (page > HIDPP10_MAX_PAGE_NUMBER) return -EINVAL; hidpp_log_raw(&dev->base, "Reading memory page %d, offset %#x\n", page, offset); res = hidpp10_request_command(dev, &readmem); if (res) return res; memcpy(bytes, readmem.msg.string, sizeof(readmem.msg.string)); return 0; } int hidpp10_read_page(struct hidpp10_device *dev, uint8_t page, uint8_t bytes[HIDPP10_PAGE_SIZE]) { unsigned int i; int res; uint16_t crc, read_crc; for (i = 0; i < HIDPP10_PAGE_SIZE; i += 16) { res = hidpp10_read_memory(dev, page, i, bytes + i); if (res < 0) return res; } crc = hidpp_crc_ccitt(bytes, HIDPP10_PAGE_SIZE - 2); read_crc = get_unaligned_be_u16(&bytes[HIDPP10_PAGE_SIZE - 2]); if (crc != read_crc) return -EILSEQ; /* return illegal sequence */ return 0; } /* -------------------------------------------------------------------------- */ /* 0xB2: Device Connection and Disconnection (Pairing) */ /* -------------------------------------------------------------------------- */ #define __CMD_DEVICE_CONNECTION_DISCONNECTION 0xB2 #define CONNECT_DEVICES_OPEN_LOCK 1 #define CONNECT_DEVICES_CLOSE_LOCK 2 #define CONNECT_DEVICES_DISCONNECT 3 #define CMD_DEVICE_CONNECTION_DISCONNECTION(idx, cmd, timeout) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = HIDPP_RECEIVER_IDX, \ .sub_id = SET_REGISTER_REQ, \ .address = __CMD_DEVICE_CONNECTION_DISCONNECTION, \ .parameters = {cmd, idx - 1, timeout }, \ } \ } int hidpp10_open_lock(struct hidpp10_device *device, uint8_t timeout) { union hidpp10_message open_lock = CMD_DEVICE_CONNECTION_DISCONNECTION(0x00, CONNECT_DEVICES_OPEN_LOCK, timeout); return hidpp10_request_command(device, &open_lock); } int hidpp10_close_lock(struct hidpp10_device *device) { union hidpp10_message open_lock = CMD_DEVICE_CONNECTION_DISCONNECTION(0x00, CONNECT_DEVICES_CLOSE_LOCK, 0); return hidpp10_request_command(device, &open_lock); } int hidpp10_disconnect(struct hidpp10_device *device, int idx) { union hidpp10_message disconnect = CMD_DEVICE_CONNECTION_DISCONNECTION(idx + 1, CONNECT_DEVICES_DISCONNECT, 0x00); return hidpp10_request_command(device, &disconnect); } /* -------------------------------------------------------------------------- */ /* 0xB5: Pairing Information */ /* -------------------------------------------------------------------------- */ #define __CMD_PAIRING_INFORMATION 0xB5 #define DEVICE_PAIRING_INFORMATION 0x20 #define DEVICE_EXTENDED_PAIRING_INFORMATION 0x30 #define DEVICE_NAME 0x40 #define CMD_PAIRING_INFORMATION(idx, type) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = HIDPP_RECEIVER_IDX, \ .sub_id = GET_LONG_REGISTER_REQ, \ .address = __CMD_PAIRING_INFORMATION, \ .parameters = {type + idx - 1, 0x00, 0x00 }, \ } \ } int hidpp10_get_pairing_information(struct hidpp10_device *dev, uint8_t *report_interval, uint16_t *wpid, uint8_t *device_type) { unsigned int idx = dev->index; union hidpp10_message pairing_information = CMD_PAIRING_INFORMATION(idx, DEVICE_PAIRING_INFORMATION); int res; hidpp_log_raw(&dev->base, "Fetching pairing information\n"); res = hidpp10_request_command(dev, &pairing_information); if (res) return -1; *report_interval = pairing_information.msg.string[2]; *wpid = get_unaligned_be_u16(&pairing_information.msg.string[3]); *device_type = pairing_information.msg.string[7]; return 0; } int hidpp10_get_pairing_information_device_name(struct hidpp10_device *dev, char *name, size_t *name_size) { unsigned int idx = dev->index; union hidpp10_message device_name = CMD_PAIRING_INFORMATION(idx, DEVICE_NAME); int res; hidpp_log_raw(&dev->base, "Fetching device name\n"); res = hidpp10_request_command(dev, &device_name); if (res) return -1; *name_size = min(*name_size, device_name.msg.string[1] + 1U); strncpy_safe(name, (char*)&device_name.msg.string[2], *name_size); return 0; } int hidpp10_get_extended_pairing_information(struct hidpp10_device *dev, uint32_t *serial) { unsigned int idx = dev->index; union hidpp10_message info = CMD_PAIRING_INFORMATION(idx, DEVICE_EXTENDED_PAIRING_INFORMATION); int res; hidpp_log_raw(&dev->base, "Fetching extended pairing information\n"); res = hidpp10_request_command(dev, &info); if (res) return -1; *serial = get_unaligned_be_u32(&info.msg.string[1]); return 0; } /* -------------------------------------------------------------------------- */ /* 0xF1: Device Firmware Information */ /* -------------------------------------------------------------------------- */ #define __CMD_DEVICE_FIRMWARE_INFORMATION 0xF1 #define FIRMWARE_INFO_ITEM_FW_NAME_AND_VERSION(MCU) ((MCU - 1) << 4 | 0x01) #define FIRMWARE_INFO_ITEM_FW_BUILD_NUMBER(MCU) ((MCU - 1) << 4 | 0x02) #define FIRMWARE_INFO_ITEM_HW_VERSION(MCU) ((MCU - 1) << 4 | 0x03) #define FIRMWARE_INFO_ITEM_BOOTLOADER_VERSION(MCU) ((MCU - 1) << 4 | 0x04) #define CMD_DEVICE_FIRMWARE_INFORMATION(idx, fw_info_item) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = GET_REGISTER_REQ, \ .address = __CMD_DEVICE_FIRMWARE_INFORMATION, \ .parameters = {fw_info_item, 0x00, 0x00 }, \ } \ } int hidpp10_get_firmare_information(struct hidpp10_device *dev, uint8_t *major_out, uint8_t *minor_out, uint8_t *build_out) { unsigned idx = dev->index; union hidpp10_message firmware_information = CMD_DEVICE_FIRMWARE_INFORMATION(idx, FIRMWARE_INFO_ITEM_FW_NAME_AND_VERSION(1)); union hidpp10_message build_information = CMD_DEVICE_FIRMWARE_INFORMATION(idx, FIRMWARE_INFO_ITEM_FW_BUILD_NUMBER(1)); int res; uint8_t maj, min, build; hidpp_log_raw(&dev->base, "Fetching firmware information\n"); /* * This may fail on some devices * => we can not retrieve their FW version through HID++ 1.0. */ res = hidpp10_request_command(dev, &firmware_information); if (res) return res; maj = firmware_information.msg.string[1]; min = firmware_information.msg.string[2]; res = hidpp10_request_command(dev, &build_information); if (res) return res; build = get_unaligned_be_u16(&build_information.msg.string[1]); *major_out = maj; *minor_out = min; *build_out = build; return 0; } /* -------------------------------------------------------------------------- */ /* general device handling */ /* -------------------------------------------------------------------------- */ static int hidpp10_get_device_info(struct hidpp10_device *dev) { uint32_t feature_mask, notifications; uint8_t reflect; uint16_t xres, yres; uint16_t refresh_rate; enum hidpp10_led_status led[6]; uint8_t current_profile; hidpp10_get_individual_features(dev, &feature_mask); hidpp10_get_hidpp_notifications(dev, ¬ifications); hidpp10_get_current_resolution(dev, &xres, &yres); hidpp10_get_led_status(dev, led); hidpp10_get_usb_refresh_rate(dev, &refresh_rate); hidpp10_get_optical_sensor_settings(dev, &reflect); return hidpp10_get_current_profile(dev, ¤t_profile); } struct hidpp10_device* hidpp10_device_new(const struct hidpp_device *base, int idx, enum hidpp10_profile_type type, unsigned int profile_count) { struct hidpp10_device *dev; dev = zalloc(sizeof(*dev)); dev->index = idx; dev->base = *base; dev->profile_type = type; dev->profile_count = profile_count; dev->profiles = zalloc(dev->profile_count * sizeof(struct hidpp10_profile)); if (hidpp10_get_device_info(dev) != 0) { hidpp10_device_destroy(dev); dev = NULL; } return dev; } int hidpp10_device_read_profiles(struct hidpp10_device *dev) { unsigned int i; hidpp10_read_profile_directory(dev); for (i = 0; i < dev->profile_count && i < HIDPP10_NUM_PROFILES; i++) hidpp10_read_profile(dev, i); return 0; } void hidpp10_device_destroy(struct hidpp10_device *dev) { union hidpp10_macro_data **macro; unsigned i; free(dev->dpi_table); for (i = 0; i < dev->profile_count; i++) { ARRAY_FOR_EACH(dev->profiles[i].macros, macro) { if (*macro) { free(*macro); *macro = NULL; } } } free(dev->profiles); free(dev); } libratbag-0.13/src/hidpp10.h000066400000000000000000000545601362011324700156020ustar00rootroot00000000000000/* * HID++ 1.0 library - headers file. * * 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 (including the next * paragraph) 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. */ /* * Based on the HID++ 1.0 documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #pragma once #include #include #include "hidpp-generic.h" struct dpi_list; struct dpi_range; /* FIXME: that's what my G500s supports, but only pages 3-5 are valid. * 0 is zeroed, 1 and 2 are garbage, all above 6 is garbage */ #define HIDPP10_NUM_PROFILES 5 #define HIDPP10_MAX_PAGE_NUMBER 31 enum hidpp10_profile_type { HIDPP10_PROFILE_UNKNOWN = -1, HIDPP10_PROFILE_G500, HIDPP10_PROFILE_G700, HIDPP10_PROFILE_G9, }; struct hidpp10_directory; struct hidpp10_profile; struct hidpp10_dpi_mapping { uint8_t raw_value; unsigned dpi; }; struct hidpp10_device { struct hidpp_device base; unsigned index; bool dpi_table_is_range; uint8_t dpi_count; struct hidpp10_dpi_mapping *dpi_table; enum hidpp10_profile_type profile_type; struct hidpp10_profile *profiles; unsigned int profile_count; }; struct hidpp10_device* hidpp10_device_new(const struct hidpp_device *base, int idx, enum hidpp10_profile_type type, unsigned int profile_count); int hidpp10_device_read_profiles(struct hidpp10_device *dev); void hidpp10_device_destroy(struct hidpp10_device *dev); /** * Builds the table of dpi for the device from the given string. * * @param dev a struct hidpp10_device previously allocated * @param str_list a string representing the dpi table * * The given string contains only positive integer values, separated by * semicolons (';'). The n-th element in the list corresponds to the * raw value (0x80 + n - 1) */ int hidpp10_build_dpi_table_from_list(struct hidpp10_device *dev, const struct dpi_list *list); /** * Builds the table of dpi for the device from the given dpi description. * * @param dev a struct hidpp10_device previously allocated * @param str_dpi a string representing the dpi parameters * * The given string contains 3 float values, separated by colons (':'). * The format is MIN:MAX@STEP * MIN corresponds to the raw_value '0' * MAX corresponds to the raw_value floor((MAX - MIN) / STEP) */ int hidpp10_build_dpi_table_from_dpi_info(struct hidpp10_device *dev, const struct dpi_range *range); /** * Returns the lowest dpi stored in the dpi mapping table generated by * hidpp10_build_dpi_table_from_list() or * hidpp10_build_dpi_table_from_dpi_info() */ unsigned int hidpp10_dpi_table_get_min_dpi(struct hidpp10_device *dev); /** * Returns the highest dpi stored in the dpi mapping table generated by * hidpp10_build_dpi_table_from_list() or * hidpp10_build_dpi_table_from_dpi_info() */ unsigned int hidpp10_dpi_table_get_max_dpi(struct hidpp10_device *dev); /* -------------------------------------------------------------------------- */ /* 0x00: Enable HID++ Notifications */ /* -------------------------------------------------------------------------- */ /** * All notifications are disabled by default on powerup. */ enum hidpp10_hidpp_notifications { /** * enabled: Multimedia and MS vendor specific keys are reported as * HID++ notification 0x03 * disabled: reported as normal HID reports */ HIDPP10_NOTIFICATIONS_CONSUMER_VENDOR_SPECIFIC_CONTROL = (1 << 0), /** * enabled: power keys are reported as HID++ notification 0x04 * disabled: reported as normal HID reports */ HIDPP10_NOTIFICATIONS_POWER_KEYS = (1 << 1), /** * enabled: Vertical scroll wheel/iNav are reported as HID++ * notification 0x05 * disabled: reported as normal HID reports */ HIDPP10_NOTIFICATIONS_ROLLER_V = (1 << 2), /** * enabled: buttons not available in standard HID are reported as * HID++ notification 0x06 * disabled: buttons not available in standard HID are not reported */ HIDPP10_NOTIFICATIONS_MOUSE_EXTRA_BUTTONS = (1 << 3), /** * enabled: battery status/milage are reported as HID++ notification * 0x07 or 0x0D (device-dependent) * disabled: battery status/milage are not reported */ HIDPP10_NOTIFICATIONS_BATTERY_STATUS = (1 << 4), /** * enabled: Horizontal scroll wheel/iNav are reported as HID++ * notification 0x05 * disabled: reported as normal HID reports */ HIDPP10_NOTIFICATIONS_ROLLER_H = (1 << 5), /** * enabled: F-Lock status is reported as HID++ notification 0x09 * disabled: F-Lock status is not reported */ HIDPP10_NOTIFICATIONS_F_LOCK_STATUS = (1 << 6), /** * enabled: Numpad keys are reported as buttons in HID++ * notification 0x03 * disabled: reported as normal keys */ HIDPP10_NOTIFICATIONS_NUMPAD_NUMERIC_KEYS = (1 << 7), /** * enabled: Device arrival/removal/... are reported as HID++ * notifications 0x40, 0x41, 0x46 or 0x78 * disabled: these events are not reported */ HIDPP10_NOTIFICATIONS_WIRELESS_NOTIFICATIONS = (1 << 8), /** * enabled: User interface events are reported as HID++ notification * 0x08 * disabled: these events are not reported */ HIDPP10_NOTIFICATIONS_UI_NOTIFICATIONS = (1 << 9), /** * enabled: Quad link quality info events are reported as HID++ notification * 0x49 * disabled: these events are not reported */ HIDPP10_NOTIFICATIONS_QUAD_LINK_QUALITY_INFO = (1 << 10), HIDPP10_NOTIFICATIONS_SOFTWARE_PRESENT = (1 << 11), HIDPP10_NOTIFICATIONS_TOUCHPAD_MULTITOUCH_NOTIFICATIONS = (1 << 12), /* 1 << 13 is reserved */ /* 1 << 14 is reserved */ /* 1 << 15 is reserved */ /** * enabled: 3D gestures are reported as HID++ notification 0x65 * disabled: these events are not reported */ HIDPP10_NOTIFICATIONS_3D_GESTURE = (1 << 16), HIDPP10_NOTIFICATIONS_VOIP_TELEPHONY = (1 << 17), HIDPP10_NOTIFICATIONS_CONFIGURATION_COMPLETE = (1 << 18), /* 1 << 19 is reserved */ /* 1 << 20 is reserved */ /* 1 << 21 is reserved */ /* 1 << 22 is reserved */ /* 1 << 23 is reserved */ }; int hidpp10_get_hidpp_notifications(struct hidpp10_device *dev, uint32_t *reporting_flags); int hidpp10_set_hidpp_notifications(struct hidpp10_device *dev, uint32_t reporting_flags); /* -------------------------------------------------------------------------- */ /* 0x01: Enable Individual Features */ /* -------------------------------------------------------------------------- */ enum hidpp10_individual_features { HIDPP10_FEATURE_BIT_MOUSE_SENSOR_RESOLUTION = (1 << 0), /** * disabled: buttons send button codes * enabled: buttons have special functions (default) * @note Do not use, use 0x63 instead */ HIDPP10_FEATURE_BIT_SPECIAL_BUTTON_FUNCTION = (1 << 1), /** * disabled: normal key usage (default) * enabled: enhanced key usage */ HIDPP10_FEATURE_BIT_ENHANCED_KEY_USAGE = (1 << 2), /** * disabled: (default) * enabled: */ HIDPP10_FEATURE_BIT_FAST_FORWARD_REWIND = (1 << 3), /** * disabled: (default) * enabled: */ HIDPP10_FEATURE_BIT_SEND_CALCULATOR_RESULT = (1 << 4), /** * disabled: * enabled: (default) */ HIDPP10_FEATURE_BIT_MOTION_WAKEUP = (1 << 5), /** * disabled: (default) * enabled: */ HIDPP10_FEATURE_BIT_FAST_SCROLLING = (1 << 6), /** * disabled: work as buttons * enabled: control the resolution (default) */ HIDPP10_FEATURE_BIT_BUTTONS_CONTROL_RESOLUTION = (1 << 7), /* 1 << 8 is reserved */ /** * disabled: (default) * enabled: */ HIDPP10_FEATURE_BIT_RECEIVER_MULTIPLE_RF_LOCK = (1 << 9), /** * disabled: (default) * enabled: */ HIDPP10_FEATURE_BIT_RECEIVER_DISABLE_RFSCAN_IN_SUSPEND = (1 << 10), /** * disabled: (default) * enabled: * * When enabled,removes all compatibility checks. */ HIDPP10_FEATURE_BIT_RECEIVER_ACCEPT_ALL_DEVICES_IN_PAIRING = (1 << 11), /* 1 << 12 is reserved */ /* 1 << 13 is reserved */ /* 1 << 14 is reserved */ /* 1 << 15 is reserved */ /** * disabled: (default) * enabled: no sound */ HIDPP10_FEATURE_BIT_INHIBIT_LOCK_KEY_SOUND = (1 << 16), /** * disabled: (default) * enabled: */ HIDPP10_FEATURE_BIT_INHIBIT_TOUCHPAD = (1 << 17), /** * disabled: * enabled: (default) */ HIDPP10_FEATURE_BIT_3D_ENGINE = (1 << 18), /** * disabled: (disabled) * enabled: */ HIDPP10_FEATURE_BIT_SW_CONTROLS_LEDS = (1 << 19), /** * disabled: (disabled) * enabled: */ HIDPP10_FEATURE_BIT_NO_NUMLOCK_TOGGLE = (1 << 20), /** * disabled: (disabled) * enabled: */ HIDPP10_FEATURE_BIT_INHIBIT_PRESENCE_DETECTION = (1 << 21), }; int hidpp10_get_individual_features(struct hidpp10_device *dev, uint32_t *feature_mask); int hidpp10_set_individual_features(struct hidpp10_device *dev, uint32_t feature_mask); /* -------------------------------------------------------------------------- */ /* 0x07: Battery Status */ /* -------------------------------------------------------------------------- */ enum hidpp10_battery_level { HIDPP10_BATTERY_LEVEL_UNKNOWN = 0x00, HIDPP10_BATTERY_LEVEL_CRITICAL = 0x01, HIDPP10_BATTERY_LEVEL_CRITICAL_LEGACY = 0x02, HIDPP10_BATTERY_LEVEL_LOW = 0x03, HIDPP10_BATTERY_LEVEL_LOW_LEGACY = 0x04, HIDPP10_BATTERY_LEVEL_GOOD = 0x05, HIDPP10_BATTERY_LEVEL_GOOD_LEGACY = 0x06, HIDPP10_BATTERY_LEVEL_FULL_LEGACY = 0x07, /* 0x08..0xFF ... reserved */ }; enum hidpp10_battery_charge_state { HIDPP10_BATTERY_CHARGE_STATE_NOT_CHARGING = 0x00, /* 0x01 ... 0x1F ... reserved (not charging) */ HIDPP10_BATTERY_CHARGE_STATE_UNKNOWN = 0x20, HIDPP10_BATTERY_CHARGE_STATE_CHARGING = 0x21, HIDPP10_BATTERY_CHARGE_STATE_CHARGING_COMPLETE = 0x22, HIDPP10_BATTERY_CHARGE_STATE_CHARGING_ERROR = 0x23, HIDPP10_BATTERY_CHARGE_STATE_CHARGING_FAST = 0x24, HIDPP10_BATTERY_CHARGE_STATE_CHARGING_SLOW = 0x25, HIDPP10_BATTERY_CHARGE_STATE_TOPPING_CHARGE = 0x26, /* 0x27 .. 0xff ... reserved */ }; int hidpp10_get_battery_status(struct hidpp10_device *dev, enum hidpp10_battery_level *level, enum hidpp10_battery_charge_state *charge_state, uint8_t *low_threshold_in_percent); /* -------------------------------------------------------------------------- */ /* 0x0D: Battery Mileage */ /* -------------------------------------------------------------------------- */ int hidpp10_get_battery_mileage(struct hidpp10_device *dev, uint8_t *level_in_percent, uint32_t *max_seconds, enum hidpp10_battery_charge_state *state); /* -------------------------------------------------------------------------- */ /* 0x0F: Profile queries */ /* -------------------------------------------------------------------------- */ #define PROFILE_NUM_BUTTONS 13 #define PROFILE_NUM_BUTTONS_G9 10 #define PROFILE_NUM_DPI_MODES 5 #define PROFILE_BUTTON_TYPE_BUTTON 0x81 #define PROFILE_BUTTON_TYPE_KEYS 0x82 #define PROFILE_BUTTON_TYPE_SPECIAL 0x83 #define PROFILE_BUTTON_TYPE_CONSUMER_CONTROL 0x84 #define PROFILE_BUTTON_TYPE_DISABLED 0x8F #define PROFILE_BUTTON_SPECIAL_PAN_LEFT 0x1 #define PROFILE_BUTTON_SPECIAL_PAN_RIGHT 0x2 #define PROFILE_BUTTON_SPECIAL_DPI_NEXT 0x4 #define PROFILE_BUTTON_SPECIAL_DPI_PREV 0x8 #define HIDPP10_MACRO_NOOP 0x00 #define HIDPP10_MACRO_WAIT_FOR_BUTTON_RELEASE 0x01 #define HIDPP10_MACRO_REPEAT_UNTIL_BUTTON_RELEASE 0x02 #define HIDPP10_MACRO_REPEAT 0x03 #define HIDPP10_MACRO_KEY_PRESS 0x20 #define HIDPP10_MACRO_KEY_RELEASE 0x21 #define HIDPP10_MACRO_MOD_PRESS 0x22 #define HIDPP10_MACRO_MOD_RELEASE 0x23 #define HIDPP10_MACRO_MOUSE_WHEEL 0x24 #define HIDPP10_MACRO_MOUSE_BUTTON_PRESS 0x40 #define HIDPP10_MACRO_MOUSE_BUTTON_RELEASE 0x41 #define HIDPP10_MACRO_KEY_CONSUMER_CONTROL 0x42 #define HIDPP10_MACRO_DELAY 0x43 #define HIDPP10_MACRO_JUMP 0x44 #define HIDPP10_MACRO_JUMP_IF_PRESSED 0x45 #define HIDPP10_MACRO_MOUSE_POINTER_MOVE 0x60 #define HIDPP10_MACRO_JUMP_IF_RELEASED_TIMEOUT 0x61 #define HIDPP10_MACRO_END 0xff union hidpp10_macro_data { struct { uint8_t type; } __attribute__((packed)) any; struct { uint8_t type; /* HIDPP10_MACRO_KEY_PRESS or HIDPP10_MACRO_KEY_RELEASE */ uint8_t key; } __attribute__((packed)) key; struct { uint8_t type; /* HIDPP10_MACRO_MOD_PRESS or HIDPP10_MACRO_MOD_RELEASE */ uint8_t key; } __attribute__((packed)) modifier; struct { uint8_t type; /* HIDPP10_MACRO_MOUSE_WHEEL */ int8_t value; } __attribute__((packed)) wheel; struct { uint8_t type; /* HIDPP10_MACRO_MOUSE_BUTTON_PRESS or HIDPP10_MACRO_MOUSE_BUTTON_RELEASE */ uint16_t flags; } __attribute__((packed)) button; struct { uint8_t type; /* HIDPP10_MACRO_KEY_CONSUMER_CONTROL */ uint16_t key; } __attribute__((packed)) consumer_control; struct { uint8_t type; /* HIDPP10_MACRO_DELAY */ uint16_t time; } __attribute__((packed)) delay; struct { uint8_t type; /* HIDPP10_MACRO_JUMP or HIDPP10_MACRO_JUMP_IF_PRESSED */ uint8_t page; uint8_t offset; } __attribute__((packed)) jump; struct { uint8_t type; /* HIDPP10_MACRO_MOUSE_POINTER_MOVE */ int16_t x_rel; uint16_t y_rel; } __attribute__((packed)) pointer; struct { uint8_t type; /* HIDPP10_MACRO_JUMP_IF_RELEASED_TIMEOUT */ int16_t timeout; uint8_t page; uint8_t offset; } __attribute__((packed)) jump_timeout; struct { uint8_t type; /* HIDPP10_MACRO_END */ } __attribute__((packed)) end; } __attribute__((packed)); _Static_assert(sizeof(union hidpp10_macro_data) == 5, "Invalid size"); struct hidpp10_profile { uint8_t page; uint8_t offset; struct { uint16_t xres; uint16_t yres; bool led[4]; } dpi_modes[5]; size_t num_dpi_modes; unsigned char name[24]; /* the G700 has 23 chars, add one for terminating 0 */ unsigned char macro_names[11][18]; /* adding one extra terminating 0 per name */ uint8_t red; uint8_t green; uint8_t blue; bool angle_correction; uint8_t default_dpi_mode; uint16_t refresh_rate; union hidpp10_button { struct { uint8_t type; } any; struct { uint8_t type; uint16_t button; } button; struct { uint8_t type; uint8_t modifier_flags; uint8_t key; } keys; struct { uint8_t type; uint16_t special; } special; struct { uint8_t type; uint16_t consumer_control; } consumer_control; struct { uint8_t type; } disabled; struct { uint8_t page; uint8_t offset; uint8_t address; } macro; } buttons[PROFILE_NUM_BUTTONS]; union hidpp10_macro_data *macros[PROFILE_NUM_BUTTONS]; size_t num_buttons; size_t num_leds; unsigned int initialized; bool enabled; }; struct hidpp10_directory { uint8_t page; uint8_t offset; uint8_t led_mask; } __attribute__((packed)); int hidpp10_get_current_profile(struct hidpp10_device *dev, uint8_t *current_profile); int hidpp10_set_current_profile(struct hidpp10_device *dev, uint16_t current_profile); int hidpp10_get_profile(struct hidpp10_device *dev, uint8_t number, struct hidpp10_profile *profile); int hidpp10_set_profile(struct hidpp10_device *dev, uint8_t number, struct hidpp10_profile *profile); enum ratbag_button_action_special hidpp10_onboard_profiles_get_special(uint8_t code); uint8_t hidpp10_onboard_profiles_get_code_from_special(enum ratbag_button_action_special special); /* -------------------------------------------------------------------------- */ /* 0x51: LED Status */ /* -------------------------------------------------------------------------- */ enum hidpp10_led_status { HIDPP10_LED_STATUS_NO_CHANGE = 0x0, /**< LED does not exist, or should not change */ HIDPP10_LED_STATUS_OFF = 0x1, HIDPP10_LED_STATUS_ON = 0x2, HIDPP10_LED_STATUS_BLINK = 0x3, HIDPP10_LED_STATUS_HEARTBEAT = 0x4, HIDPP10_LED_STATUS_SLOW_ON = 0x5, HIDPP10_LED_STATUS_SLOW_OFF = 0x6, }; int hidpp10_get_led_status(struct hidpp10_device *dev, enum hidpp10_led_status led[6]); int hidpp10_set_led_status(struct hidpp10_device *dev, const enum hidpp10_led_status led[6]); /* -------------------------------------------------------------------------- */ /* 0x54: LED Intensity */ /* -------------------------------------------------------------------------- */ int hidpp10_get_led_intensity(struct hidpp10_device *dev, uint8_t led_intensity_in_percent[6]); /* Granularity for the led intensity is 10% increments. A value of 0 leaves * the intensity unchanged */ int hidpp10_set_led_intensity(struct hidpp10_device *dev, const uint8_t led_intensity_in_percent[6]); /* -------------------------------------------------------------------------- */ /* 0x57: LED Color */ /* -------------------------------------------------------------------------- */ /* Note: this changes the color of the LED only, use 0x51 to turn the LED * on/off */ int hidpp10_get_led_color(struct hidpp10_device *dev, uint8_t *red, uint8_t *green, uint8_t *blue); int hidpp10_set_led_color(struct hidpp10_device *dev, uint8_t red, uint8_t green, uint8_t blue); /* -------------------------------------------------------------------------- */ /* 0x61: Optical Sensor Settings */ /* -------------------------------------------------------------------------- */ int hidpp10_get_optical_sensor_settings(struct hidpp10_device *dev, uint8_t *surface_reflectivity); /* -------------------------------------------------------------------------- */ /* 0x63: Current Resolution */ /* -------------------------------------------------------------------------- */ int hidpp10_get_current_resolution(struct hidpp10_device *dev, uint16_t *xres, uint16_t *yres); int hidpp10_set_current_resolution(struct hidpp10_device *dev, uint16_t xres, uint16_t yres); /* -------------------------------------------------------------------------- */ /* 0x64: USB Refresh Rate */ /* -------------------------------------------------------------------------- */ int hidpp10_get_usb_refresh_rate(struct hidpp10_device *dev, uint16_t *rate); int hidpp10_set_usb_refresh_rate(struct hidpp10_device *dev, uint16_t rate); /* -------------------------------------------------------------------------- */ /* 0xA0: Generic Memory Management */ /* -------------------------------------------------------------------------- */ int hidpp10_erase_memory(struct hidpp10_device *dev, uint8_t page); int hidpp10_write_flash(struct hidpp10_device *dev, uint8_t src_page, uint16_t src_offset, uint8_t dst_page, uint16_t dst_offset, uint16_t size); /* -------------------------------------------------------------------------- */ /* 0x9x: HOT payload */ /* 0xA1: HOT Control Register */ /* -------------------------------------------------------------------------- */ int hidpp10_send_hot_payload(struct hidpp10_device *dev, uint8_t dst_page, uint16_t dst_offset, uint8_t *data, unsigned size); /* -------------------------------------------------------------------------- */ /* 0xA2: Read Sector */ /* -------------------------------------------------------------------------- */ #define HIDPP10_PAGE_SIZE (16 * 2 * 16) int hidpp10_read_memory(struct hidpp10_device *dev, uint8_t page, uint16_t offset, uint8_t bytes[16]); int hidpp10_read_page(struct hidpp10_device *dev, uint8_t page, uint8_t bytes[HIDPP10_PAGE_SIZE]); /* -------------------------------------------------------------------------- */ /* 0xB2: Device Connection and Disconnection (Pairing) */ /* -------------------------------------------------------------------------- */ /** * Open the receiver's lock to allow new devices be paired with this * receiver. The timeout is in seconds, a value of 0 uses the device's * default value (30s). */ int hidpp10_open_lock(struct hidpp10_device *device, uint8_t timeout); int hidpp10_close_lock(struct hidpp10_device *device); int hidpp10_disconnect(struct hidpp10_device *device, int idx); /* -------------------------------------------------------------------------- */ /* 0xB5: Pairing Information */ /* -------------------------------------------------------------------------- */ int hidpp10_get_pairing_information(struct hidpp10_device *dev, uint8_t *report_interval, uint16_t *wpid, uint8_t *device_type); int hidpp10_get_pairing_information_device_name(struct hidpp10_device *dev, char *name, size_t *name_sz); int hidpp10_get_extended_pairing_information(struct hidpp10_device *dev, uint32_t *serial); /* -------------------------------------------------------------------------- */ /* 0xF1: Device Firmware Information */ /* -------------------------------------------------------------------------- */ int hidpp10_get_firmare_information(struct hidpp10_device *dev, uint8_t *major, uint8_t *minor, uint8_t *build_number); libratbag-0.13/src/hidpp20.c000066400000000000000000002367411362011324700156010ustar00rootroot00000000000000/* * HID++ 2.0 library. * * Copyright 2015 Benjamin Tissoires * Copyright 2015 Red Hat, Inc * * 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 (including the next * paragraph) 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. */ /* * Based on the HID++ 2.0 documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #include "config.h" #include #include #include #include #include #include "hidpp20.h" #include "libratbag.h" #include "libratbag-hidraw.h" #include "libratbag-util.h" #include "libratbag-private.h" const char* hidpp20_feature_get_name(uint16_t feature) { static char numeric[8]; const char *str; switch(feature) { CASE_RETURN_STRING(HIDPP_PAGE_ROOT); CASE_RETURN_STRING(HIDPP_PAGE_FEATURE_SET); CASE_RETURN_STRING(HIDPP_PAGE_DEVICE_INFO); CASE_RETURN_STRING(HIDPP_PAGE_DEVICE_NAME); CASE_RETURN_STRING(HIDPP_PAGE_RESET); CASE_RETURN_STRING(HIDPP_PAGE_BATTERY_LEVEL_STATUS); CASE_RETURN_STRING(HIDPP_PAGE_BATTERY_VOLTAGE); CASE_RETURN_STRING(HIDPP_PAGE_KBD_REPROGRAMMABLE_KEYS); CASE_RETURN_STRING(HIDPP_PAGE_SPECIAL_KEYS_BUTTONS); CASE_RETURN_STRING(HIDPP_PAGE_WIRELESS_DEVICE_STATUS); CASE_RETURN_STRING(HIDPP_PAGE_MOUSE_POINTER_BASIC); CASE_RETURN_STRING(HIDPP_PAGE_ADJUSTABLE_DPI); CASE_RETURN_STRING(HIDPP_PAGE_ADJUSTABLE_REPORT_RATE); CASE_RETURN_STRING(HIDPP_PAGE_COLOR_LED_EFFECTS); CASE_RETURN_STRING(HIDPP_PAGE_RGB_EFFECTS); CASE_RETURN_STRING(HIDPP_PAGE_ONBOARD_PROFILES); CASE_RETURN_STRING(HIDPP_PAGE_MOUSE_BUTTON_SPY); default: sprintf_safe(numeric, "%#4x", feature); str = numeric; break; } return str; } const char* hidpp20_sw_led_control_get_mode_string(const enum hidpp20_led_sw_ctrl_led_mode mode) { static char numeric[8]; const char* str; switch (mode) { CASE_RETURN_STRING(HIDPP20_LED_MODE_OFF); CASE_RETURN_STRING(HIDPP20_LED_MODE_ON); CASE_RETURN_STRING(HIDPP20_LED_MODE_BLINK); CASE_RETURN_STRING(HIDPP20_LED_MODE_RAMP_UP); CASE_RETURN_STRING(HIDPP20_LED_MODE_RAMP_DOWN); CASE_RETURN_STRING(HIDPP20_LED_MODE_BREATHING); CASE_RETURN_STRING(HIDPP20_LED_MODE_HEARTBEAT); CASE_RETURN_STRING(HIDPP20_LED_MODE_TRAVEL); default: sprintf_safe(numeric, "%#4x", mode); str = numeric; break; } return str; } const char* hidpp20_get_quirk_string(enum hidpp20_quirk quirk) { switch (quirk) { CASE_RETURN_STRING(HIDPP20_QUIRK_NONE); CASE_RETURN_STRING(HIDPP20_QUIRK_G305); CASE_RETURN_STRING(HIDPP20_QUIRK_G602); } abort(); } static int hidpp20_request_command_allow_error(struct hidpp20_device *device, union hidpp20_message *msg, bool allow_error) { union hidpp20_message read_buffer; int ret; uint8_t hidpp_err = 0; size_t msg_len; /* msg->address is 4 MSB: subcommand, 4 LSB: 4-bit SW identifier so * the device knows who to respond to. kernel uses 0x1 */ const int DEVICE_SW_ID = 0x8; if (msg->msg.address & 0xf) { hidpp_log_raw(&device->base, "hidpp20 error: sw address is already set\n"); return -EINVAL; } msg->msg.address |= DEVICE_SW_ID; /* some mice don't support short reports */ if (msg->msg.report_id == REPORT_ID_SHORT && !(device->base.supported_report_types & HIDPP_REPORT_SHORT)) msg->msg.report_id = REPORT_ID_LONG; /* sanity check */ if (msg->msg.report_id == REPORT_ID_LONG && !(device->base.supported_report_types & HIDPP_REPORT_LONG)) { hidpp_log_error(&device->base, "hidpp20: trying to use unsupported report type\n"); return -EINVAL; } msg_len = msg->msg.report_id == REPORT_ID_SHORT ? SHORT_MESSAGE_LENGTH : LONG_MESSAGE_LENGTH; /* Send the message to the Device */ ret = hidpp_write_command(&device->base, msg->data, msg_len); if (ret) goto out_err; /* * Now read the answers from the device: * loop until we get the actual answer or an error code. */ do { ret = hidpp_read_response(&device->base, read_buffer.data, LONG_MESSAGE_LENGTH); /* Wait and retry if the USB timed out */ if (ret == -ETIMEDOUT) { msleep(10); ret = hidpp_read_response(&device->base, read_buffer.data, LONG_MESSAGE_LENGTH); } if (read_buffer.msg.report_id != REPORT_ID_SHORT && read_buffer.msg.report_id != REPORT_ID_LONG) continue; /* actual answer */ if (read_buffer.msg.sub_id == msg->msg.sub_id && read_buffer.msg.address == msg->msg.address) break; /* error */ if ((read_buffer.msg.sub_id == __ERROR_MSG || read_buffer.msg.sub_id == 0xff) && read_buffer.msg.address == msg->msg.sub_id && read_buffer.msg.parameters[0] == msg->msg.address) { hidpp_err = read_buffer.msg.parameters[1]; if (allow_error) hidpp_log_debug(&device->base, " HID++ error from the device (%d): %s (%02x)\n", read_buffer.msg.device_idx, hidpp20_errors[hidpp_err] ? hidpp20_errors[hidpp_err] : "Undocumented error code", hidpp_err); else hidpp_log_error(&device->base, " HID++ error from the device (%d): %s (%02x)\n", read_buffer.msg.device_idx, hidpp20_errors[hidpp_err] ? hidpp20_errors[hidpp_err] : "Undocumented error code", hidpp_err); break; } } while (ret > 0); if (ret < 0) { hidpp_log_error(&device->base, " USB error: %s (%d)\n", strerror(-ret), -ret); perror("write"); goto out_err; } if (!hidpp_err) { /* copy the answer for the caller */ *msg = read_buffer; } ret = hidpp_err; out_err: return ret; } int hidpp20_request_command(struct hidpp20_device *device, union hidpp20_message *msg) { int ret = hidpp20_request_command_allow_error(device, msg, false); return ret > 0 ? -EPROTO : ret; } /* -------------------------------------------------------------------------- */ /* 0x0000: Root */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_ROOT_IDX 0x00 #define CMD_ROOT_GET_FEATURE 0x00 #define CMD_ROOT_GET_PROTOCOL_VERSION 0x10 /** * Returns the feature index or 0x00 if it is not found. */ static uint8_t hidpp_root_get_feature_idx(struct hidpp20_device *device, uint16_t feature) { unsigned i; /* error or not, we should not ask for feature 0 */ if (feature == 0x0000) return 0; /* feature 0x0000 is always at 0 */ for (i = 1; i < device->feature_count; i++) { if (device->feature_list[i].feature == feature) return i; } return 0; } int hidpp_root_get_feature(struct hidpp20_device *device, uint16_t feature, uint8_t *feature_index, uint8_t *feature_type, uint8_t *feature_version) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = HIDPP_PAGE_ROOT_IDX, .msg.address = CMD_ROOT_GET_FEATURE, }; set_unaligned_be_u16(&msg.msg.parameters[0], feature); rc = hidpp20_request_command(device, &msg); if (rc) return rc; *feature_index = msg.msg.parameters[0]; *feature_type = msg.msg.parameters[1]; *feature_version = msg.msg.parameters[2]; hidpp_log_raw(&device->base, "feature 0x%04x is at 0x%02x\n", feature, *feature_index); return 0; } int hidpp20_root_get_protocol_version(struct hidpp20_device *device, unsigned *major, unsigned *minor) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = HIDPP_PAGE_ROOT_IDX, .msg.address = CMD_ROOT_GET_PROTOCOL_VERSION, }; rc = hidpp20_request_command_allow_error(device, &msg, true); if (rc == HIDPP10_ERR_INVALID_SUBID) { *major = 1; *minor = 0; return 0; } if (rc == 0) { *major = msg.msg.parameters[0]; *minor = msg.msg.parameters[1]; } return rc; } /* -------------------------------------------------------------------------- */ /* 0x0001: Feature Set */ /* -------------------------------------------------------------------------- */ #define CMD_FEATURE_SET_GET_COUNT 0x00 #define CMD_FEATURE_SET_GET_FEATURE_ID 0x10 static int hidpp20_feature_set_get_count(struct hidpp20_device *device, uint8_t reg) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_FEATURE_SET_GET_COUNT, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return msg.msg.parameters[0]; } static int hidpp20_feature_set_get_feature_id(struct hidpp20_device *device, uint8_t reg, uint8_t feature_index, uint16_t *feature, uint8_t *type) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_FEATURE_SET_GET_FEATURE_ID, .msg.parameters[0] = feature_index, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; *feature = get_unaligned_be_u16(msg.msg.parameters); *type = msg.msg.parameters[2]; return 0; } /** * allocates the list of features. * * returns 0 or a negative error */ static int hidpp20_feature_set_get(struct hidpp20_device *device) { uint8_t feature_index, feature_type, feature_version; struct hidpp20_feature *flist; int rc; uint8_t feature_count; unsigned int i; rc = hidpp_root_get_feature(device, HIDPP_PAGE_FEATURE_SET, &feature_index, &feature_type, &feature_version); if (rc) return rc; rc = hidpp20_feature_set_get_count(device, feature_index); if (rc < 0) return rc; /* feature set count does not include the root feature as documented here: * https://6xq.net/git/lars/lshidpp.git/plain/doc/logitech_hidpp_2.0_specification_draft_2012-06-04.pdf **/ feature_count = ((uint8_t)rc) + 1; if (feature_count == 1) return -ENOTSUP; flist = zalloc((feature_count + 1) * sizeof(struct hidpp20_feature)); for (i = 0; i < feature_count; i++) { rc = hidpp20_feature_set_get_feature_id(device, feature_index, i, &flist[i].feature, &flist[i].type); if (rc) goto err; } device->feature_list = flist; device->feature_count = feature_count; return 0; err: free(flist); return rc; } /* -------------------------------------------------------------------------- */ /* 0x1000: Battery level status */ /* -------------------------------------------------------------------------- */ #define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS 0x00 #define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_CAPABILITY 0x10 int hidpp20_batterylevel_get_battery_level(struct hidpp20_device *device, uint16_t *level, uint16_t *next_level) { uint8_t feature_index; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS, }; int rc; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_BATTERY_LEVEL_STATUS); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; *level = msg.msg.parameters[0]; *next_level = msg.msg.parameters[1]; return msg.msg.parameters[2]; } /* -------------------------------------------------------------------------- */ /* 0x1001: Battery voltage */ /* -------------------------------------------------------------------------- */ #define CMD_BATTERY_VOLTAGE_GET_BATTERY_VOLTAGE 0x00 #define CMD_BATTERY_VOLTAGE_GET_SHOW_BATTERY_STATUS 0x10 int hidpp20_batteryvoltage_get_battery_voltage(struct hidpp20_device *device, uint16_t *voltage) { uint8_t feature_index; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_BATTERY_VOLTAGE_GET_BATTERY_VOLTAGE, }; int rc; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_BATTERY_VOLTAGE); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; *voltage = get_unaligned_be_u16(msg.msg.parameters); return msg.msg.parameters[2]; } /* -------------------------------------------------------------------------- */ /* 0x1300: Non-RGB led support */ /* -------------------------------------------------------------------------- */ #define CMD_LED_SW_CONTROL_GET_LED_COUNT 0x00 #define CMD_LED_SW_CONTROL_GET_LED_INFO 0x10 #define CMD_LED_SW_CONTROL_GET_SW_CTRL 0x20 #define CMD_LED_SW_CONTROL_SET_SW_CTRL 0x30 #define CMD_LED_SW_CONTROL_GET_LED_STATE 0x40 #define CMD_LED_SW_CONTROL_SET_LED_STATE 0x50 #define CMD_LED_SW_CONTROL_GET_NV_CONFIG 0x60 static bool hidpp20_led_sw_control_check_state(uint16_t state) { switch (state) { case HIDPP20_LED_MODE_ON: case HIDPP20_LED_MODE_OFF: case HIDPP20_LED_MODE_BLINK: case HIDPP20_LED_MODE_TRAVEL: case HIDPP20_LED_MODE_RAMP_UP: case HIDPP20_LED_MODE_RAMP_DOWN: case HIDPP20_LED_MODE_HEARTBEAT: case HIDPP20_LED_MODE_BREATHING: return true; } return false; } int hidpp20_led_sw_control_read_leds(struct hidpp20_device* device, struct hidpp20_led_sw_ctrl_led_info** info_list) { int rc; struct hidpp20_led_sw_ctrl_led_info *i_list, *info; unsigned i; uint8_t num_infos; rc = hidpp20_led_sw_control_get_led_count(device); if (rc < 0) return rc; num_infos = rc; if(rc == 0) { *info_list = NULL; return 0; } i_list = zalloc(rc * sizeof(struct hidpp20_led_sw_ctrl_led_info)); for (i = 0; i < num_infos; i++) { info = &i_list[i]; info->index = i; rc = hidpp20_led_sw_control_get_led_info(device, i, info); if (rc != 0) goto err; hidpp_log_raw(&device->base, "non-color led %d: type: %d supports: %d\n", info->index, info->type, info->caps); } *info_list = i_list; return num_infos; err: free(i_list); return rc; } int hidpp20_led_sw_control_get_led_count(struct hidpp20_device* device) { union hidpp20_message msg = { .msg = { .report_id = REPORT_ID_LONG, .address = CMD_LED_SW_CONTROL_GET_LED_COUNT, .device_idx = device->index, }, }; uint8_t feature_idx; feature_idx = hidpp_root_get_feature_idx(device, HIDPP_PAGE_LED_SW_CONTROL); if (feature_idx == 0) { return -ENOTSUP; } msg.msg.sub_id = feature_idx; if (hidpp20_request_command(device, &msg)) { return -ENOTSUP; } return msg.msg.parameters[0]; } int hidpp20_led_sw_control_get_led_info(struct hidpp20_device* device, uint8_t led_idx, struct hidpp20_led_sw_ctrl_led_info *info) { union hidpp20_message msg = { .msg = { .report_id = REPORT_ID_LONG, .address = CMD_LED_SW_CONTROL_GET_LED_INFO, .device_idx = device->index, }, }; struct hidpp20_led_sw_ctrl_led_info *params; uint8_t feature_idx; feature_idx = hidpp_root_get_feature_idx(device, HIDPP_PAGE_LED_SW_CONTROL); if (feature_idx == 0) return -ENOTSUP; msg.msg.sub_id = feature_idx; msg.msg.parameters[0] = led_idx; if (hidpp20_request_command(device, &msg)) // Only error possible is an invalid index, which means the led doesn't exist return -ENOENT; params = (struct hidpp20_led_sw_ctrl_led_info*) msg.msg.parameters; params->caps = hidpp_be_u16_to_cpu(params->caps); *info = *params; return 0; } bool hidpp20_led_sw_control_get_sw_ctrl(struct hidpp20_device* device) { uint8_t feature_idx; int rc; union hidpp20_message msg = { .msg = { .report_id = REPORT_ID_SHORT, .address = CMD_LED_SW_CONTROL_GET_SW_CTRL, .device_idx = device->index, }, }; feature_idx = hidpp_root_get_feature_idx(device, HIDPP_PAGE_LED_SW_CONTROL); if (feature_idx == 0) return -ENOTSUP; msg.msg.sub_id = feature_idx; rc = hidpp20_request_command(device, &msg); if (rc) { return -ENOTSUP; } return msg.msg.parameters[0]; } int hidpp20_led_sw_control_set_sw_ctrl(struct hidpp20_device* device, bool ctrl) { uint8_t feature_idx; union hidpp20_message msg = { .msg = { .report_id = REPORT_ID_SHORT, .address = CMD_LED_SW_CONTROL_SET_SW_CTRL, .device_idx = device->index, }, }; feature_idx = hidpp_root_get_feature_idx(device, HIDPP_PAGE_LED_SW_CONTROL); if (feature_idx == 0) return -ENOTSUP; msg.msg.sub_id = feature_idx; msg.msg.parameters[0] = ctrl; if (hidpp20_request_command(device, &msg)) return -EINVAL; return 0; } int hidpp20_led_sw_control_get_led_state(struct hidpp20_device* device, uint8_t led_idx, struct hidpp20_led_sw_ctrl_led_state *out) { int rc; uint8_t feature_idx; union hidpp20_message msg = { .msg = { .report_id = REPORT_ID_LONG, .address = CMD_LED_SW_CONTROL_GET_LED_STATE, .device_idx = device->index, } }; struct hidpp20_led_sw_ctrl_led_state *state; feature_idx = hidpp_root_get_feature_idx(device, HIDPP_PAGE_LED_SW_CONTROL); if (feature_idx == 0) { return -ENOTSUP; } msg.msg.sub_id = feature_idx; msg.msg.parameters[0] = led_idx; rc = hidpp20_request_command(device, &msg); if (rc) return -ENOENT; state = (struct hidpp20_led_sw_ctrl_led_state*) msg.msg.parameters; // This field has to be stored in little-endian state->mode = hidpp_be_u16_to_cpu(state->mode); if (state->mode == HIDPP20_LED_MODE_BREATHING) { // Only parameters that is reported by these LEDs is brightness when breathing state->breathing.brightness = hidpp_be_u16_to_cpu(state->breathing.brightness); } *out = *state; return 0; } int hidpp20_led_sw_control_set_led_state(struct hidpp20_device* device, const struct hidpp20_led_sw_ctrl_led_state *state) { int rc; uint8_t feature_idx; union hidpp20_message msg = { .msg = { .report_id = REPORT_ID_LONG, .address = CMD_LED_SW_CONTROL_SET_LED_STATE, .device_idx = device->index, } }; feature_idx = hidpp_root_get_feature_idx(device, HIDPP_PAGE_LED_SW_CONTROL); if (feature_idx == 0) return -ENOTSUP; msg.msg.sub_id = feature_idx; if (!hidpp20_led_sw_control_check_state(state->mode)) return -EINVAL; msg.msg.parameters[0] = state->index; set_unaligned_be_u16(&msg.msg.parameters[1], state->mode); set_unaligned_be_u16(&msg.msg.parameters[3], state->blink.index); set_unaligned_be_u16(&msg.msg.parameters[5], state->blink.on_time); set_unaligned_be_u16(&msg.msg.parameters[7], state->blink.off_time); rc = hidpp20_request_command(device, &msg); if (rc) return -EINVAL; return 0; } /* -------------------------------------------------------------------------- */ /* 0x1b00: KBD reprogrammable keys and mouse buttons */ /* -------------------------------------------------------------------------- */ #define CMD_KBD_REPROGRAMMABLE_KEYS_GET_COUNT 0x00 #define CMD_KBD_REPROGRAMMABLE_KEYS_GET_CTRL_ID_INFO 0x10 static int hidpp20_kbd_reprogrammable_keys_get_count(struct hidpp20_device *device, uint8_t reg) { union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_KBD_REPROGRAMMABLE_KEYS_GET_COUNT, }; int rc; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return msg.msg.parameters[0]; } static int hidpp20_kbd_reprogrammable_keys_get_info(struct hidpp20_device *device, uint8_t reg, struct hidpp20_control_id *control) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_KBD_REPROGRAMMABLE_KEYS_GET_CTRL_ID_INFO, .msg.parameters[0] = control->index, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; control->control_id = get_unaligned_be_u16(&msg.msg.parameters[0]); control->task_id = get_unaligned_be_u16(&msg.msg.parameters[2]); control->flags = msg.msg.parameters[4]; return 0; } int hidpp20_kbd_reprogrammable_keys_get_controls(struct hidpp20_device *device, struct hidpp20_control_id **controls_list) { uint8_t feature_index; struct hidpp20_control_id *c_list, *control; uint8_t num_controls; unsigned i; int rc; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_KBD_REPROGRAMMABLE_KEYS); if (feature_index == 0) return -ENOTSUP; rc = hidpp20_kbd_reprogrammable_keys_get_count(device, feature_index); if (rc < 0) return rc; num_controls = rc; if (num_controls == 0) { *controls_list = NULL; return 0; } c_list = zalloc(num_controls * sizeof(struct hidpp20_control_id)); for (i = 0; i < num_controls; i++) { control = &c_list[i]; control->index = i; rc = hidpp20_kbd_reprogrammable_keys_get_info(device, feature_index, control); if (rc) goto err; /* 0x1b00 and 0x1b04 have the same control/task id mappings. * I hope */ hidpp_log_raw(&device->base, "control %d: cid: '%s' (%d) tid: '%s' (%d) flags: 0x%02x\n", control->index, hidpp20_1b04_get_logical_mapping_name(control->control_id), control->control_id, hidpp20_1b04_get_physical_mapping_name(control->task_id), control->task_id, control->flags); } *controls_list = c_list; return num_controls; err: free(c_list); return rc; } /* -------------------------------------------------------------------------- */ /* 0x8070 - Color LED effects */ /* -------------------------------------------------------------------------- */ #define CMD_COLOR_LED_EFFECTS_GET_INFO 0x00 #define CMD_COLOR_LED_EFFECTS_GET_ZONE_INFO 0x10 #define CMD_COLOR_LED_EFFECTS_GET_ZONE_EFFECT_INFO 0x20 #define CMD_COLOR_LED_EFFECTS_SET_ZONE_EFFECT 0x30 #define CMD_COLOR_LED_EFFECTS_GET_ZONE_EFFECT 0xe0 int hidpp20_color_led_effects_get_info(struct hidpp20_device *device, struct hidpp20_color_led_info *info) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_COLOR_LED_EFFECTS_GET_INFO, }; uint8_t feature_index; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_COLOR_LED_EFFECTS); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; *info = *(struct hidpp20_color_led_info *)msg.msg.parameters; device->led_ext_caps = info->ext_caps; return 0; } int hidpp20_color_led_effects_get_zone_info(struct hidpp20_device *device, uint8_t reg, struct hidpp20_color_led_zone_info *info) { int rc; struct hidpp20_color_led_zone_info *msg_info; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_COLOR_LED_EFFECTS_GET_ZONE_INFO, .msg.parameters[0] = info->index, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; msg_info = (struct hidpp20_color_led_zone_info *)msg.msg.parameters; info->location = hidpp_be_u16_to_cpu(msg_info->location); info->num_effects = msg_info->num_effects; info->persistency_caps = msg_info->persistency_caps; return 0; } int hidpp20_color_led_effects_get_zone_infos(struct hidpp20_device *device, struct hidpp20_color_led_zone_info **infos_list) { uint8_t feature_index; struct hidpp20_color_led_zone_info *i_list, *info; struct hidpp20_color_led_info ledinfo = {0}; uint8_t num_infos; unsigned i; int rc; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_COLOR_LED_EFFECTS); if (feature_index == 0) return -ENOTSUP; rc = hidpp20_color_led_effects_get_info(device, &ledinfo); if (rc < 0) return rc; num_infos = ledinfo.zone_count; if (num_infos == 0) { *infos_list = NULL; return 0; } i_list = zalloc(num_infos * sizeof(struct hidpp20_color_led_zone_info)); for (i = 0; i < num_infos; i++) { info = &i_list[i]; info->index = i; rc = hidpp20_color_led_effects_get_zone_info(device, feature_index, info); if (rc) goto err; hidpp_log_raw(&device->base, "led_info %d: location: %d type %s num_effects: %d persistency_caps: 0x%02x\n", info->index, info->location, hidpp20_led_get_location_mapping_name(info->location), info->num_effects, info->persistency_caps); } *infos_list = i_list; return num_infos; err: free(i_list); return rc; } int hidpp20_color_led_effect_get_zone_effect_info(struct hidpp20_device *device, uint8_t zone_index, uint8_t zone_effect_index, struct hidpp20_color_led_zone_effect_info *info) { uint8_t feature_index; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.address = CMD_COLOR_LED_EFFECTS_GET_ZONE_EFFECT_INFO, .msg.device_idx = device->index, .msg.parameters[0] = zone_index, .msg.parameters[1] = zone_effect_index, }; int rc; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_COLOR_LED_EFFECTS); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; info->zone_index = msg.msg.parameters[0]; info->zone_effect_index = msg.msg.parameters[1]; info->effect_id = get_unaligned_be_u16(&msg.msg.parameters[2]); info->effect_caps = get_unaligned_be_u16(&msg.msg.parameters[4]); info->effect_period = get_unaligned_be_u16(&msg.msg.parameters[6]); return 0; } int hidpp20_color_led_effects_set_zone_effect(struct hidpp20_device *device, uint8_t zone_index, struct hidpp20_led led) { uint8_t feature_index; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.address = CMD_COLOR_LED_EFFECTS_SET_ZONE_EFFECT, .msg.device_idx = device->index, .msg.parameters[0] = zone_index, .msg.parameters[12] = 1, /* write to RAM and flash */ }; int rc; struct hidpp20_internal_led *internal_led = (struct hidpp20_internal_led*) &msg.msg.parameters[1]; hidpp20_onboard_profiles_write_led(internal_led, &led); feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_COLOR_LED_EFFECTS); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return 0; } int hidpp20_color_led_effects_get_zone_effect(struct hidpp20_device *device, uint8_t zone_index, struct hidpp20_led *led) { uint8_t feature_index; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.address = CMD_COLOR_LED_EFFECTS_GET_ZONE_EFFECT, .msg.device_idx = device->index, .msg.parameters[0] = zone_index, }; struct hidpp20_internal_led *internal_led; int rc; /* hidpp20_color_led_effects_get_info() must be called first to set the capabilities */ if (!(device->led_ext_caps & HIDPP20_COLOR_LED_INFO_EXT_CAP_HAS_ZONE_EFFECT)) return -ENOTSUP; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_COLOR_LED_EFFECTS); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; if (msg.msg.parameters[0] != zone_index) return -EPROTO; internal_led = (struct hidpp20_internal_led*) &msg.msg.parameters[1]; hidpp20_onboard_profiles_read_led(led, *internal_led); hidpp_log_debug(&device->base, "zone %u has effect %u\n", zone_index, led->mode); return 0; } /* -------------------------------------------------------------------------- */ /* 0x8071: RGB Effects */ /* -------------------------------------------------------------------------- */ #define CMD_RGB_EFFECTS_GET_INFO 0x00 #define CMD_RGB_EFFECTS_SET_RGB_CLUSTER_EFFECT 0x10 #define CMD_RGB_EFFECTS_SET_MULTI_LED_RGB_CLUSTER_PATTERN 0x20 #define CMD_RGB_EFFECTS_MANAGE_NV_CONFIG 0x30 #define CMD_RGB_EFFECTS_MANAGE_RGB_LED_BIN_INFO 0x40 #define CMD_RGB_EFFECTS_MANAGE_SW_CONTROL 0x50 #define CMD_RGB_EFFECTS_SET_EFFECT_SYNC_CORRECTION 0x60 #define CMD_RGB_EFFECTS_MANAGE_RGB_POWER_MODE_CONFIG 0x70 #define CMD_RGB_EFFECTS_MANAGE_RGB_POWER_MODE 0x80 int hidpp20_rgb_effects_get_device_info(struct hidpp20_device *device, struct hidpp20_rgb_device_info *info) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_RGB_EFFECTS_GET_INFO, .msg.parameters[0] = HIDPP20_RGB_EFFECTS_INDEX_ALL, .msg.parameters[1] = HIDPP20_RGB_EFFECTS_INDEX_ALL, .msg.parameters[2] = HIDPP20_RGB_EFFECTS_TOI_GENERAL, }; uint8_t feature_index; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_RGB_EFFECTS); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; info->cluster_index = msg.msg.parameters[0]; info->effect_index = msg.msg.parameters[1]; info->cluster_count = msg.msg.parameters[2]; info->nv_caps = get_unaligned_be_u16(&msg.msg.parameters[3]); info->ext_caps = get_unaligned_be_u16(&msg.msg.parameters[4]); return 0; } int hidpp20_rgb_effects_get_cluster_info(struct hidpp20_device *device, uint8_t cluster_index, struct hidpp20_rgb_cluster_info *info) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_RGB_EFFECTS_GET_INFO, .msg.parameters[0] = cluster_index, .msg.parameters[1] = HIDPP20_RGB_EFFECTS_INDEX_ALL, .msg.parameters[2] = HIDPP20_RGB_EFFECTS_TOI_GENERAL, }; uint8_t feature_index; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_RGB_EFFECTS); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; info->index = msg.msg.parameters[0]; info->effect_index = msg.msg.parameters[1]; info->location = get_unaligned_be_u16(&msg.msg.parameters[2]); info->num_effects = msg.msg.parameters[4]; info->persistency_caps = msg.msg.parameters[5]; return 0; } int hidpp20_rgb_effects_get_cluster_infos(struct hidpp20_device *device, struct hidpp20_rgb_cluster_info **infos_list) { struct hidpp20_rgb_cluster_info *i_list, *info; struct hidpp20_rgb_device_info device_info = {0}; uint8_t num_infos; unsigned i; int rc; rc = hidpp20_rgb_effects_get_device_info(device, &device_info); if (rc < 0) return rc; num_infos = device_info.cluster_count; if (num_infos == 0) { *infos_list = NULL; return 0; } i_list = zalloc(num_infos * sizeof(struct hidpp20_rgb_cluster_info)); for (i = 0; i < num_infos; i++) { info = &i_list[i]; info->index = i; rc = hidpp20_rgb_effects_get_cluster_info(device, i, info); if (rc) goto err; hidpp_log_raw(&device->base, "cluster_info %d: location: %d type %s num_effects: %d persistency_caps: 0x%02x\n", info->index, info->location, hidpp20_led_get_location_mapping_name(info->location), info->num_effects, info->persistency_caps); } *infos_list = i_list; return num_infos; err: free(i_list); return rc; } int hidpp20_rgb_effects_get_effect_info(struct hidpp20_device *device, uint8_t cluster_index, uint8_t effect_index, struct hidpp20_rgb_effect_info *info) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_RGB_EFFECTS_GET_INFO, .msg.parameters[0] = cluster_index, .msg.parameters[1] = effect_index, .msg.parameters[2] = HIDPP20_RGB_EFFECTS_TOI_GENERAL, }; uint8_t feature_index; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_RGB_EFFECTS); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; info->cluster_index = msg.msg.parameters[0]; info->effect_index = msg.msg.parameters[1]; info->effect_id = get_unaligned_be_u16(&msg.msg.parameters[2]); info->capabilities = get_unaligned_be_u16(&msg.msg.parameters[4]); info->effect_period = get_unaligned_be_u16(&msg.msg.parameters[6]); return 0; } /* -------------------------------------------------------------------------- */ /* 0x1b04: Special keys and mouse buttons */ /* -------------------------------------------------------------------------- */ #define CMD_SPECIAL_KEYS_BUTTONS_GET_COUNT 0x00 #define CMD_SPECIAL_KEYS_BUTTONS_GET_INFO 0x10 #define CMD_SPECIAL_KEYS_BUTTONS_GET_REPORTING 0x20 #define CMD_SPECIAL_KEYS_BUTTONS_SET_REPORTING 0x30 static int hidpp20_special_keys_buttons_get_count(struct hidpp20_device *device, uint8_t reg) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_SPECIAL_KEYS_BUTTONS_GET_COUNT, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return msg.msg.parameters[0]; } static int hidpp20_special_keys_buttons_get_info(struct hidpp20_device *device, uint8_t reg, struct hidpp20_control_id *control) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_SPECIAL_KEYS_BUTTONS_GET_INFO, .msg.parameters[0] = control->index, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; control->control_id = get_unaligned_be_u16(&msg.msg.parameters[0]); control->task_id = get_unaligned_be_u16(&msg.msg.parameters[2]); control->flags = msg.msg.parameters[4]; control->position = msg.msg.parameters[5]; control->group = msg.msg.parameters[6]; control->group_mask = msg.msg.parameters[7]; control->raw_XY = msg.msg.parameters[8] & 0x01; return 0; } static int hidpp20_special_keys_buttons_get_reporting(struct hidpp20_device *device, uint8_t reg, struct hidpp20_control_id *control) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_SPECIAL_KEYS_BUTTONS_GET_REPORTING, }; set_unaligned_be_u16(&msg.msg.parameters[0], control->control_id); rc = hidpp20_request_command(device, &msg); if (rc) return rc; control->reporting.remapped = get_unaligned_be_u16(&msg.msg.parameters[3]); control->reporting.raw_XY = !!(msg.msg.parameters[2] & 0x10); control->reporting.persist = !!(msg.msg.parameters[2] & 0x04); control->reporting.divert = !!(msg.msg.parameters[2] & 0x01); return 0; } int hidpp20_special_key_mouse_get_controls(struct hidpp20_device *device, struct hidpp20_control_id **controls_list) { uint8_t feature_index; struct hidpp20_control_id *c_list, *control; uint8_t num_controls, real_num_controls = 0; unsigned i; int rc; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_SPECIAL_KEYS_BUTTONS); if (feature_index == 0) return -ENOTSUP; rc = hidpp20_special_keys_buttons_get_count(device, feature_index); if (rc < 0) return rc; num_controls = rc; if (num_controls == 0) { *controls_list = NULL; return 0; } hidpp_log_debug(&device->base, "device has %d buttons\n", num_controls); c_list = zalloc(num_controls * sizeof(struct hidpp20_control_id)); for (i = 0; i < num_controls; i++) { control = &c_list[real_num_controls]; control->index = i; rc = hidpp20_special_keys_buttons_get_info(device, feature_index, control); if (rc) { hidpp_log_error(&device->base, "error getting button info for control %d, ignoring\n", i); continue; } rc = hidpp20_special_keys_buttons_get_reporting(device, feature_index, control); if (rc) { hidpp_log_error(&device->base, "error getting button reporting for control %d, ignoring\n", i); continue; } hidpp_log_raw(&device->base, "control %d: cid: '%s' (%d) tid: '%s' (%d) flags: 0x%02x pos: %d group: %d gmask: 0x%02x raw_XY: %s\n" " reporting: raw_xy: %s persist: %s divert: %s remapped: '%s' (%d)\n", control->index, hidpp20_1b04_get_logical_mapping_name(control->control_id), control->control_id, hidpp20_1b04_get_physical_mapping_name(control->task_id), control->task_id, control->flags, control->position, control->group, control->group_mask, control->raw_XY ? "yes" : "no", control->reporting.raw_XY ? "yes" : "no", control->reporting.persist ? "yes" : "no", control->reporting.divert ? "yes" : "no", hidpp20_1b04_get_logical_mapping_name(control->reporting.remapped), control->reporting.remapped); real_num_controls++; } *controls_list = realloc(c_list, real_num_controls * sizeof(struct hidpp20_control_id)); return real_num_controls; } int hidpp20_special_key_mouse_set_control(struct hidpp20_device *device, struct hidpp20_control_id *control) { uint8_t feature_index; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_SPECIAL_KEYS_BUTTONS_SET_REPORTING, }; set_unaligned_be_u16(&msg.msg.parameters[0], control->control_id); set_unaligned_be_u16(&msg.msg.parameters[3], control->reporting.remapped); feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_SPECIAL_KEYS_BUTTONS); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; msg.msg.parameters[2] |= 0x02; if (control->reporting.divert) msg.msg.parameters[2] |= 0x01; msg.msg.parameters[2] |= 0x08; if (control->reporting.persist) msg.msg.parameters[2] |= 0x04; msg.msg.parameters[2] |= 0x20; if (control->reporting.raw_XY) msg.msg.parameters[2] |= 0x10; return hidpp20_request_command(device, &msg); } /* -------------------------------------------------------------------------- */ /* 0x2200: Mouse Pointer Basic Optical Sensors */ /* -------------------------------------------------------------------------- */ #define CMD_MOUSE_POINTER_BASIC_GET_INFO 0x00 int hidpp20_mousepointer_get_mousepointer_info(struct hidpp20_device *device, uint16_t *resolution, uint8_t *flags) { uint8_t feature_index; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_MOUSE_POINTER_BASIC_GET_INFO, }; int rc; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_MOUSE_POINTER_BASIC); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; *resolution = get_unaligned_be_u16(msg.msg.parameters); *flags = msg.msg.parameters[2]; return 0; } /* -------------------------------------------------------------------------- */ /* 0x2201: Adjustable DPI */ /* -------------------------------------------------------------------------- */ #define CMD_ADJUSTABLE_DPI_GET_SENSOR_COUNT 0x00 #define CMD_ADJUSTABLE_DPI_GET_SENSOR_DPI_LIST 0x10 #define CMD_ADJUSTABLE_DPI_GET_SENSOR_DPI 0x20 #define CMD_ADJUSTABLE_DPI_SET_SENSOR_DPI 0x30 static int hidpp20_adjustable_dpi_get_count(struct hidpp20_device *device, uint8_t reg) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_ADJUSTABLE_DPI_GET_SENSOR_COUNT, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return msg.msg.parameters[0]; } static int hidpp20_adjustable_dpi_get_dpi_list(struct hidpp20_device *device, uint8_t reg, struct hidpp20_sensor *sensor) { int rc; unsigned i = 1, dpi_index = 0; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_ADJUSTABLE_DPI_GET_SENSOR_DPI_LIST, .msg.parameters[0] = sensor->index, }; if (device->quirk == HIDPP20_QUIRK_G602) { msg.msg.parameters[0] = 1; i = 0; } rc = hidpp20_request_command(device, &msg); if (rc) return rc; sensor->dpi_min = 0xffff; sensor->index = msg.msg.parameters[0]; while (i < LONG_MESSAGE_LENGTH - 4U && get_unaligned_be_u16(&msg.msg.parameters[i]) != 0) { uint16_t value = get_unaligned_be_u16(&msg.msg.parameters[i]); if (device->quirk == HIDPP20_QUIRK_G602 && i == 2) value += 0xe000; if (value > 0xe000) { sensor->dpi_steps = value - 0xe000; } else { sensor->dpi_min = min(value, sensor->dpi_min); sensor->dpi_max = max(value, sensor->dpi_max); sensor->dpi_list[dpi_index++] = value; } assert(sensor->dpi_list[dpi_index] == 0x0000); i += 2; } return 0; } static int hidpp20_adjustable_dpi_get_dpi(struct hidpp20_device *device, uint8_t reg, struct hidpp20_sensor *sensor) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_ADJUSTABLE_DPI_GET_SENSOR_DPI, .msg.parameters[0] = sensor->index, }; if (device->quirk == HIDPP20_QUIRK_G602) msg.msg.parameters[0] = 1; rc = hidpp20_request_command(device, &msg); if (rc) return rc; sensor->dpi = get_unaligned_be_u16(&msg.msg.parameters[1]); sensor->default_dpi = get_unaligned_be_u16(&msg.msg.parameters[3]); return 0; } int hidpp20_adjustable_dpi_get_sensors(struct hidpp20_device *device, struct hidpp20_sensor **sensors_list) { uint8_t feature_index; struct hidpp20_sensor *s_list, *sensor; uint8_t num_sensors; unsigned i; int rc; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ADJUSTABLE_DPI); if (feature_index == 0) return -ENOTSUP; rc = hidpp20_adjustable_dpi_get_count(device, feature_index); if (rc < 0) return rc; num_sensors = rc; if (num_sensors == 0) { *sensors_list = NULL; return 0; } s_list = zalloc(num_sensors * sizeof(struct hidpp20_sensor)); for (i = 0; i < num_sensors; i++) { sensor = &s_list[i]; sensor->index = i; rc = hidpp20_adjustable_dpi_get_dpi_list(device, feature_index, sensor); if (rc) goto err; rc = hidpp20_adjustable_dpi_get_dpi(device, feature_index, sensor); if (rc) goto err; hidpp_log_raw(&device->base, "sensor %d: current dpi: %d (default: %d) min: %d max: %d steps: %d\n", sensor->index, sensor->dpi, sensor->default_dpi, sensor->dpi_min, sensor->dpi_max, sensor->dpi_steps); } *sensors_list = s_list; return num_sensors; err: free(s_list); return rc > 0 ? -EPROTO : rc; } int hidpp20_adjustable_dpi_set_sensor_dpi(struct hidpp20_device *device, struct hidpp20_sensor *sensor, uint16_t dpi) { uint8_t feature_index; uint16_t returned_parameters; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_ADJUSTABLE_DPI_SET_SENSOR_DPI, .msg.parameters[0] = sensor->index, }; if (device->quirk == HIDPP20_QUIRK_G602) msg.msg.parameters[0] = 1; set_unaligned_be_u16(&msg.msg.parameters[1], dpi); feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ADJUSTABLE_DPI); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; returned_parameters = get_unaligned_be_u16(&msg.msg.parameters[1]); /* version 0 of the protocol does not echo the parameters */ if (returned_parameters != dpi && returned_parameters) return -EIO; return 0; } /* -------------------------------------------------------------------------- */ /* 0x8060 - Adjustable Report Rate */ /* -------------------------------------------------------------------------- */ #define CMD_ADJUSTABLE_REPORT_RATE_GET_REPORT_RATE_LIST 0x00 #define CMD_ADJUSTABLE_REPORT_RATE_GET_REPORT_RATE 0x10 #define CMD_ADJUSTABLE_REPORT_RATE_SET_REPORT_RATE 0x20 int hidpp20_adjustable_report_rate_get_report_rate_list(struct hidpp20_device *device, uint8_t *bitflags_ms) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_ADJUSTABLE_REPORT_RATE_GET_REPORT_RATE_LIST, .msg.parameters[0] = 0, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ADJUSTABLE_REPORT_RATE); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; *bitflags_ms = msg.msg.parameters[0]; return 0; } int hidpp20_adjustable_report_rate_get_report_rate(struct hidpp20_device *device, uint8_t *rate_ms) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ADJUSTABLE_REPORT_RATE_GET_REPORT_RATE, .msg.parameters[0] = 0, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ADJUSTABLE_REPORT_RATE); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; *rate_ms = msg.msg.parameters[0]; return 0; } int hidpp20_adjustable_report_rate_set_report_rate(struct hidpp20_device *device, uint8_t rate_ms) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ADJUSTABLE_REPORT_RATE_SET_REPORT_RATE, .msg.parameters[0] = rate_ms, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ADJUSTABLE_REPORT_RATE); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return 0; } /* -------------------------------------------------------------------------- */ /* 0x8100 - Onboard Profiles */ /* -------------------------------------------------------------------------- */ #define CMD_ONBOARD_PROFILES_GET_PROFILES_DESCR 0x00 #define CMD_ONBOARD_PROFILES_SET_ONBOARD_MODE 0x10 #define CMD_ONBOARD_PROFILES_GET_ONBOARD_MODE 0x20 #define CMD_ONBOARD_PROFILES_SET_CURRENT_PROFILE 0x30 #define CMD_ONBOARD_PROFILES_GET_CURRENT_PROFILE 0x40 #define CMD_ONBOARD_PROFILES_MEMORY_READ 0x50 #define CMD_ONBOARD_PROFILES_MEMORY_ADDR_WRITE 0x60 #define CMD_ONBOARD_PROFILES_MEMORY_WRITE 0x70 #define CMD_ONBOARD_PROFILES_MEMORY_WRITE_END 0x80 #define CMD_ONBOARD_PROFILES_GET_CURRENT_DPI_INDEX 0xb0 #define CMD_ONBOARD_PROFILES_SET_CURRENT_DPI_INDEX 0xc0 #define HIDPP20_PROFILE_SIZE 256 #define HIDPP20_BUTTON_HID 0x80 #define HIDPP20_MODE_NO_CHANGE 0x00 #define HIDPP20_ONBOARD_MODE 0x01 #define HIDPP20_HOST_MODE 0x02 #define HIDPP20_ONBOARD_PROFILES_MEMORY_TYPE_G402 0x01 #define HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G402 0x01 #define HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G303 0x02 #define HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G900 0x03 #define HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G915 0x04 #define HIDPP20_ONBOARD_PROFILES_MACRO_TYPE_G402 0x01 #define HIDPP20_USER_PROFILES_G402 0x0000 #define HIDPP20_ROM_PROFILES_G402 0x0100 #define HIDPP20_PROFILE_DIR_END 0xFFFF #define HIDPP20_PROFILE_DIR_ENABLED 2 union hidpp20_internal_profile { uint8_t data[HIDPP20_PROFILE_SIZE]; struct { uint8_t report_rate; uint8_t default_dpi; uint8_t switched_dpi; uint16_t dpi[5]; struct hidpp20_color profile_color; uint8_t power_mode; uint8_t angle_snapping; uint8_t reserved[10]; uint16_t powersave_timeout; uint16_t poweroff_timeout; union hidpp20_button_binding buttons[16]; union hidpp20_button_binding alternate_buttons[16]; union { char txt[16 * 3]; uint8_t raw[16 * 3]; } name; struct hidpp20_internal_led leds[2]; /* G303, g502, g900 only */ struct hidpp20_internal_led alt_leds[2]; uint8_t free[2]; uint16_t crc; } __attribute__((packed)) profile; }; _Static_assert(sizeof(union hidpp20_internal_profile) == HIDPP20_PROFILE_SIZE, "Invalid size"); int hidpp20_onboard_profiles_get_profiles_desc(struct hidpp20_device *device, struct hidpp20_onboard_profiles_info *info) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_GET_PROFILES_DESCR, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; *info = *(struct hidpp20_onboard_profiles_info *)msg.msg.parameters; info->sector_size = hidpp_be_u16_to_cpu(info->sector_size); return 0; } int hidpp20_onboard_profiles_read_sector(struct hidpp20_device *device, uint16_t sector, uint16_t sector_size, uint8_t *data) { uint16_t offset; uint8_t feature_index; int rc, count; union hidpp20_message buf; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_MEMORY_READ, }; hidpp_log_debug(&device->base, "Reading sector 0x%04x\n", sector); feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; set_unaligned_be_u16(&msg.msg.parameters[0], sector); count = sector_size; for (offset = 0; offset < sector_size; offset += 16) { /* * the firmware replies with an ERR_INVALID_ARGUMENT error * if we try to read past sector_size - 16, so when we are left with * less than 16 bytes to read we need to read from sector_size - 16 */ offset = (sector_size - offset < 16) ? sector_size - 16 : offset; set_unaligned_be_u16(&msg.msg.parameters[2], offset); buf = msg; rc = hidpp20_request_command(device, &buf); if (rc) return rc; /* msg.msg.parameters is guaranteed to have a size >= 16 */ memcpy(data + offset, buf.msg.parameters, 16); /* * no need to check for count >= 0: * if count is negative, then offset will be greater than * sector_size, thus stopping the loop. */ count -= 16; } return 0; } static bool hidpp20_onboard_profiles_is_sector_valid(struct hidpp20_device *device, uint16_t sector_size, uint8_t *data) { uint16_t crc, read_crc; crc = hidpp_crc_ccitt(data, sector_size - 2); read_crc = get_unaligned_be_u16(&data[sector_size - 2]); if (crc != read_crc) hidpp_log_debug(&device->base, "Invalid CRC (%04x != %04x)\n", read_crc, crc); return crc == read_crc; } static int hidpp20_onboard_profiles_write_start(struct hidpp20_device *device, uint16_t sector, uint16_t sub_address, uint16_t count, uint8_t feature_index) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = feature_index, .msg.address = CMD_ONBOARD_PROFILES_MEMORY_ADDR_WRITE, }; set_unaligned_be_u16(&msg.msg.parameters[0], sector); set_unaligned_be_u16(&msg.msg.parameters[2], sub_address); set_unaligned_be_u16(&msg.msg.parameters[4], count); rc = hidpp20_request_command(device, &msg); if (rc) return rc; return 0; } static int hidpp20_onboard_profiles_write_end(struct hidpp20_device *device, uint8_t feature_index) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.sub_id = feature_index, .msg.address = CMD_ONBOARD_PROFILES_MEMORY_WRITE_END, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return 0; } static int hidpp20_onboard_profiles_write_data(struct hidpp20_device *device, uint8_t *data, uint8_t feature_index) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = feature_index, .msg.address = CMD_ONBOARD_PROFILES_MEMORY_WRITE, }; memcpy(msg.msg.parameters, data, 16); rc = hidpp20_request_command(device, &msg); if (rc) return rc; return 0; } int hidpp20_onboard_profiles_write_sector(struct hidpp20_device *device, uint16_t sector, uint16_t sector_size, uint8_t *data, bool write_crc) { uint8_t feature_index; uint16_t crc; int rc, transferred; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; if (write_crc) { crc = hidpp_crc_ccitt(data, sector_size - 2); set_unaligned_be_u16(&data[sector_size - 2], crc); } rc = hidpp20_onboard_profiles_write_start(device, sector, 0, sector_size, feature_index); if (rc) return rc; for (transferred = 0; transferred < sector_size; transferred += 16) { rc = hidpp20_onboard_profiles_write_data(device, data, feature_index); if (rc) return rc; data += 16; } rc = hidpp20_onboard_profiles_write_end(device, feature_index); if (rc) return rc; return 0; } static int hidpp20_onboard_profiles_get_onboard_mode(struct hidpp20_device *device) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_GET_ONBOARD_MODE, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return msg.msg.parameters[0]; } static int hidpp20_onboard_profiles_set_onboard_mode(struct hidpp20_device *device, uint8_t onboard_mode) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_SET_ONBOARD_MODE, .msg.parameters[1] = onboard_mode, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return 0; } int hidpp20_onboard_profiles_get_current_profile(struct hidpp20_device *device) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_GET_CURRENT_PROFILE, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return msg.msg.parameters[1]; } int hidpp20_onboard_profiles_set_current_dpi_index(struct hidpp20_device *device, uint8_t index) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_SET_CURRENT_DPI_INDEX, .msg.parameters[0] = index, }; if (index > 4) return -EINVAL; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return 0; } int hidpp20_onboard_profiles_get_current_dpi_index(struct hidpp20_device *device) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_GET_CURRENT_DPI_INDEX, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return msg.msg.parameters[0]; } int hidpp20_onboard_profiles_set_current_profile(struct hidpp20_device *device, uint8_t index) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_SET_CURRENT_PROFILE, .msg.parameters[1] = index + 1, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return 0; } static bool hidpp20_onboard_profiles_validate(struct hidpp20_device *device, struct hidpp20_onboard_profiles_info *info) { if (info->memory_model_id != HIDPP20_ONBOARD_PROFILES_MEMORY_TYPE_G402) { hidpp_log_error(&device->base, "Memory layout not supported: 0x%02x.\n", info->memory_model_id); return false; } if ((info->profile_format_id != HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G402) && (info->profile_format_id != HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G303) && (info->profile_format_id != HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G900) && (info->profile_format_id != HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G915)) { hidpp_log_error(&device->base, "Profile layout not supported: 0x%02x.\n", info->profile_format_id); return false; } if (info->macro_format_id != HIDPP20_ONBOARD_PROFILES_MACRO_TYPE_G402) { hidpp_log_error(&device->base, "Macro format not supported: 0x%02x.\n", info->macro_format_id); return false; } return true; } int hidpp20_onboard_profiles_allocate(struct hidpp20_device *device, struct hidpp20_profiles **profiles_list) { struct hidpp20_onboard_profiles_info info = { 0 }; struct hidpp20_profiles *profiles; int onboard_mode; int rc; rc = hidpp20_onboard_profiles_get_profiles_desc(device, &info); if (rc) return rc; if (!hidpp20_onboard_profiles_validate(device, &info)) return -ENOTSUP; rc = hidpp20_onboard_profiles_get_onboard_mode(device); if (rc < 0) return rc; onboard_mode = rc; if (onboard_mode != HIDPP20_ONBOARD_MODE) { hidpp_log_raw(&device->base, "not on the correct mode: %d.\n", onboard_mode); rc = hidpp20_onboard_profiles_set_onboard_mode(device, HIDPP20_ONBOARD_MODE); if (rc < 0) return rc; } profiles = zalloc(sizeof(struct hidpp20_profiles)); profiles->profiles = zalloc(info.profile_count * sizeof(struct hidpp20_profile)); profiles->sector_size = info.sector_size; profiles->sector_count = info.sector_count; profiles->num_profiles = info.profile_count; profiles->num_rom_profiles = info.profile_count_oob; profiles->num_buttons = min(info.button_count, 16); profiles->num_modes = HIDPP20_DPI_COUNT; profiles->num_leds = HIDPP20_LED_COUNT; profiles->has_g_shift = (info.mechanical_layout & 0x03) == 0x02; profiles->has_dpi_shift = ((info.mechanical_layout & 0x0c) >> 2) == 0x02; switch(info.various_info & 0x07) { case 1: profiles->corded = 1; break; case 2: profiles->wireless = 1; break; case 4: profiles->corded = 1; profiles->wireless = 1; break; } *profiles_list = profiles; return 0; } static int hidpp20_onboard_profiles_macro_next(struct hidpp20_device *device, uint8_t memory[32], uint16_t *index, union hidpp20_macro_data *macro) { int rc = 0; unsigned int step = 1; if (*index >= 32 - sizeof(union hidpp20_macro_data)) { hidpp_log_error(&device->base, "error while parsing macro.\n"); return -EFAULT; } memcpy(macro, &memory[*index], sizeof(union hidpp20_macro_data)); switch (macro->any.type) { case HIDPP20_MACRO_DELAY: /* fallthrough */ case HIDPP20_MACRO_KEY_PRESS: /* fallthrough */ case HIDPP20_MACRO_KEY_RELEASE: /* fallthrough */ case HIDPP20_MACRO_JUMP: step = 3; rc = -EAGAIN; break; case HIDPP20_MACRO_NOOP: step = 1; rc = -EAGAIN; break; case HIDPP20_MACRO_END: return 0; default: hidpp_log_error(&device->base, "unknown tag: 0x%02x\n", macro->any.type); rc = -EFAULT; } if ((*index + step) & 0xF0) /* the next item will be on the following chunk */ return -ENOMEM; *index += step; return rc; } static int hidpp20_onboard_profiles_read_macro(struct hidpp20_device *device, struct hidpp20_profiles *profiles, uint8_t page, uint8_t offset, union hidpp20_macro_data **return_macro) { _cleanup_free_ uint8_t *memory = NULL; union hidpp20_macro_data *macro = NULL; unsigned count = 0; unsigned index = 0; uint16_t mem_index = offset; int rc = -ENOMEM; memory = hidpp20_onboard_profiles_allocate_sector(profiles); do { if (count == index) { union hidpp20_macro_data *tmp; count += 32; /* manual realloc to have the memory zero-initialized */ tmp = zalloc(count * sizeof(union hidpp20_macro_data)); if (macro) { memcpy(tmp, macro, (count - 32) * sizeof(union hidpp20_macro_data)); free(macro); } macro = tmp; } if (rc == -ENOMEM) { rc = hidpp20_onboard_profiles_read_sector(device, page, profiles->sector_size, memory); if (rc) goto out_err; } rc = hidpp20_onboard_profiles_macro_next(device, memory, &mem_index, ¯o[index]); if (rc == -EFAULT) goto out_err; if (rc == -ENOMEM) { mem_index = 0; page++; } else if (macro[index].any.type == HIDPP20_MACRO_JUMP) { page = macro[index].jump.page; offset = macro[index].jump.offset; mem_index = offset; /* no need to store the jump in memory */ index--; /* force memory fetching */ rc = -ENOMEM; } else { index++; } } while (rc); *return_macro = macro; return index; out_err: free(macro); return rc; } static int hidpp20_onboard_profiles_parse_macro(struct hidpp20_device *device, struct hidpp20_profiles *profiles, uint8_t page, uint8_t offset, union hidpp20_macro_data **return_macro) { union hidpp20_macro_data *m, *macro = NULL; unsigned i, count = 0; int rc; rc = hidpp20_onboard_profiles_read_macro(device, profiles, page, offset, ¯o); if (rc <= 0) return rc; count = rc; for (i = 0; i < count; i++) { m = ¯o[i]; assert(m != NULL); switch (m->any.type) { case HIDPP20_MACRO_DELAY: m->delay.time = hidpp_be_u16_to_cpu(m->delay.time); break; case HIDPP20_MACRO_KEY_PRESS: break; case HIDPP20_MACRO_KEY_RELEASE: break; case HIDPP20_MACRO_JUMP: break; case HIDPP20_MACRO_END: break; case HIDPP20_MACRO_NOOP: break; default: hidpp_log_error(&device->base, "unknown tag: 0x%02x\n", m->any.type); } } *return_macro = macro; return 0; } static unsigned int hidpp20_onboard_profiles_compute_dict_size(const struct hidpp20_device *device, const struct hidpp20_profiles *profiles) { unsigned p, num_offset; num_offset = 0; p = profiles->num_profiles; while (p) { p >>= 2; num_offset += 16; } return num_offset; } void hidpp20_onboard_profiles_destroy(struct hidpp20_profiles *profiles_list) { struct hidpp20_profile *profile; union hidpp20_macro_data **macro; unsigned i; if (!profiles_list) return; for (i = 0; i < profiles_list->num_profiles; i++) { profile = &profiles_list->profiles[i]; ARRAY_FOR_EACH(profile->macros, macro) { free(*macro); } } free(profiles_list->profiles); free(profiles_list); } static int hidpp20_onboard_profiles_write_dict(struct hidpp20_device *device, struct hidpp20_profiles *profiles_list) { unsigned int i, buffer_index = 0; uint16_t sector_size = profiles_list->sector_size; _cleanup_free_ uint8_t *data = NULL; int rc; data = hidpp20_onboard_profiles_allocate_sector(profiles_list); for (i = 0; i < profiles_list->num_profiles; i++) { data[buffer_index++] = 0x00; data[buffer_index++] = i + 1; data[buffer_index++] = !!profiles_list->profiles[i].enabled; data[buffer_index++] = 0x00; } data[buffer_index++] = 0xFF; data[buffer_index++] = 0xFF; data[buffer_index++] = 0x00; data[buffer_index++] = 0x00; memset(data + buffer_index, 0xff, sector_size - buffer_index); hidpp_log_buf_raw(&device->base, "dictionary: ", data, hidpp20_onboard_profiles_compute_dict_size(device, profiles_list)); rc = hidpp20_onboard_profiles_write_sector(device, 0x0000, sector_size, data, true); if (rc) hidpp_log_error(&device->base, "failed to write profile dictionary\n"); return rc; } static void hidpp20_buttons_to_cpu(struct hidpp20_device *device, struct hidpp20_profiles *profiles, struct hidpp20_profile *profile, union hidpp20_button_binding *buttons, unsigned int count) { unsigned int i; for (i = 0; i < count; i++) { union hidpp20_button_binding *b = &buttons[i]; union hidpp20_button_binding *button = &profile->buttons[i]; button->any.type = b->any.type; switch (b->any.type) { case HIDPP20_BUTTON_HID_TYPE: button->subany.subtype = b->subany.subtype; switch (b->subany.subtype) { case HIDPP20_BUTTON_HID_TYPE_MOUSE: button->button.buttons = ffs(hidpp_be_u16_to_cpu(b->button.buttons)); break; case HIDPP20_BUTTON_HID_TYPE_KEYBOARD: button->keyboard_keys.modifier_flags = b->keyboard_keys.modifier_flags; button->keyboard_keys.key = b->keyboard_keys.key; break; case HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL: button->consumer_control.consumer_control = hidpp_be_u16_to_cpu(b->consumer_control.consumer_control); break; } break; case HIDPP20_BUTTON_SPECIAL: button->special.special = b->special.special; button->special.profile = b->special.profile; break; case HIDPP20_BUTTON_MACRO: if (profile->macros[i]) { free(profile->macros[i]); profile->macros[i] = NULL; } hidpp20_onboard_profiles_parse_macro(device, profiles, b->macro.page, b->macro.offset, &profile->macros[i]); /* the actual page is stored in the 'zero' field */ button->macro.page = i; button->macro.offset = b->macro.offset; button->macro.zero = b->macro.page; break; case HIDPP20_BUTTON_DISABLED: break; default: break; } } } static void hidpp20_buttons_from_cpu(struct hidpp20_profile *profile, union hidpp20_button_binding *buttons, unsigned int count) { unsigned int i; for (i = 0; i < count; i++) { union hidpp20_button_binding *button = &buttons[i]; union hidpp20_button_binding *b = &profile->buttons[i]; button->any.type = b->any.type; switch (b->any.type) { case HIDPP20_BUTTON_HID_TYPE: button->subany.subtype = b->subany.subtype; switch (b->subany.subtype) { case HIDPP20_BUTTON_HID_TYPE_MOUSE: button->button.buttons = hidpp_cpu_to_be_u16(1U << (b->button.buttons - 1)); break; case HIDPP20_BUTTON_HID_TYPE_KEYBOARD: button->keyboard_keys.modifier_flags = b->keyboard_keys.modifier_flags; button->keyboard_keys.key = b->keyboard_keys.key; break; case HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL: button->consumer_control.type = HIDPP20_BUTTON_HID_TYPE; button->consumer_control.subtype = HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL; button->consumer_control.consumer_control = hidpp_cpu_to_be_u16(b->consumer_control.consumer_control); break; } break; case HIDPP20_BUTTON_SPECIAL: button->special.special = b->special.special; button->special.profile = b->special.profile; break; case HIDPP20_BUTTON_DISABLED: break; case HIDPP20_BUTTON_MACRO: /* the actual page is stored in the 'zero' field */ button->macro.page = b->macro.zero; button->macro.offset = b->macro.offset; button->macro.zero = 0; break; default: break; } } } void hidpp20_onboard_profiles_read_led(struct hidpp20_led *led, struct hidpp20_internal_led internal_led) { uint16_t period = 0; uint8_t brightness = 0; led->mode = (enum hidpp20_led_mode)internal_led.mode; switch (led->mode) { case HIDPP20_LED_CYCLE: period = hidpp_be_u16_to_cpu(internal_led.effect.cycle.period_or_speed); brightness = internal_led.effect.cycle.intensity; if (brightness == 0) brightness = 100; break; case HIDPP20_LED_STARLIGHT: led->color = internal_led.effect.starlight.color_sky; led->extra_color = internal_led.effect.starlight.color_star; break; case HIDPP20_LED_BREATHING: period = hidpp_be_u16_to_cpu(internal_led.effect.breath.period_or_speed); brightness = internal_led.effect.breath.intensity; if (brightness == 0) brightness = 100; led->color = internal_led.effect.breath.color; break; case HIDPP20_LED_RIPPLE: period = hidpp_be_u16_to_cpu(internal_led.effect.breath.period_or_speed); led->color = internal_led.effect.ripple.color; break; case HIDPP20_LED_ON: led->color = internal_led.effect.fixed.color; break; case HIDPP20_LED_OFF: break; default: memcpy(led->original, &internal_led, sizeof(internal_led)); break; } led->period = period; led->brightness = brightness; } static int hidpp20_onboard_profiles_parse_profile(struct hidpp20_device *device, struct hidpp20_profiles *profiles_list, unsigned index, bool check_crc) { union hidpp20_internal_profile *pdata; struct hidpp20_profile *profile = &profiles_list->profiles[index]; uint16_t sector = profile->address; _cleanup_free_ uint8_t *data = NULL; unsigned i; int rc; if (index >= profiles_list->num_profiles) return -EINVAL; data = hidpp20_onboard_profiles_allocate_sector(profiles_list); pdata = (union hidpp20_internal_profile *)data; rc = hidpp20_onboard_profiles_read_sector(device, sector, profiles_list->sector_size, data); if (rc < 0) return rc; if (check_crc) { if (!hidpp20_onboard_profiles_is_sector_valid(device, profiles_list->sector_size, data)) { return -EAGAIN; } } profile->report_rate = 1000 / max(1, pdata->profile.report_rate); profile->default_dpi = pdata->profile.default_dpi; profile->switched_dpi = pdata->profile.switched_dpi; profile->powersave_timeout = pdata->profile.powersave_timeout; profile->poweroff_timeout = pdata->profile.poweroff_timeout; for (i = 0; i < 5; i++) { profile->dpi[i] = get_unaligned_le_u16(&data[2 * i + 3]); } for (i = 0; i < profiles_list->num_leds; i++) { hidpp20_onboard_profiles_read_led(&profile->leds[i], pdata->profile.leds[i]); hidpp20_onboard_profiles_read_led(&profile->alt_leds[i], pdata->profile.alt_leds[i]); } hidpp20_buttons_to_cpu(device, profiles_list, profile, pdata->profile.buttons, profiles_list->num_buttons); memcpy(profile->name, pdata->profile.name.txt, sizeof(profile->name)); /* force terminating '\0' */ profile->name[sizeof(profile->name) - 1] = '\0'; /* check if we are using the default name or not */ for (i = 0; i < sizeof(profile->name); i++) { if (pdata->profile.name.raw[i] != 0xff) break; } if (i == sizeof(profile->name)) memset(profile->name, 0, sizeof(profile->name)); return 0; } int hidpp20_onboard_profiles_initialize(struct hidpp20_device *device, struct hidpp20_profiles *profiles) { _cleanup_free_ uint8_t *data = NULL; int rc; unsigned i; uint16_t addr; bool crc_valid; bool read_userdata = true; assert(profiles); for (i = 0; i < profiles->num_profiles; i++) { profiles->profiles[i].address = 0; profiles->profiles[i].enabled = 0; } data = hidpp20_onboard_profiles_allocate_sector(profiles); rc = hidpp20_onboard_profiles_read_sector(device, HIDPP20_USER_PROFILES_G402, profiles->sector_size, data); if (rc && device->quirk == HIDPP20_QUIRK_G305) { /* The G305 has a bug where it throws an ERR_INVALID_ARGUMENT if the sector has not been written to yet. If this happens we will read the ROM profiles.*/ read_userdata = false; goto read_profiles; } if (rc < 0) return rc; // ignore_clang_sa_mem_leak crc_valid = hidpp20_onboard_profiles_is_sector_valid(device, profiles->sector_size, data); if (crc_valid) { for (i = 0; i < profiles->num_profiles; i++) { uint8_t *d = data + 4 * i; addr = get_unaligned_be_u16(d); if(addr == HIDPP20_PROFILE_DIR_END) break; profiles->profiles[i].address = addr; /* profile address sanity check */ if (profiles->profiles[i].address != (HIDPP20_USER_PROFILES_G402 | (i + 1))) hidpp_log_info(&device->base, "profile %d: error in the address: 0x%04x instead of 0x%04x\n", i + 1, profiles->profiles[i].address, HIDPP20_USER_PROFILES_G402 | (i + 1)); profiles->profiles[i].enabled = !!d[HIDPP20_PROFILE_DIR_ENABLED]; } } else { hidpp_log_debug(&device->base, "Profile directory has an invalid CRC... Reading ROM profiles.\n"); read_userdata = false; } read_profiles: for (i = 0; i < profiles->num_profiles; i++) { if (read_userdata) { hidpp_log_debug(&device->base, "Parsing profile %u\n", i); rc = hidpp20_onboard_profiles_parse_profile(device, profiles, i, true); /* on fail to read the user profile fallback to the default profile */ if (rc) hidpp_log_debug(&device->base, "Profile %u is bad. Falling back to the ROM settings.\n", i); else continue; } /* the number of rom profiles can be different than the number of user profiles so we if there are not enough rom profiles to populate all the user profiles we just use the first rom profile */ if (i + 1 > profiles->num_rom_profiles) profiles->profiles[i].address = HIDPP20_ROM_PROFILES_G402 + 1; else profiles->profiles[i].address = HIDPP20_ROM_PROFILES_G402 + i + 1; rc = hidpp20_onboard_profiles_parse_profile(device, profiles, i, false); if (rc < 0) return rc; } return profiles->num_profiles; } void hidpp20_onboard_profiles_write_led(struct hidpp20_internal_led *internal_led, struct hidpp20_led *led) { uint16_t period = led->period; uint8_t brightness = led->brightness; memset(internal_led, 0, sizeof(*internal_led)); internal_led->mode = (uint8_t)led->mode; switch (led->mode) { case HIDPP20_LED_CYCLE: internal_led->effect.cycle.period_or_speed = hidpp_cpu_to_be_u16(period); if (brightness < 100) internal_led->effect.cycle.intensity = brightness; else internal_led->effect.cycle.intensity = 0; break; case HIDPP20_LED_STARLIGHT: internal_led->effect.starlight.color_sky = led->color; internal_led->effect.starlight.color_star = led->extra_color; break; case HIDPP20_LED_BREATHING: internal_led->effect.breath.color = led->color; internal_led->effect.breath.period_or_speed = hidpp_cpu_to_be_u16(period); if (brightness < 100) internal_led->effect.breath.intensity = brightness; else internal_led->effect.breath.intensity = 0; break; case HIDPP20_LED_RIPPLE: internal_led->effect.ripple.color = led->color; internal_led->effect.ripple.period = hidpp_cpu_to_be_u16(period); break; case HIDPP20_LED_ON: internal_led->effect.fixed.color = led->color; internal_led->effect.fixed.effect = 0; break; case HIDPP20_LED_OFF: break; default: memcpy(internal_led, led->original, sizeof(*internal_led)); break; } } static int hidpp20_onboard_profiles_write_profile(struct hidpp20_device *device, struct hidpp20_profiles *profiles_list, unsigned int index) { union hidpp20_internal_profile *pdata; _cleanup_free_ uint8_t *data = NULL; uint16_t sector_size = profiles_list->sector_size; uint16_t sector = index + 1; struct hidpp20_profile *profile = &profiles_list->profiles[index]; unsigned i; int rc; if (index >= profiles_list->num_profiles) return -EINVAL; data = hidpp20_onboard_profiles_allocate_sector(profiles_list); pdata = (union hidpp20_internal_profile *)data; memset(data, 0xff, profiles_list->sector_size); pdata->profile.report_rate = 1000 / profile->report_rate; pdata->profile.default_dpi = profile->default_dpi; pdata->profile.switched_dpi = profile->switched_dpi; pdata->profile.powersave_timeout = profile->powersave_timeout; pdata->profile.poweroff_timeout = profile->poweroff_timeout; for (i = 0; i < 5; i++) { pdata->profile.dpi[i] = hidpp_cpu_to_le_u16(profile->dpi[i]); } for (i = 0; i < profiles_list->num_leds; i++) { hidpp20_onboard_profiles_write_led(&pdata->profile.leds[i], &profile->leds[i]); /* we write the current led instead of the stored value */ hidpp20_onboard_profiles_write_led(&pdata->profile.alt_leds[i], &profile->leds[i]); } hidpp20_buttons_from_cpu(profile, pdata->profile.buttons, profiles_list->num_buttons); memcpy(pdata->profile.name.txt, profile->name, sizeof(profile->name)); rc = hidpp20_onboard_profiles_write_sector(device, sector, sector_size, data, true); if (rc < 0) { hidpp_log_error(&device->base, "failed to write profile\n"); return rc; } return 0; } int hidpp20_onboard_profiles_commit(struct hidpp20_device *device, struct hidpp20_profiles *profiles_list) { struct hidpp20_profile *profile; unsigned int i; bool enabled_profile = false; int rc; for (i = 0; i < profiles_list->num_profiles; i++) { profile = &profiles_list->profiles[i]; if (profile->enabled) { rc = hidpp20_onboard_profiles_write_profile(device, profiles_list, i); if (rc < 0) return rc; enabled_profile = true; } } if (!enabled_profile) { if (profiles_list->num_profiles > 0) { profiles_list->profiles[0].enabled = 1; rc = hidpp20_onboard_profiles_write_profile(device, profiles_list, 0); if (rc < 0) return rc; } } return hidpp20_onboard_profiles_write_dict(device, profiles_list); } static const enum ratbag_button_action_special hidpp20_profiles_specials[] = { [0x00] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, [0x01] = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT, [0x02] = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT, [0x03] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP, [0x04] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN, [0x05] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP, [0x06] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT, [0x07] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE, [0x08] = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP, [0x09] = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN, [0x0a] = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP, [0x0b] = RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE, [0x0c ... 0xff] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, }; enum ratbag_button_action_special hidpp20_onboard_profiles_get_special(uint8_t code) { return hidpp20_profiles_specials[code]; } uint8_t hidpp20_onboard_profiles_get_code_from_special(enum ratbag_button_action_special special) { uint8_t i = 0; while (++i) { if (hidpp20_profiles_specials[i] == special) return i; } return RATBAG_BUTTON_ACTION_SPECIAL_INVALID; } /* -------------------------------------------------------------------------- */ /* generic hidpp20 device operations */ /* -------------------------------------------------------------------------- */ struct hidpp20_device * hidpp20_device_new(const struct hidpp_device *base, unsigned int idx, struct hidpp_hid_report *reports, unsigned int num_reports) { struct hidpp20_device *dev; int rc; dev = zalloc(sizeof(*dev)); dev->index = idx; dev->base = *base; dev->proto_major = 1; dev->proto_minor = 0; dev->led_ext_caps = 0; hidpp_get_supported_report_types(&(dev->base), reports, num_reports); if (!(dev->base.supported_report_types & HIDPP_REPORT_SHORT) && !(dev->base.supported_report_types & HIDPP_REPORT_LONG)) goto err; rc = hidpp20_root_get_protocol_version(dev, &dev->proto_major, &dev->proto_minor); if (rc) { /* communication error, best to ignore the device */ goto err; } if (dev->proto_major < 2) goto err; rc = hidpp20_feature_set_get(dev); if (rc < 0) goto err; return dev; err: free(dev); return NULL; } void hidpp20_device_destroy(struct hidpp20_device *device) { free(device->feature_list); free(device); } libratbag-0.13/src/hidpp20.h000066400000000000000000000752661362011324700156110ustar00rootroot00000000000000/* * HID++ 2.0 library - headers file. * * 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 (including the next * paragraph) 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. */ /* * Based on the HID++ 2.0 documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #pragma once #include #include "hidpp-generic.h" #include "libratbag-util.h" struct _hidpp20_message { uint8_t report_id; uint8_t device_idx; uint8_t sub_id; uint8_t address; uint8_t parameters[LONG_MESSAGE_LENGTH - 4U]; } __attribute__((packed)); union hidpp20_message { struct _hidpp20_message msg; uint8_t data[LONG_MESSAGE_LENGTH]; }; struct hidpp20_feature { uint16_t feature; uint8_t type; }; enum hidpp20_quirk { HIDPP20_QUIRK_NONE, HIDPP20_QUIRK_G305, HIDPP20_QUIRK_G602, }; struct hidpp20_device { struct hidpp_device base; unsigned int index; unsigned proto_major; unsigned proto_minor; unsigned feature_count; struct hidpp20_feature *feature_list; enum hidpp20_quirk quirk; unsigned int led_ext_caps; }; int hidpp20_request_command(struct hidpp20_device *dev, union hidpp20_message *msg); #define CASE_RETURN_STRING(a) case a: return #a; break const char *hidpp20_feature_get_name(uint16_t feature); const char *hidpp20_get_quirk_string(enum hidpp20_quirk quirk); /* -------------------------------------------------------------------------- */ /* generic hidpp20 device operations */ /* -------------------------------------------------------------------------- */ struct hidpp20_device * hidpp20_device_new(const struct hidpp_device *base, unsigned int idx, struct hidpp_hid_report *reports, unsigned int num_reports); void hidpp20_device_destroy(struct hidpp20_device *device); /* -------------------------------------------------------------------------- */ /* 0x0000: Root */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_ROOT 0x0000 int hidpp_root_get_feature(struct hidpp20_device *device, uint16_t feature, uint8_t *feature_index, uint8_t *feature_type, uint8_t *feature_version); int hidpp20_root_get_protocol_version(struct hidpp20_device *dev, unsigned *major, unsigned *minor); /* -------------------------------------------------------------------------- */ /* 0x0001: Feature Set */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_FEATURE_SET 0x0001 /* -------------------------------------------------------------------------- */ /* 0x0003: Device Info */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_DEVICE_INFO 0x0003 /* -------------------------------------------------------------------------- */ /* 0x0005: Device Name */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_DEVICE_NAME 0x0005 /* -------------------------------------------------------------------------- */ /* 0x0020: Reset */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_RESET 0x0020 /* -------------------------------------------------------------------------- */ /* 0x1000: Battery level status */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_BATTERY_LEVEL_STATUS 0x1000 enum hidpp20_battery_status { BATTERY_STATUS_DISCHARGING = 0, BATTERY_STATUS_RECHARGING, BATTERY_STATUS_CHARGING_IN_FINAL_STATE, BATTERY_STATUS_CHARGE_COMPLETE, BATTERY_STATUS_RECHARGING_BELOW_OPTIMAL_SPEED, BATTERY_STATUS_INVALID_BATTERY_TYPE, BATTERY_STATUS_THERMAL_ERROR, BATTERY_STATUS_OTHER_CHARGING_ERROR, BATTERY_STATUS_INVALID, }; /** * Retrieves the battery level status. * * @return the battery status or a negative errno on error */ int hidpp20_batterylevel_get_battery_level(struct hidpp20_device *device, uint16_t *level, uint16_t *next_level); /* -------------------------------------------------------------------------- */ /* 0x1001: Battery Voltage */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_BATTERY_VOLTAGE 0x1001 enum hidpp20_battery_voltage_status { BATTERY_VOLTAGE_STATUS_DISCHARGING = 0, BATTERY_VOLTAGE_STATUS_WIRELESS_CHARGING = 0x10, BATTERY_VOLTAGE_STATUS_CHARGING = 0x80, }; /** * Retrieves the battery voltage * * @return the battery status or a negative errno on error */ int hidpp20_batteryvoltage_get_battery_voltage(struct hidpp20_device *device, uint16_t *voltage); /* -------------------------------------------------------------------------- */ /* 0x1300: LED software control */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_LED_SW_CONTROL 0x1300 enum hidpp20_led_sw_ctrl_led_type { HIDPP20_LED_TYPE_BATTERY = 0x01, HIDPP20_LED_TYPE_DPI, HIDPP20_LED_TYPE_PROFILE, HIDPP20_LED_TYPE_LOGO, HIDPP20_LED_TYPE_COSMETIC }; enum hidpp20_led_sw_ctrl_led_mode { HIDPP20_LED_MODE_OFF = 0x01, HIDPP20_LED_MODE_ON = 0x02, HIDPP20_LED_MODE_BLINK = 0x04, HIDPP20_LED_MODE_TRAVEL = 0x08, HIDPP20_LED_MODE_RAMP_UP = 0x10, HIDPP20_LED_MODE_RAMP_DOWN = 0x20, HIDPP20_LED_MODE_HEARTBEAT = 0x40, HIDPP20_LED_MODE_BREATHING = 0x80 }; #define HIDPP20_LED_SW_CONTROL_LED_INDEX_ALL 0xff struct hidpp20_led_sw_ctrl_led_info { uint8_t index; uint8_t type; uint8_t physical_count; uint16_t caps; uint8_t nvconfig_caps; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_led_sw_ctrl_led_info) == 6, "Invalid size"); struct hidpp20_led_sw_ctrl_led_state { uint8_t index; uint16_t mode; union { struct { uint16_t brightness; uint16_t period; uint16_t timeout; } breathing; struct { uint16_t unused; uint16_t delay; } traveling; struct { uint16_t index; uint16_t on_time; uint16_t off_time; } blink; struct { /* Logical information to display on the LED * Meaning and value range depend on the LED */ uint16_t index; } on; }; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_led_sw_ctrl_led_state) == 9, "Invalid size"); int hidpp20_led_sw_control_read_leds(struct hidpp20_device* device, struct hidpp20_led_sw_ctrl_led_info** info_list); const char* hidpp20_sw_led_control_get_mode_string(const enum hidpp20_led_sw_ctrl_led_mode mode); int hidpp20_led_sw_control_read_leds(struct hidpp20_device* device, struct hidpp20_led_sw_ctrl_led_info** info_list); /** * Retrieves the number of non-RGB logical leds from the mouse * @return the led count or a negative errno */ int hidpp20_led_sw_control_get_led_count(struct hidpp20_device* device); /** * Retrieves the info for a given logical led * @return 0 or a negative errno in case of error */ int hidpp20_led_sw_control_get_led_info(struct hidpp20_device* device, uint8_t led_idx, struct hidpp20_led_sw_ctrl_led_info* info); /** * Reads all LEDs on the device for their current state * @return 0 on success or a negative errno */ int hidpp20_led_sw_control_read_leds(struct hidpp20_device* device, struct hidpp20_led_sw_ctrl_led_info** info_list); /** * Get the current sw control value * @return 0 if the leds are sofware-controlled 1 if they are firmware controlled */ bool hidpp20_led_sw_control_get_sw_ctrl(struct hidpp20_device* device); /** * Sets who controls the LEDs * ctrl = 0, the software controls the LEDs * ctrl = 1, the firmware controls the LEDs * @return 0 on success or a negative errno */ int hidpp20_led_sw_control_set_sw_ctrl(struct hidpp20_device* device, bool ctrl); /** * Gets the state of a LED * @return 0 on success or a negative errno */ int hidpp20_led_sw_control_get_led_state(struct hidpp20_device* device, uint8_t led_idx, struct hidpp20_led_sw_ctrl_led_state* state); /** * Sets the state of a LED * The LED index should be passed in the state structure * @return 0 on success or a negative errno */ int hidpp20_led_sw_control_set_led_state(struct hidpp20_device* device, const struct hidpp20_led_sw_ctrl_led_state* state); /* -------------------------------------------------------------------------- */ /* 0x1b00: KBD reprogrammable keys and mouse buttons */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_KBD_REPROGRAMMABLE_KEYS 0x1b00 enum hidpp2_controL_id_flags { HIDPP20_CONTROL_ID_FLAG_NONE = 0, HIDPP20_CONTROL_ID_FLAG_MOUSE_BUTTON = (1 << 0), /**< Is a mouse button */ HIDPP20_CONTROL_ID_FLAG_FN_KEY = (1 << 1), /**< Is a fn button */ HIDPP20_CONTROL_ID_FLAG_HOTKEY = (1 << 2), /**< Is a Hot key, not a standard kbd key */ HIDPP20_CONTROL_ID_FLAG_FN_TOGGLE_AFFECTED = (1 << 3), /**< Fn toggle affects this key */ HIDPP20_CONTROL_ID_FLAG_REPROGRAMMABLE = (1 << 4), /**< Key can be reprogrammed */ }; struct hidpp20_control_id { uint8_t index; uint16_t control_id; uint16_t task_id; uint8_t flags; /* fields below are only set for 0x1b04, not for 0x1b00 */ uint8_t position; uint8_t group; uint8_t group_mask; uint8_t raw_XY; struct { uint8_t raw_XY; uint8_t persist; uint8_t divert; uint16_t remapped; int updated; } reporting; }; int hidpp20_kbd_reprogrammable_keys_get_controls(struct hidpp20_device *device, struct hidpp20_control_id **controls_list); /* -------------------------------------------------------------------------- */ /* 0x1b04: Special keys and mouse buttons */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_SPECIAL_KEYS_BUTTONS 0x1b04 /** * allocates a list of controls that has to be freed by the caller. * * returns the elements in the list or a negative error */ int hidpp20_special_key_mouse_get_controls(struct hidpp20_device *device, struct hidpp20_control_id **controls_list); /** * commit a control previously allocated by * hidpp20_special_key_mouse_get_controls(). * * returns 0 or a negative error */ int hidpp20_special_key_mouse_set_control(struct hidpp20_device *device, struct hidpp20_control_id *control); const struct ratbag_button_action *hidpp20_1b04_get_logical_mapping(uint16_t value); uint16_t hidpp20_1b04_get_logical_control_id(const struct ratbag_button_action *action); const char *hidpp20_1b04_get_logical_mapping_name(uint16_t value); enum ratbag_button_type hidpp20_1b04_get_physical_mapping(uint16_t value); const char *hidpp20_1b04_get_physical_mapping_name(uint16_t value); /* -------------------------------------------------------------------------- */ /* 0x1d4b: Wireless Device Status */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_WIRELESS_DEVICE_STATUS 0x1d4b /* -------------------------------------------------------------------------- */ /* 0x2200: Mouse Pointer Basic Optical Sensors */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_MOUSE_POINTER_BASIC 0x2200 #define HIDDP20_MOUSE_POINTER_FLAGS_VERTICAL_TUNING (1 << 4) #define HIDDP20_MOUSE_POINTER_FLAGS_OS_BALLISTICS (1 << 3) #define HIDDP20_MOUSE_POINTER_FLAGS_ACCELERATION_MASK 0x03 #define HIDDP20_MOUSE_POINTER_ACCELERATION_NONE 0x00 #define HIDDP20_MOUSE_POINTER_ACCELERATION_LOW 0x01 #define HIDDP20_MOUSE_POINTER_ACCELERATION_MEDIUM 0x02 #define HIDDP20_MOUSE_POINTER_ACCELERATION_HIGH 0x03 int hidpp20_mousepointer_get_mousepointer_info(struct hidpp20_device *device, uint16_t *resolution, uint8_t *flags); /* -------------------------------------------------------------------------- */ /* 0x2201: Adjustable DPI */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_ADJUSTABLE_DPI 0x2201 /** * either dpi_steps is not null or the values are stored in the null terminated * array dpi_list. */ struct hidpp20_sensor { uint8_t index; uint16_t dpi; uint16_t dpi_min; uint16_t dpi_max; uint16_t dpi_steps; uint16_t default_dpi; uint16_t dpi_list[LONG_MESSAGE_LENGTH / 2 + 1]; }; /** * allocates a list of sensors that has to be freed by the caller. * * returns the elements in the list or a negative error */ int hidpp20_adjustable_dpi_get_sensors(struct hidpp20_device *device, struct hidpp20_sensor **sensors_list); /** * set the current dpi of the provided sensor. sensor must have been * allocated by hidpp20_adjustable_dpi_get_sensors() */ int hidpp20_adjustable_dpi_set_sensor_dpi(struct hidpp20_device *device, struct hidpp20_sensor *sensor, uint16_t dpi); /* -------------------------------------------------------------------------- */ /* 0x8060 - Adjustable Report Rate */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_ADJUSTABLE_REPORT_RATE 0x8060 /** * set the bitmap_ms to the supported report rates. Bits enabled reflect a * supported report rate, where bit 0 equals 1ms, bit 1 2ms, bit 2 3ms, etc. */ int hidpp20_adjustable_report_rate_get_report_rate_list(struct hidpp20_device *device, uint8_t *bitflags_ms); int hidpp20_adjustable_report_rate_get_report_rate(struct hidpp20_device *device, uint8_t *rate_ms); int hidpp20_adjustable_report_rate_set_report_rate(struct hidpp20_device *device, uint8_t rate_ms); /* -------------------------------------------------------------------------- */ /* 0x8070v4 - Color LED effects */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_COLOR_LED_EFFECTS 0x8070 enum hidpp20_color_led_info_ext_caps { HIDPP20_COLOR_LED_INFO_EXT_CAP_HAS_ZONE_EFFECT = 0x01, HIDPP20_COLOR_LED_INFO_EXT_CAP_NO_GET_EFFECT_SETTINGS = 0x02, HIDPP20_COLOR_LED_INFO_EXT_CAP_HAS_SET_LED_BIN_INFO = 0x04, HIDPP20_COLOR_LED_INFO_EXT_CAP_MONOCHROME_ONLY = 0x08, }; struct hidpp20_color_led_info; int hidpp20_color_led_effects_get_info(struct hidpp20_device *device, struct hidpp20_color_led_info *info); struct hidpp20_color_led_zone_info; int hidpp20_color_led_effects_get_zone_info(struct hidpp20_device *device, uint8_t reg, struct hidpp20_color_led_zone_info *info); int hidpp20_color_led_effects_get_zone_infos(struct hidpp20_device *device, struct hidpp20_color_led_zone_info **infos_list); struct hidpp20_color_led_zone_effect_info; int hidpp20_color_led_effect_get_zone_effect_info(struct hidpp20_device *device, uint8_t zone_index, uint8_t zone_effect_index, struct hidpp20_color_led_zone_effect_info *info); struct hidpp20_rgb_device_info; int hidpp20_rgb_effects_get_device_info(struct hidpp20_device *device, struct hidpp20_rgb_device_info *info); struct hidpp20_rgb_cluster_info; int hidpp20_rgb_effects_get_cluster_info(struct hidpp20_device *device, uint8_t cluster_index, struct hidpp20_rgb_cluster_info *info); int hidpp20_rgb_effects_get_cluster_infos(struct hidpp20_device *device, struct hidpp20_rgb_cluster_info **infos_list); struct hidpp20_rgb_effect_info; int hidpp20_rgb_effects_get_effect_info(struct hidpp20_device *device, uint8_t cluster_index, uint8_t effect_index, struct hidpp20_rgb_effect_info *info); struct hidpp20_led; int hidpp20_color_led_effects_set_zone_effect(struct hidpp20_device *device, uint8_t zone_index, struct hidpp20_led led); int hidpp20_color_led_effects_get_zone_effect(struct hidpp20_device *device, uint8_t zone_index, struct hidpp20_led *led); enum hidpp20_color_led_location { HIDPP20_COLOR_LED_LOCATION_UNDEFINED = 0, HIDPP20_COLOR_LED_LOCATION_PRIMARY, HIDPP20_COLOR_LED_LOCATION_LOGO, HIDPP20_COLOR_LED_LOCATION_LEFT, HIDPP20_COLOR_LED_LOCATION_RIGHT, HIDPP20_COLOR_LED_LOCATION_COMBINED, HIDPP20_COLOR_LED_LOCATION_PRIMARY_1, HIDPP20_COLOR_LED_LOCATION_PRIMARY_2, HIDPP20_COLOR_LED_LOCATION_PRIMARY_3, HIDPP20_COLOR_LED_LOCATION_PRIMARY_4, HIDPP20_COLOR_LED_LOCATION_PRIMARY_5, HIDPP20_COLOR_LED_LOCATION_PRIMARY_6, }; enum hidpp20_color_led_persistency { HIDPP20_COLOR_LED_PERSISTENCY_UNSUPPORTED, HIDPP20_COLOR_LED_PERSISTENCY_ON, HIDPP20_COLOR_LED_PERSISTENCY_OFF, HIDPP20_COLOR_LED_PERSISTENCY_ON_OFF, }; enum hidpp20_color_led_zone_effect { HIDPP20_COLOR_LED_ZONE_EFFECT_DISABLED = 0, HIDPP20_COLOR_LED_ZONE_EFFECT_FIXED = 1, HIDPP20_COLOR_LED_ZONE_EFFECT_CYCLING = 3, HIDPP20_COLOR_LED_ZONE_EFFECT_WAVE = 4, HIDPP20_COLOR_LED_ZONE_EFFECT_STARLIGHT = 5, HIDPP20_COLOR_LED_ZONE_EFFECT_LIGHT_ON_PRESS = 6, HIDPP20_COLOR_LED_ZONE_EFFECT_AUDIO_VISUALIZER = 7, HIDPP20_COLOR_LED_ZONE_EFFECT_BOOT_UP = 8, HIDPP20_COLOR_LED_ZONE_EFFECT_DEMO_MODE = 8, HIDPP20_COLOR_LED_ZONE_EFFECT_BREATHING = 10, HIDPP20_COLOR_LED_ZONE_EFFECT_RIPPLE = 11, HIDPP20_COLOR_LED_ZONE_EFFECT_CUSTOM = 12, }; /* -------------------------------------------------------------------------- */ /* 0x8071 - RGB Effects */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_RGB_EFFECTS 0x8071 #define HIDPP20_RGB_EFFECTS_INDEX_ALL 0xFF /* type of info */ #define HIDPP20_RGB_EFFECTS_TOI_GENERAL 0x00 #define HIDPP20_RGB_EFFECTS_TOI_EFFECT 0x01 #define HIDPP20_RGB_EFFECTS_SLOT_INFO_STATE 0x00 #define HIDPP20_RGB_EFFECTS_SLOT_INFO_DEFAULTS 0x01 #define HIDPP20_RGB_EFFECTS_SLOT_INFO_UUID_0_10 0x02 #define HIDPP20_RGB_EFFECTS_SLOT_INFO_UUID_11_16 0x03 #define HIDPP20_RGB_EFFECTS_SLOT_INFO_EFCT_NAME_0_10 0x04 #define HIDPP20_RGB_EFFECTS_SLOT_INFO_EFCT_NAME_11_21 0x05 #define HIDPP20_RGB_EFFECTS_SLOT_INFO_EFCT_NAME_21_31 0x06 /* -------------------------------------------------------------------------- */ /* 0x8100 - Onboard Profiles */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_ONBOARD_PROFILES 0x8100 #define HIDPP20_BUTTON_HID_TYPE 0x80 #define HIDPP20_BUTTON_HID_TYPE_MOUSE 0x01 #define HIDPP20_BUTTON_HID_TYPE_KEYBOARD 0x02 #define HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL 0x03 #define HIDPP20_BUTTON_SPECIAL 0x90 #define HIDPP20_BUTTON_MACRO 0x00 #define HIDPP20_BUTTON_DISABLED 0xFF #define HIDPP20_MODIFIER_KEY_CTRL 0x01 #define HIDPP20_MODIFIER_KEY_SHIFT 0x02 #define HIDPP20_DPI_COUNT 5 #define HIDPP20_LED_COUNT 2 union hidpp20_button_binding { struct { uint8_t type; } any; struct { uint8_t type; uint8_t subtype; } subany; struct { uint8_t type; /* HIDPP20_BUTTON_HID_TYPE */ uint8_t subtype; /* HIDPP20_BUTTON_HID_TYPE_MOUSE */ uint16_t buttons; /* flags when internal */ } __attribute__((packed)) button; struct { uint8_t type; /* HIDPP20_BUTTON_HID_TYPE */ uint8_t subtype; /* HIDPP20_BUTTON_HID_TYPE_KEYBOARD */ uint8_t modifier_flags; uint8_t key; } __attribute__((packed)) keyboard_keys; struct { uint8_t type; /* HIDPP20_BUTTON_HID_TYPE */ uint8_t subtype; /* HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL */ uint16_t consumer_control; } __attribute__((packed)) consumer_control; struct { uint8_t type; /* HIDPP20_BUTTON_SPECIAL */ uint8_t special; uint8_t reserved; uint8_t profile; } __attribute__((packed)) special; struct { uint8_t type; /* HIDPP20_BUTTON_MACRO */ uint8_t page; uint8_t zero; uint8_t offset; } __attribute__((packed)) macro; struct { uint8_t type; /* PROFILE_BUTTON_TYPE_DISABLED */ } disabled; } __attribute__((packed)); _Static_assert(sizeof(union hidpp20_button_binding) == 4, "Invalid size"); struct hidpp20_color_led_info { uint8_t zone_count; /* we don't care about NV capabilities for libratbag, they just * indicate sale demo effects */ uint16_t nv_caps; uint16_t ext_caps; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_color_led_info) == 5, "Invalid size"); struct hidpp20_non_color_led_zone_info { uint8_t index; uint8_t type; uint16_t capabilities; uint8_t nv_config; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_non_color_led_zone_info) == 5, "Invalid size"); struct hidpp20_color_led_zone_info { uint8_t index; uint16_t location; uint8_t num_effects; uint8_t persistency_caps; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_color_led_zone_info) == 5, "Invalid size"); struct hidpp20_color_led_zone_effect_info { uint8_t zone_index; uint8_t zone_effect_index; uint16_t effect_id; uint16_t effect_caps; uint16_t effect_period; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_color_led_zone_effect_info) == 8, "Invalid size"); struct hidpp20_rgb_device_info { uint8_t cluster_index; uint8_t effect_index; uint8_t cluster_count; /* we don't care about NV capabilities for libratbag, they just * indicate sale demo effects */ uint16_t nv_caps; uint16_t ext_caps; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_rgb_device_info) == 7, "Invalid size"); struct hidpp20_rgb_cluster_info { uint8_t index; uint8_t effect_index; uint16_t location; uint16_t num_effects; uint16_t persistency_caps; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_rgb_cluster_info) == 8, "Invalid size"); struct hidpp20_rgb_effect_info { uint8_t cluster_index; uint8_t effect_index; uint16_t effect_id; uint16_t capabilities; uint16_t effect_period; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_rgb_effect_info) == 8, "Invalid size"); union hidpp20_generic_led_zone_info { struct hidpp20_led_sw_ctrl_led_info* leds; struct hidpp20_color_led_zone_info* color_leds_8070; struct hidpp20_rgb_cluster_info* color_leds_8071; }; struct hidpp20_color { uint8_t red; uint8_t green; uint8_t blue; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_color) == 3, "Invalid size"); enum hidpp20_led_type { HIDPP20_LED_UNKNOWN = -1, HIDPP20_LED_LOGO = 0, HIDPP20_LED_SIDE, }; enum hidpp20_led_mode { HIDPP20_LED_OFF = 0x00, HIDPP20_LED_ON = 0x01, HIDPP20_LED_CYCLE = 0x03, HIDPP20_LED_COLOR_WAVE = 0x04, HIDPP20_LED_STARLIGHT = 0x05, HIDPP20_LED_BREATHING = 0x0a, HIDPP20_LED_RIPPLE = 0x0b, HIDPP20_LED_CUSTOM = 0x0c, }; enum hidpp20_led_waveform { HIDPP20_LED_WF_DEFAULT = 0x00, HIDPP20_LED_WF_SINE = 0x01, HIDPP20_LED_WF_SQUARE = 0x02, HIDPP20_LED_WF_TRIANGLE = 0x03, HIDPP20_LED_WF_SAWTOOTH = 0x04, HIDPP20_LED_WF_SHARKFIN = 0x05, HIDPP20_LED_WF_EXPONENTIAL = 0x06, }; struct hidpp20_internal_led { uint8_t mode; /* enum hidpp20_led_mode */ union { struct hidpp20_led_fixed { struct hidpp20_color color; uint8_t effect; } __attribute__((packed)) fixed; struct hidpp20_led_cycle { uint8_t unused[5]; uint16_t period_or_speed; /* period in ms, speed is device dependent */ uint8_t intensity; /* 1 - 100 percent, 0 means 100 */ } __attribute__((packed)) cycle; struct hidpp20_led_starlight { struct hidpp20_color color_sky; struct hidpp20_color color_star; } __attribute__((packed)) starlight; struct hidpp20_led_breath { struct hidpp20_color color; uint16_t period_or_speed; /* period in ms, speed is device dependent */ uint8_t waveform; /* enum hidpp20_led_waveform */ uint8_t intensity; /* 1 - 100 percent, 0 means 100 */ } __attribute__((packed)) breath; struct hidpp20_led_ripple { struct hidpp20_color color; uint8_t reserved; uint16_t period; } __attribute__((packed)) ripple; struct hidpp20_led_custom { uint8_t slot; uint16_t init_frame; uint16_t lenght; uint16_t frame_period; uint8_t intensity; } __attribute__((packed)) custom; uint8_t padding[10]; } __attribute__((packed)) effect; }; _Static_assert(sizeof(struct hidpp20_led_fixed) == 4, "Invalid size"); _Static_assert(sizeof(struct hidpp20_led_cycle) == 8, "Invalid size"); _Static_assert(sizeof(struct hidpp20_led_starlight) == 6, "Invalid size"); _Static_assert(sizeof(struct hidpp20_led_breath) == 7, "Invalid size"); _Static_assert(sizeof(struct hidpp20_internal_led) == 11, "Invalid size"); _Static_assert(sizeof(struct hidpp20_led_ripple) == 6, "Invalid size"); _Static_assert(sizeof(struct hidpp20_led_custom) == 8, "Invalid size"); typedef uint8_t percent_t; struct hidpp20_led { enum hidpp20_led_mode mode; struct hidpp20_color color; struct hidpp20_color extra_color; uint16_t period; percent_t brightness; uint8_t original[sizeof(struct hidpp20_internal_led)]; }; #define HIDPP20_MACRO_NOOP 0x01 #define HIDPP20_MACRO_DELAY 0x40 #define HIDPP20_MACRO_KEY_PRESS 0x43 #define HIDPP20_MACRO_KEY_RELEASE 0x44 #define HIDPP20_MACRO_JUMP 0x60 #define HIDPP20_MACRO_END 0xff union hidpp20_macro_data { struct { uint8_t type; } __attribute__((packed)) any; struct { uint8_t type; /* HIDPP20_MACRO_DELAY */ uint16_t time; } __attribute__((packed)) delay; struct { uint8_t type; /* HIDPP20_MACRO_KEY_PRESS or HIDPP20_MACRO_KEY_RELEASE */ uint8_t modifier; uint8_t key; } __attribute__((packed)) key; struct { uint8_t type; /* HIDPP20_MACRO_JUMP */ uint8_t offset; uint8_t page; } __attribute__((packed)) jump; struct { uint8_t type; /* HIDPP20_MACRO_END */ } __attribute__((packed)) end; } __attribute__((packed)); _Static_assert(sizeof(union hidpp20_macro_data) == 3, "Invalid size"); struct hidpp20_profile { uint16_t address; uint8_t enabled; char name[16 * 3]; uint16_t powersave_timeout; uint16_t poweroff_timeout; unsigned report_rate; unsigned default_dpi; unsigned switched_dpi; unsigned current_dpi; uint16_t dpi[HIDPP20_DPI_COUNT]; union hidpp20_button_binding buttons[32]; union hidpp20_macro_data *macros[32]; struct hidpp20_led leds[HIDPP20_LED_COUNT]; struct hidpp20_led alt_leds[HIDPP20_LED_COUNT]; }; struct hidpp20_onboard_profiles_info { uint8_t memory_model_id; uint8_t profile_format_id; uint8_t macro_format_id; uint8_t profile_count; uint8_t profile_count_oob; uint8_t button_count; uint8_t sector_count; uint16_t sector_size; uint8_t mechanical_layout; uint8_t various_info; uint8_t reserved[5]; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_onboard_profiles_info) == 16, "Invalid size"); struct hidpp20_profiles { uint8_t num_profiles; uint8_t num_rom_profiles; uint8_t num_buttons; uint8_t num_modes; uint8_t num_leds; uint8_t has_g_shift; uint8_t has_dpi_shift; uint8_t corded; uint8_t wireless; uint8_t sector_count; uint16_t sector_size; struct hidpp20_profile *profiles; }; /** * fetches the profiles description as reported by the mouse. * * returns 0 or a negative error. */ int hidpp20_onboard_profiles_get_profiles_desc(struct hidpp20_device *device, struct hidpp20_onboard_profiles_info *info); /** * allocates a list of profiles that has to be destroyed by the caller. * The caller must use hidpp20_onboard_profiles_destroy() to free the memory. * * returns the number of profiles in the list or a negative error */ int hidpp20_onboard_profiles_allocate(struct hidpp20_device *device, struct hidpp20_profiles **profiles_list); /** * free a list of profiles allocated by hidpp20_onboard_profiles_allocate() */ void hidpp20_onboard_profiles_destroy(struct hidpp20_profiles *profiles_list); /** * initialize a struct hidpp20_profiles previous allocated with * hidpp20_onboard_profiles_allocate(). */ int hidpp20_onboard_profiles_initialize(struct hidpp20_device *device, struct hidpp20_profiles *profiles); /** * return the current profile index or a negative error. */ int hidpp20_onboard_profiles_get_current_profile(struct hidpp20_device *device); /** * Sets the current profile index. * Indexes are 1-indexed. * * return 0 or a negative error. */ int hidpp20_onboard_profiles_set_current_profile(struct hidpp20_device *device, uint8_t index); /** * return the current dpi index of the current active profile * or a negative error. */ int hidpp20_onboard_profiles_get_current_dpi_index(struct hidpp20_device *device); /** * Sets the current dpi index on the current active profile. * Indexes are 0-indexed. * * return 0 or a negative error. */ int hidpp20_onboard_profiles_set_current_dpi_index(struct hidpp20_device *device, uint8_t index); /** * Write the internal state of the device onto the FLASH. */ int hidpp20_onboard_profiles_commit(struct hidpp20_device *device, struct hidpp20_profiles *profiles_list); enum ratbag_button_action_special hidpp20_onboard_profiles_get_special(uint8_t code); uint8_t hidpp20_onboard_profiles_get_code_from_special(enum ratbag_button_action_special special); int hidpp20_onboard_profiles_read_sector(struct hidpp20_device *device, uint16_t sector, uint16_t sector_size, uint8_t *data); int hidpp20_onboard_profiles_write_sector(struct hidpp20_device *device, uint16_t sector, uint16_t sector_size, uint8_t *data, bool write_crc); static inline uint8_t * hidpp20_onboard_profiles_allocate_sector(struct hidpp20_profiles *profiles) { return zalloc(profiles->sector_size); } void hidpp20_onboard_profiles_read_led(struct hidpp20_led *led, struct hidpp20_internal_led internal_led); void hidpp20_onboard_profiles_write_led(struct hidpp20_internal_led *internal_led, struct hidpp20_led *led); /* -------------------------------------------------------------------------- */ /* 0x8110 - Mouse Button Spy */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_MOUSE_BUTTON_SPY 0x8110 libratbag-0.13/src/liblur.c000066400000000000000000000166461362011324700156240ustar00rootroot00000000000000/* * Copyright 2015 Red Hat, Inc * * liblur - Logitech Unifying Receiver access library * * 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 (including the next * paragraph) 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. */ #include "config.h" #include #include #include #include #include #include #include "usb-ids.h" #include "hidpp10.h" #include "libratbag-util.h" #include "liblur.h" #define _EXPORT_ __attribute__ ((visibility("default"))) #define MAX_DEVICES 6 static inline void cleanup_hidpp10_device_destroy(struct hidpp10_device **hidpp10_device) { if (*hidpp10_device) hidpp10_device_destroy(*hidpp10_device); } #define _cleanup_hidpp10_device_destroy_ _cleanup_(cleanup_hidpp10_device_destroy) struct lur_receiver { int refcount; int fd; void *userdata; struct hidpp10_device *hidppdev; struct list devices; }; struct lur_device { struct lur_receiver *receiver; int refcount; void *userdata; char *name; uint16_t vid, pid; uint32_t serial; enum lur_device_type type; int hidppidx; struct list node; bool present; /* used during re-enumeration */ }; _EXPORT_ const char * lur_device_get_name(struct lur_device *dev) { return dev->name; } _EXPORT_ uint16_t lur_device_get_vendor_id(struct lur_device *dev) { return dev->vid; } _EXPORT_ uint16_t lur_device_get_product_id(struct lur_device *dev) { return dev->pid; } _EXPORT_ enum lur_device_type lur_device_get_type(struct lur_device *dev) { return dev->type; } _EXPORT_ uint32_t lur_device_get_serial(struct lur_device *dev) { return dev->serial; } _EXPORT_ int lur_device_disconnect(struct lur_device *dev) { int rc; rc = hidpp10_disconnect(dev->receiver->hidppdev, dev->hidppidx); if (rc == 0) { list_remove(&dev->node); list_init(&dev->node); } return rc; } _EXPORT_ int lur_is_receiver(uint16_t vid, uint16_t pid) { return (vid == USB_VENDOR_ID_LOGITECH && (pid == 0xc52b || pid == 0xc532)); } static bool hidraw_is_receiver(int fd) { struct hidraw_devinfo info; int rc; rc = ioctl(fd, HIDIOCGRAWINFO, &info); if (rc < 0) return false; if (!lur_is_receiver(info.vendor, info.product)) return false; return true; } static struct hidpp10_device * hidpp10_init(int fd) { struct hidpp_device base; hidpp_device_init(&base, fd); return hidpp10_device_new(&base, HIDPP_RECEIVER_IDX, HIDPP10_PROFILE_UNKNOWN, 1); } _EXPORT_ int lur_receiver_new_from_hidraw(int fd, void *userdata, struct lur_receiver **out) { struct lur_receiver *receiver; if (!hidraw_is_receiver(fd)) return -EINVAL; receiver = zalloc(sizeof(*receiver)); receiver->refcount = 1; receiver->fd = fd; receiver->userdata = userdata; list_init(&receiver->devices); receiver->hidppdev = hidpp10_init(fd); if (!receiver->hidppdev) goto error; *out = receiver; return 0; error: free(receiver); return -errno; } _EXPORT_ int lur_receiver_enumerate(struct lur_receiver *lur, struct lur_device ***devices_out) { int i; int ndevices = 0; struct hidpp_device base; struct lur_device *dev, *tmp; int rc; struct lur_device **devices; hidpp_device_init(&base, lur->fd); list_for_each(dev, &lur->devices, node) dev->present = false; for (i = 0; i < MAX_DEVICES; i++) { _cleanup_hidpp10_device_destroy_ struct hidpp10_device *d = NULL; size_t name_size = 64; char name[name_size]; uint8_t interval, type; uint16_t wpid; uint32_t serial; bool is_new_device = true; d = hidpp10_device_new(&base, i, HIDPP10_PROFILE_UNKNOWN, 1); if (!d) continue; rc = hidpp10_get_pairing_information_device_name(d, name, &name_size); if (rc) continue; rc = hidpp10_get_pairing_information(d, &interval, &wpid, &type); if (rc) continue; rc = hidpp10_get_extended_pairing_information(d, &serial); if (rc) continue; /* check if we have the device already in the list */ list_for_each(dev, &lur->devices, node) { if (dev->pid == wpid && dev->type == type && dev->serial == serial && streq(dev->name, name)) { /* index may have changed, doesn't make it a * new device, just update it */ dev->hidppidx = i; dev->present = true; is_new_device = false; break; } } if (is_new_device) { dev = zalloc(sizeof *dev); dev->receiver = lur; lur_receiver_ref(lur); dev->refcount = 1; dev->name = strdup_safe(name); dev->vid = USB_VENDOR_ID_LOGITECH; dev->pid = wpid; dev->type = type; dev->serial = serial; dev->hidppidx = i; dev->present = true; list_insert(&lur->devices, &dev->node); } } devices = zalloc(MAX_DEVICES * sizeof(*devices)); /* Now drop all devices that disappeared */ list_for_each_safe(dev, tmp, &lur->devices, node) { if (dev->present) { devices[ndevices++] = dev; } else { list_remove(&dev->node); list_init(&dev->node); lur_device_unref(dev); } } *devices_out = devices; return ndevices; } _EXPORT_ int lur_receiver_open(struct lur_receiver *lur, uint8_t timeout) { return !!hidpp10_open_lock(lur->hidppdev, timeout); } _EXPORT_ int lur_receiver_close(struct lur_receiver *lur) { return 0; } _EXPORT_ int lur_receiver_get_fd(struct lur_receiver *lur) { return lur->fd; } _EXPORT_ struct lur_receiver * lur_receiver_ref(struct lur_receiver *lur) { assert(lur->refcount > 0); lur->refcount++; return lur; } _EXPORT_ struct lur_receiver * lur_receiver_unref(struct lur_receiver *lur) { if (lur == NULL) return NULL; assert(lur->refcount > 0); lur->refcount--; if (lur->refcount > 0) return NULL; /* when we get here, all the devices have alrady been removed from * the receiver */ hidpp10_device_destroy(lur->hidppdev); free(lur); return NULL; } _EXPORT_ void lur_receiver_set_user_data(struct lur_receiver *lur, void *userdata) { lur->userdata = userdata; } _EXPORT_ void* lur_receiver_get_user_data(const struct lur_receiver *lur) { return lur->userdata; } _EXPORT_ struct lur_device * lur_device_ref(struct lur_device *dev) { assert(dev->refcount > 0); dev->refcount++; return dev; } _EXPORT_ struct lur_device * lur_device_unref(struct lur_device *dev) { if (dev == NULL) return NULL; assert(dev->refcount > 0); dev->refcount--; if (dev->refcount > 0) return NULL; list_remove(&dev->node); lur_receiver_unref(dev->receiver); free(dev->name); free(dev); return NULL; } _EXPORT_ void lur_device_set_user_data(struct lur_device *dev, void *userdata) { dev->userdata = userdata; } _EXPORT_ void* lur_device_get_user_data(const struct lur_device *dev) { return dev->userdata; } libratbag-0.13/src/liblur.h000066400000000000000000000200541362011324700156150ustar00rootroot00000000000000/* * liblur - Logitech Unifying Receiver access library * * Copyright 2015 Red Hat, Inc * * 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 (including the next * paragraph) 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. */ #pragma once #ifdef __cplusplus extern "C" { #endif #include /** * @struct lur_receiver * * A handle for accessing Logitech Unifying Receivers. * * This struct is refcounted, use lur_device_ref() and lur_device_unref(). */ struct lur_receiver; /** * @struct lur_device * * A handle for accessing devices paired with a lur_receiver. * * This struct is refcounted, use lur_device_ref() and lur_device_unref(). */ struct lur_device; enum lur_device_type { LUR_DEVICE_TYPE_UNKNOWN = 0x00, LUR_DEVICE_TYPE_KEYBOARD = 0x01, LUR_DEVICE_TYPE_MOUSE = 0x02, LUR_DEVICE_TYPE_NUMPAD = 0x03, LUR_DEVICE_TYPE_PRESENTER = 0x04, LUR_DEVICE_TYPE_TRACKBALL = 0x08, LUR_DEVICE_TYPE_TOUCHPAD = 0x09, }; const char * lur_device_get_name(struct lur_device *dev); uint16_t lur_device_get_vendor_id(struct lur_device *dev); uint16_t lur_device_get_product_id(struct lur_device *dev); enum lur_device_type lur_device_get_type(struct lur_device *dev); uint32_t lur_device_get_serial(struct lur_device *dev); /** * Disconnect this device from the receiver it is currently paired with. * * @param lur A valid receiver object * @return 0 on success or nonzero on error */ int lur_device_disconnect(struct lur_device *dev); /** * Returns non-zero if a device with the given vid/pid is a Logitech * Unifying Receiver device. * * @param vid The vendor ID * @param pid The product ID * * @return non-zero if the device is a unifying receiver, zero otherwise */ int lur_is_receiver(uint16_t vid, uint16_t pid); /** * Creates a new Logitech Unifying Receiver object from the file descriptor. * The fd must be a hidraw device, opened in O_RDWR. * * The returned struct has a refcount of at least 1, use lur_device_unref() * to release resources associated with it. * It is the caller's responsibility to close the fd after the * resources associated with this object have been freed. * * @param fd An O_RDWR file descriptor pointing to a /dev/hidraw node * @param userdata Caller-specific data * @param lur Set to the lur object on success, otherwise unmodified * * @return 0 on success or a negative errno on error * @retval -EINVAL The fd does not point to a lur receiver * * @note liblur does not have OOM handling. If an allocation fails, liblur * will simply abort() */ int lur_receiver_new_from_hidraw(int fd, void *userdata, struct lur_receiver **lur); /** * Enumerate devices currently paired with the given receiver. * * liblur does not have a device detection mechanism, it is recommend that a * caller monitors udev for hidraw devices being added and removed. When * such devices appear or disappear, a call to lur_receiver_enumerate() * yields the new list of devices. If no new unifying devices are available, * the returned list is identical to the list returned in the last call to * lur_receiver_enumerate(). Otherwise, the diff between the two lists * indicate the set of newly added and/or removed devices. * * The devices returned have a refcount of at least 1, use * lur_device_unref(). Repeated calls to this function do not increase the * devices' refcount. * * @param lur A valid receiver object * @param[out] devices An array of devices paired with this receiver. Use * free() to free the array, and lur_device_unref() to destroy each device. * * @return The number of devices returned, or -1 on error. */ int lur_receiver_enumerate(struct lur_receiver *lur, struct lur_device ***devices); /** * Allow devices to be paired with this receiver for the given timeout. * Once 'open', the receiver will pair with a currently disconnected * device. * * @param lur A valid receiver object * @param timeout The time in seconds the receiver should accept new * pairings. The value 0 uses the receiver's default value (usually 30s). * * @return 0 on success or nonzero on error */ int lur_receiver_open(struct lur_receiver *lur, uint8_t timeout); /** * If a receiver is currently accepting devices, stop doing so. If the * receiver is not currently accepting devices, this function has no effect * and returns success. * * @param lur A valid receiver object * @return 0 on success or nonzero on error */ int lur_receiver_close(struct lur_receiver *lur); /** * Return the file descriptor used to initialize this receiver. * * @param lur A valid receiver object * @return The file descriptor passed into lur_receiver_new_from_hidraw */ int lur_receiver_get_fd(struct lur_receiver* lur); /** * Add a reference to the context. A context is destroyed whenever the * reference count reaches 0. See @ref lur_unref. * * @param lur A valid receiver object * @return The passed context */ struct lur_receiver * lur_receiver_ref(struct lur_receiver *lur); /** * Dereference the context. After this, the context may have been * destroyed, if the last reference was dereferenced. If so, the context is * invalid and may not be interacted with. * * @param lur A valid receiver object * @retval NULL */ struct lur_receiver * lur_receiver_unref(struct lur_receiver *lur); /** * Set caller-specific data associated with this object. liblur does * not manage, look at, or modify this data. The caller must ensure the * data is valid. * * Setting userdata overrides the one provided to * lur_receiver_new_from_hidraw(). * * @param lur A valid receiver object * @param userdata Caller-specific data passed to the various callback * interfaces. */ void lur_receiver_set_user_data(struct lur_receiver *lur, void *userdata); /** * Get the caller-specific data associated with this object, if any. * * @param lur A valid receiver object * @return The caller-specific data previously assigned in * lur_receiver_new_from_hidraw (or lur_receiver_set_user_data()). */ void* lur_receiver_get_user_data(const struct lur_receiver *lur); /** * Add a reference to the device. A device is destroyed whenever the * reference count reaches 0. See @ref lur_unref. * * @param lur A valid device object * @return The passed device */ struct lur_device * lur_device_ref(struct lur_device *dev); /** * Dereference the device. After this, the device may have been * destroyed, if the last reference was dereferenced. If so, the device is * invalid and may not be interacted with. * * @param lur A valid device object * @retval NULL */ struct lur_device * lur_device_unref(struct lur_device *dev); /** * Set caller-specific data associated with this object. liblur does * not manage, look at, or modify this data. The caller must ensure the * data is valid. * * @param dev A valid device object * @param userdata Caller-specific data */ void lur_device_set_user_data(struct lur_device *dev, void *userdata); /** * Get the caller-specific data associated with this object, if any. * * @param dev A valid device object * @return The caller-specific data previously assigned in * lur_device_set_user_data(). */ void* lur_device_get_user_data(const struct lur_device *dev); #ifdef __cplusplus } #endif libratbag-0.13/src/liblur.sym000066400000000000000000000010341362011324700161730ustar00rootroot00000000000000/* in alphabetical order! */ LIBLUR_0.4.0 { global: lur_device_disconnect; lur_device_get_name; lur_device_get_product_id; lur_device_get_serial; lur_device_get_type; lur_device_get_user_data; lur_device_get_vendor_id; lur_device_ref; lur_device_set_user_data; lur_device_unref; lur_is_receiver; lur_receiver_close; lur_receiver_enumerate; lur_receiver_get_fd; lur_receiver_get_user_data; lur_receiver_new_from_hidraw; lur_receiver_open; lur_receiver_ref; lur_receiver_set_user_data; lur_receiver_unref; local: *; }; libratbag-0.13/src/libratbag-data.c000066400000000000000000000354011362011324700171570ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include "config.h" #include #include #include #include #include #include "libratbag.h" #include "libratbag-private.h" #include "libratbag-data.h" #include "hidpp20.h" #define GROUP_DEVICE "Device" DEFINE_TRIVIAL_CLEANUP_FUNC(GKeyFile *, g_key_file_free); DEFINE_TRIVIAL_CLEANUP_FUNC(GError *, g_error_free); DEFINE_TRIVIAL_CLEANUP_FUNC(char **, g_strfreev); enum driver { NONE = 0, HIDPP10, HIDPP20, ROCCAT, ETEKCITY, GSKILL, LOGITECH_G300, LOGITECH_G600, STEELSERIES, }; struct data_hidpp20 { int index; enum hidpp20_quirk quirk; }; struct data_hidpp10 { int index; int profile_count; char *profile_type; struct dpi_list *dpi_list; struct dpi_range *dpi_range; int led_count; }; struct data_steelseries { int device_version; int button_count; int led_count; struct dpi_list *dpi_list; struct dpi_range *dpi_range; int macro_length; int mono_led; int short_button; }; struct ratbag_device_data { int refcount; char *name; char *driver; enum driver drivertype; union { struct data_hidpp20 hidpp20; struct data_hidpp10 hidpp10; struct data_steelseries steelseries; }; enum ratbag_led_type led_types[20]; size_t nled_types; }; static void init_data_hidpp10(struct ratbag *ratbag, GKeyFile *keyfile, struct ratbag_device_data *data) { const char *group = "Driver/hidpp10"; char *profile_type; GError *error = NULL; _cleanup_(freep) char *str = NULL; int num; data->hidpp10.index = -1; data->hidpp10.profile_count = -1; data->hidpp10.profile_type = NULL; data->hidpp10.led_count = -1; num = g_key_file_get_integer(keyfile, group, "DeviceIndex", &error); if (num != 0 || !error) data->hidpp10.index = num; if (error) g_error_free(error); error = NULL; num = g_key_file_get_integer(keyfile, group, "Profiles", &error); if (num > 0 || !error) data->hidpp10.profile_count = num; if (error) g_error_free(error); error = NULL; num = g_key_file_get_integer(keyfile, group, "Leds", &error); if (num > 0 || !error) data->hidpp10.led_count = num; if (error) g_error_free(error); profile_type = g_key_file_get_string(keyfile, group, "ProfileType", NULL); if (profile_type) data->hidpp10.profile_type = profile_type; str = g_key_file_get_string(keyfile, group, "DpiRange", NULL); if (str) { data->hidpp10.dpi_range = dpi_range_from_string(str); } else { str = g_key_file_get_string(keyfile, group, "DpiList", NULL); if (str) data->hidpp10.dpi_list = dpi_list_from_string(str); } } static void init_data_hidpp20(struct ratbag *ratbag, GKeyFile *keyfile, struct ratbag_device_data *data) { const char *group = "Driver/hidpp20"; GError *error = NULL; int num; char *str; data->hidpp20.index = -1; num = g_key_file_get_integer(keyfile, group, "DeviceIndex", &error); if (num != 0 || !error) data->hidpp20.index = num; if (error) g_error_free(error); str = g_key_file_get_string(keyfile, group, "Quirk", NULL); data->hidpp20.quirk = HIDPP20_QUIRK_NONE; if (str) { if (streq(str, "G305")) data->hidpp20.quirk = HIDPP20_QUIRK_G305; else if(streq(str, "G602")) data->hidpp20.quirk = HIDPP20_QUIRK_G602; } } static void init_data_steelseries(struct ratbag *ratbag, GKeyFile *keyfile, struct ratbag_device_data *data) { const char *group = "Driver/steelseries"; GError *error = NULL; _cleanup_(freep) char *str = NULL; int num; data->steelseries.device_version = -1; data->steelseries.button_count = -1; data->steelseries.led_count = -1; data->steelseries.dpi_list = NULL; data->steelseries.dpi_range = NULL; data->steelseries.mono_led = 0; data->steelseries.short_button = 0; num = g_key_file_get_integer(keyfile, group, "Buttons", &error); if (num != 0 || !error) data->steelseries.button_count = num; if (error) g_error_free(error); error = NULL; num = g_key_file_get_integer(keyfile, group, "Leds", &error); if (num > 0 || !error) data->steelseries.led_count = num; if (error) g_error_free(error); error = NULL; num = g_key_file_get_integer(keyfile, group, "DeviceVersion", &error); if (num > 0 || !error) data->steelseries.device_version = num; if (error) g_error_free(error); str = g_key_file_get_string(keyfile, group, "DpiRange", NULL); if (str) { data->steelseries.dpi_range = dpi_range_from_string(str); } else { str = g_key_file_get_string(keyfile, group, "DpiList", NULL); if (str) data->steelseries.dpi_list = dpi_list_from_string(str); } error = NULL; num = g_key_file_get_integer(keyfile, group, "MacroLength", &error); if (num > 0 || !error) data->steelseries.macro_length = num; if (error) g_error_free(error); error = NULL; num = g_key_file_get_integer(keyfile, group, "MonoLed", &error); if (num > 0 || !error) data->steelseries.mono_led = num; if (error) g_error_free(error); error = NULL; num = g_key_file_get_integer(keyfile, group, "ShortButton", &error); if (num > 0 || !error) data->steelseries.short_button = num; if (error) g_error_free(error); } static const struct driver_map { enum driver map; const char *driver; void (*init)(struct ratbag *ratbag, GKeyFile *keyfile, struct ratbag_device_data *data); } driver_map[] = { { HIDPP10, "hidpp10", init_data_hidpp10 }, { HIDPP20, "hidpp20", init_data_hidpp20 }, { ROCCAT, "roccat", NULL }, { ETEKCITY, "etekcity", NULL}, { GSKILL, "gskill", NULL }, { LOGITECH_G300, "logitech_g300", NULL}, { LOGITECH_G600, "logitech_g600", NULL}, { STEELSERIES, "steelseries", init_data_steelseries }, }; const char * ratbag_device_data_get_driver(const struct ratbag_device_data *data) { return data->driver; } const char * ratbag_device_data_get_name(const struct ratbag_device_data *data) { return data->name; } enum ratbag_led_type ratbag_device_data_get_led_type(const struct ratbag_device_data *data, unsigned int index) { assert(index < ARRAY_LENGTH(data->led_types)); return data->led_types[index]; } struct ratbag_device_data * ratbag_device_data_ref(struct ratbag_device_data *data) { data->refcount++; return data; } static void ratbag_device_data_destroy(struct ratbag_device_data *data) { switch (data->drivertype) { case HIDPP10: free(data->hidpp10.profile_type); break; default: break; } free(data->name); free(data->driver); free(data); } struct ratbag_device_data * ratbag_device_data_unref(struct ratbag_device_data *data) { if (data == NULL) return NULL; assert(data->refcount > 0); data->refcount--; if (data->refcount == 0) ratbag_device_data_destroy(data); return NULL; } DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbag_device_data *, ratbag_device_data_unref); static int parse_ledtypes(char **strv, enum ratbag_led_type *types, size_t ntypes) { unsigned int i; int count = 0; for (i = 0; i < ntypes; i++) types[i] = RATBAG_LED_TYPE_UNKNOWN; if (!strv) return count; i = 0; while(strv[i]) { const char *s = strv[i]; if (streq(s, "logo")) types[i] = RATBAG_LED_TYPE_LOGO; else if (streq(s, "side")) types[i] = RATBAG_LED_TYPE_SIDE; else if (streq(s, "dpi")) types[i] = RATBAG_LED_TYPE_SIDE; else if (streq(s, "battery")) types[i] = RATBAG_LED_TYPE_SIDE; else if (streq(s, "wheel")) types[i] = RATBAG_LED_TYPE_WHEEL; else if (streq(s, "switches")) types[i] = RATBAG_LED_TYPE_SWITCHES; else return -1; count++; i++; } return count; } static bool match(const struct input_id *id, char **strv) { const char *bus; char str[64]; int i = 0; switch (id->bustype) { case BUS_USB: bus = "usb"; break; case BUS_BLUETOOTH: bus = "bluetooth"; break; default: return false; } snprintf(str, sizeof(str), "%s:%04x:%04x", bus, id->vendor, id->product); while (strv[i]) { if (streq(strv[i], str)) return true; i++; } return false; } static bool file_data_matches(struct ratbag *ratbag, const char *path, const struct input_id *id, struct ratbag_device_data **data_out) { _cleanup_(g_key_file_freep) GKeyFile *keyfile = NULL; _cleanup_(g_error_freep) GError *error = NULL; _cleanup_(g_strfreevp) char **match_strv = NULL; _cleanup_(g_strfreevp) char **ledtypes_strv = NULL; _cleanup_(ratbag_device_data_unrefp) struct ratbag_device_data *data = NULL; int rc; keyfile = g_key_file_new(); rc = g_key_file_load_from_file(keyfile, path, G_KEY_FILE_NONE, &error); if (!rc) { log_error(ratbag, "Failed to parse keyfile %s: %s\n", path, error->message); return false; } match_strv = g_key_file_get_string_list(keyfile, GROUP_DEVICE, "DeviceMatch", NULL, NULL); if (!match_strv) { log_error(ratbag, "Missing DeviceMatch in %s\n", basename(path)); return false; } if (!match(id, match_strv)) return false; data = zalloc(sizeof(*data)); data->refcount = 1; data->name = g_key_file_get_string(keyfile, GROUP_DEVICE, "Name", NULL); if (!data->name) { return false; // ignore_clang_sa_mem_leak } data->driver = g_key_file_get_string(keyfile, GROUP_DEVICE, "Driver", NULL); if (!data->driver) { log_error(ratbag, "Missing Driver in %s\n", basename(path)); return false; } else { const struct driver_map *map; data->drivertype = NONE; ARRAY_FOR_EACH(driver_map, map) { if (streq(map->driver, data->driver)) { data->drivertype = map->map; if (map->init) map->init(ratbag, keyfile, data); break; } } if (data->drivertype == NONE) { log_error(ratbag, "Unknown driver %s in %s\n", data->driver, basename(path)); return false; } } ledtypes_strv = g_key_file_get_string_list(keyfile, GROUP_DEVICE, "LedTypes", NULL, NULL); if (parse_ledtypes(ledtypes_strv, data->led_types, ARRAY_LENGTH(data->led_types)) < 0) { log_error(ratbag, "Invalid LedTypes string in '%s'\n", basename(path)); return false; } *data_out = data; data = NULL; return true; } static int filter_device_files(const struct dirent *entry) { const char *SUFFIX = ".device"; const char *name = entry->d_name; int len, slen; if (!name || name[0] == '.') return 0; len = strlen(name); slen = strlen(SUFFIX); if (len <= slen) return 0; return streq(&name[len - slen], SUFFIX); } struct ratbag_device_data * ratbag_device_data_new_for_id(struct ratbag *ratbag, const struct input_id *id) { struct ratbag_device_data *data = NULL; struct dirent **files; int n, nfiles; const char *datadir; datadir = getenv("LIBRATBAG_DATA_DIR"); if (!datadir) datadir = LIBRATBAG_DATA_DIR; log_debug(ratbag, "Using data directory '%s'\n", datadir); n = scandir(datadir, &files, filter_device_files, alphasort); if (n <= 0) { log_error(ratbag, "Unable to locate device files in %s: %s\n", datadir, n == 0 ? "No files found" : strerror(errno)); return NULL; } nfiles = n; while(n--) { _cleanup_(freep) char *file = NULL; int rc; rc = xasprintf(&file, "%s/%s", datadir, files[n]->d_name); if (rc == -1) goto out; if (file_data_matches(ratbag, file, id, &data)) goto out; } if (!data) log_debug(ratbag, "No data file found for %04x:%04x\n", id->vendor, id->product); out: while(nfiles--) free(files[nfiles]); free(files); return data; } /* HID++ 1.0 */ int ratbag_device_data_hidpp10_get_index(const struct ratbag_device_data *data) { assert(data->drivertype == HIDPP10); return data->hidpp10.index; } int ratbag_device_data_hidpp10_get_profile_count(const struct ratbag_device_data *data) { assert(data->drivertype == HIDPP10); return data->hidpp10.profile_count; } const char * ratbag_device_data_hidpp10_get_profile_type(const struct ratbag_device_data *data) { assert(data->drivertype == HIDPP10); return data->hidpp10.profile_type; } struct dpi_list * ratbag_device_data_hidpp10_get_dpi_list(const struct ratbag_device_data *data) { assert(data->drivertype == HIDPP10); return data->hidpp10.dpi_list; } struct dpi_range * ratbag_device_data_hidpp10_get_dpi_range(const struct ratbag_device_data *data) { assert(data->drivertype == HIDPP10); return data->hidpp10.dpi_range; } int ratbag_device_data_hidpp10_get_led_count(const struct ratbag_device_data *data) { assert(data->drivertype == HIDPP10); return data->hidpp10.led_count; } /* HID++ 2.0 */ int ratbag_device_data_hidpp20_get_index(const struct ratbag_device_data *data) { assert(data->drivertype == HIDPP20); return data->hidpp20.index; } enum hidpp20_quirk ratbag_device_data_hidpp20_get_quirk(const struct ratbag_device_data *data) { assert(data->drivertype == HIDPP20); return data->hidpp20.quirk; } /* SteelSeries */ int ratbag_device_data_steelseries_get_device_version(const struct ratbag_device_data *data) { assert(data->drivertype == STEELSERIES); return data->steelseries.device_version; } int ratbag_device_data_steelseries_get_button_count(const struct ratbag_device_data *data) { assert(data->drivertype == STEELSERIES); return data->steelseries.button_count; } int ratbag_device_data_steelseries_get_led_count(const struct ratbag_device_data *data) { assert(data->drivertype == STEELSERIES); return data->steelseries.led_count; } struct dpi_list * ratbag_device_data_steelseries_get_dpi_list(const struct ratbag_device_data *data) { assert(data->drivertype == STEELSERIES); return data->steelseries.dpi_list; } struct dpi_range * ratbag_device_data_steelseries_get_dpi_range(const struct ratbag_device_data *data) { assert(data->drivertype == STEELSERIES); return data->steelseries.dpi_range; } int ratbag_device_data_steelseries_get_macro_length(const struct ratbag_device_data *data) { assert(data->drivertype == STEELSERIES); return data->steelseries.macro_length; } int ratbag_device_data_steelseries_get_mono_led(const struct ratbag_device_data *data) { assert(data->drivertype == STEELSERIES); return data->steelseries.mono_led; } int ratbag_device_data_steelseries_get_short_button(const struct ratbag_device_data *data) { assert(data->drivertype == STEELSERIES); return data->steelseries.short_button; } libratbag-0.13/src/libratbag-data.h000066400000000000000000000071521362011324700171660ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #pragma once struct ratbag_device_data; struct ratbag_device_data * ratbag_device_data_new_for_id(struct ratbag *ratbag, const struct input_id *id); struct ratbag_device_data * ratbag_device_data_unref(struct ratbag_device_data *data); struct ratbag_device_data * ratbag_device_data_ref(struct ratbag_device_data *data); const char * ratbag_device_data_get_driver(const struct ratbag_device_data *data); const char * ratbag_device_data_get_name(const struct ratbag_device_data *data); enum ratbag_led_type ratbag_device_data_get_led_type(const struct ratbag_device_data *data, unsigned int index); /* HID++ 1.0 */ /** * @return The device index or -1 if not set */ int ratbag_device_data_hidpp10_get_index(const struct ratbag_device_data *data); const char * ratbag_device_data_hidpp10_get_profile_type(const struct ratbag_device_data *data); /** * @return The profile count index or -1 if not set */ int ratbag_device_data_hidpp10_get_profile_count(const struct ratbag_device_data *data); struct dpi_list * ratbag_device_data_hidpp10_get_dpi_list(const struct ratbag_device_data *data); struct dpi_range * ratbag_device_data_hidpp10_get_dpi_range(const struct ratbag_device_data *data); /** * @return The led count index or -1 if not set */ int ratbag_device_data_hidpp10_get_led_count(const struct ratbag_device_data *data); /* HID++ 2.0 */ /** * @return The device index or -1 if not set */ int ratbag_device_data_hidpp20_get_index(const struct ratbag_device_data *data); enum hidpp20_quirk ratbag_device_data_hidpp20_get_quirk(const struct ratbag_device_data *data); /* SteelSeries */ /** * @return The device version or -1 if not set */ int ratbag_device_data_steelseries_get_device_version(const struct ratbag_device_data *data); /** * @return The button count or -1 if not set */ int ratbag_device_data_steelseries_get_button_count(const struct ratbag_device_data *data); /** * @return The led count or -1 if not set */ int ratbag_device_data_steelseries_get_led_count(const struct ratbag_device_data *data); struct dpi_list * ratbag_device_data_steelseries_get_dpi_list(const struct ratbag_device_data *data); struct dpi_range * ratbag_device_data_steelseries_get_dpi_range(const struct ratbag_device_data *data); int ratbag_device_data_steelseries_get_macro_length(const struct ratbag_device_data *data); int ratbag_device_data_steelseries_get_mono_led(const struct ratbag_device_data *data); int ratbag_device_data_steelseries_get_short_button(const struct ratbag_device_data *data); libratbag-0.13/src/libratbag-enums.h000066400000000000000000000210361362011324700174010ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #pragma once /** * This file contains enum values that are used in the DBus API and thus * considered ABI. */ /** * @defgroup enums Enumerations used in the DBus API */ /** * @ingroup enums * * Error codes used by libratbag. */ enum ratbag_error_code { RATBAG_SUCCESS = 0, /** * An error occured on the device. Either the device is not a * libratbag device or communication with the device failed. */ RATBAG_ERROR_DEVICE = -1000, /** * Insufficient capabilities. This error occurs when a requested change is * beyond the device's capabilities. */ RATBAG_ERROR_CAPABILITY = -1001, /** * Invalid value or value range. The provided value or value range * is outside of the legal or supported range. */ RATBAG_ERROR_VALUE = -1002, /** * A low-level system error has occured, e.g. a failure to access * files that should be there. This error is usually unrecoverable * and libratbag will print a log message with details about the * error. */ RATBAG_ERROR_SYSTEM = -1003, /** * Implementation bug, either in libratbag or in the caller. This * error is usually unrecoverable and libratbag will print a log * message with details about the * error. */ RATBAG_ERROR_IMPLEMENTATION = -1004, }; /** * @ingroup enums */ enum ratbag_profile_capability { RATBAG_PROFILE_CAP_NONE = 0, /** * This profile can be assigned as the default profile. A default * profile is the profile that is selected when the device * is plugged in. Where no profile is assigned as the default * profile, the device either picks last-used profile or a specific * profile (usually the first). */ RATBAG_PROFILE_CAP_SET_DEFAULT = 101, /** * The profile can be disabled and enabled. Profiles are not * immediately deleted after being disabled, it is not guaranteed * that the device will remember any disabled profiles the next time * ratbag runs. Furthermore, the order of profiles may get changed * the next time ratbag runs if profiles are disabled. * * Note that this capability only notes the general capability. A * specific profile may still fail to be disabled, e.g. when it is * the last enabled profile on the device. */ RATBAG_PROFILE_CAP_DISABLE, /** * The profile information cannot be queried from the hardware. * Where this capability is present, libratbag cannot * query the device for its current configuration and the * configured resolutions and button mappings are unknown. * libratbag will still provide information about the structure of * the device such as the number of buttons and resolutions. * Clients that encounter a device without this resolution are * encouraged to upload a configuration stored on-disk to the * device to reset the device to a known state. * * Any changes uploaded to the device will be cached in libratbag, * once a client has sent a full configuration to the device * libratbag can be used to query the device as normal. */ RATBAG_PROFILE_CAP_WRITE_ONLY, }; /** * @ingroup enums */ enum ratbag_resolution_capability { /** * The resolution can be set for x and y separately. */ RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION = 1, }; /** * @ingroup enums * * Button types describing the physical button. * * This enum is deprecated and should not be used. * * @deprecated */ enum ratbag_button_type { RATBAG_BUTTON_TYPE_UNKNOWN = 0, /* mouse buttons */ RATBAG_BUTTON_TYPE_LEFT, RATBAG_BUTTON_TYPE_MIDDLE, RATBAG_BUTTON_TYPE_RIGHT, RATBAG_BUTTON_TYPE_THUMB, RATBAG_BUTTON_TYPE_THUMB2, RATBAG_BUTTON_TYPE_THUMB3, RATBAG_BUTTON_TYPE_THUMB4, RATBAG_BUTTON_TYPE_WHEEL_LEFT, RATBAG_BUTTON_TYPE_WHEEL_RIGHT, RATBAG_BUTTON_TYPE_WHEEL_CLICK, RATBAG_BUTTON_TYPE_WHEEL_UP, RATBAG_BUTTON_TYPE_WHEEL_DOWN, /** * A button to toggle the wheel from free-spinning to click-based. */ RATBAG_BUTTON_TYPE_WHEEL_RATCHET_MODE_SHIFT, RATBAG_BUTTON_TYPE_EXTRA, RATBAG_BUTTON_TYPE_SIDE, RATBAG_BUTTON_TYPE_PINKIE, RATBAG_BUTTON_TYPE_PINKIE2, /* DPI switch */ RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP, RATBAG_BUTTON_TYPE_RESOLUTION_UP, RATBAG_BUTTON_TYPE_RESOLUTION_DOWN, /* Profile */ RATBAG_BUTTON_TYPE_PROFILE_CYCLE_UP, RATBAG_BUTTON_TYPE_PROFILE_UP, RATBAG_BUTTON_TYPE_PROFILE_DOWN, }; /** * @ingroup enums * * The type assigned to a button. */ enum ratbag_button_action_type { /** * Button is disabled */ RATBAG_BUTTON_ACTION_TYPE_NONE = 0, /** * Button sends numeric button events */ RATBAG_BUTTON_ACTION_TYPE_BUTTON, /** * Button triggers a mouse-specific special function. This includes * resolution changes and profile changes. */ RATBAG_BUTTON_ACTION_TYPE_SPECIAL, /** * Button sends a key or key + modifier combination */ RATBAG_BUTTON_ACTION_TYPE_KEY, /** * Button sends a user-defined key or button sequence */ RATBAG_BUTTON_ACTION_TYPE_MACRO, /** * Button action is unknown */ RATBAG_BUTTON_ACTION_TYPE_UNKNOWN = 1000, }; /** * @ingroup enums */ enum ratbag_button_action_special { /** * This button is not set up for a special action */ RATBAG_BUTTON_ACTION_SPECIAL_INVALID = -1, RATBAG_BUTTON_ACTION_SPECIAL_UNKNOWN = (1 << 30), RATBAG_BUTTON_ACTION_SPECIAL_DOUBLECLICK, /* Wheel mappings */ RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT, RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT, RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP, RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN, RATBAG_BUTTON_ACTION_SPECIAL_RATCHET_MODE_SWITCH, /* DPI switch */ RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP, RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_DOWN, RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP, RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN, RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE, RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT, /* Profile */ RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP, RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_DOWN, RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP, RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN, /* second mode for buttons */ RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE, /* battery level */ RATBAG_BUTTON_ACTION_SPECIAL_BATTERY_LEVEL, }; /** * @ingroup enums * * Each LED mode has different properties, e.g. the brightness and rate are only * available in modes @ref RATBAG_LED_CYCLE and @ref RATBAG_LED_BREATHING modes */ enum ratbag_led_mode { /** * led is now off */ RATBAG_LED_OFF = 0, /** * led is on with static color */ RATBAG_LED_ON, /** * led is cycling between all colors */ RATBAG_LED_CYCLE, /** * led is pulsating with static color */ RATBAG_LED_BREATHING, }; /** * @ingroup enums * * LED types, usually based on their physical location * * This enum is deprecated and should not be used. * * @deprecated */ enum ratbag_led_type { RATBAG_LED_TYPE_LOGO = 1, RATBAG_LED_TYPE_SIDE, RATBAG_LED_TYPE_BATTERY, RATBAG_LED_TYPE_DPI, RATBAG_LED_TYPE_WHEEL, RATBAG_LED_TYPE_SWITCHES, }; /** * @ingroup enums */ enum ratbag_led_colordepth { /** * The device only supports a single color. * All color components should be set to 255. */ RATBAG_LED_COLORDEPTH_MONOCHROME = 0, /** * The device supports RBG color with 8 bits per color. */ RATBAG_LED_COLORDEPTH_RGB_888, /** * The device supports RBG colors with 1 bit per color. */ RATBAG_LED_COLORDEPTH_RGB_111, }; /** * @ingroup enums * * Macro event types describing the event. */ enum ratbag_macro_event_type { RATBAG_MACRO_EVENT_INVALID = -1, RATBAG_MACRO_EVENT_NONE = 0, RATBAG_MACRO_EVENT_KEY_PRESSED, RATBAG_MACRO_EVENT_KEY_RELEASED, RATBAG_MACRO_EVENT_WAIT, }; libratbag-0.13/src/libratbag-hidraw.c000066400000000000000000001544521362011324700175340ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include "config.h" #include #include #include #include #include #include #include "libratbag-hidraw.h" #include "libratbag-private.h" #ifndef KEY_SCREENSAVER #define KEY_SCREENSAVER 0x245 #endif #ifndef KEY_VOICECOMMAND #define KEY_VOICECOMMAND 0x246 #endif /* defined in include/linux.hid.h in the kernel, but not exported */ #ifndef HID_MAX_BUFFER_SIZE #define HID_MAX_BUFFER_SIZE 4096 /* 4kb */ #endif #define HID_REPORT_ID 0b10000100 #define HID_COLLECTION 0b10100000 #define HID_USAGE_PAGE 0b00000100 #define HID_USAGE 0b00001000 #define HID_PHYSICAL 0 #define HID_APPLICATION 1 #define HID_LOGICAL 2 #define HID_KEY_RESERVED 0x00 /* Reserved (no event indicated) */ #define HID_KEY_ERRORROLLOVER 0x01 /* ErrorRollOver */ #define HID_KEY_POSTFAIL 0x02 /* POSTFail */ #define HID_KEY_ERRORUNDEFINE 0x03 /* ErrorUndefine */ #define HID_KEY_A 0x04 /* a and A */ #define HID_KEY_B 0x05 /* b and B */ #define HID_KEY_C 0x06 /* c and C */ #define HID_KEY_D 0x07 /* d and D */ #define HID_KEY_E 0x08 /* e and E */ #define HID_KEY_F 0x09 /* f and F */ #define HID_KEY_G 0x0A /* g and G */ #define HID_KEY_H 0x0B /* h and H */ #define HID_KEY_I 0x0C /* i and I */ #define HID_KEY_J 0x0D /* j and J */ #define HID_KEY_K 0x0E /* k and K */ #define HID_KEY_L 0x0F /* l and L */ #define HID_KEY_M 0x10 /* m and M */ #define HID_KEY_N 0x11 /* n and N */ #define HID_KEY_O 0x12 /* o and O */ #define HID_KEY_P 0x13 /* p and P */ #define HID_KEY_Q 0x14 /* q and Q */ #define HID_KEY_R 0x15 /* r and R */ #define HID_KEY_S 0x16 /* s and S */ #define HID_KEY_T 0x17 /* t and T */ #define HID_KEY_U 0x18 /* u and U */ #define HID_KEY_V 0x19 /* v and V */ #define HID_KEY_W 0x1A /* w and W */ #define HID_KEY_X 0x1B /* x and X */ #define HID_KEY_Y 0x1C /* y and Y */ #define HID_KEY_Z 0x1D /* z and Z */ #define HID_KEY_1 0x1E /* 1 and ! */ #define HID_KEY_2 0x1F /* 2 and @ */ #define HID_KEY_3 0x20 /* 3 and # */ #define HID_KEY_4 0x21 /* 4 and $ */ #define HID_KEY_5 0x22 /* 5 and % */ #define HID_KEY_6 0x23 /* 6 and ^ */ #define HID_KEY_7 0x24 /* 7 and & */ #define HID_KEY_8 0x25 /* 8 and * */ #define HID_KEY_9 0x26 /* 9 and ( */ #define HID_KEY_0 0x27 /* 0 and ) */ #define HID_KEY_RETURN_ENTER 0x28 /* Return (ENTER) */ #define HID_KEY_ESCAPE 0x29 /* ESCAPE */ #define HID_KEY_DELETE_BACKSPACE 0x2A /* DELETE (Backspace) */ #define HID_KEY_TAB 0x2B /* Tab */ #define HID_KEY_SPACEBAR 0x2C /* Spacebar */ #define HID_KEY_MINUS_AND_UNDERSCORE 0x2D /* - and (underscore) */ #define HID_KEY_EQUAL_AND_PLUS 0x2E /* = and + */ #define HID_KEY_CLOSE_BRACKET 0x2F /* [ and { */ #define HID_KEY_OPEN_BRACKET 0x30 /* ] and } */ #define HID_KEY_BACK_SLASH_AND_PIPE 0x31 /* \ and | */ #define HID_KEY_NON_US_HASH_AND_TILDE 0x32 /* Non-US # and ~ */ #define HID_KEY_SEMICOLON_AND_COLON 0x33 /* ; and : */ #define HID_KEY_QUOTE_AND_DOUBLEQUOTE 0x34 /* ' and " */ #define HID_KEY_GRAVE_ACCENT_AND_TILDE 0x35 /* Grave Accent and Tilde */ #define HID_KEY_COMMA_AND_LESSER_THAN 0x36 /* Keyboard, and < */ #define HID_KEY_PERIOD_AND_GREATER_THAN 0x37 /* . and > */ #define HID_KEY_SLASH_AND_QUESTION_MARK 0x38 /* / and ? */ #define HID_KEY_CAPS_LOCK 0x39 /* Caps Lock */ #define HID_KEY_F1 0x3A /* F1 */ #define HID_KEY_F2 0x3B /* F2 */ #define HID_KEY_F3 0x3C /* F3 */ #define HID_KEY_F4 0x3D /* F4 */ #define HID_KEY_F5 0x3E /* F5 */ #define HID_KEY_F6 0x3F /* F6 */ #define HID_KEY_F7 0x40 /* F7 */ #define HID_KEY_F8 0x41 /* F8 */ #define HID_KEY_F9 0x42 /* F9 */ #define HID_KEY_F10 0x43 /* F10 */ #define HID_KEY_F11 0x44 /* F11 */ #define HID_KEY_F12 0x45 /* F12 */ #define HID_KEY_PRINTSCREEN 0x46 /* PrintScreen */ #define HID_KEY_SCROLL_LOCK 0x47 /* Scroll Lock */ #define HID_KEY_PAUSE 0x48 /* Pause */ #define HID_KEY_INSERT 0x49 /* Insert */ #define HID_KEY_HOME 0x4A /* Home */ #define HID_KEY_PAGEUP 0x4B /* PageUp */ #define HID_KEY_DELETE_FORWARD 0x4C /* Delete Forward */ #define HID_KEY_END 0x4D /* End */ #define HID_KEY_PAGEDOWN 0x4E /* PageDown */ #define HID_KEY_RIGHTARROW 0x4F /* RightArrow */ #define HID_KEY_LEFTARROW 0x50 /* LeftArrow */ #define HID_KEY_DOWNARROW 0x51 /* DownArrow */ #define HID_KEY_UPARROW 0x52 /* UpArrow */ #define HID_KEY_KEYPAD_NUM_LOCK_AND_CLEAR 0x53 /* Keypad Num Lock and Clear */ #define HID_KEY_KEYPAD_SLASH 0x54 /* Keypad / */ #define HID_KEY_KEYPAD_ASTERISK 0x55 /* Keypad * */ #define HID_KEY_KEYPAD_MINUS 0x56 /* Keypad - */ #define HID_KEY_KEYPAD_PLUS 0x57 /* Keypad + */ #define HID_KEY_KEYPAD_ENTER 0x58 /* Keypad ENTER */ #define HID_KEY_KEYPAD_1_AND_END 0x59 /* Keypad 1 and End */ #define HID_KEY_KEYPAD_2_AND_DOWN_ARROW 0x5A /* Keypad 2 and Down Arrow */ #define HID_KEY_KEYPAD_3_AND_PAGEDN 0x5B /* Keypad 3 and PageDn */ #define HID_KEY_KEYPAD_4_AND_LEFT_ARROW 0x5C /* Keypad 4 and Left Arrow */ #define HID_KEY_KEYPAD_5 0x5D /* Keypad 5 */ #define HID_KEY_KEYPAD_6_AND_RIGHT_ARROW 0x5E /* Keypad 6 and Right Arrow */ #define HID_KEY_KEYPAD_7_AND_HOME 0x5F /* Keypad 7 and Home */ #define HID_KEY_KEYPAD_8_AND_UP_ARROW 0x60 /* Keypad 8 and Up Arrow */ #define HID_KEY_KEYPAD_9_AND_PAGEUP 0x61 /* Keypad 9 and PageUp */ #define HID_KEY_KEYPAD_0_AND_INSERT 0x62 /* Keypad 0 and Insert */ #define HID_KEY_KEYPAD_PERIOD_AND_DELETE 0x63 /* Keypad . and Delete */ #define HID_KEY_NON_US_BACKSLASH_AND_PIPE 0x64 /* Non-US \ and | */ #define HID_KEY_APPLICATION 0x65 /* Application */ #define HID_KEY_POWER 0x66 /* Power */ #define HID_KEY_KEYPAD_EQUAL 0x67 /* Keypad = */ #define HID_KEY_F13 0x68 /* F13 */ #define HID_KEY_F14 0x69 /* F14 */ #define HID_KEY_F15 0x6A /* F15 */ #define HID_KEY_F16 0x6B /* F16 */ #define HID_KEY_F17 0x6C /* F17 */ #define HID_KEY_F18 0x6D /* F18 */ #define HID_KEY_F19 0x6E /* F19 */ #define HID_KEY_F20 0x6F /* F20 */ #define HID_KEY_F21 0x70 /* F21 */ #define HID_KEY_F22 0x71 /* F22 */ #define HID_KEY_F23 0x72 /* F23 */ #define HID_KEY_F24 0x73 /* F24 */ #define HID_KEY_EXECUTE 0x74 /* Execute */ #define HID_KEY_HELP 0x75 /* Help */ #define HID_KEY_MENU 0x76 /* Menu */ #define HID_KEY_SELECT 0x77 /* Select */ #define HID_KEY_STOP 0x78 /* Stop */ #define HID_KEY_AGAIN 0x79 /* Again */ #define HID_KEY_UNDO 0x7A /* Undo */ #define HID_KEY_CUT 0x7B /* Cut */ #define HID_KEY_COPY 0x7C /* Copy */ #define HID_KEY_PASTE 0x7D /* Paste */ #define HID_KEY_FIND 0x7E /* Find */ #define HID_KEY_MUTE 0x7F /* Mute */ #define HID_KEY_VOLUME_UP 0x80 /* Volume Up */ #define HID_KEY_VOLUME_DOWN 0x81 /* Volume Down */ #define HID_KEY_LOCKING_CAPS_LOCK 0x82 /* Locking Caps Lock */ #define HID_KEY_LOCKING_NUM_LOCK 0x83 /* Locking Num Lock */ #define HID_KEY_LOCKING_SCROLL_LOCK 0x84 /* Locking Scroll Lock */ #define HID_KEY_KEYPAD_COMMA 0x85 /* Keypad Comma */ #define HID_KEY_KEYPAD_EQUAL_SIGN 0x86 /* Keypad Equal Sign */ #define HID_KEY_KANJI1 0x87 /* Kanji1 */ #define HID_KEY_KANJI2 0x88 /* Kanji2 */ #define HID_KEY_KANJI3 0x89 /* Kanji3 */ #define HID_KEY_KANJI4 0x8A /* Kanji4 */ #define HID_KEY_KANJI5 0x8B /* Kanji5 */ #define HID_KEY_KANJI6 0x8C /* Kanji6 */ #define HID_KEY_KANJI7 0x8D /* Kanji7 */ #define HID_KEY_KANJI8 0x8E /* Kanji8 */ #define HID_KEY_KANJI9 0x8F /* Kanji9 */ #define HID_KEY_LANG1 0x90 /* LANG1 */ #define HID_KEY_LANG2 0x91 /* LANG2 */ #define HID_KEY_LANG3 0x92 /* LANG3 */ #define HID_KEY_LANG4 0x93 /* LANG4 */ #define HID_KEY_LANG5 0x94 /* LANG5 */ #define HID_KEY_LANG6 0x95 /* LANG6 */ #define HID_KEY_LANG7 0x96 /* LANG7 */ #define HID_KEY_LANG8 0x97 /* LANG8 */ #define HID_KEY_LANG9 0x98 /* LANG9 */ #define HID_KEY_ALTERNATE_ERASE 0x99 /* Alternate Erase */ #define HID_KEY_SYSREQ_ATTENTION 0x9A /* SysReq/Attention */ #define HID_KEY_CANCEL 0x9B /* Cancel */ #define HID_KEY_CLEAR 0x9C /* Clear */ #define HID_KEY_PRIOR 0x9D /* Prior */ #define HID_KEY_RETURN 0x9E /* Return */ #define HID_KEY_SEPARATOR 0x9F /* Separator */ #define HID_KEY_OUT 0xA0 /* Out */ #define HID_KEY_OPER 0xA1 /* Oper */ #define HID_KEY_CLEAR_AGAIN 0xA2 /* Clear/Again */ #define HID_KEY_CRSEL_PROPS 0xA3 /* CrSel/Props */ #define HID_KEY_EXSEL 0xA4 /* ExSel */ /* RESERVED 0xA5-DF */ /* Reserved */ #define HID_KEY_LEFTCONTROL 0xE0 /* LeftControl */ #define HID_KEY_LEFTSHIFT 0xE1 /* LeftShift */ #define HID_KEY_LEFTALT 0xE2 /* LeftAlt */ #define HID_KEY_LEFT_GUI 0xE3 /* Left GUI */ #define HID_KEY_RIGHTCONTROL 0xE4 /* RightControl */ #define HID_KEY_RIGHTSHIFT 0xE5 /* RightShift */ #define HID_KEY_RIGHTALT 0xE6 /* RightAlt */ #define HID_KEY_RIGHT_GUI 0xE7 /* Right GUI */ static const unsigned int hid_keyboard_mapping[] = { [HID_KEY_RESERVED ] = 0, [HID_KEY_ERRORROLLOVER ] = 0, [HID_KEY_POSTFAIL ] = 0, [HID_KEY_ERRORUNDEFINE ] = 0, [HID_KEY_A ] = KEY_A, [HID_KEY_B ] = KEY_B, [HID_KEY_C ] = KEY_C, [HID_KEY_D ] = KEY_D, [HID_KEY_E ] = KEY_E, [HID_KEY_F ] = KEY_F, [HID_KEY_G ] = KEY_G, [HID_KEY_H ] = KEY_H, [HID_KEY_I ] = KEY_I, [HID_KEY_J ] = KEY_J, [HID_KEY_K ] = KEY_K, [HID_KEY_L ] = KEY_L, [HID_KEY_M ] = KEY_M, [HID_KEY_N ] = KEY_N, [HID_KEY_O ] = KEY_O, [HID_KEY_P ] = KEY_P, [HID_KEY_Q ] = KEY_Q, [HID_KEY_R ] = KEY_R, [HID_KEY_S ] = KEY_S, [HID_KEY_T ] = KEY_T, [HID_KEY_U ] = KEY_U, [HID_KEY_V ] = KEY_V, [HID_KEY_W ] = KEY_W, [HID_KEY_X ] = KEY_X, [HID_KEY_Y ] = KEY_Y, [HID_KEY_Z ] = KEY_Z, [HID_KEY_1 ] = KEY_1, [HID_KEY_2 ] = KEY_2, [HID_KEY_3 ] = KEY_3, [HID_KEY_4 ] = KEY_4, [HID_KEY_5 ] = KEY_5, [HID_KEY_6 ] = KEY_6, [HID_KEY_7 ] = KEY_7, [HID_KEY_8 ] = KEY_8, [HID_KEY_9 ] = KEY_9, [HID_KEY_0 ] = KEY_0, [HID_KEY_RETURN_ENTER ] = KEY_ENTER, [HID_KEY_ESCAPE ] = KEY_ESC, [HID_KEY_DELETE_BACKSPACE ] = KEY_BACKSPACE, [HID_KEY_TAB ] = KEY_TAB, [HID_KEY_SPACEBAR ] = KEY_SPACE, [HID_KEY_MINUS_AND_UNDERSCORE ] = KEY_MINUS, [HID_KEY_EQUAL_AND_PLUS ] = KEY_EQUAL, [HID_KEY_CLOSE_BRACKET ] = KEY_LEFTBRACE, [HID_KEY_OPEN_BRACKET ] = KEY_RIGHTBRACE, [HID_KEY_BACK_SLASH_AND_PIPE ] = KEY_BACKSLASH, [HID_KEY_NON_US_HASH_AND_TILDE ] = KEY_BACKSLASH, [HID_KEY_SEMICOLON_AND_COLON ] = KEY_SEMICOLON, [HID_KEY_QUOTE_AND_DOUBLEQUOTE ] = KEY_APOSTROPHE, [HID_KEY_GRAVE_ACCENT_AND_TILDE ] = KEY_GRAVE, [HID_KEY_COMMA_AND_LESSER_THAN ] = KEY_COMMA, [HID_KEY_PERIOD_AND_GREATER_THAN ] = KEY_DOT, [HID_KEY_SLASH_AND_QUESTION_MARK ] = KEY_SLASH, [HID_KEY_CAPS_LOCK ] = KEY_CAPSLOCK, [HID_KEY_F1 ] = KEY_F1, [HID_KEY_F2 ] = KEY_F2, [HID_KEY_F3 ] = KEY_F3, [HID_KEY_F4 ] = KEY_F4, [HID_KEY_F5 ] = KEY_F5, [HID_KEY_F6 ] = KEY_F6, [HID_KEY_F7 ] = KEY_F7, [HID_KEY_F8 ] = KEY_F8, [HID_KEY_F9 ] = KEY_F9, [HID_KEY_F10 ] = KEY_F10, [HID_KEY_F11 ] = KEY_F11, [HID_KEY_F12 ] = KEY_F12, [HID_KEY_PRINTSCREEN ] = KEY_SYSRQ, [HID_KEY_SCROLL_LOCK ] = KEY_SCROLLLOCK, [HID_KEY_PAUSE ] = KEY_PAUSE, [HID_KEY_INSERT ] = KEY_INSERT, [HID_KEY_HOME ] = KEY_HOME, [HID_KEY_PAGEUP ] = KEY_PAGEUP, [HID_KEY_DELETE_FORWARD ] = KEY_DELETE, [HID_KEY_END ] = KEY_END, [HID_KEY_PAGEDOWN ] = KEY_PAGEDOWN, [HID_KEY_RIGHTARROW ] = KEY_RIGHT, [HID_KEY_LEFTARROW ] = KEY_LEFT, [HID_KEY_DOWNARROW ] = KEY_DOWN, [HID_KEY_UPARROW ] = KEY_UP, [HID_KEY_KEYPAD_NUM_LOCK_AND_CLEAR ] = KEY_NUMLOCK, [HID_KEY_KEYPAD_SLASH ] = KEY_KPSLASH, [HID_KEY_KEYPAD_ASTERISK ] = KEY_KPASTERISK, [HID_KEY_KEYPAD_MINUS ] = KEY_KPMINUS, [HID_KEY_KEYPAD_PLUS ] = KEY_KPPLUS, [HID_KEY_KEYPAD_ENTER ] = KEY_KPENTER, [HID_KEY_KEYPAD_1_AND_END ] = KEY_KP1, [HID_KEY_KEYPAD_2_AND_DOWN_ARROW ] = KEY_KP2, [HID_KEY_KEYPAD_3_AND_PAGEDN ] = KEY_KP3, [HID_KEY_KEYPAD_4_AND_LEFT_ARROW ] = KEY_KP4, [HID_KEY_KEYPAD_5 ] = KEY_KP5, [HID_KEY_KEYPAD_6_AND_RIGHT_ARROW ] = KEY_KP6, [HID_KEY_KEYPAD_7_AND_HOME ] = KEY_KP7, [HID_KEY_KEYPAD_8_AND_UP_ARROW ] = KEY_KP8, [HID_KEY_KEYPAD_9_AND_PAGEUP ] = KEY_KP9, [HID_KEY_KEYPAD_0_AND_INSERT ] = KEY_KP0, [HID_KEY_KEYPAD_PERIOD_AND_DELETE ] = KEY_KPDOT, [HID_KEY_NON_US_BACKSLASH_AND_PIPE ] = KEY_102ND, [HID_KEY_APPLICATION ] = KEY_COMPOSE, [HID_KEY_POWER ] = KEY_POWER, [HID_KEY_KEYPAD_EQUAL ] = KEY_KPEQUAL, [HID_KEY_F13 ] = KEY_F13, [HID_KEY_F14 ] = KEY_F14, [HID_KEY_F15 ] = KEY_F15, [HID_KEY_F16 ] = KEY_F16, [HID_KEY_F17 ] = KEY_F17, [HID_KEY_F18 ] = KEY_F18, [HID_KEY_F19 ] = KEY_F19, [HID_KEY_F20 ] = KEY_F20, [HID_KEY_F21 ] = KEY_F21, [HID_KEY_F22 ] = KEY_F22, [HID_KEY_F23 ] = KEY_F23, [HID_KEY_F24 ] = KEY_F24, [HID_KEY_EXECUTE ] = 0, [HID_KEY_HELP ] = KEY_HELP, [HID_KEY_MENU ] = KEY_MENU, [HID_KEY_SELECT ] = KEY_SELECT, [HID_KEY_STOP ] = KEY_STOP, [HID_KEY_AGAIN ] = KEY_AGAIN, [HID_KEY_UNDO ] = KEY_UNDO, [HID_KEY_CUT ] = KEY_CUT, [HID_KEY_COPY ] = KEY_COPY, [HID_KEY_PASTE ] = KEY_PASTE, [HID_KEY_FIND ] = KEY_FIND, [HID_KEY_MUTE ] = KEY_MUTE, [HID_KEY_VOLUME_UP ] = KEY_VOLUMEUP, [HID_KEY_VOLUME_DOWN ] = KEY_VOLUMEDOWN, [HID_KEY_LOCKING_CAPS_LOCK ] = 0, [HID_KEY_LOCKING_NUM_LOCK ] = 0, [HID_KEY_LOCKING_SCROLL_LOCK ] = 0, [HID_KEY_KEYPAD_COMMA ] = KEY_KPCOMMA, [HID_KEY_KEYPAD_EQUAL_SIGN ] = KEY_KPEQUAL, [HID_KEY_KANJI1 ] = 0, [HID_KEY_KANJI2 ] = 0, [HID_KEY_KANJI3 ] = 0, [HID_KEY_KANJI4 ] = 0, [HID_KEY_KANJI5 ] = 0, [HID_KEY_KANJI6 ] = 0, [HID_KEY_KANJI7 ] = 0, [HID_KEY_KANJI8 ] = 0, [HID_KEY_KANJI9 ] = 0, [HID_KEY_LANG1 ] = 0, [HID_KEY_LANG2 ] = 0, [HID_KEY_LANG3 ] = 0, [HID_KEY_LANG4 ] = 0, [HID_KEY_LANG5 ] = 0, [HID_KEY_LANG6 ] = 0, [HID_KEY_LANG7 ] = 0, [HID_KEY_LANG8 ] = 0, [HID_KEY_LANG9 ] = 0, [HID_KEY_ALTERNATE_ERASE ] = 0, [HID_KEY_SYSREQ_ATTENTION ] = KEY_SYSRQ, [HID_KEY_CANCEL ] = KEY_CANCEL, [HID_KEY_CLEAR ] = KEY_CLEAR, [HID_KEY_PRIOR ] = 0, [HID_KEY_RETURN ] = 0, [HID_KEY_SEPARATOR ] = 0, [HID_KEY_OUT ] = 0, [HID_KEY_OPER ] = 0, [HID_KEY_CLEAR_AGAIN ] = 0, [HID_KEY_CRSEL_PROPS ] = 0, [HID_KEY_EXSEL ] = 0, [0xA5 ... 0xDF] = 0, [HID_KEY_LEFTCONTROL ] = KEY_LEFTCTRL, [HID_KEY_LEFTSHIFT ] = KEY_LEFTSHIFT, [HID_KEY_LEFTALT ] = KEY_LEFTALT, [HID_KEY_LEFT_GUI ] = KEY_LEFTMETA, [HID_KEY_RIGHTCONTROL ] = KEY_RIGHTCTRL, [HID_KEY_RIGHTSHIFT ] = KEY_RIGHTSHIFT, [HID_KEY_RIGHTALT ] = KEY_RIGHTALT, [HID_KEY_RIGHT_GUI ] = KEY_RIGHTMETA, [0xe8 ... 0xff] = 0, }; #define HID_CC_CONSUMER_CONTROL 0x01 #define HID_CC_NUMERIC_KEY_PAD 0x02 #define HID_CC_PROGRAMMABLE_BUTTONS 0x03 #define HID_CC_MICROPHONE 0x04 #define HID_CC_HEADPHONE 0x05 #define HID_CC_GRAPHIC_EQUALIZER 0x06 #define HID_CC_PLUS_10 0x20 #define HID_CC_PLUS_100 0x21 #define HID_CC_AM_PM 0x22 #define HID_CC_POWER 0x30 #define HID_CC_RESET 0x31 #define HID_CC_SLEEP 0x32 #define HID_CC_SLEEP_AFTER 0x33 #define HID_CC_SLEEP_MODE 0x34 #define HID_CC_ILLUMINATION 0x35 #define HID_CC_FUNCTION_BUTTONS 0x36 #define HID_CC_MENU 0x40 #define HID_CC_MENU_PICK 0x41 #define HID_CC_MENU_UP 0x42 #define HID_CC_MENU_DOWN 0x43 #define HID_CC_MENU_LEFT 0x44 #define HID_CC_MENU_RIGHT 0x45 #define HID_CC_MENU_ESCAPE 0x46 #define HID_CC_MENU_VALUE_INCREASE 0x47 #define HID_CC_MENU_VALUE_DECREASE 0x48 #define HID_CC_DATA_ON_SCREEN 0x60 #define HID_CC_CLOSED_CAPTION 0x61 #define HID_CC_CLOSED_CAPTION_SELECT 0x62 #define HID_CC_VCR_TV 0x63 #define HID_CC_BROADCAST_MODE 0x64 #define HID_CC_SNAPSHOT 0x65 #define HID_CC_STILL 0x66 #define HID_CC_ASPECT 0x6D #define HID_CC_3D_MODE_SELECT 0x6E #define HID_CC_DISPLAY_BRIGHTNESS_INCREMENT 0x6F #define HID_CC_DISPLAY_BRIGHTNESS_DECREMENT 0x70 #define HID_CC_DISPLAY_BRIGHTNESS 0x71 #define HID_CC_DISPLAY_BACKLIGHT_TOGGLE 0x72 #define HID_CC_DISPLAY_SET_BRIGHTNESS_TO_MINIMUM 0x73 #define HID_CC_DISPLAY_SET_BRIGHTNESS_TO_MAXIMUM 0x74 #define HID_CC_DISPLAY_SET_AUTO_BRIGHTNESS 0x75 #define HID_CC_SELECTION 0x80 #define HID_CC_ASSIGN_SELECTION 0x81 #define HID_CC_MODE_STEP 0x82 #define HID_CC_RECALL_LAST 0x83 #define HID_CC_ENTER_CHANNEL 0x84 #define HID_CC_ORDER_MOVIE 0x85 #define HID_CC_CHANNEL 0x86 #define HID_CC_MEDIA_SELECTION 0x87 #define HID_CC_MEDIA_SELECT_COMPUTER 0x88 #define HID_CC_MEDIA_SELECT_TV 0x89 #define HID_CC_MEDIA_SELECT_WWW 0x8A #define HID_CC_MEDIA_SELECT_DVD 0x8B #define HID_CC_MEDIA_SELECT_TELEPHONE 0x8C #define HID_CC_MEDIA_SELECT_PROGRAM_GUIDE 0x8D #define HID_CC_MEDIA_SELECT_VIDEO_PHONE 0x8E #define HID_CC_MEDIA_SELECT_GAMES 0x8F #define HID_CC_MEDIA_SELECT_MESSAGES 0x90 #define HID_CC_MEDIA_SELECT_CD 0x91 #define HID_CC_MEDIA_SELECT_VCR 0x92 #define HID_CC_MEDIA_SELECT_TUNER 0x93 #define HID_CC_QUIT 0x94 #define HID_CC_HELP 0x95 #define HID_CC_MEDIA_SELECT_TAPE 0x96 #define HID_CC_MEDIA_SELECT_CABLE 0x97 #define HID_CC_MEDIA_SELECT_SATELLITE 0x98 #define HID_CC_MEDIA_SELECT_SECURITY 0x99 #define HID_CC_MEDIA_SELECT_HOME 0x9A #define HID_CC_MEDIA_SELECT_CALL 0x9B #define HID_CC_CHANNEL_INCREMENT 0x9C #define HID_CC_CHANNEL_DECREMENT 0x9D #define HID_CC_MEDIA_SELECT_SAP 0x9E #define HID_CC_VCR_PLUS 0xA0 #define HID_CC_ONCE 0xA1 #define HID_CC_DAILY 0xA2 #define HID_CC_WEEKLY 0xA3 #define HID_CC_MONTHLY 0xA4 #define HID_CC_PLAY 0xB0 #define HID_CC_PAUSE 0xB1 #define HID_CC_RECORD 0xB2 #define HID_CC_FAST_FORWARD 0xB3 #define HID_CC_REWIND 0xB4 #define HID_CC_SCAN_NEXT_TRACK 0xB5 #define HID_CC_SCAN_PREVIOUS_TRACK 0xB6 #define HID_CC_STOP 0xB7 #define HID_CC_EJECT 0xB8 #define HID_CC_RANDOM_PLAY 0xB9 #define HID_CC_SELECT_DISC 0xBA #define HID_CC_ENTER_DISC 0xBB #define HID_CC_REPEAT 0xBC #define HID_CC_TRACKING 0xBD #define HID_CC_TRACK_NORMAL 0xBE #define HID_CC_SLOW_TRACKING 0xBF #define HID_CC_FRAME_FORWARD 0xC0 #define HID_CC_FRAME_BACK 0xC1 #define HID_CC_MARK 0xC2 #define HID_CC_CLEAR_MARK 0xC3 #define HID_CC_REPEAT_FROM_MARK 0xC4 #define HID_CC_RETURN_TO_MARK 0xC5 #define HID_CC_SEARCH_MARK_FORWARD 0xC6 #define HID_CC_SEARCH_MARK_BACKWARDS 0xC7 #define HID_CC_COUNTER_RESET 0xC8 #define HID_CC_SHOW_COUNTER 0xC9 #define HID_CC_TRACKING_INCREMENT 0xCA #define HID_CC_TRACKING_DECREMENT 0xCB #define HID_CC_STOP_EJECT 0xCC #define HID_CC_PLAY_PAUSE 0xCD #define HID_CC_PLAY_SKIP 0xCE #define HID_CC_VOICE_COMMAND 0xCF #define HID_CC_VOLUME 0xE0 #define HID_CC_BALANCE 0xE1 #define HID_CC_MUTE 0xE2 #define HID_CC_BASS 0xE3 #define HID_CC_TREBLE 0xE4 #define HID_CC_BASS_BOOST 0xE5 #define HID_CC_SURROUND_MODE 0xE6 #define HID_CC_LOUDNESS 0xE7 #define HID_CC_MPX 0xE8 #define HID_CC_VOLUME_UP 0xE9 #define HID_CC_VOLUME_DOWN 0xEA #define HID_CC_SPEED_SELECT 0xF0 #define HID_CC_PLAYBACK_SPEED 0xF1 #define HID_CC_STANDARD_PLAY 0xF2 #define HID_CC_LONG_PLAY 0xF3 #define HID_CC_EXTENDED_PLAY 0xF4 #define HID_CC_SLOW 0xF5 #define HID_CC_FAN_ENABLE 0x100 #define HID_CC_FAN_SPEED 0x101 #define HID_CC_LIGHT_ENABLE 0x102 #define HID_CC_LIGHT_ILLUMINATION_LEVEL 0x103 #define HID_CC_CLIMATE_CONTROL_ENABLE 0x104 #define HID_CC_ROOM_TEMPERATURE 0x105 #define HID_CC_SECURITY_ENABLE 0x106 #define HID_CC_FIRE_ALARM 0x107 #define HID_CC_POLICE_ALARM 0x108 #define HID_CC_PROXIMITY 0x109 #define HID_CC_MOTION 0x10A #define HID_CC_DURESS_ALARM 0x10B #define HID_CC_HOLDUP_ALARM 0x10C #define HID_CC_MEDICAL_ALARM 0x10D #define HID_CC_BALANCE_RIGHT 0x150 #define HID_CC_BALANCE_LEFT 0x151 #define HID_CC_BASS_INCREMENT 0x152 #define HID_CC_BASS_DECREMENT 0x153 #define HID_CC_TREBLE_INCREMENT 0x154 #define HID_CC_TREBLE_DECREMENT 0x155 #define HID_CC_SPEAKER_SYSTEM 0x160 #define HID_CC_CHANNEL_LEFT 0x161 #define HID_CC_CHANNEL_RIGHT 0x162 #define HID_CC_CHANNEL_CENTER 0x163 #define HID_CC_CHANNEL_FRONT 0x164 #define HID_CC_CHANNEL_CENTER_FRONT 0x165 #define HID_CC_CHANNEL_SIDE 0x166 #define HID_CC_CHANNEL_SURROUND 0x167 #define HID_CC_CHANNEL_LOW_FREQ_ENHANCEMENT 0x168 #define HID_CC_CHANNEL_TOP 0x169 #define HID_CC_CHANNEL_UNKNOWN 0x16A #define HID_CC_SUB_CHANNEL 0x170 #define HID_CC_SUB_CHANNEL_INCREMENT 0x171 #define HID_CC_SUB_CHANNEL_DECREMENT 0x172 #define HID_CC_ALTERNATE_AUDIO_INCREMENT 0x173 #define HID_CC_ALTERNATE_AUDIO_DECREMENT 0x174 #define HID_CC_APPLICATION_LAUNCH_BUTTONS 0x180 #define HID_CC_AL_LAUNCH_BUTTON_CONFIG_TOOL 0x181 #define HID_CC_AL_PROGRAMMABLE_BUTTON_CONFIG 0x182 #define HID_CC_AL_CONSUMER_CONTROL_CONFIG 0x183 #define HID_CC_AL_WORD_PROCESSOR 0x184 #define HID_CC_AL_TEXT_EDITOR 0x185 #define HID_CC_AL_SPREADSHEET 0x186 #define HID_CC_AL_GRAPHICS_EDITOR 0x187 #define HID_CC_AL_PRESENTATION_APP 0x188 #define HID_CC_AL_DATABASE_APP 0x189 #define HID_CC_AL_EMAIL_READER 0x18A #define HID_CC_AL_NEWSREADER 0x18B #define HID_CC_AL_VOICEMAIL 0x18C #define HID_CC_AL_CONTACTS_ADDRESS_BOOK 0x18D #define HID_CC_AL_CALENDAR_SCHEDULE 0x18E #define HID_CC_AL_TASK_PROJECT_MANAGER 0x18F #define HID_CC_AL_LOG_JOURNAL_TIMECARD 0x190 #define HID_CC_AL_CHECKBOOK_FINANCE 0x191 #define HID_CC_AL_CALCULATOR 0x192 #define HID_CC_AL_A_VCAPTURE_PLAYBACK 0x193 #define HID_CC_AL_LOCAL_MACHINE_BROWSER 0x194 #define HID_CC_AL_LAN_WANBROWSER 0x195 #define HID_CC_AL_INTERNET_BROWSER 0x196 #define HID_CC_AL_REMOTE_NETWORKING_ISPCONNECT 0x197 #define HID_CC_AL_NETWORK_CONFERENCE 0x198 #define HID_CC_AL_NETWORK_CHAT 0x199 #define HID_CC_AL_TELEPHONY_DIALER 0x19A #define HID_CC_AL_LOGON 0x19B #define HID_CC_AL_LOGOFF 0x19C #define HID_CC_AL_LOGON_LOGOFF 0x19D #define HID_CC_AL_TERMINAL_LOCK_SCREENSAVER 0x19E #define HID_CC_AL_CONTROL_PANEL 0x19F #define HID_CC_AL_COMMAND_LINE_PROCESSOR_RUN 0x1A0 #define HID_CC_AL_PROCESS_TASK_MANAGER 0x1A1 #define HID_CC_AL_SELECT_TASK_APPLICATION 0x1A2 #define HID_CC_AL_NEXT_TASK_APPLICATION 0x1A3 #define HID_CC_AL_PREVIOUS_TASK_APPLICATION 0x1A4 #define HID_CC_AL_PREEMPT_HALT_TASK_APPLICATION 0x1A5 #define HID_CC_AL_INTEGRATED_HELP_CENTER 0x1A6 #define HID_CC_AL_DOCUMENTS 0x1A7 #define HID_CC_AL_THESAURUS 0x1A8 #define HID_CC_AL_DICTIONARY 0x1A9 #define HID_CC_AL_DESKTOP 0x1AA #define HID_CC_AL_SPELL_CHECK 0x1AB #define HID_CC_AL_GRAMMAR_CHECK 0x1AC #define HID_CC_AL_WIRELESS_STATUS 0x1AD #define HID_CC_AL_KEYBOARD_LAYOUT 0x1AE #define HID_CC_AL_VIRUS_PROTECTION 0x1AF #define HID_CC_AL_ENCRYPTION 0x1B0 #define HID_CC_AL_SCREEN_SAVER 0x1B1 #define HID_CC_AL_ALARMS 0x1B2 #define HID_CC_AL_CLOCK 0x1B3 #define HID_CC_AL_FILE_BROWSER 0x1B4 #define HID_CC_AL_POWER_STATUS 0x1B5 #define HID_CC_AL_IMAGE_BROWSER 0x1B6 #define HID_CC_AL_AUDIO_BROWSER 0x1B7 #define HID_CC_AL_MOVIE_BROWSER 0x1B8 #define HID_CC_AL_DIGITAL_RIGHTS_MANAGER 0x1B9 #define HID_CC_AL_DIGITAL_WALLET 0x1BA #define HID_CC_AL_INSTANT_MESSAGING 0x1BC #define HID_CC_AL_OEMFEATURES_TIPS_TUTO_BROWSER 0x1BD #define HID_CC_AL_OEMHELP 0x1BE #define HID_CC_AL_ONLINE_COMMUNITY 0x1BF #define HID_CC_AL_ENTERTAINMENT_CONTENT_BROWSER 0x1C0 #define HID_CC_AL_ONLINE_SHOPPING_BROWSER 0x1C1 #define HID_CC_AL_SMART_CARD_INFORMATION_HELP 0x1C2 #define HID_CC_AL_MARKET_MONITOR_FINANCE_BROWSER 0x1C3 #define HID_CC_AL_CUSTOMIZED_CORP_NEWS_BROWSER 0x1C4 #define HID_CC_AL_ONLINE_ACTIVITY_BROWSER 0x1C5 #define HID_CC_AL_RESEARCH_SEARCH_BROWSER 0x1C6 #define HID_CC_AL_AUDIO_PLAYER 0x1C7 #define HID_CC_GENERIC_GUIAPPLICATION_CONTROLS 0x200 #define HID_CC_AC_NEW 0x201 #define HID_CC_AC_OPEN 0x202 #define HID_CC_AC_CLOSE 0x203 #define HID_CC_AC_EXIT 0x204 #define HID_CC_AC_MAXIMIZE 0x205 #define HID_CC_AC_MINIMIZE 0x206 #define HID_CC_AC_SAVE 0x207 #define HID_CC_AC_PRINT 0x208 #define HID_CC_AC_PROPERTIES 0x209 #define HID_CC_AC_UNDO 0x21A #define HID_CC_AC_COPY 0x21B #define HID_CC_AC_CUT 0x21C #define HID_CC_AC_PASTE 0x21D #define HID_CC_AC_SELECT_ALL 0x21E #define HID_CC_AC_FIND 0x21F #define HID_CC_AC_FINDAND_REPLACE 0x220 #define HID_CC_AC_SEARCH 0x221 #define HID_CC_AC_GO_TO 0x222 #define HID_CC_AC_HOME 0x223 #define HID_CC_AC_BACK 0x224 #define HID_CC_AC_FORWARD 0x225 #define HID_CC_AC_STOP 0x226 #define HID_CC_AC_REFRESH 0x227 #define HID_CC_AC_PREVIOUS_LINK 0x228 #define HID_CC_AC_NEXT_LINK 0x229 #define HID_CC_AC_BOOKMARKS 0x22A #define HID_CC_AC_HISTORY 0x22B #define HID_CC_AC_SUBSCRIPTIONS 0x22C #define HID_CC_AC_ZOOM_IN 0x22D #define HID_CC_AC_ZOOM_OUT 0x22E #define HID_CC_AC_ZOOM 0x22F #define HID_CC_AC_FULL_SCREEN_VIEW 0x230 #define HID_CC_AC_NORMAL_VIEW 0x231 #define HID_CC_AC_VIEW_TOGGLE 0x232 #define HID_CC_AC_SCROLL_UP 0x233 #define HID_CC_AC_SCROLL_DOWN 0x234 #define HID_CC_AC_SCROLL 0x235 #define HID_CC_AC_PAN_LEFT 0x236 #define HID_CC_AC_PAN_RIGHT 0x237 #define HID_CC_AC_PAN 0x238 #define HID_CC_AC_NEW_WINDOW 0x239 #define HID_CC_AC_TILE_HORIZONTALLY 0x23A #define HID_CC_AC_TILE_VERTICALLY 0x23B #define HID_CC_AC_FORMAT 0x23C #define HID_CC_AC_EDIT 0x23D #define HID_CC_AC_BOLD 0x23E #define HID_CC_AC_ITALICS 0x23F #define HID_CC_AC_UNDERLINE 0x240 #define HID_CC_AC_STRIKETHROUGH 0x241 #define HID_CC_AC_SUBSCRIPT 0x242 #define HID_CC_AC_SUPERSCRIPT 0x243 #define HID_CC_AC_ALL_CAPS 0x244 #define HID_CC_AC_ROTATE 0x245 #define HID_CC_AC_RESIZE 0x246 #define HID_CC_AC_FLIPHORIZONTAL 0x247 #define HID_CC_AC_FLIP_VERTICAL 0x248 #define HID_CC_AC_MIRROR_HORIZONTAL 0x249 #define HID_CC_AC_MIRROR_VERTICAL 0x24A #define HID_CC_AC_FONT_SELECT 0x24B #define HID_CC_AC_FONT_COLOR 0x24C #define HID_CC_AC_FONT_SIZE 0x24D #define HID_CC_AC_JUSTIFY_LEFT 0x24E #define HID_CC_AC_JUSTIFY_CENTER_H 0x24F #define HID_CC_AC_JUSTIFY_RIGHT 0x250 #define HID_CC_AC_JUSTIFY_BLOCK_H 0x251 #define HID_CC_AC_JUSTIFY_TOP 0x252 #define HID_CC_AC_JUSTIFY_CENTER_V 0x253 #define HID_CC_AC_JUSTIFY_BOTTOM 0x254 #define HID_CC_AC_JUSTIFY_BLOCK_V 0x255 #define HID_CC_AC_INDENT_DECREASE 0x256 #define HID_CC_AC_INDENT_INCREASE 0x257 #define HID_CC_AC_NUMBERED_LIST 0x258 #define HID_CC_AC_RESTART_NUMBERING 0x259 #define HID_CC_AC_BULLETED_LIST 0x25A #define HID_CC_AC_PROMOTE 0x25B #define HID_CC_AC_DEMOTE 0x25C #define HID_CC_AC_YES 0x25D #define HID_CC_AC_NO 0x25E #define HID_CC_AC_CANCEL 0x25F #define HID_CC_AC_CATALOG 0x260 #define HID_CC_AC_BUY_CHECKOUT 0x261 #define HID_CC_AC_ADDTO_CART 0x262 #define HID_CC_AC_EXPAND 0x263 #define HID_CC_AC_EXPAND_ALL 0x264 #define HID_CC_AC_COLLAPSE 0x265 #define HID_CC_AC_COLLAPSE_ALL 0x266 #define HID_CC_AC_PRINT_PREVIEW 0x267 #define HID_CC_AC_PASTE_SPECIAL 0x268 #define HID_CC_AC_INSERT_MODE 0x269 #define HID_CC_AC_DELETE 0x26A #define HID_CC_AC_LOCK 0x26B #define HID_CC_AC_UNLOCK 0x26C #define HID_CC_AC_PROTECT 0x26D #define HID_CC_AC_UNPROTECT 0x26E #define HID_CC_AC_ATTACH_COMMENT 0x26F #define HID_CC_AC_DELETE_COMMENT 0x270 #define HID_CC_AC_VIEW_COMMENT 0x271 #define HID_CC_AC_SELECT_WORD 0x272 #define HID_CC_AC_SELECT_SENTENCE 0x273 #define HID_CC_AC_SELECT_PARAGRAPH 0x274 #define HID_CC_AC_SELECT_COLUMN 0x275 #define HID_CC_AC_SELECT_ROW 0x276 #define HID_CC_AC_SELECT_TABLE 0x277 #define HID_CC_AC_SELECT_OBJECT 0x278 #define HID_CC_AC_REDO_REPEAT 0x279 #define HID_CC_AC_SORT 0x27A #define HID_CC_AC_SORT_ASCENDING 0x27B #define HID_CC_AC_SORT_DESCENDING 0x27C #define HID_CC_AC_FILTER 0x27D #define HID_CC_AC_SET_CLOCK 0x27E #define HID_CC_AC_VIEW_CLOCK 0x27F #define HID_CC_AC_SELECT_TIME_ZONE 0x280 #define HID_CC_AC_EDIT_TIME_ZONES 0x281 #define HID_CC_AC_SET_ALARM 0x282 #define HID_CC_AC_CLEAR_ALARM 0x283 #define HID_CC_AC_SNOOZE_ALARM 0x284 #define HID_CC_AC_RESET_ALARM 0x285 #define HID_CC_AC_SYNCHRONIZE 0x286 #define HID_CC_AC_SEND_RECEIVE 0x287 #define HID_CC_AC_SEND_TO 0x288 #define HID_CC_AC_REPLY 0x289 #define HID_CC_AC_REPLY_ALL 0x28A #define HID_CC_AC_FORWARD_MSG 0x28B #define HID_CC_AC_SEND 0x28C #define HID_CC_AC_ATTACH_FILE 0x28D #define HID_CC_AC_UPLOAD 0x28E #define HID_CC_AC_DOWNLOAD(SAVE_TARGET_AS) 0x28F #define HID_CC_AC_SET_BORDERS 0x290 #define HID_CC_AC_INSERT_ROW 0x291 #define HID_CC_AC_INSERT_COLUMN 0x292 #define HID_CC_AC_INSERT_FILE 0x293 #define HID_CC_AC_INSERT_PICTURE 0x294 #define HID_CC_AC_INSERT_OBJECT 0x295 #define HID_CC_AC_INSERT_SYMBOL 0x296 #define HID_CC_AC_SAVEAND_CLOSE 0x297 #define HID_CC_AC_RENAME 0x298 #define HID_CC_AC_MERGE 0x299 #define HID_CC_AC_SPLIT 0x29A #define HID_CC_AC_DISRIBUTE_HORIZONTALLY 0x29B #define HID_CC_AC_DISTRIBUTE_VERTICALLY 0x29C static const unsigned int hid_consumer_mapping[] = { [0x00] = 0, [0x07 ... 0x1F] = 0, [0x23 ... 0x2F] = 0, [0x37 ... 0x3F] = 0, [0x49 ... 0x5F] = 0, [0x67 ... 0x6C] = 0, [0x76 ... 0x7F] = 0, [0x9F ... 0x9F] = 0, [0xA5 ... 0xAF] = 0, [0xD0 ... 0xDF] = 0, [0xEB ... 0xEF] = 0, [0xF6 ... 0xFF] = 0, [0x10E ... 0x14F] = 0, [0x156 ... 0x15F] = 0, [0x16B ... 0x16F] = 0, [0x175 ... 0x17F] = 0, [0x1BB ... 0x1BB] = 0, [0x1C8 ... 0x1FF] = 0, [0x20A ... 0x219] = 0, [0x29D ... 0xFFF] = 0, [HID_CC_CONSUMER_CONTROL ] = 0, [HID_CC_NUMERIC_KEY_PAD ] = 0, [HID_CC_PROGRAMMABLE_BUTTONS ] = 0, [HID_CC_MICROPHONE ] = 0, [HID_CC_HEADPHONE ] = 0, [HID_CC_GRAPHIC_EQUALIZER ] = 0, [HID_CC_PLUS_10 ] = 0, [HID_CC_PLUS_100 ] = 0, [HID_CC_AM_PM ] = 0, [HID_CC_POWER ] = KEY_POWER, [HID_CC_RESET ] = 0, [HID_CC_SLEEP ] = KEY_SLEEP, [HID_CC_SLEEP_AFTER ] = 0, [HID_CC_SLEEP_MODE ] = 0, [HID_CC_ILLUMINATION ] = 0, [HID_CC_FUNCTION_BUTTONS ] = 0, [HID_CC_MENU ] = KEY_MENU, [HID_CC_MENU_PICK ] = 0, [HID_CC_MENU_UP ] = 0, [HID_CC_MENU_DOWN ] = 0, [HID_CC_MENU_LEFT ] = 0, [HID_CC_MENU_RIGHT ] = 0, [HID_CC_MENU_ESCAPE ] = 0, [HID_CC_MENU_VALUE_INCREASE ] = 0, [HID_CC_MENU_VALUE_DECREASE ] = 0, [HID_CC_DATA_ON_SCREEN ] = 0, [HID_CC_CLOSED_CAPTION ] = 0, [HID_CC_CLOSED_CAPTION_SELECT ] = 0, [HID_CC_VCR_TV ] = 0, [HID_CC_BROADCAST_MODE ] = 0, [HID_CC_SNAPSHOT ] = 0, [HID_CC_STILL ] = 0, [HID_CC_ASPECT ] = 0, [HID_CC_3D_MODE_SELECT ] = 0, [HID_CC_DISPLAY_BRIGHTNESS_INCREMENT ] = 0, [HID_CC_DISPLAY_BRIGHTNESS_DECREMENT ] = 0, [HID_CC_DISPLAY_BRIGHTNESS ] = 0, [HID_CC_DISPLAY_BACKLIGHT_TOGGLE ] = 0, [HID_CC_DISPLAY_SET_BRIGHTNESS_TO_MINIMUM ] = 0, [HID_CC_DISPLAY_SET_BRIGHTNESS_TO_MAXIMUM ] = 0, [HID_CC_DISPLAY_SET_AUTO_BRIGHTNESS ] = 0, [HID_CC_SELECTION ] = 0, [HID_CC_ASSIGN_SELECTION ] = 0, [HID_CC_MODE_STEP ] = 0, [HID_CC_RECALL_LAST ] = 0, [HID_CC_ENTER_CHANNEL ] = 0, [HID_CC_ORDER_MOVIE ] = 0, [HID_CC_CHANNEL ] = 0, [HID_CC_MEDIA_SELECTION ] = 0, [HID_CC_MEDIA_SELECT_COMPUTER ] = 0, [HID_CC_MEDIA_SELECT_TV ] = 0, [HID_CC_MEDIA_SELECT_WWW ] = 0, [HID_CC_MEDIA_SELECT_DVD ] = 0, [HID_CC_MEDIA_SELECT_TELEPHONE ] = 0, [HID_CC_MEDIA_SELECT_PROGRAM_GUIDE ] = 0, [HID_CC_MEDIA_SELECT_VIDEO_PHONE ] = 0, [HID_CC_MEDIA_SELECT_GAMES ] = 0, [HID_CC_MEDIA_SELECT_MESSAGES ] = 0, [HID_CC_MEDIA_SELECT_CD ] = 0, [HID_CC_MEDIA_SELECT_VCR ] = 0, [HID_CC_MEDIA_SELECT_TUNER ] = 0, [HID_CC_QUIT ] = 0, [HID_CC_HELP ] = KEY_HELP, [HID_CC_MEDIA_SELECT_TAPE ] = 0, [HID_CC_MEDIA_SELECT_CABLE ] = 0, [HID_CC_MEDIA_SELECT_SATELLITE ] = 0, [HID_CC_MEDIA_SELECT_SECURITY ] = 0, [HID_CC_MEDIA_SELECT_HOME ] = 0, [HID_CC_MEDIA_SELECT_CALL ] = 0, [HID_CC_CHANNEL_INCREMENT ] = 0, [HID_CC_CHANNEL_DECREMENT ] = 0, [HID_CC_MEDIA_SELECT_SAP ] = 0, [HID_CC_VCR_PLUS ] = 0, [HID_CC_ONCE ] = 0, [HID_CC_DAILY ] = 0, [HID_CC_WEEKLY ] = 0, [HID_CC_MONTHLY ] = 0, [HID_CC_PLAY ] = KEY_PLAY, [HID_CC_PAUSE ] = KEY_PAUSE, [HID_CC_RECORD ] = KEY_RECORD, [HID_CC_FAST_FORWARD ] = KEY_FASTFORWARD, [HID_CC_REWIND ] = KEY_REWIND, [HID_CC_SCAN_NEXT_TRACK ] = KEY_NEXTSONG, [HID_CC_SCAN_PREVIOUS_TRACK ] = KEY_PREVIOUSSONG, [HID_CC_STOP ] = KEY_STOP, [HID_CC_EJECT ] = KEY_EJECTCD, [HID_CC_RANDOM_PLAY ] = 0, [HID_CC_SELECT_DISC ] = 0, [HID_CC_ENTER_DISC ] = 0, [HID_CC_REPEAT ] = 0, [HID_CC_TRACKING ] = 0, [HID_CC_TRACK_NORMAL ] = 0, [HID_CC_SLOW_TRACKING ] = 0, [HID_CC_FRAME_FORWARD ] = 0, [HID_CC_FRAME_BACK ] = 0, [HID_CC_MARK ] = 0, [HID_CC_CLEAR_MARK ] = 0, [HID_CC_REPEAT_FROM_MARK ] = 0, [HID_CC_RETURN_TO_MARK ] = 0, [HID_CC_SEARCH_MARK_FORWARD ] = 0, [HID_CC_SEARCH_MARK_BACKWARDS ] = 0, [HID_CC_COUNTER_RESET ] = 0, [HID_CC_SHOW_COUNTER ] = 0, [HID_CC_TRACKING_INCREMENT ] = 0, [HID_CC_TRACKING_DECREMENT ] = 0, [HID_CC_STOP_EJECT ] = 0, [HID_CC_PLAY_PAUSE ] = KEY_PLAYPAUSE, [HID_CC_PLAY_SKIP ] = 0, [HID_CC_VOICE_COMMAND ] = KEY_VOICECOMMAND, [HID_CC_VOLUME ] = 0, [HID_CC_BALANCE ] = 0, [HID_CC_MUTE ] = KEY_MUTE, [HID_CC_BASS ] = 0, [HID_CC_TREBLE ] = 0, [HID_CC_BASS_BOOST ] = KEY_BASSBOOST, [HID_CC_SURROUND_MODE ] = 0, [HID_CC_LOUDNESS ] = 0, [HID_CC_MPX ] = 0, [HID_CC_VOLUME_UP ] = KEY_VOLUMEUP, [HID_CC_VOLUME_DOWN ] = KEY_VOLUMEDOWN, [HID_CC_SPEED_SELECT ] = 0, [HID_CC_PLAYBACK_SPEED ] = 0, [HID_CC_STANDARD_PLAY ] = 0, [HID_CC_LONG_PLAY ] = 0, [HID_CC_EXTENDED_PLAY ] = 0, [HID_CC_SLOW ] = KEY_SLOW, [HID_CC_FAN_ENABLE ] = 0, [HID_CC_FAN_SPEED ] = 0, [HID_CC_LIGHT_ENABLE ] = 0, [HID_CC_LIGHT_ILLUMINATION_LEVEL ] = 0, [HID_CC_CLIMATE_CONTROL_ENABLE ] = 0, [HID_CC_ROOM_TEMPERATURE ] = 0, [HID_CC_SECURITY_ENABLE ] = 0, [HID_CC_FIRE_ALARM ] = 0, [HID_CC_POLICE_ALARM ] = 0, [HID_CC_PROXIMITY ] = 0, [HID_CC_MOTION ] = 0, [HID_CC_DURESS_ALARM ] = 0, [HID_CC_HOLDUP_ALARM ] = 0, [HID_CC_MEDICAL_ALARM ] = 0, [HID_CC_BALANCE_RIGHT ] = 0, [HID_CC_BALANCE_LEFT ] = 0, [HID_CC_BASS_INCREMENT ] = 0, [HID_CC_BASS_DECREMENT ] = 0, [HID_CC_TREBLE_INCREMENT ] = 0, [HID_CC_TREBLE_DECREMENT ] = 0, [HID_CC_SPEAKER_SYSTEM ] = 0, [HID_CC_CHANNEL_LEFT ] = 0, [HID_CC_CHANNEL_RIGHT ] = 0, [HID_CC_CHANNEL_CENTER ] = 0, [HID_CC_CHANNEL_FRONT ] = 0, [HID_CC_CHANNEL_CENTER_FRONT ] = 0, [HID_CC_CHANNEL_SIDE ] = 0, [HID_CC_CHANNEL_SURROUND ] = 0, [HID_CC_CHANNEL_LOW_FREQ_ENHANCEMENT ] = 0, [HID_CC_CHANNEL_TOP ] = 0, [HID_CC_CHANNEL_UNKNOWN ] = 0, [HID_CC_SUB_CHANNEL ] = 0, [HID_CC_SUB_CHANNEL_INCREMENT ] = 0, [HID_CC_SUB_CHANNEL_DECREMENT ] = 0, [HID_CC_ALTERNATE_AUDIO_INCREMENT ] = 0, [HID_CC_ALTERNATE_AUDIO_DECREMENT ] = 0, [HID_CC_APPLICATION_LAUNCH_BUTTONS ] = 0, [HID_CC_AL_LAUNCH_BUTTON_CONFIG_TOOL ] = 0, [HID_CC_AL_PROGRAMMABLE_BUTTON_CONFIG ] = 0, [HID_CC_AL_CONSUMER_CONTROL_CONFIG ] = KEY_CONFIG, [HID_CC_AL_WORD_PROCESSOR ] = KEY_WORDPROCESSOR, [HID_CC_AL_TEXT_EDITOR ] = KEY_EDITOR, [HID_CC_AL_SPREADSHEET ] = KEY_SPREADSHEET, [HID_CC_AL_GRAPHICS_EDITOR ] = KEY_GRAPHICSEDITOR, [HID_CC_AL_PRESENTATION_APP ] = KEY_PRESENTATION, [HID_CC_AL_DATABASE_APP ] = KEY_DATABASE, [HID_CC_AL_EMAIL_READER ] = KEY_EMAIL, [HID_CC_AL_NEWSREADER ] = KEY_NEWS, [HID_CC_AL_VOICEMAIL ] = KEY_VOICEMAIL, [HID_CC_AL_CONTACTS_ADDRESS_BOOK ] = KEY_ADDRESSBOOK, [HID_CC_AL_CALENDAR_SCHEDULE ] = 0, [HID_CC_AL_TASK_PROJECT_MANAGER ] = 0, [HID_CC_AL_LOG_JOURNAL_TIMECARD ] = 0, [HID_CC_AL_CHECKBOOK_FINANCE ] = KEY_FINANCE, [HID_CC_AL_CALCULATOR ] = KEY_CALC, [HID_CC_AL_A_VCAPTURE_PLAYBACK ] = 0, [HID_CC_AL_LOCAL_MACHINE_BROWSER ] = KEY_FILE, [HID_CC_AL_LAN_WANBROWSER ] = 0, [HID_CC_AL_INTERNET_BROWSER ] = KEY_WWW, [HID_CC_AL_REMOTE_NETWORKING_ISPCONNECT ] = 0, [HID_CC_AL_NETWORK_CONFERENCE ] = 0, [HID_CC_AL_NETWORK_CHAT ] = 0, [HID_CC_AL_TELEPHONY_DIALER ] = KEY_PHONE, [HID_CC_AL_LOGON ] = 0, [HID_CC_AL_LOGOFF ] = 0, [HID_CC_AL_LOGON_LOGOFF ] = 0, [HID_CC_AL_TERMINAL_LOCK_SCREENSAVER ] = KEY_COFFEE, [HID_CC_AL_CONTROL_PANEL ] = 0, [HID_CC_AL_COMMAND_LINE_PROCESSOR_RUN ] = 0, [HID_CC_AL_PROCESS_TASK_MANAGER ] = 0, [HID_CC_AL_SELECT_TASK_APPLICATION ] = 0, [HID_CC_AL_NEXT_TASK_APPLICATION ] = 0, [HID_CC_AL_PREVIOUS_TASK_APPLICATION ] = 0, [HID_CC_AL_PREEMPT_HALT_TASK_APPLICATION ] = 0, [HID_CC_AL_INTEGRATED_HELP_CENTER ] = KEY_HELP, [HID_CC_AL_DOCUMENTS ] = 0, [HID_CC_AL_THESAURUS ] = 0, [HID_CC_AL_DICTIONARY ] = 0, [HID_CC_AL_DESKTOP ] = 0, [HID_CC_AL_SPELL_CHECK ] = 0, [HID_CC_AL_GRAMMAR_CHECK ] = 0, [HID_CC_AL_WIRELESS_STATUS ] = 0, [HID_CC_AL_KEYBOARD_LAYOUT ] = 0, [HID_CC_AL_VIRUS_PROTECTION ] = 0, [HID_CC_AL_ENCRYPTION ] = 0, [HID_CC_AL_SCREEN_SAVER ] = KEY_SCREENSAVER, [HID_CC_AL_ALARMS ] = 0, [HID_CC_AL_CLOCK ] = 0, [HID_CC_AL_FILE_BROWSER ] = KEY_FILE, [HID_CC_AL_POWER_STATUS ] = 0, [HID_CC_AL_IMAGE_BROWSER ] = KEY_IMAGES, [HID_CC_AL_AUDIO_BROWSER ] = KEY_AUDIO, [HID_CC_AL_MOVIE_BROWSER ] = KEY_VIDEO, [HID_CC_AL_DIGITAL_RIGHTS_MANAGER ] = 0, [HID_CC_AL_DIGITAL_WALLET ] = 0, [HID_CC_AL_INSTANT_MESSAGING ] = KEY_MESSENGER, [HID_CC_AL_OEMFEATURES_TIPS_TUTO_BROWSER ] = KEY_INFO, [HID_CC_AL_OEMHELP ] = 0, [HID_CC_AL_ONLINE_COMMUNITY ] = 0, [HID_CC_AL_ENTERTAINMENT_CONTENT_BROWSER ] = 0, [HID_CC_AL_ONLINE_SHOPPING_BROWSER ] = 0, [HID_CC_AL_SMART_CARD_INFORMATION_HELP ] = 0, [HID_CC_AL_MARKET_MONITOR_FINANCE_BROWSER ] = 0, [HID_CC_AL_CUSTOMIZED_CORP_NEWS_BROWSER ] = 0, [HID_CC_AL_ONLINE_ACTIVITY_BROWSER ] = 0, [HID_CC_AL_RESEARCH_SEARCH_BROWSER ] = 0, [HID_CC_AL_AUDIO_PLAYER ] = 0, [HID_CC_GENERIC_GUIAPPLICATION_CONTROLS ] = 0, [HID_CC_AC_NEW ] = KEY_NEW, [HID_CC_AC_OPEN ] = KEY_OPEN, [HID_CC_AC_CLOSE ] = KEY_CLOSE, [HID_CC_AC_EXIT ] = KEY_EXIT, [HID_CC_AC_MAXIMIZE ] = 0, [HID_CC_AC_MINIMIZE ] = 0, [HID_CC_AC_SAVE ] = KEY_SAVE, [HID_CC_AC_PRINT ] = KEY_PRINT, [HID_CC_AC_PROPERTIES ] = KEY_PROPS, [HID_CC_AC_UNDO ] = KEY_UNDO, [HID_CC_AC_COPY ] = KEY_COPY, [HID_CC_AC_CUT ] = KEY_CUT, [HID_CC_AC_PASTE ] = KEY_PASTE, [HID_CC_AC_SELECT_ALL ] = KEY_SELECT, [HID_CC_AC_FIND ] = KEY_FIND, [HID_CC_AC_FINDAND_REPLACE ] = 0, [HID_CC_AC_SEARCH ] = KEY_SEARCH, [HID_CC_AC_GO_TO ] = KEY_GOTO, [HID_CC_AC_HOME ] = KEY_HOMEPAGE, [HID_CC_AC_BACK ] = KEY_BACK, [HID_CC_AC_FORWARD ] = KEY_FORWARD, [HID_CC_AC_STOP ] = KEY_STOP, [HID_CC_AC_REFRESH ] = KEY_REFRESH, [HID_CC_AC_PREVIOUS_LINK ] = KEY_PREVIOUS, [HID_CC_AC_NEXT_LINK ] = KEY_NEXT, [HID_CC_AC_BOOKMARKS ] = KEY_BOOKMARKS, [HID_CC_AC_HISTORY ] = 0, [HID_CC_AC_SUBSCRIPTIONS ] = 0, [HID_CC_AC_ZOOM_IN ] = KEY_ZOOMIN, [HID_CC_AC_ZOOM_OUT ] = KEY_ZOOMOUT, [HID_CC_AC_ZOOM ] = KEY_ZOOMRESET, [HID_CC_AC_FULL_SCREEN_VIEW ] = 0, [HID_CC_AC_NORMAL_VIEW ] = 0, [HID_CC_AC_VIEW_TOGGLE ] = 0, [HID_CC_AC_SCROLL_UP ] = KEY_SCROLLUP, [HID_CC_AC_SCROLL_DOWN ] = KEY_SCROLLDOWN, [HID_CC_AC_SCROLL ] = 0, [HID_CC_AC_PAN_LEFT ] = 0, [HID_CC_AC_PAN_RIGHT ] = 0, [HID_CC_AC_PAN ] = 0, [HID_CC_AC_NEW_WINDOW ] = 0, [HID_CC_AC_TILE_HORIZONTALLY ] = 0, [HID_CC_AC_TILE_VERTICALLY ] = 0, [HID_CC_AC_FORMAT ] = 0, [HID_CC_AC_EDIT ] = KEY_EDIT, [HID_CC_AC_BOLD ] = 0, [HID_CC_AC_ITALICS ] = 0, [HID_CC_AC_UNDERLINE ] = 0, [HID_CC_AC_STRIKETHROUGH ] = 0, [HID_CC_AC_SUBSCRIPT ] = 0, [HID_CC_AC_SUPERSCRIPT ] = 0, [HID_CC_AC_ALL_CAPS ] = 0, [HID_CC_AC_ROTATE ] = 0, [HID_CC_AC_RESIZE ] = 0, [HID_CC_AC_FLIPHORIZONTAL ] = 0, [HID_CC_AC_FLIP_VERTICAL ] = 0, [HID_CC_AC_MIRROR_HORIZONTAL ] = 0, [HID_CC_AC_MIRROR_VERTICAL ] = 0, [HID_CC_AC_FONT_SELECT ] = 0, [HID_CC_AC_FONT_COLOR ] = 0, [HID_CC_AC_FONT_SIZE ] = 0, [HID_CC_AC_JUSTIFY_LEFT ] = 0, [HID_CC_AC_JUSTIFY_CENTER_H ] = 0, [HID_CC_AC_JUSTIFY_RIGHT ] = 0, [HID_CC_AC_JUSTIFY_BLOCK_H ] = 0, [HID_CC_AC_JUSTIFY_TOP ] = 0, [HID_CC_AC_JUSTIFY_CENTER_V ] = 0, [HID_CC_AC_JUSTIFY_BOTTOM ] = 0, [HID_CC_AC_JUSTIFY_BLOCK_V ] = 0, [HID_CC_AC_INDENT_DECREASE ] = 0, [HID_CC_AC_INDENT_INCREASE ] = 0, [HID_CC_AC_NUMBERED_LIST ] = 0, [HID_CC_AC_RESTART_NUMBERING ] = 0, [HID_CC_AC_BULLETED_LIST ] = 0, [HID_CC_AC_PROMOTE ] = 0, [HID_CC_AC_DEMOTE ] = 0, [HID_CC_AC_YES ] = 0, [HID_CC_AC_NO ] = 0, [HID_CC_AC_CANCEL ] = KEY_CANCEL, [HID_CC_AC_CATALOG ] = 0, [HID_CC_AC_BUY_CHECKOUT ] = 0, [HID_CC_AC_ADDTO_CART ] = 0, [HID_CC_AC_EXPAND ] = 0, [HID_CC_AC_EXPAND_ALL ] = 0, [HID_CC_AC_COLLAPSE ] = 0, [HID_CC_AC_COLLAPSE_ALL ] = 0, [HID_CC_AC_PRINT_PREVIEW ] = 0, [HID_CC_AC_PASTE_SPECIAL ] = 0, [HID_CC_AC_INSERT_MODE ] = 0, [HID_CC_AC_DELETE ] = KEY_DELETE, [HID_CC_AC_LOCK ] = 0, [HID_CC_AC_UNLOCK ] = 0, [HID_CC_AC_PROTECT ] = 0, [HID_CC_AC_UNPROTECT ] = 0, [HID_CC_AC_ATTACH_COMMENT ] = 0, [HID_CC_AC_DELETE_COMMENT ] = 0, [HID_CC_AC_VIEW_COMMENT ] = 0, [HID_CC_AC_SELECT_WORD ] = 0, [HID_CC_AC_SELECT_SENTENCE ] = 0, [HID_CC_AC_SELECT_PARAGRAPH ] = 0, [HID_CC_AC_SELECT_COLUMN ] = 0, [HID_CC_AC_SELECT_ROW ] = 0, [HID_CC_AC_SELECT_TABLE ] = 0, [HID_CC_AC_SELECT_OBJECT ] = 0, [HID_CC_AC_REDO_REPEAT ] = KEY_REDO, [HID_CC_AC_SORT ] = 0, [HID_CC_AC_SORT_ASCENDING ] = 0, [HID_CC_AC_SORT_DESCENDING ] = 0, [HID_CC_AC_FILTER ] = 0, [HID_CC_AC_SET_CLOCK ] = 0, [HID_CC_AC_VIEW_CLOCK ] = 0, [HID_CC_AC_SELECT_TIME_ZONE ] = 0, [HID_CC_AC_EDIT_TIME_ZONES ] = 0, [HID_CC_AC_SET_ALARM ] = 0, [HID_CC_AC_CLEAR_ALARM ] = 0, [HID_CC_AC_SNOOZE_ALARM ] = 0, [HID_CC_AC_RESET_ALARM ] = 0, [HID_CC_AC_SYNCHRONIZE ] = 0, [HID_CC_AC_SEND_RECEIVE ] = 0, [HID_CC_AC_SEND_TO ] = 0, [HID_CC_AC_REPLY ] = KEY_REPLY, [HID_CC_AC_REPLY_ALL ] = 0, [HID_CC_AC_FORWARD_MSG ] = KEY_FORWARDMAIL, [HID_CC_AC_SEND ] = KEY_SEND, [HID_CC_AC_ATTACH_FILE ] = 0, [HID_CC_AC_UPLOAD ] = 0, [HID_CC_AC_DOWNLOAD(SAVE_TARGET_AS) ] = 0, [HID_CC_AC_SET_BORDERS ] = 0, [HID_CC_AC_INSERT_ROW ] = 0, [HID_CC_AC_INSERT_COLUMN ] = 0, [HID_CC_AC_INSERT_FILE ] = 0, [HID_CC_AC_INSERT_PICTURE ] = 0, [HID_CC_AC_INSERT_OBJECT ] = 0, [HID_CC_AC_INSERT_SYMBOL ] = 0, [HID_CC_AC_SAVEAND_CLOSE ] = 0, [HID_CC_AC_RENAME ] = 0, [HID_CC_AC_MERGE ] = 0, [HID_CC_AC_SPLIT ] = 0, [HID_CC_AC_DISRIBUTE_HORIZONTALLY ] = 0, [HID_CC_AC_DISTRIBUTE_VERTICALLY ] = 0, }; unsigned int ratbag_hidraw_get_keycode_from_keyboard_usage(struct ratbag_device *device, uint8_t hid_code) { return hid_keyboard_mapping[hid_code]; } uint8_t ratbag_hidraw_get_keyboard_usage_from_keycode(struct ratbag_device *device, unsigned keycode) { unsigned int j; for (j = 0; j < ARRAY_LENGTH(hid_keyboard_mapping); j++) { if (hid_keyboard_mapping[j] == keycode) return j; } return 0; } unsigned int ratbag_hidraw_get_keycode_from_consumer_usage(struct ratbag_device *device, uint16_t hid_code) { return hid_consumer_mapping[hid_code]; } uint16_t ratbag_hidraw_get_consumer_usage_from_keycode(struct ratbag_device *device, unsigned keycode) { unsigned int j; for (j = 0; j < ARRAY_LENGTH(hid_consumer_mapping); j++) { if (hid_consumer_mapping[j] == keycode) return j; } return 0; } static int ratbag_hidraw_parse_report_descriptor(struct ratbag_device *device) { int rc, desc_size = 0; struct ratbag_hidraw *hidraw = &device->hidraw[0]; struct hidraw_report_descriptor report_desc = {0}; unsigned int i, j; unsigned int usage_page, usage; hidraw->num_reports = 0; rc = ioctl(hidraw->fd, HIDIOCGRDESCSIZE, &desc_size); if (rc < 0) return rc; report_desc.size = desc_size; rc = ioctl(hidraw->fd, HIDIOCGRDESC, &report_desc); if (rc < 0) return rc; log_debug(device->ratbag, "Parsing HID report descriptor\n"); i = 0; usage_page = 0; usage = 0; while (i < report_desc.size) { uint8_t value = report_desc.value[i]; uint8_t hid = value & 0xfc; uint8_t size = value & 0x3; unsigned content = 0; if (size == 3) size = 4; if (i + size >= report_desc.size) return -EPROTO; for (j = 0; j < size; j++) content |= report_desc.value[i + j + 1] << (j * 8); switch (hid) { case HID_REPORT_ID: if (hidraw->reports) { log_debug(device->ratbag, "- HID report ID %02x\n", content); hidraw->reports[hidraw->num_reports].report_id = content; hidraw->reports[hidraw->num_reports].usage_page = usage_page; hidraw->reports[hidraw->num_reports].usage = usage; } hidraw->num_reports++; break; case HID_COLLECTION: if (content == HID_APPLICATION && hidraw->reports && !hidraw->num_reports && !hidraw->reports[0].report_id) { hidraw->reports[hidraw->num_reports].usage_page = usage_page; hidraw->reports[hidraw->num_reports].usage = usage; } break; case HID_USAGE_PAGE: usage_page = content; break; case HID_USAGE: usage = content; break; } i += 1 + size; } return 0; } static int ratbag_open_hidraw_node(struct ratbag_device *device, struct udev_device *hidraw_udev, int idx) { struct hidraw_devinfo info; struct ratbag_device *tmp_device; int fd, res; const char *devnode; const char *sysname; size_t reports_size; assert(idx >= 0 && idx < MAX_HIDRAW); device->hidraw[idx].fd = -1; sysname = udev_device_get_sysname(hidraw_udev); if (!strneq("hidraw", sysname, 6)) return -ENODEV; list_for_each(tmp_device, &device->ratbag->devices, link) { if (tmp_device->hidraw[idx].sysname && streq(tmp_device->hidraw[idx].sysname, sysname)) { return -ENODEV; } } devnode = udev_device_get_devnode(hidraw_udev); fd = ratbag_open_path(device, devnode, O_RDWR); if (fd < 0) goto err; /* Get Raw Info */ res = ioctl(fd, HIDIOCGRAWINFO, &info); if (res < 0) { log_error(device->ratbag, "error while getting info from device"); goto err; } log_debug(device->ratbag, "hidraw info: bus %#04x vendor %#04x product %#04x\n", info.bustype, info.vendor, info.product); /* check basic matching between the hidraw node and the ratbag device */ if (info.bustype != device->ids.bustype || (info.vendor & 0xFFFF )!= device->ids.vendor || (info.product & 0xFFFF) != device->ids.product) { errno = ENODEV; goto err; } log_debug(device->ratbag, "%s is device '%s'.\n", device->name, udev_device_get_devnode(hidraw_udev)); device->hidraw[idx].fd = fd; /* parse first to count the number of reports */ res = ratbag_hidraw_parse_report_descriptor(device); if (res) { log_error(device->ratbag, "Error while parsing the report descriptor: '%s' (%d)\n", strerror(-res), res); device->hidraw[idx].fd = -1; goto err; } if (device->hidraw[idx].num_reports) reports_size = device->hidraw[idx].num_reports * sizeof(struct ratbag_hid_report); else reports_size = sizeof(struct ratbag_hid_report); device->hidraw[idx].reports = zalloc(reports_size); ratbag_hidraw_parse_report_descriptor(device); device->hidraw[idx].sysname = strdup_safe(sysname); return 0; err: if (fd >= 0) ratbag_close_fd(device, fd); return -errno; } static int ratbag_find_hidraw_node(struct ratbag_device *device, int (*match)(struct ratbag_device *device), int use_usb_parent, int match_index, int hidraw_index) { struct ratbag *ratbag = device->ratbag; _cleanup_(udev_enumerate_unrefp) struct udev_enumerate *e = NULL; struct udev_list_entry *entry; const char *path; struct udev_device *hid_udev; struct udev_device *parent_udev; struct udev *udev = ratbag->udev; int rc = -ENODEV; int matched, endpoint_index = 0; assert(match); hid_udev = udev_device_get_parent_with_subsystem_devtype(device->udev_device, "hid", NULL); if (!hid_udev) return -ENODEV; if (use_usb_parent && device->ids.bustype == BUS_USB) { /* using the parent usb_device to match siblings */ parent_udev = udev_device_get_parent(hid_udev); if (!streq("uhid", udev_device_get_sysname(parent_udev))) parent_udev = udev_device_get_parent_with_subsystem_devtype(hid_udev, "usb", "usb_device"); } else { parent_udev = hid_udev; } e = udev_enumerate_new(udev); udev_enumerate_add_match_subsystem(e, "hidraw"); udev_enumerate_add_match_parent(e, parent_udev); udev_enumerate_scan_devices(e); udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { _cleanup_(udev_device_unrefp) struct udev_device *udev_device = NULL; path = udev_list_entry_get_name(entry); udev_device = udev_device_new_from_syspath(udev, path); if (!udev_device) continue; if (match_index > 0 && match_index != endpoint_index++) continue; rc = ratbag_open_hidraw_node(device, udev_device, hidraw_index); if (rc) goto skip; matched = match(device); rc = matched ? 0 : -ENODEV; if (matched == 1) return rc; skip: ratbag_close_hidraw_index(device, hidraw_index); } return rc; } int ratbag_find_hidraw(struct ratbag_device *device, int (*match)(struct ratbag_device *device)) { return ratbag_find_hidraw_node(device, match, true, -1, 0); } static int hidraw_match_all(struct ratbag_device *device) { return 1; } int ratbag_open_hidraw(struct ratbag_device *device) { return ratbag_find_hidraw_node(device, hidraw_match_all, false, -1, 0); } int ratbag_open_hidraw_index(struct ratbag_device *device, int endpoint_index, int hidraw_index) { return ratbag_find_hidraw_node(device, hidraw_match_all, true, endpoint_index, hidraw_index); } static struct ratbag_hid_report * ratbag_hidraw_get_report(struct ratbag_device *device, unsigned int report_id) { unsigned i; if (report_id == 0) { if (device->hidraw[0].reports[0].report_id == report_id) return &device->hidraw[0].reports[0]; else return NULL; } for (i = 0; i < device->hidraw[0].num_reports; i++) { if (device->hidraw[0].reports[i].report_id == report_id) return &device->hidraw[0].reports[i]; } return NULL; } int ratbag_hidraw_has_report(struct ratbag_device *device, unsigned int report_id) { return ratbag_hidraw_get_report(device, report_id) != NULL; } unsigned int ratbag_hidraw_get_usage_page(struct ratbag_device *device, unsigned int report_id) { struct ratbag_hid_report *report; report = ratbag_hidraw_get_report(device, report_id); if (!report) return 0; return report->usage_page; } unsigned int ratbag_hidraw_get_usage(struct ratbag_device *device, unsigned int report_id) { struct ratbag_hid_report *report; report = ratbag_hidraw_get_report(device, report_id); if (!report) return 0; return report->usage; } void ratbag_close_hidraw(struct ratbag_device *device) { ratbag_close_hidraw_index(device, 0); } void ratbag_close_hidraw_index(struct ratbag_device *device, int idx) { assert(idx >= 0 && idx < MAX_HIDRAW); if (device->hidraw[idx].fd < 0) return; if (device->hidraw[idx].sysname) { free(device->hidraw[idx].sysname); device->hidraw[idx].sysname = NULL; } ratbag_close_fd(device, device->hidraw[idx].fd); device->hidraw[idx].fd = -1; if (device->hidraw[idx].reports) { free(device->hidraw[idx].reports); device->hidraw[idx].reports = NULL; } } int ratbag_hidraw_raw_request(struct ratbag_device *device, unsigned char reportnum, uint8_t *buf, size_t len, unsigned char rtype, int reqtype) { uint8_t tmp_buf[HID_MAX_BUFFER_SIZE]; int rc; if (len < 1 || len > HID_MAX_BUFFER_SIZE || !buf || device->hidraw[0].fd < 0) return -EINVAL; if (rtype != HID_FEATURE_REPORT) return -ENOTSUP; switch (reqtype) { case HID_REQ_GET_REPORT: memset(tmp_buf, 0, len); tmp_buf[0] = reportnum; rc = ioctl(device->hidraw[0].fd, HIDIOCGFEATURE(len), tmp_buf); if (rc < 0) return -errno; log_buf_raw(device->ratbag, "feature get: ", tmp_buf, (unsigned)rc); memcpy(buf, tmp_buf, rc); return rc; case HID_REQ_SET_REPORT: buf[0] = reportnum; log_buf_raw(device->ratbag, "feature set: ", buf, len); rc = ioctl(device->hidraw[0].fd, HIDIOCSFEATURE(len), buf); if (rc < 0) return -errno; return rc; } return -EINVAL; } int ratbag_hidraw_get_feature_report(struct ratbag_device *device, unsigned char reportnum, uint8_t *buf, size_t len) { return ratbag_hidraw_raw_request(device, reportnum, buf, len, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); } int ratbag_hidraw_set_feature_report(struct ratbag_device *device, unsigned char reportnum, uint8_t *buf, size_t len) { return ratbag_hidraw_raw_request(device, reportnum, buf, len, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); } int ratbag_hidraw_output_report(struct ratbag_device *device, uint8_t *buf, size_t len) { int rc; if (len < 1 || len > HID_MAX_BUFFER_SIZE || !buf || device->hidraw[0].fd < 0) return -EINVAL; log_buf_raw(device->ratbag, "output report: ", buf, len); rc = write(device->hidraw[0].fd, buf, len); if (rc < 0) return -errno; if (rc != (int)len) return -EIO; return 0; } int ratbag_hidraw_read_input_report(struct ratbag_device *device, uint8_t *buf, size_t len) { return ratbag_hidraw_read_input_report_index(device, buf, len, 0); } int ratbag_hidraw_read_input_report_index(struct ratbag_device *device, uint8_t *buf, size_t len, int hidrawno) { int rc; struct pollfd fds; assert(hidrawno >= 0 && hidrawno < MAX_HIDRAW); if (len < 1 || !buf || device->hidraw[hidrawno].fd < 0) return -EINVAL; fds.fd = device->hidraw[hidrawno].fd; fds.events = POLLIN; rc = poll(&fds, 1, 1000); if (rc == -1) return -errno; if (rc == 0) return -ETIMEDOUT; rc = read(device->hidraw[hidrawno].fd, buf, len); if (rc > 0) log_buf_raw(device->ratbag, "input report: ", buf, rc); return rc >= 0 ? rc : -errno; } libratbag-0.13/src/libratbag-hidraw.h000066400000000000000000000171531362011324700175350ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #pragma once #include #include #include "libratbag.h" /* defined in the internal hid API in the kernel */ #define HID_INPUT_REPORT 0 #define HID_OUTPUT_REPORT 1 #define HID_FEATURE_REPORT 2 #define MAX_HIDRAW 2 struct ratbag_hid_report { unsigned int report_id; unsigned int usage_page; unsigned int usage; }; struct ratbag_hidraw { int fd; struct ratbag_hid_report *reports; unsigned num_reports; char *sysname; }; /** * Open the hidraw device associated with the device. * * @param device the ratbag device * * @return 0 on success or a negative errno on error */ int ratbag_open_hidraw(struct ratbag_device *device); /** * Open a hidraw device associated with the device by its enumeration order to * a specific internal hidraw index. * * @param device the ratbag device * @param enumeration index of the endpoint * @param interal index of hidraw array * * @return 0 on success or a negative errno on error */ int ratbag_open_hidraw_index(struct ratbag_device *device, int endpoint_index, int hidraw_index); /** * Find and open the hidraw device associated with the device by using the * given matching function. * * @param device the ratbag device * @param match the matching test (return 1 if matched, 0 if not) * * @return 0 on success or a negative errno on error */ int ratbag_find_hidraw(struct ratbag_device *device, int (*match)(struct ratbag_device *device)); /** * Close the hidraw device associated with the device. * * @param device the ratbag device */ void ratbag_close_hidraw(struct ratbag_device *device); /** * Close a hidraw device associated with the device by the internal index. * * @param device the ratbag device * @param interal index of hidraw array */ void ratbag_close_hidraw_index(struct ratbag_device *device, int idx); /** * Send report request to device * * @param device the ratbag device * @param reportnum report ID * @param buf[in/out] data to transfer * @param len length of buf * @param rtype HID report type * @param reqtype HID_REQ_GET_REPORT or HID_REQ_SET_REPORT * * @return count of data transfered, or a negative errno on error * * Same behavior as hid_hw_request, but with raw buffers instead. */ int ratbag_hidraw_raw_request(struct ratbag_device *device, unsigned char reportnum, uint8_t *buf, size_t len, unsigned char rtype, int reqtype); /** * Get feature report from device. * * This call sends a HID_REQ_GET_REPORT to the device for the given feature * and returns the data in the caller-allocated buffer buf of size len. * * @param device the ratbag device * @param reportnum report ID * @param buf[out] data returned from device * @param len length of buf * * @return count of data transfered, or a negative errno on error * * Convenience wrapper around ratbag_hidraw_raw_request() */ int ratbag_hidraw_get_feature_report(struct ratbag_device *device, unsigned char reportnum, uint8_t *buf, size_t len); /** * Set feature report on device. * * This call sends a HID_REQ_SET_REPORT to the device for the given feature * and the buffer buf of size len. * * @param device the ratbag device * @param reportnum report ID * @param buf[in] data returned from device * @param len length of buf * * @return count of data transfered, or a negative errno on error * * Convenience wrapper around ratbag_hidraw_raw_request() */ int ratbag_hidraw_set_feature_report(struct ratbag_device *device, unsigned char reportnum, uint8_t *buf, size_t len); /** * Send output report to device * * @param device the ratbag device * @param buf raw data to transfer * @param len length of buf * * @return count of data transfered, or a negative errno on error */ int ratbag_hidraw_output_report(struct ratbag_device *device, uint8_t *buf, size_t len); /** * Read an input report from the device * * @param device the ratbag device * @param[out] buf resulting raw data * @param len length of buf * * @return count of data transfered, or a negative errno on error */ int ratbag_hidraw_read_input_report(struct ratbag_device *device, uint8_t *buf, size_t len); /** * Read an input report from the device from a specific hidraw index * * @param device the ratbag device * @param[out] buf resulting raw data * @param len length of buf * @param interal index of hidraw array * * @return count of data transfered, or a negative errno on error */ int ratbag_hidraw_read_input_report_index(struct ratbag_device *device, uint8_t *buf, size_t len, int hidrawno); /** * Tells if a given device has the specified report ID. * * @param device the ratbag device which hidraw node is opened * @param report_id the report ID we inquire about * * @return 1 if the device has the given report id, 0 otherwise */ int ratbag_hidraw_has_report(struct ratbag_device *device, unsigned int report_id); /** * Gives the usage page of a report with the specified report ID. * * @param device the ratbag device which hidraw node is opened * @param report_id the report ID we inquire about * * @return the usage page of the report if the device has the given report id, * 0 otherwise */ unsigned int ratbag_hidraw_get_usage_page(struct ratbag_device *device, unsigned int report_id); /** * Gives the usage of a report with the specified report ID. * * @param device the ratbag device which hidraw node is opened * @param report_id the report ID we inquire about * * @return the usage of the report if the device has the given report id, * 0 otherwise */ unsigned int ratbag_hidraw_get_usage(struct ratbag_device *device, unsigned int report_id); /** * Gives the input key code associated to the keyboard HID usage. * * @return the key code of the HID usage or 0. */ unsigned int ratbag_hidraw_get_keycode_from_keyboard_usage(struct ratbag_device *device, uint8_t hid_code); /** * Gives the HID keyboard usage associated to the input keycode. * * @return the HID keyboard usage or 0. */ uint8_t ratbag_hidraw_get_keyboard_usage_from_keycode(struct ratbag_device *device, unsigned keycode); /** * Gives the input key code associated to the Consumer Control HID usage. * * @return the key code of the HID usage or 0. */ unsigned int ratbag_hidraw_get_keycode_from_consumer_usage(struct ratbag_device *device, uint16_t hid_code); /** * Gives the HID Consumer Control usage associated to the input keycode. * * @return the HID Consumer Control usage or 0. */ uint16_t ratbag_hidraw_get_consumer_usage_from_keycode(struct ratbag_device *device, unsigned keycode); libratbag-0.13/src/libratbag-private.h000066400000000000000000000407171362011324700177330ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #pragma once #include #include #include #include #include "libratbag.h" #include "libratbag-util.h" #include "libratbag-hidraw.h" #ifdef NDEBUG #error "libratbag relies on assert(). Do not define NDEBUG" #endif #define RATBAG_LED_TYPE_UNKNOWN 0 void log_msg_va(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *format, va_list args) LIBRATBAG_ATTRIBUTE_PRINTF(3, 0); void log_msg(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *format, ...) LIBRATBAG_ATTRIBUTE_PRINTF(3, 4); void log_buffer(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *header, uint8_t *buf, size_t len); #define log_raw(li_, ...) log_msg((li_), RATBAG_LOG_PRIORITY_RAW, __VA_ARGS__) #define log_debug(li_, ...) log_msg((li_), RATBAG_LOG_PRIORITY_DEBUG, __VA_ARGS__) #define log_info(li_, ...) log_msg((li_), RATBAG_LOG_PRIORITY_INFO, __VA_ARGS__) #define log_error(li_, ...) log_msg((li_), RATBAG_LOG_PRIORITY_ERROR, __VA_ARGS__) #define log_bug_kernel(li_, ...) log_msg((li_), RATBAG_LOG_PRIORITY_ERROR, "kernel bug: " __VA_ARGS__) #define log_bug_libratbag(li_, ...) log_msg((li_), RATBAG_LOG_PRIORITY_ERROR, "libratbag bug: " __VA_ARGS__) #define log_bug_client(li_, ...) log_msg((li_), RATBAG_LOG_PRIORITY_ERROR, "client bug: " __VA_ARGS__) #define log_buf_raw(li_, h_, buf_, len_) log_buffer(li_, RATBAG_LOG_PRIORITY_RAW, h_, buf_, len_) #define log_buf_debug(li_, h_, buf_, len_) log_buffer(li_, RATBAG_LOG_PRIORITY_DEBUG, h_, buf_, len_) #define log_buf_info(li_, h_, buf_, len_) log_buffer(li_, RATBAG_LOG_PRIORITY_INFO, h_, buf_, len_) #define log_buf_error(li_, h_, buf_, len_) log_buffer(li_, RATBAG_LOG_PRIORITY_ERROR, h_, buf_, len_) static inline void cleanup_device(struct ratbag_device **d) { ratbag_device_unref(*d); } static inline void cleanup_profile(struct ratbag_profile **p) { ratbag_profile_unref(*p); } static inline void cleanup_resolution(struct ratbag_resolution **r) { ratbag_resolution_unref(*r); } static inline void cleanup_button(struct ratbag_button **b) { ratbag_button_unref(*b); } static inline void cleanup_led(struct ratbag_led **l) { ratbag_led_unref(*l); } #define _cleanup_device_ _cleanup_(cleanup_device) #define _cleanup_profile_ _cleanup_(cleanup_profile) #define _cleanup_resolution_ _cleanup_(cleanup_resolution) #define _cleanup_button_ _cleanup_(cleanup_button) #define _cleanup_led_ _cleanup_(cleanup_led) #define BUS_ANY 0xffff #define VENDOR_ANY 0xffff #define PRODUCT_ANY 0xffff #define VERSION_ANY 0xffff /* This struct is used by the test suite only */ struct ratbag_test_device; struct ratbag_driver; struct ratbag_button_action; struct ratbag { const struct ratbag_interface *interface; void *userdata; struct udev *udev; struct list drivers; struct list devices; int refcount; ratbag_log_handler log_handler; enum ratbag_log_priority log_priority; }; #define MAX_CAP 1000 struct ratbag_device { char *name; void *userdata; struct udev_device *udev_device; struct ratbag_hidraw hidraw[MAX_HIDRAW]; int refcount; struct input_id ids; struct ratbag_driver *driver; struct ratbag *ratbag; struct ratbag_device_data *data; unsigned num_profiles; struct list profiles; unsigned num_buttons; unsigned num_leds; void *drv_data; struct list link; }; /** * struct ratbag_driver - user space driver for a ratbag device */ struct ratbag_driver { /** A human-readable name of the driver */ char *name; /** The id of the driver used to match with RATBAG_DRIVER in udev */ char *id; /** * Callback called while trying to open a device by libratbag. * This function should decide whether or not this driver will * handle the given device. * * Return -ENODEV to ignore the device and let other drivers * probe the device. Any other error code will stop the probing. * * If a matching driver is found it should initialize itself * and synchronize all profiles with the current state on the device * in this callback. */ int (*probe)(struct ratbag_device *device); /** * Callback called right before the struct ratbag_device is * unref-ed. * * In this callback, the extra memory allocated in probe should * be freed. */ void (*remove)(struct ratbag_device *device); /** * Callback called when the driver should write any profiles that * were modified back to the device. * * Both profile and button structs have a dirty variable that can * be used to tell whether or not they've actually changed since * the last commit. In order to reduce the amount of time * committing takes, drivers should use this information to avoid * writing back profiles and buttons that haven't actually changed. */ int (*commit)(struct ratbag_device *device); /* * FIXME: This function is deprecated and should be removed. Once * we've updated all the device drivers to stop using it we'll remove * it. Look at commit() instead. */ int (*write_profile)(struct ratbag_profile *profile); /** * Called to mark a previously writen profile as active. * * There should be no need to write the profile here, a * .write_profile() call is issued before calling this. */ int (*set_active_profile)(struct ratbag_device *device, unsigned int index); /* * FIXME: This function is deprecated and should be removed. Once * we've updated all the device drivers to stop using it we'll remove * it. Look at commit() instead. */ int (*write_button)(struct ratbag_button *button, const struct ratbag_button_action *action); /* * FIXME: This function is deprecated and should be removed. Once * we've updated all the device drivers to stop using it we'll remove * it. Look at commit() instead. */ int (*write_resolution_dpi)(struct ratbag_resolution *resolution, int dpi_x, int dpi_y); /* * FIXME: This function is deprecated and should be removed. Once * we've updated all the device drivers to stop using it we'll remove * it. Look at commit() instead. */ int (*write_led)(struct ratbag_led *led, enum ratbag_led_mode mode, struct ratbag_color color, unsigned int ms, unsigned int brightness); /* private */ int (*test_probe)(struct ratbag_device *device, const void *data); struct list link; }; struct ratbag_resolution { struct ratbag_profile *profile; int refcount; void *userdata; struct list link; unsigned index; unsigned int dpis[300]; size_t ndpis; unsigned int dpi_x; /**< x resolution in dpi */ unsigned int dpi_y; /**< y resolution in dpi */ bool is_active; bool is_default; bool dirty; uint32_t capabilities; }; struct ratbag_led { int refcount; void *userdata; struct list link; struct ratbag_profile *profile; unsigned index; enum ratbag_led_type type; enum ratbag_led_mode mode; uint32_t modes; /**< supported modes */ struct ratbag_color color; enum ratbag_led_colordepth colordepth; unsigned int ms; /**< duration of action in ms */ unsigned int brightness; /**< brightness of the LED */ bool dirty; }; struct ratbag_profile { int refcount; void *userdata; char *name; struct list link; unsigned index; struct ratbag_device *device; struct list buttons; void *drv_data; void *user_data; struct list resolutions; struct list leds; unsigned int hz; /**< report rate in Hz */ unsigned int rates[8]; /**< report rates available */ size_t nrates; /**< number of entries in rates */ bool rate_dirty; unsigned int num_resolutions; bool is_active; /**< profile is the currently active one */ bool is_enabled; bool dirty; /**< profile changed since last commit */ unsigned long capabilities[NLONGS(MAX_CAP)]; }; #define ratbag_device_for_each_profile(device_, profile_) \ list_for_each(profile_, &(device_)->profiles, link) #define ratbag_profile_for_each_button(profile_, button_) \ list_for_each(button_, &(profile_)->buttons, link) \ #define ratbag_profile_for_each_led(profile_, led_) \ list_for_each(led_, &(profile_)->leds, link) #define ratbag_profile_for_each_resolution(profile_, resolution_) \ list_for_each(resolution_, &(profile_)->resolutions, link) #define BUTTON_ACTION_NONE \ { .type = RATBAG_BUTTON_ACTION_TYPE_NONE } #define BUTTON_ACTION_BUTTON(num_) \ { .type = RATBAG_BUTTON_ACTION_TYPE_BUTTON, \ .action.button = num_ } #define BUTTON_ACTION_SPECIAL(sp_) \ { .type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL, \ .action.special = sp_ } #define BUTTON_ACTION_KEY(k_) \ { .type = RATBAG_BUTTON_ACTION_TYPE_KEY, \ .action.key.key = k_ } #define BUTTON_ACTION_MACRO \ { .type = RATBAG_BUTTON_ACTION_TYPE_MACRO, \ /* FIXME: add the macro keys */ } struct ratbag_macro_event { enum ratbag_macro_event_type type; union ratbag_macro_evnt { unsigned int key; unsigned int timeout; } event; }; #define MAX_MACRO_EVENTS 256 struct ratbag_macro { char *name; char *group; struct ratbag_macro_event events[MAX_MACRO_EVENTS]; }; struct ratbag_button_macro { int refcount; struct ratbag_macro macro; }; #define MODIFIER_LEFTCTRL (1 << 0) #define MODIFIER_LEFTSHIFT (1 << 1) #define MODIFIER_LEFTALT (1 << 2) #define MODIFIER_LEFTMETA (1 << 3) #define MODIFIER_RIGHTCTRL (1 << 4) #define MODIFIER_RIGHTSHIFT (1 << 5) #define MODIFIER_RIGHTALT (1 << 6) #define MODIFIER_RIGHTMETA (1 << 7) struct ratbag_button_action { enum ratbag_button_action_type type; union ratbag_btn_action { unsigned int button; /* action_type == button */ enum ratbag_button_action_special special; /* action_type == special */ struct { unsigned int key; /* action_type == key */ /* FIXME: modifiers */ } key; } action; struct ratbag_macro *macro; /* dynamically allocated, so kept aside */ }; struct ratbag_button { int refcount; void *userdata; struct list link; struct ratbag_profile *profile; unsigned index; enum ratbag_button_type type; struct ratbag_button_action action; uint32_t action_caps; bool dirty; /* changed since last commit to device */ }; void ratbag_button_set_action(struct ratbag_button *button, const struct ratbag_button_action *action); static inline void ratbag_button_enable_action_type(struct ratbag_button *button, enum ratbag_button_action_type type) { button->action_caps |= 1 << type; } static inline int ratbag_open_path(struct ratbag_device *device, const char *path, int flags) { struct ratbag *ratbag = device->ratbag; return ratbag->interface->open_restricted(path, flags, ratbag->userdata); } static inline void ratbag_close_fd(struct ratbag_device *device, int fd) { struct ratbag *ratbag = device->ratbag; return ratbag->interface->close_restricted(fd, ratbag->userdata); } static inline void ratbag_set_drv_data(struct ratbag_device *device, void *drv_data) { device->drv_data = drv_data; } static inline void * ratbag_get_drv_data(const struct ratbag_device *device) { return device->drv_data; } int ratbag_device_init_profiles(struct ratbag_device *device, unsigned int num_profiles, unsigned int num_resolutions, unsigned int num_buttons, unsigned int num_leds); static inline void ratbag_profile_set_drv_data(struct ratbag_profile *profile, void *drv_data) { profile->drv_data = drv_data; } static inline void * ratbag_profile_get_drv_data(struct ratbag_profile *profile) { return profile->drv_data; } static inline void ratbag_profile_set_cap(struct ratbag_profile *profile, enum ratbag_profile_capability cap) { assert(cap <= MAX_CAP); long_set_bit(profile->capabilities, cap); } static inline int ratbag_button_action_match(const struct ratbag_button_action *action, const struct ratbag_button_action *match) { if (action->type != match->type) return 0; switch (action->type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: return match->action.button == action->action.button; case RATBAG_BUTTON_ACTION_TYPE_KEY: return match->action.key.key == action->action.key.key; case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: return match->action.special == action->action.special; case RATBAG_BUTTON_ACTION_TYPE_MACRO: return 1; default: break; } return 0; } int ratbag_action_macro_num_keys(struct ratbag_button_action *action); int ratbag_button_macro_new_from_keycode(struct ratbag_button *button, unsigned int key, unsigned int modifiers); int ratbag_action_keycode_from_macro(struct ratbag_button_action *action, unsigned int *key_out, unsigned int *modifiers_out); static inline void ratbag_resolution_set_resolution(struct ratbag_resolution *res, int dpi_x, int dpi_y) { res->dpi_x = dpi_x; res->dpi_y = dpi_y; } static inline void ratbag_resolution_set_dpi_list_from_range(struct ratbag_resolution *res, unsigned int min, unsigned int max) { unsigned int stepsize = 50; unsigned int dpi = min; bool maxed_out = false; res->ndpis = 0; while (res->ndpis < ARRAY_LENGTH(res->dpis)) { if (dpi > (unsigned)max) { maxed_out = true; break; } res->dpis[res->ndpis] = dpi; res->ndpis++; if (dpi < 1000) stepsize = 50; else if (dpi < 2600) stepsize = 100; else if (dpi < 5000) stepsize = 200; else stepsize = 500; dpi += stepsize; } if (!maxed_out) log_bug_libratbag(res->profile->device->ratbag, "%s: resolution range exceeds available space.\n", res->profile->device->name); } static inline void ratbag_resolution_set_dpi_list(struct ratbag_resolution *res, unsigned int *dpis, size_t ndpis) { assert(ndpis <= ARRAY_LENGTH(res->dpis)); _Static_assert(sizeof(*dpis) == sizeof(*res->dpis), "Mismatching size"); for (size_t i = 0; i < ndpis; i++) { res->dpis[i] = dpis[i]; if (i > 0) assert(dpis[i] > dpis[i - 1]); } res->ndpis = ndpis; } static inline void ratbag_profile_set_report_rate_list(struct ratbag_profile *profile, unsigned int *rates, size_t nrates) { assert(nrates <= ARRAY_LENGTH(profile->rates)); _Static_assert(sizeof(*rates) == sizeof(*profile->rates), "Mismatching size"); for (size_t i = 0; i < nrates; i++) { profile->rates[i] = rates[i]; if (i > 0) assert(rates[i] > rates[i - 1]); } profile->nrates = nrates; } static inline void ratbag_resolution_set_cap(struct ratbag_resolution *res, enum ratbag_resolution_capability cap) { assert(cap <= RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION); res->capabilities |= (1 << cap); } static inline void ratbag_led_set_mode_capability(struct ratbag_led *led, enum ratbag_led_mode mode) { assert(mode <= RATBAG_LED_BREATHING); assert(mode < sizeof(led->modes) * 8); led->modes |= (1 << mode); } /* list of all supported drivers */ extern struct ratbag_driver etekcity_driver; extern struct ratbag_driver hidpp20_driver; extern struct ratbag_driver hidpp10_driver; extern struct ratbag_driver logitech_g300_driver; extern struct ratbag_driver logitech_g600_driver; extern struct ratbag_driver roccat_driver; extern struct ratbag_driver gskill_driver; extern struct ratbag_driver steelseries_driver; struct ratbag_device* ratbag_device_new(struct ratbag *ratbag, struct udev_device *udev_device, const char *name, const struct input_id *id); void ratbag_device_destroy(struct ratbag_device *device); const char * ratbag_device_get_udev_property(const struct ratbag_device* device, const char *name); bool ratbag_assign_driver(struct ratbag_device *device, const struct input_id *dev_id, const struct ratbag_test_device *test_device); void ratbag_register_driver(struct ratbag *ratbag, struct ratbag_driver *driver); void ratbag_button_copy_macro(struct ratbag_button *button, const struct ratbag_button_macro *macro); libratbag-0.13/src/libratbag-test.c000066400000000000000000000045031362011324700172240ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include "config.h" #include #include #include "libratbag-private.h" #include "libratbag-util.h" #include "libratbag-test.h" extern struct ratbag_driver test_driver; static inline void ratbag_register_test_drivers(struct ratbag *ratbag) { struct ratbag_driver *driver; /* Don't use a static variable here, otherwise the CK_FORK=no case * will fail */ list_for_each(driver, &ratbag->drivers, link) { if (streq(driver->name, test_driver.name)) return; } ratbag_register_driver(ratbag, &test_driver); } LIBRATBAG_EXPORT struct ratbag_device* ratbag_device_new_test_device(struct ratbag *ratbag, const struct ratbag_test_device *test_device) { struct ratbag_device* device = NULL; #if BUILD_TESTS struct input_id id = { .bustype = 0x00, .vendor = 0x00, .product = 0x00, .version = 0x00, }; ratbag_register_test_drivers(ratbag); if (getenv("RATBAG_TEST") == NULL) { fprintf(stderr, "RATBAG_TEST environment variable not set\n"); abort(); } device = ratbag_device_new(ratbag, NULL, "Test device", &id); if (!ratbag_assign_driver(device, &device->ids, test_device)) { ratbag_device_destroy(device); return NULL; } #endif return device; } libratbag-0.13/src/libratbag-test.h000066400000000000000000000054171362011324700172360ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #pragma once #include #include "libratbag.h" #define RATBAG_TEST_MAX_PROFILES 12 #define RATBAG_TEST_MAX_BUTTONS 25 #define RATBAG_TEST_MAX_RESOLUTIONS 8 #define RATBAG_TEST_MAX_LEDS 8 struct ratbag_test_macro_event { enum ratbag_macro_event_type type; unsigned int value; }; struct ratbag_test_button { enum ratbag_button_type button_type; enum ratbag_button_action_type action_type; union { int button; int key; enum ratbag_button_action_special special; struct ratbag_test_macro_event macro[24]; }; }; struct ratbag_test_resolution { int xres, yres; bool active; bool dflt; uint32_t caps[10]; int dpi_min, dpi_max; }; struct ratbag_test_color { unsigned short red; unsigned short green; unsigned short blue; }; struct ratbag_test_led { enum ratbag_led_type type; enum ratbag_led_mode mode; struct ratbag_test_color color; unsigned int ms; unsigned int brightness; }; struct ratbag_test_profile { char *name; struct ratbag_test_button buttons[RATBAG_TEST_MAX_BUTTONS]; struct ratbag_test_resolution resolutions[RATBAG_TEST_MAX_RESOLUTIONS]; struct ratbag_test_led leds[RATBAG_TEST_MAX_LEDS]; bool active; bool dflt; bool disabled; uint32_t caps[10]; int hz; unsigned int report_rates[5]; }; struct ratbag_test_device { unsigned int num_profiles; unsigned int num_resolutions; unsigned int num_buttons; unsigned int num_leds; struct ratbag_test_profile profiles[RATBAG_TEST_MAX_PROFILES]; void (*destroyed)(struct ratbag_device *device, void *data); void *destroyed_data; }; struct ratbag_device* ratbag_device_new_test_device(struct ratbag *ratbag, const struct ratbag_test_device *test_device); libratbag-0.13/src/libratbag-util.c000066400000000000000000000106371362011324700172270ustar00rootroot00000000000000/* * Copyright © 2008-2011 Kristian Høgsberg * Copyright © 2011 Intel Corporation * Copyright © 2013-2015 Red Hat, Inc. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that copyright * notice and this permission notice appear in supporting documentation, and * that the name of the copyright holders not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. The copyright holders make no representations * about the suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE * OF THIS SOFTWARE. */ /* * This list data structure is verbatim copy from wayland-util.h from the * Wayland project; except that wl_ prefix has been removed. */ #include "config.h" #include #include #include #include #include #include #include #include #include "libratbag-util.h" #include "libratbag-private.h" void list_init(struct list *list) { list->prev = list; list->next = list; } void list_insert(struct list *list, struct list *elm) { elm->prev = list; elm->next = list->next; list->next = elm; elm->next->prev = elm; } void list_append(struct list *list, struct list *elm) { elm->next = list; elm->prev = list->prev; list->prev = elm; elm->prev->next = elm; } void list_remove(struct list *elm) { elm->prev->next = elm->next; elm->next->prev = elm->prev; elm->next = NULL; elm->prev = NULL; } int list_empty(const struct list *list) { return list->next == list; } const char * udev_prop_value(struct udev_device *device, const char *prop_name) { struct udev_device *parent; const char *prop_value = NULL; parent = device; while (parent && !prop_value) { prop_value = udev_device_get_property_value(parent, prop_name); parent = udev_device_get_parent(parent); } return prop_value; } ssize_t ratbag_utf8_to_enc(char *buf, size_t buf_len, const char *to_enc, const char *format, ...) { va_list args; iconv_t converter; char str[buf_len]; char *in_buf = str, *out_buf = (char*)buf; size_t in_bytes_left, out_bytes_left = buf_len; int ret; memset(buf, 0, buf_len); va_start(args, format); ret = vsnprintf(str, buf_len, format, args); va_end(args); if (ret < 0) return ret; in_bytes_left = ret; converter = iconv_open(to_enc, "UTF-8"); if (converter == (iconv_t)-1) return -errno; ret = iconv(converter, &in_buf, &in_bytes_left, &out_buf, &out_bytes_left); if (ret) ret = -errno; else ret = buf_len - out_bytes_left; iconv_close(converter); return ret; } ssize_t ratbag_utf8_from_enc(char *in_buf, size_t in_len, const char *from_enc, char **out) { iconv_t converter; size_t len = in_len * 6, in_bytes_left = in_len, out_bytes_left = len; char *pos; ssize_t ret; converter = iconv_open("UTF-8", from_enc); if (converter == (iconv_t)-1) return -errno; /* * We *could* dynamically allocate the out buffer. However iconv's * obnoxious semantics, mainly the fact that it modifies every pointer * given to it, would make this code a lot larger and complex than it * needs to be for a mouse with blinking lights. * * So, since there's no encoding that takes more then 6 bytes per * character, allocate for that and just fail if that's not enough. */ *out = zalloc(len); pos = *out; if (!*out) { ret = -errno; goto err; } ret = iconv(converter, &in_buf, &in_bytes_left, &pos, &out_bytes_left); if (ret) { ret = -errno; goto err; } /* Now get rid of any space in the buffer we don't need */ *out = realloc(*out, (len - out_bytes_left) + 1); if (!*out) ret = -errno; else ret = (len - out_bytes_left) + 1; err: if (ret < 0 && *out) { free(*out); *out = NULL; } iconv_close(converter); return ret; } libratbag-0.13/src/libratbag-util.h000066400000000000000000000172121362011324700172300ustar00rootroot00000000000000/* * Copyright © 2008 Kristian Høgsberg * Copyright © 2013-2015 Red Hat, Inc. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that copyright * notice and this permission notice appear in supporting documentation, and * that the name of the copyright holders not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. The copyright holders make no representations * about the suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE * OF THIS SOFTWARE. */ #pragma once #include "config.h" #include #include #include #include #include #include "shared-macro.h" /* * This list data structure is a verbatim copy from wayland-util.h from the * Wayland project; except that wl_ prefix has been removed. */ struct list { struct list *prev; struct list *next; }; void list_init(struct list *list); void list_insert(struct list *list, struct list *elm); void list_append(struct list *list, struct list *elm); void list_remove(struct list *elm); int list_empty(const struct list *list); #define list_for_each(pos, head, member) \ for (pos = 0, pos = container_of((head)->next, pos, member); \ &pos->member != (head); \ pos = container_of(pos->member.next, pos, member)) #define list_for_each_safe(pos, tmp, head, member) \ for (pos = 0, tmp = 0, \ pos = container_of((head)->next, pos, member), \ tmp = container_of((pos)->member.next, tmp, member); \ &pos->member != (head); \ pos = tmp, \ tmp = container_of(pos->member.next, tmp, member)) static inline char* strncpy_safe(char *dest, const char *src, size_t n) { strncpy(dest, src, n); dest[n - 1] = '\0'; return dest; } #define LIBRATBAG_EXPORT __attribute__ ((visibility("default"))) static inline void * zalloc(size_t size) { void *p = calloc(1, size); if (!p) abort(); return p; } /** * returns NULL if str is NULL, otherwise guarantees a successful strdup. */ static inline char * strdup_safe(const char *str) { char *s; if (!str) return NULL; s = strdup(str); if (!s) abort(); return s; } static inline int snprintf_safe(char *buf, size_t n, const char *fmt, ...) { va_list args; int rc; va_start(args, fmt); rc = vsnprintf(buf, n, fmt, args); va_end(args); if (rc < 0 || n < (size_t)rc) abort(); return rc; } /** * Returns a strdup'd string with all non-ascii characters replaced with a * space. */ static inline char * strdup_ascii_only(const char *str_in) { char *str, *ascii_only; ascii_only = strdup_safe(str_in); str = ascii_only; while (str && *str) { unsigned char c = *str; if (c > 127) *str = ' '; str++; } return ascii_only; } #define sprintf_safe(buf, fmt, ...) \ snprintf_safe(buf, ARRAY_LENGTH(buf), fmt, __VA_ARGS__) __attribute__((format(printf, 1, 2))) static inline void * asprintf_safe(const char *fmt, ...) { va_list args; int rc; char *result; va_start(args, fmt); rc = vasprintf(&result, fmt, args); va_end(args); if (rc < 0) abort(); return result; } static inline void msleep(unsigned int ms) { usleep(ms * 1000); } static inline int long_bit_is_set(const unsigned long *array, int bit) { return !!(array[bit / LONG_BITS] & (1LL << (bit % LONG_BITS))); } static inline void long_set_bit(unsigned long *array, int bit) { array[bit / LONG_BITS] |= (1LL << (bit % LONG_BITS)); } static inline void long_clear_bit(unsigned long *array, int bit) { array[bit / LONG_BITS] &= ~(1LL << (bit % LONG_BITS)); } static inline void long_set_bit_state(unsigned long *array, int bit, int state) { if (state) long_set_bit(array, bit); else long_clear_bit(array, bit); } const char * udev_prop_value(struct udev_device *device, const char *property_name); /** * Converts a string from UTF-8 to the encoding specified. Returns the number * of bytes written to buf on success, or negative errno value on failure. */ ssize_t ratbag_utf8_to_enc(char *buf, size_t buf_len, const char *to_enc, const char *format, ...) __attribute__((format(printf, 4, 5))); /** * Converts a string from the given encoding into UTF-8. The memory for the * result is allocated and a pointer to the result is placed in *out. Returns * the number of bytes in the UTF-8 version of the string on success, negative * errno value on failure. */ ssize_t ratbag_utf8_from_enc(char *in_buf, size_t in_len, const char *from_enc, char **out); __attribute__((format(printf, 2, 3))) static inline int xasprintf(char **strp, const char *fmt, ...) { int rc = 0; va_list args; va_start(args, fmt); rc = vasprintf(strp, fmt, args); va_end(args); if ((rc == -1) && strp) *strp = NULL; return rc; } struct dpi_range { unsigned int min; unsigned int max; float step; }; /* Parse a string in the form min:max@step to a dpi_range */ static inline struct dpi_range * dpi_range_from_string(const char *str) { float min, max, step; int rc; struct dpi_range *range; rc = sscanf(str, "%f:%f@%f", &min, &max, &step); if (rc != 3 || min < 0 || max <= min || step < 20) return NULL; range = zalloc(sizeof(*range)); range->min = min; range->max = max; range->step = step; return range; } struct dpi_list { int *entries; size_t nentries; }; static inline void dpi_list_free(struct dpi_list *list) { free(list->entries); free(list); } DEFINE_TRIVIAL_CLEANUP_FUNC(struct dpi_list *, dpi_list_free); /* Parse a string in the form "100;200;400"to a dpi_list */ static inline struct dpi_list * dpi_list_from_string(const char *str) { struct dpi_list *list; unsigned int i, count, index; int nread, dpi = 0; char c; void *tmp; if (!str || str[0] == '\0') return NULL; printf("%s: ", str); /* first, count how many elements do we have in the table */ count = 1; i = 0; while (str[i] != 0) { c = str[i++]; if (c == ';') count++; } list = zalloc(sizeof *list); list->nentries = count; list->entries = zalloc(count * sizeof(list->entries[0])); index = 0; while (*str != 0 && index < count) { if (*str == ';') { str++; continue; } nread = 0; sscanf(str, "%d%n", &dpi, &nread); if (!nread || dpi < 0) goto err; list->entries[index] = dpi; str += nread; index++; } if (index == 0) goto err; /* Drop empty entries for trailing semicolons */ list->nentries = index; tmp = realloc(list->entries, index * sizeof(list->entries[0])); if (tmp) list->entries = tmp; return list; err: dpi_list_free(list); return NULL; } static inline uint16_t get_unaligned_be_u16(uint8_t *buf) { return (buf[0] << 8) | buf[1]; } static inline void set_unaligned_be_u16(uint8_t *buf, uint16_t value) { buf[0] = value >> 8; buf[1] = value & 0xFF; } static inline uint16_t get_unaligned_le_u16(uint8_t *buf) { return (buf[1] << 8) | buf[0]; } static inline void set_unaligned_le_u16(uint8_t *buf, uint16_t value) { buf[0] = value & 0xFF; buf[1] = value >> 8; } static inline uint32_t get_unaligned_be_u32(uint8_t *buf) { return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; } libratbag-0.13/src/libratbag.c000066400000000000000000001335161362011324700162560ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "usb-ids.h" #include "libratbag-private.h" #include "libratbag-util.h" #include "libratbag-data.h" static enum ratbag_error_code error_code(enum ratbag_error_code code) { switch(code) { case RATBAG_SUCCESS: case RATBAG_ERROR_DEVICE: case RATBAG_ERROR_CAPABILITY: case RATBAG_ERROR_VALUE: case RATBAG_ERROR_SYSTEM: case RATBAG_ERROR_IMPLEMENTATION: break; default: assert(!"Invalid error code. This is a library bug."); } return code; } static void ratbag_profile_destroy(struct ratbag_profile *profile); static void ratbag_button_destroy(struct ratbag_button *button); static void ratbag_led_destroy(struct ratbag_led *led); static void ratbag_resolution_destroy(struct ratbag_resolution *resolution); static void ratbag_default_log_func(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *format, va_list args) { const char *prefix; FILE *out = stdout; switch(priority) { case RATBAG_LOG_PRIORITY_RAW: prefix = "raw"; break; case RATBAG_LOG_PRIORITY_DEBUG: prefix = "debug"; break; case RATBAG_LOG_PRIORITY_INFO: prefix = "info"; break; case RATBAG_LOG_PRIORITY_ERROR: prefix = "error"; out = stderr; break; default: prefix=""; break; } fprintf(out, "ratbag %s: ", prefix); vfprintf(out, format, args); } void log_msg_va(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *format, va_list args) { if (ratbag->log_handler && ratbag->log_priority <= priority) ratbag->log_handler(ratbag, priority, format, args); } void log_msg(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *format, ...) { va_list args; va_start(args, format); log_msg_va(ratbag, priority, format, args); va_end(args); } void log_buffer(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *header, uint8_t *buf, size_t len) { _cleanup_free_ char *output_buf = NULL; char *sep = ""; unsigned int i, n; unsigned int buf_len; if (ratbag->log_handler && ratbag->log_priority > priority) return; buf_len = header ? strlen(header) : 0; buf_len += len * 3; buf_len += 1; /* terminating '\0' */ output_buf = zalloc(buf_len); n = 0; if (header) n += snprintf_safe(output_buf, buf_len - n, "%s", header); for (i = 0; i < len; ++i) { n += snprintf_safe(&output_buf[n], buf_len - n, "%s%02x", sep, buf[i] & 0xFF); sep = " "; } log_msg(ratbag, priority, "%s\n", output_buf); } LIBRATBAG_EXPORT void ratbag_log_set_priority(struct ratbag *ratbag, enum ratbag_log_priority priority) { switch (priority) { case RATBAG_LOG_PRIORITY_RAW: case RATBAG_LOG_PRIORITY_DEBUG: case RATBAG_LOG_PRIORITY_INFO: case RATBAG_LOG_PRIORITY_ERROR: break; default: log_bug_client(ratbag, "Invalid log priority %d. Using INFO instead\n", priority); priority = RATBAG_LOG_PRIORITY_INFO; } ratbag->log_priority = priority; } LIBRATBAG_EXPORT enum ratbag_log_priority ratbag_log_get_priority(const struct ratbag *ratbag) { return ratbag->log_priority; } LIBRATBAG_EXPORT void ratbag_log_set_handler(struct ratbag *ratbag, ratbag_log_handler log_handler) { ratbag->log_handler = log_handler; } struct ratbag_device* ratbag_device_new(struct ratbag *ratbag, struct udev_device *udev_device, const char *name, const struct input_id *id) { struct ratbag_device *device = NULL; device = zalloc(sizeof(*device)); device->name = strdup_safe(name); device->ratbag = ratbag_ref(ratbag); device->refcount = 1; device->udev_device = udev_device_ref(udev_device); device->ids = *id; device->data = ratbag_device_data_new_for_id(ratbag, id); list_init(&device->profiles); list_insert(&ratbag->devices, &device->link); return device; } void ratbag_device_destroy(struct ratbag_device *device) { struct ratbag_profile *profile, *next; if (!device) return; /* if we get to the point where the device is destroyed, profiles, * buttons, etc. are at a refcount of 0, so we can destroy * everything */ if (device->driver && device->driver->remove) device->driver->remove(device); list_for_each_safe(profile, next, &device->profiles, link) ratbag_profile_destroy(profile); if (device->udev_device) udev_device_unref(device->udev_device); list_remove(&device->link); ratbag_unref(device->ratbag); ratbag_device_data_unref(device->data); free(device->name); free(device); } static inline bool ratbag_sanity_check_device(struct ratbag_device *device) { struct ratbag *ratbag = device->ratbag; struct ratbag_profile *profile = NULL; bool has_active = false; unsigned int nres; bool rc = false; /* arbitrary number: max 16 profiles, does any mouse have more? but * since we have num_profiles unsigned, it also checks for * accidental negative */ if (device->num_profiles == 0 || device->num_profiles > 16) { log_bug_libratbag(ratbag, "%s: invalid number of profiles (%d)\n", device->name, device->num_profiles); goto out; } ratbag_device_for_each_profile(device, profile) { struct ratbag_resolution *resolution; unsigned int vals[300]; unsigned int nvals = ARRAY_LENGTH(vals); /* Allow max 1 active profile */ if (profile->is_active) { if (has_active) { log_bug_libratbag(ratbag, "%s: multiple active profiles\n", device->name); goto out; } has_active = true; } nres = ratbag_profile_get_num_resolutions(profile); if (nres > 16) { log_bug_libratbag(ratbag, "%s: invalid number of resolutions (%d)\n", device->name, nres); goto out; } ratbag_profile_for_each_resolution(profile, resolution) { nvals = ratbag_resolution_get_dpi_list(resolution, vals, nvals); if (nvals == 0) { log_bug_libratbag(ratbag, "%s: invalid dpi list\n", device->name); goto out; } } nvals = ratbag_profile_get_report_rate_list(profile, vals, nvals); if (nvals == 0) { log_bug_libratbag(ratbag, "%s: invalid report rate list\n", device->name); goto out; } } /* Require 1 active profile */ if (!has_active) { log_bug_libratbag(ratbag, "%s: no profile set as active profile\n", device->name); goto out; } /* Require LED to be the same type in each profile */ ratbag_device_for_each_profile(device, profile) { _cleanup_profile_ struct ratbag_profile *p0 = ratbag_device_get_profile(device, 0); struct ratbag_led *led; ratbag_profile_for_each_led(profile, led) { _cleanup_led_ struct ratbag_led *p0_led = ratbag_profile_get_led(p0, led->index); assert(led->type != RATBAG_LED_TYPE_UNKNOWN); if (p0_led->type != led->type) { log_bug_libratbag(ratbag, "%s: mismatching LED types (profile %d led %d)\n", device->name, profile->index, led->type); goto out; } } } rc = true; out: return rc; } static inline bool ratbag_try_driver(struct ratbag_device *device, const struct input_id *dev_id, const char *driver_name, const struct ratbag_test_device *test_device) { struct ratbag *ratbag = device->ratbag; struct ratbag_driver *driver; int rc; list_for_each(driver, &ratbag->drivers, link) { if (streq(driver->id, driver_name)) { device->driver = driver; break; } } if (!device->driver) { log_error(ratbag, "%s: driver '%s' does not exist\n", device->name, driver_name); goto error; } if (test_device) rc = device->driver->test_probe(device, test_device); else rc = device->driver->probe(device); if (rc == 0) { if (!ratbag_sanity_check_device(device)) { goto error; } else { log_debug(ratbag, "driver match found: %s\n", device->driver->name); return true; } } if (rc != -ENODEV) log_error(ratbag, "%s: error opening hidraw node (%s)\n", device->name, strerror(-rc)); device->driver = NULL; error: return false; } static inline bool ratbag_driver_fallback_logitech(struct ratbag_device *device, const struct input_id *dev_id) { int rc; if (dev_id->vendor != USB_VENDOR_ID_LOGITECH) return false; rc = ratbag_try_driver(device, dev_id, "hidpp20", NULL); if (!rc) rc = ratbag_try_driver(device, dev_id, "hidpp10", NULL); return rc; } bool ratbag_assign_driver(struct ratbag_device *device, const struct input_id *dev_id, const struct ratbag_test_device *test_device) { bool rc; const char *driver_name; if (!test_device) { driver_name = ratbag_device_data_get_driver(device->data); } else { log_debug(device->ratbag, "This is a test device\n"); driver_name = "test_driver"; } if (driver_name) { log_debug(device->ratbag, "device assigned driver %s\n", driver_name); rc = ratbag_try_driver(device, dev_id, driver_name, test_device); } else { rc = ratbag_driver_fallback_logitech(device, dev_id); } return rc; } static char * get_device_name(struct udev_device *device) { const char *prop; prop = udev_prop_value(device, "HID_NAME"); return strdup_safe(prop); } static inline int get_product_id(struct udev_device *device, struct input_id *id) { const char *product; struct input_id ids = {0}; int rc; product = udev_prop_value(device, "HID_ID"); if (!product) return -1; rc = sscanf(product, "%hx:%hx:%hx", &ids.bustype, &ids.vendor, &ids.product); if (rc != 3) return -1; *id = ids; return 0; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_device_new_from_udev_device(struct ratbag *ratbag, struct udev_device *udev_device, struct ratbag_device **device_out) { struct ratbag_device *device = NULL; enum ratbag_error_code error = RATBAG_ERROR_DEVICE; _cleanup_free_ char *name = NULL; struct input_id id; assert(ratbag != NULL); assert(udev_device != NULL); assert(device_out != NULL); if (get_product_id(udev_device, &id) != 0) goto out_err; if ((name = get_device_name(udev_device)) == 0) goto out_err; log_debug(ratbag, "New device: %s\n", name); device = ratbag_device_new(ratbag, udev_device, name, &id); if (!device || !device->data) goto out_err; if (!ratbag_assign_driver(device, &device->ids, NULL)) goto out_err; error = RATBAG_SUCCESS; out_err: if (error != RATBAG_SUCCESS) ratbag_device_destroy(device); else *device_out = device; return error_code(error); } LIBRATBAG_EXPORT struct ratbag_device * ratbag_device_ref(struct ratbag_device *device) { assert(device->refcount < INT_MAX); device->refcount++; return device; } LIBRATBAG_EXPORT struct ratbag_device * ratbag_device_unref(struct ratbag_device *device) { if (device == NULL) return NULL; assert(device->refcount > 0); device->refcount--; if (device->refcount == 0) ratbag_device_destroy(device); return NULL; } LIBRATBAG_EXPORT const char * ratbag_device_get_name(const struct ratbag_device* device) { return device->name; } LIBRATBAG_EXPORT const char * ratbag_device_get_bustype(const struct ratbag_device *device) { switch (device->ids.bustype) { case BUS_USB: return "usb"; case BUS_BLUETOOTH: return "bluetooth"; default: return NULL; } } LIBRATBAG_EXPORT uint32_t ratbag_device_get_vendor_id(const struct ratbag_device *device) { return device->ids.vendor; } LIBRATBAG_EXPORT uint32_t ratbag_device_get_product_id(const struct ratbag_device *device) { return device->ids.product; } LIBRATBAG_EXPORT uint32_t ratbag_device_get_product_version(const struct ratbag_device *device) { /* change this when we have a need for it, i.e. when we start supporting devices * where the USB ID gets reused */ return 0; } void ratbag_register_driver(struct ratbag *ratbag, struct ratbag_driver *driver) { if (!driver->name) { log_bug_libratbag(ratbag, "Driver is missing name\n"); return; } if (!driver->probe || !driver->remove) { log_bug_libratbag(ratbag, "Driver %s is incomplete.\n", driver->name); return; } list_insert(&ratbag->drivers, &driver->link); } LIBRATBAG_EXPORT struct ratbag * ratbag_create_context(const struct ratbag_interface *interface, void *userdata) { struct ratbag *ratbag; assert(interface != NULL); assert(interface->open_restricted != NULL); assert(interface->close_restricted != NULL); ratbag = zalloc(sizeof(*ratbag)); ratbag->refcount = 1; ratbag->interface = interface; ratbag->userdata = userdata; list_init(&ratbag->drivers); list_init(&ratbag->devices); ratbag->udev = udev_new(); if (!ratbag->udev) { free(ratbag); return NULL; } ratbag->log_handler = ratbag_default_log_func; ratbag->log_priority = RATBAG_LOG_PRIORITY_INFO; ratbag_register_driver(ratbag, &etekcity_driver); ratbag_register_driver(ratbag, &hidpp20_driver); ratbag_register_driver(ratbag, &hidpp10_driver); ratbag_register_driver(ratbag, &logitech_g300_driver); ratbag_register_driver(ratbag, &logitech_g600_driver); ratbag_register_driver(ratbag, &roccat_driver); ratbag_register_driver(ratbag, &gskill_driver); ratbag_register_driver(ratbag, &steelseries_driver); return ratbag; } LIBRATBAG_EXPORT struct ratbag * ratbag_ref(struct ratbag *ratbag) { ratbag->refcount++; return ratbag; } LIBRATBAG_EXPORT struct ratbag * ratbag_unref(struct ratbag *ratbag) { if (ratbag == NULL) return NULL; assert(ratbag->refcount > 0); ratbag->refcount--; if (ratbag->refcount == 0) { ratbag->udev = udev_unref(ratbag->udev); free(ratbag); } return NULL; } static struct ratbag_button * ratbag_create_button(struct ratbag_profile *profile, unsigned int index) { struct ratbag_button *button; button = zalloc(sizeof(*button)); button->refcount = 0; button->profile = profile; button->index = index; button->type = RATBAG_BUTTON_TYPE_UNKNOWN; list_append(&profile->buttons, &button->link); return button; } static struct ratbag_led * ratbag_create_led(struct ratbag_profile *profile, unsigned int index) { struct ratbag_device *device = profile->device; struct ratbag_led *led; led = zalloc(sizeof(*led)); led->refcount = 0; led->profile = profile; led->index = index; led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888; led->type = RATBAG_LED_TYPE_UNKNOWN; if (device->data) led->type = ratbag_device_data_get_led_type(device->data, led->index); list_append(&profile->leds, &led->link); return led; } LIBRATBAG_EXPORT bool ratbag_profile_has_capability(const struct ratbag_profile *profile, enum ratbag_profile_capability cap) { if (cap == RATBAG_PROFILE_CAP_NONE || cap >= MAX_CAP) abort(); return long_bit_is_set(profile->capabilities, cap); } static inline void ratbag_create_resolution(struct ratbag_profile *profile, int index) { struct ratbag_resolution *res; res = zalloc(sizeof(*res)); res->refcount = 0; res->profile = profile; res->index = index; list_append(&profile->resolutions, &res->link); profile->num_resolutions++; } static struct ratbag_profile * ratbag_create_profile(struct ratbag_device *device, unsigned int index, unsigned int num_resolutions, unsigned int num_buttons, unsigned int num_leds) { struct ratbag_profile *profile; unsigned i; profile = zalloc(sizeof(*profile)); profile->refcount = 0; profile->device = device; profile->index = index; profile->num_resolutions = 0; profile->is_enabled = true; profile->name = NULL; list_append(&device->profiles, &profile->link); list_init(&profile->buttons); list_init(&profile->leds); list_init(&profile->resolutions); profile->device->num_buttons = num_buttons; profile->device->num_leds = num_leds; for (i = 0; i < num_resolutions; i++) ratbag_create_resolution(profile, i); for (i = 0; i < num_buttons; i++) ratbag_create_button(profile, i); for (i = 0; i < num_leds; i++) ratbag_create_led(profile, i); return profile; } int ratbag_device_init_profiles(struct ratbag_device *device, unsigned int num_profiles, unsigned int num_resolutions, unsigned int num_buttons, unsigned int num_leds) { unsigned int i; for (i = 0; i < num_profiles; i++) { ratbag_create_profile(device, i, num_resolutions, num_buttons, num_leds); } device->num_profiles = num_profiles; return 0; } LIBRATBAG_EXPORT struct ratbag_profile * ratbag_profile_ref(struct ratbag_profile *profile) { assert(profile->refcount < INT_MAX); ratbag_device_ref(profile->device); profile->refcount++; return profile; } static void ratbag_profile_destroy(struct ratbag_profile *profile) { struct ratbag_button *button, *b_next; struct ratbag_led *led, *l_next; struct ratbag_resolution *res, *r_next; /* if we get to the point where the profile is destroyed, buttons, * resolutions , etc. are at a refcount of 0, so we can destroy * everything */ list_for_each_safe(button, b_next, &profile->buttons, link) ratbag_button_destroy(button); list_for_each_safe(led, l_next, &profile->leds, link) ratbag_led_destroy(led); list_for_each_safe(res, r_next, &profile->resolutions, link) ratbag_resolution_destroy(res); free(profile->name); list_remove(&profile->link); free(profile); } LIBRATBAG_EXPORT struct ratbag_profile * ratbag_profile_unref(struct ratbag_profile *profile) { if (profile == NULL) return NULL; assert(profile->refcount > 0); profile->refcount--; ratbag_device_unref(profile->device); return NULL; } LIBRATBAG_EXPORT struct ratbag_profile * ratbag_device_get_profile(struct ratbag_device *device, unsigned int index) { struct ratbag_profile *profile; if (index >= ratbag_device_get_num_profiles(device)) { log_bug_client(device->ratbag, "Requested invalid profile %d\n", index); return NULL; } list_for_each(profile, &device->profiles, link) { if (profile->index == index) return ratbag_profile_ref(profile); } log_bug_libratbag(device->ratbag, "Profile %d not found\n", index); return NULL; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_profile_set_enabled(struct ratbag_profile *profile, bool enabled) { if (!ratbag_profile_has_capability(profile, RATBAG_PROFILE_CAP_DISABLE)) return RATBAG_ERROR_CAPABILITY; profile->is_enabled = enabled; profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT bool ratbag_profile_is_active(struct ratbag_profile *profile) { return !!profile->is_active; } LIBRATBAG_EXPORT bool ratbag_profile_is_enabled(const struct ratbag_profile *profile) { return !!profile->is_enabled; } LIBRATBAG_EXPORT unsigned int ratbag_device_get_num_profiles(struct ratbag_device *device) { return device->num_profiles; } LIBRATBAG_EXPORT unsigned int ratbag_device_get_num_buttons(struct ratbag_device *device) { return device->num_buttons; } LIBRATBAG_EXPORT unsigned int ratbag_device_get_num_leds(struct ratbag_device *device) { return device->num_leds; } static inline enum ratbag_error_code write_led_helper(struct ratbag_device *device, struct ratbag_led *led) { return device->driver->write_led(led, led->mode, led->color, led->ms, led->brightness); } /* FIXME: This is a temporary fix for all of the drivers that have yet to be * converted to the new profile-oriented API. Once all of the drivers have been * converted, this code should be removed. */ static enum ratbag_error_code ratbag_old_write_profile(struct ratbag_device *device) { struct ratbag_profile *profile; struct ratbag_button *button; struct ratbag_led *led; struct ratbag_resolution *resolution; int rc; assert(device->driver->write_profile); list_for_each(profile, &device->profiles, link) { if (!profile->dirty) continue; rc = device->driver->write_profile(profile); if (rc) return RATBAG_ERROR_DEVICE; if (device->driver->write_resolution_dpi) { ratbag_profile_for_each_resolution(profile, resolution) { rc = device->driver->write_resolution_dpi( resolution, resolution->dpi_x, resolution->dpi_y); if (rc) return RATBAG_ERROR_DEVICE; } } if (device->driver->write_button) { list_for_each(button, &profile->buttons, link) { struct ratbag_button_action action = button->action; if (!button->dirty) continue; rc = device->driver->write_button(button, &action); if (rc) return RATBAG_ERROR_DEVICE; } } if (device->driver->write_led) { list_for_each(led, &profile->leds, link) { if (!led->dirty) continue; rc = write_led_helper(device, led); if (rc) return RATBAG_ERROR_DEVICE; } } } return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_device_commit(struct ratbag_device *device) { struct ratbag_profile *profile; struct ratbag_button *button; struct ratbag_led *led; struct ratbag_resolution *resolution; int rc; if (!device->driver->commit) { rc = ratbag_old_write_profile(device); if (rc != RATBAG_SUCCESS) return rc; } else { rc = device->driver->commit(device); if (rc) return RATBAG_ERROR_DEVICE; } list_for_each(profile, &device->profiles, link) { profile->dirty = false; profile->rate_dirty = false; list_for_each(button, &profile->buttons, link) button->dirty = false; list_for_each(led, &profile->leds, link) led->dirty = false; list_for_each(resolution, &profile->resolutions, link) resolution->dirty = false; } return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_profile_set_active(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct ratbag_profile *p; int rc; if (device->num_profiles == 1) return RATBAG_SUCCESS; assert(device->driver->set_active_profile); rc = device->driver->set_active_profile(device, profile->index); if (rc) return RATBAG_ERROR_DEVICE; list_for_each(p, &device->profiles, link) p->is_active = false; profile->is_active = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT unsigned int ratbag_profile_get_num_resolutions(struct ratbag_profile *profile) { return profile->num_resolutions; } LIBRATBAG_EXPORT struct ratbag_resolution * ratbag_profile_get_resolution(struct ratbag_profile *profile, unsigned int idx) { struct ratbag_device *device = profile->device; struct ratbag_resolution *res; unsigned max = ratbag_profile_get_num_resolutions(profile); if (idx >= max) { log_bug_client(profile->device->ratbag, "Requested invalid resolution %d\n", idx); return NULL; } ratbag_profile_for_each_resolution(profile, res) { if (res->index == idx) return ratbag_resolution_ref(res); } log_bug_libratbag(device->ratbag, "Resolution %d, profile %d not found\n", idx, profile->index); return NULL; } LIBRATBAG_EXPORT struct ratbag_resolution * ratbag_resolution_ref(struct ratbag_resolution *resolution) { assert(resolution->refcount < INT_MAX); ratbag_profile_ref(resolution->profile); resolution->refcount++; return resolution; } LIBRATBAG_EXPORT struct ratbag_resolution * ratbag_resolution_unref(struct ratbag_resolution *resolution) { if (resolution == NULL) return NULL; assert(resolution->refcount > 0); resolution->refcount--; ratbag_profile_unref(resolution->profile); return NULL; } LIBRATBAG_EXPORT bool ratbag_resolution_has_capability(struct ratbag_resolution *resolution, enum ratbag_resolution_capability cap) { switch (cap) { case RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION: break; default: return 0; } return !!(resolution->capabilities & (1 << cap)); } static inline bool resolution_has_dpi(struct ratbag_resolution *resolution, unsigned int dpi) { for (size_t i = 0; i < resolution->ndpis; i++) { if (dpi == resolution->dpis[i]) return true; } return false; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_resolution_set_dpi(struct ratbag_resolution *resolution, unsigned int dpi) { struct ratbag_profile *profile = resolution->profile; if (!resolution_has_dpi(resolution, dpi)) return RATBAG_ERROR_VALUE; if (resolution->dpi_x != dpi || resolution->dpi_y != dpi) { resolution->dpi_x = dpi; resolution->dpi_y = dpi; resolution->dirty = true; profile->dirty = true; } return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_resolution_set_dpi_xy(struct ratbag_resolution *resolution, unsigned int x, unsigned int y) { struct ratbag_profile *profile = resolution->profile; if (!ratbag_resolution_has_capability(resolution, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION)) return RATBAG_ERROR_CAPABILITY; if ((x == 0 && y != 0) || (x != 0 && y == 0)) return RATBAG_ERROR_VALUE; if (!resolution_has_dpi(resolution, x) || !resolution_has_dpi(resolution, y)) return RATBAG_ERROR_VALUE; if (resolution->dpi_x != x || resolution->dpi_y != y) { resolution->dpi_x = x; resolution->dpi_y = y; resolution->dirty = true; profile->dirty = true; } return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_profile_set_report_rate(struct ratbag_profile *profile, unsigned int hz) { if (profile->hz != hz) { profile->hz = hz; profile->dirty = true; profile->rate_dirty = true; } return RATBAG_SUCCESS; } LIBRATBAG_EXPORT int ratbag_resolution_get_dpi(struct ratbag_resolution *resolution) { return resolution->dpi_x; } LIBRATBAG_EXPORT size_t ratbag_resolution_get_dpi_list(struct ratbag_resolution *resolution, unsigned int *resolutions, size_t nres) { _Static_assert(sizeof(*resolutions) == sizeof(*resolution->dpis), "type mismatch"); assert(nres > 0); if (resolution->ndpis == 0) return 0; memcpy(resolutions, resolution->dpis, sizeof(unsigned int) * min(nres, resolution->ndpis)); return resolution->ndpis; } LIBRATBAG_EXPORT int ratbag_resolution_get_dpi_x(struct ratbag_resolution *resolution) { return resolution->dpi_x; } LIBRATBAG_EXPORT int ratbag_resolution_get_dpi_y(struct ratbag_resolution *resolution) { return resolution->dpi_y; } LIBRATBAG_EXPORT int ratbag_profile_get_report_rate(struct ratbag_profile *profile) { return profile->hz; } LIBRATBAG_EXPORT size_t ratbag_profile_get_report_rate_list(struct ratbag_profile *profile, unsigned int *rates, size_t nrates) { _Static_assert(sizeof(*rates) == sizeof(*profile->rates), "type mismatch"); assert(nrates > 0); if (profile->nrates == 0) return 0; memcpy(rates, profile->rates, sizeof(unsigned int) * min(nrates, profile->nrates)); return profile->nrates; } LIBRATBAG_EXPORT bool ratbag_resolution_is_active(const struct ratbag_resolution *resolution) { return !!resolution->is_active; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_resolution_set_active(struct ratbag_resolution *resolution) { struct ratbag_profile *profile = resolution->profile; struct ratbag_resolution *res; ratbag_profile_for_each_resolution(profile, res) res->is_active = false; resolution->is_active = true; resolution->dirty = true; profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT bool ratbag_resolution_is_default(const struct ratbag_resolution *resolution) { return !!resolution->is_default; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_resolution_set_default(struct ratbag_resolution *resolution) { struct ratbag_profile *profile = resolution->profile; struct ratbag_resolution *other; /* Unset the default on the other resolutions */ ratbag_profile_for_each_resolution(profile, other) { if (other == resolution || !other->is_default) continue; other->is_default = false; resolution->dirty = true; profile->dirty = true; } if (!resolution->is_default) { resolution->is_default = true; resolution->dirty = true; profile->dirty = true; } return RATBAG_SUCCESS; } LIBRATBAG_EXPORT struct ratbag_button* ratbag_profile_get_button(struct ratbag_profile *profile, unsigned int index) { struct ratbag_device *device = profile->device; struct ratbag_button *button; if (index >= ratbag_device_get_num_buttons(device)) { log_bug_client(device->ratbag, "Requested invalid button %d\n", index); return NULL; } list_for_each(button, &profile->buttons, link) { if (button->index == index) return ratbag_button_ref(button); } log_bug_libratbag(device->ratbag, "Button %d, profile %d not found\n", index, profile->index); return NULL; } LIBRATBAG_EXPORT enum ratbag_button_action_type ratbag_button_get_action_type(struct ratbag_button *button) { return button->action.type; } LIBRATBAG_EXPORT bool ratbag_button_has_action_type(struct ratbag_button *button, enum ratbag_button_action_type action_type) { switch (action_type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: case RATBAG_BUTTON_ACTION_TYPE_KEY: case RATBAG_BUTTON_ACTION_TYPE_MACRO: break; default: return 0; } return !!(button->action_caps & (1 << action_type)); } LIBRATBAG_EXPORT unsigned int ratbag_button_get_button(struct ratbag_button *button) { if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_BUTTON) return 0; return button->action.action.button; } void ratbag_button_set_action(struct ratbag_button *button, const struct ratbag_button_action *action) { struct ratbag_macro *macro = button->action.macro; button->action = *action; if (action->type != RATBAG_BUTTON_ACTION_TYPE_MACRO) button->action.macro = macro; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_button_set_button(struct ratbag_button *button, unsigned int btn) { struct ratbag_button_action action = {0}; if (!ratbag_button_has_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON)) return RATBAG_ERROR_CAPABILITY; action.type = RATBAG_BUTTON_ACTION_TYPE_BUTTON; action.action.button = btn; ratbag_button_set_action(button, &action); button->dirty = true; button->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_button_action_special ratbag_button_get_special(struct ratbag_button *button) { if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_SPECIAL) return RATBAG_BUTTON_ACTION_SPECIAL_INVALID; return button->action.action.special; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_button_set_special(struct ratbag_button *button, enum ratbag_button_action_special act) { struct ratbag_button_action action = {0}; /* FIXME: range checks */ if (!ratbag_button_has_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL)) return RATBAG_ERROR_CAPABILITY; action.type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; action.action.special = act; ratbag_button_set_action(button, &action); button->dirty = true; button->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT unsigned int ratbag_button_get_key(struct ratbag_button *button, unsigned int *modifiers, size_t *sz) { if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_KEY) return 0; /* FIXME: modifiers */ *sz = 0; return button->action.action.key.key; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_button_set_key(struct ratbag_button *button, unsigned int key, unsigned int *modifiers, size_t sz) { struct ratbag_button_action action = {0}; /* FIXME: range checks */ if (!ratbag_button_has_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY)) return RATBAG_ERROR_CAPABILITY; /* FIXME: modifiers */ action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; action.action.key.key = key; ratbag_button_set_action(button, &action); button->dirty = true; button->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_button_disable(struct ratbag_button *button) { struct ratbag_button_action action = {0}; if (!ratbag_button_has_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY)) return RATBAG_ERROR_CAPABILITY; action.type = RATBAG_BUTTON_ACTION_TYPE_NONE; ratbag_button_set_action(button, &action); button->dirty = true; button->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT struct ratbag_button * ratbag_button_ref(struct ratbag_button *button) { assert(button->refcount < INT_MAX); ratbag_profile_ref(button->profile); button->refcount++; return button; } LIBRATBAG_EXPORT struct ratbag_led * ratbag_led_ref(struct ratbag_led *led) { assert(led->refcount < INT_MAX); ratbag_profile_ref(led->profile); led->refcount++; return led; } static void ratbag_button_destroy(struct ratbag_button *button) { list_remove(&button->link); if (button->action.macro) { free(button->action.macro->name); free(button->action.macro->group); free(button->action.macro); } free(button); } static void ratbag_led_destroy(struct ratbag_led *led) { list_remove(&led->link); free(led); } static void ratbag_resolution_destroy(struct ratbag_resolution *res) { list_remove(&res->link); free(res); } LIBRATBAG_EXPORT struct ratbag_button * ratbag_button_unref(struct ratbag_button *button) { if (button == NULL) return NULL; assert(button->refcount > 0); button->refcount--; ratbag_profile_unref(button->profile); return NULL; } LIBRATBAG_EXPORT struct ratbag_led * ratbag_led_unref(struct ratbag_led *led) { if (led == NULL) return NULL; assert(led->refcount > 0); led->refcount--; ratbag_profile_unref(led->profile); return NULL; } LIBRATBAG_EXPORT enum ratbag_led_mode ratbag_led_get_mode(struct ratbag_led *led) { return led->mode; } LIBRATBAG_EXPORT bool ratbag_led_has_mode(struct ratbag_led *led, enum ratbag_led_mode mode) { assert(mode <= RATBAG_LED_BREATHING); if (mode == RATBAG_LED_OFF) return 1; return !!(led->modes & (1 << mode)); } LIBRATBAG_EXPORT struct ratbag_color ratbag_led_get_color(struct ratbag_led *led) { return led->color; } LIBRATBAG_EXPORT int ratbag_led_get_effect_duration(struct ratbag_led *led) { return led->ms; } LIBRATBAG_EXPORT unsigned int ratbag_led_get_brightness(struct ratbag_led *led) { return led->brightness; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_led_set_mode(struct ratbag_led *led, enum ratbag_led_mode mode) { led->mode = mode; led->dirty = true; led->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_led_set_color(struct ratbag_led *led, struct ratbag_color color) { led->color = color; led->dirty = true; led->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_led_colordepth ratbag_led_get_colordepth(struct ratbag_led *led) { return led->colordepth; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_led_set_effect_duration(struct ratbag_led *led, unsigned int ms) { led->ms = ms; led->dirty = true; led->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_led_set_brightness(struct ratbag_led *led, unsigned int brightness) { led->brightness = brightness; led->dirty = true; led->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT struct ratbag_led * ratbag_profile_get_led(struct ratbag_profile *profile, unsigned int index) { struct ratbag_device *device = profile->device; struct ratbag_led *led; if (index >= ratbag_device_get_num_leds(device)) { log_bug_client(device->ratbag, "Requested invalid led %d\n", index); return NULL; } list_for_each(led, &profile->leds, link) { if (led->index == index) return ratbag_led_ref(led); } log_bug_libratbag(device->ratbag, "Led %d, profile %d not found\n", index, profile->index); return NULL; } LIBRATBAG_EXPORT const char * ratbag_profile_get_name(struct ratbag_profile *profile) { return profile->name; } LIBRATBAG_EXPORT int ratbag_profile_set_name(struct ratbag_profile *profile, const char *name) { char *name_copy; if (!profile->name) return RATBAG_ERROR_CAPABILITY; name_copy = strdup_safe(name); if (profile->name) free(profile->name); profile->name = name_copy; profile->dirty = true; return 0; } LIBRATBAG_EXPORT void ratbag_set_user_data(struct ratbag *ratbag, void *userdata) { ratbag->userdata = userdata; } LIBRATBAG_EXPORT void ratbag_device_set_user_data(struct ratbag_device *ratbag_device, void *userdata) { ratbag_device->userdata = userdata; } LIBRATBAG_EXPORT void ratbag_profile_set_user_data(struct ratbag_profile *ratbag_profile, void *userdata) { ratbag_profile->userdata = userdata; } LIBRATBAG_EXPORT void ratbag_button_set_user_data(struct ratbag_button *ratbag_button, void *userdata) { ratbag_button->userdata = userdata; } LIBRATBAG_EXPORT void ratbag_resolution_set_user_data(struct ratbag_resolution *ratbag_resolution, void *userdata) { ratbag_resolution->userdata = userdata; } LIBRATBAG_EXPORT void* ratbag_get_user_data(const struct ratbag *ratbag) { return ratbag->userdata; } LIBRATBAG_EXPORT void* ratbag_device_get_user_data(const struct ratbag_device *ratbag_device) { return ratbag_device->userdata; } LIBRATBAG_EXPORT void* ratbag_profile_get_user_data(const struct ratbag_profile *ratbag_profile) { return ratbag_profile->userdata; } LIBRATBAG_EXPORT void* ratbag_button_get_user_data(const struct ratbag_button *ratbag_button) { return ratbag_button->userdata; } LIBRATBAG_EXPORT void* ratbag_resolution_get_user_data(const struct ratbag_resolution *ratbag_resolution) { return ratbag_resolution->userdata; } LIBRATBAG_EXPORT struct ratbag_button_macro * ratbag_button_get_macro(struct ratbag_button *button) { struct ratbag_button_macro *macro; if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_MACRO) return NULL; macro = ratbag_button_macro_new(button->action.macro->name); memcpy(macro->macro.events, button->action.macro->events, sizeof(macro->macro.events)); return macro; } void ratbag_button_copy_macro(struct ratbag_button *button, const struct ratbag_button_macro *macro) { if (!button->action.macro) button->action.macro = zalloc(sizeof(struct ratbag_macro)); else { free(button->action.macro->name); free(button->action.macro->group); memset(button->action.macro, 0, sizeof(struct ratbag_macro)); } button->action.type = RATBAG_BUTTON_ACTION_TYPE_MACRO; memcpy(button->action.macro->events, macro->macro.events, sizeof(macro->macro.events)); button->action.macro->name = strdup_safe(macro->macro.name); button->action.macro->group = strdup_safe(macro->macro.group); } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_button_set_macro(struct ratbag_button *button, const struct ratbag_button_macro *macro) { if (!ratbag_button_has_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO)) return RATBAG_ERROR_CAPABILITY; ratbag_button_copy_macro(button, macro); button->dirty = true; button->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_button_macro_set_event(struct ratbag_button_macro *m, unsigned int index, enum ratbag_macro_event_type type, unsigned int data) { struct ratbag_macro *macro = &m->macro; if (index >= MAX_MACRO_EVENTS) return RATBAG_ERROR_VALUE; switch (type) { case RATBAG_MACRO_EVENT_KEY_PRESSED: case RATBAG_MACRO_EVENT_KEY_RELEASED: macro->events[index].type = type; macro->events[index].event.key = data; break; case RATBAG_MACRO_EVENT_WAIT: macro->events[index].type = type; macro->events[index].event.timeout = data; break; case RATBAG_MACRO_EVENT_NONE: macro->events[index].type = type; break; default: return RATBAG_ERROR_VALUE; } return 0; } LIBRATBAG_EXPORT enum ratbag_macro_event_type ratbag_button_macro_get_event_type(struct ratbag_button_macro *macro, unsigned int index) { if (index >= MAX_MACRO_EVENTS) return RATBAG_MACRO_EVENT_INVALID; return macro->macro.events[index].type; } LIBRATBAG_EXPORT int ratbag_button_macro_get_event_key(struct ratbag_button_macro *m, unsigned int index) { struct ratbag_macro *macro = &m->macro; if (index >= MAX_MACRO_EVENTS) return 0; if (macro->events[index].type != RATBAG_MACRO_EVENT_KEY_PRESSED && macro->events[index].type != RATBAG_MACRO_EVENT_KEY_RELEASED) return -EINVAL; return macro->events[index].event.key; } LIBRATBAG_EXPORT int ratbag_button_macro_get_event_timeout(struct ratbag_button_macro *m, unsigned int index) { struct ratbag_macro *macro = &m->macro; if (index >= MAX_MACRO_EVENTS) return 0; if (macro->events[index].type != RATBAG_MACRO_EVENT_WAIT) return 0; return macro->events[index].event.timeout; } LIBRATBAG_EXPORT unsigned int ratbag_button_macro_get_num_events(struct ratbag_button_macro *macro) { return MAX_MACRO_EVENTS; } LIBRATBAG_EXPORT const char * ratbag_button_macro_get_name(struct ratbag_button_macro *macro) { return macro->macro.name; } static void ratbag_button_macro_destroy(struct ratbag_button_macro *macro) { assert(macro->refcount == 0); free(macro->macro.name); free(macro->macro.group); free(macro); } LIBRATBAG_EXPORT struct ratbag_button_macro * ratbag_button_macro_ref(struct ratbag_button_macro *macro) { assert(macro->refcount < INT_MAX); macro->refcount++; return macro; } LIBRATBAG_EXPORT struct ratbag_button_macro * ratbag_button_macro_unref(struct ratbag_button_macro *macro) { if (macro == NULL) return NULL; assert(macro->refcount > 0); macro->refcount--; if (macro->refcount == 0) ratbag_button_macro_destroy(macro); return NULL; } LIBRATBAG_EXPORT struct ratbag_button_macro * ratbag_button_macro_new(const char *name) { struct ratbag_button_macro *macro; macro = zalloc(sizeof *macro); macro->refcount = 1; macro->macro.name = strdup_safe(name); return macro; } struct ratbag_modifier_mapping { unsigned int modifier_mask; unsigned int key; }; struct ratbag_modifier_mapping ratbag_modifier_mapping[] = { { MODIFIER_LEFTCTRL, KEY_LEFTCTRL }, { MODIFIER_LEFTSHIFT, KEY_LEFTSHIFT }, { MODIFIER_LEFTALT, KEY_LEFTALT }, { MODIFIER_LEFTMETA, KEY_LEFTMETA }, { MODIFIER_RIGHTCTRL, KEY_RIGHTCTRL }, { MODIFIER_RIGHTSHIFT, KEY_RIGHTSHIFT }, { MODIFIER_RIGHTALT, KEY_RIGHTALT }, { MODIFIER_RIGHTMETA, KEY_RIGHTMETA }, }; int ratbag_button_macro_new_from_keycode(struct ratbag_button *button, unsigned int key, unsigned int modifiers) { struct ratbag_button_macro *macro; struct ratbag_modifier_mapping *mapping; int i; macro = ratbag_button_macro_new("key"); i = 0; ARRAY_FOR_EACH(ratbag_modifier_mapping, mapping) { if (modifiers & mapping->modifier_mask) ratbag_button_macro_set_event(macro, i++, RATBAG_MACRO_EVENT_KEY_PRESSED, mapping->key); } ratbag_button_macro_set_event(macro, i++, RATBAG_MACRO_EVENT_KEY_PRESSED, key); ratbag_button_macro_set_event(macro, i++, RATBAG_MACRO_EVENT_KEY_RELEASED, key); ARRAY_FOR_EACH(ratbag_modifier_mapping, mapping) { if (modifiers & mapping->modifier_mask) ratbag_button_macro_set_event(macro, i++, RATBAG_MACRO_EVENT_KEY_RELEASED, mapping->key); } ratbag_button_copy_macro(button, macro); ratbag_button_macro_unref(macro); return 0; } int ratbag_action_macro_num_keys(struct ratbag_button_action *action) { struct ratbag_macro *macro = action->macro; int i, count; count = 0; for (i = 0; i < MAX_MACRO_EVENTS; i++) { struct ratbag_macro_event event; event = macro->events[i]; if (event.type == RATBAG_MACRO_EVENT_KEY_PRESSED) { switch(event.event.key) { case KEY_LEFTCTRL: case KEY_LEFTSHIFT: case KEY_LEFTALT: case KEY_LEFTMETA: case KEY_RIGHTCTRL: case KEY_RIGHTSHIFT: case KEY_RIGHTALT: case KEY_RIGHTMETA: break; default: count++; } } } return count; } int ratbag_action_keycode_from_macro(struct ratbag_button_action *action, unsigned int *key_out, unsigned int *modifiers_out) { struct ratbag_macro *macro = action->macro; unsigned int key = KEY_RESERVED; unsigned int modifiers = 0; unsigned int i; if (!macro || action->type != RATBAG_BUTTON_ACTION_TYPE_MACRO) return -EINVAL; if (macro->events[0].type == RATBAG_MACRO_EVENT_NONE) return -EINVAL; if (ratbag_action_macro_num_keys(action) != 1) return -EINVAL; for (i = 0; i < MAX_MACRO_EVENTS; i++) { struct ratbag_macro_event event; event = macro->events[i]; switch (event.type) { case RATBAG_MACRO_EVENT_INVALID: return -EINVAL; case RATBAG_MACRO_EVENT_NONE: return 0; case RATBAG_MACRO_EVENT_KEY_PRESSED: switch(event.event.key) { case KEY_LEFTCTRL: modifiers |= MODIFIER_LEFTCTRL; break; case KEY_LEFTSHIFT: modifiers |= MODIFIER_LEFTSHIFT; break; case KEY_LEFTALT: modifiers |= MODIFIER_LEFTALT; break; case KEY_LEFTMETA: modifiers |= MODIFIER_LEFTMETA; break; case KEY_RIGHTCTRL: modifiers |= MODIFIER_RIGHTCTRL; break; case KEY_RIGHTSHIFT: modifiers |= MODIFIER_RIGHTSHIFT; break; case KEY_RIGHTALT: modifiers |= MODIFIER_RIGHTALT; break; case KEY_RIGHTMETA: modifiers |= MODIFIER_RIGHTMETA; break; default: if (key != KEY_RESERVED) return -EINVAL; key = event.event.key; } break; case RATBAG_MACRO_EVENT_KEY_RELEASED: switch(event.event.key) { case KEY_LEFTCTRL: modifiers &= ~MODIFIER_LEFTCTRL; break; case KEY_LEFTSHIFT: modifiers &= ~MODIFIER_LEFTSHIFT; break; case KEY_LEFTALT: modifiers &= ~MODIFIER_LEFTALT; break; case KEY_LEFTMETA: modifiers &= ~MODIFIER_LEFTMETA; break; case KEY_RIGHTCTRL: modifiers &= ~MODIFIER_RIGHTCTRL; break; case KEY_RIGHTSHIFT: modifiers &= ~MODIFIER_RIGHTSHIFT; break; case KEY_RIGHTALT: modifiers &= ~MODIFIER_RIGHTALT; break; case KEY_RIGHTMETA: modifiers &= ~MODIFIER_RIGHTMETA; break; default: if (event.event.key != key) return -EINVAL; *key_out = key; *modifiers_out = modifiers; return 1; } case RATBAG_MACRO_EVENT_WAIT: break; default: return -EINVAL; } } return -EINVAL; } libratbag-0.13/src/libratbag.h000066400000000000000000001363711362011324700162650ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #pragma once #include #include #include #include #include #include "libratbag-enums.h" #define LIBRATBAG_ATTRIBUTE_PRINTF(_format, _args) \ __attribute__ ((format (printf, _format, _args))) #define LIBRATBAG_ATTRIBUTE_DEPRECATED __attribute__ ((deprecated)) /** * @defgroup base Initialization and manipulation of ratbag contexts * @defgroup device Querying and manipulating devices * * Device configuration is managed by "profiles" (see @ref profile). * In the simplest case, a device has a single profile that can be fetched, * queried and manipulated and then re-applied to the device. Other devices * may have multiple profiles, each of which can be queried and managed * independently. * * @defgroup profile Device profiles * * A profile on a device consists of a set of button functions and, where * applicable, a range of resolution settings, one of which is currently * active. * * @defgroup button Button configuration * * @defgroup led LED configuration * * @defgroup resolution Resolution and frequency mappings * * A device's sensor resolution and report rate can be configured per * profile, with each profile reporting a number of resolution modes (see * @ref ratbag_resolution). The number depends on the hardware, but at least * one is provided by libratbag. * * Each resolution mode is a tuple of a resolution and report rate and * represents the modes that the mouse can switch through, usually with the * use of a button on the mouse to cycle through the preconfigured * resolutions. * * The resolutions have a default resolution and a currently active * resolution. The currently active one is the one used by the device now * and only applies if the profile is currently active too. The default * resolution is the one the device will chose when the profile is selected * next. */ /** * @ingroup base * @struct ratbag * * A handle for accessing ratbag contexts. This struct is refcounted, use * ratbag_ref() and ratbag_unref(). */ struct ratbag; /** * @ingroup device * @struct ratbag_device * * A ratbag context represents one single device. This struct is * refcounted, use ratbag_device_ref() and ratbag_device_unref(). */ struct ratbag_device; /** * @ingroup profile * @struct ratbag_profile * * A handle to a profile context on devices. * This struct is refcounted, use ratbag_profile_ref() and * ratbag_profile_unref(). */ struct ratbag_profile; /** * @ingroup button * @struct ratbag_button * * Represents a button on the device. * * This struct is refcounted, use ratbag_button_ref() and * ratbag_button_unref(). */ struct ratbag_button; /** * @ingroup resolution * @struct ratbag_resolution * * Represents a resolution setting on the device. Most devices have multiple * resolutions per profile, one of which is active at a time. * * This struct is refcounted, use ratbag_resolution_ref() and * ratbag_resolution_unref(). */ struct ratbag_resolution; /** * @ingroup led * @struct ratbag_color * * Represents LED color in RGB format. * each color component is integer 0 - 255 */ struct ratbag_color { unsigned int red; unsigned int green; unsigned int blue; }; /** * @ingroup led * @struct ratbag_led * * Represents a led on the device. */ struct ratbag_led; /** * @ingroup button * @struct ratbag_macro * * Represents a macro that can be assigned to a button. * * This struct is refcounted, use ratbag_button_macro_ref() and * ratbag_button_macro_unref(). */ struct ratbag_button_macro; /** * @ingroup base * * Log priority for internal logging messages. */ enum ratbag_log_priority { /** * Raw protocol messages. Using this log level results in *a lot* of * output. */ RATBAG_LOG_PRIORITY_RAW = 10, RATBAG_LOG_PRIORITY_DEBUG = 20, RATBAG_LOG_PRIORITY_INFO = 30, RATBAG_LOG_PRIORITY_ERROR = 40, }; /** * @ingroup base * * Log handler type for custom logging. * * @param ratbag The ratbag context * @param priority The priority of the current message * @param format Message format in printf-style * @param args Message arguments * * @see ratbag_log_set_priority * @see ratbag_log_get_priority * @see ratbag_log_set_handler */ typedef void (*ratbag_log_handler)(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *format, va_list args) LIBRATBAG_ATTRIBUTE_PRINTF(3, 0); /** * @ingroup base * * Set the log priority for the ratbag context. Messages with priorities * equal to or higher than the argument will be printed to the context's * log handler. * * The default log priority is @ref RATBAG_LOG_PRIORITY_ERROR. * * @param ratbag A previously initialized ratbag context * @param priority The minimum priority of log messages to print. * * @see ratbag_log_set_handler * @see ratbag_log_get_priority */ void ratbag_log_set_priority(struct ratbag *ratbag, enum ratbag_log_priority priority); /** * @ingroup base * * Get the context's log priority. Messages with priorities equal to or * higher than the argument will be printed to the current log handler. * * The default log priority is @ref RATBAG_LOG_PRIORITY_ERROR. * * @param ratbag A previously initialized ratbag context * @return The minimum priority of log messages to print. * * @see ratbag_log_set_handler * @see ratbag_log_set_priority */ enum ratbag_log_priority ratbag_log_get_priority(const struct ratbag *ratbag); /** * @ingroup base * * Set the context's log handler. Messages with priorities equal to or * higher than the context's log priority will be passed to the given * log handler. * * The default log handler prints to stderr. * * @param ratbag A previously initialized ratbag context * @param log_handler The log handler for library messages. * * @see ratbag_log_set_priority * @see ratbag_log_get_priority */ void ratbag_log_set_handler(struct ratbag *ratbag, ratbag_log_handler log_handler); /** * @ingroup base * @struct ratbag_interface * * libratbag does not open file descriptors to devices directly, instead * open_restricted() and close_restricted() are called for each path that * must be opened. * * @see ratbag_create_context */ struct ratbag_interface { /** * Open the device at the given path with the flags provided and * return the fd. * * @param path The device path to open * @param flags Flags as defined by open(2) * @param user_data The user_data provided in * ratbag_create_context() * * @return The file descriptor, or a negative errno on failure. */ int (*open_restricted)(const char *path, int flags, void *user_data); /** * Close the file descriptor. * * @param fd The file descriptor to close * @param user_data The user_data provided in * ratbag_create_context() */ void (*close_restricted)(int fd, void *user_data); }; /** * @ingroup base * * Create a new ratbag context. * * The context is refcounted with an initial value of at least 1. * Use ratbag_unref() to release the context. * * @return An initialized ratbag context or NULL on error */ struct ratbag * ratbag_create_context(const struct ratbag_interface *interface, void *userdata); /** * @ingroup base * * Set caller-specific data associated with this context. libratbag does * not manage, look at, or modify this data. The caller must ensure the * data is valid. * * Setting userdata overrides the one provided to ratbag_create_context(). * * @param ratbag A previously initialized ratbag context * @param userdata Caller-specific data passed to the various callback * interfaces. */ void ratbag_set_user_data(struct ratbag *ratbag, void *userdata); /** * @ingroup base * * Get the caller-specific data associated with this context, if any. * * @param ratbag A previously initialized ratbag context * @return The caller-specific data previously assigned in * ratbag_create_context (or ratbag_set_user_data()). */ void* ratbag_get_user_data(const struct ratbag *ratbag); /** * @ingroup base * * Add a reference to the context. A context is destroyed whenever the * reference count reaches 0. See @ref ratbag_unref. * * @param ratbag A previously initialized valid ratbag context * @return The passed ratbag context */ struct ratbag * ratbag_ref(struct ratbag *ratbag); /** * @ingroup base * * Dereference the ratbag context. After this, the context may have been * destroyed, if the last reference was dereferenced. If so, the context is * invalid and may not be interacted with. * * @param ratbag A previously initialized ratbag context * @return Always NULL */ struct ratbag * ratbag_unref(struct ratbag *ratbag); /** * @ingroup base * * Create a new ratbag context from the given udev device. * * The device is refcounted with an initial value of at least 1. * Use ratbag_device_unref() to release the device. * * @param ratbag A previously initialized ratbag context * @param udev_device The udev device that points at the device * @param device Set to a new device based on the udev device. * * @return 0 on success or the error. * @retval RATBAG_ERROR_DEVICE The given device does not exist or is not * supported by libratbag. */ enum ratbag_error_code ratbag_device_new_from_udev_device(struct ratbag *ratbag, struct udev_device *udev_device, struct ratbag_device **device); /** * @ingroup device * * Add a reference to the device. A device is destroyed whenever the * reference count reaches 0. See @ref ratbag_device_unref. * * @param device A previously initialized valid ratbag device * @return The passed ratbag device */ struct ratbag_device * ratbag_device_ref(struct ratbag_device *device); /** * @ingroup device * * Dereference the ratbag device. When the internal refcount reaches * zero, all resources associated with this object are released. The object * must be considered invalid once unref is called. * * @param device A previously initialized ratbag device * @return Always NULL */ struct ratbag_device * ratbag_device_unref(struct ratbag_device *device); /** * @ingroup device * * Set caller-specific data associated with this device. libratbag does * not manage, look at, or modify this data. The caller must ensure the * data is valid. * * @param device A previously initialized device * @param userdata Caller-specific data passed to the various callback * interfaces. */ void ratbag_device_set_user_data(struct ratbag_device *device, void *userdata); /** * @ingroup device * * Get the caller-specific data associated with this device, if any. * * @param device A previously initialized ratbag device * @return The caller-specific data previously assigned in * ratbag_device_set_user_data(). */ void* ratbag_device_get_user_data(const struct ratbag_device *device); /** * @ingroup device * * @param device A previously initialized ratbag device * @return The name of the device associated with the given ratbag. */ const char * ratbag_device_get_name(const struct ratbag_device* device); /** * @ingroup device * * Returns the bustype of the device, "usb", "bluetooth" or NULL for * unknown. */ const char * ratbag_device_get_bustype(const struct ratbag_device *device); /** * @ingroup device * * Returns the vendor ID of the device. */ uint32_t ratbag_device_get_vendor_id(const struct ratbag_device *device); /** * @ingroup device * * Returns the product ID of the device. */ uint32_t ratbag_device_get_product_id(const struct ratbag_device *device); /** * @ingroup device * * Returns the product version of the device. This is a made-up number, * internal to libratbag. It's purpose is to provide an extra bit of * differentiation where devices use the same VID/PID for different models. * * Devices with identical VID/PID but different versions must be considered * different devices. * * Usually the version will be 0. */ uint32_t ratbag_device_get_product_version(const struct ratbag_device *device); /** * @ingroup device * * Write any changes to the device. Depending on the device, this may take * a couple of seconds. * * @param device A previously initialized ratbag device * @return 0 on success or an error code otherwise */ enum ratbag_error_code ratbag_device_commit(struct ratbag_device *device); /** * @ingroup device * * Return the number of profiles supported by this device. * * Note that the number of profiles available may be different to the number * of profiles currently active. This function returns the maximum number of * profiles available and is static for the lifetime of the device. * * A device that does not support profiles in hardware provides a single * profile that reflects the current settings of the device. * * @param device A previously initialized ratbag device * @return The number of profiles available on this device. */ unsigned int ratbag_device_get_num_profiles(struct ratbag_device *device); /** * @ingroup device * * Return the number of buttons available on this device. * * @param device A previously initialized ratbag device * @return The number of buttons available on this device. */ unsigned int ratbag_device_get_num_buttons(struct ratbag_device *device); /** * @ingroup device * * Return the number of LEDs available on this device. * * @param device A previously initialized ratbag device * @return The number of LEDs available on this device. */ unsigned int ratbag_device_get_num_leds(struct ratbag_device *device); /** * @ingroup profile * * Add a reference to the profile. A profile is destroyed whenever the * reference count reaches 0. See @ref ratbag_profile_unref. * * @param profile A previously initialized valid ratbag profile * @return The passed ratbag profile */ struct ratbag_profile * ratbag_profile_ref(struct ratbag_profile *profile); /** * @ingroup profile * * Dereference the ratbag profile. When the internal refcount reaches * zero, all resources associated with this object are released. The object * must be considered invalid once unref is called. * * @param profile A previously initialized ratbag profile * @return Always NULL */ struct ratbag_profile * ratbag_profile_unref(struct ratbag_profile *profile); /** * @ingroup profile * * Check if a profile has a specific capability. * * @return non-zero if the capability is available, zero otherwise. */ bool ratbag_profile_has_capability(const struct ratbag_profile *profile, enum ratbag_profile_capability cap); /** * @ingroup profile * * Return the ratbag profile name. * * @param profile A previously initialized ratbag profile * @return the profile name */ const char * ratbag_profile_get_name(struct ratbag_profile *profile); /** * @ingroup profile * * Set the name of a ratbag profile. * * @param profile A previously initialized ratbag profile * @param name the profile name * * @return 0 on success or an error code otherwise */ int ratbag_profile_set_name(struct ratbag_profile *profile, const char *name); /** * @ingroup profile * * Enable/disable the ratbag profile. For this to work, the profile must support * @ref RATBAG_PROFILE_CAP_DISABLE. * * @param profile A previously initialized ratbag profile * @param enabled Whether to enable or disable the profile * * @return 0 on success or an error code otherwise */ enum ratbag_error_code ratbag_profile_set_enabled(struct ratbag_profile *profile, bool enabled); /** * @ingroup profile * * Check whether the ratbag profile is enabled or not. For devices that don't * support @ref RATBAG_PROFILE_CAP_DISABLE the profile will always be * set to enabled. * * @param profile A previously initialized ratbag profile * * @return Whether the profile is enabled or not. */ bool ratbag_profile_is_enabled(const struct ratbag_profile *profile); /** * @ingroup profile * * Set caller-specific data associated with this profile. libratbag does * not manage, look at, or modify this data. The caller must ensure the * data is valid. * * @param profile A previously initialized profile * @param userdata Caller-specific data passed to the various callback * interfaces. */ void ratbag_profile_set_user_data(struct ratbag_profile *profile, void *userdata); /** * @ingroup profile * * Get the caller-specific data associated with this profile, if any. * * @param profile A previously initialized ratbag profile * @return The caller-specific data previously assigned in * ratbag_profile_set_user_data(). */ void* ratbag_profile_get_user_data(const struct ratbag_profile *profile); /** * @ingroup profile * * This function creates if necessary and returns a profile for the given * index. The index must be less than the number returned by * ratbag_get_num_profiles(). * * The profile is refcounted with an initial value of at least 1. * Use ratbag_profile_unref() to release the profile. * * @param device A previously initialized ratbag device * @param index The index of the profile * * @return The profile at the given index, or NULL if the profile does not * exist. * * @see ratbag_get_num_profiles */ struct ratbag_profile * ratbag_device_get_profile(struct ratbag_device *device, unsigned int index); /** * @ingroup profile * * Check if the given profile is the currently active one. Note that some * devices allow switching profiles with hardware buttons thus making the * use of this function racy. * * @param profile A previously initialized ratbag profile * * @return non-zero if the profile is currently active, zero otherwise */ bool ratbag_profile_is_active(struct ratbag_profile *profile); /** * @ingroup profile * * Make the given profile the currently active profile * * @param profile The profile to make the active profile. * * @return 0 on success or an error code otherwise */ enum ratbag_error_code ratbag_profile_set_active(struct ratbag_profile *profile); /** * @ingroup profile * * Set the report rate in Hz for the profile. * * A value of 0 hz disables the mode. * * If the profile mode is the currently active profile, * the change takes effect immediately. * * @param profile A previously initialized ratbag profile * @param hz Set to the report rate in Hz, may be 0 * * @return zero on success or an error code on failure */ enum ratbag_error_code ratbag_profile_set_report_rate(struct ratbag_profile *profile, unsigned int hz); /** * @ingroup profile * * Get the report rate in Hz for the profile. * * @param profile A previously initialized ratbag profile * * @return The report rate for this profile in Hz */ int ratbag_profile_get_report_rate(struct ratbag_profile *profile); /** * @ingroup profile * * Get the number of report rates in Hz available for this profile. * The list of report rates is sorted in ascending order but may be filtered * by libratbag and does not necessarily reflect all report rates supported by * the physical device. * * This function writes at most nrates values but returns the number of * report rates available on this resolution. In other words, if it returns a * number larger than nrates, call it again with an array the size of the * return value. * * @param[out] rates Set to the supported report rates in ascending order * @param[in] nrates The number of elements in resolutions * * @return The number of valid items in rates. If the returned value * is larger than nrates, the list was truncated. */ size_t ratbag_profile_get_report_rate_list(struct ratbag_profile *profile, unsigned int *rates, size_t nrates); /** * @ingroup profile * * Get the number of @ref ratbag_resolution available in this profile. A * resolution mode is a tuple of (resolution, report rate), each mode can be * fetched with ratbag_profile_get_resolution(). * * The returned value is the maximum number of modes available and thus * identical for all profiles. However, some of the modes may not be * configured. * * @param profile A previously initialized ratbag profile * * @return The number of resolutions available. */ unsigned int ratbag_profile_get_num_resolutions(struct ratbag_profile *profile); /** * @ingroup profile * * Return the resolution in DPI and the report rate in Hz for the resolution * mode identified by the given index. The index must be between 0 and * ratbag_profile_get_num_resolution_modes(). * * See ratbag_profile_get_num_resolution_modes() for a description of * resolution_modes. * * Profiles available but not currently configured on the device return * success but set dpi and hz to 0. * * The returned struct has a refcount of at least 1, use * ratbag_resolution_unref() to release the resources associated. * * @param profile A previously initialized ratbag profile * @param idx The index of the resolution mode to get * * @return zero on success, non-zero otherwise. On error, dpi and hz are * unmodified. */ struct ratbag_resolution * ratbag_profile_get_resolution(struct ratbag_profile *profile, unsigned int idx); /** * @ingroup resolution * * Add a reference to the resolution. A resolution is destroyed whenever the * reference count reaches 0. See @ref ratbag_resolution_unref. * * @param resolution A previously initialized valid ratbag resolution * @return The passed ratbag resolution */ struct ratbag_resolution * ratbag_resolution_ref(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Dereference the ratbag resolution. When the internal refcount reaches * zero, all resources associated with this object are released. The object * must be considered invalid once unref is called. * * @param resolution A previously initialized ratbag resolution * @return Always NULL */ struct ratbag_resolution * ratbag_resolution_unref(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Set caller-specific data associated with this resolution. libratbag does * not manage, look at, or modify this data. The caller must ensure the * data is valid. * * @param resolution A previously initialized resolution * @param userdata Caller-specific data passed to the various callback * interfaces. */ void ratbag_resolution_set_user_data(struct ratbag_resolution *resolution, void *userdata); /** * @ingroup resolution * * Get the caller-specific data associated with this resolution, if any. * * @param resolution A previously initialized ratbag resolution * @return The caller-specific data previously assigned in * ratbag_resolution_set_user_data(). */ void* ratbag_resolution_get_user_data(const struct ratbag_resolution *resolution); /** * @ingroup resolution * * Check if a resolution has a specific capability. * * @return non-zero if the capability is available, zero otherwise. */ bool ratbag_resolution_has_capability(struct ratbag_resolution *resolution, enum ratbag_resolution_capability cap); /** * @ingroup resolution * * Set the resolution in DPI for the resolution mode. * If the resolution has the @ref * RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION capability, this function * sets both x and y resolution to the given value. * * A value of 0 for dpi disables the mode. * * If the resolution mode is the currently active mode and the profile is * the currently active profile, the change takes effect immediately. * * @param resolution A previously initialized ratbag resolution * @param dpi Set to the resolution in dpi, 0 to disable * * @return zero on success or an error code on failure */ enum ratbag_error_code ratbag_resolution_set_dpi(struct ratbag_resolution *resolution, unsigned int dpi); /** * @ingroup resolution * * Set the x and y resolution in DPI for the resolution mode. * If the resolution does not have the @ref * RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION capability, this function * returns an error and does nothing. * * A value of 0 for both x and y disables the mode. If either value is 0 and * the other value is non-zero, this function returns an error and does * nothing. * * If the resolution mode is the currently active mode and the profile is * the currently active profile, the change takes effect immediately. * * @param resolution A previously initialized ratbag resolution with the * @ref RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION capability * @param x The x resolution in dpi * @param y The y resolution in dpi * * @return zero on success or an error code on failure */ enum ratbag_error_code ratbag_resolution_set_dpi_xy(struct ratbag_resolution *resolution, unsigned int x, unsigned int y); /** * @ingroup resolution * * Get the resolution in DPI for the resolution mode. * If the resolution has the @ref * RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION capability, this function * returns the x resolution, see ratbag_resolution_get_dpi_x(). * * A value of 0 for dpi indicates the mode is disabled. * * @param resolution A previously initialized ratbag resolution * * @return The resolution in dpi */ int ratbag_resolution_get_dpi(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Get the number of resolutions in DPI available for this resolution. * The list of DPI values is sorted in ascending order but may be filtered * by libratbag and does not necessarily reflect all resolutions supported * by the physical device. * * This function writes at most nres values but returns the number of * DPI values available on this resolution. In other words, if it returns a * number larger than nres, call it again with an array the size of the * return value. * * @param[out] resolutions Set to the supported DPI values in ascending order * @param[in] nres The number of elements in resolutions * * @return The number of valid items in resolutions. If the returned value * is larger than nres, the list was truncated. */ size_t ratbag_resolution_get_dpi_list(struct ratbag_resolution *resolution, unsigned int *resolutions, size_t nres); /** * @ingroup resolution * * Get the x resolution in DPI for the resolution mode. If the resolution * does not have the @ref RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION * capability, this function is identical to ratbag_resolution_get_dpi(). * * A value of 0 for dpi indicates the mode is disabled. * * @param resolution A previously initialized ratbag resolution with the * @ref RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION capability * * @return The resolution in dpi */ int ratbag_resolution_get_dpi_x(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Get the y resolution in DPI for the resolution mode. If the resolution * does not have the @ref RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION * capability, this function is identical to ratbag_resolution_get_dpi(). * * A value of 0 for dpi indicates the mode is disabled. * * @param resolution A previously initialized ratbag resolution with the * @ref RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION capability * * @return The resolution in dpi */ int ratbag_resolution_get_dpi_y(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Activate the given resolution mode. If the mode is not configured, this * function returns an error and the result is undefined. * * The mode must be one of the current profile, otherwise an error is * returned. * * @param resolution A previously initialized ratbag resolution * * @return zero on success or an error code on failure */ enum ratbag_error_code ratbag_resolution_set_active(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Check if the resolution mode is the currently active one. * * If the profile is the currently active profile, the mode is the one * currently active. For profiles not currently active, this always returns 0. * * @param resolution A previously initialized ratbag resolution * * @return Non-zero if the resolution mode is the active one, zero * otherwise. */ bool ratbag_resolution_is_active(const struct ratbag_resolution *resolution); /** * @ingroup resolution * * Set the default resolution mode for the associated profile. When the * device switches to the profile next, this mode will be the active * resolution. If the mode is not configured, this function returns an error * and the result is undefined. * * This only switches the default resolution, not the currently active * resolution. Use ratbag_resolution_set_active() instead. * * @param resolution A previously initialized ratbag resolution * * @return zero on success or an error code on failure */ enum ratbag_error_code ratbag_resolution_set_default(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Check if the resolution mode is the default one in this profile. * * The default resolution is the one the device selects when switching to * the corresponding profile. It may not be the currently active resolution, * use ratbag_resolution_is_active() instead. * * @param resolution A previously initialized ratbag resolution * * @return Non-zero if the resolution mode is the default one, zero * otherwise. */ bool ratbag_resolution_is_default(const struct ratbag_resolution *resolution); /** * @ingroup profile * * Return a reference to the button given by the index. The order of the * buttons is device-specific though indices 0, 1 and 2 should always refer * to left, middle, right buttons. Use ratbag_button_get_type() to get the * physical type of the button. * * The button is refcounted with an initial value of at least 1. * Use ratbag_button_unref() to release the button. * * @param profile A previously initialized ratbag profile * @param index The index of the button * * @return A button context, or NULL if the button does not exist. * * @see ratbag_device_get_num_buttons */ struct ratbag_button* ratbag_profile_get_button(struct ratbag_profile *profile, unsigned int index); /** * @ingroup button * * Set caller-specific data associated with this button. libratbag does * not manage, look at, or modify this data. The caller must ensure the * data is valid. * * @param button A previously initialized button * @param userdata Caller-specific data passed to the various callback * interfaces. */ void ratbag_button_set_user_data(struct ratbag_button *button, void *userdata); /** * @ingroup button * * Get the caller-specific data associated with this button, if any. * * @param button A previously initialized ratbag button * @return The caller-specific data previously assigned in * ratbag_button_set_user_data(). */ void* ratbag_button_get_user_data(const struct ratbag_button *button); /** * @ingroup button * * @return The type of the action currently configured for this button */ enum ratbag_button_action_type ratbag_button_get_action_type(struct ratbag_button *button); /** * @ingroup button * * Check if a button supports a specific action type. Not all devices allow * all buttons to be assigned any action. Ability to change a button to a * given action type does not guarantee that any specific action can be * configured. * * @note It is a client bug to pass in @ref * RATBAG_BUTTON_ACTION_TYPE_UNKNOWN or @ref * RATBAG_BUTTON_ACTION_TYPE_NONE. * * @param button A previously initialized button * @param action_type An action type * * @return non-zero if the action type is supported, zero otherwise. */ bool ratbag_button_has_action_type(struct ratbag_button *button, enum ratbag_button_action_type action_type); /** * @ingroup button * * If a button's action is @ref RATBAG_BUTTON_ACTION_TYPE_BUTTON, * this function returns the logical button number this button is mapped to, * starting at 1. The button numbers are in sequence and do not correspond * to any meaning other than its numeric value. It is up to the input stack * how to map that logical button number, but usually buttons 1, 2 and 3 are * mapped into left, middle, right. * * If the button's action type is not @ref RATBAG_BUTTON_ACTION_TYPE_BUTTON, * this function returns 0. * * @return The logical button number this button sends. * @retval 0 This button is disabled or its action type is not @ref * RATBAG_BUTTON_ACTION_TYPE_BUTTON. * * @see ratbag_button_set_button */ unsigned int ratbag_button_get_button(struct ratbag_button *button); /** * @ingroup button * * See ratbag_button_get_button() for a description of the button number. * * @param button A previously initialized ratbag button * @param btn The logical button number to assign to this button. * @return 0 on success or an error code otherwise. On success, the button's * action is set to @ref RATBAG_BUTTON_ACTION_TYPE_BUTTON. * * @see ratbag_button_get_button */ enum ratbag_error_code ratbag_button_set_button(struct ratbag_button *button, unsigned int btn); /** * @ingroup button * * If a button's action is @ref RATBAG_BUTTON_ACTION_TYPE_SPECIAL, * this function returns the special function assigned to this button. * * If the button's action type is not @ref RATBAG_BUTTON_ACTION_TYPE_SPECIAL, * this function returns @ref RATBAG_BUTTON_ACTION_SPECIAL_INVALID. * * @return The special function assigned to this button * * @see ratbag_button_set_button */ enum ratbag_button_action_special ratbag_button_get_special(struct ratbag_button *button); /** * @ingroup led * * Return a reference to the LED given by the index. The order of the * LEDs is device-specific though. * * The LED is refcounted with an initial value of at least 1. * Use ratbag_led_unref() to release the LED. * * @param profile A previously initialized ratbag profile * @param index The index of the LED * * @return A LED context, or NULL if the LED does not exist. * * @see ratbag_device_get_profile */ struct ratbag_led * ratbag_profile_get_led(struct ratbag_profile *profile, unsigned int index); /** * @ingroup led * * This function returns true if the given mode is supported by the LED, or * false otherwise. * * @param led A previously initialized ratbag LED * @param mode The LED mode @ref ratbag_led_mode to check for * @return True if supported, false otherwise * * @see ratbag_led_set_mode */ bool ratbag_led_has_mode(struct ratbag_led *led, enum ratbag_led_mode mode); /** * @ingroup led * * This function returns the mode for ratbag_led. * * @param led A previously initialized ratbag LED * @return The LED mode @ref ratbag_led_mode * * @see ratbag_led_set_mode */ enum ratbag_led_mode ratbag_led_get_mode(struct ratbag_led *led); /** * @ingroup led * * This function returns the led color. * * If any color scaling applies because of the device's color depth * this is not reflected in the returned value. In other words, * the returned value always matches the most recent value provided * to ratbag_led_set_color(). * * @param led A previously initialized ratbag LED * @return The LED color in @ref ratbag_led_mode * * @see ratbag_led_set_color */ struct ratbag_color ratbag_led_get_color(struct ratbag_led *led); /** * @ingroup led * * This function returns the color depth of this LED. * * @param led A previously initialized ratbag LED * @return The bit depth of this LED * * @see ratbag_led_set_color */ enum ratbag_led_colordepth ratbag_led_get_colordepth(struct ratbag_led *led); /** * @ingroup led * * This function returns the LED effect duration. * * @param led A previously initialized ratbag LED * @return The LED duration in ms, can be 0 - 10000 * * @see ratbag_led_set_effect_duration */ int ratbag_led_get_effect_duration(struct ratbag_led *led); /** * @ingroup led * * This function returns the LED brightness. * * @param led A previously initialized ratbag LED * @return The LED brightness 0 - 255 * * @see ratbag_led_get_brightness */ unsigned int ratbag_led_get_brightness(struct ratbag_led *led); /** * @ingroup led * * this function sets the LED mode. * * @param led A previously initialized ratbag LED * @param mode LED mode @ref ratbag_led_mode. * @return 0 on success or an error code otherwise. * * @see ratbag_led_get_mode */ enum ratbag_error_code ratbag_led_set_mode(struct ratbag_led *led, enum ratbag_led_mode mode); /** * @ingroup led * * If the LED's mode is @ref RATBAG_LED_ON or @ref RATBAG_LED_BREATHING * then this function sets the LED color, otherwise it has no effect. * * The color provided has to be within the allowed color range (see @ref * ratbag_color). libratbag silently scales and/or clamps this range into * the device's color depth. It is the caller's responsibility to set the * colors in a non-ambiguous way for the device's bit depth. See @ref * ratbag_led_colordepth for more details. * * @param led A previously initialized ratbag LED * @param color A LED color. * @return 0 on success or an error code otherwise. * * @see ratbag_led_get_color */ enum ratbag_error_code ratbag_led_set_color(struct ratbag_led *led, struct ratbag_color color); /** * @ingroup led * * If the LED's mode is @ref RATBAG_LED_CYCLE or @ref RATBAG_LED_BREATHING * then this function sets the LED duration in ms * * @param led A previously initialized ratbag LED * @param rate Effect duration in ms, 0 - 10000 * @return 0 on success or an error code otherwise. * * @see ratbag_led_get_effect_duration */ enum ratbag_error_code ratbag_led_set_effect_duration(struct ratbag_led *led, unsigned int rate); /** * @ingroup led * * If the LED's mode is @ref RATBAG_LED_CYCLE or @ref RATBAG_LED_BREATHING * then this function sets the LED brightness, otherwise it has no effect. * * @param led A previously initialized ratbag LED * @param brightness Effect brightness 0 - 255 * @return 0 on success or an error code otherwise. * * @see ratbag_led_get_brightness */ enum ratbag_error_code ratbag_led_set_brightness(struct ratbag_led *led, unsigned int brightness); /** * @ingroup button * * This function sets the special function assigned to this button. * * @param button A previously initialized ratbag button * @param action The special action to assign to this button. * @return 0 on success or an error code otherwise. On success, the button's * action is set to @ref RATBAG_BUTTON_ACTION_TYPE_SPECIAL. * * @see ratbag_button_get_button */ enum ratbag_error_code ratbag_button_set_special(struct ratbag_button *button, enum ratbag_button_action_special action); /** * @ingroup button * * If a button's action is @ref RATBAG_BUTTON_ACTION_TYPE_KEY, * this function returns the key or button configured for this button. * * If the button's action type is not @ref RATBAG_BUTTON_ACTION_TYPE_KEY, * this function returns 0 and leaves modifiers and sz untouched. * * @param button A previously initialized ratbag button * @param[out] modifiers Will be filled with the modifiers required for this * action. The modifiers are as defined in linux/input.h. * @param[in,out] sz Takes the size of the modifiers array and returns the * number of modifiers filled in. sz may be 0 if no modifiers are required. * * @note The caller must ensure that modifiers is large enough to accomodate * for the key combination. * * @return The button number */ unsigned int ratbag_button_get_key(struct ratbag_button *button, unsigned int *modifiers, size_t *sz); /** * @ingroup button * * @param button A previously initialized ratbag button * @param key The button number to assign to this button, one of BTN_* as * defined in linux/input.h * @param modifiers The modifiers required for this action. The * modifiers are as defined in linux/input.h, in the order they should be * pressed. * @param sz The size of the modifiers array. sz may be 0 if no modifiers * are required. * * @return 0 on success or an error code otherwise. On success, the button's * action is set to @ref RATBAG_BUTTON_ACTION_TYPE_KEY. */ enum ratbag_error_code ratbag_button_set_key(struct ratbag_button *button, unsigned int key, unsigned int *modifiers, size_t sz); /** * @ingroup button * * @param button A previously initialized ratbag button * * @return 0 on success or an error code otherwise. On success, the button's * action is set to @ref RATBAG_BUTTON_ACTION_TYPE_NONE. */ enum ratbag_error_code ratbag_button_disable(struct ratbag_button *button); /** * @ingroup button * * @param macro A previously initialized ratbag button macro * * @return The name of the macro */ const char * ratbag_button_macro_get_name(struct ratbag_button_macro *macro); /** * @ingroup button * * @param macro A previously initialized ratbag button macro * * @return The maximum number of events that can be assigned to this macro */ unsigned int ratbag_button_macro_get_num_events(struct ratbag_button_macro *macro); /** * @ingroup button * * Returns the macro event type configured for the event at the * given index. * * The behavior of this function for an index equal to or greater than the * return value of ratbag_button_macro_get_num_events() is undefined. * * @param macro A previously initialized ratbag button macro * @param index An index of the event within the macro we are interested in. * * @return The type of the event at the given index */ enum ratbag_macro_event_type ratbag_button_macro_get_event_type(struct ratbag_button_macro *macro, unsigned int index); /** * @ingroup button * * If the event stored at the given index is @ref * RATBAG_MACRO_EVENT_KEY_PRESSED or @ref RATBAG_MACRO_EVENT_KEY_RELEASED, * this function returns the key code configured for the event at the given * index. * * The behavior of this function for an index equal to or greater than the * return value of ratbag_button_macro_get_num_events() is undefined. * * @param macro A previously initialized ratbag button macro * @param index An index of the event within the macro we are interested in. * * @return The key of the event at the given index */ int ratbag_button_macro_get_event_key(struct ratbag_button_macro*macro, unsigned int index); /** * @ingroup button * * If the event stored at the given index is @ref RATBAG_MACRO_EVENT_WAIT, * this function returns the timeout configured for the event at the given * index. * * The behavior of this function for an index equal to or greater than the * return value of ratbag_button_macro_get_num_events() is undefined. * * @param macro A previously initialized ratbag button macro * @param index An index of the event within the macro we are interested in. * * @return The timeout of the event at the given index */ int ratbag_button_macro_get_event_timeout(struct ratbag_button_macro *macro, unsigned int index); /** * @ingroup button * * Sets the button's action to @ref RATBAG_BUTTON_ACTION_TYPE_MACRO and * assigns the given macro to this button. * * libratbag does not use the macro struct passed in, it extracts the * required information from the struct. Changes to the macro after a call * to ratbag_button_set_macro() are not reflected in the device until a * subsequent call to ratbag_button_set_macro(). * * @param button A previously intialized ratbag button * @param macro A fully initialized macro * * @return 0 on success or nonzero otherwise */ enum ratbag_error_code ratbag_button_set_macro(struct ratbag_button *button, const struct ratbag_button_macro *macro); /** * @ingroup button * * Initialize a new button macro. * * The macro is refcounted with an initial value of at least 1. * Use ratbag_button_macro_unref() to release the macro. * * Note that some devices have limited storage for the macro names. * libratbag silently shortens macro names to the longest string the device * is capable of storing. * * @param name The name to assign to this macro. * * @return An "empty" button macro */ struct ratbag_button_macro * ratbag_button_macro_new(const char *name); /** * @ingroup button * * If a button's action is @ref RATBAG_BUTTON_ACTION_TYPE_MACRO, * this function returns the current button macro. The macro is a copy of * the one used on the device, changes to the macro are not reflected on the * device until a subsequent call to ratbag_button_set_macro(). * * If a button's action is not @ref RATBAG_BUTTON_ACTION_TYPE_MACRO, * this function returns NULL. * * @param button A previously initialized ratbag button */ struct ratbag_button_macro * ratbag_button_get_macro(struct ratbag_button *button); /** * @ingroup button * * Sets the macro's event at the given index to the given type with the * key code or timeout given. * * The behavior of this function for an index equal to or greater than the * return value of ratbag_button_macro_get_num_events() is undefined. */ enum ratbag_error_code ratbag_button_macro_set_event(struct ratbag_button_macro *macro, unsigned int index, enum ratbag_macro_event_type type, unsigned int data); /** * @ingroup button * * Add a reference to the macro. A macro is destroyed whenever the * reference count reaches 0. See @ref ratbag_button_macro_unref. * * @param macro A previously initialized valid ratbag button macro * @return The passed ratbag macro */ struct ratbag_button_macro * ratbag_button_macro_ref(struct ratbag_button_macro *macro); /** * @ingroup button * * Dereference the ratbag button macro. When the internal refcount reaches * zero, all resources associated with this object are released. The object * must be considered invalid once unref is called. * * @param macro A previously initialized ratbag button macro * @return Always NULL */ struct ratbag_button_macro * ratbag_button_macro_unref(struct ratbag_button_macro *macro); /** * @ingroup button * * Add a reference to the button. A button is destroyed whenever the * reference count reaches 0. See @ref ratbag_button_unref. * * @param button A previously initialized valid ratbag button * @return The passed ratbag button */ struct ratbag_button * ratbag_button_ref(struct ratbag_button *button); /** * @ingroup button * * Dereference the ratbag button. When the internal refcount reaches * zero, all resources associated with this object are released. The object * must be considered invalid once unref is called. * * @param button A previously initialized ratbag button * @return Always NULL */ struct ratbag_button * ratbag_button_unref(struct ratbag_button *button); /** * @ingroup led * * Add a reference to the led. A led is destroyed whenever the * reference count reaches 0. See @ref ratbag_led_unref. * * @param led A previously initialized valid ratbag led * @return The passed ratbag led */ struct ratbag_led * ratbag_led_ref(struct ratbag_led *led); /** * @ingroup led * * Dereference the ratbag led. When the internal refcount reaches * zero, all resources associated with this object are released. The object * must be considered invalid once unref is called. * * @param led A previously initialized ratbag led * @return Always NULL */ struct ratbag_led * ratbag_led_unref(struct ratbag_led *led); libratbag-0.13/src/libratbag.i000066400000000000000000000044521362011324700162600ustar00rootroot00000000000000/* wrapping libratbag functions from libratbag.h using SWIG. */ %module libratbag %{ /* the resulting C file should be built as a python extension */ #define SWIG_FILE_WITH_INIT /* Includes the header in the wrapper code */ #include #include %} #define __attribute__(x) /* custom typemap for handling ratbag_resolution_get_dpi_list */ %typemap(in) (unsigned int *resolutions, size_t nres) { unsigned int i; if (!PyList_Check($input)) { PyErr_SetString(PyExc_ValueError, "Expecting a list"); return NULL; } $2 = PyList_Size($input); $1 = (unsigned int *) malloc(($2 + 1) * sizeof(unsigned int)); for (i = 0; i < $2; i++) { PyObject *s = PyList_GetItem($input, i); if (!PyInt_Check(s)) { free($1); PyErr_SetString(PyExc_ValueError, "List items must be integers"); return NULL; } $1[i] = PyInt_AsLong(s); } $1[i] = 0; } %typemap(argout) (unsigned int *resolutions, size_t nres) { unsigned int i; for (i = 0; i < $2; i++) { PyList_SetItem($input, i, PyInt_FromLong($1[i])); } } %typemap(freearg) (unsigned int *resolutions, size_t nres) { if ($1) free($1); } /* END OF custom typemap for handling ratbag_resolution_get_dpi_list */ /* custom typemap for handling ratbag_resolution_get_report_rate_list */ /* We re-use the ratbag_resolution_get_dpi_list typemap */ %typemap(in) (unsigned int *rates, size_t nrates) = (unsigned int *resolutions, size_t nres); %typemap(argout) (unsigned int *rates, size_t nrates) = (unsigned int *resolutions, size_t nres); %typemap(freearg) (unsigned int *rates, size_t nrates) = (unsigned int *resolutions, size_t nres); /* END OF custom typemap for handling ratbag_resolution_get_report_rate_list */ /* uintXX_t mapping: Python -> C */ %typemap(in) uint8_t { $1 = (uint8_t) PyInt_AsLong($input); } %typemap(in) uint16_t { $1 = (uint16_t) PyInt_AsLong($input); } %typemap(in) uint32_t { $1 = (uint32_t) PyInt_AsLong($input); } /* uintXX_t mapping: C -> Python */ %typemap(out) uint8_t { $result = PyInt_FromLong((long) $1); } %typemap(out) uint16_t { $result = PyInt_FromLong((long) $1); } %typemap(out) uint32_t { $result = PyInt_FromLong((long) $1); } /* Parse the header file to generate wrappers */ %include "libratbag.h" %include "libratbag-enums.h" %include "shared.h" libratbag-0.13/src/shared-macro.h000066400000000000000000000203221362011324700166670ustar00rootroot00000000000000#pragma once /*** This file is part of ratbagd. Copyright 2015 David Herrmann 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 (including the next paragraph) 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. ***/ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include /* * We require: * sizeof(void*) == sizeof(long) * sizeof(long) == 4 || sizeof(long) == 8 * sizeof(int) == 4 * The linux kernel requires the same from the toolchain, so this should work * just fine. */ #if __SIZEOF_POINTER__ != __SIZEOF_LONG__ # error "sizeof(void*) != sizeof(long)" #elif __SIZEOF_LONG__ != 4 && __SIZEOF_LONG__ != 8 # error "sizeof(long) != 4 && sizeof(long) != 8" #elif __SIZEOF_INT__ != 4 # error "sizeof(int) != 4" #endif /* * Shortcuts for gcc attributes. See GCC manual for details. */ #define _alignas_(_x) __attribute__((aligned(__alignof(_x)))) #define _alloc_(...) __attribute__((alloc_size(__VA_ARGS__))) #define _cleanup_(_x) __attribute__((cleanup(_x))) #define _const_ __attribute__((const)) #define _deprecated_ __attribute__((deprecated)) #define _hidden_ __attribute__((visibility("hidden"))) #define _likely_(_x) (__builtin_expect(!!(_x), 1)) #define _malloc_ __attribute__((malloc)) #define _packed_ __attribute__((packed)) #define _printf_(_a, _b) __attribute__((format (printf, _a, _b))) #define _public_ __attribute__((visibility("default"))) #define _pure_ __attribute__((pure)) #define _sentinel_ __attribute__((sentinel)) #define _unlikely_(_x) (__builtin_expect(!!(_x), 0)) #define _unused_ __attribute__((unused)) #define _weak_ __attribute__((weak)) #define _weakref_(_x) __attribute__((weakref(#_x))) /* container_of macro */ #ifdef __GNUC__ #define container_of(ptr, sample, member) \ (__typeof__(sample))((char *)(ptr) - \ ((char *)&(sample)->member - (char *)(sample))) #else #define container_of(ptr, sample, member) \ (void *)((char *)(ptr) - \ ((char *)&(sample)->member - (char *)(sample))) #endif #define LONG_BITS (sizeof(long) * 8) #define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS) #define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) #define ARRAY_FOR_EACH(_arr, _elem) \ for (size_t _i = 0; _i < ARRAY_LENGTH(_arr) && (_elem = &_arr[_i]); _i++) #define AS_MASK(v) (1 << (v)) #define min(a, b) (((a) < (b)) ? (a) : (b)) #define max(a, b) (((a) > (b)) ? (a) : (b)) /* * DECIMAL_TOKEN_MAX() - calculate maximum length of the decimal representation * of an integer * @_type: type of integer */ #define DECIMAL_TOKEN_MAX(_type) \ (1 + (sizeof(_type) <= 1 ? 3 : \ sizeof(_type) <= 2 ? 5 : \ sizeof(_type) <= 4 ? 10 : \ sizeof(_type) <= 8 ? 20 : \ sizeof(int[-2 * (sizeof(_type) > 8)]))) /* * PROTECT_ERRNO: make sure a function protects errno */ static inline void reset_errno(int *saved_errno) { errno = *saved_errno; } #define PROTECT_ERRNO _cleanup_(reset_errno) _unused_ int saved_errno = errno #define _cleanup_free_ _cleanup_(freep) #define _cleanup_close_ _cleanup_(safe_closep) /* * DEFINE_TRIVIAL_CLEANUP_FUNC() - define helper suitable for _cleanup_() * @_type: type of object to cleanup * @_func: function to call on cleanup */ #define DEFINE_TRIVIAL_CLEANUP_FUNC(_type, _func) \ static inline void _func##p(_type *_p) \ { \ if (*_p) \ _func(*_p); \ } \ struct __useless_struct_to_allow_trailing_semicolon__ DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev*, udev_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_device*, udev_device_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_enumerate*, udev_enumerate_unref); /* * ELEMENTSOF() - number of array elements * @_array: C array * * This calculates the number of elements (compared to the byte size) of * the given C array. This returns (void) if the passed argument is not * actually a valid C array (decided at compile time). */ #define ELEMENTSOF(_array) \ __extension__ (__builtin_choose_expr( \ !__builtin_types_compatible_p(typeof(_array), \ typeof(&*(_array))), \ sizeof(_array) / sizeof((_array)[0]), \ (void)0)) /* * negative_errno() - return negative errno * * This helper should be used to shut up gcc if you know 'errno' is * negative. Instead of "return -errno;", use "return negative_errno();" * It will suppress bogus gcc warnings in case it assumes 'errno' might * be 0 and thus the caller's error-handling might not be triggered. */ static inline int negative_errno(void) { return (errno <= 0) ? -EINVAL : -errno; } /* * mfree() - free memory * @mem: memory to free * * This is basically the same as free(), but returns NULL. This makes free() * follow the same style as all our other destructors. */ static inline void *mfree(void *mem) { free(mem); return NULL; } /* * safe_close() - safe variant of close(2) * @fd: fd to close * * This is the same as close(2), but allows passing negative FDs, which makes * it a no-op. Furthermore, this always returns -1. */ static inline int safe_close(int fd) { if (fd >= 0) { PROTECT_ERRNO; close(fd); } return -1; } /* * Miscellaneous cleanup helpers * All these helpers are suitable for use with _cleanup_(). They call the helper * they're named after on the variable marked for cleanup. */ static inline void freep(void *p) { free(*(void**)p); } static inline void safe_closep(int *fd) { safe_close(*fd); } /* * streq() - test whether two strings are equal * @_a: string A * @_b: string B */ #define streq(_a, _b) (strcmp((_a), (_b)) == 0) #define strneq(s1, s2, n) (strncmp((s1), (s2), (n)) == 0) /* * streq_ptr() - test whether two strings are equal, considering NULL valid * @a: string A or NULL * @b: string B or NULL */ static inline bool streq_ptr(const char *a, const char *b) { return (a && b) ? streq(a, b) : (!a && !b); } /* * startswith() - test prefix of a string * @s: string to test * @prefix: prefix to test for * * This returns a pointer to the first character in @s that follows @prefix. If * @s does not start with @prefix, NULL is returned. */ static inline char *startswith(const char *s, const char *prefix) { size_t l; l = strlen(prefix); if (strncmp(s, prefix, l) == 0) return (char*) s + l; return NULL; } /* * safe_atou() - safe variant of strtoul() * @s: string to parse * @ret_u: output storage for parsed integer * * This is a sane and safe variant of strtoul(), which rejects any invalid * input, or trailing garbage. * * Return: 0 on success, negative error code on failure. */ static inline int safe_atou(const char *s, unsigned int *ret_u) { char *x = NULL; unsigned long l; assert(s); assert(ret_u); errno = 0; l = strtoul(s, &x, 0); if (!x || x == s || *x || errno) return errno > 0 ? -errno : -EINVAL; if ((unsigned long)(unsigned int)l != l) return -ERANGE; *ret_u = (unsigned int)l; return 0; } /* * now() - returns current time in nano-seconds * @clock: clock to use */ static inline uint64_t now(clockid_t clock) { struct timespec spec = {}; clock_gettime(clock, &spec); return spec.tv_sec * 1000ULL * 1000ULL * 1000ULL + spec.tv_nsec; } libratbag-0.13/src/shared-rbtree.c000066400000000000000000000421111362011324700170440ustar00rootroot00000000000000/*** This file is part of ratbagd. Copyright 2015 David Herrmann 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 (including the next paragraph) 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. ***/ /* * Open Red-Black-Tree Implementation * You're highly recommended to read a paper on rb-trees before reading this * code. The reader is expected to be familiar with the different cases that * might occur during insertion and removal of elements. The comments in this * file do not contain a full prove for correctness. */ #include "config.h" #include "shared-rbtree.h" RBNode *rbnode_leftmost(RBNode *n) { if (n) while (n->left) n = n->left; return n; } RBNode *rbnode_rightmost(RBNode *n) { if (n) while (n->right) n = n->right; return n; } RBNode *rbtree_first(RBTree *t) { return rbnode_leftmost(t->root); } RBNode *rbtree_last(RBTree *t) { return rbnode_rightmost(t->root); } RBNode *rbnode_next(RBNode *n) { RBNode *p; if (!rbnode_linked(n)) return NULL; if (n->right) return rbnode_leftmost(n->right); while ((p = rbnode_parent(n)) && n == p->right) n = p; return p; } RBNode *rbnode_prev(RBNode *n) { RBNode *p; if (!rbnode_linked(n)) return NULL; if (n->left) return rbnode_rightmost(n->left); while ((p = rbnode_parent(n)) && n == p->left) n = p; return p; } static inline void rbnode_reparent(RBNode *n, RBNode *p, unsigned long c) { /* change color and/or parent of a node */ rbtree_assert(!((unsigned long)p & 1)); rbtree_assert(c < 2); n->__parent_and_color = (RBNode*)((unsigned long)p | c); } static inline void rbtree_reparent(RBTree *t, RBNode *p, RBNode *old, RBNode *new) { /* change previous parent/root from old to new node */ if (p) { if (p->left == old) p->left = new; else p->right = new; } else { t->root = new; } } static inline RBNode *rbtree_paint_one(RBTree *t, RBNode *n) { RBNode *p, *g, *gg, *u, *x; /* * Paint a single node according to RB-Tree rules. The node must * already be linked into the tree and painted red. * We repaint the node or rotate the tree, if required. In case a * recursive repaint is required, the next node to be re-painted * is returned. * p: parent * g: grandparent * gg: grandgrandparent * u: uncle * x: temporary */ /* node is red, so we can access the parent directly */ p = n->__parent_and_color; if (!p) { /* Case 1: * We reached the root. Mark it black and be done. As all * leaf-paths share the root, the ratio of black nodes on each * path stays the same. */ rbnode_reparent(n, p, RBNODE_BLACK); n = NULL; } else if (rbnode_black(p)) { /* Case 2: * The parent is already black. As our node is red, we did not * change the number of black nodes on any path, nor do we have * multiple consecutive red nodes. */ n = NULL; } else if (p == p->__parent_and_color->left) { /* parent is red, so grandparent exists */ g = p->__parent_and_color; gg = rbnode_parent(g); u = g->right; if (u && rbnode_red(u)) { /* Case 3: * Parent and uncle are both red. We know the * grandparent must be black then. Repaint parent and * uncle black, the grandparent red and recurse into * the grandparent. */ rbnode_reparent(p, g, RBNODE_BLACK); rbnode_reparent(u, g, RBNODE_BLACK); rbnode_reparent(g, gg, RBNODE_RED); n = g; } else { /* parent is red, uncle is black */ if (n == p->right) { /* Case 4: * We're the right child. Rotate on parent to * become left child, so we can handle it the * same as case 5. */ x = n->left; p->right = n->left; n->left = p; if (x) rbnode_reparent(x, p, RBNODE_BLACK); rbnode_reparent(p, n, RBNODE_RED); p = n; } /* 'n' is invalid from here on! */ n = NULL; /* Case 5: * We're the red left child or a red parent, black * grandparent and uncle. Rotate on grandparent and * switch color with parent. Number of black nodes on * each path stays the same, but we got rid of the * double red path. As the grandparent is still black, * we're done. */ x = p->right; g->left = x; p->right = g; if (x) rbnode_reparent(x, g, RBNODE_BLACK); rbnode_reparent(p, gg, RBNODE_BLACK); rbnode_reparent(g, p, RBNODE_RED); rbtree_reparent(t, gg, g, p); } } else /* if (p == p->__parent_and_color->left) */ { /* same as above, but mirrored */ g = p->__parent_and_color; gg = rbnode_parent(g); u = g->left; if (u && rbnode_red(u)) { rbnode_reparent(p, g, RBNODE_BLACK); rbnode_reparent(u, g, RBNODE_BLACK); rbnode_reparent(g, gg, RBNODE_RED); n = g; } else { if (n == p->left) { x = n->right; p->left = n->right; n->right = p; if (x) rbnode_reparent(x, p, RBNODE_BLACK); rbnode_reparent(p, n, RBNODE_RED); p = n; } n = NULL; x = p->left; g->right = x; p->left = g; if (x) rbnode_reparent(x, g, RBNODE_BLACK); rbnode_reparent(p, gg, RBNODE_BLACK); rbnode_reparent(g, p, RBNODE_RED); rbtree_reparent(t, gg, g, p); } } return n; } static inline void rbtree_paint(RBTree *t, RBNode *n) { while (n) n = rbtree_paint_one(t, n); } void rbtree_add(RBTree *t, RBNode *p, RBNode **l, RBNode *n) { n->__parent_and_color = p; n->left = n->right = NULL; *l = n; rbtree_paint(t, n); } static inline RBNode *rbtree_rebalance_one(RBTree *t, RBNode *p, RBNode *n) { RBNode *s, *x, *y, *g; /* * Rebalance tree after a node was removed. This happens only if you * remove a black node and one path is now left with an unbalanced * number or black nodes. * This function assumes all paths through p and n have one black node * less than all other paths. If recursive fixup is required, the * current node is returned. */ if (n == p->left) { s = p->right; if (rbnode_red(s)) { /* Case 3: * We have a red node as sibling. Rotate it onto our * side so we can later on turn it black. This way, we * gain the additional black node in our path. */ g = rbnode_parent(p); x = s->left; p->right = x; s->left = p; rbnode_reparent(x, p, RBNODE_BLACK); rbnode_reparent(s, g, rbnode_color(p)); rbnode_reparent(p, s, RBNODE_RED); rbtree_reparent(t, g, p, s); s = x; } x = s->right; if (!x || rbnode_black(x)) { y = s->left; if (!y || rbnode_black(y)) { /* Case 4: * Our sibling is black and has only black * children. Flip it red and turn parent black. * This way we gained a black node in our path, * or we fix it recursively one layer up, which * will rotate the red sibling as parent. */ rbnode_reparent(s, p, RBNODE_RED); if (rbnode_black(p)) return p; rbnode_reparent(p, rbnode_parent(p), RBNODE_BLACK); return NULL; } /* Case 5: * Left child of our sibling is red, right one is black. * Rotate on parent so the right child of our sibling is * now red, and we can fall through to case 6. */ x = y->right; s->left = y->right; y->right = s; p->right = y; if (x) rbnode_reparent(x, s, RBNODE_BLACK); x = s; s = y; } /* Case 6: * The right child of our sibling is red. Rotate left and flip * colors, which gains us an additional black node in our path, * that was previously on our sibling. */ g = rbnode_parent(p); y = s->left; p->right = y; s->left = p; rbnode_reparent(x, s, RBNODE_BLACK); if (y) rbnode_reparent(y, p, rbnode_color(y)); rbnode_reparent(s, g, rbnode_color(p)); rbnode_reparent(p, s, RBNODE_BLACK); rbtree_reparent(t, g, p, s); } else /* if (!n || n == p->right) */ { /* same as above, but mirrored */ s = p->left; if (rbnode_red(s)) { g = rbnode_parent(p); x = s->right; p->left = x; s->right = p; rbnode_reparent(x, p, RBNODE_BLACK); rbnode_reparent(s, g, RBNODE_BLACK); rbnode_reparent(p, s, RBNODE_RED); rbtree_reparent(t, g, p, s); s = x; } x = s->left; if (!x || rbnode_black(x)) { y = s->right; if (!y || rbnode_black(y)) { rbnode_reparent(s, p, RBNODE_RED); if (rbnode_black(p)) return p; rbnode_reparent(p, rbnode_parent(p), RBNODE_BLACK); return NULL; } x = y->left; s->right = y->left; y->left = s; p->left = y; if (x) rbnode_reparent(x, s, RBNODE_BLACK); x = s; s = y; } g = rbnode_parent(p); y = s->right; p->left = y; s->right = p; rbnode_reparent(x, s, RBNODE_BLACK); if (y) rbnode_reparent(y, p, rbnode_color(y)); rbnode_reparent(s, g, rbnode_color(p)); rbnode_reparent(p, s, RBNODE_BLACK); rbtree_reparent(t, g, p, s); } return NULL; } static inline void rbtree_rebalance(RBTree *t, RBNode *p) { RBNode *n = NULL; while (p) { n = rbtree_rebalance_one(t, p, n); p = n ? rbnode_parent(n) : NULL; } } void rbtree_remove(RBTree *t, RBNode *n) { RBNode *p, *s, *gc, *x, *next = NULL; unsigned long c; /* * To remove an interior node from a binary tree, we simply find its * successor, swap both nodes and then remove the node. Therefore, the * only interesting case is were the node to be removed has at most one * child. * p: parent * s: successor * gc: grand-...-child * x: temporary * next: next node to rebalance on */ if (!n->left) { /* Case 1: * We have at most one child, which must be red. We're * guaranteed to be black then. Replace our node with the child * (in case it exists), but turn it black. * If no child exists, we need to rebalance, in case we were * black as our path now lost a black node. */ p = rbnode_parent(n); c = rbnode_color(n); rbtree_reparent(t, p, n, n->right); if (n->right) rbnode_reparent(n->right, p, c); else next = (c == RBNODE_BLACK) ? p : NULL; } else if (!n->right) { /* case 1 mirrored (but n->left guaranteed non-NULL) */ p = rbnode_parent(n); c = rbnode_color(n); rbnode_reparent(n->left, p, c); rbtree_reparent(t, p, n, n->left); } else { /* Case 2: * We're an interior node. Find our successor and swap it with * our node. Then remove our node. For performance reasons we * don't perform the full swap, but skip links that are about to * be removed, anyway. */ s = n->right; if (!s->left) { /* right child is next, no need to touch grandchild */ p = s; gc = s->right; } else { /* find successor and swap partially */ s = rbnode_leftmost(s); p = rbnode_parent(s); gc = s->right; p->left = s->right; s->right = n->right; rbnode_reparent(n->right, s, rbnode_color(n->right)); } /* node is partially swapped, now remove as in case 1 */ s->left = n->left; rbnode_reparent(n->left, s, rbnode_color(n->left)); x = rbnode_parent(n); c = rbnode_color(n); rbtree_reparent(t, x, n, s); if (gc) { rbnode_reparent(s, x, c); rbnode_reparent(gc, p, RBNODE_BLACK); } else { next = rbnode_black(s) ? p : NULL; rbnode_reparent(s, x, c); } } if (next) rbtree_rebalance(t, next); } libratbag-0.13/src/shared-rbtree.h000066400000000000000000000060051362011324700170530ustar00rootroot00000000000000#pragma once /*** This file is part of ratbagd. Copyright 2015 David Herrmann 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 (including the next paragraph) 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. ***/ #include "config.h" #ifdef __cplusplus extern "C" { #endif #include #ifdef RBTREE_DEBUG # include # define rbtree_assert(_x) assert(_x) #else # define rbtree_assert(_x) #endif typedef struct RBTree RBTree; typedef struct RBNode RBNode; enum { RBNODE_RED = 0, RBNODE_BLACK = 1, }; struct RBTree { RBNode *root; }; struct RBNode { RBNode *__parent_and_color; RBNode *left; RBNode *right; }; #define rbnode_of(_ptr, _type, _member) \ __extension__ ({ \ const typeof( ((_type*)0)->_member ) *__ptr = (_ptr); \ __ptr ? (_type*)( (char*)__ptr - offsetof(_type, _member) ) : NULL; \ }) #define RBNODE_INIT(var) ((RBNode){ .__parent_and_color = (&var) }) static inline RBNode *rbnode_init(RBNode *n) { *n = RBNODE_INIT(*n); return n; } static inline RBNode *rbnode_parent(RBNode *n) { return (RBNode*)((unsigned long)n->__parent_and_color & ~1UL); } static inline int rbnode_linked(RBNode *n) { return n && n->__parent_and_color != n; } static inline unsigned long rbnode_color(RBNode *n) { return (unsigned long)n->__parent_and_color & 1UL; } static inline int rbnode_red(RBNode *n) { return rbnode_color(n) == RBNODE_RED; } static inline int rbnode_black(RBNode *n) { return rbnode_color(n) == RBNODE_BLACK; } RBNode *rbnode_leftmost(RBNode *n); RBNode *rbnode_rightmost(RBNode *n); RBNode *rbtree_first(RBTree *t); RBNode *rbtree_last(RBTree *t); RBNode *rbnode_next(RBNode *n); RBNode *rbnode_prev(RBNode *n); void rbtree_add(RBTree *t, RBNode *p, RBNode **l, RBNode *n); void rbtree_remove(RBTree *t, RBNode *n); #ifdef __cplusplus } #endif libratbag-0.13/src/usb-ids.h000066400000000000000000000023361362011324700156750ustar00rootroot00000000000000/* * Copyright © 2016 Red Hat, Inc. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that copyright * notice and this permission notice appear in supporting documentation, and * that the name of the copyright holders not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. The copyright holders make no representations * about the suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE * OF THIS SOFTWARE. */ #pragma once #define USB_VENDOR_ID_LOGITECH 0x046d libratbag-0.13/test/000077500000000000000000000000001362011324700143425ustar00rootroot00000000000000libratbag-0.13/test/test-context.c000066400000000000000000000102731362011324700171520ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include #include #include #include #include #include #include #include #include #include #include "libratbag.h" #define _unused_ __attribute__ ((unused)) static int open_restricted(const char *path, int flags, void *user_data) { int fd = open(path, flags); if (fd < 0) fprintf(stderr, "Failed to open %s (%s)\n", path, strerror(errno)); return fd < 0 ? -errno : fd; } static void close_restricted(int fd, void *user_data) { close(fd); } struct ratbag_interface simple_iface = { .open_restricted = open_restricted, .close_restricted = close_restricted, }; START_TEST(context_init_NULL) { struct ratbag *lr; lr = ratbag_create_context(NULL, NULL); ck_assert(lr == NULL); } END_TEST START_TEST(context_init_bad_iface) { struct ratbag *lr _unused_; struct ratbag_interface iface = { .open_restricted = NULL, .close_restricted = NULL, }; lr = ratbag_create_context(&iface, NULL); /* abort */ } END_TEST START_TEST(context_init_bad_iface_open) { struct ratbag *lr _unused_; struct ratbag_interface iface = { .open_restricted = open_restricted, .close_restricted = NULL, }; lr = ratbag_create_context(&iface, NULL); /* abort */ } END_TEST START_TEST(context_init_bad_iface_close) { struct ratbag *lr _unused_; struct ratbag_interface iface = { .open_restricted = NULL, .close_restricted = close_restricted, }; lr = ratbag_create_context(&iface, NULL); /* abort */ } END_TEST START_TEST(context_init) { struct ratbag *lr; lr = ratbag_create_context(&simple_iface, NULL); ck_assert(lr != NULL); ratbag_unref(lr); } END_TEST START_TEST(context_ref) { struct ratbag *lr; struct ratbag *lr2; lr = ratbag_create_context(&simple_iface, NULL); ck_assert(lr != NULL); lr2 = ratbag_ref(lr); ck_assert_ptr_eq(lr, lr2); lr2 = ratbag_unref(lr2); ck_assert_ptr_eq(lr2, NULL); lr2 = ratbag_unref(lr); ck_assert_ptr_eq(lr2, NULL); } END_TEST static Suite * test_context_suite(bool using_valgrind) { TCase *tc; Suite *s; s = suite_create("context"); tc = tcase_create("init"); if (!using_valgrind) { tcase_add_test_raise_signal(tc, context_init_NULL, SIGABRT); tcase_add_test_raise_signal(tc, context_init_bad_iface, SIGABRT); tcase_add_test_raise_signal(tc, context_init_bad_iface_open, SIGABRT); tcase_add_test_raise_signal(tc, context_init_bad_iface_close, SIGABRT); } tcase_add_test(tc, context_init); tcase_add_test(tc, context_ref); suite_add_tcase(s, tc); return s; } int main(void) { int nfailed; Suite *s; SRunner *sr; bool using_valgrind; const struct rlimit corelimit = { 0, 0 }; setenv("RATBAG_TEST", "1", 0); setrlimit(RLIMIT_CORE, &corelimit); /* when running under valgrind we're using nofork mode, so a * signal raised by a test will fail in valgrind */ using_valgrind = !!getenv("USING_VALGRIND"); s = test_context_suite(using_valgrind); sr = srunner_create(s); srunner_run_all(sr, CK_ENV); nfailed = srunner_ntests_failed(sr); srunner_free(sr); return (nfailed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } libratbag-0.13/test/test-device.c000066400000000000000000000541111362011324700167240ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include #include #include #include #include #include #include #include #include #include #include "libratbag.h" #include "libratbag-util.h" #include "libratbag-test.h" static void device_destroyed(struct ratbag_device *device, void *data) { int *count = data; if (!data) return; ++*count; } /* A pre-setup sane device. Use for sanity testing by toggling the various * error conditions. */ const struct ratbag_test_device sane_device = { .num_profiles = 3, .num_resolutions = 3, .num_buttons = 1, .num_leds = 2, .profiles = { { .resolutions = { { .xres = 100, .yres = 200, .dpi_min = 100, .dpi_max = 5000 }, { .xres = 200, .yres = 300 }, { .xres = 300, .yres = 400 }, }, .leds = { { .type = RATBAG_LED_TYPE_SIDE, }, { .type = RATBAG_LED_TYPE_LOGO, }, }, .active = true, .dflt = false, .report_rates = { 500, 1000 }, .hz = 1000, }, { .resolutions = { { .xres = 1100, .yres = 1200 }, { .xres = 1200, .yres = 1300 }, { .xres = 1300, .yres = 1400 }, }, .active = false, .dflt = true, .hz = 2000, }, { .resolutions = { { .xres = 2100, .yres = 2200 }, { .xres = 2200, .yres = 2300 }, { .xres = 2300, .yres = 2400 }, }, .leds = { { .mode = RATBAG_LED_ON, .color = { .red = 255, .green = 0, .blue = 0 }, .ms = 1000, .brightness = 20, }, { .mode = RATBAG_LED_CYCLE, .color = { .red = 255, .green = 255, .blue = 0 }, .ms = 333, .brightness = 40, } }, .active = false, .dflt = false, .hz = 3000, }, }, .destroyed = device_destroyed, .destroyed_data = NULL, }; static int open_restricted(const char *path, int flags, void *user_data) { /* for test devices we don't expect this to be called */ ck_abort(); return 0; } static void close_restricted(int fd, void *user_data) { /* for test devices we don't expect this to be called */ ck_abort(); } struct ratbag_interface abort_iface = { .open_restricted = open_restricted, .close_restricted = close_restricted, }; START_TEST(device_init) { struct ratbag *r; struct ratbag_device *d; int nprofiles, nbuttons, nleds; struct ratbag_test_device td = sane_device; int device_freed_count = 0; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); nprofiles = ratbag_device_get_num_profiles(d); ck_assert_int_eq(nprofiles, 3); nbuttons = ratbag_device_get_num_buttons(d); ck_assert_int_eq(nbuttons, 1); nleds = ratbag_device_get_num_leds(d); ck_assert_int_eq(nleds, 2); ratbag_device_unref(d); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST #define ref_unref_test(T, a) \ { \ int i; \ struct T *tmp = NULL; \ \ for (i = 0; i <= 256; i++) { \ tmp = T##_ref(a); \ ck_assert(tmp == a); \ } \ for (i = 0; i <= 256; i++) { \ tmp = T##_unref(a); \ ck_assert(tmp == NULL); \ } \ for (i = 0; i <= 256; i++) { \ tmp = T##_ref(a); \ ck_assert(tmp == a); \ tmp = T##_unref(a); \ ck_assert(tmp == NULL); \ } \ } START_TEST(device_ref_unref) { struct ratbag *r; struct ratbag_device *d; struct ratbag_test_device td = sane_device; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); ratbag_unref(r); ref_unref_test(ratbag_device, d); ratbag_device_unref(d); } END_TEST START_TEST(device_free_context_before_device) { struct ratbag *r; struct ratbag_device *d; struct ratbag_test_device td = sane_device; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); r = ratbag_unref(r); ck_assert(r == NULL); d = ratbag_device_unref(d); ck_assert(d == NULL); } END_TEST START_TEST(device_profiles) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; int nprofiles; int i; bool is_active; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); nprofiles = ratbag_device_get_num_profiles(d); ck_assert_int_eq(nprofiles, 3); for (i = 0; i < nprofiles; i++) { p = ratbag_device_get_profile(d, i); ck_assert(p != NULL); is_active = ratbag_profile_is_active(p); ck_assert_int_eq(is_active, (i == 0)); ratbag_profile_unref(p); } ratbag_device_unref(d); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_profiles_ref_unref) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_test_device td = sane_device; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); p = ratbag_device_get_profile(d, 1); ratbag_unref(r); ratbag_device_unref(d); ref_unref_test(ratbag_profile, p); ratbag_profile_unref(p); } END_TEST START_TEST(device_profiles_num_0) { struct ratbag *r; struct ratbag_device *d; struct ratbag_test_device td = sane_device; td.num_profiles = 0; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d == NULL); ratbag_unref(r); } END_TEST START_TEST(device_profiles_multiple_active) { struct ratbag *r; struct ratbag_device *d; struct ratbag_test_device td = sane_device; td.profiles[0].active = true; td.profiles[1].active = true; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d == NULL); ratbag_unref(r); } END_TEST START_TEST(device_profiles_get_invalid) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; int nprofiles; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); nprofiles = ratbag_device_get_num_profiles(d); ck_assert_int_eq(nprofiles, 3); p = ratbag_device_get_profile(d, nprofiles); ck_assert(p == NULL); p = ratbag_device_get_profile(d, nprofiles + 1); ck_assert(p == NULL); p = ratbag_device_get_profile(d, -1); ck_assert(p == NULL); p = ratbag_device_get_profile(d, INT_MAX); ck_assert(p == NULL); p = ratbag_device_get_profile(d, UINT_MAX); ck_assert(p == NULL); ratbag_device_unref(d); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_resolutions) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_resolution *res; int nprofiles, nresolutions; int i, j; int xres, yres, rate; int device_freed_count = 0; bool is_active; struct ratbag_test_device td = { .num_profiles = 3, .num_resolutions = 3, .num_buttons = 1, .profiles = { { .resolutions = { { .xres = 100, .yres = 200, .dpi_min = 50, .dpi_max = 5000 }, { .xres = 200, .yres = 300, .active = true }, { .xres = 300, .yres = 400 }, }, .active = true, .hz = 1000, .report_rates = { 500, 1000 }, }, { .resolutions = { { .xres = 1100, .yres = 1200 }, { .xres = 1200, .yres = 1300, .active = true }, { .xres = 1300, .yres = 1400 }, }, .hz = 2000, }, { .resolutions = { { .xres = 2100, .yres = 2200 }, { .xres = 2200, .yres = 2300, .active = true }, { .xres = 2300, .yres = 2400 }, }, .hz = 3000, }, }, .destroyed = device_destroyed, .destroyed_data = &device_freed_count, }; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); nprofiles = ratbag_device_get_num_profiles(d); for (i = 0; i < nprofiles; i++) { p = ratbag_device_get_profile(d, i); nresolutions = ratbag_profile_get_num_resolutions(p); ck_assert_int_eq(nresolutions, 3); rate = ratbag_profile_get_report_rate(p); for (j = 0; j < nresolutions; j++) { unsigned int dpis[200]; int ndpis = ARRAY_LENGTH(dpis); res = ratbag_profile_get_resolution(p, j); xres = ratbag_resolution_get_dpi_x(res); yres = ratbag_resolution_get_dpi_y(res); is_active = ratbag_resolution_is_active(res); ndpis = ratbag_resolution_get_dpi_list(res, dpis, ndpis); ck_assert_int_lt(ndpis, ARRAY_LENGTH(dpis)); ck_assert_int_gt(ndpis, 20); ck_assert_int_eq(xres, i * 1000 + (j + 1) * 100); ck_assert_int_eq(yres, i * 1000 + (j + 1) * 100 + 100); ck_assert_int_eq(xres, ratbag_resolution_get_dpi(res)); ck_assert_int_eq(is_active, (j == 1)); ck_assert_int_eq(dpis[0], 50); ck_assert_int_eq(dpis[ndpis - 1], 5000); ck_assert_int_eq(rate, (i + 1) * 1000); ratbag_resolution_unref(res); } ratbag_profile_unref(p); } ratbag_device_unref(d); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_resolutions_ref_unref) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_resolution *res; struct ratbag_test_device td = sane_device; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); p = ratbag_device_get_profile(d, 1); res = ratbag_profile_get_resolution(p, 0); ratbag_unref(r); ratbag_device_unref(d); ratbag_profile_unref(p); ref_unref_test(ratbag_resolution, res); ratbag_resolution_unref(res); } END_TEST START_TEST(device_resolutions_num_0) { struct ratbag *r; struct ratbag_device *d; struct ratbag_test_device td = { .num_profiles = 1, .num_buttons = 1, .num_resolutions = 0, /* failure trigger */ .profiles = { { .active = true, }, }, }; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d == NULL); ratbag_unref(r); } END_TEST START_TEST(device_freed_before_profile) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; int is_active, rc; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; td.profiles[0].active = false; td.profiles[1].active = true; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); p = ratbag_device_get_profile(d, 0); ck_assert(p != NULL); is_active = ratbag_profile_is_active(p); ck_assert_int_eq(is_active, 0); d = ratbag_device_unref(d); /* a ref to d is still kept through p */ ck_assert(d == NULL); rc = ratbag_profile_set_active(p); ck_assert_int_eq(rc, 0); is_active = ratbag_profile_is_active(p); ck_assert_int_eq(is_active, 1); ratbag_profile_unref(p); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_and_profile_freed_before_button) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_button *b; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); p = ratbag_device_get_profile(d, 0); ck_assert(p != NULL); d = ratbag_device_unref(d); /* a ref to d is still kept through p */ ck_assert(d == NULL); b = ratbag_profile_get_button(p, 0); ck_assert(b != NULL); p = ratbag_profile_unref(p); /* a ref to p is still kept through b */ ck_assert(p == NULL); /* FIXME: should probably call something for the button */ b = ratbag_button_unref(b); ck_assert(b == NULL); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_and_profile_freed_before_resolution) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_resolution *res; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); p = ratbag_device_get_profile(d, 0); ck_assert(p != NULL); d = ratbag_device_unref(d); /* a ref to d is still kept through p */ ck_assert(d == NULL); res = ratbag_profile_get_resolution(p, 0); ck_assert(res != NULL); p = ratbag_profile_unref(p); /* a ref to p is still kept through res */ ck_assert(p == NULL); /* FIXME: should probably call something for the resolution */ res = ratbag_resolution_unref(res); ck_assert(res == NULL); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_and_profile_and_button_freed_before_resolution) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_resolution *res; struct ratbag_button *b; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); p = ratbag_device_get_profile(d, 0); ck_assert(p != NULL); d = ratbag_device_unref(d); /* a ref to d is still kept through p, so d can not be NULL */ ck_assert(d == NULL); res = ratbag_profile_get_resolution(p, 0); ck_assert(res != NULL); b = ratbag_profile_get_button(p, 0); ck_assert(b != NULL); p = ratbag_profile_unref(p); /* a ref to p is still kept through res and b */ ck_assert(p == NULL); /* a ref to p is still in res */ b = ratbag_button_unref(b); ck_assert(b == NULL); /* FIXME: should probably call something for the resolution */ res = ratbag_resolution_unref(res); ck_assert(res == NULL); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_and_profile_and_resolution_freed_before_button) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_resolution *res; struct ratbag_button *b; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); p = ratbag_device_get_profile(d, 0); ck_assert(p != NULL); d = ratbag_device_unref(d); /* a ref to d is still kept through p */ ck_assert(d == NULL); res = ratbag_profile_get_resolution(p, 0); ck_assert(res != NULL); b = ratbag_profile_get_button(p, 0); ck_assert(b != NULL); p = ratbag_profile_unref(p); /* a ref to p is still kept through res and b */ ck_assert(p == NULL); /* a ref to p is still in res */ res = ratbag_resolution_unref(res); ck_assert(res == NULL); /* FIXME: should probably call something for the button */ b = ratbag_button_unref(b); ck_assert(b == NULL); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_buttons) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_button *b; struct ratbag_button_macro *m; int nprofiles, nbuttons; int i, j; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.num_buttons = 10; td.profiles[0].buttons[8].action_type = RATBAG_BUTTON_ACTION_TYPE_MACRO; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); nprofiles = ratbag_device_get_num_profiles(d); ck_assert_int_eq(nprofiles, 3); nbuttons = ratbag_device_get_num_buttons(d); ck_assert_int_eq(nbuttons, 10); for (i = 0; i < nprofiles; i++) { p = ratbag_device_get_profile(d, i); ck_assert(p != NULL); for (j = 0; j < nbuttons; j++) { b = ratbag_profile_get_button(p, j); ck_assert(b != NULL); if (ratbag_button_get_action_type(b) == RATBAG_BUTTON_ACTION_TYPE_MACRO) { m = ratbag_button_get_macro(b); ck_assert(m != NULL); ratbag_button_macro_unref(m); } ratbag_button_unref(b); } ratbag_profile_unref(p); } ratbag_device_unref(d); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_buttons_ref_unref) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_button *b; struct ratbag_test_device td = sane_device; td.num_buttons = 10; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); p = ratbag_device_get_profile(d, 1); b = ratbag_profile_get_button(p, 0); ratbag_unref(r); ratbag_device_unref(d); ratbag_profile_unref(p); ref_unref_test(ratbag_button, b); ratbag_button_unref(b); } END_TEST START_TEST(device_buttons_set) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_button *b; struct ratbag_test_device td = sane_device; td.num_buttons = 10; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); p = ratbag_device_get_profile(d, 1); b = ratbag_profile_get_button(p, 0); ratbag_button_set_button(b, 3); ratbag_button_unref(b); ratbag_profile_unref(p); ratbag_device_unref(d); ratbag_unref(r); } END_TEST static void assert_led_equals(struct ratbag_led *l, struct ratbag_test_led e_l) { enum ratbag_led_mode mode; struct ratbag_color color; int brightness, ms; ck_assert(l != NULL); mode = ratbag_led_get_mode(l); color = ratbag_led_get_color(l); ms = ratbag_led_get_effect_duration(l); brightness = ratbag_led_get_brightness(l); ck_assert_int_eq(mode, e_l.mode); ck_assert_int_eq(color.red, e_l.color.red); ck_assert_int_eq(color.green, e_l.color.green); ck_assert_int_eq(color.blue, e_l.color.blue); ck_assert_int_eq(ms, e_l.ms); ck_assert_int_eq(brightness, e_l.brightness); } START_TEST(device_leds) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_led *led_logo, *led_side; int nprofiles; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); nprofiles = ratbag_device_get_num_profiles(d); ck_assert_int_eq(nprofiles, 3); p = ratbag_device_get_profile(d, 2); ck_assert(p != NULL); led_logo = ratbag_profile_get_led(p, 0); assert_led_equals(led_logo, td.profiles[2].leds[0]); led_side = ratbag_profile_get_led(p, 1); assert_led_equals(led_side, td.profiles[2].leds[1]); ratbag_led_unref(led_logo); ratbag_led_unref(led_side); ratbag_profile_unref(p); ratbag_device_unref(d); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_leds_set) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_led *l; int nprofiles; int device_freed_count = 0; struct ratbag_test_device td = sane_device; struct ratbag_color c = { .red = 0, .green = 111, .blue = 222 }; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); nprofiles = ratbag_device_get_num_profiles(d); ck_assert_int_eq(nprofiles, 3); p = ratbag_device_get_profile(d, 0); ck_assert(p != NULL); l = ratbag_profile_get_led(p, 0); ratbag_led_set_mode(l, RATBAG_LED_BREATHING); ratbag_led_set_color(l, c); ratbag_led_set_effect_duration(l, 90); ratbag_led_set_brightness(l, 22); l = ratbag_profile_get_led(p, 0); struct ratbag_test_led e_l = { .mode = RATBAG_LED_BREATHING, .color = { .red = c.red, .green = c.green, .blue = c.blue }, .ms = 90, .brightness = 22 }; assert_led_equals(l, e_l); ratbag_led_unref(l); ratbag_led_unref(l); ratbag_profile_unref(p); ratbag_device_unref(d); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST static Suite * test_context_suite(void) { TCase *tc; Suite *s; s = suite_create("device"); tc = tcase_create("device"); tcase_add_test(tc, device_init); tcase_add_test(tc, device_ref_unref); tcase_add_test(tc, device_free_context_before_device); suite_add_tcase(s, tc); tc = tcase_create("profiles"); tcase_add_test(tc, device_profiles); tcase_add_test(tc, device_profiles_ref_unref); tcase_add_test(tc, device_profiles_num_0); tcase_add_test(tc, device_profiles_multiple_active); tcase_add_test(tc, device_profiles_get_invalid); tcase_add_test(tc, device_freed_before_profile); tcase_add_test(tc, device_and_profile_freed_before_button); tcase_add_test(tc, device_and_profile_freed_before_resolution); tcase_add_test(tc, device_and_profile_and_button_freed_before_resolution); tcase_add_test(tc, device_and_profile_and_resolution_freed_before_button); suite_add_tcase(s, tc); tc = tcase_create("resolutions"); tcase_add_test(tc, device_resolutions); tcase_add_test(tc, device_resolutions_ref_unref); tcase_add_test(tc, device_resolutions_num_0); suite_add_tcase(s, tc); tc = tcase_create("buttons"); tcase_add_test(tc, device_buttons); tcase_add_test(tc, device_buttons_ref_unref); tcase_add_test(tc, device_buttons_set); suite_add_tcase(s, tc); tc = tcase_create("led"); tcase_add_test(tc, device_leds); tcase_add_test(tc, device_leds_set); suite_add_tcase(s, tc); return s; } int main(void) { int nfailed; Suite *s; SRunner *sr; const struct rlimit corelimit = { 0, 0 }; setenv("RATBAG_TEST", "1", 0); setrlimit(RLIMIT_CORE, &corelimit); s = test_context_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_ENV); nfailed = srunner_ntests_failed(sr); srunner_free(sr); return (nfailed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } libratbag-0.13/test/test-iconv-helper.c000066400000000000000000000123131362011324700200560ustar00rootroot00000000000000/* * Copyright © 2016 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include #include #include #include #include #include "libratbag-util.h" static const char sample_utf16le[] = { 'F', '\0', 'o', '\0', 'o', '\0' }; static const char sample_single_char_utf16le[] = { 'A', '\0' }; /* Sample UTF-8 string used: 🐺🖖🗺🗹💯👏 */ static const char sample_emoji_utf8[] = { 0xf0, 0x9f, 0x90, 0xba, 0xf0, 0x9f, 0x96, 0x96, 0xf0, 0x9f, 0x97, 0xba, 0xf0, 0x9f, 0x97, 0xb9, 0xf0, 0x9f, 0x92, 0xaf, 0xf0, 0x9f, 0x91, 0x8f, 0x0a, 0x00 }; static const char sample_emoji_utf16le[] = { 0x3d, 0xd8, 0x3a, 0xdc, 0x3d, 0xd8, 0x96, 0xdd, 0x3d, 0xd8, 0xfa, 0xdd, 0x3d, 0xd8, 0xf9, 0xdd, 0x3d, 0xd8, 0xaf, 0xdc, 0x3d, 0xd8, 0x4f, 0xdc, 0x0a, 0x00 }; START_TEST(iconv_convert_to_utf16le) { char output[4096]; ssize_t rc; rc = ratbag_utf8_to_enc(output, sizeof(sample_utf16le), "UTF-16LE", "%s", "Foo"); ck_assert_int_eq(rc, sizeof(sample_utf16le)); ck_assert(memcmp(output, sample_utf16le, sizeof(sample_utf16le)) == 0); rc = ratbag_utf8_to_enc(output, sizeof(sample_emoji_utf16le), "UTF-16LE", "%s", sample_emoji_utf8); ck_assert_int_eq(rc, sizeof(sample_emoji_utf16le)); ck_assert(memcmp(output, sample_emoji_utf16le, sizeof(sample_emoji_utf16le)) == 0); rc = ratbag_utf8_to_enc(output, sizeof(sample_single_char_utf16le), "UTF-16LE", "%s", "A"); ck_assert_int_eq(rc, sizeof(sample_single_char_utf16le)); ck_assert(memcmp(output, sample_single_char_utf16le, sizeof(sample_single_char_utf16le)) == 0); } END_TEST START_TEST(iconv_convert_from_utf16le) { char input[4096]; char *output; ssize_t rc; memcpy(input, sample_utf16le, sizeof(sample_utf16le)); rc = ratbag_utf8_from_enc(input, sizeof(sample_utf16le), "UTF-16LE", &output); ck_assert_int_eq(rc, sizeof("Foo")); ck_assert(memcmp(output, "Foo", sizeof("Foo")) == 0); free(output); memcpy(input, sample_emoji_utf16le, sizeof(sample_emoji_utf16le)); rc = ratbag_utf8_from_enc(input, sizeof(sample_emoji_utf16le), "UTF-16LE", &output); ck_assert_int_eq(rc, sizeof(sample_emoji_utf8)); ck_assert(memcmp(output, sample_emoji_utf8, sizeof(sample_emoji_utf8)) == 0); free(output); memcpy(input, sample_single_char_utf16le, sizeof(sample_single_char_utf16le)); rc = ratbag_utf8_from_enc(input, sizeof(sample_single_char_utf16le), "UTF-16LE", &output); ck_assert_int_eq(rc, sizeof("A")); ck_assert(memcmp(output, "A", sizeof("A")) == 0); free(output); } END_TEST START_TEST(iconv_invalid_encoding) { char output[10] = { 0 }; ssize_t rc; rc = ratbag_utf8_to_enc(output, sizeof(output), "This encoding is invalid", "%s", "Foo"); ck_assert_int_eq(rc, -EINVAL); } END_TEST START_TEST(iconv_bad_utf16le) { char odd_numbered[] = { 'F', '\0', 'o' }; char single_char[] = { 'F' }; char single_null[] = { '\0' }; char double_null[] = { 'F', '\0', '\0', 'o', '\0', 'o', '\0' }; char *output; ssize_t rc; rc = ratbag_utf8_from_enc(odd_numbered, sizeof(odd_numbered), "UTF-16LE", &output); ck_assert_int_eq(rc, -EINVAL); rc = ratbag_utf8_from_enc(single_char, sizeof(single_char), "UTF-16LE", &output); ck_assert_int_eq(rc, -EINVAL); rc = ratbag_utf8_from_enc(single_null, sizeof(single_null), "UTF-16LE", &output); ck_assert_int_eq(rc, -EINVAL); rc = ratbag_utf8_from_enc(double_null, sizeof(double_null), "UTF-16LE", &output); ck_assert_int_eq(rc, -EINVAL); } END_TEST static Suite * test_context_suite(void) { TCase *tc; Suite *s; s = suite_create("iconv-helper"); tc = tcase_create("iconv-helper"); tcase_add_test(tc, iconv_convert_to_utf16le); tcase_add_test(tc, iconv_convert_from_utf16le); tcase_add_test(tc, iconv_invalid_encoding); tcase_add_test(tc, iconv_bad_utf16le); suite_add_tcase(s, tc); return s; } int main(void) { int nfailed; Suite *s; SRunner *sr; const struct rlimit corelimit = { 0, 0 }; setenv("RATBAG_TEST", "1", 0); setrlimit(RLIMIT_CORE, &corelimit); s = test_context_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_ENV); nfailed = srunner_ntests_failed(sr); srunner_free(sr); return (nfailed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } libratbag-0.13/test/test-util.c000066400000000000000000000075301362011324700164450ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include #include #include #include #include #include #include #include #include #include #include "libratbag-util.h" START_TEST(dpi_range_parser) { struct testcase { const char *str; bool success; struct dpi_range result; } tests[] = { { "", false, { 0, 0, 0 }}, { "1", false, { 0, 0, 0 }}, { "a", false, { 0, 0, 0 }}, { "1:1", false, { 0, 0, 0 }}, { "2:1", false, { 0, 0, 0 }}, { "2:1@0", false, { 0, 0, 0 }}, { "10:100@0", false, { 0, 0, 0 }}, { "100:10@50", false, { 0, 0, 0 }}, { "100:10@", false, { 0, 0, 0 }}, { ":10@50", false, { 0, 0, 0 }}, { "10:100@50", true, { 10, 100, 50 }}, { "100:12000@20", true, { 100, 12000, 20 }}, { "50:12000@250", true, { 50, 12000, 250 }}, }; struct testcase *t; ARRAY_FOR_EACH(tests, t) { struct dpi_range *range; range = dpi_range_from_string(t->str); if (!t->success) { ck_assert(range == NULL); continue; } ck_assert(range != NULL); ck_assert_int_eq(t->result.min, range->min); ck_assert_int_eq(t->result.max, range->max); ck_assert_int_eq(t->result.step, range->step); free(range); } } END_TEST START_TEST(dpi_list_parser) { struct testcase { const char *str; int nentries; int entries[64]; } tests[] = { { "", -1, {0}}, { "a", -1, {0}}, { "a;b", -1, {0}}, { "1;a;b", -1, {0}}, { "100;200;b", -1, {0}}, { "10.2;200", -1, {0}}, { "0xab;100", -1, {0}}, { "100", 1, { 100 }}, { "100;200", 2, { 100, 200 }}, { "100;200;", 2, { 100, 200 }}, { "100;300;;;;", 2, { 100, 300 }}, { "0;300;", 2, { 0, 300 }}, { "0;300;400;", 3, { 0, 300, 400 }}, { "0;300;400;500;100;23;", 6, { 0, 300, 400, 500, 100, 23 }}, }; struct testcase *t; ARRAY_FOR_EACH(tests, t) { struct dpi_list *list; list = dpi_list_from_string(t->str); if (t->nentries == -1) { ck_assert(list == NULL); continue; } ck_assert(list != NULL); ck_assert_int_eq(t->nentries, list->nentries); ck_assert_int_eq(memcmp(t->entries, list->entries, t->nentries * sizeof(int)), 0); dpi_list_free(list); } } END_TEST static Suite * test_context_suite(void) { TCase *tc; Suite *s; s = suite_create("device"); tc = tcase_create("util"); tcase_add_test(tc, dpi_range_parser); tcase_add_test(tc, dpi_list_parser); suite_add_tcase(s, tc); return s; } int main(void) { int nfailed; Suite *s; SRunner *sr; const struct rlimit corelimit = { 0, 0 }; setenv("RATBAG_TEST", "1", 0); setrlimit(RLIMIT_CORE, &corelimit); s = test_context_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_ENV); nfailed = srunner_ntests_failed(sr); srunner_free(sr); return (nfailed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } libratbag-0.13/test/valgrind.suppressions000066400000000000000000000005241362011324700206500ustar00rootroot00000000000000{ srunner_run::timer_create-uninitialized-bytes Memcheck:Param timer_create(evp) fun:timer_create@@GLIBC_2.3.3 fun:srunner_run ... fun:main } { Python::PyObject_Malloc Memcheck:Leak fun:malloc obj:/*lib*/libpython3* } { Python::PyObject_Malloc Memcheck:Leak fun:realloc obj:/*lib*/libpython3* } libratbag-0.13/tools/000077500000000000000000000000001362011324700145235ustar00rootroot00000000000000libratbag-0.13/tools/check_scan_build.py000077500000000000000000000060711362011324700203440ustar00rootroot00000000000000#!/bin/env python3 # # 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. # import bs4 import os import re import sys class Bug(object): def __init__(self): self.line_number = None self.cfile = None self.sa_type = None self.sa_descr = None def main(argv): if len(argv) < 2: print(f'usage {argv[0]} PATH') sys.exit(1) scanbuild_path = argv[1] ignored = [] bugs = [] line_re = re.compile(r'line (\d+), column (\d+)') for root, dirs, files in os.walk(scanbuild_path): for filename in files: if not filename.startswith('report'): continue with open(os.path.join(root, filename)) as f: soup = bs4.BeautifulSoup(f, 'html.parser') # the first table is the summary summary = soup.table bug = Bug() # iterate over the table for tr in summary('tr'): if 'File:' in tr.contents[0].string: bug.cfile = os.path.abspath(tr.contents[1].string) else: bug.sa_type = tr.contents[0].string.rstrip(':').lower() for s in tr.contents[1].strings: # retrieve the line number and the description m = line_re.match(s) if m is None: bug.sa_descr = s else: bug.line_number = int(m.group(1)) # retrieve the html line corresponding to the code code = soup.find('td', id=f'LN{bug.line_number}').parent # fetching any comments in this line try: comments = code.find('span', class_='comment').string except AttributeError: # no comments on the line bugs.append(bug) else: if 'ignore_clang_sa_' in comments: ignored.append(bug) else: bugs.append(bug) if len(ignored) > 0: print(f'{len(ignored)} bugs are ignored:') for b in ignored: print(f' {b.sa_type}: {b.sa_descr} at {b.cfile}:{b.line_number}') if len(bugs) > 0: print() if len(bugs) > 0: print(f'ERROR: {len(bugs)} bugs found:') for b in bugs: print(f' * {b.sa_type.upper()}: {b.sa_descr} at {b.cfile}:{b.line_number}') sys.exit(1) sys.exit(0) if __name__ == '__main__': main(sys.argv) libratbag-0.13/tools/hidpp10-dump-page.c000066400000000000000000000056621362011324700200220ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include #include #include #include #include #include static inline int dump_page(struct hidpp10_device *dev, size_t page, size_t offset) { int rc = 0; uint8_t bytes[16]; while (offset < 512) { hidpp_log_info(&dev->base, "page 0x%02zx off 0x%03zx: ", page, offset); rc = hidpp10_read_memory(dev, page, offset, bytes); if (rc != 0) break; hidpp_log_buffer(&dev->base, HIDPP_LOG_PRIORITY_INFO, " ", bytes, ARRAY_LENGTH(bytes)); offset += 16; } return rc; } static inline int dump_all_pages(struct hidpp10_device *dev) { uint8_t page; int rc = 0; for (page = 0; page < 31; page++) { rc = dump_page(dev, page, 0); if (rc != 0) break; } /* We dumped at least one page successfully and get EAGAIN, so we're * on the last page. Overwrite the last line with a blank one so it * doesn't look like an error */ if (page > 0 && rc == EAGAIN) { hidpp_log_info(&dev->base, "\r \n"); rc = 0; } return rc; } static void usage(void) { printf("Usage: %s [page] [offset] /dev/hidraw0\n", program_invocation_short_name); } int main(int argc, char **argv) { _cleanup_close_ int fd = 0; const char *path; size_t page = 0, offset = 0; struct hidpp10_device *dev = NULL; struct hidpp_device base; int rc; if (argc < 2 || argc > 4) { usage(); return 1; } path = argv[argc - 1]; fd = open(path, O_RDWR); if (fd < 0) error(1, errno, "Failed to open path %s", path); hidpp_device_init(&base, fd); dev = hidpp10_device_new(&base, HIDPP_WIRED_DEVICE_IDX, HIDPP10_PROFILE_UNKNOWN, 5); if (argc == 2) rc = dump_all_pages(dev); else { page = atoi(argv[1]); if (argc > 3) offset = atoi(argv[2]); rc = dump_page(dev, page, offset); } hidpp10_device_destroy(dev); return rc; } libratbag-0.13/tools/hidpp20-dump-page.c000066400000000000000000000070171362011324700200170ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include #include #include #include #include #include static inline int dump_page(struct hidpp20_device *dev, uint16_t sector_size, uint8_t rom, size_t page, size_t offset) { int rc = 0; uint16_t sector = (rom << 8) | page; uint8_t *data = zalloc(sector_size); rc = hidpp20_onboard_profiles_read_sector(dev, sector, sector_size, data); if (rc != 0) goto out; while (offset < 256) { hidpp_log_info(&dev->base, "%s: page 0x%02zx off 0x%02zx: ", rom ? "ROM " : "FLASH", page, offset); hidpp_log_buffer(&dev->base, HIDPP_LOG_PRIORITY_INFO, " ", data + offset, 16); offset += 16; } out: free(data); return rc; } static inline int dump_all_pages(struct hidpp20_device *dev, uint16_t sector_size, uint8_t rom) { uint8_t page; int rc = 0; for (page = 0; page < 31; page++) { rc = dump_page(dev, sector_size, rom, page, 0); if (rc != 0) break; } /* We dumped at least one page successfully and get EAGAIN, so we're * on the last page. Overwrite the last line with a blank one so it * doesn't look like an error */ if (page > 0 && rc == ENOENT) { hidpp_log_info(&dev->base, "\r \n"); rc = 0; } return rc; } static inline int dump_everything(struct hidpp20_device *dev, uint16_t sector_size) { int rc; rc = dump_all_pages(dev, sector_size, 0); if (rc) return rc; return dump_all_pages(dev, sector_size, 1); } static void usage(void) { printf("Usage: %s [page] [offset] /dev/hidraw0\n", program_invocation_short_name); } int main(int argc, char **argv) { _cleanup_close_ int fd = 0; const char *path; size_t page = 0, offset = 0; struct hidpp20_device *dev = NULL; struct hidpp_device base; struct hidpp20_onboard_profiles_info info = { 0 }; int rc; if (argc < 2 || argc > 4) { usage(); return 1; } path = argv[argc - 1]; fd = open(path, O_RDWR); if (fd < 0) error(1, errno, "Failed to open path %s", path); hidpp_device_init(&base, fd); dev = hidpp20_device_new(&base, 0xff, NULL, 0); if (!dev) error(1, 0, "Failed to open %s as a HID++ 2.0 device", path); hidpp20_onboard_profiles_get_profiles_desc(dev, &info); if (argc == 2) rc = dump_everything(dev, info.sector_size); else { page = atoi(argv[1]); if (argc > 3) offset = atoi(argv[2]); rc = dump_page(dev, info.sector_size, 0, page, offset); } hidpp20_device_destroy(dev); return rc; } libratbag-0.13/tools/hidpp20-reset.c000066400000000000000000000054621362011324700172640ustar00rootroot00000000000000/* * Copyright © 2017 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include #include #include #include #include #include static inline int reset_sector(struct hidpp20_device *dev, uint16_t sector_size, uint16_t sector) { int rc = 0; _cleanup_free_ uint8_t *data = NULL; data = zalloc(sector_size); /* this returns error 4: hardware error and is expected */ rc = hidpp20_onboard_profiles_write_sector(dev, sector, sector_size, data, false); if (rc == 4) rc = 0; return rc; } static int reset_all_sectors(struct hidpp20_device *dev, uint16_t sector_size) { uint8_t sector; int rc = 0; for (sector = 1; sector < 31; sector++) { rc = reset_sector(dev, sector_size, sector); if (rc != 0) break; } if (!rc) rc = reset_sector(dev, sector_size, 0); return rc; } static void usage(void) { printf("Usage: %s [sector] /dev/hidraw0\n", program_invocation_short_name); } int main(int argc, char **argv) { _cleanup_close_ int fd = -1; const char *path; size_t sector = 0; struct hidpp20_device *dev = NULL; struct hidpp_device base; struct hidpp20_onboard_profiles_info info = { 0 }; int rc; if (argc < 2 || argc > 3) { usage(); return 1; } path = argv[argc - 1]; fd = open(path, O_RDWR); if (fd < 0) error(1, errno, "Failed to open path %s", path); hidpp_device_init(&base, fd); dev = hidpp20_device_new(&base, 0xff, NULL, 0); if (!dev) error(1, 0, "Failed to open %s as a HID++ 2.0 device", path); hidpp20_onboard_profiles_get_profiles_desc(dev, &info); if (argc == 2) rc = reset_all_sectors(dev, info.sector_size); else { sector = atoi(argv[1]); rc = reset_sector(dev, info.sector_size, sector); } hidpp20_device_destroy(dev); return rc; } libratbag-0.13/tools/lur-command.c000066400000000000000000000140001362011324700171000ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include enum options { OPT_HELP, }; static struct lur_receiver * open_receiver(const char *path) { struct lur_receiver *receiver = NULL; int fd = -1; int rc; fd = open(path, O_RDWR); if (fd < 0) { fprintf(stderr, "Failed to open %s (%s)\n", path, strerror(errno)); return NULL; } rc = lur_receiver_new_from_hidraw(fd, NULL, &receiver); if (rc != 0) close(fd); return receiver; } static void list_connected_devices(struct lur_receiver *receiver) { _cleanup_free_ struct lur_device **devices = NULL; int ndevices; int i; ndevices = lur_receiver_enumerate(receiver, &devices); if (ndevices < 0) { fprintf(stderr, "Failed to enumerate devices\n"); return; } else if (ndevices == 0) { fprintf(stderr, "No devices connected to this receiver\n"); return; } for (i = 0; i < ndevices; i++) { struct lur_device *dev = devices[i]; const char *name, *strtype; enum lur_device_type type; uint32_t serial; name = lur_device_get_name(dev); type = lur_device_get_type(dev); serial = lur_device_get_serial(dev); switch(type) { case LUR_DEVICE_TYPE_UNKNOWN: strtype = "unknown"; break; case LUR_DEVICE_TYPE_KEYBOARD: strtype = "keyboard"; break; case LUR_DEVICE_TYPE_MOUSE: strtype = "mouse"; break; case LUR_DEVICE_TYPE_NUMPAD: strtype = "numpad"; break; case LUR_DEVICE_TYPE_PRESENTER: strtype = "presenter"; break; case LUR_DEVICE_TYPE_TRACKBALL: strtype = "trackball"; break; case LUR_DEVICE_TYPE_TOUCHPAD: strtype = "touchpad"; break; default: strtype = ""; break; } printf("%d: %s (%s) serial %#x\n", i, name, strtype, serial); lur_device_unref(dev); } } static void disconnect_device(struct lur_receiver *receiver, int index) { _cleanup_free_ struct lur_device **devices = NULL; int ndevices; int i; ndevices = lur_receiver_enumerate(receiver, &devices); if (ndevices < 0) { fprintf(stderr, "Failed to enumerate devices\n"); return; } else if (ndevices == 0) { fprintf(stderr, "No devices connected to this receiver\n"); return; } if (index < 0 || index >= ndevices) { fprintf(stderr, "Invalid index %d, only %d devices connected\n", index, ndevices); } else { lur_device_disconnect(devices[index]); } for (i = 0; i < ndevices; i++) lur_device_unref(devices[i]); } static int filter_hidraw(const struct dirent *entry) { return strneq(entry->d_name, "hidraw", 6); } static void find_receiver(void) { _cleanup_free_ struct dirent **hidraw_list = NULL; int n, i; char path[PATH_MAX] = {0}; bool found = false; n = scandir("/dev/", &hidraw_list, filter_hidraw, alphasort); if (n < 0) return; for (i = 0; i < n; i++) { struct lur_receiver *receiver; sprintf_safe(path, "/dev/%s", hidraw_list[i]->d_name); receiver = open_receiver(path); if (receiver) { found = true; printf("%s\n", path); lur_receiver_unref(receiver); } free(hidraw_list[i]); } if (!found) fprintf(stderr, "No receivers found.\n"); } static void usage(void) { printf("Usage: %s COMMAND /dev/hidrawX\n" "\n" "Commands:\n" " list ............. list devices connected to receiver\n" " open ............. open receiver for pairing (timeout 30s)\n" " close ............ close receiver if currently open\n" " disconnect N ..... disconnect device N\n" " find ............. find a receiver amongst the /dev/hidraw devices \n", program_invocation_short_name); } int main(int argc, char **argv) { struct lur_receiver *receiver; const char *path; const char *command; while (1) { int c; int option_index = 0; static struct option opts[] = { { "help", 0, 0, OPT_HELP }, }; c = getopt_long(argc, argv, "+h", opts, &option_index); if (c == -1) break; switch(c) { case 'h': case OPT_HELP: usage(); return EXIT_SUCCESS; default: usage(); return EXIT_FAILURE; } } if (argc < 2) { usage(); return EXIT_FAILURE; } command = argv[1]; if (streq(command, "find")) { find_receiver(); return EXIT_SUCCESS; } if (argc < 3) { usage(); return EXIT_FAILURE; } path = argv[argc - 1]; receiver = open_receiver(path); if (!receiver) { fprintf(stderr, "Failed to open receiver at %s\n", path); return EXIT_FAILURE; } if (streq(command, "list")) { list_connected_devices(receiver); } else if (streq(command, "open")) { lur_receiver_open(receiver, 0); } else if (streq(command, "close")) { lur_receiver_close(receiver); } else if (streq(command, "disconnect")) { int index; if (argc < 4) { usage(); return EXIT_FAILURE; } index = atoi(argv[2]); disconnect_device(receiver, index); } else { usage(); return EXIT_FAILURE; } lur_receiver_unref(receiver); return EXIT_SUCCESS; } libratbag-0.13/tools/lur-command.man000066400000000000000000000021301362011324700174320ustar00rootroot00000000000000.TH lur\-command 1 "@version@" lur\-command .SH NAME lur\-command \- list, pair and unpair Logitech Unifying devices .SH SYNOPSIS .B lur\-command find .br .B lur\-command list .RI < device > .br .B lur\-command open .RI < device > .br .B lur\-command close .RI < device > .br .B lur\-command disconnect .RI < index "> <" device > .SH DESCRIPTION .SS Common options .TP .BR \-h displays a short help message. .SS Listing receivers .TP .B find lists all identified receivers on the system (scanning .BI /dev/hidraw n devices). .SS Listing connected devices .TP .BR list " <" \fIdevice\fP > lists the devices connected to the given receiver device. .SS Pairing devices .TP .BR open " <" \fIdevice\fP > opens the given receiver for pairing, for 30s. .SS Closing a receiver .TP .BR close " <" \fIdevice\fP > closes the given receiver, if it is currently open for pairing. .SS Disconnecting a device .TP .BR disconnect " <" \fIindex\fP "> <" \fIdevice\fP > disconnects the device matching the given index from the given receiver. The index is as determined by the .B list command. .SH SEE ALSO .BR ratbag\-command (1) libratbag-0.13/tools/merge_ratbagd.py000077500000000000000000000052301362011324700176630ustar00rootroot00000000000000#!/usr/bin/env python3 # # Copyright 2017 Red Hat, Inc. # # 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 (including the next # paragraph) 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. import argparse import os import stat import sys def print_ratbagctl(ratbagctl_path, ratbagd_path, version_string): with open(ratbagctl_path, 'r', encoding='utf-8') as ratbagctl, open(ratbagd_path, 'r', encoding='utf-8') as ratbagd: for line in ratbagctl.readlines(): if line.startswith("from ratbagd import "): headers = True for r in ratbagd.readlines(): if not r.startswith('#') and r.strip(): headers = False if not headers: print(r.rstrip('\n')) else: if '@version@' in line: line = line.replace('@version@', version_string) print(line.rstrip('\n')) def main(argv): parser = argparse.ArgumentParser(description="merge ratbagd.py into ratbagctl") parser.add_argument("ratbagctl", action='store') parser.add_argument("ratbagd", action='store') parser.add_argument("--output", action="store") parser.add_argument("--version", action="store", default="git_master") ns = parser.parse_args(sys.argv[1:]) if ns.output: ns.output_file = open(ns.output, 'w', encoding='utf-8') st = os.stat(ns.output) os.chmod(ns.output, st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) sys.stdout = ns.output_file print_ratbagctl(ns.ratbagctl, ns.ratbagd, ns.version) try: ns.output_file.close() except AttributeError: pass if __name__ == "__main__": main(sys.argv[1:]) libratbag-0.13/tools/ratbagc.py.in000066400000000000000000000737701362011324700171230ustar00rootroot00000000000000# Copyright 2017-2019 Red Hat, Inc. # # 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 (including the next # paragraph) 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. import libratbag import os import sys from enum import IntEnum from evdev import ecodes LIBRATBAG_DATA_DIR = "@LIBRATBAG_DATA_DIR@" # Deferred translations, see https://docs.python.org/3/library/gettext.html#deferred-translations def N_(x): return x # we use a metaclass to automatically load symbols from libratbag in the classes # define _PREFIX in subclasses to take advantage of this. class MetaRatbag(type): def __new__(cls, name, bases, dct): try: prefix = dct["_PREFIX"] except KeyError: pass else: for k in libratbag.__dict__.keys(): if k.startswith(prefix) and k.isupper(): key = k[len(prefix):] dct[key] = getattr(libratbag, k) c = type.__new__(cls, name, bases, dct) if "__late_init__" in dct: c.__late_init__() # Add a noop self.notify() so we're compatible with GObjects c.notify = lambda *args: None return c class RatbagErrorCode(metaclass=MetaRatbag): SUCCESS = libratbag.RATBAG_SUCCESS """An error occured on the device. Either the device is not a libratbag device or communication with the device failed.""" DEVICE = libratbag.RATBAG_ERROR_DEVICE """Insufficient capabilities. This error occurs when a requested change is beyond the device's capabilities.""" CAPABILITY = libratbag.RATBAG_ERROR_CAPABILITY """Invalid value or value range. The provided value or value range is outside of the legal or supported range.""" VALUE = libratbag.RATBAG_ERROR_VALUE """A low-level system error has occured, e.g. a failure to access files that should be there. This error is usually unrecoverable and libratbag will print a log message with details about the error.""" SYSTEM = libratbag.RATBAG_ERROR_SYSTEM """Implementation bug, either in libratbag or in the caller. This error is usually unrecoverable and libratbag will print a log message with details about the error.""" IMPLEMENTATION = libratbag.RATBAG_ERROR_IMPLEMENTATION class RatbagdUnavailable(Exception): """Signals DBus is unavailable or the ratbagd daemon is not available.""" pass class RatbagError(Exception): """A common base exception to catch any ratbag exception.""" pass class RatbagErrorDevice(RatbagError): """An exception corresponding to RatbagErrorCode.DEVICE.""" pass class RatbagErrorCapability(RatbagError): """An exception corresponding to RatbagErrorCode.CAPABILITY.""" pass class RatbagErrorValue(RatbagError): """An exception corresponding to RatbagErrorCode.VALUE.""" pass class RatbagErrorSystem(RatbagError): """An exception corresponding to RatbagErrorCode.SYSTEM.""" pass class RatbagErrorImplementation(RatbagError): """An exception corresponding to RatbagErrorCode.IMPLEMENTATION.""" pass """A table mapping RatbagErrorCode values to RatbagError* exceptions.""" EXCEPTION_TABLE = { RatbagErrorCode.DEVICE: RatbagErrorDevice, RatbagErrorCode.CAPABILITY: RatbagErrorCapability, RatbagErrorCode.VALUE: RatbagErrorValue, RatbagErrorCode.SYSTEM: RatbagErrorSystem, RatbagErrorCode.IMPLEMENTATION: RatbagErrorImplementation } class Ratbagd(object): """The ratbagd top-level object. Provides a list of devices available through libratbag; actual interaction with the devices is via the RatbagdDevice, RatbagdProfile, RatbagdResolution and RatbagdButton objects. """ def __init__(self, apiversion): os.environ['LIBRATBAG_DATA_DIR'] = LIBRATBAG_DATA_DIR self._ratbag = libratbag.ratbag_create_context(libratbag.interface, None) self._devices = {} self._devices_initialized = False def _init_devices(self): for event in os.listdir("/dev/"): if not event.startswith("hidraw"): continue name = os.path.join("/dev/", event) try: dev = RatbagdDevice(self._ratbag, name) except RatbagErrorDevice: pass else: self._devices[name] = dev self._devices_initialized = True @property def verbose(self): v = libratbag.ratbag_log_get_priority(self._ratbag) if v == libratbag.RATBAG_LOG_PRIORITY_RAW: return 3 elif v == libratbag.RATBAG_LOG_PRIORITY_DEBUG: # to match with setter action, we return 1 instead of 2 return 1 elif v == libratbag.RATBAG_LOG_PRIORITY_INFO: return 1 elif v == libratbag.RATBAG_LOG_PRIORITY_ERROR: return 0 @verbose.setter def verbose(self, verbosity): if verbosity > 2: libratbag.ratbag_log_set_priority(self._ratbag, libratbag.RATBAG_LOG_PRIORITY_RAW) elif verbosity >= 1: libratbag.ratbag_log_set_priority(self._ratbag, libratbag.RATBAG_LOG_PRIORITY_DEBUG) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): for d in self._devices.values(): d.__exit__() libratbag.ratbag_unref(self._ratbag) @property def devices(self): """A list of RatbagdDevice objects supported by ratbagd.""" if not self._devices_initialized: self._init_devices() return self._devices.values() def __getitem__(self, id): """Returns the requested device, or None.""" if id.startswith("hidraw"): id = "/dev/{}".format(id) if id not in self._devices.keys(): try: dev = RatbagdDevice(self._ratbag, id) except RatbagErrorDevice: pass else: self._devices[id] = dev return self._devices[id] if id in self._devices.keys() else None def set_verbose(self, verbose): if verbose > 2: libratbag.ratbag_log_set_priority(self._ratbag, libratbag.RATBAG_LOG_PRIORITY_RAW) elif verbose >= 1: libratbag.ratbag_log_set_priority(self._ratbag, libratbag.RATBAG_LOG_PRIORITY_DEBUG) def get_capabilities(type, object): capabilities = [] for k in libratbag.__dict__.keys(): if k.startswith("RATBAG_{}_CAP_".format(type.upper())) and "CAP_NONE" not in k: cap = getattr(libratbag, k) func = getattr(libratbag, "ratbag_{}_has_capability".format(type.lower())) if func(object, cap): capabilities.append(cap) return capabilities class RatbagdDevice(metaclass=MetaRatbag): """Represents a ratbagd device.""" _PREFIX = "RATBAG_DEVICE_" def __init__(self, ratbag, path): self._path = path self._ratbag = ratbag self._device = libratbag.ratbag_cmd_open_device(ratbag, path) if self._device is None: raise RatbagErrorDevice("device not compatible") self._profiles = [RatbagdProfile(self._device, i) for i in range(libratbag.ratbag_device_get_num_profiles(self._device))] def __exit__(self): for p in self._profiles: p.__exit__() libratbag.ratbag_device_unref(self._device) @property def id(self): """The unique identifier of this device.""" return self._path @property def model(self): """The unique identifier for this device model.""" bus = libratbag.ratbag_device_get_bustype(self._device) if not bus: return "unknown" vid = libratbag.ratbag_device_get_vendor_id(self._device) pid = libratbag.ratbag_device_get_product_id(self._device) version = libratbag.ratbag_device_get_product_version(self._device) return "{}:{:04x}:{:04x}:{:d}".format(bus, vid, pid, version) @property def name(self): """The device name, usually provided by the kernel.""" return libratbag.ratbag_device_get_name(self._device) @property def profiles(self): """A list of RatbagdProfile objects provided by this device.""" return self._profiles @property def active_profile(self): """The currently active profile. This is a non-DBus property computed over the cached list of profiles. In the unlikely case that your device driver is misconfigured and there is no active profile, this returns the first profile.""" for profile in self._profiles: if profile.is_active: return profile print("No active profile. Please report this bug to the libratbag developers", file=sys.stderr) return self._profiles[0] def commit(self): """Commits all changes made to the device. This is implemented asynchronously inside ratbagd. Hence, we just call this method and always succeed. Any failure is handled inside ratbagd by emitting the Resync signal, which automatically resynchronizes the device. No further interaction is required by the client. """ return libratbag.ratbag_device_commit(self._device) class RatbagdProfile(metaclass=MetaRatbag): """Represents a ratbagd profile.""" _PREFIX = "RATBAG_PROFILE_" def __init__(self, device, id): self._id = id self._profile = libratbag.ratbag_device_get_profile(device, id) self._dirty = False self._capabilities = get_capabilities("profile", self._profile) self._resolutions = [RatbagdResolution(self._profile, i) for i in range(libratbag.ratbag_profile_get_num_resolutions(self._profile))] self._buttons = [RatbagdButton(self._profile, i) for i in range(libratbag.ratbag_device_get_num_buttons(device))] self._leds = [RatbagdLed(self._profile, i) for i in range(libratbag.ratbag_device_get_num_leds(device))] def __exit__(self): for r in self._resolutions: r.__exit__() for b in self._buttons: b.__exit__() for l in self._leds: l.__exit__() libratbag.ratbag_profile_unref(self._profile) @property def capabilities(self): """The capabilities of this profile as an array. Capabilities not present on the profile are not in the list. Thus use e.g. if RatbagdProfile.CAP_WRITABLE_NAME in profile.capabilities: do something """ return self._capabilities @property def name(self): """The name of the profile""" return libratbag.ratbag_profile_get_name(self._profile) @name.setter def name(self, name): """Set the name of this profile. @param name The new name, as str""" return libratbag.ratbag_profile_set_name(self._profile, name) @property def index(self): """The index of this profile.""" return self._id @property def dirty(self): """Whether this profile is dirty.""" return self._dirty @property def enabled(self): """tells if the profile is enabled.""" return libratbag.ratbag_profile_is_enabled(self._profile) @enabled.setter def enabled(self, enabled): """Enable/Disable this profile. @param enabled The new state, as boolean""" libratbag.ratbag_profile_set_enabled(self._profile, enabled) @property def report_rate(self): """The report rate in Hz.""" return libratbag.ratbag_profile_get_report_rate(self._profile) @report_rate.setter def report_rate(self, rate): """Set the report rate in Hz. @param rate The new report rate, as int """ libratbag.ratbag_profile_set_report_rate(self._profile, rate) @property def report_rates(self): """The list of supported report rates""" rates = [0 for i in range(300)] n = libratbag.ratbag_resolution_get_report_rate_list(self._profile, rates) return rates[:n] @property def resolutions(self): """A list of RatbagdResolution objects with this profile's resolutions. Note that the list of resolutions differs between profiles but the number of resolutions is identical across profiles.""" return self._resolutions @property def active_resolution(self): """The currently active resolution of this profile. This is a non-DBus property computed over the cached list of resolutions. In the unlikely case that your device driver is misconfigured and there is no active resolution, this returns the first resolution.""" for resolution in self._resolutions: if resolution.is_active: return resolution print("No active resolution. Please report this bug to the libratbag developers", file=sys.stderr) return self._resolutions[0] @property def buttons(self): """A list of RatbagdButton objects with this profile's button mappings. Note that the list of buttons differs between profiles but the number of buttons is identical across profiles.""" return self._buttons @property def leds(self): """A list of RatbagdLed objects with this profile's leds. Note that the list of leds differs between profiles but the number of leds is identical across profiles.""" return self._leds @property def is_active(self): """Returns True if the profile is currenly active, false otherwise.""" return libratbag.ratbag_profile_is_active(self._profile) def set_active(self): """Set this profile to be the active profile.""" libratbag.ratbag_profile_set_active(self._profile) class RatbagdResolution(metaclass=MetaRatbag): """Represents a ratbagd resolution.""" _PREFIX = "RATBAG_RESOLUTION_" def __init__(self, profile, id): self._id = id self._res = libratbag.ratbag_profile_get_resolution(profile, id) self._capabilities = get_capabilities("resolution", self._res) def __exit__(self): libratbag.ratbag_resolution_unref(self._res) @property def index(self): """The index of this resolution.""" return self._id @property def resolution(self): """The tuple (xres, yres) with each resolution in DPI.""" dpi_y = dpi_x = libratbag.ratbag_resolution_get_dpi_x(self._res) if libratbag.RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION in self._capabilities: dpi_y = libratbag.ratbag_resolution_get_dpi_y(self._res) return (dpi_x, dpi_y) return (dpi_x, ) @resolution.setter def resolution(self, res): """Set the x- and y-resolution using the given (xres, yres) tuple. @param res The new resolution, as (int, int) """ if libratbag.RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION in self._capabilities: libratbag.ratbag_resolution_set_dpi_xy(self._res, *res) else: libratbag.ratbag_resolution_set_dpi(self._res, res[0]) @property def resolutions(self): """The list of supported DPI values""" dpis = [0 for i in range(300)] n = libratbag.ratbag_resolution_get_dpi_list(self._res, dpis) return dpis[:n] @property def is_active(self): """True if this is the currently active resolution, False otherwise""" return libratbag.ratbag_resolution_is_active(self._res) @property def is_default(self): """True if this is the currently default resolution, False otherwise""" return libratbag.ratbag_resolution_is_default(self._res) def set_default(self): """Set this resolution to be the default.""" return libratbag.ratbag_resolution_set_default(self._res) def set_active(self): """Set this resolution to be the active one.""" return libratbag.ratbag_resolution_set_active(self._res) class RatbagdButton(metaclass=MetaRatbag): """Represents a ratbagd button.""" _PREFIX = "RATBAG_BUTTON_" class ActionType(IntEnum): NONE = libratbag.RATBAG_BUTTON_ACTION_TYPE_NONE BUTTON = libratbag.RATBAG_BUTTON_ACTION_TYPE_BUTTON SPECIAL = libratbag.RATBAG_BUTTON_ACTION_TYPE_SPECIAL MACRO = libratbag.RATBAG_BUTTON_ACTION_TYPE_MACRO class ActionSpecial(IntEnum): INVALID = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_INVALID UNKNOWN = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_UNKNOWN DOUBLECLICK = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_DOUBLECLICK WHEEL_LEFT = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT WHEEL_RIGHT = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT WHEEL_UP = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP WHEEL_DOWN = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN RATCHET_MODE_SWITCH = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_RATCHET_MODE_SWITCH RESOLUTION_CYCLE_UP = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP RESOLUTION_CYCLE_DOWN = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_DOWN RESOLUTION_UP = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP RESOLUTION_DOWN = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN RESOLUTION_ALTERNATE = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE RESOLUTION_DEFAULT = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT PROFILE_CYCLE_UP = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP PROFILE_CYCLE_DOWN = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_DOWN PROFILE_UP = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP PROFILE_DOWN = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN SECOND_MODE = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE BATTERY_LEVEL = libratbag.RATBAG_BUTTON_ACTION_SPECIAL_BATTERY_LEVEL class Macro(IntEnum): NONE = libratbag.RATBAG_MACRO_EVENT_NONE KEY_PRESS = libratbag.RATBAG_MACRO_EVENT_KEY_PRESSED KEY_RELEASE = libratbag.RATBAG_MACRO_EVENT_KEY_RELEASED WAIT = libratbag.RATBAG_MACRO_EVENT_WAIT """A table mapping a button's index to its usual function as defined by X and the common desktop environments.""" BUTTON_DESCRIPTION = { 0: N_("Left mouse button click"), 1: N_("Right mouse button click"), 2: N_("Middle mouse button click"), 3: N_("Backward"), 4: N_("Forward"), } """A table mapping a special function to its human-readable description.""" SPECIAL_DESCRIPTION = {} @classmethod def __late_init__(cls): cls.SPECIAL_DESCRIPTION = { cls.ActionSpecial.UNKNOWN: N_("Unknown"), cls.ActionSpecial.DOUBLECLICK: N_("Doubleclick"), cls.ActionSpecial.WHEEL_LEFT: N_("Wheel Left"), cls.ActionSpecial.WHEEL_RIGHT: N_("Wheel Right"), cls.ActionSpecial.WHEEL_UP: N_("Wheel Up"), cls.ActionSpecial.WHEEL_DOWN: N_("Wheel Down"), cls.ActionSpecial.RATCHET_MODE_SWITCH: N_("Ratchet Mode"), cls.ActionSpecial.RESOLUTION_CYCLE_UP: N_("Cycle Resolution Up"), cls.ActionSpecial.RESOLUTION_CYCLE_DOWN: N_("Cycle Resolution Down"), cls.ActionSpecial.RESOLUTION_UP: N_("Resolution Up"), cls.ActionSpecial.RESOLUTION_DOWN: N_("Resolution Down"), cls.ActionSpecial.RESOLUTION_ALTERNATE: N_("Resolution Switch"), cls.ActionSpecial.RESOLUTION_DEFAULT: N_("Default Resolution"), cls.ActionSpecial.PROFILE_CYCLE_UP: N_("Cycle Profile Up"), cls.ActionSpecial.PROFILE_CYCLE_DOWN: N_("Cycle Profile Down"), cls.ActionSpecial.PROFILE_UP: N_("Profile Up"), cls.ActionSpecial.PROFILE_DOWN: N_("Profile Down"), cls.ActionSpecial.SECOND_MODE: N_("Second Mode"), cls.ActionSpecial.BATTERY_LEVEL: N_("Battery Level"), } def __init__(self, profile, id): self._id = id self._button = libratbag.ratbag_profile_get_button(profile, id) self._capabilities = get_capabilities("button", self._button) def __exit__(self): libratbag.ratbag_button_unref(self._button) @property def index(self): """The index of this button.""" return self._id @property def mapping(self): """An integer of the current button mapping, if mapping to a button.""" return libratbag.ratbag_button_get_button(self._button) @mapping.setter def mapping(self, button): """Set the button mapping to the given button. @param button The button to map to, as int """ libratbag.ratbag_button_set_button(self._button, button) @property def macro(self): """A RatbagdMacro object representing the currently set macro.""" return RatbagdMacro.from_ratbag(libratbag.ratbag_button_get_macro(self._button)) @macro.setter def macro(self, macro): """Set the macro to the macro represented by the given RatbagdMacro object. @param macro A RatbagdMacro object representing the macro to apply to the button, as RatbagdMacro. """ macro_object = libratbag.ratbag_button_macro_new("macro") i = 0 for type, value in macro.keys: libratbag.ratbag_button_macro_set_event(macro_object, i, type, value) i += 1 libratbag.ratbag_button_set_macro(self._button, macro_object) libratbag.ratbag_button_macro_unref(macro_object) @property def special(self): """An enum describing the current special mapping, if mapped to special.""" return libratbag.ratbag_button_get_special(self._button) @special.setter def special(self, special): """Set the button mapping to the given special entry. @param special The special entry, as one of RatbagdButton.ActionSpecial """ libratbag.ratbag_button_set_special(self._button, special) @property def action_type(self): """An enum describing the action type of the button. One of ActionType.NONE, ActionType.BUTTON, ActionType.SPECIAL, ActionType.MACRO. This decides which *Mapping property has a value. """ return libratbag.ratbag_button_get_action_type(self._button) @property def action_types(self): """An array of possible values for ActionType.""" return [t for t in (RatbagdButton.ActionType.BUTTON, RatbagdButton.ActionType.SPECIAL, RatbagdButton.ActionType.MACRO) if libratbag.ratbag_button_has_action_type(self._button, t)] def disable(self): """Disables this button.""" return libratbag.ratbag_button_disable(self._button) class RatbagdMacro(metaclass=MetaRatbag): """Represents a button macro. Note that it uses keycodes as defined by linux/input.h and not those used by X.Org or any other higher layer such as Gdk.""" # All keys from ecodes.KEY have a KEY_ prefix. We strip it. _PREFIX_LEN = len("KEY_") # Both a key press and release. _MACRO_KEY = 1000 _MACRO_DESCRIPTION = { RatbagdButton.Macro.NONE: lambda key: ".", RatbagdButton.Macro.KEY_PRESS: lambda key: "↓{}".format(ecodes.KEY[key][RatbagdMacro._PREFIX_LEN:]), RatbagdButton.Macro.KEY_RELEASE: lambda key: "↑{}".format(ecodes.KEY[key][RatbagdMacro._PREFIX_LEN:]), RatbagdButton.Macro.WAIT: lambda val: "{}ms".format(val), _MACRO_KEY: lambda key: "↕{}".format(ecodes.KEY[key][RatbagdMacro._PREFIX_LEN:]), } def __init__(self): self._macro = [] def __str__(self): if not self._macro: return "None" keys = [] idx = 0 while idx < len(self._macro): t, v = self._macro[idx] try: if t == RatbagdButton.Macro.KEY_PRESS: # Check for a paired press/release event t2, v2 = self._macro[idx + 1] if t2 == RatbagdButton.Macro.KEY_RELEASE and v == v2: t = self._MACRO_KEY idx += 1 except IndexError: pass keys.append(self._MACRO_DESCRIPTION[t](v)) idx += 1 return " ".join(keys) @property def keys(self): """A list of (RatbagdButton.MACRO_*, value) tuples representing the current macro.""" return self._macro @staticmethod def from_ratbag(macro_object): """Instantiates a new RatbagdMacro instance from the given macro in libratbag format. @param macro The macro in libratbag format, as [(RatbagdButton.MACRO_*, value)]. """ ratbagd_macro = RatbagdMacro() for i in range(libratbag.ratbag_button_macro_get_num_events(macro_object)): type = libratbag.ratbag_button_macro_get_event_type(macro_object, i) value = None if type == RatbagdButton.Macro.WAIT: value = libratbag.ratbag_button_macro_get_event_timeout(macro_object, i) else: value = libratbag.ratbag_button_macro_get_event_key(macro_object, i) ratbagd_macro.append(type, value) return ratbagd_macro def accept(self): """Applies the currently cached macro.""" self.emit("macro-set") def append(self, type, value): """Appends the given event to the current macro. @param type The type of event, as one of RatbagdButton.MACRO_*. @param value If the type denotes a key event, the X.Org or Gdk keycode of the event, as int. Otherwise, the value of the timeout in milliseconds, as int. """ # Only append if the entry isn't identical to the last one, as we cannot # e.g. have two identical key presses in a row. if len(self._macro) == 0 or (type, value) != self._macro[-1]: self._macro.append((type, value)) self.notify("keys") class RatbagdLed(metaclass=MetaRatbag): """Represents a ratbagd led.""" _PREFIX = "RATBAG_LED_" class Mode(IntEnum): OFF = libratbag.RATBAG_LED_OFF ON = libratbag.RATBAG_LED_ON CYCLE = libratbag.RATBAG_LED_CYCLE BREATHING = libratbag.RATBAG_LED_BREATHING class ColorDepth(IntEnum): MONOCHROME = libratbag.RATBAG_LED_COLORDEPTH_MONOCHROME RGB_888 = libratbag.RATBAG_LED_COLORDEPTH_RGB_888 RGB_111 = libratbag.RATBAG_LED_COLORDEPTH_RGB_111 LED_DESCRIPTION = { # Translators: the LED is off. Mode.OFF: N_("Off"), # Translators: the LED has a single, solid color. Mode.ON: N_("Solid"), # Translators: the LED is cycling between red, green and blue. Mode.CYCLE: N_("Cycle"), # Translators: the LED's is pulsating a single color on different # brightnesses. Mode.BREATHING: N_("Breathing"), } def __init__(self, profile, id): self._id = id self._led = libratbag.ratbag_profile_get_led(profile, id) self._capabilities = get_capabilities("led", self._led) def __exit__(self): libratbag.ratbag_led_unref(self._led) @property def index(self): """The index of this led.""" return self._id @property def mode(self): """This led's mode, one of Mode.OFF, Mode.ON, Mode.CYCLE and Mode.BREATHING.""" return libratbag.ratbag_led_get_mode(self._led) @mode.setter def mode(self, mode): """Set the led's mode to the given mode. @param mode The new mode, as one of Mode.OFF, Mode.ON, Mode.CYCLE and Mode.BREATHING. """ libratbag.ratbag_led_set_mode(self._led, mode) @property def color(self): """An integer triple of the current LED color.""" c = libratbag.ratbag_led_get_color(self._led) return (c.red, c.green, c.blue) @color.setter def color(self, color): """Set the led color to the given color. @param color An RGB color, as an integer triplet with values 0-255. """ libratbag.ratbag_led_set_color(self._led, libratbag.ratbag_color(*color)) @property def colordepth(self): """An enum describing this led's colordepth, one of RatbagdLed.ColorDepth.MONOCHROME, RatbagdLed.ColorDepth.RGB""" return libratbag.ratbag_led_get_colordepth(self._led) @property def effect_duration(self): """The LED's effect duration in ms, values range from 0 to 10000.""" return libratbag.ratbag_led_get_effect_duration(self._led) @effect_duration.setter def effect_duration(self, effect_duration): """Set the effect duration in ms. Allowed values range from 0 to 10000. @param effect_duration The new effect duration, as int """ self._set_dbus_property("EffectDuration", "u", effect_duration) @property def brightness(self): """The LED's brightness, values range from 0 to 255.""" return libratbag.ratbag_led_get_brightness(self._led) @brightness.setter def brightness(self, brightness): """Set the brightness. Allowed values range from 0 to 255. @param brightness The new brightness, as int """ libratbag.ratbag_led_set_brightness(self._led, brightness) libratbag-0.13/tools/ratbagctl.1000066400000000000000000000110411362011324700165450ustar00rootroot00000000000000.TH RATBAGCTL "1" "@version@" .SH NAME ratbagctl \- inspect and modify configurable mice .SH SYNOPSIS .B ratbagctl [OPTIONS] {COMMAND} ... device .SH DESCRIPTION .PP The .B ratbagctl tool queries or changes a device's settings. .PP This tool usually needs ratbagd to be running. .SH OPTIONS .TP 8 .B \-\-version, \-V Show program's version number and exit. .TP 8 .B \-\-verbose, \-v Print debugging output. Multiple -v options increase the verbosity. For example, .B \-vvv will show the protocol output. .TP 8 .B \-\-nocommit Do not immediately write the settings to the mouse. This allows to set multiple parameters in a script, and the last call to .B ratbagctl will write them all. .TP 8 .B \-\-help, \-h Print the help. .SH General Commands .TP 8 .B list List supported devices (does not take a device argument) .SH Device Commands .TP 8 .B info Print information about a device .TP 8 .B name Print the device name .SH Profile Commands .TP 8 .B profile active get Print the currently active profile .TP 8 .B profile active set N Set profile N as to the active profile .TP 8 .B profile N {COMMAND} Use profile N for COMMAND .PP .B Available COMMANDs: .RS .TP 8 .B get Print selected profile information .TP 8 .B name get Print the name of the profile .TP 8 .B name set blah Set the name of the profile .TP 8 .B enable Enable a profile .TP 8 .B disable Disable a profile .TP 8 .B [resolution|dpi|rate|button|led] ... Use profile N for the specified command. .RE .SH Resolution Commands Resolution commands work on the given profile, or on the active profile if none is given. .TP 8 .B resolution active get Print the currently active resolution .TP 8 .B resolution active set N Set resolution N as the active resolution .TP 8 .B resolution default get Print the current default resolution .TP 8 .B resolution default set N Set resolution N as the default resolution .TP 8 .B resolution N {COMMAND} Use resolution N for COMMAND .PP .B Available COMMANDs: .RS .TP 8 .B get Print selected resolution .TP 8 .B [dpi|rate] ... Use resolution N for the specified command. .RE .SH DPI Commands DPI commands work on the given profile and resolution, or on the active resolution of the active profile if none are given. .TP 8 .B dpi get Print the dpi value .TP 8 .B dpi get-all Print the supported dpi values .TP 8 .B dpi set N Set the dpi value to N .SH Rate Commands Rate commands work on the given profile and resolution, or on the active resolution of the active profile if none are given. .TP 8 .B rate get Print the report rate in ms .TP 8 .B rate get-all Print the supported report rates in ms .TP 8 .B rate set N Set the report rate in N ms .SH Button Commands Button commands work on the given profile, or on the active profile if none is given. .TP 8 .B button count Print the number of buttons .TP 8 .B button N get Print the selected button .TP 8 .B button N action get Print the button action .TP 8 .B button N action set button B Set the button action to button B .TP 8 .B button N action set special S Set the button action to special action S .TP 8 .B button N action set macro ... Set the button action to the given macro .PP .B Macro syntax: .HP 8 A macro is a series of key events or waiting periods. Keys must be specified in linux/input-event-codes.h key names. .RS .TP 8 .B KEY_A Press and release 'a' .TP 8 .B +KEY_A Press 'a' .TP 8 .B \-KEY_A Release 'a' .TP 8 .B t300 Wait 300ms .RE .SH LED Commands LED commands work on the given profile, or on the active profile if none is given. .TP 8 .B led get Print the current led values .TP 8 .B led N get Print the selected LED value .TP 8 .B led N {COMMAND} Use led N for COMMAND .PP .B Available COMMANDs: .RS .TP 8 .B mode [on|off|cycle|breathing] The mode to set as current .TP 8 .B color RRGGBB The color to set as current (the color should be in the hexadecimal format). .TP 8 .B rate R The rate to set as current .TP 8 .B brightness B The brightness to set as current .RE .SH Examples .TP 8 ratbagctl profile active get eventX .TP 8 ratbagctl profile 0 resolution active set 4 eventX .TP 8 ratbagctl profile 0 resolution 1 dpi get eventX .TP 8 ratbagctl resolution 4 rate get eventX .TP 8 ratbagctl dpi set 800 eventX .SH NOTES .PP There is currently no guarantee that the output format of .B ratbagctl will not change in the future. There should be some stability with the commands mentioned in this man page, but do not expect it to stay the same. .SH AUTHORS .B ratbagctl was written by David Herrmann, Peter Hutterer and Benjamin Tissoires. .PP This manual page was written by Stephen Kitt for the Debian GNU/Linux system (but may be used by others). libratbag-0.13/tools/ratbagctl.devel.in000077500000000000000000000066251362011324700201300ustar00rootroot00000000000000#!/usr/bin/env python3 # # This file is part of libratbag. # # Copyright 2017 Red Hat, Inc. # # 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 (including the next # paragraph) 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. import argparse import os import time import toolbox import signal import sys from ratbagctl import RatbagErrorCapability # NOQA def main(argv, start_ratbagd=True): if not os.geteuid() == 0: sys.exit('Script must be run as root') # replaced by meson os.environ['LIBRATBAG_DATA_DIR'] = '@LIBRATBAG_DATA_DIR@' os.environ['RATBAG_TEST'] = "1" if not argv: argv = ["list"] cmd = None ratbagd_process = None try: parser = toolbox.get_parser() parser.want_keepalive = True cmd = parser.parse(argv) if cmd.help: parser.print_help() return if start_ratbagd: ratbagd_process = toolbox.start_ratbagd(verbosity=cmd.verbose) if ratbagd_process is None: sys.exit("Failed to start or connect to ratbagd") _ratbagd = toolbox.open_ratbagd() if _ratbagd is not None: with _ratbagd as ratbagd: try: f = cmd.func except AttributeError: parser.print_help() return else: try: f(ratbagd, cmd) # give time for ratbagd to receive async requests before # we try to terminate it time.sleep(0.5) except RatbagErrorCapability as e: print("Error: {}".format(e), file=sys.stderr) finally: try: if cmd.keepalive: signal.pause() except KeyboardInterrupt: pass except AttributeError: # in case the parsing failed and cmd is None pass if ratbagd_process: toolbox.terminate_ratbagd(ratbagd_process) if __name__ == "__main__": parser = argparse.ArgumentParser(description='commandline tool to access the ratbagd.devel debug server') parser.add_argument('--use-existing-ratbagd', dest='use_existing', action='store_true', default=False, help='Don\'t start up ratbagd.devel, connect to the already running one') args, remainder = parser.parse_known_args() main(remainder, not args.use_existing) libratbag-0.13/tools/ratbagctl.in.in000077500000000000000000001467451362011324700174470ustar00rootroot00000000000000#!/usr/bin/env python3 # # Copyright 2016 Red Hat, Inc. # # 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 (including the next # paragraph) 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. import evdev import sys import argparse import re from ratbagd import Ratbagd, RatbagdDevice, RatbagdProfile, RatbagdMacro, RatbagdResolution, RatbagdButton, RatbagdLed, RatbagdUnavailable, RatbagError, RatbagErrorCode, RatbagErrorCapability # NOQA def humanize(string): return string.lower().replace('_', '-') button_special_names = [humanize(e.name) for e in RatbagdButton.ActionSpecial if e.name != "INVALID"] led_mode_names = [humanize(e.name) for e in RatbagdLed.Mode] button_specials_strmap = { **{e: e.name.lower().replace("_", "-") for e in RatbagdButton.ActionSpecial}, **{e.name.lower().replace("_", "-"): e for e in RatbagdButton.ActionSpecial} } def list_devices(r, args): if not r.devices: print("No devices available.") for d in r.devices: print("{:20s} {:32s}".format(d.id + ":", d.name)) def find_device(r, args): dev = r[args.device] if dev is None: for d in r.devices: if args.device in d.name: return d print("Unable to find device {}".format(args.device)) sys.exit(1) return dev def find_profile(r, args): d = find_device(r, args) try: p = d.profiles[args.profile_n] except IndexError: print("Invalid profile index {}".format(args.profile_n)) sys.exit(1) except AttributeError: p = d.active_profile return p, d def find_resolution(r, args): p, d = find_profile(r, args) try: r = p.resolutions[args.resolution_n] except IndexError: print("Invalid resolution index {}".format(args.resolution_n)) sys.exit(1) except AttributeError: r = p.active_resolution return r, p, d def find_button(r, args): p, d = find_profile(r, args) try: b = p.buttons[args.button_n] except IndexError: print("Invalid button index {}".format(args.button_n)) sys.exit(1) return b, p, d def find_led(r, args): p, d = find_profile(r, args) try: l = p.leds[args.led_n] except IndexError: print("Invalid LED index {}".format(args.led_n)) sys.exit(1) return l, p, d def print_led(d, p, l, level): leds = { RatbagdLed.Mode.BREATHING: "breathing", RatbagdLed.Mode.CYCLE: "cycle", RatbagdLed.Mode.OFF: "off", RatbagdLed.Mode.ON: "on", } depths = { RatbagdLed.ColorDepth.MONOCHROME: "monochrome", RatbagdLed.ColorDepth.RGB_888: "rgb", RatbagdLed.ColorDepth.RGB_111: "rgb111", } if l.mode == RatbagdLed.Mode.OFF: print(" " * level + "LED: {}, depth: {}, mode: {}".format(l.index, depths[l.colordepth], leds[l.mode])) elif l.mode == RatbagdLed.Mode.ON: print(" " * level + "LED: {}, depth: {}, mode: {}, color: {:02x}{:02x}{:02x}".format(l.index, depths[l.colordepth], leds[l.mode], l.color[0], l.color[1], l.color[2])) elif l.mode == RatbagdLed.Mode.CYCLE: print(" " * level + "LED: {}, depth: {}, mode: {}, duration: {}, brightness: {}".format(l.index, depths[l.colordepth], leds[l.mode], l.effect_duration, l.brightness)) elif l.mode == RatbagdLed.Mode.BREATHING: print(" " * level + "LED: {}, depth: {}, mode: {}, color: {:02x}{:02x}{:02x}, duration: {}, brightness: {}".format(l.index, depths[l.colordepth], leds[l.mode], l.color[0], l.color[1], l.color[2], l.effect_duration, l.brightness)) def print_led_caps(d, p, l, level): leds = { RatbagdLed.Mode.BREATHING: "breathing", RatbagdLed.Mode.CYCLE: "cycle", RatbagdLed.Mode.OFF: "off", RatbagdLed.Mode.ON: "on", } supported = sorted([v for k, v in leds.items() if k in l.modes]) print(" " * level + "Modes: {}".format(", ".join(supported))) def print_button(d, p, b, level): header = " " * level + "Button: {} is mapped to ".format(b.index) if b.action_type == RatbagdButton.ActionType.BUTTON: print("{}'button {}'".format(header, b.mapping)) elif b.action_type == RatbagdButton.ActionType.SPECIAL: print("{}'{}'".format(header, button_specials_strmap[b.special])) elif b.action_type == RatbagdButton.ActionType.MACRO: print("{}macro '{}'".format(header, str(b.macro))) elif b.action_type == RatbagdButton.ActionType.NONE: print("{}none".format(header)) else: print("{}UNKNOWN".format(header)) def print_resolution(d, p, r, level): if r.resolution == (0, 0): print(" " * level + "{}: ".format(r.index)) return if len(r.resolution) == 2: dpi = "{}x{}".format(r.resolution[0], r.resolution[1]) else: dpi = "{}".format(r.resolution[0]) print(" " * level + "{}: {}dpi{}{}".format(r.index, dpi, " (active)" if r.is_active else "", " (default)" if r.is_default else "", )) def print_profile(d, p, level): print(" " * (level - 2) + "Profile {}:{}{}".format(p.index, " (disabled)" if not p.enabled else "", " (active)" if p.is_active else "")) if p.enabled: print(" " * level + "Name: {}".format(p.name or 'n/a')) print(" " * level + "Report Rate: {}Hz".format(p.report_rate)) print(" " * level + "Resolutions:") for r in p.resolutions: print_resolution(d, p, r, level + 2) for b in p.buttons: print_button(d, p, b, level) for l in p.leds: print_led(d, p, l, level) def print_device(d, level): p = d.profiles[0] # there should be always one print(" " * level + "{} - {}".format(d.id, d.name)) print(" " * level + " Model: {}".format(d.model)) print(" " * level + " Number of Buttons: {}".format(len(p.buttons))) print(" " * level + " Number of Leds: {}".format(len(p.leds))) print(" " * level + "Number of Profiles: {}".format(len(d.profiles))) for p in d.profiles: print_profile(d, p, level + 2) def show_device(r, args): d = find_device(r, args) print_device(d, 0) def show_profile(r, args): p, d = find_profile(r, args) print("Profile {} on {} ({})".format(args.profile, d.id, d.name)) print_profile(d, p, 0) def show_resolution(r, args): r, p, d = find_resolution(r, args) print("Resolution {} on Profile {} on {} ({})".format(args.resolution, args.profile, d.id, d.name)) print_resolution(d, p, r, 0) caps = {RatbagdResolution.CAP_INDIVIDUAL_REPORT_RATE: "individual-report-rate", RatbagdResolution.CAP_SEPARATE_XY_RESOLUTION: "separate-xy-resolution"} capabilities = [caps[c] for c in r.capabilities] print(" Capabilities: {}".format(", ".join(capabilities))) def show_button(r, args): b, p, d = find_button(r, args) print("Button {} on Profile {} on {} ({})".format(args.button, args.profile, d.id, d.name)) print_button(d, p, b, 0) def func_led_get(r, args): l, p, d = find_led(r, args) print_led(d, p, l, 0) def func_led_caps(r, args): l, p, d = find_led(r, args) print_led_caps(d, p, l, 0) def func_led_set(r, args): l, p, d = find_led(r, args) try: mode = args.mode except AttributeError: pass else: leds = { "breathing": RatbagdLed.Mode.BREATHING, "cycle": RatbagdLed.Mode.CYCLE, "off": RatbagdLed.Mode.OFF, "on": RatbagdLed.Mode.ON, } l.mode = leds[mode] try: color = args.color except AttributeError: pass else: l.color = color try: duration = args.duration except AttributeError: pass else: l.effect_duration = duration try: brightness = args.brightness except AttributeError: pass else: l.brightness = brightness commit(d, args) def func_led_get_all(r, args): p, d = find_profile(r, args) for l in p.leds: print_led(d, p, l, 0) def func_button_get(r, args): b, p, d = find_button(r, args) print_button(b, p, b, 0) def func_button_action_set_button(r, args): b, p, d = find_button(r, args) b.mapping = args.target_button commit(d, args) def func_button_action_set_special(r, args): b, p, d = find_button(r, args) try: special = args.target_special except AttributeError: pass else: b.special = button_specials_strmap[special] commit(d, args) def func_button_action_set_macro(r, args): b, p, d = find_button(r, args) if not b.ActionType.MACRO in b.action_types: raise RatbagErrorCapability("assigning a macro is not supported on this device") macro_keys = args.target_macro macro = RatbagdMacro() for s in macro_keys: is_press = True is_release = True is_timeout = False s = s.upper() if s[0] == 'T': is_timeout = True is_press = False is_release = False elif s[0] == '+': is_release = False s = s[1:] elif s[0] == '-': is_press = False s = s[1:] if is_timeout: t = int(s[1:]) macro.append(RatbagdButton.Macro.WAIT, t) else: if not s.startswith("KEY_") and not s.startswith("BTN_"): msg = "Don't know how to convert {}".format(s) raise argparse.ArgumentTypeError(msg) code = evdev.ecodes.ecodes[s] if is_press: macro.append(RatbagdButton.Macro.KEY_PRESS, code) if is_release: macro.append(RatbagdButton.Macro.KEY_RELEASE, code) b.macro = macro commit(d, args) def func_button_count(r, args): p, d = find_profile(r, args) print(len(p.buttons)) def func_dpi_get(r, args): r, p, d = find_resolution(r, args) if len(r.resolution) == 2: print("{}x{}dpi".format(r.resolution[0], r.resolution[1])) else: print("{}dpi".format(r.resolution[0])) def func_dpi_get_all(r, args): r, p, d = find_resolution(r, args) dpis = r.resolutions print(" ".join([str(x) for x in dpis])) def func_dpi_set(r, args): r, p, d = find_resolution(r, args) dpi = args.dpi_n if len(r.resolution) > len(dpi): dpi = (dpi[0], dpi[0]) r.resolution = dpi commit(d, args) def func_report_rate_get(r, args): p, d = find_profile(r, args) print(p.report_rate) def func_report_rate_get_all(r, args): p, d = find_profile(r, args) rates = p.report_rates print(" ".join([str(x) for x in rates])) def func_report_rate_set(r, args): p, d = find_profile(r, args) p.report_rate = args.rate_n commit(d, args) def func_resolution_get(r, args): r, p, d = find_resolution(r, args) print_resolution(d, p, r, 0) def func_resolution_active_get(r, args): p, d = find_profile(r, args) print(p.active_resolution.index) def func_resolution_active_set(r, args): r, p, d = find_resolution(r, args) r.set_active() commit(d, args) def func_resolution_default_get(r, args): p, d = find_profile(r, args) for r in p.resolutions: if r.is_default: break else: r = None print(r.index) def func_default_resolution_set(r, args): # FIXME: capabilities check? r, p, d = find_resolution(r, args) r.set_default() commit(d, args) def func_profile_get(r, args): p, d = find_profile(r, args) print_profile(d, p, 0) def func_profile_name_get(r, args): p, d = find_profile(r, args) # See https://github.com/libratbag/libratbag/issues/617 # ratbag converts to ascii, so this has no real effect there, but # ratbag-command may still have a non-ascii string. string = bytes(p.name, 'utf-8', 'ignore') print(string.decode('utf-8')) def func_profile_name_set(r, args): p, d = find_profile(r, args) if not p.name: raise RatbagErrorCapability("assigning a profile name is not supported on this profile") p.name = args.name commit(d, args) def func_profile_active_get(r, args): d = find_device(r, args) print(d.active_profile.index) def func_profile_active_set(r, args): p, d = find_profile(r, args) p.set_active() commit(d, args) def func_profile_enable(r, args): p, d = find_profile(r, args) p.enabled = True commit(d, args) def func_profile_disable(r, args): p, d = find_profile(r, args) p.enabled = False commit(d, args) def func_device_name_get(r, args): d = find_device(r, args) print(d.name) ################################################################################ # these are definitions to be reused in the dict that defines our language # key elements """the type of the element (see 'types' below)""" of_type = 'type' """the name of the element, it'll be the one matching the args on the CLI""" name = 'name' """the group to logically associate commands while printing the help""" group = 'group' """list of positional arguments for the given command""" pos_args = 'pos_args' """a tag that we can refer latrer in an element of type 'link'""" tag = 'tag' """the element pointed to in an element of type 'link'""" dest = 'dest' """the function to associate to the switch or command""" func = 'func' """this is a particular command that is an integer, but not an terminating argument. example: profile active get profile **2** button 3 get - "profile" needs to be a switch - "2" needs to be translated as a N_access, given it is a requirement to be able to call 'button' """ N_access = 'N_access' # argparse.add_argument parameters (forwarded as such) """'type' of the argument""" arg_type = 'arg_type' """'metavar' of the argument""" metavar = 'metavar' """'help' of the argument""" help_str = 'help' """'nargs' of the argument""" nargs = 'nargs' """'choices' of the argument""" choices = 'choices' # types """an option to interprete as a command (example 'list', 'info')""" command = 'command' """an argument that is required for the given command arguments are leaf nodes and can not have children """ argument = 'argument' """provides a list of choice of commands for instance, a switch of [A, B] means we can have A or B only when parsing the command line """ switch = 'switch' """same as list, except we can loop inside the list for instance, a set of [A, B] means we can have A and B (and A, ...) one after the other, no matter the order """ set = 'set' """a reference to any other element in the tree marked with a tag""" link = 'link' ################################################################################ def commit(device, args): if args.nocommit: return device.commit() def color(string): try: int_value = int(string, 16) except ValueError: msg = "%r is not a color in hex format" % string raise argparse.ArgumentTypeError(msg) r = (int_value >> 16) & 0xff g = (int_value >> 8) & 0xff b = (int_value >> 0) & 0xff return (r, g, b) def u8(string): int_value = int(string) msg = "%r is not a single byte" % string if int_value < 0 or int_value > 255: raise argparse.ArgumentTypeError(msg) return int_value def dpi(string): try: int_value = int(string) except ValueError: pass else: return (int_value, ) if string.endswith("dpi"): string = string[:-3] x, y = string.split("x") try: int_x = int(x) int_y = int(y) except ValueError: raise argparse.ArgumentTypeError("%r is not a valid dpi" % string) else: return (int_x, int_y) # note: 'hidrawX' is assumed before each command parser_def = [ { of_type: command, name: 'info', help_str: 'Show device information', func: show_device, group: 'Device', }, { of_type: command, name: 'name', help_str: 'Returns the device name', func: func_device_name_get, }, { of_type: switch, name: 'profile', help_str: 'Access profile information', tag: 'profile', group: 'Profile', switch: [ { of_type: switch, name: 'active', help_str: 'access active profile information', switch: [ { of_type: command, name: 'get', help_str: 'Show current active profile', func: func_profile_active_get, }, { of_type: command, name: 'set', help_str: 'Set current active profile', pos_args: [ { of_type: argument, name: 'profile_n', metavar: 'N', help_str: 'The profile to set as current', arg_type: int, }, ], func: func_profile_active_set, }, ], }, ], N_access: { of_type: N_access, name: 'profile_n', metavar: 'N', help_str: 'The profile to act on', switch: [ { of_type: command, name: 'get', help_str: 'Show selected profile information', func: func_profile_get, }, { of_type: switch, name: 'name', help_str: 'access profile name information', switch: [ { of_type: command, name: 'get', help_str: 'Show the name of the profile', func: func_profile_name_get, }, { of_type: command, name: 'set', help_str: 'Set the name of the profile', pos_args: [ { of_type: argument, name: 'name', metavar: 'blah', help_str: 'The name to set', }, ], func: func_profile_name_set, }, ], }, { of_type: command, name: 'enable', help_str: 'Enable a profile', func: func_profile_enable, }, { of_type: command, name: 'disable', help_str: 'Disable a profile', func: func_profile_disable, }, { of_type: link, dest: 'resolution', }, { of_type: link, dest: 'dpi', }, { of_type: link, dest: 'rate', }, { of_type: link, dest: 'button', }, { of_type: link, dest: 'led', }, ], }, }, { of_type: switch, name: 'resolution', help_str: """Access resolution information Resolution commands work on the given profile, or on the active profile if none is given.""", tag: 'resolution', group: 'Resolution', switch: [ { of_type: switch, name: 'active', help_str: 'access active resolution information', switch: [ { of_type: command, name: 'get', help_str: 'Show current active resolution', func: func_resolution_active_get, }, { of_type: command, name: 'set', help_str: 'Set current active resolution', pos_args: [ { of_type: argument, name: 'resolution_n', metavar: 'N', help_str: 'The resolution to set as current', arg_type: int, }, ], func: func_resolution_active_set, }, ], }, { of_type: switch, name: 'default', help_str: 'access default resolution information', switch: [ { of_type: command, name: 'get', help_str: 'Show current default resolution', func: func_resolution_default_get, }, { of_type: command, name: 'set', help_str: 'Set current default resolution', pos_args: [ { of_type: argument, name: 'resolution_n', metavar: 'N', help_str: 'The resolution to set as default', arg_type: int, }, ], func: func_default_resolution_set, }, ], }, ], N_access: { of_type: N_access, name: 'resolution_n', metavar: 'N', help_str: 'The resolution to act on', switch: [ { of_type: command, name: 'get', help_str: 'Show selected resolution', func: func_resolution_get, }, { of_type: link, dest: 'dpi', }, ], }, }, { of_type: switch, name: 'dpi', help_str: """Access DPI information DPI commands work on the given profile and resolution, or on the active resolution of the active profile if none are given.""", tag: 'dpi', group: 'DPI', switch: [ { of_type: command, name: 'get', help_str: 'Show current DPI value', func: func_dpi_get, }, { of_type: command, name: 'get-all', help_str: 'Show all available DPIs', func: func_dpi_get_all, }, { of_type: command, name: 'set', help_str: 'Set the DPI value to N', pos_args: [ { of_type: argument, name: 'dpi_n', metavar: 'N', help_str: 'The resolution to set as current', arg_type: dpi, }, ], func: func_dpi_set, }, ], }, { of_type: switch, name: 'rate', help_str: """Access report rate information Rate commands work on the given profile, or on the active profile if none is given.""", tag: 'rate', group: 'Rate', switch: [ { of_type: command, name: 'get', help_str: 'Show current report rate', func: func_report_rate_get, }, { of_type: command, name: 'get-all', help_str: 'Show all available report rates', func: func_report_rate_get_all, }, { of_type: command, name: 'set', help_str: 'Set the report rate to N', pos_args: [ { of_type: argument, name: 'rate_n', metavar: 'N', help_str: 'The report rate to set as current', arg_type: int, }, ], func: func_report_rate_set, }, ], }, { of_type: switch, name: 'button', help_str: """Access Button information Button commands work on the given profile, or on the active profile if none is given.""", tag: 'button', group: 'Button', switch: [ { of_type: command, name: 'count', help_str: 'Print the number of buttons', func: func_button_count, }, ], N_access: { of_type: N_access, name: 'button_n', metavar: 'N', help_str: 'The button to act on', switch: [ { of_type: command, name: 'get', help_str: 'Show selected button', func: func_button_get, }, { of_type: switch, name: 'action', help_str: 'Act on the selected button', switch: [ { of_type: command, name: 'get', help_str: 'Print the button action', func: func_button_get, }, { of_type: switch, name: 'set', help_str: 'Set an action on the selected button', switch: [ { of_type: command, name: 'button', help_str: 'Set the button action to button B', pos_args: [ { of_type: argument, name: 'target_button', metavar: 'B', help_str: 'The new button value to assign', arg_type: int, }, ], func: func_button_action_set_button, }, { of_type: command, name: 'special', help_str: 'Set the button action to special action S', pos_args: [ { of_type: argument, name: 'target_special', metavar: 'S', help_str: 'The new special value to assign', choices: button_special_names }, ], func: func_button_action_set_special, }, { of_type: command, name: 'macro', help_str: """Set the button action to the given macro Macro syntax: A macro is a series of key events or waiting periods. Keys must be specified in linux/input.h key names. KEY_A Press and release 'a' +KEY_A Press 'a' -KEY_A Release 'a' t300 Wait 300ms""", pos_args: [ { of_type: argument, name: 'target_macro', metavar: '...', help_str: 'The new macro to assign', nargs: argparse.REMAINDER, }, ], func: func_button_action_set_macro, }, ] }, ] }, ], }, }, { of_type: switch, name: 'led', help_str: """Access LED information LED commands work on the given profile, or on the active profile if none is given.""", tag: 'led', group: 'LED', switch: [ { of_type: command, name: 'get', help_str: 'Show current LED value', func: func_led_get_all, }, ], N_access: { of_type: N_access, name: 'led_n', metavar: 'N', help_str: 'The LED to act on', switch: [ { of_type: command, name: 'get', help_str: 'Show current LED value', func: func_led_get, }, { of_type: command, name: 'capabilities', help_str: 'Show LED capabilities', func: func_led_caps, }, { of_type: set, name: 'set', help_str: 'Act on the selected LED', switch: [ { of_type: command, name: 'mode', help_str: 'The mode to set as current', pos_args: [ { of_type: argument, name: 'mode', metavar: 'mode', help_str: 'The mode to set as current', choices: led_mode_names, }, ], }, { of_type: command, name: 'color', help_str: 'The color to set as current', pos_args: [ { of_type: argument, name: 'color', metavar: 'RRGGBB', help_str: 'The color in hex format to set as current', arg_type: color, }, ], }, { of_type: command, name: 'duration', help_str: 'The duration to set as current', pos_args: [ { of_type: argument, name: 'duration', metavar: 'R', help_str: 'The duration in ms to set as current', arg_type: int, }, ], }, { of_type: command, name: 'brightness', help_str: 'The brightness to set as current', pos_args: [ { of_type: argument, name: 'brightness', metavar: 'B', help_str: 'The brightness to set as current', arg_type: u8, }, ], }, ], func: func_led_set, }, ], }, }, ] class ParseError(Exception): pass class RatbagParser(object): tagged = {} def __init__(self, type, name, group=None, tag=None, func=None, help=None): self.type = type self.name = name self.tag = tag self.group = group if tag is not None: RatbagParser.tagged[tag] = self self.func = func self.help = help def repr_args(self): return "name='{}', tag='{}', func='{}', help='{}'".format(self.name, self.tag, self.func, self.help) def __repr__(self): return "{}({})".format(type(self), self.repr_args()) def store_function(self, parser): if self.func is not None: parser.set_defaults(func=self.func) def _add_to_subparsers(self, parent, input_string, ns): raise ParseError("please implement _add_to_subparsers on {}".format(type(self))) def add_to_subparsers(self, parent): self._add_to_subparsers(parent) def _sub_parse(self, input_string, ns): raise ParseError("please implement _sub_parse on {}".format(type(self))) def sub_parse(self, input_string, ns): r = self._sub_parse(input_string, ns) return r def build_cmd_args_name(self): """uniquely tag the arguments of the command in the namespace""" return "{}_args_{}".format(self.name) def print_help(self, group, prefix=""): if self.group is not None: print("\n{} Commands:".format(self.group)) self._print_help(prefix) def _print_help(self, prefix): raise ParseError("please implement _print_help on {}".format(type(self))) class RatbagParserSwitch(RatbagParser): def __init__(self, type, name, group=None, switch=[], N_access=None, tag=None, func=None, help=None): super().__init__(type, name, group, tag, func, help) self.switch = [classes[obj[of_type]](**obj) for obj in switch] if N_access is not None: self.N_access = RatbagParserNAccess(**N_access) else: self.N_access = None def repr_args(self): return """switch='{}', N_access='{}', {}""".format([repr(o) for o in self.switch], self.N_access, RatbagParser.repr_args(self)) def _add_to_subparsers(self, parent): parser = parent.add_parser(self.name, help=self.help) parser.set_defaults(subparse=self.sub_parse) def _sub_parse(self, input_string, ns): if input_string and self.N_access is not None: # retrieve first numbered element if any try: int(input_string[0]) except ValueError: # there are arguments, but they look like commands pass else: # we have a single int as first argument, switch to the # N_access subtree of the command return self.N_access._sub_parse(self, input_string, ns) parser = argparse.ArgumentParser(prog="{} {}".format(sys.argv[0], self.name), description=self.help, add_help=False) # create a new subparser to handle all commands subs = parser.add_subparsers(title="COMMANDS", help=None) for e in self.switch: e.add_to_subparsers(subs) return parser def _print_help(self, prefix): if self.help and self.group is not None: string = self.help.split('\n') string = "\n ".join(string) print(" ", string, '\n') for e in self.switch: e.print_help(None, "{}{} ".format(prefix, self.name)) if self.N_access is not None: self.N_access.print_help(None, self.name + " ") def __repr__(self): return "switch({})".format(self.repr_args()) class RatbagParserNAccess(RatbagParserSwitch): def __init__(self, type, name, group=None, switch=[], metavar=None, tag=None, func=None, help=None): super().__init__(type, name, group, switch, None, tag, func, help) self.metavar = metavar def _sub_parse(self, parent, input_string, ns): parser = argparse.ArgumentParser(prog="{} {}".format(sys.argv[0], parent.name), add_help=False) parser.add_argument(self.name, help=self.help, type=int) # create a new subparser to handle all commands subs = parser.add_subparsers(title="COMMANDS", help=None) for e in self.switch: e.add_to_subparsers(subs) return parser def repr_args(self): return """switch='{}', metavar = '{}', {}""".format([repr(o) for o in self.switch], self.metavar, RatbagParser.repr_args(self)) def __repr__(self): return "N_Access({})".format(self.repr_args()) def _print_help(self, prefix): for e in self.switch: e.print_help(None, "{}N ".format(prefix)) class RatbagParserSet(RatbagParserSwitch): def __init__(self, type, name, group=None, switch=[], N_access=None, tag=None, func=None, help=None): super().__init__(type, name, group, switch, N_access, tag, func, help) def _add_to_subparsers(self, parent): parser = parent.add_parser(self.name, help=self.help) parser.set_defaults(subparse=self.sub_parse) def _sub_parse(self, input_string, ns): parser = argparse.ArgumentParser(prog="{} {}".format(sys.argv[0], self.name), add_help=False) # create a new subparser to handle all commands subs = parser.add_subparsers(title="COMMANDS", help=None) for e in self.switch: e.add_to_subparsers(subs) if len(input_string) == 2: self.store_function(parser) else: parser.set_defaults(subparse=self.sub_parse) return parser def __repr__(self): return "set({})".format(self.repr_args()) def _print_help(self, prefix): command = prefix + "{COMMAND} ..." print(" {:<36}{}".format(command, self.help if self.help else "")) for e in self.switch: e.print_help(None, " " * len(prefix)) class RatbagParserCommand(RatbagParser): def __init__(self, type, name, group=None, pos_args=[], tag=None, func=None, help=None): super().__init__(type, name, group, tag, func, help) self.pos_args = [classes[obj[of_type]](**obj) for obj in pos_args] def _add_to_subparsers(self, parent): parser = parent.add_parser(self.name, help=self.help) for a in self.pos_args: a.add_to_subparsers(parser) self.store_function(parser) def __repr__(self): return "command({})".format(self.repr_args()) def _print_help(self, prefix): command = prefix + self.name for a in self.pos_args: if a.choices is None or len(a.choices) > 5: command += " {}".format(a.metavar) else: command += " [{}]".format("|".join(a.choices)) print(" {:<36}{}".format(command, self.help if self.help else "")) class RatbagParserArgument(RatbagParser): def __init__(self, type, name, group=None, arg_type=None, metavar=None, nargs=None, choices=None, tag=None, func=None, help=None): super().__init__(type, name, group, tag, func, help) self.arg_type = arg_type self.metavar = metavar self.nargs = nargs self.choices = choices def _add_to_subparsers(self, parent): parent.add_argument(self.name, metavar=self.metavar, help=self.help, type=self.arg_type, nargs=self.nargs, choices=self.choices) class RatbagParserLink(RatbagParser): def __init__(self, type, dest=None, group=None, tag=None, func=None, help=None): super().__init__(type, dest, group, tag, func, help) self.dest = dest def get_dest(self): try: dest = RatbagParser.tagged[self.dest] except KeyError: raise ParseError("link '{}' points to nothing".format(self.dest)) return dest def _add_to_subparsers(self, parent): dest = self.get_dest() dest.add_to_subparsers(parent) def _print_help(self, prefix): dest = self.get_dest() print(" {:<36}Use {}for '{} Commands'".format(prefix + self.dest + " ...", prefix, dest.group)) classes = { switch: RatbagParserSwitch, set: RatbagParserSet, command: RatbagParserCommand, argument: RatbagParserArgument, link: RatbagParserLink, N_access: RatbagParserNAccess, } class RatbagParserRoot(object): def __init__(self, commands): self.children = [classes[def_parser[of_type]](**def_parser) for def_parser in commands] self.want_keepalive = False def parse(self, input_string): self.parser = argparse.ArgumentParser(description="Inspect and modify a configurable device", add_help=False) self.parser.add_argument("-V", "--version", action="version", version="@version@") self.parser.add_argument('--verbose', '-v', action='count', default=0) self.parser.add_argument('--help', '-h', action='store_true', default=False) self.parser.add_argument('--nocommit', action='store_true', default=False) if self.want_keepalive: self.parser.add_argument('--keepalive', action='store_true', default=False) # retrieve the global options now and remove them from the processing ns, rest = self.parser.parse_known_args(input_string) if ns.help: return ns # retrieve the device and remove it from the command processing self.parser.add_argument('device_or_list', action="store") ns, rest = self.parser.parse_known_args(rest, namespace=ns) if ns.device_or_list == 'list': if rest: self.parser.error("extra arguments: '{}'".format(" ".join(rest))) ns.func = list_devices return ns ns.device = ns.device_or_list # we need a new parser or 'device_or_list' will eat all of our commands command_parser = argparse.ArgumentParser(description="command parser", prog="{} ".format(sys.argv[0]), add_help=False) subs = command_parser.add_subparsers(title="COMMANDS") subparser = command_parser for child in self.children: child.add_to_subparsers(subs) ns.subparse = None while rest and subparser: old_rest = rest ns, rest = subparser.parse_known_args(rest, namespace=ns) if hasattr(ns, func): break if old_rest == rest: break if ns.subparse: subparser = ns.subparse(rest, ns) if rest: self.parser.error("extra arguments: '{}'".format(" ".join(rest))) return ns def print_help(self): print("usage: {} [OPTIONS] list".format(self.parser.prog)) print(" {} [OPTIONS] {{COMMAND}} ...\n".format(self.parser.prog)) print(self.parser.description) print(""" Common options: --version -V show program's version number and exit --verbose, -v increase verbosity level --nocommit Do not immediately write the settings to the mouse --help, -h show this help and exit""") if self.want_keepalive: print(" --keepalive do not terminate ratbagd after the processing") print(""" General Commands: list List supported devices (does not take a device argument)""") for c in self.children: c.print_help(None) print(""" Examples: {0} profile active get {0} profile 0 resolution active set 4 {0} profile 0 resolution 1 dpi get {0} resolution 4 rate get {0} dpi set 800 {0} profile 0 led 0 set mode on {0} profile 0 led 0 set color ff00ff {0} profile 0 led 0 set duration 50 Exit codes: 0 Success 1 Unsupported feature, index out of available range or invalid device 2 Commandline arguments are invalid 3 A command failed on the device """.format(self.parser.prog)) def get_parser(): return RatbagParserRoot(parser_def) def on_device_added(ratbagd, device): device_names = [ 'mara', 'capybara', 'porcupine', 'paca', 'vole', 'woodrat', 'gerbil', 'shrew', 'hutia', 'beaver', 'squirrel', 'chinchilla', 'rabbit', 'viscacha', 'hare', 'degu', 'gundi', 'acouchy', 'nutria', 'paca', 'hamster', 'zokor', 'chipmunk', 'gopher', 'marmot', 'groundhog', 'suslik', 'agouti', 'blesmol', ] device_attr = [ 'sobbing', 'whooping', 'barking', 'yapping', 'howling', 'squawking', 'cheering', 'warbling', 'thundering', 'booming', 'blustering', 'humming', 'crying', 'bawling', 'roaring', 'raging', 'chanting', 'crooning', 'murmuring', 'bellowing', 'wailing', 'weeping', 'screaming', 'yelling', 'yodeling', 'singing', 'honking', 'hooting', 'whispering', 'hollering', ] # Let's convert the sha into something not boring. This takes the first # 4 characters, creates two different indices from it to generate a # name. The rest is hope hope that never get a collision here but it's # unlikely enough. name = device_names[int(device.id[0:2], 16) % len(device_names)] attr = device_attr[int(device.id[2:4], 16) % len(device_attr)] device.id = "-".join([attr, name]) def open_ratbagd(ratbagd_process=None, verbose=0): try: r = Ratbagd(@RATBAGD_API_VERSION@) r.verbose = verbose try: r.connect('device-added', on_device_added) for d in r.devices: on_device_added(r, d) except AttributeError: pass # the ratbag-command case except RatbagdUnavailable as e: print("Unable to connect to ratbagd: {}".format(e)) return None if ratbagd_process is not None: # if some old version of ratbagd is still running, ratbagd_process may # have never started but our DBus bindings may succeed. Check for the # return code here, this also gives ratbagd enough time to start and # die. If we check immediately we may not have terminated yet. ratbagd_process.poll() assert ratbagd_process.returncode is None return r def main(argv): if not argv: argv = ["list"] parser = get_parser() cmd = parser.parse(argv) if cmd.help: parser.print_help() return _r = open_ratbagd(verbose=cmd.verbose) if _r is not None: with _r as r: try: f = cmd.func except AttributeError: parser.print_help() return else: try: f(r, cmd) except RatbagErrorCapability as e: print("Error: {}".format(e), file=sys.stderr) if __name__ == "__main__": main(sys.argv[1:]) libratbag-0.13/tools/ratbagctl.test.in000077500000000000000000001002301362011324700177730ustar00rootroot00000000000000#!/usr/bin/env python3 # # This file is part of libratbag. # # Copyright 2017 Red Hat, Inc. # # 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 (including the next # paragraph) 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. import argparse import io import os import resource import subprocess import sys import toolbox import unittest run_ratbagctl_in_subprocess = False class TestRatbagCtl(unittest.TestCase): json = None @classmethod def setUpClass(cls): super().setUpClass() # it's a lot easier to debug a validataion failure here than # with ratbagd failing if cls.json is not None: import json json.loads(cls.json) if cls.json is not None: cls.load_test_device(cls.json) cls.find_test_device() @classmethod def run_ratbagctl_subprocess(cls, params): ratbagctl = subprocess.Popen(" ".join([toolbox.RATBAGCTL_PATH, params]), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env={"RATBAG_TEST": "1"}) (stdout, stderr) = ratbagctl.communicate() return (ratbagctl.returncode, stdout.decode('utf-8').rstrip('\n'), stderr.decode('utf-8').rstrip('\n')) @classmethod def run_ratbagctl_fast(cls, params): global parser, ratbagd stdout = sys.stdout stderr = sys.stderr returncode = 0 sys.stdout = io.StringIO() sys.stderr = io.StringIO() try: cmd = parser.parse(params.split()) cmd.func(ratbagd, cmd) except SystemExit as e: returncode = e.code except AttributeError: returncode = 2 except argparse.ArgumentTypeError: returncode = 2 output = sys.stdout.getvalue() error = sys.stderr.getvalue() sys.stdout = stdout sys.stderr = stderr toolbox.sync_dbus() return returncode, output.rstrip('\n'), error.rstrip('\n') @classmethod def run_ratbagctl(cls, params): params = params.replace('test_device', cls.test_device) if run_ratbagctl_in_subprocess: return cls.run_ratbagctl_subprocess(params) return cls.run_ratbagctl_fast(params) @classmethod def reset_test_device(cls): global ratbagd ratbagd._dbus_call("ResetTestDevice", "") toolbox.sync_dbus() @classmethod def load_test_device(cls, json): global ratbagd rc = ratbagd._dbus_call("LoadTestDevice", "s", json) assert(rc == 0) toolbox.sync_dbus() @classmethod def find_test_device(cls): rc, stdout, stderr = cls.run_ratbagctl_subprocess('list') testdevice = None for line in stdout.split('\n'): if 'Test device' in line: testdevice = line.split(':')[0] break assert testdevice is not None cls.test_device = testdevice def launch_good_test(self, params): returncode, stdout, stderr = self.run_ratbagctl(params) self.assertEqual(returncode, 0, msg=stderr + stdout) self.maxDiff = None self.assertEqual(stderr, '') return stdout def launch_fail_test(self, params): returncode, stdout, stderr = self.run_ratbagctl(params) self.assertNotEqual(returncode, 0, msg=stderr + stdout) return stdout def launch_fail_with_exception_test(self, params, exception): with self.assertRaises(exception): returncode, stdout, stderr = self.run_ratbagctl(params) return '' @classmethod def setProfile(cls, profile): cls.run_ratbagctl("test_device profile active set {}".format(profile)) class TestRatbagCtlList(TestRatbagCtl): def test_list(self): r = self.launch_good_test("list") self.assertIn(self.test_device, r) self.launch_fail_test("test_device list") self.launch_fail_test("list test_device") class TestRatbagCtlInfo(TestRatbagCtl): def test_info(self): self.launch_good_test("test_device info") self.launch_fail_test("test_device info X") self.launch_fail_test("info X") class TestRatbagCtlName(TestRatbagCtl): def test_get(self): r = self.launch_good_test("test_device name") self.assertEqual(r, 'Test device') self.launch_fail_test("test_device name X") self.launch_fail_test("name X") class TestRatbagCtlProfile(TestRatbagCtl): json = ''' { "profiles": [ { "is_active": false }, { "name": "test profile x2", "is_active": false }, { "is_active": true }, { "is_active": false, "is_disabled": true } ] } ''' @classmethod def setUpClass(cls): super().setUpClass() def test_profile_name_get(self): command = "profile 1 name get" r = self.launch_good_test("test_device " + command) self.assertEqual(r, 'test profile x2') self.launch_fail_test(command) self.launch_fail_test("test_device " + command + " X") self.launch_fail_test("test_device profile name get") def test_profile_name_set(self): r = self.launch_good_test("test_device profile 1 name get") self.assertEqual(r, 'test profile x2') self.launch_good_test("test_device profile 1 name set banana") r = self.launch_good_test("test_device profile 1 name get") self.assertEqual(r, 'banana') # profile 0 doesn't have the capability RATBAG_PROFILE_CAP_WRITABLE_NAME self.launch_fail_with_exception_test("test_device profile 0 name set kiwi", toolbox.RatbagErrorCapability) # better be safe than sorry, checking that the previous actually failed :) r = self.launch_good_test("test_device profile 0 name get") self.assertNotEqual(r, 'kiwi') self.launch_fail_test("profile 1 name set blah") self.launch_fail_test("test_device profile 1 name set blah X") self.launch_fail_test("test_device profile name set blah") def test_profile_active_get(self): command = "profile active get" r = self.launch_good_test("test_device " + command) self.assertEqual(int(r), 2) self.launch_fail_test(command) self.launch_fail_test("test_device " + command + " X") def test_profile_active_set(self): command = "profile active set" r = self.launch_good_test("test_device " + command + " 1") self.assertEqual(r, '') r = self.launch_good_test("test_device profile active get") self.assertEqual(int(r), 1) self.launch_fail_test(command) self.launch_fail_test("test_device " + command + " X") self.launch_fail_test("test_device " + command + " 1 X") def test_profile_n(self): r0 = self.launch_good_test("test_device profile 0 get") r1 = self.launch_good_test("test_device profile 1 get") self.assertNotEqual(r0, r1) self.launch_fail_test("profile 0 get") self.launch_fail_test("test_device profile 0") self.launch_fail_test("test_device profile 0 get X") self.launch_fail_test("test_device profile 10 get") def test_profile_enable(self): r = self.launch_good_test("test_device profile 0 get") self.assertNotIn('disabled', r) r = self.launch_good_test("test_device profile 3 get") self.assertIn('disabled', r) self.launch_good_test("test_device profile 3 enable") r = self.launch_good_test("test_device profile 3 get") self.assertNotIn('Profile 3: (disabled)', r) self.launch_fail_test("profile enable") self.launch_fail_test("test_device profile X enable") self.launch_fail_test("test_device profile 1 enable X") self.launch_fail_test("test_device profile 10 enable") def test_profile_disable(self): r = self.launch_good_test("test_device profile 2 get") self.assertNotIn('disabled', r) self.launch_good_test("test_device profile 2 disable") r = self.launch_good_test("test_device profile 2 get") self.assertEqual('Profile 2: (disabled)', r) self.launch_fail_test("profile disable") self.launch_fail_test("test_device profile X disable") self.launch_fail_test("test_device profile 1 disable X") self.launch_fail_test("test_device profile 10 disable") class TestRatbagCtlResolution(TestRatbagCtl): json = ''' { "profiles": [ { "is_active": false }, { "is_active": false }, { "is_active": true, "resolutions": [ { "is_active": false, "is_default": true }, { "xres": 1200, "yres": 1300, "capabilities": [1], "is_default": false, "is_active": false }, { "xres": 2300, "yres": 2400, "capabilities": [1], "is_active": true, "is_default": false } ] }, { "is_active": false, "is_disabled": true } ] } ''' @classmethod def setUpClass(cls): super().setUpClass() def test_resolution_active_get(self): command = "resolution active get" r = self.launch_good_test("test_device " + command) self.assertEqual(int(r), 2) self.launch_fail_test("test_device " + command + " X") self.launch_fail_test("test_device " + command + " 10") def test_resolution_active_set(self): command = "resolution active set" r = self.launch_good_test("test_device resolution active get") self.assertEqual(int(r), 2) r = self.launch_good_test("test_device " + command + " 0") self.assertEqual(r, '') r = self.launch_good_test("test_device resolution active get") self.assertEqual(int(r), 0) self.launch_fail_test(command) self.launch_fail_test("test_device " + command + " X") self.launch_fail_test("test_device " + command + " 1 X") def test_resolution_n_get(self): r = self.launch_good_test("test_device resolution 1 get") self.assertEqual(r, "1: 1200x1300dpi (default)") self.launch_fail_test("test_device resolution 10 get") self.launch_fail_test("test_device resolution 1 get X") self.launch_fail_test("resolution 1 get X") def test_profile_resolution(self): r = self.launch_good_test("test_device profile 2 resolution 1 get") self.assertEqual(r, "1: 1200x1300dpi") def test_resolution_default_get(self): command = "resolution default get" r = self.launch_good_test("test_device " + command) self.assertEqual(int(r), 0) self.launch_fail_test("test_device " + command + " X") self.launch_fail_test("test_device " + command + " 10") def test_resolution_default_set(self): command = "resolution default set" self.launch_good_test("test_device " + command + " 2") r = self.launch_good_test("test_device resolution default get") self.assertEqual(int(r), 2) self.launch_fail_test(command) self.launch_fail_test("test_device " + command + " X") self.launch_fail_test("test_device " + command + " 1 X") # Reset for other tests self.launch_good_test("test_device " + command + " 1") class TestRatbagCtlDPI(TestRatbagCtl): json = ''' { "profiles": [ { "is_active": true, "resolutions": [ { "xres": 200, "is_active": true, "dpi_min": 50, "dpi_max": 5000 }, { "xres": 250, "is_active": false }, { "xres": 300, "is_active": false } ] }, { "is_active": false, "resolutions": [ { "xres": 1000, "yres": 1100, "capabilities": [1], "is_active": false }, { "xres": 1300, "yres": 1400, "capabilities": [1], "is_active": true } ] }, { "is_active": false, "resolutions": [ { "xres": 2100, "yres": 2200, "capabilities": [1], "is_active": true}, { "xres": 2200, "yres": 2300, "capabilities": [1], "is_active": false } ] } ] } ''' @classmethod def setUpClass(cls): super().setUpClass() def test_dpi_get(self): command = "dpi get" r = self.launch_good_test("test_device " + command) self.assertEqual(int(r[:-3]), 200) # drop 'dpi' suffix self.setProfile(1) r = self.launch_good_test("test_device " + command) self.assertEqual(r, "1300x1400dpi") self.launch_fail_test(command) self.launch_fail_test("test_device " + command + " 1") self.launch_fail_test("test_device " + command + " X") def test_dpi_get_all(self): dpi_list = "50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 850 900 950 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000 2100 2200 2300 2400 2500 2600 2800 3000 3200 3400 3600 3800 4000 4200 4400 4600 4800 5000" command = "dpi get-all" r = self.launch_good_test("test_device " + command) self.assertEqual(r, dpi_list) self.setProfile(1) r = self.launch_good_test("test_device " + command) self.assertEqual(r, dpi_list) def test_dpi_set(self): command = "dpi set" self.setProfile(0) r = self.launch_good_test("test_device dpi get ") res = int(r[:-3]) + 50 # drop 'dpi' suffix r = self.launch_good_test("test_device " + command + " {}".format(res)) self.assertEqual(r, '') r = self.launch_good_test("test_device dpi get") self.assertEqual(int(r[:-3]), res) # drop 'dpi' suffix self.launch_fail_test(command) self.launch_fail_test("test_device " + command + " X") self.launch_fail_test("test_device " + command + " 100 X") def test_dpi_set_xy(self): command = "dpi set" self.setProfile(2) r = self.launch_good_test("test_device dpi get") self.assertEqual(r, "2100x2200dpi") self.launch_good_test("test_device " + command + " 1350x1450") r = self.launch_good_test("test_device dpi get") self.assertEqual(r, "1350x1450dpi") self.launch_good_test("test_device " + command + " 2350x2450dpi") r = self.launch_good_test("test_device dpi get") self.assertEqual(r, "2350x2450dpi") self.launch_good_test("test_device " + command + " 2350") r = self.launch_good_test("test_device dpi get") self.assertEqual(r, "2350x2350dpi") self.launch_fail_test("test_device " + command + " 2350dpi") self.launch_fail_test(command) self.launch_fail_test("test_device " + command + " X") self.launch_fail_test("test_device " + command + " 100 X") def test_prefix_dpi(self): self.setProfile(0) r = self.launch_good_test("test_device profile 1 dpi get") self.assertEqual(r, "1300x1400dpi") r = self.launch_good_test("test_device profile 2 resolution 1 dpi get") self.assertEqual(r, "2200x2300dpi") r = self.launch_good_test("test_device resolution 2 dpi get") self.assertEqual(int(r[:-3]), 300) # drop 'dpi' suffix self.launch_fail_test("test_device profile 1 profile 1 dpi get") self.launch_fail_test("test_device profile 1 resolution 1 resolution 2 dpi get") class TestRatbagCtlReportRate(TestRatbagCtl): json = ''' { "profiles": [ { "is_active": true, "rate": 1000, "report_rates": [500, 1000] }, { "is_active": false, "rate": 2000 }, { "is_active": false, "rate": 3000 } ] } ''' @classmethod def setUpClass(cls): super().setUpClass() def test_rate_get(self): command = "rate get" r = self.launch_good_test("test_device " + command) self.assertEqual(int(r), 1000) self.launch_fail_test(command) self.launch_fail_test("test_device " + command + " 1") self.launch_fail_test("test_device " + command + " X") def test_rate_get_all(self): rate_list = "500 1000" command = "rate get-all" r = self.launch_good_test("test_device " + command) self.assertEqual(r, rate_list) self.setProfile(1) r = self.launch_good_test("test_device " + command) self.assertEqual(r, rate_list) def test_rate_set(self): command = "rate set" r = self.launch_good_test("test_device rate get") res = int(r) + 500 r = self.launch_good_test("test_device " + command + " {}".format(res)) self.assertEqual(r, '') r = self.launch_good_test("test_device rate get") self.assertEqual(int(r), res) self.launch_fail_test(command) self.launch_fail_test("test_device " + command + " X") self.launch_fail_test("test_device " + command + " 100 X") def test_prefix_rate(self): r = self.launch_good_test("test_device rate get") self.assertEqual(int(r), 1000) r = self.launch_good_test("test_device profile 1 rate get") self.assertEqual(int(r), 2000) r = self.launch_good_test("test_device profile 2 rate get") self.assertEqual(int(r), 3000) self.launch_fail_test("test_device profile 1 profile 1 rate get") self.launch_fail_test("test_device profile 1 resolution 0 rate get") self.launch_fail_test("test_device profile 1 resolution 1 resolution 2 rate get") class TestRatbagCtlButton(TestRatbagCtl): json = ''' { "profiles": [ { "is_active": true, "buttons": [ { "button": 0 }, { "action_type": "none" }, { "action_type": "special", "special": "profile-cycle-up" }, { "action_type": "macro", "macro": [ "+B", "-B", "t300" ] } ] }, { "is_active": false }, { "is_active": false, "buttons": [ { "action_type": "special", "special": "profile-cycle-up" }, { "action_type": "special" }, { "button": 2, "action_type": "button" }, { "button": 3, "action_type": "button" } ] } ] } ''' @classmethod def setUpClass(cls): super().setUpClass() def test_button_count(self): command = "button count" r = self.launch_good_test("test_device " + command) self.assertEqual(int(r), 4) self.launch_fail_test(command) self.launch_fail_test("test_device " + command + " 1") self.launch_fail_test("test_device " + command + " X") def test_button_n_get(self): self.setProfile(0) r = self.launch_good_test("test_device button 0 get") self.assertEqual(r, "Button: 0 is mapped to 'button 0'") r = self.launch_good_test("test_device button 1 get") self.assertEqual(r, "Button: 1 is mapped to UNKNOWN") r = self.launch_good_test("test_device button 2 get") self.assertEqual(r, "Button: 2 is mapped to 'profile-cycle-up'") r = self.launch_good_test("test_device button 3 get") self.assertEqual(r, "Button: 3 is mapped to macro '↕B 300ms'") self.launch_fail_test("test_device button 1 get 10") self.launch_fail_test("test_device button 10 get") self.launch_fail_test("test_device button 1 get X") def test_button_n_action_get(self): self.setProfile(0) r = self.launch_good_test("test_device button 0 action get") self.assertEqual(r, "Button: 0 is mapped to 'button 0'") self.launch_fail_test("test_device button 1 action get 10") self.launch_fail_test("test_device button 10 action get") self.launch_fail_test("test_device button 1 action get X") def test_button_n_action_set_button(self): self.setProfile(2) command = "button 2 action set button" r = self.launch_good_test("test_device button 2 action get") self.assertEqual(r, "Button: 2 is mapped to 'button 2'") r = self.launch_good_test("test_device " + command + " 1") self.assertEqual(r, '') r = self.launch_good_test("test_device button 2 action get") self.assertEqual(r, "Button: 2 is mapped to 'button 1'") self.launch_fail_test(command) self.launch_fail_test("test_device " + command + " X") self.launch_fail_test("test_device " + command + " 100 X") def test_button_n_action_set_special(self): self.setProfile(2) command = "button 0 action set special " r = self.launch_good_test("test_device button 0 action get") self.assertEqual(r, "Button: 0 is mapped to 'profile-cycle-up'") r = self.launch_good_test("test_device " + command + " second-mode") self.assertEqual(r, '') r = self.launch_good_test("test_device button 0 action get") self.assertEqual(r, "Button: 0 is mapped to 'second-mode'") self.launch_fail_test(command) self.launch_fail_test("test_device " + command + " X") self.launch_fail_test("test_device " + command + " 100 X") def test_button_n_action_set_macro(self): command = "button 2 action set macro " self.launch_good_test("test_device " + command + '+KEY_B KEY_A -KEY_B KEY_C') r = self.launch_good_test("test_device button 2 get") self.assertEqual(r, "Button: 2 is mapped to macro '↓B ↕A ↑B ↕C'") self.launch_good_test("test_device " + command + 't100 t200') r = self.launch_good_test("test_device button 2 get") self.assertEqual(r, "Button: 2 is mapped to macro '100ms 200ms'") self.launch_good_test("test_device " + command + 'KeY_z') r = self.launch_good_test("test_device button 2 get") self.assertEqual(r, "Button: 2 is mapped to macro '↕Z'") self.launch_good_test("test_device " + command) r = self.launch_good_test("test_device button 2 get") self.assertEqual(r, "Button: 2 is mapped to macro 'None'") self.launch_fail_test(command) self.launch_fail_test("test_device " + command + " KEY_A X") def test_prefix_button(self): r = self.launch_good_test("test_device profile 2 button 3 get") self.assertEqual(r, "Button: 3 is mapped to 'button 3'") self.launch_fail_test("test_device profile 2 profile 2 button 3 get") self.launch_fail_test("test_device profile 2 resolution 2 button 3 get") class TestRatbagCtlLED(TestRatbagCtl): json = ''' { "profiles": [ { "is_active": true, "leds": [ { "type": 1, "mode": 0, "duration": 1000, "brightness": 20 }, { "type": 1, "mode": 1, "duration": 333, "brightness": 40, "color": [255, 0, 0] }, { "type": 1, "mode": 2, "duration": 333, "brightness": 40 } ] }, { "leds": [ { "type": 1, "mode": 1 }, { "type": 1, "mode": 0 }, { "type": 1, "mode": 1 } ] }, { "leds": [ { "type": 1, "mode": 1, "color": [255, 0, 0], "duration": 1000 }, { "type": 1, "mode": 2, "brightness": 40, "duration": 333 }, { "type": 1, "mode": 1 } ] } ] } ''' @classmethod def setUpClass(cls): super().setUpClass() def test_led_n_get(self): self.setProfile(0) r = self.launch_good_test("test_device led 0 get") self.assertEqual(r, "LED: 0, depth: rgb, mode: off") r = self.launch_good_test("test_device led 1 get") self.assertEqual(r, "LED: 1, depth: rgb, mode: on, color: ff0000") r = self.launch_good_test("test_device led 2 get") self.assertEqual(r, "LED: 2, depth: rgb, mode: cycle, duration: 333, brightness: 40") self.launch_fail_test("test_device led 0 get 10") self.launch_fail_test("test_device led 10 get") self.launch_fail_test("test_device led 0 get X") def test_led_n_set_mode(self): self.setProfile(0) r = self.launch_good_test("test_device led 0 get") self.assertEqual(r, "LED: 0, depth: rgb, mode: off") r = self.launch_good_test("test_device led 0 set mode cycle") self.assertEqual(r, '') r = self.launch_good_test("test_device led 0 get") self.assertEqual(r, "LED: 0, depth: rgb, mode: cycle, duration: 1000, brightness: 20") self.launch_fail_test("test_device led 0 set mode bicycle") self.launch_fail_test("test_device led 0 set mode") self.launch_fail_test("test_device led 0 set mode cycle X") def test_led_n_set_color(self): self.setProfile(0) r = self.launch_good_test("test_device led 1 get") self.assertEqual(r, "LED: 1, depth: rgb, mode: on, color: ff0000") r = self.launch_good_test("test_device led 1 set color 00ff00") self.assertEqual(r, '') r = self.launch_good_test("test_device led 1 get") self.assertEqual(r, "LED: 1, depth: rgb, mode: on, color: 00ff00") r = self.launch_good_test("test_device led 1 set color 0x0000ff") self.assertEqual(r, '') r = self.launch_good_test("test_device led 1 get") self.assertEqual(r, "LED: 1, depth: rgb, mode: on, color: 0000ff") self.launch_fail_test("test_device led 1 set color g0ff00") self.launch_fail_test("test_device led 1 set color") self.launch_fail_test("test_device led 1 set color 00ff00 X") def test_led_n_set_duration(self): self.setProfile(0) r = self.launch_good_test("test_device led 2 get") self.assertEqual(r, "LED: 2, depth: rgb, mode: cycle, duration: 333, brightness: 40") r = self.launch_good_test("test_device led 2 set duration 10") self.assertEqual(r, '') r = self.launch_good_test("test_device led 2 get") self.assertEqual(r, "LED: 2, depth: rgb, mode: cycle, duration: 10, brightness: 40") self.launch_fail_test("test_device led 2 set duration this_is_not_an_int") self.launch_fail_test("test_device led 2 set duration") self.launch_fail_test("test_device led 2 set duration 100 X") def test_led_n_set_brightness(self): self.setProfile(2) r = self.launch_good_test("test_device led 1 get") self.assertEqual(r, "LED: 1, depth: rgb, mode: cycle, duration: 333, brightness: 40") r = self.launch_good_test("test_device led 1 set brightness 100") self.assertEqual(r, '') r = self.launch_good_test("test_device led 1 get") self.assertEqual(r, "LED: 1, depth: rgb, mode: cycle, duration: 333, brightness: 100") self.launch_fail_test("test_device led 1 set brightness this_is_not_an_int") self.launch_fail_test("test_device led 1 set brightness") self.launch_fail_test("test_device led 1 set brightness 100 X") self.launch_fail_test("test_device led 1 set brightness this_is_not_an_int") self.launch_fail_test("test_device led 1 set brightness 256") self.launch_fail_test("test_device led 1 set brightness -10") def test_led_n_set_combination(self): self.setProfile(2) r = self.launch_good_test("test_device led 0 get") self.assertEqual(r, "LED: 0, depth: rgb, mode: on, color: ff0000") r = self.launch_good_test("test_device led 0 set mode cycle brightness 100") r = self.launch_good_test("test_device led 0 get") self.assertEqual(r, "LED: 0, depth: rgb, mode: cycle, duration: 1000, brightness: 100") r = self.launch_good_test("test_device led 0 set mode cycle duration 100 brightness 255") r = self.launch_good_test("test_device led 0 get") self.assertEqual(r, "LED: 0, depth: rgb, mode: cycle, duration: 100, brightness: 255") r = self.launch_good_test("test_device led 0 set mode on color 00ff00") r = self.launch_good_test("test_device led 0 get") self.assertEqual(r, "LED: 0, depth: rgb, mode: on, color: 00ff00") r = self.launch_good_test("test_device led 0 set duration 100 brightness 200 mode cycle") r = self.launch_good_test("test_device led 0 get") self.assertEqual(r, "LED: 0, depth: rgb, mode: cycle, duration: 100, brightness: 200") r = self.launch_good_test("test_device led 0 set duration 100 brightness 200 mode cycle brightness 100") r = self.launch_good_test("test_device led 0 get") self.assertEqual(r, "LED: 0, depth: rgb, mode: cycle, duration: 100, brightness: 100") self.launch_fail_test("test_device led 0 set brightness 10 mode bicycle") def test_prefix_led(self): self.setProfile(2) r = self.launch_good_test("test_device profile 1 led 1 get") self.assertEqual(r, "LED: 1, depth: rgb, mode: off") self.launch_fail_test("test_device profile 1 profile 2 led 1 get") self.launch_fail_test("test_device profile 1 resolution 2 led 1 get") def test_led_capabilities(self): r = self.launch_good_test("test_device led 0 capabilities") self.assertEqual(r, "Modes: breathing, cycle, off, on") r = self.launch_good_test("test_device led 1 capabilities") self.assertEqual(r, "Modes: breathing, cycle, off, on") def setUpModule(): global ratbagd_process, ratbagd, parser ratbagd_process = None if start_ratbagd: ratbagd_process = toolbox.start_ratbagd() assert ratbagd_process is not None try: ratbagd = toolbox.open_ratbagd(ratbagd_process) assert ratbagd is not None except Exception as e: if ratbagd_process is not None: toolbox.terminate_ratbagd(ratbagd_process) raise e parser = toolbox.get_parser() def tearDownModule(): global ratbagd_process if start_ratbagd: toolbox.terminate_ratbagd(ratbagd_process) def parse(input_string): global run_ratbagctl_in_subprocess, start_ratbagd parser_test = argparse.ArgumentParser(description="Testsuite for ratbagd/ratbagctl", add_help=False) parser_test.add_argument("--subprocess", action='store_true', default=False) parser_test.add_argument("--use-existing-ratbagd", dest='use_existing', action='store_true', default=False, help='Don\'t start up ratbagd.devel, connect to the already running one') ns, rest = parser_test.parse_known_args(input_string) run_ratbagctl_in_subprocess = ns.subprocess start_ratbagd = not ns.use_existing if start_ratbagd and not os.geteuid() == 0: print('Script must be run as root', file=sys.stderr) sys.exit(77) return rest def main(argv): os.environ['RATBAG_TEST'] = '1' os.environ['LIBRATBAG_DATA_DIR'] = '@LIBRATBAG_DATA_DIR@' resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) args = parse(argv) unittest.main(argv=[sys.argv[0], *args]) if __name__ == "__main__": main(sys.argv[1:]) libratbag-0.13/tools/ratbagd.py000066400000000000000000001027031362011324700165040ustar00rootroot00000000000000# Copyright 2016-2019 Red Hat, Inc. # # 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 (including the next # paragraph) 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. import os import sys import hashlib from enum import IntEnum from evdev import ecodes from gettext import gettext as _ from gi.repository import Gio, GLib, GObject # Deferred translations, see https://docs.python.org/3/library/gettext.html#deferred-translations def N_(x): return x class RatbagErrorCode(IntEnum): SUCCESS = 0 """An error occured on the device. Either the device is not a libratbag device or communication with the device failed.""" DEVICE = -1000 """Insufficient capabilities. This error occurs when a requested change is beyond the device's capabilities.""" CAPABILITY = -1001 """Invalid value or value range. The provided value or value range is outside of the legal or supported range.""" VALUE = -1002 """A low-level system error has occured, e.g. a failure to access files that should be there. This error is usually unrecoverable and libratbag will print a log message with details about the error.""" SYSTEM = -1003 """Implementation bug, either in libratbag or in the caller. This error is usually unrecoverable and libratbag will print a log message with details about the error.""" IMPLEMENTATION = -1004 class RatbagdIncompatible(Exception): """ratbagd is incompatible with this client""" def __init__(self, ratbagd_version, required_version): super().__init__() self.ratbagd_version = ratbagd_version self.required_version = required_version self.message = "ratbagd API version is {} but we require {}".format(ratbagd_version, required_version) def __str__(self): return self.message class RatbagdUnavailable(Exception): """Signals DBus is unavailable or the ratbagd daemon is not available.""" pass class RatbagdDBusTimeout(Exception): """Signals that a timeout occurred during a DBus method call.""" pass class RatbagError(Exception): """A common base exception to catch any ratbag exception.""" pass class RatbagErrorDevice(RatbagError): """An exception corresponding to RatbagErrorCode.DEVICE.""" pass class RatbagErrorCapability(RatbagError): """An exception corresponding to RatbagErrorCode.CAPABILITY.""" pass class RatbagErrorValue(RatbagError): """An exception corresponding to RatbagErrorCode.VALUE.""" pass class RatbagErrorSystem(RatbagError): """An exception corresponding to RatbagErrorCode.SYSTEM.""" pass class RatbagErrorImplementation(RatbagError): """An exception corresponding to RatbagErrorCode.IMPLEMENTATION.""" pass """A table mapping RatbagErrorCode values to RatbagError* exceptions.""" EXCEPTION_TABLE = { RatbagErrorCode.DEVICE: RatbagErrorDevice, RatbagErrorCode.CAPABILITY: RatbagErrorCapability, RatbagErrorCode.VALUE: RatbagErrorValue, RatbagErrorCode.SYSTEM: RatbagErrorSystem, RatbagErrorCode.IMPLEMENTATION: RatbagErrorImplementation } class _RatbagdDBus(GObject.GObject): _dbus = None def __init__(self, interface, object_path): super().__init__() if _RatbagdDBus._dbus is None: try: _RatbagdDBus._dbus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) except GLib.Error as e: raise RatbagdUnavailable(e.message) ratbag1 = "org.freedesktop.ratbag1" if os.environ.get('RATBAG_TEST'): ratbag1 = "org.freedesktop.ratbag_devel1" if object_path is None: object_path = "/" + ratbag1.replace('.', '/') self._object_path = object_path self._interface = "{}.{}".format(ratbag1, interface) try: self._proxy = Gio.DBusProxy.new_sync(_RatbagdDBus._dbus, Gio.DBusProxyFlags.NONE, None, ratbag1, object_path, self._interface, None) except GLib.Error as e: raise RatbagdUnavailable(e.message) if self._proxy.get_name_owner() is None: raise RatbagdUnavailable("No one currently owns {}".format(ratbag1)) self._proxy.connect("g-properties-changed", self._on_properties_changed) self._proxy.connect("g-signal", self._on_signal_received) def _on_properties_changed(self, proxy, changed_props, invalidated_props): # Implement this in derived classes to respond to property changes. pass def _on_signal_received(self, proxy, sender_name, signal_name, parameters): # Implement this in derived classes to respond to signals. pass def _find_object_with_path(self, iterable, object_path): # Find the index of an object in an iterable that whose object path # matches the given object path. for index, obj in enumerate(iterable): if obj._object_path == object_path: return index return -1 def _get_dbus_property(self, property): # Retrieves a cached property from the bus, or None. p = self._proxy.get_cached_property(property) if p is not None: return p.unpack() return p def _set_dbus_property(self, property, type, value, readwrite=True): # Sets a cached property on the bus. # Take our real value and wrap it into a variant. To call # org.freedesktop.DBus.Properties.Set we need to wrap that again # into a (ssv), where v is our value's variant. # args to .Set are "interface name", "function name", value-variant val = GLib.Variant("{}".format(type), value) if readwrite: pval = GLib.Variant("(ssv)".format(type), (self._interface, property, val)) self._proxy.call_sync("org.freedesktop.DBus.Properties.Set", pval, Gio.DBusCallFlags.NO_AUTO_START, 2000, None) # This is our local copy, so we don't have to wait for the async # update self._proxy.set_cached_property(property, val) def _dbus_call(self, method, type, *value): # Calls a method synchronously on the bus, using the given method name, # type signature and values. # # If the result is valid, it is returned. Invalid results raise the # appropriate RatbagError* or RatbagdDBus* exception, or GLib.Error if # it is an unexpected exception that probably shouldn't be passed up to # the UI. val = GLib.Variant("({})".format(type), value) try: res = self._proxy.call_sync(method, val, Gio.DBusCallFlags.NO_AUTO_START, 2000, None) if res in EXCEPTION_TABLE: raise EXCEPTION_TABLE[res] return res.unpack()[0] # Result is always a tuple except GLib.Error as e: if e.code == Gio.IOErrorEnum.TIMED_OUT: raise RatbagdDBusTimeout(e.message) else: # Unrecognized error code; print the message to stderr and raise # the GLib.Error. print(e.message, file=sys.stderr) raise def __eq__(self, other): return other and self._object_path == other._object_path class Ratbagd(_RatbagdDBus): """The ratbagd top-level object. Provides a list of devices available through ratbagd; actual interaction with the devices is via the RatbagdDevice, RatbagdProfile, RatbagdResolution and RatbagdButton objects. Throws RatbagdUnavailable when the DBus service is not available. """ __gsignals__ = { "device-added": (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), "device-removed": (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), "daemon-disappeared": (GObject.SignalFlags.RUN_FIRST, None, ()), } def __init__(self, api_version): super().__init__("Manager", None) result = self._get_dbus_property("Devices") if result is None and not self._proxy.get_cached_property_names(): raise RatbagdUnavailable("Make sure it is running and your user is in the required groups.") if self.api_version != api_version: raise RatbagdIncompatible(self.api_version or -1, api_version) self._devices = [RatbagdDevice(objpath) for objpath in result or []] self._proxy.connect("notify::g-name-owner", self._on_name_owner_changed) def _on_name_owner_changed(self, *kwargs): self.emit("daemon-disappeared") def _on_properties_changed(self, proxy, changed_props, invalidated_props): if "Devices" in changed_props.keys(): object_paths = [d._object_path for d in self._devices] for object_path in changed_props["Devices"]: if object_path not in object_paths: device = RatbagdDevice(object_path) self._devices.append(device) self.emit("device-added", device) for device in self.devices: if device._object_path not in changed_props["Devices"]: self._devices.remove(device) self.emit("device-removed", device) self.notify("devices") @GObject.Property def api_version(self): return self._get_dbus_property("APIVersion") @GObject.Property def devices(self): """A list of RatbagdDevice objects supported by ratbagd.""" return self._devices def __getitem__(self, id): """Returns the requested device, or None.""" for d in self.devices: if d.id == id: return d return None def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): pass class RatbagdDevice(_RatbagdDBus): """Represents a ratbagd device.""" __gsignals__ = { "active-profile-changed": (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)), "resync": (GObject.SignalFlags.RUN_FIRST, None, ()), } def __init__(self, object_path): super().__init__("Device", object_path) # FIXME: if we start adding and removing objects from this list, # things will break! result = self._get_dbus_property("Profiles") or [] self._profiles = [RatbagdProfile(objpath) for objpath in result] for profile in self._profiles: profile.connect("notify::is-active", self._on_active_profile_changed) # Use a SHA1 of our object path as our device's ID self._id = hashlib.sha1(object_path.encode('utf-8')).hexdigest() def _on_signal_received(self, proxy, sender_name, signal_name, parameters): if signal_name == "Resync": self.emit("resync") def _on_active_profile_changed(self, profile, pspec): if profile.is_active: self.emit("active-profile-changed", self._profiles[profile.index]) @GObject.Property def id(self): return self._id @id.setter def id(self, id): self._id = id @GObject.Property def model(self): """The unique identifier for this device model.""" return self._get_dbus_property("Model") @GObject.Property def name(self): """The device name, usually provided by the kernel.""" return self._get_dbus_property("Name") @GObject.Property def profiles(self): """A list of RatbagdProfile objects provided by this device.""" return self._profiles @GObject.Property def active_profile(self): """The currently active profile. This is a non-DBus property computed over the cached list of profiles. In the unlikely case that your device driver is misconfigured and there is no active profile, this returns the first profile.""" for profile in self._profiles: if profile.is_active: return profile print("No active profile. Please report this bug to the libratbag developers", file=sys.stderr) return self._profiles[0] def commit(self): """Commits all changes made to the device. This is implemented asynchronously inside ratbagd. Hence, we just call this method and always succeed. Any failure is handled inside ratbagd by emitting the Resync signal, which automatically resynchronizes the device. No further interaction is required by the client. """ self._dbus_call("Commit", "") for profile in self._profiles: if profile.dirty: profile._dirty = False profile.notify("dirty") class RatbagdProfile(_RatbagdDBus): """Represents a ratbagd profile.""" CAP_WRITABLE_NAME = 100 CAP_SET_DEFAULT = 101 CAP_DISABLE = 102 CAP_WRITE_ONLY = 103 def __init__(self, object_path): super().__init__("Profile", object_path) self._dirty = False self._active = self._get_dbus_property("IsActive") # FIXME: if we start adding and removing objects from any of these # lists, things will break! result = self._get_dbus_property("Resolutions") or [] self._resolutions = [RatbagdResolution(objpath) for objpath in result] self._subscribe_dirty(self._resolutions) result = self._get_dbus_property("Buttons") or [] self._buttons = [RatbagdButton(objpath) for objpath in result] self._subscribe_dirty(self._buttons) result = self._get_dbus_property("Leds") or [] self._leds = [RatbagdLed(objpath) for objpath in result] self._subscribe_dirty(self._leds) def _subscribe_dirty(self, objects): for obj in objects: obj.connect("notify", self._on_obj_notify) def _on_obj_notify(self, obj, pspec): if not self._dirty: self._dirty = True self.notify("dirty") def _on_properties_changed(self, proxy, changed_props, invalidated_props): if "IsActive" in changed_props.keys(): active = changed_props["IsActive"] if active != self._active: self._active = active self.notify("is-active") self._on_obj_notify(None, None) @GObject.Property def capabilities(self): """The capabilities of this profile as an array. Capabilities not present on the profile are not in the list. Thus use e.g. if RatbagdProfile.CAP_WRITABLE_NAME in profile.capabilities: do something """ return self._get_dbus_property("Capabilities") or [] @GObject.Property def name(self): """The name of the profile""" return self._get_dbus_property("Name") @name.setter def name(self, name): """Set the name of this profile. @param name The new name, as str""" self._set_dbus_property("Name", "s", name) @GObject.Property def index(self): """The index of this profile.""" return self._get_dbus_property("Index") @GObject.Property def dirty(self): """Whether this profile is dirty.""" return self._dirty @GObject.Property def enabled(self): """tells if the profile is enabled.""" return self._get_dbus_property("Enabled") @enabled.setter def enabled(self, enabled): """Enable/Disable this profile. @param enabled The new state, as boolean""" self._set_dbus_property("Enabled", "b", enabled) @GObject.Property def report_rate(self): """The report rate in Hz.""" return self._get_dbus_property("ReportRate") @report_rate.setter def report_rate(self, rate): """Set the report rate in Hz. @param rate The new report rate, as int """ self._set_dbus_property("ReportRate", "u", rate) @GObject.Property def report_rates(self): """The list of supported report rates""" return self._get_dbus_property("ReportRates") or [] @GObject.Property def resolutions(self): """A list of RatbagdResolution objects with this profile's resolutions. Note that the list of resolutions differs between profiles but the number of resolutions is identical across profiles.""" return self._resolutions @GObject.Property def active_resolution(self): """The currently active resolution of this profile. This is a non-DBus property computed over the cached list of resolutions. In the unlikely case that your device driver is misconfigured and there is no active resolution, this returns the first resolution.""" for resolution in self._resolutions: if resolution.is_active: return resolution print("No active resolution. Please report this bug to the libratbag developers", file=sys.stderr) return self._resolutions[0] @GObject.Property def buttons(self): """A list of RatbagdButton objects with this profile's button mappings. Note that the list of buttons differs between profiles but the number of buttons is identical across profiles.""" return self._buttons @GObject.Property def leds(self): """A list of RatbagdLed objects with this profile's leds. Note that the list of leds differs between profiles but the number of leds is identical across profiles.""" return self._leds @GObject.Property def is_active(self): """Returns True if the profile is currently active, false otherwise.""" return self._active def set_active(self): """Set this profile to be the active profile.""" ret = self._dbus_call("SetActive", "") self._set_dbus_property("IsActive", "b", True, readwrite=False) return ret class RatbagdResolution(_RatbagdDBus): """Represents a ratbagd resolution.""" def __init__(self, object_path): super().__init__("Resolution", object_path) self._active = self._get_dbus_property("IsActive") self._default = self._get_dbus_property("IsDefault") def _on_properties_changed(self, proxy, changed_props, invalidated_props): if "IsActive" in changed_props.keys(): active = changed_props["IsActive"] if active != self._active: self._active = active self.notify("is-active") elif "IsDefault" in changed_props.keys(): default = changed_props["IsDefault"] if default != self._default: self._default = default self.notify("is-default") @GObject.Property def index(self): """The index of this resolution.""" return self._get_dbus_property("Index") @GObject.Property def resolution(self): """The resolution in DPI, either as single value tuple ``(res, )`` or as tuple ``(xres, yres)``. """ res = self._get_dbus_property("Resolution") if isinstance(res, int): res = tuple([res]) return res @resolution.setter def resolution(self, resolution): """Set the x- and y-resolution using the given (xres, yres) tuple. @param res The new resolution, as (int, int) """ res = self.resolution if len(res) != len(resolution) or len(res) > 2: raise ValueError('invalid resolution precision') if len(res) == 1: variant = GLib.Variant('u', resolution[0]) else: variant = GLib.Variant('(uu)', resolution) self._set_dbus_property("Resolution", "v", variant) @GObject.Property def resolutions(self): """The list of supported DPI values""" return self._get_dbus_property("Resolutions") or [] @GObject.Property def is_active(self): """True if this is the currently active resolution, False otherwise""" return self._active @GObject.Property def is_default(self): """True if this is the currently default resolution, False otherwise""" return self._default def set_default(self): """Set this resolution to be the default.""" ret = self._dbus_call("SetDefault", "") self._set_dbus_property("IsDefault", "b", True, readwrite=False) return ret def set_active(self): """Set this resolution to be the active one.""" ret = self._dbus_call("SetActive", "") self._set_dbus_property("IsActive", "b", True, readwrite=False) return ret class RatbagdButton(_RatbagdDBus): """Represents a ratbagd button.""" class ActionType(IntEnum): NONE = 0 BUTTON = 1 SPECIAL = 2 MACRO = 4 class ActionSpecial(IntEnum): INVALID = -1 UNKNOWN = (1 << 30) DOUBLECLICK = (1 << 30) + 1 WHEEL_LEFT = (1 << 30) + 2 WHEEL_RIGHT = (1 << 30) + 3 WHEEL_UP = (1 << 30) + 4 WHEEL_DOWN = (1 << 30) + 5 RATCHET_MODE_SWITCH = (1 << 30) + 6 RESOLUTION_CYCLE_UP = (1 << 30) + 7 RESOLUTION_CYCLE_DOWN = (1 << 30) + 8 RESOLUTION_UP = (1 << 30) + 9 RESOLUTION_DOWN = (1 << 30) + 10 RESOLUTION_ALTERNATE = (1 << 30) + 11 RESOLUTION_DEFAULT = (1 << 30) + 12 PROFILE_CYCLE_UP = (1 << 30) + 13 PROFILE_CYCLE_DOWN = (1 << 30) + 14 PROFILE_UP = (1 << 30) + 15 PROFILE_DOWN = (1 << 30) + 16 SECOND_MODE = (1 << 30) + 17 BATTERY_LEVEL = (1 << 30) + 18 class Macro(IntEnum): NONE = 0 KEY_PRESS = 1 KEY_RELEASE = 2 WAIT = 3 """A table mapping a button's index to its usual function as defined by X and the common desktop environments.""" BUTTON_DESCRIPTION = { 0: N_("Left mouse button click"), 1: N_("Right mouse button click"), 2: N_("Middle mouse button click"), 3: N_("Backward"), 4: N_("Forward"), } """A table mapping a special function to its human-readable description.""" SPECIAL_DESCRIPTION = { ActionSpecial.INVALID: N_("Invalid"), ActionSpecial.UNKNOWN: N_("Unknown"), ActionSpecial.DOUBLECLICK: N_("Doubleclick"), ActionSpecial.WHEEL_LEFT: N_("Wheel Left"), ActionSpecial.WHEEL_RIGHT: N_("Wheel Right"), ActionSpecial.WHEEL_UP: N_("Wheel Up"), ActionSpecial.WHEEL_DOWN: N_("Wheel Down"), ActionSpecial.RATCHET_MODE_SWITCH: N_("Ratchet Mode"), ActionSpecial.RESOLUTION_CYCLE_UP: N_("Cycle Resolution Up"), ActionSpecial.RESOLUTION_CYCLE_DOWN: N_("Cycle Resolution Down"), ActionSpecial.RESOLUTION_UP: N_("Resolution Up"), ActionSpecial.RESOLUTION_DOWN: N_("Resolution Down"), ActionSpecial.RESOLUTION_ALTERNATE: N_("Resolution Switch"), ActionSpecial.RESOLUTION_DEFAULT: N_("Default Resolution"), ActionSpecial.PROFILE_CYCLE_UP: N_("Cycle Profile Up"), ActionSpecial.PROFILE_CYCLE_DOWN: N_("Cycle Profile Down"), ActionSpecial.PROFILE_UP: N_("Profile Up"), ActionSpecial.PROFILE_DOWN: N_("Profile Down"), ActionSpecial.SECOND_MODE: N_("Second Mode"), ActionSpecial.BATTERY_LEVEL: N_("Battery Level"), } def __init__(self, object_path): super().__init__("Button", object_path) def _on_properties_changed(self, proxy, changed_props, invalidated_props): if "Mapping" in changed_props.keys(): self.notify("action-type") def _mapping(self): return self._get_dbus_property("Mapping") @GObject.Property def index(self): """The index of this button.""" return self._get_dbus_property("Index") @GObject.Property def mapping(self): """An integer of the current button mapping, if mapping to a button or None otherwise.""" type, button = self._mapping() if type != RatbagdButton.ActionType.BUTTON: return None return button @mapping.setter def mapping(self, button): """Set the button mapping to the given button. @param button The button to map to, as int """ button = GLib.Variant("u", button) self._set_dbus_property("Mapping", "(uv)", (RatbagdButton.ActionType.BUTTON, button)) @GObject.Property def macro(self): """A RatbagdMacro object representing the currently set macro or None otherwise.""" type, macro = self._mapping() if type != RatbagdButton.ActionType.MACRO: return None return RatbagdMacro.from_ratbag(macro) @macro.setter def macro(self, macro): """Set the macro to the macro represented by the given RatbagdMacro object. @param macro A RatbagdMacro object representing the macro to apply to the button, as RatbagdMacro. """ macro = GLib.Variant("a(uu)", macro.keys) self._set_dbus_property("Mapping", "(uv)", (RatbagdButton.ActionType.MACRO, macro)) @GObject.Property def special(self): """An enum describing the current special mapping, if mapped to special or None otherwise.""" type, special = self._mapping() if type != RatbagdButton.ActionType.SPECIAL: return None return special @special.setter def special(self, special): """Set the button mapping to the given special entry. @param special The special entry, as one of RatbagdButton.ActionSpecial """ special = GLib.Variant("u", special) self._set_dbus_property("Mapping", "(uv)", (RatbagdButton.ActionType.SPECIAL, special)) @GObject.Property def action_type(self): """An enum describing the action type of the button. One of ActionType.NONE, ActionType.BUTTON, ActionType.SPECIAL, ActionType.MACRO. This decides which *Mapping property has a value. """ type, mapping = self._mapping() return type @GObject.Property def action_types(self): """An array of possible values for ActionType.""" return self._get_dbus_property("ActionTypes") def disable(self): """Disables this button.""" return self._dbus_call("Disable", "") class RatbagdMacro(GObject.Object): """Represents a button macro. Note that it uses keycodes as defined by linux/input.h and not those used by X.Org or any other higher layer such as Gdk.""" # All keys from ecodes.KEY have a KEY_ prefix. We strip it. _PREFIX_LEN = len("KEY_") # Both a key press and release. _MACRO_KEY = 1000 _MACRO_DESCRIPTION = { RatbagdButton.Macro.KEY_PRESS: lambda key: "↓{}".format(ecodes.KEY[key][RatbagdMacro._PREFIX_LEN:]), RatbagdButton.Macro.KEY_RELEASE: lambda key: "↑{}".format(ecodes.KEY[key][RatbagdMacro._PREFIX_LEN:]), RatbagdButton.Macro.WAIT: lambda val: "{}ms".format(val), _MACRO_KEY: lambda key: "↕{}".format(ecodes.KEY[key][RatbagdMacro._PREFIX_LEN:]), } __gsignals__ = { 'macro-set': (GObject.SignalFlags.RUN_FIRST, None, ()), } def __init__(self, **kwargs): super().__init__(**kwargs) self._macro = [] def __str__(self): if not self._macro: # Translators: this is used when there is no macro to preview. return _("None") keys = [] idx = 0 while idx < len(self._macro): t, v = self._macro[idx] try: if t == RatbagdButton.Macro.KEY_PRESS: # Check for a paired press/release event t2, v2 = self._macro[idx + 1] if t2 == RatbagdButton.Macro.KEY_RELEASE and v == v2: t = self._MACRO_KEY idx += 1 except IndexError: pass keys.append(self._MACRO_DESCRIPTION[t](v)) idx += 1 return " ".join(keys) @GObject.Property def keys(self): """A list of (RatbagdButton.Macro.*, value) tuples representing the current macro.""" return self._macro @staticmethod def from_ratbag(macro): """Instantiates a new RatbagdMacro instance from the given macro in libratbag format. @param macro The macro in libratbag format, as [(RatbagdButton.Macro.*, value)]. """ ratbagd_macro = RatbagdMacro() # Do not emit notify::keys for every key that we add. with ratbagd_macro.freeze_notify(): for (type, value) in macro: ratbagd_macro.append(type, value) return ratbagd_macro def accept(self): """Applies the currently cached macro.""" self.emit("macro-set") def append(self, type, value): """Appends the given event to the current macro. @param type The type of event, as one of RatbagdButton.Macro.*. @param value If the type denotes a key event, the X.Org or Gdk keycode of the event, as int. Otherwise, the value of the timeout in milliseconds, as int. """ # Only append if the entry isn't identical to the last one, as we cannot # e.g. have two identical key presses in a row. if len(self._macro) == 0 or (type, value) != self._macro[-1]: self._macro.append((type, value)) self.notify("keys") class RatbagdLed(_RatbagdDBus): """Represents a ratbagd led.""" TYPE_LOGO = 1 TYPE_SIDE = 2 TYPE_BATTERY = 3 TYPE_DPI = 4 TYPE_WHEEL = 5 class Mode(IntEnum): OFF = 0 ON = 1 CYCLE = 2 BREATHING = 3 class ColorDepth(IntEnum): MONOCHROME = 0 RGB_888 = 1 RGB_111 = 2 LED_DESCRIPTION = { # Translators: the LED is off. Mode.OFF: N_("Off"), # Translators: the LED has a single, solid color. Mode.ON: N_("Solid"), # Translators: the LED is cycling between red, green and blue. Mode.CYCLE: N_("Cycle"), # Translators: the LED's is pulsating a single color on different # brightnesses. Mode.BREATHING: N_("Breathing"), } def __init__(self, object_path): super().__init__("Led", object_path) @GObject.Property def index(self): """The index of this led.""" return self._get_dbus_property("Index") @GObject.Property def mode(self): """This led's mode, one of Mode.OFF, Mode.ON, Mode.CYCLE and Mode.BREATHING.""" return self._get_dbus_property("Mode") @mode.setter def mode(self, mode): """Set the led's mode to the given mode. @param mode The new mode, as one of Mode.OFF, Mode.ON, Mode.CYCLE and Mode.BREATHING. """ self._set_dbus_property("Mode", "u", mode) @GObject.Property def modes(self): """The supported modes as a list""" return self._get_dbus_property("Modes") @GObject.Property def color(self): """An integer triple of the current LED color.""" return self._get_dbus_property("Color") @color.setter def color(self, color): """Set the led color to the given color. @param color An RGB color, as an integer triplet with values 0-255. """ self._set_dbus_property("Color", "(uuu)", color) @GObject.Property def colordepth(self): """An enum describing this led's colordepth, one of RatbagdLed.ColorDepth.MONOCHROME, RatbagdLed.ColorDepth.RGB""" return self._get_dbus_property("ColorDepth") @GObject.Property def effect_duration(self): """The LED's effect duration in ms, values range from 0 to 10000.""" return self._get_dbus_property("EffectDuration") @effect_duration.setter def effect_duration(self, effect_duration): """Set the effect duration in ms. Allowed values range from 0 to 10000. @param effect_duration The new effect duration, as int """ self._set_dbus_property("EffectDuration", "u", effect_duration) @GObject.Property def brightness(self): """The LED's brightness, values range from 0 to 255.""" return self._get_dbus_property("Brightness") @brightness.setter def brightness(self, brightness): """Set the brightness. Allowed values range from 0 to 255. @param brightness The new brightness, as int """ self._set_dbus_property("Brightness", "u", brightness) libratbag-0.13/tools/shared.c000066400000000000000000000240061362011324700161370ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #include "shared.h" struct udev_device* udev_device_from_path(struct udev *udev, const char *path) { struct udev_device *udev_device; const char *hidraw_prefix = "/dev/"; _cleanup_(freep) char *path_canonical = NULL; if ((path_canonical = realpath(path, NULL)) == NULL) { error("Failed to canonicalize path '%s': %s\n", path, strerror(errno)); return NULL; } if (strneq(path_canonical, hidraw_prefix, strlen(hidraw_prefix))) { struct stat st; if (stat(path_canonical, &st) == -1) { error("Failed to stat '%s': %s\n", path, strerror(errno)); return NULL; } udev_device = udev_device_new_from_devnum(udev, 'c', st.st_rdev); } else { udev_device = udev_device_new_from_syspath(udev, path_canonical); } if (!udev_device) { error("Can't open '%s': %s\n", path, strerror(errno)); return NULL; } return udev_device; } const char* led_type_to_str(enum ratbag_led_type type) { const char *str = NULL; switch(type) { case RATBAG_LED_TYPE_LOGO: str = "logo"; break; case RATBAG_LED_TYPE_SIDE: str = "side"; break; case RATBAG_LED_TYPE_BATTERY: str = "battery"; break; case RATBAG_LED_TYPE_DPI: str = "dpi"; break; case RATBAG_LED_TYPE_WHEEL: str = "wheel"; break; case RATBAG_LED_TYPE_SWITCHES: str = "switches"; break; default: assert(!"Invalid LED type"); break; } return str; } const char * led_mode_to_str(enum ratbag_led_mode mode) { const char *str = "UNKNOWN"; switch (mode) { case RATBAG_LED_OFF: str = "off"; break; case RATBAG_LED_ON: str = "on"; break; case RATBAG_LED_CYCLE: str = "cycle"; break; case RATBAG_LED_BREATHING: str = "breathing"; break; } return str; } const char* button_type_to_str(enum ratbag_button_type type) { const char *str = "UNKNOWN"; switch(type) { case RATBAG_BUTTON_TYPE_UNKNOWN: str = "unknown"; break; case RATBAG_BUTTON_TYPE_LEFT: str = "left"; break; case RATBAG_BUTTON_TYPE_MIDDLE: str = "middle"; break; case RATBAG_BUTTON_TYPE_RIGHT: str = "right"; break; case RATBAG_BUTTON_TYPE_THUMB: str = "thumb"; break; case RATBAG_BUTTON_TYPE_THUMB2: str = "thumb2"; break; case RATBAG_BUTTON_TYPE_THUMB3: str = "thumb3"; break; case RATBAG_BUTTON_TYPE_THUMB4: str = "thumb4"; break; case RATBAG_BUTTON_TYPE_WHEEL_LEFT: str = "wheel left"; break; case RATBAG_BUTTON_TYPE_WHEEL_RIGHT: str = "wheel right"; break; case RATBAG_BUTTON_TYPE_WHEEL_CLICK: str = "wheel click"; break; case RATBAG_BUTTON_TYPE_WHEEL_UP: str = "wheel up"; break; case RATBAG_BUTTON_TYPE_WHEEL_DOWN: str = "wheel down"; break; case RATBAG_BUTTON_TYPE_WHEEL_RATCHET_MODE_SHIFT: str = "wheel ratchet mode switch"; break; case RATBAG_BUTTON_TYPE_EXTRA: str = "extra (forward)"; break; case RATBAG_BUTTON_TYPE_SIDE: str = "side (backward)"; break; case RATBAG_BUTTON_TYPE_PINKIE: str = "pinkie"; break; case RATBAG_BUTTON_TYPE_PINKIE2: str = "pinkie2"; break; /* DPI switch */ case RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP: str = "resolution cycle up"; break; case RATBAG_BUTTON_TYPE_RESOLUTION_UP: str = "resolution up"; break; case RATBAG_BUTTON_TYPE_RESOLUTION_DOWN: str = "resolution down"; break; /* Profile */ case RATBAG_BUTTON_TYPE_PROFILE_CYCLE_UP: str = "profile cycle up"; break; case RATBAG_BUTTON_TYPE_PROFILE_UP: str = "profile up"; break; case RATBAG_BUTTON_TYPE_PROFILE_DOWN: str = "profile down"; break; } return str; } static const struct map { enum ratbag_button_action_special special; const char *str; } special_map[] = { { RATBAG_BUTTON_ACTION_SPECIAL_UNKNOWN, "unknown" }, { RATBAG_BUTTON_ACTION_SPECIAL_DOUBLECLICK, "doubleclick" }, /* Wheel mappings */ { RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT, "wheel left" }, { RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT, "wheel right" }, { RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP, "wheel up" }, { RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN, "wheel down" }, { RATBAG_BUTTON_ACTION_SPECIAL_RATCHET_MODE_SWITCH, "ratchet mode switch" }, /* DPI switch */ { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP, "resolution cycle up" }, { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_DOWN, "resolution cycle down" }, { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP, "resolution up" }, { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN, "resolution down" }, { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE, "resolution alternate" }, { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT, "resolution default" }, /* Profile */ { RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP, "profile cycle up" }, { RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_DOWN, "profile cycle down" }, { RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP, "profile up" }, { RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN, "profile down" }, /* Second mode for buttons */ { RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE, "secondary mode" }, /* battery level */ { RATBAG_BUTTON_ACTION_SPECIAL_BATTERY_LEVEL, "battery level" }, /* must be the last entry in the table */ { RATBAG_BUTTON_ACTION_SPECIAL_INVALID, NULL }, }; const char * button_action_special_to_str(struct ratbag_button *button) { enum ratbag_button_action_special special; const struct map *m = special_map; special = ratbag_button_get_special(button); while (m->special != RATBAG_BUTTON_ACTION_SPECIAL_INVALID) { if (m->special == special) return m->str; m++; } return "UNKNOWN"; } char * button_action_button_to_str(struct ratbag_button *button) { char str[96]; sprintf_safe(str, "button %d", ratbag_button_get_button(button)); return strdup_safe(str); } char * button_action_key_to_str(struct ratbag_button *button) { const char *str; unsigned int modifiers[10]; size_t m_size = 10; str = libevdev_event_code_get_name(EV_KEY, ratbag_button_get_key(button, modifiers, &m_size)); if (!str) str = "UNKNOWN"; return strdup_safe(str); } static const char *strip_ev_key(int key) { const char *str = libevdev_event_code_get_name(EV_KEY, key); if (strneq(str, "KEY_", 4)) return str + 4; return str; }; char * button_action_macro_to_str(struct ratbag_button *button) { struct ratbag_button_macro *macro; char str[4096] = {0}; const char *name; int offset; unsigned int i; macro = ratbag_button_get_macro(button); name = ratbag_button_macro_get_name(macro); offset = snprintf(str, sizeof(str), "macro \"%s\":", name ? name : "UNKNOWN"); for (i = 0; i < MAX_MACRO_EVENTS; i++) { enum ratbag_macro_event_type type = ratbag_button_macro_get_event_type(macro, i); int key = ratbag_button_macro_get_event_key(macro, i); int timeout = ratbag_button_macro_get_event_timeout(macro, i); if (type == RATBAG_MACRO_EVENT_NONE) break; switch (type) { case RATBAG_MACRO_EVENT_KEY_PRESSED: offset += snprintf(str + offset, sizeof(str) - offset, " %s↓", strip_ev_key(key)); break; case RATBAG_MACRO_EVENT_KEY_RELEASED: offset += snprintf(str + offset, sizeof(str) - offset, " %s↑", strip_ev_key(key)); break; case RATBAG_MACRO_EVENT_WAIT: offset += snprintf(str + offset, sizeof(str) - offset, " %.03f⏱", timeout / 1000.0); break; default: offset += snprintf(str + offset, sizeof(str) - offset, " ###"); } } ratbag_button_macro_unref(macro); return strdup_safe(str); } char * button_action_to_str(struct ratbag_button *button) { enum ratbag_button_action_type type; char *str; type = ratbag_button_get_action_type(button); switch (type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: str = button_action_button_to_str(button); break; case RATBAG_BUTTON_ACTION_TYPE_KEY: str = button_action_key_to_str(button); break; case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: str = strdup_safe(button_action_special_to_str(button)); break; case RATBAG_BUTTON_ACTION_TYPE_MACRO: str = button_action_macro_to_str(button); break; case RATBAG_BUTTON_ACTION_TYPE_NONE: str = strdup_safe("none"); break; default: error("type %d unknown\n", type); str = strdup_safe("UNKNOWN"); } return str; } struct ratbag_device * ratbag_cmd_open_device(struct ratbag *ratbag, const char *path) { struct ratbag_device *device; _cleanup_(udev_unrefp) struct udev *udev = NULL; _cleanup_(udev_device_unrefp) struct udev_device *udev_device = NULL; enum ratbag_error_code error; udev = udev_new(); udev_device = udev_device_from_path(udev, path); if (!udev_device) return NULL; error = ratbag_device_new_from_udev_device(ratbag, udev_device, &device); if (error != RATBAG_SUCCESS) return NULL; return device; } enum ratbag_button_action_special str_to_special_action(const char *str) { const struct map *m = special_map; while (m->str) { if (streq(m->str, str)) return m->special; m++; } return RATBAG_BUTTON_ACTION_SPECIAL_INVALID; } static int open_restricted(const char *path, int flags, void *user_data) { int fd = open(path, flags); if (fd < 0) error("Failed to open %s (%s)\n", path, strerror(errno)); return fd < 0 ? -errno : fd; } static void close_restricted(int fd, void *user_data) { close(fd); } const struct ratbag_interface interface = { .open_restricted = open_restricted, .close_restricted = close_restricted, }; libratbag-0.13/tools/shared.h000066400000000000000000000047231362011324700161500ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * 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 (including the next * paragraph) 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. */ #pragma once #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_MACRO_EVENTS 256 LIBRATBAG_ATTRIBUTE_PRINTF(1, 2) static inline void error(const char *format, ...) { va_list args; fprintf(stderr, "Error: "); va_start(args, format); vfprintf(stderr, format, args); va_end(args); } struct udev_device* udev_device_from_path(struct udev *udev, const char *path); const char* button_type_to_str(enum ratbag_button_type type); const char* led_type_to_str(enum ratbag_led_type type); const char * led_mode_to_str(enum ratbag_led_mode mode); const char * button_action_special_to_str(struct ratbag_button *button); char * button_action_button_to_str(struct ratbag_button *button); char * button_action_key_to_str(struct ratbag_button *button); char * button_action_to_str(struct ratbag_button *button); char * button_action_macro_to_str(struct ratbag_button *button); enum ratbag_button_action_special str_to_special_action(const char *str); struct ratbag_device * ratbag_cmd_open_device(struct ratbag *ratbag, const char *path); extern const struct ratbag_interface interface; libratbag-0.13/tools/test-ratbag-command.sh000077500000000000000000000037621362011324700207230ustar00rootroot00000000000000#!/bin/bash # # Script to test ratbag-command for valid argument parsing. Must be run # against a device file. # command="`dirname $0`/ratbag-command" device=$1 if ! [[ -e "$device" ]]; then echo "Invalid device path or path missing" exit 1 fi # no device argument cmds=() cmds+=("list") for i in "${cmds[@]}"; do echo "Testing arguments '$i $device'" eval $command "$i" if [[ $? == 2 ]]; then echo "Invalid command: $i" exit 1 fi sleep 1 done # commands with device argument cmds=() cmds+=("info") cmds+=("switch-etekcity") cmds+=("profile active get") cmds+=("profile active set 0") cmds+=("resolution active get") cmds+=("resolution active set 0") cmds+=("dpi get") cmds+=("dpi set 800") cmds+=("rate get") cmds+=("rate set 500") cmds+=("profile 0 resolution active get") cmds+=("profile 0 resolution active set 0") cmds+=("resolution 0 dpi get") cmds+=("resolution 0 dpi set 800") cmds+=("resolution 0 rate get") cmds+=("resolution 0 rate set 500") cmds+=("profile 0 resolution 0 dpi get") cmds+=("profile 0 resolution 0 dpi set 800") cmds+=("profile 0 resolution 0 rate get") cmds+=("profile 0 resolution 0 rate set 500") cmds+=("button count") cmds+=("profile 0 button count") cmds+=("button 0 action get") cmds+=("button 0 action set button 1") cmds+=("profile 0 button 0 action get") cmds+=("profile 0 button 0 action set button 1") cmds+=("button 0 action set key KEY_ENTER") cmds+=("profile 0 button 0 action set key KEY_ENTER") cmds+=("button 0 action set special doubleclick") cmds+=("profile 0 button 0 action set special doubleclick") cmds+=("profile 0 button 0 action set macro +KEY_ENTER t05 -KEY_ENTER") cmds+=("profile 0 led 0 get") cmds+=("profile 0 led side get") cmds+=("profile 0 led 0 set mode on") cmds+=("profile 0 led 0 set color ffffff") cmds+=("profile 0 led 0 set rate 1") cmds+=("profile 0 led 0 set brightness 1") for i in "${cmds[@]}"; do echo "Testing arguments '$i $device'" eval $command "$i" $device if [[ $? == 2 ]]; then echo "Invalid command: $i" exit 1 fi sleep 1 done libratbag-0.13/tools/toolbox.py000066400000000000000000000075601362011324700165730ustar00rootroot00000000000000# This file is part of libratbag. # # Copyright 2017 Red Hat, Inc. # # 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 (including the next # paragraph) 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. import imp import os import subprocess import sys from gi.repository import GLib # various constants RATBAGCTL_NAME = 'ratbagctl' RATBAGCTL_PATH = os.path.join('@MESON_BUILD_ROOT@', RATBAGCTL_NAME) RATBAGCTL_DEVEL_NAME = 'ratbagctl.devel' RATBAGCTL_DEVEL_PATH = os.path.join('@MESON_BUILD_ROOT@', RATBAGCTL_DEVEL_NAME) def import_non_standard_path(name, path): # Fast path: see if the module has already been imported. try: return sys.modules[name] except KeyError: pass # If any of the following calls raises an exception, # there's a problem we can't handle -- let the caller handle it. with open(path, 'rb') as fp: module = imp.load_module(name, fp, os.path.basename(path), ('.py', 'rb', imp.PY_SOURCE)) return module def start_ratbagd(verbosity=0): from gi.repository import Gio import time # FIXME: kill any running ratbagd.devel args = [os.path.join('@MESON_BUILD_ROOT@', "ratbagd.devel")] if verbosity >= 3: args.append('--verbose') elif verbosity >= 2: args.append('--verbose=debug') elif verbosity == 0: args.append('--quiet') ratbagd_process = subprocess.Popen(args, shell=False, stdout=sys.stdout, stderr=sys.stderr) dbus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) name_owner = None start_time = time.perf_counter() while name_owner is None and time.perf_counter() - start_time < 30: proxy = Gio.DBusProxy.new_sync(dbus, Gio.DBusProxyFlags.NONE, None, "org.freedesktop.ratbag_devel1", "/org/freedesktop/ratbag_devel1", "org.freedesktop.ratbag_devel1.Manager", None) name_owner = proxy.get_name_owner() if name_owner is None: time.sleep(0.2) os.environ['RATBAG_TEST'] = "1" if name_owner is None or ratbagd_process.poll() is not None: return None return ratbagd_process def terminate_ratbagd(ratbagd): if ratbagd is not None: try: ratbagd.terminate() ratbagd.wait(5) except subprocess.TimeoutExpired: ratbagd.kill() def sync_dbus(): main_context = GLib.MainContext.default() while main_context.pending(): main_context.iteration(False) ratbagctl = import_non_standard_path(RATBAGCTL_NAME, RATBAGCTL_PATH) from ratbagctl import open_ratbagd, get_parser, RatbagError, RatbagErrorCapability # NOQA __all__ = [ RATBAGCTL_NAME, RATBAGCTL_PATH, start_ratbagd, terminate_ratbagd, open_ratbagd, get_parser, RatbagError, RatbagErrorCapability, ] libratbag-0.13/tools/valgrind-ratbag-command.supp000066400000000000000000000023071362011324700221160ustar00rootroot00000000000000{ Remove python related leak checks Memcheck:Leak ... obj:*/libpython*.so* } { Remove dlopens, as libratbag does not do those Memcheck:Leak match-leak-kinds: reachable fun:*alloc ... fun:openaux fun:_dl_catch_error fun:_dl_map_object_deps fun:dl_open_worker fun:_dl_catch_error fun:_dl_open fun:dlopen_doit fun:_dl_catch_error } { Remove calloc static allocated glib entries Memcheck:Leak match-leak-kinds: reachable fun:*alloc ... obj:/usr/lib64/libglib*.so* fun:g_key_file_new } { Remove any libglib-2 static alloc Memcheck:Leak match-leak-kinds: reachable fun:*alloc ... obj:/usr/lib64/libglib*.so* fun:_dl_init obj:/usr/lib64/ld*.so* } { quarks in glib-2 are staying around Memcheck:Leak match-leak-kinds: reachable fun:malloc ... obj:/usr/lib64/libglib*.so* fun:g_quark_from_static_string } { Remove pooled allocated memory from systemd Memcheck:Leak match-leak-kinds: reachable fun:malloc fun:mempool_alloc_tile } { Remove pooled allocated memory from systemd Memcheck:Leak match-leak-kinds: reachable fun:malloc ... fun:internal_ordered_hashmap_ensure_allocated }