pax_global_header00006660000000000000000000000064131155266150014517gustar00rootroot0000000000000052 comment=7da53c61cf7a4dea749edcfe9cec491588eeb5e2 libratbag-0.9/000077500000000000000000000000001311552661500133165ustar00rootroot00000000000000libratbag-0.9/.gitignore000066400000000000000000000001041311552661500153010ustar00rootroot00000000000000*.o *.pc *.swp *~ *.sig *.tar.* *.announce *.patch *.rej *.trs tags libratbag-0.9/COPYING000066400000000000000000000021001311552661500143420ustar00rootroot00000000000000Copyright © 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. libratbag-0.9/README.md000066400000000000000000000103201311552661500145710ustar00rootroot00000000000000libratbag ========= libratbag is a configuration library for gaming mice. It provides a generic way to access the various features exposed by these mice and abstracts away hardware-specific and kernel-specific quirks. Documentation ------------- API documentation is available here: http://libratbag.github.io/libratbag/ Architecture ------------ libratbag has two main components, the front-end API and wrapper code, and the back-end HW-specific drivers: +-----+ +-----+ +-----------+ +----------+ | app | -> | API | -> | hw-driver | -> | 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 -------------- As of commit 3605bede4 libratbag now uses a hwdb entry to match the device with the drivers. To access a device through libratbag, the udev property RATBAG_DRIVER must be set for a device's event node. Check with sudo udevadm info /sys/class/input/eventX | grep RATBAG_DRIVER If your device is not yet assigned the property, it is not in the hwdb. It may however be supported by one of our existing drivers. Try enabling it by adding the device vendor-id/product-id to the hwdb file in `hwdb/70-libratbag-mouse.hwdb`. For example, for a HID++ 1.0 device, edit the 70-libratbag-mouse.hwdb file and add an entry with `RATBAG_DRIVER=hidpp10`. For the other drivers, look for the id of the driver in driver-{drivername}.c file and do the same. Once your device is added to the hwdb, install libratbag and trigger a hwdb update: sudo udevadm hwdb --update sudo udevadm control --reload Then unplug/replug your mouse. `RATBAG_DRIVER` should appear in the udev properties of your device with the value you previously set. If the property is not assigned, the hwdb entry does not correctly match your device or the installed udev rules/hwdb entries are not picked up by udev. If the device doesn't work, you'll have to start reverse-engineering the device-specific protocol. Good luck :) Source ------ git clone https://github.com/libratbag/libratbag.git Building -------- libratbag uses the meson build system, see http://mesonbuild.com which in turn uses ninja to invoke the compiler (`ninja` may be `ninja-build` on your distribution). From a fresh git checkout, run the following commands to init the repository: meson builddir --prefix=/usr/ And to build or re-build after code-changes, run: ninja -C builddir sudo ninja -C builddir install Note: 'builddir' is the build output directory and can be changed to any other directory name. To set configure-time options, use e.g. mesonconf builddir -Denable-documentation=no Run 'mesonconf builddir' to list the options. Bugs ---- Bugs can be reported in the issue tracker on our github repo: https://github.com/libratbag/libratbag/issues Mailing list ------------ libratbag discussions happen on the input-tools mailing list hosted on freedesktop.org: http://lists.freedesktop.org/archives/input-tools/ Device-specific notes --------------------- A number of device-specific notes and observations can be found in our "device-notes" repository: http://libratbag.github.io/device-notes/ License ------- libratbag is licensed under the MIT license. > Permission is hereby granted, free of charge, to any person obtaining a > copy of this software and associated documentation files (the "Software"), > to deal in the Software without restriction, including without limitation > the rights to use, copy, modify, merge, publish, distribute, sublicense, > and/or sell copies of the Software, and to permit persons to whom the > Software is furnished to do so, subject to the following conditions: [...] See the COPYING file for the full license information. [![Build Status](https://semaphoreci.com/api/v1/projects/7905244a-c0b5-468b-9071-d846de3ce9f1/545192/badge.svg)](https://semaphoreci.com/libratbag/libratbag) libratbag-0.9/TODO000066400000000000000000000044661311552661500140200ustar00rootroot00000000000000some 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.9/data/000077500000000000000000000000001311552661500142275ustar00rootroot00000000000000libratbag-0.9/data/README.md000066400000000000000000000020211311552661500155010ustar00rootroot00000000000000libratbag data files ==================== These SVG files should approximate the device to make it immediately recognizable. Detail work not required, the graphic is an illustration only. Requirements ------------ - Canvas size: 750x750 pixels - Two layers in the final SVG: a lower layer named "Device" with the device itself, "Leaders" with the buttons and leader-lines - The device should be approximately centered on the canvas and occupy a meaningful portion of the canvas without making it look cramped - Button labels must be in 12pt Sans, starting with Button0 - Wheel buttons may be labeled separately if required (look at the Etekcity for an example) Technique --------- The simplest approach is to find a photo of the device and import it into inkscape. Put it on the lowest layer, create a new layer "Device" above it and start tracing the outlines and edges of the device. Fill in the shapes and your device should resemble the underlying photo. Delete the photo layer, add leaders and button labels and you're done. libratbag-0.9/data/etekcity.svg000066400000000000000000000561071311552661500166020ustar00rootroot00000000000000 image/svg+xml Button0 Button1 Button2 Button3 Button4 Button5 Button6 Button7 Button8 Button9 libratbag-0.9/data/logitech-g300.svg000066400000000000000000001340351311552661500172230ustar00rootroot00000000000000 image/svg+xml Button 0 Button 1 Button 2 Button 7 Button 6 Button 5 Button 4 Button 3 Button 8 libratbag-0.9/data/logitech-g303.svg000066400000000000000000000715261311552661500172330ustar00rootroot00000000000000 image/svg+xml Button 0 Button 2 Button 1 Button 4 Button 3 Button 5 libratbag-0.9/data/logitech-g500s.svg000066400000000000000000001736241311552661500174170ustar00rootroot00000000000000 image/svg+xml G500s Button0 Button1 Button2 Button4 Button3 Button5 Button8 Button9 Button6 Button7 libratbag-0.9/data/logitech-g502.svg000066400000000000000000001132141311552661500172230ustar00rootroot00000000000000 image/svg+xml Button0 Button10 Button9 Button7 Button2 Button6 Button4 Button5 Button3 Button1 Button8 libratbag-0.9/data/logitech-g700.svg000066400000000000000000001103561311552661500172270ustar00rootroot00000000000000 image/svg+xml Button0 Button1 Button2 Button6 Button5 Button4 Button9 Button8 Button11 Button12 Button10 Button3 Button7 libratbag-0.9/data/logitech-g900.svg000066400000000000000000002017071311552661500172320ustar00rootroot00000000000000 image/svg+xml Button 0 Button 1 Button 2 Button 5 Button 6 Button 3 Button 4 Button 7 Button 8 Button 10 Button 9 Button 11 libratbag-0.9/data/logitech-mx_master.svg000066400000000000000000000522701311552661500205510ustar00rootroot00000000000000 image/svg+xml Button0 Button1 Button2 Button3 Button4 Button5 Button6 libratbag-0.9/data/roccat-kone-xtd.svg000066400000000000000000000726151311552661500177650ustar00rootroot00000000000000 image/svg+xml > Button0 Button12 Button3 Button15 Button4 Button16 Button10 Button22 Button9 Button21 Button5 Button17 Button11 Button23 Button1 Button13 Button2 Button14 Button6 Button18 libratbag-0.9/doc/000077500000000000000000000000001311552661500140635ustar00rootroot00000000000000libratbag-0.9/doc/libratbag.doxygen.in000066400000000000000000000017761311552661500200310ustar00rootroot00000000000000PROJECT_NAME = @PACKAGE_NAME@ PROJECT_NUMBER = @PACKAGE_VERSION@ PROJECT_BRIEF = "A mouse configuration library" JAVADOC_AUTOBRIEF = YES TAB_SIZE = 8 OPTIMIZE_OUTPUT_FOR_C = YES EXTRACT_ALL = YES EXTRACT_STATIC = YES MAX_INITIALIZER_LINES = 0 QUIET = YES # Keep this list in sync with meson.build INPUT = @top_srcdir@/src/libratbag.h \ @top_srcdir@/README.md GENERATE_HTML = YES HTML_TIMESTAMP = YES USE_MATHJAX = YES GENERATE_LATEX = NO MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES PREDEFINED = LIBRATBAG_ATTRIBUTE_PRINTF(f, \ a)= \ LIBRATBAG_ATTRIBUTE_DEPRECATED HTML_HEADER = @top_srcdir@/doc/style/header.html HTML_FOOTER = @top_srcdir@/doc/style/footer.html HTML_EXTRA_STYLESHEET = @top_srcdir@/doc/style/customdoxygen.css \ @top_srcdir@/doc/style/bootstrap.css USE_MDFILE_AS_MAINPAGE = README.md libratbag-0.9/doc/style/000077500000000000000000000000001311552661500152235ustar00rootroot00000000000000libratbag-0.9/doc/style/LICENSE000066400000000000000000000275161311552661500162430ustar00rootroot00000000000000These licenses apply to the doxygen documentation HTML style only. They do not apply or affect libratbag itself. Apache: https://github.com/Velron/doxygen-bootstrapped/ MIT: https://bootswatch.com/paper/bootstrap.css Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. The MIT License (MIT) Copyright (c) 2011-2015 Twitter, 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 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.9/doc/style/bootstrap.css000066400000000000000000005061041311552661500177600ustar00rootroot00000000000000@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500,700"); /*! * bootswatch v3.3.5 * Homepage: http://bootswatch.com * Copyright 2012-2015 Thomas Park * Licensed under MIT * Based on Bootstrap */ /*! * Bootstrap v3.3.5 (http://getbootstrap.com) * Copyright 2011-2015 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ html { font-family: sans-serif; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; } body { margin: 0; } article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { display: block; } audio, canvas, progress, video { display: inline-block; vertical-align: baseline; } audio:not([controls]) { display: none; height: 0; } [hidden], template { display: none; } a { background-color: transparent; } a:active, a:hover { outline: 0; } abbr[title] { border-bottom: 1px dotted; } b, strong { font-weight: bold; } dfn { font-style: italic; } h1 { font-size: 2em; margin: 0.67em 0; } mark { background: #ff0; color: #000; } small { font-size: 80%; } sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } img { border: 0; } svg:not(:root) { overflow: hidden; } figure { margin: 1em 40px; } hr { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box; height: 0; } pre { overflow: auto; } code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; } button, input, optgroup, select, textarea { color: inherit; font: inherit; margin: 0; } button { overflow: visible; } button, select { text-transform: none; } button, html input[type="button"], input[type="reset"], input[type="submit"] { -webkit-appearance: button; cursor: pointer; } button[disabled], html input[disabled] { cursor: default; } button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } input { line-height: normal; } input[type="checkbox"], input[type="radio"] { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 0; } input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { height: auto; } input[type="search"] { -webkit-appearance: textfield; -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box; } input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } legend { border: 0; padding: 0; } textarea { overflow: auto; } optgroup { font-weight: bold; } table { border-collapse: collapse; border-spacing: 0; } td, th { padding: 0; } /*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ @media print { *, *:before, *:after { background: transparent !important; color: #000 !important; -webkit-box-shadow: none !important; box-shadow: none !important; text-shadow: none !important; } a, a:visited { text-decoration: underline; } a[href]:after { content: " (" attr(href) ")"; } abbr[title]:after { content: " (" attr(title) ")"; } a[href^="#"]:after, a[href^="javascript:"]:after { content: ""; } pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } thead { display: table-header-group; } tr, img { page-break-inside: avoid; } img { max-width: 100% !important; } p, h2, h3 { orphans: 3; widows: 3; } h2, h3 { page-break-after: avoid; } .navbar { display: none; } .btn > .caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px solid #000; } .table { border-collapse: collapse !important; } .table td, .table th { background-color: #fff !important; } .table-bordered th, .table-bordered td { border: 1px solid #ddd !important; } } @font-face { font-family: 'Glyphicons Halflings'; src: url('../fonts/glyphicons-halflings-regular.eot'); src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); } .glyphicon { position: relative; top: 1px; display: inline-block; font-family: 'Glyphicons Halflings'; font-style: normal; font-weight: normal; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .glyphicon-asterisk:before { content: "\2a"; } .glyphicon-plus:before { content: "\2b"; } .glyphicon-euro:before, .glyphicon-eur:before { content: "\20ac"; } .glyphicon-minus:before { content: "\2212"; } .glyphicon-cloud:before { content: "\2601"; } .glyphicon-envelope:before { content: "\2709"; } .glyphicon-pencil:before { content: "\270f"; } .glyphicon-glass:before { content: "\e001"; } .glyphicon-music:before { content: "\e002"; } .glyphicon-search:before { content: "\e003"; } .glyphicon-heart:before { content: "\e005"; } .glyphicon-star:before { content: "\e006"; } .glyphicon-star-empty:before { content: "\e007"; } .glyphicon-user:before { content: "\e008"; } .glyphicon-film:before { content: "\e009"; } .glyphicon-th-large:before { content: "\e010"; } .glyphicon-th:before { content: "\e011"; } .glyphicon-th-list:before { content: "\e012"; } .glyphicon-ok:before { content: "\e013"; } .glyphicon-remove:before { content: "\e014"; } .glyphicon-zoom-in:before { content: "\e015"; } .glyphicon-zoom-out:before { content: "\e016"; } .glyphicon-off:before { content: "\e017"; } .glyphicon-signal:before { content: "\e018"; } .glyphicon-cog:before { content: "\e019"; } .glyphicon-trash:before { content: "\e020"; } .glyphicon-home:before { content: "\e021"; } .glyphicon-file:before { content: "\e022"; } .glyphicon-time:before { content: "\e023"; } .glyphicon-road:before { content: "\e024"; } .glyphicon-download-alt:before { content: "\e025"; } .glyphicon-download:before { content: "\e026"; } .glyphicon-upload:before { content: "\e027"; } .glyphicon-inbox:before { content: "\e028"; } .glyphicon-play-circle:before { content: "\e029"; } .glyphicon-repeat:before { content: "\e030"; } .glyphicon-refresh:before { content: "\e031"; } .glyphicon-list-alt:before { content: "\e032"; } .glyphicon-lock:before { content: "\e033"; } .glyphicon-flag:before { content: "\e034"; } .glyphicon-headphones:before { content: "\e035"; } .glyphicon-volume-off:before { content: "\e036"; } .glyphicon-volume-down:before { content: "\e037"; } .glyphicon-volume-up:before { content: "\e038"; } .glyphicon-qrcode:before { content: "\e039"; } .glyphicon-barcode:before { content: "\e040"; } .glyphicon-tag:before { content: "\e041"; } .glyphicon-tags:before { content: "\e042"; } .glyphicon-book:before { content: "\e043"; } .glyphicon-bookmark:before { content: "\e044"; } .glyphicon-print:before { content: "\e045"; } .glyphicon-camera:before { content: "\e046"; } .glyphicon-font:before { content: "\e047"; } .glyphicon-bold:before { content: "\e048"; } .glyphicon-italic:before { content: "\e049"; } .glyphicon-text-height:before { content: "\e050"; } .glyphicon-text-width:before { content: "\e051"; } .glyphicon-align-left:before { content: "\e052"; } .glyphicon-align-center:before { content: "\e053"; } .glyphicon-align-right:before { content: "\e054"; } .glyphicon-align-justify:before { content: "\e055"; } .glyphicon-list:before { content: "\e056"; } .glyphicon-indent-left:before { content: "\e057"; } .glyphicon-indent-right:before { content: "\e058"; } .glyphicon-facetime-video:before { content: "\e059"; } .glyphicon-picture:before { content: "\e060"; } .glyphicon-map-marker:before { content: "\e062"; } .glyphicon-adjust:before { content: "\e063"; } .glyphicon-tint:before { content: "\e064"; } .glyphicon-edit:before { content: "\e065"; } .glyphicon-share:before { content: "\e066"; } .glyphicon-check:before { content: "\e067"; } .glyphicon-move:before { content: "\e068"; } .glyphicon-step-backward:before { content: "\e069"; } .glyphicon-fast-backward:before { content: "\e070"; } .glyphicon-backward:before { content: "\e071"; } .glyphicon-play:before { content: "\e072"; } .glyphicon-pause:before { content: "\e073"; } .glyphicon-stop:before { content: "\e074"; } .glyphicon-forward:before { content: "\e075"; } .glyphicon-fast-forward:before { content: "\e076"; } .glyphicon-step-forward:before { content: "\e077"; } .glyphicon-eject:before { content: "\e078"; } .glyphicon-chevron-left:before { content: "\e079"; } .glyphicon-chevron-right:before { content: "\e080"; } .glyphicon-plus-sign:before { content: "\e081"; } .glyphicon-minus-sign:before { content: "\e082"; } .glyphicon-remove-sign:before { content: "\e083"; } .glyphicon-ok-sign:before { content: "\e084"; } .glyphicon-question-sign:before { content: "\e085"; } .glyphicon-info-sign:before { content: "\e086"; } .glyphicon-screenshot:before { content: "\e087"; } .glyphicon-remove-circle:before { content: "\e088"; } .glyphicon-ok-circle:before { content: "\e089"; } .glyphicon-ban-circle:before { content: "\e090"; } .glyphicon-arrow-left:before { content: "\e091"; } .glyphicon-arrow-right:before { content: "\e092"; } .glyphicon-arrow-up:before { content: "\e093"; } .glyphicon-arrow-down:before { content: "\e094"; } .glyphicon-share-alt:before { content: "\e095"; } .glyphicon-resize-full:before { content: "\e096"; } .glyphicon-resize-small:before { content: "\e097"; } .glyphicon-exclamation-sign:before { content: "\e101"; } .glyphicon-gift:before { content: "\e102"; } .glyphicon-leaf:before { content: "\e103"; } .glyphicon-fire:before { content: "\e104"; } .glyphicon-eye-open:before { content: "\e105"; } .glyphicon-eye-close:before { content: "\e106"; } .glyphicon-warning-sign:before { content: "\e107"; } .glyphicon-plane:before { content: "\e108"; } .glyphicon-calendar:before { content: "\e109"; } .glyphicon-random:before { content: "\e110"; } .glyphicon-comment:before { content: "\e111"; } .glyphicon-magnet:before { content: "\e112"; } .glyphicon-chevron-up:before { content: "\e113"; } .glyphicon-chevron-down:before { content: "\e114"; } .glyphicon-retweet:before { content: "\e115"; } .glyphicon-shopping-cart:before { content: "\e116"; } .glyphicon-folder-close:before { content: "\e117"; } .glyphicon-folder-open:before { content: "\e118"; } .glyphicon-resize-vertical:before { content: "\e119"; } .glyphicon-resize-horizontal:before { content: "\e120"; } .glyphicon-hdd:before { content: "\e121"; } .glyphicon-bullhorn:before { content: "\e122"; } .glyphicon-bell:before { content: "\e123"; } .glyphicon-certificate:before { content: "\e124"; } .glyphicon-thumbs-up:before { content: "\e125"; } .glyphicon-thumbs-down:before { content: "\e126"; } .glyphicon-hand-right:before { content: "\e127"; } .glyphicon-hand-left:before { content: "\e128"; } .glyphicon-hand-up:before { content: "\e129"; } .glyphicon-hand-down:before { content: "\e130"; } .glyphicon-circle-arrow-right:before { content: "\e131"; } .glyphicon-circle-arrow-left:before { content: "\e132"; } .glyphicon-circle-arrow-up:before { content: "\e133"; } .glyphicon-circle-arrow-down:before { content: "\e134"; } .glyphicon-globe:before { content: "\e135"; } .glyphicon-wrench:before { content: "\e136"; } .glyphicon-tasks:before { content: "\e137"; } .glyphicon-filter:before { content: "\e138"; } .glyphicon-briefcase:before { content: "\e139"; } .glyphicon-fullscreen:before { content: "\e140"; } .glyphicon-dashboard:before { content: "\e141"; } .glyphicon-paperclip:before { content: "\e142"; } .glyphicon-heart-empty:before { content: "\e143"; } .glyphicon-link:before { content: "\e144"; } .glyphicon-phone:before { content: "\e145"; } .glyphicon-pushpin:before { content: "\e146"; } .glyphicon-usd:before { content: "\e148"; } .glyphicon-gbp:before { content: "\e149"; } .glyphicon-sort:before { content: "\e150"; } .glyphicon-sort-by-alphabet:before { content: "\e151"; } .glyphicon-sort-by-alphabet-alt:before { content: "\e152"; } .glyphicon-sort-by-order:before { content: "\e153"; } .glyphicon-sort-by-order-alt:before { content: "\e154"; } .glyphicon-sort-by-attributes:before { content: "\e155"; } .glyphicon-sort-by-attributes-alt:before { content: "\e156"; } .glyphicon-unchecked:before { content: "\e157"; } .glyphicon-expand:before { content: "\e158"; } .glyphicon-collapse-down:before { content: "\e159"; } .glyphicon-collapse-up:before { content: "\e160"; } .glyphicon-log-in:before { content: "\e161"; } .glyphicon-flash:before { content: "\e162"; } .glyphicon-log-out:before { content: "\e163"; } .glyphicon-new-window:before { content: "\e164"; } .glyphicon-record:before { content: "\e165"; } .glyphicon-save:before { content: "\e166"; } .glyphicon-open:before { content: "\e167"; } .glyphicon-saved:before { content: "\e168"; } .glyphicon-import:before { content: "\e169"; } .glyphicon-export:before { content: "\e170"; } .glyphicon-send:before { content: "\e171"; } .glyphicon-floppy-disk:before { content: "\e172"; } .glyphicon-floppy-saved:before { content: "\e173"; } .glyphicon-floppy-remove:before { content: "\e174"; } .glyphicon-floppy-save:before { content: "\e175"; } .glyphicon-floppy-open:before { content: "\e176"; } .glyphicon-credit-card:before { content: "\e177"; } .glyphicon-transfer:before { content: "\e178"; } .glyphicon-cutlery:before { content: "\e179"; } .glyphicon-header:before { content: "\e180"; } .glyphicon-compressed:before { content: "\e181"; } .glyphicon-earphone:before { content: "\e182"; } .glyphicon-phone-alt:before { content: "\e183"; } .glyphicon-tower:before { content: "\e184"; } .glyphicon-stats:before { content: "\e185"; } .glyphicon-sd-video:before { content: "\e186"; } .glyphicon-hd-video:before { content: "\e187"; } .glyphicon-subtitles:before { content: "\e188"; } .glyphicon-sound-stereo:before { content: "\e189"; } .glyphicon-sound-dolby:before { content: "\e190"; } .glyphicon-sound-5-1:before { content: "\e191"; } .glyphicon-sound-6-1:before { content: "\e192"; } .glyphicon-sound-7-1:before { content: "\e193"; } .glyphicon-copyright-mark:before { content: "\e194"; } .glyphicon-registration-mark:before { content: "\e195"; } .glyphicon-cloud-download:before { content: "\e197"; } .glyphicon-cloud-upload:before { content: "\e198"; } .glyphicon-tree-conifer:before { content: "\e199"; } .glyphicon-tree-deciduous:before { content: "\e200"; } .glyphicon-cd:before { content: "\e201"; } .glyphicon-save-file:before { content: "\e202"; } .glyphicon-open-file:before { content: "\e203"; } .glyphicon-level-up:before { content: "\e204"; } .glyphicon-copy:before { content: "\e205"; } .glyphicon-paste:before { content: "\e206"; } .glyphicon-alert:before { content: "\e209"; } .glyphicon-equalizer:before { content: "\e210"; } .glyphicon-king:before { content: "\e211"; } .glyphicon-queen:before { content: "\e212"; } .glyphicon-pawn:before { content: "\e213"; } .glyphicon-bishop:before { content: "\e214"; } .glyphicon-knight:before { content: "\e215"; } .glyphicon-baby-formula:before { content: "\e216"; } .glyphicon-tent:before { content: "\26fa"; } .glyphicon-blackboard:before { content: "\e218"; } .glyphicon-bed:before { content: "\e219"; } .glyphicon-apple:before { content: "\f8ff"; } .glyphicon-erase:before { content: "\e221"; } .glyphicon-hourglass:before { content: "\231b"; } .glyphicon-lamp:before { content: "\e223"; } .glyphicon-duplicate:before { content: "\e224"; } .glyphicon-piggy-bank:before { content: "\e225"; } .glyphicon-scissors:before { content: "\e226"; } .glyphicon-bitcoin:before { content: "\e227"; } .glyphicon-btc:before { content: "\e227"; } .glyphicon-xbt:before { content: "\e227"; } .glyphicon-yen:before { content: "\00a5"; } .glyphicon-jpy:before { content: "\00a5"; } .glyphicon-ruble:before { content: "\20bd"; } .glyphicon-rub:before { content: "\20bd"; } .glyphicon-scale:before { content: "\e230"; } .glyphicon-ice-lolly:before { content: "\e231"; } .glyphicon-ice-lolly-tasted:before { content: "\e232"; } .glyphicon-education:before { content: "\e233"; } .glyphicon-option-horizontal:before { content: "\e234"; } .glyphicon-option-vertical:before { content: "\e235"; } .glyphicon-menu-hamburger:before { content: "\e236"; } .glyphicon-modal-window:before { content: "\e237"; } .glyphicon-oil:before { content: "\e238"; } .glyphicon-grain:before { content: "\e239"; } .glyphicon-sunglasses:before { content: "\e240"; } .glyphicon-text-size:before { content: "\e241"; } .glyphicon-text-color:before { content: "\e242"; } .glyphicon-text-background:before { content: "\e243"; } .glyphicon-object-align-top:before { content: "\e244"; } .glyphicon-object-align-bottom:before { content: "\e245"; } .glyphicon-object-align-horizontal:before { content: "\e246"; } .glyphicon-object-align-left:before { content: "\e247"; } .glyphicon-object-align-vertical:before { content: "\e248"; } .glyphicon-object-align-right:before { content: "\e249"; } .glyphicon-triangle-right:before { content: "\e250"; } .glyphicon-triangle-left:before { content: "\e251"; } .glyphicon-triangle-bottom:before { content: "\e252"; } .glyphicon-triangle-top:before { content: "\e253"; } .glyphicon-console:before { content: "\e254"; } .glyphicon-superscript:before { content: "\e255"; } .glyphicon-subscript:before { content: "\e256"; } .glyphicon-menu-left:before { content: "\e257"; } .glyphicon-menu-right:before { content: "\e258"; } .glyphicon-menu-down:before { content: "\e259"; } .glyphicon-menu-up:before { content: "\e260"; } * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } *:before, *:after { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } html { font-size: 10px; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } body { font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 13px; line-height: 1.846; color: #666666; background-color: #ffffff; } input, button, select, textarea { font-family: inherit; font-size: inherit; line-height: inherit; } a { color: #2196f3; text-decoration: none; } a:hover, a:focus { color: #0a6ebd; text-decoration: underline; } a:focus { outline: thin dotted; outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } figure { margin: 0; } img { vertical-align: middle; } .img-responsive, .thumbnail > img, .thumbnail a > img, .carousel-inner > .item > img, .carousel-inner > .item > a > img { display: block; max-width: 100%; height: auto; } .img-rounded { border-radius: 3px; } .img-thumbnail { padding: 4px; line-height: 1.846; background-color: #ffffff; border: 1px solid #dddddd; border-radius: 3px; -webkit-transition: all 0.2s ease-in-out; -o-transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out; display: inline-block; max-width: 100%; height: auto; } .img-circle { border-radius: 50%; } hr { margin-top: 23px; margin-bottom: 23px; border: 0; border-top: 1px solid #eeeeee; } .sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; } .sr-only-focusable:active, .sr-only-focusable:focus { position: static; width: auto; height: auto; margin: 0; overflow: visible; clip: auto; } [role="button"] { cursor: pointer; } h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { font-family: inherit; font-weight: 400; line-height: 1.1; color: #444444; } h1 small, h2 small, h3 small, h4 small, h5 small, h6 small, .h1 small, .h2 small, .h3 small, .h4 small, .h5 small, .h6 small, h1 .small, h2 .small, h3 .small, h4 .small, h5 .small, h6 .small, .h1 .small, .h2 .small, .h3 .small, .h4 .small, .h5 .small, .h6 .small { font-weight: normal; line-height: 1; color: #bbbbbb; } h1, .h1, h2, .h2, h3, .h3 { margin-top: 23px; margin-bottom: 11.5px; } h1 small, .h1 small, h2 small, .h2 small, h3 small, .h3 small, h1 .small, .h1 .small, h2 .small, .h2 .small, h3 .small, .h3 .small { font-size: 65%; } h4, .h4, h5, .h5, h6, .h6 { margin-top: 11.5px; margin-bottom: 11.5px; } h4 small, .h4 small, h5 small, .h5 small, h6 small, .h6 small, h4 .small, .h4 .small, h5 .small, .h5 .small, h6 .small, .h6 .small { font-size: 75%; } h1, .h1 { font-size: 56px; } h2, .h2 { font-size: 45px; } h3, .h3 { font-size: 34px; } h4, .h4 { font-size: 24px; } h5, .h5 { font-size: 20px; } h6, .h6 { font-size: 14px; } p { margin: 0 0 11.5px; } .lead { margin-bottom: 23px; font-size: 14px; font-weight: 300; line-height: 1.4; } @media (min-width: 768px) { .lead { font-size: 19.5px; } } small, .small { font-size: 92%; } mark, .mark { background-color: #ffe0b2; padding: .2em; } .text-left { text-align: left; } .text-right { text-align: right; } .text-center { text-align: center; } .text-justify { text-align: justify; } .text-nowrap { white-space: nowrap; } .text-lowercase { text-transform: lowercase; } .text-uppercase { text-transform: uppercase; } .text-capitalize { text-transform: capitalize; } .text-muted { color: #bbbbbb; } .text-primary { color: #2196f3; } a.text-primary:hover, a.text-primary:focus { color: #0c7cd5; } .text-success { color: #4caf50; } a.text-success:hover, a.text-success:focus { color: #3d8b40; } .text-info { color: #9c27b0; } a.text-info:hover, a.text-info:focus { color: #771e86; } .text-warning { color: #ff9800; } a.text-warning:hover, a.text-warning:focus { color: #cc7a00; } .text-danger { color: #e51c23; } a.text-danger:hover, a.text-danger:focus { color: #b9151b; } .bg-primary { color: #fff; background-color: #2196f3; } a.bg-primary:hover, a.bg-primary:focus { background-color: #0c7cd5; } .bg-success { background-color: #dff0d8; } a.bg-success:hover, a.bg-success:focus { background-color: #c1e2b3; } .bg-info { background-color: #e1bee7; } a.bg-info:hover, a.bg-info:focus { background-color: #d099d9; } .bg-warning { background-color: #ffe0b2; } a.bg-warning:hover, a.bg-warning:focus { background-color: #ffcb7f; } .bg-danger { background-color: #f9bdbb; } a.bg-danger:hover, a.bg-danger:focus { background-color: #f5908c; } .page-header { padding-bottom: 10.5px; margin: 46px 0 23px; border-bottom: 1px solid #eeeeee; } ul, ol { margin-top: 0; margin-bottom: 11.5px; } ul ul, ol ul, ul ol, ol ol { margin-bottom: 0; } .list-unstyled { padding-left: 0; list-style: none; } .list-inline { padding-left: 0; list-style: none; margin-left: -5px; } .list-inline > li { display: inline-block; padding-left: 5px; padding-right: 5px; } dl { margin-top: 0; margin-bottom: 23px; } dt, dd { line-height: 1.846; } dt { font-weight: bold; } dd { margin-left: 0; } @media (min-width: 768px) { .dl-horizontal dt { float: left; width: 160px; clear: left; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .dl-horizontal dd { margin-left: 180px; } } abbr[title], abbr[data-original-title] { cursor: help; border-bottom: 1px dotted #bbbbbb; } .initialism { font-size: 90%; text-transform: uppercase; } blockquote { padding: 11.5px 23px; margin: 0 0 23px; font-size: 16.25px; border-left: 5px solid #eeeeee; } blockquote p:last-child, blockquote ul:last-child, blockquote ol:last-child { margin-bottom: 0; } blockquote footer, blockquote small, blockquote .small { display: block; font-size: 80%; line-height: 1.846; color: #bbbbbb; } blockquote footer:before, blockquote small:before, blockquote .small:before { content: '\2014 \00A0'; } .blockquote-reverse, blockquote.pull-right { padding-right: 15px; padding-left: 0; border-right: 5px solid #eeeeee; border-left: 0; text-align: right; } .blockquote-reverse footer:before, blockquote.pull-right footer:before, .blockquote-reverse small:before, blockquote.pull-right small:before, .blockquote-reverse .small:before, blockquote.pull-right .small:before { content: ''; } .blockquote-reverse footer:after, blockquote.pull-right footer:after, .blockquote-reverse small:after, blockquote.pull-right small:after, .blockquote-reverse .small:after, blockquote.pull-right .small:after { content: '\00A0 \2014'; } address { margin-bottom: 23px; font-style: normal; line-height: 1.846; } code, kbd, pre, samp { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; } code { padding: 2px 4px; font-size: 90%; color: #c7254e; background-color: #f9f2f4; border-radius: 3px; } kbd { padding: 2px 4px; font-size: 90%; color: #ffffff; background-color: #333333; border-radius: 3px; -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); } kbd kbd { padding: 0; font-size: 100%; font-weight: bold; -webkit-box-shadow: none; box-shadow: none; } pre { display: block; padding: 11px; margin: 0 0 11.5px; font-size: 12px; line-height: 1.846; word-break: break-all; word-wrap: break-word; color: #212121; background-color: #f5f5f5; border: 1px solid #cccccc; border-radius: 3px; } pre code { padding: 0; font-size: inherit; color: inherit; white-space: pre-wrap; background-color: transparent; border-radius: 0; } .pre-scrollable { max-height: 340px; overflow-y: scroll; } .container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px; } @media (min-width: 768px) { .container { width: 750px; } } @media (min-width: 992px) { .container { width: 970px; } } @media (min-width: 1200px) { .container { width: 1170px; } } .container-fluid { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px; } .row { margin-left: -15px; margin-right: -15px; } .col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { position: relative; min-height: 1px; padding-left: 15px; padding-right: 15px; } .col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { float: left; } .col-xs-12 { width: 100%; } .col-xs-11 { width: 91.66666667%; } .col-xs-10 { width: 83.33333333%; } .col-xs-9 { width: 75%; } .col-xs-8 { width: 66.66666667%; } .col-xs-7 { width: 58.33333333%; } .col-xs-6 { width: 50%; } .col-xs-5 { width: 41.66666667%; } .col-xs-4 { width: 33.33333333%; } .col-xs-3 { width: 25%; } .col-xs-2 { width: 16.66666667%; } .col-xs-1 { width: 8.33333333%; } .col-xs-pull-12 { right: 100%; } .col-xs-pull-11 { right: 91.66666667%; } .col-xs-pull-10 { right: 83.33333333%; } .col-xs-pull-9 { right: 75%; } .col-xs-pull-8 { right: 66.66666667%; } .col-xs-pull-7 { right: 58.33333333%; } .col-xs-pull-6 { right: 50%; } .col-xs-pull-5 { right: 41.66666667%; } .col-xs-pull-4 { right: 33.33333333%; } .col-xs-pull-3 { right: 25%; } .col-xs-pull-2 { right: 16.66666667%; } .col-xs-pull-1 { right: 8.33333333%; } .col-xs-pull-0 { right: auto; } .col-xs-push-12 { left: 100%; } .col-xs-push-11 { left: 91.66666667%; } .col-xs-push-10 { left: 83.33333333%; } .col-xs-push-9 { left: 75%; } .col-xs-push-8 { left: 66.66666667%; } .col-xs-push-7 { left: 58.33333333%; } .col-xs-push-6 { left: 50%; } .col-xs-push-5 { left: 41.66666667%; } .col-xs-push-4 { left: 33.33333333%; } .col-xs-push-3 { left: 25%; } .col-xs-push-2 { left: 16.66666667%; } .col-xs-push-1 { left: 8.33333333%; } .col-xs-push-0 { left: auto; } .col-xs-offset-12 { margin-left: 100%; } .col-xs-offset-11 { margin-left: 91.66666667%; } .col-xs-offset-10 { margin-left: 83.33333333%; } .col-xs-offset-9 { margin-left: 75%; } .col-xs-offset-8 { margin-left: 66.66666667%; } .col-xs-offset-7 { margin-left: 58.33333333%; } .col-xs-offset-6 { margin-left: 50%; } .col-xs-offset-5 { margin-left: 41.66666667%; } .col-xs-offset-4 { margin-left: 33.33333333%; } .col-xs-offset-3 { margin-left: 25%; } .col-xs-offset-2 { margin-left: 16.66666667%; } .col-xs-offset-1 { margin-left: 8.33333333%; } .col-xs-offset-0 { margin-left: 0%; } @media (min-width: 768px) { .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { float: left; } .col-sm-12 { width: 100%; } .col-sm-11 { width: 91.66666667%; } .col-sm-10 { width: 83.33333333%; } .col-sm-9 { width: 75%; } .col-sm-8 { width: 66.66666667%; } .col-sm-7 { width: 58.33333333%; } .col-sm-6 { width: 50%; } .col-sm-5 { width: 41.66666667%; } .col-sm-4 { width: 33.33333333%; } .col-sm-3 { width: 25%; } .col-sm-2 { width: 16.66666667%; } .col-sm-1 { width: 8.33333333%; } .col-sm-pull-12 { right: 100%; } .col-sm-pull-11 { right: 91.66666667%; } .col-sm-pull-10 { right: 83.33333333%; } .col-sm-pull-9 { right: 75%; } .col-sm-pull-8 { right: 66.66666667%; } .col-sm-pull-7 { right: 58.33333333%; } .col-sm-pull-6 { right: 50%; } .col-sm-pull-5 { right: 41.66666667%; } .col-sm-pull-4 { right: 33.33333333%; } .col-sm-pull-3 { right: 25%; } .col-sm-pull-2 { right: 16.66666667%; } .col-sm-pull-1 { right: 8.33333333%; } .col-sm-pull-0 { right: auto; } .col-sm-push-12 { left: 100%; } .col-sm-push-11 { left: 91.66666667%; } .col-sm-push-10 { left: 83.33333333%; } .col-sm-push-9 { left: 75%; } .col-sm-push-8 { left: 66.66666667%; } .col-sm-push-7 { left: 58.33333333%; } .col-sm-push-6 { left: 50%; } .col-sm-push-5 { left: 41.66666667%; } .col-sm-push-4 { left: 33.33333333%; } .col-sm-push-3 { left: 25%; } .col-sm-push-2 { left: 16.66666667%; } .col-sm-push-1 { left: 8.33333333%; } .col-sm-push-0 { left: auto; } .col-sm-offset-12 { margin-left: 100%; } .col-sm-offset-11 { margin-left: 91.66666667%; } .col-sm-offset-10 { margin-left: 83.33333333%; } .col-sm-offset-9 { margin-left: 75%; } .col-sm-offset-8 { margin-left: 66.66666667%; } .col-sm-offset-7 { margin-left: 58.33333333%; } .col-sm-offset-6 { margin-left: 50%; } .col-sm-offset-5 { margin-left: 41.66666667%; } .col-sm-offset-4 { margin-left: 33.33333333%; } .col-sm-offset-3 { margin-left: 25%; } .col-sm-offset-2 { margin-left: 16.66666667%; } .col-sm-offset-1 { margin-left: 8.33333333%; } .col-sm-offset-0 { margin-left: 0%; } } @media (min-width: 992px) { .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { float: left; } .col-md-12 { width: 100%; } .col-md-11 { width: 91.66666667%; } .col-md-10 { width: 83.33333333%; } .col-md-9 { width: 75%; } .col-md-8 { width: 66.66666667%; } .col-md-7 { width: 58.33333333%; } .col-md-6 { width: 50%; } .col-md-5 { width: 41.66666667%; } .col-md-4 { width: 33.33333333%; } .col-md-3 { width: 25%; } .col-md-2 { width: 16.66666667%; } .col-md-1 { width: 8.33333333%; } .col-md-pull-12 { right: 100%; } .col-md-pull-11 { right: 91.66666667%; } .col-md-pull-10 { right: 83.33333333%; } .col-md-pull-9 { right: 75%; } .col-md-pull-8 { right: 66.66666667%; } .col-md-pull-7 { right: 58.33333333%; } .col-md-pull-6 { right: 50%; } .col-md-pull-5 { right: 41.66666667%; } .col-md-pull-4 { right: 33.33333333%; } .col-md-pull-3 { right: 25%; } .col-md-pull-2 { right: 16.66666667%; } .col-md-pull-1 { right: 8.33333333%; } .col-md-pull-0 { right: auto; } .col-md-push-12 { left: 100%; } .col-md-push-11 { left: 91.66666667%; } .col-md-push-10 { left: 83.33333333%; } .col-md-push-9 { left: 75%; } .col-md-push-8 { left: 66.66666667%; } .col-md-push-7 { left: 58.33333333%; } .col-md-push-6 { left: 50%; } .col-md-push-5 { left: 41.66666667%; } .col-md-push-4 { left: 33.33333333%; } .col-md-push-3 { left: 25%; } .col-md-push-2 { left: 16.66666667%; } .col-md-push-1 { left: 8.33333333%; } .col-md-push-0 { left: auto; } .col-md-offset-12 { margin-left: 100%; } .col-md-offset-11 { margin-left: 91.66666667%; } .col-md-offset-10 { margin-left: 83.33333333%; } .col-md-offset-9 { margin-left: 75%; } .col-md-offset-8 { margin-left: 66.66666667%; } .col-md-offset-7 { margin-left: 58.33333333%; } .col-md-offset-6 { margin-left: 50%; } .col-md-offset-5 { margin-left: 41.66666667%; } .col-md-offset-4 { margin-left: 33.33333333%; } .col-md-offset-3 { margin-left: 25%; } .col-md-offset-2 { margin-left: 16.66666667%; } .col-md-offset-1 { margin-left: 8.33333333%; } .col-md-offset-0 { margin-left: 0%; } } @media (min-width: 1200px) { .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { float: left; } .col-lg-12 { width: 100%; } .col-lg-11 { width: 91.66666667%; } .col-lg-10 { width: 83.33333333%; } .col-lg-9 { width: 75%; } .col-lg-8 { width: 66.66666667%; } .col-lg-7 { width: 58.33333333%; } .col-lg-6 { width: 50%; } .col-lg-5 { width: 41.66666667%; } .col-lg-4 { width: 33.33333333%; } .col-lg-3 { width: 25%; } .col-lg-2 { width: 16.66666667%; } .col-lg-1 { width: 8.33333333%; } .col-lg-pull-12 { right: 100%; } .col-lg-pull-11 { right: 91.66666667%; } .col-lg-pull-10 { right: 83.33333333%; } .col-lg-pull-9 { right: 75%; } .col-lg-pull-8 { right: 66.66666667%; } .col-lg-pull-7 { right: 58.33333333%; } .col-lg-pull-6 { right: 50%; } .col-lg-pull-5 { right: 41.66666667%; } .col-lg-pull-4 { right: 33.33333333%; } .col-lg-pull-3 { right: 25%; } .col-lg-pull-2 { right: 16.66666667%; } .col-lg-pull-1 { right: 8.33333333%; } .col-lg-pull-0 { right: auto; } .col-lg-push-12 { left: 100%; } .col-lg-push-11 { left: 91.66666667%; } .col-lg-push-10 { left: 83.33333333%; } .col-lg-push-9 { left: 75%; } .col-lg-push-8 { left: 66.66666667%; } .col-lg-push-7 { left: 58.33333333%; } .col-lg-push-6 { left: 50%; } .col-lg-push-5 { left: 41.66666667%; } .col-lg-push-4 { left: 33.33333333%; } .col-lg-push-3 { left: 25%; } .col-lg-push-2 { left: 16.66666667%; } .col-lg-push-1 { left: 8.33333333%; } .col-lg-push-0 { left: auto; } .col-lg-offset-12 { margin-left: 100%; } .col-lg-offset-11 { margin-left: 91.66666667%; } .col-lg-offset-10 { margin-left: 83.33333333%; } .col-lg-offset-9 { margin-left: 75%; } .col-lg-offset-8 { margin-left: 66.66666667%; } .col-lg-offset-7 { margin-left: 58.33333333%; } .col-lg-offset-6 { margin-left: 50%; } .col-lg-offset-5 { margin-left: 41.66666667%; } .col-lg-offset-4 { margin-left: 33.33333333%; } .col-lg-offset-3 { margin-left: 25%; } .col-lg-offset-2 { margin-left: 16.66666667%; } .col-lg-offset-1 { margin-left: 8.33333333%; } .col-lg-offset-0 { margin-left: 0%; } } table { background-color: transparent; } caption { padding-top: 8px; padding-bottom: 8px; color: #bbbbbb; text-align: left; } th { text-align: left; } .table { width: 100%; max-width: 100%; margin-bottom: 23px; } .table > thead > tr > th, .table > tbody > tr > th, .table > tfoot > tr > th, .table > thead > tr > td, .table > tbody > tr > td, .table > tfoot > tr > td { padding: 8px; line-height: 1.846; vertical-align: top; border-top: 1px solid #dddddd; } .table > thead > tr > th { vertical-align: bottom; border-bottom: 2px solid #dddddd; } .table > caption + thead > tr:first-child > th, .table > colgroup + thead > tr:first-child > th, .table > thead:first-child > tr:first-child > th, .table > caption + thead > tr:first-child > td, .table > colgroup + thead > tr:first-child > td, .table > thead:first-child > tr:first-child > td { border-top: 0; } .table > tbody + tbody { border-top: 2px solid #dddddd; } .table .table { background-color: #ffffff; } .table-condensed > thead > tr > th, .table-condensed > tbody > tr > th, .table-condensed > tfoot > tr > th, .table-condensed > thead > tr > td, .table-condensed > tbody > tr > td, .table-condensed > tfoot > tr > td { padding: 5px; } .table-bordered { border: 1px solid #dddddd; } .table-bordered > thead > tr > th, .table-bordered > tbody > tr > th, .table-bordered > tfoot > tr > th, .table-bordered > thead > tr > td, .table-bordered > tbody > tr > td, .table-bordered > tfoot > tr > td { border: 1px solid #dddddd; } .table-bordered > thead > tr > th, .table-bordered > thead > tr > td { border-bottom-width: 2px; } .table-striped > tbody > tr:nth-of-type(odd) { background-color: #f9f9f9; } .table-hover > tbody > tr:hover { background-color: #f5f5f5; } table col[class*="col-"] { position: static; float: none; display: table-column; } table td[class*="col-"], table th[class*="col-"] { position: static; float: none; display: table-cell; } .table > thead > tr > td.active, .table > tbody > tr > td.active, .table > tfoot > tr > td.active, .table > thead > tr > th.active, .table > tbody > tr > th.active, .table > tfoot > tr > th.active, .table > thead > tr.active > td, .table > tbody > tr.active > td, .table > tfoot > tr.active > td, .table > thead > tr.active > th, .table > tbody > tr.active > th, .table > tfoot > tr.active > th { background-color: #f5f5f5; } .table-hover > tbody > tr > td.active:hover, .table-hover > tbody > tr > th.active:hover, .table-hover > tbody > tr.active:hover > td, .table-hover > tbody > tr:hover > .active, .table-hover > tbody > tr.active:hover > th { background-color: #e8e8e8; } .table > thead > tr > td.success, .table > tbody > tr > td.success, .table > tfoot > tr > td.success, .table > thead > tr > th.success, .table > tbody > tr > th.success, .table > tfoot > tr > th.success, .table > thead > tr.success > td, .table > tbody > tr.success > td, .table > tfoot > tr.success > td, .table > thead > tr.success > th, .table > tbody > tr.success > th, .table > tfoot > tr.success > th { background-color: #dff0d8; } .table-hover > tbody > tr > td.success:hover, .table-hover > tbody > tr > th.success:hover, .table-hover > tbody > tr.success:hover > td, .table-hover > tbody > tr:hover > .success, .table-hover > tbody > tr.success:hover > th { background-color: #d0e9c6; } .table > thead > tr > td.info, .table > tbody > tr > td.info, .table > tfoot > tr > td.info, .table > thead > tr > th.info, .table > tbody > tr > th.info, .table > tfoot > tr > th.info, .table > thead > tr.info > td, .table > tbody > tr.info > td, .table > tfoot > tr.info > td, .table > thead > tr.info > th, .table > tbody > tr.info > th, .table > tfoot > tr.info > th { background-color: #e1bee7; } .table-hover > tbody > tr > td.info:hover, .table-hover > tbody > tr > th.info:hover, .table-hover > tbody > tr.info:hover > td, .table-hover > tbody > tr:hover > .info, .table-hover > tbody > tr.info:hover > th { background-color: #d8abe0; } .table > thead > tr > td.warning, .table > tbody > tr > td.warning, .table > tfoot > tr > td.warning, .table > thead > tr > th.warning, .table > tbody > tr > th.warning, .table > tfoot > tr > th.warning, .table > thead > tr.warning > td, .table > tbody > tr.warning > td, .table > tfoot > tr.warning > td, .table > thead > tr.warning > th, .table > tbody > tr.warning > th, .table > tfoot > tr.warning > th { background-color: #ffe0b2; } .table-hover > tbody > tr > td.warning:hover, .table-hover > tbody > tr > th.warning:hover, .table-hover > tbody > tr.warning:hover > td, .table-hover > tbody > tr:hover > .warning, .table-hover > tbody > tr.warning:hover > th { background-color: #ffd699; } .table > thead > tr > td.danger, .table > tbody > tr > td.danger, .table > tfoot > tr > td.danger, .table > thead > tr > th.danger, .table > tbody > tr > th.danger, .table > tfoot > tr > th.danger, .table > thead > tr.danger > td, .table > tbody > tr.danger > td, .table > tfoot > tr.danger > td, .table > thead > tr.danger > th, .table > tbody > tr.danger > th, .table > tfoot > tr.danger > th { background-color: #f9bdbb; } .table-hover > tbody > tr > td.danger:hover, .table-hover > tbody > tr > th.danger:hover, .table-hover > tbody > tr.danger:hover > td, .table-hover > tbody > tr:hover > .danger, .table-hover > tbody > tr.danger:hover > th { background-color: #f7a6a4; } .table-responsive { overflow-x: auto; min-height: 0.01%; } @media screen and (max-width: 767px) { .table-responsive { width: 100%; margin-bottom: 17.25px; overflow-y: hidden; -ms-overflow-style: -ms-autohiding-scrollbar; border: 1px solid #dddddd; } .table-responsive > .table { margin-bottom: 0; } .table-responsive > .table > thead > tr > th, .table-responsive > .table > tbody > tr > th, .table-responsive > .table > tfoot > tr > th, .table-responsive > .table > thead > tr > td, .table-responsive > .table > tbody > tr > td, .table-responsive > .table > tfoot > tr > td { white-space: nowrap; } .table-responsive > .table-bordered { border: 0; } .table-responsive > .table-bordered > thead > tr > th:first-child, .table-responsive > .table-bordered > tbody > tr > th:first-child, .table-responsive > .table-bordered > tfoot > tr > th:first-child, .table-responsive > .table-bordered > thead > tr > td:first-child, .table-responsive > .table-bordered > tbody > tr > td:first-child, .table-responsive > .table-bordered > tfoot > tr > td:first-child { border-left: 0; } .table-responsive > .table-bordered > thead > tr > th:last-child, .table-responsive > .table-bordered > tbody > tr > th:last-child, .table-responsive > .table-bordered > tfoot > tr > th:last-child, .table-responsive > .table-bordered > thead > tr > td:last-child, .table-responsive > .table-bordered > tbody > tr > td:last-child, .table-responsive > .table-bordered > tfoot > tr > td:last-child { border-right: 0; } .table-responsive > .table-bordered > tbody > tr:last-child > th, .table-responsive > .table-bordered > tfoot > tr:last-child > th, .table-responsive > .table-bordered > tbody > tr:last-child > td, .table-responsive > .table-bordered > tfoot > tr:last-child > td { border-bottom: 0; } } fieldset { padding: 0; margin: 0; border: 0; min-width: 0; } legend { display: block; width: 100%; padding: 0; margin-bottom: 23px; font-size: 19.5px; line-height: inherit; color: #212121; border: 0; border-bottom: 1px solid #e5e5e5; } label { display: inline-block; max-width: 100%; margin-bottom: 5px; font-weight: bold; } input[type="search"] { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } input[type="radio"], input[type="checkbox"] { margin: 4px 0 0; margin-top: 1px \9; line-height: normal; } input[type="file"] { display: block; } input[type="range"] { display: block; width: 100%; } select[multiple], select[size] { height: auto; } input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { outline: thin dotted; outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } output { display: block; padding-top: 7px; font-size: 13px; line-height: 1.846; color: #666666; } .form-control { display: block; width: 100%; height: 37px; padding: 6px 16px; font-size: 13px; line-height: 1.846; color: #666666; background-color: transparent; background-image: none; border: 1px solid transparent; border-radius: 3px; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; } .form-control:focus { border-color: #66afe9; outline: 0; -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); } .form-control::-moz-placeholder { color: #bbbbbb; opacity: 1; } .form-control:-ms-input-placeholder { color: #bbbbbb; } .form-control::-webkit-input-placeholder { color: #bbbbbb; } .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { background-color: transparent; opacity: 1; } .form-control[disabled], fieldset[disabled] .form-control { cursor: not-allowed; } textarea.form-control { height: auto; } input[type="search"] { -webkit-appearance: none; } @media screen and (-webkit-min-device-pixel-ratio: 0) { input[type="date"].form-control, input[type="time"].form-control, input[type="datetime-local"].form-control, input[type="month"].form-control { line-height: 37px; } input[type="date"].input-sm, input[type="time"].input-sm, input[type="datetime-local"].input-sm, input[type="month"].input-sm, .input-group-sm input[type="date"], .input-group-sm input[type="time"], .input-group-sm input[type="datetime-local"], .input-group-sm input[type="month"] { line-height: 30px; } input[type="date"].input-lg, input[type="time"].input-lg, input[type="datetime-local"].input-lg, input[type="month"].input-lg, .input-group-lg input[type="date"], .input-group-lg input[type="time"], .input-group-lg input[type="datetime-local"], .input-group-lg input[type="month"] { line-height: 45px; } } .form-group { margin-bottom: 15px; } .radio, .checkbox { position: relative; display: block; margin-top: 10px; margin-bottom: 10px; } .radio label, .checkbox label { min-height: 23px; padding-left: 20px; margin-bottom: 0; font-weight: normal; cursor: pointer; } .radio input[type="radio"], .radio-inline input[type="radio"], .checkbox input[type="checkbox"], .checkbox-inline input[type="checkbox"] { position: absolute; margin-left: -20px; margin-top: 4px \9; } .radio + .radio, .checkbox + .checkbox { margin-top: -5px; } .radio-inline, .checkbox-inline { position: relative; display: inline-block; padding-left: 20px; margin-bottom: 0; vertical-align: middle; font-weight: normal; cursor: pointer; } .radio-inline + .radio-inline, .checkbox-inline + .checkbox-inline { margin-top: 0; margin-left: 10px; } input[type="radio"][disabled], input[type="checkbox"][disabled], input[type="radio"].disabled, input[type="checkbox"].disabled, fieldset[disabled] input[type="radio"], fieldset[disabled] input[type="checkbox"] { cursor: not-allowed; } .radio-inline.disabled, .checkbox-inline.disabled, fieldset[disabled] .radio-inline, fieldset[disabled] .checkbox-inline { cursor: not-allowed; } .radio.disabled label, .checkbox.disabled label, fieldset[disabled] .radio label, fieldset[disabled] .checkbox label { cursor: not-allowed; } .form-control-static { padding-top: 7px; padding-bottom: 7px; margin-bottom: 0; min-height: 36px; } .form-control-static.input-lg, .form-control-static.input-sm { padding-left: 0; padding-right: 0; } .input-sm { height: 30px; padding: 5px 10px; font-size: 12px; line-height: 1.5; border-radius: 3px; } select.input-sm { height: 30px; line-height: 30px; } textarea.input-sm, select[multiple].input-sm { height: auto; } .form-group-sm .form-control { height: 30px; padding: 5px 10px; font-size: 12px; line-height: 1.5; border-radius: 3px; } .form-group-sm select.form-control { height: 30px; line-height: 30px; } .form-group-sm textarea.form-control, .form-group-sm select[multiple].form-control { height: auto; } .form-group-sm .form-control-static { height: 30px; min-height: 35px; padding: 6px 10px; font-size: 12px; line-height: 1.5; } .input-lg { height: 45px; padding: 10px 16px; font-size: 17px; line-height: 1.3333333; border-radius: 3px; } select.input-lg { height: 45px; line-height: 45px; } textarea.input-lg, select[multiple].input-lg { height: auto; } .form-group-lg .form-control { height: 45px; padding: 10px 16px; font-size: 17px; line-height: 1.3333333; border-radius: 3px; } .form-group-lg select.form-control { height: 45px; line-height: 45px; } .form-group-lg textarea.form-control, .form-group-lg select[multiple].form-control { height: auto; } .form-group-lg .form-control-static { height: 45px; min-height: 40px; padding: 11px 16px; font-size: 17px; line-height: 1.3333333; } .has-feedback { position: relative; } .has-feedback .form-control { padding-right: 46.25px; } .form-control-feedback { position: absolute; top: 0; right: 0; z-index: 2; display: block; width: 37px; height: 37px; line-height: 37px; text-align: center; pointer-events: none; } .input-lg + .form-control-feedback, .input-group-lg + .form-control-feedback, .form-group-lg .form-control + .form-control-feedback { width: 45px; height: 45px; line-height: 45px; } .input-sm + .form-control-feedback, .input-group-sm + .form-control-feedback, .form-group-sm .form-control + .form-control-feedback { width: 30px; height: 30px; line-height: 30px; } .has-success .help-block, .has-success .control-label, .has-success .radio, .has-success .checkbox, .has-success .radio-inline, .has-success .checkbox-inline, .has-success.radio label, .has-success.checkbox label, .has-success.radio-inline label, .has-success.checkbox-inline label { color: #4caf50; } .has-success .form-control { border-color: #4caf50; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } .has-success .form-control:focus { border-color: #3d8b40; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #92cf94; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #92cf94; } .has-success .input-group-addon { color: #4caf50; border-color: #4caf50; background-color: #dff0d8; } .has-success .form-control-feedback { color: #4caf50; } .has-warning .help-block, .has-warning .control-label, .has-warning .radio, .has-warning .checkbox, .has-warning .radio-inline, .has-warning .checkbox-inline, .has-warning.radio label, .has-warning.checkbox label, .has-warning.radio-inline label, .has-warning.checkbox-inline label { color: #ff9800; } .has-warning .form-control { border-color: #ff9800; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } .has-warning .form-control:focus { border-color: #cc7a00; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffc166; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffc166; } .has-warning .input-group-addon { color: #ff9800; border-color: #ff9800; background-color: #ffe0b2; } .has-warning .form-control-feedback { color: #ff9800; } .has-error .help-block, .has-error .control-label, .has-error .radio, .has-error .checkbox, .has-error .radio-inline, .has-error .checkbox-inline, .has-error.radio label, .has-error.checkbox label, .has-error.radio-inline label, .has-error.checkbox-inline label { color: #e51c23; } .has-error .form-control { border-color: #e51c23; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } .has-error .form-control:focus { border-color: #b9151b; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ef787c; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ef787c; } .has-error .input-group-addon { color: #e51c23; border-color: #e51c23; background-color: #f9bdbb; } .has-error .form-control-feedback { color: #e51c23; } .has-feedback label ~ .form-control-feedback { top: 28px; } .has-feedback label.sr-only ~ .form-control-feedback { top: 0; } .help-block { display: block; margin-top: 5px; margin-bottom: 10px; color: #a6a6a6; } @media (min-width: 768px) { .form-inline .form-group { display: inline-block; margin-bottom: 0; vertical-align: middle; } .form-inline .form-control { display: inline-block; width: auto; vertical-align: middle; } .form-inline .form-control-static { display: inline-block; } .form-inline .input-group { display: inline-table; vertical-align: middle; } .form-inline .input-group .input-group-addon, .form-inline .input-group .input-group-btn, .form-inline .input-group .form-control { width: auto; } .form-inline .input-group > .form-control { width: 100%; } .form-inline .control-label { margin-bottom: 0; vertical-align: middle; } .form-inline .radio, .form-inline .checkbox { display: inline-block; margin-top: 0; margin-bottom: 0; vertical-align: middle; } .form-inline .radio label, .form-inline .checkbox label { padding-left: 0; } .form-inline .radio input[type="radio"], .form-inline .checkbox input[type="checkbox"] { position: relative; margin-left: 0; } .form-inline .has-feedback .form-control-feedback { top: 0; } } .form-horizontal .radio, .form-horizontal .checkbox, .form-horizontal .radio-inline, .form-horizontal .checkbox-inline { margin-top: 0; margin-bottom: 0; padding-top: 7px; } .form-horizontal .radio, .form-horizontal .checkbox { min-height: 30px; } .form-horizontal .form-group { margin-left: -15px; margin-right: -15px; } @media (min-width: 768px) { .form-horizontal .control-label { text-align: right; margin-bottom: 0; padding-top: 7px; } } .form-horizontal .has-feedback .form-control-feedback { right: 15px; } @media (min-width: 768px) { .form-horizontal .form-group-lg .control-label { padding-top: 14.333333px; font-size: 17px; } } @media (min-width: 768px) { .form-horizontal .form-group-sm .control-label { padding-top: 6px; font-size: 12px; } } .btn { display: inline-block; margin-bottom: 0; font-weight: normal; text-align: center; vertical-align: middle; -ms-touch-action: manipulation; touch-action: manipulation; cursor: pointer; background-image: none; border: 1px solid transparent; white-space: nowrap; padding: 6px 16px; font-size: 13px; line-height: 1.846; border-radius: 3px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .btn:focus, .btn:active:focus, .btn.active:focus, .btn.focus, .btn:active.focus, .btn.active.focus { outline: thin dotted; outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } .btn:hover, .btn:focus, .btn.focus { color: #666666; text-decoration: none; } .btn:active, .btn.active { outline: 0; background-image: none; -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } .btn.disabled, .btn[disabled], fieldset[disabled] .btn { cursor: not-allowed; opacity: 0.65; filter: alpha(opacity=65); -webkit-box-shadow: none; box-shadow: none; } a.btn.disabled, fieldset[disabled] a.btn { pointer-events: none; } .btn-default { color: #666666; background-color: #ffffff; border-color: #eeeeee; } .btn-default:focus, .btn-default.focus { color: #666666; background-color: #e6e6e6; border-color: #aeaeae; } .btn-default:hover { color: #666666; background-color: #e6e6e6; border-color: #cfcfcf; } .btn-default:active, .btn-default.active, .open > .dropdown-toggle.btn-default { color: #666666; background-color: #e6e6e6; border-color: #cfcfcf; } .btn-default:active:hover, .btn-default.active:hover, .open > .dropdown-toggle.btn-default:hover, .btn-default:active:focus, .btn-default.active:focus, .open > .dropdown-toggle.btn-default:focus, .btn-default:active.focus, .btn-default.active.focus, .open > .dropdown-toggle.btn-default.focus { color: #666666; background-color: #d4d4d4; border-color: #aeaeae; } .btn-default:active, .btn-default.active, .open > .dropdown-toggle.btn-default { background-image: none; } .btn-default.disabled, .btn-default[disabled], fieldset[disabled] .btn-default, .btn-default.disabled:hover, .btn-default[disabled]:hover, fieldset[disabled] .btn-default:hover, .btn-default.disabled:focus, .btn-default[disabled]:focus, fieldset[disabled] .btn-default:focus, .btn-default.disabled.focus, .btn-default[disabled].focus, fieldset[disabled] .btn-default.focus, .btn-default.disabled:active, .btn-default[disabled]:active, fieldset[disabled] .btn-default:active, .btn-default.disabled.active, .btn-default[disabled].active, fieldset[disabled] .btn-default.active { background-color: #ffffff; border-color: #eeeeee; } .btn-default .badge { color: #ffffff; background-color: #666666; } .btn-primary { color: #ffffff; background-color: #2196f3; border-color: transparent; } .btn-primary:focus, .btn-primary.focus { color: #ffffff; background-color: #0c7cd5; border-color: rgba(0, 0, 0, 0); } .btn-primary:hover { color: #ffffff; background-color: #0c7cd5; border-color: rgba(0, 0, 0, 0); } .btn-primary:active, .btn-primary.active, .open > .dropdown-toggle.btn-primary { color: #ffffff; background-color: #0c7cd5; border-color: rgba(0, 0, 0, 0); } .btn-primary:active:hover, .btn-primary.active:hover, .open > .dropdown-toggle.btn-primary:hover, .btn-primary:active:focus, .btn-primary.active:focus, .open > .dropdown-toggle.btn-primary:focus, .btn-primary:active.focus, .btn-primary.active.focus, .open > .dropdown-toggle.btn-primary.focus { color: #ffffff; background-color: #0a68b4; border-color: rgba(0, 0, 0, 0); } .btn-primary:active, .btn-primary.active, .open > .dropdown-toggle.btn-primary { background-image: none; } .btn-primary.disabled, .btn-primary[disabled], fieldset[disabled] .btn-primary, .btn-primary.disabled:hover, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary:hover, .btn-primary.disabled:focus, .btn-primary[disabled]:focus, fieldset[disabled] .btn-primary:focus, .btn-primary.disabled.focus, .btn-primary[disabled].focus, fieldset[disabled] .btn-primary.focus, .btn-primary.disabled:active, .btn-primary[disabled]:active, fieldset[disabled] .btn-primary:active, .btn-primary.disabled.active, .btn-primary[disabled].active, fieldset[disabled] .btn-primary.active { background-color: #2196f3; border-color: transparent; } .btn-primary .badge { color: #2196f3; background-color: #ffffff; } .btn-success { color: #ffffff; background-color: #4caf50; border-color: transparent; } .btn-success:focus, .btn-success.focus { color: #ffffff; background-color: #3d8b40; border-color: rgba(0, 0, 0, 0); } .btn-success:hover { color: #ffffff; background-color: #3d8b40; border-color: rgba(0, 0, 0, 0); } .btn-success:active, .btn-success.active, .open > .dropdown-toggle.btn-success { color: #ffffff; background-color: #3d8b40; border-color: rgba(0, 0, 0, 0); } .btn-success:active:hover, .btn-success.active:hover, .open > .dropdown-toggle.btn-success:hover, .btn-success:active:focus, .btn-success.active:focus, .open > .dropdown-toggle.btn-success:focus, .btn-success:active.focus, .btn-success.active.focus, .open > .dropdown-toggle.btn-success.focus { color: #ffffff; background-color: #327334; border-color: rgba(0, 0, 0, 0); } .btn-success:active, .btn-success.active, .open > .dropdown-toggle.btn-success { background-image: none; } .btn-success.disabled, .btn-success[disabled], fieldset[disabled] .btn-success, .btn-success.disabled:hover, .btn-success[disabled]:hover, fieldset[disabled] .btn-success:hover, .btn-success.disabled:focus, .btn-success[disabled]:focus, fieldset[disabled] .btn-success:focus, .btn-success.disabled.focus, .btn-success[disabled].focus, fieldset[disabled] .btn-success.focus, .btn-success.disabled:active, .btn-success[disabled]:active, fieldset[disabled] .btn-success:active, .btn-success.disabled.active, .btn-success[disabled].active, fieldset[disabled] .btn-success.active { background-color: #4caf50; border-color: transparent; } .btn-success .badge { color: #4caf50; background-color: #ffffff; } .btn-info { color: #ffffff; background-color: #9c27b0; border-color: transparent; } .btn-info:focus, .btn-info.focus { color: #ffffff; background-color: #771e86; border-color: rgba(0, 0, 0, 0); } .btn-info:hover { color: #ffffff; background-color: #771e86; border-color: rgba(0, 0, 0, 0); } .btn-info:active, .btn-info.active, .open > .dropdown-toggle.btn-info { color: #ffffff; background-color: #771e86; border-color: rgba(0, 0, 0, 0); } .btn-info:active:hover, .btn-info.active:hover, .open > .dropdown-toggle.btn-info:hover, .btn-info:active:focus, .btn-info.active:focus, .open > .dropdown-toggle.btn-info:focus, .btn-info:active.focus, .btn-info.active.focus, .open > .dropdown-toggle.btn-info.focus { color: #ffffff; background-color: #5d1769; border-color: rgba(0, 0, 0, 0); } .btn-info:active, .btn-info.active, .open > .dropdown-toggle.btn-info { background-image: none; } .btn-info.disabled, .btn-info[disabled], fieldset[disabled] .btn-info, .btn-info.disabled:hover, .btn-info[disabled]:hover, fieldset[disabled] .btn-info:hover, .btn-info.disabled:focus, .btn-info[disabled]:focus, fieldset[disabled] .btn-info:focus, .btn-info.disabled.focus, .btn-info[disabled].focus, fieldset[disabled] .btn-info.focus, .btn-info.disabled:active, .btn-info[disabled]:active, fieldset[disabled] .btn-info:active, .btn-info.disabled.active, .btn-info[disabled].active, fieldset[disabled] .btn-info.active { background-color: #9c27b0; border-color: transparent; } .btn-info .badge { color: #9c27b0; background-color: #ffffff; } .btn-warning { color: #ffffff; background-color: #ff9800; border-color: transparent; } .btn-warning:focus, .btn-warning.focus { color: #ffffff; background-color: #cc7a00; border-color: rgba(0, 0, 0, 0); } .btn-warning:hover { color: #ffffff; background-color: #cc7a00; border-color: rgba(0, 0, 0, 0); } .btn-warning:active, .btn-warning.active, .open > .dropdown-toggle.btn-warning { color: #ffffff; background-color: #cc7a00; border-color: rgba(0, 0, 0, 0); } .btn-warning:active:hover, .btn-warning.active:hover, .open > .dropdown-toggle.btn-warning:hover, .btn-warning:active:focus, .btn-warning.active:focus, .open > .dropdown-toggle.btn-warning:focus, .btn-warning:active.focus, .btn-warning.active.focus, .open > .dropdown-toggle.btn-warning.focus { color: #ffffff; background-color: #a86400; border-color: rgba(0, 0, 0, 0); } .btn-warning:active, .btn-warning.active, .open > .dropdown-toggle.btn-warning { background-image: none; } .btn-warning.disabled, .btn-warning[disabled], fieldset[disabled] .btn-warning, .btn-warning.disabled:hover, .btn-warning[disabled]:hover, fieldset[disabled] .btn-warning:hover, .btn-warning.disabled:focus, .btn-warning[disabled]:focus, fieldset[disabled] .btn-warning:focus, .btn-warning.disabled.focus, .btn-warning[disabled].focus, fieldset[disabled] .btn-warning.focus, .btn-warning.disabled:active, .btn-warning[disabled]:active, fieldset[disabled] .btn-warning:active, .btn-warning.disabled.active, .btn-warning[disabled].active, fieldset[disabled] .btn-warning.active { background-color: #ff9800; border-color: transparent; } .btn-warning .badge { color: #ff9800; background-color: #ffffff; } .btn-danger { color: #ffffff; background-color: #e51c23; border-color: transparent; } .btn-danger:focus, .btn-danger.focus { color: #ffffff; background-color: #b9151b; border-color: rgba(0, 0, 0, 0); } .btn-danger:hover { color: #ffffff; background-color: #b9151b; border-color: rgba(0, 0, 0, 0); } .btn-danger:active, .btn-danger.active, .open > .dropdown-toggle.btn-danger { color: #ffffff; background-color: #b9151b; border-color: rgba(0, 0, 0, 0); } .btn-danger:active:hover, .btn-danger.active:hover, .open > .dropdown-toggle.btn-danger:hover, .btn-danger:active:focus, .btn-danger.active:focus, .open > .dropdown-toggle.btn-danger:focus, .btn-danger:active.focus, .btn-danger.active.focus, .open > .dropdown-toggle.btn-danger.focus { color: #ffffff; background-color: #991216; border-color: rgba(0, 0, 0, 0); } .btn-danger:active, .btn-danger.active, .open > .dropdown-toggle.btn-danger { background-image: none; } .btn-danger.disabled, .btn-danger[disabled], fieldset[disabled] .btn-danger, .btn-danger.disabled:hover, .btn-danger[disabled]:hover, fieldset[disabled] .btn-danger:hover, .btn-danger.disabled:focus, .btn-danger[disabled]:focus, fieldset[disabled] .btn-danger:focus, .btn-danger.disabled.focus, .btn-danger[disabled].focus, fieldset[disabled] .btn-danger.focus, .btn-danger.disabled:active, .btn-danger[disabled]:active, fieldset[disabled] .btn-danger:active, .btn-danger.disabled.active, .btn-danger[disabled].active, fieldset[disabled] .btn-danger.active { background-color: #e51c23; border-color: transparent; } .btn-danger .badge { color: #e51c23; background-color: #ffffff; } .btn-link { color: #2196f3; font-weight: normal; border-radius: 0; } .btn-link, .btn-link:active, .btn-link.active, .btn-link[disabled], fieldset[disabled] .btn-link { background-color: transparent; -webkit-box-shadow: none; box-shadow: none; } .btn-link, .btn-link:hover, .btn-link:focus, .btn-link:active { border-color: transparent; } .btn-link:hover, .btn-link:focus { color: #0a6ebd; text-decoration: underline; background-color: transparent; } .btn-link[disabled]:hover, fieldset[disabled] .btn-link:hover, .btn-link[disabled]:focus, fieldset[disabled] .btn-link:focus { color: #bbbbbb; text-decoration: none; } .btn-lg, .btn-group-lg > .btn { padding: 10px 16px; font-size: 17px; line-height: 1.3333333; border-radius: 3px; } .btn-sm, .btn-group-sm > .btn { padding: 5px 10px; font-size: 12px; line-height: 1.5; border-radius: 3px; } .btn-xs, .btn-group-xs > .btn { padding: 1px 5px; font-size: 12px; line-height: 1.5; border-radius: 3px; } .btn-block { display: block; width: 100%; } .btn-block + .btn-block { margin-top: 5px; } input[type="submit"].btn-block, input[type="reset"].btn-block, input[type="button"].btn-block { width: 100%; } .fade { opacity: 0; -webkit-transition: opacity 0.15s linear; -o-transition: opacity 0.15s linear; transition: opacity 0.15s linear; } .fade.in { opacity: 1; } .collapse { display: none; } .collapse.in { display: block; } tr.collapse.in { display: table-row; } tbody.collapse.in { display: table-row-group; } .collapsing { position: relative; height: 0; overflow: hidden; -webkit-transition-property: height, visibility; -o-transition-property: height, visibility; transition-property: height, visibility; -webkit-transition-duration: 0.35s; -o-transition-duration: 0.35s; transition-duration: 0.35s; -webkit-transition-timing-function: ease; -o-transition-timing-function: ease; transition-timing-function: ease; } .caret { display: inline-block; width: 0; height: 0; margin-left: 2px; vertical-align: middle; border-top: 4px dashed; border-top: 4px solid \9; border-right: 4px solid transparent; border-left: 4px solid transparent; } .dropup, .dropdown { position: relative; } .dropdown-toggle:focus { outline: 0; } .dropdown-menu { position: absolute; top: 100%; left: 0; z-index: 1000; display: none; float: left; min-width: 160px; padding: 5px 0; margin: 2px 0 0; list-style: none; font-size: 13px; text-align: left; background-color: #ffffff; border: 1px solid #cccccc; border: 1px solid rgba(0, 0, 0, 0.15); border-radius: 3px; -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); -webkit-background-clip: padding-box; background-clip: padding-box; } .dropdown-menu.pull-right { right: 0; left: auto; } .dropdown-menu .divider { height: 1px; margin: 10.5px 0; overflow: hidden; background-color: #e5e5e5; } .dropdown-menu > li > a { display: block; padding: 3px 20px; clear: both; font-weight: normal; line-height: 1.846; color: #666666; white-space: nowrap; } .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus { text-decoration: none; color: #141414; background-color: #eeeeee; } .dropdown-menu > .active > a, .dropdown-menu > .active > a:hover, .dropdown-menu > .active > a:focus { color: #ffffff; text-decoration: none; outline: 0; background-color: #2196f3; } .dropdown-menu > .disabled > a, .dropdown-menu > .disabled > a:hover, .dropdown-menu > .disabled > a:focus { color: #bbbbbb; } .dropdown-menu > .disabled > a:hover, .dropdown-menu > .disabled > a:focus { text-decoration: none; background-color: transparent; background-image: none; filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); cursor: not-allowed; } .open > .dropdown-menu { display: block; } .open > a { outline: 0; } .dropdown-menu-right { left: auto; right: 0; } .dropdown-menu-left { left: 0; right: auto; } .dropdown-header { display: block; padding: 3px 20px; font-size: 12px; line-height: 1.846; color: #bbbbbb; white-space: nowrap; } .dropdown-backdrop { position: fixed; left: 0; right: 0; bottom: 0; top: 0; z-index: 990; } .pull-right > .dropdown-menu { right: 0; left: auto; } .dropup .caret, .navbar-fixed-bottom .dropdown .caret { border-top: 0; border-bottom: 4px dashed; border-bottom: 4px solid \9; content: ""; } .dropup .dropdown-menu, .navbar-fixed-bottom .dropdown .dropdown-menu { top: auto; bottom: 100%; margin-bottom: 2px; } @media (min-width: 768px) { .navbar-right .dropdown-menu { left: auto; right: 0; } .navbar-right .dropdown-menu-left { left: 0; right: auto; } } .btn-group, .btn-group-vertical { position: relative; display: inline-block; vertical-align: middle; } .btn-group > .btn, .btn-group-vertical > .btn { position: relative; float: left; } .btn-group > .btn:hover, .btn-group-vertical > .btn:hover, .btn-group > .btn:focus, .btn-group-vertical > .btn:focus, .btn-group > .btn:active, .btn-group-vertical > .btn:active, .btn-group > .btn.active, .btn-group-vertical > .btn.active { z-index: 2; } .btn-group .btn + .btn, .btn-group .btn + .btn-group, .btn-group .btn-group + .btn, .btn-group .btn-group + .btn-group { margin-left: -1px; } .btn-toolbar { margin-left: -5px; } .btn-toolbar .btn, .btn-toolbar .btn-group, .btn-toolbar .input-group { float: left; } .btn-toolbar > .btn, .btn-toolbar > .btn-group, .btn-toolbar > .input-group { margin-left: 5px; } .btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { border-radius: 0; } .btn-group > .btn:first-child { margin-left: 0; } .btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { border-bottom-right-radius: 0; border-top-right-radius: 0; } .btn-group > .btn:last-child:not(:first-child), .btn-group > .dropdown-toggle:not(:first-child) { border-bottom-left-radius: 0; border-top-left-radius: 0; } .btn-group > .btn-group { float: left; } .btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { border-radius: 0; } .btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, .btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { border-bottom-right-radius: 0; border-top-right-radius: 0; } .btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { border-bottom-left-radius: 0; border-top-left-radius: 0; } .btn-group .dropdown-toggle:active, .btn-group.open .dropdown-toggle { outline: 0; } .btn-group > .btn + .dropdown-toggle { padding-left: 8px; padding-right: 8px; } .btn-group > .btn-lg + .dropdown-toggle { padding-left: 12px; padding-right: 12px; } .btn-group.open .dropdown-toggle { -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } .btn-group.open .dropdown-toggle.btn-link { -webkit-box-shadow: none; box-shadow: none; } .btn .caret { margin-left: 0; } .btn-lg .caret { border-width: 5px 5px 0; border-bottom-width: 0; } .dropup .btn-lg .caret { border-width: 0 5px 5px; } .btn-group-vertical > .btn, .btn-group-vertical > .btn-group, .btn-group-vertical > .btn-group > .btn { display: block; float: none; width: 100%; max-width: 100%; } .btn-group-vertical > .btn-group > .btn { float: none; } .btn-group-vertical > .btn + .btn, .btn-group-vertical > .btn + .btn-group, .btn-group-vertical > .btn-group + .btn, .btn-group-vertical > .btn-group + .btn-group { margin-top: -1px; margin-left: 0; } .btn-group-vertical > .btn:not(:first-child):not(:last-child) { border-radius: 0; } .btn-group-vertical > .btn:first-child:not(:last-child) { border-top-right-radius: 3px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .btn-group-vertical > .btn:last-child:not(:first-child) { border-bottom-left-radius: 3px; border-top-right-radius: 0; border-top-left-radius: 0; } .btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { border-radius: 0; } .btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, .btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { border-top-right-radius: 0; border-top-left-radius: 0; } .btn-group-justified { display: table; width: 100%; table-layout: fixed; border-collapse: separate; } .btn-group-justified > .btn, .btn-group-justified > .btn-group { float: none; display: table-cell; width: 1%; } .btn-group-justified > .btn-group .btn { width: 100%; } .btn-group-justified > .btn-group .dropdown-menu { left: auto; } [data-toggle="buttons"] > .btn input[type="radio"], [data-toggle="buttons"] > .btn-group > .btn input[type="radio"], [data-toggle="buttons"] > .btn input[type="checkbox"], [data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { position: absolute; clip: rect(0, 0, 0, 0); pointer-events: none; } .input-group { position: relative; display: table; border-collapse: separate; } .input-group[class*="col-"] { float: none; padding-left: 0; padding-right: 0; } .input-group .form-control { position: relative; z-index: 2; float: left; width: 100%; margin-bottom: 0; } .input-group-lg > .form-control, .input-group-lg > .input-group-addon, .input-group-lg > .input-group-btn > .btn { height: 45px; padding: 10px 16px; font-size: 17px; line-height: 1.3333333; border-radius: 3px; } select.input-group-lg > .form-control, select.input-group-lg > .input-group-addon, select.input-group-lg > .input-group-btn > .btn { height: 45px; line-height: 45px; } textarea.input-group-lg > .form-control, textarea.input-group-lg > .input-group-addon, textarea.input-group-lg > .input-group-btn > .btn, select[multiple].input-group-lg > .form-control, select[multiple].input-group-lg > .input-group-addon, select[multiple].input-group-lg > .input-group-btn > .btn { height: auto; } .input-group-sm > .form-control, .input-group-sm > .input-group-addon, .input-group-sm > .input-group-btn > .btn { height: 30px; padding: 5px 10px; font-size: 12px; line-height: 1.5; border-radius: 3px; } select.input-group-sm > .form-control, select.input-group-sm > .input-group-addon, select.input-group-sm > .input-group-btn > .btn { height: 30px; line-height: 30px; } textarea.input-group-sm > .form-control, textarea.input-group-sm > .input-group-addon, textarea.input-group-sm > .input-group-btn > .btn, select[multiple].input-group-sm > .form-control, select[multiple].input-group-sm > .input-group-addon, select[multiple].input-group-sm > .input-group-btn > .btn { height: auto; } .input-group-addon, .input-group-btn, .input-group .form-control { display: table-cell; } .input-group-addon:not(:first-child):not(:last-child), .input-group-btn:not(:first-child):not(:last-child), .input-group .form-control:not(:first-child):not(:last-child) { border-radius: 0; } .input-group-addon, .input-group-btn { width: 1%; white-space: nowrap; vertical-align: middle; } .input-group-addon { padding: 6px 16px; font-size: 13px; font-weight: normal; line-height: 1; color: #666666; text-align: center; background-color: transparent; border: 1px solid transparent; border-radius: 3px; } .input-group-addon.input-sm { padding: 5px 10px; font-size: 12px; border-radius: 3px; } .input-group-addon.input-lg { padding: 10px 16px; font-size: 17px; border-radius: 3px; } .input-group-addon input[type="radio"], .input-group-addon input[type="checkbox"] { margin-top: 0; } .input-group .form-control:first-child, .input-group-addon:first-child, .input-group-btn:first-child > .btn, .input-group-btn:first-child > .btn-group > .btn, .input-group-btn:first-child > .dropdown-toggle, .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), .input-group-btn:last-child > .btn-group:not(:last-child) > .btn { border-bottom-right-radius: 0; border-top-right-radius: 0; } .input-group-addon:first-child { border-right: 0; } .input-group .form-control:last-child, .input-group-addon:last-child, .input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn-group > .btn, .input-group-btn:last-child > .dropdown-toggle, .input-group-btn:first-child > .btn:not(:first-child), .input-group-btn:first-child > .btn-group:not(:first-child) > .btn { border-bottom-left-radius: 0; border-top-left-radius: 0; } .input-group-addon:last-child { border-left: 0; } .input-group-btn { position: relative; font-size: 0; white-space: nowrap; } .input-group-btn > .btn { position: relative; } .input-group-btn > .btn + .btn { margin-left: -1px; } .input-group-btn > .btn:hover, .input-group-btn > .btn:focus, .input-group-btn > .btn:active { z-index: 2; } .input-group-btn:first-child > .btn, .input-group-btn:first-child > .btn-group { margin-right: -1px; } .input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn-group { z-index: 2; margin-left: -1px; } .nav { margin-bottom: 0; padding-left: 0; list-style: none; } .nav > li { position: relative; display: block; } .nav > li > a { position: relative; display: block; padding: 10px 15px; } .nav > li > a:hover, .nav > li > a:focus { text-decoration: none; background-color: #eeeeee; } .nav > li.disabled > a { color: #bbbbbb; } .nav > li.disabled > a:hover, .nav > li.disabled > a:focus { color: #bbbbbb; text-decoration: none; background-color: transparent; cursor: not-allowed; } .nav .open > a, .nav .open > a:hover, .nav .open > a:focus { background-color: #eeeeee; border-color: #2196f3; } .nav .nav-divider { height: 1px; margin: 10.5px 0; overflow: hidden; background-color: #e5e5e5; } .nav > li > a > img { max-width: none; } .nav-tabs { border-bottom: 1px solid transparent; } .nav-tabs > li { float: left; margin-bottom: -1px; } .nav-tabs > li > a { margin-right: 2px; line-height: 1.846; border: 1px solid transparent; border-radius: 3px 3px 0 0; } .nav-tabs > li > a:hover { border-color: #eeeeee #eeeeee transparent; } .nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus { color: #666666; background-color: transparent; border: 1px solid transparent; border-bottom-color: transparent; cursor: default; } .nav-tabs.nav-justified { width: 100%; border-bottom: 0; } .nav-tabs.nav-justified > li { float: none; } .nav-tabs.nav-justified > li > a { text-align: center; margin-bottom: 5px; } .nav-tabs.nav-justified > .dropdown .dropdown-menu { top: auto; left: auto; } @media (min-width: 768px) { .nav-tabs.nav-justified > li { display: table-cell; width: 1%; } .nav-tabs.nav-justified > li > a { margin-bottom: 0; } } .nav-tabs.nav-justified > li > a { margin-right: 0; border-radius: 3px; } .nav-tabs.nav-justified > .active > a, .nav-tabs.nav-justified > .active > a:hover, .nav-tabs.nav-justified > .active > a:focus { border: 1px solid transparent; } @media (min-width: 768px) { .nav-tabs.nav-justified > li > a { border-bottom: 1px solid transparent; border-radius: 3px 3px 0 0; } .nav-tabs.nav-justified > .active > a, .nav-tabs.nav-justified > .active > a:hover, .nav-tabs.nav-justified > .active > a:focus { border-bottom-color: #ffffff; } } .nav-pills > li { float: left; } .nav-pills > li > a { border-radius: 3px; } .nav-pills > li + li { margin-left: 2px; } .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { color: #ffffff; background-color: #2196f3; } .nav-stacked > li { float: none; } .nav-stacked > li + li { margin-top: 2px; margin-left: 0; } .nav-justified { width: 100%; } .nav-justified > li { float: none; } .nav-justified > li > a { text-align: center; margin-bottom: 5px; } .nav-justified > .dropdown .dropdown-menu { top: auto; left: auto; } @media (min-width: 768px) { .nav-justified > li { display: table-cell; width: 1%; } .nav-justified > li > a { margin-bottom: 0; } } .nav-tabs-justified { border-bottom: 0; } .nav-tabs-justified > li > a { margin-right: 0; border-radius: 3px; } .nav-tabs-justified > .active > a, .nav-tabs-justified > .active > a:hover, .nav-tabs-justified > .active > a:focus { border: 1px solid transparent; } @media (min-width: 768px) { .nav-tabs-justified > li > a { border-bottom: 1px solid transparent; border-radius: 3px 3px 0 0; } .nav-tabs-justified > .active > a, .nav-tabs-justified > .active > a:hover, .nav-tabs-justified > .active > a:focus { border-bottom-color: #ffffff; } } .tab-content > .tab-pane { display: none; } .tab-content > .active { display: block; } .nav-tabs .dropdown-menu { margin-top: -1px; border-top-right-radius: 0; border-top-left-radius: 0; } .navbar { position: relative; min-height: 64px; margin-bottom: 23px; border: 1px solid transparent; } @media (min-width: 768px) { .navbar { border-radius: 3px; } } @media (min-width: 768px) { .navbar-header { float: left; } } .navbar-collapse { overflow-x: visible; padding-right: 15px; padding-left: 15px; border-top: 1px solid transparent; -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); -webkit-overflow-scrolling: touch; } .navbar-collapse.in { overflow-y: auto; } @media (min-width: 768px) { .navbar-collapse { width: auto; border-top: 0; -webkit-box-shadow: none; box-shadow: none; } .navbar-collapse.collapse { display: block !important; height: auto !important; padding-bottom: 0; overflow: visible !important; } .navbar-collapse.in { overflow-y: visible; } .navbar-fixed-top .navbar-collapse, .navbar-static-top .navbar-collapse, .navbar-fixed-bottom .navbar-collapse { padding-left: 0; padding-right: 0; } } .navbar-fixed-top .navbar-collapse, .navbar-fixed-bottom .navbar-collapse { max-height: 340px; } @media (max-device-width: 480px) and (orientation: landscape) { .navbar-fixed-top .navbar-collapse, .navbar-fixed-bottom .navbar-collapse { max-height: 200px; } } .container > .navbar-header, .container-fluid > .navbar-header, .container > .navbar-collapse, .container-fluid > .navbar-collapse { margin-right: -15px; margin-left: -15px; } @media (min-width: 768px) { .container > .navbar-header, .container-fluid > .navbar-header, .container > .navbar-collapse, .container-fluid > .navbar-collapse { margin-right: 0; margin-left: 0; } } .navbar-static-top { z-index: 1000; border-width: 0 0 1px; } @media (min-width: 768px) { .navbar-static-top { border-radius: 0; } } .navbar-fixed-top, .navbar-fixed-bottom { position: fixed; right: 0; left: 0; z-index: 1030; } @media (min-width: 768px) { .navbar-fixed-top, .navbar-fixed-bottom { border-radius: 0; } } .navbar-fixed-top { top: 0; border-width: 0 0 1px; } .navbar-fixed-bottom { bottom: 0; margin-bottom: 0; border-width: 1px 0 0; } .navbar-brand { float: left; padding: 20.5px 15px; font-size: 17px; line-height: 23px; height: 64px; } .navbar-brand:hover, .navbar-brand:focus { text-decoration: none; } .navbar-brand > img { display: block; } @media (min-width: 768px) { .navbar > .container .navbar-brand, .navbar > .container-fluid .navbar-brand { margin-left: -15px; } } .navbar-toggle { position: relative; float: right; margin-right: 15px; padding: 9px 10px; margin-top: 15px; margin-bottom: 15px; background-color: transparent; background-image: none; border: 1px solid transparent; border-radius: 3px; } .navbar-toggle:focus { outline: 0; } .navbar-toggle .icon-bar { display: block; width: 22px; height: 2px; border-radius: 1px; } .navbar-toggle .icon-bar + .icon-bar { margin-top: 4px; } @media (min-width: 768px) { .navbar-toggle { display: none; } } .navbar-nav { margin: 10.25px -15px; } .navbar-nav > li > a { padding-top: 10px; padding-bottom: 10px; line-height: 23px; } @media (max-width: 767px) { .navbar-nav .open .dropdown-menu { position: static; float: none; width: auto; margin-top: 0; background-color: transparent; border: 0; -webkit-box-shadow: none; box-shadow: none; } .navbar-nav .open .dropdown-menu > li > a, .navbar-nav .open .dropdown-menu .dropdown-header { padding: 5px 15px 5px 25px; } .navbar-nav .open .dropdown-menu > li > a { line-height: 23px; } .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-nav .open .dropdown-menu > li > a:focus { background-image: none; } } @media (min-width: 768px) { .navbar-nav { float: left; margin: 0; } .navbar-nav > li { float: left; } .navbar-nav > li > a { padding-top: 20.5px; padding-bottom: 20.5px; } } .navbar-form { margin-left: -15px; margin-right: -15px; padding: 10px 15px; border-top: 1px solid transparent; border-bottom: 1px solid transparent; -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); margin-top: 13.5px; margin-bottom: 13.5px; } @media (min-width: 768px) { .navbar-form .form-group { display: inline-block; margin-bottom: 0; vertical-align: middle; } .navbar-form .form-control { display: inline-block; width: auto; vertical-align: middle; } .navbar-form .form-control-static { display: inline-block; } .navbar-form .input-group { display: inline-table; vertical-align: middle; } .navbar-form .input-group .input-group-addon, .navbar-form .input-group .input-group-btn, .navbar-form .input-group .form-control { width: auto; } .navbar-form .input-group > .form-control { width: 100%; } .navbar-form .control-label { margin-bottom: 0; vertical-align: middle; } .navbar-form .radio, .navbar-form .checkbox { display: inline-block; margin-top: 0; margin-bottom: 0; vertical-align: middle; } .navbar-form .radio label, .navbar-form .checkbox label { padding-left: 0; } .navbar-form .radio input[type="radio"], .navbar-form .checkbox input[type="checkbox"] { position: relative; margin-left: 0; } .navbar-form .has-feedback .form-control-feedback { top: 0; } } @media (max-width: 767px) { .navbar-form .form-group { margin-bottom: 5px; } .navbar-form .form-group:last-child { margin-bottom: 0; } } @media (min-width: 768px) { .navbar-form { width: auto; border: 0; margin-left: 0; margin-right: 0; padding-top: 0; padding-bottom: 0; -webkit-box-shadow: none; box-shadow: none; } } .navbar-nav > li > .dropdown-menu { margin-top: 0; border-top-right-radius: 0; border-top-left-radius: 0; } .navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { margin-bottom: 0; border-top-right-radius: 3px; border-top-left-radius: 3px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .navbar-btn { margin-top: 13.5px; margin-bottom: 13.5px; } .navbar-btn.btn-sm { margin-top: 17px; margin-bottom: 17px; } .navbar-btn.btn-xs { margin-top: 21px; margin-bottom: 21px; } .navbar-text { margin-top: 20.5px; margin-bottom: 20.5px; } @media (min-width: 768px) { .navbar-text { float: left; margin-left: 15px; margin-right: 15px; } } @media (min-width: 768px) { .navbar-left { float: left !important; } .navbar-right { float: right !important; margin-right: -15px; } .navbar-right ~ .navbar-right { margin-right: 0; } } .navbar-default { background-color: #ffffff; border-color: transparent; } .navbar-default .navbar-brand { color: #666666; } .navbar-default .navbar-brand:hover, .navbar-default .navbar-brand:focus { color: #212121; background-color: transparent; } .navbar-default .navbar-text { color: #bbbbbb; } .navbar-default .navbar-nav > li > a { color: #666666; } .navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus { color: #212121; background-color: transparent; } .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus { color: #212121; background-color: #eeeeee; } .navbar-default .navbar-nav > .disabled > a, .navbar-default .navbar-nav > .disabled > a:hover, .navbar-default .navbar-nav > .disabled > a:focus { color: #cccccc; background-color: transparent; } .navbar-default .navbar-toggle { border-color: transparent; } .navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus { background-color: transparent; } .navbar-default .navbar-toggle .icon-bar { background-color: rgba(0, 0, 0, 0.5); } .navbar-default .navbar-collapse, .navbar-default .navbar-form { border-color: transparent; } .navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:hover, .navbar-default .navbar-nav > .open > a:focus { background-color: #eeeeee; color: #212121; } @media (max-width: 767px) { .navbar-default .navbar-nav .open .dropdown-menu > li > a { color: #666666; } .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { color: #212121; background-color: transparent; } .navbar-default .navbar-nav .open .dropdown-menu > .active > a, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { color: #212121; background-color: #eeeeee; } .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { color: #cccccc; background-color: transparent; } } .navbar-default .navbar-link { color: #666666; } .navbar-default .navbar-link:hover { color: #212121; } .navbar-default .btn-link { color: #666666; } .navbar-default .btn-link:hover, .navbar-default .btn-link:focus { color: #212121; } .navbar-default .btn-link[disabled]:hover, fieldset[disabled] .navbar-default .btn-link:hover, .navbar-default .btn-link[disabled]:focus, fieldset[disabled] .navbar-default .btn-link:focus { color: #cccccc; } .navbar-inverse { background-color: #2196f3; border-color: transparent; } .navbar-inverse .navbar-brand { color: #b2dbfb; } .navbar-inverse .navbar-brand:hover, .navbar-inverse .navbar-brand:focus { color: #ffffff; background-color: transparent; } .navbar-inverse .navbar-text { color: #bbbbbb; } .navbar-inverse .navbar-nav > li > a { color: #b2dbfb; } .navbar-inverse .navbar-nav > li > a:hover, .navbar-inverse .navbar-nav > li > a:focus { color: #ffffff; background-color: transparent; } .navbar-inverse .navbar-nav > .active > a, .navbar-inverse .navbar-nav > .active > a:hover, .navbar-inverse .navbar-nav > .active > a:focus { color: #ffffff; background-color: #0c7cd5; } .navbar-inverse .navbar-nav > .disabled > a, .navbar-inverse .navbar-nav > .disabled > a:hover, .navbar-inverse .navbar-nav > .disabled > a:focus { color: #444444; background-color: transparent; } .navbar-inverse .navbar-toggle { border-color: transparent; } .navbar-inverse .navbar-toggle:hover, .navbar-inverse .navbar-toggle:focus { background-color: transparent; } .navbar-inverse .navbar-toggle .icon-bar { background-color: rgba(0, 0, 0, 0.5); } .navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form { border-color: #0c84e4; } .navbar-inverse .navbar-nav > .open > a, .navbar-inverse .navbar-nav > .open > a:hover, .navbar-inverse .navbar-nav > .open > a:focus { background-color: #0c7cd5; color: #ffffff; } @media (max-width: 767px) { .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { border-color: transparent; } .navbar-inverse .navbar-nav .open .dropdown-menu .divider { background-color: transparent; } .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { color: #b2dbfb; } .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { color: #ffffff; background-color: transparent; } .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { color: #ffffff; background-color: #0c7cd5; } .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { color: #444444; background-color: transparent; } } .navbar-inverse .navbar-link { color: #b2dbfb; } .navbar-inverse .navbar-link:hover { color: #ffffff; } .navbar-inverse .btn-link { color: #b2dbfb; } .navbar-inverse .btn-link:hover, .navbar-inverse .btn-link:focus { color: #ffffff; } .navbar-inverse .btn-link[disabled]:hover, fieldset[disabled] .navbar-inverse .btn-link:hover, .navbar-inverse .btn-link[disabled]:focus, fieldset[disabled] .navbar-inverse .btn-link:focus { color: #444444; } .breadcrumb { padding: 8px 15px; margin-bottom: 23px; list-style: none; background-color: #f5f5f5; border-radius: 3px; } .breadcrumb > li { display: inline-block; } .breadcrumb > li + li:before { content: "/\00a0"; padding: 0 5px; color: #cccccc; } .breadcrumb > .active { color: #bbbbbb; } .pagination { display: inline-block; padding-left: 0; margin: 23px 0; border-radius: 3px; } .pagination > li { display: inline; } .pagination > li > a, .pagination > li > span { position: relative; float: left; padding: 6px 16px; line-height: 1.846; text-decoration: none; color: #2196f3; background-color: #ffffff; border: 1px solid #dddddd; margin-left: -1px; } .pagination > li:first-child > a, .pagination > li:first-child > span { margin-left: 0; border-bottom-left-radius: 3px; border-top-left-radius: 3px; } .pagination > li:last-child > a, .pagination > li:last-child > span { border-bottom-right-radius: 3px; border-top-right-radius: 3px; } .pagination > li > a:hover, .pagination > li > span:hover, .pagination > li > a:focus, .pagination > li > span:focus { z-index: 3; color: #0a6ebd; background-color: #eeeeee; border-color: #dddddd; } .pagination > .active > a, .pagination > .active > span, .pagination > .active > a:hover, .pagination > .active > span:hover, .pagination > .active > a:focus, .pagination > .active > span:focus { z-index: 2; color: #ffffff; background-color: #2196f3; border-color: #2196f3; cursor: default; } .pagination > .disabled > span, .pagination > .disabled > span:hover, .pagination > .disabled > span:focus, .pagination > .disabled > a, .pagination > .disabled > a:hover, .pagination > .disabled > a:focus { color: #bbbbbb; background-color: #ffffff; border-color: #dddddd; cursor: not-allowed; } .pagination-lg > li > a, .pagination-lg > li > span { padding: 10px 16px; font-size: 17px; line-height: 1.3333333; } .pagination-lg > li:first-child > a, .pagination-lg > li:first-child > span { border-bottom-left-radius: 3px; border-top-left-radius: 3px; } .pagination-lg > li:last-child > a, .pagination-lg > li:last-child > span { border-bottom-right-radius: 3px; border-top-right-radius: 3px; } .pagination-sm > li > a, .pagination-sm > li > span { padding: 5px 10px; font-size: 12px; line-height: 1.5; } .pagination-sm > li:first-child > a, .pagination-sm > li:first-child > span { border-bottom-left-radius: 3px; border-top-left-radius: 3px; } .pagination-sm > li:last-child > a, .pagination-sm > li:last-child > span { border-bottom-right-radius: 3px; border-top-right-radius: 3px; } .pager { padding-left: 0; margin: 23px 0; list-style: none; text-align: center; } .pager li { display: inline; } .pager li > a, .pager li > span { display: inline-block; padding: 5px 14px; background-color: #ffffff; border: 1px solid #dddddd; border-radius: 15px; } .pager li > a:hover, .pager li > a:focus { text-decoration: none; background-color: #eeeeee; } .pager .next > a, .pager .next > span { float: right; } .pager .previous > a, .pager .previous > span { float: left; } .pager .disabled > a, .pager .disabled > a:hover, .pager .disabled > a:focus, .pager .disabled > span { color: #bbbbbb; background-color: #ffffff; cursor: not-allowed; } .label { display: inline; padding: .2em .6em .3em; font-size: 75%; font-weight: bold; line-height: 1; color: #ffffff; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: .25em; } a.label:hover, a.label:focus { color: #ffffff; text-decoration: none; cursor: pointer; } .label:empty { display: none; } .btn .label { position: relative; top: -1px; } .label-default { background-color: #bbbbbb; } .label-default[href]:hover, .label-default[href]:focus { background-color: #a2a2a2; } .label-primary { background-color: #2196f3; } .label-primary[href]:hover, .label-primary[href]:focus { background-color: #0c7cd5; } .label-success { background-color: #4caf50; } .label-success[href]:hover, .label-success[href]:focus { background-color: #3d8b40; } .label-info { background-color: #9c27b0; } .label-info[href]:hover, .label-info[href]:focus { background-color: #771e86; } .label-warning { background-color: #ff9800; } .label-warning[href]:hover, .label-warning[href]:focus { background-color: #cc7a00; } .label-danger { background-color: #e51c23; } .label-danger[href]:hover, .label-danger[href]:focus { background-color: #b9151b; } .badge { display: inline-block; min-width: 10px; padding: 3px 7px; font-size: 12px; font-weight: normal; color: #ffffff; line-height: 1; vertical-align: middle; white-space: nowrap; text-align: center; background-color: #bbbbbb; border-radius: 10px; } .badge:empty { display: none; } .btn .badge { position: relative; top: -1px; } .btn-xs .badge, .btn-group-xs > .btn .badge { top: 0; padding: 1px 5px; } a.badge:hover, a.badge:focus { color: #ffffff; text-decoration: none; cursor: pointer; } .list-group-item.active > .badge, .nav-pills > .active > a > .badge { color: #2196f3; background-color: #ffffff; } .list-group-item > .badge { float: right; } .list-group-item > .badge + .badge { margin-right: 5px; } .nav-pills > li > a > .badge { margin-left: 3px; } .jumbotron { padding-top: 30px; padding-bottom: 30px; margin-bottom: 30px; color: inherit; background-color: #f9f9f9; } .jumbotron h1, .jumbotron .h1 { color: #444444; } .jumbotron p { margin-bottom: 15px; font-size: 20px; font-weight: 200; } .jumbotron > hr { border-top-color: #e0e0e0; } .container .jumbotron, .container-fluid .jumbotron { border-radius: 3px; } .jumbotron .container { max-width: 100%; } @media screen and (min-width: 768px) { .jumbotron { padding-top: 48px; padding-bottom: 48px; } .container .jumbotron, .container-fluid .jumbotron { padding-left: 60px; padding-right: 60px; } .jumbotron h1, .jumbotron .h1 { font-size: 59px; } } .thumbnail { display: block; padding: 4px; margin-bottom: 23px; line-height: 1.846; background-color: #ffffff; border: 1px solid #dddddd; border-radius: 3px; -webkit-transition: border 0.2s ease-in-out; -o-transition: border 0.2s ease-in-out; transition: border 0.2s ease-in-out; } .thumbnail > img, .thumbnail a > img { margin-left: auto; margin-right: auto; } a.thumbnail:hover, a.thumbnail:focus, a.thumbnail.active { border-color: #2196f3; } .thumbnail .caption { padding: 9px; color: #666666; } .alert { padding: 15px; margin-bottom: 23px; border: 1px solid transparent; border-radius: 3px; } .alert h4 { margin-top: 0; color: inherit; } .alert .alert-link { font-weight: bold; } .alert > p, .alert > ul { margin-bottom: 0; } .alert > p + p { margin-top: 5px; } .alert-dismissable, .alert-dismissible { padding-right: 35px; } .alert-dismissable .close, .alert-dismissible .close { position: relative; top: -2px; right: -21px; color: inherit; } .alert-success { background-color: #dff0d8; border-color: #d6e9c6; color: #4caf50; } .alert-success hr { border-top-color: #c9e2b3; } .alert-success .alert-link { color: #3d8b40; } .alert-info { background-color: #e1bee7; border-color: #cba4dd; color: #9c27b0; } .alert-info hr { border-top-color: #c191d6; } .alert-info .alert-link { color: #771e86; } .alert-warning { background-color: #ffe0b2; border-color: #ffc599; color: #ff9800; } .alert-warning hr { border-top-color: #ffb67f; } .alert-warning .alert-link { color: #cc7a00; } .alert-danger { background-color: #f9bdbb; border-color: #f7a4af; color: #e51c23; } .alert-danger hr { border-top-color: #f58c9a; } .alert-danger .alert-link { color: #b9151b; } @-webkit-keyframes progress-bar-stripes { from { background-position: 40px 0; } to { background-position: 0 0; } } @-o-keyframes progress-bar-stripes { from { background-position: 40px 0; } to { background-position: 0 0; } } @keyframes progress-bar-stripes { from { background-position: 40px 0; } to { background-position: 0 0; } } .progress { overflow: hidden; height: 23px; margin-bottom: 23px; background-color: #f5f5f5; border-radius: 3px; -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); } .progress-bar { float: left; width: 0%; height: 100%; font-size: 12px; line-height: 23px; color: #ffffff; text-align: center; background-color: #2196f3; -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); -webkit-transition: width 0.6s ease; -o-transition: width 0.6s ease; transition: width 0.6s ease; } .progress-striped .progress-bar, .progress-bar-striped { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -webkit-background-size: 40px 40px; background-size: 40px 40px; } .progress.active .progress-bar, .progress-bar.active { -webkit-animation: progress-bar-stripes 2s linear infinite; -o-animation: progress-bar-stripes 2s linear infinite; animation: progress-bar-stripes 2s linear infinite; } .progress-bar-success { background-color: #4caf50; } .progress-striped .progress-bar-success { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } .progress-bar-info { background-color: #9c27b0; } .progress-striped .progress-bar-info { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } .progress-bar-warning { background-color: #ff9800; } .progress-striped .progress-bar-warning { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } .progress-bar-danger { background-color: #e51c23; } .progress-striped .progress-bar-danger { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } .media { margin-top: 15px; } .media:first-child { margin-top: 0; } .media, .media-body { zoom: 1; overflow: hidden; } .media-body { width: 10000px; } .media-object { display: block; } .media-object.img-thumbnail { max-width: none; } .media-right, .media > .pull-right { padding-left: 10px; } .media-left, .media > .pull-left { padding-right: 10px; } .media-left, .media-right, .media-body { display: table-cell; vertical-align: top; } .media-middle { vertical-align: middle; } .media-bottom { vertical-align: bottom; } .media-heading { margin-top: 0; margin-bottom: 5px; } .media-list { padding-left: 0; list-style: none; } .list-group { margin-bottom: 20px; padding-left: 0; } .list-group-item { position: relative; display: block; padding: 10px 15px; margin-bottom: -1px; background-color: #ffffff; border: 1px solid #dddddd; } .list-group-item:first-child { border-top-right-radius: 3px; border-top-left-radius: 3px; } .list-group-item:last-child { margin-bottom: 0; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; } a.list-group-item, button.list-group-item { color: #555555; } a.list-group-item .list-group-item-heading, button.list-group-item .list-group-item-heading { color: #333333; } a.list-group-item:hover, button.list-group-item:hover, a.list-group-item:focus, button.list-group-item:focus { text-decoration: none; color: #555555; background-color: #f5f5f5; } button.list-group-item { width: 100%; text-align: left; } .list-group-item.disabled, .list-group-item.disabled:hover, .list-group-item.disabled:focus { background-color: #eeeeee; color: #bbbbbb; cursor: not-allowed; } .list-group-item.disabled .list-group-item-heading, .list-group-item.disabled:hover .list-group-item-heading, .list-group-item.disabled:focus .list-group-item-heading { color: inherit; } .list-group-item.disabled .list-group-item-text, .list-group-item.disabled:hover .list-group-item-text, .list-group-item.disabled:focus .list-group-item-text { color: #bbbbbb; } .list-group-item.active, .list-group-item.active:hover, .list-group-item.active:focus { z-index: 2; color: #ffffff; background-color: #2196f3; border-color: #2196f3; } .list-group-item.active .list-group-item-heading, .list-group-item.active:hover .list-group-item-heading, .list-group-item.active:focus .list-group-item-heading, .list-group-item.active .list-group-item-heading > small, .list-group-item.active:hover .list-group-item-heading > small, .list-group-item.active:focus .list-group-item-heading > small, .list-group-item.active .list-group-item-heading > .small, .list-group-item.active:hover .list-group-item-heading > .small, .list-group-item.active:focus .list-group-item-heading > .small { color: inherit; } .list-group-item.active .list-group-item-text, .list-group-item.active:hover .list-group-item-text, .list-group-item.active:focus .list-group-item-text { color: #e3f2fd; } .list-group-item-success { color: #4caf50; background-color: #dff0d8; } a.list-group-item-success, button.list-group-item-success { color: #4caf50; } a.list-group-item-success .list-group-item-heading, button.list-group-item-success .list-group-item-heading { color: inherit; } a.list-group-item-success:hover, button.list-group-item-success:hover, a.list-group-item-success:focus, button.list-group-item-success:focus { color: #4caf50; background-color: #d0e9c6; } a.list-group-item-success.active, button.list-group-item-success.active, a.list-group-item-success.active:hover, button.list-group-item-success.active:hover, a.list-group-item-success.active:focus, button.list-group-item-success.active:focus { color: #fff; background-color: #4caf50; border-color: #4caf50; } .list-group-item-info { color: #9c27b0; background-color: #e1bee7; } a.list-group-item-info, button.list-group-item-info { color: #9c27b0; } a.list-group-item-info .list-group-item-heading, button.list-group-item-info .list-group-item-heading { color: inherit; } a.list-group-item-info:hover, button.list-group-item-info:hover, a.list-group-item-info:focus, button.list-group-item-info:focus { color: #9c27b0; background-color: #d8abe0; } a.list-group-item-info.active, button.list-group-item-info.active, a.list-group-item-info.active:hover, button.list-group-item-info.active:hover, a.list-group-item-info.active:focus, button.list-group-item-info.active:focus { color: #fff; background-color: #9c27b0; border-color: #9c27b0; } .list-group-item-warning { color: #ff9800; background-color: #ffe0b2; } a.list-group-item-warning, button.list-group-item-warning { color: #ff9800; } a.list-group-item-warning .list-group-item-heading, button.list-group-item-warning .list-group-item-heading { color: inherit; } a.list-group-item-warning:hover, button.list-group-item-warning:hover, a.list-group-item-warning:focus, button.list-group-item-warning:focus { color: #ff9800; background-color: #ffd699; } a.list-group-item-warning.active, button.list-group-item-warning.active, a.list-group-item-warning.active:hover, button.list-group-item-warning.active:hover, a.list-group-item-warning.active:focus, button.list-group-item-warning.active:focus { color: #fff; background-color: #ff9800; border-color: #ff9800; } .list-group-item-danger { color: #e51c23; background-color: #f9bdbb; } a.list-group-item-danger, button.list-group-item-danger { color: #e51c23; } a.list-group-item-danger .list-group-item-heading, button.list-group-item-danger .list-group-item-heading { color: inherit; } a.list-group-item-danger:hover, button.list-group-item-danger:hover, a.list-group-item-danger:focus, button.list-group-item-danger:focus { color: #e51c23; background-color: #f7a6a4; } a.list-group-item-danger.active, button.list-group-item-danger.active, a.list-group-item-danger.active:hover, button.list-group-item-danger.active:hover, a.list-group-item-danger.active:focus, button.list-group-item-danger.active:focus { color: #fff; background-color: #e51c23; border-color: #e51c23; } .list-group-item-heading { margin-top: 0; margin-bottom: 5px; } .list-group-item-text { margin-bottom: 0; line-height: 1.3; } .panel { margin-bottom: 23px; background-color: #ffffff; border: 1px solid transparent; border-radius: 3px; -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); } .panel-body { padding: 15px; } .panel-heading { padding: 10px 15px; border-bottom: 1px solid transparent; border-top-right-radius: 2px; border-top-left-radius: 2px; } .panel-heading > .dropdown .dropdown-toggle { color: inherit; } .panel-title { margin-top: 0; margin-bottom: 0; font-size: 15px; color: inherit; } .panel-title > a, .panel-title > small, .panel-title > .small, .panel-title > small > a, .panel-title > .small > a { color: inherit; } .panel-footer { padding: 10px 15px; background-color: #f5f5f5; border-top: 1px solid #dddddd; border-bottom-right-radius: 2px; border-bottom-left-radius: 2px; } .panel > .list-group, .panel > .panel-collapse > .list-group { margin-bottom: 0; } .panel > .list-group .list-group-item, .panel > .panel-collapse > .list-group .list-group-item { border-width: 1px 0; border-radius: 0; } .panel > .list-group:first-child .list-group-item:first-child, .panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { border-top: 0; border-top-right-radius: 2px; border-top-left-radius: 2px; } .panel > .list-group:last-child .list-group-item:last-child, .panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { border-bottom: 0; border-bottom-right-radius: 2px; border-bottom-left-radius: 2px; } .panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { border-top-right-radius: 0; border-top-left-radius: 0; } .panel-heading + .list-group .list-group-item:first-child { border-top-width: 0; } .list-group + .panel-footer { border-top-width: 0; } .panel > .table, .panel > .table-responsive > .table, .panel > .panel-collapse > .table { margin-bottom: 0; } .panel > .table caption, .panel > .table-responsive > .table caption, .panel > .panel-collapse > .table caption { padding-left: 15px; padding-right: 15px; } .panel > .table:first-child, .panel > .table-responsive:first-child > .table:first-child { border-top-right-radius: 2px; border-top-left-radius: 2px; } .panel > .table:first-child > thead:first-child > tr:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { border-top-left-radius: 2px; border-top-right-radius: 2px; } .panel > .table:first-child > thead:first-child > tr:first-child td:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, .panel > .table:first-child > thead:first-child > tr:first-child th:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { border-top-left-radius: 2px; } .panel > .table:first-child > thead:first-child > tr:first-child td:last-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, .panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, .panel > .table:first-child > thead:first-child > tr:first-child th:last-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, .panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { border-top-right-radius: 2px; } .panel > .table:last-child, .panel > .table-responsive:last-child > .table:last-child { border-bottom-right-radius: 2px; border-bottom-left-radius: 2px; } .panel > .table:last-child > tbody:last-child > tr:last-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; } .panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, .panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, .panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, .panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { border-bottom-left-radius: 2px; } .panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, .panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { border-bottom-right-radius: 2px; } .panel > .panel-body + .table, .panel > .panel-body + .table-responsive, .panel > .table + .panel-body, .panel > .table-responsive + .panel-body { border-top: 1px solid #dddddd; } .panel > .table > tbody:first-child > tr:first-child th, .panel > .table > tbody:first-child > tr:first-child td { border-top: 0; } .panel > .table-bordered, .panel > .table-responsive > .table-bordered { border: 0; } .panel > .table-bordered > thead > tr > th:first-child, .panel > .table-responsive > .table-bordered > thead > tr > th:first-child, .panel > .table-bordered > tbody > tr > th:first-child, .panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, .panel > .table-bordered > tfoot > tr > th:first-child, .panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, .panel > .table-bordered > thead > tr > td:first-child, .panel > .table-responsive > .table-bordered > thead > tr > td:first-child, .panel > .table-bordered > tbody > tr > td:first-child, .panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, .panel > .table-bordered > tfoot > tr > td:first-child, .panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { border-left: 0; } .panel > .table-bordered > thead > tr > th:last-child, .panel > .table-responsive > .table-bordered > thead > tr > th:last-child, .panel > .table-bordered > tbody > tr > th:last-child, .panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, .panel > .table-bordered > tfoot > tr > th:last-child, .panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, .panel > .table-bordered > thead > tr > td:last-child, .panel > .table-responsive > .table-bordered > thead > tr > td:last-child, .panel > .table-bordered > tbody > tr > td:last-child, .panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, .panel > .table-bordered > tfoot > tr > td:last-child, .panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { border-right: 0; } .panel > .table-bordered > thead > tr:first-child > td, .panel > .table-responsive > .table-bordered > thead > tr:first-child > td, .panel > .table-bordered > tbody > tr:first-child > td, .panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, .panel > .table-bordered > thead > tr:first-child > th, .panel > .table-responsive > .table-bordered > thead > tr:first-child > th, .panel > .table-bordered > tbody > tr:first-child > th, .panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { border-bottom: 0; } .panel > .table-bordered > tbody > tr:last-child > td, .panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, .panel > .table-bordered > tfoot > tr:last-child > td, .panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, .panel > .table-bordered > tbody > tr:last-child > th, .panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, .panel > .table-bordered > tfoot > tr:last-child > th, .panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { border-bottom: 0; } .panel > .table-responsive { border: 0; margin-bottom: 0; } .panel-group { margin-bottom: 23px; } .panel-group .panel { margin-bottom: 0; border-radius: 3px; } .panel-group .panel + .panel { margin-top: 5px; } .panel-group .panel-heading { border-bottom: 0; } .panel-group .panel-heading + .panel-collapse > .panel-body, .panel-group .panel-heading + .panel-collapse > .list-group { border-top: 1px solid #dddddd; } .panel-group .panel-footer { border-top: 0; } .panel-group .panel-footer + .panel-collapse .panel-body { border-bottom: 1px solid #dddddd; } .panel-default { border-color: #dddddd; } .panel-default > .panel-heading { color: #212121; background-color: #f5f5f5; border-color: #dddddd; } .panel-default > .panel-heading + .panel-collapse > .panel-body { border-top-color: #dddddd; } .panel-default > .panel-heading .badge { color: #f5f5f5; background-color: #212121; } .panel-default > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #dddddd; } .panel-primary { border-color: #2196f3; } .panel-primary > .panel-heading { color: #ffffff; background-color: #2196f3; border-color: #2196f3; } .panel-primary > .panel-heading + .panel-collapse > .panel-body { border-top-color: #2196f3; } .panel-primary > .panel-heading .badge { color: #2196f3; background-color: #ffffff; } .panel-primary > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #2196f3; } .panel-success { border-color: #d6e9c6; } .panel-success > .panel-heading { color: #ffffff; background-color: #4caf50; border-color: #d6e9c6; } .panel-success > .panel-heading + .panel-collapse > .panel-body { border-top-color: #d6e9c6; } .panel-success > .panel-heading .badge { color: #4caf50; background-color: #ffffff; } .panel-success > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #d6e9c6; } .panel-info { border-color: #cba4dd; } .panel-info > .panel-heading { color: #ffffff; background-color: #9c27b0; border-color: #cba4dd; } .panel-info > .panel-heading + .panel-collapse > .panel-body { border-top-color: #cba4dd; } .panel-info > .panel-heading .badge { color: #9c27b0; background-color: #ffffff; } .panel-info > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #cba4dd; } .panel-warning { border-color: #ffc599; } .panel-warning > .panel-heading { color: #ffffff; background-color: #ff9800; border-color: #ffc599; } .panel-warning > .panel-heading + .panel-collapse > .panel-body { border-top-color: #ffc599; } .panel-warning > .panel-heading .badge { color: #ff9800; background-color: #ffffff; } .panel-warning > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #ffc599; } .panel-danger { border-color: #f7a4af; } .panel-danger > .panel-heading { color: #ffffff; background-color: #e51c23; border-color: #f7a4af; } .panel-danger > .panel-heading + .panel-collapse > .panel-body { border-top-color: #f7a4af; } .panel-danger > .panel-heading .badge { color: #e51c23; background-color: #ffffff; } .panel-danger > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #f7a4af; } .embed-responsive { position: relative; display: block; height: 0; padding: 0; overflow: hidden; } .embed-responsive .embed-responsive-item, .embed-responsive iframe, .embed-responsive embed, .embed-responsive object, .embed-responsive video { position: absolute; top: 0; left: 0; bottom: 0; height: 100%; width: 100%; border: 0; } .embed-responsive-16by9 { padding-bottom: 56.25%; } .embed-responsive-4by3 { padding-bottom: 75%; } .well { min-height: 20px; padding: 19px; margin-bottom: 20px; background-color: #f9f9f9; border: 1px solid transparent; border-radius: 3px; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); } .well blockquote { border-color: #ddd; border-color: rgba(0, 0, 0, 0.15); } .well-lg { padding: 24px; border-radius: 3px; } .well-sm { padding: 9px; border-radius: 3px; } .close { float: right; font-size: 19.5px; font-weight: normal; line-height: 1; color: #000000; text-shadow: none; opacity: 0.2; filter: alpha(opacity=20); } .close:hover, .close:focus { color: #000000; text-decoration: none; cursor: pointer; opacity: 0.5; filter: alpha(opacity=50); } button.close { padding: 0; cursor: pointer; background: transparent; border: 0; -webkit-appearance: none; } .modal-open { overflow: hidden; } .modal { display: none; overflow: hidden; position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1050; -webkit-overflow-scrolling: touch; outline: 0; } .modal.fade .modal-dialog { -webkit-transform: translate(0, -25%); -ms-transform: translate(0, -25%); -o-transform: translate(0, -25%); transform: translate(0, -25%); -webkit-transition: -webkit-transform 0.3s ease-out; -o-transition: -o-transform 0.3s ease-out; transition: transform 0.3s ease-out; } .modal.in .modal-dialog { -webkit-transform: translate(0, 0); -ms-transform: translate(0, 0); -o-transform: translate(0, 0); transform: translate(0, 0); } .modal-open .modal { overflow-x: hidden; overflow-y: auto; } .modal-dialog { position: relative; width: auto; margin: 10px; } .modal-content { position: relative; background-color: #ffffff; border: 1px solid #999999; border: 1px solid transparent; border-radius: 3px; -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); -webkit-background-clip: padding-box; background-clip: padding-box; outline: 0; } .modal-backdrop { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1040; background-color: #000000; } .modal-backdrop.fade { opacity: 0; filter: alpha(opacity=0); } .modal-backdrop.in { opacity: 0.5; filter: alpha(opacity=50); } .modal-header { padding: 15px; border-bottom: 1px solid transparent; min-height: 16.846px; } .modal-header .close { margin-top: -2px; } .modal-title { margin: 0; line-height: 1.846; } .modal-body { position: relative; padding: 15px; } .modal-footer { padding: 15px; text-align: right; border-top: 1px solid transparent; } .modal-footer .btn + .btn { margin-left: 5px; margin-bottom: 0; } .modal-footer .btn-group .btn + .btn { margin-left: -1px; } .modal-footer .btn-block + .btn-block { margin-left: 0; } .modal-scrollbar-measure { position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll; } @media (min-width: 768px) { .modal-dialog { width: 600px; margin: 30px auto; } .modal-content { -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); } .modal-sm { width: 300px; } } @media (min-width: 992px) { .modal-lg { width: 900px; } } .tooltip { position: absolute; z-index: 1070; display: block; font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; font-style: normal; font-weight: normal; letter-spacing: normal; line-break: auto; line-height: 1.846; text-align: left; text-align: start; text-decoration: none; text-shadow: none; text-transform: none; white-space: normal; word-break: normal; word-spacing: normal; word-wrap: normal; font-size: 12px; opacity: 0; filter: alpha(opacity=0); } .tooltip.in { opacity: 0.9; filter: alpha(opacity=90); } .tooltip.top { margin-top: -3px; padding: 5px 0; } .tooltip.right { margin-left: 3px; padding: 0 5px; } .tooltip.bottom { margin-top: 3px; padding: 5px 0; } .tooltip.left { margin-left: -3px; padding: 0 5px; } .tooltip-inner { max-width: 200px; padding: 3px 8px; color: #ffffff; text-align: center; background-color: #727272; border-radius: 3px; } .tooltip-arrow { position: absolute; width: 0; height: 0; border-color: transparent; border-style: solid; } .tooltip.top .tooltip-arrow { bottom: 0; left: 50%; margin-left: -5px; border-width: 5px 5px 0; border-top-color: #727272; } .tooltip.top-left .tooltip-arrow { bottom: 0; right: 5px; margin-bottom: -5px; border-width: 5px 5px 0; border-top-color: #727272; } .tooltip.top-right .tooltip-arrow { bottom: 0; left: 5px; margin-bottom: -5px; border-width: 5px 5px 0; border-top-color: #727272; } .tooltip.right .tooltip-arrow { top: 50%; left: 0; margin-top: -5px; border-width: 5px 5px 5px 0; border-right-color: #727272; } .tooltip.left .tooltip-arrow { top: 50%; right: 0; margin-top: -5px; border-width: 5px 0 5px 5px; border-left-color: #727272; } .tooltip.bottom .tooltip-arrow { top: 0; left: 50%; margin-left: -5px; border-width: 0 5px 5px; border-bottom-color: #727272; } .tooltip.bottom-left .tooltip-arrow { top: 0; right: 5px; margin-top: -5px; border-width: 0 5px 5px; border-bottom-color: #727272; } .tooltip.bottom-right .tooltip-arrow { top: 0; left: 5px; margin-top: -5px; border-width: 0 5px 5px; border-bottom-color: #727272; } .popover { position: absolute; top: 0; left: 0; z-index: 1060; display: none; max-width: 276px; padding: 1px; font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; font-style: normal; font-weight: normal; letter-spacing: normal; line-break: auto; line-height: 1.846; text-align: left; text-align: start; text-decoration: none; text-shadow: none; text-transform: none; white-space: normal; word-break: normal; word-spacing: normal; word-wrap: normal; font-size: 13px; background-color: #ffffff; -webkit-background-clip: padding-box; background-clip: padding-box; border: 1px solid transparent; border-radius: 3px; -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); } .popover.top { margin-top: -10px; } .popover.right { margin-left: 10px; } .popover.bottom { margin-top: 10px; } .popover.left { margin-left: -10px; } .popover-title { margin: 0; padding: 8px 14px; font-size: 13px; background-color: #f7f7f7; border-bottom: 1px solid #ebebeb; border-radius: 2px 2px 0 0; } .popover-content { padding: 9px 14px; } .popover > .arrow, .popover > .arrow:after { position: absolute; display: block; width: 0; height: 0; border-color: transparent; border-style: solid; } .popover > .arrow { border-width: 11px; } .popover > .arrow:after { border-width: 10px; content: ""; } .popover.top > .arrow { left: 50%; margin-left: -11px; border-bottom-width: 0; border-top-color: rgba(0, 0, 0, 0); border-top-color: rgba(0, 0, 0, 0.075); bottom: -11px; } .popover.top > .arrow:after { content: " "; bottom: 1px; margin-left: -10px; border-bottom-width: 0; border-top-color: #ffffff; } .popover.right > .arrow { top: 50%; left: -11px; margin-top: -11px; border-left-width: 0; border-right-color: rgba(0, 0, 0, 0); border-right-color: rgba(0, 0, 0, 0.075); } .popover.right > .arrow:after { content: " "; left: 1px; bottom: -10px; border-left-width: 0; border-right-color: #ffffff; } .popover.bottom > .arrow { left: 50%; margin-left: -11px; border-top-width: 0; border-bottom-color: rgba(0, 0, 0, 0); border-bottom-color: rgba(0, 0, 0, 0.075); top: -11px; } .popover.bottom > .arrow:after { content: " "; top: 1px; margin-left: -10px; border-top-width: 0; border-bottom-color: #ffffff; } .popover.left > .arrow { top: 50%; right: -11px; margin-top: -11px; border-right-width: 0; border-left-color: rgba(0, 0, 0, 0); border-left-color: rgba(0, 0, 0, 0.075); } .popover.left > .arrow:after { content: " "; right: 1px; border-right-width: 0; border-left-color: #ffffff; bottom: -10px; } .carousel { position: relative; } .carousel-inner { position: relative; overflow: hidden; width: 100%; } .carousel-inner > .item { display: none; position: relative; -webkit-transition: 0.6s ease-in-out left; -o-transition: 0.6s ease-in-out left; transition: 0.6s ease-in-out left; } .carousel-inner > .item > img, .carousel-inner > .item > a > img { line-height: 1; } @media all and (transform-3d), (-webkit-transform-3d) { .carousel-inner > .item { -webkit-transition: -webkit-transform 0.6s ease-in-out; -o-transition: -o-transform 0.6s ease-in-out; transition: transform 0.6s ease-in-out; -webkit-backface-visibility: hidden; backface-visibility: hidden; -webkit-perspective: 1000px; perspective: 1000px; } .carousel-inner > .item.next, .carousel-inner > .item.active.right { -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); left: 0; } .carousel-inner > .item.prev, .carousel-inner > .item.active.left { -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); left: 0; } .carousel-inner > .item.next.left, .carousel-inner > .item.prev.right, .carousel-inner > .item.active { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); left: 0; } } .carousel-inner > .active, .carousel-inner > .next, .carousel-inner > .prev { display: block; } .carousel-inner > .active { left: 0; } .carousel-inner > .next, .carousel-inner > .prev { position: absolute; top: 0; width: 100%; } .carousel-inner > .next { left: 100%; } .carousel-inner > .prev { left: -100%; } .carousel-inner > .next.left, .carousel-inner > .prev.right { left: 0; } .carousel-inner > .active.left { left: -100%; } .carousel-inner > .active.right { left: 100%; } .carousel-control { position: absolute; top: 0; left: 0; bottom: 0; width: 15%; opacity: 0.5; filter: alpha(opacity=50); font-size: 20px; color: #ffffff; text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); } .carousel-control.left { background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001))); background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); } .carousel-control.right { left: auto; right: 0; background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5))); background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); } .carousel-control:hover, .carousel-control:focus { outline: 0; color: #ffffff; text-decoration: none; opacity: 0.9; filter: alpha(opacity=90); } .carousel-control .icon-prev, .carousel-control .icon-next, .carousel-control .glyphicon-chevron-left, .carousel-control .glyphicon-chevron-right { position: absolute; top: 50%; margin-top: -10px; z-index: 5; display: inline-block; } .carousel-control .icon-prev, .carousel-control .glyphicon-chevron-left { left: 50%; margin-left: -10px; } .carousel-control .icon-next, .carousel-control .glyphicon-chevron-right { right: 50%; margin-right: -10px; } .carousel-control .icon-prev, .carousel-control .icon-next { width: 20px; height: 20px; line-height: 1; font-family: serif; } .carousel-control .icon-prev:before { content: '\2039'; } .carousel-control .icon-next:before { content: '\203a'; } .carousel-indicators { position: absolute; bottom: 10px; left: 50%; z-index: 15; width: 60%; margin-left: -30%; padding-left: 0; list-style: none; text-align: center; } .carousel-indicators li { display: inline-block; width: 10px; height: 10px; margin: 1px; text-indent: -999px; border: 1px solid #ffffff; border-radius: 10px; cursor: pointer; background-color: #000 \9; background-color: rgba(0, 0, 0, 0); } .carousel-indicators .active { margin: 0; width: 12px; height: 12px; background-color: #ffffff; } .carousel-caption { position: absolute; left: 15%; right: 15%; bottom: 20px; z-index: 10; padding-top: 20px; padding-bottom: 20px; color: #ffffff; text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); } .carousel-caption .btn { text-shadow: none; } @media screen and (min-width: 768px) { .carousel-control .glyphicon-chevron-left, .carousel-control .glyphicon-chevron-right, .carousel-control .icon-prev, .carousel-control .icon-next { width: 30px; height: 30px; margin-top: -15px; font-size: 30px; } .carousel-control .glyphicon-chevron-left, .carousel-control .icon-prev { margin-left: -15px; } .carousel-control .glyphicon-chevron-right, .carousel-control .icon-next { margin-right: -15px; } .carousel-caption { left: 20%; right: 20%; padding-bottom: 30px; } .carousel-indicators { bottom: 20px; } } .clearfix:before, .clearfix:after, .dl-horizontal dd:before, .dl-horizontal dd:after, .container:before, .container:after, .container-fluid:before, .container-fluid:after, .row:before, .row:after, .form-horizontal .form-group:before, .form-horizontal .form-group:after, .btn-toolbar:before, .btn-toolbar:after, .btn-group-vertical > .btn-group:before, .btn-group-vertical > .btn-group:after, .nav:before, .nav:after, .navbar:before, .navbar:after, .navbar-header:before, .navbar-header:after, .navbar-collapse:before, .navbar-collapse:after, .pager:before, .pager:after, .panel-body:before, .panel-body:after, .modal-footer:before, .modal-footer:after { content: " "; display: table; } .clearfix:after, .dl-horizontal dd:after, .container:after, .container-fluid:after, .row:after, .form-horizontal .form-group:after, .btn-toolbar:after, .btn-group-vertical > .btn-group:after, .nav:after, .navbar:after, .navbar-header:after, .navbar-collapse:after, .pager:after, .panel-body:after, .modal-footer:after { clear: both; } .center-block { display: block; margin-left: auto; margin-right: auto; } .pull-right { float: right !important; } .pull-left { float: left !important; } .hide { display: none !important; } .show { display: block !important; } .invisible { visibility: hidden; } .text-hide { font: 0/0 a; color: transparent; text-shadow: none; background-color: transparent; border: 0; } .hidden { display: none !important; } .affix { position: fixed; } @-ms-viewport { width: device-width; } .visible-xs, .visible-sm, .visible-md, .visible-lg { display: none !important; } .visible-xs-block, .visible-xs-inline, .visible-xs-inline-block, .visible-sm-block, .visible-sm-inline, .visible-sm-inline-block, .visible-md-block, .visible-md-inline, .visible-md-inline-block, .visible-lg-block, .visible-lg-inline, .visible-lg-inline-block { display: none !important; } @media (max-width: 767px) { .visible-xs { display: block !important; } table.visible-xs { display: table !important; } tr.visible-xs { display: table-row !important; } th.visible-xs, td.visible-xs { display: table-cell !important; } } @media (max-width: 767px) { .visible-xs-block { display: block !important; } } @media (max-width: 767px) { .visible-xs-inline { display: inline !important; } } @media (max-width: 767px) { .visible-xs-inline-block { display: inline-block !important; } } @media (min-width: 768px) and (max-width: 991px) { .visible-sm { display: block !important; } table.visible-sm { display: table !important; } tr.visible-sm { display: table-row !important; } th.visible-sm, td.visible-sm { display: table-cell !important; } } @media (min-width: 768px) and (max-width: 991px) { .visible-sm-block { display: block !important; } } @media (min-width: 768px) and (max-width: 991px) { .visible-sm-inline { display: inline !important; } } @media (min-width: 768px) and (max-width: 991px) { .visible-sm-inline-block { display: inline-block !important; } } @media (min-width: 992px) and (max-width: 1199px) { .visible-md { display: block !important; } table.visible-md { display: table !important; } tr.visible-md { display: table-row !important; } th.visible-md, td.visible-md { display: table-cell !important; } } @media (min-width: 992px) and (max-width: 1199px) { .visible-md-block { display: block !important; } } @media (min-width: 992px) and (max-width: 1199px) { .visible-md-inline { display: inline !important; } } @media (min-width: 992px) and (max-width: 1199px) { .visible-md-inline-block { display: inline-block !important; } } @media (min-width: 1200px) { .visible-lg { display: block !important; } table.visible-lg { display: table !important; } tr.visible-lg { display: table-row !important; } th.visible-lg, td.visible-lg { display: table-cell !important; } } @media (min-width: 1200px) { .visible-lg-block { display: block !important; } } @media (min-width: 1200px) { .visible-lg-inline { display: inline !important; } } @media (min-width: 1200px) { .visible-lg-inline-block { display: inline-block !important; } } @media (max-width: 767px) { .hidden-xs { display: none !important; } } @media (min-width: 768px) and (max-width: 991px) { .hidden-sm { display: none !important; } } @media (min-width: 992px) and (max-width: 1199px) { .hidden-md { display: none !important; } } @media (min-width: 1200px) { .hidden-lg { display: none !important; } } .visible-print { display: none !important; } @media print { .visible-print { display: block !important; } table.visible-print { display: table !important; } tr.visible-print { display: table-row !important; } th.visible-print, td.visible-print { display: table-cell !important; } } .visible-print-block { display: none !important; } @media print { .visible-print-block { display: block !important; } } .visible-print-inline { display: none !important; } @media print { .visible-print-inline { display: inline !important; } } .visible-print-inline-block { display: none !important; } @media print { .visible-print-inline-block { display: inline-block !important; } } @media print { .hidden-print { display: none !important; } } .navbar { border: none; -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); } .navbar-brand { font-size: 24px; } .navbar-inverse .form-control { color: #fff; } .navbar-inverse .form-control::-moz-placeholder { color: #b2dbfb; opacity: 1; } .navbar-inverse .form-control:-ms-input-placeholder { color: #b2dbfb; } .navbar-inverse .form-control::-webkit-input-placeholder { color: #b2dbfb; } .navbar-inverse .form-control[type=text], .navbar-inverse .form-control[type=password] { -webkit-box-shadow: inset 0 -1px 0 #b2dbfb; box-shadow: inset 0 -1px 0 #b2dbfb; } .navbar-inverse .form-control[type=text]:focus, .navbar-inverse .form-control[type=password]:focus { -webkit-box-shadow: inset 0 -2px 0 #ffffff; box-shadow: inset 0 -2px 0 #ffffff; } .btn-default { -webkit-background-size: 200% 200%; background-size: 200%; background-position: 50%; } .btn-default:hover, .btn-default:active:hover, .btn-default:focus { background-color: #f0f0f0; } .btn-default:active { background-color: #f0f0f0; background-image: -webkit-radial-gradient(circle, #f0f0f0 10%, #ffffff 11%); background-image: -o-radial-gradient(circle, #f0f0f0 10%, #ffffff 11%); background-image: radial-gradient(circle, #f0f0f0 10%, #ffffff 11%); background-repeat: no-repeat; -webkit-background-size: 1000% 1000%; background-size: 1000%; -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3); box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3); } .btn-primary { -webkit-background-size: 200% 200%; background-size: 200%; background-position: 50%; } .btn-primary:hover, .btn-primary:active:hover, .btn-primary:focus { background-color: #0d87e9; } .btn-primary:active { background-color: #0d87e9; background-image: -webkit-radial-gradient(circle, #0d87e9 10%, #2196f3 11%); background-image: -o-radial-gradient(circle, #0d87e9 10%, #2196f3 11%); background-image: radial-gradient(circle, #0d87e9 10%, #2196f3 11%); background-repeat: no-repeat; -webkit-background-size: 1000% 1000%; background-size: 1000%; -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3); box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3); } .btn-success { -webkit-background-size: 200% 200%; background-size: 200%; background-position: 50%; } .btn-success:hover, .btn-success:active:hover, .btn-success:focus { background-color: #439a46; } .btn-success:active { background-color: #439a46; background-image: -webkit-radial-gradient(circle, #439a46 10%, #4caf50 11%); background-image: -o-radial-gradient(circle, #439a46 10%, #4caf50 11%); background-image: radial-gradient(circle, #439a46 10%, #4caf50 11%); background-repeat: no-repeat; -webkit-background-size: 1000% 1000%; background-size: 1000%; -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3); box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3); } .btn-info { -webkit-background-size: 200% 200%; background-size: 200%; background-position: 50%; } .btn-info:hover, .btn-info:active:hover, .btn-info:focus { background-color: #862197; } .btn-info:active { background-color: #862197; background-image: -webkit-radial-gradient(circle, #862197 10%, #9c27b0 11%); background-image: -o-radial-gradient(circle, #862197 10%, #9c27b0 11%); background-image: radial-gradient(circle, #862197 10%, #9c27b0 11%); background-repeat: no-repeat; -webkit-background-size: 1000% 1000%; background-size: 1000%; -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3); box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3); } .btn-warning { -webkit-background-size: 200% 200%; background-size: 200%; background-position: 50%; } .btn-warning:hover, .btn-warning:active:hover, .btn-warning:focus { background-color: #e08600; } .btn-warning:active { background-color: #e08600; background-image: -webkit-radial-gradient(circle, #e08600 10%, #ff9800 11%); background-image: -o-radial-gradient(circle, #e08600 10%, #ff9800 11%); background-image: radial-gradient(circle, #e08600 10%, #ff9800 11%); background-repeat: no-repeat; -webkit-background-size: 1000% 1000%; background-size: 1000%; -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3); box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3); } .btn-danger { -webkit-background-size: 200% 200%; background-size: 200%; background-position: 50%; } .btn-danger:hover, .btn-danger:active:hover, .btn-danger:focus { background-color: #cb171e; } .btn-danger:active { background-color: #cb171e; background-image: -webkit-radial-gradient(circle, #cb171e 10%, #e51c23 11%); background-image: -o-radial-gradient(circle, #cb171e 10%, #e51c23 11%); background-image: radial-gradient(circle, #cb171e 10%, #e51c23 11%); background-repeat: no-repeat; -webkit-background-size: 1000% 1000%; background-size: 1000%; -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3); box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.3); } .btn { text-transform: uppercase; border-right: none; border-bottom: none; -webkit-box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3); -webkit-transition: all 0.2s; -o-transition: all 0.2s; transition: all 0.2s; } .btn-link { -webkit-box-shadow: none; box-shadow: none; } .btn-link:hover, .btn-link:focus { color: #2196f3; text-decoration: none; } .btn-default.disabled { border: 1px solid #eeeeee; } .btn-group .btn + .btn, .btn-group .btn + .btn-group, .btn-group .btn-group + .btn, .btn-group .btn-group + .btn-group { margin-left: 0; } .btn-group-vertical > .btn + .btn, .btn-group-vertical > .btn + .btn-group, .btn-group-vertical > .btn-group + .btn, .btn-group-vertical > .btn-group + .btn-group { margin-top: 0; } body { -webkit-font-smoothing: antialiased; letter-spacing: .1px; } p { margin: 0 0 1em; } input, button { -webkit-font-smoothing: antialiased; letter-spacing: .1px; } a { -webkit-transition: all 0.2s; -o-transition: all 0.2s; transition: all 0.2s; } .table-hover > tbody > tr, .table-hover > tbody > tr > th, .table-hover > tbody > tr > td { -webkit-transition: all 0.2s; -o-transition: all 0.2s; transition: all 0.2s; } label { font-weight: normal; } textarea, textarea.form-control, input.form-control, input[type=text], input[type=password], input[type=email], input[type=number], [type=text].form-control, [type=password].form-control, [type=email].form-control, [type=tel].form-control, [contenteditable].form-control { padding: 0; border: none; border-radius: 0; -webkit-appearance: none; -webkit-box-shadow: inset 0 -1px 0 #dddddd; box-shadow: inset 0 -1px 0 #dddddd; font-size: 16px; } textarea:focus, textarea.form-control:focus, input.form-control:focus, input[type=text]:focus, input[type=password]:focus, input[type=email]:focus, input[type=number]:focus, [type=text].form-control:focus, [type=password].form-control:focus, [type=email].form-control:focus, [type=tel].form-control:focus, [contenteditable].form-control:focus { -webkit-box-shadow: inset 0 -2px 0 #2196f3; box-shadow: inset 0 -2px 0 #2196f3; } textarea[disabled], textarea.form-control[disabled], input.form-control[disabled], input[type=text][disabled], input[type=password][disabled], input[type=email][disabled], input[type=number][disabled], [type=text].form-control[disabled], [type=password].form-control[disabled], [type=email].form-control[disabled], [type=tel].form-control[disabled], [contenteditable].form-control[disabled], textarea[readonly], textarea.form-control[readonly], input.form-control[readonly], input[type=text][readonly], input[type=password][readonly], input[type=email][readonly], input[type=number][readonly], [type=text].form-control[readonly], [type=password].form-control[readonly], [type=email].form-control[readonly], [type=tel].form-control[readonly], [contenteditable].form-control[readonly] { -webkit-box-shadow: none; box-shadow: none; border-bottom: 1px dotted #ddd; } textarea.input-sm, textarea.form-control.input-sm, input.form-control.input-sm, input[type=text].input-sm, input[type=password].input-sm, input[type=email].input-sm, input[type=number].input-sm, [type=text].form-control.input-sm, [type=password].form-control.input-sm, [type=email].form-control.input-sm, [type=tel].form-control.input-sm, [contenteditable].form-control.input-sm { font-size: 12px; } textarea.input-lg, textarea.form-control.input-lg, input.form-control.input-lg, input[type=text].input-lg, input[type=password].input-lg, input[type=email].input-lg, input[type=number].input-lg, [type=text].form-control.input-lg, [type=password].form-control.input-lg, [type=email].form-control.input-lg, [type=tel].form-control.input-lg, [contenteditable].form-control.input-lg { font-size: 17px; } select, select.form-control { border: 0; border-radius: 0; -webkit-appearance: none; -moz-appearance: none; appearance: none; padding-left: 0; padding-right: 0\9; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAMAAACelLz8AAAAJ1BMVEVmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmaP/QSjAAAADHRSTlMAAgMJC0uWpKa6wMxMdjkoAAAANUlEQVR4AeXJyQEAERAAsNl7Hf3X6xt0QL6JpZWq30pdvdadme+0PMdzvHm8YThHcT1H7K0BtOMDniZhWOgAAAAASUVORK5CYII=); -webkit-background-size: 13px 13px; background-size: 13px; background-repeat: no-repeat; background-position: right center; -webkit-box-shadow: inset 0 -1px 0 #dddddd; box-shadow: inset 0 -1px 0 #dddddd; font-size: 16px; line-height: 1.5; } select::-ms-expand, select.form-control::-ms-expand { display: none; } select.input-sm, select.form-control.input-sm { font-size: 12px; } select.input-lg, select.form-control.input-lg { font-size: 17px; } select:focus, select.form-control:focus { -webkit-box-shadow: inset 0 -2px 0 #2196f3; box-shadow: inset 0 -2px 0 #2196f3; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAMAAACelLz8AAAAJ1BMVEUhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISF8S9ewAAAADHRSTlMAAgMJC0uWpKa6wMxMdjkoAAAANUlEQVR4AeXJyQEAERAAsNl7Hf3X6xt0QL6JpZWq30pdvdadme+0PMdzvHm8YThHcT1H7K0BtOMDniZhWOgAAAAASUVORK5CYII=); } select[multiple], select.form-control[multiple] { background: none; } .radio label, .radio-inline label, .checkbox label, .checkbox-inline label { padding-left: 25px; } .radio input[type="radio"], .radio-inline input[type="radio"], .checkbox input[type="radio"], .checkbox-inline input[type="radio"], .radio input[type="checkbox"], .radio-inline input[type="checkbox"], .checkbox input[type="checkbox"], .checkbox-inline input[type="checkbox"] { margin-left: -25px; } input[type="radio"], .radio input[type="radio"], .radio-inline input[type="radio"] { position: relative; margin-top: 5px; margin-right: 4px; vertical-align: -4px; border: none; background-color: transparent; -webkit-appearance: none; appearance: none; cursor: pointer; } input[type="radio"]:focus, .radio input[type="radio"]:focus, .radio-inline input[type="radio"]:focus { outline: none; } input[type="radio"]:before, .radio input[type="radio"]:before, .radio-inline input[type="radio"]:before, input[type="radio"]:after, .radio input[type="radio"]:after, .radio-inline input[type="radio"]:after { content: ""; display: block; width: 18px; height: 18px; margin-top: -3px; border-radius: 50%; -webkit-transition: 240ms; -o-transition: 240ms; transition: 240ms; } input[type="radio"]:before, .radio input[type="radio"]:before, .radio-inline input[type="radio"]:before { position: absolute; left: 0; top: 0; background-color: #2196f3; -webkit-transform: scale(0); -ms-transform: scale(0); -o-transform: scale(0); transform: scale(0); } input[type="radio"]:after, .radio input[type="radio"]:after, .radio-inline input[type="radio"]:after { border: 2px solid #666666; } input[type="radio"]:checked:before, .radio input[type="radio"]:checked:before, .radio-inline input[type="radio"]:checked:before { -webkit-transform: scale(0.5); -ms-transform: scale(0.5); -o-transform: scale(0.5); transform: scale(0.5); } input[type="radio"]:disabled:checked:before, .radio input[type="radio"]:disabled:checked:before, .radio-inline input[type="radio"]:disabled:checked:before { background-color: #bbbbbb; } input[type="radio"]:checked:after, .radio input[type="radio"]:checked:after, .radio-inline input[type="radio"]:checked:after { border-color: #2196f3; } input[type="radio"]:disabled:after, .radio input[type="radio"]:disabled:after, .radio-inline input[type="radio"]:disabled:after, input[type="radio"]:disabled:checked:after, .radio input[type="radio"]:disabled:checked:after, .radio-inline input[type="radio"]:disabled:checked:after { border-color: #bbbbbb; } input[type="checkbox"], .checkbox input[type="checkbox"], .checkbox-inline input[type="checkbox"] { position: relative; vertical-align: -4px; border: none; -webkit-appearance: none; appearance: none; cursor: pointer; } input[type="checkbox"]:focus, .checkbox input[type="checkbox"]:focus, .checkbox-inline input[type="checkbox"]:focus { outline: none; } input[type="checkbox"]:after, .checkbox input[type="checkbox"]:after, .checkbox-inline input[type="checkbox"]:after { content: ""; display: block; width: 18px; height: 18px; margin-top: -2px; margin-right: 5px; border: 2px solid #666666; border-radius: 2px; -webkit-transition: 240ms; -o-transition: 240ms; transition: 240ms; } input[type="checkbox"]:checked:before, .checkbox input[type="checkbox"]:checked:before, .checkbox-inline input[type="checkbox"]:checked:before { content: ""; position: absolute; top: 0; left: 6px; display: table; width: 6px; height: 12px; border: 2px solid #fff; border-top-width: 0; border-left-width: 0; -webkit-transform: rotate(45deg); -ms-transform: rotate(45deg); -o-transform: rotate(45deg); transform: rotate(45deg); } input[type="checkbox"]:checked:after, .checkbox input[type="checkbox"]:checked:after, .checkbox-inline input[type="checkbox"]:checked:after { background-color: #2196f3; border-color: #2196f3; } input[type="checkbox"]:disabled:after, .checkbox input[type="checkbox"]:disabled:after, .checkbox-inline input[type="checkbox"]:disabled:after { border-color: #bbbbbb; } input[type="checkbox"]:disabled:checked:after, .checkbox input[type="checkbox"]:disabled:checked:after, .checkbox-inline input[type="checkbox"]:disabled:checked:after { background-color: #bbbbbb; border-color: transparent; } .has-warning input:not([type=checkbox]), .has-warning .form-control, .has-warning input:not([type=checkbox]):focus, .has-warning .form-control:focus { -webkit-box-shadow: inset 0 -2px 0 #ff9800; box-shadow: inset 0 -2px 0 #ff9800; } .has-error input:not([type=checkbox]), .has-error .form-control, .has-error input:not([type=checkbox]):focus, .has-error .form-control:focus { -webkit-box-shadow: inset 0 -2px 0 #e51c23; box-shadow: inset 0 -2px 0 #e51c23; } .has-success input:not([type=checkbox]), .has-success .form-control, .has-success input:not([type=checkbox]):focus, .has-success .form-control:focus { -webkit-box-shadow: inset 0 -2px 0 #4caf50; box-shadow: inset 0 -2px 0 #4caf50; } .has-warning .input-group-addon, .has-error .input-group-addon, .has-success .input-group-addon { color: #666666; border-color: transparent; background-color: transparent; } .nav-tabs > li > a, .nav-tabs > li > a:focus { margin-right: 0; background-color: transparent; border: none; color: #666666; -webkit-box-shadow: inset 0 -1px 0 #dddddd; box-shadow: inset 0 -1px 0 #dddddd; -webkit-transition: all 0.2s; -o-transition: all 0.2s; transition: all 0.2s; } .nav-tabs > li > a:hover, .nav-tabs > li > a:focus:hover { background-color: transparent; -webkit-box-shadow: inset 0 -2px 0 #2196f3; box-shadow: inset 0 -2px 0 #2196f3; color: #2196f3; } .nav-tabs > li.active > a, .nav-tabs > li.active > a:focus { border: none; -webkit-box-shadow: inset 0 -2px 0 #2196f3; box-shadow: inset 0 -2px 0 #2196f3; color: #2196f3; } .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus:hover { border: none; color: #2196f3; } .nav-tabs > li.disabled > a { -webkit-box-shadow: inset 0 -1px 0 #dddddd; box-shadow: inset 0 -1px 0 #dddddd; } .nav-tabs.nav-justified > li > a, .nav-tabs.nav-justified > li > a:hover, .nav-tabs.nav-justified > li > a:focus, .nav-tabs.nav-justified > .active > a, .nav-tabs.nav-justified > .active > a:hover, .nav-tabs.nav-justified > .active > a:focus { border: none; } .nav-tabs .dropdown-menu { margin-top: 0; } .dropdown-menu { margin-top: 0; border: none; -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); } .alert { border: none; color: #fff; } .alert-success { background-color: #4caf50; } .alert-info { background-color: #9c27b0; } .alert-warning { background-color: #ff9800; } .alert-danger { background-color: #e51c23; } .alert a:not(.close), .alert .alert-link { color: #fff; font-weight: bold; } .alert .close { color: #fff; } .badge { padding: 3px 6px 5px; } .progress { position: relative; z-index: 1; height: 6px; border-radius: 0; -webkit-box-shadow: none; box-shadow: none; } .progress-bar { -webkit-box-shadow: none; box-shadow: none; } .progress-bar:last-child { border-radius: 0 3px 3px 0; } .progress-bar:last-child:before { display: block; content: ""; position: absolute; width: 100%; height: 100%; left: 0; right: 0; z-index: -1; background-color: #cae6fc; } .progress-bar-success:last-child.progress-bar:before { background-color: #c7e7c8; } .progress-bar-info:last-child.progress-bar:before { background-color: #edc9f3; } .progress-bar-warning:last-child.progress-bar:before { background-color: #ffe0b3; } .progress-bar-danger:last-child.progress-bar:before { background-color: #f28e92; } .close { font-size: 34px; font-weight: 300; line-height: 24px; opacity: 0.6; -webkit-transition: all 0.2s; -o-transition: all 0.2s; transition: all 0.2s; } .close:hover { opacity: 1; } .list-group-item { padding: 15px; } .list-group-item-text { color: #bbbbbb; } .well { border-radius: 0; -webkit-box-shadow: none; box-shadow: none; } .panel { border: none; border-radius: 2px; -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); } .panel-heading { border-bottom: none; } .panel-footer { border-top: none; } .popover { border: none; -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); } .carousel-caption h1, .carousel-caption h2, .carousel-caption h3, .carousel-caption h4, .carousel-caption h5, .carousel-caption h6 { color: inherit; } libratbag-0.9/doc/style/customdoxygen.css000066400000000000000000000104571311552661500206540ustar00rootroot00000000000000h1, .h1, h2, .h2, h3, .h3{ font-weight: 200 !important; } #navrow1, #navrow2, #navrow3, #navrow4, #navrow5{ border-bottom: 1px solid #EEEEEE; } .adjust-right { margin-left: 30px !important; font-size: 1.15em !important; } .navbar{ border: 0px solid #222 !important; } /* Sticky footer styles -------------------------------------------------- */ html, body { height: 100%; /* The html and body elements cannot have any padding or margin. */ } /* Wrapper for page content to push down footer */ #wrap { min-height: 100%; height: auto; /* Negative indent footer by its height */ margin: 0 auto -60px; /* Pad bottom by footer height */ padding: 0 0 60px; } /* Set the fixed height of the footer here */ #footer { font-size: 0.9em; padding: 8px 0px; background-color: #f5f5f5; } .footer-row { line-height: 44px; } #footer > .container { padding-left: 15px; padding-right: 15px; } .footer-follow-icon { margin-left: 3px; text-decoration: none !important; } .footer-follow-icon img { width: 20px; } .footer-link { padding-top: 5px; display: inline-block; color: #999999; text-decoration: none; } .footer-copyright { text-align: center; } @media (min-width: 992px) { .footer-row { text-align: left; } .footer-icons { text-align: right; } } @media (max-width: 991px) { .footer-row { text-align: center; } .footer-icons { text-align: center; } } /* DOXYGEN Code Styles ----------------------------------- */ a.qindex { font-weight: bold; } a.qindexHL { font-weight: bold; background-color: #9CAFD4; color: #ffffff; border: 1px double #869DCA; } .contents a.qindexHL:visited { color: #ffffff; } a.code, a.code:visited, a.line, a.line:visited { color: #4665A2; } a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { color: #4665A2; } /* @end */ dl.el { margin-left: -1cm; } pre.fragment { border: 1px solid #C4CFE5; background-color: #FBFCFD; padding: 4px 6px; margin: 4px 8px 4px 2px; overflow: auto; word-wrap: break-word; font-size: 9pt; line-height: 125%; font-family: monospace, fixed; font-size: 105%; } div.fragment { padding: 4px 6px; margin: 4px 8px 4px 2px; border: 1px solid #C4CFE5; } div.line { font-family: monospace, fixed; font-size: 13px; min-height: 13px; line-height: 1.0; text-wrap: unrestricted; white-space: -moz-pre-wrap; /* Moz */ white-space: -pre-wrap; /* Opera 4-6 */ white-space: -o-pre-wrap; /* Opera 7 */ white-space: pre-wrap; /* CSS3 */ word-wrap: break-word; /* IE 5.5+ */ text-indent: -53px; padding-left: 53px; padding-bottom: 0px; margin: 0px; -webkit-transition-property: background-color, box-shadow; -webkit-transition-duration: 0.5s; -moz-transition-property: background-color, box-shadow; -moz-transition-duration: 0.5s; -ms-transition-property: background-color, box-shadow; -ms-transition-duration: 0.5s; -o-transition-property: background-color, box-shadow; -o-transition-duration: 0.5s; transition-property: background-color, box-shadow; transition-duration: 0.5s; } div.line.glow { background-color: cyan; box-shadow: 0 0 10px cyan; } span.lineno { padding-right: 4px; text-align: right; border-right: 2px solid #0F0; background-color: #E8E8E8; white-space: pre; } span.lineno a { background-color: #D8D8D8; } span.lineno a:hover { background-color: #C8C8C8; } div.groupHeader { margin-left: 16px; margin-top: 12px; font-weight: bold; } div.groupText { margin-left: 16px; font-style: italic; } /* @group Code Colorization */ span.keyword { color: #008000 } span.keywordtype { color: #604020 } span.keywordflow { color: #e08000 } span.comment { color: #800000 } span.preprocessor { color: #806020 } span.stringliteral { color: #002080 } span.charliteral { color: #008080 } span.vhdldigit { color: #ff00ff } span.vhdlchar { color: #000000 } span.vhdlkeyword { color: #700070 } span.vhdllogic { color: #ff0000 } blockquote { background-color: #F7F8FB; border-left: 2px solid #9CAFD4; margin: 0 24px 0 4px; padding: 0 12px 0 16px; } libratbag-0.9/doc/style/footer.html000066400000000000000000000013561311552661500174140ustar00rootroot00000000000000 libratbag-0.9/doc/style/header.html000066400000000000000000000040131311552661500173370ustar00rootroot00000000000000 $projectname: $title $title $treeview $search $mathjax $extrastylesheet
libratbag-0.9/hwdb/000077500000000000000000000000001311552661500142425ustar00rootroot00000000000000libratbag-0.9/hwdb/70-libratbag-mouse.hwdb000066400000000000000000000115701311552661500204150ustar00rootroot00000000000000# Database for libratbag to select the correct driver to use for gaming mice. # # The lookup keys are composed in: # 70-libratbag-mouse.rules # # Match string format: # mouse::vp:name:: # # Supported subsystems: usb, bluetooth # vid/pid as 4-digit hex lowercase vendor/product # # if vid/pid is unavailable, use # mouse:*:name:: # if name is unavailable, use # mouse::vp:* # # For example, the following 5 matches all match the same mouse: # mouse:usb:v17efp6019:name:Lenovo Optical USB Mouse: # mouse:usb:*:name:Lenovo Optical USB Mouse: # mouse:usb:v17efp6019:* # mouse:*:name:Lenovo Optical USB Mouse: # # To add local entries, create a new file # /etc/udev/hwdb.d/71-libratbat-mouse-local.hwdb # and add your rules there. To load the new rules execute (as root): # udevadm hwdb --update # udevadm trigger /dev/input/eventXX # where /dev/input/eventXX is the mouse in question. If in # doubt, simply use /dev/input/event* to reload all input rules. # # If your changes are generally applicable, preferably send them as a pull # request to # https://github.com/libratbag/libratbag # or create a bug report on https://github.com/libratbag/libratbag/issues and # include your new rules, a description of the device, and the output of # udevadm info /dev/input/eventXX # (or /dev/input/event*). # # Allowed properties are: # RATBAG_DRIVER # RATBAG_SVG # Sort by brand, driver ########################################## # Etekcity ########################################## mouse:usb:v1ea7p4011:name:*: RATBAG_DRIVER=etekcity RATBAG_SVG=etekcity.svg ########################################## # Logitech ########################################## # M705 mouse:usb:v046dp101b:name:*: RATBAG_DRIVER=hidpp10 # M570 mouse:usb:v046dp1028:name:*: RATBAG_DRIVER=hidpp10 # G5 mouse:usb:v046dpc041:name:*: RATBAG_DRIVER=hidpp10 RATBAG_HIDPP10_DPI_LIST=400;800;1600;2000 # G5 2007 mouse:usb:v046dpc049:name:*: RATBAG_DRIVER=hidpp10 RATBAG_HIDPP10_DPI_LIST=400;800;1600;2000 # G7 mouse:usb:v046dpc51a:name:*: RATBAG_DRIVER=hidpp10 # G9 mouse:usb:v046dpc048:name:*: RATBAG_DRIVER=hidpp10 RATBAG_HIDPP10_DPI_LIST=0;200;400;600;800;1000;1200;1400;1600;1800;2000;2200;2400;2600;2800;3000;3200 RATBAG_HIDPP10_PROFILE=G9 # G9x [Original] mouse:usb:v046dpc066:name:*: RATBAG_DRIVER=hidpp10 RATBAG_HIDPP10_DPI=0:5700@23.53 RATBAG_HIDPP10_PROFILE=G500 # G9x [Call of Duty MW3 Edition] mouse:usb:v046dpc249:name:*: RATBAG_DRIVER=hidpp10 RATBAG_HIDPP10_DPI=0:5700@23.53 RATBAG_HIDPP10_PROFILE=G500 # G300 mouse:usb:v046dpc246:name:*: RATBAG_DRIVER=logitech_g300 RATBAG_SVG=logitech-g300.svg # G500 mouse:usb:v046dpc068:name:*: RATBAG_DRIVER=hidpp10 RATBAG_HIDPP10_DPI=0:5700@23.53 RATBAG_HIDPP10_PROFILE=G500 # G500s mouse:usb:v046dpc24e:name:*: RATBAG_DRIVER=hidpp10 RATBAG_SVG=logitech-g500s.svg RATBAG_HIDPP10_DPI=0:8200@50 RATBAG_HIDPP10_PROFILE=G500 # G502 Proteus Core over USB mouse:usb:v046dpc07d:name:*: RATBAG_DRIVER=hidpp20 RATBAG_SVG=logitech-g502.svg # G502 Proteus Spectrum over USB mouse:usb:v046dpc332:name:*: RATBAG_DRIVER=hidpp20 RATBAG_SVG=logitech-g502.svg # G700 over USB mouse:usb:v046dpc06b:name:*: RATBAG_DRIVER=hidpp10 RATBAG_SVG=logitech-g700.svg RATBAG_HIDPP10_DPI=0:5700@23.53 RATBAG_HIDPP10_PROFILE=G700 # G700 over wireless USB mouse:usb:v046dpc531:name:*: RATBAG_DRIVER=hidpp10 RATBAG_HIDPP10_DPI=0:5700@23.53 RATBAG_HIDPP10_PROFILE=G700 RATBAG_HIDPP10_INDEX=1 # G700s over USB mouse:usb:v046dpc07c:name:*: RATBAG_DRIVER=hidpp10 RATBAG_SVG=logitech-g700.svg RATBAG_HIDPP10_DPI=0:8200@50 RATBAG_HIDPP10_PROFILE=G700 # G700s over wireless USB mouse:usb:v046dpc531:name:*: RATBAG_DRIVER=hidpp10 RATBAG_SVG=logitech-g700.svg RATBAG_HIDPP10_DPI=0:8200@50 RATBAG_HIDPP10_PROFILE=G700 RATBAG_HIDPP10_INDEX=1 # MX Master over unifying mouse:usb:v046dp4041:name:*: # MX Master over bluetooth mouse:bluetooth:v046dpb012:name:*: RATBAG_DRIVER=hidpp20 RATBAG_SVG=logitech-mx_master.svg # T650 over unifying mouse:usb:v046dp4101:name:*: RATBAG_DRIVER=hidpp20 # M325 over unifying mouse:usb:v046dp400a:name:*: RATBAG_DRIVER=hidpp20 # G303 over USB mouse:usb:v046dpc080:name:*: RATBAG_DRIVER=hidpp20 RATBAG_SVG=logitech-g303.svg # Wireless Touchpad (Unifying) mouse:usb:v046dp4011:name:*: RATBAG_DRIVER=hidpp20 # G602 over wireless USB mouse:usb:v046dpc537:name:*: RATBAG_HIDPP20_INDEX=1 # G900 over USB mouse:usb:v046dpc081:name:*: RATBAG_SVG=logitech-g900.svg # G900 over wireless USB mouse:usb:v046dpc539:name:*: RATBAG_DRIVER=hidpp20 RATBAG_SVG=logitech-g900.svg RATBAG_HIDPP20_INDEX=1 ########################################## # Roccat ########################################## mouse:usb:v1e7dp2e22:name:*: RATBAG_DRIVER=roccat RATBAG_SVG=roccat-kone-xtd.svg ########################################## # G.Skill ########################################## # MX-780 mouse:usb:v28dap3101:name:*: RATBAG_DRIVER=gskill libratbag-0.9/hwdb/70-libratbag-mouse.rules000066400000000000000000000021501311552661500206150ustar00rootroot00000000000000# do not edit this file, it will be overwritten on update ACTION=="remove", GOTO="mouse_end" KERNEL!="event*", GOTO="mouse_end" ENV{ID_INPUT}=="", GOTO="mouse_end" # mouse::vp:name::* KERNELS=="input*", ENV{ID_BUS}=="usb", \ IMPORT{builtin}="hwdb 'mouse:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \ GOTO="mouse_end" KERNELS=="input*", ENV{ID_BUS}=="bluetooth", \ IMPORT{builtin}="hwdb 'mouse:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \ GOTO="mouse_end" KERNELS=="input*", ENV{DEVPATH}=="/devices/virtual/misc/uhid/0003:*", \ IMPORT{builtin}="hwdb 'mouse:usb:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \ GOTO="mouse_end" KERNELS=="input*", ENV{DEVPATH}=="/devices/virtual/misc/uhid/0005:*", \ IMPORT{builtin}="hwdb 'mouse:bluetooth:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \ GOTO="mouse_end" DRIVERS=="psmouse", SUBSYSTEMS=="serio", \ IMPORT{builtin}="hwdb 'mouse:ps2::name:$attr{device/name}:'", \ GOTO="mouse_end" LABEL="mouse_end" libratbag-0.9/meson.build000066400000000000000000000263221311552661500154650ustar00rootroot00000000000000project('libratbag', 'c', 'cpp', version : '0.9.0', license : 'MIT/Expat', default_options : [ 'c_std=gnu99', 'warning_level=2' ]) libratbag_version = meson.project_version().split('.') # 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 libratbag_so_c=4 libratbag_so_r=3 libratbag_so_a=0 liblur_so_c=3 liblur_so_r=3 liblur_so_a=0 # convert to sonames libratbag_so_version = '@0@.@1@.@2@'.format((libratbag_so_c - libratbag_so_a), libratbag_so_a, libratbag_so_r) 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') cpp = meson.get_compiler('cpp') cppflags = ['-Wno-unused-parameter', '-fvisibility=hidden'] cflags = cppflags + ['-Wmissing-prototypes', '-Wstrict-prototypes'] add_project_arguments(cflags, language: 'c') add_project_arguments(cppflags, language: 'cpp') # 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') # dependencies pkgconfig = import('pkgconfig') dep_udev = dependency('libudev') dep_libevdev = dependency('libevdev') dep_lm = cc.find_library('m') #### libutil.a #### src_libutil = [ 'src/libratbag-util.c', 'src/libratbag-util.h' ] deps_libutil = [ dep_udev, ] lib_libutil = static_library('util', src_libutil, dependencies : deps_libutil ) dep_libutil = declare_dependency(link_with: lib_libutil) ### libhidpp.a #### src_libhidpp = [ 'src/hidpp-generic.h', 'src/hidpp-generic.c', 'src/hidpp10.h', 'src/hidpp10.c', 'src/hidpp20.h', 'src/hidpp20.c', 'src/usb-ids.h' ] deps_libhidpp = [ dep_lm ] lib_libhidpp = static_library('hidpp', src_libhidpp, dependencies : deps_libhidpp) dep_libhidpp = declare_dependency(link_with: lib_libhidpp) ### liblur.a #### install_headers('src/liblur.h') src_liblur = [ 'src/liblur.c', 'src/liblur.h' ] deps_liblur = [ dep_libutil, dep_libhidpp, ] lur_mapfile = 'src/liblur.sym' lur_version_flag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), lur_mapfile) lib_liblur = shared_library('lur', src_liblur, include_directories : include_directories('.'), dependencies : deps_liblur, version : liblur_so_version, link_args : lur_version_flag, link_depends : lur_mapfile, install : true, ) dep_liblur = declare_dependency(link_with: lib_liblur) pkgconfig.generate ( filebase: 'liblur', name: 'Liblur', description: 'Logitech Unifying Receiver configuration library', version: meson.project_version(), libraries: lib_liblur ) #### libratbag.so #### install_headers('src/libratbag.h') src_libratbag = [ 'src/driver-etekcity.c', 'src/driver-hidpp20.c', 'src/driver-hidpp10.c', 'src/driver-logitech-g300.c', 'src/driver-roccat.c', 'src/driver-gskill.c', 'src/driver-test.c', 'src/libratbag.c', 'src/libratbag.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_libutil, dep_libhidpp, ] libratbag_version_h_config = configuration_data() libratbag_version_h_config.set('LIBRATBAG_VERSION_MAJOR', libratbag_version[0]) libratbag_version_h_config.set('LIBRATBAG_VERSION_MINOR', libratbag_version[1]) libratbag_version_h_config.set('LIBRATBAG_VERSION_MICRO', libratbag_version[2]) libratbag_version_h_config.set_quoted('LIBRATBAG_VERSION', meson.project_version()) libratbag_version_h = configure_file( output : 'libratbag-version.h', configuration : libratbag_version_h_config, install: false, ) mapfile = 'src/libratbag.sym' version_flag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile) lib_libratbag = shared_library('ratbag', src_libratbag, include_directories : include_directories('.'), dependencies : deps_libratbag, version : libratbag_so_version, link_args : version_flag, link_depends : mapfile, install : true, ) dep_libratbag = declare_dependency( link_with : lib_libratbag, dependencies : deps_libratbag ) pkgconfigdata = configuration_data() pkgconfigdata.set('prefix', get_option('prefix')) pkgconfigdata.set('libdir', get_option('libdir')) pkgconfigdata.set('datadir', get_option('datadir')) pkgconfigdata.set('includedir', get_option('includedir')) pkgconfigdata.set('LIBRATBAG_VERSION', meson.project_version()) pkgconfigfile = configure_file( input : 'src/libratbag.pc.in', output : 'libratbag.pc', configuration : pkgconfigdata, install : true, install_dir : get_option('libdir') + '/pkgconfig' ) #### 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) #### ratbag-command #### src_ratbag_command = [ 'tools/ratbag-command.c' ] deps_ratbag_command = [ dep_libshared, dep_libratbag, dep_libevdev ] executable('ratbag-command', src_ratbag_command, dependencies : deps_ratbag_command, include_directories : include_directories('src'), install : true, ) man_config = configuration_data() man_config.set('version', meson.project_version()) man_ratbag_command = configure_file ( input: 'tools/ratbag-command.man', output: 'ratbag-command.1', configuration: man_config, install : true, install_dir : join_paths(get_option('mandir'), 'man1') ) #### 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, ) #### lur-command #### 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_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') ) #### hwdb #### udev_dir = get_option('udev-dir') if udev_dir == '' udev_dir = '@0@/lib/udev'.format(get_option('prefix')) endif udev_rules_dir = '@0@/rules.d'.format(udev_dir) udev_hwdb_dir = '@0@/hwdb.d'.format(udev_dir) install_data('hwdb/70-libratbag-mouse.rules', install_dir : udev_rules_dir) install_data('hwdb/70-libratbag-mouse.hwdb', install_dir : udev_hwdb_dir) #### svg files #### svg_files = [ 'data/etekcity.svg', 'data/logitech-g300.svg', 'data/logitech-g303.svg', 'data/logitech-g500s.svg', 'data/logitech-g502.svg', 'data/logitech-g700.svg', 'data/logitech-g900.svg', 'data/logitech-mx_master.svg', 'data/roccat-kone-xtd.svg' ] install_data(svg_files, install_dir : '@0@/libratbag'.format(get_option('datadir'))) #### tests #### enable_tests = get_option('enable-tests') if enable_tests dep_check = dependency('check', version: '>= 0.9.10') config_h.set('BUILD_TESTS', '1') env_test = ['RATBAG_TEST=1'] valgrind_args = [ '--leak-check=full', '--quiet', '--error-exitcode=3', '--suppressions=@0@/test/valgrind.suppressions'.format(meson.current_source_dir()) ] 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_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, env : env_test, valgrind_args : valgrind_args) test('test-device', test_device, env : env_test, valgrind_args : valgrind_args) test('test-iconv-helper', test_iconv_helper, env : env_test, valgrind_args : valgrind_args) executable('test-build-cxx', ['test/build-cxx.cc'], dependencies : [ dep_libratbag ], include_directories : include_directories('src'), cpp_args : ['-Wall', '-Wextra', '-Wno-unused-parameter'], install : false) executable('test-build-linker', ['test/build-pedantic.c'], dependencies : [ dep_libratbag ], include_directories : include_directories('src'), install : false) executable('test-build-gnuc90', ['test/build-pedantic.c'], dependencies : [ dep_libratbag ], include_directories : include_directories('src'), c_args : ['-std=gnu90', '-Werror'], install : false) executable('test-build-pedantic', ['test/build-pedantic.c'], dependencies : [ dep_libratbag ], include_directories : include_directories('src'), c_args : ['-std=c99', '-pedantic', '-Werror'], install : false) endif test_symbols_leak = find_program('test/symbols-leak-test') test('symbols-leak-test', test_symbols_leak, args : [ meson.current_source_dir() ]) #### documentation #### enable_doc = get_option('enable-documentation') if enable_doc cmd_doxygen = find_program('doxygen') cmd_dot = find_program('dot') v = run_command(cmd_doxygen, ['--version']).stdout() if not v.version_compare('>=1.8.3') error('Doxygen $doxygen_version too old. ' + 'Doxygen 1.8.3+ required for documentation build. ' + 'Install required doxygen version or ' + 'disable the documentation using --disable-documentation') endif v = run_command(cmd_dot, ['-V']).stderr() v = v.split(' ')[4] if v.version_compare('<2.26.0') error('Graphviz dot $dot_version too old. ' + 'Graphviz 2.26+ required for documentation build. ' + 'Install required graphviz version or ' + 'disable the documentation using --disable-documentation') endif doc_config = configuration_data() doc_config.set('PACKAGE_NAME', meson.project_name()) doc_config.set('PACKAGE_VERSION', meson.project_version()) doc_config.set('top_srcdir', meson.source_root()) doxyfile = configure_file(input : 'doc/libratbag.doxygen.in', output : 'libratbag.doxygen', configuration : doc_config, install : false) src_doxygen = [ doxyfile, 'src/libratbag.h', 'README.md'] custom_target('doxygen', input : src_doxygen, output : [ 'html' ], command : [ cmd_doxygen, doxyfile ], install : false, build_by_default : true) endif #### output files #### configure_file(output: 'config.h', install: false, configuration: config_h) libratbag-0.9/meson_options.txt000066400000000000000000000005421311552661500167540ustar00rootroot00000000000000option('udev-dir', type: 'string', default: '', description: 'udev base directory [default=$prefix/lib/udev]') option('enable-tests', type: 'boolean', value: true, description: 'Build the tests (default=yes)') option('enable-documentation', type: 'boolean', value: true, description: 'Build the documentation (default=yes)') libratbag-0.9/release.sh000077500000000000000000000404751311552661500153070ustar00rootroot00000000000000#!/bin/bash # # Generate the announce template # # Completely copy/paste of Xorg/util/modular/release.sh: # # Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved. # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice (including the next # paragraph) shall be included in all copies or substantial portions of the # Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. export LC_ALL=C #------------------------------------------------------------------------------ # Function: check_local_changes #------------------------------------------------------------------------------ # check_local_changes() { git diff --quiet HEAD > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "" echo "Uncommitted changes found. Did you forget to commit? Aborting." echo "" echo "You can perform a 'git stash' to save your local changes and" echo "a 'git stash apply' to recover them after the tarball release." echo "Make sure to rebuild and run 'make distcheck' again." echo "" echo "Alternatively, you can clone the module in another directory" echo "and run ./configure. No need to build if testing was finished." echo "" return 1 fi return 0 } #------------------------------------------------------------------------------ # Function: check_option_args #------------------------------------------------------------------------------ # # perform sanity checks on cmdline args which require arguments # arguments: # $1 - the option being examined # $2 - the argument to the option # returns: # if it returns, everything is good # otherwise it exit's check_option_args() { option=$1 arg=$2 # check for an argument if [ x"$arg" = x ]; then echo "" echo "Error: the '$option' option is missing its required argument." echo "" usage exit 1 fi # does the argument look like an option? echo $arg | $GREP "^-" > /dev/null if [ $? -eq 0 ]; then echo "" echo "Error: the argument '$arg' of option '$option' looks like an option itself." echo "" usage exit 1 fi } #------------------------------------------------------------------------------ # Function: check_modules_specification #------------------------------------------------------------------------------ # check_modules_specification() { if [ x"$MODFILE" = x ]; then if [ x"${INPUT_MODULES}" = x ]; then echo "" echo "Error: no modules specified (blank command line)." usage exit 1 fi 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 < build-script.sh # sh ./build-script.sh sudo apt-get update sudo apt-get install -y valgrind check libevdev-dev libudev-dev doxygen graphviz # no meson on 14.04 sudo apt-get install -y python3-pip sudo pip3 install meson # ninja on 14.04 is too old git clone git://github.com/ninja-build/ninja.git cd ninja git checkout release ./configure.py --bootstrap sudo cp ninja /usr/bin/ninja cd .. meson builddir ninja -C builddir test libratbag-0.9/src/000077500000000000000000000000001311552661500141055ustar00rootroot00000000000000libratbag-0.9/src/driver-etekcity.c000066400000000000000000000501211311552661500173620ustar00rootroot00000000000000/* * 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_type_mapping { uint8_t raw; enum ratbag_button_type type; }; static const struct etekcity_button_type_mapping etekcity_button_type_mapping[] = { { 0, RATBAG_BUTTON_TYPE_LEFT }, { 1, RATBAG_BUTTON_TYPE_RIGHT }, { 2, RATBAG_BUTTON_TYPE_MIDDLE }, { 3, RATBAG_BUTTON_TYPE_EXTRA }, { 4, RATBAG_BUTTON_TYPE_SIDE }, { 5, RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP }, { 6, RATBAG_BUTTON_TYPE_PINKIE }, { 7, RATBAG_BUTTON_TYPE_PINKIE2 }, { 8, RATBAG_BUTTON_TYPE_WHEEL_UP }, { 9, RATBAG_BUTTON_TYPE_WHEEL_DOWN }, }; static enum ratbag_button_type etekcity_raw_to_button_type(uint8_t data) { const struct etekcity_button_type_mapping *mapping; ARRAY_FOR_EACH(etekcity_button_type_mapping, mapping) { if (mapping->raw == data) return mapping->type; } return RATBAG_BUTTON_TYPE_UNKNOWN; } struct etekcity_button_mapping { uint8_t raw; struct ratbag_button_action action; }; static struct etekcity_button_mapping etekcity_button_mapping[] = { { 1, BUTTON_ACTION_BUTTON(1) }, { 2, BUTTON_ACTION_BUTTON(2) }, { 3, BUTTON_ACTION_BUTTON(3) }, { 4, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_DOUBLECLICK) }, { 6, BUTTON_ACTION_NONE }, { 7, BUTTON_ACTION_BUTTON(4) }, { 8, BUTTON_ACTION_BUTTON(5) }, { 9, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP) }, { 10, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN) }, { 11, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT) }, { 12, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT) }, { 13, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) }, { 14, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) }, { 15, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) }, { 16, BUTTON_ACTION_MACRO }, { 18, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) }, { 19, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP) }, { 20, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN) }, { 25, BUTTON_ACTION_KEY(KEY_CONFIG) }, { 26, BUTTON_ACTION_KEY(KEY_PREVIOUSSONG) }, { 27, BUTTON_ACTION_KEY(KEY_NEXTSONG) }, { 28, BUTTON_ACTION_KEY(KEY_PLAYPAUSE) }, { 29, BUTTON_ACTION_KEY(KEY_STOPCD) }, { 30, BUTTON_ACTION_KEY(KEY_MUTE) }, { 31, BUTTON_ACTION_KEY(KEY_VOLUMEUP) }, { 32, BUTTON_ACTION_KEY(KEY_VOLUMEDOWN) }, { 33, BUTTON_ACTION_KEY(KEY_CALC) }, { 34, BUTTON_ACTION_KEY(KEY_MAIL) }, { 35, BUTTON_ACTION_KEY(KEY_BOOKMARKS) }, { 36, BUTTON_ACTION_KEY(KEY_FORWARD) }, { 37, BUTTON_ACTION_KEY(KEY_BACK) }, { 38, BUTTON_ACTION_KEY(KEY_STOP) }, { 39, BUTTON_ACTION_KEY(KEY_FILE) }, { 40, BUTTON_ACTION_KEY(KEY_REFRESH) }, { 41, BUTTON_ACTION_KEY(KEY_HOMEPAGE) }, { 42, BUTTON_ACTION_KEY(KEY_SEARCH) }, }; static const struct ratbag_button_action* etekcity_raw_to_button_action(uint8_t data) { struct etekcity_button_mapping *mapping; ARRAY_FOR_EACH(etekcity_button_mapping, mapping) { if (mapping->raw == data) return &mapping->action; } return NULL; } static uint8_t etekcity_button_action_to_raw(const struct ratbag_button_action *action) { struct etekcity_button_mapping *mapping; ARRAY_FOR_EACH(etekcity_button_mapping, mapping) { if (ratbag_button_action_match(&mapping->action, action)) return mapping->raw; } return 0; } static int etekcity_current_profile(struct ratbag_device *device) { uint8_t buf[3]; int ret; ret = ratbag_hidraw_raw_request(device, ETEKCITY_REPORT_ID_PROFILE, buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (ret < 0) return ret; if (ret != 3) return -EIO; return buf[2]; } static int etekcity_set_current_profile(struct ratbag_device *device, unsigned int index) { uint8_t buf[] = {ETEKCITY_REPORT_ID_PROFILE, 0x03, index}; int ret; if (index > ETEKCITY_PROFILE_MAX) return -EINVAL; ret = ratbag_hidraw_raw_request(device, buf[0], buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); msleep(100); return ret == sizeof(buf) ? 0 : ret; } static int etekcity_set_config_profile(struct ratbag_device *device, uint8_t profile, uint8_t type) { uint8_t buf[] = {ETEKCITY_REPORT_ID_CONFIGURE_PROFILE, profile, type}; int ret; if (profile > ETEKCITY_PROFILE_MAX) return -EINVAL; ret = ratbag_hidraw_raw_request(device, buf[0], buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); msleep(100); return ret == sizeof(buf) ? 0 : ret; } static inline unsigned etekcity_button_to_index(unsigned button) { return button < 8 ? button : button + 5; } static const struct ratbag_button_action * etekcity_button_to_action(struct ratbag_profile *profile, unsigned int button_index) { struct ratbag_device *device = profile->device; struct etekcity_data *drv_data = ratbag_get_drv_data(device); uint8_t data; unsigned raw_index = etekcity_button_to_index(button_index); data = drv_data->profiles[profile->index][3 + raw_index * 3]; log_raw(device->ratbag, " - button%d: %s (%02x) %s:%d\n", button_index, print_key(data), data, __FILE__, __LINE__); return etekcity_raw_to_button_action(data); } static void etekcity_read_profile(struct ratbag_profile *profile, unsigned int index) { struct ratbag_device *device = profile->device; struct etekcity_data *drv_data; struct ratbag_resolution *resolution; struct etekcity_settings_report *setting_report; uint8_t *buf; unsigned int report_rate; unsigned int i; int dpi_x, dpi_y, hz; int rc; assert(index <= ETEKCITY_PROFILE_MAX); drv_data = ratbag_get_drv_data(device); setting_report = &drv_data->settings[index]; buf = (uint8_t*)setting_report; etekcity_set_config_profile(device, 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 */ switch (setting_report->report_rate) { case 0x00: report_rate = 125; break; case 0x01: report_rate = 250; break; case 0x02: report_rate = 500; break; case 0x03: report_rate = 1000; break; default: log_error(device->ratbag, "error while reading the report rate of the mouse (0x%02x)\n", buf[26]); report_rate = 0; } for (i = 0; i < profile->resolution.num_modes; i++) { dpi_x = setting_report->xres[i] * 50; dpi_y = setting_report->yres[i] * 50; hz = report_rate; if (!(setting_report->dpi_mask & (1 << i))) { /* the profile is disabled, overwrite it */ dpi_x = 0; dpi_y = 0; hz = 0; } resolution = ratbag_resolution_init(profile, i, dpi_x, dpi_y, hz); ratbag_resolution_set_cap(resolution, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION); resolution->is_active = (i == setting_report->current_dpi); } 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_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_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) button->action = *action; button->type = etekcity_raw_to_button_type(button->index); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); if (action && action->type == RATBAG_BUTTON_ACTION_TYPE_MACRO) { device = button->profile->device; drv_data = ratbag_get_drv_data(device); etekcity_set_config_profile(device, button->profile->index, button->index); macro = &drv_data->macros[button->profile->index][button->index]; buf = (uint8_t*)macro; rc = ratbag_hidraw_raw_request(device, ETEKCITY_REPORT_ID_MACRO, buf, ETEKCITY_REPORT_SIZE_MACRO, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (rc != ETEKCITY_REPORT_SIZE_MACRO) { log_error(device->ratbag, "Unable to retrieve the macro for button %d of profile %d: %s (%d)\n", button->index, button->profile->index, rc < 0 ? strerror(-rc) : "not read enough", rc); } else { m = ratbag_button_macro_new(macro->name); log_raw(device->ratbag, "macro on button %d of profile %d is named '%s', and contains %d events:\n", button->index, button->profile->index, macro->name, macro->length); for (j = 0; j < macro->length; j++) { unsigned int keycode = ratbag_hidraw_get_keycode_from_keyboard_usage(device, macro->keys[j].keycode); ratbag_button_macro_set_event(m, j, macro->keys[j].flag & 0x80 ? RATBAG_MACRO_EVENT_KEY_RELEASED : RATBAG_MACRO_EVENT_KEY_PRESSED, keycode); log_raw(device->ratbag, " - %s %s\n", libevdev_event_code_get_name(EV_KEY, keycode), macro->keys[j].flag & 0x80 ? "released" : "pressed"); } ratbag_button_copy_macro(button, m); ratbag_button_macro_unref(m); } msleep(10); } } static int etekcity_write_macro(struct ratbag_button *button, const struct ratbag_button_action *action) { struct ratbag_device *device; struct etekcity_macro *macro; struct etekcity_data *drv_data; uint8_t *buf; unsigned i, count = 0; int rc; if (action->type != RATBAG_BUTTON_ACTION_TYPE_MACRO) return 0; device = button->profile->device; drv_data = ratbag_get_drv_data(device); macro = &drv_data->macros[button->profile->index][button->index]; buf = (uint8_t*)macro; for (i = 0; i < MAX_MACRO_EVENTS && count < ETEKCITY_MAX_MACRO_LENGTH; i++) { if (action->macro->events[i].type == RATBAG_MACRO_EVENT_INVALID) return -EINVAL; /* should not happen, ever */ if (action->macro->events[i].type == RATBAG_MACRO_EVENT_NONE) break; /* ignore timeout events */ if (action->macro->events[i].type == RATBAG_MACRO_EVENT_WAIT) continue; macro->keys[count].keycode = ratbag_hidraw_get_keyboard_usage_from_keycode(device, action->macro->events[i].event.key); if (action->macro->events[i].type == RATBAG_MACRO_EVENT_KEY_PRESSED) macro->keys[count].flag = 0x00; else macro->keys[count].flag = 0x80; count++; } macro->reportID = ETEKCITY_REPORT_ID_MACRO; macro->heightytwo = 0x82; macro->profile = button->profile->index; macro->button_index = button->index; macro->active = 0x01; strncpy(macro->name, action->macro->name, 23); macro->length = count; etekcity_set_config_profile(device, button->profile->index, button->index); rc = ratbag_hidraw_raw_request(device, ETEKCITY_REPORT_ID_MACRO, buf, ETEKCITY_REPORT_SIZE_MACRO, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc < 0) return rc; return rc == ETEKCITY_REPORT_SIZE_MACRO ? 0 : -EIO; } static int etekcity_write_button(struct ratbag_button *button, const struct ratbag_button_action *action) { struct ratbag_profile *profile = button->profile; struct ratbag_device *device = profile->device; struct etekcity_data *drv_data = ratbag_get_drv_data(device); uint8_t rc, *data; unsigned index = etekcity_button_to_index(button->index); data = &drv_data->profiles[profile->index][3 + index * 3]; rc = etekcity_button_action_to_raw(action); if (!rc) return -EINVAL; *data = rc; rc = etekcity_write_profile(button->profile); if (rc) { log_error(device->ratbag, "unable to write the profile to the device: '%s' (%d)\n", strerror(-rc), rc); return rc; } rc = etekcity_write_macro(button, action); if (rc) { log_error(device->ratbag, "unable to write the macro to the device: '%s' (%d)\n", strerror(-rc), rc); return rc; } return rc; } static int etekcity_write_resolution_dpi(struct ratbag_resolution *resolution, int dpi_x, int dpi_y) { struct ratbag_profile *profile = resolution->profile; struct ratbag_device *device = profile->device; struct etekcity_data *drv_data = ratbag_get_drv_data(device); struct etekcity_settings_report *settings_report; unsigned int index; uint8_t *buf; int rc; if (dpi_x < 50 || dpi_x > 8200 || dpi_x % 50) return -EINVAL; if (dpi_y < 50 || dpi_y > 8200 || dpi_y % 50) return -EINVAL; settings_report = &drv_data->settings[profile->index]; /* retrieve which resolution is asked to be changed */ index = resolution - profile->resolution.modes; settings_report->x_sensitivity = 0x0a; settings_report->y_sensitivity = 0x0a; settings_report->xres[index] = dpi_x / 50; settings_report->yres[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); 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_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_MACROS); active_idx = etekcity_current_profile(device); if (active_idx < 0) { log_error(device->ratbag, "Can't talk to the mouse: '%s' (%d)\n", strerror(-active_idx), active_idx); rc = -ENODEV; goto err; } list_for_each(profile, &device->profiles, link) { if (profile->index == (unsigned int)active_idx) { profile->is_active = true; break; } } log_raw(device->ratbag, "'%s' is in profile %d\n", ratbag_device_get_name(device), profile->index); return 0; err: free(drv_data); ratbag_set_drv_data(device, NULL); return rc; } static void etekcity_remove(struct ratbag_device *device) { ratbag_close_hidraw(device); free(ratbag_get_drv_data(device)); } struct ratbag_driver etekcity_driver = { .name = "EtekCity", .id = "etekcity", .probe = etekcity_probe, .remove = etekcity_remove, .read_profile = etekcity_read_profile, .write_profile = etekcity_write_profile, .set_active_profile = etekcity_set_current_profile, .read_button = etekcity_read_button, .write_button = etekcity_write_button, .write_resolution_dpi = etekcity_write_resolution_dpi, }; libratbag-0.9/src/driver-gskill.c000066400000000000000000001201241311552661500170270ustar00rootroot00000000000000/* * Copyright © 2016 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include "libratbag-private.h" #include "libratbag-hidraw.h" #include #include #define GSKILL_PROFILE_MAX 5 #define GSKILL_NUM_DPI 5 #define GSKILL_BUTTON_MAX 10 #define GSKILL_NUM_LED 0 #define GSKILL_MAX_POLLING_RATE 1000 #define GSKILL_MIN_DPI 100 #define GSKILL_MAX_DPI 8200 #define GSKILL_DPI_UNIT 50 /* Commands */ #define GSKILL_GET_CURRENT_PROFILE_NUM 0x03 #define GSKILL_GET_SET_MACRO 0x04 #define GSKILL_GET_SET_PROFILE 0x05 #define GSKILL_GENERAL_CMD 0x0c #define GSKILL_REPORT_SIZE_PROFILE 644 #define GSKILL_REPORT_SIZE_CMD 9 #define GSKILL_REPORT_SIZE_MACRO 2052 #define GSKILL_CHECKSUM_OFFSET 3 /* Command status codes */ #define GSKILL_CMD_SUCCESS 0xb0 #define GSKILL_CMD_IN_PROGRESS 0xb1 #define GSKILL_CMD_FAILURE 0xb2 #define GSKILL_CMD_IDLE 0xb3 /* LED groups. DPI is omitted here since it's handled specially */ #define GSKILL_LED_TYPE_LOGO 0 #define GSKILL_LED_TYPE_WHEEL 1 #define GSKILL_LED_TYPE_TAIL 2 #define GSKILL_LED_TYPE_COUNT 3 #define GSKILL_KBD_MOD_CTRL_LEFT AS_MASK(0) #define GSKILL_KBD_MOD_SHIFT_LEFT AS_MASK(1) #define GSKILL_KBD_MOD_ALT_LEFT AS_MASK(2) #define GSKILL_KBD_MOD_SUPER_LEFT AS_MASK(3) #define GSKILL_KBD_MOD_CTRL_RIGHT AS_MASK(4) #define GSKILL_KBD_MOD_SHIFT_RIGHT AS_MASK(5) #define GSKILL_KBD_MOD_ALT_RIGHT AS_MASK(6) #define GSKILL_KBD_MOD_SUPER_RIGHT AS_MASK(7) struct gskill_raw_dpi_level { uint8_t x; uint8_t y; } __attribute__((packed)); struct gskill_led_color { uint8_t red; uint8_t green; uint8_t blue; } __attribute__((packed)); struct gskill_led_values { uint8_t brightness; struct gskill_led_color color; } __attribute__((packed)); enum gskill_led_control_type { GSKILL_LED_ALL_OFF = 0x0, GSKILL_LED_ALL_ON = 0x1, GSKILL_LED_BREATHING = 0x2, GSKILL_DPI_LED_RIGHT_CYCLE = 0x3, GSKILL_DPI_LED_LEFT_CYCLE = 0x4 }; struct gskill_background_led_cfg { uint8_t brightness; struct gskill_led_color dpi[4]; struct gskill_led_color leds[GSKILL_LED_TYPE_COUNT]; } __attribute__((packed)); struct gskill_dpi_led_group_cfg { uint8_t duration_step; uint8_t duration_high; uint8_t duration_low; uint8_t cycle_num; struct gskill_led_values steps[12]; }; struct gskill_led_group_cfg { enum gskill_led_control_type type :3; uint8_t :5; /* unused */ uint8_t duration_step; uint8_t duration_high; uint8_t duration_low; uint8_t cycle_num; struct gskill_led_values steps[12]; } __attribute__((packed)); struct gskill_dpi_led_cycle_cfg { enum gskill_led_control_type type :3; uint8_t :5; /* unused */ /* Don't worry, the low/high flip-flop here is intentional */ uint8_t duration_low; uint8_t duration_high; uint8_t cycle_num; struct gskill_led_values cycles[12]; } __attribute__((packed)); /* * We may occasionally run into codes outside this, however those codes * indicate functionalities that aren't too useful for us */ enum gskill_button_function_type { GSKILL_BUTTON_FUNCTION_WHEEL = 0x00, GSKILL_BUTTON_FUNCTION_MOUSE = 0x01, GSKILL_BUTTON_FUNCTION_KBD = 0x02, GSKILL_BUTTON_FUNCTION_CONSUMER = 0x03, GSKILL_BUTTON_FUNCTION_MACRO = 0x06, GSKILL_BUTTON_FUNCTION_DPI_UP = 0x09, GSKILL_BUTTON_FUNCTION_DPI_DOWN = 0x0a, GSKILL_BUTTON_FUNCTION_CYCLE_DPI_UP = 0x0b, GSKILL_BUTTON_FUNCTION_CYCLE_DPI_DOWN = 0x0c, GSKILL_BUTTON_FUNCTION_PROFILE_SWITCH = 0x0d, GSKILL_BUTTON_FUNCTION_TEMPORARY_CPI_ADJUST = 0x15, GSKILL_BUTTON_FUNCTION_DIRECT_DPI_CHANGE = 0x16, GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_UP = 0x18, GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_DOWN = 0x19, GSKILL_BUTTON_FUNCTION_DISABLE = 0xff }; struct gskill_button_cfg { enum gskill_button_function_type type :8; union { struct { enum { GSKILL_WHEEL_SCROLL_UP = 0, GSKILL_WHEEL_SCROLL_DOWN = 1, } direction :8; } wheel; struct { enum { GSKILL_BTN_MASK_LEFT = AS_MASK(0), GSKILL_BTN_MASK_RIGHT = AS_MASK(1), GSKILL_BTN_MASK_MIDDLE = AS_MASK(2), GSKILL_BTN_MASK_SIDE = AS_MASK(3), GSKILL_BTN_MASK_EXTRA = AS_MASK(4) } button_mask :8; } mouse; struct { uint16_t code; } consumer; struct { uint8_t modifier_mask; uint8_t hid_code; /* * XXX: Supposedly this is supposed to have additional * parts of the kbd code, however that doesn't seem to * be the case in practice… */ uint16_t :16; } kbd; struct { uint8_t level; } dpi; } params; } __attribute__((packed)); struct gskill_action_mapping { struct gskill_button_cfg config; struct ratbag_button_action action; }; struct gskill_profile_report { uint16_t :16; uint8_t profile_num; uint8_t checksum; uint8_t polling_rate :4; uint8_t angle_snap_ratio :4; uint8_t liftoff_value :5; bool liftoff_enabled :1; bool disable_leds_in_sleep:1; enum { GSKILL_LED_PROFILE_MODE_BACKGROUND = 0, GSKILL_LED_PROFILE_MODE_OTHER = 1, } led_profile_mode :1; uint8_t :8; /* unused */ uint8_t current_dpi_level :4; uint8_t dpi_num :4; struct gskill_raw_dpi_level dpi_levels[GSKILL_NUM_DPI]; /* LEDs */ struct gskill_background_led_cfg background_lighting; struct gskill_dpi_led_cycle_cfg led_dpi_cycle; struct gskill_dpi_led_group_cfg dpi_led; struct gskill_led_group_cfg leds[GSKILL_LED_TYPE_COUNT]; /* Button assignments */ uint8_t button_function_redirections[8]; struct gskill_button_cfg btn_cfgs[GSKILL_BUTTON_MAX]; /* A mystery */ uint8_t _unused1[27]; char name[256]; } __attribute__((packed)); _Static_assert(sizeof(struct gskill_profile_report) == GSKILL_REPORT_SIZE_PROFILE, "Size of gskill_profile_report is wrong"); struct gskill_macro_delay { uint8_t tag; /* should be 0x1 to indicate delay */ uint16_t count; } __attribute__((packed)); struct gskill_macro_report { /* yes, the report can be both at offset 0 and 1 :( */ union { struct { uint8_t result; uint8_t report_id; } read; struct { uint8_t report_id; uint8_t :8; /* unused */ } write; } header; uint8_t macro_num; uint8_t checksum; enum { GSKILL_MACRO_METHOD_BUTTON_PRESS = 0x5, GSKILL_MACRO_METHOD_BUTTON_RELEASE = 0x1, GSKILL_MACRO_METHOD_BUTTON_LOOP_START = 0x7, GSKILL_MACRO_METHOD_BUTTON_LOOP_END = 0x0 } macro_exec_method :8; uint8_t loop_count; uint8_t please_set_me_to_1_thank_you; uint16_t macro_length; uint8_t macro_name_length; char macro_name[256]; uint8_t macro_content[1786]; } __attribute__((packed)); _Static_assert(sizeof(struct gskill_macro_report) == GSKILL_REPORT_SIZE_MACRO, "Size of gskill_macro_report is wrong"); enum ratbag_button_type gskill_button_type_mapping[] = { RATBAG_BUTTON_TYPE_LEFT, RATBAG_BUTTON_TYPE_RIGHT, RATBAG_BUTTON_TYPE_MIDDLE, RATBAG_BUTTON_TYPE_THUMB, RATBAG_BUTTON_TYPE_THUMB2, RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP, RATBAG_BUTTON_TYPE_THUMB3, RATBAG_BUTTON_TYPE_THUMB4, RATBAG_BUTTON_TYPE_WHEEL_UP, RATBAG_BUTTON_TYPE_WHEEL_DOWN, }; struct gskill_button_function_mapping { enum gskill_button_function_type type; struct ratbag_button_action action; }; static const struct gskill_button_function_mapping gskill_button_function_mapping[] = { { GSKILL_BUTTON_FUNCTION_MACRO, BUTTON_ACTION_MACRO }, { GSKILL_BUTTON_FUNCTION_DPI_UP, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) }, { GSKILL_BUTTON_FUNCTION_DPI_DOWN, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) }, { GSKILL_BUTTON_FUNCTION_CYCLE_DPI_UP, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) }, { GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_UP, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) }, { GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_DOWN, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN) }, { GSKILL_BUTTON_FUNCTION_DISABLE, BUTTON_ACTION_NONE }, }; struct gskill_profile_data { struct gskill_profile_report report; uint8_t res_idx_to_dev_idx[GSKILL_NUM_DPI]; struct gskill_macro_report macros[GSKILL_BUTTON_MAX]; }; struct gskill_data { uint8_t profile_count; struct gskill_profile_data profile_data[GSKILL_PROFILE_MAX]; }; static inline struct gskill_profile_data * profile_to_pdata(struct ratbag_profile *profile) { struct gskill_data *drv_data = profile->device->drv_data; return &drv_data->profile_data[profile->index]; } static const struct ratbag_button_action * gskill_button_function_to_action(enum gskill_button_function_type type) { const struct gskill_button_function_mapping *mapping; ARRAY_FOR_EACH(gskill_button_function_mapping, mapping) { if (mapping->type == type) return &mapping->action; } return NULL; } static enum gskill_button_function_type gskill_button_function_from_action(const struct ratbag_button_action *action) { const struct gskill_button_function_mapping *mapping; ARRAY_FOR_EACH(gskill_button_function_mapping, mapping) { if (ratbag_button_action_match(&mapping->action, action)) return mapping->type; } return GSKILL_BUTTON_FUNCTION_DISABLE; } static uint8_t gskill_calculate_checksum(const uint8_t *buf, size_t len) { uint8_t checksum = 0; unsigned i; for (i = GSKILL_CHECKSUM_OFFSET + 1; i < len; i++) checksum += buf[i]; checksum = ~checksum + 1; return checksum; } static int gskill_general_cmd(struct ratbag_device *device, uint8_t buf[GSKILL_REPORT_SIZE_CMD]) { int rc; int retries; assert(buf[0] == GSKILL_GENERAL_CMD); rc = ratbag_hidraw_raw_request(device, GSKILL_GENERAL_CMD, buf, GSKILL_REPORT_SIZE_CMD, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc != GSKILL_REPORT_SIZE_CMD) { log_error(device->ratbag, "Error while sending command to mouse: %d\n", rc); return rc < 0 ? rc : -EPROTO; } rc = -EAGAIN; for (retries = 0; retries < 10 && rc == -EAGAIN; retries++) { /* * Wait for the device to be ready * Spec says this should be 10ms, but 20ms seems to get the * mouse to return slightly less nonsense responses */ msleep(20); rc = ratbag_hidraw_raw_request(device, 0, buf, GSKILL_REPORT_SIZE_CMD, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); /* * Sometimes the mouse just doesn't send anything when it wants * to tell us it's ready. In this case rc will be 0 and this * function will succeed. */ if (rc < GSKILL_REPORT_SIZE_CMD) break; /* Check the command status bit */ switch (buf[1]) { case 0: /* sometimes the mouse gets lazy and just returns a blank buffer on success */ case GSKILL_CMD_SUCCESS: rc = 0; break; case GSKILL_CMD_IN_PROGRESS: rc = -EAGAIN; continue; case GSKILL_CMD_IDLE: log_error(device->ratbag, "Command response indicates idle status? Uh huh.\n"); rc = -EPROTO; break; case GSKILL_CMD_FAILURE: log_error(device->ratbag, "Command failed\n"); rc = -EIO; break; default: log_error(device->ratbag, "Received unknown command status from mouse: 0x%x\n", buf[1]); rc = -EPROTO; break; } } if (rc == -EAGAIN) { log_error(device->ratbag, "Failed to get command response from mouse after %d tries, giving up\n", retries); rc = -ETIMEDOUT; } else if (rc) { log_error(device->ratbag, "Failed to perform command on mouse: %d\n", rc); if (rc > 0) rc = -EPROTO; } return rc; } static int gskill_get_active_profile_idx(struct ratbag_device *device) { uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x7, 0x0, 0x1 }; int rc; rc = gskill_general_cmd(device, buf); if (rc) { log_error(device->ratbag, "Error while getting active profile number from mouse: %d\n", rc); return rc; } return buf[3]; } static int gskill_set_active_profile(struct ratbag_device *device, unsigned index) { uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x7, index, 0x0 }; int rc; rc = gskill_general_cmd(device, buf); if (rc) { log_error(device->ratbag, "Error while changing active profile on mouse: %d\n", rc); return rc; } return 0; } static int gskill_get_profile_count(struct ratbag_device *device) { uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x12, 0x0, 0x1 }; int rc; rc = gskill_general_cmd(device, buf); if (rc) { log_error(device->ratbag, "Error while getting the number of profiles: %d\n", rc); return rc; } log_debug(device->ratbag, "Profile count: %d\n", buf[3]); return buf[3]; } static int gskill_set_profile_count(struct ratbag_device *device, unsigned int count) { uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x12, count, 0x0 }; int rc; log_debug(device->ratbag, "Setting profile count to %d\n", count); rc = gskill_general_cmd(device, buf); if (rc) { log_error(device->ratbag, "Error while setting the number of profiles: %d\n", rc); return rc; } return 0; } /* * This is used for setting the profile index argument on the mouse for both * reading and writing profiles */ static int gskill_select_profile(struct ratbag_device *device, unsigned index, bool write) { uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x0c, index, write }; int rc; /* * While this looks like a normal command and should have the same * behavior, trying to receive the command return status from the mouse * breaks reading the profile */ rc = ratbag_hidraw_raw_request(device, GSKILL_GENERAL_CMD, buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc != sizeof(buf)) { log_error(device->ratbag, "Error while setting profile number to read/write: %d\n", rc); return rc < 0 ? rc : -EPROTO; } return 0; } /* * Instructs the mouse to reload the data from a profile we've just written to * it. */ static int gskill_reload_profile_data(struct ratbag_device *device) { uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x0 }; int rc; log_debug(device->ratbag, "Asking mouse to reload profile data\n"); rc = gskill_general_cmd(device, buf); if (rc) { log_error(device->ratbag, "Failed to get mouse to reload profile data: %d\n", rc); return rc; } return 0; } static int gskill_write_profile(struct ratbag_device *device, struct gskill_profile_report *report) { uint8_t *buf = (uint8_t*)report; int rc; /* * The G.Skill configuration software doesn't take kindly to blank * profile names, so ensure we have one */ if (report->name[0] == '\0') { log_debug(device->ratbag, "Setting profile name to \"Ratbag profile %d\"\n", report->profile_num); rc = ratbag_utf8_to_enc(report->name, sizeof(report->name), "UTF-16LE", "Ratbag profile %d", report->profile_num); if (rc < 0) return rc; } report->checksum = gskill_calculate_checksum(buf, sizeof(*report)); rc = gskill_select_profile(device, report->profile_num, true); if (rc) return rc; /* Wait for the device to be ready */ msleep(200); rc = ratbag_hidraw_raw_request(device, GSKILL_GET_SET_PROFILE, buf, sizeof(*report), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc != sizeof(*report)) { log_error(device->ratbag, "Error while writing profile: %d\n", rc); return rc < 0 ? rc : -EPROTO; } return 0; } static int gskill_get_firmware_version(struct ratbag_device *device) { uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x08 }; int rc; rc = gskill_general_cmd(device, buf); if (rc) { log_error(device->ratbag, "Failed to read the firmware version of the mouse: %d\n", rc); return rc; } return buf[4]; } static unsigned int gskill_mouse_button_macro_code_to_keycode(uint8_t code) { unsigned int keycode; switch (code & 0x0f) { case 0x8: keycode = BTN_LEFT; break; case 0x9: keycode = BTN_RIGHT; break; case 0xa: keycode = BTN_MIDDLE; break; case 0xb: keycode = BTN_SIDE; break; case 0xc: keycode = BTN_EXTRA; break; default: keycode = 0; break; } return keycode; } static uint8_t gskill_macro_code_from_event(struct ratbag_device *device, struct ratbag_macro_event *event) { uint8_t macro_code; /* * The miscellanious keycodes are ORd with 0x70 to indicate press, 0xF0 * to indicate release */ if (event->type == RATBAG_MACRO_EVENT_KEY_PRESSED) macro_code = 0x70; else macro_code = 0xF0; switch (event->event.key) { case KEY_LEFTCTRL: macro_code |= 0x00; break; case KEY_LEFTSHIFT: macro_code |= 0x01; break; case KEY_LEFTALT: macro_code |= 0x02; break; case KEY_LEFTMETA: macro_code |= 0x03; break; case KEY_RIGHTCTRL: macro_code |= 0x04; break; case KEY_RIGHTSHIFT: macro_code |= 0x05; break; case KEY_RIGHTALT: macro_code |= 0x06; break; case KEY_RIGHTMETA: macro_code |= 0x07; break; case BTN_LEFT: macro_code |= 0x08; break; case BTN_RIGHT: macro_code |= 0x09; break; case BTN_MIDDLE: macro_code |= 0x0a; break; case BTN_SIDE: macro_code |= 0x0b; break; case BTN_EXTRA: macro_code |= 0x0c; break; case KEY_SCROLLDOWN: macro_code = 0x7e; break; case KEY_SCROLLUP: macro_code = 0xfe; break; default: macro_code = ratbag_hidraw_get_keyboard_usage_from_keycode( device, event->event.key); if (event->type == RATBAG_MACRO_EVENT_KEY_RELEASED) macro_code += 0x80; break; } return macro_code; } static struct ratbag_button_macro * gskill_macro_from_report(struct ratbag_device *device, struct gskill_macro_report *report) { struct ratbag_button_macro *macro; enum ratbag_macro_event_type type; const uint8_t *data = (uint8_t*)&report->macro_content; unsigned int event_data; int ret, i, event_idx, increment; /* The macro is empty */ if (report->macro_length == 0xff) { return NULL; } else if (report->macro_length > sizeof(report->macro_content)) { log_error(device->ratbag, "Macro length too large (max should be %ld, we got %d)\n", sizeof(report->macro_content), report->macro_length); return NULL; } /* * Since the length is only 8 bits long, it's impossible to specify a * length that's too large for the macro name */ macro = ratbag_button_macro_new(NULL); ret = ratbag_utf8_from_enc(report->macro_name, report->macro_name_length, "UTF-16LE", ¯o->macro.name); if (ret < 0) { ratbag_button_macro_unref(macro); return NULL; } for (i = 0, event_idx = 0, increment = 1; i < report->macro_length; i += increment, increment = 1, event_idx++) { const struct gskill_macro_delay *delay; switch (data[i]) { case 0x01: /* delay */ delay = (struct gskill_macro_delay*)&data[i]; increment = sizeof(struct gskill_macro_delay); type = RATBAG_MACRO_EVENT_WAIT; event_data = delay->count; break; case 0x04 ... 0x6a: /* HID KBD code, press */ type = RATBAG_MACRO_EVENT_KEY_PRESSED; event_data = ratbag_hidraw_get_keycode_from_keyboard_usage( device, data[i]); break; case 0x70 ... 0x77: /* KBD modifier, press */ type = RATBAG_MACRO_EVENT_KEY_PRESSED; event_data = ratbag_hidraw_get_keycode_from_keyboard_usage( device, data[i] + 0x70); break; case 0x78 ... 0x7c: /* Mouse button, press */ type = RATBAG_MACRO_EVENT_KEY_PRESSED; event_data = gskill_mouse_button_macro_code_to_keycode( data[i]); break; case 0x7e: /* Scroll down */ type = RATBAG_MACRO_EVENT_KEY_PRESSED; event_data = KEY_SCROLLDOWN; break; case 0x84 ... 0xef: /* HID KBD code, release */ type = RATBAG_MACRO_EVENT_KEY_RELEASED; event_data = ratbag_hidraw_get_keycode_from_keyboard_usage( device, data[i] - 0x80); break; case 0xf0 ... 0xf7: /* KBD modifier, release */ type = RATBAG_MACRO_EVENT_KEY_RELEASED; event_data = ratbag_hidraw_get_keycode_from_keyboard_usage( device, data[i] - 0x10); break; case 0xf8 ... 0xfc: /* Mouse button, release */ type = RATBAG_MACRO_EVENT_KEY_RELEASED; event_data = gskill_mouse_button_macro_code_to_keycode( data[i]); break; case 0xfe: /* Scroll up */ type = RATBAG_MACRO_EVENT_KEY_PRESSED; event_data = KEY_SCROLLUP; break; default: /* should never get there */ type = RATBAG_MACRO_EVENT_INVALID; event_data = 0; } ratbag_button_macro_set_event(macro, event_idx, type, event_data); } return macro; } /* FIXME: the macro struct here should be a const, but it looks like there's a * couple of functions in ratbag that need to have a const qualifier added to * their function declarations */ static struct gskill_macro_report * gskill_macro_to_report(struct ratbag_device *device, struct ratbag_button_macro *macro, unsigned int profile, unsigned int button) { struct gskill_data *drv_data = ratbag_get_drv_data(device); struct gskill_macro_report *report = &drv_data->profile_data[profile].macros[button]; struct gskill_macro_delay *delay; unsigned int event_num = ratbag_button_macro_get_num_events(macro); struct ratbag_macro_event *event; uint8_t *buf = report->macro_content; int profile_pos, increment, event_idx; ssize_t ret; memset(report, 0, sizeof(*report)); /* * G.Skill's configuration software will cry if we don't have a name, * so make sure we assign one */ if (!macro->macro.name || macro->macro.name[0] == '\0') { ret = ratbag_utf8_to_enc(report->macro_name, sizeof(report->macro_name), "UTF-16LE", "Ratbag macro for profile %d button %d", profile, button); } else { ret = ratbag_utf8_to_enc(report->macro_name, sizeof(report->macro_name), "UTF-16LE", "%s", macro->macro.name); } if (ret < 0) return NULL; report->macro_name_length = ret; report->macro_num = (profile * 10) + button; report->macro_exec_method = GSKILL_MACRO_METHOD_BUTTON_PRESS; report->loop_count = 0; report->please_set_me_to_1_thank_you = 1; /* No prob! Happy to help :) */ for (profile_pos = 0, increment = 1, event_idx = 0; event_idx < (signed)event_num; event_idx++, profile_pos += increment, increment = 1) { event = ¯o->macro.events[event_idx]; switch (event->type) { case RATBAG_MACRO_EVENT_WAIT: delay = (struct gskill_macro_delay*)&buf[profile_pos]; increment = sizeof(*delay); delay->tag = 1; delay->count = event->event.timeout; break; case RATBAG_MACRO_EVENT_KEY_PRESSED: case RATBAG_MACRO_EVENT_KEY_RELEASED: buf[profile_pos] = gskill_macro_code_from_event(device, event); break; case RATBAG_MACRO_EVENT_INVALID: case RATBAG_MACRO_EVENT_NONE: goto out; } } out: report->macro_length = profile_pos; return report; } static int gskill_select_macro(struct ratbag_device *device, unsigned profile, unsigned button, bool write) { uint8_t macro_num = (profile * 10) + button; uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x0b, macro_num, write }; int rc; /* * Just like in gskill_select_profile(), we can't use the normal * command handler for this */ rc = ratbag_hidraw_raw_request(device, GSKILL_GENERAL_CMD, buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc != sizeof(buf)) { log_error(device->ratbag, "Error while setting macro number to read/write: %d\n", rc); return rc < 0 ? rc : -EPROTO; } return 0; } static struct gskill_macro_report * gskill_read_button_macro(struct ratbag_device *device, unsigned int profile, unsigned int button) { struct gskill_data *drv_data = ratbag_get_drv_data(device); struct gskill_macro_report *report = &drv_data->profile_data[profile].macros[button]; uint8_t checksum; int rc; rc = gskill_select_macro(device, profile, button, false); if (rc) return NULL; /* Wait for the device to be ready */ msleep(100); rc = ratbag_hidraw_raw_request(device, GSKILL_GET_SET_MACRO, (uint8_t*)report, sizeof(*report), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (rc < (signed)sizeof(*report)) { log_error(device->ratbag, "Failed to retrieve macro for profile %d for button %d: %d\n", profile, button, rc); return NULL; } checksum = gskill_calculate_checksum((uint8_t*)report, sizeof(*report)); if (checksum != report->checksum) { log_error(device->ratbag, "Invalid checksum on macro for profile %d button %d\n", profile, button); return NULL; } return report; } static int gskill_write_button_macro(struct ratbag_device *device, struct gskill_macro_report *report) { unsigned int profile = report->macro_num / 10; unsigned int button = report->macro_num % 10; int rc; rc = gskill_select_macro(device, profile, button, true); if (rc) return rc; /* Wait for the device to be ready */ msleep(200); memset(&report->header, 0, sizeof(report->header)); report->header.write.report_id = 0x4; report->checksum = gskill_calculate_checksum((uint8_t*)report, sizeof(*report)); rc = ratbag_hidraw_raw_request(device, GSKILL_GET_SET_MACRO, (uint8_t*)report, sizeof(*report), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc < 0) { log_error(device->ratbag, "Failed to write macro for profile %d button %d to mouse: %d\n", profile, button, rc); return rc; } return 0; } static 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); ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_QUERY_CONFIGURATION); ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_KEY); ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_MACROS); ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_DISABLE_PROFILE); ret = gskill_get_active_profile_idx(device); if (ret < 0) goto err; active_idx = ret; list_for_each(profile, &device->profiles, link) { if (profile->index == active_idx) { profile->is_active = true; break; } } return 0; err: if (drv_data) { ratbag_set_drv_data(device, NULL); free(drv_data); } ratbag_close_hidraw(device); return ret; } static void gskill_read_resolutions(struct ratbag_profile *profile, struct gskill_profile_report *report) { struct gskill_profile_data *pdata = profile_to_pdata(profile); struct ratbag_resolution *resolution; 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); for (i = 0; i < report->dpi_num; i++) { dpi_x = report->dpi_levels[i].x * GSKILL_DPI_UNIT; dpi_y = report->dpi_levels[i].y * GSKILL_DPI_UNIT; resolution = ratbag_resolution_init(profile, i, dpi_x, dpi_y, hz); 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); } } 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, unsigned int index) { 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 (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, 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 == index) break; log_debug(device->ratbag, "Mouse send wrong profile %d instead of %d, retrying...\n", profile->index, index); } 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++) { struct ratbag_resolution *res = &profile->resolution.modes[i]; struct gskill_raw_dpi_level *level = &report->dpi_levels[report->dpi_num]; 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 %ld mapped to %d\n", profile->index, res - profile->resolution.modes, report->dpi_num); report->dpi_num++; } return 0; } #if 0 static int gskill_reset_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; uint8_t buf[GSKILL_REPORT_SIZE_CMD] = { GSKILL_GENERAL_CMD, 0xc4, 0x0a, profile->index }; int rc; rc = gskill_general_cmd(device, buf); if (rc < 0) return rc; log_debug(device->ratbag, "reset profile %d to factory defaults\n", profile->index); return 0; } #endif static void gskill_read_button(struct ratbag_button *button) { struct ratbag_profile *profile = button->profile; struct ratbag_device *device = profile->device; struct gskill_profile_report *report = &profile_to_pdata(profile)->report; struct gskill_macro_report *macro_report; struct ratbag_button_macro *macro; struct gskill_button_cfg *bcfg = &report->btn_cfgs[button->index]; struct ratbag_button_action *act = &button->action; button->type = gskill_button_type_mapping[button->index]; ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); /* * G.Skill mice can't save disabled profiles, so buttons from disabled * profiles shouldn't be set to anything */ if (!profile->is_enabled) { act->type = RATBAG_BUTTON_ACTION_TYPE_NONE; return; } /* Parse any parameters that might accompany the action type */ switch (bcfg->type) { case GSKILL_BUTTON_FUNCTION_WHEEL: act->type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; if (bcfg->params.wheel.direction == GSKILL_WHEEL_SCROLL_UP) act->action.special = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP; else act->action.special = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN; break; case GSKILL_BUTTON_FUNCTION_MOUSE: act->type = RATBAG_BUTTON_ACTION_TYPE_BUTTON; switch (bcfg->params.mouse.button_mask) { case GSKILL_BTN_MASK_LEFT: act->action.button = RATBAG_BUTTON_TYPE_LEFT; break; case GSKILL_BTN_MASK_RIGHT: act->action.button = RATBAG_BUTTON_TYPE_RIGHT; break; case GSKILL_BTN_MASK_MIDDLE: act->action.button = RATBAG_BUTTON_TYPE_MIDDLE; break; case GSKILL_BTN_MASK_SIDE: act->action.button = RATBAG_BUTTON_TYPE_SIDE; break; case GSKILL_BTN_MASK_EXTRA: act->action.button = RATBAG_BUTTON_TYPE_EXTRA; break; } break; case GSKILL_BUTTON_FUNCTION_KBD: act->type = RATBAG_BUTTON_ACTION_TYPE_KEY; act->action.key.key = ratbag_hidraw_get_keycode_from_keyboard_usage( device, bcfg->params.kbd.hid_code); break; case GSKILL_BUTTON_FUNCTION_CONSUMER: act->type = RATBAG_BUTTON_ACTION_TYPE_KEY; act->action.key.key = ratbag_hidraw_get_keycode_from_consumer_usage( device, bcfg->params.consumer.code); break; case GSKILL_BUTTON_FUNCTION_DPI_UP: case GSKILL_BUTTON_FUNCTION_DPI_DOWN: case GSKILL_BUTTON_FUNCTION_CYCLE_DPI_UP: case GSKILL_BUTTON_FUNCTION_CYCLE_PROFILE_UP: case GSKILL_BUTTON_FUNCTION_DISABLE: *act = *gskill_button_function_to_action(bcfg->type); break; case GSKILL_BUTTON_FUNCTION_MACRO: macro_report = gskill_read_button_macro(device, button->profile->index, button->index); if (!macro_report) goto err; macro = gskill_macro_from_report(device, macro_report); if (!macro) goto err; act->type = RATBAG_BUTTON_ACTION_TYPE_MACRO; ratbag_button_copy_macro(button, macro); ratbag_button_macro_unref(macro); break; default: break; } return; err: act->type = RATBAG_BUTTON_ACTION_TYPE_NONE; } static int gskill_update_button(struct ratbag_button *button) { struct ratbag_profile *profile = button->profile; struct ratbag_device *device = profile->device; struct ratbag_button_action *action = &button->action; struct ratbag_button_macro *macro = NULL; struct gskill_profile_data *pdata = profile_to_pdata(profile); struct gskill_button_cfg *bcfg = &pdata->report.btn_cfgs[button->index]; uint16_t code = 0; macro = container_of(action->macro, macro, macro); memset(&bcfg->params, 0, sizeof(bcfg->params)); switch (action->type) { case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: switch (action->action.special) { case RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP: bcfg->type = GSKILL_BUTTON_FUNCTION_WHEEL; bcfg->params.wheel.direction = GSKILL_WHEEL_SCROLL_UP; break; case RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN: bcfg->type = GSKILL_BUTTON_FUNCTION_WHEEL; bcfg->params.wheel.direction = GSKILL_WHEEL_SCROLL_DOWN; break; case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP: case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP: case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN: case RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP: case RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP: case RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN: bcfg->type = gskill_button_function_from_action(action); break; default: return -EINVAL; } break; case RATBAG_BUTTON_ACTION_TYPE_BUTTON: bcfg->type = GSKILL_BUTTON_FUNCTION_MOUSE; switch (action->action.button) { case RATBAG_BUTTON_TYPE_LEFT: bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_LEFT; break; case RATBAG_BUTTON_TYPE_RIGHT: bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_RIGHT; break; case RATBAG_BUTTON_TYPE_MIDDLE: bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_MIDDLE; break; case RATBAG_BUTTON_TYPE_SIDE: bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_SIDE; break; case RATBAG_BUTTON_TYPE_EXTRA: bcfg->params.mouse.button_mask = GSKILL_BTN_MASK_EXTRA; break; default: return -EINVAL; } break; case RATBAG_BUTTON_ACTION_TYPE_KEY: code = ratbag_hidraw_get_keyboard_usage_from_keycode( device, action->action.key.key); if (code) { bcfg->type = GSKILL_BUTTON_FUNCTION_KBD; bcfg->params.kbd.hid_code = code; } else { code = ratbag_hidraw_get_consumer_usage_from_keycode( device, action->action.key.key); bcfg->type = GSKILL_BUTTON_FUNCTION_CONSUMER; bcfg->params.consumer.code = code; } break; case RATBAG_BUTTON_ACTION_TYPE_MACRO: bcfg->type = GSKILL_BUTTON_FUNCTION_MACRO; gskill_write_button_macro( device, gskill_macro_to_report(device, macro, profile->index, button->index)); break; case RATBAG_BUTTON_ACTION_TYPE_NONE: bcfg->type = GSKILL_BUTTON_FUNCTION_DISABLE; break; default: break; } return 0; } static int gskill_update_profile(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct ratbag_button *button; struct gskill_profile_data *pdata = profile_to_pdata(profile); struct gskill_profile_report *report = &pdata->report; int rc; gskill_update_resolutions(profile); list_for_each(button, &profile->buttons, link) { if (!button->dirty) continue; rc = gskill_update_button(button); if (rc) return rc; } rc = gskill_write_profile(device, report); if (rc) return rc; return 0; } static int gskill_commit(struct ratbag_device *device) { struct ratbag_profile *profile; struct gskill_data *drv_data = ratbag_get_drv_data(device); struct gskill_profile_report *report; uint8_t profile_count = 0, new_idx, i; bool reload = false; int rc; /* * G.Skill mice only have a concept of how many profiles are enabled, * not which ones are and aren't enabled. So in order to provide the * ability to disable individual profiles we need to only write the * enabled profiles and make sure no holes are left inbetween profiles */ for (i = 0; i < GSKILL_PROFILE_MAX; i++) { profile = ratbag_device_get_profile(device, i); if (!profile->is_enabled) continue; report = &drv_data->profile_data[profile->index].report; new_idx = profile_count++; if (new_idx == report->profile_num) continue; log_debug(device->ratbag, "Profile %d remapped to %d\n", profile->index, new_idx); profile->dirty = true; report->profile_num = new_idx; } if (profile_count != drv_data->profile_count) { rc = gskill_set_profile_count(device, profile_count); if (rc < 0) return rc; drv_data->profile_count = profile_count; } list_for_each(profile, &device->profiles, link) { if (!profile->is_enabled || !profile->dirty) continue; log_debug(device->ratbag, "Profile %d changed, rewriting\n", profile->index); reload = true; rc = gskill_update_profile(profile); if (rc) return rc; } if (reload) { rc = gskill_reload_profile_data(device); if (rc) return rc; } return 0; } static void gskill_remove(struct ratbag_device *device) { ratbag_close_hidraw(device); free(ratbag_get_drv_data(device)); } struct ratbag_driver gskill_driver = { .name = "G.Skill Ripjaws MX780", .id = "gskill", .probe = gskill_probe, .remove = gskill_remove, .read_profile = gskill_read_profile, .commit = gskill_commit, .set_active_profile = gskill_set_active_profile, .read_button = gskill_read_button, }; libratbag-0.9/src/driver-hidpp10.c000066400000000000000000000454151311552661500170200ustar00rootroot00000000000000/* * 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" struct hidpp10drv_data { struct hidpp10_device *dev; }; static unsigned int hidpp10drv_read_macro_modifier(struct ratbag_device *device, union hidpp10_macro_data *macro) { switch (macro->key.key) { case 0x01: return KEY_LEFTCTRL; case 0x02: return KEY_LEFTSHIFT; case 0x04: return KEY_LEFTALT; case 0x08: return KEY_LEFTMETA; case 0x10: return KEY_RIGHTCTRL; case 0x20: return KEY_RIGHTSHIFT; case 0x40: return KEY_RIGHTALT; case 0x80: return KEY_RIGHTMETA; } return KEY_RESERVED; } static void hidpp10drv_read_macro(struct ratbag_button *button, struct hidpp10_profile *profile, union hidpp10_button *binding) { struct ratbag_device *device = button->profile->device; struct ratbag_button_macro *m; const char *name; union hidpp10_macro_data *macro; unsigned int i, keycode; bool delay = true; macro = profile->macros[binding->macro.address]; name = binding->macro.address > 1 ? (const char *)profile->macro_names[binding->macro.address - 2] : ""; i = 0; m = ratbag_button_macro_new(name); while (macro && macro->any.type != HIDPP10_MACRO_END && i < MAX_MACRO_EVENTS) { switch (macro->any.type) { case HIDPP10_MACRO_DELAY: ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, macro->delay.time); delay = true; break; case HIDPP10_MACRO_KEY_PRESS: keycode = ratbag_hidraw_get_keycode_from_keyboard_usage(device, macro->key.key); if (!delay) ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, 1); ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_KEY_PRESSED, keycode); delay = false; break; case HIDPP10_MACRO_KEY_RELEASE: keycode = ratbag_hidraw_get_keycode_from_keyboard_usage(device, macro->key.key); if (!delay) ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, 1); ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_KEY_RELEASED, keycode); delay = false; break; case HIDPP10_MACRO_MOD_PRESS: keycode = hidpp10drv_read_macro_modifier(device, macro); if (!delay) ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, 1); ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_KEY_PRESSED, keycode); delay = false; break; case HIDPP10_MACRO_MOD_RELEASE: keycode = hidpp10drv_read_macro_modifier(device, macro); if (!delay) ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, 1); ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_KEY_RELEASED, keycode); delay = false; break; } macro++; } ratbag_button_copy_macro(button, m); ratbag_button_macro_unref(m); } static void hidpp10drv_map_button(struct ratbag_device *device, struct hidpp10_device *hidpp10, struct ratbag_button *button) { struct hidpp10_profile profile; int ret; ret = hidpp10_get_profile(hidpp10, button->profile->index, &profile); if (ret) return; switch (profile.buttons[button->index].any.type) { case PROFILE_BUTTON_TYPE_BUTTON: button->action.type = RATBAG_BUTTON_ACTION_TYPE_BUTTON; button->action.action.button = profile.buttons[button->index].button.button; break; case PROFILE_BUTTON_TYPE_KEYS: button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; button->action.action.key.key = ratbag_hidraw_get_keycode_from_keyboard_usage(device, profile.buttons[button->index].keys.key); break; case PROFILE_BUTTON_TYPE_CONSUMER_CONTROL: button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; button->action.action.key.key = ratbag_hidraw_get_keycode_from_consumer_usage(device, profile.buttons[button->index].consumer_control.consumer_control); break; case PROFILE_BUTTON_TYPE_SPECIAL: button->action.type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; button->action.action.special = hidpp10_onboard_profiles_get_special(profile.buttons[button->index].special.special); break; case PROFILE_BUTTON_TYPE_DISABLED: button->action.type = RATBAG_BUTTON_ACTION_TYPE_NONE; break; default: if (profile.buttons[button->index].any.type & 0x80) { button->action.type = RATBAG_BUTTON_ACTION_TYPE_UNKNOWN; } else { hidpp10drv_read_macro(button, &profile, &profile.buttons[button->index]); } } ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); } static void hidpp10drv_read_button(struct ratbag_button *button) { enum ratbag_button_type type = RATBAG_BUTTON_TYPE_UNKNOWN; struct ratbag_device *device = button->profile->device; struct hidpp10drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp10_device *hidpp10 = drv_data->dev; switch (hidpp10->profile_type) { case HIDPP10_PROFILE_G500: switch (button->index) { case 0: type = RATBAG_BUTTON_TYPE_LEFT; break; case 1: type = RATBAG_BUTTON_TYPE_MIDDLE; break; case 2: type = RATBAG_BUTTON_TYPE_RIGHT; break; case 3: type = RATBAG_BUTTON_TYPE_THUMB; break; case 4: type = RATBAG_BUTTON_TYPE_THUMB2; break; case 5: type = RATBAG_BUTTON_TYPE_THUMB3; break; case 6: type = RATBAG_BUTTON_TYPE_WHEEL_LEFT; break; case 7: type = RATBAG_BUTTON_TYPE_WHEEL_RIGHT; break; case 8: type = RATBAG_BUTTON_TYPE_RESOLUTION_UP; break; case 9: type = RATBAG_BUTTON_TYPE_RESOLUTION_DOWN; break; case 10: case 11: case 12: /* these don't actually exist on the device */ type = RATBAG_BUTTON_TYPE_UNKNOWN; break; default: break; } hidpp10drv_map_button(device, hidpp10, button); break; case HIDPP10_PROFILE_G700: switch (button->index) { case 0: type = RATBAG_BUTTON_TYPE_LEFT; break; case 1: type = RATBAG_BUTTON_TYPE_MIDDLE; break; case 2: type = RATBAG_BUTTON_TYPE_RIGHT; break; case 3: type = RATBAG_BUTTON_TYPE_THUMB; break; case 4: type = RATBAG_BUTTON_TYPE_THUMB2; break; case 5: type = RATBAG_BUTTON_TYPE_THUMB3; break; case 6: type = RATBAG_BUTTON_TYPE_THUMB4; break; case 7: type = RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP; break; case 8: type = RATBAG_BUTTON_TYPE_RESOLUTION_DOWN; break; case 9: type = RATBAG_BUTTON_TYPE_RESOLUTION_UP; break; case 10: type = RATBAG_BUTTON_TYPE_PROFILE_CYCLE_UP; break; case 11: type = RATBAG_BUTTON_TYPE_WHEEL_LEFT; break; case 12: type = RATBAG_BUTTON_TYPE_WHEEL_RIGHT; break; default: break; } hidpp10drv_map_button(device, hidpp10, button); break; case HIDPP10_PROFILE_G9: switch (button->index) { case 0: type = RATBAG_BUTTON_TYPE_LEFT; break; case 1: type = RATBAG_BUTTON_TYPE_RIGHT; break; case 2: type = RATBAG_BUTTON_TYPE_MIDDLE; break; case 3: type = RATBAG_BUTTON_TYPE_THUMB; break; case 4: type = RATBAG_BUTTON_TYPE_THUMB2; break; case 5: type = RATBAG_BUTTON_TYPE_UNKNOWN; break; case 6: type = RATBAG_BUTTON_TYPE_WHEEL_LEFT; break; case 7: type = RATBAG_BUTTON_TYPE_WHEEL_RIGHT; break; case 8: type = RATBAG_BUTTON_TYPE_RESOLUTION_UP; break; case 9: type = RATBAG_BUTTON_TYPE_RESOLUTION_DOWN; break; case 10: case 11: case 12: /* these don't actually exist on the device */ type = RATBAG_BUTTON_TYPE_UNKNOWN; break; default: break; } hidpp10drv_map_button(device, hidpp10, button); break; default: switch (button->index) { case 0: type = RATBAG_BUTTON_TYPE_LEFT; break; case 1: type = RATBAG_BUTTON_TYPE_MIDDLE; break; case 2: type = RATBAG_BUTTON_TYPE_RIGHT; break; default: break; } } button->type = type; ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); } static int hidpp10drv_write_button(struct 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; struct hidpp10_profile profile; uint8_t code; int ret; if (hidpp10->profile_type == HIDPP10_PROFILE_UNKNOWN) return -ENOTSUP; ret = hidpp10_get_profile(hidpp10, button->profile->index, &profile); if (ret) return ret; switch (action->type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: profile.buttons[button->index].button.type = PROFILE_BUTTON_TYPE_BUTTON; profile.buttons[button->index].button.button = action->action.button; break; case RATBAG_BUTTON_ACTION_TYPE_KEY: code = ratbag_hidraw_get_keyboard_usage_from_keycode(device, action->action.key.key); if (code == 0) { code = ratbag_hidraw_get_consumer_usage_from_keycode(device, action->action.key.key); if (code == 0) return -EINVAL; profile.buttons[button->index].consumer_control.type = PROFILE_BUTTON_TYPE_CONSUMER_CONTROL; profile.buttons[button->index].consumer_control.consumer_control = code; } else { profile.buttons[button->index].keys.type = PROFILE_BUTTON_TYPE_KEYS; profile.buttons[button->index].keys.key = code; } break; case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: code = hidpp10_onboard_profiles_get_code_from_special(action->action.special); if (code == 0) return -EINVAL; profile.buttons[button->index].special.type = PROFILE_BUTTON_TYPE_SPECIAL; profile.buttons[button->index].special.special = code; break; case RATBAG_BUTTON_ACTION_TYPE_MACRO: default: return -ENOTSUP; } return hidpp10_set_profile(drv_data->dev, button->profile->index, &profile); } 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, unsigned int index) { struct ratbag_device *device = profile->device; struct hidpp10drv_data *drv_data; struct hidpp10_device *hidpp10; struct hidpp10_profile p; struct ratbag_resolution *res; int rc; unsigned int i; uint16_t xres, yres; int8_t idx; drv_data = ratbag_get_drv_data(device); hidpp10 = drv_data->dev; rc = hidpp10_get_profile(hidpp10, index, &p); if (rc) return; 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; for (i = 0; i < profile->resolution.num_modes; i++) { res = ratbag_resolution_init(profile, i, p.dpi_modes[i].xres, p.dpi_modes[i].yres, p.refresh_rate); 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 (i == p.default_dpi_mode) res->is_default = true; } } static int hidpp10drv_write_profile(struct ratbag_profile *profile) { return 0; } static int hidpp10drv_write_resolution_dpi(struct ratbag_resolution *resolution, int dpi_x, int dpi_y) { struct ratbag_profile *profile = resolution->profile; struct ratbag_device *device = profile->device; struct hidpp10drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp10_device *hidpp10 = drv_data->dev; struct hidpp10_profile p; unsigned int index; uint16_t cur_dpi_x, cur_dpi_y; int rc; rc = hidpp10_get_profile(hidpp10, profile->index, &p); if (rc) return rc; /* store the current resolution */ rc = hidpp10_get_current_resolution(hidpp10, &cur_dpi_x, &cur_dpi_y); if (rc) return rc; if (resolution->is_active) { /* we need to switch to the new resolution */ cur_dpi_x = dpi_x; cur_dpi_y = dpi_y; } /* retrieve which resolution is asked to be changed */ index = resolution - profile->resolution.modes; p.dpi_modes[index].xres = dpi_x; p.dpi_modes[index].yres = dpi_y; /* this effectively switches the resolution to the default in the profile */ rc = hidpp10_set_profile(drv_data->dev, profile->index, &p); if (rc) return rc; /* restore the current setting */ return hidpp10_set_current_resolution(hidpp10, cur_dpi_x, cur_dpi_y); } static int hidpp10drv_fill_from_profile(struct ratbag_device *device, struct hidpp10_device *dev) { int rc; struct hidpp10_profile profile; struct hidpp10_directory directory[16]; int count; count = hidpp10_get_profile_directory(dev, directory, ARRAY_LENGTH(directory)); if (count < 0) return count; /* We don't know the HID++1.0 requests to query for buttons, etc. * Simply get the first profile and fill the device information in * from that. */ rc = hidpp10_get_profile(dev, 0, &profile); if (rc) return rc; ratbag_device_init_profiles(device, count, profile.num_dpi_modes, profile.num_buttons, profile.num_leds); if (dev->profile_type != HIDPP10_PROFILE_UNKNOWN) { ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE); ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_KEY); ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_MACROS); } return 0; } static int hidpp10drv_test_hidraw(struct ratbag_device *device) { return ratbag_hidraw_has_report(device, REPORT_ID_SHORT); } static void hidpp10_log(void *userdata, enum hidpp_log_priority priority, const char *format, va_list args) { struct ratbag_device *device = userdata; log_msg_va(device->ratbag, priority, format, args); } static int hidpp10drv_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 *prop; int device_idx = HIDPP_WIRED_DEVICE_IDX; int nread = 0; rc = ratbag_find_hidraw(device, hidpp10drv_test_hidraw); if (rc) goto err; drv_data = zalloc(sizeof(*drv_data)); hidpp_device_init(&base, device->hidraw.fd); hidpp_device_set_log_handler(&base, hidpp10_log, HIDPP_LOG_PRIORITY_RAW, device); prop = ratbag_device_get_udev_property(device, "RATBAG_HIDPP10_PROFILE"); if (prop) { if (strcasecmp("G500", prop) == 0) type = HIDPP10_PROFILE_G500; else if (strcasecmp("G700", prop) == 0) type = HIDPP10_PROFILE_G700; else if (strcasecmp("G9", prop) == 0) type = HIDPP10_PROFILE_G9; } prop = ratbag_device_get_udev_property(device, "RATBAG_HIDPP10_INDEX"); if (prop) { sscanf(prop, "%d%n", &device_idx, &nread); if (!nread || (prop[nread]) != '\0' || device_idx < 0) { log_error(device->ratbag, "Error parsing RATBAG_HIDPP10_INDEX: '%s' for %s\n", prop, device->name); 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), we can pass a * udev prop RATBAG_HIDPP10_INDEX. */ dev = hidpp10_device_new(&base, device_idx, type); if (!dev) { log_error(device->ratbag, "Failed to get HID++1.0 device for %s\n", device->name); goto err; } if (type != HIDPP10_PROFILE_UNKNOWN) { prop = ratbag_device_get_udev_property(device, "RATBAG_HIDPP10_DPI"); if (prop) { rc = hidpp10_build_dpi_table_from_dpi_info(dev, prop); if (rc) log_error(device->ratbag, "Error parsing RATBAG_HIDPP10_DPI: '%s' for %s\n", prop, device->name); } prop = ratbag_device_get_udev_property(device, "RATBAG_HIDPP10_DPI_LIST"); if (prop) { rc = hidpp10_build_dpi_table_from_list(dev, prop); if (rc) log_error(device->ratbag, "Error parsing RATBAG_HIDPP10_DPI_LIST: '%s' for %s\n", prop, device->name); } if (!dev->dpi_count) log_info(device->ratbag, "Device %s might have wrong dpi settings. " "Please add RATBAG_HIDPP10_DPI or RATBAG_HIDPP10_DPI_LIST " "to the udev properties.\n", device->name); } 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 */ struct ratbag_profile *profile; ratbag_device_init_profiles(device, 1, 1, 3, 0); profile = ratbag_device_get_profile(device, 0); profile->is_active = true; ratbag_profile_unref(profile); } return 0; err: free(drv_data); ratbag_set_drv_data(device, NULL); if (dev) hidpp10_device_destroy(dev); return rc; } static void hidpp10drv_remove(struct ratbag_device *device) { struct hidpp10drv_data *drv_data; struct hidpp10_device *dev; ratbag_close_hidraw(device); drv_data = ratbag_get_drv_data(device); dev = drv_data->dev; hidpp10_device_destroy(dev); free(drv_data); } struct ratbag_driver hidpp10_driver = { .name = "Logitech HID++1.0", .id = "hidpp10", .probe = hidpp10drv_probe, .remove = hidpp10drv_remove, .read_profile = hidpp10drv_read_profile, .write_profile = hidpp10drv_write_profile, .set_active_profile = hidpp10drv_set_current_profile, .read_button = hidpp10drv_read_button, .write_button = hidpp10drv_write_button, .write_resolution_dpi = hidpp10drv_write_resolution_dpi, }; libratbag-0.9/src/driver-hidpp20.c000066400000000000000000000732161311552661500170210ustar00rootroot00000000000000/* * 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" #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) 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_color_led_zone_info *led_infos; 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.divert || control->reporting.persist) mapping = control->reporting.remapped; log_raw(device->ratbag, " - button%d: %s (%02x) %s%s:%d\n", button->index, hidpp20_1b04_get_logical_mapping_name(mapping), mapping, control->reporting.divert || control->reporting.persist ? "(redirected) " : "", __FILE__, __LINE__); button->type = hidpp20_1b04_get_physical_mapping(control->task_id); action = hidpp20_1b04_get_logical_mapping(mapping); if (action) button->action = *action; ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); } static unsigned int hidpp20drv_read_macro_key_8100(struct ratbag_device *device, union hidpp20_macro_data *macro) { switch (macro->key.modifier) { case 0x01: return KEY_LEFTCTRL; case 0x02: return KEY_LEFTSHIFT; case 0x04: return KEY_LEFTALT; case 0x08: return KEY_LEFTMETA; case 0x10: return KEY_RIGHTCTRL; case 0x20: return KEY_RIGHTSHIFT; case 0x40: return KEY_RIGHTALT; case 0x80: return KEY_RIGHTMETA; } return ratbag_hidraw_get_keycode_from_keyboard_usage(device, macro->key.key); } static int hidpp20drv_read_macro_8100(struct ratbag_button *button, struct hidpp20_profile *profile, union hidpp20_button_binding *binding) { struct ratbag_device *device = button->profile->device; struct ratbag_button_macro *m; union hidpp20_macro_data *macro; unsigned int i, keycode; bool delay = true; macro = profile->macros[binding->macro.page]; if (!macro) return -EINVAL; i = 0; m = ratbag_button_macro_new("macro"); while (macro && macro->any.type != HIDPP20_MACRO_END && i < MAX_MACRO_EVENTS) { switch (macro->any.type) { case HIDPP20_MACRO_DELAY: ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, macro->delay.time); delay = true; break; case HIDPP20_MACRO_KEY_PRESS: keycode = hidpp20drv_read_macro_key_8100(device, macro); if (!delay) ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, 1); ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_KEY_PRESSED, keycode); delay = false; break; case HIDPP20_MACRO_KEY_RELEASE: keycode = hidpp20drv_read_macro_key_8100(device, macro); if (!delay) ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_WAIT, 1); ratbag_button_macro_set_event(m, i++, RATBAG_MACRO_EVENT_KEY_RELEASED, keycode); delay = false; break; } macro++; } ratbag_button_copy_macro(button, m); ratbag_button_macro_unref(m); return 0; } static void hidpp20drv_read_button_8100(struct ratbag_button *button) { struct ratbag_device *device = button->profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp20_profile *profile; int rc; if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)) return; profile = &drv_data->profiles->profiles[button->profile->index]; switch (profile->buttons[button->index].any.type) { case HIDPP20_BUTTON_HID_TYPE: switch (profile->buttons[button->index].subany.subtype) { case HIDPP20_BUTTON_HID_TYPE_MOUSE: button->action.type = RATBAG_BUTTON_ACTION_TYPE_BUTTON; button->action.action.button = profile->buttons[button->index].button.buttons; break; case HIDPP20_BUTTON_HID_TYPE_KEYBOARD: button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; button->action.action.key.key = ratbag_hidraw_get_keycode_from_keyboard_usage(device, profile->buttons[button->index].keyboard_keys.key); break; case HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL: button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; button->action.action.key.key = ratbag_hidraw_get_keycode_from_consumer_usage(device, profile->buttons[button->index].consumer_control.consumer_control); break; } break; case HIDPP20_BUTTON_SPECIAL: button->action.type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; button->action.action.special = hidpp20_onboard_profiles_get_special(profile->buttons[button->index].special.special); break; case HIDPP20_BUTTON_MACRO: rc = hidpp20drv_read_macro_8100(button, profile, &profile->buttons[button->index]); if (rc) button->action.type = RATBAG_BUTTON_ACTION_TYPE_NONE; break; default: button->action.type = RATBAG_BUTTON_ACTION_TYPE_UNKNOWN; break; } 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(struct ratbag_led *led) { struct ratbag_device *device = led->profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp20_profile *profile; struct hidpp20_color_led_zone_info *led_info; struct hidpp20_led *h_led; if (!(drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070)) return; led_info = &drv_data->led_infos[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->type = hidpp20_8070_get_location_mapping(led_info->location); led->color.red = h_led->color.red; led->color.green = h_led->color.green; led->color.blue = h_led->color.blue; led->hz = h_led->period; /* FIXME: should be ceil(1000.0 / h_led->period), but that would be very small */ led->brightness = h_led->brightness * 255 / 100; } static int hidpp20drv_write_button_1b04(struct ratbag_button *button, const struct ratbag_button_action *action) { struct ratbag_device *device = button->profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp20_control_id *control; 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 = 1; control->reporting.remapped = mapping; control->reporting.updated = 1; rc = hidpp20_special_key_mouse_set_control(drv_data->dev, control); if (rc == ERR_INVALID_ADDRESS) return -EINVAL; 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, const struct ratbag_button_action *action) { struct ratbag_device *device = button->profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct hidpp20_profile *profile; 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_KEY: type = HIDPP20_BUTTON_HID_TYPE; subtype = HIDPP20_BUTTON_HID_TYPE_KEYBOARD; code = ratbag_hidraw_get_keyboard_usage_from_keycode(device, action->action.key.key); if (code == 0) { subtype = HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL; code = ratbag_hidraw_get_consumer_usage_from_keycode(device, action->action.key.key); if (code == 0) return -EINVAL; } profile->buttons[button->index].subany.type = type; profile->buttons[button->index].subany.subtype = subtype; if (subtype == HIDPP20_BUTTON_HID_TYPE_KEYBOARD) profile->buttons[button->index].keyboard_keys.key = code; else profile->buttons[button->index].consumer_control.consumer_control = code; break; case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: code = hidpp20_onboard_profiles_get_code_from_special(action->action.special); if (code == 0) return -EINVAL; profile->buttons[button->index].special.type = HIDPP20_BUTTON_SPECIAL; profile->buttons[button->index].special.special = code; break; case RATBAG_BUTTON_ACTION_TYPE_MACRO: default: return -ENOTSUP; } return hidpp20_onboard_profiles_write(drv_data->dev, button->profile->index, drv_data->profiles); } static int hidpp20drv_update_button(struct ratbag_button *button, const struct ratbag_button_action *action) { 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, action); if (drv_data->capabilities & HIDPP_CAP_BUTTON_KEY_1b04) return hidpp20drv_write_button_1b04(button, action); return -ENOTSUP; } 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); struct hidpp20_profile *h_profile; struct hidpp20_led *h_led; if (!(drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070)) return -ENOTSUP; 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->hz; /* FIXME: should be 1000 / led->hz, but that would be very small */ h_led->brightness = led->brightness * 100 / 255; return RATBAG_SUCCESS; } static int hidpp20drv_current_profile(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data((struct ratbag_device *)device); int rc; if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)) return 0; rc = hidpp20_onboard_profiles_get_current_profile(drv_data->dev); if (rc < 0) return rc; return rc - 1; } static int hidpp20drv_set_current_profile(struct ratbag_device *device, unsigned int index) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data((struct ratbag_device *)device); struct hidpp20_profile *h_profile; int rc; if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)) return 0; if (index >= drv_data->num_profiles) return -EINVAL; h_profile = &drv_data->profiles->profiles[index]; if (!h_profile->enabled) { rc = hidpp20_onboard_profiles_write(drv_data->dev, index, 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; drv_data->num_resolutions = drv_data->num_sensors; 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; int rc; unsigned int i; if (drv_data->capabilities & HIDPP_CAP_RESOLUTION_2200) { 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; for (i = 0; i < profile->resolution.num_modes; i++) { int dpi = drv_data->sensors[i].dpi; /* FIXME: retrieve the refresh rate */ res = ratbag_resolution_init(profile, i, dpi, dpi, 0); /* FIXME: we mark all resolutions as active because * they are from different sensors */ res->is_active = true; } return 0; } 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; unsigned int index; int dpi = dpi_x; /* dpi_x == dpi_y if we don't have the individual resolution cap */ /* retrieve which resolution is asked to be changed */ index = resolution - profile->resolution.modes; h_profile = &drv_data->profiles->profiles[profile->index]; h_profile->dpi[index] = dpi; 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 rc, i; int dpi = dpi_x; /* dpi_x == dpi_y if we don't have the individual resolution cap */ if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100) return hidpp20drv_update_resolution_dpi_8100(resolution, dpi_x, dpi_y); if (!(drv_data->capabilities & HIDPP_CAP_SWITCHABLE_RESOLUTION_2201)) return -ENOTSUP; if (!drv_data->num_sensors) return -ENOTSUP; /* just for clarity, we use the first available sensor only */ sensor = &drv_data->sensors[0]; /* validate that the sensor accepts the given DPI */ rc = -EINVAL; if (dpi < sensor->dpi_min || dpi > sensor->dpi_max) goto out; if (sensor->dpi_steps) { for (i = sensor->dpi_min; i < dpi; i += sensor->dpi_steps) { } if (i != dpi) goto out; } else { i = 0; while (sensor->dpi_list[i]) { if (sensor->dpi_list[i] == dpi) break; } if (sensor->dpi_list[i] != dpi) goto out; } rc = hidpp20_adjustable_dpi_set_sensor_dpi(drv_data->dev, sensor, dpi); out: return rc; } static int hidpp20drv_read_special_key_mouse(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); int rc; if (!(drv_data->capabilities & HIDPP_CAP_BUTTON_KEY_1b04)) return 0; free(drv_data->controls); drv_data->controls = NULL; drv_data->num_controls = 0; rc = hidpp20_special_key_mouse_get_controls(drv_data->dev, &drv_data->controls); if (rc > 0) { drv_data->num_controls = rc; rc = 0; } return rc; } static int hidpp20drv_read_kbd_reprogrammable_key(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); int rc; if (!(drv_data->capabilities & HIDPP_CAP_KBD_REPROGRAMMABLE_KEYS_1b00)) return 0; free(drv_data->controls); drv_data->controls = NULL; drv_data->num_controls = 0; rc = hidpp20_kbd_reprogrammable_keys_get_controls(drv_data->dev, &drv_data->controls); if (rc > 0) { drv_data->num_controls = rc; rc = 0; } return rc; } static int hidpp20drv_read_color_leds(struct ratbag_device *device) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); int rc; if (!(drv_data->capabilities & HIDPP_CAP_COLOR_LED_EFFECTS_8070)) return 0; free(drv_data->led_infos); drv_data->led_infos = NULL; drv_data->num_leds = 0; rc = hidpp20_color_led_effects_get_zone_infos(drv_data->dev, &drv_data->led_infos); if (rc > 0) { drv_data->num_leds = rc; rc = 0; } return rc; } static int hidpp20drv_read_onboard_profile(struct ratbag_device *device, unsigned index) { struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); int rc; if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)) return 0; rc = hidpp20_onboard_profiles_read(drv_data->dev, index, drv_data->profiles); if (rc < 0) return rc; return 0; } static void hidpp20drv_read_profile_8100(struct ratbag_profile *profile, unsigned int index) { struct ratbag_device *device = profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); struct ratbag_resolution *res; struct hidpp20_profile *p; unsigned i, dpi = 0; hidpp20drv_read_onboard_profile(device, profile->index); profile->is_active = false; if ((int)index == hidpp20drv_current_profile(device)) profile->is_active = true; /* retrieve the resolution through 220X as the profile doesn't has it */ if (profile->is_active) hidpp20drv_read_resolution_dpi(profile); dpi = ratbag_resolution_get_dpi(profile->resolution.modes); p = &drv_data->profiles->profiles[index]; for (i = 0; i < profile->resolution.num_modes; i++) { res = ratbag_resolution_init(profile, i, p->dpi[i], p->dpi[i], p->report_rate); if (profile->is_active && res->dpi_x == dpi) res->is_active = true; if (i == p->default_dpi) res->is_default = true; } } static void hidpp20drv_read_profile(struct ratbag_profile *profile, unsigned int index) { struct ratbag_device *device = profile->device; struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device); if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100) return hidpp20drv_read_profile_8100(profile, index); hidpp20drv_read_resolution_dpi(profile); hidpp20drv_read_special_key_mouse(device); profile->is_active = (index == 0); } 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); 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 */ ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_RESOLUTION); 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; ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_KEY); /* we read the profile once to get the correct number of * supported buttons. */ if (!hidpp20drv_read_special_key_mouse(device)) device->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_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)) device->num_buttons = drv_data->num_controls; break; } case HIDPP_PAGE_ADJUSTABLE_REPORT_RATE: { log_debug(ratbag, "device has adjustable report rate\n"); break; } case HIDPP_PAGE_COLOR_LED_EFFECTS: { log_debug(ratbag, "device has color effects\n"); drv_data->capabilities |= HIDPP_CAP_COLOR_LED_EFFECTS_8070; /* we read the profile once to get the correct number of * supported leds. */ if (hidpp20drv_read_color_leds(device)) return 0; ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_LED); device->num_leds = drv_data->num_leds; break; } case HIDPP_PAGE_ONBOARD_PROFILES: { log_debug(ratbag, "device has onboard profiles\n"); drv_data->capabilities |= HIDPP_CAP_ONBOARD_PROFILES_8100; rc = hidpp20_onboard_profiles_allocate(drv_data->dev, &drv_data->profiles); if (rc < 0) return rc; drv_data->num_profiles = drv_data->profiles->num_profiles; drv_data->num_resolutions = drv_data->profiles->num_modes; drv_data->num_buttons = drv_data->profiles->num_buttons; drv_data->num_leds = drv_data->profiles->num_leds; 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 hidpp20_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; unsigned int i; list_for_each(profile, &device->profiles, link) { if (!profile->dirty) continue; for (i = 0; i < profile->resolution.num_modes; i++) { resolution = &profile->resolution.modes[i]; rc = hidpp20drv_update_resolution_dpi(resolution, resolution->dpi_x, resolution->dpi_y); if (rc) return RATBAG_ERROR_DEVICE; } list_for_each(button, &profile->buttons, link) { struct ratbag_button_action action = button->action; if (!button->dirty) continue; rc = hidpp20drv_update_button(button, &action); if (rc) return RATBAG_ERROR_DEVICE; } list_for_each(led, &profile->leds, link) { if (!led->dirty) continue; rc = hidpp20drv_update_led(led); if (rc) return RATBAG_ERROR_DEVICE; } if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100) hidpp20_onboard_profiles_write(drv_data->dev, profile->index, drv_data->profiles); } 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; } return 0; } static int hidpp20drv_test_hidraw(struct ratbag_device *device) { return ratbag_hidraw_has_report(device, REPORT_ID_LONG); } static void hidpp20_log(void *userdata, enum hidpp_log_priority priority, const char *format, va_list args) { struct ratbag_device *device = userdata; log_msg_va(device->ratbag, priority, format, args); } static void hidpp20drv_remove(struct ratbag_device *device) { struct hidpp20drv_data *drv_data; struct hidpp20_device *dev; if (!device) return; drv_data = ratbag_get_drv_data(device); dev = drv_data->dev; ratbag_close_hidraw(device); if (drv_data->profiles) hidpp20_onboard_profiles_destroy(dev, drv_data->profiles); free(drv_data->controls); free(drv_data->sensors); if (drv_data->dev) hidpp20_device_destroy(drv_data->dev); free(drv_data); } static int hidpp20drv_probe(struct ratbag_device *device) { int rc; struct hidpp20drv_data *drv_data; struct hidpp_device base; struct hidpp20_device *dev; const char *prop; int device_idx = HIDPP_RECEIVER_IDX; int nread = 0; 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.fd); hidpp_device_set_log_handler(&base, hidpp20_log, HIDPP_LOG_PRIORITY_RAW, device); prop = ratbag_device_get_udev_property(device, "RATBAG_HIDPP20_INDEX"); if (prop) { sscanf(prop, "%d%n", &device_idx, &nread); if (!nread || (prop[nread]) != '\0' || device_idx < 0) { log_error(device->ratbag, "Error parsing RATBAG_HIDPP20_INDEX: '%s' for %s\n", prop, device->name); 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 pass a * udev prop RATBAG_HIDPP20_INDEX. */ dev = hidpp20_device_new(&base, device_idx); if (!dev) { rc = -ENODEV; goto err; } 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); /* add some defaults that will be overwritten by the device */ drv_data->num_profiles = 1; drv_data->num_resolutions = 1; drv_data->num_buttons = 8; drv_data->num_leds = 2; rc = hidpp20drv_20_probe(device); if (rc) goto err; ratbag_device_init_profiles(device, drv_data->num_profiles, drv_data->num_resolutions, drv_data->num_buttons, drv_data->num_leds); 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 = hidpp20_commit, .read_profile = hidpp20drv_read_profile, .set_active_profile = hidpp20drv_set_current_profile, .read_button = hidpp20drv_read_button, .read_led = hidpp20drv_read_led, }; libratbag-0.9/src/driver-logitech-g300.c000066400000000000000000000340141311552661500200110ustar00rootroot00000000000000/* * 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 0 #define LOGITECH_G300_REPORT_ID_SET_ACTIVE_PROFILE 0xF0 #define LOGITECH_G300_REPORT_ID_GET_ACTIVE_PROFILE 0xF1 #define LOGITECH_G300_REPORT_ID_SET_ACTIVE_RESOLUTION 0xF0 #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_PROFILE 2 #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; /* 00=off, 01=red, 02=green, 03=yellow, 04=blue, 05=pink, 06=turquoise, 07=white */ uint8_t frequency; /* 00=1000, 01=125, 02=250, 03=500 */ struct logitech_g300_resolution dpi_levels[LOGITECH_G300_NUM_DPI]; uint8_t unknown; /* dpi index for shift, but something else too */ struct logitech_g300_button buttons[LOGITECH_G300_BUTTON_MAX + 1]; } __attribute__((packed)); struct logitech_g300_profile_data { struct logitech_g300_profile_report report; }; struct logitech_g300_data { struct logitech_g300_profile_data profile_data[LOGITECH_G300_PROFILE_MAX + 1]; }; _Static_assert(sizeof(struct logitech_g300_profile_report) == LOGITECH_G300_REPORT_SIZE_PROFILE, "Size of logitech_g300_profile_report is wrong"); struct logitech_g300_button_type_mapping { uint8_t raw; enum ratbag_button_type type; }; static const struct logitech_g300_button_type_mapping logitech_g300_button_type_mapping[] = { { 0, RATBAG_BUTTON_TYPE_LEFT }, { 1, RATBAG_BUTTON_TYPE_RIGHT }, { 2, RATBAG_BUTTON_TYPE_MIDDLE }, { 3, RATBAG_BUTTON_TYPE_THUMB }, { 4, RATBAG_BUTTON_TYPE_THUMB2 }, { 5, RATBAG_BUTTON_TYPE_PINKIE }, { 6, RATBAG_BUTTON_TYPE_PINKIE2 }, { 7, RATBAG_BUTTON_TYPE_PROFILE_CYCLE_UP }, { 8, RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP }, }; static enum ratbag_button_type logitech_g300_raw_to_button_type(uint8_t data) { const struct logitech_g300_button_type_mapping *mapping; ARRAY_FOR_EACH(logitech_g300_button_type_mapping, mapping) { if (mapping->raw == data) return mapping->type; } return RATBAG_BUTTON_TYPE_UNKNOWN; } struct logitech_g300_button_mapping { uint8_t raw; struct ratbag_button_action action; }; static struct logitech_g300_button_mapping logitech_g300_button_mapping[] = { /* 0x00 is either key or unassigned. Must be handled separatly */ { 0x01, BUTTON_ACTION_BUTTON(1) }, { 0x02, BUTTON_ACTION_BUTTON(2) }, { 0x03, BUTTON_ACTION_BUTTON(3) }, { 0x04, BUTTON_ACTION_BUTTON(4) }, { 0x05, BUTTON_ACTION_BUTTON(5) }, { 0x06, BUTTON_ACTION_BUTTON(6) }, { 0x07, BUTTON_ACTION_BUTTON(7) }, { 0x08, BUTTON_ACTION_BUTTON(8) }, { 0x09, BUTTON_ACTION_BUTTON(9) }, { 0x0A, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) }, { 0x0B, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) }, { 0x0C, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) }, { 0x0D, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) }, { 0x0E, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE) }, { 0x0F, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT) }, }; static const struct ratbag_button_action* logitech_g300_raw_to_button_action(uint8_t data) { struct logitech_g300_button_mapping *mapping; ARRAY_FOR_EACH(logitech_g300_button_mapping, mapping) { if (mapping->raw == data) return &mapping->action; } return NULL; } static uint8_t logitech_g300_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; } static int logitech_g300_current_profile(struct ratbag_device *device) { struct logitech_g300_data *drv_data = device->drv_data; uint8_t buf[LOGITECH_G300_REPORT_SIZE_ACTIVE_PROFILE]; int i, ret; ret = ratbag_hidraw_raw_request(device, LOGITECH_G300_REPORT_ID_GET_ACTIVE_PROFILE, buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (ret < 0) return ret; if (ret != sizeof(buf)) return -EIO; /* The current profile is identifed by its LED color code. */ for (i = 0; i <= LOGITECH_G300_PROFILE_MAX; i++) { struct logitech_g300_profile_report report; report = drv_data->profile_data[i].report; if (report.led == buf[1]) return i; } return -EPROTO; } static int logitech_g300_set_current_profile(struct ratbag_device *device, unsigned int index) { uint8_t buf[] = {LOGITECH_G300_REPORT_ID_SET_ACTIVE_PROFILE, 0x80 + index, 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); return ret == sizeof(buf) ? 0 : ret; } #if 0 static int logitech_g300_set_current_resolution(struct ratbag_device *device, unsigned int index) { uint8_t buf[] = {LOGITECH_G300_REPORT_ID_SET_ACTIVE_PROFILE, 0x40 + index*2, 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; } #endif static void logitech_g300_read_profile(struct ratbag_profile *profile, unsigned int index) { 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 i, hz; uint8_t report_id; int rc; assert(index <= LOGITECH_G300_PROFILE_MAX); pdata = &drv_data->profile_data[index]; report = &pdata->report; switch (index) { case 0: report_id = LOGITECH_G300_REPORT_ID_PROFILE_0; break; case 1: report_id = LOGITECH_G300_REPORT_ID_PROFILE_1; break; case 2: report_id = LOGITECH_G300_REPORT_ID_PROFILE_2; break; } rc = ratbag_hidraw_raw_request(device, report_id, (uint8_t*)report, sizeof(*report), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (rc < (signed)sizeof(*report)) { log_error(device->ratbag, "Error while requesting profile: %d\n", rc); return; } hz = logitech_g300_raw_to_frequency(report->frequency); for (i = 0; i < profile->resolution.num_modes; i++) { struct logitech_g300_resolution *res = &report->dpi_levels[i]; resolution = &profile->resolution.modes[i]; resolution->dpi_x = res->dpi * 250; resolution->dpi_y = res->dpi * 250; resolution->hz = hz; resolution->is_default = res->is_default; } } static void logitech_g300_read_button(struct ratbag_button *button) { const struct ratbag_button_action *action; struct ratbag_profile *profile = button->profile; struct ratbag_device *device = profile->device; struct logitech_g300_data *drv_data = device->drv_data; struct logitech_g300_profile_data *pdata; struct logitech_g300_profile_report *profile_report; struct logitech_g300_button *button_report; pdata = &drv_data->profile_data[profile->index]; profile_report = &pdata->report; button_report = &profile_report->buttons[button->index]; ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); button->type = logitech_g300_raw_to_button_type(button->index); action = logitech_g300_raw_to_button_action(button_report->code); if (action) { button->action = *action; } else if (button_report->code == 0x00 && (button_report->modifier > 0x00 || button_report->key > 0x00)) { struct ratbag_button_action *key_action = &button->action; key_action->type = RATBAG_BUTTON_ACTION_TYPE_KEY; key_action->action.key.key = ratbag_hidraw_get_keycode_from_keyboard_usage( device, button_report->key); } } static int logitech_g300_test_hidraw(struct ratbag_device *device) { return ratbag_hidraw_has_report(device, LOGITECH_G300_REPORT_ID_GET_ACTIVE_PROFILE); } static int logitech_g300_probe(struct ratbag_device *device) { int rc; struct ratbag_profile *profile; struct logitech_g300_data *drv_data = NULL; int active_idx; 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_set_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_RESOLUTION); ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE); ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_KEY); active_idx = logitech_g300_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 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_button *button; uint8_t *buf; unsigned int hz, i; int rc; pdata = &drv_data->profile_data[profile->index]; report = &pdata->report; /* The same hz is used for all resolutions */ hz = profile->resolution.modes[0].hz; report->frequency = logitech_g300_frequency_to_raw(hz); for (i = 0; i < profile->resolution.num_modes; i++) { struct ratbag_resolution *resolution = &profile->resolution.modes[i]; struct logitech_g300_resolution *res = &report->dpi_levels[i]; res->dpi = resolution->dpi_x / 250; res->is_default = resolution->is_default; } 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_KEY) { raw_button->key = ratbag_hidraw_get_keyboard_usage_from_keycode( device, action->action.key.key); } } buf = (uint8_t*)report; rc = ratbag_hidraw_raw_request(device, report->id, buf, sizeof(*report), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); return rc; } 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, .read_profile = logitech_g300_read_profile, .commit = logitech_g300_commit, .set_active_profile = logitech_g300_set_current_profile, .read_button = logitech_g300_read_button, }; libratbag-0.9/src/driver-roccat.c000066400000000000000000000557161311552661500170330ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include "libratbag-private.h" #include "libratbag-hidraw.h" #define ROCCAT_PROFILE_MAX 4 #define ROCCAT_BUTTON_MAX 23 #define ROCCAT_NUM_DPI 5 #define ROCCAT_LED_MAX 0 #define ROCCAT_MAX_RETRY_READY 10 #define ROCCAT_REPORT_ID_CONFIGURE_PROFILE 4 #define ROCCAT_REPORT_ID_PROFILE 5 #define ROCCAT_REPORT_ID_SETTINGS 6 #define ROCCAT_REPORT_ID_KEY_MAPPING 7 #define ROCCAT_REPORT_ID_MACRO 8 #define ROCCAT_REPORT_SIZE_PROFILE 77 #define ROCCAT_REPORT_SIZE_SETTINGS 43 #define ROCCAT_REPORT_SIZE_MACRO 2082 #define ROCCAT_CONFIG_SETTINGS 0x80 #define ROCCAT_CONFIG_KEY_MAPPING 0x90 #define ROCCAT_MAX_MACRO_LENGTH 500 struct roccat_settings_report { uint8_t reportID; uint8_t twoB; uint8_t profileID; uint8_t x_y_linked; uint8_t x_sensitivity; /* 0x06 means 0 */ uint8_t y_sensitivity; /* 0x06 means 0 */ uint8_t dpi_mask; uint8_t xres[5]; uint8_t current_dpi; uint8_t yres[5]; uint8_t padding1; uint8_t report_rate; uint8_t padding2[21]; uint16_t checksum; // uint8_t padding2[4]; // uint8_t light; // uint8_t light_heartbit; // uint8_t padding3[5]; } __attribute__((packed)); struct roccat_macro { uint8_t reportID; uint8_t twentytwo; uint8_t height; uint8_t profile; uint8_t button_index; uint8_t active; uint8_t padding[24]; char group[24]; char name[24]; uint16_t length; struct { uint8_t keycode; uint8_t flag; uint16_t time; } keys[ROCCAT_MAX_MACRO_LENGTH]; uint16_t checksum; } __attribute__((packed)); struct roccat_data { uint8_t profiles[(ROCCAT_PROFILE_MAX + 1)][ROCCAT_REPORT_SIZE_PROFILE]; struct roccat_settings_report settings[(ROCCAT_PROFILE_MAX + 1)]; struct roccat_macro macros[(ROCCAT_PROFILE_MAX + 1)][(ROCCAT_BUTTON_MAX + 1)]; }; struct roccat_button_type_mapping { uint8_t raw; enum ratbag_button_type type; }; static const struct roccat_button_type_mapping roccat_button_type_mapping[] = { { 0, RATBAG_BUTTON_TYPE_LEFT }, { 1, RATBAG_BUTTON_TYPE_RIGHT }, { 2, RATBAG_BUTTON_TYPE_MIDDLE }, { 3, RATBAG_BUTTON_TYPE_EXTRA }, { 4, RATBAG_BUTTON_TYPE_SIDE }, { 5, RATBAG_BUTTON_TYPE_WHEEL_LEFT }, { 6, RATBAG_BUTTON_TYPE_WHEEL_RIGHT }, { 7, RATBAG_BUTTON_TYPE_WHEEL_UP }, { 8, RATBAG_BUTTON_TYPE_WHEEL_DOWN }, { 9, RATBAG_BUTTON_TYPE_RESOLUTION_UP }, { 10, RATBAG_BUTTON_TYPE_RESOLUTION_DOWN }, // { 11, RATBAG_BUTTON_TYPE_ }, /* top button above the wheel */ { 12, RATBAG_BUTTON_TYPE_LEFT }, { 13, RATBAG_BUTTON_TYPE_RIGHT }, { 14, RATBAG_BUTTON_TYPE_MIDDLE }, { 15, RATBAG_BUTTON_TYPE_EXTRA }, { 16, RATBAG_BUTTON_TYPE_SIDE }, { 17, RATBAG_BUTTON_TYPE_WHEEL_LEFT }, { 18, RATBAG_BUTTON_TYPE_WHEEL_RIGHT }, { 19, RATBAG_BUTTON_TYPE_WHEEL_UP }, { 20, RATBAG_BUTTON_TYPE_WHEEL_DOWN }, { 21, RATBAG_BUTTON_TYPE_RESOLUTION_UP }, { 22, RATBAG_BUTTON_TYPE_RESOLUTION_DOWN }, // { 23, RATBAG_BUTTON_TYPE_ }, /* top button above the wheel */ }; static enum ratbag_button_type roccat_raw_to_button_type(uint8_t data) { const struct roccat_button_type_mapping *mapping; ARRAY_FOR_EACH(roccat_button_type_mapping, mapping) { if (mapping->raw == data) return mapping->type; } return RATBAG_BUTTON_TYPE_UNKNOWN; } struct roccat_button_mapping { uint8_t raw; struct ratbag_button_action action; }; static struct roccat_button_mapping roccat_button_mapping[] = { /* FIXME: { 0, Disabled }, */ { 1, BUTTON_ACTION_BUTTON(1) }, { 2, BUTTON_ACTION_BUTTON(2) }, { 3, BUTTON_ACTION_BUTTON(3) }, { 4, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_DOUBLECLICK) }, /* FIXME: { 5, Shortcut (modifier + key) }, */ { 6, BUTTON_ACTION_NONE }, { 7, BUTTON_ACTION_BUTTON(4) }, { 8, BUTTON_ACTION_BUTTON(5) }, { 9, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT) }, { 10, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT) }, { 13, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP) }, { 14, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN) }, /* FIXME: { 15, quicklaunch }, -> hidraw report 03 00 60 07 01 00 00 00 */ { 16, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP) }, { 17, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP) }, { 18, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN) }, { 20, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP) }, { 21, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP) }, { 22, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN) }, { 26, BUTTON_ACTION_KEY(KEY_LEFTMETA) }, /* FIXME: { 27, open driver }, -> hidraw report 02 83 01 00 00 00 00 00 */ { 32, BUTTON_ACTION_KEY(KEY_CONFIG) }, { 33, BUTTON_ACTION_KEY(KEY_PREVIOUSSONG) }, { 34, BUTTON_ACTION_KEY(KEY_NEXTSONG) }, { 35, BUTTON_ACTION_KEY(KEY_PLAYPAUSE) }, { 36, BUTTON_ACTION_KEY(KEY_STOPCD) }, { 37, BUTTON_ACTION_KEY(KEY_MUTE) }, { 38, BUTTON_ACTION_KEY(KEY_VOLUMEUP) }, { 39, BUTTON_ACTION_KEY(KEY_VOLUMEDOWN) }, { 48, BUTTON_ACTION_MACRO }, { 65, BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE) }, /* FIXME: { 66, Easywheel sensitivity }, */ /* FIXME: { 67, Easywheel profile }, */ /* FIXME: { 68, Easywheel CPI }, */ /* FIXME: { 81, Other Easyshift }, -> hidraw report 03 00 ff 05 01 00 00 00 */ /* FIXME: { 82, Other Easyshift Lock }, -> hidraw report 03 00 ff 05 01 00 00 00 */ /* FIXME: { 83, Both Easyshift }, -> hidraw report 03 00 ff 04 01 00 00 00 */ }; static const struct ratbag_button_action* roccat_raw_to_button_action(uint8_t data) { struct roccat_button_mapping *mapping; ARRAY_FOR_EACH(roccat_button_mapping, mapping) { if (mapping->raw == data) return &mapping->action; } return NULL; } static uint8_t roccat_button_action_to_raw(const struct ratbag_button_action *action) { struct roccat_button_mapping *mapping; ARRAY_FOR_EACH(roccat_button_mapping, mapping) { if (ratbag_button_action_match(&mapping->action, action)) return mapping->raw; } return 0; } static inline uint16_t roccat_get_unaligned_u16(uint8_t *buf) { return (buf[1] << 8) | buf[0]; } static inline uint16_t roccat_compute_crc(uint8_t *buf, unsigned int len) { unsigned i; uint16_t crc = 0; if (len < 3) return 0; for (i = 0; i < len - 2; i++) { crc += buf[i]; } return crc; } static inline int roccat_crc_is_valid(struct ratbag_device *device, uint8_t *buf, unsigned int len) { uint16_t crc; uint16_t given_crc; if (len < 3) return 0; crc = roccat_compute_crc(buf, len); given_crc = roccat_get_unaligned_u16(&buf[len - 2]); log_raw(device->ratbag, "checksum computed: 0x%04x, checksum given: 0x%04x\n", crc, given_crc); return crc == given_crc; } static int roccat_is_ready(struct ratbag_device *device) { uint8_t buf[3] = { 0 }; int rc; rc = ratbag_hidraw_raw_request(device, ROCCAT_REPORT_ID_CONFIGURE_PROFILE, buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_GET_REPORT); 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_raw_request(device, ROCCAT_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 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_raw_request(device, buf[0], buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); 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_raw_request(device, buf[0], buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); 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 void roccat_read_profile(struct ratbag_profile *profile, unsigned int index) { struct ratbag_device *device = profile->device; struct roccat_data *drv_data; struct ratbag_resolution *resolution; struct roccat_settings_report *setting_report; uint8_t *buf; unsigned int report_rate; unsigned int i; int dpi_x, dpi_y, hz; int rc; assert(index <= ROCCAT_PROFILE_MAX); drv_data = ratbag_get_drv_data(device); setting_report = &drv_data->settings[index]; buf = (uint8_t*)setting_report; roccat_set_config_profile(device, index, ROCCAT_CONFIG_SETTINGS); rc = ratbag_hidraw_raw_request(device, ROCCAT_REPORT_ID_SETTINGS, buf, ROCCAT_REPORT_SIZE_SETTINGS, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); if (rc < ROCCAT_REPORT_SIZE_SETTINGS) return; /* first retrieve the report rate, it is set per profile */ switch (setting_report->report_rate) { case 0x00: report_rate = 125; break; case 0x01: report_rate = 250; break; case 0x02: report_rate = 500; break; case 0x03: report_rate = 1000; break; default: log_error(device->ratbag, "error while reading the report rate of the mouse (0x%02x)\n", buf[26]); report_rate = 0; } for (i = 0; i < profile->resolution.num_modes; i++) { dpi_x = setting_report->xres[i] * 50; dpi_y = setting_report->yres[i] * 50; hz = report_rate; if (!(setting_report->dpi_mask & (1 << i))) { /* the profile is disabled, overwrite it */ dpi_x = 0; dpi_y = 0; hz = 0; } resolution = ratbag_resolution_init(profile, i, dpi_x, dpi_y, hz); ratbag_resolution_set_cap(resolution, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION); resolution->is_active = (i == setting_report->current_dpi); } buf = drv_data->profiles[index]; roccat_set_config_profile(device, index, ROCCAT_CONFIG_KEY_MAPPING); rc = ratbag_hidraw_raw_request(device, ROCCAT_REPORT_ID_KEY_MAPPING, buf, ROCCAT_REPORT_SIZE_PROFILE, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); 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_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_raw_request(device, ROCCAT_REPORT_ID_KEY_MAPPING, buf, ROCCAT_REPORT_SIZE_PROFILE, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); 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) button->action = *action; button->type = roccat_raw_to_button_type(button->index); // if (action == NULL) // log_error(device->ratbag, "button: %d -> %d %s:%d\n", // button->index, drv_data->profiles[button->profile->index][3 + button->index * 3], // __FILE__, __LINE__); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); if (action && action->type == RATBAG_BUTTON_ACTION_TYPE_MACRO) { struct ratbag_button_macro *m = NULL; roccat_set_config_profile(device, button->profile->index, 0); roccat_set_config_profile(device, button->profile->index, button->index); macro = &drv_data->macros[button->profile->index][button->index]; buf = (uint8_t*)macro; buf[0] = ROCCAT_REPORT_ID_MACRO; rc = ratbag_hidraw_raw_request(device, ROCCAT_REPORT_ID_MACRO, buf, ROCCAT_REPORT_SIZE_MACRO, HID_FEATURE_REPORT, HID_REQ_GET_REPORT); 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_raw_request(device, ROCCAT_REPORT_ID_MACRO, buf, ROCCAT_REPORT_SIZE_MACRO, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (rc < 0) return rc; if (rc != ROCCAT_REPORT_SIZE_MACRO) return -EIO; rc = roccat_wait_ready(device); if (rc) log_error(device->ratbag, "Error while waiting for the device to be ready: %s (%d)\n", strerror(-rc), rc); return rc; } static int roccat_write_button(struct ratbag_button *button, const struct ratbag_button_action *action) { struct ratbag_profile *profile = button->profile; struct ratbag_device *device = profile->device; struct roccat_data *drv_data = ratbag_get_drv_data(device); uint8_t rc, *data; data = &drv_data->profiles[profile->index][3 + button->index * 3]; rc = roccat_button_action_to_raw(action); if (!rc) return -EINVAL; *data = rc; rc = roccat_write_profile(button->profile); if (rc) { log_error(device->ratbag, "unable to write the profile to the device: '%s' (%d)\n", strerror(-rc), rc); return rc; } rc = roccat_write_macro(button, action); if (rc) { log_error(device->ratbag, "unable to write the macro to the device: '%s' (%d)\n", strerror(-rc), rc); return rc; } return rc; } static int roccat_write_resolution_dpi(struct ratbag_resolution *resolution, int dpi_x, int dpi_y) { struct ratbag_profile *profile = resolution->profile; struct ratbag_device *device = profile->device; struct roccat_data *drv_data = ratbag_get_drv_data(device); struct roccat_settings_report *settings_report; unsigned int index; uint8_t *buf; int rc; if (dpi_x < 200 || dpi_x > 8200 || dpi_x % 50) return -EINVAL; if (dpi_y < 200 || dpi_y > 8200 || dpi_y % 50) return -EINVAL; settings_report = &drv_data->settings[profile->index]; /* retrieve which resolution is asked to be changed */ index = resolution - profile->resolution.modes; settings_report->xres[index] = dpi_x / 50; settings_report->yres[index] = dpi_y / 50; buf = (uint8_t*)settings_report; settings_report->checksum = roccat_compute_crc(buf, ROCCAT_REPORT_SIZE_SETTINGS); rc = ratbag_hidraw_raw_request(device, ROCCAT_REPORT_ID_SETTINGS, buf, ROCCAT_REPORT_SIZE_SETTINGS, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); 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 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_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_MACROS); active_idx = roccat_current_profile(device); if (active_idx < 0) { log_error(device->ratbag, "Can't talk to the mouse: '%s' (%d)\n", strerror(-active_idx), active_idx); rc = -ENODEV; goto err; } list_for_each(profile, &device->profiles, link) { if (profile->index == (unsigned int)active_idx) { profile->is_active = true; break; } } log_raw(device->ratbag, "'%s' is in profile %d\n", ratbag_device_get_name(device), profile->index); return 0; err: free(drv_data); ratbag_set_drv_data(device, NULL); return rc; } static void roccat_remove(struct ratbag_device *device) { ratbag_close_hidraw(device); free(ratbag_get_drv_data(device)); } struct ratbag_driver roccat_driver = { .name = "Roccat Kone XTD", .id = "roccat", .probe = roccat_probe, .remove = roccat_remove, .read_profile = roccat_read_profile, .write_profile = roccat_write_profile, .set_active_profile = roccat_set_current_profile, .read_button = roccat_read_button, .write_button = roccat_write_button, .write_resolution_dpi = roccat_write_resolution_dpi, }; libratbag-0.9/src/driver-test.c000066400000000000000000000146421311552661500165300ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "libratbag-private.h" #include "libratbag-test.h" static void test_read_profile(struct ratbag_profile *profile, unsigned int index) { struct ratbag_test_device *d = ratbag_get_drv_data(profile->device); struct ratbag_test_profile *p; struct ratbag_test_resolution *r; unsigned int i; assert(index < d->num_profiles); p = &d->profiles[index]; for (i = 0; i < d->num_resolutions; i++) { struct ratbag_resolution *res; r = &p->resolutions[i]; res = ratbag_resolution_init(profile, i, r->xres, r->yres, r->hz); res->is_active = r->active; res->is_default = r->dflt; res->capabilities = r->caps; res->hz = r->hz; assert(res); } profile->is_active = p->active; } static int test_write_profile(struct ratbag_profile *profile) { /* check if the device is still valid */ assert(ratbag_get_drv_data(profile->device) != NULL); return 0; } static int test_set_active_profile(struct ratbag_device *device, unsigned int index) { struct ratbag_test_device *d = ratbag_get_drv_data(device); /* check if the device is still valid */ assert(d != NULL); assert(index < d->num_profiles); return 0; } static void test_read_button(struct ratbag_button *button) { struct ratbag_device *device = button->profile->device; struct ratbag_test_device *d = ratbag_get_drv_data(device); struct ratbag_test_profile *p = &d->profiles[button->profile->index]; struct ratbag_button_macro *m; const char data[] = "TEST"; const char *c; switch (p->buttons[button->index].type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: button->action.type = RATBAG_BUTTON_ACTION_TYPE_BUTTON; button->action.action.button = p->buttons[button->index].button; break; case RATBAG_BUTTON_ACTION_TYPE_KEY: button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; button->action.action.key.key = p->buttons[button->index].key; break; case RATBAG_BUTTON_ACTION_TYPE_MACRO: button->action.type = RATBAG_BUTTON_ACTION_TYPE_MACRO; m = ratbag_button_macro_new("test macro"); ARRAY_FOR_EACH(data, c) { char str[6]; snprintf_safe(str, 6, "KEY_%c", *c); ratbag_button_macro_set_event(m, 0, RATBAG_MACRO_EVENT_KEY_PRESSED, libevdev_event_code_from_name(EV_KEY, str)); ratbag_button_macro_set_event(m, 0, RATBAG_MACRO_EVENT_KEY_RELEASED, libevdev_event_code_from_name(EV_KEY, str)); } ratbag_button_copy_macro(button, m); ratbag_button_macro_unref(m); break; default: button->action.type = RATBAG_BUTTON_ACTION_TYPE_UNKNOWN; } ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_BUTTON); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_KEY); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_SPECIAL); ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); } static void test_read_led(struct ratbag_led *led) { struct ratbag_device *device = led->profile->device; struct ratbag_test_device *d = ratbag_get_drv_data(device); struct ratbag_test_profile *p = &d->profiles[led->profile->index]; struct ratbag_test_led t_led = p->leds[led->index]; switch (t_led.mode) { case RATBAG_LED_ON: led->mode = RATBAG_LED_ON; break; case RATBAG_LED_CYCLE: led->mode = RATBAG_LED_CYCLE; break; case RATBAG_LED_BREATHING: led->mode = RATBAG_LED_BREATHING; break; default: led->mode = RATBAG_LED_OFF; } led->color.red = t_led.color.red; led->color.green = t_led.color.green; led->color.blue = t_led.color.blue; led->hz = t_led.hz; led->brightness = t_led.brightness; } static int test_write_button(struct ratbag_button *button, const struct ratbag_button_action *action) { /* check if the device is still valid */ assert(ratbag_get_drv_data(button->profile->device) != NULL); return 0; } static int test_write_led(struct ratbag_led *led, enum ratbag_led_mode mode, struct ratbag_color color, unsigned int hz, unsigned int brightness) { /* check if the device is still valid */ assert(ratbag_get_drv_data(led->profile->device) != NULL); return 0; } static int test_fake_probe(struct ratbag_device *device) { return -ENODEV; } static int test_probe(struct ratbag_device *device, void *data) { struct ratbag_test_device *test_device = data; ratbag_set_drv_data(device, test_device); ratbag_device_init_profiles(device, test_device->num_profiles, test_device->num_resolutions, test_device->num_buttons, test_device->num_leds); if (test_device->num_profiles > 1) ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_MACROS); return 0; } static void test_remove(struct ratbag_device *device) { struct ratbag_test_device *d = ratbag_get_drv_data(device); /* remove must be called only once */ assert(d != NULL); if (d->destroyed) d->destroyed(device, d->destroyed_data); ratbag_set_drv_data(device, NULL); } struct ratbag_driver test_driver = { .name = "Test driver", .id = "test_driver", .probe = test_fake_probe, .test_probe = test_probe, .remove = test_remove, .read_profile = test_read_profile, .write_profile = test_write_profile, .set_active_profile = test_set_active_profile, .read_button = test_read_button, .read_led = test_read_led, .write_button = test_write_button, .write_resolution_dpi = NULL, .write_led = test_write_led, }; libratbag-0.9/src/hidpp-generic.c000066400000000000000000000266561311552661500170060ustar00rootroot00000000000000/* * HID++ generic definitions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /* * Based on the HID++ documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #include "config.h" #include "hidpp-generic.h" #include #include #include #include "libratbag-private.h" const char *hidpp_errors[0xFF] = { [0x00] = "ERR_SUCCESS", [0x01] = "ERR_INVALID_SUBID", [0x02] = "ERR_INVALID_ADDRESS", [0x03] = "ERR_INVALID_VALUE", [0x04] = "ERR_CONNECT_FAIL", [0x05] = "ERR_TOO_MANY_DEVICES", [0x06] = "ERR_ALREADY_EXISTS", [0x07] = "ERR_BUSY", [0x08] = "ERR_UNKNOWN_DEVICE", [0x09] = "ERR_RESOURCE_ERROR", [0x0A] = "ERR_REQUEST_UNAVAILABLE", [0x0B] = "ERR_INVALID_PARAM_VALUE", [0x0C] = "ERR_WRONG_PIN_CODE", [0x0D ... 0xFE] = NULL, }; struct hidpp20_1b04_action_mapping { uint16_t value; const char *name; struct ratbag_button_action action; }; #define BUTTON_ACTION_MAPPING(v_, n_, act_) \ { v_, n_, act_ } static const struct hidpp20_1b04_action_mapping hidpp20_1b04_logical_mapping[] = { BUTTON_ACTION_MAPPING(0, "None" , BUTTON_ACTION_NONE), BUTTON_ACTION_MAPPING(1, "Volume Up" , BUTTON_ACTION_KEY(KEY_VOLUMEUP)), BUTTON_ACTION_MAPPING(2, "Volume Down" , BUTTON_ACTION_KEY(KEY_VOLUMEDOWN)), BUTTON_ACTION_MAPPING(3, "Mute" , BUTTON_ACTION_KEY(KEY_MUTE)), BUTTON_ACTION_MAPPING(4, "Play/Pause" , BUTTON_ACTION_KEY(KEY_PLAYPAUSE)), BUTTON_ACTION_MAPPING(5, "Next" , BUTTON_ACTION_KEY(KEY_NEXTSONG)), BUTTON_ACTION_MAPPING(6, "Previous" , BUTTON_ACTION_KEY(KEY_PREVIOUSSONG)), BUTTON_ACTION_MAPPING(7, "Stop" , BUTTON_ACTION_KEY(KEY_STOPCD)), BUTTON_ACTION_MAPPING(80, "Left" , BUTTON_ACTION_BUTTON(1)), BUTTON_ACTION_MAPPING(81, "Right" , BUTTON_ACTION_BUTTON(2)), BUTTON_ACTION_MAPPING(82, "Middle" , BUTTON_ACTION_BUTTON(3)), BUTTON_ACTION_MAPPING(83, "Back" , BUTTON_ACTION_BUTTON(4)), BUTTON_ACTION_MAPPING(86, "Forward" , BUTTON_ACTION_BUTTON(5)), BUTTON_ACTION_MAPPING(89, "Button 6" , BUTTON_ACTION_BUTTON(6)), BUTTON_ACTION_MAPPING(90, "Button 7" , BUTTON_ACTION_BUTTON(7)), BUTTON_ACTION_MAPPING(91, "Left Scroll" , BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT)), BUTTON_ACTION_MAPPING(92, "Button 8" , BUTTON_ACTION_BUTTON(8)), BUTTON_ACTION_MAPPING(93, "Right Scroll" , BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT)), BUTTON_ACTION_MAPPING(94, "Button 9" , BUTTON_ACTION_BUTTON(9)), BUTTON_ACTION_MAPPING(95, "Button 10" , BUTTON_ACTION_BUTTON(10)), BUTTON_ACTION_MAPPING(96, "Button 11" , BUTTON_ACTION_BUTTON(11)), BUTTON_ACTION_MAPPING(97, "Button 12" , BUTTON_ACTION_BUTTON(12)), BUTTON_ACTION_MAPPING(98, "Button 13" , BUTTON_ACTION_BUTTON(13)), BUTTON_ACTION_MAPPING(99, "Button 14" , BUTTON_ACTION_BUTTON(14)), BUTTON_ACTION_MAPPING(100, "Button 15" , BUTTON_ACTION_BUTTON(15)), BUTTON_ACTION_MAPPING(101, "Button 16" , BUTTON_ACTION_BUTTON(16)), BUTTON_ACTION_MAPPING(102, "Button 17" , BUTTON_ACTION_BUTTON(17)), BUTTON_ACTION_MAPPING(103, "Button 18" , BUTTON_ACTION_BUTTON(18)), BUTTON_ACTION_MAPPING(104, "Button 19" , BUTTON_ACTION_BUTTON(19)), BUTTON_ACTION_MAPPING(105, "Button 20" , BUTTON_ACTION_BUTTON(20)), BUTTON_ACTION_MAPPING(106, "Button 21" , BUTTON_ACTION_BUTTON(21)), BUTTON_ACTION_MAPPING(107, "Button 22" , BUTTON_ACTION_BUTTON(22)), BUTTON_ACTION_MAPPING(108, "Button 23" , BUTTON_ACTION_BUTTON(23)), BUTTON_ACTION_MAPPING(109, "Button 24" , BUTTON_ACTION_BUTTON(24)), BUTTON_ACTION_MAPPING(184, "Second Left" , BUTTON_ACTION_BUTTON(1)), BUTTON_ACTION_MAPPING(195, "AppSwitchGesture" , BUTTON_ACTION_NONE), BUTTON_ACTION_MAPPING(196, "SmartShift" , BUTTON_ACTION_SPECIAL(RATBAG_BUTTON_ACTION_SPECIAL_RATCHET_MODE_SWITCH)), BUTTON_ACTION_MAPPING(315, "LedToggle" , BUTTON_ACTION_NONE), }; struct hidpp20_1b04_physical_mapping { uint16_t value; const char *name; enum ratbag_button_type type; }; #define BUTTON_PHYS_MAPPING(v_, n_, t_) \ { v_, n_, t_ } static const struct hidpp20_1b04_physical_mapping hidpp20_1b04_physical_mapping[] = { BUTTON_PHYS_MAPPING(0, "None" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(1, "Volume Up" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(2, "Volume Down" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(3, "Mute" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(4, "Play/Pause" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(5, "Next" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(6, "Previous" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(7, "Stop" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(56, "Left Click" , RATBAG_BUTTON_TYPE_LEFT), BUTTON_PHYS_MAPPING(57, "Right Click" , RATBAG_BUTTON_TYPE_RIGHT), BUTTON_PHYS_MAPPING(58, "Middle Click" , RATBAG_BUTTON_TYPE_MIDDLE), BUTTON_PHYS_MAPPING(59, "Wheel Side Click Left" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(60, "Back Click" , RATBAG_BUTTON_TYPE_SIDE), BUTTON_PHYS_MAPPING(61, "Wheel Side Click Right", RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(62, "Forward Click" , RATBAG_BUTTON_TYPE_EXTRA), BUTTON_PHYS_MAPPING(63, "Left Scroll" , RATBAG_BUTTON_TYPE_WHEEL_LEFT), BUTTON_PHYS_MAPPING(64, "Right Scroll" , RATBAG_BUTTON_TYPE_WHEEL_RIGHT), BUTTON_PHYS_MAPPING(98, "Do Nothing" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(156, "Gesture Button" , RATBAG_BUTTON_TYPE_UNKNOWN), BUTTON_PHYS_MAPPING(157, "SmartShift" , RATBAG_BUTTON_TYPE_WHEEL_RATCHET_MODE_SHIFT), BUTTON_PHYS_MAPPING(221, "LedToggle" , RATBAG_BUTTON_TYPE_UNKNOWN), }; struct hidpp20_8070_location_mapping { uint16_t value; const char *name; enum ratbag_led_type type; }; #define LED_LOC_MAPPING(v_, n_, t_) \ { v_, n_, t_ } static const struct hidpp20_8070_location_mapping hidpp20_8070_location_mapping[] = { LED_LOC_MAPPING(0, "None", RATBAG_LED_TYPE_UNKNOWN), LED_LOC_MAPPING(1, "Logo LED", RATBAG_LED_TYPE_LOGO), LED_LOC_MAPPING(2, "Side LED", RATBAG_LED_TYPE_SIDE), }; const struct ratbag_button_action * hidpp20_1b04_get_logical_mapping(uint16_t value) { const struct hidpp20_1b04_action_mapping *map; ARRAY_FOR_EACH(hidpp20_1b04_logical_mapping, map) { if (map->value == value) return &map->action; } return NULL; } uint16_t hidpp20_1b04_get_logical_control_id(const struct ratbag_button_action *action) { const struct hidpp20_1b04_action_mapping *mapping; ARRAY_FOR_EACH(hidpp20_1b04_logical_mapping, mapping) { if (ratbag_button_action_match(&mapping->action, action)) return mapping->value; } return 0; } const char * hidpp20_1b04_get_logical_mapping_name(uint16_t value) { const struct hidpp20_1b04_action_mapping *mapping; ARRAY_FOR_EACH(hidpp20_1b04_logical_mapping, mapping) { if (mapping->value == value) return mapping->name; } return "UNKNOWN"; } enum ratbag_button_type hidpp20_1b04_get_physical_mapping(uint16_t value) { const struct hidpp20_1b04_physical_mapping *map; ARRAY_FOR_EACH(hidpp20_1b04_physical_mapping, map) { if (map->value == value) return map->type; } return RATBAG_BUTTON_TYPE_UNKNOWN; } const char * hidpp20_1b04_get_physical_mapping_name(uint16_t value) { const struct hidpp20_1b04_physical_mapping *map; ARRAY_FOR_EACH(hidpp20_1b04_physical_mapping, map) { if (map->value == value) return map->name; } return "UNKNOWN"; } enum ratbag_led_type hidpp20_8070_get_location_mapping(uint16_t value) { const struct hidpp20_8070_location_mapping *map; ARRAY_FOR_EACH(hidpp20_8070_location_mapping, map) { if (map->value == value) return map->type; } return RATBAG_LED_TYPE_UNKNOWN; } const char * hidpp20_8070_get_location_mapping_name(uint16_t value) { const struct hidpp20_8070_location_mapping *map; ARRAY_FOR_EACH(hidpp20_8070_location_mapping, map) { if (map->value == value) return map->name; } return "UNKNOWN"; } int hidpp_write_command(struct hidpp_device *dev, uint8_t *cmd, int size) { int fd = dev->hidraw_fd; int res; if (size < 1 || !cmd || fd < 0) return -EINVAL; hidpp_log_buf_raw(dev, "hidpp write: ", cmd, size); res = write(fd, cmd, size); if (res < 0) { res = -errno; hidpp_log_error(dev, "Error: %s (%d)\n", strerror(-res), -res); } return res < 0 ? res : 0; } int hidpp_read_response(struct hidpp_device *dev, uint8_t *buf, size_t size) { int fd = dev->hidraw_fd; struct pollfd fds; int rc; if (size < 1 || !buf || fd < 0) return -EINVAL; fds.fd = fd; fds.events = POLLIN; rc = poll(&fds, 1, 1000); if (rc == -1) return -errno; if (rc == 0) return -ETIMEDOUT; rc = read(fd, buf, size); if (rc > 0) hidpp_log_buf_raw(dev, "hidpp read: ", buf, rc); return rc >= 0 ? rc : -errno; } void hidpp_log(struct hidpp_device *dev, enum hidpp_log_priority priority, const char *format, ...) { va_list args; if (dev->log_priority > priority) return; va_start(args, format); dev->log_handler(dev->userdata, priority, format, args); va_end(args); } void hidpp_log_buffer(struct hidpp_device *dev, enum hidpp_log_priority priority, const char *header, uint8_t *buf, size_t len) { _cleanup_free_ char *output_buf = NULL; char *sep = ""; unsigned int i, n; unsigned int buf_len; buf_len = header ? strlen(header) : 0; buf_len += len * 3; buf_len += 1; /* terminating '\0' */ output_buf = zalloc(buf_len); n = 0; if (header) n += snprintf_safe(output_buf, buf_len - n, "%s", header); for (i = 0; i < len; ++i) { n += snprintf_safe(&output_buf[n], buf_len - n, "%s%02x", sep, buf[i] & 0xFF); sep = " "; } hidpp_log(dev, priority, "%s\n", output_buf); } static void simple_log(void *userdata, enum hidpp_log_priority priority, const char *format, va_list args) { vprintf(format, args); } void hidpp_device_init(struct hidpp_device *dev, int fd) { dev->hidraw_fd = fd; hidpp_device_set_log_handler(dev, simple_log, HIDPP_LOG_PRIORITY_INFO, NULL); } void hidpp_device_set_log_handler(struct hidpp_device *dev, hidpp_log_handler log_handler, enum hidpp_log_priority priority, void *userdata) { dev->log_handler = log_handler; dev->log_priority = priority; dev->userdata = userdata; } /* * The following crc computation has been provided by Logitech */ #define CRC_CCITT_SEED 0xFFFF uint16_t hidpp_crc_ccitt(uint8_t *data, unsigned int length) { uint16_t crc, temp, quick; unsigned int i; crc = CRC_CCITT_SEED; for (i = 0; i < length; i++) { temp = (crc >> 8) ^ (*data++); crc <<= 8; quick = temp ^ (temp >> 4); crc ^= quick; quick <<= 5; crc ^= quick; quick <<= 7; crc ^= quick; } return crc; } libratbag-0.9/src/hidpp-generic.h000066400000000000000000000142041311552661500167750ustar00rootroot00000000000000/* * HID++ generic definitions * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /* * Based on the HID++ documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #pragma once #include #include #include #define HIDPP_RECEIVER_IDX 0xFF #define HIDPP_WIRED_DEVICE_IDX 0x00 #define REPORT_ID_SHORT 0x10 #define REPORT_ID_LONG 0x11 #define SHORT_MESSAGE_LENGTH 7 #define LONG_MESSAGE_LENGTH 20 #define SET_REGISTER_REQ 0x80 #define SET_REGISTER_RSP 0x80 #define GET_REGISTER_REQ 0x81 #define GET_REGISTER_RSP 0x81 #define SET_LONG_REGISTER_REQ 0x82 #define SET_LONG_REGISTER_RSP 0x82 #define GET_LONG_REGISTER_REQ 0x83 #define GET_LONG_REGISTER_RSP 0x83 #define __ERROR_MSG 0x8F #define ERR_SUCCESS 0x00 #define ERR_INVALID_SUBID 0x01 #define ERR_INVALID_ADDRESS 0x02 #define ERR_INVALID_VALUE 0x03 #define ERR_CONNECT_FAIL 0x04 #define ERR_TOO_MANY_DEVICES 0x05 #define ERR_ALREADY_EXISTS 0x06 #define ERR_BUSY 0x07 #define ERR_UNKNOWN_DEVICE 0x08 #define ERR_RESOURCE_ERROR 0x09 #define ERR_REQUEST_UNAVAILABLE 0x0A #define ERR_INVALID_PARAM_VALUE 0x0B #define ERR_WRONG_PIN_CODE 0x0C /* Keep this in sync with ratbag_log_priority */ enum hidpp_log_priority { /** * Raw protocol messages. Using this log level results in *a lot* of * output. */ HIDPP_LOG_PRIORITY_RAW = 10, HIDPP_LOG_PRIORITY_DEBUG = 20, HIDPP_LOG_PRIORITY_INFO = 30, HIDPP_LOG_PRIORITY_ERROR = 40, }; typedef void (*hidpp_log_handler)(void *userdata, enum hidpp_log_priority priority, const char *format, va_list args) __attribute__ ((format (printf, 3, 0))); struct hidpp_device { int hidraw_fd; void *userdata; hidpp_log_handler log_handler; enum hidpp_log_priority log_priority; }; void hidpp_device_init(struct hidpp_device *dev, int fd); void hidpp_device_set_log_handler(struct hidpp_device *dev, hidpp_log_handler log_handler, enum hidpp_log_priority priority, void *userdata); extern const char *hidpp_errors[0xFF]; const char * hidpp20_1b04_get_physical_mapping_name(uint16_t value); enum ratbag_button_type hidpp20_1b04_get_physical_mapping(uint16_t value); enum ratbag_led_type hidpp20_8070_get_location_mapping(uint16_t value); const char * hidpp20_8070_get_location_mapping_name(uint16_t value); const char * hidpp20_1b04_get_logical_mapping_name(uint16_t value); const struct ratbag_button_action * hidpp20_1b04_get_logical_mapping(uint16_t value); uint16_t hidpp20_1b04_get_logical_control_id(const struct ratbag_button_action *action); int hidpp_write_command(struct hidpp_device *dev, uint8_t *cmd, int size); int hidpp_read_response(struct hidpp_device *dev, uint8_t *buf, size_t size); void hidpp_log(struct hidpp_device *dev, enum hidpp_log_priority priority, const char *format, ...) __attribute__ ((format (printf, 3, 4))); void hidpp_log_buffer(struct hidpp_device *dev, enum hidpp_log_priority priority, const char *header, uint8_t *buf, size_t len); #define hidpp_log_raw(li_, ...) hidpp_log((li_), HIDPP_LOG_PRIORITY_RAW, __VA_ARGS__) #define hidpp_log_debug(li_, ...) hidpp_log((li_), HIDPP_LOG_PRIORITY_DEBUG, __VA_ARGS__) #define hidpp_log_info(li_, ...) hidpp_log((li_), HIDPP_LOG_PRIORITY_INFO, __VA_ARGS__) #define hidpp_log_error(li_, ...) hidpp_log((li_), HIDPP_LOG_PRIORITY_ERROR, __VA_ARGS__) #define hidpp_log_bug_kernel(li_, ...) hidpp((li_), HIDPP_LOG_PRIORITY_ERROR, "kernel bug: " __VA_ARGS__) #define hidpp_log_buf_raw(li_, h_, buf_, len_) hidpp_log_buffer(li_, HIDPP_LOG_PRIORITY_RAW, h_, buf_, len_) #define hidpp_log_buf_debug(li_, h_, buf_, len_) hidpp_log_buffer(li_, HIDPP_LOG_PRIORITY_DEBUG, h_, buf_, len_) #define hidpp_log_buf_info(li_, h_, buf_, len_) hidpp_log_buffer(li_, HIDPP_LOG_PRIORITY_INFO, h_, buf_, len_) #define hidpp_log_buf_error(li_, h_, buf_, len_) hidpp_log_buffer(li_, HIDPP_LOG_PRIORITY_ERROR, h_, buf_, len_) uint16_t hidpp_crc_ccitt(uint8_t *data, unsigned int length); static inline uint16_t hidpp_get_unaligned_be_u16(uint8_t *buf) { return (buf[0] << 8) | buf[1]; } static inline uint16_t hidpp_be_u16_to_cpu(uint16_t data) { return hidpp_get_unaligned_be_u16((uint8_t *)&data); } static inline void hidpp_set_unaligned_be_u16(uint8_t *buf, uint16_t value) { buf[0] = value >> 8; buf[1] = value & 0xFF; } static inline uint16_t hidpp_cpu_to_be_u16(uint16_t data) { uint16_t result; hidpp_set_unaligned_be_u16((uint8_t *)&result, data); return result; } static inline uint16_t hidpp_get_unaligned_le_u16(uint8_t *buf) { return (buf[1] << 8) | buf[0]; } static inline uint16_t hidpp_le_u16_to_cpu(uint16_t data) { return hidpp_get_unaligned_le_u16((uint8_t *)&data); } static inline void hidpp_set_unaligned_le_u16(uint8_t *buf, uint16_t value) { buf[0] = value & 0xFF; buf[1] = value >> 8; } static inline uint16_t hidpp_cpu_to_le_u16(uint16_t data) { uint16_t result; hidpp_set_unaligned_le_u16((uint8_t *)&result, data); return result; } static inline uint32_t hidpp_get_unaligned_be_u32(uint8_t *buf) { return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; } libratbag-0.9/src/hidpp10.c000066400000000000000000002227171311552661500155310ustar00rootroot00000000000000/* * HID++ 1.0 library. * * Copyright 2013-2015 Benjamin Tissoires * Copyright 2013-2015 Red Hat, Inc * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /* * Based on the HID++ 1.0 documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #include "config.h" #include #include #include #include #include #include #include #include #include "hidpp10.h" #include "libratbag-util.h" #include "libratbag-hidraw.h" struct _hidpp10_message { uint8_t report_id; uint8_t device_idx; uint8_t sub_id; uint8_t address; union { uint8_t parameters[3]; uint8_t string[16]; }; } __attribute__((packed)); union hidpp10_message { struct _hidpp10_message msg; uint8_t data[LONG_MESSAGE_LENGTH]; }; #define ERROR_MSG(__hidpp_msg, idx) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = __ERROR_MSG, \ .address = __hidpp_msg->msg.sub_id, \ .parameters = {__hidpp_msg->msg.address, 0x00, 0x00 }, \ } \ } const char *device_types[0xFF] = { [0x00] = "Unknown", [0x01] = "Keyboard", [0x02] = "Mouse", [0x03] = "Numpad", [0x04] = "Presenter", [0x05] = "Reserved for future", [0x06] = "Reserved for future", [0x07] = "Reserved for future", [0x08] = "Trackball", [0x09] = "Touchpad", [0x0A ... 0xFE] = NULL, }; int hidpp10_build_dpi_table_from_list(struct hidpp10_device *dev, const char *str_list) { unsigned int i, count, index; int nread, dpi = 0; char c; /* * str_list is in the form: * "0;200;400;600;800;1000;1200" */ /* first, count how many elements do we have in the table */ count = 1; i = 0; while (str_list[i] != 0) { c = str_list[i++]; if (c == ';') count++; } index = 0; /* check that the max raw value fits in a uint8_t */ if (count + 0x80 - 1> 0xff) return -EINVAL; dev->dpi_count = count; dev->dpi_table = zalloc(count * sizeof(*dev->dpi_table)); while (*str_list != 0 && index < count) { if (*str_list == ';') { str_list++; continue; } nread = 0; sscanf(str_list, "%d%n", &dpi, &nread); if (!nread || dpi < 0) goto err; dev->dpi_table[index].raw_value = index + 0x80; dev->dpi_table[index].dpi = dpi; str_list += nread; index++; } if (index != count) goto err; /* mark the state as invalid */ dev->profile_count = 0; return 0; err: dev->dpi_count = 0; free(dev->dpi_table); dev->dpi_table = NULL; return -EINVAL; } int hidpp10_build_dpi_table_from_dpi_info(struct hidpp10_device *dev, const char *str_dpi) { float min, max, step; unsigned raw_max, i; int rc; /* * str_list is in the form: * "MIN:MAX@STEP" */ rc = sscanf(str_dpi, "%f:%f@%f", &min, &max, &step); if (rc != 3) return -EINVAL; raw_max = (max - min) / step; if (raw_max > 0xff) return -EINVAL; dev->dpi_count = raw_max + 1; dev->dpi_table = zalloc((raw_max + 1) * sizeof(*dev->dpi_table)); for (i = 1; i <= raw_max; i++) { dev->dpi_table[i].raw_value = i; dev->dpi_table[i].dpi = round((min + step * i) / 25.0f) * 25; } /* mark the state as invalid */ dev->profile_count = 0; return 0; } static int hidpp10_request_command(struct hidpp10_device *dev, union hidpp10_message *msg) { union hidpp10_message read_buffer; union hidpp10_message expected_header; union hidpp10_message expected_error_dev = ERROR_MSG(msg, msg->msg.device_idx); int ret; uint8_t hidpp_err = 0; int command_size; switch (msg->msg.report_id) { case REPORT_ID_SHORT: command_size = SHORT_MESSAGE_LENGTH; break; case REPORT_ID_LONG: command_size = LONG_MESSAGE_LENGTH; break; default: abort(); } /* create the expected header */ expected_header = *msg; /* response message length doesn't depend on request length */ hidpp_log_buf_raw(&dev->base, " expected_header: ?? ", &expected_header.data[1], 3); hidpp_log_buf_raw(&dev->base, " expected_error_dev: ", expected_error_dev.data, SHORT_MESSAGE_LENGTH); /* Send the message to the Device */ ret = hidpp_write_command(&dev->base, msg->data, command_size); if (ret) goto out_err; /* * Now read the answers from the device: * loop until we get the actual answer or an error code. */ do { ret = hidpp_read_response(&dev->base, read_buffer.data, LONG_MESSAGE_LENGTH); /* Wait and retry if the USB timed out */ if (ret == -ETIMEDOUT) { msleep(10); ret = hidpp_read_response(&dev->base, read_buffer.data, LONG_MESSAGE_LENGTH); } /* Overwrite the return device index with ours. The kernel * sets our device index on write, but gives us the real * device index on reply. Overwrite it with our index so the * messages are easier to check and compare. */ read_buffer.msg.device_idx = msg->msg.device_idx; /* actual answer */ if (!memcmp(&read_buffer.data[1], &expected_header.data[1], 3)) break; /* error */ if (!memcmp(read_buffer.data, expected_error_dev.data, 5)) { hidpp_err = read_buffer.msg.parameters[1]; hidpp_log_raw(&dev->base, " HID++ error from the %s (%d): %s (%02x)\n", read_buffer.msg.device_idx == HIDPP_RECEIVER_IDX ? "receiver" : "device", read_buffer.msg.device_idx, hidpp_errors[hidpp_err] ? hidpp_errors[hidpp_err] : "Undocumented error code", hidpp_err); break; } } while (ret > 0); if (ret < 0) { hidpp_log_error(&dev->base, " USB error: %s (%d)\n", strerror(-ret), -ret); perror("write"); goto out_err; } if (!hidpp_err) { hidpp_log_buf_raw(&dev->base, " received: ", read_buffer.data, ret); /* copy the answer for the caller */ *msg = read_buffer; } ret = hidpp_err; out_err: return ret; } /* -------------------------------------------------------------------------- */ /* HID++ 1.0 commands 10 */ /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* 0x00: Enable HID++ Notifications */ /* -------------------------------------------------------------------------- */ #define __CMD_HIDPP_NOTIFICATIONS 0x00 #define CMD_HIDPP_NOTIFICATIONS(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_HIDPP_NOTIFICATIONS, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_hidpp_notifications(struct hidpp10_device *dev, uint32_t *reporting_flags) { unsigned idx = dev->index; union hidpp10_message notifications = CMD_HIDPP_NOTIFICATIONS(idx, GET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Fetching HID++ notifications\n"); res = hidpp10_request_command(dev, ¬ifications); if (res) return res; *reporting_flags = notifications.msg.parameters[0]; *reporting_flags |= (notifications.msg.parameters[1] & 0x1F) << 8; *reporting_flags |= (notifications.msg.parameters[2] & 0x7 ) << 16; return res; } int hidpp10_set_hidpp_notifications(struct hidpp10_device *dev, uint32_t reporting_flags) { unsigned idx = dev->index; union hidpp10_message notifications = CMD_HIDPP_NOTIFICATIONS(idx, SET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Setting HID++ notifications\n"); notifications.msg.parameters[0] = reporting_flags & 0xFF; notifications.msg.parameters[1] = (reporting_flags >> 8) & 0x1F; notifications.msg.parameters[2] = (reporting_flags >> 16) & 0x7; res = hidpp10_request_command(dev, ¬ifications); return res; } /* -------------------------------------------------------------------------- */ /* 0x01: Enable Individual Features */ /* -------------------------------------------------------------------------- */ #define __CMD_ENABLE_INDIVIDUAL_FEATURES 0x01 #define CMD_ENABLE_INDIVIDUAL_FEATURES(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_ENABLE_INDIVIDUAL_FEATURES, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_individual_features(struct hidpp10_device *dev, uint32_t *feature_mask) { unsigned idx = dev->index; union hidpp10_message features = CMD_ENABLE_INDIVIDUAL_FEATURES(idx, GET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Fetching individual features\n"); res = hidpp10_request_command(dev, &features); if (res) return res; *feature_mask = features.msg.parameters[0]; /* bits 0 and 4-7 are reserved */ *feature_mask |= (features.msg.parameters[1] & 0x0E) << 8; /* bits 6-7 are reserved */ *feature_mask |= (features.msg.parameters[2] & 0x3F) << 16; return 0; } int hidpp10_set_individual_features(struct hidpp10_device *dev, uint32_t feature_mask) { unsigned idx = HIDPP_RECEIVER_IDX; union hidpp10_message mode = CMD_ENABLE_INDIVIDUAL_FEATURES(idx, SET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Setting individual features\n"); mode.msg.device_idx = dev->index; mode.msg.parameters[0] = feature_mask & 0xFF; mode.msg.parameters[1] = (feature_mask >> 8) & 0x0E; mode.msg.parameters[2] = (feature_mask >> 16) & 0x3F; res = hidpp10_request_command(dev, &mode); return res; } /* -------------------------------------------------------------------------- */ /* 0x07: Battery status */ /* -------------------------------------------------------------------------- */ #define __CMD_BATTERY_STATUS 0x07 #define CMD_BATTERY_STATUS(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_BATTERY_STATUS, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_battery_status(struct hidpp10_device *dev, enum hidpp10_battery_level *level, enum hidpp10_battery_charge_state *charge_state, uint8_t *low_threshold_in_percent) { unsigned idx = dev->index; union hidpp10_message battery = CMD_BATTERY_STATUS(idx, GET_REGISTER_REQ); int res; res = hidpp10_request_command(dev, &battery); *level = battery.msg.parameters[0]; *charge_state = battery.msg.parameters[1]; *low_threshold_in_percent = battery.msg.parameters[2]; if (*low_threshold_in_percent >= 7) { /* reserved value, we just silently truncate it to 0 */ *low_threshold_in_percent = 0; } *low_threshold_in_percent *= 5; /* in 5% increments */ return res; } /* -------------------------------------------------------------------------- */ /* 0x0D: Battery mileage */ /* -------------------------------------------------------------------------- */ #define __CMD_BATTERY_MILEAGE 0x0D #define CMD_BATTERY_MILEAGE(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_BATTERY_MILEAGE, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_battery_mileage(struct hidpp10_device *dev, uint8_t *level_in_percent, uint32_t *max_seconds, enum hidpp10_battery_charge_state *state) { unsigned idx = dev->index; union hidpp10_message battery = CMD_BATTERY_MILEAGE(idx, GET_REGISTER_REQ); int res; int max; res = hidpp10_request_command(dev, &battery); *level_in_percent = battery.msg.parameters[0] & 0x7F; max = battery.msg.parameters[1]; max |= (battery.msg.parameters[2] & 0xF) << 8; switch((battery.msg.parameters[2] & 0x30) >> 4) { case 0x03: /* days */ max *= 24; /* fallthrough */ case 0x02: /* hours */ max *= 60; /* fallthrough */ case 0x01: /* min */ max *= 60; break; case 0x00: /* seconds */ break; } *max_seconds = max; switch(battery.msg.parameters[2] >> 6) { case 0x00: *state = HIDPP10_BATTERY_CHARGE_STATE_NOT_CHARGING; break; case 0x01: *state = HIDPP10_BATTERY_CHARGE_STATE_CHARGING; break; case 0x02: *state = HIDPP10_BATTERY_CHARGE_STATE_CHARGING_COMPLETE; break; case 0x03: *state = HIDPP10_BATTERY_CHARGE_STATE_CHARGING_ERROR; break; } return res; } /* -------------------------------------------------------------------------- */ /* 0x0F: Profile queries */ /* -------------------------------------------------------------------------- */ #define __CMD_PROFILE 0x0F #define PROFILE_TYPE_INDEX 0x00 #define PROFILE_TYPE_ADDRESS 0x01 #define PROFILE_TYPE_EEPROM 0xEE #define PROFILE_TYPE_FACTORY 0xFF #define CMD_PROFILE(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_PROFILE, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } struct _hidpp10_dpi_mode_8 { uint8_t res; uint8_t led1:4; uint8_t led2:4; uint8_t led3:4; uint8_t led4:4; } __attribute__((packed)); _Static_assert(sizeof(struct _hidpp10_dpi_mode_8) == 3, "Invalid size"); struct _hidpp10_dpi_mode_8_dual { uint8_t xres; uint8_t yres; uint8_t led1:4; uint8_t led2:4; uint8_t led3:4; uint8_t led4:4; } __attribute__((packed)); _Static_assert(sizeof(struct _hidpp10_dpi_mode_8_dual) == 4, "Invalid size"); struct _hidpp10_dpi_mode_16 { uint16_t xres; uint16_t yres; uint8_t led1:4; uint8_t led2:4; uint8_t led3:4; uint8_t led4:4; } __attribute__((packed)); _Static_assert(sizeof(struct _hidpp10_dpi_mode_16) == 6, "Invalid size"); union _hidpp10_button_binding { struct { uint8_t type; uint8_t pad; uint8_t pad1; } any; struct { uint8_t type; /* 0x81 */ uint16_t button_flags; } __attribute__((packed)) button; struct { uint8_t type; /* 0x82 */ uint8_t modifier_flags; uint8_t key; } keyboard_keys; struct { uint8_t type; /* 0x83 */ uint16_t flags; } __attribute__((packed)) special; struct { uint8_t type; /* 0x84 */ uint16_t consumer_control; } __attribute__((packed)) consumer_control; struct { uint8_t type; /* 0x8F */ uint8_t zero0; uint8_t zero1; } disabled; struct { uint8_t page; uint8_t offset; uint8_t zero; } __attribute__((packed)) macro; } __attribute__((packed)); _Static_assert(sizeof(union _hidpp10_button_binding) == 3, "Invalid size"); union _hidpp10_profile_metadata { struct { uint8_t marker[5]; uint8_t padding[420]; } __attribute__((packed)) any; struct { uint8_t marker[5]; /* { 'L', 'G', 'S', '0', '2' } */ uint16_t name[23]; uint16_t macro_names[11][17]; } __attribute__((packed)) lgs02; } __attribute__((packed)); struct _hidpp10_profile_500 { uint8_t red; uint8_t green; uint8_t blue; uint8_t unknown; struct _hidpp10_dpi_mode_16 dpi_modes[PROFILE_NUM_DPI_MODES]; uint8_t angle_correction; uint8_t default_dpi_mode; uint8_t unknown2[2]; uint8_t usb_refresh_rate; union _hidpp10_button_binding buttons[PROFILE_NUM_BUTTONS]; union _hidpp10_profile_metadata metadata; } __attribute__((packed)); _Static_assert(sizeof(struct _hidpp10_profile_500) == 503, "Invalid size"); static const uint8_t _hidpp10_profile_700_unknown1[3] = { 0x80, 0x01, 0x10 }; static const uint8_t _hidpp10_profile_700_unknown2[10] = { 0x01, 0x2c, 0x02, 0x58, 0x64, 0xff, 0xbc, 0x00, 0x09, 0x31 }; struct _hidpp10_profile_700 { struct _hidpp10_dpi_mode_8_dual dpi_modes[PROFILE_NUM_DPI_MODES]; uint8_t default_dpi_mode; uint8_t unknown1[3]; uint8_t usb_refresh_rate; uint8_t unknown2[10]; union _hidpp10_button_binding buttons[PROFILE_NUM_BUTTONS]; union _hidpp10_profile_metadata metadata; } __attribute__((packed)); _Static_assert(sizeof(struct _hidpp10_profile_700) == 499, "Invalid size"); struct _hidpp10_profile_9 { uint8_t red; uint8_t green; uint8_t blue; uint8_t unknown1; struct _hidpp10_dpi_mode_8 dpi_modes[PROFILE_NUM_DPI_MODES]; uint8_t default_dpi_mode; uint8_t unknown2[2]; uint8_t usb_refresh_rate; union _hidpp10_button_binding buttons[PROFILE_NUM_BUTTONS_G9]; uint8_t unknown3[3]; union _hidpp10_profile_metadata metadata; } __attribute__((packed)); _Static_assert(sizeof(struct _hidpp10_profile_500) == 503, "Invalid size"); union _hidpp10_profile_data { struct _hidpp10_profile_500 profile_500; struct _hidpp10_profile_700 profile_700; struct _hidpp10_profile_9 profile_9; uint8_t data[HIDPP10_PAGE_SIZE]; }; _Static_assert((sizeof(union _hidpp10_profile_data) % 16) == 0, "Invalid size"); static uint8_t hidpp10_get_dpi_mapping(struct hidpp10_device *dev, unsigned int value) { struct hidpp10_dpi_mapping *m; unsigned int i, delta, min_delta; uint8_t result = 0; if (!dev->dpi_table) return value / 50; m = dev->dpi_table; min_delta = INT_MAX; for (i = 0; i < dev->dpi_count; i++) { delta = abs(value - m[i].dpi); if (delta < min_delta) { result = m[i].raw_value; min_delta = delta; } } return result; } static unsigned int hidpp10_get_dpi_value(struct hidpp10_device *dev, uint8_t raw_value) { struct hidpp10_dpi_mapping *m; unsigned int i; if (!dev->dpi_table) return raw_value * 50; m = dev->dpi_table; for (i = 0; i < dev->dpi_count; i++) { if (raw_value == m[i].raw_value) return m[i].dpi; } return 0; } int hidpp10_get_profile_directory(struct hidpp10_device *dev, struct hidpp10_directory *out, size_t nelems) { unsigned int i; int res; uint8_t bytes[HIDPP10_PAGE_SIZE] = { 0 }; struct hidpp10_directory *directory = (struct hidpp10_directory *)bytes; size_t count; if (dev->profile_type == HIDPP10_PROFILE_UNKNOWN) { hidpp_log_debug(&dev->base, "no profile type given\n"); return 0; } if (dev->profile_directory) { count = 0; while (dev->profile_directory[count].page) { count++; } goto out; } hidpp_log_raw(&dev->base, "Fetching the profiles' directory\n"); res = hidpp10_read_page(dev, 0x01, bytes); if (res) return res; count = 0; /* assume 16 profiles max */ for (i = 0; i < 16; i++) { if (directory[i].page == 0xFF) break; count++; } dev->profile_directory = zalloc((count + 1) * sizeof(struct hidpp10_directory)); memcpy(dev->profile_directory, directory, count * sizeof(struct hidpp10_directory)); out: if (dev->profile_count != count) { if (dev->profiles) free(dev->profiles); dev->profiles = zalloc(count * sizeof(struct hidpp10_profile)); dev->profile_count = count; } count = min(count, nelems); memcpy(out, dev->profile_directory, count * sizeof(out[0])); return count; } int hidpp10_get_current_profile(struct hidpp10_device *dev, int8_t *current_profile) { unsigned idx = dev->index; union hidpp10_message profile = CMD_PROFILE(idx, GET_REGISTER_REQ); int res; unsigned i; int8_t type, page, offset; struct hidpp10_directory directory[16]; /* completely random profile count */ int count = 0; hidpp_log_raw(&dev->base, "Fetching the profiles' directory\n"); count = hidpp10_get_profile_directory(dev, directory, ARRAY_LENGTH(directory)); if (count < 0) return count; hidpp_log_raw(&dev->base, "Fetching current profile\n"); res = hidpp10_request_command(dev, &profile); if (res) return res; type = profile.msg.parameters[0]; page = profile.msg.parameters[1]; switch (type) { case PROFILE_TYPE_INDEX: *current_profile = page; /* If the profile exceeds the directory length, default to * the first */ if (*current_profile > count) *current_profile = 0; return 0; case PROFILE_TYPE_ADDRESS: offset = profile.msg.parameters[2]; for (i = 0; i < ARRAY_LENGTH(directory) && directory[i].page < 32; i++) { if (page == directory[i].page && offset == directory[i].offset) { *current_profile = i; return 0; } } hidpp_log_error(&dev->base, "unable to find the profile at (%d,%d) in the directory\n", page, offset); break; default: hidpp_log_error(&dev->base, "Unexpected value: %02x\n", type); } return -ENAVAIL; } static int hidpp10_set_internal_current_profile(struct hidpp10_device *dev, int16_t current_profile, uint8_t profile_type) { unsigned idx = dev->index; union hidpp10_message profile = CMD_PROFILE(idx, SET_REGISTER_REQ); int8_t page, offset; struct hidpp10_directory directory[16]; /* completely random profile count */ int count = 0; hidpp_log_raw(&dev->base, "Fetching the profiles' directory\n"); count = hidpp10_get_profile_directory(dev, directory, ARRAY_LENGTH(directory)); if (count < 0) return count; hidpp_log_raw(&dev->base, "Setting current profile\n"); profile.msg.parameters[0] = profile_type; switch (profile_type) { case PROFILE_TYPE_INDEX: if (current_profile > count) return -EINVAL; profile.msg.parameters[1] = current_profile & 0xFF; break; case PROFILE_TYPE_ADDRESS: page = current_profile >> 8; offset = current_profile & 0xFF; profile.msg.parameters[1] = page; profile.msg.parameters[2] = offset; break; case PROFILE_TYPE_FACTORY: break; default: hidpp_log_error(&dev->base, "Unexpected value: %02x\n", profile_type); return -EINVAL; } return hidpp10_request_command(dev, &profile); } int hidpp10_set_current_profile(struct hidpp10_device *dev, int16_t current_profile) { return hidpp10_set_internal_current_profile(dev, current_profile, PROFILE_TYPE_INDEX); } static void hidpp10_fill_dpi_modes_8(struct hidpp10_device *dev, struct hidpp10_profile *profile, struct _hidpp10_dpi_mode_8 *dpi_list, unsigned int count) { unsigned int i; profile->num_dpi_modes = count; for (i = 0; i < count; i++) { struct _hidpp10_dpi_mode_8 *dpi = &dpi_list[i]; profile->dpi_modes[i].xres = hidpp10_get_dpi_value(dev, dpi->res); profile->dpi_modes[i].yres = hidpp10_get_dpi_value(dev, dpi->res); profile->dpi_modes[i].led[0] = dpi->led1 == 0x2; profile->dpi_modes[i].led[1] = dpi->led2 == 0x2; profile->dpi_modes[i].led[2] = dpi->led3 == 0x2; profile->dpi_modes[i].led[3] = dpi->led4 == 0x2; } } static void hidpp10_write_dpi_modes_8(struct hidpp10_device *dev, struct hidpp10_profile *profile, struct _hidpp10_dpi_mode_8 *dpi_list, unsigned int count) { unsigned int i; for (i = 0; i < count; i++) { struct _hidpp10_dpi_mode_8 *dpi = &dpi_list[i]; dpi->res = hidpp10_get_dpi_mapping(dev, profile->dpi_modes[i].xres); dpi->led1 = profile->dpi_modes[i].led[0] ? 0x02 : 0x01; dpi->led2 = profile->dpi_modes[i].led[1] ? 0x02 : 0x01; dpi->led3 = profile->dpi_modes[i].led[2] ? 0x02 : 0x01; dpi->led4 = profile->dpi_modes[i].led[3] ? 0x02 : 0x01; } } static void hidpp10_fill_dpi_modes_8_dual(struct hidpp10_device *dev, struct hidpp10_profile *profile, struct _hidpp10_dpi_mode_8_dual *dpi_list, unsigned int count) { unsigned int i; profile->num_dpi_modes = count; for (i = 0; i < count; i++) { struct _hidpp10_dpi_mode_8_dual *dpi = &dpi_list[i]; profile->dpi_modes[i].xres = hidpp10_get_dpi_value(dev, dpi->xres); profile->dpi_modes[i].yres = hidpp10_get_dpi_value(dev, dpi->yres); profile->dpi_modes[i].led[0] = dpi->led1 == 0x2; profile->dpi_modes[i].led[1] = dpi->led2 == 0x2; profile->dpi_modes[i].led[2] = dpi->led3 == 0x2; profile->dpi_modes[i].led[3] = dpi->led4 == 0x2; } } static void hidpp10_write_dpi_modes_8_dual(struct hidpp10_device *dev, struct hidpp10_profile *profile, struct _hidpp10_dpi_mode_8_dual *dpi_list, unsigned int count) { unsigned int i; for (i = 0; i < count; i++) { struct _hidpp10_dpi_mode_8_dual *dpi = &dpi_list[i]; dpi->xres = hidpp10_get_dpi_mapping(dev, profile->dpi_modes[i].xres); dpi->yres = hidpp10_get_dpi_mapping(dev, profile->dpi_modes[i].yres); dpi->led1 = profile->dpi_modes[i].led[0] ? 0x02 : 0x01; dpi->led2 = profile->dpi_modes[i].led[1] ? 0x02 : 0x01; dpi->led3 = profile->dpi_modes[i].led[2] ? 0x02 : 0x01; dpi->led4 = profile->dpi_modes[i].led[3] ? 0x02 : 0x01; } } static void hidpp10_fill_dpi_modes_16(struct hidpp10_device *dev, struct hidpp10_profile *profile, struct _hidpp10_dpi_mode_16 *dpi_list, unsigned int count) { unsigned int i; profile->num_dpi_modes = count; for (i = 0; i < count; i++) { uint8_t *be; /* in big endian */ struct _hidpp10_dpi_mode_16 *dpi = &dpi_list[i]; be = (uint8_t*)&dpi->xres; profile->dpi_modes[i].xres = hidpp10_get_dpi_value(dev, hidpp_get_unaligned_be_u16(be)); be = (uint8_t*)&dpi->yres; profile->dpi_modes[i].yres = hidpp10_get_dpi_value(dev, hidpp_get_unaligned_be_u16(be)); profile->dpi_modes[i].led[0] = dpi->led1 == 0x2; profile->dpi_modes[i].led[1] = dpi->led2 == 0x2; profile->dpi_modes[i].led[2] = dpi->led3 == 0x2; profile->dpi_modes[i].led[3] = dpi->led4 == 0x2; } } static void hidpp10_write_dpi_modes_16(struct hidpp10_device *dev, struct hidpp10_profile *profile, struct _hidpp10_dpi_mode_16 *dpi_list, unsigned int count) { unsigned int i; for (i = 0; i < count; i++) { uint8_t *be; /* in big endian */ struct _hidpp10_dpi_mode_16 *dpi = &dpi_list[i]; be = (uint8_t*)&dpi->xres; hidpp_set_unaligned_be_u16(be, hidpp10_get_dpi_mapping(dev, profile->dpi_modes[i].xres)); be = (uint8_t*)&dpi->yres; hidpp_set_unaligned_be_u16(be, hidpp10_get_dpi_mapping(dev, profile->dpi_modes[i].yres)); dpi->led1 = profile->dpi_modes[i].led[0] ? 0x02 : 0x01; dpi->led2 = profile->dpi_modes[i].led[1] ? 0x02 : 0x01; dpi->led3 = profile->dpi_modes[i].led[2] ? 0x02 : 0x01; dpi->led4 = profile->dpi_modes[i].led[3] ? 0x02 : 0x01; } } static int hidpp10_onboard_profiles_macro_next(struct hidpp10_device *device, uint8_t memory[32], uint16_t *index, union hidpp10_macro_data *macro) { int rc = 0; unsigned int step = 0; if (*index >= 32 - sizeof(union hidpp10_macro_data)) { hidpp_log_error(&device->base, "error while parsing macro.\n"); return -EFAULT; } memcpy(macro, &memory[*index], sizeof(union hidpp10_macro_data)); switch (macro->any.type) { case HIDPP10_MACRO_NOOP: /* fallthrough */ case HIDPP10_MACRO_WAIT_FOR_BUTTON_RELEASE: /* fallthrough */ case HIDPP10_MACRO_REPEAT_UNTIL_BUTTON_RELEASE: /* fallthrough */ case HIDPP10_MACRO_REPEAT: step = 1; rc = -EAGAIN; break; case HIDPP10_MACRO_KEY_PRESS: /* fallthrough */ case HIDPP10_MACRO_KEY_RELEASE: /* fallthrough */ case HIDPP10_MACRO_MOD_PRESS: /* fallthrough */ case HIDPP10_MACRO_MOD_RELEASE: /* fallthrough */ case HIDPP10_MACRO_MOUSE_WHEEL: step = 2; rc = -EAGAIN; break; case HIDPP10_MACRO_MOUSE_BUTTON_PRESS: /* fallthrough */ case HIDPP10_MACRO_MOUSE_BUTTON_RELEASE: /* fallthrough */ case HIDPP10_MACRO_KEY_CONSUMER_CONTROL: /* fallthrough */ case HIDPP10_MACRO_DELAY: /* fallthrough */ case HIDPP10_MACRO_JUMP: /* fallthrough */ case HIDPP10_MACRO_JUMP_IF_PRESSED: step = 3; rc = -EAGAIN; break; case HIDPP10_MACRO_MOUSE_POINTER_MOVE: /* fallthrough */ case HIDPP10_MACRO_JUMP_IF_RELEASED_TIMEOUT: step = 5; rc = -EAGAIN; break; case HIDPP10_MACRO_END: step = 1; return 0; default: if (macro->any.type >= 0x80 && macro->any.type <= 0xFE) { step = 1; rc = -EAGAIN; } else { hidpp_log_error(&device->base, "unknown tag: 0x%02x\n", macro->any.type); return -EFAULT; } } if ((*index + step) & 0xF0) /* the next item will be on the following chunk */ return -ENOMEM; *index += step; return rc; } static int hidpp10_onboard_profiles_read_macro(struct hidpp10_device *device, uint8_t page, uint8_t offset, union hidpp10_macro_data **return_macro) { uint8_t memory[HIDPP10_PAGE_SIZE]; union hidpp10_macro_data *macro = NULL; unsigned count = 0; unsigned index = 0; uint16_t mem_index = 0; int rc = -ENOMEM; do { if (count == index) { union hidpp10_macro_data *tmp; count += 32; /* manual realloc to have the memory zero-initialized */ tmp = zalloc(count * sizeof(union hidpp10_macro_data)); if (macro) { memcpy(tmp, macro, (count - 32) * sizeof(union hidpp10_macro_data)); free(macro); } macro = tmp; } if (rc == -ENOMEM) { offset += mem_index; if (offset & 0x01) offset--; rc = hidpp10_read_memory(device, page, offset, memory); if (rc) goto out_err; mem_index &= 0x01; hidpp_log_buf_raw(&device->base, "-> ", memory + mem_index, 16 - mem_index); } rc = hidpp10_onboard_profiles_macro_next(device, memory, &mem_index, ¯o[index]); if (rc == -EFAULT) goto out_err; if (rc != -ENOMEM) { if (macro[index].any.type == HIDPP10_MACRO_JUMP) { page = macro[index].jump.page; offset = macro[index].jump.offset; mem_index = 0; /* no need to store the jump in memory */ index--; /* force memory fetching */ rc = -ENOMEM; } index++; } } while (rc); *return_macro = macro; return index; out_err: if (macro) free(macro); return rc; } static int hidpp10_onboard_profiles_parse_macro(struct hidpp10_device *device, uint8_t page, uint8_t offset, union hidpp10_macro_data **return_macro) { union hidpp10_macro_data *m, *macro = NULL; unsigned i, count = 0; int rc; hidpp_log_raw(&device->base, "*** macro starts at (0x%02x, 0x%04x) ***\n", page, offset); rc = hidpp10_onboard_profiles_read_macro(device, page, offset, ¯o); if (rc < 0) return rc; count = rc; for (i = 0; i < count; i++) { m = ¯o[i]; switch (m->any.type) { case HIDPP10_MACRO_NOOP: hidpp_log_raw(&device->base, "noop\n"); break; case HIDPP10_MACRO_WAIT_FOR_BUTTON_RELEASE: hidpp_log_raw(&device->base, "wait for button release\n"); break; case HIDPP10_MACRO_REPEAT_UNTIL_BUTTON_RELEASE: hidpp_log_raw(&device->base, "repeat from beginning until button release\n"); break; case HIDPP10_MACRO_REPEAT: hidpp_log_raw(&device->base, "repeat from beginning\n"); break; case HIDPP10_MACRO_KEY_PRESS: hidpp_log_raw(&device->base, "key press: %02x\n", m->key.key); break; case HIDPP10_MACRO_KEY_RELEASE: hidpp_log_raw(&device->base, "key release: %02x\n", m->key.key); break; case HIDPP10_MACRO_MOD_PRESS: hidpp_log_raw(&device->base, "modifier press: %02x\n", m->modifier.key); break; case HIDPP10_MACRO_MOD_RELEASE: hidpp_log_raw(&device->base, "modifier release: %02x\n", m->modifier.key); break; case HIDPP10_MACRO_MOUSE_WHEEL: hidpp_log_raw(&device->base, "mouse wheel: %+d\n", m->wheel.value); break; case HIDPP10_MACRO_MOUSE_BUTTON_PRESS: m->button.flags = ffs(hidpp_le_u16_to_cpu(m->button.flags)); hidpp_log_raw(&device->base, "mouse button press: %d\n", m->button.flags); break; case HIDPP10_MACRO_MOUSE_BUTTON_RELEASE: m->button.flags = ffs(hidpp_le_u16_to_cpu(m->button.flags)); hidpp_log_raw(&device->base, "mouse button release: %d\n", m->button.flags); break; case HIDPP10_MACRO_KEY_CONSUMER_CONTROL: m->consumer_control.key = hidpp_be_u16_to_cpu(m->consumer_control.key); hidpp_log_raw(&device->base, "switched to consumer control: 0x%04x\n", m->consumer_control.key); break; case HIDPP10_MACRO_DELAY: m->delay.time = hidpp_be_u16_to_cpu(m->delay.time); hidpp_log_raw(&device->base, "delay: %0.03f\n", m->delay.time/1000.0); break; case HIDPP10_MACRO_JUMP: /* should be skipped by hidpp10_onboard_profiles_read_macro */ hidpp_log_raw(&device->base, "jump to: (0x%02x, 0x%02x)\n", m->jump.page, m->jump.offset); break; case HIDPP10_MACRO_JUMP_IF_PRESSED: hidpp_log_raw(&device->base, "conditional jump to: (0x%02x, 0x%02x)\n", m->jump.page, m->jump.offset); break; case HIDPP10_MACRO_MOUSE_POINTER_MOVE: break; case HIDPP10_MACRO_JUMP_IF_RELEASED_TIMEOUT: m->jump_timeout.timeout = hidpp_be_u16_to_cpu(m->jump_timeout.timeout); hidpp_log_raw(&device->base, "conditional jump to: (0x%02x, 0x%02x) if released whithin %0.03f msecs.\n", m->jump_timeout.page, m->jump_timeout.offset, m->jump_timeout.timeout / 1000.0); break; case HIDPP10_MACRO_END: break; default: if (m->any.type >= 0x80 && m->any.type <= 0x9F) { m->delay.time = 8 + (m->any.type - 0x80) * 4; m->any.type = HIDPP10_MACRO_DELAY; hidpp_log_raw(&device->base, "short delay: %0.03f\n", m->delay.time/1000.0); } else if (m->any.type >= 0xA0 && m->any.type <= 0xBF) { m->delay.time = 132 + (m->any.type - 0x9F) * 8; m->any.type = HIDPP10_MACRO_DELAY; hidpp_log_raw(&device->base, "short delay: %0.03f\n", m->delay.time/1000.0); } else if (m->any.type >= 0xC0 && m->any.type <= 0xDF) { m->delay.time = 388 + (m->any.type - 0xBF) * 16; m->any.type = HIDPP10_MACRO_DELAY; hidpp_log_raw(&device->base, "short delay: %0.03f\n", m->delay.time/1000.0); } else if (m->any.type >= 0xE0 && m->any.type <= 0xFE) { m->delay.time = 900 + (m->any.type - 0xDF) * 32; m->any.type = HIDPP10_MACRO_DELAY; hidpp_log_raw(&device->base, "short delay: %0.03f\n", m->delay.time/1000.0); } else { hidpp_log_error(&device->base, "unknown tag: 0x%02x\n", m->any.type); } } } hidpp_log_raw(&device->base, "*** end of macro ***\n"); *return_macro = macro; return 0; } static void hidpp10_fill_buttons(struct hidpp10_device *dev, struct hidpp10_profile *profile, union _hidpp10_button_binding *buttons, unsigned int count) { unsigned int i; profile->num_buttons = count; for (i = 0; i < count; i++) { union _hidpp10_button_binding *b = &buttons[i]; union hidpp10_button *button = &profile->buttons[i]; button->any.type = b->any.type; switch (b->any.type) { case PROFILE_BUTTON_TYPE_BUTTON: button->button.button = ffs(hidpp_le_u16_to_cpu(b->button.button_flags)); break; case PROFILE_BUTTON_TYPE_KEYS: button->keys.modifier_flags = b->keyboard_keys.modifier_flags; button->keys.key = b->keyboard_keys.key; break; case PROFILE_BUTTON_TYPE_SPECIAL: button->special.special = hidpp_le_u16_to_cpu(b->special.flags); break; case PROFILE_BUTTON_TYPE_CONSUMER_CONTROL: button->consumer_control.consumer_control = hidpp_be_u16_to_cpu(b->consumer_control.consumer_control); break; case PROFILE_BUTTON_TYPE_DISABLED: break; default: /* macros */ button->macro.page = b->macro.page; button->macro.offset = b->macro.offset; button->macro.address = i; if (profile->macros[i]) { free(profile->macros[i]); profile->macros[i] = NULL; } hidpp10_onboard_profiles_parse_macro(dev, b->macro.page, b->macro.offset * 2, &profile->macros[i]); } } } static void hidpp10_write_buttons(struct hidpp10_device *dev, struct hidpp10_profile *profile, union _hidpp10_button_binding *buttons, unsigned int count) { unsigned int i; for (i = 0; i < count; i++) { union _hidpp10_button_binding *button = &buttons[i]; union hidpp10_button *b = &profile->buttons[i]; button->any.type = b->any.type; switch (b->any.type) { case PROFILE_BUTTON_TYPE_BUTTON: button->button.button_flags = hidpp_cpu_to_le_u16(1U << (b->button.button - 1)); break; case PROFILE_BUTTON_TYPE_KEYS: button->keyboard_keys.modifier_flags = b->keys.modifier_flags; button->keyboard_keys.key = b->keys.key; break; case PROFILE_BUTTON_TYPE_SPECIAL: button->special.flags = hidpp_cpu_to_le_u16(b->special.special); break; case PROFILE_BUTTON_TYPE_CONSUMER_CONTROL: button->consumer_control.consumer_control = hidpp_cpu_to_be_u16(b->consumer_control.consumer_control); break; case PROFILE_BUTTON_TYPE_DISABLED: break; default: /* macros */ button->macro.page = b->macro.page; button->macro.offset = b->macro.offset; button->macro.zero = 0; } } } static void hidpp10_uchar16_to_uchar8(uint8_t *dst, uint16_t *src, size_t len) { unsigned i; for (i = 0; i < len; i++) dst[i] = hidpp_le_u16_to_cpu(src[i]) & 0xFF; } static void hidpp10_uchar8_to_uchar16(uint16_t *dst, uint8_t *src, size_t len) { unsigned i; for (i = 0; i < len; i++) dst[i] = hidpp_cpu_to_le_u16(src[i]); } static void hidpp10_profile_parse_names(struct hidpp10_device *dev, struct hidpp10_profile *profile, uint8_t number, union _hidpp10_profile_metadata *metadata) { unsigned i; if (strneq((char *)metadata->any.marker, "LGS02", 5)) { hidpp10_uchar16_to_uchar8(profile->name, metadata->lgs02.name, ARRAY_LENGTH(metadata->lgs02.name)); hidpp_log_raw(&dev->base, "profile %d is named '%s'\n", number, profile->name); for (i = 0; i < ARRAY_LENGTH(metadata->lgs02.macro_names); i++) { hidpp10_uchar16_to_uchar8(profile->macro_names[i], metadata->lgs02.macro_names[i], ARRAY_LENGTH(metadata->lgs02.macro_names[i])); if (profile->macro_names[i][0]) hidpp_log_raw(&dev->base, "macro %d of profile %d is named: '%s'\n", (unsigned)i, number, profile->macro_names[i]); } } else { snprintf((char *)profile->name, sizeof(profile->name) - 1, "Profile %d", number + 1); } } static void hidpp10_profile_set_names(struct hidpp10_device *dev, struct hidpp10_profile *profile, uint8_t number, union _hidpp10_profile_metadata *metadata) { unsigned i; memcpy(metadata->lgs02.marker, "LGS02", sizeof(metadata->lgs02.marker)); hidpp10_uchar8_to_uchar16(metadata->lgs02.name, profile->name, ARRAY_LENGTH(metadata->lgs02.name)); for (i = 0; i < ARRAY_LENGTH(metadata->lgs02.macro_names); i++) { hidpp10_uchar8_to_uchar16(metadata->lgs02.macro_names[i], profile->macro_names[i], ARRAY_LENGTH(metadata->lgs02.macro_names[i])); } } int hidpp10_get_profile(struct hidpp10_device *dev, int8_t number, struct hidpp10_profile *profile_return) { uint8_t page_data[HIDPP10_PAGE_SIZE]; union _hidpp10_profile_data *data = (union _hidpp10_profile_data *)page_data; struct _hidpp10_profile_500 *p500 = &data->profile_500; struct _hidpp10_profile_700 *p700 = &data->profile_700; struct _hidpp10_profile_9 *p9 = &data->profile_9; size_t i; int res; struct hidpp10_profile *profile; struct hidpp10_directory directory[16]; /* completely random profile count */ union _hidpp10_button_binding *buttons; int count = 0; uint8_t page; /* Page 0 is RAM * Page 1 is the profile directory * Page 2-31 are Flash * -> profiles are stored in the Flash * * For now we assume that number refers to the index in the profile * directory. */ hidpp_log_raw(&dev->base, "Fetching profile %d\n", number); count = hidpp10_get_profile_directory(dev, directory, ARRAY_LENGTH(directory)); if (count < 0) return count; if (count == 0 || dev->profile_type == HIDPP10_PROFILE_UNKNOWN) return -ENOTSUP; if (number >= count) { hidpp_log_error(&dev->base, "Profile number %d not in the directory.\n", number); return -EINVAL; } switch (dev->profile_type) { case HIDPP10_PROFILE_G500: buttons = p500->buttons; break; case HIDPP10_PROFILE_G700: buttons = p700->buttons; break; case HIDPP10_PROFILE_G9: buttons = p9->buttons; break; default: hidpp_log_error(&dev->base, "This should never happen, complain to your maintainer.\n"); return -EINVAL; } profile = &dev->profiles[number]; if (!profile->initialized) { page = directory[number].page; res = hidpp10_read_page(dev, page, page_data); if (res == -EILSEQ) { /* * if the CRC is wrong, the mouse still handles the * profile. Warn the user. */ hidpp_log_info(&dev->base, "Profile %d has a wrong CRC, assuming valid.\n", number); res = 0; } if (res) return res; switch (dev->profile_type) { case HIDPP10_PROFILE_G500: profile->red = p500->red; profile->green = p500->green; profile->blue = p500->blue; profile->angle_correction = p500->angle_correction; profile->default_dpi_mode = p500->default_dpi_mode; profile->refresh_rate = p500->usb_refresh_rate ? 1000/p500->usb_refresh_rate : 0; hidpp10_fill_dpi_modes_16(dev, profile, p500->dpi_modes, PROFILE_NUM_DPI_MODES); hidpp10_profile_parse_names(dev, profile, number, &p500->metadata); hidpp10_fill_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS); break; case HIDPP10_PROFILE_G700: profile->default_dpi_mode = p700->default_dpi_mode; profile->refresh_rate = p700->usb_refresh_rate ? 1000/p700->usb_refresh_rate : 0; hidpp10_fill_dpi_modes_8_dual(dev, profile, p700->dpi_modes, PROFILE_NUM_DPI_MODES); hidpp10_profile_parse_names(dev, profile, number, &p700->metadata); hidpp10_fill_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS); break; case HIDPP10_PROFILE_G9: profile->default_dpi_mode = p9->default_dpi_mode; profile->refresh_rate = p9->usb_refresh_rate ? 1000/p9->usb_refresh_rate : 0; hidpp10_fill_dpi_modes_8(dev, profile, p9->dpi_modes, PROFILE_NUM_DPI_MODES); hidpp10_profile_parse_names(dev, profile, number, &p9->metadata); hidpp10_fill_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS_G9); break; default: hidpp_log_error(&dev->base, "This should never happen, complain to your maintainer.\n"); } profile->initialized = 1; hidpp_log_buf_raw(&dev->base, "+++++++++++++++++++ Profile data: +++++++++++++++++ \n", data->data, 78); } hidpp_log_raw(&dev->base, "Profile %d:\n", number); for (i = 0; i < 5; i++) { hidpp_log_raw(&dev->base, "DPI mode: %dx%d dpi\n", profile->dpi_modes[i].xres, profile->dpi_modes[i].yres); hidpp_log_raw(&dev->base, "LED status: 1:%s 2:%s 3:%s 4:%s\n", profile->dpi_modes[i].led[0] ? "on" : "off", profile->dpi_modes[i].led[1] ? "on" : "off", profile->dpi_modes[i].led[2] ? "on" : "off", profile->dpi_modes[i].led[3] ? "on" : "off"); } hidpp_log_raw(&dev->base, "Angle correction: %d\n", profile->angle_correction); hidpp_log_raw(&dev->base, "Default DPI mode: %d\n", profile->default_dpi_mode); hidpp_log_raw(&dev->base, "Refresh rate: %d\n", profile->refresh_rate); for (i = 0; i < 13; i++) { union hidpp10_button *button = &profile->buttons[i]; switch (button->any.type) { case PROFILE_BUTTON_TYPE_BUTTON: hidpp_log_raw(&dev->base, "Button %zd: button %d\n", i, button->button.button); break; case PROFILE_BUTTON_TYPE_KEYS: hidpp_log_raw(&dev->base, "Button %zd: key %d modifier %x\n", i, button->keys.key, button->keys.modifier_flags); break; case PROFILE_BUTTON_TYPE_SPECIAL: hidpp_log_raw(&dev->base, "Button %zd: special %x\n", i, button->special.special); break; case PROFILE_BUTTON_TYPE_CONSUMER_CONTROL: hidpp_log_raw(&dev->base, "Button %zd: consumer: %x\n", i, button->consumer_control.consumer_control); break; case PROFILE_BUTTON_TYPE_DISABLED: hidpp_log_raw(&dev->base, "Button %zd: disabled\n", i); break; default: /* FIXME: this is the page number for the macro, * followed by a 1-byte offset */ break ; } } *profile_return = *profile; return 0; } static const enum ratbag_button_action_special hidpp10_profiles_specials[] = { [0x00] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, [0x01] = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT, [0x02] = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT, [0x03] = RATBAG_BUTTON_ACTION_SPECIAL_BATTERY_LEVEL, [0x04] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP, [0x05] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP, [0x06 ... 0x07] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, [0x08] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN, [0x09] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_DOWN, [0x0a ... 0x0f] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, [0x10] = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP, [0x11] = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP, [0x12 ... 0x1f] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, [0x20] = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN, [0x21] = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_DOWN, [0x22 ... 0xff] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, }; enum ratbag_button_action_special hidpp10_onboard_profiles_get_special(uint8_t code) { return hidpp10_profiles_specials[code]; } uint8_t hidpp10_onboard_profiles_get_code_from_special(enum ratbag_button_action_special special) { uint8_t i = 0; while (++i) { if (hidpp10_profiles_specials[i] == special) return i; } return RATBAG_BUTTON_ACTION_SPECIAL_INVALID; } int hidpp10_set_profile(struct hidpp10_device *dev, int8_t number, struct hidpp10_profile *profile) { uint8_t page_data[HIDPP10_PAGE_SIZE]; union _hidpp10_profile_data *data = (union _hidpp10_profile_data *)page_data; struct _hidpp10_profile_500 *p500 = &data->profile_500; struct _hidpp10_profile_700 *p700 = &data->profile_700; struct _hidpp10_profile_9 *p9 = &data->profile_9; int res; struct hidpp10_directory directory[16]; /* completely random profile count */ union _hidpp10_button_binding *buttons; int count = 0; uint16_t crc; uint8_t page; hidpp_log_raw(&dev->base, "Fetching profile %d\n", number); count = hidpp10_get_profile_directory(dev, directory, ARRAY_LENGTH(directory)); if (count < 0) return count; if (count == 0 || dev->profile_type == HIDPP10_PROFILE_UNKNOWN) return -ENOTSUP; if (number >= count) { hidpp_log_error(&dev->base, "Profile number %d not in the directory.\n", number); return -EINVAL; } memset(page_data, 0xff, sizeof(page_data)); switch (dev->profile_type) { case HIDPP10_PROFILE_G500: buttons = p500->buttons; break; case HIDPP10_PROFILE_G700: buttons = p700->buttons; break; case HIDPP10_PROFILE_G9: buttons = p9->buttons; break; default: hidpp_log_error(&dev->base, "This should never happen, complain to your maintainer.\n"); } /* First, fill out the unknown fields with the constants or the current * values when we are not sure. */ switch (dev->profile_type) { case HIDPP10_PROFILE_G500: case HIDPP10_PROFILE_G9: /* we do not know the actual values of the remaining field right now * so pre-fill with the current data */ res = hidpp10_read_page(dev, directory[number].page, page_data); if (res) return res; break; case HIDPP10_PROFILE_G700: memcpy(p700->unknown1, _hidpp10_profile_700_unknown1, sizeof(p700->unknown1)); memcpy(p700->unknown2, _hidpp10_profile_700_unknown2, sizeof(p700->unknown2)); break; default: hidpp_log_error(&dev->base, "This should never happen, complain to your maintainer.\n"); } switch (dev->profile_type) { case HIDPP10_PROFILE_G500: p500->red = profile->red; p500->green = profile->green; p500->blue = profile->blue; p500->angle_correction = profile->angle_correction; p500->default_dpi_mode = profile->default_dpi_mode; p500->usb_refresh_rate = 1000/profile->refresh_rate; hidpp10_write_dpi_modes_16(dev, profile, p500->dpi_modes, PROFILE_NUM_DPI_MODES); hidpp10_write_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS); hidpp10_profile_set_names(dev, profile, number, &p500->metadata); break; case HIDPP10_PROFILE_G700: p700->default_dpi_mode = profile->default_dpi_mode; p700->usb_refresh_rate = 1000 / profile->refresh_rate; hidpp10_write_dpi_modes_8_dual(dev, profile, p700->dpi_modes, PROFILE_NUM_DPI_MODES); hidpp10_write_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS); hidpp10_profile_set_names(dev, profile, number, &p700->metadata); break; case HIDPP10_PROFILE_G9: p9->red = profile->red; p9->green = profile->green; p9->blue = profile->blue; p9->default_dpi_mode = profile->default_dpi_mode; p9->usb_refresh_rate = 1000 / profile->refresh_rate; hidpp10_write_dpi_modes_8(dev, profile, p9->dpi_modes, PROFILE_NUM_DPI_MODES); hidpp10_write_buttons(dev, profile, buttons, PROFILE_NUM_BUTTONS); hidpp10_profile_set_names(dev, profile, number, &p9->metadata); break; default: hidpp_log_error(&dev->base, "This should never happen, complain to your maintainer.\n"); } crc = hidpp_crc_ccitt(page_data, HIDPP10_PAGE_SIZE - 2); hidpp_set_unaligned_be_u16(&page_data[HIDPP10_PAGE_SIZE - 2], crc); /* * writing the data in several steps to prevent shroedinger state * if the device is unplugged while uploading the data: * - first disable the current profile by using the factory one * (this prevents the user to change the current profile by pressing * a button) * - then upload in RAM half of the data * - erase the portion of the flash we are overwriting * - write the uploaded data to the flash * - upload the rest * - write the uploaded data to the flash * - switch to the new profile */ res = hidpp10_set_internal_current_profile(dev, 0, PROFILE_TYPE_FACTORY); if (res < 0) return res; res = hidpp10_send_hot_payload(dev, 0x00, 0x0000, /* destination: RAM */ page_data, HIDPP10_PAGE_SIZE / 2); if (res < 0) return res; page = directory[number].page; /* according to the spec, a profile can have an offset. * For all the devices we know, they all start at 0x0000 */ res = hidpp10_erase_memory(dev, page); if (res < 0) return res; res = hidpp10_write_flash(dev, 0x00, 0x0000, page, 0x0000, HIDPP10_PAGE_SIZE / 2); if (res < 0) return res; res = hidpp10_send_hot_payload(dev, 0x00, 0x0000, /* destination: RAM */ page_data + HIDPP10_PAGE_SIZE / 2, HIDPP10_PAGE_SIZE / 2); if (res < 0) return res; res = hidpp10_write_flash(dev, 0x00, 0x0000, page, HIDPP10_PAGE_SIZE / 2, HIDPP10_PAGE_SIZE / 2); if (res < 0) return res; res = hidpp10_set_internal_current_profile(dev, number, PROFILE_TYPE_INDEX); if (res < 0) return res; return res; } /* -------------------------------------------------------------------------- */ /* 0x51: LED Status */ /* -------------------------------------------------------------------------- */ #define __CMD_LED_STATUS 0x51 #define CMD_LED_STATUS(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_LED_STATUS, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_led_status(struct hidpp10_device *dev, enum hidpp10_led_status led[6]) { unsigned idx = dev->index; union hidpp10_message led_status = CMD_LED_STATUS(idx, GET_REGISTER_REQ); uint8_t *status = led_status.msg.parameters; int res; hidpp_log_raw(&dev->base, "Fetching LED status\n"); res = hidpp10_request_command(dev, &led_status); if (res) return res; led[0] = status[0] & 0xF; led[1] = (status[0] >> 4) & 0xF; led[2] = status[1] & 0xF; led[3] = (status[1] >> 4) & 0xF; led[4] = status[2] & 0xF; led[5] = (status[2] >> 4) & 0xF; return 0; } int hidpp10_set_led_status(struct hidpp10_device *dev, const enum hidpp10_led_status led[6]) { unsigned idx = dev->index; union hidpp10_message led_status = CMD_LED_STATUS(idx, SET_REGISTER_REQ); uint8_t *status = led_status.msg.parameters; int res; int i; hidpp_log_raw(&dev->base, "Setting LED status\n"); for (i = 0; i < 6; i++) { switch (led[i]) { case HIDPP10_LED_STATUS_NO_CHANGE: case HIDPP10_LED_STATUS_OFF: case HIDPP10_LED_STATUS_ON: case HIDPP10_LED_STATUS_BLINK: case HIDPP10_LED_STATUS_HEARTBEAT: case HIDPP10_LED_STATUS_SLOW_ON: case HIDPP10_LED_STATUS_SLOW_OFF: break; default: abort(); } } /* each led is 4-bits, 0x1 == off, 0x2 == on */ status[0] = led[0] | (led[1] << 4); status[1] = led[2] | (led[3] << 4); status[2] = led[4] | (led[5] << 4); res = hidpp10_request_command(dev, &led_status); return res; } /* -------------------------------------------------------------------------- */ /* 0x54: LED Intensity */ /* -------------------------------------------------------------------------- */ #define __CMD_LED_INTENSITY 0x54 #define CMD_LED_INTENSITY(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_LED_INTENSITY, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_led_intensity(struct hidpp10_device *dev, uint8_t led_intensity_in_percent[6]) { unsigned idx = dev->index; union hidpp10_message led_intensity = CMD_LED_INTENSITY(idx, GET_REGISTER_REQ); uint8_t *intensity = led_intensity.msg.parameters; int res; hidpp_log_raw(&dev->base, "Fetching LED intensity\n"); res = hidpp10_request_command(dev, &led_intensity); if (res) return res; led_intensity_in_percent[0] = 10 * ((intensity[0] ) & 0xF); led_intensity_in_percent[1] = 10 * ((intensity[0] >> 4) & 0xF); led_intensity_in_percent[2] = 10 * ((intensity[1] ) & 0xF); led_intensity_in_percent[3] = 10 * ((intensity[1] >> 4) & 0xF); led_intensity_in_percent[4] = 10 * ((intensity[2] ) & 0xF); led_intensity_in_percent[5] = 10 * ((intensity[2] >> 4) & 0xF); return 0; } int hidpp10_set_led_intensity(struct hidpp10_device *dev, const uint8_t led_intensity_in_percent[6]) { unsigned idx = dev->index; union hidpp10_message led_intensity = CMD_LED_INTENSITY(idx, SET_REGISTER_REQ); uint8_t *intensity = led_intensity.msg.parameters; int res; hidpp_log_raw(&dev->base, "Setting LED intensity\n"); intensity[0] = led_intensity_in_percent[0]/10 & 0xF; intensity[0] |= (led_intensity_in_percent[1]/10 & 0xF) << 4; intensity[1] = led_intensity_in_percent[2]/10 & 0xF; intensity[1] |= (led_intensity_in_percent[3]/10 & 0xF) << 4; intensity[2] = led_intensity_in_percent[4]/10 & 0xF; intensity[2] |= (led_intensity_in_percent[5]/10 & 0xF) << 4; res = hidpp10_request_command(dev, &led_intensity); return res; } /* -------------------------------------------------------------------------- */ /* 0x57: LED Color */ /* -------------------------------------------------------------------------- */ #define __CMD_LED_COLOR 0x57 #define CMD_LED_COLOR(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_LED_COLOR, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_led_color(struct hidpp10_device *dev, uint8_t *red, uint8_t *green, uint8_t *blue) { unsigned idx = dev->index; union hidpp10_message led_color = CMD_LED_COLOR(idx, GET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Fetching LED color\n"); res = hidpp10_request_command(dev, &led_color); if (res) return res; *red = led_color.msg.parameters[0]; *green = led_color.msg.parameters[1]; *blue = led_color.msg.parameters[2]; return 0; } int hidpp10_set_led_color(struct hidpp10_device *dev, uint8_t red, uint8_t green, uint8_t blue) { unsigned idx = dev->index; union hidpp10_message led_color = CMD_LED_COLOR(idx, SET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Setting LED color\n"); led_color.msg.parameters[0] = red; led_color.msg.parameters[1] = green; led_color.msg.parameters[2] = blue; res = hidpp10_request_command(dev, &led_color); return res; } /* -------------------------------------------------------------------------- */ /* 0x61: Optical Sensor Settings */ /* -------------------------------------------------------------------------- */ #define __CMD_OPTICAL_SENSOR_SETTINGS 0x61 #define CMD_OPTICAL_SENSOR_SETTINGS(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_OPTICAL_SENSOR_SETTINGS, \ .parameters = {0x00, 0x00, 0x00}, \ } \ } int hidpp10_get_optical_sensor_settings(struct hidpp10_device *dev, uint8_t *surface_reflectivity) { unsigned idx = dev->index; union hidpp10_message sensor = CMD_OPTICAL_SENSOR_SETTINGS(idx, GET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Fetching optical sensor settings\n"); res = hidpp10_request_command(dev, &sensor); if (res) return res; *surface_reflectivity = sensor.msg.parameters[0]; /* Don't know what the other values are */ return 0; } /* -------------------------------------------------------------------------- */ /* 0x63: Current Resolution */ /* -------------------------------------------------------------------------- */ #define __CMD_CURRENT_RESOLUTION 0x63 #define CMD_CURRENT_RESOLUTION(id, idx, sub) { \ .msg = { \ .report_id = id, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_CURRENT_RESOLUTION, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_current_resolution(struct hidpp10_device *dev, uint16_t *xres, uint16_t *yres) { unsigned idx = dev->index; union hidpp10_message resolution = CMD_CURRENT_RESOLUTION(REPORT_ID_SHORT, idx, GET_REGISTER_REQ); union hidpp10_message resolution_long = CMD_CURRENT_RESOLUTION(REPORT_ID_SHORT, idx, GET_LONG_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Fetching current resolution\n"); switch (dev->profile_type) { case HIDPP10_PROFILE_G9: res = hidpp10_request_command(dev, &resolution); if (res) return res; /* resolution is in 50dpi multiples */ *xres = *yres = hidpp10_get_dpi_value(dev, hidpp_get_unaligned_le_u16(&resolution.data[4])); break; default: res = hidpp10_request_command(dev, &resolution_long); if (res) return res; /* resolution is in 50dpi multiples */ *xres = hidpp10_get_dpi_value(dev, hidpp_get_unaligned_le_u16(&resolution_long.data[4])); *yres = hidpp10_get_dpi_value(dev, hidpp_get_unaligned_le_u16(&resolution_long.data[6])); } return 0; } int hidpp10_set_current_resolution(struct hidpp10_device *dev, uint16_t xres, uint16_t yres) { unsigned idx = dev->index; union hidpp10_message resolution = CMD_CURRENT_RESOLUTION(REPORT_ID_LONG, idx, SET_LONG_REGISTER_REQ); hidpp_set_unaligned_le_u16(&resolution.data[4], hidpp10_get_dpi_mapping(dev, xres)); hidpp_set_unaligned_le_u16(&resolution.data[6], hidpp10_get_dpi_mapping(dev, yres)); return hidpp10_request_command(dev, &resolution); } /* -------------------------------------------------------------------------- */ /* 0x64: USB Refresh Rate */ /* -------------------------------------------------------------------------- */ #define __CMD_USB_REFRESH_RATE 0x64 #define CMD_USB_REFRESH_RATE(idx, sub) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = sub, \ .address = __CMD_USB_REFRESH_RATE, \ .parameters = {0x00, 0x00, 0x00 }, \ } \ } int hidpp10_get_usb_refresh_rate(struct hidpp10_device *dev, uint16_t *rate) { unsigned idx = dev->index; union hidpp10_message refresh = CMD_USB_REFRESH_RATE(idx, GET_REGISTER_REQ); int res; hidpp_log_raw(&dev->base, "Fetching USB refresh rate\n"); res = hidpp10_request_command(dev, &refresh); if (res) return res; *rate = 1000/refresh.msg.parameters[0]; return 0; } int hidpp10_set_usb_refresh_rate(struct hidpp10_device *dev, uint16_t rate) { unsigned idx = dev->index; union hidpp10_message refresh = CMD_USB_REFRESH_RATE(idx, GET_REGISTER_REQ); hidpp_log_raw(&dev->base, "Setting USB refresh rate\n"); refresh.msg.parameters[0] = 1000/rate; return hidpp10_request_command(dev, &refresh); } /* -------------------------------------------------------------------------- */ /* 0xA0: Generic Memory Management */ /* -------------------------------------------------------------------------- */ #define __CMD_GENERIC_MEMORY_MANAGEMENT 0xA0 #define CMD_ERASE_MEMORY(idx, page) { \ .msg = { \ .report_id = REPORT_ID_LONG, \ .device_idx = idx, \ .sub_id = SET_LONG_REGISTER_REQ, \ .address = __CMD_GENERIC_MEMORY_MANAGEMENT, \ .string = {0x02, 0x00, \ 0x00, 0x00, 0x00, 0x00, \ page, 0x00, 0x00, 0x00,\ 0x00, 0x00, 0x00, 0x00}, \ } \ } #define CMD_WRITE_FLASH(idx, src_page, src_woffset, dst_page, dst_woffset, size) { \ .msg = { \ .report_id = REPORT_ID_LONG, \ .device_idx = idx, \ .sub_id = SET_LONG_REGISTER_REQ, \ .address = __CMD_GENERIC_MEMORY_MANAGEMENT, \ .string = {0x03, 0x00, \ src_page, src_woffset, 0x00, 0x00, \ dst_page, dst_woffset, 0x00, 0x00,\ size >> 8, size & 0xFF}, \ } \ } int hidpp10_erase_memory(struct hidpp10_device *dev, uint8_t page) { unsigned idx = dev->index; union hidpp10_message erase = CMD_ERASE_MEMORY(idx, page); hidpp_log_raw(&dev->base, "Erasing page 0x%02x\n", page); return hidpp10_request_command(dev, &erase); } int hidpp10_write_flash(struct hidpp10_device *dev, uint8_t src_page, uint16_t src_offset, uint8_t dst_page, uint16_t dst_offset, uint16_t size) { unsigned idx = dev->index; union hidpp10_message copy = CMD_WRITE_FLASH(idx, src_page, src_offset / 2, dst_page, dst_offset / 2, size); if ((src_offset % 2 != 0) || (dst_offset % 2 != 0)) { hidpp_log_error(&dev->base, "Accessing memory with odd offset is not supported.\n"); return -EINVAL; } hidpp_log_raw(&dev->base, "Copying %d bytes from (0x%02x,0x%04x) to (0x%02x,0x%04x)\n", size, src_page, src_offset, dst_page, dst_offset); return hidpp10_request_command(dev, ©); } /* -------------------------------------------------------------------------- */ /* 0x9x: HOT payload */ /* 0xA1: HOT Control Register */ /* -------------------------------------------------------------------------- */ #define __CMD_HOT_CONTROL 0xA1 #define CMD_HOT_RESET(idx) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = SET_REGISTER_REQ, \ .address = __CMD_HOT_CONTROL, \ .parameters = {0x01, 0x00, 0x00 }, \ } \ } #define HOT_NOTIFICATION 0x50 #define HOT_WRITE 0x92 #define HOT_CONTINUE 0x93 static int hidpp10_hot_ctrl_reset(struct hidpp10_device *dev) { unsigned idx = dev->index; union hidpp10_message ctrl_reset = CMD_HOT_RESET(idx); return hidpp10_request_command(dev, &ctrl_reset); } static int hidpp10_hot_request_command(struct hidpp10_device *dev, uint8_t data[LONG_MESSAGE_LENGTH]) { uint8_t read_buffer[LONG_MESSAGE_LENGTH] = {0}; int ret; uint8_t id = data[3]; if ((data[0] != REPORT_ID_LONG) || ((data[2] != HOT_WRITE) && (data[2] != HOT_CONTINUE))) return -EINVAL; /* Send the message to the Device */ ret = hidpp_write_command(&dev->base, data, LONG_MESSAGE_LENGTH); if (ret) goto out_err; /* * Now read the answers from the device: * loop until we get the actual answer or an error code. */ do { ret = hidpp_read_response(&dev->base, read_buffer, LONG_MESSAGE_LENGTH); /* Wait and retry if the USB timed out */ if (ret == -ETIMEDOUT) { msleep(10); ret = hidpp_read_response(&dev->base, read_buffer, LONG_MESSAGE_LENGTH); } /* actual answer */ if (read_buffer[2] == HOT_NOTIFICATION) break; } while (ret > 0); if (ret < 0) { hidpp_log_error(&dev->base, " USB error: %s (%d)\n", strerror(-ret), -ret); perror("write"); goto out_err; } if (read_buffer[4] != id) { ret = -EPROTO; hidpp_log_error(&dev->base, " Protocol error: ids do not match.\n"); perror("write"); goto out_err; } ret = 0; out_err: return ret; } struct hot_header { uint8_t id; uint8_t page; uint8_t offset; uint16_t zero; uint16_t size; uint16_t zero1; } __attribute__ ((__packed__)); static int hidpp10_send_hot_chunk(struct hidpp10_device *dev, uint8_t index, bool first, uint8_t dst_page, uint16_t dst_offset, uint8_t *data, unsigned size) { struct hot_header header = {0}; uint8_t buffer[LONG_MESSAGE_LENGTH] = {0}; unsigned offset = 0; unsigned count; int res; buffer[offset++] = REPORT_ID_LONG; buffer[offset++] = dev->index; if (first) { if (dst_offset % 2 != 0) { hidpp_log_error(&dev->base, "Writing memory with odd offset is not supported.\n"); return -EINVAL; } buffer[offset++] = HOT_WRITE; buffer[offset++] = index; header.id = 0x01; header.page = dst_page; header.offset = dst_offset / 2; header.size = hidpp_cpu_to_be_u16(size); memcpy(&buffer[offset], &header, sizeof(header)); offset += sizeof(header); } else { buffer[offset++] = HOT_CONTINUE; buffer[offset++] = index; } count = min(LONG_MESSAGE_LENGTH - offset, size); if (count <= 0) return -EINVAL; memcpy(&buffer[offset], data, count); res = hidpp10_hot_request_command(dev, buffer); if (res < 0) return res; return count; } int hidpp10_send_hot_payload(struct hidpp10_device *dev, uint8_t dst_page, uint16_t dst_offset, uint8_t *data, unsigned size) { bool first = true; unsigned int count = 0; unsigned int index = 0; int res; res = hidpp10_hot_ctrl_reset(dev); if (res < 0) return res; do { res = hidpp10_send_hot_chunk(dev, index, first, dst_page, dst_offset, data + count, size - count); if (res < 0) return res; first = false; count += res; index++; } while (size > count); return 0; } /* -------------------------------------------------------------------------- */ /* 0xA2: Read Sector */ /* -------------------------------------------------------------------------- */ #define __CMD_READ_MEMORY 0xA2 #define CMD_READ_MEMORY(idx, page, offset) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = GET_LONG_REGISTER_REQ, \ .address = __CMD_READ_MEMORY, \ .parameters = {page, offset, 0x00 }, \ } \ } int hidpp10_read_memory(struct hidpp10_device *dev, uint8_t page, uint16_t offset, uint8_t bytes[16]) { unsigned idx = dev->index; union hidpp10_message readmem = CMD_READ_MEMORY(idx, page, offset / 2); int res; if (offset % 2 != 0) { hidpp_log_error(&dev->base, "Reading memory with odd offset is not supported.\n"); return -EINVAL; } if (page > HIDPP10_MAX_PAGE_NUMBER) return -EINVAL; hidpp_log_raw(&dev->base, "Reading memory page %d, offset %#x\n", page, offset); res = hidpp10_request_command(dev, &readmem); if (res) return res; memcpy(bytes, readmem.msg.string, sizeof(readmem.msg.string)); return 0; } int hidpp10_read_page(struct hidpp10_device *dev, uint8_t page, uint8_t bytes[HIDPP10_PAGE_SIZE]) { unsigned int i; int res; uint16_t crc, read_crc; for (i = 0; i < HIDPP10_PAGE_SIZE; i += 16) { res = hidpp10_read_memory(dev, page, i, bytes + i); if (res < 0) return res; } crc = hidpp_crc_ccitt(bytes, HIDPP10_PAGE_SIZE - 2); read_crc = hidpp_get_unaligned_be_u16(&bytes[HIDPP10_PAGE_SIZE - 2]); if (crc != read_crc) return -EILSEQ; /* return illegal sequence */ return 0; } /* -------------------------------------------------------------------------- */ /* 0xB2: Device Connection and Disconnection (Pairing) */ /* -------------------------------------------------------------------------- */ #define __CMD_DEVICE_CONNECTION_DISCONNECTION 0xB2 #define CONNECT_DEVICES_OPEN_LOCK 1 #define CONNECT_DEVICES_CLOSE_LOCK 2 #define CONNECT_DEVICES_DISCONNECT 3 #define CMD_DEVICE_CONNECTION_DISCONNECTION(idx, cmd, timeout) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = HIDPP_RECEIVER_IDX, \ .sub_id = SET_REGISTER_REQ, \ .address = __CMD_DEVICE_CONNECTION_DISCONNECTION, \ .parameters = {cmd, idx - 1, timeout }, \ } \ } int hidpp10_open_lock(struct hidpp10_device *device, uint8_t timeout) { union hidpp10_message open_lock = CMD_DEVICE_CONNECTION_DISCONNECTION(0x00, CONNECT_DEVICES_OPEN_LOCK, timeout); return hidpp10_request_command(device, &open_lock); } int hidpp10_close_lock(struct hidpp10_device *device) { union hidpp10_message open_lock = CMD_DEVICE_CONNECTION_DISCONNECTION(0x00, CONNECT_DEVICES_CLOSE_LOCK, 0); return hidpp10_request_command(device, &open_lock); } int hidpp10_disconnect(struct hidpp10_device *device, int idx) { union hidpp10_message disconnect = CMD_DEVICE_CONNECTION_DISCONNECTION(idx + 1, CONNECT_DEVICES_DISCONNECT, 0x00); return hidpp10_request_command(device, &disconnect); } /* -------------------------------------------------------------------------- */ /* 0xB5: Pairing Information */ /* -------------------------------------------------------------------------- */ #define __CMD_PAIRING_INFORMATION 0xB5 #define DEVICE_PAIRING_INFORMATION 0x20 #define DEVICE_EXTENDED_PAIRING_INFORMATION 0x30 #define DEVICE_NAME 0x40 #define CMD_PAIRING_INFORMATION(idx, type) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = HIDPP_RECEIVER_IDX, \ .sub_id = GET_LONG_REGISTER_REQ, \ .address = __CMD_PAIRING_INFORMATION, \ .parameters = {type + idx - 1, 0x00, 0x00 }, \ } \ } int hidpp10_get_pairing_information(struct hidpp10_device *dev, uint8_t *report_interval, uint16_t *wpid, uint8_t *device_type) { unsigned int idx = dev->index; union hidpp10_message pairing_information = CMD_PAIRING_INFORMATION(idx, DEVICE_PAIRING_INFORMATION); int res; hidpp_log_raw(&dev->base, "Fetching pairing information\n"); res = hidpp10_request_command(dev, &pairing_information); if (res) return -1; *report_interval = pairing_information.msg.string[2]; *wpid = hidpp_get_unaligned_be_u16(&pairing_information.msg.string[3]); *device_type = pairing_information.msg.string[7]; return 0; } int hidpp10_get_pairing_information_device_name(struct hidpp10_device *dev, char *name, size_t *name_size) { unsigned int idx = dev->index; union hidpp10_message device_name = CMD_PAIRING_INFORMATION(idx, DEVICE_NAME); int res; hidpp_log_raw(&dev->base, "Fetching device name\n"); res = hidpp10_request_command(dev, &device_name); if (res) return -1; *name_size = min(*name_size, device_name.msg.string[1] + 1U); strncpy_safe(name, (char*)&device_name.msg.string[2], *name_size); return 0; } int hidpp10_get_extended_pairing_information(struct hidpp10_device *dev, uint32_t *serial) { unsigned int idx = dev->index; union hidpp10_message info = CMD_PAIRING_INFORMATION(idx, DEVICE_EXTENDED_PAIRING_INFORMATION); int res; hidpp_log_raw(&dev->base, "Fetching extended pairing information\n"); res = hidpp10_request_command(dev, &info); if (res) return -1; *serial = hidpp_get_unaligned_be_u32(&info.msg.string[1]); return 0; } /* -------------------------------------------------------------------------- */ /* 0xF1: Device Firmware Information */ /* -------------------------------------------------------------------------- */ #define __CMD_DEVICE_FIRMWARE_INFORMATION 0xF1 #define FIRMWARE_INFO_ITEM_FW_NAME_AND_VERSION(MCU) ((MCU - 1) << 4 | 0x01) #define FIRMWARE_INFO_ITEM_FW_BUILD_NUMBER(MCU) ((MCU - 1) << 4 | 0x02) #define FIRMWARE_INFO_ITEM_HW_VERSION(MCU) ((MCU - 1) << 4 | 0x03) #define FIRMWARE_INFO_ITEM_BOOTLOADER_VERSION(MCU) ((MCU - 1) << 4 | 0x04) #define CMD_DEVICE_FIRMWARE_INFORMATION(idx, fw_info_item) { \ .msg = { \ .report_id = REPORT_ID_SHORT, \ .device_idx = idx, \ .sub_id = GET_REGISTER_REQ, \ .address = __CMD_DEVICE_FIRMWARE_INFORMATION, \ .parameters = {fw_info_item, 0x00, 0x00 }, \ } \ } int hidpp10_get_firmare_information(struct hidpp10_device *dev, uint8_t *major_out, uint8_t *minor_out, uint8_t *build_out) { unsigned idx = dev->index; union hidpp10_message firmware_information = CMD_DEVICE_FIRMWARE_INFORMATION(idx, FIRMWARE_INFO_ITEM_FW_NAME_AND_VERSION(1)); union hidpp10_message build_information = CMD_DEVICE_FIRMWARE_INFORMATION(idx, FIRMWARE_INFO_ITEM_FW_BUILD_NUMBER(1)); int res; uint8_t maj, min, build; hidpp_log_raw(&dev->base, "Fetching firmware information\n"); /* * This may fail on some devices * => we can not retrieve their FW version through HID++ 1.0. */ res = hidpp10_request_command(dev, &firmware_information); if (res) return res; maj = firmware_information.msg.string[1]; min = firmware_information.msg.string[2]; res = hidpp10_request_command(dev, &build_information); if (res) return res; build = hidpp_get_unaligned_be_u16(&build_information.msg.string[1]); *major_out = maj; *minor_out = min; *build_out = build; return 0; } /* -------------------------------------------------------------------------- */ /* general device handling */ /* -------------------------------------------------------------------------- */ static int hidpp10_get_device_info(struct hidpp10_device *dev) { uint32_t feature_mask, notifications; uint8_t reflect; int i; uint16_t xres, yres; uint16_t refresh_rate; enum hidpp10_led_status led[6]; int8_t current_profile; struct hidpp10_profile profiles[HIDPP10_NUM_PROFILES]; int count; struct hidpp10_directory directory[16]; hidpp10_get_individual_features(dev, &feature_mask); hidpp10_get_hidpp_notifications(dev, ¬ifications); hidpp10_get_current_resolution(dev, &xres, &yres); hidpp10_get_led_status(dev, led); hidpp10_get_usb_refresh_rate(dev, &refresh_rate); hidpp10_get_optical_sensor_settings(dev, &reflect); hidpp10_get_current_profile(dev, ¤t_profile); count = hidpp10_get_profile_directory(dev, directory, ARRAY_LENGTH(directory)); for (i = 0; i < count && i < HIDPP10_NUM_PROFILES; i++) hidpp10_get_profile(dev, i, &profiles[i]); return 0; } struct hidpp10_device* hidpp10_device_new(const struct hidpp_device *base, int idx, enum hidpp10_profile_type type) { struct hidpp10_device *dev; dev = zalloc(sizeof(*dev)); dev->index = idx; dev->base = *base; dev->profile_type = type; if (hidpp10_get_device_info(dev) != 0) { hidpp10_device_destroy(dev); dev = NULL; } return dev; } void hidpp10_device_destroy(struct hidpp10_device *dev) { union hidpp10_macro_data **macro; unsigned i; if (dev->dpi_table) free(dev->dpi_table); if (dev->profile_directory) free(dev->profile_directory); if (dev->profiles) { for (i = 0; i < dev->profile_count; i++) { ARRAY_FOR_EACH(dev->profiles[i].macros, macro) { if (*macro) { free(*macro); *macro = NULL; } } } free(dev->profiles); } free(dev); } libratbag-0.9/src/hidpp10.h000066400000000000000000000536301311552661500155320ustar00rootroot00000000000000/* * HID++ 1.0 library - headers file. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /* * Based on the HID++ 1.0 documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #pragma once #include #include #include "hidpp-generic.h" /* FIXME: that's what my G500s supports, but only pages 3-5 are valid. * 0 is zeroed, 1 and 2 are garbage, all above 6 is garbage */ #define HIDPP10_NUM_PROFILES 5 #define HIDPP10_MAX_PAGE_NUMBER 31 enum hidpp10_profile_type { HIDPP10_PROFILE_UNKNOWN = -1, HIDPP10_PROFILE_G500, HIDPP10_PROFILE_G700, HIDPP10_PROFILE_G9, }; struct hidpp10_directory; struct hidpp10_profile; struct hidpp10_dpi_mapping { uint8_t raw_value; unsigned dpi; }; struct hidpp10_device { struct hidpp_device base; unsigned index; uint8_t dpi_count; struct hidpp10_dpi_mapping *dpi_table; /* must be null terminated */ struct hidpp10_directory *profile_directory; /* must be null terminated */ enum hidpp10_profile_type profile_type; struct hidpp10_profile *profiles; unsigned int profile_count; }; struct hidpp10_device* hidpp10_device_new(const struct hidpp_device *base, int idx, enum hidpp10_profile_type type); void hidpp10_device_destroy(struct hidpp10_device *dev); /** * Builds the table of dpi for the device from the given string. * * @param dev a struct hidpp10_device previously allocated * @param str_list a string representing the dpi table * * The given string contains only positive integer values, separated by * semicolons (';'). The n-th element in the list corresponds to the * raw value (0x80 + n - 1) */ int hidpp10_build_dpi_table_from_list(struct hidpp10_device *dev, const char *str_list); /** * Builds the table of dpi for the device from the given dpi description. * * @param dev a struct hidpp10_device previously allocated * @param str_dpi a string representing the dpi parameters * * The given string contains 3 float values, separated by colons (':'). * The format is MIN:MAX@STEP * MIN corresponds to the raw_value '0' * MAX corresponds to the raw_value floor((MAX - MIN) / STEP) */ int hidpp10_build_dpi_table_from_dpi_info(struct hidpp10_device *dev, const char *str_dpi); /* -------------------------------------------------------------------------- */ /* 0x00: Enable HID++ Notifications */ /* -------------------------------------------------------------------------- */ /** * All notifications are disabled by default on powerup. */ enum hidpp10_hidpp_notifications { /** * enabled: Multimedia and MS vendor specific keys are reported as * HID++ notification 0x03 * disabled: reported as normal HID reports */ HIDPP10_NOTIFICATIONS_CONSUMER_VENDOR_SPECIFIC_CONTROL = (1 << 0), /** * enabled: power keys are reported as HID++ notification 0x04 * disabled: reported as normal HID reports */ HIDPP10_NOTIFICATIONS_POWER_KEYS = (1 << 1), /** * enabled: Vertical scroll wheel/iNav are reported as HID++ * notification 0x05 * disabled: reported as normal HID reports */ HIDPP10_NOTIFICATIONS_ROLLER_V = (1 << 2), /** * enabled: buttons not available in standard HID are reported as * HID++ notification 0x06 * disabled: buttons not available in standard HID are not reported */ HIDPP10_NOTIFICATIONS_MOUSE_EXTRA_BUTTONS = (1 << 3), /** * enabled: battery status/milage are reported as HID++ notification * 0x07 or 0x0D (device-dependent) * disabled: battery status/milage are not reported */ HIDPP10_NOTIFICATIONS_BATTERY_STATUS = (1 << 4), /** * enabled: Horizontal scroll wheel/iNav are reported as HID++ * notification 0x05 * disabled: reported as normal HID reports */ HIDPP10_NOTIFICATIONS_ROLLER_H = (1 << 5), /** * enabled: F-Lock status is reported as HID++ notification 0x09 * disabled: F-Lock status is not reported */ HIDPP10_NOTIFICATIONS_F_LOCK_STATUS = (1 << 6), /** * enabled: Numpad keys are reported as buttons in HID++ * notification 0x03 * disabled: reported as normal keys */ HIDPP10_NOTIFICATIONS_NUMPAD_NUMERIC_KEYS = (1 << 7), /** * enabled: Device arrival/removal/... are reported as HID++ * notifications 0x40, 0x41, 0x46 or 0x78 * disabled: these events are not reported */ HIDPP10_NOTIFICATIONS_WIRELESS_NOTIFICATIONS = (1 << 8), /** * enabled: User interface events are reported as HID++ notification * 0x08 * disabled: these events are not reported */ HIDPP10_NOTIFICATIONS_UI_NOTIFICATIONS = (1 << 9), /** * enabled: Quad link quality info events are reported as HID++ notification * 0x49 * disabled: these events are not reported */ HIDPP10_NOTIFICATIONS_QUAD_LINK_QUALITY_INFO = (1 << 10), HIDPP10_NOTIFICATIONS_SOFTWARE_PRESENT = (1 << 11), HIDPP10_NOTIFICATIONS_TOUCHPAD_MULTITOUCH_NOTIFICATIONS = (1 << 12), /* 1 << 13 is reserved */ /* 1 << 14 is reserved */ /* 1 << 15 is reserved */ /** * enabled: 3D gestures are reported as HID++ notification 0x65 * disabled: these events are not reported */ HIDPP10_NOTIFICATIONS_3D_GESTURE = (1 << 16), HIDPP10_NOTIFICATIONS_VOIP_TELEPHONY = (1 << 17), HIDPP10_NOTIFICATIONS_CONFIGURATION_COMPLETE = (1 << 18), /* 1 << 19 is reserved */ /* 1 << 20 is reserved */ /* 1 << 21 is reserved */ /* 1 << 22 is reserved */ /* 1 << 23 is reserved */ }; int hidpp10_get_hidpp_notifications(struct hidpp10_device *dev, uint32_t *reporting_flags); int hidpp10_set_hidpp_notifications(struct hidpp10_device *dev, uint32_t reporting_flags); /* -------------------------------------------------------------------------- */ /* 0x01: Enable Individual Features */ /* -------------------------------------------------------------------------- */ enum hidpp10_individual_features { HIDPP10_FEATURE_BIT_MOUSE_SENSOR_RESOLUTION = (1 << 0), /** * disabled: buttons send button codes * enabled: buttons have special functions (default) * @note Do not use, use 0x63 instead */ HIDPP10_FEATURE_BIT_SPECIAL_BUTTON_FUNCTION = (1 << 1), /** * disabled: normal key usage (default) * enabled: enhanced key usage */ HIDPP10_FEATURE_BIT_ENHANCED_KEY_USAGE = (1 << 2), /** * disabled: (default) * enabled: */ HIDPP10_FEATURE_BIT_FAST_FORWARD_REWIND = (1 << 3), /** * disabled: (default) * enabled: */ HIDPP10_FEATURE_BIT_SEND_CALCULATOR_RESULT = (1 << 4), /** * disabled: * enabled: (default) */ HIDPP10_FEATURE_BIT_MOTION_WAKEUP = (1 << 5), /** * disabled: (default) * enabled: */ HIDPP10_FEATURE_BIT_FAST_SCROLLING = (1 << 6), /** * disabled: work as buttons * enabled: control the resolution (default) */ HIDPP10_FEATURE_BIT_BUTTONS_CONTROL_RESOLUTION = (1 << 7), /* 1 << 8 is reserved */ /** * disabled: (default) * enabled: */ HIDPP10_FEATURE_BIT_RECEIVER_MULTIPLE_RF_LOCK = (1 << 9), /** * disabled: (default) * enabled: */ HIDPP10_FEATURE_BIT_RECEIVER_DISABLE_RFSCAN_IN_SUSPEND = (1 << 10), /** * disabled: (default) * enabled: * * When enabled,removes all compatibility checks. */ HIDPP10_FEATURE_BIT_RECEIVER_ACCEPT_ALL_DEVICES_IN_PAIRING = (1 << 11), /* 1 << 12 is reserved */ /* 1 << 13 is reserved */ /* 1 << 14 is reserved */ /* 1 << 15 is reserved */ /** * disabled: (default) * enabled: no sound */ HIDPP10_FEATURE_BIT_INHIBIT_LOCK_KEY_SOUND = (1 << 16), /** * disabled: (default) * enabled: */ HIDPP10_FEATURE_BIT_INHIBIT_TOUCHPAD = (1 << 17), /** * disabled: * enabled: (default) */ HIDPP10_FEATURE_BIT_3D_ENGINE = (1 << 18), /** * disabled: (disabled) * enabled: */ HIDPP10_FEATURE_BIT_SW_CONTROLS_LEDS = (1 << 19), /** * disabled: (disabled) * enabled: */ HIDPP10_FEATURE_BIT_NO_NUMLOCK_TOGGLE = (1 << 20), /** * disabled: (disabled) * enabled: */ HIDPP10_FEATURE_BIT_INHIBIT_PRESENCE_DETECTION = (1 << 21), }; int hidpp10_get_individual_features(struct hidpp10_device *dev, uint32_t *feature_mask); int hidpp10_set_individual_features(struct hidpp10_device *dev, uint32_t feature_mask); /* -------------------------------------------------------------------------- */ /* 0x07: Battery Status */ /* -------------------------------------------------------------------------- */ enum hidpp10_battery_level { HIDPP10_BATTERY_LEVEL_UNKNOWN = 0x00, HIDPP10_BATTERY_LEVEL_CRITICAL = 0x01, HIDPP10_BATTERY_LEVEL_CRITICAL_LEGACY = 0x02, HIDPP10_BATTERY_LEVEL_LOW = 0x03, HIDPP10_BATTERY_LEVEL_LOW_LEGACY = 0x04, HIDPP10_BATTERY_LEVEL_GOOD = 0x05, HIDPP10_BATTERY_LEVEL_GOOD_LEGACY = 0x06, HIDPP10_BATTERY_LEVEL_FULL_LEGACY = 0x07, /* 0x08..0xFF ... reserved */ }; enum hidpp10_battery_charge_state { HIDPP10_BATTERY_CHARGE_STATE_NOT_CHARGING = 0x00, /* 0x01 ... 0x1F ... reserved (not charging) */ HIDPP10_BATTERY_CHARGE_STATE_UNKNOWN = 0x20, HIDPP10_BATTERY_CHARGE_STATE_CHARGING = 0x21, HIDPP10_BATTERY_CHARGE_STATE_CHARGING_COMPLETE = 0x22, HIDPP10_BATTERY_CHARGE_STATE_CHARGING_ERROR = 0x23, HIDPP10_BATTERY_CHARGE_STATE_CHARGING_FAST = 0x24, HIDPP10_BATTERY_CHARGE_STATE_CHARGING_SLOW = 0x25, HIDPP10_BATTERY_CHARGE_STATE_TOPPING_CHARGE = 0x26, /* 0x27 .. 0xff ... reserved */ }; int hidpp10_get_battery_status(struct hidpp10_device *dev, enum hidpp10_battery_level *level, enum hidpp10_battery_charge_state *charge_state, uint8_t *low_threshold_in_percent); /* -------------------------------------------------------------------------- */ /* 0x0D: Battery Mileage */ /* -------------------------------------------------------------------------- */ int hidpp10_get_battery_mileage(struct hidpp10_device *dev, uint8_t *level_in_percent, uint32_t *max_seconds, enum hidpp10_battery_charge_state *state); /* -------------------------------------------------------------------------- */ /* 0x0F: Profile queries */ /* -------------------------------------------------------------------------- */ #define PROFILE_NUM_BUTTONS 13 #define PROFILE_NUM_BUTTONS_G9 10 #define PROFILE_NUM_DPI_MODES 5 #define PROFILE_BUTTON_TYPE_BUTTON 0x81 #define PROFILE_BUTTON_TYPE_KEYS 0x82 #define PROFILE_BUTTON_TYPE_SPECIAL 0x83 #define PROFILE_BUTTON_TYPE_CONSUMER_CONTROL 0x84 #define PROFILE_BUTTON_TYPE_DISABLED 0x8F #define PROFILE_BUTTON_SPECIAL_PAN_LEFT 0x1 #define PROFILE_BUTTON_SPECIAL_PAN_RIGHT 0x2 #define PROFILE_BUTTON_SPECIAL_DPI_NEXT 0x4 #define PROFILE_BUTTON_SPECIAL_DPI_PREV 0x8 #define HIDPP10_MACRO_NOOP 0x00 #define HIDPP10_MACRO_WAIT_FOR_BUTTON_RELEASE 0x01 #define HIDPP10_MACRO_REPEAT_UNTIL_BUTTON_RELEASE 0x02 #define HIDPP10_MACRO_REPEAT 0x03 #define HIDPP10_MACRO_KEY_PRESS 0x20 #define HIDPP10_MACRO_KEY_RELEASE 0x21 #define HIDPP10_MACRO_MOD_PRESS 0x22 #define HIDPP10_MACRO_MOD_RELEASE 0x23 #define HIDPP10_MACRO_MOUSE_WHEEL 0x24 #define HIDPP10_MACRO_MOUSE_BUTTON_PRESS 0x40 #define HIDPP10_MACRO_MOUSE_BUTTON_RELEASE 0x41 #define HIDPP10_MACRO_KEY_CONSUMER_CONTROL 0x42 #define HIDPP10_MACRO_DELAY 0x43 #define HIDPP10_MACRO_JUMP 0x44 #define HIDPP10_MACRO_JUMP_IF_PRESSED 0x45 #define HIDPP10_MACRO_MOUSE_POINTER_MOVE 0x60 #define HIDPP10_MACRO_JUMP_IF_RELEASED_TIMEOUT 0x61 #define HIDPP10_MACRO_END 0xff union hidpp10_macro_data { struct { uint8_t type; } __attribute__((packed)) any; struct { uint8_t type; /* HIDPP10_MACRO_KEY_PRESS or HIDPP10_MACRO_KEY_RELEASE */ uint8_t key; } __attribute__((packed)) key; struct { uint8_t type; /* HIDPP10_MACRO_MOD_PRESS or HIDPP10_MACRO_MOD_RELEASE */ uint8_t key; } __attribute__((packed)) modifier; struct { uint8_t type; /* HIDPP10_MACRO_MOUSE_WHEEL */ int8_t value; } __attribute__((packed)) wheel; struct { uint8_t type; /* HIDPP10_MACRO_MOUSE_BUTTON_PRESS or HIDPP10_MACRO_MOUSE_BUTTON_RELEASE */ uint16_t flags; } __attribute__((packed)) button; struct { uint8_t type; /* HIDPP10_MACRO_KEY_CONSUMER_CONTROL */ uint16_t key; } __attribute__((packed)) consumer_control; struct { uint8_t type; /* HIDPP10_MACRO_DELAY */ uint16_t time; } __attribute__((packed)) delay; struct { uint8_t type; /* HIDPP10_MACRO_JUMP or HIDPP10_MACRO_JUMP_IF_PRESSED */ uint8_t page; uint8_t offset; } __attribute__((packed)) jump; struct { uint8_t type; /* HIDPP10_MACRO_MOUSE_POINTER_MOVE */ int16_t x_rel; uint16_t y_rel; } __attribute__((packed)) pointer; struct { uint8_t type; /* HIDPP10_MACRO_JUMP_IF_RELEASED_TIMEOUT */ int16_t timeout; uint8_t page; uint8_t offset; } __attribute__((packed)) jump_timeout; struct { uint8_t type; /* HIDPP10_MACRO_END */ } __attribute__((packed)) end; } __attribute__((packed)); _Static_assert(sizeof(union hidpp10_macro_data) == 5, "Invalid size"); struct hidpp10_profile { struct { uint16_t xres; uint16_t yres; bool led[4]; } dpi_modes[5]; size_t num_dpi_modes; unsigned char name[24]; /* the G700 has 23 chars, add one for terminating 0 */ unsigned char macro_names[11][18]; /* adding one extra terminating 0 per name */ uint8_t red; uint8_t green; uint8_t blue; bool angle_correction; uint8_t default_dpi_mode; uint16_t refresh_rate; union hidpp10_button { struct { uint8_t type; } any; struct { uint8_t type; uint16_t button; } button; struct { uint8_t type; uint8_t modifier_flags; uint8_t key; } keys; struct { uint8_t type; uint16_t special; } special; struct { uint8_t type; uint16_t consumer_control; } consumer_control; struct { uint8_t type; } disabled; struct { uint8_t page; uint8_t offset; uint8_t address; } macro; } buttons[PROFILE_NUM_BUTTONS]; union hidpp10_macro_data *macros[PROFILE_NUM_BUTTONS]; size_t num_buttons; size_t num_leds; unsigned int initialized; }; struct hidpp10_directory { uint8_t page; uint8_t offset; uint8_t led_mask; } __attribute__((packed)); int hidpp10_get_profile_directory(struct hidpp10_device *dev, struct hidpp10_directory *out, size_t nelems); int hidpp10_get_current_profile(struct hidpp10_device *dev, int8_t *current_profile); int hidpp10_set_current_profile(struct hidpp10_device *dev, int16_t current_profile); int hidpp10_get_profile(struct hidpp10_device *dev, int8_t number, struct hidpp10_profile *profile); int hidpp10_set_profile(struct hidpp10_device *dev, int8_t number, struct hidpp10_profile *profile); enum ratbag_button_action_special hidpp10_onboard_profiles_get_special(uint8_t code); uint8_t hidpp10_onboard_profiles_get_code_from_special(enum ratbag_button_action_special special); /* -------------------------------------------------------------------------- */ /* 0x51: LED Status */ /* -------------------------------------------------------------------------- */ enum hidpp10_led_status { HIDPP10_LED_STATUS_NO_CHANGE = 0x0, /**< LED does not exist, or should not change */ HIDPP10_LED_STATUS_OFF = 0x1, HIDPP10_LED_STATUS_ON = 0x2, HIDPP10_LED_STATUS_BLINK = 0x3, HIDPP10_LED_STATUS_HEARTBEAT = 0x4, HIDPP10_LED_STATUS_SLOW_ON = 0x5, HIDPP10_LED_STATUS_SLOW_OFF = 0x6, }; int hidpp10_get_led_status(struct hidpp10_device *dev, enum hidpp10_led_status led[6]); int hidpp10_set_led_status(struct hidpp10_device *dev, const enum hidpp10_led_status led[6]); /* -------------------------------------------------------------------------- */ /* 0x54: LED Intensity */ /* -------------------------------------------------------------------------- */ int hidpp10_get_led_intensity(struct hidpp10_device *dev, uint8_t led_intensity_in_percent[6]); /* Granularity for the led intensity is 10% increments. A value of 0 leaves * the intensity unchanged */ int hidpp10_set_led_intensity(struct hidpp10_device *dev, const uint8_t led_intensity_in_percent[6]); /* -------------------------------------------------------------------------- */ /* 0x57: LED Color */ /* -------------------------------------------------------------------------- */ /* Note: this changes the color of the LED only, use 0x51 to turn the LED * on/off */ int hidpp10_get_led_color(struct hidpp10_device *dev, uint8_t *red, uint8_t *green, uint8_t *blue); int hidpp10_set_led_color(struct hidpp10_device *dev, uint8_t red, uint8_t green, uint8_t blue); /* -------------------------------------------------------------------------- */ /* 0x61: Optical Sensor Settings */ /* -------------------------------------------------------------------------- */ int hidpp10_get_optical_sensor_settings(struct hidpp10_device *dev, uint8_t *surface_reflectivity); /* -------------------------------------------------------------------------- */ /* 0x63: Current Resolution */ /* -------------------------------------------------------------------------- */ int hidpp10_get_current_resolution(struct hidpp10_device *dev, uint16_t *xres, uint16_t *yres); int hidpp10_set_current_resolution(struct hidpp10_device *dev, uint16_t xres, uint16_t yres); /* -------------------------------------------------------------------------- */ /* 0x64: USB Refresh Rate */ /* -------------------------------------------------------------------------- */ int hidpp10_get_usb_refresh_rate(struct hidpp10_device *dev, uint16_t *rate); int hidpp10_set_usb_refresh_rate(struct hidpp10_device *dev, uint16_t rate); /* -------------------------------------------------------------------------- */ /* 0xA0: Generic Memory Management */ /* -------------------------------------------------------------------------- */ int hidpp10_erase_memory(struct hidpp10_device *dev, uint8_t page); int hidpp10_write_flash(struct hidpp10_device *dev, uint8_t src_page, uint16_t src_offset, uint8_t dst_page, uint16_t dst_offset, uint16_t size); /* -------------------------------------------------------------------------- */ /* 0x9x: HOT payload */ /* 0xA1: HOT Control Register */ /* -------------------------------------------------------------------------- */ int hidpp10_send_hot_payload(struct hidpp10_device *dev, uint8_t dst_page, uint16_t dst_offset, uint8_t *data, unsigned size); /* -------------------------------------------------------------------------- */ /* 0xA2: Read Sector */ /* -------------------------------------------------------------------------- */ #define HIDPP10_PAGE_SIZE (16 * 2 * 16) int hidpp10_read_memory(struct hidpp10_device *dev, uint8_t page, uint16_t offset, uint8_t bytes[16]); int hidpp10_read_page(struct hidpp10_device *dev, uint8_t page, uint8_t bytes[HIDPP10_PAGE_SIZE]); /* -------------------------------------------------------------------------- */ /* 0xB2: Device Connection and Disconnection (Pairing) */ /* -------------------------------------------------------------------------- */ /** * Open the receiver's lock to allow new devices be paired with this * receiver. The timeout is in seconds, a value of 0 uses the device's * default value (30s). */ int hidpp10_open_lock(struct hidpp10_device *device, uint8_t timeout); int hidpp10_close_lock(struct hidpp10_device *device); int hidpp10_disconnect(struct hidpp10_device *device, int idx); /* -------------------------------------------------------------------------- */ /* 0xB5: Pairing Information */ /* -------------------------------------------------------------------------- */ int hidpp10_get_pairing_information(struct hidpp10_device *dev, uint8_t *report_interval, uint16_t *wpid, uint8_t *device_type); int hidpp10_get_pairing_information_device_name(struct hidpp10_device *dev, char *name, size_t *name_sz); int hidpp10_get_extended_pairing_information(struct hidpp10_device *dev, uint32_t *serial); /* -------------------------------------------------------------------------- */ /* 0xF1: Device Firmware Information */ /* -------------------------------------------------------------------------- */ int hidpp10_get_firmare_information(struct hidpp10_device *dev, uint8_t *major, uint8_t *minor, uint8_t *build_number); libratbag-0.9/src/hidpp20.c000066400000000000000000001546121311552661500155300ustar00rootroot00000000000000/* * HID++ 2.0 library. * * Copyright 2015 Benjamin Tissoires * Copyright 2015 Red Hat, Inc * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /* * Based on the HID++ 2.0 documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #include #include #include #include #include #include "hidpp20.h" #include "libratbag.h" #include "libratbag-hidraw.h" #include "libratbag-util.h" #include "libratbag-private.h" const char* hidpp20_feature_get_name(uint16_t feature) { static char numeric[8]; const char *str; #define CASE_RETURN_STRING(a) case a: return #a; break switch(feature) { CASE_RETURN_STRING(HIDPP_PAGE_ROOT); CASE_RETURN_STRING(HIDPP_PAGE_FEATURE_SET); CASE_RETURN_STRING(HIDPP_PAGE_DEVICE_INFO); CASE_RETURN_STRING(HIDPP_PAGE_BATTERY_LEVEL_STATUS); CASE_RETURN_STRING(HIDPP_PAGE_KBD_REPROGRAMMABLE_KEYS); CASE_RETURN_STRING(HIDPP_PAGE_SPECIAL_KEYS_BUTTONS); CASE_RETURN_STRING(HIDPP_PAGE_MOUSE_POINTER_BASIC); CASE_RETURN_STRING(HIDPP_PAGE_ADJUSTABLE_DPI); CASE_RETURN_STRING(HIDPP_PAGE_ADJUSTABLE_REPORT_RATE); CASE_RETURN_STRING(HIDPP_PAGE_COLOR_LED_EFFECTS); CASE_RETURN_STRING(HIDPP_PAGE_ONBOARD_PROFILES); CASE_RETURN_STRING(HIDPP_PAGE_MOUSE_BUTTON_SPY); default: sprintf_safe(numeric, "%#4x", feature); str = numeric; break; } #undef CASE_RETURN_STRING return str; } static int hidpp20_request_command_allow_error(struct hidpp20_device *device, union hidpp20_message *msg, bool allow_error) { union hidpp20_message read_buffer; int ret; uint8_t hidpp_err = 0; size_t msg_len; /* msg->address is 4 MSB: subcommand, 4 LSB: 4-bit SW identifier so * the device knows who to respond to. kernel uses 0x1 */ const int DEVICE_SW_ID = 0x8; if (msg->msg.address & 0xf) { hidpp_log_raw(&device->base, "hidpp20 error: sw address is already set\n"); return -EINVAL; } msg->msg.address |= DEVICE_SW_ID; msg_len = msg->msg.report_id == REPORT_ID_SHORT ? SHORT_MESSAGE_LENGTH : LONG_MESSAGE_LENGTH; /* Send the message to the Device */ ret = hidpp_write_command(&device->base, msg->data, msg_len); if (ret) goto out_err; /* * Now read the answers from the device: * loop until we get the actual answer or an error code. */ do { ret = hidpp_read_response(&device->base, read_buffer.data, LONG_MESSAGE_LENGTH); /* Wait and retry if the USB timed out */ if (ret == -ETIMEDOUT) { msleep(10); ret = hidpp_read_response(&device->base, read_buffer.data, LONG_MESSAGE_LENGTH); } if (read_buffer.msg.report_id != REPORT_ID_SHORT && read_buffer.msg.report_id != REPORT_ID_LONG) continue; /* actual answer */ if (read_buffer.msg.sub_id == msg->msg.sub_id && read_buffer.msg.address == msg->msg.address) break; /* error */ if ((read_buffer.msg.sub_id == __ERROR_MSG || read_buffer.msg.sub_id == 0xff) && read_buffer.msg.address == msg->msg.sub_id && read_buffer.msg.parameters[0] == msg->msg.address) { hidpp_err = read_buffer.msg.parameters[1]; if (allow_error) hidpp_log_debug(&device->base, " HID++ error from the device (%d): %s (%02x)\n", read_buffer.msg.device_idx, hidpp_errors[hidpp_err] ? hidpp_errors[hidpp_err] : "Undocumented error code", hidpp_err); else hidpp_log_error(&device->base, " HID++ error from the device (%d): %s (%02x)\n", read_buffer.msg.device_idx, hidpp_errors[hidpp_err] ? hidpp_errors[hidpp_err] : "Undocumented error code", hidpp_err); break; } } while (ret > 0); if (ret < 0) { hidpp_log_error(&device->base, " USB error: %s (%d)\n", strerror(-ret), -ret); perror("write"); goto out_err; } if (!hidpp_err) { /* copy the answer for the caller */ *msg = read_buffer; } ret = hidpp_err; out_err: return ret; } int hidpp20_request_command(struct hidpp20_device *device, union hidpp20_message *msg) { return hidpp20_request_command_allow_error(device, msg, false); } /* -------------------------------------------------------------------------- */ /* 0x0000: Root */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_ROOT_IDX 0x00 #define CMD_ROOT_GET_FEATURE 0x00 #define CMD_ROOT_GET_PROTOCOL_VERSION 0x10 /** * Returns the feature index or 0x00 if it is not found. */ static uint8_t hidpp_root_get_feature_idx(struct hidpp20_device *device, uint16_t feature) { unsigned i; /* error or not, we should not ask for feature 0 */ if (feature == 0x0000) return 0; /* feature 0x0000 is always at 0 */ for (i = 1; i < device->feature_count; i++) { if (device->feature_list[i].feature == feature) return i; } return 0; } int hidpp_root_get_feature(struct hidpp20_device *device, uint16_t feature, uint8_t *feature_index, uint8_t *feature_type, uint8_t *feature_version) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = HIDPP_PAGE_ROOT_IDX, .msg.address = CMD_ROOT_GET_FEATURE, }; hidpp_set_unaligned_be_u16(&msg.msg.parameters[0], feature); rc = hidpp20_request_command(device, &msg); if (rc) return rc; *feature_index = msg.msg.parameters[0]; *feature_type = msg.msg.parameters[1]; *feature_version = msg.msg.parameters[2]; hidpp_log_raw(&device->base, "feature 0x%04x is at 0x%02x\n", feature, *feature_index); return 0; } int hidpp20_root_get_protocol_version(struct hidpp20_device *device, unsigned *major, unsigned *minor) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = HIDPP_PAGE_ROOT_IDX, .msg.address = CMD_ROOT_GET_PROTOCOL_VERSION, }; rc = hidpp20_request_command_allow_error(device, &msg, true); if (rc == ERR_INVALID_SUBID) { *major = 1; *minor = 0; return 0; } if (rc == 0) { *major = msg.msg.parameters[0]; *minor = msg.msg.parameters[1]; } return rc; } /* -------------------------------------------------------------------------- */ /* 0x0001: Feature Set */ /* -------------------------------------------------------------------------- */ #define CMD_FEATURE_SET_GET_COUNT 0x00 #define CMD_FEATURE_SET_GET_FEATURE_ID 0x10 static int hidpp20_feature_set_get_count(struct hidpp20_device *device, uint8_t reg) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_FEATURE_SET_GET_COUNT, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return msg.msg.parameters[0]; } static int hidpp20_feature_set_get_feature_id(struct hidpp20_device *device, uint8_t reg, uint8_t feature_index, uint16_t *feature, uint8_t *type) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_FEATURE_SET_GET_FEATURE_ID, .msg.parameters[0] = feature_index, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; *feature = hidpp_get_unaligned_be_u16(msg.msg.parameters); *type = msg.msg.parameters[2]; return 0; } /** * allocates the list of features. * * returns 0 or a negative error */ static int hidpp20_feature_set_get(struct hidpp20_device *device) { uint8_t feature_index, feature_type, feature_version; struct hidpp20_feature *flist; int rc; uint8_t feature_count; unsigned int i; rc = hidpp_root_get_feature(device, HIDPP_PAGE_FEATURE_SET, &feature_index, &feature_type, &feature_version); if (rc) return rc; rc = hidpp20_feature_set_get_count(device, feature_index); if (rc < 0) return rc; feature_count = (uint8_t)rc; if (!feature_count) return -ENOTSUP; flist = zalloc(feature_count * sizeof(struct hidpp20_feature)); for (i = 0; i < feature_count; i++) { rc = hidpp20_feature_set_get_feature_id(device, feature_index, i, &flist[i].feature, &flist[i].type); if (rc) goto err; } device->feature_list = flist; device->feature_count = feature_count; return 0; err: free(flist); return rc; } /* -------------------------------------------------------------------------- */ /* 0x1000: Battery level status */ /* -------------------------------------------------------------------------- */ #define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS 0x00 #define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_CAPABILITY 0x10 int hidpp20_batterylevel_get_battery_level(struct hidpp20_device *device, uint16_t *level, uint16_t *next_level) { uint8_t feature_index; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS, }; int rc; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_BATTERY_LEVEL_STATUS); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; *level = msg.msg.parameters[0]; *next_level = msg.msg.parameters[1]; return msg.msg.parameters[2]; } /* -------------------------------------------------------------------------- */ /* 0x1b00: KBD reprogrammable keys and mouse buttons */ /* -------------------------------------------------------------------------- */ #define CMD_KBD_REPROGRAMMABLE_KEYS_GET_COUNT 0x00 #define CMD_KBD_REPROGRAMMABLE_KEYS_GET_CTRL_ID_INFO 0x10 static int hidpp20_kbd_reprogrammable_keys_get_count(struct hidpp20_device *device, uint8_t reg) { union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_KBD_REPROGRAMMABLE_KEYS_GET_COUNT, }; int rc; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return msg.msg.parameters[0]; } static int hidpp20_kbd_reprogrammable_keys_get_info(struct hidpp20_device *device, uint8_t reg, struct hidpp20_control_id *control) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_KBD_REPROGRAMMABLE_KEYS_GET_CTRL_ID_INFO, .msg.parameters[0] = control->index, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; control->control_id = hidpp_get_unaligned_be_u16(&msg.msg.parameters[0]); control->task_id = hidpp_get_unaligned_be_u16(&msg.msg.parameters[2]); control->flags = msg.msg.parameters[4]; return 0; } int hidpp20_kbd_reprogrammable_keys_get_controls(struct hidpp20_device *device, struct hidpp20_control_id **controls_list) { uint8_t feature_index; struct hidpp20_control_id *c_list, *control; uint8_t num_controls; unsigned i; int rc; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_KBD_REPROGRAMMABLE_KEYS); if (feature_index == 0) return -ENOTSUP; rc = hidpp20_kbd_reprogrammable_keys_get_count(device, feature_index); if (rc < 0) return rc; num_controls = rc; if (num_controls == 0) { *controls_list = NULL; return 0; } c_list = zalloc(num_controls * sizeof(struct hidpp20_control_id)); for (i = 0; i < num_controls; i++) { control = &c_list[i]; control->index = i; rc = hidpp20_kbd_reprogrammable_keys_get_info(device, feature_index, control); if (rc) goto err; /* 0x1b00 and 0x1b04 have the same control/task id mappings. * I hope */ hidpp_log_raw(&device->base, "control %d: cid: '%s' (%d) tid: '%s' (%d) flags: 0x%02x\n", control->index, hidpp20_1b04_get_logical_mapping_name(control->control_id), control->control_id, hidpp20_1b04_get_physical_mapping_name(control->task_id), control->task_id, control->flags); } *controls_list = c_list; return num_controls; err: free(c_list); return rc; } /* -------------------------------------------------------------------------- */ /* 0x8070 - Color LED effects */ /* -------------------------------------------------------------------------- */ #define CMD_COLOR_LED_EFFECTS_GET_INFO 0x00 #define CMD_COLOR_LED_EFFECTS_GET_ZONE_INFO 0x10 static int hidpp20_color_led_zones_get_count(struct hidpp20_device *device, uint8_t reg) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_COLOR_LED_EFFECTS_GET_INFO, }; struct hidpp20_color_led_info *info; uint8_t feature_index; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_COLOR_LED_EFFECTS); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; info = (struct hidpp20_color_led_info *)msg.msg.parameters; return info->zone_count; } int hidpp20_color_led_effects_get_zone_info(struct hidpp20_device *device, uint8_t reg, struct hidpp20_color_led_zone_info *info) { int rc; struct hidpp20_color_led_zone_info *msg_info; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_COLOR_LED_EFFECTS_GET_ZONE_INFO, .msg.parameters[0] = info->index, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; msg_info = (struct hidpp20_color_led_zone_info *)msg.msg.parameters; info->location = hidpp_be_u16_to_cpu(msg_info->location); info->num_effects = msg_info->num_effects; info->persistency_caps = msg_info->persistency_caps; return 0; } int hidpp20_color_led_effects_get_zone_infos(struct hidpp20_device *device, struct hidpp20_color_led_zone_info **infos_list) { uint8_t feature_index; struct hidpp20_color_led_zone_info *i_list, *info; uint8_t num_infos; unsigned i; int rc; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_COLOR_LED_EFFECTS); if (feature_index == 0) return -ENOTSUP; rc = hidpp20_color_led_zones_get_count(device, feature_index); if (rc < 0) return rc; num_infos = rc; if (num_infos == 0) { *infos_list = NULL; return 0; } i_list = zalloc(num_infos * sizeof(struct hidpp20_color_led_zone_info)); for (i = 0; i < num_infos; i++) { info = &i_list[i]; info->index = i; rc = hidpp20_color_led_effects_get_zone_info(device, feature_index, info); if (rc) goto err; hidpp_log_raw(&device->base, "led_info %d: location: %d type %s num_effects: %d persistency_caps: 0x%02x\n", info->index, info->location, hidpp20_8070_get_location_mapping_name(info->location), info->num_effects, info->persistency_caps); } *infos_list = i_list; return num_infos; err: free(i_list); return rc; } /* -------------------------------------------------------------------------- */ /* 0x1b04: Special keys and mouse buttons */ /* -------------------------------------------------------------------------- */ #define CMD_SPECIAL_KEYS_BUTTONS_GET_COUNT 0x00 #define CMD_SPECIAL_KEYS_BUTTONS_GET_INFO 0x10 #define CMD_SPECIAL_KEYS_BUTTONS_GET_REPORTING 0x20 #define CMD_SPECIAL_KEYS_BUTTONS_SET_REPORTING 0x30 static int hidpp20_special_keys_buttons_get_count(struct hidpp20_device *device, uint8_t reg) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_SPECIAL_KEYS_BUTTONS_GET_COUNT, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return msg.msg.parameters[0]; } static int hidpp20_special_keys_buttons_get_info(struct hidpp20_device *device, uint8_t reg, struct hidpp20_control_id *control) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_SPECIAL_KEYS_BUTTONS_GET_INFO, .msg.parameters[0] = control->index, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; control->control_id = hidpp_get_unaligned_be_u16(&msg.msg.parameters[0]); control->task_id = hidpp_get_unaligned_be_u16(&msg.msg.parameters[2]); control->flags = msg.msg.parameters[4]; control->position = msg.msg.parameters[5]; control->group = msg.msg.parameters[6]; control->group_mask = msg.msg.parameters[7]; control->raw_XY = msg.msg.parameters[8] & 0x01; return 0; } static int hidpp20_special_keys_buttons_get_reporting(struct hidpp20_device *device, uint8_t reg, struct hidpp20_control_id *control) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_SPECIAL_KEYS_BUTTONS_GET_REPORTING, }; hidpp_set_unaligned_be_u16(&msg.msg.parameters[0], control->control_id); rc = hidpp20_request_command(device, &msg); if (rc) return rc; control->reporting.remapped = hidpp_get_unaligned_be_u16(&msg.msg.parameters[3]); control->reporting.raw_XY = !!(msg.msg.parameters[2] & 0x10); control->reporting.persist = !!(msg.msg.parameters[2] & 0x04); control->reporting.divert = !!(msg.msg.parameters[2] & 0x01); return 0; } int hidpp20_special_key_mouse_get_controls(struct hidpp20_device *device, struct hidpp20_control_id **controls_list) { uint8_t feature_index; struct hidpp20_control_id *c_list, *control; uint8_t num_controls; unsigned i; int rc; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_SPECIAL_KEYS_BUTTONS); if (feature_index == 0) return -ENOTSUP; rc = hidpp20_special_keys_buttons_get_count(device, feature_index); if (rc < 0) return rc; num_controls = rc; if (num_controls == 0) { *controls_list = NULL; return 0; } c_list = zalloc(num_controls * sizeof(struct hidpp20_control_id)); for (i = 0; i < num_controls; i++) { control = &c_list[i]; control->index = i; rc = hidpp20_special_keys_buttons_get_info(device, feature_index, control); if (rc) goto err; rc = hidpp20_special_keys_buttons_get_reporting(device, feature_index, control); if (rc) goto err; hidpp_log_raw(&device->base, "control %d: cid: '%s' (%d) tid: '%s' (%d) flags: 0x%02x pos: %d group: %d gmask: 0x%02x raw_XY: %s\n" " reporting: raw_xy: %s persist: %s divert: %s remapped: '%s' (%d)\n", control->index, hidpp20_1b04_get_logical_mapping_name(control->control_id), control->control_id, hidpp20_1b04_get_physical_mapping_name(control->task_id), control->task_id, control->flags, control->position, control->group, control->group_mask, control->raw_XY ? "yes" : "no", control->reporting.raw_XY ? "yes" : "no", control->reporting.persist ? "yes" : "no", control->reporting.divert ? "yes" : "no", hidpp20_1b04_get_logical_mapping_name(control->reporting.remapped), control->reporting.remapped); } *controls_list = c_list; return num_controls; err: free(c_list); return rc; } int hidpp20_special_key_mouse_set_control(struct hidpp20_device *device, struct hidpp20_control_id *control) { uint8_t feature_index; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_SPECIAL_KEYS_BUTTONS_SET_REPORTING, }; hidpp_set_unaligned_be_u16(&msg.msg.parameters[0], control->control_id); hidpp_set_unaligned_be_u16(&msg.msg.parameters[3], control->reporting.remapped); feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_SPECIAL_KEYS_BUTTONS); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; if (control->reporting.divert) msg.msg.parameters[2] |= 0x03; if (control->reporting.persist) msg.msg.parameters[2] |= 0x0c; if (control->reporting.raw_XY) msg.msg.parameters[2] |= 0x20; return hidpp20_request_command(device, &msg); } /* -------------------------------------------------------------------------- */ /* 0x2200: Mouse Pointer Basic Optical Sensors */ /* -------------------------------------------------------------------------- */ #define CMD_MOUSE_POINTER_BASIC_GET_INFO 0x00 int hidpp20_mousepointer_get_mousepointer_info(struct hidpp20_device *device, uint16_t *resolution, uint8_t *flags) { uint8_t feature_index; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_MOUSE_POINTER_BASIC_GET_INFO, }; int rc; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_MOUSE_POINTER_BASIC); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; *resolution = hidpp_get_unaligned_be_u16(msg.msg.parameters); *flags = msg.msg.parameters[2]; return 0; } /* -------------------------------------------------------------------------- */ /* 0x2201: Adjustable DPI */ /* -------------------------------------------------------------------------- */ #define CMD_ADJUSTABLE_DPI_GET_SENSOR_COUNT 0x00 #define CMD_ADJUSTABLE_DPI_GET_SENSOR_DPI_LIST 0x10 #define CMD_ADJUSTABLE_DPI_GET_SENSOR_DPI 0x20 #define CMD_ADJUSTABLE_DPI_SET_SENSOR_DPI 0x30 static int hidpp20_adjustable_dpi_get_count(struct hidpp20_device *device, uint8_t reg) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_ADJUSTABLE_DPI_GET_SENSOR_COUNT, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return msg.msg.parameters[0]; } static int hidpp20_adjustable_dpi_get_dpi_list(struct hidpp20_device *device, uint8_t reg, struct hidpp20_sensor *sensor) { int rc; unsigned i = 1, dpi_index = 0; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_ADJUSTABLE_DPI_GET_SENSOR_DPI_LIST, .msg.parameters[0] = sensor->index, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; sensor->dpi_min = 0xffff; sensor->index = msg.msg.parameters[0]; while (i < LONG_MESSAGE_LENGTH - 4U && hidpp_get_unaligned_be_u16(&msg.msg.parameters[i]) != 0) { uint16_t value = hidpp_get_unaligned_be_u16(&msg.msg.parameters[i]); if (value > 0xe000) { sensor->dpi_steps = value - 0xe000; } else { sensor->dpi_min = value < sensor->dpi_min ? value : sensor->dpi_min; sensor->dpi_max = value > sensor->dpi_max ? value : sensor->dpi_max; sensor->dpi_list[dpi_index++] = value; } assert(sensor->dpi_list[dpi_index] == 0x0000); i += 2; } return 0; } static int hidpp20_adjustable_dpi_get_dpi(struct hidpp20_device *device, uint8_t reg, struct hidpp20_sensor *sensor) { int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.sub_id = reg, .msg.address = CMD_ADJUSTABLE_DPI_GET_SENSOR_DPI, .msg.parameters[0] = sensor->index, }; rc = hidpp20_request_command(device, &msg); if (rc) return rc; sensor->dpi = hidpp_get_unaligned_be_u16(&msg.msg.parameters[1]); sensor->default_dpi = hidpp_get_unaligned_be_u16(&msg.msg.parameters[3]); return 0; } int hidpp20_adjustable_dpi_get_sensors(struct hidpp20_device *device, struct hidpp20_sensor **sensors_list) { uint8_t feature_index; struct hidpp20_sensor *s_list, *sensor; uint8_t num_sensors; unsigned i; int rc; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ADJUSTABLE_DPI); if (feature_index == 0) return -ENOTSUP; rc = hidpp20_adjustable_dpi_get_count(device, feature_index); if (rc < 0) return rc; num_sensors = rc; if (num_sensors == 0) { *sensors_list = NULL; return 0; } s_list = zalloc(num_sensors * sizeof(struct hidpp20_sensor)); for (i = 0; i < num_sensors; i++) { sensor = &s_list[i]; sensor->index = i; rc = hidpp20_adjustable_dpi_get_dpi_list(device, feature_index, sensor); if (rc) goto err; rc = hidpp20_adjustable_dpi_get_dpi(device, feature_index, sensor); if (rc) goto err; hidpp_log_raw(&device->base, "sensor %d: current dpi: %d (default: %d) min: %d max: %d steps: %d\n", sensor->index, sensor->dpi, sensor->default_dpi, sensor->dpi_min, sensor->dpi_max, sensor->dpi_steps); } *sensors_list = s_list; return num_sensors; err: num_sensors = 0; free(s_list); return rc > 0 ? -EPROTO : rc; } int hidpp20_adjustable_dpi_set_sensor_dpi(struct hidpp20_device *device, struct hidpp20_sensor *sensor, uint16_t dpi) { uint8_t feature_index; uint16_t returned_parameters; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_ADJUSTABLE_DPI_SET_SENSOR_DPI, .msg.parameters[0] = sensor->index, }; hidpp_set_unaligned_be_u16(&msg.msg.parameters[1], dpi); feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ADJUSTABLE_DPI); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; returned_parameters = hidpp_get_unaligned_be_u16(&msg.msg.parameters[1]); /* version 0 of the protocol does not echo the parameters */ if (returned_parameters != dpi && returned_parameters) return -EIO; return 0; } /* -------------------------------------------------------------------------- */ /* 0x8100 - Onboard Profiles */ /* -------------------------------------------------------------------------- */ #define CMD_ONBOARD_PROFILES_GET_PROFILES_DESCR 0x00 #define CMD_ONBOARD_PROFILES_SET_ONBOARD_MODE 0x10 #define CMD_ONBOARD_PROFILES_GET_ONBOARD_MODE 0x20 #define CMD_ONBOARD_PROFILES_SET_CURRENT_PROFILE 0x30 #define CMD_ONBOARD_PROFILES_GET_CURRENT_PROFILE 0x40 #define CMD_ONBOARD_PROFILES_MEMORY_READ 0x50 #define CMD_ONBOARD_PROFILES_MEMORY_ADDR_WRITE 0x60 #define CMD_ONBOARD_PROFILES_MEMORY_WRITE 0x70 #define CMD_ONBOARD_PROFILES_MEMORY_WRITE_END 0x80 #define CMD_ONBOARD_PROFILES_GET_CURRENT_DPI_INDEX 0xb0 #define CMD_ONBOARD_PROFILES_SET_CURRENT_DPI_INDEX 0xc0 #define HIDPP20_PAGE_SIZE (16*16) #define HIDPP20_PROFILE_SIZE HIDPP20_PAGE_SIZE #define HIDPP20_BUTTON_HID 0x80 #define HIDPP20_MODE_NO_CHANGE 0x00 #define HIDPP20_ONBOARD_MODE 0x01 #define HIDPP20_HOST_MODE 0x02 #define HIDPP20_ONBOARD_PROFILES_MEMORY_TYPE_G402 0x01 #define HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G402 0x01 #define HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G303 0x02 #define HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G900 0x03 #define HIDPP20_ONBOARD_PROFILES_MACRO_TYPE_G402 0x01 struct hidpp20_onboard_profiles_info { uint8_t memory_model_id; uint8_t profile_format_id; uint8_t macro_format_id; uint8_t profile_count; uint8_t profile_count_oob; uint8_t button_count; uint8_t sector_count; uint16_t sector_size; uint8_t mechanical_layout; uint8_t various_info; uint8_t reserved[5]; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_onboard_profiles_info) == 16, "Invalid size"); int hidpp20_onboard_profiles_read_memory(struct hidpp20_device *device, uint8_t read_rom, uint8_t page, uint16_t section, uint8_t result[16]) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_MEMORY_READ, .msg.parameters[0] = read_rom, .msg.parameters[1] = page, }; if (read_rom > 1) return -EINVAL; hidpp_set_unaligned_be_u16(&msg.msg.parameters[2], section); feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; /* msg.msg.parameters is guaranteed to have a size >= 16 */ memcpy(result, msg.msg.parameters, 16); return 0; } static int hidpp20_onboard_profiles_write_start(struct hidpp20_device *device, uint16_t page, uint16_t section, uint16_t size) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_MEMORY_ADDR_WRITE, }; hidpp_set_unaligned_be_u16(&msg.msg.parameters[0], page); hidpp_set_unaligned_be_u16(&msg.msg.parameters[2], section); hidpp_set_unaligned_be_u16(&msg.msg.parameters[4], size); feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return 0; } static int hidpp20_onboard_profiles_write_end(struct hidpp20_device *device) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_MEMORY_WRITE_END, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return 0; } static int hidpp20_onboard_profiles_write_data(struct hidpp20_device *device, uint16_t page, uint16_t section, uint8_t *data, size_t len) { uint8_t feature_index; int transfered = 0; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_LONG, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_MEMORY_WRITE, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_onboard_profiles_write_start(device, page, section, HIDPP20_PAGE_SIZE); if (rc) return rc; while (len >= 16) { msg.msg.address = CMD_ONBOARD_PROFILES_MEMORY_WRITE; memcpy(msg.msg.parameters, data, 16); rc = hidpp20_request_command(device, &msg); if (rc) return rc; len -= 16; data += 16; transfered += 16; } rc = hidpp20_onboard_profiles_write_end(device); if (rc) return rc; return transfered; } static int hidpp20_onboard_profiles_write_page(struct hidpp20_device *device, uint16_t page, uint8_t data[HIDPP20_PAGE_SIZE]) { uint16_t crc; crc = hidpp_crc_ccitt(data, HIDPP20_PAGE_SIZE - 2); hidpp_set_unaligned_be_u16(&data[HIDPP20_PROFILE_SIZE - 2], crc); return hidpp20_onboard_profiles_write_data(device, page, 0x00, data, HIDPP20_PAGE_SIZE); } static int hidpp20_onboard_profiles_get_onboard_mode(struct hidpp20_device *device) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_GET_ONBOARD_MODE, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return msg.msg.parameters[0]; } static int hidpp20_onboard_profiles_set_onboard_mode(struct hidpp20_device *device, uint8_t onboard_mode) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_SET_ONBOARD_MODE, .msg.parameters[1] = onboard_mode, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return 0; } int hidpp20_onboard_profiles_get_current_profile(struct hidpp20_device *device) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_GET_CURRENT_PROFILE, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return msg.msg.parameters[1]; } int hidpp20_onboard_profiles_set_current_profile(struct hidpp20_device *device, uint8_t index) { uint8_t feature_index; int rc; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_SET_CURRENT_PROFILE, .msg.parameters[1] = index + 1, }; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; return 0; } static int hidpp20_onboard_profiles_initialize(struct hidpp20_device *device, struct hidpp20_profiles **profiles_list) { uint8_t feature_index; int rc; struct hidpp20_profiles *profiles; union hidpp20_message msg = { .msg.report_id = REPORT_ID_SHORT, .msg.device_idx = device->index, .msg.address = CMD_ONBOARD_PROFILES_GET_PROFILES_DESCR, }; struct hidpp20_onboard_profiles_info *info; int onboard_mode; feature_index = hidpp_root_get_feature_idx(device, HIDPP_PAGE_ONBOARD_PROFILES); if (feature_index == 0) return -ENOTSUP; msg.msg.sub_id = feature_index; rc = hidpp20_request_command(device, &msg); if (rc) return rc; info = (struct hidpp20_onboard_profiles_info *)msg.msg.parameters; if (info->memory_model_id != HIDPP20_ONBOARD_PROFILES_MEMORY_TYPE_G402) { hidpp_log_error(&device->base, "Memory layout not supported: 0x%02x.\n", info->memory_model_id); return -ENOTSUP; } if ((info->profile_format_id != HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G402) && (info->profile_format_id != HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G303) && (info->profile_format_id != HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G900)) { hidpp_log_error(&device->base, "Profile layout not supported: 0x%02x.\n", info->profile_format_id); return -ENOTSUP; } if (info->macro_format_id != HIDPP20_ONBOARD_PROFILES_MACRO_TYPE_G402) { hidpp_log_error(&device->base, "Macro format not supported: 0x%02x.\n", info->macro_format_id); return -ENOTSUP; } info->sector_size = hidpp_be_u16_to_cpu(info->sector_size); if (info->sector_size != HIDPP20_PAGE_SIZE) { hidpp_log_error(&device->base, "Unsupported sector size: %d.\n", info->sector_size); return -ENOTSUP; } rc = hidpp20_onboard_profiles_get_onboard_mode(device); if (rc < 0) return rc; onboard_mode = rc; if (onboard_mode != HIDPP20_ONBOARD_MODE) { hidpp_log_raw(&device->base, "not on the correct mode: %d.\n", onboard_mode); rc = hidpp20_onboard_profiles_set_onboard_mode(device, HIDPP20_ONBOARD_MODE); if (rc < 0) return rc; } profiles = zalloc(sizeof(struct hidpp20_profiles) + info->profile_count * sizeof(struct hidpp20_profile)); profiles->num_profiles = info->profile_count; profiles->num_rom_profiles = info->profile_count_oob; profiles->num_buttons = msg.msg.parameters[5] <= 16 ? msg.msg.parameters[5] : 16; profiles->num_modes = HIDPP20_DPI_COUNT; profiles->num_leds = HIDPP20_LED_COUNT; profiles->has_g_shift = (info->mechanical_layout & 0x03) == 2; profiles->has_dpi_shift = ((info->mechanical_layout & 0x0c) >> 2) == 2; switch(info->various_info & 0x07) { case 1: profiles->corded = 1; break; case 2: profiles->wireless = 1; break; case 4: profiles->corded = 1; profiles->wireless = 1; break; } *profiles_list = profiles; return 0; } static int hidpp20_onboard_profiles_macro_next(struct hidpp20_device *device, uint8_t memory[32], uint16_t *index, union hidpp20_macro_data *macro) { int rc = 0; unsigned int step = 1; if (*index >= 32 - sizeof(union hidpp20_macro_data)) { hidpp_log_error(&device->base, "error while parsing macro.\n"); return -EFAULT; } memcpy(macro, &memory[*index], sizeof(union hidpp20_macro_data)); switch (macro->any.type) { case HIDPP20_MACRO_DELAY: /* fallthrough */ case HIDPP20_MACRO_KEY_PRESS: /* fallthrough */ case HIDPP20_MACRO_KEY_RELEASE: /* fallthrough */ case HIDPP20_MACRO_JUMP: step = 3; rc = -EAGAIN; break; case HIDPP20_MACRO_NOOP: step = 1; rc = -EAGAIN; break; case HIDPP20_MACRO_END: step = 1; return 0; default: hidpp_log_error(&device->base, "unknown tag: 0x%02x\n", macro->any.type); rc = -EFAULT; } if ((*index + step) & 0xF0) /* the next item will be on the following chunk */ return -ENOMEM; *index += step; return rc; } static int hidpp20_onboard_profiles_read_macro(struct hidpp20_device *device, uint8_t page, uint8_t offset, union hidpp20_macro_data **return_macro) { uint8_t memory[HIDPP20_PAGE_SIZE]; union hidpp20_macro_data *macro = NULL; unsigned count = 0; unsigned index = 0; uint16_t mem_index = 0; int rc = -ENOMEM; do { if (count == index) { union hidpp20_macro_data *tmp; count += 32; /* manual realloc to have the memory zero-initialized */ tmp = zalloc(count * sizeof(union hidpp20_macro_data)); if (macro) { memcpy(tmp, macro, (count - 32) * sizeof(union hidpp20_macro_data)); free(macro); } macro = tmp; } if (rc == -ENOMEM) { offset += mem_index; rc = hidpp20_onboard_profiles_read_memory(device, 0, page, offset, memory); if (rc) goto out_err; mem_index = 0; } rc = hidpp20_onboard_profiles_macro_next(device, memory, &mem_index, ¯o[index]); if (rc == -EFAULT) goto out_err; if (rc != -ENOMEM) { if (macro[index].any.type == HIDPP20_MACRO_JUMP) { page = macro[index].jump.page; offset = macro[index].jump.offset; mem_index = 0; /* no need to store the jump in memory */ index--; /* force memory fetching */ rc = -ENOMEM; } index++; } } while (rc); *return_macro = macro; return index; out_err: if (macro) free(macro); return rc; } static int hidpp20_onboard_profiles_parse_macro(struct hidpp20_device *device, uint8_t page, uint8_t offset, union hidpp20_macro_data **return_macro) { union hidpp20_macro_data *m, *macro = NULL; unsigned i, count = 0; int rc; rc = hidpp20_onboard_profiles_read_macro(device, page, offset, ¯o); if (rc < 0) return rc; count = rc; for (i = 0; i < count; i++) { m = ¯o[i]; switch (m->any.type) { case HIDPP20_MACRO_DELAY: m->delay.time = hidpp_be_u16_to_cpu(m->delay.time); break; case HIDPP20_MACRO_KEY_PRESS: break; case HIDPP20_MACRO_KEY_RELEASE: break; case HIDPP20_MACRO_JUMP: break; case HIDPP20_MACRO_END: break; case HIDPP20_MACRO_NOOP: break; default: hidpp_log_error(&device->base, "unknown tag: 0x%02x\n", m->any.type); } } *return_macro = macro; return 0; } static unsigned int hidpp20_onboard_profiles_compute_dict_size(const struct hidpp20_device *device, const struct hidpp20_profiles *profiles) { unsigned p, num_offset; num_offset = 0; p = profiles->num_profiles; while (p) { p >>= 2; num_offset += 16; } return num_offset; } int hidpp20_onboard_profiles_allocate(struct hidpp20_device *device, struct hidpp20_profiles **profiles_list) { unsigned i, offset, num_offset; int rc; uint8_t data[HIDPP20_PAGE_SIZE] = {0}; struct hidpp20_profiles *profiles = NULL; rc = hidpp20_onboard_profiles_initialize(device, &profiles); if (rc < 0) return rc; assert(profiles); num_offset = hidpp20_onboard_profiles_compute_dict_size(device, profiles); for (offset = 0; offset < num_offset; offset += 16) { rc = hidpp20_onboard_profiles_read_memory(device, 0x00, 0x00, offset, data + offset); if (rc < 0) return rc; } for (i = 0; i < profiles->num_profiles; i++) { uint8_t *d = data + offset + 4 * i; if (d[0] == 0xFF && d[1] == 0xFF) break; profiles->profiles[i].index = d[1]; profiles->profiles[i].enabled = d[2]; } *profiles_list = profiles; return profiles->num_profiles; } void hidpp20_onboard_profiles_destroy(struct hidpp20_device *device, struct hidpp20_profiles *profiles_list) { struct hidpp20_profile *profile; union hidpp20_macro_data **macro; unsigned i; if (!profiles_list) return; for (i = 0; i < profiles_list->num_profiles; i++) { profile = &profiles_list->profiles[i]; ARRAY_FOR_EACH(profile->macros, macro) { if (*macro) free(*macro); } } free(profiles_list); } static int hidpp20_onboard_profiles_find_and_read_profile(struct hidpp20_device *device, unsigned int index, uint8_t *data, unsigned int num_rom_profiles) { int rc; unsigned i; uint16_t crc, read_crc; rc = hidpp20_onboard_profiles_read_memory(device, 0, index + 1, 0x00, data); if (rc < 0) return rc; if (data[0] > 0) { for (i = 1; i < HIDPP20_PROFILE_SIZE / 0x10; i++) { rc = hidpp20_onboard_profiles_read_memory(device, 0, index + 1, i * 0x10, data + i * 0x10); if (rc < 0) return rc; } crc = hidpp_crc_ccitt(data, HIDPP20_PROFILE_SIZE - 2); read_crc = hidpp_get_unaligned_be_u16(&data[HIDPP20_PROFILE_SIZE - 2]); if (crc == read_crc) return 0; } /* something went wrong, the mouse is using the factory profile in ROM */ if (num_rom_profiles == 0) return -EINVAL; if (index >= num_rom_profiles) index = num_rom_profiles - 1; for (i = 0; i < HIDPP20_PROFILE_SIZE / 0x10; i++) { rc = hidpp20_onboard_profiles_read_memory(device, 1, index + 1, i * 0x10, data + i * 0x10); if (rc != 0) return rc; } return 0; } static int hidpp20_onboard_profiles_enable_profile(struct hidpp20_device *device, unsigned int index, struct hidpp20_profiles *profiles_list) { unsigned int i, buffer_index = 0; uint8_t data[HIDPP20_PROFILE_SIZE] = {0}; if (profiles_list->profiles[index].enabled) return 0; profiles_list->profiles[index].index = index + 1; profiles_list->profiles[index].enabled = 0x01; for (i = 0; i < profiles_list->num_profiles; i++) { data[buffer_index++] = 0x00; data[buffer_index++] = i + 1; data[buffer_index++] = profiles_list->profiles[i].enabled; data[buffer_index++] = 0x00; } data[buffer_index++] = 0xFF; data[buffer_index++] = 0xFF; data[buffer_index++] = 0x00; data[buffer_index++] = 0x00; memset(data + buffer_index, 0xff, sizeof(data) - buffer_index); hidpp_log_buf_debug(&device->base, "dictionary: ", data, hidpp20_onboard_profiles_compute_dict_size(device, profiles_list)); return hidpp20_onboard_profiles_write_page(device, 0x0000, data); } union hidpp20_internal_profile { uint8_t data[HIDPP20_PROFILE_SIZE]; struct { uint8_t report_rate; uint8_t default_dpi; uint8_t switched_dpi; uint16_t dpi[5]; struct hidpp20_color profile_color; uint8_t power_mode; uint8_t angle_snapping; uint8_t reserved[14]; union hidpp20_button_binding buttons[16]; union hidpp20_button_binding alternate_buttons[16]; union { char txt[16 * 3]; uint8_t raw[16 * 3]; } name; struct hidpp20_internal_led leds[2]; /* G303, g502, g900 only */ uint8_t free[24]; uint16_t crc; } __attribute__((packed)) profile; }; _Static_assert(sizeof(union hidpp20_internal_profile) == HIDPP20_PROFILE_SIZE, "Invalid size"); static void hidpp20_buttons_to_cpu(struct hidpp20_device *device, struct hidpp20_profile *profile, union hidpp20_button_binding *buttons, unsigned int count) { unsigned int i; for (i = 0; i < count; i++) { union hidpp20_button_binding *b = &buttons[i]; union hidpp20_button_binding *button = &profile->buttons[i]; button->any.type = b->any.type; switch (b->any.type) { case HIDPP20_BUTTON_HID_TYPE: button->subany.subtype = b->subany.subtype; switch (b->subany.subtype) { case HIDPP20_BUTTON_HID_TYPE_MOUSE: button->button.buttons = ffs(hidpp_be_u16_to_cpu(b->button.buttons)); break; case HIDPP20_BUTTON_HID_TYPE_KEYBOARD: button->keyboard_keys.modifier_flags = b->keyboard_keys.modifier_flags; button->keyboard_keys.key = b->keyboard_keys.key; break; case HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL: button->consumer_control.consumer_control = hidpp_be_u16_to_cpu(b->consumer_control.consumer_control); break; } break; case HIDPP20_BUTTON_SPECIAL: button->special.special = b->special.special; break; case HIDPP20_BUTTON_MACRO: if (profile->macros[i]) { free(profile->macros[i]); profile->macros[i] = NULL; } hidpp20_onboard_profiles_parse_macro(device, b->macro.page, b->macro.offset, &profile->macros[i]); /* the actual page is stored in the 'zero' field */ button->macro.page = i; button->macro.offset = b->macro.offset; button->macro.zero = b->macro.page; break; case HIDPP20_BUTTON_DISABLED: break; default: break; } } } static void hidpp20_buttons_from_cpu(struct hidpp20_profile *profile, union hidpp20_button_binding *buttons, unsigned int count) { unsigned int i; for (i = 0; i < count; i++) { union hidpp20_button_binding *button = &buttons[i]; union hidpp20_button_binding *b = &profile->buttons[i]; button->any.type = b->any.type; switch (b->any.type) { case HIDPP20_BUTTON_HID_TYPE: button->subany.subtype = b->subany.subtype; switch (b->subany.subtype) { case HIDPP20_BUTTON_HID_TYPE_MOUSE: button->button.buttons = hidpp_cpu_to_be_u16(1U << (b->button.buttons - 1)); break; case HIDPP20_BUTTON_HID_TYPE_KEYBOARD: button->keyboard_keys.modifier_flags = b->keyboard_keys.modifier_flags; button->keyboard_keys.key = b->keyboard_keys.key; break; case HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL: button->consumer_control.type = HIDPP20_BUTTON_HID_TYPE; button->consumer_control.subtype = HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL; button->consumer_control.consumer_control = hidpp_cpu_to_be_u16(b->consumer_control.consumer_control); break; } break; case HIDPP20_BUTTON_SPECIAL: button->special.special = b->special.special; break; case HIDPP20_BUTTON_DISABLED: break; case HIDPP20_BUTTON_MACRO: /* the actual page is stored in the 'zero' field */ button->macro.page = b->macro.zero; button->macro.offset = b->macro.offset; button->macro.zero = 0; break; default: break; } } } static void hidpp20_onboard_profiles_read_led(struct hidpp20_led *led, struct hidpp20_internal_led internal_led) { uint16_t period = 0; uint8_t brightness = 0; led->mode = (enum hidpp20_led_mode)internal_led.mode; switch (led->mode) { case HIDPP20_LED_CYCLE: period = hidpp_be_u16_to_cpu(internal_led.effect.cycle.period_or_speed); brightness = internal_led.effect.cycle.intensity; if (brightness == 0) brightness = 100; break; case HIDPP20_LED_BREATHING: period = hidpp_be_u16_to_cpu(internal_led.effect.breath.period_or_speed); brightness = internal_led.effect.breath.intensity; if (brightness == 0) brightness = 100; led->color = internal_led.effect.breath.color; break; case HIDPP20_LED_ON: led->color = internal_led.effect.fixed.color; break; case HIDPP20_LED_OFF: break; } led->period = period; led->brightness = brightness; } int hidpp20_onboard_profiles_read(struct hidpp20_device *device, unsigned int index, struct hidpp20_profiles *profiles_list) { union hidpp20_internal_profile pdata = {0}; uint8_t *data = pdata.data; struct hidpp20_profile *profile = &profiles_list->profiles[index]; unsigned i; int rc; if (index >= profiles_list->num_profiles) return -EINVAL; rc = hidpp20_onboard_profiles_find_and_read_profile(device, index, data, profiles_list->num_rom_profiles); if (rc != 0) return rc; profile->report_rate = 1000 / max(1, pdata.profile.report_rate); profile->default_dpi = pdata.profile.default_dpi; profile->switched_dpi = pdata.profile.switched_dpi; for (i = 0; i < 5; i++) { profile->dpi[i] = hidpp_get_unaligned_le_u16(&data[2 * i + 3]); } for (i = 0; i < profiles_list->num_leds; i++) hidpp20_onboard_profiles_read_led(&profile->leds[i], pdata.profile.leds[i]); hidpp20_buttons_to_cpu(device, profile, pdata.profile.buttons, profiles_list->num_buttons); memcpy(profile->name, pdata.profile.name.txt, sizeof(profile->name)); /* force terminating '\0' */ profile->name[sizeof(profile->name) - 1] = '\0'; /* check if we are using the default name or not */ for (i = 0; i < sizeof(profile->name); i++) { if (pdata.profile.name.raw[i] != 0xff) break; } if (i == sizeof(profile->name)) memset(profile->name, 0, sizeof(profile->name)); return 0; } static void hidpp20_onboard_profiles_write_led(struct hidpp20_internal_led *internal_led, struct hidpp20_led *led) { uint16_t period = led->period; uint8_t brightness = led->brightness; memset(internal_led, 0, sizeof(*internal_led)); internal_led->mode = (uint8_t)led->mode; switch (led->mode) { case HIDPP20_LED_CYCLE: internal_led->effect.cycle.period_or_speed = hidpp_cpu_to_be_u16(period); if (brightness < 100) internal_led->effect.cycle.intensity = brightness; else internal_led->effect.cycle.intensity = 0; break; case HIDPP20_LED_BREATHING: internal_led->effect.breath.color.red = led->color.red; internal_led->effect.breath.color.blue = led->color.blue; internal_led->effect.breath.color.green = led->color.green; internal_led->effect.breath.period_or_speed = hidpp_cpu_to_be_u16(period); if (brightness < 100) internal_led->effect.breath.intensity = brightness; else internal_led->effect.breath.intensity = 0; break; case HIDPP20_LED_ON: internal_led->effect.fixed.color.red = led->color.red; internal_led->effect.fixed.color.blue = led->color.blue; internal_led->effect.fixed.color.green = led->color.green; internal_led->effect.fixed.effect = 0; break; case HIDPP20_LED_OFF: break; } } int hidpp20_onboard_profiles_write(struct hidpp20_device *device, unsigned int index, struct hidpp20_profiles *profiles_list) { union hidpp20_internal_profile pdata = {0}; uint8_t *data = pdata.data; struct hidpp20_profile *profile = &profiles_list->profiles[index]; unsigned i; int rc; if (index >= profiles_list->num_profiles) return -EINVAL; rc = hidpp20_onboard_profiles_find_and_read_profile(device, index, data, profiles_list->num_rom_profiles); if (rc < 0) return rc; rc = hidpp20_onboard_profiles_enable_profile(device, index, profiles_list); if (rc < 0) return rc; pdata.profile.report_rate = 1000 / profile->report_rate; pdata.profile.default_dpi = profile->default_dpi; pdata.profile.switched_dpi = profile->switched_dpi; for (i = 0; i < 5; i++) { pdata.profile.dpi[i] = hidpp_cpu_to_le_u16(profile->dpi[i]); } for (i = 0; i < profiles_list->num_leds; i++) hidpp20_onboard_profiles_write_led(&pdata.profile.leds[i], &profile->leds[i]); hidpp20_buttons_from_cpu(profile, pdata.profile.buttons, profiles_list->num_buttons); memcpy(pdata.profile.name.txt, profile->name, sizeof(profile->name)); rc = hidpp20_onboard_profiles_write_page(device, index + 1, data); if (rc < 0) return rc; if (rc != sizeof(pdata.data)) return -EIO; return 0; } static const enum ratbag_button_action_special hidpp20_profiles_specials[] = { [0x00] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, [0x01] = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT, [0x02] = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT, [0x03] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP, [0x04] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN, [0x05] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT, [0x06] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP, [0x07] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE, [0x08] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, [0x09] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, [0x0a] = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP, [0x0b] = RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE, [0x0c] = RATBAG_BUTTON_ACTION_SPECIAL_BATTERY_LEVEL, [0x0d ... 0xff] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID, }; enum ratbag_button_action_special hidpp20_onboard_profiles_get_special(uint8_t code) { return hidpp20_profiles_specials[code]; } uint8_t hidpp20_onboard_profiles_get_code_from_special(enum ratbag_button_action_special special) { uint8_t i = 0; while (++i) { if (hidpp20_profiles_specials[i] == special) return i; } return RATBAG_BUTTON_ACTION_SPECIAL_INVALID; } /* -------------------------------------------------------------------------- */ /* generic hidpp20 device operations */ /* -------------------------------------------------------------------------- */ struct hidpp20_device * hidpp20_device_new(const struct hidpp_device *base, unsigned int idx) { struct hidpp20_device *dev; int rc; dev = zalloc(sizeof(*dev)); dev->index = idx; dev->base = *base; dev->proto_major = 1; dev->proto_minor = 0; rc = hidpp20_root_get_protocol_version(dev, &dev->proto_major, &dev->proto_minor); if (rc) { /* communication error, best to ignore the device */ goto err; } if (dev->proto_major < 2) goto err; rc = hidpp20_feature_set_get(dev); if (rc < 0) goto err; return dev; err: free(dev); return NULL; } void hidpp20_device_destroy(struct hidpp20_device *device) { free(device->feature_list); free(device); } libratbag-0.9/src/hidpp20.h000066400000000000000000000433241311552661500155320ustar00rootroot00000000000000/* * HID++ 2.0 library - headers file. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /* * Based on the HID++ 2.0 documentation provided by Nestor Lopez Casado at: * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ #pragma once #include #include "hidpp-generic.h" struct _hidpp20_message { uint8_t report_id; uint8_t device_idx; uint8_t sub_id; uint8_t address; uint8_t parameters[LONG_MESSAGE_LENGTH - 4U]; } __attribute__((packed)); union hidpp20_message { struct _hidpp20_message msg; uint8_t data[LONG_MESSAGE_LENGTH]; }; struct hidpp20_feature { uint16_t feature; uint8_t type; }; struct hidpp20_device { struct hidpp_device base; unsigned int index; unsigned proto_major; unsigned proto_minor; unsigned feature_count; struct hidpp20_feature *feature_list; }; int hidpp20_request_command(struct hidpp20_device *dev, union hidpp20_message *msg); const char *hidpp20_feature_get_name(uint16_t feature); /* -------------------------------------------------------------------------- */ /* generic hidpp20 device operations */ /* -------------------------------------------------------------------------- */ struct hidpp20_device * hidpp20_device_new(const struct hidpp_device *base, unsigned int idx); void hidpp20_device_destroy(struct hidpp20_device *device); /* -------------------------------------------------------------------------- */ /* 0x0000: Root */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_ROOT 0x0000 int hidpp_root_get_feature(struct hidpp20_device *device, uint16_t feature, uint8_t *feature_index, uint8_t *feature_type, uint8_t *feature_version); int hidpp20_root_get_protocol_version(struct hidpp20_device *dev, unsigned *major, unsigned *minor); /* -------------------------------------------------------------------------- */ /* 0x0001: Feature Set */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_FEATURE_SET 0x0001 /* -------------------------------------------------------------------------- */ /* 0x0003: Device Info */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_DEVICE_INFO 0x0003 /* -------------------------------------------------------------------------- */ /* 0x1000: Battery level status */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_BATTERY_LEVEL_STATUS 0x1000 enum hidpp20_battery_status { BATTERY_STATUS_DISCHARGING = 0, BATTERY_STATUS_RECHARGING, BATTERY_STATUS_CHARGING_IN_FINAL_STATE, BATTERY_STATUS_CHARGE_COMPLETE, BATTERY_STATUS_RECHARGING_BELOW_OPTIMAL_SPEED, BATTERY_STATUS_INVALID_BATTERY_TYPE, BATTERY_STATUS_THERMAL_ERROR, BATTERY_STATUS_OTHER_CHARGING_ERROR, BATTERY_STATUS_INVALID, }; /** * Retrieves the battery level status. * * @return the battery status or a negative errno on error */ int hidpp20_batterylevel_get_battery_level(struct hidpp20_device *device, uint16_t *level, uint16_t *next_level); /* -------------------------------------------------------------------------- */ /* 0x1b00: KBD reprogrammable keys and mouse buttons */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_KBD_REPROGRAMMABLE_KEYS 0x1b00 enum hidpp2_controL_id_flags { HIDPP20_CONTROL_ID_FLAG_NONE = 0, HIDPP20_CONTROL_ID_FLAG_MOUSE_BUTTON = (1 << 0), /**< Is a mouse button */ HIDPP20_CONTROL_ID_FLAG_FN_KEY = (1 << 1), /**< Is a fn button */ HIDPP20_CONTROL_ID_FLAG_HOTKEY = (1 << 2), /**< Is a Hot key, not a standard kbd key */ HIDPP20_CONTROL_ID_FLAG_FN_TOGGLE_AFFECTED = (1 << 3), /**< Fn toggle affects this key */ HIDPP20_CONTROL_ID_FLAG_REPROGRAMMABLE = (1 << 4), /**< Key can be reprogrammed */ }; struct hidpp20_control_id { uint8_t index; uint16_t control_id; uint16_t task_id; uint8_t flags; /* fields below are only set for 0x1b04, not for 0x1b00 */ uint8_t position; uint8_t group; uint8_t group_mask; uint8_t raw_XY; struct { uint8_t raw_XY; uint8_t persist; uint8_t divert; uint16_t remapped; int updated; } reporting; }; int hidpp20_kbd_reprogrammable_keys_get_controls(struct hidpp20_device *device, struct hidpp20_control_id **controls_list); /* -------------------------------------------------------------------------- */ /* 0x1b04: Special keys and mouse buttons */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_SPECIAL_KEYS_BUTTONS 0x1b04 /** * allocates a list of controls that has to be freed by the caller. * * returns the elements in the list or a negative error */ int hidpp20_special_key_mouse_get_controls(struct hidpp20_device *device, struct hidpp20_control_id **controls_list); /** * commit a control previously allocated by * hidpp20_special_key_mouse_get_controls(). * * returns 0 or a negative error */ int hidpp20_special_key_mouse_set_control(struct hidpp20_device *device, struct hidpp20_control_id *control); const struct ratbag_button_action *hidpp20_1b04_get_logical_mapping(uint16_t value); uint16_t hidpp20_1b04_get_logical_control_id(const struct ratbag_button_action *action); const char *hidpp20_1b04_get_logical_mapping_name(uint16_t value); enum ratbag_button_type hidpp20_1b04_get_physical_mapping(uint16_t value); const char *hidpp20_1b04_get_physical_mapping_name(uint16_t value); /* -------------------------------------------------------------------------- */ /* 0x2200: Mouse Pointer Basic Optical Sensors */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_MOUSE_POINTER_BASIC 0x2200 #define HIDDP20_MOUSE_POINTER_FLAGS_VERTICAL_TUNING (1 << 4) #define HIDDP20_MOUSE_POINTER_FLAGS_OS_BALLISTICS (1 << 3) #define HIDDP20_MOUSE_POINTER_FLAGS_ACCELERATION_MASK 0x03 #define HIDDP20_MOUSE_POINTER_ACCELERATION_NONE 0x00 #define HIDDP20_MOUSE_POINTER_ACCELERATION_LOW 0x01 #define HIDDP20_MOUSE_POINTER_ACCELERATION_MEDIUM 0x02 #define HIDDP20_MOUSE_POINTER_ACCELERATION_HIGH 0x03 int hidpp20_mousepointer_get_mousepointer_info(struct hidpp20_device *device, uint16_t *resolution, uint8_t *flags); /* -------------------------------------------------------------------------- */ /* 0x2201: Adjustable DPI */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_ADJUSTABLE_DPI 0x2201 /** * either dpi_steps is not null or the values are stored in the null terminated * array dpi_list. */ struct hidpp20_sensor { uint8_t index; uint16_t dpi; uint16_t dpi_min; uint16_t dpi_max; uint16_t dpi_steps; uint16_t default_dpi; uint16_t dpi_list[LONG_MESSAGE_LENGTH / 2 + 1]; }; /** * allocates a list of sensors that has to be freed by the caller. * * returns the elements in the list or a negative error */ int hidpp20_adjustable_dpi_get_sensors(struct hidpp20_device *device, struct hidpp20_sensor **sensors_list); /** * set the current dpi of the provided sensor. sensor must have been * allocated by hidpp20_adjustable_dpi_get_sensors() */ int hidpp20_adjustable_dpi_set_sensor_dpi(struct hidpp20_device *device, struct hidpp20_sensor *sensor, uint16_t dpi); /* -------------------------------------------------------------------------- */ /* 0x8060 - Adjustable Report Rate */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_ADJUSTABLE_REPORT_RATE 0x8060 /* -------------------------------------------------------------------------- */ /* 0x8070v4 - Color LED effects */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_COLOR_LED_EFFECTS 0x8070 struct hidpp20_color_led_info; struct hidpp20_color_led_zone_info; int hidpp20_color_led_effects_get_zone_info(struct hidpp20_device *device, uint8_t reg, struct hidpp20_color_led_zone_info *info); int hidpp20_color_led_effects_get_zone_infos(struct hidpp20_device *device, struct hidpp20_color_led_zone_info **infos_list); enum hidpp20_color_led_location { HIDPP20_COLOR_LED_LOCATION_UNDEFINED = 0, HIDPP20_COLOR_LED_LOCATION_PRIMARY, HIDPP20_COLOR_LED_LOCATION_LOGO, HIDPP20_COLOR_LED_LOCATION_LEFT, HIDPP20_COLOR_LED_LOCATION_RIGHT, HIDPP20_COLOR_LED_LOCATION_COMBINED, HIDPP20_COLOR_LED_LOCATION_PRIMARY_1, HIDPP20_COLOR_LED_LOCATION_PRIMARY_2, HIDPP20_COLOR_LED_LOCATION_PRIMARY_3, HIDPP20_COLOR_LED_LOCATION_PRIMARY_4, HIDPP20_COLOR_LED_LOCATION_PRIMARY_5, HIDPP20_COLOR_LED_LOCATION_PRIMARY_6, }; enum hidpp20_color_led_persistency { HIDPP20_COLOR_LED_PERSISTENCY_UNSUPPORTED, HIDPP20_COLOR_LED_PERSISTENCY_ON, HIDPP20_COLOR_LED_PERSISTENCY_OFF, HIDPP20_COLOR_LED_PERSISTENCY_ON_OFF, }; /* -------------------------------------------------------------------------- */ /* 0x8100 - Onboard Profiles */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_ONBOARD_PROFILES 0x8100 #define HIDPP20_BUTTON_HID_TYPE 0x80 #define HIDPP20_BUTTON_HID_TYPE_MOUSE 0x01 #define HIDPP20_BUTTON_HID_TYPE_KEYBOARD 0x02 #define HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL 0x03 #define HIDPP20_BUTTON_SPECIAL 0x90 #define HIDPP20_BUTTON_MACRO 0x00 #define HIDPP20_BUTTON_DISABLED 0xFF #define HIDPP20_MODIFIER_KEY_CTRL 0x01 #define HIDPP20_MODIFIER_KEY_SHIFT 0x02 #define HIDPP20_DPI_COUNT 5 #define HIDPP20_LED_COUNT 2 union hidpp20_button_binding { struct { uint8_t type; } any; struct { uint8_t type; uint8_t subtype; } subany; struct { uint8_t type; /* HIDPP20_BUTTON_HID_TYPE */ uint8_t subtype; /* HIDPP20_BUTTON_HID_TYPE_MOUSE */ uint16_t buttons; /* flags when internal */ } __attribute__((packed)) button; struct { uint8_t type; /* HIDPP20_BUTTON_HID_TYPE */ uint8_t subtype; /* HIDPP20_BUTTON_HID_TYPE_KEYBOARD */ uint8_t modifier_flags; uint8_t key; } __attribute__((packed)) keyboard_keys; struct { uint8_t type; /* HIDPP20_BUTTON_HID_TYPE */ uint8_t subtype; /* HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL */ uint16_t consumer_control; } __attribute__((packed)) consumer_control; struct { uint8_t type; /* HIDPP20_BUTTON_SPECIAL */ uint8_t special; } __attribute__((packed)) special; struct { uint8_t type; /* HIDPP20_BUTTON_MACRO */ uint8_t page; uint8_t zero; uint8_t offset; } __attribute__((packed)) macro; struct { uint8_t type; /* PROFILE_BUTTON_TYPE_DISABLED */ } disabled; } __attribute__((packed)); _Static_assert(sizeof(union hidpp20_button_binding) == 4, "Invalid size"); struct hidpp20_color_led_info { uint8_t zone_count; /* we don't care about NV capabilities for libratbag, they just * indicate sale demo effects */ uint16_t nv_caps; uint16_t ext_caps; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_color_led_info) == 5, "Invalid size"); struct hidpp20_color_led_zone_info { uint8_t index; uint16_t location; uint8_t num_effects; uint8_t persistency_caps; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_color_led_zone_info) == 5, "Invalid size"); struct hidpp20_color { uint8_t red; uint8_t green; uint8_t blue; } __attribute__((packed)); _Static_assert(sizeof(struct hidpp20_color) == 3, "Invalid size"); enum hidpp20_led_type { HIDPP20_LED_UNKNOWN = -1, HIDPP20_LED_LOGO = 0, HIDPP20_LED_SIDE, }; enum hidpp20_led_mode { HIDPP20_LED_OFF = 0x00, HIDPP20_LED_ON = 0x01, HIDPP20_LED_CYCLE = 0x03, HIDPP20_LED_BREATHING = 0x0a, }; enum hidpp20_led_waveform { HIDPP20_LED_WF_DEFAULT = 0x00, HIDPP20_LED_WF_SINE = 0x01, HIDPP20_LED_WF_SQUARE = 0x02, HIDPP20_LED_WF_TRIANGLE = 0x03, HIDPP20_LED_WF_SAWTOOTH = 0x04, HIDPP20_LED_WF_SHARKFIN = 0x05, HIDPP20_LED_WF_EXPONENTIAL = 0x06, }; struct hidpp20_internal_led { uint8_t mode; /* enum hidpp20_led_mode */ union { struct hidpp20_led_fixed { struct hidpp20_color color; uint8_t effect; } __attribute__((packed)) fixed; struct hidpp20_led_cycle { uint8_t unused[5]; uint16_t period_or_speed; /* period in ms, speed is device dependent */ uint8_t intensity; /* 1 - 100 percent, 0 means 100 */ } __attribute__((packed)) cycle; struct hidpp20_led_breath { struct hidpp20_color color; uint16_t period_or_speed; /* period in ms, speed is device dependent */ uint8_t waveform; /* enum hidpp20_led_waveform */ uint8_t intensity; /* 1 - 100 percent, 0 means 100 */ } __attribute__((packed)) breath; uint8_t padding[10]; } __attribute__((packed)) effect; }; _Static_assert(sizeof(struct hidpp20_led_fixed) == 4, "Invalid size"); _Static_assert(sizeof(struct hidpp20_led_cycle) == 8, "Invalid size"); _Static_assert(sizeof(struct hidpp20_led_breath) == 7, "Invalid size"); _Static_assert(sizeof(struct hidpp20_internal_led) == 11, "Invalid size"); typedef uint8_t percent_t; struct hidpp20_led { enum hidpp20_led_mode mode; struct hidpp20_color color; uint16_t period; percent_t brightness; }; #define HIDPP20_MACRO_NOOP 0x01 #define HIDPP20_MACRO_DELAY 0x40 #define HIDPP20_MACRO_KEY_PRESS 0x43 #define HIDPP20_MACRO_KEY_RELEASE 0x44 #define HIDPP20_MACRO_JUMP 0x60 #define HIDPP20_MACRO_END 0xff union hidpp20_macro_data { struct { uint8_t type; } __attribute__((packed)) any; struct { uint8_t type; /* HIDPP20_MACRO_DELAY */ uint16_t time; } __attribute__((packed)) delay; struct { uint8_t type; /* HIDPP20_MACRO_KEY_PRESS or HIDPP20_MACRO_KEY_RELEASE */ uint8_t modifier; uint8_t key; } __attribute__((packed)) key; struct { uint8_t type; /* HIDPP20_MACRO_JUMP */ uint8_t offset; uint8_t page; } __attribute__((packed)) jump; struct { uint8_t type; /* HIDPP20_MACRO_END */ } __attribute__((packed)) end; } __attribute__((packed)); _Static_assert(sizeof(union hidpp20_macro_data) == 3, "Invalid size"); struct hidpp20_profile { uint8_t index; uint8_t enabled; char name[16 * 3]; unsigned report_rate; unsigned default_dpi; unsigned switched_dpi; uint16_t dpi[HIDPP20_DPI_COUNT]; union hidpp20_button_binding buttons[32]; union hidpp20_macro_data *macros[32]; struct hidpp20_led leds[HIDPP20_LED_COUNT]; }; struct hidpp20_profiles { uint8_t num_profiles; uint8_t num_rom_profiles; uint8_t num_buttons; uint8_t num_modes; uint8_t num_leds; uint8_t has_g_shift; uint8_t has_dpi_shift; uint8_t corded; uint8_t wireless; struct hidpp20_profile profiles[0]; }; /** * allocates a list of profiles that has to be destroyed by the caller. * The caller must use hidpp20_onboard_profiles_destroy() to free the memory. * * returns the number of profiles in the list or a negative error */ int hidpp20_onboard_profiles_allocate(struct hidpp20_device *device, struct hidpp20_profiles **profiles_list); /** * free a list of profiles allocated by hidpp20_onboard_profiles_allocate() */ void hidpp20_onboard_profiles_destroy(struct hidpp20_device *device, struct hidpp20_profiles *profiles_list); /** * return the current profile index or a negative error. */ int hidpp20_onboard_profiles_get_current_profile(struct hidpp20_device *device); /** * Sets the current profile index. * Indexes are 1-indexed. * * return 0 or a negative error. */ int hidpp20_onboard_profiles_set_current_profile(struct hidpp20_device *device, uint8_t index); /** * parse a given profile from the mouse and fill in the right profile in * profiles_list. * * return 0 or a negative error. */ int hidpp20_onboard_profiles_read(struct hidpp20_device *device, unsigned int index, struct hidpp20_profiles *profiles_list); int hidpp20_onboard_profiles_write(struct hidpp20_device *device, unsigned int index, struct hidpp20_profiles *profiles_list); enum ratbag_button_action_special hidpp20_onboard_profiles_get_special(uint8_t code); uint8_t hidpp20_onboard_profiles_get_code_from_special(enum ratbag_button_action_special special); int hidpp20_onboard_profiles_read_memory(struct hidpp20_device *device, uint8_t read_rom, uint8_t page, uint16_t section, uint8_t result[16]); /* -------------------------------------------------------------------------- */ /* 0x8110 - Mouse Button Spy */ /* -------------------------------------------------------------------------- */ #define HIDPP_PAGE_MOUSE_BUTTON_SPY 0x8110 libratbag-0.9/src/liblur.c000066400000000000000000000166121311552661500155500ustar00rootroot00000000000000/* * Copyright 2015 Red Hat, Inc * * liblur - Logitech Unifying Receiver access library * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include "usb-ids.h" #include "hidpp10.h" #include "libratbag-util.h" #include "liblur.h" #define _EXPORT_ __attribute__ ((visibility("default"))) #define MAX_DEVICES 6 static inline void cleanup_hidpp10_device_destroy(struct hidpp10_device **hidpp10_device) { if (*hidpp10_device) hidpp10_device_destroy(*hidpp10_device); } #define _cleanup_hidpp10_device_destroy_ _cleanup_(cleanup_hidpp10_device_destroy) struct lur_receiver { int refcount; int fd; void *userdata; struct hidpp10_device *hidppdev; struct list devices; }; struct lur_device { struct lur_receiver *receiver; int refcount; void *userdata; char *name; uint16_t vid, pid; uint32_t serial; enum lur_device_type type; int hidppidx; struct list node; bool present; /* used during re-enumeration */ }; _EXPORT_ const char * lur_device_get_name(struct lur_device *dev) { return dev->name; } _EXPORT_ uint16_t lur_device_get_vendor_id(struct lur_device *dev) { return dev->vid; } _EXPORT_ uint16_t lur_device_get_product_id(struct lur_device *dev) { return dev->pid; } _EXPORT_ enum lur_device_type lur_device_get_type(struct lur_device *dev) { return dev->type; } _EXPORT_ uint32_t lur_device_get_serial(struct lur_device *dev) { return dev->serial; } _EXPORT_ int lur_device_disconnect(struct lur_device *dev) { int rc; rc = hidpp10_disconnect(dev->receiver->hidppdev, dev->hidppidx); if (rc == 0) { list_remove(&dev->node); list_init(&dev->node); } return rc; } _EXPORT_ int lur_is_receiver(uint16_t vid, uint16_t pid) { return (vid == USB_VENDOR_ID_LOGITECH && (pid == 0xc52b || pid == 0xc532)); } static bool hidraw_is_receiver(int fd) { struct hidraw_devinfo info; int rc; rc = ioctl(fd, HIDIOCGRAWINFO, &info); if (rc < 0) return false; if (!lur_is_receiver(info.vendor, info.product)) return false; return true; } static struct hidpp10_device * hidpp10_init(int fd) { struct hidpp_device base; hidpp_device_init(&base, fd); return hidpp10_device_new(&base, HIDPP_RECEIVER_IDX, HIDPP10_PROFILE_UNKNOWN); } _EXPORT_ int lur_receiver_new_from_hidraw(int fd, void *userdata, struct lur_receiver **out) { struct lur_receiver *receiver; if (!hidraw_is_receiver(fd)) return -EINVAL; receiver = zalloc(sizeof(*receiver)); receiver->refcount = 1; receiver->fd = fd; receiver->userdata = userdata; list_init(&receiver->devices); receiver->hidppdev = hidpp10_init(fd); if (!receiver->hidppdev) goto error; *out = receiver; return 0; error: free(receiver); return -errno; } _EXPORT_ int lur_receiver_enumerate(struct lur_receiver *lur, struct lur_device ***devices_out) { int i; int ndevices = 0; struct hidpp_device base; struct lur_device *dev, *tmp; int rc; struct lur_device **devices; hidpp_device_init(&base, lur->fd); list_for_each(dev, &lur->devices, node) dev->present = false; for (i = 0; i < MAX_DEVICES; i++) { _cleanup_hidpp10_device_destroy_ struct hidpp10_device *d; size_t name_size = 64; char name[name_size]; uint8_t interval, type; uint16_t wpid; uint32_t serial; bool is_new_device = true; d = hidpp10_device_new(&base, i, HIDPP10_PROFILE_UNKNOWN); if (!d) continue; rc = hidpp10_get_pairing_information_device_name(d, name, &name_size); if (rc) continue; rc = hidpp10_get_pairing_information(d, &interval, &wpid, &type); if (rc) continue; rc = hidpp10_get_extended_pairing_information(d, &serial); if (rc) continue; /* check if we have the device already in the list */ list_for_each(dev, &lur->devices, node) { if (dev->pid == wpid && dev->type == type && dev->serial == serial && streq(dev->name, name)) { /* index may have changed, doesn't make it a * new device, just update it */ dev->hidppidx = i; dev->present = true; is_new_device = false; break; } } if (is_new_device) { dev = zalloc(sizeof *dev); dev->receiver = lur; lur_receiver_ref(lur); dev->refcount = 1; dev->name = strdup_safe(name); dev->vid = USB_VENDOR_ID_LOGITECH; dev->pid = wpid; dev->type = type; dev->serial = serial; dev->hidppidx = i; dev->present = true; list_insert(&lur->devices, &dev->node); } } devices = zalloc(MAX_DEVICES * sizeof(*devices)); /* Now drop all devices that disappeared */ list_for_each_safe(dev, tmp, &lur->devices, node) { if (dev->present) { devices[ndevices++] = dev; } else { list_remove(&dev->node); list_init(&dev->node); lur_device_unref(dev); } } *devices_out = devices; return ndevices; } _EXPORT_ int lur_receiver_open(struct lur_receiver *lur, uint8_t timeout) { return !!hidpp10_open_lock(lur->hidppdev, timeout); } _EXPORT_ int lur_receiver_close(struct lur_receiver *lur) { return 0; } _EXPORT_ int lur_receiver_get_fd(struct lur_receiver *lur) { return lur->fd; } _EXPORT_ struct lur_receiver * lur_receiver_ref(struct lur_receiver *lur) { assert(lur->refcount > 0); lur->refcount++; return lur; } _EXPORT_ struct lur_receiver * lur_receiver_unref(struct lur_receiver *lur) { if (lur == NULL) return NULL; assert(lur->refcount > 0); lur->refcount--; if (lur->refcount > 0) return NULL; /* when we get here, all the devices have alrady been removed from * the receiver */ hidpp10_device_destroy(lur->hidppdev); free(lur); return NULL; } _EXPORT_ void lur_receiver_set_user_data(struct lur_receiver *lur, void *userdata) { lur->userdata = userdata; } _EXPORT_ void* lur_receiver_get_user_data(const struct lur_receiver *lur) { return lur->userdata; } _EXPORT_ struct lur_device * lur_device_ref(struct lur_device *dev) { assert(dev->refcount > 0); dev->refcount++; return dev; } _EXPORT_ struct lur_device * lur_device_unref(struct lur_device *dev) { if (dev == NULL) return NULL; assert(dev->refcount > 0); dev->refcount--; if (dev->refcount > 0) return NULL; list_remove(&dev->node); lur_receiver_unref(dev->receiver); free(dev->name); free(dev); return NULL; } _EXPORT_ void lur_device_set_user_data(struct lur_device *dev, void *userdata) { dev->userdata = userdata; } _EXPORT_ void* lur_device_get_user_data(const struct lur_device *dev) { return dev->userdata; } libratbag-0.9/src/liblur.h000066400000000000000000000200541311552661500155500ustar00rootroot00000000000000/* * liblur - Logitech Unifying Receiver access library * * Copyright 2015 Red Hat, Inc * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #ifdef __cplusplus extern "C" { #endif #include /** * @struct lur_receiver * * A handle for accessing Logitech Unifying Receivers. * * This struct is refcounted, use lur_device_ref() and lur_device_unref(). */ struct lur_receiver; /** * @struct lur_device * * A handle for accessing devices paired with a lur_receiver. * * This struct is refcounted, use lur_device_ref() and lur_device_unref(). */ struct lur_device; enum lur_device_type { LUR_DEVICE_TYPE_UNKNOWN = 0x00, LUR_DEVICE_TYPE_KEYBOARD = 0x01, LUR_DEVICE_TYPE_MOUSE = 0x02, LUR_DEVICE_TYPE_NUMPAD = 0x03, LUR_DEVICE_TYPE_PRESENTER = 0x04, LUR_DEVICE_TYPE_TRACKBALL = 0x08, LUR_DEVICE_TYPE_TOUCHPAD = 0x09, }; const char * lur_device_get_name(struct lur_device *dev); uint16_t lur_device_get_vendor_id(struct lur_device *dev); uint16_t lur_device_get_product_id(struct lur_device *dev); enum lur_device_type lur_device_get_type(struct lur_device *dev); uint32_t lur_device_get_serial(struct lur_device *dev); /** * Disconnect this device from the receiver it is currently paired with. * * @param lur A valid receiver object * @return 0 on success or nonzero on error */ int lur_device_disconnect(struct lur_device *dev); /** * Returns non-zero if a device with the given vid/pid is a Logitech * Unifying Receiver device. * * @param vid The vendor ID * @param pid The product ID * * @return non-zero if the device is a unifying receiver, zero otherwise */ int lur_is_receiver(uint16_t vid, uint16_t pid); /** * Creates a new Logitech Unifying Receiver object from the file descriptor. * The fd must be a hidraw device, opened in O_RDWR. * * The returned struct has a refcount of at least 1, use lur_device_unref() * to release resources associated with it. * It is the caller's responsibility to close the fd after the * resources associated with this object have been freed. * * @param fd An O_RDWR file descriptor pointing to a /dev/hidraw node * @param userdata Caller-specific data * @param lur Set to the lur object on success, otherwise unmodified * * @return 0 on success or a negative errno on error * @retval -EINVAL The fd does not point to a lur receiver * * @note liblur does not have OOM handling. If an allocation fails, liblur * will simply abort() */ int lur_receiver_new_from_hidraw(int fd, void *userdata, struct lur_receiver **lur); /** * Enumerate devices currently paired with the given receiver. * * liblur does not have a device detection mechanism, it is recommend that a * caller monitors udev for hidraw devices being added and removed. When * such devices appear or disappear, a call to lur_receiver_enumerate() * yields the new list of devices. If no new unifying devices are available, * the returned list is identical to the list returned in the last call to * lur_receiver_enumerate(). Otherwise, the diff between the two lists * indicate the set of newly added and/or removed devices. * * The devices returned have a refcount of at least 1, use * lur_device_unref(). Repeated calls to this function do not increase the * devices' refcount. * * @param lur A valid receiver object * @param[out] devices An array of devices paired with this receiver. Use * free() to free the array, and lur_device_unref() to destroy each device. * * @return The number of devices returned, or -1 on error. */ int lur_receiver_enumerate(struct lur_receiver *lur, struct lur_device ***devices); /** * Allow devices to be paired with this receiver for the given timeout. * Once 'open', the receiver will pair with a currently disconnected * device. * * @param lur A valid receiver object * @param timeout The time in seconds the receiver should accept new * pairings. The value 0 uses the receiver's default value (usually 30s). * * @return 0 on success or nonzero on error */ int lur_receiver_open(struct lur_receiver *lur, uint8_t timeout); /** * If a receiver is currently accepting devices, stop doing so. If the * receiver is not currently accepting devices, this function has no effect * and returns success. * * @param lur A valid receiver object * @return 0 on success or nonzero on error */ int lur_receiver_close(struct lur_receiver *lur); /** * Return the file descriptor used to initialize this receiver. * * @param lur A valid receiver object * @return The file descriptor passed into lur_receiver_new_from_hidraw */ int lur_receiver_get_fd(struct lur_receiver* lur); /** * Add a reference to the context. A context is destroyed whenever the * reference count reaches 0. See @ref lur_unref. * * @param lur A valid receiver object * @return The passed context */ struct lur_receiver * lur_receiver_ref(struct lur_receiver *lur); /** * Dereference the context. After this, the context may have been * destroyed, if the last reference was dereferenced. If so, the context is * invalid and may not be interacted with. * * @param lur A valid receiver object * @retval NULL */ struct lur_receiver * lur_receiver_unref(struct lur_receiver *lur); /** * Set caller-specific data associated with this object. liblur does * not manage, look at, or modify this data. The caller must ensure the * data is valid. * * Setting userdata overrides the one provided to * lur_receiver_new_from_hidraw(). * * @param lur A valid receiver object * @param userdata Caller-specific data passed to the various callback * interfaces. */ void lur_receiver_set_user_data(struct lur_receiver *lur, void *userdata); /** * Get the caller-specific data associated with this object, if any. * * @param lur A valid receiver object * @return The caller-specific data previously assigned in * lur_receiver_new_from_hidraw (or lur_receiver_set_user_data()). */ void* lur_receiver_get_user_data(const struct lur_receiver *lur); /** * Add a reference to the device. A device is destroyed whenever the * reference count reaches 0. See @ref lur_unref. * * @param lur A valid device object * @return The passed device */ struct lur_device * lur_device_ref(struct lur_device *dev); /** * Dereference the device. After this, the device may have been * destroyed, if the last reference was dereferenced. If so, the device is * invalid and may not be interacted with. * * @param lur A valid device object * @retval NULL */ struct lur_device * lur_device_unref(struct lur_device *dev); /** * Set caller-specific data associated with this object. liblur does * not manage, look at, or modify this data. The caller must ensure the * data is valid. * * @param dev A valid device object * @param userdata Caller-specific data */ void lur_device_set_user_data(struct lur_device *dev, void *userdata); /** * Get the caller-specific data associated with this object, if any. * * @param dev A valid device object * @return The caller-specific data previously assigned in * lur_device_set_user_data(). */ void* lur_device_get_user_data(const struct lur_device *dev); #ifdef __cplusplus } #endif libratbag-0.9/src/liblur.sym000066400000000000000000000010341311552661500161260ustar00rootroot00000000000000/* in alphabetical order! */ LIBLUR_0.4.0 { global: lur_device_disconnect; lur_device_get_name; lur_device_get_product_id; lur_device_get_serial; lur_device_get_type; lur_device_get_user_data; lur_device_get_vendor_id; lur_device_ref; lur_device_set_user_data; lur_device_unref; lur_is_receiver; lur_receiver_close; lur_receiver_enumerate; lur_receiver_get_fd; lur_receiver_get_user_data; lur_receiver_new_from_hidraw; lur_receiver_open; lur_receiver_ref; lur_receiver_set_user_data; lur_receiver_unref; local: *; }; libratbag-0.9/src/libratbag-hidraw.c000066400000000000000000001512331311552661500174610ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include "libratbag-hidraw.h" #include "libratbag-private.h" #ifndef KEY_SCREENSAVER #define KEY_SCREENSAVER 0x245 #endif #ifndef KEY_VOICECOMMAND #define KEY_VOICECOMMAND 0x246 #endif /* defined in include/linux.hid.h in the kernel, but not exported */ #ifndef HID_MAX_BUFFER_SIZE #define HID_MAX_BUFFER_SIZE 4096 /* 4kb */ #endif #define HID_REPORT_ID 0b10000100 #define HID_COLLECTION 0b10100000 #define HID_USAGE_PAGE 0b00000100 #define HID_USAGE 0b00001000 #define HID_PHYSICAL 0 #define HID_APPLICATION 1 #define HID_LOGICAL 2 #define HID_KEY_RESERVED 0x00 /* Reserved (no event indicated) */ #define HID_KEY_ERRORROLLOVER 0x01 /* ErrorRollOver */ #define HID_KEY_POSTFAIL 0x02 /* POSTFail */ #define HID_KEY_ERRORUNDEFINE 0x03 /* ErrorUndefine */ #define HID_KEY_A 0x04 /* a and A */ #define HID_KEY_B 0x05 /* b and B */ #define HID_KEY_C 0x06 /* c and C */ #define HID_KEY_D 0x07 /* d and D */ #define HID_KEY_E 0x08 /* e and E */ #define HID_KEY_F 0x09 /* f and F */ #define HID_KEY_G 0x0A /* g and G */ #define HID_KEY_H 0x0B /* h and H */ #define HID_KEY_I 0x0C /* i and I */ #define HID_KEY_J 0x0D /* j and J */ #define HID_KEY_K 0x0E /* k and K */ #define HID_KEY_L 0x0F /* l and L */ #define HID_KEY_M 0x10 /* m and M */ #define HID_KEY_N 0x11 /* n and N */ #define HID_KEY_O 0x12 /* o and O */ #define HID_KEY_P 0x13 /* p and P */ #define HID_KEY_Q 0x14 /* q and Q */ #define HID_KEY_R 0x15 /* r and R */ #define HID_KEY_S 0x16 /* s and S */ #define HID_KEY_T 0x17 /* t and T */ #define HID_KEY_U 0x18 /* u and U */ #define HID_KEY_V 0x19 /* v and V */ #define HID_KEY_W 0x1A /* w and W */ #define HID_KEY_X 0x1B /* x and X */ #define HID_KEY_Y 0x1C /* y and Y */ #define HID_KEY_Z 0x1D /* z and Z */ #define HID_KEY_1 0x1E /* 1 and ! */ #define HID_KEY_2 0x1F /* 2 and @ */ #define HID_KEY_3 0x20 /* 3 and # */ #define HID_KEY_4 0x21 /* 4 and $ */ #define HID_KEY_5 0x22 /* 5 and % */ #define HID_KEY_6 0x23 /* 6 and ^ */ #define HID_KEY_7 0x24 /* 7 and & */ #define HID_KEY_8 0x25 /* 8 and * */ #define HID_KEY_9 0x26 /* 9 and ( */ #define HID_KEY_0 0x27 /* 0 and ) */ #define HID_KEY_RETURN_ENTER 0x28 /* Return (ENTER) */ #define HID_KEY_ESCAPE 0x29 /* ESCAPE */ #define HID_KEY_DELETE_BACKSPACE 0x2A /* DELETE (Backspace) */ #define HID_KEY_TAB 0x2B /* Tab */ #define HID_KEY_SPACEBAR 0x2C /* Spacebar */ #define HID_KEY_MINUS_AND_UNDERSCORE 0x2D /* - and (underscore) */ #define HID_KEY_EQUAL_AND_PLUS 0x2E /* = and + */ #define HID_KEY_CLOSE_BRACKET 0x2F /* [ and { */ #define HID_KEY_OPEN_BRACKET 0x30 /* ] and } */ #define HID_KEY_BACK_SLASH_AND_PIPE 0x31 /* \ and | */ #define HID_KEY_NON_US_HASH_AND_TILDE 0x32 /* Non-US # and ~ */ #define HID_KEY_SEMICOLON_AND_COLON 0x33 /* ; and : */ #define HID_KEY_QUOTE_AND_DOUBLEQUOTE 0x34 /* ' and " */ #define HID_KEY_GRAVE_ACCENT_AND_TILDE 0x35 /* Grave Accent and Tilde */ #define HID_KEY_COMMA_AND_LESSER_THAN 0x36 /* Keyboard, and < */ #define HID_KEY_PERIOD_AND_GREATER_THAN 0x37 /* . and > */ #define HID_KEY_SLASH_AND_QUESTION_MARK 0x38 /* / and ? */ #define HID_KEY_CAPS_LOCK 0x39 /* Caps Lock */ #define HID_KEY_F1 0x3A /* F1 */ #define HID_KEY_F2 0x3B /* F2 */ #define HID_KEY_F3 0x3C /* F3 */ #define HID_KEY_F4 0x3D /* F4 */ #define HID_KEY_F5 0x3E /* F5 */ #define HID_KEY_F6 0x3F /* F6 */ #define HID_KEY_F7 0x40 /* F7 */ #define HID_KEY_F8 0x41 /* F8 */ #define HID_KEY_F9 0x42 /* F9 */ #define HID_KEY_F10 0x43 /* F10 */ #define HID_KEY_F11 0x44 /* F11 */ #define HID_KEY_F12 0x45 /* F12 */ #define HID_KEY_PRINTSCREEN 0x46 /* PrintScreen */ #define HID_KEY_SCROLL_LOCK 0x47 /* Scroll Lock */ #define HID_KEY_PAUSE 0x48 /* Pause */ #define HID_KEY_INSERT 0x49 /* Insert */ #define HID_KEY_HOME 0x4A /* Home */ #define HID_KEY_PAGEUP 0x4B /* PageUp */ #define HID_KEY_DELETE_FORWARD 0x4C /* Delete Forward */ #define HID_KEY_END 0x4D /* End */ #define HID_KEY_PAGEDOWN 0x4E /* PageDown */ #define HID_KEY_RIGHTARROW 0x4F /* RightArrow */ #define HID_KEY_LEFTARROW 0x50 /* LeftArrow */ #define HID_KEY_DOWNARROW 0x51 /* DownArrow */ #define HID_KEY_UPARROW 0x52 /* UpArrow */ #define HID_KEY_KEYPAD_NUM_LOCK_AND_CLEAR 0x53 /* Keypad Num Lock and Clear */ #define HID_KEY_KEYPAD_SLASH 0x54 /* Keypad / */ #define HID_KEY_KEYPAD_ASTERISK 0x55 /* Keypad * */ #define HID_KEY_KEYPAD_MINUS 0x56 /* Keypad - */ #define HID_KEY_KEYPAD_PLUS 0x57 /* Keypad + */ #define HID_KEY_KEYPAD_ENTER 0x58 /* Keypad ENTER */ #define HID_KEY_KEYPAD_1_AND_END 0x59 /* Keypad 1 and End */ #define HID_KEY_KEYPAD_2_AND_DOWN_ARROW 0x5A /* Keypad 2 and Down Arrow */ #define HID_KEY_KEYPAD_3_AND_PAGEDN 0x5B /* Keypad 3 and PageDn */ #define HID_KEY_KEYPAD_4_AND_LEFT_ARROW 0x5C /* Keypad 4 and Left Arrow */ #define HID_KEY_KEYPAD_5 0x5D /* Keypad 5 */ #define HID_KEY_KEYPAD_6_AND_RIGHT_ARROW 0x5E /* Keypad 6 and Right Arrow */ #define HID_KEY_KEYPAD_7_AND_HOME 0x5F /* Keypad 7 and Home */ #define HID_KEY_KEYPAD_8_AND_UP_ARROW 0x60 /* Keypad 8 and Up Arrow */ #define HID_KEY_KEYPAD_9_AND_PAGEUP 0x61 /* Keypad 9 and PageUp */ #define HID_KEY_KEYPAD_0_AND_INSERT 0x62 /* Keypad 0 and Insert */ #define HID_KEY_KEYPAD_PERIOD_AND_DELETE 0x63 /* Keypad . and Delete */ #define HID_KEY_NON_US_BACKSLASH_AND_PIPE 0x64 /* Non-US \ and | */ #define HID_KEY_APPLICATION 0x65 /* Application */ #define HID_KEY_POWER 0x66 /* Power */ #define HID_KEY_KEYPAD_EQUAL 0x67 /* Keypad = */ #define HID_KEY_F13 0x68 /* F13 */ #define HID_KEY_F14 0x69 /* F14 */ #define HID_KEY_F15 0x6A /* F15 */ #define HID_KEY_F16 0x6B /* F16 */ #define HID_KEY_F17 0x6C /* F17 */ #define HID_KEY_F18 0x6D /* F18 */ #define HID_KEY_F19 0x6E /* F19 */ #define HID_KEY_F20 0x6F /* F20 */ #define HID_KEY_F21 0x70 /* F21 */ #define HID_KEY_F22 0x71 /* F22 */ #define HID_KEY_F23 0x72 /* F23 */ #define HID_KEY_F24 0x73 /* F24 */ #define HID_KEY_EXECUTE 0x74 /* Execute */ #define HID_KEY_HELP 0x75 /* Help */ #define HID_KEY_MENU 0x76 /* Menu */ #define HID_KEY_SELECT 0x77 /* Select */ #define HID_KEY_STOP 0x78 /* Stop */ #define HID_KEY_AGAIN 0x79 /* Again */ #define HID_KEY_UNDO 0x7A /* Undo */ #define HID_KEY_CUT 0x7B /* Cut */ #define HID_KEY_COPY 0x7C /* Copy */ #define HID_KEY_PASTE 0x7D /* Paste */ #define HID_KEY_FIND 0x7E /* Find */ #define HID_KEY_MUTE 0x7F /* Mute */ #define HID_KEY_VOLUME_UP 0x80 /* Volume Up */ #define HID_KEY_VOLUME_DOWN 0x81 /* Volume Down */ #define HID_KEY_LOCKING_CAPS_LOCK 0x82 /* Locking Caps Lock */ #define HID_KEY_LOCKING_NUM_LOCK 0x83 /* Locking Num Lock */ #define HID_KEY_LOCKING_SCROLL_LOCK 0x84 /* Locking Scroll Lock */ #define HID_KEY_KEYPAD_COMMA 0x85 /* Keypad Comma */ #define HID_KEY_KEYPAD_EQUAL_SIGN 0x86 /* Keypad Equal Sign */ #define HID_KEY_KANJI1 0x87 /* Kanji1 */ #define HID_KEY_KANJI2 0x88 /* Kanji2 */ #define HID_KEY_KANJI3 0x89 /* Kanji3 */ #define HID_KEY_KANJI4 0x8A /* Kanji4 */ #define HID_KEY_KANJI5 0x8B /* Kanji5 */ #define HID_KEY_KANJI6 0x8C /* Kanji6 */ #define HID_KEY_KANJI7 0x8D /* Kanji7 */ #define HID_KEY_KANJI8 0x8E /* Kanji8 */ #define HID_KEY_KANJI9 0x8F /* Kanji9 */ #define HID_KEY_LANG1 0x90 /* LANG1 */ #define HID_KEY_LANG2 0x91 /* LANG2 */ #define HID_KEY_LANG3 0x92 /* LANG3 */ #define HID_KEY_LANG4 0x93 /* LANG4 */ #define HID_KEY_LANG5 0x94 /* LANG5 */ #define HID_KEY_LANG6 0x95 /* LANG6 */ #define HID_KEY_LANG7 0x96 /* LANG7 */ #define HID_KEY_LANG8 0x97 /* LANG8 */ #define HID_KEY_LANG9 0x98 /* LANG9 */ #define HID_KEY_ALTERNATE_ERASE 0x99 /* Alternate Erase */ #define HID_KEY_SYSREQ_ATTENTION 0x9A /* SysReq/Attention */ #define HID_KEY_CANCEL 0x9B /* Cancel */ #define HID_KEY_CLEAR 0x9C /* Clear */ #define HID_KEY_PRIOR 0x9D /* Prior */ #define HID_KEY_RETURN 0x9E /* Return */ #define HID_KEY_SEPARATOR 0x9F /* Separator */ #define HID_KEY_OUT 0xA0 /* Out */ #define HID_KEY_OPER 0xA1 /* Oper */ #define HID_KEY_CLEAR_AGAIN 0xA2 /* Clear/Again */ #define HID_KEY_CRSEL_PROPS 0xA3 /* CrSel/Props */ #define HID_KEY_EXSEL 0xA4 /* ExSel */ /* RESERVED 0xA5-DF */ /* Reserved */ #define HID_KEY_LEFTCONTROL 0xE0 /* LeftControl */ #define HID_KEY_LEFTSHIFT 0xE1 /* LeftShift */ #define HID_KEY_LEFTALT 0xE2 /* LeftAlt */ #define HID_KEY_LEFT_GUI 0xE3 /* Left GUI */ #define HID_KEY_RIGHTCONTROL 0xE4 /* RightControl */ #define HID_KEY_RIGHTSHIFT 0xE5 /* RightShift */ #define HID_KEY_RIGHTALT 0xE6 /* RightAlt */ #define HID_KEY_RIGHT_GUI 0xE7 /* Right GUI */ static const unsigned int hid_keyboard_mapping[] = { [HID_KEY_RESERVED ] = 0, [HID_KEY_ERRORROLLOVER ] = 0, [HID_KEY_POSTFAIL ] = 0, [HID_KEY_ERRORUNDEFINE ] = 0, [HID_KEY_A ] = KEY_A, [HID_KEY_B ] = KEY_B, [HID_KEY_C ] = KEY_C, [HID_KEY_D ] = KEY_D, [HID_KEY_E ] = KEY_E, [HID_KEY_F ] = KEY_F, [HID_KEY_G ] = KEY_G, [HID_KEY_H ] = KEY_H, [HID_KEY_I ] = KEY_I, [HID_KEY_J ] = KEY_J, [HID_KEY_K ] = KEY_K, [HID_KEY_L ] = KEY_L, [HID_KEY_M ] = KEY_M, [HID_KEY_N ] = KEY_N, [HID_KEY_O ] = KEY_O, [HID_KEY_P ] = KEY_P, [HID_KEY_Q ] = KEY_Q, [HID_KEY_R ] = KEY_R, [HID_KEY_S ] = KEY_S, [HID_KEY_T ] = KEY_T, [HID_KEY_U ] = KEY_U, [HID_KEY_V ] = KEY_V, [HID_KEY_W ] = KEY_W, [HID_KEY_X ] = KEY_X, [HID_KEY_Y ] = KEY_Y, [HID_KEY_Z ] = KEY_Z, [HID_KEY_1 ] = KEY_1, [HID_KEY_2 ] = KEY_2, [HID_KEY_3 ] = KEY_3, [HID_KEY_4 ] = KEY_4, [HID_KEY_5 ] = KEY_5, [HID_KEY_6 ] = KEY_6, [HID_KEY_7 ] = KEY_7, [HID_KEY_8 ] = KEY_8, [HID_KEY_9 ] = KEY_9, [HID_KEY_0 ] = KEY_0, [HID_KEY_RETURN_ENTER ] = KEY_ENTER, [HID_KEY_ESCAPE ] = KEY_ESC, [HID_KEY_DELETE_BACKSPACE ] = KEY_BACKSPACE, [HID_KEY_TAB ] = KEY_TAB, [HID_KEY_SPACEBAR ] = KEY_SPACE, [HID_KEY_MINUS_AND_UNDERSCORE ] = KEY_MINUS, [HID_KEY_EQUAL_AND_PLUS ] = KEY_EQUAL, [HID_KEY_CLOSE_BRACKET ] = KEY_LEFTBRACE, [HID_KEY_OPEN_BRACKET ] = KEY_RIGHTBRACE, [HID_KEY_BACK_SLASH_AND_PIPE ] = KEY_BACKSLASH, [HID_KEY_NON_US_HASH_AND_TILDE ] = KEY_BACKSLASH, [HID_KEY_SEMICOLON_AND_COLON ] = KEY_SEMICOLON, [HID_KEY_QUOTE_AND_DOUBLEQUOTE ] = KEY_APOSTROPHE, [HID_KEY_GRAVE_ACCENT_AND_TILDE ] = KEY_GRAVE, [HID_KEY_COMMA_AND_LESSER_THAN ] = KEY_COMMA, [HID_KEY_PERIOD_AND_GREATER_THAN ] = KEY_DOT, [HID_KEY_SLASH_AND_QUESTION_MARK ] = KEY_SLASH, [HID_KEY_CAPS_LOCK ] = KEY_CAPSLOCK, [HID_KEY_F1 ] = KEY_F1, [HID_KEY_F2 ] = KEY_F2, [HID_KEY_F3 ] = KEY_F3, [HID_KEY_F4 ] = KEY_F4, [HID_KEY_F5 ] = KEY_F5, [HID_KEY_F6 ] = KEY_F6, [HID_KEY_F7 ] = KEY_F7, [HID_KEY_F8 ] = KEY_F8, [HID_KEY_F9 ] = KEY_F9, [HID_KEY_F10 ] = KEY_F10, [HID_KEY_F11 ] = KEY_F11, [HID_KEY_F12 ] = KEY_F12, [HID_KEY_PRINTSCREEN ] = KEY_SYSRQ, [HID_KEY_SCROLL_LOCK ] = KEY_SCROLLLOCK, [HID_KEY_PAUSE ] = KEY_PAUSE, [HID_KEY_INSERT ] = KEY_INSERT, [HID_KEY_HOME ] = KEY_HOME, [HID_KEY_PAGEUP ] = KEY_PAGEUP, [HID_KEY_DELETE_FORWARD ] = KEY_DELETE, [HID_KEY_END ] = KEY_END, [HID_KEY_PAGEDOWN ] = KEY_PAGEDOWN, [HID_KEY_RIGHTARROW ] = KEY_RIGHT, [HID_KEY_LEFTARROW ] = KEY_LEFT, [HID_KEY_DOWNARROW ] = KEY_DOWN, [HID_KEY_UPARROW ] = KEY_UP, [HID_KEY_KEYPAD_NUM_LOCK_AND_CLEAR ] = KEY_NUMLOCK, [HID_KEY_KEYPAD_SLASH ] = KEY_KPSLASH, [HID_KEY_KEYPAD_ASTERISK ] = KEY_KPASTERISK, [HID_KEY_KEYPAD_MINUS ] = KEY_KPMINUS, [HID_KEY_KEYPAD_PLUS ] = KEY_KPPLUS, [HID_KEY_KEYPAD_ENTER ] = KEY_KPENTER, [HID_KEY_KEYPAD_1_AND_END ] = KEY_KP1, [HID_KEY_KEYPAD_2_AND_DOWN_ARROW ] = KEY_KP2, [HID_KEY_KEYPAD_3_AND_PAGEDN ] = KEY_KP3, [HID_KEY_KEYPAD_4_AND_LEFT_ARROW ] = KEY_KP4, [HID_KEY_KEYPAD_5 ] = KEY_KP5, [HID_KEY_KEYPAD_6_AND_RIGHT_ARROW ] = KEY_KP6, [HID_KEY_KEYPAD_7_AND_HOME ] = KEY_KP7, [HID_KEY_KEYPAD_8_AND_UP_ARROW ] = KEY_KP8, [HID_KEY_KEYPAD_9_AND_PAGEUP ] = KEY_KP9, [HID_KEY_KEYPAD_0_AND_INSERT ] = KEY_KP0, [HID_KEY_KEYPAD_PERIOD_AND_DELETE ] = KEY_KPDOT, [HID_KEY_NON_US_BACKSLASH_AND_PIPE ] = KEY_102ND, [HID_KEY_APPLICATION ] = KEY_COMPOSE, [HID_KEY_POWER ] = KEY_POWER, [HID_KEY_KEYPAD_EQUAL ] = KEY_KPEQUAL, [HID_KEY_F13 ] = KEY_F13, [HID_KEY_F14 ] = KEY_F14, [HID_KEY_F15 ] = KEY_F15, [HID_KEY_F16 ] = KEY_F16, [HID_KEY_F17 ] = KEY_F17, [HID_KEY_F18 ] = KEY_F18, [HID_KEY_F19 ] = KEY_F19, [HID_KEY_F20 ] = KEY_F20, [HID_KEY_F21 ] = KEY_F21, [HID_KEY_F22 ] = KEY_F22, [HID_KEY_F23 ] = KEY_F23, [HID_KEY_F24 ] = KEY_F24, [HID_KEY_EXECUTE ] = 0, [HID_KEY_HELP ] = KEY_HELP, [HID_KEY_MENU ] = KEY_MENU, [HID_KEY_SELECT ] = KEY_SELECT, [HID_KEY_STOP ] = KEY_STOP, [HID_KEY_AGAIN ] = KEY_AGAIN, [HID_KEY_UNDO ] = KEY_UNDO, [HID_KEY_CUT ] = KEY_CUT, [HID_KEY_COPY ] = KEY_COPY, [HID_KEY_PASTE ] = KEY_PASTE, [HID_KEY_FIND ] = KEY_FIND, [HID_KEY_MUTE ] = KEY_MUTE, [HID_KEY_VOLUME_UP ] = KEY_VOLUMEUP, [HID_KEY_VOLUME_DOWN ] = KEY_VOLUMEDOWN, [HID_KEY_LOCKING_CAPS_LOCK ] = 0, [HID_KEY_LOCKING_NUM_LOCK ] = 0, [HID_KEY_LOCKING_SCROLL_LOCK ] = 0, [HID_KEY_KEYPAD_COMMA ] = KEY_KPCOMMA, [HID_KEY_KEYPAD_EQUAL_SIGN ] = KEY_KPEQUAL, [HID_KEY_KANJI1 ] = 0, [HID_KEY_KANJI2 ] = 0, [HID_KEY_KANJI3 ] = 0, [HID_KEY_KANJI4 ] = 0, [HID_KEY_KANJI5 ] = 0, [HID_KEY_KANJI6 ] = 0, [HID_KEY_KANJI7 ] = 0, [HID_KEY_KANJI8 ] = 0, [HID_KEY_KANJI9 ] = 0, [HID_KEY_LANG1 ] = 0, [HID_KEY_LANG2 ] = 0, [HID_KEY_LANG3 ] = 0, [HID_KEY_LANG4 ] = 0, [HID_KEY_LANG5 ] = 0, [HID_KEY_LANG6 ] = 0, [HID_KEY_LANG7 ] = 0, [HID_KEY_LANG8 ] = 0, [HID_KEY_LANG9 ] = 0, [HID_KEY_ALTERNATE_ERASE ] = 0, [HID_KEY_SYSREQ_ATTENTION ] = KEY_SYSRQ, [HID_KEY_CANCEL ] = KEY_CANCEL, [HID_KEY_CLEAR ] = KEY_CLEAR, [HID_KEY_PRIOR ] = 0, [HID_KEY_RETURN ] = 0, [HID_KEY_SEPARATOR ] = 0, [HID_KEY_OUT ] = 0, [HID_KEY_OPER ] = 0, [HID_KEY_CLEAR_AGAIN ] = 0, [HID_KEY_CRSEL_PROPS ] = 0, [HID_KEY_EXSEL ] = 0, [0xA5 ... 0xDF] = 0, [HID_KEY_LEFTCONTROL ] = KEY_LEFTCTRL, [HID_KEY_LEFTSHIFT ] = KEY_LEFTSHIFT, [HID_KEY_LEFTALT ] = KEY_LEFTALT, [HID_KEY_LEFT_GUI ] = KEY_LEFTMETA, [HID_KEY_RIGHTCONTROL ] = KEY_RIGHTCTRL, [HID_KEY_RIGHTSHIFT ] = KEY_RIGHTSHIFT, [HID_KEY_RIGHTALT ] = KEY_RIGHTALT, [HID_KEY_RIGHT_GUI ] = KEY_RIGHTMETA, [0xe8 ... 0xff] = 0, }; #define HID_CC_CONSUMER_CONTROL 0x01 #define HID_CC_NUMERIC_KEY_PAD 0x02 #define HID_CC_PROGRAMMABLE_BUTTONS 0x03 #define HID_CC_MICROPHONE 0x04 #define HID_CC_HEADPHONE 0x05 #define HID_CC_GRAPHIC_EQUALIZER 0x06 #define HID_CC_PLUS_10 0x20 #define HID_CC_PLUS_100 0x21 #define HID_CC_AM_PM 0x22 #define HID_CC_POWER 0x30 #define HID_CC_RESET 0x31 #define HID_CC_SLEEP 0x32 #define HID_CC_SLEEP_AFTER 0x33 #define HID_CC_SLEEP_MODE 0x34 #define HID_CC_ILLUMINATION 0x35 #define HID_CC_FUNCTION_BUTTONS 0x36 #define HID_CC_MENU 0x40 #define HID_CC_MENU_PICK 0x41 #define HID_CC_MENU_UP 0x42 #define HID_CC_MENU_DOWN 0x43 #define HID_CC_MENU_LEFT 0x44 #define HID_CC_MENU_RIGHT 0x45 #define HID_CC_MENU_ESCAPE 0x46 #define HID_CC_MENU_VALUE_INCREASE 0x47 #define HID_CC_MENU_VALUE_DECREASE 0x48 #define HID_CC_DATA_ON_SCREEN 0x60 #define HID_CC_CLOSED_CAPTION 0x61 #define HID_CC_CLOSED_CAPTION_SELECT 0x62 #define HID_CC_VCR_TV 0x63 #define HID_CC_BROADCAST_MODE 0x64 #define HID_CC_SNAPSHOT 0x65 #define HID_CC_STILL 0x66 #define HID_CC_ASPECT 0x6D #define HID_CC_3D_MODE_SELECT 0x6E #define HID_CC_DISPLAY_BRIGHTNESS_INCREMENT 0x6F #define HID_CC_DISPLAY_BRIGHTNESS_DECREMENT 0x70 #define HID_CC_DISPLAY_BRIGHTNESS 0x71 #define HID_CC_DISPLAY_BACKLIGHT_TOGGLE 0x72 #define HID_CC_DISPLAY_SET_BRIGHTNESS_TO_MINIMUM 0x73 #define HID_CC_DISPLAY_SET_BRIGHTNESS_TO_MAXIMUM 0x74 #define HID_CC_DISPLAY_SET_AUTO_BRIGHTNESS 0x75 #define HID_CC_SELECTION 0x80 #define HID_CC_ASSIGN_SELECTION 0x81 #define HID_CC_MODE_STEP 0x82 #define HID_CC_RECALL_LAST 0x83 #define HID_CC_ENTER_CHANNEL 0x84 #define HID_CC_ORDER_MOVIE 0x85 #define HID_CC_CHANNEL 0x86 #define HID_CC_MEDIA_SELECTION 0x87 #define HID_CC_MEDIA_SELECT_COMPUTER 0x88 #define HID_CC_MEDIA_SELECT_TV 0x89 #define HID_CC_MEDIA_SELECT_WWW 0x8A #define HID_CC_MEDIA_SELECT_DVD 0x8B #define HID_CC_MEDIA_SELECT_TELEPHONE 0x8C #define HID_CC_MEDIA_SELECT_PROGRAM_GUIDE 0x8D #define HID_CC_MEDIA_SELECT_VIDEO_PHONE 0x8E #define HID_CC_MEDIA_SELECT_GAMES 0x8F #define HID_CC_MEDIA_SELECT_MESSAGES 0x90 #define HID_CC_MEDIA_SELECT_CD 0x91 #define HID_CC_MEDIA_SELECT_VCR 0x92 #define HID_CC_MEDIA_SELECT_TUNER 0x93 #define HID_CC_QUIT 0x94 #define HID_CC_HELP 0x95 #define HID_CC_MEDIA_SELECT_TAPE 0x96 #define HID_CC_MEDIA_SELECT_CABLE 0x97 #define HID_CC_MEDIA_SELECT_SATELLITE 0x98 #define HID_CC_MEDIA_SELECT_SECURITY 0x99 #define HID_CC_MEDIA_SELECT_HOME 0x9A #define HID_CC_MEDIA_SELECT_CALL 0x9B #define HID_CC_CHANNEL_INCREMENT 0x9C #define HID_CC_CHANNEL_DECREMENT 0x9D #define HID_CC_MEDIA_SELECT_SAP 0x9E #define HID_CC_VCR_PLUS 0xA0 #define HID_CC_ONCE 0xA1 #define HID_CC_DAILY 0xA2 #define HID_CC_WEEKLY 0xA3 #define HID_CC_MONTHLY 0xA4 #define HID_CC_PLAY 0xB0 #define HID_CC_PAUSE 0xB1 #define HID_CC_RECORD 0xB2 #define HID_CC_FAST_FORWARD 0xB3 #define HID_CC_REWIND 0xB4 #define HID_CC_SCAN_NEXT_TRACK 0xB5 #define HID_CC_SCAN_PREVIOUS_TRACK 0xB6 #define HID_CC_STOP 0xB7 #define HID_CC_EJECT 0xB8 #define HID_CC_RANDOM_PLAY 0xB9 #define HID_CC_SELECT_DISC 0xBA #define HID_CC_ENTER_DISC 0xBB #define HID_CC_REPEAT 0xBC #define HID_CC_TRACKING 0xBD #define HID_CC_TRACK_NORMAL 0xBE #define HID_CC_SLOW_TRACKING 0xBF #define HID_CC_FRAME_FORWARD 0xC0 #define HID_CC_FRAME_BACK 0xC1 #define HID_CC_MARK 0xC2 #define HID_CC_CLEAR_MARK 0xC3 #define HID_CC_REPEAT_FROM_MARK 0xC4 #define HID_CC_RETURN_TO_MARK 0xC5 #define HID_CC_SEARCH_MARK_FORWARD 0xC6 #define HID_CC_SEARCH_MARK_BACKWARDS 0xC7 #define HID_CC_COUNTER_RESET 0xC8 #define HID_CC_SHOW_COUNTER 0xC9 #define HID_CC_TRACKING_INCREMENT 0xCA #define HID_CC_TRACKING_DECREMENT 0xCB #define HID_CC_STOP_EJECT 0xCC #define HID_CC_PLAY_PAUSE 0xCD #define HID_CC_PLAY_SKIP 0xCE #define HID_CC_VOICE_COMMAND 0xCF #define HID_CC_VOLUME 0xE0 #define HID_CC_BALANCE 0xE1 #define HID_CC_MUTE 0xE2 #define HID_CC_BASS 0xE3 #define HID_CC_TREBLE 0xE4 #define HID_CC_BASS_BOOST 0xE5 #define HID_CC_SURROUND_MODE 0xE6 #define HID_CC_LOUDNESS 0xE7 #define HID_CC_MPX 0xE8 #define HID_CC_VOLUME_UP 0xE9 #define HID_CC_VOLUME_DOWN 0xEA #define HID_CC_SPEED_SELECT 0xF0 #define HID_CC_PLAYBACK_SPEED 0xF1 #define HID_CC_STANDARD_PLAY 0xF2 #define HID_CC_LONG_PLAY 0xF3 #define HID_CC_EXTENDED_PLAY 0xF4 #define HID_CC_SLOW 0xF5 #define HID_CC_FAN_ENABLE 0x100 #define HID_CC_FAN_SPEED 0x101 #define HID_CC_LIGHT_ENABLE 0x102 #define HID_CC_LIGHT_ILLUMINATION_LEVEL 0x103 #define HID_CC_CLIMATE_CONTROL_ENABLE 0x104 #define HID_CC_ROOM_TEMPERATURE 0x105 #define HID_CC_SECURITY_ENABLE 0x106 #define HID_CC_FIRE_ALARM 0x107 #define HID_CC_POLICE_ALARM 0x108 #define HID_CC_PROXIMITY 0x109 #define HID_CC_MOTION 0x10A #define HID_CC_DURESS_ALARM 0x10B #define HID_CC_HOLDUP_ALARM 0x10C #define HID_CC_MEDICAL_ALARM 0x10D #define HID_CC_BALANCE_RIGHT 0x150 #define HID_CC_BALANCE_LEFT 0x151 #define HID_CC_BASS_INCREMENT 0x152 #define HID_CC_BASS_DECREMENT 0x153 #define HID_CC_TREBLE_INCREMENT 0x154 #define HID_CC_TREBLE_DECREMENT 0x155 #define HID_CC_SPEAKER_SYSTEM 0x160 #define HID_CC_CHANNEL_LEFT 0x161 #define HID_CC_CHANNEL_RIGHT 0x162 #define HID_CC_CHANNEL_CENTER 0x163 #define HID_CC_CHANNEL_FRONT 0x164 #define HID_CC_CHANNEL_CENTER_FRONT 0x165 #define HID_CC_CHANNEL_SIDE 0x166 #define HID_CC_CHANNEL_SURROUND 0x167 #define HID_CC_CHANNEL_LOW_FREQ_ENHANCEMENT 0x168 #define HID_CC_CHANNEL_TOP 0x169 #define HID_CC_CHANNEL_UNKNOWN 0x16A #define HID_CC_SUB_CHANNEL 0x170 #define HID_CC_SUB_CHANNEL_INCREMENT 0x171 #define HID_CC_SUB_CHANNEL_DECREMENT 0x172 #define HID_CC_ALTERNATE_AUDIO_INCREMENT 0x173 #define HID_CC_ALTERNATE_AUDIO_DECREMENT 0x174 #define HID_CC_APPLICATION_LAUNCH_BUTTONS 0x180 #define HID_CC_AL_LAUNCH_BUTTON_CONFIG_TOOL 0x181 #define HID_CC_AL_PROGRAMMABLE_BUTTON_CONFIG 0x182 #define HID_CC_AL_CONSUMER_CONTROL_CONFIG 0x183 #define HID_CC_AL_WORD_PROCESSOR 0x184 #define HID_CC_AL_TEXT_EDITOR 0x185 #define HID_CC_AL_SPREADSHEET 0x186 #define HID_CC_AL_GRAPHICS_EDITOR 0x187 #define HID_CC_AL_PRESENTATION_APP 0x188 #define HID_CC_AL_DATABASE_APP 0x189 #define HID_CC_AL_EMAIL_READER 0x18A #define HID_CC_AL_NEWSREADER 0x18B #define HID_CC_AL_VOICEMAIL 0x18C #define HID_CC_AL_CONTACTS_ADDRESS_BOOK 0x18D #define HID_CC_AL_CALENDAR_SCHEDULE 0x18E #define HID_CC_AL_TASK_PROJECT_MANAGER 0x18F #define HID_CC_AL_LOG_JOURNAL_TIMECARD 0x190 #define HID_CC_AL_CHECKBOOK_FINANCE 0x191 #define HID_CC_AL_CALCULATOR 0x192 #define HID_CC_AL_A_VCAPTURE_PLAYBACK 0x193 #define HID_CC_AL_LOCAL_MACHINE_BROWSER 0x194 #define HID_CC_AL_LAN_WANBROWSER 0x195 #define HID_CC_AL_INTERNET_BROWSER 0x196 #define HID_CC_AL_REMOTE_NETWORKING_ISPCONNECT 0x197 #define HID_CC_AL_NETWORK_CONFERENCE 0x198 #define HID_CC_AL_NETWORK_CHAT 0x199 #define HID_CC_AL_TELEPHONY_DIALER 0x19A #define HID_CC_AL_LOGON 0x19B #define HID_CC_AL_LOGOFF 0x19C #define HID_CC_AL_LOGON_LOGOFF 0x19D #define HID_CC_AL_TERMINAL_LOCK_SCREENSAVER 0x19E #define HID_CC_AL_CONTROL_PANEL 0x19F #define HID_CC_AL_COMMAND_LINE_PROCESSOR_RUN 0x1A0 #define HID_CC_AL_PROCESS_TASK_MANAGER 0x1A1 #define HID_CC_AL_SELECT_TASK_APPLICATION 0x1A2 #define HID_CC_AL_NEXT_TASK_APPLICATION 0x1A3 #define HID_CC_AL_PREVIOUS_TASK_APPLICATION 0x1A4 #define HID_CC_AL_PREEMPT_HALT_TASK_APPLICATION 0x1A5 #define HID_CC_AL_INTEGRATED_HELP_CENTER 0x1A6 #define HID_CC_AL_DOCUMENTS 0x1A7 #define HID_CC_AL_THESAURUS 0x1A8 #define HID_CC_AL_DICTIONARY 0x1A9 #define HID_CC_AL_DESKTOP 0x1AA #define HID_CC_AL_SPELL_CHECK 0x1AB #define HID_CC_AL_GRAMMAR_CHECK 0x1AC #define HID_CC_AL_WIRELESS_STATUS 0x1AD #define HID_CC_AL_KEYBOARD_LAYOUT 0x1AE #define HID_CC_AL_VIRUS_PROTECTION 0x1AF #define HID_CC_AL_ENCRYPTION 0x1B0 #define HID_CC_AL_SCREEN_SAVER 0x1B1 #define HID_CC_AL_ALARMS 0x1B2 #define HID_CC_AL_CLOCK 0x1B3 #define HID_CC_AL_FILE_BROWSER 0x1B4 #define HID_CC_AL_POWER_STATUS 0x1B5 #define HID_CC_AL_IMAGE_BROWSER 0x1B6 #define HID_CC_AL_AUDIO_BROWSER 0x1B7 #define HID_CC_AL_MOVIE_BROWSER 0x1B8 #define HID_CC_AL_DIGITAL_RIGHTS_MANAGER 0x1B9 #define HID_CC_AL_DIGITAL_WALLET 0x1BA #define HID_CC_AL_INSTANT_MESSAGING 0x1BC #define HID_CC_AL_OEMFEATURES_TIPS_TUTO_BROWSER 0x1BD #define HID_CC_AL_OEMHELP 0x1BE #define HID_CC_AL_ONLINE_COMMUNITY 0x1BF #define HID_CC_AL_ENTERTAINMENT_CONTENT_BROWSER 0x1C0 #define HID_CC_AL_ONLINE_SHOPPING_BROWSER 0x1C1 #define HID_CC_AL_SMART_CARD_INFORMATION_HELP 0x1C2 #define HID_CC_AL_MARKET_MONITOR_FINANCE_BROWSER 0x1C3 #define HID_CC_AL_CUSTOMIZED_CORP_NEWS_BROWSER 0x1C4 #define HID_CC_AL_ONLINE_ACTIVITY_BROWSER 0x1C5 #define HID_CC_AL_RESEARCH_SEARCH_BROWSER 0x1C6 #define HID_CC_AL_AUDIO_PLAYER 0x1C7 #define HID_CC_GENERIC_GUIAPPLICATION_CONTROLS 0x200 #define HID_CC_AC_NEW 0x201 #define HID_CC_AC_OPEN 0x202 #define HID_CC_AC_CLOSE 0x203 #define HID_CC_AC_EXIT 0x204 #define HID_CC_AC_MAXIMIZE 0x205 #define HID_CC_AC_MINIMIZE 0x206 #define HID_CC_AC_SAVE 0x207 #define HID_CC_AC_PRINT 0x208 #define HID_CC_AC_PROPERTIES 0x209 #define HID_CC_AC_UNDO 0x21A #define HID_CC_AC_COPY 0x21B #define HID_CC_AC_CUT 0x21C #define HID_CC_AC_PASTE 0x21D #define HID_CC_AC_SELECT_ALL 0x21E #define HID_CC_AC_FIND 0x21F #define HID_CC_AC_FINDAND_REPLACE 0x220 #define HID_CC_AC_SEARCH 0x221 #define HID_CC_AC_GO_TO 0x222 #define HID_CC_AC_HOME 0x223 #define HID_CC_AC_BACK 0x224 #define HID_CC_AC_FORWARD 0x225 #define HID_CC_AC_STOP 0x226 #define HID_CC_AC_REFRESH 0x227 #define HID_CC_AC_PREVIOUS_LINK 0x228 #define HID_CC_AC_NEXT_LINK 0x229 #define HID_CC_AC_BOOKMARKS 0x22A #define HID_CC_AC_HISTORY 0x22B #define HID_CC_AC_SUBSCRIPTIONS 0x22C #define HID_CC_AC_ZOOM_IN 0x22D #define HID_CC_AC_ZOOM_OUT 0x22E #define HID_CC_AC_ZOOM 0x22F #define HID_CC_AC_FULL_SCREEN_VIEW 0x230 #define HID_CC_AC_NORMAL_VIEW 0x231 #define HID_CC_AC_VIEW_TOGGLE 0x232 #define HID_CC_AC_SCROLL_UP 0x233 #define HID_CC_AC_SCROLL_DOWN 0x234 #define HID_CC_AC_SCROLL 0x235 #define HID_CC_AC_PAN_LEFT 0x236 #define HID_CC_AC_PAN_RIGHT 0x237 #define HID_CC_AC_PAN 0x238 #define HID_CC_AC_NEW_WINDOW 0x239 #define HID_CC_AC_TILE_HORIZONTALLY 0x23A #define HID_CC_AC_TILE_VERTICALLY 0x23B #define HID_CC_AC_FORMAT 0x23C #define HID_CC_AC_EDIT 0x23D #define HID_CC_AC_BOLD 0x23E #define HID_CC_AC_ITALICS 0x23F #define HID_CC_AC_UNDERLINE 0x240 #define HID_CC_AC_STRIKETHROUGH 0x241 #define HID_CC_AC_SUBSCRIPT 0x242 #define HID_CC_AC_SUPERSCRIPT 0x243 #define HID_CC_AC_ALL_CAPS 0x244 #define HID_CC_AC_ROTATE 0x245 #define HID_CC_AC_RESIZE 0x246 #define HID_CC_AC_FLIPHORIZONTAL 0x247 #define HID_CC_AC_FLIP_VERTICAL 0x248 #define HID_CC_AC_MIRROR_HORIZONTAL 0x249 #define HID_CC_AC_MIRROR_VERTICAL 0x24A #define HID_CC_AC_FONT_SELECT 0x24B #define HID_CC_AC_FONT_COLOR 0x24C #define HID_CC_AC_FONT_SIZE 0x24D #define HID_CC_AC_JUSTIFY_LEFT 0x24E #define HID_CC_AC_JUSTIFY_CENTER_H 0x24F #define HID_CC_AC_JUSTIFY_RIGHT 0x250 #define HID_CC_AC_JUSTIFY_BLOCK_H 0x251 #define HID_CC_AC_JUSTIFY_TOP 0x252 #define HID_CC_AC_JUSTIFY_CENTER_V 0x253 #define HID_CC_AC_JUSTIFY_BOTTOM 0x254 #define HID_CC_AC_JUSTIFY_BLOCK_V 0x255 #define HID_CC_AC_INDENT_DECREASE 0x256 #define HID_CC_AC_INDENT_INCREASE 0x257 #define HID_CC_AC_NUMBERED_LIST 0x258 #define HID_CC_AC_RESTART_NUMBERING 0x259 #define HID_CC_AC_BULLETED_LIST 0x25A #define HID_CC_AC_PROMOTE 0x25B #define HID_CC_AC_DEMOTE 0x25C #define HID_CC_AC_YES 0x25D #define HID_CC_AC_NO 0x25E #define HID_CC_AC_CANCEL 0x25F #define HID_CC_AC_CATALOG 0x260 #define HID_CC_AC_BUY_CHECKOUT 0x261 #define HID_CC_AC_ADDTO_CART 0x262 #define HID_CC_AC_EXPAND 0x263 #define HID_CC_AC_EXPAND_ALL 0x264 #define HID_CC_AC_COLLAPSE 0x265 #define HID_CC_AC_COLLAPSE_ALL 0x266 #define HID_CC_AC_PRINT_PREVIEW 0x267 #define HID_CC_AC_PASTE_SPECIAL 0x268 #define HID_CC_AC_INSERT_MODE 0x269 #define HID_CC_AC_DELETE 0x26A #define HID_CC_AC_LOCK 0x26B #define HID_CC_AC_UNLOCK 0x26C #define HID_CC_AC_PROTECT 0x26D #define HID_CC_AC_UNPROTECT 0x26E #define HID_CC_AC_ATTACH_COMMENT 0x26F #define HID_CC_AC_DELETE_COMMENT 0x270 #define HID_CC_AC_VIEW_COMMENT 0x271 #define HID_CC_AC_SELECT_WORD 0x272 #define HID_CC_AC_SELECT_SENTENCE 0x273 #define HID_CC_AC_SELECT_PARAGRAPH 0x274 #define HID_CC_AC_SELECT_COLUMN 0x275 #define HID_CC_AC_SELECT_ROW 0x276 #define HID_CC_AC_SELECT_TABLE 0x277 #define HID_CC_AC_SELECT_OBJECT 0x278 #define HID_CC_AC_REDO_REPEAT 0x279 #define HID_CC_AC_SORT 0x27A #define HID_CC_AC_SORT_ASCENDING 0x27B #define HID_CC_AC_SORT_DESCENDING 0x27C #define HID_CC_AC_FILTER 0x27D #define HID_CC_AC_SET_CLOCK 0x27E #define HID_CC_AC_VIEW_CLOCK 0x27F #define HID_CC_AC_SELECT_TIME_ZONE 0x280 #define HID_CC_AC_EDIT_TIME_ZONES 0x281 #define HID_CC_AC_SET_ALARM 0x282 #define HID_CC_AC_CLEAR_ALARM 0x283 #define HID_CC_AC_SNOOZE_ALARM 0x284 #define HID_CC_AC_RESET_ALARM 0x285 #define HID_CC_AC_SYNCHRONIZE 0x286 #define HID_CC_AC_SEND_RECEIVE 0x287 #define HID_CC_AC_SEND_TO 0x288 #define HID_CC_AC_REPLY 0x289 #define HID_CC_AC_REPLY_ALL 0x28A #define HID_CC_AC_FORWARD_MSG 0x28B #define HID_CC_AC_SEND 0x28C #define HID_CC_AC_ATTACH_FILE 0x28D #define HID_CC_AC_UPLOAD 0x28E #define HID_CC_AC_DOWNLOAD(SAVE_TARGET_AS) 0x28F #define HID_CC_AC_SET_BORDERS 0x290 #define HID_CC_AC_INSERT_ROW 0x291 #define HID_CC_AC_INSERT_COLUMN 0x292 #define HID_CC_AC_INSERT_FILE 0x293 #define HID_CC_AC_INSERT_PICTURE 0x294 #define HID_CC_AC_INSERT_OBJECT 0x295 #define HID_CC_AC_INSERT_SYMBOL 0x296 #define HID_CC_AC_SAVEAND_CLOSE 0x297 #define HID_CC_AC_RENAME 0x298 #define HID_CC_AC_MERGE 0x299 #define HID_CC_AC_SPLIT 0x29A #define HID_CC_AC_DISRIBUTE_HORIZONTALLY 0x29B #define HID_CC_AC_DISTRIBUTE_VERTICALLY 0x29C static const unsigned int hid_consumer_mapping[] = { [0x00] = 0, [0x07 ... 0x1F] = 0, [0x23 ... 0x2F] = 0, [0x37 ... 0x3F] = 0, [0x49 ... 0x5F] = 0, [0x67 ... 0x6C] = 0, [0x76 ... 0x7F] = 0, [0x9F ... 0x9F] = 0, [0xA5 ... 0xAF] = 0, [0xD0 ... 0xDF] = 0, [0xEB ... 0xEF] = 0, [0xF6 ... 0xFF] = 0, [0x10E ... 0x14F] = 0, [0x156 ... 0x15F] = 0, [0x16B ... 0x16F] = 0, [0x175 ... 0x17F] = 0, [0x1BB ... 0x1BB] = 0, [0x1C8 ... 0x1FF] = 0, [0x20A ... 0x219] = 0, [0x29D ... 0xFFF] = 0, [HID_CC_CONSUMER_CONTROL ] = 0, [HID_CC_NUMERIC_KEY_PAD ] = 0, [HID_CC_PROGRAMMABLE_BUTTONS ] = 0, [HID_CC_MICROPHONE ] = 0, [HID_CC_HEADPHONE ] = 0, [HID_CC_GRAPHIC_EQUALIZER ] = 0, [HID_CC_PLUS_10 ] = 0, [HID_CC_PLUS_100 ] = 0, [HID_CC_AM_PM ] = 0, [HID_CC_POWER ] = KEY_POWER, [HID_CC_RESET ] = 0, [HID_CC_SLEEP ] = KEY_SLEEP, [HID_CC_SLEEP_AFTER ] = 0, [HID_CC_SLEEP_MODE ] = 0, [HID_CC_ILLUMINATION ] = 0, [HID_CC_FUNCTION_BUTTONS ] = 0, [HID_CC_MENU ] = KEY_MENU, [HID_CC_MENU_PICK ] = 0, [HID_CC_MENU_UP ] = 0, [HID_CC_MENU_DOWN ] = 0, [HID_CC_MENU_LEFT ] = 0, [HID_CC_MENU_RIGHT ] = 0, [HID_CC_MENU_ESCAPE ] = 0, [HID_CC_MENU_VALUE_INCREASE ] = 0, [HID_CC_MENU_VALUE_DECREASE ] = 0, [HID_CC_DATA_ON_SCREEN ] = 0, [HID_CC_CLOSED_CAPTION ] = 0, [HID_CC_CLOSED_CAPTION_SELECT ] = 0, [HID_CC_VCR_TV ] = 0, [HID_CC_BROADCAST_MODE ] = 0, [HID_CC_SNAPSHOT ] = 0, [HID_CC_STILL ] = 0, [HID_CC_ASPECT ] = 0, [HID_CC_3D_MODE_SELECT ] = 0, [HID_CC_DISPLAY_BRIGHTNESS_INCREMENT ] = 0, [HID_CC_DISPLAY_BRIGHTNESS_DECREMENT ] = 0, [HID_CC_DISPLAY_BRIGHTNESS ] = 0, [HID_CC_DISPLAY_BACKLIGHT_TOGGLE ] = 0, [HID_CC_DISPLAY_SET_BRIGHTNESS_TO_MINIMUM ] = 0, [HID_CC_DISPLAY_SET_BRIGHTNESS_TO_MAXIMUM ] = 0, [HID_CC_DISPLAY_SET_AUTO_BRIGHTNESS ] = 0, [HID_CC_SELECTION ] = 0, [HID_CC_ASSIGN_SELECTION ] = 0, [HID_CC_MODE_STEP ] = 0, [HID_CC_RECALL_LAST ] = 0, [HID_CC_ENTER_CHANNEL ] = 0, [HID_CC_ORDER_MOVIE ] = 0, [HID_CC_CHANNEL ] = 0, [HID_CC_MEDIA_SELECTION ] = 0, [HID_CC_MEDIA_SELECT_COMPUTER ] = 0, [HID_CC_MEDIA_SELECT_TV ] = 0, [HID_CC_MEDIA_SELECT_WWW ] = 0, [HID_CC_MEDIA_SELECT_DVD ] = 0, [HID_CC_MEDIA_SELECT_TELEPHONE ] = 0, [HID_CC_MEDIA_SELECT_PROGRAM_GUIDE ] = 0, [HID_CC_MEDIA_SELECT_VIDEO_PHONE ] = 0, [HID_CC_MEDIA_SELECT_GAMES ] = 0, [HID_CC_MEDIA_SELECT_MESSAGES ] = 0, [HID_CC_MEDIA_SELECT_CD ] = 0, [HID_CC_MEDIA_SELECT_VCR ] = 0, [HID_CC_MEDIA_SELECT_TUNER ] = 0, [HID_CC_QUIT ] = 0, [HID_CC_HELP ] = KEY_HELP, [HID_CC_MEDIA_SELECT_TAPE ] = 0, [HID_CC_MEDIA_SELECT_CABLE ] = 0, [HID_CC_MEDIA_SELECT_SATELLITE ] = 0, [HID_CC_MEDIA_SELECT_SECURITY ] = 0, [HID_CC_MEDIA_SELECT_HOME ] = 0, [HID_CC_MEDIA_SELECT_CALL ] = 0, [HID_CC_CHANNEL_INCREMENT ] = 0, [HID_CC_CHANNEL_DECREMENT ] = 0, [HID_CC_MEDIA_SELECT_SAP ] = 0, [HID_CC_VCR_PLUS ] = 0, [HID_CC_ONCE ] = 0, [HID_CC_DAILY ] = 0, [HID_CC_WEEKLY ] = 0, [HID_CC_MONTHLY ] = 0, [HID_CC_PLAY ] = KEY_PLAY, [HID_CC_PAUSE ] = KEY_PAUSE, [HID_CC_RECORD ] = KEY_RECORD, [HID_CC_FAST_FORWARD ] = KEY_FASTFORWARD, [HID_CC_REWIND ] = KEY_REWIND, [HID_CC_SCAN_NEXT_TRACK ] = KEY_NEXTSONG, [HID_CC_SCAN_PREVIOUS_TRACK ] = KEY_PREVIOUSSONG, [HID_CC_STOP ] = KEY_STOP, [HID_CC_EJECT ] = KEY_EJECTCD, [HID_CC_RANDOM_PLAY ] = 0, [HID_CC_SELECT_DISC ] = 0, [HID_CC_ENTER_DISC ] = 0, [HID_CC_REPEAT ] = 0, [HID_CC_TRACKING ] = 0, [HID_CC_TRACK_NORMAL ] = 0, [HID_CC_SLOW_TRACKING ] = 0, [HID_CC_FRAME_FORWARD ] = 0, [HID_CC_FRAME_BACK ] = 0, [HID_CC_MARK ] = 0, [HID_CC_CLEAR_MARK ] = 0, [HID_CC_REPEAT_FROM_MARK ] = 0, [HID_CC_RETURN_TO_MARK ] = 0, [HID_CC_SEARCH_MARK_FORWARD ] = 0, [HID_CC_SEARCH_MARK_BACKWARDS ] = 0, [HID_CC_COUNTER_RESET ] = 0, [HID_CC_SHOW_COUNTER ] = 0, [HID_CC_TRACKING_INCREMENT ] = 0, [HID_CC_TRACKING_DECREMENT ] = 0, [HID_CC_STOP_EJECT ] = 0, [HID_CC_PLAY_PAUSE ] = KEY_PLAYPAUSE, [HID_CC_PLAY_SKIP ] = 0, [HID_CC_VOICE_COMMAND ] = KEY_VOICECOMMAND, [HID_CC_VOLUME ] = 0, [HID_CC_BALANCE ] = 0, [HID_CC_MUTE ] = KEY_MUTE, [HID_CC_BASS ] = 0, [HID_CC_TREBLE ] = 0, [HID_CC_BASS_BOOST ] = KEY_BASSBOOST, [HID_CC_SURROUND_MODE ] = 0, [HID_CC_LOUDNESS ] = 0, [HID_CC_MPX ] = 0, [HID_CC_VOLUME_UP ] = KEY_VOLUMEUP, [HID_CC_VOLUME_DOWN ] = KEY_VOLUMEDOWN, [HID_CC_SPEED_SELECT ] = 0, [HID_CC_PLAYBACK_SPEED ] = 0, [HID_CC_STANDARD_PLAY ] = 0, [HID_CC_LONG_PLAY ] = 0, [HID_CC_EXTENDED_PLAY ] = 0, [HID_CC_SLOW ] = KEY_SLOW, [HID_CC_FAN_ENABLE ] = 0, [HID_CC_FAN_SPEED ] = 0, [HID_CC_LIGHT_ENABLE ] = 0, [HID_CC_LIGHT_ILLUMINATION_LEVEL ] = 0, [HID_CC_CLIMATE_CONTROL_ENABLE ] = 0, [HID_CC_ROOM_TEMPERATURE ] = 0, [HID_CC_SECURITY_ENABLE ] = 0, [HID_CC_FIRE_ALARM ] = 0, [HID_CC_POLICE_ALARM ] = 0, [HID_CC_PROXIMITY ] = 0, [HID_CC_MOTION ] = 0, [HID_CC_DURESS_ALARM ] = 0, [HID_CC_HOLDUP_ALARM ] = 0, [HID_CC_MEDICAL_ALARM ] = 0, [HID_CC_BALANCE_RIGHT ] = 0, [HID_CC_BALANCE_LEFT ] = 0, [HID_CC_BASS_INCREMENT ] = 0, [HID_CC_BASS_DECREMENT ] = 0, [HID_CC_TREBLE_INCREMENT ] = 0, [HID_CC_TREBLE_DECREMENT ] = 0, [HID_CC_SPEAKER_SYSTEM ] = 0, [HID_CC_CHANNEL_LEFT ] = 0, [HID_CC_CHANNEL_RIGHT ] = 0, [HID_CC_CHANNEL_CENTER ] = 0, [HID_CC_CHANNEL_FRONT ] = 0, [HID_CC_CHANNEL_CENTER_FRONT ] = 0, [HID_CC_CHANNEL_SIDE ] = 0, [HID_CC_CHANNEL_SURROUND ] = 0, [HID_CC_CHANNEL_LOW_FREQ_ENHANCEMENT ] = 0, [HID_CC_CHANNEL_TOP ] = 0, [HID_CC_CHANNEL_UNKNOWN ] = 0, [HID_CC_SUB_CHANNEL ] = 0, [HID_CC_SUB_CHANNEL_INCREMENT ] = 0, [HID_CC_SUB_CHANNEL_DECREMENT ] = 0, [HID_CC_ALTERNATE_AUDIO_INCREMENT ] = 0, [HID_CC_ALTERNATE_AUDIO_DECREMENT ] = 0, [HID_CC_APPLICATION_LAUNCH_BUTTONS ] = 0, [HID_CC_AL_LAUNCH_BUTTON_CONFIG_TOOL ] = 0, [HID_CC_AL_PROGRAMMABLE_BUTTON_CONFIG ] = 0, [HID_CC_AL_CONSUMER_CONTROL_CONFIG ] = KEY_CONFIG, [HID_CC_AL_WORD_PROCESSOR ] = KEY_WORDPROCESSOR, [HID_CC_AL_TEXT_EDITOR ] = KEY_EDITOR, [HID_CC_AL_SPREADSHEET ] = KEY_SPREADSHEET, [HID_CC_AL_GRAPHICS_EDITOR ] = KEY_GRAPHICSEDITOR, [HID_CC_AL_PRESENTATION_APP ] = KEY_PRESENTATION, [HID_CC_AL_DATABASE_APP ] = KEY_DATABASE, [HID_CC_AL_EMAIL_READER ] = KEY_EMAIL, [HID_CC_AL_NEWSREADER ] = KEY_NEWS, [HID_CC_AL_VOICEMAIL ] = KEY_VOICEMAIL, [HID_CC_AL_CONTACTS_ADDRESS_BOOK ] = KEY_ADDRESSBOOK, [HID_CC_AL_CALENDAR_SCHEDULE ] = 0, [HID_CC_AL_TASK_PROJECT_MANAGER ] = 0, [HID_CC_AL_LOG_JOURNAL_TIMECARD ] = 0, [HID_CC_AL_CHECKBOOK_FINANCE ] = KEY_FINANCE, [HID_CC_AL_CALCULATOR ] = KEY_CALC, [HID_CC_AL_A_VCAPTURE_PLAYBACK ] = 0, [HID_CC_AL_LOCAL_MACHINE_BROWSER ] = KEY_FILE, [HID_CC_AL_LAN_WANBROWSER ] = 0, [HID_CC_AL_INTERNET_BROWSER ] = KEY_WWW, [HID_CC_AL_REMOTE_NETWORKING_ISPCONNECT ] = 0, [HID_CC_AL_NETWORK_CONFERENCE ] = 0, [HID_CC_AL_NETWORK_CHAT ] = 0, [HID_CC_AL_TELEPHONY_DIALER ] = KEY_PHONE, [HID_CC_AL_LOGON ] = 0, [HID_CC_AL_LOGOFF ] = 0, [HID_CC_AL_LOGON_LOGOFF ] = 0, [HID_CC_AL_TERMINAL_LOCK_SCREENSAVER ] = KEY_COFFEE, [HID_CC_AL_CONTROL_PANEL ] = 0, [HID_CC_AL_COMMAND_LINE_PROCESSOR_RUN ] = 0, [HID_CC_AL_PROCESS_TASK_MANAGER ] = 0, [HID_CC_AL_SELECT_TASK_APPLICATION ] = 0, [HID_CC_AL_NEXT_TASK_APPLICATION ] = 0, [HID_CC_AL_PREVIOUS_TASK_APPLICATION ] = 0, [HID_CC_AL_PREEMPT_HALT_TASK_APPLICATION ] = 0, [HID_CC_AL_INTEGRATED_HELP_CENTER ] = KEY_HELP, [HID_CC_AL_DOCUMENTS ] = 0, [HID_CC_AL_THESAURUS ] = 0, [HID_CC_AL_DICTIONARY ] = 0, [HID_CC_AL_DESKTOP ] = 0, [HID_CC_AL_SPELL_CHECK ] = 0, [HID_CC_AL_GRAMMAR_CHECK ] = 0, [HID_CC_AL_WIRELESS_STATUS ] = 0, [HID_CC_AL_KEYBOARD_LAYOUT ] = 0, [HID_CC_AL_VIRUS_PROTECTION ] = 0, [HID_CC_AL_ENCRYPTION ] = 0, [HID_CC_AL_SCREEN_SAVER ] = KEY_SCREENSAVER, [HID_CC_AL_ALARMS ] = 0, [HID_CC_AL_CLOCK ] = 0, [HID_CC_AL_FILE_BROWSER ] = KEY_FILE, [HID_CC_AL_POWER_STATUS ] = 0, [HID_CC_AL_IMAGE_BROWSER ] = KEY_IMAGES, [HID_CC_AL_AUDIO_BROWSER ] = KEY_AUDIO, [HID_CC_AL_MOVIE_BROWSER ] = KEY_VIDEO, [HID_CC_AL_DIGITAL_RIGHTS_MANAGER ] = 0, [HID_CC_AL_DIGITAL_WALLET ] = 0, [HID_CC_AL_INSTANT_MESSAGING ] = KEY_MESSENGER, [HID_CC_AL_OEMFEATURES_TIPS_TUTO_BROWSER ] = KEY_INFO, [HID_CC_AL_OEMHELP ] = 0, [HID_CC_AL_ONLINE_COMMUNITY ] = 0, [HID_CC_AL_ENTERTAINMENT_CONTENT_BROWSER ] = 0, [HID_CC_AL_ONLINE_SHOPPING_BROWSER ] = 0, [HID_CC_AL_SMART_CARD_INFORMATION_HELP ] = 0, [HID_CC_AL_MARKET_MONITOR_FINANCE_BROWSER ] = 0, [HID_CC_AL_CUSTOMIZED_CORP_NEWS_BROWSER ] = 0, [HID_CC_AL_ONLINE_ACTIVITY_BROWSER ] = 0, [HID_CC_AL_RESEARCH_SEARCH_BROWSER ] = 0, [HID_CC_AL_AUDIO_PLAYER ] = 0, [HID_CC_GENERIC_GUIAPPLICATION_CONTROLS ] = 0, [HID_CC_AC_NEW ] = KEY_NEW, [HID_CC_AC_OPEN ] = KEY_OPEN, [HID_CC_AC_CLOSE ] = KEY_CLOSE, [HID_CC_AC_EXIT ] = KEY_EXIT, [HID_CC_AC_MAXIMIZE ] = 0, [HID_CC_AC_MINIMIZE ] = 0, [HID_CC_AC_SAVE ] = KEY_SAVE, [HID_CC_AC_PRINT ] = KEY_PRINT, [HID_CC_AC_PROPERTIES ] = KEY_PROPS, [HID_CC_AC_UNDO ] = KEY_UNDO, [HID_CC_AC_COPY ] = KEY_COPY, [HID_CC_AC_CUT ] = KEY_CUT, [HID_CC_AC_PASTE ] = KEY_PASTE, [HID_CC_AC_SELECT_ALL ] = KEY_SELECT, [HID_CC_AC_FIND ] = KEY_FIND, [HID_CC_AC_FINDAND_REPLACE ] = 0, [HID_CC_AC_SEARCH ] = KEY_SEARCH, [HID_CC_AC_GO_TO ] = KEY_GOTO, [HID_CC_AC_HOME ] = KEY_HOMEPAGE, [HID_CC_AC_BACK ] = KEY_BACK, [HID_CC_AC_FORWARD ] = KEY_FORWARD, [HID_CC_AC_STOP ] = KEY_STOP, [HID_CC_AC_REFRESH ] = KEY_REFRESH, [HID_CC_AC_PREVIOUS_LINK ] = KEY_PREVIOUS, [HID_CC_AC_NEXT_LINK ] = KEY_NEXT, [HID_CC_AC_BOOKMARKS ] = KEY_BOOKMARKS, [HID_CC_AC_HISTORY ] = 0, [HID_CC_AC_SUBSCRIPTIONS ] = 0, [HID_CC_AC_ZOOM_IN ] = KEY_ZOOMIN, [HID_CC_AC_ZOOM_OUT ] = KEY_ZOOMOUT, [HID_CC_AC_ZOOM ] = KEY_ZOOMRESET, [HID_CC_AC_FULL_SCREEN_VIEW ] = 0, [HID_CC_AC_NORMAL_VIEW ] = 0, [HID_CC_AC_VIEW_TOGGLE ] = 0, [HID_CC_AC_SCROLL_UP ] = KEY_SCROLLUP, [HID_CC_AC_SCROLL_DOWN ] = KEY_SCROLLDOWN, [HID_CC_AC_SCROLL ] = 0, [HID_CC_AC_PAN_LEFT ] = 0, [HID_CC_AC_PAN_RIGHT ] = 0, [HID_CC_AC_PAN ] = 0, [HID_CC_AC_NEW_WINDOW ] = 0, [HID_CC_AC_TILE_HORIZONTALLY ] = 0, [HID_CC_AC_TILE_VERTICALLY ] = 0, [HID_CC_AC_FORMAT ] = 0, [HID_CC_AC_EDIT ] = KEY_EDIT, [HID_CC_AC_BOLD ] = 0, [HID_CC_AC_ITALICS ] = 0, [HID_CC_AC_UNDERLINE ] = 0, [HID_CC_AC_STRIKETHROUGH ] = 0, [HID_CC_AC_SUBSCRIPT ] = 0, [HID_CC_AC_SUPERSCRIPT ] = 0, [HID_CC_AC_ALL_CAPS ] = 0, [HID_CC_AC_ROTATE ] = 0, [HID_CC_AC_RESIZE ] = 0, [HID_CC_AC_FLIPHORIZONTAL ] = 0, [HID_CC_AC_FLIP_VERTICAL ] = 0, [HID_CC_AC_MIRROR_HORIZONTAL ] = 0, [HID_CC_AC_MIRROR_VERTICAL ] = 0, [HID_CC_AC_FONT_SELECT ] = 0, [HID_CC_AC_FONT_COLOR ] = 0, [HID_CC_AC_FONT_SIZE ] = 0, [HID_CC_AC_JUSTIFY_LEFT ] = 0, [HID_CC_AC_JUSTIFY_CENTER_H ] = 0, [HID_CC_AC_JUSTIFY_RIGHT ] = 0, [HID_CC_AC_JUSTIFY_BLOCK_H ] = 0, [HID_CC_AC_JUSTIFY_TOP ] = 0, [HID_CC_AC_JUSTIFY_CENTER_V ] = 0, [HID_CC_AC_JUSTIFY_BOTTOM ] = 0, [HID_CC_AC_JUSTIFY_BLOCK_V ] = 0, [HID_CC_AC_INDENT_DECREASE ] = 0, [HID_CC_AC_INDENT_INCREASE ] = 0, [HID_CC_AC_NUMBERED_LIST ] = 0, [HID_CC_AC_RESTART_NUMBERING ] = 0, [HID_CC_AC_BULLETED_LIST ] = 0, [HID_CC_AC_PROMOTE ] = 0, [HID_CC_AC_DEMOTE ] = 0, [HID_CC_AC_YES ] = 0, [HID_CC_AC_NO ] = 0, [HID_CC_AC_CANCEL ] = KEY_CANCEL, [HID_CC_AC_CATALOG ] = 0, [HID_CC_AC_BUY_CHECKOUT ] = 0, [HID_CC_AC_ADDTO_CART ] = 0, [HID_CC_AC_EXPAND ] = 0, [HID_CC_AC_EXPAND_ALL ] = 0, [HID_CC_AC_COLLAPSE ] = 0, [HID_CC_AC_COLLAPSE_ALL ] = 0, [HID_CC_AC_PRINT_PREVIEW ] = 0, [HID_CC_AC_PASTE_SPECIAL ] = 0, [HID_CC_AC_INSERT_MODE ] = 0, [HID_CC_AC_DELETE ] = KEY_DELETE, [HID_CC_AC_LOCK ] = 0, [HID_CC_AC_UNLOCK ] = 0, [HID_CC_AC_PROTECT ] = 0, [HID_CC_AC_UNPROTECT ] = 0, [HID_CC_AC_ATTACH_COMMENT ] = 0, [HID_CC_AC_DELETE_COMMENT ] = 0, [HID_CC_AC_VIEW_COMMENT ] = 0, [HID_CC_AC_SELECT_WORD ] = 0, [HID_CC_AC_SELECT_SENTENCE ] = 0, [HID_CC_AC_SELECT_PARAGRAPH ] = 0, [HID_CC_AC_SELECT_COLUMN ] = 0, [HID_CC_AC_SELECT_ROW ] = 0, [HID_CC_AC_SELECT_TABLE ] = 0, [HID_CC_AC_SELECT_OBJECT ] = 0, [HID_CC_AC_REDO_REPEAT ] = KEY_REDO, [HID_CC_AC_SORT ] = 0, [HID_CC_AC_SORT_ASCENDING ] = 0, [HID_CC_AC_SORT_DESCENDING ] = 0, [HID_CC_AC_FILTER ] = 0, [HID_CC_AC_SET_CLOCK ] = 0, [HID_CC_AC_VIEW_CLOCK ] = 0, [HID_CC_AC_SELECT_TIME_ZONE ] = 0, [HID_CC_AC_EDIT_TIME_ZONES ] = 0, [HID_CC_AC_SET_ALARM ] = 0, [HID_CC_AC_CLEAR_ALARM ] = 0, [HID_CC_AC_SNOOZE_ALARM ] = 0, [HID_CC_AC_RESET_ALARM ] = 0, [HID_CC_AC_SYNCHRONIZE ] = 0, [HID_CC_AC_SEND_RECEIVE ] = 0, [HID_CC_AC_SEND_TO ] = 0, [HID_CC_AC_REPLY ] = KEY_REPLY, [HID_CC_AC_REPLY_ALL ] = 0, [HID_CC_AC_FORWARD_MSG ] = KEY_FORWARDMAIL, [HID_CC_AC_SEND ] = KEY_SEND, [HID_CC_AC_ATTACH_FILE ] = 0, [HID_CC_AC_UPLOAD ] = 0, [HID_CC_AC_DOWNLOAD(SAVE_TARGET_AS) ] = 0, [HID_CC_AC_SET_BORDERS ] = 0, [HID_CC_AC_INSERT_ROW ] = 0, [HID_CC_AC_INSERT_COLUMN ] = 0, [HID_CC_AC_INSERT_FILE ] = 0, [HID_CC_AC_INSERT_PICTURE ] = 0, [HID_CC_AC_INSERT_OBJECT ] = 0, [HID_CC_AC_INSERT_SYMBOL ] = 0, [HID_CC_AC_SAVEAND_CLOSE ] = 0, [HID_CC_AC_RENAME ] = 0, [HID_CC_AC_MERGE ] = 0, [HID_CC_AC_SPLIT ] = 0, [HID_CC_AC_DISRIBUTE_HORIZONTALLY ] = 0, [HID_CC_AC_DISTRIBUTE_VERTICALLY ] = 0, }; unsigned int ratbag_hidraw_get_keycode_from_keyboard_usage(struct ratbag_device *device, uint8_t hid_code) { return hid_keyboard_mapping[hid_code]; } uint8_t ratbag_hidraw_get_keyboard_usage_from_keycode(struct ratbag_device *device, unsigned keycode) { unsigned int j; for (j = 0; j < ARRAY_LENGTH(hid_keyboard_mapping); j++) { if (hid_keyboard_mapping[j] == keycode) return j; } return 0; } unsigned int ratbag_hidraw_get_keycode_from_consumer_usage(struct ratbag_device *device, uint16_t hid_code) { return hid_consumer_mapping[hid_code]; } uint16_t ratbag_hidraw_get_consumer_usage_from_keycode(struct ratbag_device *device, unsigned keycode) { unsigned int j; for (j = 0; j < ARRAY_LENGTH(hid_consumer_mapping); j++) { if (hid_consumer_mapping[j] == keycode) return j; } return 0; } static int ratbag_hidraw_parse_report_descriptor(struct ratbag_device *device) { int rc, desc_size = 0; struct ratbag_hidraw *hidraw = &device->hidraw; struct hidraw_report_descriptor report_desc = {0}; unsigned int i, j; unsigned int usage_page, usage; hidraw->num_reports = 0; rc = ioctl(hidraw->fd, HIDIOCGRDESCSIZE, &desc_size); if (rc < 0) return rc; report_desc.size = desc_size; rc = ioctl(hidraw->fd, HIDIOCGRDESC, &report_desc); if (rc < 0) return rc; i = 0; usage_page = 0; usage = 0; while (i < report_desc.size) { uint8_t value = report_desc.value[i]; uint8_t hid = value & 0xfc; uint8_t size = value & 0x3; unsigned content = 0; if (size == 3) size = 4; if (i + size >= report_desc.size) return -EPROTO; for (j = 0; j < size; j++) content |= report_desc.value[i + j + 1] << (j * 8); switch (hid) { case HID_REPORT_ID: if (hidraw->reports) { log_debug(device->ratbag, "report ID %02x\n", content); hidraw->reports[hidraw->num_reports].report_id = content; hidraw->reports[hidraw->num_reports].usage_page = usage_page; hidraw->reports[hidraw->num_reports].usage = usage; } hidraw->num_reports++; break; case HID_COLLECTION: if (content == HID_APPLICATION && hidraw->reports && !hidraw->num_reports && !hidraw->reports[0].report_id) { hidraw->reports[hidraw->num_reports].usage_page = usage_page; hidraw->reports[hidraw->num_reports].usage = usage; } break; case HID_USAGE_PAGE: usage_page = content; break; case HID_USAGE: usage = content; break; } i += 1 + size; } return 0; } static int ratbag_open_hidraw_node(struct ratbag_device *device, struct udev_device *hidraw_udev) { struct hidraw_devinfo info; struct ratbag_device *tmp_device; int fd, res; const char *devnode; const char *sysname; size_t reports_size; device->hidraw.fd = -1; sysname = udev_device_get_sysname(hidraw_udev); if (!strneq("hidraw", sysname, 6)) return -ENODEV; list_for_each(tmp_device, &device->ratbag->devices, link) { if (tmp_device->hidraw.sysname && streq(tmp_device->hidraw.sysname, sysname)) { return -ENODEV; } } devnode = udev_device_get_devnode(hidraw_udev); fd = ratbag_open_path(device, devnode, O_RDWR); if (fd < 0) goto err; /* Get Raw Info */ res = ioctl(fd, HIDIOCGRAWINFO, &info); if (res < 0) { log_error(device->ratbag, "error while getting info from device"); goto err; } /* check basic matching between the hidraw node and the ratbag device */ if (info.bustype != device->ids.bustype || (info.vendor & 0xFFFF )!= device->ids.vendor || (info.product & 0xFFFF) != device->ids.product) { errno = ENODEV; goto err; } log_debug(device->ratbag, "%s is device '%s'.\n", device->name, udev_device_get_devnode(hidraw_udev)); device->hidraw.fd = fd; /* parse first to count the number of reports */ res = ratbag_hidraw_parse_report_descriptor(device); if (res) { log_error(device->ratbag, "Error while parsing the report descriptor: '%s' (%d)\n", strerror(-res), res); device->hidraw.fd = -1; goto err; } if (device->hidraw.num_reports) reports_size = device->hidraw.num_reports * sizeof(struct ratbag_hid_report); else reports_size = sizeof(struct ratbag_hid_report); device->hidraw.reports = zalloc(reports_size); ratbag_hidraw_parse_report_descriptor(device); device->hidraw.sysname = strdup_safe(sysname); return 0; err: if (fd >= 0) ratbag_close_fd(device, fd); return -errno; } static int ratbag_find_hidraw_node(struct ratbag_device *device, int (*match)(struct ratbag_device *device), int use_usb_parent) { struct ratbag *ratbag = device->ratbag; struct udev_enumerate *e; struct udev_list_entry *entry; const char *path; struct udev_device *hid_udev; struct udev_device *parent_udev; struct udev *udev = ratbag->udev; int rc = -ENODEV; int matched; assert(match); hid_udev = udev_device_get_parent_with_subsystem_devtype(device->udev_device, "hid", NULL); if (!hid_udev) return -ENODEV; if (use_usb_parent && device->ids.bustype == BUS_USB) { /* using the parent usb_device to match siblings */ parent_udev = udev_device_get_parent(hid_udev); if (!streq("uhid", udev_device_get_sysname(parent_udev))) parent_udev = udev_device_get_parent_with_subsystem_devtype(hid_udev, "usb", "usb_device"); } else { parent_udev = hid_udev; } e = udev_enumerate_new(udev); udev_enumerate_add_match_subsystem(e, "hidraw"); udev_enumerate_add_match_parent(e, parent_udev); udev_enumerate_scan_devices(e); udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { _cleanup_udev_device_unref_ struct udev_device *udev_device = NULL; path = udev_list_entry_get_name(entry); udev_device = udev_device_new_from_syspath(udev, path); if (!udev_device) continue; rc = ratbag_open_hidraw_node(device, udev_device); if (rc) goto skip; matched = match(device); rc = matched ? 0 : -ENODEV; if (matched == 1) goto out; skip: ratbag_close_hidraw(device); } out: udev_enumerate_unref(e); return rc; } int ratbag_find_hidraw(struct ratbag_device *device, int (*match)(struct ratbag_device *device)) { return ratbag_find_hidraw_node(device, match, true); } static int hidraw_match_all(struct ratbag_device *device) { return 1; } int ratbag_open_hidraw(struct ratbag_device *device) { return ratbag_find_hidraw_node(device, hidraw_match_all, false); } static struct ratbag_hid_report * ratbag_hidraw_get_report(struct ratbag_device *device, unsigned int report_id) { unsigned i; if (report_id == 0) { if (device->hidraw.reports[0].report_id == report_id) return &device->hidraw.reports[0]; else return NULL; } for (i = 0; i < device->hidraw.num_reports; i++) { if (device->hidraw.reports[i].report_id == report_id) return &device->hidraw.reports[i]; } return NULL; } int ratbag_hidraw_has_report(struct ratbag_device *device, unsigned int report_id) { return ratbag_hidraw_get_report(device, report_id) != NULL; } unsigned int ratbag_hidraw_get_usage_page(struct ratbag_device *device, unsigned int report_id) { struct ratbag_hid_report *report; report = ratbag_hidraw_get_report(device, report_id); if (!report) return 0; return report->usage_page; } unsigned int ratbag_hidraw_get_usage(struct ratbag_device *device, unsigned int report_id) { struct ratbag_hid_report *report; report = ratbag_hidraw_get_report(device, report_id); if (!report) return 0; return report->usage; } void ratbag_close_hidraw(struct ratbag_device *device) { if (device->hidraw.fd < 0) return; if (device->hidraw.sysname) { free(device->hidraw.sysname); device->hidraw.sysname = NULL; } ratbag_close_fd(device, device->hidraw.fd); device->hidraw.fd = -1; if (device->hidraw.reports) { free(device->hidraw.reports); device->hidraw.reports = NULL; } } int ratbag_hidraw_raw_request(struct ratbag_device *device, unsigned char reportnum, uint8_t *buf, size_t len, unsigned char rtype, int reqtype) { uint8_t tmp_buf[HID_MAX_BUFFER_SIZE]; int rc; if (len < 1 || len > HID_MAX_BUFFER_SIZE || !buf || device->hidraw.fd < 0) return -EINVAL; if (rtype != HID_FEATURE_REPORT) return -ENOTSUP; switch (reqtype) { case HID_REQ_GET_REPORT: memset(tmp_buf, 0, len); tmp_buf[0] = reportnum; rc = ioctl(device->hidraw.fd, HIDIOCGFEATURE(len), tmp_buf); if (rc < 0) return -errno; log_buf_raw(device->ratbag, "feature get: ", tmp_buf, (unsigned)rc); memcpy(buf, tmp_buf, rc); return rc; case HID_REQ_SET_REPORT: buf[0] = reportnum; log_buf_raw(device->ratbag, "feature set: ", buf, len); rc = ioctl(device->hidraw.fd, HIDIOCSFEATURE(len), buf); if (rc < 0) return -errno; return rc; } return -EINVAL; } int ratbag_hidraw_output_report(struct ratbag_device *device, uint8_t *buf, size_t len) { int rc; if (len < 1 || len > HID_MAX_BUFFER_SIZE || !buf || device->hidraw.fd < 0) return -EINVAL; log_buf_raw(device->ratbag, "output report: ", buf, len); rc = write(device->hidraw.fd, buf, len); if (rc < 0) return -errno; if (rc != (int)len) return -EIO; return 0; } int ratbag_hidraw_read_input_report(struct ratbag_device *device, uint8_t *buf, size_t len) { int rc; struct pollfd fds; if (len < 1 || !buf || device->hidraw.fd < 0) return -EINVAL; fds.fd = device->hidraw.fd; fds.events = POLLIN; rc = poll(&fds, 1, 1000); if (rc == -1) return -errno; if (rc == 0) return -ETIMEDOUT; rc = read(device->hidraw.fd, buf, len); if (rc > 0) log_buf_raw(device->ratbag, "input report: ", buf, rc); return rc >= 0 ? rc : -errno; } libratbag-0.9/src/libratbag-hidraw.h000066400000000000000000000127131311552661500174650ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include "libratbag.h" /* defined in the internal hid API in the kernel */ #define HID_INPUT_REPORT 0 #define HID_OUTPUT_REPORT 1 #define HID_FEATURE_REPORT 2 struct ratbag_hid_report { unsigned int report_id; unsigned int usage_page; unsigned int usage; }; struct ratbag_hidraw { int fd; struct ratbag_hid_report *reports; unsigned num_reports; char *sysname; }; /** * Open the hidraw device associated with the device. * * @param device the ratbag device * * @return 0 on success or a negative errno on error */ int ratbag_open_hidraw(struct ratbag_device *device); /** * Find and open the hidraw device associated with the device by using the * given matching function. * * @param device the ratbag device * @param match the matching test (return 1 if matched, 0 if not) * * @return 0 on success or a negative errno on error */ int ratbag_find_hidraw(struct ratbag_device *device, int (*match)(struct ratbag_device *device)); /** * Close the hidraw device associated with the device. * * @param device the ratbag device */ void ratbag_close_hidraw(struct ratbag_device *device); /** * Send report request to device * * @param device the ratbag device * @param reportnum report ID * @param buf in/out data to transfer * @param len length of buf * @param rtype HID report type * @param reqtype HID_REQ_GET_REPORT or HID_REQ_SET_REPORT * * @return count of data transfered, or a negative errno on error * * Same behavior as hid_hw_request, but with raw buffers instead. */ int ratbag_hidraw_raw_request(struct ratbag_device *device, unsigned char reportnum, uint8_t *buf, size_t len, unsigned char rtype, int reqtype); /** * Send output report to device * * @param device the ratbag device * @param buf raw data to transfer * @param len length of buf * * @return count of data transfered, or a negative errno on error */ int ratbag_hidraw_output_report(struct ratbag_device *device, uint8_t *buf, size_t len); /** * Read an input report from the device * * @param device the ratbag device * @param[out] buf resulting raw data * @param len length of buf * * @return count of data transfered, or a negative errno on error */ int ratbag_hidraw_read_input_report(struct ratbag_device *device, uint8_t *buf, size_t len); /** * Tells if a given device has the specified report ID. * * @param device the ratbag device which hidraw node is opened * @param report_id the report ID we inquire about * * @return 1 if the device has the given report id, 0 otherwise */ int ratbag_hidraw_has_report(struct ratbag_device *device, unsigned int report_id); /** * Gives the usage page of a report with the specified report ID. * * @param device the ratbag device which hidraw node is opened * @param report_id the report ID we inquire about * * @return the usage page of the report if the device has the given report id, * 0 otherwise */ unsigned int ratbag_hidraw_get_usage_page(struct ratbag_device *device, unsigned int report_id); /** * Gives the usage of a report with the specified report ID. * * @param device the ratbag device which hidraw node is opened * @param report_id the report ID we inquire about * * @return the usage of the report if the device has the given report id, * 0 otherwise */ unsigned int ratbag_hidraw_get_usage(struct ratbag_device *device, unsigned int report_id); /** * Gives the input key code associated to the keyboard HID usage. * * @return the key code of the HID usage or 0. */ unsigned int ratbag_hidraw_get_keycode_from_keyboard_usage(struct ratbag_device *device, uint8_t hid_code); /** * Gives the HID keyboard usage associated to the input keycode. * * @return the HID keyboard usage or 0. */ uint8_t ratbag_hidraw_get_keyboard_usage_from_keycode(struct ratbag_device *device, unsigned keycode); /** * Gives the input key code associated to the Consumer Control HID usage. * * @return the key code of the HID usage or 0. */ unsigned int ratbag_hidraw_get_keycode_from_consumer_usage(struct ratbag_device *device, uint16_t hid_code); /** * Gives the HID Consumer Control usage associated to the input keycode. * * @return the HID Consumer Control usage or 0. */ uint16_t ratbag_hidraw_get_consumer_usage_from_keycode(struct ratbag_device *device, unsigned keycode); libratbag-0.9/src/libratbag-private.h000066400000000000000000000331751311552661500176660ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include #include #include #include #include "libratbag.h" #include "libratbag-util.h" #include "libratbag-hidraw.h" static inline void cleanup_device(struct ratbag_device **d) { ratbag_device_unref(*d); } static inline void cleanup_profile(struct ratbag_profile **p) { ratbag_profile_unref(*p); } static inline void cleanup_resolution(struct ratbag_resolution **r) { ratbag_resolution_unref(*r); } #define _cleanup_device_ _cleanup_(cleanup_device) #define _cleanup_profile_ _cleanup_(cleanup_profile) #define _cleanup_resolution_ _cleanup_(cleanup_resolution) #define BUS_ANY 0xffff #define VENDOR_ANY 0xffff #define PRODUCT_ANY 0xffff #define VERSION_ANY 0xffff /* This struct is used by the test suite only */ struct ratbag_test_device; struct ratbag_driver; struct ratbag_button_action; struct ratbag { const struct ratbag_interface *interface; void *userdata; struct udev *udev; struct list drivers; struct list devices; int refcount; ratbag_log_handler log_handler; enum ratbag_log_priority log_priority; }; struct ratbag_device { char *name; void *userdata; struct udev_device *udev_device; struct ratbag_hidraw hidraw; int refcount; struct input_id ids; struct ratbag_driver *driver; struct ratbag *ratbag; unsigned long capabilities; unsigned num_profiles; struct list profiles; unsigned num_buttons; unsigned num_leds; void *drv_data; struct list link; }; /** * struct ratbag_driver - user space driver for a ratbag device */ struct ratbag_driver { /** A human-readable name of the driver */ char *name; /** The id of the driver used to match with RATBAG_DRIVER in udev */ char *id; /** * Callback called while trying to open a device by libratbag. * This function should decide whether or not this driver will * handle the given device. * * Return -ENODEV to ignore the device and let other drivers * probe the device. Any other error code will stop the probing. */ int (*probe)(struct ratbag_device *device); /** * Callback called right before the struct ratbag_device is * unref-ed. * * In this callback, the extra memory allocated in probe should * be freed. */ void (*remove)(struct ratbag_device *device); /** * Callback called when the driver should write any profiles that * were modified back to the device. * * Both profile and button structs have a dirty variable that can * be used to tell whether or not they've actually changed since * the last commit. In order to reduce the amount of time * committing takes, drivers should use this information to avoid * writing back profiles and buttons that haven't actually changed. */ int (*commit)(struct ratbag_device *device); /** * Callback called when a read profile is requested by the * caller of the library. * * The driver should probe here the device for the requested * profile and populate the related information. * There is no need to populate the various struct ratbag_button * as they are allocated when the user needs it. */ void (*read_profile)(struct ratbag_profile *profile, unsigned int index); /* * FIXME: This function is deprecated and should not be removed. Once * we've updated all the device drivers to stop using it we'll remove * it. Look at commit() instead. */ int (*write_profile)(struct ratbag_profile *profile); /** * Called to mark a previously writen profile as active. * * There should be no need to write the profile here, a * .write_profile() call is issued before calling this. */ int (*set_active_profile)(struct ratbag_device *device, unsigned int index); /** * For the given button, fill in the struct ratbag_button * with the available information. * * For devices with profiles, profile will not be NULL. For * device without, profile will be set to NULL. * * For devices with profile, there should not be the need to * actually read the profile from the device. The caller * should make sure that the profile is up to date. */ void (*read_button)(struct ratbag_button *button); /* * FIXME: This function is deprecated and should not be removed. Once * we've updated all the device drivers to stop using it we'll remove * it. Look at commit() instead. */ int (*write_button)(struct ratbag_button *button, const struct ratbag_button_action *action); /* * FIXME: This function is deprecated and should not be removed. Once * we've updated all the device drivers to stop using it we'll remove * it. Look at commit() instead. */ int (*write_resolution_dpi)(struct ratbag_resolution *resolution, int dpi_x, int dpi_y); /** * For the given led, fill in the struct ratbag_led * with the available information. */ void (*read_led)(struct ratbag_led *led); /* * FIXME: This function is deprecated and should be removed. Once * we've updated all the device drivers to stop using it we'll remove * it. Look at commit() instead. */ int (*write_led)(struct ratbag_led *led, enum ratbag_led_mode mode, struct ratbag_color color, unsigned int hz, unsigned int brightness); /* private */ int (*test_probe)(struct ratbag_device *device, void *data); struct list link; }; struct ratbag_resolution { struct ratbag_profile *profile; int refcount; void *userdata; unsigned int dpi_x; /**< x resolution in dpi */ unsigned int dpi_y; /**< y resolution in dpi */ unsigned int hz; /**< report rate in Hz */ bool is_active; bool is_default; uint32_t capabilities; }; struct ratbag_led { int refcount; void *userdata; struct list link; struct ratbag_profile *profile; unsigned index; enum ratbag_led_type type; enum ratbag_led_mode mode; struct ratbag_color color; unsigned int hz; /**< rate of action in hz */ unsigned int brightness; /**< brightness of the LED */ bool dirty; }; struct ratbag_profile { int refcount; void *userdata; struct list link; unsigned index; struct ratbag_device *device; struct list buttons; void *drv_data; void *user_data; struct { struct ratbag_resolution *modes; unsigned int num_modes; } resolution; struct list leds; bool is_active; /**< profile is the currently active one */ bool is_enabled; bool dirty; /**< profile changed since last commit */ }; #define BUTTON_ACTION_NONE \ { .type = RATBAG_BUTTON_ACTION_TYPE_NONE } #define BUTTON_ACTION_BUTTON(num_) \ { .type = RATBAG_BUTTON_ACTION_TYPE_BUTTON, \ .action.button = num_ } #define BUTTON_ACTION_SPECIAL(sp_) \ { .type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL, \ .action.special = sp_ } #define BUTTON_ACTION_KEY(k_) \ { .type = RATBAG_BUTTON_ACTION_TYPE_KEY, \ .action.key.key = k_ } #define BUTTON_ACTION_MACRO \ { .type = RATBAG_BUTTON_ACTION_TYPE_MACRO, \ /* FIXME: add the macro keys */ } struct ratbag_macro_event { enum ratbag_macro_event_type type; union ratbag_macro_evnt { unsigned int key; unsigned int timeout; } event; }; #define MAX_MACRO_EVENTS 256 struct ratbag_macro { char *name; char *group; struct ratbag_macro_event events[MAX_MACRO_EVENTS]; }; struct ratbag_button_macro { int refcount; struct ratbag_macro macro; }; struct ratbag_button_action { enum ratbag_button_action_type type; union ratbag_btn_action { unsigned int button; /* action_type == button */ enum ratbag_button_action_special special; /* action_type == special */ struct { unsigned int key; /* action_type == key */ /* FIXME: modifiers */ } key; } action; struct ratbag_macro *macro; /* dynamically allocated, so kept aside */ }; struct ratbag_button { int refcount; void *userdata; struct list link; struct ratbag_profile *profile; unsigned index; enum ratbag_button_type type; struct ratbag_button_action action; uint32_t action_caps; bool dirty; /* changed since last commit to device */ }; static inline void ratbag_button_enable_action_type(struct ratbag_button *button, enum ratbag_button_action_type type) { button->action_caps |= 1 << type; } static inline int ratbag_open_path(struct ratbag_device *device, const char *path, int flags) { struct ratbag *ratbag = device->ratbag; return ratbag->interface->open_restricted(path, flags, ratbag->userdata); } static inline void ratbag_close_fd(struct ratbag_device *device, int fd) { struct ratbag *ratbag = device->ratbag; return ratbag->interface->close_restricted(fd, ratbag->userdata); } static inline void ratbag_set_drv_data(struct ratbag_device *device, void *drv_data) { device->drv_data = drv_data; } static inline void * ratbag_get_drv_data(struct ratbag_device *device) { return device->drv_data; } int ratbag_device_init_profiles(struct ratbag_device *device, unsigned int num_profiles, unsigned int num_resolutions, unsigned int num_buttons, unsigned int num_leds); void ratbag_device_set_capability(struct ratbag_device *device, enum ratbag_device_capability cap); static inline void ratbag_profile_set_drv_data(struct ratbag_profile *profile, void *drv_data) { profile->drv_data = drv_data; } static inline void * ratbag_profile_get_drv_data(struct ratbag_profile *profile) { return profile->drv_data; } static inline int ratbag_button_action_match(const struct ratbag_button_action *action, const struct ratbag_button_action *match) { if (action->type != match->type) return 0; switch (action->type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: return match->action.button == action->action.button; case RATBAG_BUTTON_ACTION_TYPE_KEY: return match->action.key.key == action->action.key.key; case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: return match->action.special == action->action.special; case RATBAG_BUTTON_ACTION_TYPE_MACRO: return 1; default: break; } return 0; } static inline struct ratbag_resolution * ratbag_resolution_init(struct ratbag_profile *profile, int index, int dpi_x, int dpi_y, int hz) { struct ratbag_resolution *res = &profile->resolution.modes[index]; res->profile = profile; res->dpi_x = dpi_x; res->dpi_y = dpi_y; res->hz = hz; res->is_active = false; res->is_default = false; res->capabilities = 0; return res; } static inline void ratbag_resolution_set_cap(struct ratbag_resolution *res, enum ratbag_resolution_capability cap) { assert(cap <= RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION); res->capabilities = (1 << cap); } void log_msg_va(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *format, va_list args) LIBRATBAG_ATTRIBUTE_PRINTF(3, 0); void log_msg(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *format, ...) LIBRATBAG_ATTRIBUTE_PRINTF(3, 4); void log_buffer(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *header, uint8_t *buf, size_t len); #define log_raw(li_, ...) log_msg((li_), RATBAG_LOG_PRIORITY_RAW, __VA_ARGS__) #define log_debug(li_, ...) log_msg((li_), RATBAG_LOG_PRIORITY_DEBUG, __VA_ARGS__) #define log_info(li_, ...) log_msg((li_), RATBAG_LOG_PRIORITY_INFO, __VA_ARGS__) #define log_error(li_, ...) log_msg((li_), RATBAG_LOG_PRIORITY_ERROR, __VA_ARGS__) #define log_bug_kernel(li_, ...) log_msg((li_), RATBAG_LOG_PRIORITY_ERROR, "kernel bug: " __VA_ARGS__) #define log_bug_libratbag(li_, ...) log_msg((li_), RATBAG_LOG_PRIORITY_ERROR, "libratbag bug: " __VA_ARGS__) #define log_bug_client(li_, ...) log_msg((li_), RATBAG_LOG_PRIORITY_ERROR, "client bug: " __VA_ARGS__) #define log_buf_raw(li_, h_, buf_, len_) log_buffer(li_, RATBAG_LOG_PRIORITY_RAW, h_, buf_, len_) #define log_buf_debug(li_, h_, buf_, len_) log_buffer(li_, RATBAG_LOG_PRIORITY_DEBUG, h_, buf_, len_) #define log_buf_info(li_, h_, buf_, len_) log_buffer(li_, RATBAG_LOG_PRIORITY_INFO, h_, buf_, len_) #define log_buf_error(li_, h_, buf_, len_) log_buffer(li_, RATBAG_LOG_PRIORITY_ERROR, h_, buf_, len_) /* list of all supported drivers */ struct ratbag_driver etekcity_driver; struct ratbag_driver hidpp20_driver; struct ratbag_driver hidpp10_driver; struct ratbag_driver logitech_g300_driver; struct ratbag_driver roccat_driver; struct ratbag_driver gskill_driver; struct ratbag_device* ratbag_device_new(struct ratbag *ratbag, struct udev_device *udev_device, const char *name, const struct input_id *id); void ratbag_device_destroy(struct ratbag_device *device); const char * ratbag_device_get_udev_property(const struct ratbag_device* device, const char *name); bool ratbag_assign_driver(struct ratbag_device *device, const struct input_id *dev_id, struct ratbag_test_device *test_device); void ratbag_register_driver(struct ratbag *ratbag, struct ratbag_driver *driver); void ratbag_button_copy_macro(struct ratbag_button *button, const struct ratbag_button_macro *macro); libratbag-0.9/src/libratbag-test.c000066400000000000000000000044661311552661500171670ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include "libratbag-private.h" #include "libratbag-util.h" #include "libratbag-test.h" struct ratbag_driver test_driver; static inline void ratbag_register_test_drivers(struct ratbag *ratbag) { struct ratbag_driver *driver; /* Don't use a static variable here, otherwise the CK_FORK=no case * will fail */ list_for_each(driver, &ratbag->drivers, link) { if (streq(driver->name, test_driver.name)) return; } ratbag_register_driver(ratbag, &test_driver); } LIBRATBAG_EXPORT struct ratbag_device* ratbag_device_new_test_device(struct ratbag *ratbag, struct ratbag_test_device *test_device) { struct ratbag_device* device = NULL; #if BUILD_TESTS struct input_id id = { .bustype = 0x00, .vendor = 0x00, .product = 0x00, .version = 0x00, }; ratbag_register_test_drivers(ratbag); if (getenv("RATBAG_TEST") == NULL) { fprintf(stderr, "RATBAG_TEST environment variable not set\n"); abort(); } device = ratbag_device_new(ratbag, NULL, "Test device", &id); if (!ratbag_assign_driver(device, &device->ids, test_device)) { ratbag_device_destroy(device); return NULL; } #endif return device; } libratbag-0.9/src/libratbag-test.h000066400000000000000000000047201311552661500171650ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "libratbag.h" #define RATBAG_TEST_MAX_PROFILES 12 #define RATBAG_TEST_MAX_BUTTONS 25 #define RATBAG_TEST_MAX_RESOLUTIONS 8 #define RATBAG_TEST_MAX_LEDS 8 struct ratbag_test_button { enum ratbag_button_action_type type; union { int button; int key; enum ratbag_button_action_special special; }; }; struct ratbag_test_resolution { int xres, yres; int hz; bool active; bool dflt; uint32_t caps; }; struct ratbag_test_color { unsigned short red; unsigned short green; unsigned short blue; }; struct ratbag_test_led { enum ratbag_led_type type; enum ratbag_led_mode mode; struct ratbag_test_color color; unsigned int hz; unsigned int brightness; }; struct ratbag_test_profile { struct ratbag_test_button buttons[RATBAG_TEST_MAX_BUTTONS]; struct ratbag_test_resolution resolutions[RATBAG_TEST_MAX_RESOLUTIONS]; struct ratbag_test_led leds[RATBAG_TEST_MAX_LEDS]; bool active; bool dflt; }; struct ratbag_test_device { unsigned int num_profiles; unsigned int num_resolutions; unsigned int num_buttons; unsigned int num_leds; struct ratbag_test_profile profiles[RATBAG_TEST_MAX_PROFILES]; void (*destroyed)(struct ratbag_device *device, void *data); void *destroyed_data; }; struct ratbag_device* ratbag_device_new_test_device(struct ratbag *ratbag, struct ratbag_test_device *test_device); libratbag-0.9/src/libratbag-util.c000066400000000000000000000104151311552661500171540ustar00rootroot00000000000000/* * Copyright © 2008-2011 Kristian Høgsberg * Copyright © 2011 Intel Corporation * Copyright © 2013-2015 Red Hat, Inc. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that copyright * notice and this permission notice appear in supporting documentation, and * that the name of the copyright holders not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. The copyright holders make no representations * about the suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE * OF THIS SOFTWARE. */ /* * This list data structure is verbatim copy from wayland-util.h from the * Wayland project; except that wl_ prefix has been removed. */ #include "config.h" #include #include #include #include #include #include #include #include #include "libratbag-util.h" #include "libratbag-private.h" void list_init(struct list *list) { list->prev = list; list->next = list; } void list_insert(struct list *list, struct list *elm) { elm->prev = list; elm->next = list->next; list->next = elm; elm->next->prev = elm; } void list_remove(struct list *elm) { elm->prev->next = elm->next; elm->next->prev = elm->prev; elm->next = NULL; elm->prev = NULL; } int list_empty(const struct list *list) { return list->next == list; } const char * udev_prop_value(struct udev_device *device, const char *prop_name) { struct udev_device *parent; const char *prop_value = NULL; parent = device; while (parent && !prop_value) { prop_value = udev_device_get_property_value(parent, prop_name); parent = udev_device_get_parent(parent); } return prop_value; } ssize_t ratbag_utf8_to_enc(char *buf, size_t buf_len, const char *to_enc, const char *format, ...) { va_list args; iconv_t converter; char str[buf_len]; char *in_buf = str, *out_buf = (char*)buf; size_t in_bytes_left, out_bytes_left = buf_len; int ret; memset(buf, 0, buf_len); va_start(args, format); ret = vsnprintf(str, buf_len, format, args); va_end(args); if (ret < 0) return ret; in_bytes_left = ret; converter = iconv_open(to_enc, "UTF-8"); if (converter == (iconv_t)-1) return -errno; ret = iconv(converter, &in_buf, &in_bytes_left, &out_buf, &out_bytes_left); if (ret) ret = -errno; else ret = buf_len - out_bytes_left; iconv_close(converter); return ret; } ssize_t ratbag_utf8_from_enc(char *in_buf, size_t in_len, const char *from_enc, char **out) { iconv_t converter; size_t len = in_len * 6, in_bytes_left = in_len, out_bytes_left = len; char *pos; ssize_t ret; converter = iconv_open("UTF-8", from_enc); if (converter == (iconv_t)-1) return -errno; /* * We *could* dynamically allocate the out buffer. However iconv's * obnoxious semantics, mainly the fact that it modifies every pointer * given to it, would make this code a lot larger and complex than it * needs to be for a mouse with blinking lights. * * So, since there's no encoding that takes more then 6 bytes per * character, allocate for that and just fail if that's not enough. */ *out = zalloc(len); pos = *out; if (!*out) { ret = -errno; goto err; } ret = iconv(converter, &in_buf, &in_bytes_left, &pos, &out_bytes_left); if (ret) { ret = -errno; goto err; } /* Now get rid of any space in the buffer we don't need */ *out = realloc(*out, (len - out_bytes_left) + 1); if (!*out) ret = -errno; else ret = (len - out_bytes_left) + 1; err: if (ret < 0 && *out) { free(*out); *out = NULL; } iconv_close(converter); return ret; } libratbag-0.9/src/libratbag-util.h000066400000000000000000000134431311552661500171650ustar00rootroot00000000000000/* * Copyright © 2008 Kristian Høgsberg * Copyright © 2013-2015 Red Hat, Inc. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that copyright * notice and this permission notice appear in supporting documentation, and * that the name of the copyright holders not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. The copyright holders make no representations * about the suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE * OF THIS SOFTWARE. */ #pragma once #include #include #include #include #include /* * This list data structure is a verbatim copy from wayland-util.h from the * Wayland project; except that wl_ prefix has been removed. */ struct list { struct list *prev; struct list *next; }; void list_init(struct list *list); void list_insert(struct list *list, struct list *elm); void list_remove(struct list *elm); int list_empty(const struct list *list); #ifdef __GNUC__ #define container_of(ptr, sample, member) \ (__typeof__(sample))((char *)(ptr) - \ ((char *)&(sample)->member - (char *)(sample))) #else #define container_of(ptr, sample, member) \ (void *)((char *)(ptr) - \ ((char *)&(sample)->member - (char *)(sample))) #endif #define list_for_each(pos, head, member) \ for (pos = 0, pos = container_of((head)->next, pos, member); \ &pos->member != (head); \ pos = container_of(pos->member.next, pos, member)) #define list_for_each_safe(pos, tmp, head, member) \ for (pos = 0, tmp = 0, \ pos = container_of((head)->next, pos, member), \ tmp = container_of((pos)->member.next, tmp, member); \ &pos->member != (head); \ pos = tmp, \ tmp = container_of(pos->member.next, tmp, member)) #define LONG_BITS (sizeof(long) * 8) #define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS) #define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) #define ARRAY_FOR_EACH(_arr, _elem) \ for (size_t _i = 0; _i < ARRAY_LENGTH(_arr) && (_elem = &_arr[_i]); _i++) #define AS_MASK(v) (1 << (v)) #define min(a, b) (((a) < (b)) ? (a) : (b)) #define max(a, b) (((a) > (b)) ? (a) : (b)) #define streq(s1, s2) (strcmp((s1), (s2)) == 0) #define strneq(s1, s2, n) (strncmp((s1), (s2), (n)) == 0) static inline void cleanup_free(void *p) { free(*(void**)p); } static inline void cleanup_close(int *p) { close(*p); } static inline void cleanup_udev_unref(struct udev **udev) { if (*udev) udev_unref(*udev); } static inline void cleanup_udev_device_unref(struct udev_device **udev_device) { if (*udev_device) udev_device_unref(*udev_device); } #define _cleanup_(x) __attribute__((cleanup(x))) #define _cleanup_free_ _cleanup_(cleanup_free) #define _cleanup_close_ _cleanup_(cleanup_close) #define _cleanup_udev_unref_ _cleanup_(cleanup_udev_unref) #define _cleanup_udev_device_unref_ _cleanup_(cleanup_udev_device_unref) static inline char* strncpy_safe(char *dest, const char *src, size_t n) { strncpy(dest, src, n); dest[n - 1] = '\0'; return dest; } #define LIBRATBAG_EXPORT __attribute__ ((visibility("default"))) static inline void * zalloc(size_t size) { void *p = calloc(1, size); if (!p) abort(); return p; } /** * returns NULL if str is NULL, otherwise guarantees a successful strdup. */ static inline char * strdup_safe(const char *str) { char *s; if (!str) return NULL; s = strdup(str); if (!s) abort(); return s; } static inline int snprintf_safe(char *buf, size_t n, const char *fmt, ...) { va_list args; int rc; va_start(args, fmt); rc = vsnprintf(buf, n, fmt, args); va_end(args); if (rc < 0 || n < (size_t)rc) abort(); return rc; } #define sprintf_safe(buf, fmt, ...) \ snprintf_safe(buf, ARRAY_LENGTH(buf), fmt, __VA_ARGS__) static inline void msleep(unsigned int ms) { usleep(ms * 1000); } static inline int long_bit_is_set(const unsigned long *array, int bit) { return !!(array[bit / LONG_BITS] & (1LL << (bit % LONG_BITS))); } static inline void long_set_bit(unsigned long *array, int bit) { array[bit / LONG_BITS] |= (1LL << (bit % LONG_BITS)); } static inline void long_clear_bit(unsigned long *array, int bit) { array[bit / LONG_BITS] &= ~(1LL << (bit % LONG_BITS)); } static inline void long_set_bit_state(unsigned long *array, int bit, int state) { if (state) long_set_bit(array, bit); else long_clear_bit(array, bit); } const char * udev_prop_value(struct udev_device *device, const char *property_name); /** * Converts a string from UTF-8 to the encoding specified. Returns the number * of bytes written to buf on success, or negative errno value on failure. */ ssize_t ratbag_utf8_to_enc(char *buf, size_t buf_len, const char *to_enc, const char *format, ...) __attribute__((format(printf, 4, 5))); /** * Converts a string from the given encoding into UTF-8. The memory for the * result is allocated and a pointer to the result is placed in *out. Returns * the number of bytes in the UTF-8 version of the string on success, negative * errno value on failure. */ ssize_t ratbag_utf8_from_enc(char *in_buf, size_t in_len, const char *from_enc, char **out); libratbag-0.9/src/libratbag.c000066400000000000000000001161421311552661500162050ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "usb-ids.h" #include "libratbag-private.h" #include "libratbag-util.h" static enum ratbag_error_code error_code(enum ratbag_error_code code) { switch(code) { case RATBAG_SUCCESS: case RATBAG_ERROR_DEVICE: case RATBAG_ERROR_CAPABILITY: case RATBAG_ERROR_VALUE: case RATBAG_ERROR_SYSTEM: case RATBAG_ERROR_IMPLEMENTATION: break; default: assert(!"Invalid error code. This is a library bug."); } return code; } static void ratbag_profile_destroy(struct ratbag_profile *profile); static void ratbag_button_destroy(struct ratbag_button *button); static void ratbag_led_destroy(struct ratbag_led *led); static void ratbag_default_log_func(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *format, va_list args) { const char *prefix; switch(priority) { case RATBAG_LOG_PRIORITY_RAW: prefix = "raw"; break; case RATBAG_LOG_PRIORITY_DEBUG: prefix = "debug"; break; case RATBAG_LOG_PRIORITY_INFO: prefix = "info"; break; case RATBAG_LOG_PRIORITY_ERROR: prefix = "error"; break; default: prefix=""; break; } fprintf(stderr, "ratbag %s: ", prefix); vfprintf(stderr, format, args); } void log_msg_va(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *format, va_list args) { if (ratbag->log_handler && ratbag->log_priority <= priority) ratbag->log_handler(ratbag, priority, format, args); } void log_msg(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *format, ...) { va_list args; va_start(args, format); log_msg_va(ratbag, priority, format, args); va_end(args); } void log_buffer(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *header, uint8_t *buf, size_t len) { _cleanup_free_ char *output_buf = NULL; char *sep = ""; unsigned int i, n; unsigned int buf_len; if (ratbag->log_handler && ratbag->log_priority > priority) return; buf_len = header ? strlen(header) : 0; buf_len += len * 3; buf_len += 1; /* terminating '\0' */ output_buf = zalloc(buf_len); n = 0; if (header) n += snprintf_safe(output_buf, buf_len - n, "%s", header); for (i = 0; i < len; ++i) { n += snprintf_safe(&output_buf[n], buf_len - n, "%s%02x", sep, buf[i] & 0xFF); sep = " "; } log_msg(ratbag, priority, "%s\n", output_buf); } LIBRATBAG_EXPORT void ratbag_log_set_priority(struct ratbag *ratbag, enum ratbag_log_priority priority) { switch (priority) { case RATBAG_LOG_PRIORITY_RAW: case RATBAG_LOG_PRIORITY_DEBUG: case RATBAG_LOG_PRIORITY_INFO: case RATBAG_LOG_PRIORITY_ERROR: break; default: log_bug_client(ratbag, "Invalid log priority %d. Using INFO instead\n", priority); priority = RATBAG_LOG_PRIORITY_INFO; } ratbag->log_priority = priority; } LIBRATBAG_EXPORT enum ratbag_log_priority ratbag_log_get_priority(const struct ratbag *ratbag) { return ratbag->log_priority; } LIBRATBAG_EXPORT void ratbag_log_set_handler(struct ratbag *ratbag, ratbag_log_handler log_handler) { ratbag->log_handler = log_handler; } struct ratbag_device* ratbag_device_new(struct ratbag *ratbag, struct udev_device *udev_device, const char *name, const struct input_id *id) { struct ratbag_device *device = NULL; device = zalloc(sizeof(*device)); device->name = strdup_safe(name); device->ratbag = ratbag_ref(ratbag); device->refcount = 1; device->udev_device = udev_device_ref(udev_device); device->ids = *id; list_init(&device->profiles); list_insert(&ratbag->devices, &device->link); /* We assume that most devices have this capability, so let's set it * by default. The few devices that miss this capability should * unset it instead. */ ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_QUERY_CONFIGURATION); return device; } void ratbag_device_destroy(struct ratbag_device *device) { struct ratbag_profile *profile, *next; if (!device) return; /* if we get to the point where the device is destroyed, profiles, * buttons, etc. are at a refcount of 0, so we can destroy * everything */ if (device->driver && device->driver->remove) device->driver->remove(device); list_for_each_safe(profile, next, &device->profiles, link) ratbag_profile_destroy(profile); if (device->udev_device) udev_device_unref(device->udev_device); list_remove(&device->link); ratbag_unref(device->ratbag); free(device->name); free(device); } static inline bool ratbag_sanity_check_device(struct ratbag_device *device) { struct ratbag *ratbag = device->ratbag; _cleanup_profile_ struct ratbag_profile *profile = NULL; bool has_active = false; unsigned int nres, nprofiles; bool rc = false; unsigned int i; /* arbitrary number: max 16 profiles, does any mouse have more? but * since we have num_profiles unsigned, it also checks for * accidental negative */ if (device->num_profiles == 0 || device->num_profiles > 16) { log_bug_libratbag(ratbag, "%s: invalid number of profiles (%d)\n", device->name, device->num_profiles); goto out; } nprofiles = ratbag_device_get_num_profiles(device); for (i = 0; i < nprofiles; i++) { profile = ratbag_device_get_profile(device, i); if (!profile) goto out; /* Allow max 1 active profile */ if (profile->is_active) { if (has_active) { log_bug_libratbag(ratbag, "%s: multiple active profiles\n", device->name); goto out; } has_active = true; } nres = ratbag_profile_get_num_resolutions(profile); if (nres == 0 || nres > 16) { log_bug_libratbag(ratbag, "%s: invalid number of resolutions (%d)\n", device->name, nres); goto out; } ratbag_profile_unref(profile); profile = NULL; } /* Require 1 active profile */ if (!has_active) { log_bug_libratbag(ratbag, "%s: no profile set as active profile\n", device->name); goto out; } rc = true; out: return rc; } static inline bool ratbag_test_driver(struct ratbag_device *device, const struct input_id *dev_id, const char *driver_name, struct ratbag_test_device *test_device) { struct ratbag *ratbag = device->ratbag; struct ratbag_driver *driver; int rc; list_for_each(driver, &ratbag->drivers, link) { if (streq(driver->id, driver_name)) { device->driver = driver; break; } } if (!device->driver) { log_error(ratbag, "%s: driver '%s' does not exist\n", device->name, driver_name); goto error; } if (test_device) rc = device->driver->test_probe(device, test_device); else rc = device->driver->probe(device); if (rc == 0) { if (!ratbag_sanity_check_device(device)) { goto error; } else { log_debug(ratbag, "driver match found: %s\n", device->driver->name); return true; } } if (rc != -ENODEV) log_error(ratbag, "%s: error opening hidraw node (%s)\n", device->name, strerror(-rc)); error: device->driver = NULL; return false; } static inline bool ratbag_driver_fallback_logitech(struct ratbag_device *device, const struct input_id *dev_id) { int rc; if (dev_id->vendor != USB_VENDOR_ID_LOGITECH) return false; rc = ratbag_test_driver(device, dev_id, "hidpp20", NULL); if (!rc) rc = ratbag_test_driver(device, dev_id, "hidpp10", NULL); return rc; } bool ratbag_assign_driver(struct ratbag_device *device, const struct input_id *dev_id, struct ratbag_test_device *test_device) { bool rc; const char *driver_name; if (!test_device) { driver_name = udev_prop_value(device->udev_device, "RATBAG_DRIVER"); } else { driver_name = "test_driver"; } if (driver_name) { rc = ratbag_test_driver(device, dev_id, driver_name, test_device); } else { rc = ratbag_driver_fallback_logitech(device, dev_id); } return rc; } static inline char* get_device_name(struct udev_device *device) { const char *prop; prop = udev_prop_value(device, "NAME"); if (!prop) return NULL; /* udev name is enclosed by " */ return strndup(&prop[1], strlen(prop) - 2); } static inline int get_product_id(struct udev_device *device, struct input_id *id) { const char *product; struct input_id ids; int rc; product = udev_prop_value(device, "PRODUCT"); if (!product) return -1; rc = sscanf(product, "%hx/%hx/%hx/%hx", &ids.bustype, &ids.vendor, &ids.product, &ids.version); if (rc != 4) return -1; *id = ids; return 0; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_device_new_from_udev_device(struct ratbag *ratbag, struct udev_device *udev_device, struct ratbag_device **device_out) { struct ratbag_device *device = NULL; enum ratbag_error_code error = RATBAG_ERROR_DEVICE; _cleanup_free_ char *name = NULL; struct input_id id; assert(ratbag != NULL); assert(udev_device != NULL); assert(device_out != NULL); if (udev_prop_value(udev_device, "ID_INPUT_MOUSE") == NULL) goto out_err; if (get_product_id(udev_device, &id) != 0) goto out_err; name = get_device_name(udev_device); if (!name) goto out_err; device = ratbag_device_new(ratbag, udev_device, name, &id); if (!device) goto out_err; if (!ratbag_assign_driver(device, &device->ids, NULL)) goto out_err; error = RATBAG_SUCCESS; out_err: if (error != RATBAG_SUCCESS) ratbag_device_destroy(device); else *device_out = device; return error_code(error); } LIBRATBAG_EXPORT struct ratbag_device * ratbag_device_ref(struct ratbag_device *device) { assert(device->refcount < INT_MAX); device->refcount++; return device; } LIBRATBAG_EXPORT struct ratbag_device * ratbag_device_unref(struct ratbag_device *device) { if (device == NULL) return NULL; assert(device->refcount > 0); device->refcount--; if (device->refcount == 0) ratbag_device_destroy(device); return NULL; } LIBRATBAG_EXPORT const char * ratbag_device_get_name(const struct ratbag_device* device) { return device->name; } LIBRATBAG_EXPORT const char * ratbag_device_get_svg_name(const struct ratbag_device* device) { return udev_prop_value(device->udev_device, "RATBAG_SVG"); } const char * ratbag_device_get_udev_property(const struct ratbag_device* device, const char *name) { return udev_prop_value(device->udev_device, name); } void ratbag_register_driver(struct ratbag *ratbag, struct ratbag_driver *driver) { if (!driver->name) { log_bug_libratbag(ratbag, "Driver is missing name\n"); return; } if (!driver->probe || !driver->remove) { log_bug_libratbag(ratbag, "Driver %s is incomplete.\n", driver->name); return; } list_insert(&ratbag->drivers, &driver->link); } LIBRATBAG_EXPORT struct ratbag * ratbag_create_context(const struct ratbag_interface *interface, void *userdata) { struct ratbag *ratbag; assert(interface != NULL); assert(interface->open_restricted != NULL); assert(interface->close_restricted != NULL); ratbag = zalloc(sizeof(*ratbag)); ratbag->refcount = 1; ratbag->interface = interface; ratbag->userdata = userdata; list_init(&ratbag->drivers); list_init(&ratbag->devices); ratbag->udev = udev_new(); if (!ratbag->udev) { free(ratbag); return NULL; } ratbag->log_handler = ratbag_default_log_func; ratbag->log_priority = RATBAG_LOG_PRIORITY_INFO; ratbag_register_driver(ratbag, &etekcity_driver); ratbag_register_driver(ratbag, &hidpp20_driver); ratbag_register_driver(ratbag, &hidpp10_driver); ratbag_register_driver(ratbag, &logitech_g300_driver); ratbag_register_driver(ratbag, &roccat_driver); ratbag_register_driver(ratbag, &gskill_driver); return ratbag; } LIBRATBAG_EXPORT struct ratbag * ratbag_ref(struct ratbag *ratbag) { ratbag->refcount++; return ratbag; } LIBRATBAG_EXPORT struct ratbag * ratbag_unref(struct ratbag *ratbag) { if (ratbag == NULL) return NULL; assert(ratbag->refcount > 0); ratbag->refcount--; if (ratbag->refcount == 0) { ratbag->udev = udev_unref(ratbag->udev); free(ratbag); } return NULL; } static struct ratbag_button * ratbag_create_button(struct ratbag_profile *profile, unsigned int index) { struct ratbag_device *device = profile->device; struct ratbag_button *button; button = zalloc(sizeof(*button)); button->refcount = 0; button->profile = profile; button->index = index; list_insert(&profile->buttons, &button->link); if (device->driver->read_button) device->driver->read_button(button); return button; } static struct ratbag_led * ratbag_create_led(struct ratbag_profile *profile, unsigned int index) { struct ratbag_device *device = profile->device; struct ratbag_led *led; led = zalloc(sizeof(*led)); led->refcount = 0; led->profile = profile; led->index = index; list_insert(&profile->leds, &led->link); if (device->driver->read_led) device->driver->read_led(led); return led; } static int ratbag_profile_init_buttons(struct ratbag_profile *profile, unsigned int count) { unsigned int i; for (i = 0; i < count; i++) ratbag_create_button(profile, i); profile->device->num_buttons = count; return 0; } static int ratbag_profile_init_leds(struct ratbag_profile *profile, unsigned int count) { unsigned int i; for (i = 0; i < count; i++) ratbag_create_led(profile, i); profile->device->num_leds = count; return 0; } static struct ratbag_profile * ratbag_create_profile(struct ratbag_device *device, unsigned int index, unsigned int num_resolutions, unsigned int num_buttons, unsigned int num_leds) { struct ratbag_profile *profile; unsigned i; profile = zalloc(sizeof(*profile)); profile->refcount = 0; profile->device = device; profile->index = index; profile->resolution.modes = zalloc(num_resolutions * sizeof(*profile->resolution.modes)); profile->resolution.num_modes = num_resolutions; profile->is_enabled = true; list_insert(&device->profiles, &profile->link); list_init(&profile->buttons); list_init(&profile->leds); for (i = 0; i < num_resolutions; i++) ratbag_resolution_init(profile, i, 0, 0, 0); assert(device->driver->read_profile); device->driver->read_profile(profile, index); ratbag_profile_init_buttons(profile, num_buttons); ratbag_profile_init_leds(profile, num_leds); return profile; } int ratbag_device_init_profiles(struct ratbag_device *device, unsigned int num_profiles, unsigned int num_resolutions, unsigned int num_buttons, unsigned int num_leds) { unsigned int i; for (i = 0; i < num_profiles; i++) { ratbag_create_profile(device, i, num_resolutions, num_buttons, num_leds); } device->num_profiles = num_profiles; if (num_profiles > 1) { ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE); /* having more than one profile means we can remap the buttons * at least */ ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_BUTTON_KEY); } if (num_resolutions > 1) ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_RESOLUTION); if (num_leds > 1) ratbag_device_set_capability(device, RATBAG_DEVICE_CAP_LED); return 0; } LIBRATBAG_EXPORT struct ratbag_profile * ratbag_profile_ref(struct ratbag_profile *profile) { assert(profile->refcount < INT_MAX); ratbag_device_ref(profile->device); profile->refcount++; return profile; } static void ratbag_profile_destroy(struct ratbag_profile *profile) { struct ratbag_button *button, *b_next; struct ratbag_led *led, *l_next; /* if we get to the point where the profile is destroyed, buttons, * resolutions , etc. are at a refcount of 0, so we can destroy * everything */ list_for_each_safe(button, b_next, &profile->buttons, link) ratbag_button_destroy(button); list_for_each_safe(led, l_next, &profile->leds, link) ratbag_led_destroy(led); free(profile->resolution.modes); list_remove(&profile->link); free(profile); } LIBRATBAG_EXPORT struct ratbag_profile * ratbag_profile_unref(struct ratbag_profile *profile) { if (profile == NULL) return NULL; assert(profile->refcount > 0); profile->refcount--; ratbag_device_unref(profile->device); return NULL; } LIBRATBAG_EXPORT struct ratbag_profile * ratbag_device_get_profile(struct ratbag_device *device, unsigned int index) { struct ratbag_profile *profile; if (index >= ratbag_device_get_num_profiles(device)) { log_bug_client(device->ratbag, "Requested invalid profile %d\n", index); return NULL; } list_for_each(profile, &device->profiles, link) { if (profile->index == index) return ratbag_profile_ref(profile); } log_bug_libratbag(device->ratbag, "Profile %d not found\n", index); return NULL; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_profile_set_enabled(struct ratbag_profile *profile, bool enabled) { if (!ratbag_device_has_capability(profile->device, RATBAG_DEVICE_CAP_DISABLE_PROFILE)) return RATBAG_ERROR_CAPABILITY; profile->is_enabled = enabled; profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT int ratbag_profile_is_active(struct ratbag_profile *profile) { return profile->is_active; } LIBRATBAG_EXPORT bool ratbag_profile_is_enabled(const struct ratbag_profile *profile) { return profile->is_enabled; } LIBRATBAG_EXPORT unsigned int ratbag_device_get_num_profiles(struct ratbag_device *device) { return device->num_profiles; } LIBRATBAG_EXPORT unsigned int ratbag_device_get_num_buttons(struct ratbag_device *device) { return device->num_buttons; } LIBRATBAG_EXPORT unsigned int ratbag_device_get_num_leds(struct ratbag_device *device) { return device->num_leds; } void ratbag_device_set_capability(struct ratbag_device *device, enum ratbag_device_capability cap) { device->capabilities |= (1UL << cap); } LIBRATBAG_EXPORT int ratbag_device_has_capability(const struct ratbag_device *device, enum ratbag_device_capability cap) { if (cap == RATBAG_DEVICE_CAP_NONE) abort(); return !!(device->capabilities & (1UL << cap)); } static inline enum ratbag_error_code write_led_helper(struct ratbag_device *device, struct ratbag_led *led) { return device->driver->write_led(led, led->mode, led->color, led->hz, led->brightness); } /* FIXME: This is a temporary fix for all of the drivers that have yet to be * converted to the new profile-oriented API. Once all of the drivers have been * converted, this code should be removed. */ static enum ratbag_error_code ratbag_old_write_profile(struct ratbag_device *device) { struct ratbag_profile *profile; struct ratbag_button *button; struct ratbag_led *led; struct ratbag_resolution *resolution; int rc; unsigned int i; assert(device->driver->write_profile); list_for_each(profile, &device->profiles, link) { if (!profile->dirty) continue; rc = device->driver->write_profile(profile); if (rc) return RATBAG_ERROR_DEVICE; if (device->driver->write_resolution_dpi) { for (i = 0; i < profile->resolution.num_modes; i++) { resolution = &profile->resolution.modes[i]; rc = device->driver->write_resolution_dpi( resolution, resolution->dpi_x, resolution->dpi_y); if (rc) return RATBAG_ERROR_DEVICE; } } if (device->driver->write_button) { list_for_each(button, &profile->buttons, link) { struct ratbag_button_action action = button->action; if (!button->dirty) continue; rc = device->driver->write_button(button, &action); if (rc) return RATBAG_ERROR_DEVICE; } } if (device->driver->write_led) { list_for_each(led, &profile->leds, link) { if (!led->dirty) continue; rc = write_led_helper(device, led); if (rc) return RATBAG_ERROR_DEVICE; } } } return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_device_commit(struct ratbag_device *device) { struct ratbag_profile *profile; struct ratbag_button *button; int rc; if (!device->driver->commit) { rc = ratbag_old_write_profile(device); if (rc != RATBAG_SUCCESS) return rc; } else { rc = device->driver->commit(device); if (rc) return RATBAG_ERROR_DEVICE; } list_for_each(profile, &device->profiles, link) { profile->dirty = false; list_for_each(button, &profile->buttons, link) button->dirty = false; } return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_profile_set_active(struct ratbag_profile *profile) { struct ratbag_device *device = profile->device; struct ratbag_profile *p; int rc; if (!ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE)) return RATBAG_ERROR_CAPABILITY; assert(device->driver->set_active_profile); rc = device->driver->set_active_profile(device, profile->index); if (rc) return RATBAG_ERROR_DEVICE; list_for_each(p, &device->profiles, link) p->is_active = false; profile->is_active = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT unsigned int ratbag_profile_get_num_resolutions(struct ratbag_profile *profile) { return profile->resolution.num_modes; } LIBRATBAG_EXPORT struct ratbag_resolution * ratbag_profile_get_resolution(struct ratbag_profile *profile, unsigned int idx) { struct ratbag_resolution *res; unsigned max = ratbag_profile_get_num_resolutions(profile); if (idx >= max) { log_bug_client(profile->device->ratbag, "Requested invalid resolution %d\n", idx); return NULL; } res = &profile->resolution.modes[idx]; return ratbag_resolution_ref(res); } LIBRATBAG_EXPORT struct ratbag_resolution * ratbag_resolution_ref(struct ratbag_resolution *resolution) { assert(resolution->refcount < INT_MAX); ratbag_profile_ref(resolution->profile); resolution->refcount++; return resolution; } LIBRATBAG_EXPORT struct ratbag_resolution * ratbag_resolution_unref(struct ratbag_resolution *resolution) { if (resolution == NULL) return NULL; assert(resolution->refcount > 0); resolution->refcount--; ratbag_profile_unref(resolution->profile); return NULL; } LIBRATBAG_EXPORT int ratbag_resolution_has_capability(struct ratbag_resolution *resolution, enum ratbag_resolution_capability cap) { switch (cap) { case RATBAG_RESOLUTION_CAP_INDIVIDUAL_REPORT_RATE: case RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION: break; default: return 0; } return !!(resolution->capabilities & (1 << cap)); } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_resolution_set_dpi(struct ratbag_resolution *resolution, unsigned int dpi) { struct ratbag_profile *profile = resolution->profile; if (resolution->dpi_x != dpi || resolution->dpi_y != dpi) { resolution->dpi_x = dpi; resolution->dpi_y = dpi; profile->dirty = true; } return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_resolution_set_dpi_xy(struct ratbag_resolution *resolution, unsigned int x, unsigned int y) { struct ratbag_profile *profile = resolution->profile; if (!ratbag_resolution_has_capability(resolution, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION)) return RATBAG_ERROR_CAPABILITY; if ((x == 0 && y != 0) || (x != 0 && y == 0)) return RATBAG_ERROR_VALUE; if (resolution->dpi_x != x || resolution->dpi_y != y) { resolution->dpi_x = x; resolution->dpi_y = y; profile->dirty = true; } return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_resolution_set_report_rate(struct ratbag_resolution *resolution, unsigned int hz) { if (ratbag_resolution_has_capability(resolution, RATBAG_RESOLUTION_CAP_INDIVIDUAL_REPORT_RATE)) { if (resolution->hz != hz) { resolution->hz = hz; resolution->profile->dirty = true; } } else { struct ratbag_profile *profile = resolution->profile; unsigned int i; /* No indvidual report rate per resolution. Loop through all of * them and update. */ for (i = 0; i < profile->resolution.num_modes; i++) { struct ratbag_resolution *res = &profile->resolution.modes[i]; if (res->hz != hz) { res->hz = hz; profile->dirty = true; } } } return RATBAG_SUCCESS; } LIBRATBAG_EXPORT int ratbag_resolution_get_dpi(struct ratbag_resolution *resolution) { return resolution->dpi_x; } LIBRATBAG_EXPORT int ratbag_resolution_get_dpi_x(struct ratbag_resolution *resolution) { return resolution->dpi_x; } LIBRATBAG_EXPORT int ratbag_resolution_get_dpi_y(struct ratbag_resolution *resolution) { return resolution->dpi_y; } LIBRATBAG_EXPORT int ratbag_resolution_get_report_rate(struct ratbag_resolution *resolution) { return resolution->hz; } LIBRATBAG_EXPORT int ratbag_resolution_is_active(const struct ratbag_resolution *resolution) { return resolution->is_active; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_resolution_set_active(struct ratbag_resolution *resolution) { resolution->is_active = true; /* FIXME: call into the driver */ return RATBAG_ERROR_IMPLEMENTATION; } LIBRATBAG_EXPORT int ratbag_resolution_is_default(const struct ratbag_resolution *resolution) { return resolution->is_default; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_resolution_set_default(struct ratbag_resolution *resolution) { struct ratbag_profile *profile = resolution->profile; unsigned int i; /* Unset the default on the other resolutions */ for (i = 0; i < profile->resolution.num_modes; i++) { struct ratbag_resolution *other = &profile->resolution.modes[i]; if (other == resolution || !other->is_default) continue; other->is_default = false; profile->dirty = true; } if (!resolution->is_default) { resolution->is_default = true; profile->dirty = true; } return RATBAG_SUCCESS; } LIBRATBAG_EXPORT struct ratbag_button* ratbag_profile_get_button(struct ratbag_profile *profile, unsigned int index) { struct ratbag_device *device = profile->device; struct ratbag_button *button; if (index >= ratbag_device_get_num_buttons(device)) { log_bug_client(device->ratbag, "Requested invalid button %d\n", index); return NULL; } list_for_each(button, &profile->buttons, link) { if (button->index == index) return ratbag_button_ref(button); } log_bug_libratbag(device->ratbag, "Button %d, profile %d not found\n", index, profile->index); return NULL; } LIBRATBAG_EXPORT enum ratbag_button_type ratbag_button_get_type(struct ratbag_button *button) { return button->type; } LIBRATBAG_EXPORT enum ratbag_button_action_type ratbag_button_get_action_type(struct ratbag_button *button) { return button->action.type; } LIBRATBAG_EXPORT int ratbag_button_has_action_type(struct ratbag_button *button, enum ratbag_button_action_type action_type) { switch (action_type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: case RATBAG_BUTTON_ACTION_TYPE_KEY: case RATBAG_BUTTON_ACTION_TYPE_MACRO: break; default: return 0; } return !!(button->action_caps & (1 << action_type)); } LIBRATBAG_EXPORT unsigned int ratbag_button_get_button(struct ratbag_button *button) { if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_BUTTON) return 0; return button->action.action.button; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_button_set_button(struct ratbag_button *button, unsigned int btn) { struct ratbag_button_action action = {0}; if (!ratbag_device_has_capability(button->profile->device, RATBAG_DEVICE_CAP_BUTTON_KEY)) return RATBAG_ERROR_CAPABILITY; action.type = RATBAG_BUTTON_ACTION_TYPE_BUTTON; action.action.button = btn; button->action = action; button->dirty = true; button->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_button_action_special ratbag_button_get_special(struct ratbag_button *button) { if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_SPECIAL) return RATBAG_BUTTON_ACTION_SPECIAL_INVALID; return button->action.action.special; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_button_set_special(struct ratbag_button *button, enum ratbag_button_action_special act) { struct ratbag_button_action action = {0}; /* FIXME: range checks */ if (!ratbag_device_has_capability(button->profile->device, RATBAG_DEVICE_CAP_BUTTON_KEY)) return RATBAG_ERROR_CAPABILITY; action.type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; action.action.special = act; button->action = action; button->dirty = true; button->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT unsigned int ratbag_button_get_key(struct ratbag_button *button, unsigned int *modifiers, size_t *sz) { if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_KEY) return 0; /* FIXME: modifiers */ *sz = 0; return button->action.action.key.key; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_button_set_key(struct ratbag_button *button, unsigned int key, unsigned int *modifiers, size_t sz) { struct ratbag_button_action action = {0}; /* FIXME: range checks */ if (!ratbag_device_has_capability(button->profile->device, RATBAG_DEVICE_CAP_BUTTON_KEY)) return RATBAG_ERROR_CAPABILITY; /* FIXME: modifiers */ action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; action.action.key.key = key; button->action = action; button->dirty = true; button->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_button_disable(struct ratbag_button *button) { struct ratbag_button_action action = {0}; if (!ratbag_device_has_capability(button->profile->device, RATBAG_DEVICE_CAP_BUTTON_KEY)) return RATBAG_ERROR_CAPABILITY; action.type = RATBAG_BUTTON_ACTION_TYPE_NONE; button->action = action; button->dirty = true; button->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT struct ratbag_button * ratbag_button_ref(struct ratbag_button *button) { assert(button->refcount < INT_MAX); ratbag_profile_ref(button->profile); button->refcount++; return button; } LIBRATBAG_EXPORT struct ratbag_led * ratbag_led_ref(struct ratbag_led *led) { assert(led->refcount < INT_MAX); ratbag_profile_ref(led->profile); led->refcount++; return led; } static void ratbag_button_destroy(struct ratbag_button *button) { list_remove(&button->link); if (button->action.macro) { if (button->action.macro->name) free(button->action.macro->name); free(button->action.macro); } free(button); } static void ratbag_led_destroy(struct ratbag_led *led) { list_remove(&led->link); free(led); } LIBRATBAG_EXPORT struct ratbag_button * ratbag_button_unref(struct ratbag_button *button) { if (button == NULL) return NULL; assert(button->refcount > 0); button->refcount--; ratbag_profile_unref(button->profile); return NULL; } LIBRATBAG_EXPORT struct ratbag_led * ratbag_led_unref(struct ratbag_led *led) { if (led == NULL) return NULL; assert(led->refcount > 0); led->refcount--; ratbag_profile_unref(led->profile); return NULL; } LIBRATBAG_EXPORT enum ratbag_led_mode ratbag_led_get_mode(struct ratbag_led *led) { return led->mode; } LIBRATBAG_EXPORT enum ratbag_led_type ratbag_led_get_type(struct ratbag_led *led) { return led->type; } LIBRATBAG_EXPORT struct ratbag_color ratbag_led_get_color(struct ratbag_led *led) { return led->color; } LIBRATBAG_EXPORT int ratbag_led_get_effect_rate(struct ratbag_led *led) { return led->hz; } LIBRATBAG_EXPORT unsigned int ratbag_led_get_brightness(struct ratbag_led *led) { return led->brightness; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_led_set_mode(struct ratbag_led *led, enum ratbag_led_mode mode) { if (!ratbag_device_has_capability(led->profile->device, RATBAG_DEVICE_CAP_LED)) return RATBAG_ERROR_CAPABILITY; led->mode = mode; led->dirty = true; led->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_led_set_color(struct ratbag_led *led, struct ratbag_color color) { if (!ratbag_device_has_capability(led->profile->device, RATBAG_DEVICE_CAP_LED)) return RATBAG_ERROR_CAPABILITY; led->color = color; led->dirty = true; led->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_led_set_effect_rate(struct ratbag_led *led, unsigned int hz) { if (!ratbag_device_has_capability(led->profile->device, RATBAG_DEVICE_CAP_LED)) return RATBAG_ERROR_CAPABILITY; led->hz = hz; led->dirty = true; led->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_led_set_brightness(struct ratbag_led *led, unsigned int brightness) { if (!ratbag_device_has_capability(led->profile->device, RATBAG_DEVICE_CAP_LED)) return RATBAG_ERROR_CAPABILITY; led->brightness = brightness; led->dirty = true; led->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT struct ratbag_led * ratbag_profile_get_led(struct ratbag_profile *profile, unsigned int index) { struct ratbag_device *device = profile->device; struct ratbag_led *led; if (index >= ratbag_device_get_num_leds(device)) { log_bug_client(device->ratbag, "Requested invalid led %d\n", index); return NULL; } list_for_each(led, &profile->leds, link) { if (led->index == index) return ratbag_led_ref(led); } log_bug_libratbag(device->ratbag, "Led %d, profile %d not found\n", index, profile->index); return NULL; } LIBRATBAG_EXPORT void ratbag_set_user_data(struct ratbag *ratbag, void *userdata) { ratbag->userdata = userdata; } LIBRATBAG_EXPORT void ratbag_device_set_user_data(struct ratbag_device *ratbag_device, void *userdata) { ratbag_device->userdata = userdata; } LIBRATBAG_EXPORT void ratbag_profile_set_user_data(struct ratbag_profile *ratbag_profile, void *userdata) { ratbag_profile->userdata = userdata; } LIBRATBAG_EXPORT void ratbag_button_set_user_data(struct ratbag_button *ratbag_button, void *userdata) { ratbag_button->userdata = userdata; } LIBRATBAG_EXPORT void ratbag_resolution_set_user_data(struct ratbag_resolution *ratbag_resolution, void *userdata) { ratbag_resolution->userdata = userdata; } LIBRATBAG_EXPORT void* ratbag_get_user_data(const struct ratbag *ratbag) { return ratbag->userdata; } LIBRATBAG_EXPORT void* ratbag_device_get_user_data(const struct ratbag_device *ratbag_device) { return ratbag_device->userdata; } LIBRATBAG_EXPORT void* ratbag_profile_get_user_data(const struct ratbag_profile *ratbag_profile) { return ratbag_profile->userdata; } LIBRATBAG_EXPORT void* ratbag_button_get_user_data(const struct ratbag_button *ratbag_button) { return ratbag_button->userdata; } LIBRATBAG_EXPORT void* ratbag_resolution_get_user_data(const struct ratbag_resolution *ratbag_resolution) { return ratbag_resolution->userdata; } LIBRATBAG_EXPORT struct ratbag_button_macro * ratbag_button_get_macro(struct ratbag_button *button) { struct ratbag_button_macro *macro; if (button->action.type != RATBAG_BUTTON_ACTION_TYPE_MACRO) return NULL; macro = ratbag_button_macro_new(button->action.macro->name); memcpy(macro->macro.events, button->action.macro->events, sizeof(macro->macro.events)); return macro; } void ratbag_button_copy_macro(struct ratbag_button *button, const struct ratbag_button_macro *macro) { if (!button->action.macro) button->action.macro = zalloc(sizeof(struct ratbag_macro)); else { if (button->action.macro->name) free(button->action.macro->name); memset(button->action.macro, 0, sizeof(struct ratbag_macro)); } button->action.type = RATBAG_BUTTON_ACTION_TYPE_MACRO; memcpy(button->action.macro->events, macro->macro.events, sizeof(macro->macro.events)); button->action.macro->name = strdup_safe(macro->macro.name); button->action.macro->group = strdup_safe(macro->macro.group); } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_button_set_macro(struct ratbag_button *button, const struct ratbag_button_macro *macro) { if (!ratbag_device_has_capability(button->profile->device, RATBAG_DEVICE_CAP_BUTTON_MACROS)) return RATBAG_ERROR_CAPABILITY; ratbag_button_copy_macro(button, macro); button->dirty = true; button->profile->dirty = true; return RATBAG_SUCCESS; } LIBRATBAG_EXPORT enum ratbag_error_code ratbag_button_macro_set_event(struct ratbag_button_macro *m, unsigned int index, enum ratbag_macro_event_type type, unsigned int data) { struct ratbag_macro *macro = &m->macro; if (index >= MAX_MACRO_EVENTS) return RATBAG_ERROR_VALUE; switch (type) { case RATBAG_MACRO_EVENT_KEY_PRESSED: case RATBAG_MACRO_EVENT_KEY_RELEASED: macro->events[index].type = type; macro->events[index].event.key = data; break; case RATBAG_MACRO_EVENT_WAIT: macro->events[index].type = type; macro->events[index].event.timeout = data; break; case RATBAG_MACRO_EVENT_NONE: macro->events[index].type = type; break; default: return RATBAG_ERROR_VALUE; } return 0; } LIBRATBAG_EXPORT enum ratbag_macro_event_type ratbag_button_macro_get_event_type(struct ratbag_button_macro *macro, unsigned int index) { if (index >= MAX_MACRO_EVENTS) return RATBAG_MACRO_EVENT_INVALID; return macro->macro.events[index].type; } LIBRATBAG_EXPORT int ratbag_button_macro_get_event_key(struct ratbag_button_macro *m, unsigned int index) { struct ratbag_macro *macro = &m->macro; if (index >= MAX_MACRO_EVENTS) return 0; if (macro->events[index].type != RATBAG_MACRO_EVENT_KEY_PRESSED && macro->events[index].type != RATBAG_MACRO_EVENT_KEY_RELEASED) return -EINVAL; return macro->events[index].event.key; } LIBRATBAG_EXPORT int ratbag_button_macro_get_event_timeout(struct ratbag_button_macro *m, unsigned int index) { struct ratbag_macro *macro = &m->macro; if (index >= MAX_MACRO_EVENTS) return 0; if (macro->events[index].type != RATBAG_MACRO_EVENT_WAIT) return 0; return macro->events[index].event.timeout; } LIBRATBAG_EXPORT unsigned int ratbag_button_macro_get_num_events(struct ratbag_button_macro *macro) { return MAX_MACRO_EVENTS; } LIBRATBAG_EXPORT const char * ratbag_button_macro_get_name(struct ratbag_button_macro *macro) { return macro->macro.name; } static void ratbag_button_macro_destroy(struct ratbag_button_macro *macro) { assert(macro->refcount == 0); free(macro->macro.name); free(macro->macro.group); free(macro); } LIBRATBAG_EXPORT struct ratbag_button_macro * ratbag_button_macro_ref(struct ratbag_button_macro *macro) { assert(macro->refcount < INT_MAX); macro->refcount++; return macro; } LIBRATBAG_EXPORT struct ratbag_button_macro * ratbag_button_macro_unref(struct ratbag_button_macro *macro) { if (macro == NULL) return NULL; assert(macro->refcount > 0); macro->refcount--; if (macro->refcount == 0) ratbag_button_macro_destroy(macro); return NULL; } LIBRATBAG_EXPORT struct ratbag_button_macro * ratbag_button_macro_new(const char *name) { struct ratbag_button_macro *macro; macro = zalloc(sizeof *macro); macro->refcount = 1; macro->macro.name = strdup_safe(name); return macro; } libratbag-0.9/src/libratbag.h000066400000000000000000001507631311552661500162210ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #ifdef __cplusplus extern "C" { #endif #include #include #include #include #define LIBRATBAG_ATTRIBUTE_PRINTF(_format, _args) \ __attribute__ ((format (printf, _format, _args))) #define LIBRATBAG_ATTRIBUTE_DEPRECATED __attribute__ ((deprecated)) /** * @defgroup base Initialization and manipulation of ratbag contexts * @defgroup device Querying and manipulating devices * * Device configuration is managed by "profiles" (see @ref profile). * In the simplest case, a device has a single profile that can be fetched, * queried and manipulated and then re-applied to the device. Other devices * may have multiple profiles, each of which can be queried and managed * independently. * * @defgroup profile Device profiles * * A profile on a device consists of a set of button functions and, where * applicable, a range of resolution settings, one of which is currently * active. * * @defgroup button Button configuration * * @defgroup led LED configuration * * @defgroup resolution Resolution and frequency mappings * * A device's sensor resolution and report rate can be configured per * profile, with each profile reporting a number of resolution modes (see * @ref ratbag_resolution). The number depends on the hardware, but at least * one is provided by libratbag. * * Each resolution mode is a tuple of a resolution and report rate and * represents the modes that the mouse can switch through, usually with the * use of a button on the mouse to cycle through the preconfigured * resolutions. * * The resolutions have a default resolution and a currently active * resolution. The currently active one is the one used by the device now * and only applies if the profile is currently active too. The default * resolution is the one the device will chose when the profile is selected * next. */ /** * @ingroup base * @struct ratbag * * A handle for accessing ratbag contexts. This struct is refcounted, use * ratbag_ref() and ratbag_unref(). */ struct ratbag; /** * @ingroup device * @struct ratbag_device * * A ratbag context represents one single device. This struct is * refcounted, use ratbag_device_ref() and ratbag_device_unref(). */ struct ratbag_device; /** * @ingroup profile * @struct ratbag_profile * * A handle to a profile context on devices with the @ref * RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE capability. * This struct is refcounted, use ratbag_profile_ref() and * ratbag_profile_unref(). */ struct ratbag_profile; /** * @ingroup button * @struct ratbag_button * * Represents a button on the device. * * This struct is refcounted, use ratbag_button_ref() and * ratbag_button_unref(). */ struct ratbag_button; /** * @ingroup resolution * @struct ratbag_resolution * * Represents a resolution setting on the device. Most devices have multiple * resolutions per profile, one of which is active at a time. * * This struct is refcounted, use ratbag_resolution_ref() and * ratbag_resolution_unref(). */ struct ratbag_resolution; /** * @ingroup led * @struct ratbag_color * * Represents LED color in RGB format. * each color component is integer 0 - 255 */ struct ratbag_color { unsigned int red; unsigned int green; unsigned int blue; }; /** * @ingroup led * @struct ratbag_led * * Represents a led on the device. */ struct ratbag_led; /** * @ingroup button * @struct ratbag_macro * * Represents a macro that can be assigned to a button. * * This struct is refcounted, use ratbag_button_macro_ref() and * ratbag_button_macro_unref(). */ struct ratbag_button_macro; /** * @ingroup base * * Error codes used by libratbag. */ enum ratbag_error_code { RATBAG_SUCCESS = 0, /** * An error occured on the device. Either the device is not a * libratbag device or communication with the device failed. */ RATBAG_ERROR_DEVICE = -1000, /** * Insufficient capabilities. This error occurs when a requested change is * beyond the device's capabilities. */ RATBAG_ERROR_CAPABILITY = -1001, /** * Invalid value or value range. The provided value or value range * is outside of the legal or supported range. */ RATBAG_ERROR_VALUE = -1002, /** * A low-level system error has occured, e.g. a failure to access * files that should be there. This error is usually unrecoverable * and libratbag will print a log message with details about the * error. */ RATBAG_ERROR_SYSTEM = -1003, /** * Implementation bug, either in libratbag or in the caller. This * error is usually unrecoverable and libratbag will print a log * message with details about the * error. */ RATBAG_ERROR_IMPLEMENTATION = -1004, }; /** * @ingroup base * * Log priority for internal logging messages. */ enum ratbag_log_priority { /** * Raw protocol messages. Using this log level results in *a lot* of * output. */ RATBAG_LOG_PRIORITY_RAW = 10, RATBAG_LOG_PRIORITY_DEBUG = 20, RATBAG_LOG_PRIORITY_INFO = 30, RATBAG_LOG_PRIORITY_ERROR = 40, }; /** * @ingroup base * * Log handler type for custom logging. * * @param ratbag The ratbag context * @param priority The priority of the current message * @param format Message format in printf-style * @param args Message arguments * * @see ratbag_log_set_priority * @see ratbag_log_get_priority * @see ratbag_log_set_handler */ typedef void (*ratbag_log_handler)(struct ratbag *ratbag, enum ratbag_log_priority priority, const char *format, va_list args) LIBRATBAG_ATTRIBUTE_PRINTF(3, 0); /** * @ingroup base * * Set the log priority for the ratbag context. Messages with priorities * equal to or higher than the argument will be printed to the context's * log handler. * * The default log priority is @ref RATBAG_LOG_PRIORITY_ERROR. * * @param ratbag A previously initialized ratbag context * @param priority The minimum priority of log messages to print. * * @see ratbag_log_set_handler * @see ratbag_log_get_priority */ void ratbag_log_set_priority(struct ratbag *ratbag, enum ratbag_log_priority priority); /** * @ingroup base * * Get the context's log priority. Messages with priorities equal to or * higher than the argument will be printed to the current log handler. * * The default log priority is @ref RATBAG_LOG_PRIORITY_ERROR. * * @param ratbag A previously initialized ratbag context * @return The minimum priority of log messages to print. * * @see ratbag_log_set_handler * @see ratbag_log_set_priority */ enum ratbag_log_priority ratbag_log_get_priority(const struct ratbag *ratbag); /** * @ingroup base * * Set the context's log handler. Messages with priorities equal to or * higher than the context's log priority will be passed to the given * log handler. * * The default log handler prints to stderr. * * @param ratbag A previously initialized ratbag context * @param log_handler The log handler for library messages. * * @see ratbag_log_set_priority * @see ratbag_log_get_priority */ void ratbag_log_set_handler(struct ratbag *ratbag, ratbag_log_handler log_handler); /** * @ingroup base * @struct ratbag_interface * * libratbag does not open file descriptors to devices directly, instead * open_restricted() and close_restricted() are called for each path that * must be opened. * * @see ratbag_create_context */ struct ratbag_interface { /** * Open the device at the given path with the flags provided and * return the fd. * * @param path The device path to open * @param flags Flags as defined by open(2) * @param user_data The user_data provided in * ratbag_create_context() * * @return The file descriptor, or a negative errno on failure. */ int (*open_restricted)(const char *path, int flags, void *user_data); /** * Close the file descriptor. * * @param fd The file descriptor to close * @param user_data The user_data provided in * ratbag_create_context() */ void (*close_restricted)(int fd, void *user_data); }; /** * @ingroup base * * Create a new ratbag context. * * The context is refcounted with an initial value of at least 1. * Use ratbag_unref() to release the context. * * @return An initialized ratbag context or NULL on error */ struct ratbag * ratbag_create_context(const struct ratbag_interface *interface, void *userdata); /** * @ingroup base * * Set caller-specific data associated with this context. libratbag does * not manage, look at, or modify this data. The caller must ensure the * data is valid. * * Setting userdata overrides the one provided to ratbag_create_context(). * * @param ratbag A previously initialized ratbag context * @param userdata Caller-specific data passed to the various callback * interfaces. */ void ratbag_set_user_data(struct ratbag *ratbag, void *userdata); /** * @ingroup base * * Get the caller-specific data associated with this context, if any. * * @param ratbag A previously initialized ratbag context * @return The caller-specific data previously assigned in * ratbag_create_context (or ratbag_set_user_data()). */ void* ratbag_get_user_data(const struct ratbag *ratbag); /** * @ingroup base * * Add a reference to the context. A context is destroyed whenever the * reference count reaches 0. See @ref ratbag_unref. * * @param ratbag A previously initialized valid ratbag context * @return The passed ratbag context */ struct ratbag * ratbag_ref(struct ratbag *ratbag); /** * @ingroup base * * Dereference the ratbag context. After this, the context may have been * destroyed, if the last reference was dereferenced. If so, the context is * invalid and may not be interacted with. * * @param ratbag A previously initialized ratbag context * @return Always NULL */ struct ratbag * ratbag_unref(struct ratbag *ratbag); /** * @ingroup base * * Create a new ratbag context from the given udev device. * * The device is refcounted with an initial value of at least 1. * Use ratbag_device_unref() to release the device. * * @param ratbag A previously initialized ratbag context * @param udev_device The udev device that points at the device * @param device Set to a new device based on the udev device. * * @return 0 on success or the error. * @retval RATBAG_ERROR_DEVICE The given device does not exist or is not * supported by libratbag. */ enum ratbag_error_code ratbag_device_new_from_udev_device(struct ratbag *ratbag, struct udev_device *udev_device, struct ratbag_device **device); /** * @ingroup device * * Add a reference to the device. A device is destroyed whenever the * reference count reaches 0. See @ref ratbag_device_unref. * * @param device A previously initialized valid ratbag device * @return The passed ratbag device */ struct ratbag_device * ratbag_device_ref(struct ratbag_device *device); /** * @ingroup device * * Dereference the ratbag device. When the internal refcount reaches * zero, all resources associated with this object are released. The object * must be considered invalid once unref is called. * * @param device A previously initialized ratbag device * @return Always NULL */ struct ratbag_device * ratbag_device_unref(struct ratbag_device *device); /** * @ingroup device * * Set caller-specific data associated with this device. libratbag does * not manage, look at, or modify this data. The caller must ensure the * data is valid. * * @param device A previously initialized device * @param userdata Caller-specific data passed to the various callback * interfaces. */ void ratbag_device_set_user_data(struct ratbag_device *device, void *userdata); /** * @ingroup device * * Get the caller-specific data associated with this device, if any. * * @param device A previously initialized ratbag device * @return The caller-specific data previously assigned in * ratbag_device_set_user_data(). */ void* ratbag_device_get_user_data(const struct ratbag_device *device); /** * @ingroup device * * @param device A previously initialized ratbag device * @return The name of the device associated with the given ratbag. */ const char * ratbag_device_get_name(const struct ratbag_device* device); /** * @ingroup device * * @param device A previously initialized ratbag device * @return The file name of the svg drawing associated with the given ratbag. * If there is no file associated to the device, NULL is returned. */ const char * ratbag_device_get_svg_name(const struct ratbag_device* device); /** * @ingroup device */ enum ratbag_device_capability { RATBAG_DEVICE_CAP_NONE = 0, /** * The device can change resolution, either software-controlled or * by a hardware button. * * FIXME: what about devices that only have hw buttons? can we * query that, even if we can't switch it ourselves? Maybe better to * have a separate cap for that then. */ RATBAG_DEVICE_CAP_SWITCHABLE_RESOLUTION, /** * The device can switch between hardware profiles. * A device with this capability can store multiple profiles in the * hardware and provides the ability to switch between the profiles, * possibly with a button. * Devices without this capability will only have a single profile. */ RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE, /** * The device supports assigning button numbers, key events or key + * modifier combinations. */ RATBAG_DEVICE_CAP_BUTTON_KEY, /** * The device supports assigning LED colors and effects */ RATBAG_DEVICE_CAP_LED, /** * The device supports user-defined key or button sequences. */ RATBAG_DEVICE_CAP_BUTTON_MACROS, /** * The device can have one profile assigned as a default profile. * A default profile is the profile that is selected when the device * is plugged in. Devices without this capability may select the * last-used profile or a specific profile (usually the first). */ RATBAG_DEVICE_CAP_DEFAULT_PROFILE, /** * The device has the capability to query the current hardware * configuration. If this capability is missing, libratbag cannot * query the device for its current configuration and the * configured resolutions and button mappings are unknown. * libratbag will still provide information about the structure of * the device such as the number of buttons and resolutions. * Clients that encounter a device without this resolution are * encouraged to upload a configuration stored on-disk to the * device to reset the device to a known state. * * Any changes uploaded to the device will be cached in libratbag, * once a client has sent a full configuration to the device * libratbag can be used to query the device as normal. */ RATBAG_DEVICE_CAP_QUERY_CONFIGURATION, /** * The device has the capability to disable and enable profiles. While * profiles are not immediately deleted after being disabled, it is not * guaranteed that the device will remember any disabled profiles the * next time ratbag runs. Furthermore, the order of profiles may get * changed the next time ratbag runs if profiles are disabled. */ RATBAG_DEVICE_CAP_DISABLE_PROFILE, }; /** * @ingroup device * * Note that a device may not support any of the capabilities but still * initialize fine otherwise. This is the case for devices that have no * configurable options set, or for devices that have some configuration * options but none that are currently exposed by libratbag. A client is * expected to handle this situation. * * @retval 1 The device has the capability * @retval 0 The device does not have the capability */ int ratbag_device_has_capability(const struct ratbag_device *device, enum ratbag_device_capability cap); /** * @ingroup device * * Write any changes to the device. Depending on the device, this may take * a couple of seconds. * * @param device A previously initialized ratbag device * @return 0 on success or an error code otherwise */ enum ratbag_error_code ratbag_device_commit(struct ratbag_device *device); /** * @ingroup device * * Return the number of profiles supported by this device. * * Note that the number of profiles available may be different to the number * of profiles currently active. This function returns the maximum number of * profiles available and is static for the lifetime of the device. * * A device that does not support profiles in hardware provides a single * profile that reflects the current settings of the device. * * @param device A previously initialized ratbag device * @return The number of profiles available on this device. */ unsigned int ratbag_device_get_num_profiles(struct ratbag_device *device); /** * @ingroup device * * Return the number of buttons available on this device. * * @param device A previously initialized ratbag device * @return The number of buttons available on this device. */ unsigned int ratbag_device_get_num_buttons(struct ratbag_device *device); /** * @ingroup device * * Return the number of LEDs available on this device. * * @param device A previously initialized ratbag device * @return The number of LEDs available on this device. */ unsigned int ratbag_device_get_num_leds(struct ratbag_device *device); /** * @ingroup profile * * Add a reference to the profile. A profile is destroyed whenever the * reference count reaches 0. See @ref ratbag_profile_unref. * * @param profile A previously initialized valid ratbag profile * @return The passed ratbag profile */ struct ratbag_profile * ratbag_profile_ref(struct ratbag_profile *profile); /** * @ingroup profile * * Dereference the ratbag profile. When the internal refcount reaches * zero, all resources associated with this object are released. The object * must be considered invalid once unref is called. * * @param profile A previously initialized ratbag profile * @return Always NULL */ struct ratbag_profile * ratbag_profile_unref(struct ratbag_profile *profile); /** * @ingroup profile * * Enable/disable the ratbag profile. For this to work, the device must support * @ref RATBAG_DEVICE_CAP_DISABLE_PROFILE. * * @param profile A previously initialized ratbag profile * @param enabled Whether to enable or disable the profile * * @return 0 on success or an error code otherwise */ enum ratbag_error_code ratbag_profile_set_enabled(struct ratbag_profile *profile, bool enabled); /** * @ingroup profile * * Check whether the ratbag profile is enabled or not. For devices that don't * support @ref RATBAG_DEVICE_CAP_DISABLE_PROFILE the profile will always be * set to enabled. * * @param profile A previously initialized ratbag profile * * @return Whether the profile is enabled or not. */ bool ratbag_profile_is_enabled(const struct ratbag_profile *profile); /** * @ingroup profile * * Set caller-specific data associated with this profile. libratbag does * not manage, look at, or modify this data. The caller must ensure the * data is valid. * * @param profile A previously initialized profile * @param userdata Caller-specific data passed to the various callback * interfaces. */ void ratbag_profile_set_user_data(struct ratbag_profile *profile, void *userdata); /** * @ingroup profile * * Get the caller-specific data associated with this profile, if any. * * @param profile A previously initialized ratbag profile * @return The caller-specific data previously assigned in * ratbag_profile_set_user_data(). */ void* ratbag_profile_get_user_data(const struct ratbag_profile *profile); /** * @ingroup profile * * This function creates if necessary and returns a profile for the given * index. The index must be less than the number returned by * ratbag_get_num_profiles(). * * The profile is refcounted with an initial value of at least 1. * Use ratbag_profile_unref() to release the profile. * * @param device A previously initialized ratbag device * @param index The index of the profile * * @return The profile at the given index, or NULL if the profile does not * exist. * * @see ratbag_get_num_profiles */ struct ratbag_profile * ratbag_device_get_profile(struct ratbag_device *device, unsigned int index); /** * @ingroup profile * * Check if the given profile is the currently active one. Note that some * devices allow switching profiles with hardware buttons thus making the * use of this function racy. * * @param profile A previously initialized ratbag profile * * @return non-zero if the profile is currently active, zero otherwise */ int ratbag_profile_is_active(struct ratbag_profile *profile); /** * @ingroup profile * * Make the given profile the currently active profile * * @param profile The profile to make the active profile. * * @return 0 on success or an error code otherwise */ enum ratbag_error_code ratbag_profile_set_active(struct ratbag_profile *profile); /** * @ingroup profile * * Get the number of @ref ratbag_resolution available in this profile. A * resolution mode is a tuple of (resolution, report rate), each mode can be * fetched with ratbag_profile_get_resolution_by_idx(). * * The returned value is the maximum number of modes available and thus * identical for all profiles. However, some of the modes may not be * configured. * * @param profile A previously initialized ratbag profile * * @return The number of resolutions available. */ unsigned int ratbag_profile_get_num_resolutions(struct ratbag_profile *profile); /** * @ingroup profile * * Return the resolution in DPI and the report rate in Hz for the resolution * mode identified by the given index. The index must be between 0 and * ratbag_profile_get_num_resolution_modes(). * * See ratbag_profile_get_num_resolution_modes() for a description of * resolution_modes. * * Profiles available but not currently configured on the device return * success but set dpi and hz to 0. * * The returned struct has a refcount of at least 1, use * ratbag_resolution_unref() to release the resources associated. * * @param profile A previously initialized ratbag profile * @param idx The index of the resolution mode to get * * @return zero on success, non-zero otherwise. On error, dpi and hz are * unmodified. */ struct ratbag_resolution * ratbag_profile_get_resolution(struct ratbag_profile *profile, unsigned int idx); /** * @ingroup resolution * * Add a reference to the resolution. A resolution is destroyed whenever the * reference count reaches 0. See @ref ratbag_resolution_unref. * * @param resolution A previously initialized valid ratbag resolution * @return The passed ratbag resolution */ struct ratbag_resolution * ratbag_resolution_ref(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Dereference the ratbag resolution. When the internal refcount reaches * zero, all resources associated with this object are released. The object * must be considered invalid once unref is called. * * @param resolution A previously initialized ratbag resolution * @return Always NULL */ struct ratbag_resolution * ratbag_resolution_unref(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Set caller-specific data associated with this resolution. libratbag does * not manage, look at, or modify this data. The caller must ensure the * data is valid. * * @param resolution A previously initialized resolution * @param userdata Caller-specific data passed to the various callback * interfaces. */ void ratbag_resolution_set_user_data(struct ratbag_resolution *resolution, void *userdata); /** * @ingroup resolution * * Get the caller-specific data associated with this resolution, if any. * * @param resolution A previously initialized ratbag resolution * @return The caller-specific data previously assigned in * ratbag_resolution_set_user_data(). */ void* ratbag_resolution_get_user_data(const struct ratbag_resolution *resolution); enum ratbag_resolution_capability { /** * The report rate can be set per resolution mode. If this property * is not available, all resolutions within the same profile have * the same report rate and changing one changes the others. */ RATBAG_RESOLUTION_CAP_INDIVIDUAL_REPORT_RATE = 1, /** * The resolution can be set for x and y separately. */ RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION, }; /** * @ingroup resolution * * Check if a resolution has a specific capability. * * @return non-zero if the capability is available, zero otherwise. */ int ratbag_resolution_has_capability(struct ratbag_resolution *resolution, enum ratbag_resolution_capability cap); /** * @ingroup resolution * * Set the resolution in DPI for the resolution mode. * If the resolution has the @ref * RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION capability, this function * sets both x and y resolution to the given value. * * A value of 0 for dpi disables the mode. * * If the resolution mode is the currently active mode and the profile is * the currently active profile, the change takes effect immediately. * * @param resolution A previously initialized ratbag resolution * @param dpi Set to the resolution in dpi, 0 to disable * * @return zero on success or an error code on failure */ enum ratbag_error_code ratbag_resolution_set_dpi(struct ratbag_resolution *resolution, unsigned int dpi); /** * @ingroup resolution * * Set the x and y resolution in DPI for the resolution mode. * If the resolution does not have the @ref * RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION capability, this function * returns an error and does nothing. * * A value of 0 for both x and y disables the mode. If either value is 0 and * the other value is non-zero, this function returns an error and does * nothing. * * If the resolution mode is the currently active mode and the profile is * the currently active profile, the change takes effect immediately. * * @param resolution A previously initialized ratbag resolution with the * @ref RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION capability * @param x The x resolution in dpi * @param y The y resolution in dpi * * @return zero on success or an error code on failure */ enum ratbag_error_code ratbag_resolution_set_dpi_xy(struct ratbag_resolution *resolution, unsigned int x, unsigned int y); /** * @ingroup resolution * * Get the resolution in DPI for the resolution mode. * If the resolution has the @ref * RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION capability, this function * returns the x resolution, see ratbag_resolution_get_dpi_x(). * * A value of 0 for dpi indicates the mode is disabled. * * @param resolution A previously initialized ratbag resolution * * @return The resolution in dpi */ int ratbag_resolution_get_dpi(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Get the x resolution in DPI for the resolution mode. If the resolution * does not have the @ref RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION * capability, this function is identical to ratbag_resolution_get_dpi(). * * A value of 0 for dpi indicates the mode is disabled. * * @param resolution A previously initialized ratbag resolution with the * @ref RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION capability * * @return The resolution in dpi */ int ratbag_resolution_get_dpi_x(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Get the y resolution in DPI for the resolution mode. If the resolution * does not have the @ref RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION * capability, this function is identical to ratbag_resolution_get_dpi(). * * A value of 0 for dpi indicates the mode is disabled. * * @param resolution A previously initialized ratbag resolution with the * @ref RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION capability * * @return The resolution in dpi */ int ratbag_resolution_get_dpi_y(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Set the report rate in Hz for the resolution mode. * * A value of 0 hz disables the mode. * * If the resolution mode is the currently active mode and the profile is * the currently active profile, the change takes effect immediately. * * If the resolution does not have the @ref * RATBAG_RESOLUTION_CAP_INDIVIDUAL_REPORT_RATE capability, changing the * report rate on one resolution changes the report rate for all resolutions * in this profile. * * @param resolution A previously initialized ratbag resolution * @param hz Set to the report rate in Hz, may be 0 * * @return zero on success or an error code on failure */ enum ratbag_error_code ratbag_resolution_set_report_rate(struct ratbag_resolution *resolution, unsigned int hz); /** * @ingroup resolution * * Get the report rate in Hz for the resolution mode. * * A value of 0 hz indicates the mode is disabled. * * @param resolution A previously initialized ratbag resolution * * @return The report rate for this resolution in Hz */ int ratbag_resolution_get_report_rate(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Activate the given resolution mode. If the mode is not configured, this * function returns an error and the result is undefined. * * The mode must be one of the current profile, otherwise an error is * returned. * * @param resolution A previously initialized ratbag resolution * * @return zero on success or an error code on failure */ enum ratbag_error_code ratbag_resolution_set_active(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Check if the resolution mode is the currently active one. * * If the profile is the currently active profile, the mode is the one * currently active. For profiles not currently active, this always returns 0. * * @param resolution A previously initialized ratbag resolution * * @return Non-zero if the resolution mode is the active one, zero * otherwise. */ int ratbag_resolution_is_active(const struct ratbag_resolution *resolution); /** * @ingroup resolution * * Set the default resolution mode for the associated profile. When the * device switches to the profile next, this mode will be the active * resolution. If the mode is not configured, this function returns an error * and the result is undefined. * * This only switches the default resolution, not the currently active * resolution. Use ratbag_resolution_set_active() instead. * * @param resolution A previously initialized ratbag resolution * * @return zero on success or an error code on failure */ enum ratbag_error_code ratbag_resolution_set_default(struct ratbag_resolution *resolution); /** * @ingroup resolution * * Check if the resolution mode is the default one in this profile. * * The default resolution is the one the device selects when switching to * the corresponding profile. It may not be the currently active resolution, * use ratbag_resolution_is_active() instead. * * @param resolution A previously initialized ratbag resolution * * @return Non-zero if the resolution mode is the default one, zero * otherwise. */ int ratbag_resolution_is_default(const struct ratbag_resolution *resolution); /** * @ingroup profile * * Return a reference to the button given by the index. The order of the * buttons is device-specific though indices 0, 1 and 2 should always refer * to left, middle, right buttons. Use ratbag_button_get_type() to get the * physical type of the button. * * The button is refcounted with an initial value of at least 1. * Use ratbag_button_unref() to release the button. * * @param profile A previously initialized ratbag profile * @param index The index of the button * * @return A button context, or NULL if the button does not exist. * * @see ratbag_device_get_num_buttons */ struct ratbag_button* ratbag_profile_get_button(struct ratbag_profile *profile, unsigned int index); /** * @ingroup button * * Set caller-specific data associated with this button. libratbag does * not manage, look at, or modify this data. The caller must ensure the * data is valid. * * @param button A previously initialized button * @param userdata Caller-specific data passed to the various callback * interfaces. */ void ratbag_button_set_user_data(struct ratbag_button *button, void *userdata); /** * @ingroup button * * Get the caller-specific data associated with this button, if any. * * @param button A previously initialized ratbag button * @return The caller-specific data previously assigned in * ratbag_button_set_user_data(). */ void* ratbag_button_get_user_data(const struct ratbag_button *button); /** * @ingroup button * * Button types describing the physical button. */ enum ratbag_button_type { RATBAG_BUTTON_TYPE_UNKNOWN = 0, /* mouse buttons */ RATBAG_BUTTON_TYPE_LEFT, RATBAG_BUTTON_TYPE_MIDDLE, RATBAG_BUTTON_TYPE_RIGHT, RATBAG_BUTTON_TYPE_THUMB, RATBAG_BUTTON_TYPE_THUMB2, RATBAG_BUTTON_TYPE_THUMB3, RATBAG_BUTTON_TYPE_THUMB4, RATBAG_BUTTON_TYPE_WHEEL_LEFT, RATBAG_BUTTON_TYPE_WHEEL_RIGHT, RATBAG_BUTTON_TYPE_WHEEL_CLICK, RATBAG_BUTTON_TYPE_WHEEL_UP, RATBAG_BUTTON_TYPE_WHEEL_DOWN, /** * A button to toggle the wheel from free-spinning to click-based. */ RATBAG_BUTTON_TYPE_WHEEL_RATCHET_MODE_SHIFT, RATBAG_BUTTON_TYPE_EXTRA, RATBAG_BUTTON_TYPE_SIDE, RATBAG_BUTTON_TYPE_PINKIE, RATBAG_BUTTON_TYPE_PINKIE2, /* DPI switch */ RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP, RATBAG_BUTTON_TYPE_RESOLUTION_UP, RATBAG_BUTTON_TYPE_RESOLUTION_DOWN, /* Profile */ RATBAG_BUTTON_TYPE_PROFILE_CYCLE_UP, RATBAG_BUTTON_TYPE_PROFILE_UP, RATBAG_BUTTON_TYPE_PROFILE_DOWN, }; /** * @ingroup button * * Return the type of the physical button. This function is intended to be * used by configuration tools to provide a generic list of button names or * handles to configure devices. The type describes the physical location of * the button and remains constant for the lifetime of the device. * For example, a button of type @ref RATBAG_BUTTON_TYPE_WHEEL_CLICK may be * mapped to a logical middle button, but the physical description is that * of a wheel click. * * For the button currently mapped to this physical button, see * ratbag_button_get_button() * * @return The type of the button */ enum ratbag_button_type ratbag_button_get_type(struct ratbag_button *button); /** * @ingroup button * * The type assigned to a button. */ enum ratbag_button_action_type { /** * Button action is unknown */ RATBAG_BUTTON_ACTION_TYPE_UNKNOWN = -1, /** * Button is disabled */ RATBAG_BUTTON_ACTION_TYPE_NONE = 0, /** * Button sends numeric button events */ RATBAG_BUTTON_ACTION_TYPE_BUTTON, /** * Button triggers a mouse-specific special function. This includes * resolution changes and profile changes. */ RATBAG_BUTTON_ACTION_TYPE_SPECIAL, /** * Button sends a key or key + modifier combination */ RATBAG_BUTTON_ACTION_TYPE_KEY, /** * Button sends a user-defined key or button sequence */ RATBAG_BUTTON_ACTION_TYPE_MACRO, }; /** * @ingroup button * * @return The type of the action currently configured for this button */ enum ratbag_button_action_type ratbag_button_get_action_type(struct ratbag_button *button); /** * @ingroup button * * Check if a button supports a specific action type. Not all devices allow * all buttons to be assigned any action. Ability to change a button to a * given action type does not guarantee that any specific action can be * configured. * * @note It is a client bug to pass in @ref * RATBAG_BUTTON_ACTION_TYPE_UNKNOWN or @ref * RATBAG_BUTTON_ACTION_TYPE_NONE. * * @param button A previously initialized button * @param action_type An action type * * @return non-zero if the action type is supported, zero otherwise. */ int ratbag_button_has_action_type(struct ratbag_button *button, enum ratbag_button_action_type action_type); /** * @ingroup button */ enum ratbag_button_action_special { /** * This button is not set up for a special action */ RATBAG_BUTTON_ACTION_SPECIAL_INVALID = -1, RATBAG_BUTTON_ACTION_SPECIAL_UNKNOWN = (1 << 30), RATBAG_BUTTON_ACTION_SPECIAL_DOUBLECLICK, /* Wheel mappings */ RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT, RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT, RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP, RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN, RATBAG_BUTTON_ACTION_SPECIAL_RATCHET_MODE_SWITCH, /* DPI switch */ RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP, RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_DOWN, RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP, RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN, RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE, RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT, /* Profile */ RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP, RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_DOWN, RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP, RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN, /* second mode for buttons */ RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE, /* battery level */ RATBAG_BUTTON_ACTION_SPECIAL_BATTERY_LEVEL, }; /** * @ingroup button * * If a button's action is @ref RATBAG_BUTTON_ACTION_TYPE_BUTTON, * this function returns the logical button number this button is mapped to, * starting at 1. The button numbers are in sequence and do not correspond * to any meaning other than its numeric value. It is up to the input stack * how to map that logical button number, but usually buttons 1, 2 and 3 are * mapped into left, middle, right. * * If the button's action type is not @ref RATBAG_BUTTON_ACTION_TYPE_BUTTON, * this function returns 0. * * @return The logical button number this button sends. * @retval 0 This button is disabled or its action type is not @ref * RATBAG_BUTTON_ACTION_TYPE_BUTTON. * * @see ratbag_button_set_button */ unsigned int ratbag_button_get_button(struct ratbag_button *button); /** * @ingroup button * * See ratbag_button_get_button() for a description of the button number. * * @param button A previously initialized ratbag button * @param btn The logical button number to assign to this button. * @return 0 on success or an error code otherwise. On success, the button's * action is set to @ref RATBAG_BUTTON_ACTION_TYPE_BUTTON. * * @see ratbag_button_get_button */ enum ratbag_error_code ratbag_button_set_button(struct ratbag_button *button, unsigned int btn); /** * @ingroup button * * If a button's action is @ref RATBAG_BUTTON_ACTION_TYPE_SPECIAL, * this function returns the special function assigned to this button. * * If the button's action type is not @ref RATBAG_BUTTON_ACTION_TYPE_SPECIAL, * this function returns @ref RATBAG_BUTTON_ACTION_SPECIAL_INVALID. * * @return The special function assigned to this button * * @see ratbag_button_set_button */ enum ratbag_button_action_special ratbag_button_get_special(struct ratbag_button *button); /** * @ingroup led * * RATBAG_LED_OFF - led is now off, * RATBAG_LED_ON - led is on with static color, * RATBAG_LED_CYCLE - led is cycling between all colors. * RATBAG_LED_BREATHING - led is pulsating with static color * * Each LED mode has different properties, e.g. the brightness and rate are only * available in modes @ref RATBAG_LED_CYCLE and @ref RATBAG_LED_BREATHING modes */ enum ratbag_led_mode { RATBAG_LED_OFF = 0, RATBAG_LED_ON, RATBAG_LED_CYCLE, RATBAG_LED_BREATHING, }; /** * @ingroup led * * LED types, usually based on their physical location */ enum ratbag_led_type { RATBAG_LED_TYPE_UNKNOWN = -1, RATBAG_LED_TYPE_LOGO = 0, RATBAG_LED_TYPE_SIDE }; /** * @ingroup led * * Return a reference to the LED given by the index. The order of the * LEDs is device-specific though. * * The LED is refcounted with an initial value of at least 1. * Use ratbag_led_unref() to release the LED. * * @param profile A previously initialized ratbag profile * @param index The index of the LED * * @return A LED context, or NULL if the LED does not exist. * * @see ratbag_device_get_profile */ struct ratbag_led * ratbag_profile_get_led(struct ratbag_profile *profile, unsigned int index); /** * @ingroup led * * This function returns the type for ratbag_led. * * @param led A previously initialized ratbag LED * @return The LED type @ref ratbag_led_type * * @see ratbag_led_set_mode */ enum ratbag_led_type ratbag_led_get_type(struct ratbag_led *led); /** * @ingroup led * * This function returns the mode for ratbag_led. * * @param led A previously initialized ratbag LED * @return The LED mod @ref ratbag_led_mode * * @see ratbag_led_set_mode */ enum ratbag_led_mode ratbag_led_get_mode(struct ratbag_led *led); /** * @ingroup led * * This function returns the led color. * * @param led A previously initialized ratbag LED * @return The LED color in @ref ratbag_led_mode * * @see ratbag_led_set_color */ struct ratbag_color ratbag_led_get_color(struct ratbag_led *led); /** * @ingroup led * * This function returns the LED effect rate. * * @param led A previously initialized ratbag LED * @return The LED rate in Hz, can be 100 - 20000 * * @see ratbag_led_set_effect_rate */ int ratbag_led_get_effect_rate(struct ratbag_led *led); /** * @ingroup led * * This function returns the LED brightness. * * @param led A previously initialized ratbag LED * @return The LED brightness 0 - 255 * * @see ratbag_led_get_brightness */ unsigned int ratbag_led_get_brightness(struct ratbag_led *led); /** * @ingroup led * * this function sets the LED mode. * * @param led A previously initialized ratbag LED * @param mode LED mode @ref ratbag_led_mode. * @return 0 on success or an error code otherwise. * * @see ratbag_led_get_mode */ enum ratbag_error_code ratbag_led_set_mode(struct ratbag_led *led, enum ratbag_led_mode mode); /** * @ingroup led * * If the LED's mode is @ref RATBAG_LED_ON or @ref RATBAG_LED_BREATHING * then this function sets the LED color, otherwise it has no effect. * * @param led A previously initialized ratbag LED * @param color A LED color. * @return 0 on success or an error code otherwise. * * @see ratbag_led_get_color */ enum ratbag_error_code ratbag_led_set_color(struct ratbag_led *led, struct ratbag_color color); /** * @ingroup led * * If the LED's mode is @ref RATBAG_LED_CYCLE or @ref RATBAG_LED_BREATHING * then this function sets the LED rate in Hz * * @param led A previously initialized ratbag LED * @param rate Effect rate in hz, 100 - 20000 * @return 0 on success or an error code otherwise. * * @see ratbag_led_get_effect_rate */ enum ratbag_error_code ratbag_led_set_effect_rate(struct ratbag_led *led, unsigned int rate); /** * @ingroup led * * If the LED's mode is @ref RATBAG_LED_CYCLE or @ref RATBAG_LED_BREATHING * then this function sets the LED brightness, otherwise it has no effect. * * @param led A previously initialized ratbag LED * @param brightness Effect brightness 0 - 255 * @return 0 on success or an error code otherwise. * * @see ratbag_led_get_brightness */ enum ratbag_error_code ratbag_led_set_brightness(struct ratbag_led *led, unsigned int brightness); /** * @ingroup button * * This function sets the special function assigned to this button. * * @param button A previously initialized ratbag button * @param action The special action to assign to this button. * @return 0 on success or an error code otherwise. On success, the button's * action is set to @ref RATBAG_BUTTON_ACTION_TYPE_SPECIAL. * * @see ratbag_button_get_button */ enum ratbag_error_code ratbag_button_set_special(struct ratbag_button *button, enum ratbag_button_action_special action); /** * @ingroup button * * If a button's action is @ref RATBAG_BUTTON_ACTION_TYPE_KEY, * this function returns the key or button configured for this button. * * If the button's action type is not @ref RATBAG_BUTTON_ACTION_TYPE_KEY, * this function returns 0 and leaves modifiers and sz untouched. * * @param button A previously initialized ratbag button * @param[out] modifiers Will be filled with the modifiers required for this * action. The modifiers are as defined in linux/input.h. * @param[in,out] sz Takes the size of the modifiers array and returns the * number of modifiers filled in. sz may be 0 if no modifiers are required. * * @note The caller must ensure that modifiers is large enough to accomodate * for the key combination. * * @return The button number */ unsigned int ratbag_button_get_key(struct ratbag_button *button, unsigned int *modifiers, size_t *sz); /** * @ingroup button * * @param button A previously initialized ratbag button * @param key The button number to assign to this button, one of BTN_* as * defined in linux/input.h * @param modifiers The modifiers required for this action. The * modifiers are as defined in linux/input.h, in the order they should be * pressed. * @param sz The size of the modifiers array. sz may be 0 if no modifiers * are required. * * @return 0 on success or an error code otherwise. On success, the button's * action is set to @ref RATBAG_BUTTON_ACTION_TYPE_KEY. */ enum ratbag_error_code ratbag_button_set_key(struct ratbag_button *button, unsigned int key, unsigned int *modifiers, size_t sz); /** * @ingroup button * * @param button A previously initialized ratbag button * * @return 0 on success or an error code otherwise. On success, the button's * action is set to @ref RATBAG_BUTTON_ACTION_TYPE_NONE. */ enum ratbag_error_code ratbag_button_disable(struct ratbag_button *button); /** * @ingroup button * * Macro event types describing the event. */ enum ratbag_macro_event_type { RATBAG_MACRO_EVENT_INVALID = -1, RATBAG_MACRO_EVENT_NONE = 0, RATBAG_MACRO_EVENT_KEY_PRESSED, RATBAG_MACRO_EVENT_KEY_RELEASED, RATBAG_MACRO_EVENT_WAIT, }; /** * @ingroup button * * @param macro A previously initialized ratbag button macro * * @return The name of the macro */ const char * ratbag_button_macro_get_name(struct ratbag_button_macro *macro); /** * @ingroup button * * @param macro A previously initialized ratbag button macro * * @return The maximum number of events that can be assigned to this macro */ unsigned int ratbag_button_macro_get_num_events(struct ratbag_button_macro *macro); /** * @ingroup button * * Returns the macro event type configured for the event at the * given index. * * The behavior of this function for an index equal to or greater than the * return value of ratbag_button_macro_get_num_events() is undefined. * * @param macro A previously initialized ratbag button macro * @param index An index of the event within the macro we are interested in. * * @return The type of the event at the given index */ enum ratbag_macro_event_type ratbag_button_macro_get_event_type(struct ratbag_button_macro *macro, unsigned int index); /** * @ingroup button * * If the event stored at the given index is @ref * RATBAG_MACRO_EVENT_KEY_PRESSED or @ref RATBAG_MACRO_EVENT_KEY_RELEASED, * this function returns the key code configured for the event at the given * index. * * The behavior of this function for an index equal to or greater than the * return value of ratbag_button_macro_get_num_events() is undefined. * * @param macro A previously initialized ratbag button macro * @param index An index of the event within the macro we are interested in. * * @return The key of the event at the given index */ int ratbag_button_macro_get_event_key(struct ratbag_button_macro*macro, unsigned int index); /** * @ingroup button * * If the event stored at the given index is @ref RATBAG_MACRO_EVENT_WAIT, * this function returns the timeout configured for the event at the given * index. * * The behavior of this function for an index equal to or greater than the * return value of ratbag_button_macro_get_num_events() is undefined. * * @param macro A previously initialized ratbag button macro * @param index An index of the event within the macro we are interested in. * * @return The timeout of the event at the given index */ int ratbag_button_macro_get_event_timeout(struct ratbag_button_macro *macro, unsigned int index); /** * @ingroup button * * Sets the button's action to @ref RATBAG_BUTTON_ACTION_TYPE_MACRO and * assigns the given macro to this button. * * libratbag does not use the macro struct passed in, it extracts the * required information from the struct. Changes to the macro after a call * to ratbag_button_set_macro() are not reflected in the device until a * subsequent call to ratbag_button_set_macro(). * * @param button A previously intialized ratbag button * @param macro A fully initialized macro * * @return 0 on success or nonzero otherwise */ enum ratbag_error_code ratbag_button_set_macro(struct ratbag_button *button, const struct ratbag_button_macro *macro); /** * @ingroup button * * Initialize a new button macro. * * The macro is refcounted with an initial value of at least 1. * Use ratbag_button_macro_unref() to release the macro. * * Note that some devices have limited storage for the macro names. * libratbag silently shortens macro names to the longest string the device * is capable of storing. * * @param name The name to assign to this macro. * * @return An "empty" button macro */ struct ratbag_button_macro * ratbag_button_macro_new(const char *name); /** * @ingroup button * * If a button's action is @ref RATBAG_BUTTON_ACTION_TYPE_MACRO, * this function returns the current button macro. The macro is a copy of * the one used on the device, changes to the macro are not reflected on the * device until a subsequent call to ratbag_button_set_macro(). * * If a button's action is not @ref RATBAG_BUTTON_ACTION_TYPE_MACRO, * this function returns NULL. * * @param button A previously initialized ratbag button */ struct ratbag_button_macro * ratbag_button_get_macro(struct ratbag_button *button); /** * @ingroup button * * Sets the macro's event at the given index to the given type with the * key code or timeout given. * * The behavior of this function for an index equal to or greater than the * return value of ratbag_button_macro_get_num_events() is undefined. */ enum ratbag_error_code ratbag_button_macro_set_event(struct ratbag_button_macro *macro, unsigned int index, enum ratbag_macro_event_type type, unsigned int data); /** * @ingroup button * * Add a reference to the macro. A macro is destroyed whenever the * reference count reaches 0. See @ref ratbag_button_macro_unref. * * @param macro A previously initialized valid ratbag button macro * @return The passed ratbag macro */ struct ratbag_button_macro * ratbag_button_macro_ref(struct ratbag_button_macro *macro); /** * @ingroup button * * Dereference the ratbag button macro. When the internal refcount reaches * zero, all resources associated with this object are released. The object * must be considered invalid once unref is called. * * @param macro A previously initialized ratbag button macro * @return Always NULL */ struct ratbag_button_macro * ratbag_button_macro_unref(struct ratbag_button_macro *macro); /** * @ingroup button * * Add a reference to the button. A button is destroyed whenever the * reference count reaches 0. See @ref ratbag_button_unref. * * @param button A previously initialized valid ratbag button * @return The passed ratbag button */ struct ratbag_button * ratbag_button_ref(struct ratbag_button *button); /** * @ingroup button * * Dereference the ratbag button. When the internal refcount reaches * zero, all resources associated with this object are released. The object * must be considered invalid once unref is called. * * @param button A previously initialized ratbag button * @return Always NULL */ struct ratbag_button * ratbag_button_unref(struct ratbag_button *button); /** * @ingroup led * * Add a reference to the led. A led is destroyed whenever the * reference count reaches 0. See @ref ratbag_led_unref. * * @param led A previously initialized valid ratbag led * @return The passed ratbag led */ struct ratbag_led * ratbag_led_ref(struct ratbag_led *led); /** * @ingroup led * * Dereference the ratbag led. When the internal refcount reaches * zero, all resources associated with this object are released. The object * must be considered invalid once unref is called. * * @param led A previously initialized ratbag led * @return Always NULL */ struct ratbag_led * ratbag_led_unref(struct ratbag_led *led); #ifdef __cplusplus } #endif libratbag-0.9/src/libratbag.pc.in000066400000000000000000000004511311552661500167650ustar00rootroot00000000000000prefix=@prefix@ datadir=${prefix}/@datadir@ pkgdatadir=${datadir}/libratbag libdir=${prefix}/@libdir@ includedir=${prefix}/@includedir@ Name: Libratbag Description: Mouse configuration library Version: @LIBRATBAG_VERSION@ Cflags: -I${includedir} Libs: -L${libdir} -lratbag Libs.private: -lm -lrt libratbag-0.9/src/libratbag.sym000066400000000000000000000046361311552661500165770ustar00rootroot00000000000000/* in alphabetical order! */ LIBRATBAG_0.5.0 { global: ratbag_create_context; ratbag_button_disable; ratbag_button_get_action_type; ratbag_button_get_type; ratbag_button_get_button; ratbag_button_get_key; ratbag_button_get_macro; ratbag_button_get_special; ratbag_button_get_user_data; ratbag_button_has_action_type; ratbag_button_macro_get_event_key; ratbag_button_macro_get_event_timeout; ratbag_button_macro_get_event_type; ratbag_button_macro_get_name; ratbag_button_macro_get_num_events; ratbag_button_macro_new; ratbag_button_macro_ref; ratbag_button_macro_set_event; ratbag_button_macro_unref; ratbag_button_ref; ratbag_button_set_button; ratbag_button_set_key; ratbag_button_set_macro; ratbag_button_set_special; ratbag_button_set_user_data; ratbag_button_unref; ratbag_device_commit; ratbag_device_get_name; ratbag_device_get_num_buttons; ratbag_device_get_num_profiles; ratbag_device_get_profile; ratbag_device_get_svg_name; ratbag_device_get_user_data; ratbag_device_has_capability; ratbag_device_new_from_udev_device; ratbag_device_new_test_device; ratbag_device_ref; ratbag_device_set_user_data; ratbag_device_unref; ratbag_get_user_data; ratbag_log_get_priority; ratbag_log_set_handler; ratbag_log_set_priority; ratbag_profile_get_led; ratbag_led_get_mode; ratbag_led_get_type; ratbag_led_get_color; ratbag_led_get_effect_rate; ratbag_led_get_brightness; ratbag_led_ref; ratbag_led_set_mode; ratbag_led_set_color; ratbag_led_set_effect_rate; ratbag_led_set_brightness; ratbag_led_unref; ratbag_device_get_num_leds; ratbag_profile_get_button; ratbag_profile_get_num_resolutions; ratbag_profile_get_resolution; ratbag_profile_get_user_data; ratbag_profile_is_active; ratbag_profile_is_enabled; ratbag_profile_ref; ratbag_profile_set_user_data; ratbag_profile_set_active; ratbag_profile_set_enabled; ratbag_profile_unref; ratbag_resolution_get_dpi; ratbag_resolution_get_dpi_x; ratbag_resolution_get_dpi_y; ratbag_resolution_get_report_rate; ratbag_resolution_get_user_data; ratbag_resolution_has_capability; ratbag_resolution_is_active; ratbag_resolution_is_default; ratbag_resolution_ref; ratbag_resolution_set_active; ratbag_resolution_set_default; ratbag_resolution_set_dpi; ratbag_resolution_set_dpi_xy; ratbag_resolution_set_report_rate; ratbag_resolution_set_user_data; ratbag_resolution_unref; ratbag_ref; ratbag_set_user_data; ratbag_unref; local: *; }; libratbag-0.9/src/usb-ids.h000066400000000000000000000023361311552661500156300ustar00rootroot00000000000000/* * Copyright © 2016 Red Hat, Inc. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that copyright * notice and this permission notice appear in supporting documentation, and * that the name of the copyright holders not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. The copyright holders make no representations * about the suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE * OF THIS SOFTWARE. */ #pragma once #define USB_VENDOR_ID_LOGITECH 0x046d libratbag-0.9/test/000077500000000000000000000000001311552661500142755ustar00rootroot00000000000000libratbag-0.9/test/build-cxx.cc000066400000000000000000000001761311552661500165070ustar00rootroot00000000000000#include /* This is a build-test only */ using namespace std; int main(int argc, char **argv) { return 0; } libratbag-0.9/test/build-pedantic.c000066400000000000000000000001271311552661500173250ustar00rootroot00000000000000#include /* This is a build-test only */ int main(void) { return 0; } libratbag-0.9/test/symbols-leak-test000077500000000000000000000007561311552661500176120ustar00rootroot00000000000000#!/bin/bash ### simple check for exported symbols TOP_SRCDIR="$1" if [ -z "$TOP_SRCDIR" ]; then echo "usage: $0 path/to/top_srcdir" exit 1 fi # make bash exit if any command will exit with non-0 return value set -e # make sure the paths are alright diff -a -u \ <(cat "$TOP_SRCDIR"/src/libratbag.sym | \ grep '^\s\+ratbag_.*' | \ sed -e 's/^\s\+\(.*\);/\1/' | sort) \ <(cat "$TOP_SRCDIR"/src/*.c | \ grep LIBRATBAG_EXPORT -A 1 | grep '^ratbag_.*' | \ sed -e 's/(.*//' | sort) libratbag-0.9/test/test-context.c000066400000000000000000000100571311552661500171050ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "libratbag.h" #define _unused_ __attribute__ ((unused)) static int open_restricted(const char *path, int flags, void *user_data) { int fd = open(path, flags); if (fd < 0) fprintf(stderr, "Failed to open %s (%s)\n", path, strerror(errno)); return fd < 0 ? -errno : fd; } static void close_restricted(int fd, void *user_data) { close(fd); } struct ratbag_interface simple_iface = { .open_restricted = open_restricted, .close_restricted = close_restricted, }; START_TEST(context_init_NULL) { struct ratbag *lr; lr = ratbag_create_context(NULL, NULL); ck_assert(lr == NULL); } END_TEST START_TEST(context_init_bad_iface) { struct ratbag *lr _unused_; struct ratbag_interface iface = { .open_restricted = NULL, .close_restricted = NULL, }; lr = ratbag_create_context(&iface, NULL); /* abort */ } END_TEST START_TEST(context_init_bad_iface_open) { struct ratbag *lr _unused_; struct ratbag_interface iface = { .open_restricted = open_restricted, .close_restricted = NULL, }; lr = ratbag_create_context(&iface, NULL); /* abort */ } END_TEST START_TEST(context_init_bad_iface_close) { struct ratbag *lr _unused_; struct ratbag_interface iface = { .open_restricted = NULL, .close_restricted = close_restricted, }; lr = ratbag_create_context(&iface, NULL); /* abort */ } END_TEST START_TEST(context_init) { struct ratbag *lr; lr = ratbag_create_context(&simple_iface, NULL); ck_assert(lr != NULL); ratbag_unref(lr); } END_TEST START_TEST(context_ref) { struct ratbag *lr; struct ratbag *lr2; lr = ratbag_create_context(&simple_iface, NULL); ck_assert(lr != NULL); lr2 = ratbag_ref(lr); ck_assert_ptr_eq(lr, lr2); lr2 = ratbag_unref(lr2); ck_assert_ptr_eq(lr2, NULL); lr2 = ratbag_unref(lr); ck_assert_ptr_eq(lr2, NULL); } END_TEST static Suite * test_context_suite(bool using_valgrind) { TCase *tc; Suite *s; s = suite_create("context"); tc = tcase_create("init"); if (!using_valgrind) { tcase_add_test_raise_signal(tc, context_init_NULL, SIGABRT); tcase_add_test_raise_signal(tc, context_init_bad_iface, SIGABRT); tcase_add_test_raise_signal(tc, context_init_bad_iface_open, SIGABRT); tcase_add_test_raise_signal(tc, context_init_bad_iface_close, SIGABRT); } tcase_add_test(tc, context_init); tcase_add_test(tc, context_ref); suite_add_tcase(s, tc); return s; } int main(void) { int nfailed; Suite *s; SRunner *sr; bool using_valgrind; /* when running under valgrind we're using nofork mode, so a * signal raised by a test will fail in valgrind */ using_valgrind = !!getenv("USING_VALGRIND"); s = test_context_suite(using_valgrind); sr = srunner_create(s); srunner_run_all(sr, CK_ENV); nfailed = srunner_ntests_failed(sr); srunner_free(sr); return (nfailed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } libratbag-0.9/test/test-device.c000066400000000000000000000532011311552661500166560ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "libratbag.h" #include "libratbag-test.h" static void device_destroyed(struct ratbag_device *device, void *data) { int *count = data; if (!data) return; ++*count; } /* A pre-setup sane device. Use for sanity testing by toggling the various * error conditions. */ const struct ratbag_test_device sane_device = { .num_profiles = 3, .num_resolutions = 3, .num_buttons = 1, .num_leds = 2, .profiles = { { .resolutions = { { .xres = 100, .yres = 200, .hz = 1000 }, { .xres = 200, .yres = 300, .hz = 1000 }, { .xres = 300, .yres = 400, .hz = 1000 }, }, .active = true, .dflt = false, }, { .resolutions = { { .xres = 1100, .yres = 1200, .hz = 2000 }, { .xres = 1200, .yres = 1300, .hz = 2000 }, { .xres = 1300, .yres = 1400, .hz = 2000 }, }, .active = false, .dflt = true, }, { .resolutions = { { .xres = 2100, .yres = 2200, .hz = 3000 }, { .xres = 2200, .yres = 2300, .hz = 3000 }, { .xres = 2300, .yres = 2400, .hz = 3000 }, }, .leds = { { .mode = RATBAG_LED_ON, .color = { .red = 255, .green = 0, .blue = 0 }, .hz = 1, .brightness = 20 }, { .mode = RATBAG_LED_CYCLE, .color = { .red = 255, .green = 255, .blue = 0 }, .hz = 3, .brightness = 40 } }, .active = false, .dflt = false, }, }, .destroyed = device_destroyed, .destroyed_data = NULL, }; static int open_restricted(const char *path, int flags, void *user_data) { /* for test devices we don't expect this to be called */ ck_abort(); return 0; } static void close_restricted(int fd, void *user_data) { /* for test devices we don't expect this to be called */ ck_abort(); } struct ratbag_interface abort_iface = { .open_restricted = open_restricted, .close_restricted = close_restricted, }; START_TEST(device_init) { struct ratbag *r; struct ratbag_device *d; int nprofiles, nbuttons, nleds; struct ratbag_test_device td = sane_device; int device_freed_count = 0; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); nprofiles = ratbag_device_get_num_profiles(d); ck_assert_int_eq(nprofiles, 3); nbuttons = ratbag_device_get_num_buttons(d); ck_assert_int_eq(nbuttons, 1); nleds = ratbag_device_get_num_leds(d); ck_assert_int_eq(nleds, 2); ratbag_device_unref(d); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST #define ref_unref_test(T, a) \ { \ int i; \ struct T *tmp = NULL; \ \ for (i = 0; i <= 256; i++) { \ tmp = T##_ref(a); \ ck_assert(tmp == a); \ } \ for (i = 0; i <= 256; i++) { \ tmp = T##_unref(a); \ ck_assert(tmp == NULL); \ } \ for (i = 0; i <= 256; i++) { \ tmp = T##_ref(a); \ ck_assert(tmp == a); \ tmp = T##_unref(a); \ ck_assert(tmp == NULL); \ } \ } START_TEST(device_ref_unref) { struct ratbag *r; struct ratbag_device *d; struct ratbag_test_device td = sane_device; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); ratbag_unref(r); ref_unref_test(ratbag_device, d); ratbag_device_unref(d); } END_TEST START_TEST(device_free_context_before_device) { struct ratbag *r; struct ratbag_device *d; struct ratbag_test_device td = sane_device; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); r = ratbag_unref(r); ck_assert(r == NULL); d = ratbag_device_unref(d); ck_assert(d == NULL); } END_TEST START_TEST(device_profiles) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; int nprofiles; int i; bool is_active; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); nprofiles = ratbag_device_get_num_profiles(d); ck_assert_int_eq(nprofiles, 3); for (i = 0; i < nprofiles; i++) { p = ratbag_device_get_profile(d, i); ck_assert(p != NULL); is_active = ratbag_profile_is_active(p); ck_assert_int_eq(is_active, (i == 0)); ratbag_profile_unref(p); } ratbag_device_unref(d); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_profiles_ref_unref) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_test_device td = sane_device; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); p = ratbag_device_get_profile(d, 1); ratbag_unref(r); ratbag_device_unref(d); ref_unref_test(ratbag_profile, p); ratbag_profile_unref(p); } END_TEST START_TEST(device_profiles_num_0) { struct ratbag *r; struct ratbag_device *d; struct ratbag_test_device td = sane_device; td.num_profiles = 0; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d == NULL); ratbag_unref(r); } END_TEST START_TEST(device_profiles_multiple_active) { struct ratbag *r; struct ratbag_device *d; struct ratbag_test_device td = sane_device; td.profiles[0].active = true; td.profiles[1].active = true; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d == NULL); ratbag_unref(r); } END_TEST START_TEST(device_profiles_get_invalid) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; int nprofiles; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); nprofiles = ratbag_device_get_num_profiles(d); ck_assert_int_eq(nprofiles, 3); p = ratbag_device_get_profile(d, nprofiles); ck_assert(p == NULL); p = ratbag_device_get_profile(d, nprofiles + 1); ck_assert(p == NULL); p = ratbag_device_get_profile(d, -1); ck_assert(p == NULL); p = ratbag_device_get_profile(d, INT_MAX); ck_assert(p == NULL); p = ratbag_device_get_profile(d, UINT_MAX); ck_assert(p == NULL); ratbag_device_unref(d); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_resolutions) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_resolution *res; int nprofiles, nresolutions; int i, j; int xres, yres, rate; int device_freed_count = 0; bool is_active; struct ratbag_test_device td = { .num_profiles = 3, .num_resolutions = 3, .num_buttons = 1, .profiles = { { .resolutions = { { .xres = 100, .yres = 200, .hz = 1000 }, { .xres = 200, .yres = 300, .hz = 1000, .active = true }, { .xres = 300, .yres = 400, .hz = 1000 }, }, .active = true, }, { .resolutions = { { .xres = 1100, .yres = 1200, .hz = 2000 }, { .xres = 1200, .yres = 1300, .hz = 2000, .active = true }, { .xres = 1300, .yres = 1400, .hz = 2000 }, }, }, { .resolutions = { { .xres = 2100, .yres = 2200, .hz = 3000 }, { .xres = 2200, .yres = 2300, .hz = 3000, .active = true }, { .xres = 2300, .yres = 2400, .hz = 3000 }, }, }, }, .destroyed = device_destroyed, .destroyed_data = &device_freed_count, }; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); nprofiles = ratbag_device_get_num_profiles(d); for (i = 0; i < nprofiles; i++) { p = ratbag_device_get_profile(d, i); nresolutions = ratbag_profile_get_num_resolutions(p); ck_assert_int_eq(nresolutions, 3); for (j = 0; j < nresolutions; j++) { res = ratbag_profile_get_resolution(p, j); xres = ratbag_resolution_get_dpi_x(res); yres = ratbag_resolution_get_dpi_y(res); rate = ratbag_resolution_get_report_rate(res); is_active = ratbag_resolution_is_active(res); ck_assert_int_eq(xres, i * 1000 + (j + 1) * 100); ck_assert_int_eq(yres, i * 1000 + (j + 1) * 100 + 100); ck_assert_int_eq(xres, ratbag_resolution_get_dpi(res)); ck_assert_int_eq(is_active, (j == 1)); ck_assert_int_eq(rate, (i + 1) * 1000); ratbag_resolution_unref(res); } ratbag_profile_unref(p); } ratbag_device_unref(d); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_resolutions_ref_unref) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_resolution *res; struct ratbag_test_device td = sane_device; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); p = ratbag_device_get_profile(d, 1); res = ratbag_profile_get_resolution(p, 0); ratbag_unref(r); ratbag_device_unref(d); ratbag_profile_unref(p); ref_unref_test(ratbag_resolution, res); ratbag_resolution_unref(res); } END_TEST START_TEST(device_resolutions_num_0) { struct ratbag *r; struct ratbag_device *d; struct ratbag_test_device td = { .num_profiles = 1, .num_buttons = 1, .num_resolutions = 0, /* failure trigger */ .profiles = { { .active = true, }, }, }; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d == NULL); ratbag_unref(r); } END_TEST START_TEST(device_freed_before_profile) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; int is_active, rc; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; td.profiles[0].active = false; td.profiles[1].active = true; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); p = ratbag_device_get_profile(d, 0); ck_assert(p != NULL); is_active = ratbag_profile_is_active(p); ck_assert_int_eq(is_active, 0); d = ratbag_device_unref(d); /* a ref to d is still kept through p */ ck_assert(d == NULL); rc = ratbag_profile_set_active(p); ck_assert_int_eq(rc, 0); is_active = ratbag_profile_is_active(p); ck_assert_int_eq(is_active, 1); p = ratbag_profile_unref(p); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_and_profile_freed_before_button) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_button *b; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); p = ratbag_device_get_profile(d, 0); ck_assert(p != NULL); d = ratbag_device_unref(d); /* a ref to d is still kept through p */ ck_assert(d == NULL); b = ratbag_profile_get_button(p, 0); ck_assert(b != NULL); p = ratbag_profile_unref(p); /* a ref to p is still kept through b */ ck_assert(p == NULL); /* FIXME: should probably call something for the button */ b = ratbag_button_unref(b); ck_assert(b == NULL); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_and_profile_freed_before_resolution) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_resolution *res; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); p = ratbag_device_get_profile(d, 0); ck_assert(p != NULL); d = ratbag_device_unref(d); /* a ref to d is still kept through p */ ck_assert(d == NULL); res = ratbag_profile_get_resolution(p, 0); ck_assert(res != NULL); p = ratbag_profile_unref(p); /* a ref to p is still kept through res */ ck_assert(p == NULL); /* FIXME: should probably call something for the resolution */ res = ratbag_resolution_unref(res); ck_assert(res == NULL); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_and_profile_and_button_freed_before_resolution) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_resolution *res; struct ratbag_button *b; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); p = ratbag_device_get_profile(d, 0); ck_assert(p != NULL); d = ratbag_device_unref(d); /* a ref to d is still kept through p, so d can not be NULL */ ck_assert(d == NULL); res = ratbag_profile_get_resolution(p, 0); ck_assert(res != NULL); b = ratbag_profile_get_button(p, 0); ck_assert(b != NULL); p = ratbag_profile_unref(p); /* a ref to p is still kept through res and b */ ck_assert(p == NULL); /* a ref to p is still in res */ b = ratbag_button_unref(b); ck_assert(b == NULL); /* FIXME: should probably call something for the resolution */ res = ratbag_resolution_unref(res); ck_assert(res == NULL); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_and_profile_and_resolution_freed_before_button) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_resolution *res; struct ratbag_button *b; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); ck_assert(d != NULL); p = ratbag_device_get_profile(d, 0); ck_assert(p != NULL); d = ratbag_device_unref(d); /* a ref to d is still kept through p */ ck_assert(d == NULL); res = ratbag_profile_get_resolution(p, 0); ck_assert(res != NULL); b = ratbag_profile_get_button(p, 0); ck_assert(b != NULL); p = ratbag_profile_unref(p); /* a ref to p is still kept through res and b */ ck_assert(p == NULL); /* a ref to p is still in res */ res = ratbag_resolution_unref(res); ck_assert(res == NULL); /* FIXME: should probably call something for the button */ b = ratbag_button_unref(b); ck_assert(b == NULL); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_buttons) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_button *b; struct ratbag_button_macro *m; int nprofiles, nbuttons; int i, j; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.num_buttons = 10; td.profiles[0].buttons[8].type = RATBAG_BUTTON_ACTION_TYPE_MACRO; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); nprofiles = ratbag_device_get_num_profiles(d); ck_assert_int_eq(nprofiles, 3); nbuttons = ratbag_device_get_num_buttons(d); ck_assert_int_eq(nbuttons, 10); for (i = 0; i < nprofiles; i++) { p = ratbag_device_get_profile(d, i); ck_assert(p != NULL); for (j = 0; j < nbuttons; j++) { b = ratbag_profile_get_button(p, j); ck_assert(b != NULL); if (ratbag_button_get_action_type(b) == RATBAG_BUTTON_ACTION_TYPE_MACRO) { m = ratbag_button_get_macro(b); ck_assert(m != NULL); ratbag_button_macro_unref(m); } ratbag_button_unref(b); } ratbag_profile_unref(p); } ratbag_device_unref(d); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_buttons_ref_unref) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_button *b; struct ratbag_test_device td = sane_device; td.num_buttons = 10; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); p = ratbag_device_get_profile(d, 1); b = ratbag_profile_get_button(p, 0); ratbag_unref(r); ratbag_device_unref(d); ratbag_profile_unref(p); ref_unref_test(ratbag_button, b); ratbag_button_unref(b); } END_TEST START_TEST(device_buttons_set) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_button *b; struct ratbag_test_device td = sane_device; td.num_buttons = 10; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); p = ratbag_device_get_profile(d, 1); b = ratbag_profile_get_button(p, 0); ratbag_button_set_button(b, 3); ratbag_button_unref(b); ratbag_profile_unref(p); ratbag_device_unref(d); ratbag_unref(r); } END_TEST static void assert_led_equals(struct ratbag_led *l, struct ratbag_test_led e_l) { enum ratbag_led_mode mode; struct ratbag_color color; int brightness, hz; ck_assert(l != NULL); mode = ratbag_led_get_mode(l); color = ratbag_led_get_color(l); hz = ratbag_led_get_effect_rate(l); brightness = ratbag_led_get_brightness(l); ck_assert_int_eq(mode, e_l.mode); ck_assert_int_eq(color.red, e_l.color.red); ck_assert_int_eq(color.green, e_l.color.green); ck_assert_int_eq(color.blue, e_l.color.blue); ck_assert_int_eq(hz, e_l.hz); ck_assert_int_eq(brightness, e_l.brightness); } START_TEST(device_leds) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_led *led_logo, *led_side; int nprofiles; int device_freed_count = 0; struct ratbag_test_device td = sane_device; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); nprofiles = ratbag_device_get_num_profiles(d); ck_assert_int_eq(nprofiles, 3); p = ratbag_device_get_profile(d, 2); ck_assert(p != NULL); led_logo = ratbag_profile_get_led(p, RATBAG_LED_TYPE_LOGO); assert_led_equals(led_logo, td.profiles[2].leds[RATBAG_LED_TYPE_LOGO]); led_side = ratbag_profile_get_led(p, RATBAG_LED_TYPE_SIDE); assert_led_equals(led_side, td.profiles[2].leds[RATBAG_LED_TYPE_SIDE]); ratbag_led_unref(led_logo); ratbag_led_unref(led_side); ratbag_profile_unref(p); ratbag_device_unref(d); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST START_TEST(device_leds_set) { struct ratbag *r; struct ratbag_device *d; struct ratbag_profile *p; struct ratbag_led *l; int nprofiles; int device_freed_count = 0; struct ratbag_test_device td = sane_device; struct ratbag_color c = { .red = 0, .green = 111, .blue = 222 }; td.destroyed_data = &device_freed_count; r = ratbag_create_context(&abort_iface, NULL); d = ratbag_device_new_test_device(r, &td); nprofiles = ratbag_device_get_num_profiles(d); ck_assert_int_eq(nprofiles, 3); p = ratbag_device_get_profile(d, 0); ck_assert(p != NULL); l = ratbag_profile_get_led(p, RATBAG_LED_TYPE_LOGO); ratbag_led_set_mode(l, RATBAG_LED_BREATHING); ratbag_led_set_color(l, c); ratbag_led_set_effect_rate(l, 11); ratbag_led_set_brightness(l, 22); l = ratbag_profile_get_led(p, RATBAG_LED_TYPE_LOGO); struct ratbag_test_led e_l = { .mode = RATBAG_LED_BREATHING, .color = { .red = c.red, .green = c.green, .blue = c.blue }, .hz = 11, .brightness = 22 }; assert_led_equals(l, e_l); ratbag_led_unref(l); ratbag_led_unref(l); ratbag_profile_unref(p); ratbag_device_unref(d); ratbag_unref(r); ck_assert_int_eq(device_freed_count, 1); } END_TEST static Suite * test_context_suite(void) { TCase *tc; Suite *s; s = suite_create("device"); tc = tcase_create("device"); tcase_add_test(tc, device_init); tcase_add_test(tc, device_ref_unref); tcase_add_test(tc, device_free_context_before_device); suite_add_tcase(s, tc); tc = tcase_create("profiles"); tcase_add_test(tc, device_profiles); tcase_add_test(tc, device_profiles_ref_unref); tcase_add_test(tc, device_profiles_num_0); tcase_add_test(tc, device_profiles_multiple_active); tcase_add_test(tc, device_profiles_get_invalid); tcase_add_test(tc, device_freed_before_profile); tcase_add_test(tc, device_and_profile_freed_before_button); tcase_add_test(tc, device_and_profile_freed_before_resolution); tcase_add_test(tc, device_and_profile_and_button_freed_before_resolution); tcase_add_test(tc, device_and_profile_and_resolution_freed_before_button); suite_add_tcase(s, tc); tc = tcase_create("resolutions"); tcase_add_test(tc, device_resolutions); tcase_add_test(tc, device_resolutions_ref_unref); tcase_add_test(tc, device_resolutions_num_0); suite_add_tcase(s, tc); tc = tcase_create("buttons"); tcase_add_test(tc, device_buttons); tcase_add_test(tc, device_buttons_ref_unref); tcase_add_test(tc, device_buttons_set); suite_add_tcase(s, tc); tc = tcase_create("led"); tcase_add_test(tc, device_leds); tcase_add_test(tc, device_leds_set); suite_add_tcase(s, tc); return s; } int main(void) { int nfailed; Suite *s; SRunner *sr; s = test_context_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_ENV); nfailed = srunner_ntests_failed(sr); srunner_free(sr); return (nfailed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } libratbag-0.9/test/test-iconv-helper.c000066400000000000000000000120771311552661500200200ustar00rootroot00000000000000/* * Copyright © 2016 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include "libratbag-util.h" static const char sample_utf16le[] = { 'F', '\0', 'o', '\0', 'o', '\0' }; static const char sample_single_char_utf16le[] = { 'A', '\0' }; /* Sample UTF-8 string used: 🐺🖖🗺🗹💯👏 */ static const char sample_emoji_utf8[] = { 0xf0, 0x9f, 0x90, 0xba, 0xf0, 0x9f, 0x96, 0x96, 0xf0, 0x9f, 0x97, 0xba, 0xf0, 0x9f, 0x97, 0xb9, 0xf0, 0x9f, 0x92, 0xaf, 0xf0, 0x9f, 0x91, 0x8f, 0x0a, 0x00 }; static const char sample_emoji_utf16le[] = { 0x3d, 0xd8, 0x3a, 0xdc, 0x3d, 0xd8, 0x96, 0xdd, 0x3d, 0xd8, 0xfa, 0xdd, 0x3d, 0xd8, 0xf9, 0xdd, 0x3d, 0xd8, 0xaf, 0xdc, 0x3d, 0xd8, 0x4f, 0xdc, 0x0a, 0x00 }; START_TEST(iconv_convert_to_utf16le) { char output[4096]; ssize_t rc; rc = ratbag_utf8_to_enc(output, sizeof(sample_utf16le), "UTF-16LE", "%s", "Foo"); ck_assert_int_eq(rc, sizeof(sample_utf16le)); ck_assert(memcmp(output, sample_utf16le, sizeof(sample_utf16le)) == 0); rc = ratbag_utf8_to_enc(output, sizeof(sample_emoji_utf16le), "UTF-16LE", "%s", sample_emoji_utf8); ck_assert_int_eq(rc, sizeof(sample_emoji_utf16le)); ck_assert(memcmp(output, sample_emoji_utf16le, sizeof(sample_emoji_utf16le)) == 0); rc = ratbag_utf8_to_enc(output, sizeof(sample_single_char_utf16le), "UTF-16LE", "%s", "A"); ck_assert_int_eq(rc, sizeof(sample_single_char_utf16le)); ck_assert(memcmp(output, sample_single_char_utf16le, sizeof(sample_single_char_utf16le)) == 0); } END_TEST START_TEST(iconv_convert_from_utf16le) { char input[4096]; char *output; ssize_t rc; memcpy(input, sample_utf16le, sizeof(sample_utf16le)); rc = ratbag_utf8_from_enc(input, sizeof(sample_utf16le), "UTF-16LE", &output); ck_assert_int_eq(rc, sizeof("Foo")); ck_assert(memcmp(output, "Foo", sizeof("Foo")) == 0); free(output); memcpy(input, sample_emoji_utf16le, sizeof(sample_emoji_utf16le)); rc = ratbag_utf8_from_enc(input, sizeof(sample_emoji_utf16le), "UTF-16LE", &output); ck_assert_int_eq(rc, sizeof(sample_emoji_utf8)); ck_assert(memcmp(output, sample_emoji_utf8, sizeof(sample_emoji_utf8)) == 0); free(output); memcpy(input, sample_single_char_utf16le, sizeof(sample_single_char_utf16le)); rc = ratbag_utf8_from_enc(input, sizeof(sample_single_char_utf16le), "UTF-16LE", &output); ck_assert_int_eq(rc, sizeof("A")); ck_assert(memcmp(output, "A", sizeof("A")) == 0); free(output); } END_TEST START_TEST(iconv_invalid_encoding) { char output[10] = { 0 }; ssize_t rc; rc = ratbag_utf8_to_enc(output, sizeof(output), "This encoding is invalid", "%s", "Foo"); ck_assert_int_eq(rc, -EINVAL); } END_TEST START_TEST(iconv_bad_utf16le) { char odd_numbered[] = { 'F', '\0', 'o' }; char single_char[] = { 'F' }; char single_null[] = { '\0' }; char double_null[] = { 'F', '\0', '\0', 'o', '\0', 'o', '\0' }; char *output; ssize_t rc; rc = ratbag_utf8_from_enc(odd_numbered, sizeof(odd_numbered), "UTF-16LE", &output); ck_assert_int_eq(rc, -EINVAL); rc = ratbag_utf8_from_enc(single_char, sizeof(single_char), "UTF-16LE", &output); ck_assert_int_eq(rc, -EINVAL); rc = ratbag_utf8_from_enc(single_null, sizeof(single_null), "UTF-16LE", &output); ck_assert_int_eq(rc, -EINVAL); rc = ratbag_utf8_from_enc(double_null, sizeof(double_null), "UTF-16LE", &output); ck_assert_int_eq(rc, -EINVAL); } END_TEST static Suite * test_context_suite(void) { TCase *tc; Suite *s; s = suite_create("iconv-helper"); tc = tcase_create("iconv-helper"); tcase_add_test(tc, iconv_convert_to_utf16le); tcase_add_test(tc, iconv_convert_from_utf16le); tcase_add_test(tc, iconv_invalid_encoding); tcase_add_test(tc, iconv_bad_utf16le); suite_add_tcase(s, tc); return s; } int main(void) { int nfailed; Suite *s; SRunner *sr; s = test_context_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_ENV); nfailed = srunner_ntests_failed(sr); srunner_free(sr); return (nfailed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } libratbag-0.9/test/valgrind.suppressions000066400000000000000000000002431311552661500206010ustar00rootroot00000000000000{ srunner_run::timer_create-uninitialized-bytes Memcheck:Param timer_create(evp) fun:timer_create@@GLIBC_2.3.3 fun:srunner_run ... fun:main } libratbag-0.9/tools/000077500000000000000000000000001311552661500144565ustar00rootroot00000000000000libratbag-0.9/tools/hidpp10-dump-page.c000066400000000000000000000056571311552661500177610ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include static inline int dump_page(struct hidpp10_device *dev, size_t page, size_t offset) { int rc = 0; uint8_t bytes[16]; while (offset < 512) { hidpp_log_info(&dev->base, "page 0x%02zx off 0x%03zx: ", page, offset); rc = hidpp10_read_memory(dev, page, offset, bytes); if (rc != 0) break; hidpp_log_buffer(&dev->base, HIDPP_LOG_PRIORITY_INFO, " ", bytes, ARRAY_LENGTH(bytes)); offset += 16; } return rc; } static inline int dump_all_pages(struct hidpp10_device *dev) { uint8_t page; int rc = 0; for (page = 0; page < 31; page++) { rc = dump_page(dev, page, 0); if (rc != 0) break; } /* We dumped at least one page successfully and get EAGAIN, so we're * on the last page. Overwrite the last line with a blank one so it * doesn't look like an error */ if (page > 0 && rc == EAGAIN) { hidpp_log_info(&dev->base, "\r \n"); rc = 0; } return rc; } static void usage(void) { printf("Usage: %s [page] [offset] /dev/hidraw0\n", program_invocation_short_name); } int main(int argc, char **argv) { _cleanup_close_ int fd = 0; const char *path; size_t page = 0, offset = 0; struct hidpp10_device *dev = NULL; struct hidpp_device base; int rc; if (argc < 2 || argc > 4) { usage(); return 1; } path = argv[argc - 1]; fd = open(path, O_RDWR); if (fd < 0) error(1, errno, "Failed to open path %s", path); hidpp_device_init(&base, fd); dev = hidpp10_device_new(&base, HIDPP_WIRED_DEVICE_IDX, HIDPP10_PROFILE_UNKNOWN); if (argc == 2) rc = dump_all_pages(dev); else { page = atoi(argv[1]); if (argc > 3) offset = atoi(argv[2]); rc = dump_page(dev, page, offset); } hidpp10_device_destroy(dev); return rc; } libratbag-0.9/tools/hidpp20-dump-page.c000066400000000000000000000063071311552661500177530ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include static inline int dump_page(struct hidpp20_device *dev, uint8_t rom, size_t page, size_t offset) { int rc = 0; uint8_t bytes[16]; while (offset < 256) { hidpp_log_info(&dev->base, "%s: page 0x%02zx off 0x%02zx: ", rom ? "ROM " : "FLASH", page, offset); rc = hidpp20_onboard_profiles_read_memory(dev, rom, page, offset, bytes); if (rc != 0) break; hidpp_log_buffer(&dev->base, HIDPP_LOG_PRIORITY_INFO, " ", bytes, ARRAY_LENGTH(bytes)); offset += 16; } return rc; } static inline int dump_all_pages(struct hidpp20_device *dev, uint8_t rom) { uint8_t page; int rc = 0; for (page = 0; page < 31; page++) { rc = dump_page(dev, rom, page, 0); if (rc != 0) break; } /* We dumped at least one page successfully and get EAGAIN, so we're * on the last page. Overwrite the last line with a blank one so it * doesn't look like an error */ if (page > 0 && rc == ENOENT) { hidpp_log_info(&dev->base, "\r \n"); rc = 0; } return rc; } static inline int dump_everything(struct hidpp20_device *dev) { int rc; rc = dump_all_pages(dev, 0); if (rc) return rc; return dump_all_pages(dev, 1); } static void usage(void) { printf("Usage: %s [page] [offset] /dev/hidraw0\n", program_invocation_short_name); } int main(int argc, char **argv) { _cleanup_close_ int fd = 0; const char *path; size_t page = 0, offset = 0; struct hidpp20_device *dev = NULL; struct hidpp_device base; int rc; if (argc < 2 || argc > 4) { usage(); return 1; } path = argv[argc - 1]; fd = open(path, O_RDWR); if (fd < 0) error(1, errno, "Failed to open path %s", path); hidpp_device_init(&base, fd); dev = hidpp20_device_new(&base, 0xff); if (!dev) error(1, 0, "Failed to open %s as a HID++ 2.0 device", path); if (argc == 2) rc = dump_everything(dev); else { page = atoi(argv[1]); if (argc > 3) offset = atoi(argv[2]); rc = dump_page(dev, 0, page, offset); } hidpp20_device_destroy(dev); return rc; } libratbag-0.9/tools/lur-command.c000066400000000000000000000137531311552661500170510ustar00rootroot00000000000000/* * 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. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include enum options { OPT_HELP, }; static struct lur_receiver * open_receiver(const char *path) { struct lur_receiver *receiver = NULL; int fd = -1; int rc; fd = open(path, O_RDWR); if (fd < 0) { fprintf(stderr, "Failed to open %s (%s)\n", path, strerror(errno)); return NULL; } rc = lur_receiver_new_from_hidraw(fd, NULL, &receiver); if (rc != 0) close(fd); return receiver; } static void list_connected_devices(struct lur_receiver *receiver) { _cleanup_free_ struct lur_device **devices = NULL; int ndevices; int i; ndevices = lur_receiver_enumerate(receiver, &devices); if (ndevices < 0) { fprintf(stderr, "Failed to enumerate devices\n"); return; } else if (ndevices == 0) { fprintf(stderr, "No devices connected to this receiver\n"); return; } for (i = 0; i < ndevices; i++) { struct lur_device *dev = devices[i]; const char *name, *strtype; enum lur_device_type type; uint32_t serial; name = lur_device_get_name(dev); type = lur_device_get_type(dev); serial = lur_device_get_serial(dev); switch(type) { case LUR_DEVICE_TYPE_UNKNOWN: strtype = "unknown"; break; case LUR_DEVICE_TYPE_KEYBOARD: strtype = "keyboard"; break; case LUR_DEVICE_TYPE_MOUSE: strtype = "mouse"; break; case LUR_DEVICE_TYPE_NUMPAD: strtype = "numpad"; break; case LUR_DEVICE_TYPE_PRESENTER: strtype = "presenter"; break; case LUR_DEVICE_TYPE_TRACKBALL: strtype = "trackball"; break; case LUR_DEVICE_TYPE_TOUCHPAD: strtype = "touchpad"; break; default: strtype = ""; break; } printf("%d: %s (%s) serial %#x\n", i, name, strtype, serial); lur_device_unref(dev); } } static void disconnect_device(struct lur_receiver *receiver, int index) { _cleanup_free_ struct lur_device **devices = NULL; int ndevices; int i; ndevices = lur_receiver_enumerate(receiver, &devices); if (ndevices < 0) { fprintf(stderr, "Failed to enumerate devices\n"); return; } else if (ndevices == 0) { fprintf(stderr, "No devices connected to this receiver\n"); return; } if (index < 0 || index >= ndevices) { fprintf(stderr, "Invalid index %d, only %d devices connected\n", index, ndevices); } else { lur_device_disconnect(devices[index]); } for (i = 0; i < ndevices; i++) lur_device_unref(devices[i]); } static int filter_hidraw(const struct dirent *entry) { return strneq(entry->d_name, "hidraw", 6); } static void find_receiver(void) { _cleanup_free_ struct dirent **hidraw_list = NULL; int n, i; char path[PATH_MAX] = {0}; bool found = false; n = scandir("/dev/", &hidraw_list, filter_hidraw, alphasort); if (n < 0) return; for (i = 0; i < n; i++) { struct lur_receiver *receiver; sprintf_safe(path, "/dev/%s", hidraw_list[i]->d_name); receiver = open_receiver(path); if (receiver) { found = true; printf("%s\n", path); lur_receiver_unref(receiver); } free(hidraw_list[i]); } if (!found) fprintf(stderr, "No receivers found.\n"); } static void usage(void) { printf("Usage: %s COMMAND /dev/hidrawX\n" "\n" "Commands:\n" " list ............. list devices connected to receiver\n" " open ............. open receiver for pairing (timeout 30s)\n" " close ............ close receiver if currently open\n" " disconnect N ..... disconnect device N\n" " find ............. find a receiver amongst the /dev/hidraw devices \n", program_invocation_short_name); } int main(int argc, char **argv) { struct lur_receiver *receiver; const char *path; const char *command; while (1) { int c; int option_index = 0; static struct option opts[] = { { "help", 0, 0, OPT_HELP }, }; c = getopt_long(argc, argv, "+h", opts, &option_index); if (c == -1) break; switch(c) { case 'h': case OPT_HELP: usage(); return EXIT_SUCCESS; default: usage(); return EXIT_FAILURE; } } if (argc < 2) { usage(); return EXIT_FAILURE; } command = argv[1]; if (streq(command, "find")) { find_receiver(); return EXIT_SUCCESS; } if (argc < 3) { usage(); return EXIT_FAILURE; } path = argv[argc - 1]; receiver = open_receiver(path); if (!receiver) { fprintf(stderr, "Failed to open receiver at %s\n", path); return EXIT_FAILURE; } if (streq(command, "list")) { list_connected_devices(receiver); } else if (streq(command, "open")) { lur_receiver_open(receiver, 0); } else if (streq(command, "close")) { lur_receiver_close(receiver); } else if (streq(command, "disconnect")) { int index; if (argc < 4) { usage(); return EXIT_FAILURE; } index = atoi(argv[2]); disconnect_device(receiver, index); } else { usage(); return EXIT_FAILURE; } lur_receiver_unref(receiver); return EXIT_SUCCESS; } libratbag-0.9/tools/lur-command.man000066400000000000000000000021301311552661500173650ustar00rootroot00000000000000.TH lur\-command 1 "@version@" lur\-command .SH NAME lur\-command \- list, pair and unpair Logitech Unifying devices .SH SYNOPSIS .B lur\-command find .br .B lur\-command list .RI < device > .br .B lur\-command open .RI < device > .br .B lur\-command close .RI < device > .br .B lur\-command disconnect .RI < index "> <" device > .SH DESCRIPTION .SS Common options .TP .BR \-h displays a short help message. .SS Listing receivers .TP .B find lists all identified receivers on the system (scanning .BI /dev/hidraw n devices). .SS Listing connected devices .TP .BR list " <" \fIdevice\fP > lists the devices connected to the given receiver device. .SS Pairing devices .TP .BR open " <" \fIdevice\fP > opens the given receiver for pairing, for 30s. .SS Closing a receiver .TP .BR close " <" \fIdevice\fP > closes the given receiver, if it is currently open for pairing. .SS Disconnecting a device .TP .BR disconnect " <" \fIindex\fP "> <" \fIdevice\fP > disconnects the device matching the given index from the given receiver. The index is as determined by the .B list command. .SH SEE ALSO .BR ratbag\-command (1) libratbag-0.9/tools/ratbag-command.c000066400000000000000000001446701311552661500175120ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "libratbag-version.h" #include "shared.h" enum options { OPT_VERSION, OPT_VERBOSE, OPT_HELP, }; enum errors { SUCCESS = 0, ERR_UNSUPPORTED = 1, /* device doesn't support function, or an index exceeds the device */ ERR_USAGE = 2, /* invalid commandline */ ERR_DEVICE = 3, /* invalid/missing device or command failed */ }; enum cmd_flags { FLAG_VERBOSE = 1 << 0, FLAG_VERBOSE_RAW = 1 << 1, /* flags used in ratbag_cmd */ FLAG_NEED_DEVICE = 1 << 10, FLAG_NEED_PROFILE = 1 << 11, FLAG_NEED_RESOLUTION = 1 << 12, FLAG_NEED_BUTTON = 1 << 13, FLAG_NEED_LED = 1 << 14, }; struct ratbag_cmd_options { enum cmd_flags flags; struct ratbag_device *device; struct ratbag_profile *profile; struct ratbag_resolution *resolution; struct ratbag_button *button; struct ratbag_led *led; }; struct ratbag_cmd { const char *name; int (*cmd)(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv); uint32_t flags; const struct ratbag_cmd *subcommands[]; }; static const struct ratbag_cmd *ratbag_commands; static void usage(void) { printf("%s [OPTIONS] {COMMAND} ... /path/to/device\n" "\n" "Query or change a device's settings:\n" "\n" "Options:\n" " --verbose Print debugging output\n" " --verbose=raw Print debugging output with protocol output.\n" " --help Print this help.\n" "\n" "General Commands:\n" " list List supported devices (does not take a device argument)\n" "\n" "Device Commands:\n" " info Print information about a device \n" "\n" "Profile Commands:\n" " profile active get Print the currently active profile\n" " profile active set N Set profile N as to the active profile\n" " profile enable N Enable profile N\n" " profile disable N Disable profile N\n" " profile N {COMMAND} Use profile N for COMMAND\n" "\n" "Resolution Commands\n" " Resolution commands work on the given profile, or on the\n" " active profile if none is given.\n" "\n" " resolution active get Print the currently active resolution\n" " resolution active set N Set resolution N as the active resolution\n" " resolution N {COMMAND} Use resolution N for COMMAND\n" "\n" "DPI Commands:\n" " DPI commands work on the given profile and resolution, or on the\n" " active resolution of the active profile if none are given.\n" "\n" " dpi get Print the dpi value\n" " dpi set N Set the dpi value to N\n" " rate get Print the report rate in Hz\n" " rate set N Set the report rate in N Hz\n" "\n" "Button Commands:\n" " Button commands work on the given profile, or on the\n" " active profile if none is given.\n" "\n" " button count Print the number of buttons\n" " button N action get Print the button action\n" " button N action set button B Set the button action to button B\n" " button N action set special S Set the button action to special action S\n" " button N action set macro ... Set the button action to the given macro \n" "\n" " Macro syntax:\n" " A macro is a series of key events or waiting periods.\n" " Keys must be specified in linux/input.h key names.\n" " KEY_A Press and release 'a'\n" " +KEY_A Press 'a'\n" " -KEY_A Release 'a'\n" " t300 Wait 300ms\n" "\n" "LED Commands:\n" " LED commands work on the given profile, or on the\n" " active profile if none is given.\n" "\n" " led N {COMMAND}\n" " get print the LED properties\n" " set {COMMAND} ... sets LED properties (one or more)\n" " mode [on|off|cycle|breathing] sets LED mode\n" " color COLOR sets LED's color in hex format\n" " rate N sets LED's effect rate in Hz\n" " brightness N sets LED's effect brightness\n" "\n" "Special Commands:\n" "These commands are for testing purposes and may be removed without notice\n" "\n" " switch-etekcity Switch the Etekcity mouse active profile\n" "\n" "Examples:\n" " %s profile active get\n" " %s profile 0 resolution active set 4\n" " %s profile 0 resolution 1 dpi get\n" " %s resolution 4 rate get\n" " %s dpi set 800\n" " %s profile 0 led 0 set mode on\n" " %s profile 0 led 0 set color ff00ff\n" " %s profile 0 led 0 set rate 20\n" "\n" "Exit codes:\n" " 0 Success\n" " 1 Unsupported feature or index out of available range\n" " 2 Commandline arguments are invalid\n" " 3 Invalid device or a command failed on the device\n" "\n", program_invocation_short_name, program_invocation_short_name, program_invocation_short_name, program_invocation_short_name, program_invocation_short_name, program_invocation_short_name, program_invocation_short_name, program_invocation_short_name, program_invocation_short_name); } static void version(void) { printf("%s\n", LIBRATBAG_VERSION); } static inline int ratbag_cmd_device_from_arg(struct ratbag *ratbag, int *argc, char **argv, struct ratbag_device **device_out) { struct ratbag_device *device; const char *path; if (*argc == 0) { error("Missing device path.\n"); return ERR_USAGE; } path = argv[*argc - 1]; device = ratbag_cmd_open_device(ratbag, path); if (!device) { error("Device '%s' is not supported\n", path); return ERR_DEVICE; } (*argc)--; *device_out = device; return SUCCESS; } static inline struct ratbag_profile * ratbag_cmd_get_active_profile(struct ratbag_device *device) { struct ratbag_profile *profile = NULL; for (unsigned int i = 0; i < ratbag_device_get_num_profiles(device); i++) { profile = ratbag_device_get_profile(device, i); if (ratbag_profile_is_active(profile)) return profile; ratbag_profile_unref(profile); profile = NULL; } if (!profile) error("Failed to retrieve the active profile\n"); return NULL; } static inline struct ratbag_resolution * ratbag_cmd_get_active_resolution(struct ratbag_profile *profile) { struct ratbag_resolution *resolution = NULL; for (unsigned int i = 0; i < ratbag_profile_get_num_resolutions(profile); i++) { resolution = ratbag_profile_get_resolution(profile, i); if (ratbag_resolution_is_active(resolution)) return resolution; ratbag_resolution_unref(resolution); resolution = NULL; } if (!resolution) error("Failed to retrieve the active resolution\n"); return NULL; } static inline int fill_options(struct ratbag *ratbag, struct ratbag_cmd_options *options, uint32_t flags, int *argc, char **argv) { struct ratbag_device *device = options->device; struct ratbag_profile *profile = options->profile; struct ratbag_resolution *resolution = options->resolution; struct ratbag_button *button = options->button; int rc; if ((flags & (FLAG_NEED_DEVICE|FLAG_NEED_PROFILE|FLAG_NEED_RESOLUTION|FLAG_NEED_LED)) && device == NULL) { rc = ratbag_cmd_device_from_arg(ratbag, argc, argv, &device); if (rc != SUCCESS) return rc; options->device = device; } if ((flags & (FLAG_NEED_PROFILE|FLAG_NEED_RESOLUTION|FLAG_NEED_LED)) && profile == NULL) { profile = ratbag_cmd_get_active_profile(device); if (!profile) return ERR_DEVICE; options->profile = profile; } if (flags & FLAG_NEED_RESOLUTION && resolution == NULL) { resolution = ratbag_cmd_get_active_resolution(profile); if (!resolution) return ERR_DEVICE; options->resolution = resolution; } if (flags & FLAG_NEED_BUTTON && button == NULL) { error("Missing button identifier\n"); return ERR_USAGE; } return SUCCESS; } static int run_subcommand(const char *command, const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { const struct ratbag_cmd *sub = cmd->subcommands[0]; int i = 0; int rc; while (sub) { if (streq(command, sub->name)) { rc = fill_options(ratbag, options, sub->flags, &argc, argv); if (rc != SUCCESS) return rc; argc--; argv++; return sub->cmd(sub, ratbag, options, argc, argv); } sub = cmd->subcommands[i++]; } error("Invalid subcommand '%s'\n", command); return ERR_USAGE; } static int ratbag_printf_led(struct ratbag_led *led, const char *prefix_format, ...) { va_list arg; enum ratbag_led_mode mode; struct ratbag_color color; int hz; int brightness; enum ratbag_led_type type; type = ratbag_led_get_type(led); mode = ratbag_led_get_mode(led); color = ratbag_led_get_color(led); hz = ratbag_led_get_effect_rate(led); brightness = ratbag_led_get_brightness(led); va_start(arg, prefix_format); vprintf(prefix_format, arg); va_end(arg); switch (mode) { case RATBAG_LED_OFF: printf("type: %s, mode: %s\n", led_type_to_str(type), led_mode_to_str(mode)); break; case RATBAG_LED_ON: printf("type: %s, mode: %s, color: %02x%02x%02x\n", led_type_to_str(type), led_mode_to_str(mode), color.red, color.green, color.blue); break; case RATBAG_LED_CYCLE: printf("type: %s, mode: %s, rate: %d, brightness: %d\n", led_type_to_str(type), led_mode_to_str(mode), hz, brightness); break; case RATBAG_LED_BREATHING: printf("type: %s, mode: %s, color: %02x%02x%02x, rate: %d, brightness: %d\n", led_type_to_str(type), led_mode_to_str(mode), color.red, color.green, color.blue, hz, brightness); break; } return SUCCESS; } static int ratbag_cmd_info(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_device *device; struct ratbag_profile *profile; struct ratbag_button *button; struct ratbag_led *led; char *action; int num_profiles, num_buttons, num_leds; int b, l; device = options->device; printf("Device '%s'\n", ratbag_device_get_name(device)); printf("Capabilities:"); if (ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_RESOLUTION)) printf(" res"); if (ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE)) printf(" profile"); if (ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_BUTTON_KEY)) printf(" btn-key"); if (ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_BUTTON_MACROS)) printf(" btn-macros"); if (ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_LED)) printf(" led"); printf("\n"); num_buttons = ratbag_device_get_num_buttons(device); printf("Number of buttons: %d\n", num_buttons); num_leds = ratbag_device_get_num_leds(device); printf("Number of leds: %d\n", num_leds); num_profiles = ratbag_device_get_num_profiles(device); printf("Profiles supported: %d\n", num_profiles); for (int i = 0; i < num_profiles; i++) { int dpi, rate; profile = ratbag_device_get_profile(device, i); if (!profile) continue; printf(" Profile %d (%s)%s\n", i, ratbag_profile_is_enabled(profile) ? "enabled" : "disabled", ratbag_profile_is_active(profile) ? " (active)" : ""); printf(" Resolutions:\n"); for (unsigned int j = 0; j < ratbag_profile_get_num_resolutions(profile); j++) { struct ratbag_resolution *res; res = ratbag_profile_get_resolution(profile, j); dpi = ratbag_resolution_get_dpi(res); rate = ratbag_resolution_get_report_rate(res); if (dpi == 0) printf(" %d: \n", j); else if (ratbag_resolution_has_capability(res, RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION)) printf(" %d: %dx%ddpi @ %dHz%s%s\n", j, ratbag_resolution_get_dpi_x(res), ratbag_resolution_get_dpi_y(res), rate, ratbag_resolution_is_active(res) ? " (active)" : "", ratbag_resolution_is_default(res) ? " (default)" : ""); else printf(" %d: %ddpi @ %dHz%s%s\n", j, dpi, rate, ratbag_resolution_is_active(res) ? " (active)" : "", ratbag_resolution_is_default(res) ? " (default)" : ""); ratbag_resolution_unref(res); } for (b = 0; b < num_buttons; b++) { enum ratbag_button_type type; button = ratbag_profile_get_button(profile, b); type = ratbag_button_get_type(button); action = button_action_to_str(button); printf(" Button: %d type %s is mapped to '%s'\n", b, button_type_to_str(type), action); free(action); button = ratbag_button_unref(button); } for (l = 0; l < num_leds; l++) { led = ratbag_profile_get_led(profile, l); ratbag_printf_led(led, " LED: %d ", l); led = ratbag_led_unref(led); } ratbag_profile_unref(profile); } return SUCCESS; } static const struct ratbag_cmd cmd_info = { .name = "info", .cmd = ratbag_cmd_info, .flags = FLAG_NEED_DEVICE, .subcommands = { NULL }, }; static int ratbag_cmd_switch_etekcity(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_device *device; struct ratbag_button *button_6, *button_7; struct ratbag_profile *profile = NULL; int commit = 0; unsigned int modifiers[10]; size_t modifiers_sz = 10; device = options->device; profile = options->profile; if (!ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE)) { error("Device '%s' has no switchable profiles\n", ratbag_device_get_name(device)); return ERR_UNSUPPORTED; } button_6 = ratbag_profile_get_button(profile, 6); button_7 = ratbag_profile_get_button(profile, 7); if (ratbag_button_get_key(button_6, modifiers, &modifiers_sz) == KEY_VOLUMEUP && ratbag_button_get_key(button_7, modifiers, &modifiers_sz) == KEY_VOLUMEDOWN) { ratbag_button_disable(button_6); ratbag_button_disable(button_7); commit = 1; } else if (ratbag_button_get_action_type(button_6) == RATBAG_BUTTON_ACTION_TYPE_NONE && ratbag_button_get_action_type(button_7) == RATBAG_BUTTON_ACTION_TYPE_NONE) { ratbag_button_set_key(button_6, KEY_VOLUMEUP, modifiers, 0); ratbag_button_set_key(button_7, KEY_VOLUMEDOWN, modifiers, 0); commit = 2; } button_6 = ratbag_button_unref(button_6); button_7 = ratbag_button_unref(button_7); printf("Switched the current profile of '%s' to %sreport the volume keys\n", ratbag_device_get_name(device), commit == 1 ? "not " : ""); return SUCCESS; } static const struct ratbag_cmd cmd_switch_etekcity = { .name = "switch-etekcity", .cmd = ratbag_cmd_switch_etekcity, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE, .subcommands = { NULL }, }; struct macro { const char *name; struct { enum ratbag_macro_event_type type; unsigned data; } events[64]; }; static int str_to_macro(const char *action_arg, struct macro *m) { char *str, *s; enum ratbag_macro_event_type type; int code; unsigned int idx = 0; int rc = ERR_USAGE; /* FIXME: handle per-device maximum lengths of macros */ if (!action_arg) return -EINVAL; str = strdup_safe(action_arg); s = str; m->name = ""; while (idx < ARRAY_LENGTH(m->events)) { char *token; token = strsep(&s, " "); if (!token) break; if (strlen(token) == 0) continue; switch(token[0]) { case '+': type = RATBAG_MACRO_EVENT_KEY_PRESSED; token++; break; case '-': type = RATBAG_MACRO_EVENT_KEY_RELEASED; token++; break; case 't': type = RATBAG_MACRO_EVENT_WAIT; token++; break; default: type = RATBAG_MACRO_EVENT_NONE; break; } if (type == RATBAG_MACRO_EVENT_WAIT) { char *endptr; code = strtol(token, &endptr, 10); if (*endptr != '\0') { error("Invalid token name: %s\n", token); goto out; } } else { code = libevdev_event_code_from_name(EV_KEY, token); if (code == -1) { error("Invalid token name: %s\n", token); goto out; } } if (type == RATBAG_MACRO_EVENT_NONE) { m->events[idx].type = RATBAG_MACRO_EVENT_KEY_PRESSED; m->events[idx].data = code; type = RATBAG_MACRO_EVENT_KEY_RELEASED; idx++; } m->events[idx].data = code; m->events[idx].type = type; idx++; } rc = SUCCESS; out: free(str); return rc; } static int ratbag_cmd_change_button(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { const char *action_str, *action_arg; struct ratbag_device *device; struct ratbag_button *button = NULL; struct ratbag_profile *profile = NULL; struct ratbag_button_macro *m = NULL; int button_index; enum ratbag_button_action_type action_type; int rc = ERR_DEVICE; unsigned int btnkey = 0; enum ratbag_button_action_special special; struct macro macro = {0}; if (argc != 3) return ERR_USAGE; button_index = atoi(argv[0]); action_str = argv[1]; action_arg = argv[2]; argc -= 3; argv += 3; if (streq(action_str, "button")) { action_type = RATBAG_BUTTON_ACTION_TYPE_BUTTON; btnkey = atoi(action_arg); } else if (streq(action_str, "key")) { action_type = RATBAG_BUTTON_ACTION_TYPE_KEY; btnkey = libevdev_event_code_from_name(EV_KEY, action_arg); if (!btnkey) { error("Failed to resolve key %s\n", action_arg); return ERR_USAGE; } } else if (streq(action_str, "special")) { action_type = RATBAG_BUTTON_ACTION_TYPE_SPECIAL; special = str_to_special_action(action_arg); if (special == RATBAG_BUTTON_ACTION_SPECIAL_INVALID) { error("Invalid special command '%s'\n", action_arg); return ERR_USAGE; } } else if (streq(action_str, "macro")) { action_type = RATBAG_BUTTON_ACTION_TYPE_MACRO; if (str_to_macro(action_arg, ¯o)) { error("Invalid special command '%s'\n", action_arg); return ERR_USAGE; } } else { return ERR_USAGE; } device = options->device; profile = options->profile; if (!ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_BUTTON_KEY)) { error("Device '%s' has no programmable buttons\n", ratbag_device_get_name(device)); rc = ERR_UNSUPPORTED; goto out; } button = ratbag_profile_get_button(profile, button_index); if (!button) { error("Invalid button number %d\n", button_index); rc = ERR_UNSUPPORTED; goto out; } switch (action_type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: rc = ratbag_button_set_button(button, btnkey); break; case RATBAG_BUTTON_ACTION_TYPE_KEY: rc = ratbag_button_set_key(button, btnkey, NULL, 0); break; case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: rc = ratbag_button_set_special(button, special); break; case RATBAG_BUTTON_ACTION_TYPE_MACRO: m = ratbag_button_macro_new(macro.name); for (size_t i = 0; i < ARRAY_LENGTH(macro.events); i++) { if (macro.events[i].type == RATBAG_MACRO_EVENT_NONE) break; ratbag_button_macro_set_event(m, i, macro.events[i].type, macro.events[i].data); } rc = ratbag_button_set_macro(button, m); ratbag_button_macro_unref(m); break; default: error("well, that shouldn't have happened\n"); abort(); break; } if (rc) { error("Unable to perform button %d mapping %s %s\n", button_index, action_str, action_arg); rc = ERR_UNSUPPORTED; goto out; } rc = ratbag_profile_set_active(profile); if (rc) { error("Unable to apply the current profile: %s (%d)\n", strerror(-rc), rc); rc = ERR_DEVICE; goto out; } out: button = ratbag_button_unref(button); return rc; } static const struct ratbag_cmd cmd_change_button = { .name = "change-button", .cmd = ratbag_cmd_change_button, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE, .subcommands = { NULL }, }; static int filter_event_node(const struct dirent *input_entry) { return strneq(input_entry->d_name, "event", 5); } static int ratbag_cmd_list_supported_devices(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct dirent **input_list; struct ratbag_device *device; char path[256]; int n, i; int supported = 0; if (argc != 0) return ERR_USAGE; n = scandir("/dev/input", &input_list, filter_event_node, alphasort); if (n < 0) return SUCCESS; i = -1; while (++i < n) { sprintf_safe(path, "/dev/input/%s", input_list[i]->d_name); device = ratbag_cmd_open_device(ratbag, path); if (device) { printf("%s:\t%s\n", path, ratbag_device_get_name(device)); ratbag_device_unref(device); supported++; } free(input_list[i]); } free(input_list); if (!supported) printf("No supported devices found\n"); return SUCCESS; } static const struct ratbag_cmd cmd_list = { .name = "list", .cmd = ratbag_cmd_list_supported_devices, .flags = 0, .subcommands = { NULL }, }; static int ratbag_cmd_resolution_active_set(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_resolution *resolution; int rc; resolution = options->resolution; rc = ratbag_resolution_set_active(resolution); if (rc != 0) { error("Failed to to set resolution as active\n"); return ERR_DEVICE; } return SUCCESS; } static const struct ratbag_cmd cmd_resolution_active_set = { .name = "set", .cmd = ratbag_cmd_resolution_active_set, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE, .subcommands = { NULL }, }; static int ratbag_cmd_resolution_active_get(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_profile *profile; struct ratbag_resolution *resolution = NULL; int num_resolutions; int active_resolution = -1; int i; int rc = SUCCESS; profile = options->profile; num_resolutions = ratbag_profile_get_num_resolutions(profile); for (i = 0; i < num_resolutions && active_resolution < 0; i++) { resolution = ratbag_profile_get_resolution(profile, i); if (ratbag_resolution_is_active(resolution)) active_resolution = i; ratbag_resolution_unref(resolution); } if (active_resolution < 0) { error("BUG: Unable to find active resolution\n"); rc = ERR_DEVICE; } if (rc == SUCCESS) printf("%d\n", active_resolution); return rc; } static const struct ratbag_cmd cmd_resolution_active_get = { .name = "get", .cmd = ratbag_cmd_resolution_active_get, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE, .subcommands = { NULL }, }; static int ratbag_cmd_resolution_active(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { if (argc < 1) return ERR_USAGE; return run_subcommand(argv[0], cmd, ratbag, options, argc, argv); } static const struct ratbag_cmd cmd_resolution_active = { .name = "active", .cmd = ratbag_cmd_resolution_active, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE, .subcommands = { &cmd_resolution_active_get, &cmd_resolution_active_set, NULL, }, }; static int ratbag_cmd_resolution_dpi_get(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_resolution *resolution; int dpi; resolution = options->resolution; dpi = ratbag_resolution_get_dpi(resolution); printf("%d\n", dpi); return SUCCESS; } static const struct ratbag_cmd cmd_resolution_dpi_get = { .name = "get", .cmd = ratbag_cmd_resolution_dpi_get, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_RESOLUTION, .subcommands = { NULL }, }; static int ratbag_cmd_resolution_dpi_set(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_device *device; struct ratbag_resolution *resolution; int rc = SUCCESS; int dpi; if (argc != 1) return ERR_USAGE; dpi = atoi(argv[0]); argc--; argv++; device = options->device; resolution = options->resolution; if (!ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_RESOLUTION)) { error("Device '%s' has no switchable resolution\n", ratbag_device_get_name(device)); rc = ERR_UNSUPPORTED; goto out; } rc = ratbag_resolution_set_dpi(resolution, dpi); if (rc) { error("Failed to change the dpi: %s (%d)\n", strerror(-rc), rc); rc = ERR_DEVICE; } out: return rc; } static const struct ratbag_cmd cmd_resolution_dpi_set = { .name = "set", .cmd = ratbag_cmd_resolution_dpi_set, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE| FLAG_NEED_RESOLUTION, .subcommands = { NULL }, }; static int ratbag_cmd_resolution_dpi(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { if (argc < 1) return ERR_USAGE; return run_subcommand(argv[0], cmd, ratbag, options, argc, argv); } static const struct ratbag_cmd cmd_resolution_dpi = { .name = "dpi", .cmd = ratbag_cmd_resolution_dpi, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE| FLAG_NEED_RESOLUTION, .subcommands = { &cmd_resolution_dpi_get, &cmd_resolution_dpi_set, NULL, }, }; static int ratbag_cmd_resolution_rate_get(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_resolution *resolution; int rate; resolution = options->resolution; rate = ratbag_resolution_get_report_rate(resolution); printf("%d\n", rate); return SUCCESS; } static const struct ratbag_cmd cmd_resolution_rate_get = { .name = "get", .cmd = ratbag_cmd_resolution_rate_get, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_RESOLUTION, .subcommands = { NULL }, }; static int ratbag_cmd_resolution_rate_set(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_device *device; struct ratbag_resolution *resolution; int rc = SUCCESS; int rate; if (argc != 1) return ERR_USAGE; rate = atoi(argv[0]); argc--; argv++; device = options->device; resolution = options->resolution; if (!ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_RESOLUTION)) { error("Device '%s' has no switchable resolution\n", ratbag_device_get_name(device)); rc = ERR_UNSUPPORTED; goto out; } rc = ratbag_resolution_set_report_rate(resolution, rate); if (rc) { error("Failed to change the rate: %s (%d)\n", strerror(-rc), rc); rc = ERR_DEVICE; } out: return rc; } static const struct ratbag_cmd cmd_resolution_rate_set = { .name = "set", .cmd = ratbag_cmd_resolution_rate_set, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE| FLAG_NEED_RESOLUTION, .subcommands = { NULL }, }; static int ratbag_cmd_resolution_rate(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { if (argc < 1) return ERR_USAGE; return run_subcommand(argv[0], cmd, ratbag, options, argc, argv); } static const struct ratbag_cmd cmd_resolution_rate = { .name = "rate", .cmd = ratbag_cmd_resolution_rate, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE| FLAG_NEED_RESOLUTION, .subcommands = { &cmd_resolution_rate_get, &cmd_resolution_rate_set, NULL, }, }; static int ratbag_cmd_resolution(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_profile *profile; struct ratbag_resolution *resolution; const char *command; int resolution_idx = 0; char *endp; if (argc < 1) return ERR_USAGE; command = argv[0]; profile = options->profile; resolution_idx = strtol(command, &endp, 10); if (command != endp && *endp == '\0') { resolution = ratbag_profile_get_resolution(profile, resolution_idx); if (!resolution) { error("Unable to retrieve resolution %d\n", resolution_idx); return ERR_UNSUPPORTED; } argc--; argv++; command = argv[0]; } else { resolution = ratbag_cmd_get_active_resolution(profile); if (!resolution) return ERR_DEVICE; } options->resolution = resolution; return run_subcommand(command, cmd, ratbag, options, argc, argv); } static const struct ratbag_cmd cmd_resolution = { .name = "resolution", .cmd = ratbag_cmd_resolution, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE, .subcommands = { &cmd_resolution_active, &cmd_resolution_dpi, &cmd_resolution_rate, NULL, }, }; static int ratbag_cmd_button_count(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_device *device; int num_buttons; device = options->device; num_buttons = ratbag_device_get_num_buttons(device); printf("%d\n", num_buttons); return SUCCESS; } static const struct ratbag_cmd cmd_button_count = { .name = "count", .cmd = ratbag_cmd_button_count, .flags = FLAG_NEED_DEVICE, .subcommands = { NULL, }, }; static int ratbag_cmd_button_get(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_button *button; enum ratbag_button_type type; const char *action; button = options->button; type = ratbag_button_get_type(button); action = button_action_to_str(button); printf("type %s to %s\n", button_type_to_str(type), action); return SUCCESS; } static const struct ratbag_cmd cmd_button_get = { .name = "get", .cmd = ratbag_cmd_button_get, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_BUTTON, .subcommands = { NULL, }, }; static int ratbag_cmd_button_set_button(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_device *device; struct ratbag_button *button; char *str, *endptr; int b; int rc; if (argc < 1) return ERR_USAGE; str = argv[0]; b = strtol(str, &endptr, 10); if (*endptr != '\0') return ERR_USAGE; device = options->device; if (!ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_BUTTON_KEY)) return ERR_UNSUPPORTED; button = options->button; rc = ratbag_button_set_button(button, b); if (rc != 0) return ERR_DEVICE; return SUCCESS; } static const struct ratbag_cmd cmd_button_set_button = { .name = "button", .cmd = ratbag_cmd_button_set_button, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_BUTTON, .subcommands = { NULL, }, }; static int ratbag_cmd_button_set_key(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_device *device; struct ratbag_button *button; int keycode; char *str; int rc; if (argc < 1) return ERR_USAGE; str = argv[0]; keycode = libevdev_event_code_from_name(EV_KEY, str); if (keycode == -1) { error("Failed to resolve keycode '%s'\n", str); return ERR_USAGE; } device = options->device; if (!ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_BUTTON_KEY)) return ERR_UNSUPPORTED; button = options->button; rc = ratbag_button_set_key(button, keycode, NULL, 0); if (rc != 0) return ERR_DEVICE; return SUCCESS; } static const struct ratbag_cmd cmd_button_set_key = { .name = "key", .cmd = ratbag_cmd_button_set_key, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_BUTTON, .subcommands = { NULL, }, }; static int ratbag_cmd_button_set_special(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_button *button; enum ratbag_button_action_special special; char *str; int rc; if (argc < 1) return ERR_USAGE; str = argv[0]; special = str_to_special_action(str); if (special == RATBAG_BUTTON_ACTION_SPECIAL_INVALID) { error("Invalid special identifier '%s'\n", str); return ERR_USAGE; } button = options->button; rc = ratbag_button_set_special(button, special); if (rc != 0) return ERR_DEVICE; return SUCCESS; } static const struct ratbag_cmd cmd_button_set_special = { .name = "special", .cmd = ratbag_cmd_button_set_special, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_BUTTON, .subcommands = { NULL, }, }; static int ratbag_cmd_button_set_macro(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_device *device; struct ratbag_button *button; struct ratbag_button_macro *m; struct macro macro = {0}; int rc; char macro_str[PATH_MAX] = {0}; if (argc < 1) return ERR_USAGE; for (int i = 0; i < argc; i++) { strncat(macro_str, argv[i], sizeof(macro_str) - strlen(macro_str) - 1); strcat(macro_str, " "); } if (str_to_macro(macro_str, ¯o) != 0) { error("Invalid macro string '%s'\n", macro_str); return ERR_USAGE; } device = options->device; if (!ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_BUTTON_MACROS)) return ERR_UNSUPPORTED; button = options->button; m = ratbag_button_macro_new(macro.name); for (size_t i = 0; i < ARRAY_LENGTH(macro.events); i++) { if (macro.events[i].type == RATBAG_MACRO_EVENT_NONE) break; ratbag_button_macro_set_event(m, i, macro.events[i].type, macro.events[i].data); } rc = ratbag_button_set_macro(button, m); ratbag_button_macro_unref(m); if (rc != 0) return ERR_DEVICE; return SUCCESS; } static const struct ratbag_cmd cmd_button_set_macro = { .name = "macro", .cmd = ratbag_cmd_button_set_macro, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_BUTTON, .subcommands = { NULL, }, }; static int ratbag_cmd_button_set(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { const char *command; if (argc < 1) return ERR_USAGE; command = argv[0]; return run_subcommand(command, cmd, ratbag, options, argc, argv); } static const struct ratbag_cmd cmd_button_set = { .name = "set", .cmd = ratbag_cmd_button_set, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_BUTTON, .subcommands = { &cmd_button_set_button, &cmd_button_set_key, &cmd_button_set_special, &cmd_button_set_macro, NULL, }, }; static int ratbag_cmd_button_action(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { const char *command; if (argc < 1) return ERR_USAGE; command = argv[0]; return run_subcommand(command, cmd, ratbag, options, argc, argv); } static const struct ratbag_cmd cmd_button_action = { .name = "action", .cmd = ratbag_cmd_button_action, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_BUTTON, .subcommands = { &cmd_button_get, &cmd_button_set, NULL, }, }; static int ratbag_cmd_button(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_profile *profile; struct ratbag_button *button; const char *command; int button_idx = 0; char *endp; if (argc < 1) return ERR_USAGE; profile = options->profile; command = argv[0]; button_idx = strtol(command, &endp, 10); if (command != endp && *endp == '\0') { button = ratbag_profile_get_button(profile, button_idx); if (!button) { error("Invalid button %d\n", button_idx); return ERR_UNSUPPORTED; } options->button = button; argc--; argv++; command = argv[0]; } return run_subcommand(command, cmd, ratbag, options, argc, argv); } static const struct ratbag_cmd cmd_button = { .name = "button", .cmd = ratbag_cmd_button, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE, .subcommands = { &cmd_button_count, &cmd_button_action, NULL, }, }; static int ratbag_cmd_led_set_brightness(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_led *led; int brightness; int rc; if (argc < 1) return ERR_USAGE; led = options->led; brightness = atoi(argv[0]); rc = ratbag_led_set_brightness(led, brightness); if (rc != 0) return ERR_DEVICE; if (argc == 1) return SUCCESS; return run_subcommand(argv[1], cmd, ratbag, options, argc - 1, argv + 1); } static const struct ratbag_cmd cmd_led_set_color; static const struct ratbag_cmd cmd_led_set_effect_rate; static const struct ratbag_cmd cmd_led_set_brightness = { .name = "brightness", .cmd = ratbag_cmd_led_set_brightness, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_LED, .subcommands = { &cmd_led_set_color, &cmd_led_set_effect_rate, NULL, }, }; static int ratbag_cmd_led_set_effect_rate(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_led *led; char *str, *endptr; int hz; int rc; if (argc < 1) return ERR_USAGE; led = options->led; str = argv[0]; hz = strtoul(str, &endptr, 10); if (*endptr != '\0') return ERR_USAGE; rc = ratbag_led_set_effect_rate(led, hz); if (rc != 0) return ERR_DEVICE; if (argc == 1) return SUCCESS; return run_subcommand(argv[1], cmd, ratbag, options, argc - 1, argv + 1); } static const struct ratbag_cmd cmd_led_set_effect_rate = { .name = "rate", .cmd = ratbag_cmd_led_set_effect_rate, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_LED, .subcommands = { &cmd_led_set_color, &cmd_led_set_brightness, NULL, }, }; static int ratbag_cmd_led_set_color(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_led *led; struct ratbag_color color; int rc; char red[3], green[3], blue[3]; char *p; if (argc < 1) return ERR_USAGE; led = options->led; sscanf(argv[0], "%2s%2s%2s", red, green, blue); color.red = strtoul(red, &p, 16); color.green = strtoul(green, &p, 16); color.blue = strtoul(blue, &p, 16); rc = ratbag_led_set_color(led, color); if (rc != 0) return ERR_DEVICE; if (argc == 1) return SUCCESS; return run_subcommand(argv[1], cmd, ratbag, options, argc - 1, argv + 1); } static const struct ratbag_cmd cmd_led_set_color = { .name = "color", .cmd = ratbag_cmd_led_set_color, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_LED, .subcommands = { &cmd_led_set_effect_rate, &cmd_led_set_brightness, NULL, }, }; static int ratbag_cmd_led_set_mode(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_led *led; struct ratbag_color color; enum ratbag_led_mode mode; char *str; int rc; if (argc < 1) return ERR_USAGE; led = options->led; str = argv[0]; if (streq(str, "off")) mode = RATBAG_LED_OFF; else if (streq(str, "on")) mode = RATBAG_LED_ON; else if (streq(str, "cycle")) mode = RATBAG_LED_CYCLE; else if (streq(str, "breathing")) mode = RATBAG_LED_BREATHING; else { usage(); return ERR_USAGE; } /* set default rate and brightness */ switch (mode) { case RATBAG_LED_ON: color.red = 255; color.green = 255; color.blue = 255; ratbag_led_set_color(led, color); break; case RATBAG_LED_CYCLE: case RATBAG_LED_BREATHING: ratbag_led_set_effect_rate(led, 20); ratbag_led_set_brightness(led, 100); break; case RATBAG_LED_OFF: break; } rc = ratbag_led_set_mode(led, mode); if (rc != 0) return ERR_DEVICE; if (argc == 1) return SUCCESS; return run_subcommand(argv[1], cmd, ratbag, options, argc - 1, argv + 1); } static const struct ratbag_cmd cmd_led_set_mode = { .name = "mode", .cmd = ratbag_cmd_led_set_mode, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_LED, .subcommands = { &cmd_led_set_color, &cmd_led_set_effect_rate, &cmd_led_set_brightness, NULL, }, }; static int ratbag_cmd_led_set(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { if (argc < 1) return ERR_USAGE; return run_subcommand(argv[0], cmd, ratbag, options, argc, argv); } static const struct ratbag_cmd cmd_led_set = { .name = "set", .cmd = ratbag_cmd_led_set, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_LED, .subcommands = { &cmd_led_set_mode, &cmd_led_set_color, &cmd_led_set_effect_rate, &cmd_led_set_brightness, NULL, }, }; static int ratbag_cmd_led_get(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_led *led = options->led; ratbag_printf_led(led, ""); return SUCCESS; } static const struct ratbag_cmd cmd_led_get = { .name = "get", .cmd = ratbag_cmd_led_get, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE | FLAG_NEED_LED, .subcommands = { NULL, }, }; static int ratbag_cmd_led(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_profile *profile; struct ratbag_led *led; int rc, index; if (argc < 1) return ERR_USAGE; profile = options->profile; index = atoi(argv[0]); led = ratbag_profile_get_led(profile, index); if (!led) { error("Invalid led %d\n", index); return ERR_UNSUPPORTED; } options->led = led; argc--; argv++; rc = run_subcommand(argv[0], cmd, ratbag, options, argc, argv); return rc; } static const struct ratbag_cmd cmd_led = { .name = "led", .cmd = ratbag_cmd_led, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE, .subcommands = { &cmd_led_get, &cmd_led_set, NULL, }, }; static int ratbag_cmd_profile_active_set(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_device *device; struct ratbag_profile *profile = NULL; int num_profiles, index; int rc = ERR_UNSUPPORTED; if (argc != 1) return ERR_USAGE; index = atoi(argv[0]); argc--; argv++; device = options->device; if (!ratbag_device_has_capability(device, RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE)) { error("Device '%s' has no switchable profiles\n", ratbag_device_get_name(device)); goto out; } num_profiles = ratbag_device_get_num_profiles(device); if (index >= num_profiles) { error("'%d' is not a valid profile\n", index); goto out; } profile = ratbag_device_get_profile(device, index); if (ratbag_profile_is_active(profile)) { rc = SUCCESS; goto out; } rc = ratbag_profile_set_active(profile); if (rc == 0) { printf("Switched '%s' to profile '%d'\n", ratbag_device_get_name(device), index); rc = SUCCESS; } else { rc = ERR_DEVICE; } out: profile = ratbag_profile_unref(profile); return rc; } static const struct ratbag_cmd cmd_profile_active_set = { .name = "set", .cmd = ratbag_cmd_profile_active_set, .flags = FLAG_NEED_DEVICE, .subcommands = { NULL }, }; static int ratbag_cmd_profile_active_get(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_device *device; struct ratbag_profile *profile = NULL; int i; int rc = SUCCESS; int active_profile = -1; int num_profiles = 0; device = options->device; num_profiles = ratbag_device_get_num_profiles(device); for (i = 0; i < num_profiles && active_profile < 0; i++) { profile = ratbag_device_get_profile(device, i); if (ratbag_profile_is_active(profile)) active_profile = i; ratbag_profile_unref(profile); } if (active_profile < 0) { error("BUG: Unable to find active profile.\n"); rc = ERR_DEVICE; } if (rc == SUCCESS) printf("%d\n", active_profile); return rc; } static const struct ratbag_cmd cmd_profile_active_get = { .name = "get", .cmd = ratbag_cmd_profile_active_get, .flags = FLAG_NEED_DEVICE, .subcommands = { NULL }, }; static int ratbag_cmd_profile_active(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { if (argc < 1) return ERR_USAGE; return run_subcommand(argv[0], cmd, ratbag, options, argc, argv); } static const struct ratbag_cmd cmd_profile_active = { .name = "active", .cmd = ratbag_cmd_profile_active, .flags = FLAG_NEED_DEVICE, .subcommands = { &cmd_profile_active_get, &cmd_profile_active_set, NULL, }, }; static int ratbag_cmd_profile_enable(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { int rc; if (!ratbag_device_has_capability(options->device, RATBAG_DEVICE_CAP_DISABLE_PROFILE)) return ERR_UNSUPPORTED; ratbag_profile_set_enabled(options->profile, true); rc = ratbag_device_commit(options->device); return rc ? ERR_DEVICE : SUCCESS; } static const struct ratbag_cmd cmd_profile_enable = { .name = "enable", .cmd = ratbag_cmd_profile_enable, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE, .subcommands = { NULL, }, }; static int ratbag_cmd_profile_disable(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { int rc; if (!ratbag_device_has_capability(options->device, RATBAG_DEVICE_CAP_DISABLE_PROFILE)) return ERR_UNSUPPORTED; ratbag_profile_set_enabled(options->profile, false); rc = ratbag_device_commit(options->device); return rc ? ERR_DEVICE : SUCCESS; } static const struct ratbag_cmd cmd_profile_disable = { .name = "disable", .cmd = ratbag_cmd_profile_disable, .flags = FLAG_NEED_DEVICE | FLAG_NEED_PROFILE, .subcommands = { NULL, }, }; static int ratbag_cmd_profile(const struct ratbag_cmd *cmd, struct ratbag *ratbag, struct ratbag_cmd_options *options, int argc, char **argv) { struct ratbag_profile *profile; struct ratbag_device *device; const char *command; int profile_idx = 0; char *endp; device = options->device; if (argc < 1) return ERR_USAGE; command = argv[0]; profile_idx = strtol(command, &endp, 10); if (command != endp && *endp == '\0') { profile = ratbag_device_get_profile(device, profile_idx); if (!profile) { error("Unable to find profile %d\n", profile_idx); return ERR_UNSUPPORTED; } argc--; argv++; command = argv[0]; } else { profile = ratbag_cmd_get_active_profile(device); if (!profile) return ERR_DEVICE; } options->profile = profile; return run_subcommand(command, cmd, ratbag, options, argc, argv); } static const struct ratbag_cmd cmd_profile = { .name = "profile", .cmd = ratbag_cmd_profile, .flags = FLAG_NEED_DEVICE, .subcommands = { &cmd_profile_active, &cmd_profile_enable, &cmd_profile_disable, &cmd_resolution, &cmd_button, &cmd_led, NULL, }, }; static const struct ratbag_cmd top_level_commands = { .name = "ratbag-command", .cmd = NULL, .subcommands = { &cmd_info, &cmd_list, &cmd_change_button, &cmd_switch_etekcity, &cmd_button, &cmd_resolution, &cmd_profile, &cmd_resolution_dpi, &cmd_resolution_rate, &cmd_led, NULL, }, }; static const struct ratbag_cmd *ratbag_commands = &top_level_commands; int main(int argc, char **argv) { struct ratbag *ratbag; const char *command; int rc = SUCCESS; struct ratbag_cmd_options options = {0}; ratbag = ratbag_create_context(&interface, NULL); if (!ratbag) { rc = ERR_DEVICE; error("Failed to initialize ratbag\n"); goto out; } options.flags = 0; while (1) { int c; int option_index = 0; static struct option opts[] = { { "verbose", optional_argument, 0, OPT_VERBOSE }, { "version", no_argument, 0, OPT_VERSION }, { "help", no_argument, 0, OPT_HELP }, { 0, 0, 0, 0 }, }; c = getopt_long(argc, argv, "+h", opts, &option_index); if (c == -1) break; switch(c) { case 'h': case OPT_HELP: usage(); goto out; case OPT_VERSION: version(); goto out; case OPT_VERBOSE: if (optarg && streq(optarg, "raw")) options.flags |= FLAG_VERBOSE_RAW; else options.flags |= FLAG_VERBOSE; break; default: goto out; } } if (optind >= argc) { rc = ERR_USAGE; goto out; } if (options.flags & FLAG_VERBOSE_RAW) ratbag_log_set_priority(ratbag, RATBAG_LOG_PRIORITY_RAW); else if (options.flags & FLAG_VERBOSE) ratbag_log_set_priority(ratbag, RATBAG_LOG_PRIORITY_DEBUG); argc -= optind; argv += optind; command = argv[0]; rc = run_subcommand(command, ratbag_commands, ratbag, &options, argc, argv); if (options.device) rc = ratbag_device_commit(options.device); out: ratbag_resolution_unref(options.resolution); ratbag_button_unref(options.button); ratbag_led_unref(options.led); ratbag_profile_unref(options.profile); ratbag_device_unref(options.device); ratbag_unref(ratbag); if (rc == ERR_USAGE) usage(); return rc; } libratbag-0.9/tools/ratbag-command.man000066400000000000000000000061521311552661500200330ustar00rootroot00000000000000.TH RATBAG_COMMAND "1" "@version@" .SH NAME ratbag\-command \- query or change a device's settings .SH SYNOPSIS .B ratbag\-command [OPTIONS] {COMMAND} ... /path/to/device .SH DESCRIPTION .PP The .I ratbag\-command tool queries or changes a device's settings. .PP This tool usually needs to be run as root to have access to the /dev/input/eventX nodes. .SH OPTIONS .TP 8 .B \-\-verbose Print debugging output. .TP 8 .B \-\-verbose=raw Print debugging output with protocol output. No guarantees are given regarding the output format when this flag is on. .TP 8 .B \-\-version Print the tool's version. .TP 8 .B \-\-help Print the help. .SH General Commands .TP 8 .B list List supported devices (does not take a device argument) .SH Device Commands .TP 8 .B info Print information about a device .SH Profile Commands .TP 8 .B profile active get Print the currently active profile .TP 8 .B profile active set N Set profile N as to the active profile .TP 8 .B profile N {COMMAND} Use profile N for COMMAND .SH Resolution Commands Resolution commands work on the given profile, or on the active profile if none is given. .TP 8 .B resolution active get Print the currently active resolution .TP 8 .B resolution active set N Set resolution N as the active resolution .TP 8 .B resolution N {COMMAND} Use resolution N for COMMAND .SH DPI Commands DPI commands work on the given profile and resolution, or on the active resolution of the active profile if none are given. .TP 8 .B dpi get Print the dpi value .TP 8 .B dpi set N Set the dpi value to N .TP 8 .B rate get Print the report rate in Hz .TP 8 .B rate set N Set the report rate in N Hz .SH Button Commands Button commands work on the given profile, or on the active profile if none is given. .TP 8 .B button count Print the number of buttons .TP 8 .B button N action get Print the button action .TP 8 .B button N action set button B Set the button action to button B .TP 8 .B button N action set special S Set the button action to special action S .TP 8 .B button N action set macro ... Set the button action to the given macro .PP .B Macro syntax: .HP 8 A macro is a series of key events or waiting periods. Keys must be specified in linux/input.h key names. .RS .TP 8 .B KEY_A Press and release 'a' .TP 8 .B +KEY_A Press 'a' .TP 8 .B \-KEY_A Release 'a' .TP 8 .B t300 Wait 300ms .RE .SH Special Commands These commands are for testing purposes and may be removed without notice .TP 8 .B switch\-etekcity Switch the Etekcity mouse active profile .SH Examples .TP 8 ratbag\-command profile active get .TP 8 ratbag\-command profile 0 resolution active set 4 .TP 8 ratbag\-command profile 0 resolution 1 dpi get .TP 8 ratbag\-command resolution 4 rate get .TP 8 ratbag\-command dpi set 800 .SH Exit codes .TP 8 .B 0 Success .TP 8 .B 1 Unsupported feature or index out of available range .TP 8 .B 2 Commandline arguments are invalid .TP 8 .B 3 Invalid device or a command failed on the device .SH NOTES .PP There is currently no guarantees that the output format of .B ratbag\-command will not change in the future. There should be some stability with the commands mentioned in this man page, but do not expect it to stay the same. libratbag-0.9/tools/shared.c000066400000000000000000000231441311552661500160740ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "shared.h" struct udev_device* udev_device_from_path(struct udev *udev, const char *path) { struct udev_device *udev_device; const char *event_node_prefix = "/dev/input/event"; if (strneq(path, event_node_prefix, strlen(event_node_prefix))) { struct stat st; if (stat(path, &st) == -1) { error("Failed to stat '%s': %s\n", path, strerror(errno)); return NULL; } udev_device = udev_device_new_from_devnum(udev, 'c', st.st_rdev); } else { udev_device = udev_device_new_from_syspath(udev, path); } if (!udev_device) { error("Can't open '%s': %s\n", path, strerror(errno)); return NULL; } return udev_device; } const char* led_type_to_str(enum ratbag_led_type type) { const char *str = "UNKNOWN"; switch(type) { case RATBAG_LED_TYPE_UNKNOWN: str = "unknown"; break; case RATBAG_LED_TYPE_LOGO: str = "logo"; break; case RATBAG_LED_TYPE_SIDE: str = "side"; break; } return str; } const char * led_mode_to_str(enum ratbag_led_mode mode) { const char *str = "UNKNOWN"; switch (mode) { case RATBAG_LED_OFF: str = "off"; break; case RATBAG_LED_ON: str = "on"; break; case RATBAG_LED_CYCLE: str = "cycle"; break; case RATBAG_LED_BREATHING: str = "breathing"; break; } return str; } const char* button_type_to_str(enum ratbag_button_type type) { const char *str = "UNKNOWN"; switch(type) { case RATBAG_BUTTON_TYPE_UNKNOWN: str = "unknown"; break; case RATBAG_BUTTON_TYPE_LEFT: str = "left"; break; case RATBAG_BUTTON_TYPE_MIDDLE: str = "middle"; break; case RATBAG_BUTTON_TYPE_RIGHT: str = "right"; break; case RATBAG_BUTTON_TYPE_THUMB: str = "thumb"; break; case RATBAG_BUTTON_TYPE_THUMB2: str = "thumb2"; break; case RATBAG_BUTTON_TYPE_THUMB3: str = "thumb3"; break; case RATBAG_BUTTON_TYPE_THUMB4: str = "thumb4"; break; case RATBAG_BUTTON_TYPE_WHEEL_LEFT: str = "wheel left"; break; case RATBAG_BUTTON_TYPE_WHEEL_RIGHT: str = "wheel right"; break; case RATBAG_BUTTON_TYPE_WHEEL_CLICK: str = "wheel click"; break; case RATBAG_BUTTON_TYPE_WHEEL_UP: str = "wheel up"; break; case RATBAG_BUTTON_TYPE_WHEEL_DOWN: str = "wheel down"; break; case RATBAG_BUTTON_TYPE_WHEEL_RATCHET_MODE_SHIFT: str = "wheel ratchet mode switch"; break; case RATBAG_BUTTON_TYPE_EXTRA: str = "extra (forward)"; break; case RATBAG_BUTTON_TYPE_SIDE: str = "side (backward)"; break; case RATBAG_BUTTON_TYPE_PINKIE: str = "pinkie"; break; case RATBAG_BUTTON_TYPE_PINKIE2: str = "pinkie2"; break; /* DPI switch */ case RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP: str = "resolution cycle up"; break; case RATBAG_BUTTON_TYPE_RESOLUTION_UP: str = "resolution up"; break; case RATBAG_BUTTON_TYPE_RESOLUTION_DOWN: str = "resolution down"; break; /* Profile */ case RATBAG_BUTTON_TYPE_PROFILE_CYCLE_UP: str = "profile cycle up"; break; case RATBAG_BUTTON_TYPE_PROFILE_UP: str = "profile up"; break; case RATBAG_BUTTON_TYPE_PROFILE_DOWN: str = "profile down"; break; } return str; } static const struct map { enum ratbag_button_action_special special; const char *str; } special_map[] = { { RATBAG_BUTTON_ACTION_SPECIAL_UNKNOWN, "unknown" }, { RATBAG_BUTTON_ACTION_SPECIAL_DOUBLECLICK, "doubleclick" }, /* Wheel mappings */ { RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT, "wheel left" }, { RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT, "wheel right" }, { RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP, "wheel up" }, { RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN, "wheel down" }, { RATBAG_BUTTON_ACTION_SPECIAL_RATCHET_MODE_SWITCH, "ratchet mode switch" }, /* DPI switch */ { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP, "resolution cycle up" }, { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_DOWN, "resolution cycle down" }, { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP, "resolution up" }, { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN, "resolution down" }, { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE, "resolution alternate" }, { RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT, "resolution default" }, /* Profile */ { RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP, "profile cycle up" }, { RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_DOWN, "profile cycle down" }, { RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP, "profile up" }, { RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN, "profile down" }, /* Second mode for buttons */ { RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE, "secondary mode" }, /* battery level */ { RATBAG_BUTTON_ACTION_SPECIAL_BATTERY_LEVEL, "battery level" }, /* must be the last entry in the table */ { RATBAG_BUTTON_ACTION_SPECIAL_INVALID, NULL }, }; const char * button_action_special_to_str(struct ratbag_button *button) { enum ratbag_button_action_special special; const struct map *m = special_map; special = ratbag_button_get_special(button); while (m->special != RATBAG_BUTTON_ACTION_SPECIAL_INVALID) { if (m->special == special) return m->str; m++; } return "UNKNOWN"; } char * button_action_button_to_str(struct ratbag_button *button) { char str[96]; sprintf_safe(str, "button %d", ratbag_button_get_button(button)); return strdup_safe(str); } char * button_action_key_to_str(struct ratbag_button *button) { const char *str; unsigned int modifiers[10]; size_t m_size = 10; str = libevdev_event_code_get_name(EV_KEY, ratbag_button_get_key(button, modifiers, &m_size)); if (!str) str = "UNKNOWN"; return strdup_safe(str); } static const char *strip_ev_key(int key) { const char *str = libevdev_event_code_get_name(EV_KEY, key); if (strneq(str, "KEY_", 4)) return str + 4; return str; }; char * button_action_macro_to_str(struct ratbag_button *button) { struct ratbag_button_macro *macro; char str[4096] = {0}; const char *name; int offset; unsigned int i; macro = ratbag_button_get_macro(button); name = ratbag_button_macro_get_name(macro); offset = snprintf(str, sizeof(str), "macro \"%s\":", name ? name : "UNKNOWN"); for (i = 0; i < MAX_MACRO_EVENTS; i++) { enum ratbag_macro_event_type type = ratbag_button_macro_get_event_type(macro, i); int key = ratbag_button_macro_get_event_key(macro, i); int timeout = ratbag_button_macro_get_event_timeout(macro, i); if (type == RATBAG_MACRO_EVENT_NONE) break; switch (type) { case RATBAG_MACRO_EVENT_KEY_PRESSED: offset += snprintf(str + offset, sizeof(str) - offset, " %s↓", strip_ev_key(key)); break; case RATBAG_MACRO_EVENT_KEY_RELEASED: offset += snprintf(str + offset, sizeof(str) - offset, " %s↑", strip_ev_key(key)); break; case RATBAG_MACRO_EVENT_WAIT: offset += snprintf(str + offset, sizeof(str) - offset, " %.03f⏱", timeout / 1000.0); break; default: offset += snprintf(str + offset, sizeof(str) - offset, " ###"); } } ratbag_button_macro_unref(macro); return strdup_safe(str); } char * button_action_to_str(struct ratbag_button *button) { enum ratbag_button_action_type type; char *str; type = ratbag_button_get_action_type(button); switch (type) { case RATBAG_BUTTON_ACTION_TYPE_BUTTON: str = button_action_button_to_str(button); break; case RATBAG_BUTTON_ACTION_TYPE_KEY: str = button_action_key_to_str(button); break; case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: str = strdup_safe(button_action_special_to_str(button)); break; case RATBAG_BUTTON_ACTION_TYPE_MACRO: str = button_action_macro_to_str(button); break; case RATBAG_BUTTON_ACTION_TYPE_NONE: str = strdup_safe("none"); break; default: error("type %d unknown\n", type); str = strdup_safe("UNKNOWN"); } return str; } struct ratbag_device * ratbag_cmd_open_device(struct ratbag *ratbag, const char *path) { struct ratbag_device *device; _cleanup_udev_unref_ struct udev *udev = NULL; _cleanup_udev_device_unref_ struct udev_device *udev_device = NULL; enum ratbag_error_code error; udev = udev_new(); udev_device = udev_device_from_path(udev, path); if (!udev_device) return NULL; error = ratbag_device_new_from_udev_device(ratbag, udev_device, &device); if (error != RATBAG_SUCCESS) return NULL; return device; } enum ratbag_button_action_special str_to_special_action(const char *str) { const struct map *m = special_map; while (m->str) { if (streq(m->str, str)) return m->special; m++; } return RATBAG_BUTTON_ACTION_SPECIAL_INVALID; } static int open_restricted(const char *path, int flags, void *user_data) { int fd = open(path, flags); if (fd < 0) error("Failed to open %s (%s)\n", path, strerror(errno)); return fd < 0 ? -errno : fd; } static void close_restricted(int fd, void *user_data) { close(fd); } const struct ratbag_interface interface = { .open_restricted = open_restricted, .close_restricted = close_restricted, }; libratbag-0.9/tools/shared.h000066400000000000000000000047141311552661500161030ustar00rootroot00000000000000/* * Copyright © 2015 Red Hat, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #pragma once #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_MACRO_EVENTS 256 LIBRATBAG_ATTRIBUTE_PRINTF(1, 2) static inline void error(const char *format, ...) { va_list args; fprintf(stderr, "Error: "); va_start(args, format); vfprintf(stderr, format, args); va_end(args); } struct udev_device* udev_device_from_path(struct udev *udev, const char *path); const char* button_type_to_str(enum ratbag_button_type type); const char* led_type_to_str(enum ratbag_led_type type); const char * led_mode_to_str(enum ratbag_led_mode mode); const char * button_action_special_to_str(struct ratbag_button *button); char * button_action_button_to_str(struct ratbag_button *button); char * button_action_key_to_str(struct ratbag_button *button); char * button_action_to_str(struct ratbag_button *button); char * button_action_macro_to_str(struct ratbag_button *button); enum ratbag_button_action_special str_to_special_action(const char *str); struct ratbag_device * ratbag_cmd_open_device(struct ratbag *ratbag, const char *path); const struct ratbag_interface interface; libratbag-0.9/tools/test-ratbag-command.sh000077500000000000000000000037621311552661500206560ustar00rootroot00000000000000#!/bin/bash # # Script to test ratbag-command for valid argument parsing. Must be run # against a device file. # command="`dirname $0`/ratbag-command" device=$1 if ! [[ -e "$device" ]]; then echo "Invalid device path or path missing" exit 1 fi # no device argument cmds=() cmds+=("list") for i in "${cmds[@]}"; do echo "Testing arguments '$i $device'" eval $command "$i" if [[ $? == 2 ]]; then echo "Invalid command: $i" exit 1 fi sleep 1 done # commands with device argument cmds=() cmds+=("info") cmds+=("switch-etekcity") cmds+=("profile active get") cmds+=("profile active set 0") cmds+=("resolution active get") cmds+=("resolution active set 0") cmds+=("dpi get") cmds+=("dpi set 800") cmds+=("rate get") cmds+=("rate set 500") cmds+=("profile 0 resolution active get") cmds+=("profile 0 resolution active set 0") cmds+=("resolution 0 dpi get") cmds+=("resolution 0 dpi set 800") cmds+=("resolution 0 rate get") cmds+=("resolution 0 rate set 500") cmds+=("profile 0 resolution 0 dpi get") cmds+=("profile 0 resolution 0 dpi set 800") cmds+=("profile 0 resolution 0 rate get") cmds+=("profile 0 resolution 0 rate set 500") cmds+=("button count") cmds+=("profile 0 button count") cmds+=("button 0 action get") cmds+=("button 0 action set button 1") cmds+=("profile 0 button 0 action get") cmds+=("profile 0 button 0 action set button 1") cmds+=("button 0 action set key KEY_ENTER") cmds+=("profile 0 button 0 action set key KEY_ENTER") cmds+=("button 0 action set special doubleclick") cmds+=("profile 0 button 0 action set special doubleclick") cmds+=("profile 0 button 0 action set macro +KEY_ENTER t05 -KEY_ENTER") cmds+=("profile 0 led 0 get") cmds+=("profile 0 led side get") cmds+=("profile 0 led 0 set mode on") cmds+=("profile 0 led 0 set color ffffff") cmds+=("profile 0 led 0 set rate 1") cmds+=("profile 0 led 0 set brightness 1") for i in "${cmds[@]}"; do echo "Testing arguments '$i $device'" eval $command "$i" $device if [[ $? == 2 ]]; then echo "Invalid command: $i" exit 1 fi sleep 1 done