pax_global_header00006660000000000000000000000064146745610050014522gustar00rootroot0000000000000052 comment=9f4789f8746188a75cd3090e94adf984f0ac4cc6 libratbag-0.18/000077500000000000000000000000001467456100500134015ustar00rootroot00000000000000libratbag-0.18/.editorconfig000066400000000000000000000004721467456100500160610ustar00rootroot00000000000000root = 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.18/.github/000077500000000000000000000000001467456100500147415ustar00rootroot00000000000000libratbag-0.18/.github/ISSUE_TEMPLATE/000077500000000000000000000000001467456100500171245ustar00rootroot00000000000000libratbag-0.18/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000015241467456100500216200ustar00rootroot00000000000000--- 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` Device info (if applicable): ``` $ ratbagctl info OUTPUT 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.18/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011341467456100500226500ustar00rootroot00000000000000--- 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.18/.github/actions/000077500000000000000000000000001467456100500164015ustar00rootroot00000000000000libratbag-0.18/.github/actions/meson/000077500000000000000000000000001467456100500175225ustar00rootroot00000000000000libratbag-0.18/.github/actions/meson/action.yml000066400000000000000000000024021467456100500215200ustar00rootroot00000000000000# A Github Action to build a repository with meson name: "Setup and run meson" description: "Setup and run meson" inputs: builddir: description: "The build directory" default: "builddir" srcdir: description: "The source directory" default: "." meson_args: description: "Arguments passed to meson setup" default: "" meson_skip_test: description: "Set to a nonempty string to skip meson test" default: "" meson_test_args: description: "Arguments passed to meson test" default: "" meson_precmd: description: "Set to 'sudo' to run with sudo" default: "" ninja_args: description: "Arguments passed to ninja" default: "" ninja_precmd: description: "Set to 'sudo' to run with sudo" default: "" runs: using: "composite" steps: - run: | ${{inputs.meson_precmd}} meson setup ${{inputs.builddir}} ${{inputs.srcdir}} ${{inputs.meson_args}} ${{inputs.meson_precmd}} meson configure ${{inputs.builddir}} ${{inputs.ninja_precmd}} ninja -C ${{inputs.builddir}} ${{inputs.ninja_args}} if [[ -z "${{inputs.meson_skip_test}}" ]]; then ${{inputs.meson_precmd}} meson test -C ${{inputs.builddir}} --print-errorlogs ${{inputs.meson_test_args}}; fi shell: bash libratbag-0.18/.github/actions/pkginstall/000077500000000000000000000000001467456100500205515ustar00rootroot00000000000000libratbag-0.18/.github/actions/pkginstall/action.yml000066400000000000000000000010431467456100500225470ustar00rootroot00000000000000name: "Install packages" description: "Install a set of packages from sources" inputs: apt: description: "The package list to install with apt" pip: description: "The package list to install with pip" pip_precmd: description: "Set to the string 'sudo' to run through sudo" default: "" runs: using: "composite" steps: - run: sudo ${{ github.action_path }}/install-apt.sh ${{ inputs.apt }} shell: bash - run: ${{inputs.pip_precmd}} ${{ github.action_path }}/install-pip.sh ${{ inputs.pip }} shell: bash libratbag-0.18/.github/actions/pkginstall/install-apt.sh000077500000000000000000000007021467456100500233370ustar00rootroot00000000000000#!/bin/bash -x # If called without arguments, just skip the rest if [[ -z "$@" ]]; then exit fi # Don't care about these bits echo 'path-exclude=/usr/share/doc/*' > /etc/dpkg/dpkg.cfg.d/99-exclude-cruft echo 'path-exclude=/usr/share/locale/*' >> /etc/dpkg/dpkg.cfg.d/99-exclude-cruft echo 'path-exclude=/usr/share/man/*' >> /etc/dpkg/dpkg.cfg.d/99-exclude-cruft apt-get update apt-get install -yq --no-install-suggests --no-install-recommends $@ libratbag-0.18/.github/actions/pkginstall/install-pip.sh000077500000000000000000000002541467456100500233450ustar00rootroot00000000000000#!/bin/bash -x # If called without arguments, just skip the rest if [[ -z "$@" ]]; then exit fi python -m pip install --upgrade pip python -m pip install --upgrade "$@" libratbag-0.18/.github/workflows/000077500000000000000000000000001467456100500167765ustar00rootroot00000000000000libratbag-0.18/.github/workflows/main.yml000066400000000000000000000032451467456100500204510ustar00rootroot00000000000000name: linux on: [ push, pull_request ] env: UBUNTU_DEP_BUILD: | gcc g++ pkg-config systemd libevdev-dev libglib2.0-dev libjson-glib-dev libsystemd-dev libudev-dev libunistring-dev python3-dev python3-evdev swig python3-sphinx UBUNTU_DEP_TEST: check python3-gi python3-lxml valgrind PIP_PACKAGES: meson ninja libevdev pyudev pytest sphinx_rtd_theme black ruff jobs: build-and-dist: runs-on: ubuntu-20.04 strategy: matrix: meson_args: - '' - '-Dbuildtype=plain' - '-Dbuildtype=release' - '-Ddocumentation=true' steps: - uses: actions/checkout@v3 - uses: ./.github/actions/pkginstall with: apt: $UBUNTU_DEP_BUILD $UBUNTU_DEP_TEST pip: $PIP_PACKAGES - name: meson test ${{matrix.meson_args}} uses: ./.github/actions/meson with: meson_args: --prefix=$PWD/_instdir ${{matrix.meson_args}} ninja_args: install - name: check installation of data files run: diff -u <(cd data/devices; ls *.device) <(cd _instdir/share/libratbag; ls *.device) - name: ninja uninstall uses: ./.github/actions/meson with: meson_args: --prefix=$PWD/_instdir ninja_args: uninstall - name: check if any files are left after uninstall run: (test -d _instdir && tree _instdir && exit 1) || exit 0 # Capture all the meson logs, even if we failed - uses: actions/upload-artifact@v3 if: ${{ always() }} # even if we fail with: name: meson test logs path: | builddir/meson-logs/testlog*.txt builddir/meson-logs/meson-log.txt libratbag-0.18/.github/workflows/pages.yml000066400000000000000000000016061467456100500206230ustar00rootroot00000000000000name: pages on: push: branches: - master env: UBUNTU_DEP_BUILD: gcc g++ pkg-config systemd libevdev-dev libglib2.0-dev libjson-glib-dev libsystemd-dev libudev-dev libunistring-dev python3-dev python3-evdev python3-pip swig python3-sphinx python3-sphinx-rtd-theme PIP_PACKAGES: meson ninja jobs: deploy: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - uses: ./.github/actions/pkginstall with: apt: $UBUNTU_DEP_BUILD pip: $PIP_PACKAGES - name: Build docs uses: ./.github/actions/meson with: meson_args: -Ddocumentation=true -Dtests=false meson_skip_test: yes - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./builddir/doc/html/ allow_empty_commit: true libratbag-0.18/COPYING000066400000000000000000000021761467456100500144420ustar00rootroot00000000000000Copyright © 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.18/README.md000066400000000000000000000143571467456100500146720ustar00rootroot00000000000000libratbag ========= 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 To remove/uninstall simply run: sudo ninja -C builddir uninstall 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. libratbag-0.18/TODO000066400000000000000000000044661467456100500141030ustar00rootroot00000000000000some 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.18/data/000077500000000000000000000000001467456100500143125ustar00rootroot00000000000000libratbag-0.18/data/devices/000077500000000000000000000000001467456100500157345ustar00rootroot00000000000000libratbag-0.18/data/devices/README.md000066400000000000000000000006511467456100500172150ustar00rootroot00000000000000libratbag 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.18/data/devices/asus-rog-buzzard.device000066400000000000000000000002531467456100500223340ustar00rootroot00000000000000[Device] Name=ASUS ROG GX860 Buzzard Mouse DeviceMatch=usb:0b05:1816 DeviceType=mouse Driver=asus [Driver/asus] Profiles=3 Buttons=10 Leds=0 Dpis=2 DpiRange=100:8200@100 libratbag-0.18/data/devices/asus-rog-chakram-core.device000066400000000000000000000003431467456100500232070ustar00rootroot00000000000000# ASUS ROG Chakram, but corded [Device] Name=ASUS ROG Chakram Core DeviceMatch=usb:0b05:1958 DeviceType=mouse Driver=asus [Driver/asus] Profiles=3 Buttons=5 Leds=3 Dpis=4 DpiRange=100:16000@100 Quirks=DOUBLE_DPI;STRIX_PROFILE libratbag-0.18/data/devices/asus-rog-chakram-x.device000066400000000000000000000002761467456100500225330ustar00rootroot00000000000000[Device] Name=ASUS ROG Chakram X DeviceMatch=usb:0b05:1a18 DeviceType=mouse Driver=asus [Driver/asus] Profiles=3 Buttons=5 Leds=3 Dpis=4 Wireless=1 DpiRange=100:36000@100 Quirks=DOUBLE_DPI libratbag-0.18/data/devices/asus-rog-chakram.device000066400000000000000000000003301467456100500222550ustar00rootroot00000000000000[Device] Name=ASUS ROG Chakram DeviceMatch=usb:0b05:18e3;usb:0b05:18e5 DeviceType=mouse Driver=asus [Driver/asus] Profiles=3 Buttons=5 Leds=3 Dpis=4 Wireless=1 DpiRange=100:16000@100 Quirks=DOUBLE_DPI;STRIX_PROFILE libratbag-0.18/data/devices/asus-rog-gladius2-origin-pink.device000066400000000000000000000003031467456100500246050ustar00rootroot00000000000000[Device] Name=ASUS ROG Gladius II Origin PNK LTD DeviceMatch=usb:0b05:18cd DeviceType=mouse Driver=asus [Driver/asus] Profiles=3 Buttons=8 Leds=3 Dpis=2 DpiRange=100:12000@100 Quirks=DOUBLE_DPI libratbag-0.18/data/devices/asus-rog-gladius2-origin.device000066400000000000000000000003571467456100500236570ustar00rootroot00000000000000# "ASUS ROG Gladius II" without "DPI target button" [Device] Name=ASUS ROG Gladius II Origin DeviceMatch=usb:0b05:1877 DeviceType=mouse Driver=asus [Driver/asus] Profiles=3 Buttons=8 Leds=3 Dpis=2 DpiRange=100:12000@100 Quirks=DOUBLE_DPI libratbag-0.18/data/devices/asus-rog-gladius2.device000066400000000000000000000003131467456100500223620ustar00rootroot00000000000000[Device] Name=ASUS ROG Gladius II DeviceMatch=usb:0b05:1845 DeviceType=mouse Driver=asus [Driver/asus] Profiles=3 Buttons=9 Leds=3 Dpis=2 DpiRange=100:12000@100 ButtonMapping=f0;f1;f2;e4;e5;e6;e7;e8;e9 libratbag-0.18/data/devices/asus-rog-harpe-wireless.device000066400000000000000000000002601467456100500236030ustar00rootroot00000000000000[Device] Name=ASUS ROG Harpe Wireless DeviceMatch=usb:0b05:1a92 DeviceType=mouse Driver=asus [Driver/asus] Profiles=5 Buttons=5 Leds=1 Dpis=4 Wireless=1 DpiRange=100:36000@50 libratbag-0.18/data/devices/asus-rog-keris-wireless-aimpoint.device000066400000000000000000000002711467456100500254410ustar00rootroot00000000000000[Device] Name=ASUS ROG Keris Wireless AimPoint DeviceMatch=usb:0b05:1a68 DeviceType=mouse Driver=asus [Driver/asus] Profiles=5 Buttons=7 Leds=1 Dpis=4 Wireless=1 DpiRange=100:36000@50 libratbag-0.18/data/devices/asus-rog-keris-wireless.device000066400000000000000000000003211467456100500236170ustar00rootroot00000000000000[Device] Name=ASUS ROG Keris Wireless DeviceMatch=usb:0b05:195e;usb:0b05:1960 DeviceType=mouse Driver=asus [Driver/asus] Profiles=3 Buttons=8 Leds=2 Dpis=4 Wireless=1 DpiRange=100:16000@100 Quirks=DOUBLE_DPI libratbag-0.18/data/devices/asus-rog-pugio.device000066400000000000000000000003131467456100500217730ustar00rootroot00000000000000[Device] Name=ASUS ROG Pugio DeviceMatch=usb:0b05:1846 DeviceType=mouse Driver=asus [Driver/asus] Profiles=3 Buttons=10 Leds=3 Dpis=2 DpiRange=100:7200@100 ButtonMapping=f0;f1;f2;e4;e5;e6;0;e8;e9;e1;e2 libratbag-0.18/data/devices/asus-rog-spatha-x.device000066400000000000000000000003611467456100500224000ustar00rootroot00000000000000[Device] Name=ASUS ROG Spatha X DeviceMatch=usb:0b05:1979;usb:0b05:1977 DeviceType=mouse Driver=asus [Driver/asus] Profiles=5 Buttons=14 Leds=3 Dpis=4 Wireless=1 DpiRange=100:19000@50 ButtonMapping=f0;f1;f2;e4;e5;e6;e8;e9;ea;eb;ec;ed;ee;ef libratbag-0.18/data/devices/asus-rog-strix-carry.device000066400000000000000000000003021467456100500231350ustar00rootroot00000000000000[Device] Name=ASUS ROG Strix Carry DeviceMatch=usb:0b05:18b4 DeviceType=mouse Driver=asus [Driver/asus] Profiles=3 Buttons=8 Leds=0 Dpis=2 Wireless=1 DpiRange=100:7200@100 Quirks=STRIX_PROFILE libratbag-0.18/data/devices/asus-rog-strix-impact.device000066400000000000000000000002431467456100500232760ustar00rootroot00000000000000[Device] Name=ASUS ROG Strix Impact DeviceMatch=usb:0b05:1847 DeviceType=mouse Driver=asus [Driver/asus] Profiles=1 Buttons=6 Leds=1 Dpis=2 DpiRange=100:5000@100 libratbag-0.18/data/devices/asus-rog-strix-impact2-wireless.device000066400000000000000000000003111467456100500252070ustar00rootroot00000000000000[Device] Name=ASUS ROG Strix Impact II Wireless DeviceMatch=usb:0b05:1947;usb:0b05:1949 DeviceType=mouse Driver=asus [Driver/asus] Profiles=3 Buttons=8 Leds=3 Dpis=4 Wireless=1 DpiRange=100:16000@100 libratbag-0.18/data/devices/asus-rog-strix-impact2.device000066400000000000000000000002461467456100500233630ustar00rootroot00000000000000[Device] Name=ASUS ROG Strix Impact II DeviceMatch=usb:0b05:18e1 DeviceType=mouse Driver=asus [Driver/asus] Profiles=3 Buttons=8 Leds=3 Dpis=4 DpiRange=200:6200@100 libratbag-0.18/data/devices/device.example000066400000000000000000000054311467456100500205530ustar00rootroot00000000000000# 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 type of this Device: either 'mouse', 'keyboard' or 'other' DeviceType=mouse # 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 [Driver/asus] # Number of profiles Profiles=3 # Number of buttons Buttons=8 # Number of LEDs Leds=3 # Number of DPI presets Dpis=2 # Set if device is wireless # Wireless=1 # DPI range in format min:max@step DpiRange=100:16000@100 # Button mapping # ButtonMapping=f0;f1;f2;e4;e5;e6;e8;e9 # Device quirks # Quirk=DOUBLE_DPI;STRIX_PROFILE # Replace `FEED` in the group name with firmware version of the device. # To find the right value, see the output of ratbagd and search a message like: # "ratbag error: Device with firmware version FEED is not supported" # Official software utility configuration file key: `PSD` (converted from # ASCII codes). [Driver/sinowealth/devices/FEED] # Number of buttons. # Official software utility configuration file key: `KM` (divided by 2). # Default: 0. Buttons=6 # Device name. DeviceName=CoolVendor CoolMouse # The order of LED pins on the mouse. # Change this to `RBG` if LED colors are weird. # Use `None` if there are no LEDs on the mouse. # Permitted values: ("RGB", "RBG", "None"). LedType=RGB # Number of profiles, called "modes" in official software utilities. # If you are unsure what the "mode" thing is about, don't set this field. # Official software utility configuration file key: `MDNUM` (divided by 3). # Default: 1. Profiles=1 # Sensor type. # This field is unused, only used for book-keeping purposes. # Official software utility configuration file key: `Sensor`. SensorType=PMW3289 libratbag-0.18/data/devices/etekcity-scroll-alpha.device000066400000000000000000000001371467456100500233160ustar00rootroot00000000000000[Device] Name=Etekcity Scroll Alpha DeviceMatch=usb:1ea7:4011 Driver=etekcity DeviceType=mouse libratbag-0.18/data/devices/glorious-model-d.device000066400000000000000000000003031467456100500222730ustar00rootroot00000000000000[Device] Name=Glorious Model D DeviceMatch=usb:258a:0033 DeviceType=mouse Driver=sinowealth [Driver/sinowealth/devices/V102] Buttons=6 DeviceName=Glorious Model D LedType=RBG SensorType=PMW3360 libratbag-0.18/data/devices/glorious-model-o.device000066400000000000000000000003341467456100500223120ustar00rootroot00000000000000[Device] Name=Glorious Model O/O- DeviceMatch=usb:258a:0036 DeviceType=mouse Driver=sinowealth [Driver/sinowealth/devices/V103] Buttons=6 DeviceName=Glorious Model O/O- (updated firmware) LedType=RBG SensorType=PMW3360 libratbag-0.18/data/devices/gskill-MX-780.device000066400000000000000000000001471467456100500212420ustar00rootroot00000000000000# G.Skill MX-780 [Device] Name=G.Skill MX-780 DeviceMatch=usb:28da:3101 Driver=gskill DeviceType=mouse libratbag-0.18/data/devices/logitech-M325.device000066400000000000000000000001641467456100500213400ustar00rootroot00000000000000# Logitech M325 over unifying [Device] Name=Logitech M325 DeviceMatch=usb:046d:400a Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-M500s.device000066400000000000000000000001501467456100500215110ustar00rootroot00000000000000# Logitech M500s [Device] Name=Logitech M500s DeviceMatch=usb:046d:c093 DeviceType=mouse Driver=hidpp20 libratbag-0.18/data/devices/logitech-M545.device000066400000000000000000000001461467456100500213440ustar00rootroot00000000000000# Logitech M545 [Device] Name=Logitech M545 DeviceMatch=usb:046d:4052 DeviceType=mouse Driver=hidpp20 libratbag-0.18/data/devices/logitech-M570.device000066400000000000000000000001461467456100500213420ustar00rootroot00000000000000# Logitech M570 [Device] Name=Logitech M570 DeviceMatch=usb:046d:1028 Driver=hidpp10 DeviceType=mouse libratbag-0.18/data/devices/logitech-M585-M590.device000066400000000000000000000002041467456100500217730ustar00rootroot00000000000000# Logitech M585/M590 [Device] Name=Logitech M585/M590 DeviceMatch=usb:046d:406b;bluetooth:046d:b01b Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-M705.device000066400000000000000000000001461467456100500213420ustar00rootroot00000000000000# Logitech M705 [Device] Name=Logitech M705 DeviceMatch=usb:046d:101b Driver=hidpp10 DeviceType=mouse libratbag-0.18/data/devices/logitech-M720.device000066400000000000000000000001521467456100500213340ustar00rootroot00000000000000[Device] Name=Logitech M720 DeviceMatch=usb:046d:405e;bluetooth:046d:b015 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-MX-Anywhere-3.device000066400000000000000000000002141467456100500231520ustar00rootroot00000000000000# Logitech MX Anywhere 3 [Device] Name=Logitech MX Anywhere 3 DeviceMatch=bluetooth:046d:b025;usb:046d:4090 DeviceType=mouse Driver=hidpp20 libratbag-0.18/data/devices/logitech-MX-Anywhere-3S.device000066400000000000000000000002001467456100500232700ustar00rootroot00000000000000# Logitech MX Anywhere 3S [Device] Name=Logitech MX Anywhere 3S DeviceMatch=bluetooth:046d:b037 DeviceType=mouse Driver=hidpp20 libratbag-0.18/data/devices/logitech-MX-Anywhere2.device000066400000000000000000000002671467456100500231040ustar00rootroot00000000000000[Device] Name=Logitech MX Anywhere 2 DeviceMatch=usb:046d:404a;usb:046d:4072;usb:046d:4063;bluetooth:046d:b013;bluetooth:046d:b018;bluetooth:046d:b01f Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-MX-Anywhere2S.device000066400000000000000000000001641467456100500232230ustar00rootroot00000000000000[Device] Name=Logitech MX Anywhere 2S DeviceMatch=bluetooth:046d:b01a;usb:046d:406a Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-MX-Anywhere3B.device000066400000000000000000000002001467456100500231720ustar00rootroot00000000000000# Logitech MX Anywhere 3B [Device] Driver=hidpp20 Name=Logitech MX Anywhere 3B DeviceType=mouse DeviceMatch=bluetooth:046d:b02d libratbag-0.18/data/devices/logitech-MX-Ergo.device000066400000000000000000000002001467456100500221170ustar00rootroot00000000000000# Logitech MX Ergo [Device] Name=Logitech MX Ergo DeviceMatch=bluetooth:046d:b01d;usb:046d:406f Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-MX-Master-2S.device000066400000000000000000000002121467456100500227430ustar00rootroot00000000000000# Logitech MX Master 2S [Device] Name=Logitech MX Master 2S DeviceMatch=bluetooth:046d:b019;usb:046d:4069 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-MX-Master-3.device000066400000000000000000000002101467456100500226170ustar00rootroot00000000000000# Logitech MX Master 3 [Device] Name=Logitech MX Master 3 DeviceMatch=bluetooth:046d:b023;usb:046d:4082 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-MX-Master-3S-for-Business.device000066400000000000000000000001611467456100500253240ustar00rootroot00000000000000[Device] DeviceMatch=bluetooth:046d:b035 DeviceType=mouse Driver=hidpp20 Name=Logitech MX Master 3S for Business libratbag-0.18/data/devices/logitech-MX-Master-3S.device000066400000000000000000000001441467456100500227500ustar00rootroot00000000000000[Device] DeviceMatch=bluetooth:046d:b034 DeviceType=mouse Driver=hidpp20 Name=Logitech MX Master 3S libratbag-0.18/data/devices/logitech-MX-Master.device000066400000000000000000000003101467456100500224600ustar00rootroot00000000000000# 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 DeviceType=mouse libratbag-0.18/data/devices/logitech-MX-Vertical.device000066400000000000000000000001771467456100500230110ustar00rootroot00000000000000[Device] Name=Logitech MX Vertical DeviceMatch=bluetooth:046d:b020;usb:046d:407b;usb:046d:c08a Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-MX518.device000066400000000000000000000001161467456100500214710ustar00rootroot00000000000000[Device] Name=MX518 DeviceMatch=usb:046d:c08e Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-Marathon-M705.device000066400000000000000000000001371467456100500231110ustar00rootroot00000000000000[Device] Name=Logitech Marathon M705 DeviceMatch=usb:046d:406d Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-T650.device000066400000000000000000000001641467456100500213500ustar00rootroot00000000000000# Logitech T650 over unifying [Device] Name=Logitech T650 DeviceMatch=usb:046d:4101 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-Wireless-Touchpad.device000066400000000000000000000002131467456100500242470ustar00rootroot00000000000000# Logitech Wireless Touchpad (Unifying) [Device] Name=Logitech Wireless Touchpad DeviceMatch=usb:046d:4011 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g-powerplay.device000066400000000000000000000001751467456100500231620ustar00rootroot00000000000000[Device] Name=Logitech G Powerplay DeviceMatch=usb:046d:405f Driver=hidpp20 DeviceType=other [Driver/hidpp20] DeviceIndex=7 libratbag-0.18/data/devices/logitech-g-pro-keyboard.device000066400000000000000000000001431467456100500235310ustar00rootroot00000000000000[Device] DeviceMatch=usb:046d:c339 DeviceType=keyboard Driver=hidpp20 Name=Logitech G PRO Keyboard libratbag-0.18/data/devices/logitech-g-pro-wireless.device000066400000000000000000000002161467456100500235670ustar00rootroot00000000000000[Device] Name=Logitech G Pro Wireless DeviceMatch=usb:046d:4079;usb:046d:c088 Driver=hidpp20 DeviceType=mouse [Driver/hidpp20] DeviceIndex=1 libratbag-0.18/data/devices/logitech-g-pro-x-wireless-superlight.device000066400000000000000000000001731467456100500262220ustar00rootroot00000000000000[Device] Name=Logitech G Pro X Wireless Superlight DeviceMatch=usb:046d:4093;usb:046d:c094 DeviceType=mouse Driver=hidpp20 libratbag-0.18/data/devices/logitech-g-pro.device000066400000000000000000000001621467456100500217340ustar00rootroot00000000000000[Device] Name=Logitech Gaming Mouse G Pro DeviceMatch=usb:046d:c085;usb:046d:c08c Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g102-g203.device000066400000000000000000000002451467456100500220340ustar00rootroot00000000000000# G102, G103 and G203 (USB) [Device] Name=Logitech Gaming Mouse G102/G103/G203 DeviceMatch=usb:046d:c084;usb:046d:c092;usb:046d:c09d Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g300.device000066400000000000000000000002511467456100500213600ustar00rootroot00000000000000# WARNING: this file was automatically converted and has not yet been vetted [Device] Name=Logitech G300 DeviceMatch=usb:046d:c246 Driver=logitech_g300 DeviceType=mouse libratbag-0.18/data/devices/logitech-g302.device000066400000000000000000000001631467456100500213640ustar00rootroot00000000000000# G302 over USB [Device] Name=Logitech Gaming Mouse G302 DeviceMatch=usb:046d:c07f Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g303-shroud.device000066400000000000000000000001451467456100500226670ustar00rootroot00000000000000[Device] Name=Logitech G303 Shroud Edition DeviceMatch=usb:046d:c097 DeviceType=mouse Driver=hidpp20 libratbag-0.18/data/devices/logitech-g303.device000066400000000000000000000001631467456100500213650ustar00rootroot00000000000000# G303 over USB [Device] Name=Logitech Gaming Mouse G303 DeviceMatch=usb:046d:c080 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g305.device000066400000000000000000000002071467456100500213660ustar00rootroot00000000000000[Device] Name=Logitech Gaming Mouse G305 DeviceMatch=usb:046d:4074 Driver=hidpp20 DeviceType=mouse [Driver/hidpp20] Leds=0 Quirk=G305 libratbag-0.18/data/devices/logitech-g402.device000066400000000000000000000002041467456100500213610ustar00rootroot00000000000000# Logitech G402 Gaming Mouse USB [Device] Name=Logitech G402 Gaming Mouse DeviceMatch=usb:046d:c07e Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g403-hero.device000066400000000000000000000001331467456100500223160ustar00rootroot00000000000000[Device] Name=Logitech G403 Hero DeviceMatch=usb:046d:c08f Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g403-wireless.device000066400000000000000000000001551467456100500232220ustar00rootroot00000000000000[Device] Name=Logitech G403 Wireless DeviceMatch=usb:046d:405d;usb:046d:c082 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g403.device000066400000000000000000000001261467456100500213650ustar00rootroot00000000000000[Device] Name=Logitech G403 DeviceMatch=usb:046d:c083 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g413.device000066400000000000000000000001311467456100500213620ustar00rootroot00000000000000[Device] DeviceMatch=usb:046d:c33a DeviceType=keyboard Driver=hidpp20 Name=Logitech G413 libratbag-0.18/data/devices/logitech-g5-2007.device000066400000000000000000000002641467456100500216140ustar00rootroot00000000000000# Name=Logitech G5 2007 [Device] Name=Logitech G5 DeviceMatch=usb:046d:c049 Driver=hidpp10 DeviceType=mouse [Driver/hidpp10] DpiList=400;800;1600;2000 Profiles=1 ProfileType=G500 libratbag-0.18/data/devices/logitech-g5.device000066400000000000000000000001671467456100500212300ustar00rootroot00000000000000[Device] Name=G5 DeviceMatch=usb:046d:c041 Driver=hidpp10 DeviceType=mouse [Driver/hidpp10] DpiList=400;800;1600;2000 libratbag-0.18/data/devices/logitech-g500.device000066400000000000000000000002321467456100500213610ustar00rootroot00000000000000[Device] Name=Logitech G500 DeviceMatch=usb:046d:c068 Driver=hidpp10 DeviceType=mouse [Driver/hidpp10] DpiRange=0:5700@23.53 ProfileType=G500 Profiles=1 libratbag-0.18/data/devices/logitech-g500s.device000066400000000000000000000002301467456100500215420ustar00rootroot00000000000000[Device] Name=Logitech G500s DeviceMatch=usb:046d:c24e Driver=hidpp10 DeviceType=mouse [Driver/hidpp10] DpiRange=0:8200@50 ProfileType=G500 Profiles=1 libratbag-0.18/data/devices/logitech-g502-hero-wireless.device000066400000000000000000000001621467456100500241530ustar00rootroot00000000000000[Device] Name=Logitech G502 Hero Wireless DeviceMatch=usb:046d:407f;usb:046d:c08d Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g502-hero.device000066400000000000000000000001331467456100500223160ustar00rootroot00000000000000[Device] Name=Logitech G502 Hero DeviceMatch=usb:046d:c08b Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g502-proteus-core.device000066400000000000000000000001431467456100500240110ustar00rootroot00000000000000[Device] Name=Logitech G502 Proteus Core DeviceMatch=usb:046d:c07d Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g502-proteus-spectrum.device000066400000000000000000000001471467456100500247270ustar00rootroot00000000000000[Device] Name=Logitech G502 Proteus Spectrum DeviceMatch=usb:046d:c332 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g502-x-wireless.device000066400000000000000000000001411467456100500234620ustar00rootroot00000000000000[Device] Name=Logitech G502 X Wireless DeviceMatch=usb:046d:c098 DeviceType=mouse Driver=hidpp20 libratbag-0.18/data/devices/logitech-g502-x.device000066400000000000000000000001301467456100500216250ustar00rootroot00000000000000[Device] Name=Logitech G502 X DeviceMatch=usb:046d:c099 DeviceType=mouse Driver=hidpp20 libratbag-0.18/data/devices/logitech-g513.device000066400000000000000000000001311467456100500213630ustar00rootroot00000000000000[Device] Name=Logitech G513 DeviceMatch=usb:046d:c33c Driver=hidpp20 DeviceType=keyboard libratbag-0.18/data/devices/logitech-g600.device000066400000000000000000000002511467456100500213630ustar00rootroot00000000000000# WARNING: this file was automatically converted and has not yet been vetted [Device] Name=Logitech G600 DeviceMatch=usb:046d:c24a Driver=logitech_g600 DeviceType=mouse libratbag-0.18/data/devices/logitech-g602.device000066400000000000000000000002431467456100500213660ustar00rootroot00000000000000# Logitech G602 over wireless USB [Device] Name=Logitech G602 DeviceMatch=usb:046d:402c Driver=hidpp20 DeviceType=mouse [Driver/hidpp20] DeviceIndex=1 Quirk=G602 libratbag-0.18/data/devices/logitech-g603.device000066400000000000000000000001721467456100500213700ustar00rootroot00000000000000# Logitech G603 [Device] Name=Logitech G603 DeviceMatch=usb:046d:406c;bluetooth:046d:b01c Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g604.device000066400000000000000000000001521467456100500213670ustar00rootroot00000000000000[Device] Name=Logitech G604 DeviceMatch=usb:046d:4085;bluetooth:046d:b024 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g635.device000066400000000000000000000001621467456100500213740ustar00rootroot00000000000000[Device] Name=Logitech G635 DeviceMatch=usb:046d:0a89 DeviceType=mouse Driver=hidpp20 [Driver/hidpp20] Buttons=0 libratbag-0.18/data/devices/logitech-g7.device000066400000000000000000000003111467456100500212210ustar00rootroot00000000000000# Logitech G7 # http://support.logitech.com/en_us/product/g7-laser-cordless-mouse/specs [Device] Name=Logitech G7 DeviceMatch=usb:046d:c51a Driver=hidpp10 DeviceType=mouse [Driver/hidpp10] Profiles=1 libratbag-0.18/data/devices/logitech-g700-wireless.device000066400000000000000000000004271467456100500232240ustar00rootroot00000000000000# 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 DeviceType=mouse [Driver/hidpp10] DpiRange=0:5700@23.53 ProfileType=G700 DeviceIndex=1 Profiles=5 libratbag-0.18/data/devices/logitech-g700.device000066400000000000000000000004001467456100500213600ustar00rootroot00000000000000# 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 DeviceType=mouse [Driver/hidpp10] DpiRange=0:5700@23.53 ProfileType=G700 Profiles=5 libratbag-0.18/data/devices/logitech-g700s.device000066400000000000000000000004141467456100500215500ustar00rootroot00000000000000# 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 DeviceType=mouse [Driver/hidpp10] DpiRange=0:8200@50 ProfileType=G700 Profiles=5 libratbag-0.18/data/devices/logitech-g703-hero.device000066400000000000000000000001511467456100500223210ustar00rootroot00000000000000[Device] Name=Logitech G703 Hero DeviceMatch=usb:046d:4086;usb:046d:c090 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g703.device000066400000000000000000000001441467456100500213700ustar00rootroot00000000000000[Device] Name=Logitech G703 DeviceMatch=usb:046d:c087;usb:046d:4070 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g733.device000066400000000000000000000002171467456100500213740ustar00rootroot00000000000000[Device] Name=Logitech G733 Gaming Headset DeviceMatch=usb:046d:0ab5;usb:046d:0afe DeviceType=mouse Driver=hidpp20 [Driver/hidpp20] Buttons=0 libratbag-0.18/data/devices/logitech-g815.device000066400000000000000000000001311467456100500213700ustar00rootroot00000000000000[Device] Name=Logitech G815 DeviceMatch=usb:046d:c33f Driver=hidpp20 DeviceType=keyboard libratbag-0.18/data/devices/logitech-g9.device000066400000000000000000000004521467456100500212310ustar00rootroot00000000000000# Logitech G9 # http://support.logitech.com/en_us/product/g9-laser-mouse/specs [Device] Name=Logitech G9 DeviceMatch=usb:046d:c048 Driver=hidpp10 DeviceType=mouse [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.18/data/devices/logitech-g900.device000066400000000000000000000001641467456100500213710ustar00rootroot00000000000000# Logitech G900 [Device] Name=Logitech G900 DeviceMatch=usb:046d:c081;usb:046d:4053 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g903-hero.device000066400000000000000000000001511467456100500223230ustar00rootroot00000000000000[Device] Name=Logitech G903 Hero DeviceMatch=usb:046d:c091;usb:046d:4087 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g903.device000066400000000000000000000001441467456100500213720ustar00rootroot00000000000000[Device] Name=Logitech G903 DeviceMatch=usb:046d:c086;usb:046d:4067 Driver=hidpp20 DeviceType=mouse libratbag-0.18/data/devices/logitech-g910.device000066400000000000000000000001721467456100500213710ustar00rootroot00000000000000[Device] Name=Logitech G910 DeviceMatch=usb:046d:c335 Driver=hidpp20 DeviceType=keyboard [Driver/hidpp20] DeviceIndex=ff libratbag-0.18/data/devices/logitech-g915-tkl.device000066400000000000000000000002211467456100500221610ustar00rootroot00000000000000[Device] Name=Logitech G915 TKL DeviceMatch=usb:046d:c343;bluetooth:046d:b35f DeviceType=keyboard Driver=hidpp20 [Driver/hidpp20] DeviceIndex=1 libratbag-0.18/data/devices/logitech-g915.device000066400000000000000000000001711467456100500213750ustar00rootroot00000000000000[Device] Name=Logitech G915 DeviceMatch=usb:046d:c33e Driver=hidpp20 DeviceType=keyboard [Driver/hidpp20] DeviceIndex=1 libratbag-0.18/data/devices/logitech-g935.device000066400000000000000000000001261467456100500213770ustar00rootroot00000000000000[Device] Name=Logitech G935 DeviceMatch=usb:046d:0a87 Driver=hidpp20 DeviceType=other libratbag-0.18/data/devices/logitech-g9x-Call-of-Duty-MW3-Edition.device000066400000000000000000000004401467456100500256110ustar00rootroot00000000000000# 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 DeviceType=mouse [Driver/hidpp10] DpiRange=0:5700@23.53 ProfileType=G500 Profiles=5 libratbag-0.18/data/devices/logitech-g9x-Original.device000066400000000000000000000003751467456100500231670ustar00rootroot00000000000000# Logitech G9x [Original] # http://support.logitech.com/en_us/product/g9x-laser-mouse/specs [Device] Name=G9x [Original] DeviceMatch=usb:046d:c066 DeviceType=mouse Driver=hidpp10 [Driver/hidpp10] DpiRange=200:5700@50 ProfileType=G500 Profiles=5 Leds=1 libratbag-0.18/data/devices/marsgaming-mm4.device000066400000000000000000000001331467456100500217320ustar00rootroot00000000000000[Device] Name=Mars Gaming MM4 DeviceMatch=usb:04d9:fa58 DeviceType=mouse Driver=marsgaming libratbag-0.18/data/devices/nubwo-x7-spectrum.device000066400000000000000000000001441467456100500224420ustar00rootroot00000000000000[Device] Name=Nubwo x7 spectrum DeviceMatch=usb:258a:0012 DeviceType=mouse Driver=sinowealth_nubwo libratbag-0.18/data/devices/openinput.device000066400000000000000000000001241467456100500211330ustar00rootroot00000000000000[Device] Name=openinput DeviceMatch=usb:1d50:616a DeviceType=mouse Driver=openinput libratbag-0.18/data/devices/roccat-kone-emp.device000066400000000000000000000001401467456100500220740ustar00rootroot00000000000000[Device] Name=Roccat Kone EMP DeviceMatch=usb:1e7d:2e24 DeviceType=mouse Driver=roccat-kone-emp libratbag-0.18/data/devices/roccat-kone-pure.device000066400000000000000000000001761467456100500222770ustar00rootroot00000000000000[Device] Name=Roccat Kone Pure DeviceMatch=usb:1e7d:2dc2;usb:1e7d:2dbe;usb:1e7d:2dcb Driver=roccat-kone-pure DeviceType=mouse libratbag-0.18/data/devices/roccat-kone-xtd.device000066400000000000000000000001271467456100500221170ustar00rootroot00000000000000[Device] Name=Roccat Kone XTD DeviceMatch=usb:1e7d:2e22 Driver=roccat DeviceType=mouse libratbag-0.18/data/devices/sinowealth-0027.device000066400000000000000000000014011467456100500216540ustar00rootroot00000000000000[Device] DeviceMatch=usb:258a:0027 DeviceType=mouse Driver=sinowealth Name=SinoWealth Generic Mouse (0027) [Driver/sinowealth/devices/3106] Buttons=8 DeviceName=DreamMachines DM5 Blink LedType=RGB SensorType=PMW3389 [Driver/sinowealth/devices/V161] Buttons=6 # Can be both `Ace` and `Classic` version. DeviceName=G-Wolves Hati HT-M Wired LedType=None # Can also be PMW3389 SensorType=PMW3360 [Driver/sinowealth/devices/3110] # There are 5 main buttons. # Side buttons are detachable. # There can be either 3 or 9 buttons on the side. # Going for the maximum count: 5 + 9 = 14. Buttons=14 DeviceName=Genesys Xenon 770 LedType=RGB SensorType=PMW3327 [Driver/sinowealth/devices/V102] Buttons=6 DeviceName=Glorious Model O (old firmware) LedType=RBG SensorType=PMW3360 libratbag-0.18/data/devices/sinowealth-0029.device000066400000000000000000000010361467456100500216620ustar00rootroot00000000000000[Device] DeviceMatch=usb:258a:0029 DeviceType=mouse Driver=sinowealth Name=SinoWealth Generic Mouse (0029) # Configuration data size: 131 bytes. # https://github.com/libratbag/libratbag/issues/1361 [Driver/sinowealth/devices/V127] Buttons=6 DeviceName=Machenike M620 LedType=RGB SensorType=PMW3389 # Configuration data size: 131 bytes. # https://github.com/libratbag/libratbag/pull/1587 # https://maddog.pl/produkty/myszki/57,mad-dog-gm905 [Driver/sinowealth/devices/V287] Buttons=14 DeviceName=Mad Dog GM905 LedType=RGB SensorType=PMW3389 libratbag-0.18/data/devices/sinowealth-0051.device000066400000000000000000000005031467456100500216530ustar00rootroot00000000000000[Device] DeviceMatch=usb:258a:0051 DeviceType=mouse Driver=sinowealth Name=Sinowealth Generic Mouse (0051) # Configuration data size: 123 bytes. # https://github.com/libratbag/libratbag/issues/1444. [Driver/sinowealth/devices/2642] Buttons=8 DeviceName=T-Dagger Imperial T-TGM310 LedType=RGB Profiles=2 SensorType=PMW3212 libratbag-0.18/data/devices/sinowealth-1007.device000066400000000000000000000010141467456100500216530ustar00rootroot00000000000000[Device] DeviceMatch=usb:258a:1007 DeviceType=mouse Driver=sinowealth Name=SinoWealth Generic Mouse (1007) # This does NOT work. See libratbag/libratbag#1296. # For future: ID of A3050 sensor is 0x2, and maximum DPI is 4000. #[Driver/sinowealth/devices/0910] #Buttons=7 #DeviceName=Ant Esports GM500 #LedType=RBG #SensorType=A3050 # Configuration size: 123 bytes. # https://github.com/libratbag/libratbag/issues/1091 [Driver/sinowealth/devices/9677] Buttons=6 DeviceName=Marvo Scorpion G961 LedType=RGB SensorType=PMW3327 libratbag-0.18/data/devices/steelseries-kinzu-v2-pro.device000066400000000000000000000003141467456100500237230ustar00rootroot00000000000000[Device] Name=SteelSeries Kinzu V2 Pro Edition DeviceMatch=usb:1038:1366 DeviceType=mouse Driver=steelseries [Driver/steelseries] DeviceVersion=1 Buttons=3 Leds=0 DpiList=400;800;1600;3200 MacroLength=0 libratbag-0.18/data/devices/steelseries-kinzu-v2.device000066400000000000000000000003001467456100500231200ustar00rootroot00000000000000[Device] Name=SteelSeries Kinzu V2 DeviceMatch=usb:1038:1378 Driver=steelseries DeviceType=mouse [Driver/steelseries] DeviceVersion=1 Buttons=3 Leds=0 DpiList=400;800;1600;3200 MacroLength=0 libratbag-0.18/data/devices/steelseries-kinzu-v3.device000066400000000000000000000003241467456100500231270ustar00rootroot00000000000000[Device] Name=SteelSeries Kinzu V3 DeviceMatch=usb:1038:1388 Driver=steelseries DeviceType=mouse [Driver/steelseries] DeviceVersion=1 Buttons=3 Leds=0 DpiList=250;500;1000;1250;1500;1750;2000;4000 MacroLength=0 libratbag-0.18/data/devices/steelseries-rival-100.device000066400000000000000000000005701467456100500230570ustar00rootroot00000000000000[Device] # 100; 100 (Dell China); 100 Dota 2 Edition (retail); 100 Dota 2 Edition (Lenovo); 105. DeviceMatch=usb:1038:1702;usb:1038:170a;usb:1038:170b;usb:1038:170c;usb:1038:1814 DeviceType=mouse Driver=steelseries Name=SteelSeries Rival 100/105 [Driver/steelseries] # Actually 6. Buttons=0 DeviceVersion=1 DpiList=250;500;1000;1250;1500;1750;2000;4000 Leds=1 Quirk=Rival100 libratbag-0.18/data/devices/steelseries-rival-310.device000066400000000000000000000002761467456100500230650ustar00rootroot00000000000000[Device] Name=SteelSeries Rival 310 DeviceMatch=usb:1038:1720 Driver=steelseries DeviceType=mouse [Driver/steelseries] DeviceVersion=2 Buttons=6 Leds=2 DpiRange=100:12000@100 MacroLength=1 libratbag-0.18/data/devices/steelseries-rival-600.device000066400000000000000000000002761467456100500230670ustar00rootroot00000000000000[Device] Name=SteelSeries Rival 600 DeviceMatch=usb:1038:1724 Driver=steelseries DeviceType=mouse [Driver/steelseries] DeviceVersion=3 Buttons=7 Leds=8 DpiRange=100:12000@100 MacroLength=1 libratbag-0.18/data/devices/steelseries-rival-650-wireless.device000066400000000000000000000003721467456100500247240ustar00rootroot00000000000000[Device] # Wired mode; 2.4 Ghz wireless mode. DeviceMatch=usb:1038:172b;usb:1038:1726 DeviceType=mouse Driver=steelseries Name=SteelSeries Rival 650 Wireless [Driver/steelseries] Buttons=0 DeviceVersion=4 DpiRange=100:12000@100 Leds=0 MacroLength=0 libratbag-0.18/data/devices/steelseries-rival.device000066400000000000000000000004651467456100500225640ustar00rootroot00000000000000[Device] Name=SteelSeries Rival DeviceMatch=usb:1038:1384;usb:1038:1392;usb:1038:1710;usb:1038:1712;usb:1038:171c;usb:1038:1394;usb:1038:171a;usb:1038:1716;usb:1038:1714;usb:1038:1718 Driver=steelseries DeviceType=mouse [Driver/steelseries] DeviceVersion=1 Buttons=6 Leds=2 DpiRange=50:6500@50 MacroLength=1 libratbag-0.18/data/devices/steelseries-sensei-310.device000066400000000000000000000002771467456100500232370ustar00rootroot00000000000000[Device] Name=SteelSeries Sensei 310 DeviceMatch=usb:1038:1722 Driver=steelseries DeviceType=mouse [Driver/steelseries] DeviceVersion=2 Buttons=8 Leds=2 DpiRange=100:12000@100 MacroLength=1 libratbag-0.18/data/devices/steelseries-sensei-raw.device000066400000000000000000000003141467456100500235150ustar00rootroot00000000000000[Device] Name=SteelSeries Sensei Raw DeviceMatch=usb:1038:1369 Driver=steelseries DeviceType=mouse [Driver/steelseries] DeviceVersion=1 Buttons=8 Leds=1 DpiRange=90:5670@90 MacroLength=1 Quirk=SenseiRAW libratbag-0.18/data/logo.svg000066400000000000000000000057571467456100500160110ustar00rootroot00000000000000 libratbag-0.18/dbus/000077500000000000000000000000001467456100500143365ustar00rootroot00000000000000libratbag-0.18/dbus/org.freedesktop.ratbag1.conf.in000066400000000000000000000012011467456100500222250ustar00rootroot00000000000000 libratbag-0.18/dbus/org.freedesktop.ratbag1.service.in000066400000000000000000000001551467456100500227470ustar00rootroot00000000000000[D-BUS Service] Name=org.freedesktop.ratbag1 Exec=@sbindir@/ratbagd User=root SystemdService=ratbagd.service libratbag-0.18/dbus/org.freedesktop.ratbag_devel1.conf.in000066400000000000000000000015701467456100500234150ustar00rootroot00000000000000 libratbag-0.18/doc/000077500000000000000000000000001467456100500141465ustar00rootroot00000000000000libratbag-0.18/doc/conf.py.in000066400000000000000000000113501467456100500160520ustar00rootroot00000000000000#!/usr/bin/env python3 # # @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.18/doc/dbus.rst000066400000000000000000000557261467456100500156540ustar00rootroot00000000000000******** 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 occurred 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-compatibility 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:: FirmwareVersion :type: s :flags: read-only, constant A device-specific string with the firmware version, or the empty string. For devices with a major/minor or purely numeric firmware version, the conversion into a string is implementation-defined. .. 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:: Disabled :type: b :flags: read-write, mutable True if this is the profile is disabled, 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:: IsDirty :type: b :flags: read-only, mutable True if this profile is dirty, false otherwise. Dirty, that is, the device needs committing after being configured. This property becomes `true` whenever any other property of the profile is updated. .. 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:: AngleSnapping :type: i :flags: read-write, mutable Sensor angle snapping boolean value as an int (1 or 0), or `-1` to indicate that the device doesn't support reading and/or writing this value. .. attribute:: Debounce :type: i :flags: read-write, mutable Int for the button debounce time in milliseconds assigned to this profile. This time must be one of those listed in :attr:`Debounces`, or `-1` to indicate that changing debounce time for this device is not allowed. .. attribute:: Debounces :type: au :flags: read-write, constant A list of permitted button debounce times. Values in this list may be used in the :attr:`Debounce` property. This list is always sorted ascending, the lowest debounce time is the first item in the list. This list may be empty if the device does not support reading and/or writing the debounce time. .. 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 the report rate. .. 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:: Capabilities :type: au :flags: read-only, constant Bit-mask of capabilities. +-------+-----------------------------------------------------+ | Value | Definition | +=======+=====================================================+ | 1 | Resolution can be set for x and y axes separately. | +-------+-----------------------------------------------------+ | 2 | Resolution can be disabled and enabled. | | | Note that this capability only notes the general | | | capability. A specific resolution may still fail to | | | be disabled, e.g. when it is the active resolution | | | on the device. | +-------+-----------------------------------------------------+ (Source: :cpp:enum:`ratbag_resolution_capability` in `libratbag.h`). In the future, extra values may get added. Clients must ignore unknown Capabilities. .. 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 resolution 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:: IsDisabled :type: b :flags: read-write, mutable True if this resolution is disabled, false otherwise. If the device does not have the disabled resolution capability, this property is always false. .. 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:: SetActive() → () Set this resolution to be the active one .. function:: SetDefault() → () Set this resolution to be the default .. _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 *None*, the variant is an unsigned integer (``u``) of value 0. 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 | Key | Mapping to a simple key action | +---------+---------+--------------------------------------+ | 4 | 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. .. _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.18/doc/index.rst000066400000000000000000000010561467456100500160110ustar00rootroot00000000000000.. 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.18/doc/meson.build000066400000000000000000000024341467456100500163130ustar00rootroot00000000000000if 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()) # Sphinx really really wants a _static directory in its source tree. Let's # make it happy run_command('mkdir', '-p', join_paths(project_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(project_build_root, 'doc'), join_paths(project_build_root, 'doc', 'html') ], build_by_default : true, install : false) endif libratbag-0.18/meson.build000066400000000000000000000464501467456100500155540ustar00rootroot00000000000000project('libratbag', 'c', version : '0.18', license : 'MIT/Expat', default_options : [ 'c_std=gnu99', 'warning_level=2' ], meson_version : '>= 0.50.0') # TODO: remove this once we bump meson version. if meson.version().version_compare('>=0.56') project_build_root = meson.project_build_root() project_source_root = meson.project_source_root() else project_build_root = meson.build_root() project_source_root = meson.source_root() message('Embedding libratbag source will not work because an older version of meson is used') endif # 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 = 2 # 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(project_source_root, 'data', 'devices') config_h.set_quoted('LIBRATBAG_DATA_DIR', libratbag_data_dir) # 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) ### libasus.a #### src_libasus = [ 'src/asus.c', ] deps_libasus = [ ] lib_libasus = static_library('asus', src_libasus, dependencies : deps_libasus) dep_libasus = declare_dependency(link_with: lib_libasus) ### 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, dep_libasus, ] 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('src'), 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-asus.c', '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-emp.c', 'src/driver-roccat-kone-pure.c', 'src/driver-gskill.c', 'src/driver-openinput.c', 'src/driver-steelseries.c', 'src/driver-steelseries.h', 'src/driver-sinowealth.c', 'src/driver-sinowealth.h', 'src/driver-sinowealth-nubwo.c', 'src/driver-marsgaming/driver-marsgaming.c', 'src/driver-marsgaming/marsgaming-buttons.c', 'src/driver-marsgaming/marsgaming-command.c', 'src/driver-marsgaming/marsgaming-commit.c', 'src/driver-marsgaming/marsgaming-leds.c', 'src/driver-marsgaming/marsgaming-probe.c', 'src/driver-marsgaming/marsgaming-query.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, dep_libasus, ] lib_libratbag = static_library('ratbag', src_libratbag, include_directories : include_directories('src'), 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 #### install_subdir('data/devices', strip_directory : true, exclude_files : ['device.example', 'README.md'], install_dir : join_paths(get_option('datadir'), 'libratbag')) data_parse_test = find_program(join_paths(project_source_root, 'test/data-parse-test.py')) test('data-parse-test', data_parse_test, args: libratbag_data_dir_devel) duplicate_test = find_program(join_paths(project_source_root, 'test/duplicate-check.py')) test('duplicate-test', duplicate_test, args : libratbag_data_dir_devel) receiver_id_test = find_program(join_paths(project_source_root, 'test/receiver-check.py')) test('receiver-id-test', receiver_id_test, args : libratbag_data_dir_devel) #### 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', required : false) if valgrind.found() valgrind_suppressions_file = join_paths(project_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 endif subdir('rbtree') #### 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', '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_rbtree, dep_unistring, ] executable( 'ratbagd', src_ratbagd, dependencies : deps_ratbagd, include_directories : include_directories('src'), install : true, install_dir : get_option('sbindir'), ) 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. # dbus_devel_policy = configure_file(input : 'dbus/org.freedesktop.ratbag_devel1.conf.in', output : 'org.freedesktop.ratbag_devel1.conf', copy: true) # 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(project_build_root, 'org.freedesktop.ratbag_devel1.conf'), '-DDBUS_POLICY_DST="@0@/@1@"'.format(dbussystemdir, 'org.freedesktop.ratbag_devel1.conf'), '-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_sbindir = configuration_data() config_sbindir.set( 'sbindir', join_paths(get_option('prefix'), get_option('sbindir')), ) if enable_systemd configure_file( input : 'ratbagd/ratbagd.service.in', output : 'ratbagd.service', configuration : config_sbindir, 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_sbindir, 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 #### # This is a special idiomatic construct in meson to get a fast dependency that # doesn't exist and isn't logged as "not found". dep_python3 = dependency('', required : false) pymod = import('python') py3 = pymod.find_installation() if meson.version().version_compare('>= 0.53.0') dep_python3 = py3.dependency(embed : true) endif py_version = py3.language_version() # From python 3.8 we need python3-embed embed = py_version.version_compare('>= 3.8') ? '-embed' : '' if not dep_python3.found() dep_python3 = dependency('python-@0@@1@'.format(py_version, embed), required : false) if not dep_python3.found() dep_python3 = dependency('python3@0@'.format(embed)) endif endif config_ratbagctl = configuration_data() config_ratbagctl.set('RATBAGD_API_VERSION', ratbagd_api_version) config_ratbagctl.set('version', meson.project_version()) ratbagctl_body = configure_file(input : 'tools/ratbagctl.body.py.in', output : 'ratbagctl.body.py', configuration: config_ratbagctl) # ratbagctl is the commandline tool to interact with ratbagd over DBus. ratbagctl_target = custom_target('ratbagctl', output : 'ratbagctl', input : [ratbagctl_body, 'tools/ratbagd.py'], build_by_default : true, command : [py3, join_paths(project_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', project_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.py.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.py.in', output : 'ratbagctl.test', configuration : config_ratbagctl_devel) env_test = environment() env_test.set('LIBRATBAG_DATA_DIR', libratbag_data_dir_devel) # See: https://github.com/mesonbuild/meson/issues/3306. env_test.set('MESON_SOURCE_ROOT', project_source_root) ratbagctl_test = find_program(join_paths(project_build_root, 'ratbagctl.test')) test( 'ratbagctl-test', ratbagctl_test, args : ['-v'], depends : [ ratbagctl_target, ], 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(project_source_root, 'src'), '-I' + join_paths(project_source_root, 'tools'), '@INPUT@'], ) wrapper_deps = [ dep_python3, dep_libratbag, dep_libshared, ] i_source = join_paths(project_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', '-Wno-missing-field-initializers'], extra_files: [ i_source ] + src_libratbag , dependencies: deps_libratbag + wrapper_deps, include_directories : include_directories('src', 'tools'), install: false, ) i_source = join_paths(project_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', '-Wno-missing-field-initializers', '-Wno-strict-prototypes'], 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_body, ratbagc_py], build_by_default : true, command : [py3, join_paths(project_source_root, 'tools', 'merge_ratbagd.py'), '@INPUT@', '--output', '@OUTPUT@'], install: false) #### output files #### configure_file(output: 'config.h', install: false, configuration: config_h) #### code formatting ##### run_target( 'python-black', command : 'tools/python-black.sh', ) python_black_check = find_program(join_paths(project_source_root, 'test/python-black-check.sh')) test( 'python-black-check', python_black_check, env: env_test, ) #### code linting ##### run_target( 'python-ruff', command : 'tools/python-ruff.sh', ) python_ruff_check = find_program(join_paths(project_source_root, 'test/python-ruff-check.sh')) test( 'python-ruff-check', python_ruff_check, env: env_test, ) subdir('doc') libratbag-0.18/meson_options.txt000066400000000000000000000017471467456100500170470ustar00rootroot00000000000000option('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') libratbag-0.18/pyproject.toml000066400000000000000000000015301467456100500163140ustar00rootroot00000000000000[tool.black] target-version = ["py37"] [tool.ruff] target-version = "py37" lint.select = [ # pycodestyle "E", "W", # pyflakes "F", # pyupgrade # Use modern Python features for the best possible develop experience. "UP", # flake8-bugbear # Find obvious bugs. "B", # flake8-pie "PIE", # flake8-return "RET", # pep8-naming # `Error` suffix on exception names. "N818", # flake8-simplify "SIM", ] lint.ignore = [ # Line too long. # We have some places where it's just impossible to make it fit. "E501", # Use 'contextlib.suppress(...)' instead of try-except-pass. # I think it's better to use integrated language constructs instead # of libraries. "SIM105", # Use `"key" in dict` instead of `"key" in dict.keys()`. # While the lint itself is good, fixing it's warnings causes `KeyError` # errors with `GLib.Variant`. "SIM118", ] libratbag-0.18/ratbagd/000077500000000000000000000000001467456100500150055ustar00rootroot00000000000000libratbag-0.18/ratbagd/org.freedesktop.ratbag1.conf000066400000000000000000000007161467456100500223010ustar00rootroot00000000000000 libratbag-0.18/ratbagd/ratbagd-button.c000066400000000000000000000342701467456100500200740ustar00rootroot00000000000000/*** 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; } static int ratbagd_button_get_key(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; const unsigned int key = ratbag_button_get_key(button->lib_button); verify_unsigned_int(key); CHECK_CALL(sd_bus_message_append(reply, "(uv)", RATBAG_BUTTON_ACTION_TYPE_KEY, "u", key)); return 0; } static int ratbagd_button_set_key(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 key; int r; CHECK_CALL(sd_bus_message_read(m, "v", "u", &key)); r = ratbag_button_set_key(button->lib_button, key); 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_none(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { CHECK_CALL(sd_bus_message_append(reply, "(uv)", RATBAG_BUTTON_ACTION_TYPE_NONE, "u", 0)); return 0; } static int ratbagd_button_set_none(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; int r; _unused_ unsigned int zero; CHECK_CALL(sd_bus_message_read(m, "v", "u", &zero)); r = ratbag_button_disable(button->lib_button); 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); verify_unsigned_int(type); switch (type) { case RATBAG_BUTTON_ACTION_TYPE_NONE: return ratbagd_button_get_none(bus, path, interface, property, reply, userdata, error); 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_KEY: return ratbagd_button_get_key(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_NONE: CHECK_CALL(ratbagd_button_set_none(bus, path, interface, property, m, userdata, error)); break; 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_KEY: CHECK_CALL(ratbagd_button_set_key(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_NONE, RATBAG_BUTTON_ACTION_TYPE_BUTTON, RATBAG_BUTTON_ACTION_TYPE_SPECIAL, RATBAG_BUTTON_ACTION_TYPE_KEY, 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; } 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_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.18/ratbagd/ratbagd-device.c000066400000000000000000000337611467456100500200240ustar00rootroot00000000000000/*** 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 #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_device_type(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; enum ratbag_device_type devicetype; devicetype = ratbag_device_get_device_type(device->lib_device); if (!devicetype) { log_error("%s: device type unspecified\n", ratbagd_device_get_sysname(device)); } CHECK_CALL(sd_bus_message_append(reply, "u", devicetype)); 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_for_each_profile_signal(device->ctx->bus, device, ratbagd_profile_notify_dirty); 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 ratbagd_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); } static int ratbagd_device_get_firmware_version(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* version = ratbag_device_get_firmware_version(lib_device); return sd_bus_message_append(reply, "s", version); } const sd_bus_vtable ratbagd_device_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Model", "s", ratbagd_device_get_model, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DeviceType", "u", ratbagd_device_get_device_type, 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("FirmwareVersion", "s", ratbagd_device_get_firmware_version, 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.18/ratbagd/ratbagd-json.c000066400000000000000000000404331467456100500175300ustar00rootroot00000000000000/* * 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, "is_disabled": 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, "is_disabled")) { gboolean v = json_object_get_boolean_member(obj, name); resolution->disabled = v; log_verbose("json: is_disabled: %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_DISABLE) 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, "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-cycle-down", 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; g_autoptr(GList) list = 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; 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.18/ratbagd/ratbagd-json.h000066400000000000000000000023711467456100500175340ustar00rootroot00000000000000/* * 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.18/ratbagd/ratbagd-led.c000066400000000000000000000210641467456100500173220ustar00rootroot00000000000000/*** 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_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_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_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_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.18/ratbagd/ratbagd-profile.c000066400000000000000000000672031467456100500202230ustar00rootroot00000000000000/*** 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_is_dirty(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { const struct ratbagd_profile *profile = userdata; const int is_dirty = ratbag_profile_is_dirty(profile->lib_profile); CHECK_CALL(sd_bus_message_append(reply, "b", is_dirty)); 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) { sd_bus *bus = sd_bus_message_get_bus(m); 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) { r = ratbagd_device_resync(profile->device, bus); if (r < 0) return r; } ratbagd_for_each_profile_signal(bus, profile->device, ratbagd_profile_active_signal_cb); ratbagd_profile_notify_dirty(bus, profile); CHECK_CALL(sd_bus_reply_method_return(m, "u", 0)); return 0; } static int ratbagd_profile_set_disabled(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 r; int disabled; CHECK_CALL(sd_bus_message_read(m, "b", &disabled)); r = ratbag_profile_set_enabled(profile->lib_profile, !disabled); if (r == 0) { sd_bus_emit_properties_changed(bus, profile->path, RATBAGD_NAME_ROOT ".Profile", "Disabled", NULL); ratbagd_profile_notify_dirty(bus, profile); } return 0; } static int ratbagd_profile_is_disabled(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 disabled = !ratbag_profile_is_enabled(profile->lib_profile); CHECK_CALL(sd_bus_message_append(reply, "b", disabled)); 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); ratbagd_profile_notify_dirty(bus, profile); } 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_angle_snapping(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 value; value = ratbag_profile_get_angle_snapping(lib_profile); return sd_bus_message_append(reply, "i", value); } static int ratbagd_profile_get_debounce(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 value; value = ratbag_profile_get_debounce(lib_profile); return sd_bus_message_append(reply, "i", value); } 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_get_debounces(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 debounces[8]; unsigned int ndebounces = ARRAY_LENGTH(debounces); int r; r = sd_bus_message_open_container(reply, 'a', "u"); if (r < 0) return r; ndebounces = ratbag_profile_get_debounce_list(lib_profile, debounces, ndebounces); assert(ndebounces <= ARRAY_LENGTH(debounces)); for (unsigned int i = 0; i < ndebounces; i++) { verify_unsigned_int(debounces[i]); r = sd_bus_message_append(reply, "u", debounces[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 < 125) { rate = 125; } else if (rate > 8000) { rate = 8000; } r = ratbag_profile_set_report_rate(profile->lib_profile, rate); if (r == 0) { sd_bus_emit_properties_changed(bus, profile->path, RATBAGD_NAME_ROOT ".Profile", "ReportRate", NULL); ratbagd_profile_notify_dirty(bus, profile); } return 0; } static int ratbagd_profile_set_angle_snapping(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 value; int r; r = sd_bus_message_read(m, "i", &value); if (r < 0) return r; r = ratbag_profile_set_angle_snapping(profile->lib_profile, value); if (r == 0) { sd_bus *bus = sd_bus_message_get_bus(m); sd_bus_emit_properties_changed(bus, profile->path, RATBAGD_NAME_ROOT ".Profile", "AngleSnapping", NULL); ratbagd_profile_notify_dirty(bus, profile); } return 0; } static int ratbagd_profile_set_debounce(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 value; int r; r = sd_bus_message_read(m, "i", &value); if (r < 0) return r; r = ratbag_profile_set_debounce(profile->lib_profile, value); if (r == 0) { sd_bus *bus = sd_bus_message_get_bus(m); sd_bus_emit_properties_changed(bus, profile->path, RATBAGD_NAME_ROOT ".Profile", "Debounce", NULL); ratbagd_profile_notify_dirty(bus, profile); } 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("Disabled", "b", ratbagd_profile_is_disabled, ratbagd_profile_set_disabled, 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_PROPERTY("IsDirty", "b", ratbagd_profile_is_dirty, 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_WRITABLE_PROPERTY("AngleSnapping", "i", ratbagd_profile_get_angle_snapping, ratbagd_profile_set_angle_snapping, 0, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_WRITABLE_PROPERTY("Debounce", "i", ratbagd_profile_get_debounce, ratbagd_profile_set_debounce, 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_PROPERTY("Debounces", "au", ratbagd_profile_get_debounces, 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); } int ratbagd_profile_notify_dirty(sd_bus *bus, struct ratbagd_profile *profile) { (void)sd_bus_emit_properties_changed(bus, profile->path, RATBAGD_NAME_ROOT ".Profile", "IsDirty", NULL); return 0; } libratbag-0.18/ratbagd/ratbagd-resolution.c000066400000000000000000000275321467456100500207670ustar00rootroot00000000000000/*** 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", "Resolution", "ReportRate", "IsActive", "IsDefault", "IsDisabled", 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_is_disabled(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; const int is_disabled = ratbag_resolution_is_disabled(lib_resolution); CHECK_CALL(sd_bus_message_append(reply, "b", is_disabled)); return 0; } static int ratbagd_resolution_set_disabled(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; int is_disabled; int r; CHECK_CALL(sd_bus_message_read(m, "b", &is_disabled)); r = ratbag_resolution_set_disabled(resolution->lib_resolution, !!is_disabled); if (r == 0) { sd_bus_emit_properties_changed(bus, resolution->path, RATBAGD_NAME_ROOT ".Resolution", "IsDisabled", NULL); } return 0; } 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_emit_properties_changed(bus, resolution->path, RATBAGD_NAME_ROOT ".Resolution", "Resolution", NULL); } return 0; } static int ratbagd_resolution_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_resolution *resolution = userdata; struct ratbag_resolution *lib_resolution = resolution->lib_resolution; enum ratbag_resolution_capability cap; enum ratbag_resolution_capability caps[] = { RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION, RATBAG_RESOLUTION_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_resolution_has_capability(lib_resolution, cap)) { CHECK_CALL(sd_bus_message_append(reply, "u", cap)); } } CHECK_CALL(sd_bus_message_close_container(reply)); 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("IsDisabled", "b", ratbagd_resolution_is_disabled, ratbagd_resolution_set_disabled, 0, SD_BUS_VTABLE_UNPRIVILEGED|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_PROPERTY("Capabilities", "au", ratbagd_resolution_get_capabilities, 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.18/ratbagd/ratbagd-test.c000066400000000000000000000072311467456100500175350ustar00rootroot00000000000000/* * 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 = { { .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.18/ratbagd/ratbagd-test.h000066400000000000000000000030131467456100500175340ustar00rootroot00000000000000/* * 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.18/ratbagd/ratbagd.8000066400000000000000000000016421467456100500165050ustar00rootroot00000000000000.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.18/ratbagd/ratbagd.c000066400000000000000000000373511467456100500165660ustar00rootroot00000000000000/*** 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 #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; log_verbose("Starting ratbagd version %s (API version %d)\n", RATBAG_VERSION, RATBAGD_API_VERSION); 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; } if (mkdir_p(dirname(strdupa(DBUS_POLICY_DST)), 0755)) { log_error("Failed to create destination path: %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 = 0; #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_defer(ctx->event, &source, ratbagd_callback_handler, cb); } libratbag-0.18/ratbagd/ratbagd.h000066400000000000000000000202041467456100500165600ustar00rootroot00000000000000#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 #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); int ratbagd_profile_notify_dirty(sd_bus *bus, struct ratbagd_profile *profile); libratbag-0.18/ratbagd/ratbagd.service.in000066400000000000000000000003351467456100500204010ustar00rootroot00000000000000[Unit] Description=Daemon to introspect and modify configurable mice [Service] Type=dbus BusName=org.freedesktop.ratbag1 ExecStart=@sbindir@/ratbagd Restart=on-abort [Install] Alias=dbus-org.freedesktop.ratbag1.service libratbag-0.18/rbtree/000077500000000000000000000000001467456100500146645ustar00rootroot00000000000000libratbag-0.18/rbtree/meson.build000066400000000000000000000005261467456100500170310ustar00rootroot00000000000000src_rbtree = [ 'src/rbtree/shared-rbtree.h', 'src/rbtree/shared-rbtree.c', ] inc_rbtree = include_directories('src') lib_rbtree = static_library('rbtree', src_rbtree, dependencies : deps_libshared, include_directories : inc_rbtree ) dep_rbtree = declare_dependency( link_with : lib_rbtree, include_directories : inc_rbtree ) libratbag-0.18/rbtree/src/000077500000000000000000000000001467456100500154535ustar00rootroot00000000000000libratbag-0.18/rbtree/src/rbtree/000077500000000000000000000000001467456100500167365ustar00rootroot00000000000000libratbag-0.18/rbtree/src/rbtree/shared-rbtree.c000066400000000000000000000420641467456100500216370ustar00rootroot00000000000000/*** 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 "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.18/rbtree/src/rbtree/shared-rbtree.h000066400000000000000000000057601467456100500216460ustar00rootroot00000000000000#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. ***/ #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.18/release.sh000077500000000000000000000337671467456100500154000ustar00rootroot00000000000000#!/usr/bin/env 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 "libratbag-data.h" #include "libratbag-private.h" /* ASUS commands */ #define ASUS_CMD_GET_LED_DATA 0x0312 /* get all LEDs */ #define ASUS_CMD_GET_SETTINGS 0x0412 /* dpi, rate, button response, angle snapping */ #define ASUS_CMD_GET_BUTTON_DATA 0x0512 /* get all buttons */ #define ASUS_CMD_GET_PROFILE_DATA 0x0012 /* get current profile info */ #define ASUS_CMD_SET_LED 0x2851 /* set single led */ #define ASUS_CMD_SET_SETTING 0x3151 /* dpi / rate / button response / angle snapping */ #define ASUS_CMD_SET_BUTTON 0x2151 /* set single button */ #define ASUS_CMD_SET_PROFILE 0x0250 /* switch profile */ #define ASUS_CMD_SAVE 0x0350 /* save settings */ /* fields order in _asus_dpiX_data, used for setting with ASUS_CMD_SET_SETTING */ #define ASUS_FIELD_RATE 0 #define ASUS_FIELD_RESPONSE 1 #define ASUS_FIELD_SNAPPING 2 /* key mapping, the index is actual ASUS code */ static const unsigned char ASUS_KEY_MAPPING[] = { /* 00 */ 0, 0, 0, 0, /* 04 */ KEY_A, KEY_B, KEY_C, KEY_D, /* 08 */ KEY_E, KEY_F, KEY_G, KEY_H, /* 0C */ KEY_I, KEY_J, KEY_K, KEY_L, /* 0E */ KEY_M, KEY_N, KEY_O, KEY_P, /* 14 */ KEY_Q, KEY_R, KEY_S, KEY_T, /* 18 */ KEY_U, KEY_V, KEY_W, KEY_X, /* 1C */ KEY_Y, KEY_Z, KEY_1, KEY_2, /* 1E */ KEY_3, KEY_4, KEY_5, KEY_6, /* 24 */ KEY_7, KEY_8, KEY_9, KEY_0, /* 28 */ KEY_ENTER, KEY_ESC, KEY_BACKSPACE, KEY_TAB, /* 2C */ KEY_SPACE, KEY_MINUS, KEY_KPPLUS, 0, /* 2E */ 0, 0, 0, 0, /* 34 */ 0, KEY_GRAVE, KEY_EQUAL, 0, /* 38 */ KEY_SLASH, 0, KEY_F1, KEY_F2, /* 3C */ KEY_F3, KEY_F4, KEY_F5, KEY_F6, /* 3E */ KEY_F7, KEY_F8, KEY_F9, KEY_F10, /* 44 */ KEY_F11, KEY_F12, 0, 0, /* 48 */ 0, 0, KEY_HOME, KEY_PAGEUP, /* 4C */ KEY_DELETE, 0, KEY_PAGEDOWN, KEY_RIGHT, /* 4E */ KEY_LEFT, KEY_DOWN, KEY_UP, 0, /* 54 */ 0, 0, 0, 0, /* 58 */ 0, KEY_KP1, KEY_KP2, KEY_KP3, /* 5C */ KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP7, /* 5E */ KEY_KP8, KEY_KP9, 0, }; static unsigned int ASUS_POLLING_RATES[] = { 125, 250, 500, 1000 }; static unsigned int ASUS_DEBOUNCE_TIMES[] = { 4, 8, 12, 16, 20, 24, 28, 32 }; /* search for ASUS button by ratbag types */ const struct asus_button * asus_find_button_by_action(struct ratbag_button_action action) { const struct asus_button *asus_button; ARRAY_FOR_EACH(ASUS_BUTTON_MAPPING, asus_button) { if ((action.type == RATBAG_BUTTON_ACTION_TYPE_BUTTON && asus_button->button == action.action.button) || (action.type == RATBAG_BUTTON_ACTION_TYPE_SPECIAL && asus_button->special == action.action.special)) return asus_button; } return NULL; } /* search for ASUS button by ASUS button code */ const struct asus_button * asus_find_button_by_code(uint8_t asus_code) { for (unsigned int i = 0; i < ARRAY_LENGTH(ASUS_BUTTON_MAPPING); i++) { if (ASUS_BUTTON_MAPPING[i].asus_code == asus_code) return &ASUS_BUTTON_MAPPING[i]; } return NULL; } /* search for ASUS key code by Linux key code */ int asus_find_key_code(unsigned int linux_code) { for (unsigned int i = 0; i < ARRAY_LENGTH(ASUS_KEY_MAPPING); i++) { if (ASUS_KEY_MAPPING[i] == linux_code) return i; } return -1; } int asus_get_linux_key_code(uint8_t asus_code) { if (asus_code > ARRAY_LENGTH(ASUS_KEY_MAPPING)) { return -1; } return ASUS_KEY_MAPPING[asus_code]; } int asus_query(struct ratbag_device *device, union asus_request *request, union asus_response *response) { int rc; rc = ratbag_hidraw_output_report(device, request->raw, ASUS_PACKET_SIZE); if (rc < 0) return rc; memset(response, 0, sizeof(union asus_response)); rc = ratbag_hidraw_read_input_report(device, response->raw, ASUS_PACKET_SIZE, NULL); if (rc < 0) return rc; /* invalid state, disconnected or sleeping */ if (response->data.code == ASUS_STATUS_ERROR) { return ASUS_STATUS_ERROR; } return 0; } void asus_setup_profile(struct ratbag_device *device, struct ratbag_profile *profile) { ratbag_profile_set_report_rate_list( profile, ASUS_POLLING_RATES, ARRAY_LENGTH(ASUS_POLLING_RATES)); ratbag_profile_set_debounce_list( profile, ASUS_DEBOUNCE_TIMES, ARRAY_LENGTH(ASUS_DEBOUNCE_TIMES)); } void asus_setup_button(struct ratbag_device *device, struct ratbag_button *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); } void asus_setup_resolution(struct ratbag_device *device, struct ratbag_resolution *resolution) { const struct dpi_range *dpirange = ratbag_device_data_asus_get_dpi_range(device->data); if (!dpirange) return; ratbag_resolution_set_dpi_list_from_range( resolution, dpirange->min, dpirange->max); } void asus_setup_led(struct ratbag_device *device, struct ratbag_led *led) { led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888; 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); } int asus_save_profile(struct ratbag_device *device) { int rc; union asus_response response; union asus_request request = { .data.cmd = ASUS_CMD_SAVE, }; rc = asus_query(device, &request, &response); if (rc) return rc; return 0; } int asus_get_profile_data(struct ratbag_device *device, struct asus_profile_data *data) { int rc; uint32_t quirks = ratbag_device_data_asus_get_quirks(device->data); union asus_response response; union asus_request request = { .data.cmd = ASUS_CMD_GET_PROFILE_DATA, }; rc = asus_query(device, &request, &response); if (rc) return rc; if (quirks & ASUS_QUIRK_STRIX_PROFILE) data->profile_id = response.data.results[7]; else data->profile_id = response.data.results[8]; data->version_primary_major = response.data.results[13]; data->version_primary_minor = response.data.results[12]; data->version_primary_build = response.data.results[11]; data->version_secondary_major = response.data.results[4]; data->version_secondary_minor = response.data.results[3]; data->version_secondary_build = response.data.results[2]; return 0; } int asus_set_profile(struct ratbag_device *device, unsigned int index) { int rc; union asus_response response; union asus_request request = { .data.cmd = ASUS_CMD_SET_PROFILE, .data.params[0] = index, }; rc = asus_query(device, &request, &response); if (rc) return rc; return 0; } /* read button bindings */ int asus_get_binding_data(struct ratbag_device *device, union asus_binding_data *data) { int rc; union asus_response response; union asus_request request = { .data.cmd = ASUS_CMD_GET_BUTTON_DATA, }; rc = asus_query(device, &request, &response); if (rc) return rc; memcpy(data->raw, response.raw, sizeof(data->raw)); return 0; } /* set button binding using ASUS code of the button */ int asus_set_button_action(struct ratbag_device *device, uint8_t asus_code_src, uint8_t asus_code_dst, uint8_t asus_type) { int rc; union asus_response response; union asus_request request = { .data.cmd = ASUS_CMD_SET_BUTTON, }; /* source (physical mouse button) */ request.data.params[2] = asus_code_src; request.data.params[3] = ASUS_BUTTON_ACTION_TYPE_BUTTON; /* destination (mouse button or keyboard key action) */ request.data.params[4] = asus_code_dst; request.data.params[5] = asus_type; rc = asus_query(device, &request, &response); if (rc) return rc; return 0; } int asus_get_resolution_data(struct ratbag_device *device, union asus_resolution_data *data) { int rc; uint32_t quirks = ratbag_device_data_asus_get_quirks(device->data); union asus_response response; unsigned int dpi_count = ratbag_device_get_profile(device, 0)->num_resolutions; unsigned int i; union asus_request request = { .data.cmd = ASUS_CMD_GET_SETTINGS, }; rc = asus_query(device, &request, &response); if (rc) return rc; memcpy(data->raw, response.raw, sizeof(data->raw)); /* convert DPI rates */ switch (dpi_count) { case 2: /* 2 DPI presets */ for (i = 0; i < dpi_count; i++) { data->data2.dpi[i] = data->data2.dpi[i] * 50 + 50; if (quirks & ASUS_QUIRK_DOUBLE_DPI) data->data2.dpi[i] *= 2; } data->data2.rate = ASUS_POLLING_RATES[data->data2.rate]; data->data2.response = ASUS_DEBOUNCE_TIMES[data->data2.response]; break; case 4: /* 4 DPI presets */ for (i = 0; i < dpi_count; i++) { data->data4.dpi[i] = data->data4.dpi[i] * 50 + 50; if (quirks & ASUS_QUIRK_DOUBLE_DPI) data->data4.dpi[i] *= 2; } data->data4.rate = ASUS_POLLING_RATES[data->data4.rate]; data->data4.response = ASUS_DEBOUNCE_TIMES[data->data4.response]; break; default: break; } return 0; } /* set DPI for the specified preset */ int asus_set_dpi(struct ratbag_device *device, unsigned int index, unsigned int dpi) { int rc; uint32_t quirks = ratbag_device_data_asus_get_quirks(device->data); union asus_response response; unsigned int idpi; idpi = dpi; if (quirks & ASUS_QUIRK_DOUBLE_DPI) idpi /= 2; union asus_request request = { .data.cmd = ASUS_CMD_SET_SETTING, .data.params[0] = index, .data.params[2] = (idpi - 50) / 50, }; rc = asus_query(device, &request, &response); if (rc) return rc; return 0; } /* set polling rate in Hz */ int asus_set_polling_rate(struct ratbag_device *device, unsigned int hz) { int rc; union asus_response response; unsigned int dpi_count = ratbag_device_get_profile(device, 0)->num_resolutions; unsigned int i; union asus_request request = { .data.cmd = ASUS_CMD_SET_SETTING, .data.params[0] = dpi_count + ASUS_FIELD_RATE, /* field index to set */ }; for (i = 0; i < ARRAY_LENGTH(ASUS_POLLING_RATES); i++) { if (ASUS_POLLING_RATES[i] == hz) { request.data.params[2] = i; break; } } rc = asus_query(device, &request, &response); if (rc) return rc; return 0; } /* set button response/debounce in ms (from 4 to 32 with step of 4) */ int asus_set_button_response(struct ratbag_device *device, unsigned int ms) { assert(ms >= 4); int rc; unsigned int dpi_count = ratbag_device_get_profile(device, 0)->num_resolutions; union asus_response response; unsigned int index = 0; for (unsigned int i = 0; i < ARRAY_LENGTH(ASUS_DEBOUNCE_TIMES); i++) { if (ASUS_DEBOUNCE_TIMES[i] == ms) { index = i; break; } } union asus_request request = { .data.cmd = ASUS_CMD_SET_SETTING, .data.params[0] = dpi_count + ASUS_FIELD_RESPONSE, /* field index to set */ .data.params[2] = index, }; rc = asus_query(device, &request, &response); if (rc) return rc; return 0; } int asus_set_angle_snapping(struct ratbag_device *device, bool is_enabled) { int rc; union asus_response response; unsigned int dpi_count = ratbag_device_get_profile(device, 0)->num_resolutions; union asus_request request = { .data.cmd = ASUS_CMD_SET_SETTING, .data.params[0] = dpi_count + ASUS_FIELD_SNAPPING, /* field index to set */ .data.params[2] = is_enabled ? 1 : 0, }; rc = asus_query(device, &request, &response); if (rc) return rc; return 0; } int asus_get_led_data(struct ratbag_device *device, union asus_led_data *data) { int rc; union asus_response response; union asus_request request = { .data.cmd = ASUS_CMD_GET_LED_DATA, }; rc = asus_query(device, &request, &response); if (rc) return rc; memcpy(data->raw, response.raw, sizeof(data->raw)); return 0; } /* set LED mode, brightness (0-4) and color */ int asus_set_led(struct ratbag_device *device, uint8_t index, uint8_t mode, uint8_t brightness, struct ratbag_color color) { int rc; union asus_response response; union asus_request request = { .data.cmd = ASUS_CMD_SET_LED, .data.params[0] = index, .data.params[2] = mode, .data.params[3] = brightness, .data.params[4] = color.red, .data.params[5] = color.green, .data.params[6] = color.blue, }; rc = asus_query(device, &request, &response); if (rc) return rc; return 0; } libratbag-0.18/src/asus.h000066400000000000000000000160421467456100500153170ustar00rootroot00000000000000#pragma once #include #include #include "libratbag-private.h" #include "libratbag-hidraw.h" #define ASUS_QUIRK_DOUBLE_DPI 1 << 0 #define ASUS_QUIRK_STRIX_PROFILE 1 << 1 #define ASUS_QUIRK_BATTERY_V2 1 << 2 #define ASUS_PACKET_SIZE 64 #define ASUS_BUTTON_ACTION_TYPE_KEY 0 /* keyboard key */ #define ASUS_BUTTON_ACTION_TYPE_BUTTON 1 /* mouse button */ #define ASUS_BUTTON_CODE_DISABLED 0xff /* disabled mouse button */ #define ASUS_STATUS_ERROR 0xaaff /* invalid state/request, disconnected or sleeping */ /* maximum number of buttons across all ASUS devices */ #define ASUS_MAX_NUM_BUTTON 17 /* maximum number of DPI presets across all ASUS devices */ /* for 4 DPI devices: 0 - red, 1 - purple, 2 - blue (default), 3 - green */ /* for 2 DPI devices: 0 - main (default), 1 - alternative */ #define ASUS_MAX_NUM_DPI 4 /* maximum number of LEDs across all ASUS devices */ #define ASUS_MAX_NUM_LED 3 /* base request */ struct _asus_request { uint16_t cmd; uint8_t params[ASUS_PACKET_SIZE - 2]; } __attribute__((packed)); _Static_assert(sizeof(struct _asus_request) == ASUS_PACKET_SIZE, "The size of `_asus_request` is wrong."); union asus_request { struct _asus_request data; uint8_t raw[ASUS_PACKET_SIZE]; }; /* base response */ struct _asus_response { uint16_t code; uint8_t results[ASUS_PACKET_SIZE - 2]; } __attribute__((packed)); _Static_assert(sizeof(struct _asus_response) == ASUS_PACKET_SIZE, "The size of `_asus_response` is wrong."); union asus_response { struct _asus_response data; uint8_t raw[ASUS_PACKET_SIZE]; }; /* current profile ID and firmware info */ struct asus_profile_data { unsigned int profile_id; uint8_t version_primary_major; uint8_t version_primary_minor; uint8_t version_primary_build; uint8_t version_secondary_major; uint8_t version_secondary_minor; uint8_t version_secondary_build; } __attribute__((packed)); /* button bindings */ struct _asus_binding { uint8_t action; /* ASUS code (for both keyboard keys and mouse buttons) */ uint8_t type; /* ASUS action type */ } __attribute__((packed)); struct _asus_binding_data { uint32_t pad; struct _asus_binding binding[ASUS_MAX_NUM_BUTTON]; } __attribute__((packed)); union asus_binding_data { struct _asus_binding_data data; uint8_t raw[ASUS_PACKET_SIZE]; }; /* DPI data */ struct _asus_dpi2_data { uint32_t pad; uint16_t dpi[2]; /* DPI presets */ uint16_t rate; /* polling rate */ uint16_t response; /* button response */ uint16_t snapping; /* angle snapping (on/off) */ } __attribute__((packed)); /* struct for storing 2 DPI presets and extra settings */ struct _asus_dpi4_data { uint32_t pad; uint16_t dpi[4]; uint16_t rate; uint16_t response; uint16_t snapping; } __attribute__((packed)); /* struct for storing 4 DPI presets and extra settings */ union asus_resolution_data { struct _asus_dpi2_data data2; /* data for 2 DPI presets */ struct _asus_dpi4_data data4; /* data for 4 DPI presets */ uint8_t raw[sizeof(struct _asus_dpi4_data)]; }; /* LED data */ struct _asus_led { uint8_t mode; uint8_t brightness; /* 0-4 */ uint8_t r; uint8_t g; uint8_t b; } __attribute__((packed)); struct _asus_led_data { uint32_t pad; struct _asus_led led[ASUS_MAX_NUM_LED]; /* LEDs */ } __attribute__((packed)); union asus_led_data { struct _asus_led_data data; uint8_t raw[sizeof(struct _asus_led_data)]; }; /* button define */ struct asus_button { uint8_t asus_code; /* used for button action */ enum ratbag_button_action_type type; uint8_t button; /* mouse button number, optional */ enum ratbag_button_action_special special; /* special action, optional */ }; /* ASUS code, button type, button number, special button action */ static const struct asus_button ASUS_BUTTON_MAPPING[] = { { 0xf0, RATBAG_BUTTON_ACTION_TYPE_BUTTON, 1, 0 }, /* left */ { 0xf1, RATBAG_BUTTON_ACTION_TYPE_BUTTON, 2, 0 }, /* right (button 3 in xev) */ { 0xf2, RATBAG_BUTTON_ACTION_TYPE_BUTTON, 3, 0 }, /* middle (button 2 in xev) */ { 0xe8, RATBAG_BUTTON_ACTION_TYPE_SPECIAL, 0, RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP }, { 0xe9, RATBAG_BUTTON_ACTION_TYPE_SPECIAL, 0, RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN }, { 0xe6, RATBAG_BUTTON_ACTION_TYPE_SPECIAL, 0, RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP }, { 0xe4, RATBAG_BUTTON_ACTION_TYPE_BUTTON, 4, 0 }, /* backward, left side */ { 0xe5, RATBAG_BUTTON_ACTION_TYPE_BUTTON, 5, 0 }, /* forward, left side */ { 0xe1, RATBAG_BUTTON_ACTION_TYPE_BUTTON, 4, 0 }, /* backward, right side */ { 0xe2, RATBAG_BUTTON_ACTION_TYPE_BUTTON, 5, 0 }, /* forward, right side */ { 0xe7, RATBAG_BUTTON_ACTION_TYPE_SPECIAL, 0, RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE }, /* DPI target */ { 0xea, RATBAG_BUTTON_ACTION_TYPE_NONE, 0, 0 }, /* side button A */ { 0xeb, RATBAG_BUTTON_ACTION_TYPE_NONE, 0, 0 }, /* side button B */ { 0xec, RATBAG_BUTTON_ACTION_TYPE_NONE, 0, 0 }, /* side button C */ { 0xed, RATBAG_BUTTON_ACTION_TYPE_NONE, 0, 0 }, /* side button D */ { 0xee, RATBAG_BUTTON_ACTION_TYPE_NONE, 0, 0 }, /* side button E */ { 0xef, RATBAG_BUTTON_ACTION_TYPE_NONE, 0, 0 }, /* side button F */ }; const struct asus_button * asus_find_button_by_action(struct ratbag_button_action action); const struct asus_button * asus_find_button_by_code(uint8_t asus_code); int asus_find_key_code(unsigned int linux_code); /* * @param asus_code ASUS key code. * @return Linux key code (as defined defined in linux/input-event-codes.h) or * -1 on error. */ int asus_get_linux_key_code(uint8_t asus_code); /* initializers of ratbag data */ void asus_setup_profile(struct ratbag_device *device, struct ratbag_profile *profile); void asus_setup_button(struct ratbag_device *device, struct ratbag_button *button); void asus_setup_resolution(struct ratbag_device *device, struct ratbag_resolution *resolution); void asus_setup_led(struct ratbag_device *device, struct ratbag_led *led); /* generic i/o */ int asus_query(struct ratbag_device *device, union asus_request *request, union asus_response *response); /* commit */ int asus_save_profile(struct ratbag_device *device); /* profiles */ int asus_get_profile_data(struct ratbag_device *device, struct asus_profile_data *data); int asus_set_profile(struct ratbag_device *device, unsigned int index); /* button bindings */ int asus_get_binding_data(struct ratbag_device *device, union asus_binding_data *data); int asus_set_button_action(struct ratbag_device *device, uint8_t asus_code_src, uint8_t asus_code_dst, uint8_t asus_type); /* resolution settings */ int asus_get_resolution_data(struct ratbag_device *device, union asus_resolution_data *data); int asus_set_dpi(struct ratbag_device *device, unsigned int index, unsigned int dpi); int asus_set_polling_rate(struct ratbag_device *device, unsigned int hz); int asus_set_button_response(struct ratbag_device *device, unsigned int ms); int asus_set_angle_snapping(struct ratbag_device *device, bool is_enabled); /* LED settings */ int asus_get_led_data(struct ratbag_device *device, union asus_led_data *data); int asus_set_led(struct ratbag_device *device, uint8_t index, uint8_t mode, uint8_t brightness, struct ratbag_color color); libratbag-0.18/src/driver-asus.c000066400000000000000000000400561467456100500166050ustar00rootroot00000000000000/* * Copyright (C) 2021 Kyoken, kyoken@kyoken.ninja * * 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 "asus.h" /* ButtonMapping configuration property defaults */ static int ASUS_CONFIG_BUTTON_MAPPING[] = { 0xf0, /* left */ 0xf1, /* right (button 3 in xev) */ 0xf2, /* middle (button 2 in xev) */ 0xe4, /* backward */ 0xe5, /* forward */ 0xe6, /* DPI */ 0xe8, /* wheel up */ 0xe9, /* wheel down */ -1, /* placeholder */ -1, /* placeholder */ -1, /* placeholder */ -1, /* placeholder */ -1, /* placeholder */ -1, /* placeholder */ -1, /* placeholder */ -1, /* placeholder */ -1, /* placeholder */ }; static unsigned int ASUS_LED_MODE[] = { RATBAG_LED_ON, RATBAG_LED_BREATHING, RATBAG_LED_CYCLE, RATBAG_LED_ON, /* rainbow wave */ RATBAG_LED_ON, /* reactive - react to clicks */ RATBAG_LED_ON, /* custom - depends on mouse type */ RATBAG_LED_ON, /* battery - battery indicator */ }; struct asus_data { uint8_t is_ready; int button_mapping[ASUS_MAX_NUM_BUTTON]; int button_indices[ASUS_MAX_NUM_BUTTON]; }; static int asus_driver_load_profile(struct ratbag_device *device, struct ratbag_profile *profile) { int rc; const struct _asus_binding *asus_binding; const struct _asus_led *asus_led; const struct asus_button *asus_button; struct ratbag_button *button; struct ratbag_led *led; struct ratbag_resolution *resolution; union asus_binding_data binding_data; union asus_led_data led_data; union asus_resolution_data resolution_data; unsigned int dpi_count = ratbag_device_get_profile(device, 0)->num_resolutions; struct asus_data *drv_data = ratbag_get_drv_data(device); /* get buttons */ log_debug(device->ratbag, "Loading buttons data\n"); rc = asus_get_binding_data(device, &binding_data); if (rc) return rc; ratbag_profile_for_each_button(profile, button) { int asus_index = drv_data->button_indices[button->index]; if (asus_index == -1) { log_debug(device->ratbag, "No mapping for button %d\n", button->index); continue; } asus_binding = &binding_data.data.binding[asus_index]; /* disabled */ if (asus_binding->action == ASUS_BUTTON_CODE_DISABLED) { button->action.type = RATBAG_BUTTON_ACTION_TYPE_NONE; continue; } /* got action */ switch (asus_binding->type) { case ASUS_BUTTON_ACTION_TYPE_KEY: button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; rc = asus_get_linux_key_code(asus_binding->action); if (rc > 0) { button->action.action.key = (unsigned int)rc; } else { log_debug(device->ratbag, "Unknown button code %02x\n", asus_binding->action); } break; case ASUS_BUTTON_ACTION_TYPE_BUTTON: asus_button = asus_find_button_by_code(asus_binding->action); if (asus_button != NULL) { /* found button to bind to */ button->action.type = asus_button->type; if (asus_button->type == RATBAG_BUTTON_ACTION_TYPE_BUTTON) button->action.action.button = asus_button->button; else if (asus_button->type == RATBAG_BUTTON_ACTION_TYPE_SPECIAL) button->action.action.special = asus_button->special; } else { log_debug(device->ratbag, "Unknown action code %02x\n", asus_binding->action); } break; default: break; } } /* get DPIs */ log_debug(device->ratbag, "Loading resolutions data\n"); rc = asus_get_resolution_data(device, &resolution_data); if (rc) return rc; switch (dpi_count) { case 2: /* 2 DPI presets */ profile->hz = resolution_data.data2.rate; profile->angle_snapping = resolution_data.data2.snapping; profile->debounce = resolution_data.data2.response; ratbag_profile_for_each_resolution(profile, resolution) ratbag_resolution_set_resolution( resolution, resolution_data.data2.dpi[resolution->index], resolution_data.data2.dpi[resolution->index]); break; case 4: /* 4 DPI presets */ profile->hz = resolution_data.data4.rate; profile->angle_snapping = resolution_data.data4.snapping; profile->debounce = resolution_data.data4.response; ratbag_profile_for_each_resolution(profile, resolution) ratbag_resolution_set_resolution( resolution, resolution_data.data4.dpi[resolution->index], resolution_data.data4.dpi[resolution->index]); break; default: break; } /* get LEDs */ log_debug(device->ratbag, "Loading LEDs data\n"); rc = asus_get_led_data(device, &led_data); if (rc) return rc; ratbag_profile_for_each_led(profile, led) { asus_led = &led_data.data.led[led->index]; led->mode = ASUS_LED_MODE[asus_led->mode]; /* convert brightness from 0-4 to 0-256 */ led->brightness = asus_led->brightness * 64; led->color.red = asus_led->r; led->color.green = asus_led->g; led->color.blue = asus_led->b; } return 0; } static int asus_driver_save_profile(struct ratbag_device *device, struct ratbag_profile *profile) { int rc = 0; struct ratbag_button *button; struct ratbag_led *led; struct ratbag_resolution *resolution; struct asus_data *drv_data = ratbag_get_drv_data(device); /* set buttons */ ratbag_profile_for_each_button(profile, button) { if (!button->dirty) continue; int asus_index = drv_data->button_indices[button->index]; if (asus_index == -1) { log_debug(device->ratbag, "No mapping for button %d\n", button->index); continue; } const struct asus_button *asus_button; uint8_t asus_code_src; uint8_t asus_code_dst; rc = drv_data->button_mapping[asus_index]; if (rc == -1) { log_debug(device->ratbag, "No mapping for button %d\n", button->index); continue; } asus_code_src = (uint8_t)rc; log_debug(device->ratbag, "Button %d (%02x) changed\n", button->index, asus_code_src); switch (button->action.type) { case RATBAG_BUTTON_ACTION_TYPE_NONE: rc = asus_set_button_action( device, asus_code_src, ASUS_BUTTON_CODE_DISABLED, ASUS_BUTTON_ACTION_TYPE_BUTTON); break; case RATBAG_BUTTON_ACTION_TYPE_KEY: /* Linux code to ASUS code */ rc = asus_find_key_code(button->action.action.key); if (rc != -1) { asus_code_dst = (uint8_t)rc; rc = asus_set_button_action( device, asus_code_src, asus_code_dst, ASUS_BUTTON_ACTION_TYPE_KEY); } break; case RATBAG_BUTTON_ACTION_TYPE_BUTTON: case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: /* ratbag action to ASUS code */ asus_button = asus_find_button_by_action(button->action); if (asus_button != NULL) /* found button to bind to */ rc = asus_set_button_action( device, asus_code_src, asus_button->asus_code, ASUS_BUTTON_ACTION_TYPE_BUTTON); break; default: break; } if (rc) return rc; } /* set extra settings */ if (profile->rate_dirty) { log_debug(device->ratbag, "Polling rate changed to %d Hz\n", profile->hz); rc = asus_set_polling_rate(device, profile->hz); if (rc) return rc; } if (profile->angle_snapping_dirty) { log_debug(device->ratbag, "Angle snapping changed to %d\n", profile->angle_snapping); rc = asus_set_angle_snapping(device, profile->angle_snapping); if (rc) return rc; } if (profile->debounce_dirty) { log_debug(device->ratbag, "Debounce time changed to %d\n", profile->debounce); rc = asus_set_button_response(device, profile->debounce); if (rc) return rc; } /* set DPIs */ ratbag_profile_for_each_resolution(profile, resolution) { if (!resolution->dirty) continue; log_debug( device->ratbag, "Resolution %d changed to %d\n", resolution->index, resolution->dpi_x); rc = asus_set_dpi(device, resolution->index, resolution->dpi_x); if (rc) return rc; } /* set LEDs */ ratbag_profile_for_each_led(profile, led) { if (!led->dirty) continue; log_debug(device->ratbag, "LED %d changed\n", led->index); uint8_t led_mode = 0; for (unsigned int i = 0; i < ARRAY_LENGTH(ASUS_LED_MODE); i++) { if (ASUS_LED_MODE[i] == led->mode) { led_mode = i; break; } } /* convert brightness from 0-256 to 0-4 */ uint8_t led_brightness = (uint8_t)round((double)led->brightness / 64.0); rc = asus_set_led(device, led->index, led_mode, led_brightness, led->color); if (rc) return rc; } return 0; } static int asus_driver_load_profiles(struct ratbag_device *device) { int rc; struct asus_profile_data profile_data; struct ratbag_profile *profile; unsigned int current_profile_id = 0; /* get current profile id */ rc = asus_get_profile_data(device, &profile_data); if (rc) return rc; if (device->num_profiles > 1) { current_profile_id = profile_data.profile_id; log_debug(device->ratbag, "Initial profile is %d\n", current_profile_id); } log_debug( device->ratbag, "Primary version %02X.%02X.%02X\n", profile_data.version_primary_major, profile_data.version_primary_minor, profile_data.version_primary_build); log_debug( device->ratbag, "Secondary version %02X.%02X.%02X\n", profile_data.version_secondary_major, profile_data.version_secondary_minor, profile_data.version_secondary_build); /* read ratbag profiles */ ratbag_device_for_each_profile(device, profile) { if (profile->index == current_profile_id) { /* profile is already selected */ profile->is_active = true; } else { /* switch profile */ profile->is_active = false; log_debug(device->ratbag, "Switching to profile %d\n", profile->index); rc = asus_set_profile(device, profile->index); if (rc) return rc; } rc = asus_driver_load_profile(device, profile); if (rc) return rc; } /* back to initial profile */ if (device->num_profiles > 1) { log_debug(device->ratbag, "Switching back to initial profile %d\n", current_profile_id); rc = asus_set_profile(device, current_profile_id); if (rc) return rc; } return 0; } static int asus_driver_save_profiles(struct ratbag_device *device) { int rc; struct asus_profile_data profile_data; struct ratbag_profile *profile; unsigned int current_profile_id = 0; /* get current profile id */ if (device->num_profiles > 1) { rc = asus_get_profile_data(device, &profile_data); if (rc) return rc; current_profile_id = profile_data.profile_id; log_debug(device->ratbag, "Initial profile is %d\n", current_profile_id); } ratbag_device_for_each_profile(device, profile) { if (!profile->dirty) continue; log_debug(device->ratbag, "Profile %d changed\n", profile->index); /* switch profile */ if (profile->index != current_profile_id) { log_debug(device->ratbag, "Switching to profile %d\n", profile->index); rc = asus_set_profile(device, profile->index); if (rc) return rc; } rc = asus_driver_save_profile(device, profile); if (rc) return rc; /* save profile */ log_debug(device->ratbag, "Saving profile\n"); rc = asus_save_profile(device); if (rc) return rc; } /* back to initial profile */ if (device->num_profiles > 1) { log_debug(device->ratbag, "Switching back to initial profile %d\n", current_profile_id); rc = asus_set_profile(device, current_profile_id); if (rc) return rc; } return 0; } static int asus_driver_probe(struct ratbag_device *device) { int rc; unsigned int profile_count, dpi_count, button_count, led_count; const struct asus_button *asus_button; struct asus_data *drv_data; struct asus_profile_data profile_data; struct ratbag_profile *profile; struct ratbag_button *button; struct ratbag_resolution *resolution; struct ratbag_led *led; rc = ratbag_open_hidraw(device); if (rc) return rc; rc = asus_get_profile_data(device, &profile_data); if (rc) { ratbag_close_hidraw(device); return -ENODEV; } /* create device state data */ drv_data = zalloc(sizeof(*drv_data)); ratbag_set_drv_data(device, drv_data); drv_data->is_ready = 1; /* get device properties from configuration file */ profile_count = ratbag_device_data_asus_get_profile_count(device->data); dpi_count = ratbag_device_data_asus_get_dpi_count(device->data); button_count = ratbag_device_data_asus_get_button_count(device->data); led_count = ratbag_device_data_asus_get_led_count(device->data); const int *bm = ratbag_device_data_asus_get_button_mapping(device->data); /* merge ButtonMapping configuration property with defaults */ for (unsigned int i = 0; i < ASUS_MAX_NUM_BUTTON; i++) { drv_data->button_mapping[i] = bm[i] != -1 ? bm[i] : ASUS_CONFIG_BUTTON_MAPPING[i]; drv_data->button_indices[i] = -1; } /* setup a lookup table for all defined buttons */ unsigned int button_index = 0; ARRAY_FOR_EACH(ASUS_BUTTON_MAPPING, asus_button) { /* search for this button in the ButtonMapping by it's ASUS code */ for (unsigned int i = 0; i < ASUS_MAX_NUM_BUTTON; i++) { if (drv_data->button_mapping[i] == (int)asus_button->asus_code) { /* add button to indices array */ drv_data->button_indices[button_index] = (int)i; log_debug(device->ratbag, "Button %d is mapped to 0x%02x\n", button_index, (uint8_t)drv_data->button_mapping[i]); button_index++; break; } } } /* init profiles */ ratbag_device_init_profiles( device, max(profile_count, 1), max(dpi_count, 2), max(button_count, 8), max(led_count, 0)); /* setup profiles */ ratbag_device_for_each_profile(device, profile) { if (profile->index == 0) { profile->is_active = true; } asus_setup_profile(device, profile); ratbag_profile_for_each_button(profile, button) asus_setup_button(device, button); ratbag_profile_for_each_resolution(profile, resolution) asus_setup_resolution(device, resolution); ratbag_profile_for_each_led(profile, led) asus_setup_led(device, led); } /* load profiles */ rc = asus_driver_load_profiles(device); if (rc == ASUS_STATUS_ERROR) { /* mouse in invalid state */ drv_data->is_ready = 0; } else if (rc) { /* other errors */ log_error( device->ratbag, "Can't talk to the mouse: '%s' (%d)\n", strerror(-rc), rc); free(drv_data); ratbag_set_drv_data(device, NULL); return -ENODEV; } return 0; } static void asus_driver_remove(struct ratbag_device *device) { ratbag_close_hidraw(device); free(ratbag_get_drv_data(device)); } static int asus_driver_commit(struct ratbag_device *device) { int rc; struct asus_data *drv_data; /* check last device state */ drv_data = ratbag_get_drv_data(device); if (!drv_data->is_ready) { /* device was not ready */ log_error(device->ratbag, "Device was not ready, trying to reload\n"); rc = asus_driver_load_profiles(device); if (rc) { log_error(device->ratbag, "Device reloading failed (%d)\n", rc); if (rc != ASUS_STATUS_ERROR) return rc; } else { drv_data->is_ready = 1; log_error(device->ratbag, "Device was successfully reloaded\n"); } return RATBAG_ERROR_DEVICE; /* fail in any case because we tried to rollback instead of commit */ } /* save profiles */ rc = asus_driver_save_profiles(device); if (rc) { log_error(device->ratbag, "Commit failed (%d)\n", rc); if (rc != ASUS_STATUS_ERROR) return rc; return RATBAG_ERROR_DEVICE; } return 0; } struct ratbag_driver asus_driver = { .name = "ASUS", .id = "asus", .probe = asus_driver_probe, .remove = asus_driver_remove, .commit = asus_driver_commit, .set_active_profile = asus_set_profile, }; libratbag-0.18/src/driver-etekcity.c000066400000000000000000000475061467456100500174620ustar00rootroot00000000000000/* * 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 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_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); ratbag_button_enable_action_type(button, 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_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)); profile->hz = 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 = &button->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 = &button->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_macro(button); 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(struct ratbag_resolution *resolution) { 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; const unsigned int dpi_x = resolution->dpi_x; const unsigned int dpi_y = resolution->dpi_y; 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)); } static int etekcity_commit(struct ratbag_device *device) { int rc = 0; struct ratbag_button *button = NULL; struct ratbag_profile *profile = NULL; struct ratbag_resolution *resolution = NULL; ratbag_device_for_each_profile(device, profile) { if (!profile->dirty) continue; ratbag_profile_for_each_resolution(profile, resolution) { if (!resolution->dirty) continue; rc = etekcity_write_resolution(resolution); if (rc) return rc; } ratbag_profile_for_each_button(profile, button) { if (!button->dirty) continue; rc = etekcity_write_button(button); if (rc) return rc; } rc = etekcity_write_profile(profile); if (rc) return rc; } return 0; } struct ratbag_driver etekcity_driver = { .name = "EtekCity", .id = "etekcity", .probe = etekcity_probe, .remove = etekcity_remove, .commit = etekcity_commit, .set_active_profile = etekcity_set_current_profile, }; libratbag-0.18/src/driver-gskill.c000066400000000000000000001174331467456100500171230ustar00rootroot00000000000000/* * 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"); 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 miscellaneous 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)); profile->hz = 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; ratbag_button_enable_action_type(button, 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_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; /* FIXME: There is almost no chance this is correct. */ switch (bcfg->params.mouse.button_mask) { case GSKILL_BTN_MASK_LEFT: act->action.button = 1; break; case GSKILL_BTN_MASK_RIGHT: act->action.button = 3; break; case GSKILL_BTN_MASK_MIDDLE: act->action.button = 2; break; case GSKILL_BTN_MASK_SIDE: act->action.button = 15; break; case GSKILL_BTN_MASK_EXTRA: act->action.button = 14; break; } break; case GSKILL_BUTTON_FUNCTION_KBD: act->type = RATBAG_BUTTON_ACTION_TYPE_KEY; act->action.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 = 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; /* FIXME: There is almost no chance this is correct. */ switch (action->action.button) { case 1: bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_LEFT; break; case 3: bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_RIGHT; break; case 2: bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_MIDDLE; break; case 15: bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_SIDE; break; case 14: 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); 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); 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 in between 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.18/src/driver-hidpp10.c000066400000000000000000000511311467456100500170730ustar00rootroot00000000000000/* * 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; unsigned int modifiers = 0; 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 = ratbag_hidraw_get_keycode_from_keyboard_usage(device, profile.buttons[button->index].keys.key); modifiers = profile.buttons[button->index].keys.modifier_flags; break; case PROFILE_BUTTON_TYPE_CONSUMER_CONTROL: button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; button->action.action.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]); } } if (button->action.type == RATBAG_BUTTON_ACTION_TYPE_KEY) { ret = ratbag_button_macro_new_from_keycode(button, button->action.action.key, modifiers); if (ret < 0) { log_error(device->ratbag, "hidpp10: 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_MACRO); } static void hidpp10drv_read_button(struct ratbag_button *button) { 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: case HIDPP10_PROFILE_G700: case HIDPP10_PROFILE_G9: hidpp10drv_map_button(device, hidpp10, button); break; default: break; } ratbag_button_enable_action_type(button, 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); } 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; unsigned int modifiers, key; int rc; 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); if (code == 0) { code = ratbag_hidraw_get_consumer_usage_from_keycode(device, action->action.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: rc = ratbag_action_keycode_from_macro(action, &key, &modifiers); if (rc < 0) { log_error(device->ratbag, "hidpp10: can't convert macro action to keycode in button %d\n", button->index); return -EINVAL; } code = ratbag_hidraw_get_keyboard_usage_from_keycode(device, key); if (code == 0) { code = ratbag_hidraw_get_consumer_usage_from_keycode(device, 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; profile->buttons[button->index].keys.modifier_flags = modifiers; } break; 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[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)); profile->hz = 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, (enum ratbag_log_priority)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; if (profile->name != NULL) 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) { log_error(device->ratbag, "hidpp10: failed to update buttons (%d)\n", 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) { log_error(device->ratbag, "hidpp10: failed to set profile (%d)\n", 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) { log_error(device->ratbag, "hidpp10: failed to set active resolution (%d)\n", 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; 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; rc = ratbag_device_data_hidpp10_get_profile_count(device->data); if (rc == -1) { log_error(device->ratbag, "Device %s has no profile count set, even though profiles are enabled. " "Please adjust the .device file.\n", device->name); } else { profile_count = (unsigned int)rc; }; } 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. */ rc = hidpp10_device_new(&base, device_idx, type, profile_count, &dev); if (rc) { 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; } { struct ratbag_profile *profile; 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; ratbag_close_hidraw(device); drv_data = ratbag_get_drv_data(device); if (!drv_data) return; hidpp10_device_destroy(drv_data->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.18/src/driver-hidpp20.c000066400000000000000000001412541467456100500171020ustar00rootroot00000000000000/* * 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_ADJUSTABLE_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__); 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_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); } 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_NOOP: button->action.type = RATBAG_BUTTON_ACTION_TYPE_NONE; break; 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 = 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 = 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) { rc = ratbag_button_macro_new_from_keycode(button, button->action.action.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_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 (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; } 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; } 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, 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); if (code == 0) { subtype = HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL; code = ratbag_hidraw_get_consumer_usage_from_keycode(device, action->action.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; case RATBAG_BUTTON_ACTION_TYPE_NONE: profile->buttons[button->index].disabled.type = HIDPP20_BUTTON_HID_TYPE_NOOP; 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 = {0}; 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_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_ADJUSTABLE_REPORT_RATE_8060) { rc = hidpp20drv_read_report_rate_8060(device); if (rc < 0 && drv_data->report_rates[0] == 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 (resolution->is_disabled) { if (!ratbag_resolution_has_capability(resolution, RATBAG_RESOLUTION_CAP_DISABLE)) return -ENOTSUP; dpi = dpi_x = dpi_y = 0; } 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]; if (!resolution->is_disabled) { /* 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_ADJUSTABLE_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); log_raw(device->ratbag, "num_control %d\n", rc); 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; int dpi; profile->is_enabled = drv_data->profiles->profiles[profile->index].enabled; profile->is_active = (profile->index == drv_data->profiles->active_profile_index); 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]; dpi = p->dpi[res->index]; /* If the resolution is zero dpi it is disabled, * but internally we set the minimum value */ if (dpi == 0) { res->is_disabled = true; dpi = sensor->dpi_min; } ratbag_resolution_set_resolution(res, dpi, dpi); 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); profile->hz = 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_resolution *resolution; 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); ratbag_profile_for_each_resolution(profile, resolution) { ratbag_resolution_set_cap(resolution, RATBAG_RESOLUTION_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_ADJUSTABLE_REPORT_RATE_8060; break; } case HIDPP_PAGE_COLOR_LED_EFFECTS: { /* The 8070 feature 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 (%d)\n", rc); 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 (%d)\n", rc); 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 (%d)\n", rc); 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 (%d)\n", rc); 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 (%d)\n", rc); 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, (enum ratbag_log_priority)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; int num; num = ratbag_device_data_hidpp20_get_led_count(device->data); if (num >= 0) drv_data->num_leds = num; num = ratbag_device_data_hidpp20_get_button_count(device->data); if (num >= 0) drv_data->num_buttons = num; num = ratbag_device_data_hidpp20_get_report_rate(device->data); if (num >= 0) drv_data->report_rates[0] = num; 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.18/src/driver-logitech-g300.c000066400000000000000000000413731467456100500201020ustar00rootroot00000000000000/* * 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_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 separately */ { 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_NONE); 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_MACRO); 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->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; default: /* Should've been handled by the assertion above. */ abort(); 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); profile->hz = 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.18/src/driver-logitech-g600.c000066400000000000000000000433441467456100500201050ustar00rootroot00000000000000/* * 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 41 // 20 buttons + 1 color buffer + 20 G-shift #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_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 separately */ { 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_NONE); 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_MACRO); 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->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; default: /* Should've been handled by the assertion above. */ abort(); 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)); profile->hz = 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; } // For now the default will copy over the main color into the g-shift color // future update may add support to set this via cli command report->g_shift_color[0] = report->led_red; report->g_shift_color[1] = report->led_green; report->g_shift_color[2] = report->led_blue; 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.18/src/driver-marsgaming/000077500000000000000000000000001467456100500176065ustar00rootroot00000000000000libratbag-0.18/src/driver-marsgaming/driver-marsgaming.c000066400000000000000000000007701467456100500233740ustar00rootroot00000000000000#include "libratbag-private.h" #include "marsgaming-probe.h" #include "marsgaming-commit.h" static void marsgaming_remove(struct ratbag_device *device) { struct ratbag_profile *profile; ratbag_device_for_each_profile(device, profile) { free(profile->drv_data); } } struct ratbag_driver marsgaming_driver = { .name = "Mars Gaming", .id = "marsgaming", .probe = marsgaming_probe, .commit = marsgaming_commit, .remove = marsgaming_remove, .set_active_profile = marsgaming_set_active_profile }; libratbag-0.18/src/driver-marsgaming/marsgaming-buttons.c000066400000000000000000000355701467456100500236050ustar00rootroot00000000000000#include "marsgaming-buttons.h" struct marsgaming_button_action_mapping { enum marsgaming_button_action marsgaming_action_id; struct ratbag_button_action ratbag_action; }; /** * This array contains only the actions that can be statically generated. * This array is used in the marsgaming_button_action_lookup function. * For any action that cannot be statically generated (has variables in it) * we use the rest of the marsgaming_button_action_* functions */ static const struct marsgaming_button_action_mapping marsgaming_mm4_button_action_mapping[] = { { MARSGAMING_MM4_ACTION_LEFT_CLICK, BUTTON_ACTION_BUTTON(1) }, { MARSGAMING_MM4_ACTION_RIGHT_CLICK, BUTTON_ACTION_BUTTON(2) }, { MARSGAMING_MM4_ACTION_MIDDLE_CLICK, BUTTON_ACTION_BUTTON(3) }, { MARSGAMING_MM4_ACTION_BACKWARD, BUTTON_ACTION_BUTTON(4) }, { MARSGAMING_MM4_ACTION_FORWARD, BUTTON_ACTION_BUTTON(5) }, { MARSGAMING_MM4_ACTION_DPI_SWITCH, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) }, { MARSGAMING_MM4_ACTION_DPI_MINUS, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) }, { MARSGAMING_MM4_ACTION_DPI_PLUS, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) }, { MARSGAMING_MM4_ACTION_PROFILE_SWITCH, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) } }; static struct ratbag_button_action marsgaming_button_action_lookup(struct ratbag_button *button, const struct marsgaming_button_info *button_info) { const struct marsgaming_button_action_mapping *button_mapping; ARRAY_FOR_EACH(marsgaming_mm4_button_action_mapping, button_mapping) { if (button_mapping->marsgaming_action_id == button_info->action) return button_mapping->ratbag_action; } return (struct ratbag_button_action)BUTTON_ACTION_UNKNOWN; } static struct ratbag_button_action marsgaming_button_action_media(struct ratbag_button *button, const struct marsgaming_button_info *button_info) { // TODO: Convert from marsgaming media to ratbag media key codes return (struct ratbag_button_action)BUTTON_ACTION_UNKNOWN; } static int marsgaming_ratbag_button_macro_from_combo_keycode(struct ratbag_button *button, unsigned int key0, unsigned int key1, unsigned int modifiers); static struct ratbag_button_action marsgaming_button_action_key(struct ratbag_button *button, const struct marsgaming_button_info *button_info) { // Single and combo keys share some structure, so we will treat them all like combo keys const uint8_t mods = button_info->action_info.combo_key.modifiers; const uint8_t key0 = button_info->action_info.combo_key.keys[0]; const uint8_t key1 = button_info->action_info.combo_key.keys[1]; const unsigned int event_key0 = ratbag_hidraw_get_keycode_from_keyboard_usage(button->profile->device, key0); if (key1 == 0) { ratbag_button_macro_new_from_keycode(button, event_key0, mods); return button->action; } const unsigned int event_key1 = ratbag_hidraw_get_keycode_from_keyboard_usage(button->profile->device, key1); marsgaming_ratbag_button_macro_from_combo_keycode(button, event_key0, event_key1, mods); return button->action; } enum marsgaming_modifier_mask_offset { MARSGAMING_MODIFIER_LEFTCTRL = 1 << 0, MARSGAMING_MODIFIER_LEFTSHIFT = 1 << 1, MARSGAMING_MODIFIER_LEFTALT = 1 << 2, MARSGAMING_MODIFIER_LEFTMETA = 1 << 3, MARSGAMING_MODIFIER_RIGHTCTRL = 1 << 4, MARSGAMING_MODIFIER_RIGHTSHIFT = 1 << 5, MARSGAMING_MODIFIER_RIGHTALT = 1 << 6, MARSGAMING_MODIFIER_RIGHTMETA = 1 << 7, }; struct marsgaming_modifier_mapping { unsigned int modifier_mask; unsigned int key; }; static const struct marsgaming_modifier_mapping marsgaming_modifier_mapping[] = { { MARSGAMING_MODIFIER_LEFTCTRL, KEY_LEFTCTRL }, { MARSGAMING_MODIFIER_LEFTSHIFT, KEY_LEFTSHIFT }, { MARSGAMING_MODIFIER_LEFTALT, KEY_LEFTALT }, { MARSGAMING_MODIFIER_LEFTMETA, KEY_LEFTMETA }, { MARSGAMING_MODIFIER_RIGHTCTRL, KEY_RIGHTCTRL }, { MARSGAMING_MODIFIER_RIGHTSHIFT, KEY_RIGHTSHIFT }, { MARSGAMING_MODIFIER_RIGHTALT, KEY_RIGHTALT }, { MARSGAMING_MODIFIER_RIGHTMETA, KEY_RIGHTMETA }, }; static int marsgaming_ratbag_button_macro_from_combo_keycode(struct ratbag_button *button, unsigned int key0, unsigned int key1, unsigned int modifiers) { const struct marsgaming_modifier_mapping *mapping; struct ratbag_button_macro *macro = ratbag_button_macro_new("combo-key"); int i = 0; ARRAY_FOR_EACH(marsgaming_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, key0); ratbag_button_macro_set_event(macro, i++, RATBAG_MACRO_EVENT_KEY_PRESSED, key1); ratbag_button_macro_set_event(macro, i++, RATBAG_MACRO_EVENT_KEY_RELEASED, key1); ratbag_button_macro_set_event(macro, i++, RATBAG_MACRO_EVENT_KEY_RELEASED, key0); ARRAY_FOR_EACH(marsgaming_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; } static struct ratbag_button_action marsgaming_button_action_macro(struct ratbag_button *button, const struct marsgaming_button_info *button_info) { // TODO: Create a ratbag macro from the marsgaming macro return (struct ratbag_button_action)BUTTON_ACTION_UNKNOWN; } static struct ratbag_button_action marsgaming_button_action_fire(struct ratbag_button *button, const struct marsgaming_button_info *button_info) { // There's no way to convert this to ratbag structs, so we'll return unknown return (struct ratbag_button_action)BUTTON_ACTION_UNKNOWN; } #define MARSGAMING_BUTTON_ACTION_NONE \ { \ .action = MARSGAMING_MM4_ACTION_MEDIA, .action_info.media.media_key = 0 \ } static struct marsgaming_button_info marsgaming_from_ratbag_to_action_none(struct ratbag_button *button) { return (struct marsgaming_button_info)MARSGAMING_BUTTON_ACTION_NONE; } struct marsgaming_from_ratbag_to_button_map { uint8_t button_id; struct marsgaming_button_info button_info; }; static const struct marsgaming_from_ratbag_to_button_map marsgaming_from_ratbag_to_button_maps[] = { { 1, { .action = MARSGAMING_MM4_ACTION_LEFT_CLICK } }, { 2, { .action = MARSGAMING_MM4_ACTION_RIGHT_CLICK } }, { 3, { .action = MARSGAMING_MM4_ACTION_MIDDLE_CLICK } }, { 4, { .action = MARSGAMING_MM4_ACTION_BACKWARD } }, { 5, { .action = MARSGAMING_MM4_ACTION_FORWARD } }, }; static struct marsgaming_button_info marsgaming_from_ratbag_to_action_button(struct ratbag_button *button) { const unsigned int button_id = button->action.action.button; const struct marsgaming_from_ratbag_to_button_map *map; ARRAY_FOR_EACH(marsgaming_from_ratbag_to_button_maps, map) { if (button_id == map->button_id) return map->button_info; } return (struct marsgaming_button_info)MARSGAMING_BUTTON_ACTION_NONE; } struct marsgaming_from_ratbag_to_special_map { enum ratbag_button_action_special special_id; struct marsgaming_button_info button_info; }; static const struct marsgaming_from_ratbag_to_special_map marsgaming_from_ratbag_to_special_maps[] = { { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP, { .action = MARSGAMING_MM4_ACTION_DPI_SWITCH } }, { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN, { .action = MARSGAMING_MM4_ACTION_DPI_MINUS } }, { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP, { .action = MARSGAMING_MM4_ACTION_DPI_PLUS } }, { RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP, { .action = MARSGAMING_MM4_ACTION_PROFILE_SWITCH } }, }; static struct marsgaming_button_info marsgaming_from_ratbag_to_action_special(struct ratbag_button *button) { const enum ratbag_button_action_special special_type = button->action.action.special; const struct marsgaming_from_ratbag_to_special_map *map; ARRAY_FOR_EACH(marsgaming_from_ratbag_to_special_maps, map) { if (special_type == map->special_id) return map->button_info; } return (struct marsgaming_button_info)MARSGAMING_BUTTON_ACTION_NONE; } static int marsgaming_keycodes_from_ratbag_macro(struct ratbag_button_action *action, unsigned int *key0_out, unsigned int *key1_out, unsigned int *mods_out); static struct marsgaming_button_info marsgaming_from_ratbag_to_action_macro(struct ratbag_button *button) { unsigned int key0; unsigned int key1; unsigned int mods; int e = marsgaming_keycodes_from_ratbag_macro(&button->action, &key0, &key1, &mods); if (e == 1 || e == 2) { // We can convert this to 2-key combo return (struct marsgaming_button_info){ .action = MARSGAMING_MM4_ACTION_COMBO_KEY, .action_info.combo_key = { .modifiers = mods, .keys[0] = ratbag_hidraw_get_keyboard_usage_from_keycode(button->profile->device, key0), .keys[1] = ratbag_hidraw_get_keyboard_usage_from_keycode(button->profile->device, key1), } }; } // TODO: Implement macros return (struct marsgaming_button_info)MARSGAMING_BUTTON_ACTION_NONE; } static int marsgaming_keycodes_from_ratbag_macro(struct ratbag_button_action *action, unsigned int *key0_out, unsigned int *key1_out, unsigned int *modifiers_out) { struct ratbag_macro *macro = action->macro; unsigned int key0 = KEY_RESERVED; unsigned int key1 = KEY_RESERVED; unsigned int modifiers = 0; unsigned int i; unsigned int keys_pressed = 0; if (!macro || action->type != RATBAG_BUTTON_ACTION_TYPE_MACRO) return -EINVAL; if (macro->events[0].type == RATBAG_MACRO_EVENT_NONE) return -EINVAL; { unsigned int num_keys = ratbag_action_macro_num_keys(action); if (num_keys == 0 || num_keys > 2) 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 |= MARSGAMING_MODIFIER_LEFTCTRL; break; case KEY_LEFTSHIFT: modifiers |= MARSGAMING_MODIFIER_LEFTSHIFT; break; case KEY_LEFTALT: modifiers |= MARSGAMING_MODIFIER_LEFTALT; break; case KEY_LEFTMETA: modifiers |= MARSGAMING_MODIFIER_LEFTMETA; break; case KEY_RIGHTCTRL: modifiers |= MARSGAMING_MODIFIER_RIGHTCTRL; break; case KEY_RIGHTSHIFT: modifiers |= MARSGAMING_MODIFIER_RIGHTSHIFT; break; case KEY_RIGHTALT: modifiers |= MARSGAMING_MODIFIER_RIGHTALT; break; case KEY_RIGHTMETA: modifiers |= MARSGAMING_MODIFIER_RIGHTMETA; break; default: if (key0 == KEY_RESERVED) { key0 = event.event.key; } else if (key1 == KEY_RESERVED) { key1 = event.event.key; } else { return -EINVAL; } ++keys_pressed; } break; case RATBAG_MACRO_EVENT_KEY_RELEASED: switch (event.event.key) { case KEY_LEFTCTRL: modifiers &= ~MARSGAMING_MODIFIER_LEFTCTRL; break; case KEY_LEFTSHIFT: modifiers &= ~MARSGAMING_MODIFIER_LEFTSHIFT; break; case KEY_LEFTALT: modifiers &= ~MARSGAMING_MODIFIER_LEFTALT; break; case KEY_LEFTMETA: modifiers &= ~MARSGAMING_MODIFIER_LEFTMETA; break; case KEY_RIGHTCTRL: modifiers &= ~MARSGAMING_MODIFIER_RIGHTCTRL; break; case KEY_RIGHTSHIFT: modifiers &= ~MARSGAMING_MODIFIER_RIGHTSHIFT; break; case KEY_RIGHTALT: modifiers &= ~MARSGAMING_MODIFIER_RIGHTALT; break; case KEY_RIGHTMETA: modifiers &= ~MARSGAMING_MODIFIER_RIGHTMETA; break; default: // As soon as we release a key that we pressed, return what we have processed so far if (event.event.key == key0 || event.event.key == key1) { *key0_out = key0; *key1_out = key1; *modifiers_out = modifiers; return (int)keys_pressed; } return -EINVAL; } case RATBAG_MACRO_EVENT_WAIT: break; default: return -EINVAL; } } return -EINVAL; } struct marsgaming_button_action_to_ratbag_parser { enum marsgaming_button_action marsgaming_action_id; struct ratbag_button_action (*parse_action)(struct ratbag_button *button, const struct marsgaming_button_info *button_info); }; static const struct marsgaming_button_action_to_ratbag_parser marsgaming_button_action_to_ratbag_parsers[] = { { MARSGAMING_MM4_ACTION_LEFT_CLICK, marsgaming_button_action_lookup }, { MARSGAMING_MM4_ACTION_RIGHT_CLICK, marsgaming_button_action_lookup }, { MARSGAMING_MM4_ACTION_MIDDLE_CLICK, marsgaming_button_action_lookup }, { MARSGAMING_MM4_ACTION_BACKWARD, marsgaming_button_action_lookup }, { MARSGAMING_MM4_ACTION_FORWARD, marsgaming_button_action_lookup }, { MARSGAMING_MM4_ACTION_DPI_SWITCH, marsgaming_button_action_lookup }, { MARSGAMING_MM4_ACTION_DPI_MINUS, marsgaming_button_action_lookup }, { MARSGAMING_MM4_ACTION_DPI_PLUS, marsgaming_button_action_lookup }, { MARSGAMING_MM4_ACTION_PROFILE_SWITCH, marsgaming_button_action_lookup }, { MARSGAMING_MM4_ACTION_MEDIA, marsgaming_button_action_media }, { MARSGAMING_MM4_ACTION_COMBO_KEY, marsgaming_button_action_key }, { MARSGAMING_MM4_ACTION_SINGLE_KEY, marsgaming_button_action_key }, { MARSGAMING_MM4_ACTION_MACRO, marsgaming_button_action_macro }, { MARSGAMING_MM4_ACTION_FIRE, marsgaming_button_action_fire }, }; struct ratbag_button_action marsgaming_parse_button_to_action(struct ratbag_button *button, const struct marsgaming_button_info *button_info) { const struct marsgaming_button_action_to_ratbag_parser *parser; ARRAY_FOR_EACH(marsgaming_button_action_to_ratbag_parsers, parser) { if (button_info->action == parser->marsgaming_action_id) return parser->parse_action(button, button_info); } // If no action matches, set it to unknown return (struct ratbag_button_action)BUTTON_ACTION_UNKNOWN; } struct marsgaming_from_ratbag_button_action_to_parser { enum ratbag_button_action_type ratbag_action_type; struct marsgaming_button_info (*parse_action)(struct ratbag_button *button); }; static const struct marsgaming_from_ratbag_button_action_to_parser marsgaming_button_action_to_marsgaming_parsers[] = { { RATBAG_BUTTON_ACTION_TYPE_NONE, marsgaming_from_ratbag_to_action_none }, { RATBAG_BUTTON_ACTION_TYPE_BUTTON, marsgaming_from_ratbag_to_action_button }, { RATBAG_BUTTON_ACTION_TYPE_SPECIAL, marsgaming_from_ratbag_to_action_special }, { RATBAG_BUTTON_ACTION_TYPE_MACRO, marsgaming_from_ratbag_to_action_macro }, { RATBAG_BUTTON_ACTION_TYPE_UNKNOWN, marsgaming_from_ratbag_to_action_none }, }; struct marsgaming_optional_button_info marsgaming_button_of_type(struct ratbag_button *button) { const struct marsgaming_from_ratbag_button_action_to_parser *parser; ARRAY_FOR_EACH(marsgaming_button_action_to_marsgaming_parsers, parser) { if (button->action.type != parser->ratbag_action_type) continue; return (struct marsgaming_optional_button_info){ .is_present = 1, .button_info = parser->parse_action(button) }; } return (struct marsgaming_optional_button_info){ .is_present = 0, .none = 1 }; } libratbag-0.18/src/driver-marsgaming/marsgaming-buttons.h000066400000000000000000000050121467456100500235760ustar00rootroot00000000000000#pragma once #include "libratbag-private.h" enum marsgaming_button_action { MARSGAMING_MM4_ACTION_LEFT_CLICK = 0x01, MARSGAMING_MM4_ACTION_RIGHT_CLICK = 0x02, MARSGAMING_MM4_ACTION_MIDDLE_CLICK = 0x03, MARSGAMING_MM4_ACTION_BACKWARD = 0x04, MARSGAMING_MM4_ACTION_FORWARD = 0x05, MARSGAMING_MM4_ACTION_UNKNOWN_6 = 0x06, MARSGAMING_MM4_ACTION_UNKNOWN_7 = 0x07, MARSGAMING_MM4_ACTION_DPI_SWITCH = 0x08, // DPI_CYCLE_UP MARSGAMING_MM4_ACTION_DPI_MINUS = 0x09, MARSGAMING_MM4_ACTION_DPI_PLUS = 0x0a, MARSGAMING_MM4_ACTION_UNKNOWN_B = 0x0b, MARSGAMING_MM4_ACTION_UNKNOWN_C = 0x0c, MARSGAMING_MM4_ACTION_PROFILE_SWITCH = 0x0d, MARSGAMING_MM4_ACTION_DISABLE = 0x0e, // Same code as media, but with null additional data. Will handle them the same MARSGAMING_MM4_ACTION_MEDIA = 0x0e, MARSGAMING_MM4_ACTION_COMBO_KEY = 0x0f, MARSGAMING_MM4_ACTION_SINGLE_KEY = 0x10, MARSGAMING_MM4_ACTION_MACRO = 0x11, // TODO: Implement macros MARSGAMING_MM4_ACTION_FIRE = 0x12 // Execute left button key X times with specified delay } __attribute__((packed)); _Static_assert(sizeof(enum marsgaming_button_action) == sizeof(uint8_t), "enum marsgaming_button_action should be 1 byte"); struct marsgaming_button_action_media_data { uint8_t zero_1; uint8_t media_key; uint8_t zero_3; }; struct marsgaming_button_action_combo_key_data { uint8_t modifiers; uint8_t keys[2]; }; struct marsgaming_button_action_single_key_data { uint8_t zero_0; uint8_t key; uint8_t zero_2; }; struct marsgaming_button_action_macro_data { uint8_t macro_id; uint8_t macro_length; // Maybe? uint8_t unknown_2; }; struct marsgaming_button_action_fire_data { uint8_t times; uint8_t delay_ms; uint8_t unknown_2; }; struct marsgaming_button_info { enum marsgaming_button_action action; union { struct marsgaming_button_action_media_data media; struct marsgaming_button_action_combo_key_data combo_key; struct marsgaming_button_action_single_key_data single_key; struct marsgaming_button_action_macro_data macro; struct marsgaming_button_action_fire_data fire; } action_info; }; _Static_assert(sizeof(struct marsgaming_button_info) == 4, "Marsgaming button info is not 4 bytes long"); struct marsgaming_optional_button_info { unsigned is_present; union { uint32_t none; struct marsgaming_button_info button_info; }; }; struct ratbag_button_action marsgaming_parse_button_to_action(struct ratbag_button *button, const struct marsgaming_button_info *button_info); struct marsgaming_optional_button_info marsgaming_button_of_type(struct ratbag_button *button); libratbag-0.18/src/driver-marsgaming/marsgaming-command.c000066400000000000000000000072241467456100500235200ustar00rootroot00000000000000#include "marsgaming-command.h" #include "marsgaming-leds.h" #include "marsgaming-protocol.h" void marsgaming_command_set_current_profile(struct ratbag_device *device, unsigned int profile) { uint8_t writedata[16] = { 0x02, 0x02, 0x43, 0x00, 0x01, 0x00, 0xfa, 0xfa }; writedata[8] = (uint8_t)profile; ratbag_hidraw_set_feature_report(device, writedata[0], writedata, ARRAY_LENGTH(writedata)); } void marsgaming_command_profile_set_polling_interval(struct ratbag_profile *profile, uint8_t polling_interval) { uint8_t writedata[16] = { 0x02, 0x02, 0x48 /*lower 3 bits = profile*/, 0x00, 0x01, 0x00, 0xfa, 0xfa }; writedata[2] |= (uint8_t)profile->index; writedata[8] = polling_interval; ratbag_hidraw_set_feature_report(profile->device, writedata[0], writedata, ARRAY_LENGTH(writedata)); } void marsgaming_command_profile_set_led(struct ratbag_led *led) { uint8_t marsgaming_brightness; uint8_t marsgaming_breathing; switch (led->mode) { case RATBAG_LED_OFF: marsgaming_brightness = 0; marsgaming_breathing = MARSGAMING_LED_BREATHING_OFF; break; case RATBAG_LED_ON: marsgaming_brightness = led->brightness * 3 / 255; marsgaming_breathing = MARSGAMING_LED_BREATHING_OFF; break; case RATBAG_LED_BREATHING: case RATBAG_LED_CYCLE: // Not supported by the mouse, let's pretend it's breathing marsgaming_brightness = led->brightness * 3 / 255; marsgaming_breathing = led->ms / 2000; break; } if (led->profile->is_active) { uint8_t writedata[16] = { 0x02, 0x04, 0x00 /*red*/, 0x00 /*green*/, 0x00 /*blue*/, 0x00 /*brightness*/, 0x00 /*breathing*/, 0x01 }; writedata[2] = 0xff - (uint8_t)led->color.red; writedata[3] = 0xff - (uint8_t)led->color.green; writedata[4] = 0xff - (uint8_t)led->color.blue; writedata[5] = marsgaming_brightness; // Brightness level writedata[6] = marsgaming_breathing; // Breathing mode ratbag_hidraw_set_feature_report(led->profile->device, writedata[0], writedata, ARRAY_LENGTH(writedata)); } { uint8_t writedata[16] = { 0x02, 0x02, 0xf1, /*profile*/ 0x00, 0x06, 0x00 /*led_id???*/, 0xfa, 0xfa }; writedata[3] = (uint8_t)led->profile->index; writedata[8] = 0xff - (uint8_t)led->color.red; writedata[9] = 0xff - (uint8_t)led->color.green; writedata[10] = 0xff - (uint8_t)led->color.blue; writedata[11] = marsgaming_brightness; // Brightness level writedata[12] = marsgaming_breathing; // Breathing mode ratbag_hidraw_set_feature_report(led->profile->device, writedata[0], writedata, ARRAY_LENGTH(writedata)); } } void marsgaming_command_profile_set_resolutions(struct ratbag_profile *profile) { struct marsgaming_report_resolutions read_report = marsgaming_profile_get_drv_data(profile)->resolutions_report; // Copy the read report to adapt it for writing struct marsgaming_report_resolutions report; memcpy(&report, &read_report, sizeof(report)); report.report_type = MARSGAMING_MM4_REPORT_TYPE_WRITE; report.unknown_6 = 0xfa; report.unknown_7 = 0xfa; for (int i = 0; i < report.count_resolutions; ++i) { // Handle endianness set_unaligned_le_u16((uint8_t*)&report.resolutions[i].x_res, report.resolutions[i].x_res); set_unaligned_le_u16((uint8_t*)&report.resolutions[i].y_res, report.resolutions[i].y_res); } ratbag_hidraw_set_feature_report(profile->device, report.usb_report_id, (uint8_t*)&report, sizeof(report)); } void marsgaming_command_profile_set_buttons(struct ratbag_profile *profile) { struct marsgaming_report_buttons report = marsgaming_profile_get_drv_data(profile)->buttons_report; report.report_type = MARSGAMING_MM4_REPORT_TYPE_WRITE; report.unknown_6 = 0xfa; report.unknown_7 = 0xfa; ratbag_hidraw_set_feature_report(profile->device, report.usb_report_id, (uint8_t*)&report, sizeof(report)); } libratbag-0.18/src/driver-marsgaming/marsgaming-command.h000066400000000000000000000007411467456100500235220ustar00rootroot00000000000000#pragma once #include "libratbag-private.h" void marsgaming_command_set_current_profile(struct ratbag_device *device, unsigned int profile); void marsgaming_command_profile_set_polling_interval(struct ratbag_profile *profile, uint8_t polling_interval); void marsgaming_command_profile_set_led(struct ratbag_led *led); void marsgaming_command_profile_set_resolutions(struct ratbag_profile *profile); void marsgaming_command_profile_set_buttons(struct ratbag_profile *profile); libratbag-0.18/src/driver-marsgaming/marsgaming-commit.c000066400000000000000000000076301467456100500233730ustar00rootroot00000000000000#include "marsgaming-commit.h" #include "marsgaming-definitions.h" #include "marsgaming-probe.h" #include "marsgaming-query.h" #include "marsgaming-protocol.h" #include "marsgaming-buttons.h" #include "marsgaming-command.h" static int marsgaming_commit_button(struct ratbag_button *button) { struct marsgaming_profile_drv_data *profile_data = marsgaming_profile_get_drv_data(button->profile); struct marsgaming_button_info *button_info = &profile_data->buttons_report.buttons[button->index]; const struct marsgaming_optional_button_info button_data = marsgaming_button_of_type(button); if (button_data.is_present) *button_info = button_data.button_info; return 0; } static int marsgaming_commit_led(struct ratbag_led *led) { if (!led->dirty) return 0; marsgaming_command_profile_set_led(led); return 0; } static int marsgaming_commit_profile_report_rate(struct ratbag_profile *profile) { if (!profile->rate_dirty) return 0; uint8_t polling_interval = 1000 / ratbag_profile_get_report_rate(profile); marsgaming_command_profile_set_polling_interval(profile, polling_interval); return 0; } static int marsgaming_commit_profile_buttons(struct ratbag_profile *profile) { bool buttons_dirty = false; struct ratbag_button *button; ratbag_profile_for_each_button(profile, button) { if (button->dirty) { buttons_dirty = true; break; } } if (!buttons_dirty) return 0; ratbag_profile_for_each_button(profile, button) { if (!button->dirty) continue; marsgaming_commit_button(button); } marsgaming_command_profile_set_buttons(profile); return 0; } static int marsgaming_commit_profile_leds(struct ratbag_profile *profile) { struct ratbag_led *led; ratbag_profile_for_each_led(profile, led) { marsgaming_commit_led(led); } return 0; } static int marsgaming_commit_profile_resolutions(struct ratbag_profile *profile) { bool resolutions_dirty = false; struct ratbag_resolution *resolution; ratbag_profile_for_each_resolution(profile, resolution) { if (resolution->dirty) { resolutions_dirty = true; break; } } if (!resolutions_dirty) return 0; struct marsgaming_profile_drv_data *profile_data = marsgaming_profile_get_drv_data(profile); ratbag_profile_for_each_resolution(profile, resolution) { if (!resolution->dirty) continue; // Modify the drv_data report so we can send it to the mouse struct marsgaming_report_resolution_info *resolution_info = &profile_data->resolutions_report.resolutions[resolution->index]; resolution_info->enabled = true; resolution_info->x_res = resolution->dpi_x / MARSGAMING_MM4_RES_SCALING; resolution_info->y_res = resolution->dpi_y / MARSGAMING_MM4_RES_SCALING; resolution_info->led_bitset = ~((~0x0U) << resolution->index); } marsgaming_command_profile_set_resolutions(profile); return 0; } static int marsgaming_commit_profile(struct ratbag_profile *profile) { if (!profile->dirty) return 0; marsgaming_commit_profile_report_rate(profile); marsgaming_commit_profile_resolutions(profile); marsgaming_commit_profile_buttons(profile); marsgaming_commit_profile_leds(profile); return 0; } static int marsgaming_commit_profiles(struct ratbag_device *device) { uint8_t current_profile = marsgaming_query_current_profile(device); struct ratbag_profile *profile; ratbag_device_for_each_profile(device, profile) { // The user could change the current profile between probe and commit // We need to modify the active profile for the led changes to take effect // Unsure how this will interact with internals of ratbag profile->is_active = current_profile == profile->index; marsgaming_commit_profile(profile); } return 0; } int marsgaming_commit(struct ratbag_device *device) { ratbag_open_hidraw(device); marsgaming_commit_profiles(device); marsgaming_release_device(device); return 0; } int marsgaming_set_active_profile(struct ratbag_device *device, unsigned int profile) { marsgaming_command_set_current_profile(device, profile); return 0; } libratbag-0.18/src/driver-marsgaming/marsgaming-commit.h000066400000000000000000000002711467456100500233720ustar00rootroot00000000000000#pragma once #include "libratbag-private.h" int marsgaming_commit(struct ratbag_device *device); int marsgaming_set_active_profile(struct ratbag_device *device, unsigned int profile); libratbag-0.18/src/driver-marsgaming/marsgaming-definitions.h000066400000000000000000000004351467456100500244170ustar00rootroot00000000000000#pragma once #define MARSGAMING_MM4_NUM_PROFILES 5 #define MARSGAMING_MM4_NUM_RESOLUTIONS_PER_PROFILE 5 #define MARSGAMING_MM4_NUM_BUTTONS 19 #define MARSGAMING_MM4_NUM_LED 1 #define MARSGAMING_MM4_RES_MIN 50 #define MARSGAMING_MM4_RES_MAX 16400 #define MARSGAMING_MM4_RES_SCALING 50 libratbag-0.18/src/driver-marsgaming/marsgaming-leds.c000066400000000000000000000003611467456100500230240ustar00rootroot00000000000000#include "marsgaming-leds.h" struct ratbag_color marsgaming_led_color_to_ratbag(struct marsgaming_led_color color) { return (struct ratbag_color){ .red = 0xff - color.red, .green = 0xff - color.green, .blue = 0xff - color.blue }; } libratbag-0.18/src/driver-marsgaming/marsgaming-leds.h000066400000000000000000000012021467456100500230240ustar00rootroot00000000000000#pragma once #include "stdint.h" #include "libratbag.h" #define MARSGAMING_LED_BREATHING_OFF 0 /* * Colors in Mars Gaming are encoded as the inverse of the value. * That is, a value of 0x00 means fully bright, while 0xFF * means completely dark. */ struct marsgaming_led_color { uint8_t red; uint8_t green; uint8_t blue; }; struct marsgaming_led { struct marsgaming_led_color color; uint8_t brightness; uint8_t breathing_speed; } __attribute__((packed)); _Static_assert(sizeof(struct marsgaming_led) == 5, "Marsgaming led is not 5 bytes"); struct ratbag_color marsgaming_led_color_to_ratbag(struct marsgaming_led_color color); libratbag-0.18/src/driver-marsgaming/marsgaming-probe.c000066400000000000000000000117371467456100500232150ustar00rootroot00000000000000#include "marsgaming-probe.h" #include "marsgaming-definitions.h" #include "marsgaming-query.h" static void marsgaming_probe_button_action(struct ratbag_button *button, const struct marsgaming_button_info *button_info) { struct ratbag_button_action action = marsgaming_parse_button_to_action(button, button_info); ratbag_button_set_action(button, &action); } static void marsgaming_probe_profile_leds(struct ratbag_profile *profile) { struct marsgaming_profile_drv_data *profile_data = marsgaming_profile_get_drv_data(profile); profile_data->led_report = marsgaming_query_profile_led(profile); struct ratbag_led *led; ratbag_profile_for_each_led(profile, led) { 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); led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888; struct marsgaming_report_led report = profile_data->led_report; led->color = marsgaming_led_color_to_ratbag(report.led.color); led->brightness = report.led.brightness * (255 / 3); if (report.led.brightness == 0) { led->mode = RATBAG_LED_OFF; } else if (report.led.breathing_speed == 0 || report.led.breathing_speed >= 10) { led->mode = RATBAG_LED_ON; } else { // Breathing mode led->mode = RATBAG_LED_BREATHING; led->ms = report.led.breathing_speed * 2000; } } } static void marsgaming_probe_button(struct ratbag_button *button, struct marsgaming_button_info *button_info) { 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_MACRO); marsgaming_probe_button_action(button, button_info); } static void marsgaming_probe_profile_buttons(struct ratbag_profile *profile) { struct marsgaming_profile_drv_data *profile_data = marsgaming_profile_get_drv_data(profile); profile_data->buttons_report = marsgaming_query_profile_buttons(profile); struct ratbag_button *button; ratbag_profile_for_each_button(profile, button) { marsgaming_probe_button(button, &profile_data->buttons_report.buttons[button->index]); } } static void marsgaming_probe_profile_resolutions(struct ratbag_profile *profile) { struct marsgaming_profile_drv_data *profile_data = marsgaming_profile_get_drv_data(profile); profile_data->resolutions_report = marsgaming_query_profile_resolutions(profile); struct ratbag_resolution *resolution; ratbag_profile_for_each_resolution(profile, resolution) { struct marsgaming_report_resolution_info queried_resolution = profile_data->resolutions_report.resolutions[resolution->index]; ratbag_resolution_set_dpi_list_from_range(resolution, MARSGAMING_MM4_RES_MIN, MARSGAMING_MM4_RES_MAX); resolution->is_active = profile_data->resolutions_report.current_resolution == resolution->index; resolution->dpi_x = queried_resolution.x_res * MARSGAMING_MM4_RES_SCALING; resolution->dpi_y = queried_resolution.y_res * MARSGAMING_MM4_RES_SCALING; ratbag_resolution_set_cap(resolution, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION); } } static void marsgaming_probe_profile_report_rate(struct ratbag_profile *profile) { static const unsigned int rates[] = { 125, 250, 500, 1000 }; ratbag_profile_set_report_rate_list(profile, rates, ARRAY_LENGTH(rates)); const uint8_t interval = marsgaming_query_profile_polling_interval(profile); profile->hz = 1000 / interval; } static void marsgaming_probe_profiles(struct ratbag_device *device) { uint8_t current_profile = marsgaming_query_current_profile(device); struct ratbag_profile *profile; ratbag_device_for_each_profile(device, profile) { profile->drv_data = zalloc(sizeof(struct marsgaming_profile_drv_data)); profile->is_active = (profile->index == current_profile); marsgaming_probe_profile_report_rate(profile); marsgaming_probe_profile_resolutions(profile); marsgaming_probe_profile_buttons(profile); marsgaming_probe_profile_leds(profile); } } static void marsgaming_initialize_device(struct ratbag_device *device) { ratbag_device_init_profiles(device, MARSGAMING_MM4_NUM_PROFILES, MARSGAMING_MM4_NUM_RESOLUTIONS_PER_PROFILE, MARSGAMING_MM4_NUM_BUTTONS, MARSGAMING_MM4_NUM_LED); } static int marsgaming_sanity_check(struct ratbag_device *device) { int rc = ratbag_open_hidraw(device); if (rc) return rc; const uint8_t available_reports[] = { 0x02, 0x03, 0x04 }; for (size_t report_id = 0; report_id < ARRAY_LENGTH(available_reports); ++report_id) { if (!ratbag_hidraw_has_report(device, available_reports[report_id])) { ratbag_close_hidraw(device); return -ENODEV; } } return 0; } int marsgaming_probe(struct ratbag_device *device) { int rc = marsgaming_sanity_check(device); if (rc < 0) return rc; marsgaming_initialize_device(device); marsgaming_probe_profiles(device); marsgaming_release_device(device); return 0; } void marsgaming_release_device(struct ratbag_device *device) { ratbag_close_hidraw(device); } libratbag-0.18/src/driver-marsgaming/marsgaming-probe.h000066400000000000000000000002401467456100500232050ustar00rootroot00000000000000#pragma once #include "libratbag-private.h" int marsgaming_probe(struct ratbag_device *device); void marsgaming_release_device(struct ratbag_device *device); libratbag-0.18/src/driver-marsgaming/marsgaming-protocol.h000066400000000000000000000053441467456100500237510ustar00rootroot00000000000000#pragma once #include "marsgaming-buttons.h" #include "marsgaming-leds.h" enum marsgaming_report_type { MARSGAMING_MM4_REPORT_TYPE_UNKNOWN_1 = 0x01, MARSGAMING_MM4_REPORT_TYPE_WRITE = 0x02, MARSGAMING_MM4_REPORT_TYPE_READ = 0x03, MARSGAMING_MM4_REPORT_TYPE_UNKNOWN_4 = 0x04, MARSGAMING_MM4_REPORT_TYPE_UNKNOWN_6 = 0x06 } __attribute__((packed)); _Static_assert(sizeof(enum marsgaming_report_type) == sizeof(uint8_t), "enum marsgaming_report_type should be 1 byte"); struct marsgaming_report_resolution_info { bool enabled; uint16_t x_res; uint16_t y_res; // 4 lowest bits, each one corresponds to one led // Resolution 0 -> b0000 // Resolution 1 -> b0001 // Resolution 2 -> b0011 // etc uint8_t led_bitset; uint8_t zeros_3[2]; } __attribute__((packed)); struct marsgaming_report_resolutions { uint8_t usb_report_id; enum marsgaming_report_type report_type; uint8_t unknown_2; // 0x4f uint8_t profile_id; uint8_t unknown_4; // 0x2a uint8_t unknown_5; // 0x00 uint8_t unknown_6; // 0x00 from device | 0xfa from host uint8_t unknown_7; // 0x00 from device | 0xfa from host uint8_t count_resolutions; uint8_t current_resolution; struct marsgaming_report_resolution_info resolutions[6]; uint8_t padding[6]; } __attribute__((packed)); _Static_assert(sizeof(struct marsgaming_report_resolutions) == 64, "Marsgaming resolution report is not 64 bytes long"); struct marsgaming_report_buttons { uint8_t usb_report_id; enum marsgaming_report_type report_type; uint8_t unknown_2; // 0x90 uint8_t profile_id; uint8_t unknown_4; // 0x4d uint8_t unknown_5; // 0x00 uint8_t unknown_6; // 0x00 from device | 0xfa from host uint8_t unknown_7; // 0x00 from device | 0xfa from host uint8_t button_count; struct marsgaming_button_info buttons[253]; uint8_t padding[3]; } __attribute__((packed)); _Static_assert(sizeof(struct marsgaming_report_buttons) == 1024, "Marsgaming button report is not 1024 bytes long"); struct marsgaming_report_led { uint8_t usb_report_id; enum marsgaming_report_type report_type; uint8_t unknown2; // 0xf1 uint8_t profile_id; uint8_t unknown4; // 0x06 uint8_t unknown5; // 0x00 uint8_t unknown6; // 0xfa uint8_t unknown7; // 0xfa struct marsgaming_led led; uint8_t unknown13; // 0x00 uint8_t unknown14; // 0x00 uint8_t unknown15; // 0x00 }; _Static_assert(sizeof(struct marsgaming_report_led) == 16, "Marsgaming led report is not 16 bytes long"); struct marsgaming_profile_drv_data { struct marsgaming_report_buttons buttons_report; struct marsgaming_report_resolutions resolutions_report; struct marsgaming_report_led led_report; }; inline static struct marsgaming_profile_drv_data * marsgaming_profile_get_drv_data(struct ratbag_profile *profile) { return (struct marsgaming_profile_drv_data *)profile->drv_data; } libratbag-0.18/src/driver-marsgaming/marsgaming-query.c000066400000000000000000000050531467456100500232450ustar00rootroot00000000000000#include "marsgaming-query.h" uint8_t marsgaming_query_current_profile(struct ratbag_device *device) { uint8_t writedata[16] = { 0x02, 0x03, 0x43, 0x00, 0x01, 0x00, 0xfa, 0xfa }; ratbag_hidraw_set_feature_report(device, writedata[0], writedata, ARRAY_LENGTH(writedata)); uint8_t readdata[16] = { 0 }; ratbag_hidraw_get_feature_report(device, 0x02, readdata, ARRAY_LENGTH(readdata)); return readdata[8]; } struct marsgaming_report_resolutions marsgaming_query_profile_resolutions(struct ratbag_profile *profile) { uint8_t writedata[16] = { 0x02, 0x03, 0x4f, (uint8_t)profile->index, 0x2a, 0x00, 0xfa, 0xfa }; ratbag_hidraw_set_feature_report(profile->device, writedata[0], writedata, ARRAY_LENGTH(writedata)); struct marsgaming_report_resolutions report = { 0 }; ratbag_hidraw_get_feature_report(profile->device, 0x03, (uint8_t*)&report, sizeof(report)); // We need to match endianness of the uint16_t values for (int i = 0; i < report.count_resolutions; ++i) { report.resolutions[i].x_res = get_unaligned_le_u16((uint8_t*)&report.resolutions[i].x_res); report.resolutions[i].y_res = get_unaligned_le_u16((uint8_t*)&report.resolutions[i].y_res); } return report; } struct marsgaming_report_buttons marsgaming_query_profile_buttons(struct ratbag_profile *profile) { uint8_t writedata[16] = { 0x02, 0x03, 0x90, (uint8_t)profile->index, 0x4d, 0x00, 0xfa, 0xfa }; ratbag_hidraw_set_feature_report(profile->device, writedata[0], writedata, ARRAY_LENGTH(writedata)); struct marsgaming_report_buttons report = { 0 }; ratbag_hidraw_get_feature_report(profile->device, 0x04, (uint8_t*)&report, sizeof(report)); return report; } uint8_t marsgaming_query_profile_polling_interval(struct ratbag_profile *profile) { // Lower 3 bits of 3rd byte is the profile number uint8_t writedata[16] = { 0x02, 0x03, 0x48 | (uint8_t)profile->index, 0x00, 0x01, 0x00, 0xfa, 0xfa }; ratbag_hidraw_set_feature_report(profile->device, writedata[0], writedata, ARRAY_LENGTH(writedata)); uint8_t readdata[16] = { 0 }; ratbag_hidraw_get_feature_report(profile->device, 0x02, readdata, ARRAY_LENGTH(readdata)); return readdata[8]; } struct marsgaming_report_led marsgaming_query_profile_led(struct ratbag_profile *profile) { uint8_t writedata[16] = { 0x02, 0x03, 0xf1, (uint8_t)profile->index, 0x06, 0x00 /*led_id???*/, 0xfa, 0xfa }; ratbag_hidraw_set_feature_report(profile->device, writedata[0], writedata, ARRAY_LENGTH(writedata)); struct marsgaming_report_led report = { 0 }; ratbag_hidraw_get_feature_report(profile->device, 0x04, (uint8_t*)&report, sizeof(report)); return report; } libratbag-0.18/src/driver-marsgaming/marsgaming-query.h000066400000000000000000000010261467456100500232460ustar00rootroot00000000000000#pragma once #include "marsgaming-protocol.h" #include "libratbag-private.h" uint8_t marsgaming_query_current_profile(struct ratbag_device *device); struct marsgaming_report_resolutions marsgaming_query_profile_resolutions(struct ratbag_profile *profile); struct marsgaming_report_buttons marsgaming_query_profile_buttons(struct ratbag_profile *profile); uint8_t marsgaming_query_profile_polling_interval(struct ratbag_profile *profile); struct marsgaming_report_led marsgaming_query_profile_led(struct ratbag_profile *profile); libratbag-0.18/src/driver-openinput.c000066400000000000000000000307741467456100500176610ustar00rootroot00000000000000/* * Copyright © 2021 Filipe Laíns * * 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" /* reports */ #define OI_REPORT_SHORT 0x20 #define OI_REPORT_LONG 0x21 #define OI_REPORT_SHORT_SIZE 8 #define OI_REPORT_LONG_SIZE 32 #define OI_REPORT_MAX_SIZE OI_REPORT_LONG_SIZE #define OI_REPORT_DATA_INDEX 3 #define OI_REPORT_DATA_MAX_SIZE OI_REPORT_LONG_SIZE - OI_REPORT_DATA_INDEX /* protocol function pages */ #define OI_PAGE_INFO 0x00 #define OI_PAGE_GIMMICKS 0xFD #define OI_PAGE_DEBUG 0xFE #define OI_PAGE_ERROR 0xFF /* info page (0x00) functions */ #define OI_FUNCTION_VERSION 0x00 #define OI_FUNCTION_FW_INFO 0x01 #define OI_FUNCTION_SUPPORTED_FUNCTION_PAGES 0x02 #define OI_FUNCTION_SUPPORTED_FUNCTIONS 0x03 /* error page (0xFF) */ #define OI_ERROR_INVALID_VALUE 0x01 #define OI_ERROR_UNSUPPORTED_FUNCTION 0x02 #define OI_ERROR_CUSTOM 0xFE static unsigned int report_rates[] = { 125, 250, 500, 750, 1000 }; struct openinput_drv_data { unsigned int num_profiles; unsigned int num_resolutions; unsigned int num_buttons; unsigned int num_leds; unsigned int fw_major; unsigned int fw_minor; unsigned int fw_patch; uint64_t supported; }; struct oi_report_t { uint8_t id; uint8_t function_page; uint8_t function; uint8_t data[29]; } __attribute__((__packed__)); #define CASE_RETURN_STRING(a) case a: return #a; break static const char* openinput_function_page_get_name(uint8_t page) { static char numeric[16]; char *str; switch(page) { CASE_RETURN_STRING(OI_PAGE_INFO); CASE_RETURN_STRING(OI_PAGE_GIMMICKS); CASE_RETURN_STRING(OI_PAGE_DEBUG); CASE_RETURN_STRING(OI_PAGE_ERROR); default: sprintf_safe(numeric, "0x%02x", page); str = numeric; break; } return str; } static const char* openinput_function_get_name(uint8_t page, uint8_t function) { static char numeric[32]; char *str = NULL; switch(page) { case OI_PAGE_INFO: switch (function) { CASE_RETURN_STRING(OI_FUNCTION_VERSION); CASE_RETURN_STRING(OI_FUNCTION_FW_INFO); CASE_RETURN_STRING(OI_FUNCTION_SUPPORTED_FUNCTION_PAGES); CASE_RETURN_STRING(OI_FUNCTION_SUPPORTED_FUNCTIONS); } } if (!str) { sprintf_safe(numeric, "0x%02x 0x%02x", page, function); str = numeric; } return str; } #undef CASE_RETURN_STRING static const char* openinput_get_error_string(struct oi_report_t *report) { char help_str[OI_REPORT_LONG_SIZE - OI_REPORT_DATA_INDEX + 1] = {0}; char *str; switch (report->function) { case OI_ERROR_INVALID_VALUE: xasprintf(&str, "Invalid value (in position %u)", report->data[2]); break; case OI_ERROR_CUSTOM: memcpy(help_str, report->data, sizeof(help_str)-1); /* copy error string */ xasprintf(&str, "Custom error (%s)", help_str); break; case OI_ERROR_UNSUPPORTED_FUNCTION: xasprintf(&str, "Unsupported function (0x%02x, 0x%02x)", report->data[0], report->data[1]); break; default: xasprintf(&str, "Unknown error (%u)", report->function); break; } return str; } static size_t openinput_get_report_size(unsigned int report) { switch (report) { case OI_REPORT_SHORT: return OI_REPORT_SHORT_SIZE; case OI_REPORT_LONG: return OI_REPORT_LONG_SIZE; default: return 0; } } static bool openinput_report_filter(uint8_t *buf, size_t len) { if (len < 1) return false; switch(buf[0]) { case OI_REPORT_SHORT: return len == OI_REPORT_SHORT_SIZE; case OI_REPORT_LONG: return len == OI_REPORT_LONG_SIZE; default: return false; } } static int openinput_send_report(struct ratbag_device *device, struct oi_report_t *report) { int ret; uint8_t buffer[OI_REPORT_MAX_SIZE]; size_t size = openinput_get_report_size(report->id); memcpy(buffer, report, size); ret = ratbag_hidraw_output_report(device, buffer, size); if (ret < 0) { log_error(device->ratbag, "openinput: failed to send data to device (%s)\n", strerror(-ret)); return ret; } ret = ratbag_hidraw_read_input_report(device, buffer, OI_REPORT_MAX_SIZE, openinput_report_filter); if (ret < 0) { log_error(device->ratbag, "openinput: failed to read data from device (%s)\n", strerror(-ret)); return ret; } memcpy(report, buffer, openinput_get_report_size(buffer[0])); /* check for error */ if (report->function_page == OI_PAGE_ERROR) { log_error(device->ratbag, "openinput: %s\n", openinput_get_error_string(report)); return report->function; } return 0; } static int openinput_info_version(struct ratbag_device *device) { int ret; struct openinput_drv_data *drv_data = ratbag_get_drv_data(device); struct oi_report_t report = { .id = OI_REPORT_SHORT, .function_page = OI_PAGE_INFO, .function = OI_FUNCTION_VERSION }; ret = openinput_send_report(device, &report); if (ret) return ret; drv_data->fw_major = report.data[0]; drv_data->fw_minor = report.data[1]; drv_data->fw_patch = report.data[2]; log_info(device->ratbag, "openinput: protocol version %u.%u.%u\n", drv_data->fw_major, drv_data->fw_minor, drv_data->fw_patch); return 0; } #define OI_FUNCTION_FW_INFO_VENDOR 0x00 #define OI_FUNCTION_FW_INFO_VERSION 0x01 #define OI_FUNCTION_FW_INFO_DEVICE_NAME 0x02 static int openinput_info_fw_info(struct ratbag_device *device, uint8_t field_id, unsigned char *description, size_t description_size) { int ret; struct oi_report_t report = { .id = OI_REPORT_SHORT, .function_page = OI_PAGE_INFO, .function = OI_FUNCTION_FW_INFO, .data = {field_id} }; ret = openinput_send_report(device, &report); if (ret) return ret; memcpy(description, report.data, min(sizeof(report.data), description_size)); return 0; } static int openinput_info_supported_function_pages(struct ratbag_device *device, uint8_t start_index, uint8_t *pages_count, uint8_t *pages_left, uint8_t *pages, size_t pages_size) { int ret; struct oi_report_t report = { .id = OI_REPORT_SHORT, .function_page = OI_PAGE_INFO, .function = OI_FUNCTION_SUPPORTED_FUNCTION_PAGES, .data = {start_index} }; ret = openinput_send_report(device, &report); if (ret) return ret; *pages_count = report.data[0]; *pages_left = report.data[1]; memcpy(pages, report.data + 2, min(sizeof(report.data), pages_size)); return 0; } static int openinput_info_supported_functions(struct ratbag_device *device, uint8_t function_page, uint8_t start_index, uint8_t *functions_count, uint8_t *functions_left, uint8_t *functions, size_t functions_size) { int ret; struct oi_report_t report = { .id = OI_REPORT_SHORT, .function_page = OI_PAGE_INFO, .function = OI_FUNCTION_SUPPORTED_FUNCTIONS, .data = {function_page, start_index} }; ret = openinput_send_report(device, &report); if (ret) return ret; *functions_count = report.data[0]; *functions_left = report.data[1]; memcpy(functions, report.data + 2, min(sizeof(report.data), functions_size)); return 0; } static int openinput_read_supported_functions(struct ratbag_device *device, uint8_t page) { struct ratbag *ratbag = device->ratbag; int ret; uint8_t i, total, read = 0, count = 0, left = 0; uint8_t buffer[OI_REPORT_DATA_MAX_SIZE]; ret = openinput_info_supported_functions(device, page, read, &count, &left, buffer, sizeof(buffer)); if (ret) return ret; total = count + left; uint8_t functions[total]; memcpy(functions, buffer, count); /* there are still functions left to read! */ while (left) { ret = openinput_info_supported_functions(device, page, read, &count, &left, buffer, sizeof(buffer)); if (ret) return ret; /* make sure the new size values make sense, to avoid deadlocks */ if (total != (read + count + left)) { log_error(ratbag, "openinput: invalid number of functions left to read (%u)\n", left); return -EINVAL; } log_debug(ratbag, "openinput: read %u functions, %u left\n", count, left); memcpy(functions + read, buffer, count); read += count; } /* iterate over read functions */ for (i = 0; i < total; i++) { log_debug(ratbag, "openinput: found function %s\n", openinput_function_get_name(page, functions[i])); /* TODO: set bits in drv_data->supported when we implement support for certain capabilities */ } return 0; } static int openinput_read_supported_function_pages(struct ratbag_device *device) { struct ratbag *ratbag = device->ratbag; int ret; uint8_t i, total, read = 0, count = 0, left = 0; uint8_t buffer[OI_REPORT_DATA_MAX_SIZE]; log_debug(ratbag, "openinput: starting reading device functions...\n"); ret = openinput_info_supported_function_pages(device, read, &count, &left, buffer, sizeof(buffer)); if (ret) return ret; total = count + left; if (total == 0) { log_debug(ratbag, "openinput: not proceeding to read device functions as there are 0 pages\n"); return 0; } uint8_t pages[total]; memcpy(pages, buffer, count); /* there are still function pages left to read! */ while (left) { ret = openinput_info_supported_function_pages(device, read, &count, &left, buffer, sizeof(buffer)); if (ret) return ret; /* make sure the new size values make sense, to avoid deadlocks */ if (total != (read + count + left)) { log_error(ratbag, "openinput: invalid number of function pages left to read (%u)\n", left); return -EINVAL; } log_debug(ratbag, "openinput: read %u pages, %u left\n", count, left); memcpy(pages + read, buffer, count); read += count; } /* iterate over read function pages, and read their functions */ for (i = 0; i < total; i++) { log_debug(ratbag, "openinput: found function page %s\n", openinput_function_page_get_name(pages[i])); openinput_read_supported_functions(device, pages[i]); } return 0; } static void openinput_read_profile(struct ratbag_profile *profile) { ratbag_profile_set_report_rate_list(profile, report_rates, sizeof(*report_rates)); profile->is_active = true; } static int openinput_test_hidraw(struct ratbag_device *device) { return ratbag_hidraw_has_report(device, OI_REPORT_SHORT); } static int openinput_probe(struct ratbag_device *device) { int ret; struct openinput_drv_data *drv_data; struct ratbag_profile *profile; unsigned char str[OI_REPORT_DATA_MAX_SIZE]; ret = ratbag_find_hidraw(device, openinput_test_hidraw); if (ret) return ret; drv_data = zalloc(sizeof(*drv_data)); drv_data->num_profiles = 1; ratbag_set_drv_data(device, drv_data); openinput_info_version(device); ret = openinput_info_fw_info(device, OI_FUNCTION_FW_INFO_VENDOR, str, sizeof(str)); if (ret) return ret; log_info(device->ratbag, "openinput: firmware vendor: %s\n", str); ret = openinput_info_fw_info(device, OI_FUNCTION_FW_INFO_VERSION, str, sizeof(str)); if (ret) return ret; log_info(device->ratbag, "openinput: firmware version: %s\n", str); ret = openinput_info_fw_info(device, OI_FUNCTION_FW_INFO_DEVICE_NAME, str, sizeof(str)); if (ret) return ret; log_info(device->ratbag, "openinput: device: %s\n", str); ret = openinput_read_supported_function_pages(device); if (ret) return ret; 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) openinput_read_profile(profile); return 0; } static void openinput_remove(struct ratbag_device *device) { ratbag_close_hidraw(device); free(ratbag_get_drv_data(device)); } struct ratbag_driver openinput_driver = { .name = "openinput", .id = "openinput", .probe = openinput_probe, .remove = openinput_remove, }; libratbag-0.18/src/driver-roccat-kone-emp.c000066400000000000000000001034471467456100500206220ustar00rootroot00000000000000/* * Copyright © 2021 Alexandre Laurent * 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. */ /** * There is no elevation support * The LED effects is applied to the four LED of the mouse, but libratbag can set a different effect for each LED * The LED effects BLINKING and PULSING are not supported in libratbag * The maximum macro size is 480 in the mouse software. One event keeps the event data and the timing/delay - libratbag does not keep track of that number of events. It limits the mouse to 128 events * The mouse can repeat macro. Not supported in libratbag * In official soft, we can set a LED color to offset the cycle effect (only with predefined_led_colors). * Since predefined colors are not handled, we can't reproduce this effect. */ #include "config.h" #include #include #include #include #include #include #include #include #include "libratbag-enums.h" #include "libratbag-private.h" #include "libratbag-hidraw.h" #include "libratbag.h" #include "shared-macro.h" #define ROCCAT_PROFILE_MAX 5 #define ROCCAT_BUTTON_MAX 11 * 2 // (Easy Shift) #define ROCCAT_NUM_DPI 5 #define ROCCAT_LED_MAX 4 #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_MAGIC_NUMBER_SETTINGS 0x29 #define ROCCAT_MAGIC_NUMBER_KEY_MAPPING 0x47 #define ROCCAT_BANK_ID_1 1 #define ROCCAT_BANK_ID_2 2 #define ROCCAT_REPORT_SIZE_MACRO_BANK 1026 #define ROCCAT_MACRO_GROUP_NAME_LENGTH 40 #define ROCCAT_MACRO_NAME_LENGTH 32 #define ROCCAT_CONFIG_SETTINGS 0x80 // (LED and mouse configuration) #define ROCCAT_CONFIG_KEY_MAPPING 0x90 // (Buttons configuration) #define ROCCAT_MAX_MACRO_LENGTH 480 #define ROCCAT_MIN_DPI 100 #define ROCCAT_MAX_DPI 12000 #define ROCCAT_USER_DEFINED_COLOR 0x1e // The mouse knows some predefined colors. User can also set RGB values #define ROCCAT_LED_BLINKING 0x02 #define ROCCAT_LED_BREATHING 0x03 #define ROCCAT_LED_PULSING 0x04 unsigned int report_rates[] = { 125, 250, 500, 1000 }; struct color { uint8_t r; uint8_t g; uint8_t b; } __attribute__((packed)); struct color predefined_led_colors[] = { { 179, 0, 0 }, { 255, 0, 0 }, { 255, 71, 0}, { 255, 106, 0 }, { 255, 157, 71 }, { 248, 232, 0 }, { 246, 255, 78 }, { 201, 255, 78 }, { 185, 255, 78 }, { 132, 255, 78 }, { 0, 255, 0 }, { 0, 207, 55 }, { 0, 166, 44 }, { 0, 207, 124 }, { 0,207, 158 }, { 0, 203, 207 }, { 41, 197, 255 }, { 37, 162, 233 }, { 99, 158, 239 }, { 37, 132, 233 }, { 0, 72, 255 }, { 15, 15, 255 }, { 15, 15, 188 }, { 89, 7, 255 }, { 121, 12, 255 }, { 161, 12, 255 }, { 170, 108, 232 }, { 181, 10, 216 }, { 205, 10, 217 }, { 217, 10, 125 } }; struct led_data { uint8_t predefined; // Index of the predefined color. 0x1e for user defined color. struct color color; } __attribute__((packed)); struct roccat_settings_report { uint8_t reportID; // 0x06 uint8_t magic_num; // 0x29 uint8_t profile; uint8_t x_y_linked; // Always 0. Not on EMP ? uint8_t x_sensitivity; // From -5 (0x01) to 5 (0x0b) uint8_t y_sensitivity; // From -5 (0x01) to 5 (0x0b) uint8_t dpi_mask; // Bitfield to know which DPI setting is enabled uint8_t xres[ROCCAT_NUM_DPI]; // DPI on X axis (from 0x00 to 0x77) uint8_t yres[ROCCAT_NUM_DPI]; // DPI on Y axis (always same values than xres) uint8_t current_dpi; // One index, since X and Y DPIs are the same uint8_t report_rate; // From 0x00 to 0x03 uint8_t led_status; // Two bitfields of 4 bits. First four bits tells if the LED colors is predefined. Latest four bits tells if the LED is on of off uint8_t lighting_flow; // 0x01 for color cycle effect. 0x00 to disable it uint8_t lighting_effect; // From 0x01 to 0x04 : fixed, blinking, breathing, beating uint8_t effect_speed; // From 0x01 to 0x03 struct led_data leds[ROCCAT_LED_MAX]; uint16_t checksum; } __attribute__((packed)); #define ROCCAT_REPORT_SIZE_SETTINGS sizeof(struct roccat_settings_report) struct roccat_macro { uint8_t reportID; // 0x08 uint8_t bank; // 0x01 or 0x02 uint8_t profile; uint8_t button_index; uint8_t repeats; // Number of repetition for this macro char group[ROCCAT_MACRO_GROUP_NAME_LENGTH]; // Folder name char name[ROCCAT_MACRO_NAME_LENGTH]; uint16_t length; struct { uint8_t keycode; uint8_t flag; // Pressed (0x01) or released (0x02) uint16_t time; } keys[ROCCAT_MAX_MACRO_LENGTH]; } __attribute__((packed)); struct button { uint8_t keycode; uint8_t undetermined1; uint8_t undetermined2; } __attribute__((packed)); struct roccat_buttons { uint8_t reportID; // 0x07 uint8_t magic_num; // 0x47 uint8_t profile; struct button keys[ROCCAT_BUTTON_MAX]; uint16_t checksum; } __attribute__((packed)); #define ROCCAT_REPORT_SIZE_BUTTONS sizeof(struct roccat_buttons) struct roccat_data { struct roccat_buttons buttons[(ROCCAT_PROFILE_MAX)]; struct roccat_settings_report settings[(ROCCAT_PROFILE_MAX)]; struct roccat_macro macros[(ROCCAT_PROFILE_MAX)][(ROCCAT_BUTTON_MAX + 1)]; }; struct roccat_button_mapping { uint8_t raw; struct ratbag_button_action action; }; static struct roccat_button_mapping roccat_button_mapping[] = { { 0, BUTTON_ACTION_NONE }, { 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) }, */ { 7, BUTTON_ACTION_BUTTON(4) }, /* Next page in browser */ { 8, BUTTON_ACTION_BUTTON(5) }, /* Previous page in browser */ { 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 -> Open any configurated app */ { 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) }, /* FIXME: { 23, Toggle sensibility }, */ /* FIXME: { 24, Sensibility UP }, */ /* FIXME: { 25, Sensibility Down }, */ /* FIXME: { 27, open driver/swarm }, -> 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 }, /* FIXME: { 49, Start timer }, */ /* FIXME: { 50, Stop timer}, */ /* FIXME: { 51, EasyAim DPI 400 }, */ /* FIXME: { 52, EasyAim DPI 400 }, */ /* FIXME: { 53, EasyAim DPI 800 }, */ /* FIXME: { 54, EasyAim DPI 1200 }, */ /* FIXME: { 55, EasyAim DPI 1600 }, */ /* FIXME: { 56, EasyAim DPI 3200 }, */ { 65, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE) }, /* FIXME: { 66, Easywheel sensitivity }, */ /* FIXME: { 67, Easywheel profile }, */ /* FIXME: { 68, Easywheel DPI }, */ /* FIXME: { 69, EasywheelVolume }, */ /* FIXME: { 70, Easywheel Alt-Tab }, */ /* FIXME: { 98, Home }, */ /* FIXME: { 99, End }, */ /* FIXME: { 100, Previous page }, */ /* FIXME: { 100, Next page }, */ /* FIXME: { 101, Maj left }, */ /* FIXME: { 102, Maj right }, */ /* FIXME: { 113, Sensibility -5 }, */ /* FIXME: { 114, Sensibility -4 }, */ /* FIXME: { 115, Sensibility -3 }, */ /* FIXME: { 116, Sensibility -2 }, */ /* FIXME: { 117, Sensibility -1 }, */ /* FIXME: { 118, Sensibility 0 }, */ /* FIXME: { 119, Sensibility 1 }, */ /* FIXME: { 120, Sensibility 2 }, */ /* FIXME: { 121, Sensibility 3 }, */ /* FIXME: { 122, Sensibility 4 }, */ /* FIXME: { 123, Sensibility 5 }, */ /* FIXME: { 124, EasyAim DPI Userset }, - second byte of button_data is the DPI */ /* FIXME: { 128, Browser search }, */ /* FIXME: { 129, Browser home }, */ /* FIXME: { 130, Browser stop }, */ /* FIXME: { 131, Browser refresh }, */ /* FIXME: { 132, Browser new tab (ctrl+T) }, */ /* FIXME: { 133, Browser new window }, */ /* FIXME: { 134, Open "Computer" }, */ /* FIXME: { 135, Open calculator }, */ /* FIXME: { 136, Open email }, */ /* FIXME: { 137, Open file }, */ /* FIXME: { 138, Open folder }, */ /* FIXME: { 139, Open URL }, */ /* FIXME: { 140, Mute mic }, */ /* FIXME: { 141, Open Desktop }, */ /* FIXME: { 142, Open Favorites }, */ /* FIXME: { 143, Open Fonts }, */ /* FIXME: { 144, Open My Documents }, */ /* FIXME: { 145, Open Downloads }, */ /* FIXME: { 146, Open Music }, */ /* FIXME: { 147, Open Pictures }, */ /* FIXME: { 148, Open Network }, */ /* FIXME: { 149, Printers }, */ /* FIXME: { 150, Network }, */ /* FIXME: { 167, System hibernation }, */ /* FIXME: { 168, System reboot }, */ /* FIXME: { 169, System lock }, */ /* FIXME: { 179, Logout }, */ /* FIXME: { 171, Control panel }, */ /* FIXME: { 172, System settings }, */ /* FIXME: { 173, Task Manager }, */ /* FIXME: { 174, Screen settings }, */ /* FIXME: { 175, Screensaver settings }, */ /* FIXME: { 176, Themes }, */ /* FIXME: { 177, Date and Time }, */ /* FIXME: { 178, Network settings }, */ /* FIXME: { 179, Admin settings }, */ /* FIXME: { 180, Firewall }, */ /* FIXME: { 181, Regedit }, */ /* FIXME: { 182, Event monitor }, */ /* FIXME: { 183, Performance monitor }, */ /* FIXME: { 184, Audio settings }, */ /* FIXME: { 185, Internet settings }, */ /* FIXME: { 186, Directx diagnostics }, */ /* FIXME: { 187, Command line }, */ /* FIXME: { 188, System poweroff }, */ /* FIXME: { 189, System sleep }, */ /* FIXME: { 190, System wakeup }, */ /* FIXME: { 191, Set profile 1 }, */ /* FIXME: { 192, Set profile 2 }, */ /* FIXME: { 193, Set profile 3 }, */ /* FIXME: { 194, Set profile 4 }, */ /* FIXME: { 195, Set profile 5 }, */ }; 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]; } /** * Compute the CRC from buf * len should be the length of buf, including the two bytes used for CRC */ 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; } /** * Returns if the CRC in buf is valid. * The CRC is expected to be the last two bytes of buf * len should be the length of buf, including the 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_debug(device->ratbag, "checksum computed: 0x%04x, checksum given: 0x%04x - %s\n", crc, given_crc, crc == given_crc ? "OK" : "FAIL"); 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) { log_debug(device->ratbag, "'%s' Setting profile %d as active\n", ratbag_device_get_name(device), 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; } /** * Sets the profile and which information we want to get from the mouse * * @param profile is the index of the profile from which you want the info. But, it is also used as a memory bank * identifier when querying a macro. In this case, the first bank can be queried by adding 0x10 to the profile index, * and the second bank, by adding 0x20. * @param type can be either which information you need (ROCCAT_CONFIG_SETTINGS or ROCCAT_CONFIG_KEY_MAPPING) or * it can be used to specify the button from which you want to get the macro. */ 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->buttons[profile->index].keys[button_index].keycode; return roccat_raw_to_button_action(data); } static unsigned int roccat_report_rate_to_index(unsigned int rate) { for(unsigned int i = 0 ; i < ARRAY_LENGTH(report_rates) ; i++) { if(report_rates[i] == rate) { return i; } } return 0; } static int roccat_write_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; unsigned int index = profile->index; struct ratbag_resolution *resolution; struct ratbag_led *led; struct ratbag_button *button; struct roccat_data *drv_data = ratbag_get_drv_data(device); struct roccat_settings_report* report; struct roccat_buttons* buttons; struct roccat_macro* macro; uint8_t bank_buf[ROCCAT_REPORT_SIZE_MACRO_BANK] = { 0 }; int rc = 0; int i = 0, count = 0; assert(index <= ROCCAT_PROFILE_MAX); report = &drv_data->settings[profile->index]; report->reportID = ROCCAT_REPORT_ID_SETTINGS; report->magic_num = ROCCAT_MAGIC_NUMBER_SETTINGS; report->report_rate = roccat_report_rate_to_index(profile->hz); report->dpi_mask = 0; ratbag_profile_for_each_resolution(profile, resolution) { report->xres[resolution->index] = (resolution->dpi_x - 100) / 100; report->yres[resolution->index] = (resolution->dpi_y - 100) / 100; if(resolution->is_active) { report->current_dpi = resolution->index; } if(resolution->dpi_x != 0 && resolution->dpi_y != 0) { report->dpi_mask += (1 << resolution->index); } } ratbag_profile_for_each_led(profile, led) { report->leds[led->index].predefined = ROCCAT_USER_DEFINED_COLOR; // Always user defined with libratbag (easier) report->leds[led->index].color.r = led->color.red; report->leds[led->index].color.g = led->color.green; report->leds[led->index].color.b = led->color.blue; // Last LED sets the profile values switch(led->mode) { case RATBAG_LED_OFF: report->led_status = 0xf0; break; case RATBAG_LED_ON: report->led_status = 0xff; break; case RATBAG_LED_CYCLE: report->led_status = 0xff; report->lighting_flow = 1; report->effect_speed = led->ms / 1000; break; case RATBAG_LED_BREATHING: report->led_status = 0xff; report->lighting_effect = ROCCAT_LED_BREATHING; report->effect_speed = led->ms / 1000; } } report->checksum = roccat_compute_crc((uint8_t*)report, ROCCAT_REPORT_SIZE_SETTINGS); buttons = &drv_data->buttons[profile->index]; buttons->reportID = ROCCAT_REPORT_ID_KEY_MAPPING; buttons->magic_num = ROCCAT_MAGIC_NUMBER_KEY_MAPPING; ratbag_profile_for_each_button(profile, button) { buttons->keys[button->index].keycode = roccat_button_action_to_raw(&button->action); if(button->action.type == RATBAG_BUTTON_ACTION_TYPE_MACRO) { macro = &drv_data->macros[profile->index][button->index]; memset(macro, 0, sizeof(struct roccat_macro)); macro->reportID = ROCCAT_REPORT_ID_MACRO; macro->bank = ROCCAT_BANK_ID_1; macro->profile = profile->index; macro->button_index = button->index; macro->repeats = 0; // No repeats in libratbag if(button->action.macro->group) { // Seems no use of group in libratbag strncpy(macro->group, button->action.macro->group, ROCCAT_MACRO_GROUP_NAME_LENGTH); } else { strncpy(macro->group, "libratbag macros", ROCCAT_MACRO_GROUP_NAME_LENGTH); } strncpy(macro->name, button->action.macro->name, ROCCAT_MACRO_NAME_LENGTH); for (i = 0; i < MAX_MACRO_EVENTS && count < ROCCAT_MAX_MACRO_LENGTH; i++) { if (button->action.macro->events[i].type == RATBAG_MACRO_EVENT_INVALID) return -EINVAL; /* should not happen, ever */ if (button->action.macro->events[i].type == RATBAG_MACRO_EVENT_NONE) break; /* ignore the first wait */ if (button->action.macro->events[i].type == RATBAG_MACRO_EVENT_WAIT && !count) continue; if (button->action.macro->events[i].type == RATBAG_MACRO_EVENT_KEY_PRESSED || button->action.macro->events[i].type == RATBAG_MACRO_EVENT_KEY_RELEASED) { macro->keys[count].keycode = ratbag_hidraw_get_keyboard_usage_from_keycode(device, button->action.macro->events[i].event.key); } switch (button->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 = button->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->length = count; // Macro has to be send in two packets memcpy(bank_buf, macro, ROCCAT_REPORT_SIZE_MACRO_BANK); rc = ratbag_hidraw_set_feature_report(device, ROCCAT_REPORT_ID_MACRO, bank_buf, ROCCAT_REPORT_SIZE_MACRO_BANK); if (rc < 0) return rc; if (rc != ROCCAT_REPORT_SIZE_MACRO_BANK) 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); bank_buf[0] = ROCCAT_REPORT_ID_MACRO; bank_buf[1] = ROCCAT_BANK_ID_2; // The remaining macro structure is not big enough to fill the second bank // Write the remaining, fill the end with 0 unsigned int remaining_to_write = sizeof(struct roccat_macro)-ROCCAT_REPORT_SIZE_MACRO_BANK; memcpy(bank_buf+2, &((uint8_t*)macro)[ROCCAT_REPORT_SIZE_MACRO_BANK], remaining_to_write); memset(bank_buf+2+remaining_to_write, 0, ROCCAT_REPORT_SIZE_MACRO_BANK-(2+remaining_to_write)); rc = ratbag_hidraw_set_feature_report(device, ROCCAT_REPORT_ID_MACRO, bank_buf, ROCCAT_REPORT_SIZE_MACRO_BANK); if (rc < 0) return rc; if (rc != ROCCAT_REPORT_SIZE_MACRO_BANK) 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); } } buttons->checksum = roccat_compute_crc((uint8_t*)buttons, ROCCAT_REPORT_SIZE_BUTTONS); rc = ratbag_hidraw_set_feature_report(device, ROCCAT_REPORT_ID_SETTINGS, (uint8_t*)report, 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); } rc = ratbag_hidraw_set_feature_report(device, ROCCAT_REPORT_ID_KEY_MAPPING, (uint8_t*)buttons, ROCCAT_REPORT_SIZE_BUTTONS); if (rc < 0) return rc; if (rc != ROCCAT_REPORT_SIZE_BUTTONS) 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); } log_debug(device->ratbag, "profile: %d written %s:%d\n", profile->index, __FILE__, __LINE__); return rc; } static void roccat_read_macro(struct roccat_macro* macro, struct ratbag_button* button) { struct ratbag_button_macro *m = NULL; unsigned j, time; char name[ROCCAT_MACRO_NAME_LENGTH+1] = { '\0' }; strncpy(name, macro->name, ROCCAT_MACRO_NAME_LENGTH); m = ratbag_button_macro_new(name); // libratbag does offer API for macro groups m->macro.group = (char*)zalloc(ROCCAT_MACRO_GROUP_NAME_LENGTH+1); strncpy(m->macro.group, macro->group, ROCCAT_MACRO_GROUP_NAME_LENGTH); log_debug(button->profile->device->ratbag, "macro on button %d of profile %d is named '%s' (from folder '%s'), and contains %d events:\n", button->index, button->profile->index, name, m->macro.group, macro->length); // libratbag can't keep track of the whole macro (MAX_MACRO_EVENTS) // In libratbag, each event is implemented as two separate (KEY_PRESS/KEY_RELEASE and WAIT) for (j = 0; j < macro->length && j < MAX_MACRO_EVENTS/2; j++) { unsigned int keycode = ratbag_hidraw_get_keycode_from_keyboard_usage(button->profile->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_debug(button->profile->device->ratbag, " - %s %s\n", libevdev_event_code_get_name(EV_KEY, keycode), macro->keys[j].flag == 0x02 ? "released" : "pressed"); } ratbag_button_copy_macro(button, m); ratbag_button_macro_unref(m); } static void roccat_read_button(struct ratbag_button *button) { const struct ratbag_button_action *action; struct ratbag_device *device = button->profile->device; struct roccat_data *drv_data = ratbag_get_drv_data(device); struct roccat_macro *macro; int rc; action = roccat_button_to_action(button->profile, button->index); if (action) ratbag_button_set_action(button, action); // 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_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); if (action && action->type == RATBAG_BUTTON_ACTION_TYPE_MACRO) { macro = &drv_data->macros[button->profile->index][button->index]; // Macros are available through two packets // We read the second one first, to overwrite some useless data (report id) in the final structure roccat_set_config_profile(device, button->profile->index, 0); roccat_set_config_profile(device, button->profile->index + 0x20, // When setting for a specific button, the Profile ID is 0x10 (or 0x20 for second bank) * Profile ID + 1 button->index); // I know that the second bank will not fit in internal structure, so reducing the data read rc = ratbag_hidraw_get_feature_report(device, ROCCAT_REPORT_ID_MACRO, (uint8_t*)macro + ROCCAT_REPORT_SIZE_MACRO_BANK - 2, sizeof(struct roccat_macro) - (ROCCAT_REPORT_SIZE_MACRO_BANK - 2)); if (rc != sizeof(struct roccat_macro) - (ROCCAT_REPORT_SIZE_MACRO_BANK - 2)) { log_error(device->ratbag, "Unable to retrieve the second bank for macro for button %d of profile %d: %s (%d)\n", button->index, button->profile->index, rc < 0 ? strerror(-rc) : "not read enough", rc); goto out_macro; } roccat_set_config_profile(device, button->profile->index + 0x10, // When setting for a specific button, the Profile ID is 0x10 * Profile ID + 1 button->index); rc = ratbag_hidraw_get_feature_report(device, ROCCAT_REPORT_ID_MACRO, (uint8_t*)macro, ROCCAT_REPORT_SIZE_MACRO_BANK); if (rc != ROCCAT_REPORT_SIZE_MACRO_BANK) { log_error(device->ratbag, "Unable to retrieve the first bank for macro for button %d of profile %d: %s (%d)\n", button->index, button->profile->index, rc < 0 ? strerror(-rc) : "not read enough", rc); goto out_macro; } if (macro->reportID != 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; } // No checksum for macros roccat_read_macro(macro, button); out_macro: msleep(10); } } static void roccat_read_dpi(struct roccat_settings_report* settings, struct ratbag_profile* profile) { struct ratbag_resolution *resolution; int dpi_x = 0, dpi_y = 0; unsigned int report_rate = 0; /* first retrieve the report rate, it is set per profile */ if (settings->report_rate < ARRAY_LENGTH(report_rates)) { report_rate = report_rates[settings->report_rate]; } else { log_error(profile->device->ratbag, "error while reading the report rate of the mouse (0x%02x)\n", settings->report_rate); report_rate = 0; } ratbag_profile_set_report_rate_list(profile, report_rates, ARRAY_LENGTH(report_rates)); profile->hz = report_rate; ratbag_profile_for_each_resolution(profile, resolution) { dpi_x = settings->xres[resolution->index] * 100 + 100; dpi_y = settings->yres[resolution->index] * 100 + 100; resolution->is_active = (resolution->index == settings->current_dpi); if (!(settings->dpi_mask & (1 << resolution->index))) { /* this resolution 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); ratbag_resolution_set_dpi_list_from_range(resolution, ROCCAT_MIN_DPI, ROCCAT_MAX_DPI); } } static void roccat_read_led(struct roccat_settings_report* settings, struct ratbag_led *led) { if(settings->led_status == 0) { led->mode = RATBAG_LED_OFF; } else { led->mode = RATBAG_LED_ON; } if(settings->lighting_flow) { led->mode = RATBAG_LED_CYCLE; led->ms = settings->effect_speed * 1000; } if(settings->lighting_effect == ROCCAT_LED_BREATHING) { led->mode = RATBAG_LED_BREATHING; led->ms = settings->effect_speed * 1000; } led->colordepth = RATBAG_LED_COLORDEPTH_RGB_888; if(settings->leds[led->index].predefined < ROCCAT_USER_DEFINED_COLOR) { led->color.red = predefined_led_colors[settings->leds[led->index].predefined].r; led->color.green = predefined_led_colors[settings->leds[led->index].predefined].g; led->color.blue = predefined_led_colors[settings->leds[led->index].predefined].b; } else { led->color.red = settings->leds[led->index].color.r; led->color.green = settings->leds[led->index].color.g; led->color.blue = settings->leds[led->index].color.b; } 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_CYCLE); ratbag_led_set_mode_capability(led, RATBAG_LED_BREATHING); } static void roccat_read_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct roccat_data *drv_data = ratbag_get_drv_data(device); struct ratbag_button *button; struct ratbag_led *led; struct roccat_settings_report *settings; struct roccat_buttons* buttons; int rc; assert(profile->index <= ROCCAT_PROFILE_MAX); // Read data from the mouse settings = &drv_data->settings[profile->index]; roccat_set_config_profile(device, profile->index, ROCCAT_CONFIG_SETTINGS); rc = ratbag_hidraw_get_feature_report(device, ROCCAT_REPORT_ID_SETTINGS, (uint8_t*)settings, ROCCAT_REPORT_SIZE_SETTINGS); if (rc < (int)ROCCAT_REPORT_SIZE_SETTINGS) { return; } if (!roccat_crc_is_valid(device, (uint8_t*)settings, ROCCAT_REPORT_SIZE_SETTINGS)) { log_error(device->ratbag, "Error while reading settings from profile %d, checksum invalid, continuing...\n", profile->index); } buttons = &drv_data->buttons[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, (uint8_t*)buttons, ROCCAT_REPORT_SIZE_BUTTONS); if (rc < (int)ROCCAT_REPORT_SIZE_BUTTONS) { return; } if (!roccat_crc_is_valid(device, (uint8_t*)buttons, ROCCAT_REPORT_SIZE_BUTTONS)) { log_error(device->ratbag, "Error while reading buttons from profile %d, checksum invalid, continuing...\n", profile->index); } // Feed libratbag with the data roccat_read_dpi(settings, profile); ratbag_profile_for_each_led(profile, led) { roccat_read_led(settings, led); } // 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); log_debug(device->ratbag, "profile: %d %s:%d\n", settings->profile, __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); ratbag_device_init_profiles(device, ROCCAT_PROFILE_MAX, ROCCAT_NUM_DPI, ROCCAT_BUTTON_MAX, 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; } ratbag_device_for_each_profile(device, profile) { if (profile->index == (unsigned int)active_idx) { profile->is_active = true; break; } } log_debug(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 int roccat_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 = roccat_write_profile(profile); if (rc) return rc; } return 0; } static void roccat_remove(struct ratbag_device *device) { ratbag_close_hidraw(device); free(ratbag_get_drv_data(device)); } struct ratbag_driver roccat_emp_driver = { .name = "Roccat Kone EMP", .id = "roccat-kone-emp", .probe = roccat_probe, .remove = roccat_remove, .commit = roccat_commit, .set_active_profile = roccat_set_current_profile, }; libratbag-0.18/src/driver-roccat-kone-pure.c000066400000000000000000000543551467456100500210170ustar00rootroot00000000000000/* * 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_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); // 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_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 = &button->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(struct ratbag_resolution *resolution) { 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; const unsigned int dpi_x = resolution->dpi_x; const unsigned int dpi_y = resolution->dpi_y; if (dpi_x < 200 || dpi_x > 8200 || 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)); profile->hz = 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)); } static int roccat_commit(struct ratbag_device *device) { int rc = 0; struct ratbag_button *button = NULL; struct ratbag_profile *profile = NULL; struct ratbag_resolution *resolution = NULL; ratbag_device_for_each_profile(device, profile) { if (!profile->dirty) continue; rc = roccat_write_profile(profile); if (rc) return rc; ratbag_profile_for_each_resolution(profile, resolution) { if (!resolution->dirty) continue; rc = roccat_write_resolution(resolution); if (rc) return rc; } ratbag_profile_for_each_button(profile, button) { if (!button->dirty) continue; rc = roccat_write_button(button); if (rc) return rc; } } return 0; } struct ratbag_driver roccat_kone_pure_driver = { .name = "Roccat Kone Pure", .id = "roccat-kone-pure", .probe = roccat_probe, .remove = roccat_remove, .commit = roccat_commit, .set_active_profile = roccat_set_current_profile, }; libratbag-0.18/src/driver-roccat.c000066400000000000000000000537401467456100500171110ustar00rootroot00000000000000/* * 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 report_length; 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; uint16_t report_length; 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_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); // 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_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->report_length = 0x0822; 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 = &button->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(struct ratbag_resolution *resolution) { 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; const unsigned int dpi_x = resolution->dpi_x; const unsigned int dpi_y = resolution->dpi_y; 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)); profile->hz = 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)); } static int roccat_commit(struct ratbag_device *device) { int rc = 0; struct ratbag_button *button = NULL; struct ratbag_profile *profile = NULL; struct ratbag_resolution *resolution = NULL; ratbag_device_for_each_profile(device, profile) { if (!profile->dirty) continue; rc = roccat_write_profile(profile); if (rc) return rc; ratbag_profile_for_each_resolution(profile, resolution) { if (!resolution->dirty) continue; rc = roccat_write_resolution(resolution); if (rc) return rc; } ratbag_profile_for_each_button(profile, button) { if (!button->dirty) continue; rc = roccat_write_button(button); if (rc) return rc; } } return 0; } struct ratbag_driver roccat_driver = { .name = "Roccat Kone XTD", .id = "roccat", .probe = roccat_probe, .remove = roccat_remove, .commit = roccat_commit, .set_active_profile = roccat_set_current_profile, }; libratbag-0.18/src/driver-sinowealth-nubwo.c000066400000000000000000000241251467456100500211360ustar00rootroot00000000000000/* * Copyright © 2020 Pipat Saengow * * 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-enums.h" #include "libratbag-hidraw.h" #include "libratbag-private.h" #include "libratbag-util.h" #include "shared-macro.h" #include #include #define SINOWEALTHNUBWO_PERF_CMD_REPORTID 0x02 #define SINOWEALTHNUBWO_AESTHETIC_CMD_REPORTID 0x03 #define SINOWEALTHNUBWO_GET_FIRMWARE_CMD_REPORTID 0x04 #define SINOWEALTHNUBWO_GET_FIRMWARE_MSGSIZE 256 #define SINOWEALTHNUBWO_GET_FIRMWARE_MSGOFFSET 48 #define SINOWEALTHNUBWO_PERF_CMD_MSGSIZE 16 // Actually more but I only implemented one #define SINOWEALTHNUBWO_NUM_PROFILES 1 #define SINOWEALTHNUBWO_NUM_RESOLUTIONS 1 // Actually 8 but I am not going to implement macros. #define SINOWEALTHNUBWO_NUM_BUTTONS 0 #define SINOWEALTHNUBWO_NUM_LEDS 1 //Magic set_feature that must be called before requesting firmware string static uint8_t PREFIRMWARE_QUERY_MSG[] = {0x02, 0x01, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static unsigned int REPORT_RATES[] = { 125, 250, 333, 500, 1000 }; static uint8_t REPORT_RATES_ENCODED[] = {0x08, 0x04, 0x03, 0x02, 0x01}; static uint8_t REPORT_RATES_CMD[] = {0x02, 0x06, 0xbb, 0xaa, 0x28, 0x00, 0x01, 0x00}; static unsigned int DPILIST[] = { 1000, 2000, 3000, 5000, 15000}; static uint8_t DPI_ENCODED[] = {0x04, 0x03, 0x02, 0x01, 0x00}; static uint8_t DPI_CMD[] = { 0x02, 0x06, 0xbb, 0xaa, 0x32, 0x00, 0x01, 0x00 }; enum sinowealthnubwo_color_mode { SINOWEALTHNUBWO_COLOR_OFF = 0x00, SINOWEALTHNUBWO_COLOR_ON = 0x01, SINOWEALTHNUBWO_COLOR_BREATHING = 0x02, SINOWEALTHNUBWO_COLOR_COLOR_SHIFT = 0x03, SINOWEALTHNUBWO_COLOR_SPECTRUM = 0x04, SINOWEALTHNUBWO_COLOR_MARQUEE = 0x05, }; struct sinowealthnubwo_aesthetic_report { uint8_t report_id; //0x03 uint8_t cmd[7]; uint8_t r; uint8_t g; uint8_t b; uint8_t color_mode; uint8_t padzero; // 0x00 uint8_t brightness; // 0x01 to 0x03 uint8_t tempo; // 0x05 0x03 0x01 uint8_t padzero2[16*3+1]; } __attribute__((packed)); // FIXME: there is a missing static assert of size here. static uint8_t AESTHETIC_CMD[] = {0x06, 0xbb, 0xaa, 0x2a, 0x00, 0x0a, 0x00}; static int sinowealthnubwo_test_hidraw(struct ratbag_device *device) { return ratbag_hidraw_has_report(device, SINOWEALTHNUBWO_AESTHETIC_CMD_REPORTID) && ratbag_hidraw_has_report(device, SINOWEALTHNUBWO_PERF_CMD_REPORTID) && ratbag_hidraw_has_report(device, SINOWEALTHNUBWO_GET_FIRMWARE_CMD_REPORTID); } static int sinowealth_get_firmware_string(struct ratbag_device *device, char **output) { uint8_t buff[SINOWEALTHNUBWO_GET_FIRMWARE_MSGSIZE + 1] = {0}; //Purposefully overalloacate to prevent buffer overrun int size; size = ratbag_hidraw_set_feature_report(device, SINOWEALTHNUBWO_PERF_CMD_REPORTID, PREFIRMWARE_QUERY_MSG, ARRAY_LENGTH(PREFIRMWARE_QUERY_MSG)); if (size < 0) { log_error(device->ratbag, "Error while sending pre-firmware request message: %d\n", size); return size; } size = ratbag_hidraw_get_feature_report(device, SINOWEALTHNUBWO_GET_FIRMWARE_CMD_REPORTID, buff, SINOWEALTHNUBWO_GET_FIRMWARE_MSGSIZE); if (size < 0) { return size; } if (size != SINOWEALTHNUBWO_GET_FIRMWARE_MSGSIZE) { log_error(device->ratbag ,"Firmware report reply size mismatch expected %d got %d\n", SINOWEALTHNUBWO_GET_FIRMWARE_MSGSIZE, size); return -EIO; } *output = strdup_ascii_only((char *) buff + SINOWEALTHNUBWO_GET_FIRMWARE_MSGOFFSET); return 0; } static int sinowealthnubwo_probe(struct ratbag_device *device) { int error; struct ratbag_profile *profile; struct ratbag_resolution *resolution; struct ratbag_led *led; error = ratbag_find_hidraw(device, sinowealthnubwo_test_hidraw); if (error) return error; char *fwstr; error = sinowealth_get_firmware_string(device, &fwstr); if (error) return error; log_info(device->ratbag, "Firmware: %s\n", fwstr); free(fwstr); ratbag_device_init_profiles(device, SINOWEALTHNUBWO_NUM_PROFILES, SINOWEALTHNUBWO_NUM_RESOLUTIONS, SINOWEALTHNUBWO_NUM_BUTTONS, SINOWEALTHNUBWO_NUM_LEDS); 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_for_each_resolution(profile, resolution) { ratbag_resolution_set_dpi_list(resolution, DPILIST, ARRAY_LENGTH(DPILIST)); resolution->dpi_x = resolution->dpi_y = DPILIST[ARRAY_LENGTH(DPILIST)-1]; resolution->is_active = true; resolution->is_default = true; } ratbag_profile_for_each_led(profile, led) { led->mode = RATBAG_LED_OFF; led->color.red = led->color.green = led->color.blue = 0xFF; 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); //Actually more } } return 0; } static uint8_t encode_dpi(unsigned int dpi) { for (size_t i = 0; i < ARRAY_LENGTH(DPILIST); i++) if (DPILIST[i] == dpi) return DPI_ENCODED[i]; return DPI_ENCODED[0]; } static uint8_t encode_report_rate(unsigned int reportrate) { for (size_t i = 0; i < ARRAY_LENGTH(REPORT_RATES); i++) if (REPORT_RATES[i] == reportrate) return REPORT_RATES_ENCODED[i]; return REPORT_RATES_ENCODED[0]; } static int sinowealthnubwo_set_dpi(struct ratbag_device *device, int dpi) { uint8_t buf[SINOWEALTHNUBWO_PERF_CMD_MSGSIZE] = {0}; memcpy(buf, DPI_CMD, ARRAY_LENGTH(DPI_CMD)); buf[ARRAY_LENGTH(DPI_CMD)] = encode_dpi(dpi); int error = ratbag_hidraw_set_feature_report(device, SINOWEALTHNUBWO_PERF_CMD_REPORTID, buf, SINOWEALTHNUBWO_PERF_CMD_MSGSIZE); if (error < 0) return error; return 0; } static int sinowealthnubwo_set_report_rate(struct ratbag_device *device, int reportrate) { uint8_t buf[SINOWEALTHNUBWO_PERF_CMD_MSGSIZE] = {0}; memcpy(buf, REPORT_RATES_CMD, ARRAY_LENGTH(REPORT_RATES_CMD)); buf[ARRAY_LENGTH(REPORT_RATES_CMD)] = encode_report_rate(reportrate); int error = ratbag_hidraw_set_feature_report(device, SINOWEALTHNUBWO_PERF_CMD_REPORTID, buf, SINOWEALTHNUBWO_PERF_CMD_MSGSIZE); if (error < 0) return error; return 0; } static enum sinowealthnubwo_color_mode encode_color(enum ratbag_led_mode mode) { switch (mode) { case RATBAG_LED_OFF: return SINOWEALTHNUBWO_COLOR_OFF; case RATBAG_LED_ON: return SINOWEALTHNUBWO_COLOR_ON; case RATBAG_LED_CYCLE: return SINOWEALTHNUBWO_COLOR_MARQUEE; case RATBAG_LED_BREATHING: return SINOWEALTHNUBWO_COLOR_BREATHING; default: return SINOWEALTHNUBWO_COLOR_OFF; } } static uint8_t normalize_duration(int duration) { const int MAX_DURATION = 10000; const uint8_t avail_dura[] = {0x01, 0x03, 0x05}; const int selected = (duration * ((int) ARRAY_LENGTH(avail_dura)) - 1) / MAX_DURATION; return avail_dura[selected]; } static uint8_t normalize_brightness(int brightness) { const int MAX_BRIGHTNESS = 255; return 1 + (brightness * 3 - 1) / MAX_BRIGHTNESS; } static int sinowealthnubwo_set_aesthetic(struct ratbag_device *device, struct ratbag_led *led) { struct sinowealthnubwo_aesthetic_report report = {0}; report.report_id = SINOWEALTHNUBWO_AESTHETIC_CMD_REPORTID; memcpy(report.cmd, AESTHETIC_CMD, ARRAY_LENGTH(AESTHETIC_CMD)); report.r = led->color.red; report.g = led->color.green; report.b = led->color.blue; report.color_mode = encode_color(led->mode); report.tempo = normalize_duration(led->ms); report.brightness = normalize_brightness(led->brightness); int size = ratbag_hidraw_set_feature_report(device, SINOWEALTHNUBWO_AESTHETIC_CMD_REPORTID, (uint8_t *) &report, sizeof(report)); if (size < 0) return size; return 0; } static int sinowealthnubwo_write_profile(struct ratbag_device *device, struct ratbag_profile *profile) { struct ratbag_resolution *resolution; struct ratbag_led *led; int error; log_debug(device->ratbag, "Writing updates\n"); log_debug(device->ratbag, "Setting report rate\n"); error = sinowealthnubwo_set_report_rate(device, profile->hz); if (error) return error; ratbag_profile_for_each_resolution(profile, resolution) { if (!resolution->dirty) continue; log_debug(device->ratbag, "Setting DPI\n"); error = sinowealthnubwo_set_dpi(device, resolution->dpi_x); if (error) return error; } ratbag_profile_for_each_led(profile, led) { if (!led->dirty) continue; log_debug(device->ratbag, "Setting aesthetic\n"); error = sinowealthnubwo_set_aesthetic(device, led); if (error) return error; } return 0; } static int sinowealthnubwo_commit(struct ratbag_device *device) { struct ratbag_profile *profile; list_for_each(profile, &device->profiles, link) { if (!profile->dirty) continue; int error = sinowealthnubwo_write_profile(device, profile); if (error) return error; } return 0; } static void sinowealthnubwo_remove(struct ratbag_device *device) { ratbag_close_hidraw(device); } struct ratbag_driver sinowealth_nubwo_driver = { .name = "Sinowealth Nubwo", .id = "sinowealth_nubwo", .probe = sinowealthnubwo_probe, .remove = sinowealthnubwo_remove, .commit = sinowealthnubwo_commit, }; libratbag-0.18/src/driver-sinowealth.c000066400000000000000000002123021467456100500200020ustar00rootroot00000000000000/* * Copyright © 2020 Marian Beermann * * 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 "driver-sinowealth.h" #include "libratbag-data.h" #include "libratbag-private.h" #include "libratbag-hidraw.h" #include "shared-macro.h" enum sinowealth_report_id { SINOWEALTH_REPORT_ID_CONFIG = 0x4, SINOWEALTH_REPORT_ID_CMD = 0x5, SINOWEALTH_REPORT_ID_CONFIG_LONG = 0x6, } __attribute__((packed)); _Static_assert(sizeof(enum sinowealth_report_id) == sizeof(uint8_t), "Invalid size"); enum sinowealth_command_id { SINOWEALTH_CMD_FIRMWARE_VERSION = 0x1, SINOWEALTH_CMD_PROFILE = 0x2, SINOWEALTH_CMD_GET_CONFIG = 0x11, SINOWEALTH_CMD_GET_BUTTONS = 0x12, /* Doesn't work on devices with shorter configuration data (123 instead of 137). */ SINOWEALTH_CMD_DEBOUNCE = 0x1a, /* Only works on devices that use CONFIG_LONG report ID. */ SINOWEALTH_CMD_LONG_ANGLESNAPPING_AND_LOD = 0x1b, /* Same as GET_CONFIG but for the second profile. */ SINOWEALTH_CMD_GET_CONFIG2 = 0x21, /* Same as GET_BUTTONS but for the second profile. */ SINOWEALTH_CMD_GET_BUTTONS2 = 0x22, SINOWEALTH_CMD_MACRO = 0x30, /* Same as GET_CONFIG but for the third profile. */ SINOWEALTH_CMD_GET_CONFIG3 = 0x31, /* Same as GET_BUTTONS but for the third profile. */ SINOWEALTH_CMD_GET_BUTTONS3 = 0x32, /* Puts the device into DFU mode. * To reset re-plug the mouse or do a clean reboot. */ SINOWEALTH_CMD_DFU = 0x75, } __attribute__((packed)); _Static_assert(sizeof(enum sinowealth_command_id) == sizeof(uint8_t), "Invalid size"); #define SINOWEALTH_BUTTON_SIZE 88 #define SINOWEALTH_CMD_SIZE 6 /* Report length commands that get configuration data should use. */ #define SINOWEALTH_CONFIG_REPORT_SIZE 520 #define SINOWEALTH_CONFIG_SIZE_MAX 167 #define SINOWEALTH_CONFIG_SIZE_MIN 123 #define SINOWEALTH_MACRO_SIZE 515 /* The PC software only goes down to 400, but the PMW3360 doesn't care */ #define SINOWEALTH_DPI_MIN 100 #define SINOWEALTH_DPI_STEP 100 /* Arbitrary, but I think every sensor supports this DPI, and this is * about as high as most people would ever like to go anyway. */ #define SINOWEALTH_DPI_FALLBACK 2000 /* Technically it can be set to 2 ms, but Glorious Model O Software * v1.0.9 does not allow doing so, and so don't we. */ #define SINOWEALTH_DEBOUNCE_MIN 4 #define SINOWEALTH_DEBOUNCE_MAX 16 /* This is a much as can be fit in 8 bytes. * * NOTE: Glorious Model O Software v1.0.9 allows you to set * up to 4096 ms, but it's actually a bug and the sent * number overflows. */ #define SINOWEALTH_MACRO_MAX_POSSIBLE_TIMEOUT 0xff /* Different software expose different amount of DPI slots: * Glorious - 6; * G-Wolves - 7. * But in fact fact there are eight slots. */ #define SINOWEALTH_NUM_DPIS 8 /* * Depending on the mouse there may be support for up to three profiles. * In official software utility they are called confusingly called * "modes", while "profiles" are just configuration presets saved to the * hard drive you can choose from. * To show these "modes" in the utility, you may have to modify the * configuration file of the utility. It is called `Cfg.ini` and resides * near `OemDrv.exe`. * We are interested in two section: `MS` and `SENSOR_X` (where `X` is a * number that depends on the amount of sensors defined in the file). * First, look at the value of key `Sensor` of `MS` section, then find a * corresponding `SENSOR_X` section with the same `Sensor` key value. * In it find a key `MDNUM`, whose value is supposed to be `3`, `6` or * `9`. Now modify it to 3 * `. * Just note that by default they may and most likely will have empty * button configuration, so you may have to use another mouse to assign * buttons. */ #define SINOWEALTH_NUM_PROFILES_MAX 3 _Static_assert(SINOWEALTH_NUM_PROFILES_MAX <= 3, "Too many profiles enabled"); /* How much buttons we can support for a mouse. Arbitrary number. */ #define SINOWEALTH_NUM_BUTTONS_MAX 64 /* Maximum amount of real events in a macro. */ #define SINOWEALTH_MACRO_LENGTH_MAX 168 static const unsigned int SINOWEALTH_DEBOUNCE_TIMES[] = { 4, 6, 8, 10, 12, 14, 16 }; static const unsigned int SINOWEALTH_REPORT_RATES[] = { 125, 250, 500, 1000 }; /* Bit mask for @ref sinowealth_config_report.config. * * This naming may be incorrect as it's not actually known what the other bits do. */ enum sinowealth_config_data_mask { SINOWEALTH_XY_INDEPENDENT = 0b1000, }; /* Color data the way mouse stores it. * * @ref sinowealth_raw_to_color. * * @ref sinowealth_color_to_raw. * * @ref sinowealth_led_format. */ struct sinowealth_color { /* May be in either RGB or RBG format depending on the device. * See the comment above this struct. */ uint8_t data[3]; } __attribute__((packed)); _Static_assert(sizeof(struct sinowealth_color) == 3, "Invalid size"); /* Sensor IDs used in SinoWealth firmware and software. */ enum sinowealth_sensor { SINOWEALTH_SENSOR_PMW3360 = 0x06, SINOWEALTH_SENSOR_PMW3212 = 0x08, SINOWEALTH_SENSOR_PMW3327 = 0x0e, SINOWEALTH_SENSOR_PMW3389 = 0x0f, } __attribute__((packed)); _Static_assert(sizeof(enum sinowealth_sensor) == sizeof(uint8_t), "Invalid sensor enum size"); enum sinowealth_rgb_effect { RGB_OFF = 0, RGB_GLORIOUS = 0x1, /* unicorn mode */ RGB_SINGLE = 0x2, /* single constant color */ RGB_BREATHING7 = 0x3, /* breathing with seven user-defined colors */ RGB_TAIL = 0x4, RGB_BREATHING = 0x5, /* Full RGB breathing. */ RGB_CONSTANT = 0x6, /* Each LED gets its own static color. Not available in Glorious software. */ RGB_RAVE = 0x7, RGB_RANDOM = 0x8, /* Randomly change colors. Not available in Glorious software. */ RGB_WAVE = 0x9, /* Single color breathing. * Not available on some mice, for example Genesis Xenon 770 and * DreamMachines DM5 (both are 0027 mice). On them RGB_BREATHING7 * with one color should be used instead. */ RGB_BREATHING1 = 0xa, /* The value mice with no LEDs have. * Unreliable as non-constant. * Do **not** overwrite it. */ RGB_NOT_SUPPORTED = 0xff, } __attribute__((packed)); _Static_assert(sizeof(enum sinowealth_rgb_effect) == sizeof(uint8_t), "Invalid size"); struct sinowealth_rgb_mode { /* 0x1/2/3. * @ref sinowealth_duration_to_rgb_mode. * @ref sinowealth_rgb_mode_to_duration. */ uint8_t speed:4; /* 0x1/2/3/4. * @ref sinowealth_brightness_to_rgb_mode. * @ref sinowealth_rgb_mode_to_brightness. */ uint8_t brightness:4; }; _Static_assert(sizeof(struct sinowealth_rgb_mode) == sizeof(uint8_t), "Invalid size"); struct sinowealth_xy_independent_dpi { uint8_t x; uint8_t y; }; _Static_assert(sizeof(struct sinowealth_xy_independent_dpi) == sizeof(uint8_t[2]), "Invalid size"); /* DPI/CPI is encoded in the way the PMW3360 and PMW3327 sensors * accept it: * value = (DPI - 100) / 100; * or the way the PMW3389 sensor accepts it: * value = DPI / 100; * * @ref sinowealth_raw_to_dpi * @ref sinowealth_dpi_to_raw */ union sinowealth_dpis { /* You MUST use this field if no resolutions have separate X and Y. */ uint8_t dpis[8]; /* You MUST use this field if at least one resolution has separate * X and Y. */ struct sinowealth_xy_independent_dpi independent[8]; }; _Static_assert(sizeof(union sinowealth_dpis) == sizeof(uint8_t[16]), "Invalid size"); /* Configuration data the way it's stored in mouse memory. * When we want to change a setting, we basically copy the entire mouse * configuration, modify it and send it back. */ struct sinowealth_config_report { enum sinowealth_report_id report_id; enum sinowealth_command_id command_id; uint8_t unknown1; /* 0x0 - read. * CONFIG_SIZE-8 - write. */ uint8_t config_write; uint8_t unknown2[5]; enum sinowealth_sensor sensor_type; /* @ref sinowealth_report_rate_map. */ uint8_t report_rate:4; /* @ref sinowealth_config_data_mask */ uint8_t config_flags:4; uint8_t dpi_count:4; /* Starting from 1 counting only active slots. */ uint8_t active_dpi:4; /* bit set: disabled, unset: enabled */ uint8_t disabled_dpi_slots; union sinowealth_dpis dpis; struct sinowealth_color dpi_color[8]; enum sinowealth_rgb_effect rgb_effect; struct sinowealth_rgb_mode glorious_mode; uint8_t glorious_direction; struct sinowealth_rgb_mode single_mode; struct sinowealth_color single_color; struct sinowealth_rgb_mode breathing7_mode; uint8_t breathing7_colorcount; struct sinowealth_color breathing7_colors[7]; struct sinowealth_rgb_mode tail_mode; struct sinowealth_rgb_mode breathing_mode; struct sinowealth_rgb_mode constant_color_mode; struct sinowealth_color constant_color_colors[6]; uint8_t unknown3[12]; struct sinowealth_rgb_mode rave_mode; struct sinowealth_color rave_colors[2]; /* From here onward goes the data not available in short mice. * .. judging by the size of this struct. The data in them may * actually be different, we didn't test this yet. */ struct sinowealth_rgb_mode random_mode; struct sinowealth_rgb_mode wave_mode; struct sinowealth_rgb_mode breathing1_mode; struct sinowealth_color breathing1_color; /* 0x1 - 2 mm. * 0x2 - 3 mm. * 0xff - indicates that lift off distance is changed with a dedicated command. Not constant, so do **NOT** overwrite it. */ uint8_t lift_off_distance; uint8_t unknown4; /* From here onward goes the data only available in long mice. */ uint8_t unknown5[36]; uint8_t padding[SINOWEALTH_CONFIG_REPORT_SIZE - SINOWEALTH_CONFIG_SIZE_MAX]; } __attribute__((packed)); _Static_assert(sizeof(struct sinowealth_config_report) == SINOWEALTH_CONFIG_REPORT_SIZE, "Invalid size"); enum sinowealth_button_type { SINOWEALTH_BUTTON_TYPE_NONE = 0, /* This value might appear on broken configurations. */ SINOWEALTH_BUTTON_TYPE_BUTTON = 0x11, SINOWEALTH_BUTTON_TYPE_WHEEL = 0x12, SINOWEALTH_BUTTON_TYPE_KEY = 0x21, SINOWEALTH_BUTTON_TYPE_MULTIMEDIA_KEY = 0x22, SINOWEALTH_BUTTON_TYPE_REPEATED = 0x31, SINOWEALTH_BUTTON_TYPE_SWITCH_DPI = 0x41, SINOWEALTH_BUTTON_TYPE_DPI_LOCK = 0x42, SINOWEALTH_BUTTON_TYPE_SPECIAL = 0x50, SINOWEALTH_BUTTON_TYPE_MACRO = 0x70, } __attribute__((packed)); _Static_assert(sizeof(enum sinowealth_button_type) == sizeof(uint8_t), "Invalid size"); /* Bit masks. */ enum sinowealth_button_key_modifiers { SINOWEALTH_BUTTON_KEY_MODIFIER_LEFTCTRL = 0x01, SINOWEALTH_BUTTON_KEY_MODIFIER_LEFTSHIFT = 0x02, SINOWEALTH_BUTTON_KEY_MODIFIER_LEFTALT = 0x04, SINOWEALTH_BUTTON_KEY_MODIFIER_LEFTMETA = 0x08, } __attribute__((packed)); _Static_assert(sizeof(enum sinowealth_button_key_modifiers) == sizeof(uint8_t), "Invalid size"); /* * @return 0 on success or a negative errno. */ static int sinowealth_modifiers_to_raw(unsigned int modifiers) { uint8_t raw_modifiers = 0; if (modifiers & MODIFIER_LEFTCTRL) raw_modifiers |= SINOWEALTH_BUTTON_KEY_MODIFIER_LEFTCTRL; if (modifiers & MODIFIER_LEFTSHIFT) raw_modifiers |= SINOWEALTH_BUTTON_KEY_MODIFIER_LEFTSHIFT; if (modifiers & MODIFIER_LEFTALT) raw_modifiers |= SINOWEALTH_BUTTON_KEY_MODIFIER_LEFTALT; if (modifiers & MODIFIER_LEFTMETA) raw_modifiers |= SINOWEALTH_BUTTON_KEY_MODIFIER_LEFTMETA; if (modifiers & MODIFIER_RIGHTCTRL || modifiers & MODIFIER_RIGHTSHIFT || modifiers & MODIFIER_RIGHTALT || modifiers & MODIFIER_RIGHTMETA) return -EINVAL; return raw_modifiers; } enum sinowealth_button_macro_mode { /* Repeat