├── .github └── workflows │ └── main.yml ├── .gitignore ├── Changelog.md ├── LICENSE.txt ├── README.md ├── VoodooInput.xcodeproj └── project.pbxproj └── VoodooInput ├── Info.plist ├── Scripts └── bootstrap.sh ├── Trackpoint ├── TrackpointDevice.cpp └── TrackpointDevice.hpp ├── VoodooInput.cpp ├── VoodooInput.hpp ├── VoodooInputIDs.hpp ├── VoodooInputMultitouch ├── MultitouchHelpers.h ├── VoodooInputEvent.h ├── VoodooInputMessages.h └── VoodooInputTransducer.h └── VoodooInputSimulator ├── VoodooInputActuatorDevice.cpp ├── VoodooInputActuatorDevice.hpp ├── VoodooInputSimulatorDevice.cpp └── VoodooInputSimulatorDevice.hpp /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | release: 8 | types: [published] 9 | 10 | env: 11 | PROJECT_TYPE: KEXT 12 | 13 | jobs: 14 | build: 15 | name: Build 16 | runs-on: macos-12 17 | env: 18 | JOB_TYPE: BUILD 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: actions/checkout@v4 22 | with: 23 | repository: acidanthera/MacKernelSDK 24 | path: MacKernelSDK 25 | - name: CI Bootstrap 26 | run: | 27 | src=$(/usr/bin/curl -Lfs https://raw.githubusercontent.com/acidanthera/ocbuild/master/ci-bootstrap.sh) && eval "$src" || exit 1 28 | 29 | - run: xcodebuild -jobs 1 -configuration Debug 30 | - run: xcodebuild -jobs 1 -configuration Release 31 | 32 | - name: Upload to Artifacts 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: Artifacts 36 | path: build/*/*.zip 37 | - name: Upload to Release 38 | if: github.event_name == 'release' 39 | uses: svenstaro/upload-release-action@v2 40 | with: 41 | repo_token: ${{ secrets.GITHUB_TOKEN }} 42 | file: build/*/*.zip 43 | tag: ${{ github.ref }} 44 | file_glob: true 45 | 46 | analyze-clang: 47 | name: Analyze Clang 48 | runs-on: macos-latest 49 | env: 50 | JOB_TYPE: ANALYZE 51 | steps: 52 | - uses: actions/checkout@v4 53 | - uses: actions/checkout@v4 54 | with: 55 | repository: acidanthera/MacKernelSDK 56 | path: MacKernelSDK 57 | - name: CI Bootstrap 58 | run: | 59 | src=$(/usr/bin/curl -Lfs https://raw.githubusercontent.com/acidanthera/ocbuild/master/ci-bootstrap.sh) && eval "$src" || exit 1 60 | 61 | - run: xcodebuild analyze -quiet -scheme VoodooInput -configuration Debug CLANG_ANALYZER_OUTPUT=plist-html CLANG_ANALYZER_OUTPUT_DIR="$(pwd)/clang-analyze" && [ "$(find clang-analyze -name "*.html")" = "" ] 62 | - run: xcodebuild analyze -quiet -scheme VoodooInput -configuration Release CLANG_ANALYZER_OUTPUT=plist-html CLANG_ANALYZER_OUTPUT_DIR="$(pwd)/clang-analyze" && [ "$(find clang-analyze -name "*.html")" = "" ] 63 | 64 | analyze-coverity: 65 | name: Analyze Coverity 66 | runs-on: macos-latest 67 | env: 68 | JOB_TYPE: COVERITY 69 | if: github.repository_owner == 'acidanthera' && github.event_name != 'pull_request' 70 | steps: 71 | - uses: actions/checkout@v4 72 | - uses: actions/checkout@v4 73 | with: 74 | repository: acidanthera/MacKernelSDK 75 | path: MacKernelSDK 76 | - name: CI Bootstrap 77 | run: | 78 | src=$(/usr/bin/curl -Lfs https://raw.githubusercontent.com/acidanthera/ocbuild/master/ci-bootstrap.sh) && eval "$src" || exit 1 79 | 80 | - name: Run Coverity 81 | run: | 82 | src=$(/usr/bin/curl -Lfs https://raw.githubusercontent.com/acidanthera/ocbuild/master/coverity/covstrap.sh) && eval "$src" || exit 1 83 | env: 84 | COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} 85 | COVERITY_SCAN_EMAIL: ${{ secrets.COVERITY_SCAN_EMAIL }} 86 | COVERITY_BUILD_COMMAND: xcodebuild -configuration Release 87 | 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Build 2 | DerivedData 3 | Distribute 4 | *.xccheckout 5 | .DS_Store 6 | xcuserdata 7 | xcshareddata 8 | project.xcworkspace 9 | /MacKernelSDK 10 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | VoodooInput Changelog 2 | ===================== 3 | #### v1.1.6 4 | - Lowered macOS requirements to 10.10 5 | 6 | #### v1.1.5 7 | - Use MacbookAir10,1 ID instead of SPI touchpad ID on macOS 12+ 8 | 9 | #### v1.1.4 10 | - Added palm/invalid finger type 11 | 12 | #### v1.1.3 13 | - Added trackpoint logic improvements by @1Revenger1 14 | - Seperated X and Y axis 15 | - Switched to higher resolution (400) 16 | - Added divisor for scroll and mouse movement 17 | - Workaround crashes with USB devices on macOS 13, thx @kprinssu 18 | 19 | #### v1.1.2 20 | - Improved touch state abstraction 21 | 22 | #### v1.1.1 23 | - Remove angle calculation 24 | 25 | #### v1.1.0 26 | - Eliminate leftover scaling of physical dimensions by x10 27 | 28 | #### v1.0.9 29 | - Add trackpoint device from VoodooTrackpoint 30 | 31 | #### v1.0.8 32 | - Initial MacKernelSDK and Xcode 12 compatibility 33 | 34 | #### v1.0.7 35 | - Allowed to set finger type externally to fix swiping desktops when holding a dragged item 36 | - Added a message to allow client to set gesture orientation when rotating a touchscreen (thx @Goshin) 37 | 38 | #### v1.0.6 39 | - Reduced memory consumption and CPU usage 40 | - Fixed dragging issues on some touchpads 41 | 42 | #### v1.0.5 43 | - Don't read extra finger if there is no stylus 44 | - Improve handling of slow to respond devices 45 | 46 | #### v1.0.4 47 | - Improved compatibility with MT2 emulation (thx @Goshin) 48 | - Improved compatibility with VoodooI2C (thx @kprinssu) 49 | - Fix dragging/selection instability while touchpad button is pressed 50 | - Bundle SDK in resources for DEBUG builds 51 | 52 | #### v1.0.3 53 | - Fixed interpreting transducer type data 54 | 55 | #### v1.0.2 56 | - Minor deployment fixes 57 | - Resolved loading issues on some configurations (thx @Sniki) 58 | 59 | #### v1.0.1 60 | - Added support for macOS versions prior to 10.15 61 | 62 | #### v1.0.0 63 | - Initial release 64 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | VoodooInput 2 | =========== 3 | 4 | [![Build Status](https://github.com/acidanthera/VoodooInput/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/acidanthera/VoodooInput/actions) [![Scan Status](https://scan.coverity.com/projects/22196/badge.svg?flat=1)](https://scan.coverity.com/projects/22196) 5 | 6 | An opensource trackpad aggregator kernel extension providing Magic Trackpad 2 software emulation for arbitrary input sources like [VoodooPS2](https://github.com/acidanthera/VoodooPS2). 7 | 8 | **WARNING**: This kernel extension is designed to be bundled with a dedicated multitouch client driver and exists for the developers only. 9 | Please download the latest version of [VoodooPS2](https://github.com/acidanthera/VoodooPS2/releases) or [VoodooI2C](https://github.com/VoodooI2C/VoodooI2C/releases) 10 | to make use of this kext. 11 | 12 | #### Credits 13 | - [Apple](https://www.apple.com) for macOS 14 | - [VoodooI2C](https://github.com/alexandred/VoodooI2C) [Team](https://github.com/alexandred/VoodooI2C/graphs/contributors) ([alexandred](https://github.com/alexandred), [ben9923](https://github.com/ben9923), [blankmac](https://github.com/blankmac), [coolstar](https://github.com/coolstar), and others) for Magic Trackpad 2 reverse engineering, implementation, and reference example 15 | - [kprinssu](https://github.com/kprinssu) for writing the majority of this project and initial trackpad integration 16 | - [usr-sse2](https://github.com/usr-sse2) for further additions and force touch support 17 | - [vit9696](https://github.com/vit9696) for minor compatibility fixes 18 | -------------------------------------------------------------------------------- /VoodooInput.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 358914F425798FA5007A0B58 /* TrackpointDevice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 358914F225798FA5007A0B58 /* TrackpointDevice.hpp */; }; 11 | 358914F525798FA5007A0B58 /* TrackpointDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 358914F325798FA5007A0B58 /* TrackpointDevice.cpp */; }; 12 | 7BBAB1FD22E3A2F800B2941A /* VoodooInput.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 7BBAB1FC22E3A2F800B2941A /* VoodooInput.hpp */; }; 13 | 7BBAB1FF22E3A2F800B2941A /* VoodooInput.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7BBAB1FE22E3A2F800B2941A /* VoodooInput.cpp */; }; 14 | 7BBAB21722E3AD0E00B2941A /* VoodooInputSimulatorDevice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 7BBAB21122E3AD0D00B2941A /* VoodooInputSimulatorDevice.hpp */; }; 15 | 7BBAB21822E3AD0E00B2941A /* VoodooInputSimulatorDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7BBAB21222E3AD0D00B2941A /* VoodooInputSimulatorDevice.cpp */; }; 16 | 7BBAB21922E3AD0E00B2941A /* VoodooInputActuatorDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7BBAB21322E3AD0D00B2941A /* VoodooInputActuatorDevice.cpp */; }; 17 | 7BBAB21B22E3AD0E00B2941A /* VoodooInputActuatorDevice.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 7BBAB21522E3AD0E00B2941A /* VoodooInputActuatorDevice.hpp */; }; 18 | CE8DA19D2518354A008C44E8 /* libkmod.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CE8DA19C2518354A008C44E8 /* libkmod.a */; }; 19 | EEC13CEB2C1DFD300080F2D1 /* VoodooInputIDs.hpp in Headers */ = {isa = PBXBuildFile; fileRef = EEC13CEA2C1DFD270080F2D1 /* VoodooInputIDs.hpp */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 358914F225798FA5007A0B58 /* TrackpointDevice.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TrackpointDevice.hpp; sourceTree = ""; }; 24 | 358914F325798FA5007A0B58 /* TrackpointDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TrackpointDevice.cpp; sourceTree = ""; }; 25 | 7BBAB1F922E3A2F800B2941A /* VoodooInput.kext */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VoodooInput.kext; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | 7BBAB1FC22E3A2F800B2941A /* VoodooInput.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = VoodooInput.hpp; sourceTree = ""; }; 27 | 7BBAB1FE22E3A2F800B2941A /* VoodooInput.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = VoodooInput.cpp; sourceTree = ""; }; 28 | 7BBAB20022E3A2F800B2941A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | 7BBAB21122E3AD0D00B2941A /* VoodooInputSimulatorDevice.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VoodooInputSimulatorDevice.hpp; sourceTree = ""; }; 30 | 7BBAB21222E3AD0D00B2941A /* VoodooInputSimulatorDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VoodooInputSimulatorDevice.cpp; sourceTree = ""; }; 31 | 7BBAB21322E3AD0D00B2941A /* VoodooInputActuatorDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VoodooInputActuatorDevice.cpp; sourceTree = ""; }; 32 | 7BBAB21522E3AD0E00B2941A /* VoodooInputActuatorDevice.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VoodooInputActuatorDevice.hpp; sourceTree = ""; }; 33 | CE8DA19C2518354A008C44E8 /* libkmod.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libkmod.a; path = ../Lilu/MacKernelSDK/Library/x86_64/libkmod.a; sourceTree = ""; }; 34 | CEC086452439FD3E00F5B701 /* VoodooInputTransducer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VoodooInputTransducer.h; sourceTree = ""; }; 35 | CEC086462439FD3E00F5B701 /* MultitouchHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MultitouchHelpers.h; sourceTree = ""; }; 36 | CEC086472439FD3E00F5B701 /* VoodooInputEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VoodooInputEvent.h; sourceTree = ""; }; 37 | CEC086482439FD3E00F5B701 /* VoodooInputMessages.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VoodooInputMessages.h; sourceTree = ""; }; 38 | CEFB081B239658DB00215B0B /* Changelog.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Changelog.md; sourceTree = SOURCE_ROOT; }; 39 | CEFB081D2397003600215B0B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; 40 | CEFB081E2397003600215B0B /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.txt; sourceTree = SOURCE_ROOT; }; 41 | EEC13CEA2C1DFD270080F2D1 /* VoodooInputIDs.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = VoodooInputIDs.hpp; sourceTree = ""; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | 7BBAB1F622E3A2F800B2941A /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | CE8DA19D2518354A008C44E8 /* libkmod.a in Frameworks */, 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | 358914F125798FA5007A0B58 /* Trackpoint */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 358914F225798FA5007A0B58 /* TrackpointDevice.hpp */, 60 | 358914F325798FA5007A0B58 /* TrackpointDevice.cpp */, 61 | ); 62 | path = Trackpoint; 63 | sourceTree = ""; 64 | }; 65 | 7BBAB1EF22E3A2F800B2941A = { 66 | isa = PBXGroup; 67 | children = ( 68 | CEFB08182396586D00215B0B /* Docs */, 69 | 7BBAB1FB22E3A2F800B2941A /* VoodooInput */, 70 | 7BBAB1FA22E3A2F800B2941A /* Products */, 71 | CE8DA19B2518354A008C44E8 /* Frameworks */, 72 | ); 73 | sourceTree = ""; 74 | usesTabs = 0; 75 | }; 76 | 7BBAB1FA22E3A2F800B2941A /* Products */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 7BBAB1F922E3A2F800B2941A /* VoodooInput.kext */, 80 | ); 81 | name = Products; 82 | sourceTree = ""; 83 | }; 84 | 7BBAB1FB22E3A2F800B2941A /* VoodooInput */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 358914F125798FA5007A0B58 /* Trackpoint */, 88 | CEC086442439FD3E00F5B701 /* VoodooInputMultitouch */, 89 | 7BBAB20F22E3AC7E00B2941A /* VoodooInputSimulator */, 90 | 7BBAB1FC22E3A2F800B2941A /* VoodooInput.hpp */, 91 | 7BBAB1FE22E3A2F800B2941A /* VoodooInput.cpp */, 92 | EEC13CEA2C1DFD270080F2D1 /* VoodooInputIDs.hpp */, 93 | 7BBAB20022E3A2F800B2941A /* Info.plist */, 94 | ); 95 | path = VoodooInput; 96 | sourceTree = ""; 97 | }; 98 | 7BBAB20F22E3AC7E00B2941A /* VoodooInputSimulator */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 7BBAB21522E3AD0E00B2941A /* VoodooInputActuatorDevice.hpp */, 102 | 7BBAB21322E3AD0D00B2941A /* VoodooInputActuatorDevice.cpp */, 103 | 7BBAB21122E3AD0D00B2941A /* VoodooInputSimulatorDevice.hpp */, 104 | 7BBAB21222E3AD0D00B2941A /* VoodooInputSimulatorDevice.cpp */, 105 | ); 106 | path = VoodooInputSimulator; 107 | sourceTree = ""; 108 | }; 109 | CE8DA19B2518354A008C44E8 /* Frameworks */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | CE8DA19C2518354A008C44E8 /* libkmod.a */, 113 | ); 114 | name = Frameworks; 115 | sourceTree = ""; 116 | }; 117 | CEC086442439FD3E00F5B701 /* VoodooInputMultitouch */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | CEC086452439FD3E00F5B701 /* VoodooInputTransducer.h */, 121 | CEC086462439FD3E00F5B701 /* MultitouchHelpers.h */, 122 | CEC086472439FD3E00F5B701 /* VoodooInputEvent.h */, 123 | CEC086482439FD3E00F5B701 /* VoodooInputMessages.h */, 124 | ); 125 | path = VoodooInputMultitouch; 126 | sourceTree = ""; 127 | }; 128 | CEFB08182396586D00215B0B /* Docs */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | CEFB081E2397003600215B0B /* LICENSE.txt */, 132 | CEFB081D2397003600215B0B /* README.md */, 133 | CEFB081B239658DB00215B0B /* Changelog.md */, 134 | ); 135 | name = Docs; 136 | path = VoodooInput; 137 | sourceTree = ""; 138 | }; 139 | /* End PBXGroup section */ 140 | 141 | /* Begin PBXHeadersBuildPhase section */ 142 | 7BBAB1F422E3A2F800B2941A /* Headers */ = { 143 | isa = PBXHeadersBuildPhase; 144 | buildActionMask = 2147483647; 145 | files = ( 146 | 7BBAB21722E3AD0E00B2941A /* VoodooInputSimulatorDevice.hpp in Headers */, 147 | 358914F425798FA5007A0B58 /* TrackpointDevice.hpp in Headers */, 148 | 7BBAB21B22E3AD0E00B2941A /* VoodooInputActuatorDevice.hpp in Headers */, 149 | 7BBAB1FD22E3A2F800B2941A /* VoodooInput.hpp in Headers */, 150 | EEC13CEB2C1DFD300080F2D1 /* VoodooInputIDs.hpp in Headers */, 151 | ); 152 | runOnlyForDeploymentPostprocessing = 0; 153 | }; 154 | /* End PBXHeadersBuildPhase section */ 155 | 156 | /* Begin PBXNativeTarget section */ 157 | 7BBAB1F822E3A2F800B2941A /* VoodooInput */ = { 158 | isa = PBXNativeTarget; 159 | buildConfigurationList = 7BBAB20322E3A2F800B2941A /* Build configuration list for PBXNativeTarget "VoodooInput" */; 160 | buildPhases = ( 161 | 7BBAB1F422E3A2F800B2941A /* Headers */, 162 | 7BBAB1F522E3A2F800B2941A /* Sources */, 163 | 7BBAB1F622E3A2F800B2941A /* Frameworks */, 164 | CEC086492439FD5D00F5B701 /* Copy SDK */, 165 | CEFB081C239659D000215B0B /* Archive */, 166 | ); 167 | buildRules = ( 168 | ); 169 | dependencies = ( 170 | ); 171 | name = VoodooInput; 172 | productName = VoodooInput; 173 | productReference = 7BBAB1F922E3A2F800B2941A /* VoodooInput.kext */; 174 | productType = "com.apple.product-type.kernel-extension"; 175 | }; 176 | /* End PBXNativeTarget section */ 177 | 178 | /* Begin PBXProject section */ 179 | 7BBAB1F022E3A2F800B2941A /* Project object */ = { 180 | isa = PBXProject; 181 | attributes = { 182 | LastUpgradeCheck = 1320; 183 | ORGANIZATIONNAME = "Kishor Prins"; 184 | TargetAttributes = { 185 | 7BBAB1F822E3A2F800B2941A = { 186 | CreatedOnToolsVersion = 10.2.1; 187 | }; 188 | }; 189 | }; 190 | buildConfigurationList = 7BBAB1F322E3A2F800B2941A /* Build configuration list for PBXProject "VoodooInput" */; 191 | compatibilityVersion = "Xcode 9.3"; 192 | developmentRegion = en; 193 | hasScannedForEncodings = 0; 194 | knownRegions = ( 195 | en, 196 | Base, 197 | ); 198 | mainGroup = 7BBAB1EF22E3A2F800B2941A; 199 | productRefGroup = 7BBAB1FA22E3A2F800B2941A /* Products */; 200 | projectDirPath = ""; 201 | projectRoot = ""; 202 | targets = ( 203 | 7BBAB1F822E3A2F800B2941A /* VoodooInput */, 204 | ); 205 | }; 206 | /* End PBXProject section */ 207 | 208 | /* Begin PBXShellScriptBuildPhase section */ 209 | CEC086492439FD5D00F5B701 /* Copy SDK */ = { 210 | isa = PBXShellScriptBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | inputFileListPaths = ( 215 | ); 216 | inputPaths = ( 217 | ); 218 | name = "Copy SDK"; 219 | outputFileListPaths = ( 220 | ); 221 | outputPaths = ( 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | shellPath = /bin/bash; 225 | shellScript = "if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n rm -rf \"${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}/Contents/Resources\"\n mkdir -p \"${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}/Contents/Resources\"\n cp -r \"${SRCROOT}/${PRODUCT_NAME}/VoodooInputMultitouch\" \"${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}/Contents/Resources\"\nfi\n\nexit 0\n"; 226 | }; 227 | CEFB081C239659D000215B0B /* Archive */ = { 228 | isa = PBXShellScriptBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | ); 232 | inputFileListPaths = ( 233 | ); 234 | inputPaths = ( 235 | ); 236 | name = Archive; 237 | outputFileListPaths = ( 238 | ); 239 | outputPaths = ( 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | shellPath = /bin/sh; 243 | shellScript = "cd \"${TARGET_BUILD_DIR}\"\n\ndist=(\"$FULL_PRODUCT_NAME\")\nif [ -d \"$DWARF_DSYM_FILE_NAME\" ]; then dist+=(\"$DWARF_DSYM_FILE_NAME\"); fi\n\narchive=\"${PRODUCT_NAME}-${MODULE_VERSION}-$(echo $CONFIGURATION | tr /a-z/ /A-Z/).zip\"\nrm -rf *.zip\nzip -qry -FS \"${archive}\" \"${dist[@]}\"\n"; 244 | }; 245 | /* End PBXShellScriptBuildPhase section */ 246 | 247 | /* Begin PBXSourcesBuildPhase section */ 248 | 7BBAB1F522E3A2F800B2941A /* Sources */ = { 249 | isa = PBXSourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | 358914F525798FA5007A0B58 /* TrackpointDevice.cpp in Sources */, 253 | 7BBAB1FF22E3A2F800B2941A /* VoodooInput.cpp in Sources */, 254 | 7BBAB21922E3AD0E00B2941A /* VoodooInputActuatorDevice.cpp in Sources */, 255 | 7BBAB21822E3AD0E00B2941A /* VoodooInputSimulatorDevice.cpp in Sources */, 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | /* End PBXSourcesBuildPhase section */ 260 | 261 | /* Begin XCBuildConfiguration section */ 262 | 7BBAB20122E3A2F800B2941A /* Debug */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ALWAYS_SEARCH_USER_PATHS = NO; 266 | ARCHS = x86_64; 267 | CLANG_ANALYZER_NONNULL = YES; 268 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 269 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 270 | CLANG_CXX_LIBRARY = "libc++"; 271 | CLANG_ENABLE_MODULES = YES; 272 | CLANG_ENABLE_OBJC_ARC = YES; 273 | CLANG_ENABLE_OBJC_WEAK = YES; 274 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 275 | CLANG_WARN_BOOL_CONVERSION = YES; 276 | CLANG_WARN_COMMA = YES; 277 | CLANG_WARN_CONSTANT_CONVERSION = YES; 278 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 279 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 280 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 281 | CLANG_WARN_EMPTY_BODY = YES; 282 | CLANG_WARN_ENUM_CONVERSION = YES; 283 | CLANG_WARN_INFINITE_RECURSION = YES; 284 | CLANG_WARN_INT_CONVERSION = YES; 285 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 286 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 287 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 288 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 289 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 290 | CLANG_WARN_STRICT_PROTOTYPES = YES; 291 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 292 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 293 | CLANG_WARN_UNREACHABLE_CODE = YES; 294 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 295 | CODE_SIGN_IDENTITY = "-"; 296 | COPY_PHASE_STRIP = NO; 297 | DEBUG_INFORMATION_FORMAT = dwarf; 298 | ENABLE_STRICT_OBJC_MSGSEND = YES; 299 | ENABLE_TESTABILITY = YES; 300 | GCC_C_LANGUAGE_STANDARD = gnu11; 301 | GCC_DYNAMIC_NO_PIC = NO; 302 | GCC_NO_COMMON_BLOCKS = YES; 303 | GCC_OPTIMIZATION_LEVEL = 0; 304 | GCC_PREPROCESSOR_DEFINITIONS = ( 305 | "DEBUG=1", 306 | "$(inherited)", 307 | ); 308 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 309 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 310 | GCC_WARN_UNDECLARED_SELECTOR = YES; 311 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 312 | GCC_WARN_UNUSED_FUNCTION = YES; 313 | GCC_WARN_UNUSED_VARIABLE = YES; 314 | KERNEL_EXTENSION_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/MacKernelSDK/Headers"; 315 | KERNEL_FRAMEWORK_HEADERS = "$(PROJECT_DIR)/MacKernelSDK/Headers"; 316 | MACOSX_DEPLOYMENT_TARGET = 10.10; 317 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 318 | MTL_FAST_MATH = YES; 319 | ONLY_ACTIVE_ARCH = YES; 320 | SDKROOT = macosx; 321 | }; 322 | name = Debug; 323 | }; 324 | 7BBAB20222E3A2F800B2941A /* Release */ = { 325 | isa = XCBuildConfiguration; 326 | buildSettings = { 327 | ALWAYS_SEARCH_USER_PATHS = NO; 328 | ARCHS = x86_64; 329 | CLANG_ANALYZER_NONNULL = YES; 330 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 331 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 332 | CLANG_CXX_LIBRARY = "libc++"; 333 | CLANG_ENABLE_MODULES = YES; 334 | CLANG_ENABLE_OBJC_ARC = YES; 335 | CLANG_ENABLE_OBJC_WEAK = YES; 336 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 337 | CLANG_WARN_BOOL_CONVERSION = YES; 338 | CLANG_WARN_COMMA = YES; 339 | CLANG_WARN_CONSTANT_CONVERSION = YES; 340 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 341 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 342 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 343 | CLANG_WARN_EMPTY_BODY = YES; 344 | CLANG_WARN_ENUM_CONVERSION = YES; 345 | CLANG_WARN_INFINITE_RECURSION = YES; 346 | CLANG_WARN_INT_CONVERSION = YES; 347 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 348 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 349 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 350 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 351 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 352 | CLANG_WARN_STRICT_PROTOTYPES = YES; 353 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 354 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 355 | CLANG_WARN_UNREACHABLE_CODE = YES; 356 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 357 | CODE_SIGN_IDENTITY = "-"; 358 | COPY_PHASE_STRIP = NO; 359 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 360 | ENABLE_NS_ASSERTIONS = NO; 361 | ENABLE_STRICT_OBJC_MSGSEND = YES; 362 | GCC_C_LANGUAGE_STANDARD = gnu11; 363 | GCC_NO_COMMON_BLOCKS = YES; 364 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 365 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 366 | GCC_WARN_UNDECLARED_SELECTOR = YES; 367 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 368 | GCC_WARN_UNUSED_FUNCTION = YES; 369 | GCC_WARN_UNUSED_VARIABLE = YES; 370 | KERNEL_EXTENSION_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/MacKernelSDK/Headers"; 371 | KERNEL_FRAMEWORK_HEADERS = "$(PROJECT_DIR)/MacKernelSDK/Headers"; 372 | MACOSX_DEPLOYMENT_TARGET = 10.10; 373 | MTL_ENABLE_DEBUG_INFO = NO; 374 | MTL_FAST_MATH = YES; 375 | SDKROOT = macosx; 376 | }; 377 | name = Release; 378 | }; 379 | 7BBAB20422E3A2F800B2941A /* Debug */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | CLANG_ANALYZER_DEADCODE_DEADSTORES = YES; 383 | CLANG_ANALYZER_DIVIDE_BY_ZERO = YES; 384 | CLANG_ANALYZER_NULL_DEREFERENCE = YES; 385 | CODE_SIGN_STYLE = Automatic; 386 | COMBINE_HIDPI_IMAGES = YES; 387 | CURRENT_PROJECT_VERSION = "$(MODULE_VERSION)"; 388 | INFOPLIST_FILE = VoodooInput/Info.plist; 389 | LIBRARY_SEARCH_PATHS = ( 390 | "$(inherited)", 391 | "$(PROJECT_DIR)/MacKernelSDK/Library/x86_64", 392 | ); 393 | MODULE_NAME = me.kishorprins.VoodooInput; 394 | MODULE_VERSION = 1.1.7; 395 | PRODUCT_BUNDLE_IDENTIFIER = me.kishorprins.VoodooInput; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | RUN_CLANG_STATIC_ANALYZER = YES; 398 | WRAPPER_EXTENSION = kext; 399 | }; 400 | name = Debug; 401 | }; 402 | 7BBAB20522E3A2F800B2941A /* Release */ = { 403 | isa = XCBuildConfiguration; 404 | buildSettings = { 405 | CODE_SIGN_STYLE = Automatic; 406 | COMBINE_HIDPI_IMAGES = YES; 407 | CURRENT_PROJECT_VERSION = "$(MODULE_VERSION)"; 408 | INFOPLIST_FILE = VoodooInput/Info.plist; 409 | LIBRARY_SEARCH_PATHS = ( 410 | "$(inherited)", 411 | "$(PROJECT_DIR)/MacKernelSDK/Library/x86_64", 412 | ); 413 | MODULE_NAME = me.kishorprins.VoodooInput; 414 | MODULE_VERSION = 1.1.7; 415 | PRODUCT_BUNDLE_IDENTIFIER = me.kishorprins.VoodooInput; 416 | PRODUCT_NAME = "$(TARGET_NAME)"; 417 | WRAPPER_EXTENSION = kext; 418 | }; 419 | name = Release; 420 | }; 421 | /* End XCBuildConfiguration section */ 422 | 423 | /* Begin XCConfigurationList section */ 424 | 7BBAB1F322E3A2F800B2941A /* Build configuration list for PBXProject "VoodooInput" */ = { 425 | isa = XCConfigurationList; 426 | buildConfigurations = ( 427 | 7BBAB20122E3A2F800B2941A /* Debug */, 428 | 7BBAB20222E3A2F800B2941A /* Release */, 429 | ); 430 | defaultConfigurationIsVisible = 0; 431 | defaultConfigurationName = Release; 432 | }; 433 | 7BBAB20322E3A2F800B2941A /* Build configuration list for PBXNativeTarget "VoodooInput" */ = { 434 | isa = XCConfigurationList; 435 | buildConfigurations = ( 436 | 7BBAB20422E3A2F800B2941A /* Debug */, 437 | 7BBAB20522E3A2F800B2941A /* Release */, 438 | ); 439 | defaultConfigurationIsVisible = 0; 440 | defaultConfigurationName = Release; 441 | }; 442 | /* End XCConfigurationList section */ 443 | }; 444 | rootObject = 7BBAB1F022E3A2F800B2941A /* Project object */; 445 | } 446 | -------------------------------------------------------------------------------- /VoodooInput/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | KEXT 17 | CFBundleShortVersionString 18 | $(MODULE_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | IOKitPersonalities 22 | 23 | Voodoo Input 24 | 25 | CFBundleIdentifier 26 | $(PRODUCT_BUNDLE_IDENTIFIER) 27 | IOClass 28 | VoodooInput 29 | IOProbeScore 30 | 200 31 | IOPropertyMatch 32 | 33 | VoodooInputSupported 34 | 35 | 36 | IOProviderClass 37 | IOService 38 | 39 | 40 | NSHumanReadableCopyright 41 | Copyright © 2019 Kishor Prins. All rights reserved. 42 | OSBundleLibraries 43 | 44 | com.apple.iokit.IOHIDFamily 45 | 2.0 46 | com.apple.kpi.iokit 47 | 14 48 | com.apple.kpi.libkern 49 | 14 50 | com.apple.kpi.mach 51 | 13.0 52 | 53 | OSBundleRequired 54 | Root 55 | 56 | 57 | -------------------------------------------------------------------------------- /VoodooInput/Scripts/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # bootstrap.sh 5 | # VoodooInput 6 | # 7 | # Copyright © 2020 vit9696. All rights reserved. 8 | # 9 | 10 | # 11 | # This script is supposed to quickly bootstrap VoodooInput SDK for plugin building. 12 | # Depending on BUILD_MODE variable (prebuilt or compiled) a prebuilt or 13 | # a compiled VoodooInput release will be bootstrapped in the working directory. 14 | # 15 | # Latest version available at: 16 | # https://raw.githubusercontent.com/acidanthera/VoodooInput/master/VoodooInput/Scripts/bootstrap.sh 17 | # 18 | # Example usage: 19 | # src=$(/usr/bin/curl -Lfs https://raw.githubusercontent.com/acidanthera/VoodooInput/master/VoodooInput/Scripts/bootstrap.sh) && eval "$src" || exit 1 20 | # 21 | 22 | REPO_PATH="acidanthera/VoodooInput" 23 | SDK_PATH="VoodooInput.kext" 24 | DSYM_PATH="VoodooInput.kext.dSYM" 25 | SDK_CHECK_PATH="${SDK_PATH}/Contents/Resources/VoodooInputMultitouch/VoodooInputMessages.h" 26 | 27 | PROJECT_PATH="$(pwd)" 28 | if [ $? -ne 0 ] || [ ! -d "${PROJECT_PATH}" ]; then 29 | echo "ERROR: Failed to determine working directory!" 30 | exit 1 31 | fi 32 | 33 | # Avoid conflicts with PATH overrides. 34 | CURL="/usr/bin/curl" 35 | GIT="/usr/bin/git" 36 | GREP="/usr/bin/grep" 37 | MKDIR="/bin/mkdir" 38 | MV="/bin/mv" 39 | RM="/bin/rm" 40 | SED="/usr/bin/sed" 41 | UNAME="/usr/bin/uname" 42 | UNZIP="/usr/bin/unzip" 43 | UUIDGEN="/usr/bin/uuidgen" 44 | XCODEBUILD="/usr/bin/xcodebuild" 45 | 46 | TOOLS=( 47 | "${CURL}" 48 | "${GIT}" 49 | "${GREP}" 50 | "${MKDIR}" 51 | "${MV}" 52 | "${RM}" 53 | "${SED}" 54 | "${UNAME}" 55 | "${UNZIP}" 56 | "${UUIDGEN}" 57 | "${XCODEBUILD}" 58 | ) 59 | 60 | for tool in "${TOOLS[@]}"; do 61 | if [ ! -x "${tool}" ]; then 62 | echo "ERROR: Missing ${tool}!" 63 | exit 1 64 | fi 65 | done 66 | 67 | CONFS=( 68 | DEBUG 69 | RELEASE 70 | ) 71 | 72 | CONFDESTS=( 73 | Debug 74 | Release 75 | ) 76 | 77 | CONFNUM="${#CONFS[@]}" 78 | 79 | # Prepare temporary directory to avoid conflicts with other scripts. 80 | # Sets TMP_PATH. 81 | prepare_environment() { 82 | local ret=0 83 | 84 | local sys=$("${UNAME}") || ret=$? 85 | if [ $ret -ne 0 ] || [ "$sys" != "Darwin" ]; then 86 | echo "ERROR: This script is only meant to be used on Darwin systems!" 87 | return 1 88 | fi 89 | 90 | if [ -e "VoodooInput" ]; then 91 | echo "ERROR: Found existing SDK directory VoodooInput, aborting!" 92 | return 1 93 | fi 94 | 95 | local uuid=$("${UUIDGEN}") || ret=$? 96 | if [ $ret -ne 0 ]; then 97 | echo "ERROR: Failed to generate temporary UUID with code ${ret}!" 98 | return 1 99 | fi 100 | 101 | TMP_PATH="/tmp/voodooinput.${uuid}" 102 | if [ -e "${TMP_PATH}" ]; then 103 | echo "ERROR: Found existing temporary directory ${TMP_PATH}, aborting!" 104 | return 1 105 | fi 106 | 107 | "${MKDIR}" "${TMP_PATH}" || ret=$? 108 | if [ $ret -ne 0 ]; then 109 | echo "ERROR: Failed to create temporary directory ${TMP_PATH} with code ${ret}!" 110 | return 1 111 | fi 112 | 113 | cd "${TMP_PATH}" || ret=$? 114 | if [ $ret -ne 0 ]; then 115 | echo "ERROR: Failed to cd to temporary directory ${TMP_PATH} with code ${ret}!" 116 | "${RM}" -rf "${TMP_PATH}" 117 | return 1 118 | fi 119 | 120 | return 0 121 | } 122 | 123 | # Install prebuilt SDK for release distribution. 124 | install_prebuilt_sdk() { 125 | local ret=0 126 | 127 | echo "Installing prebuilt SDK..." 128 | 129 | echo "-> Obtaining release manifest..." 130 | 131 | echo "-> Cloning the latest version from master..." 132 | 133 | # This is a really ugly hack due to GitHub API rate limits. 134 | local url="https://github.com/${REPO_PATH}" 135 | "${GIT}" clone "${url}" -b "master" "tmp" || ret=$? 136 | if [ $ret -ne 0 ]; then 137 | echo "ERROR: Failed to clone repository with code ${ret}!" 138 | return 1 139 | fi 140 | 141 | echo "-> Obtaining the latest tag..." 142 | 143 | cd "tmp" || ret=$? 144 | if [ $ret -ne 0 ]; then 145 | echo "ERROR: Failed to cd to temporary directory tmp with code ${ret}!" 146 | return 1 147 | fi 148 | 149 | local vers=$("${GIT}" describe --abbrev=0 --tags) || ret=$? 150 | if [ "$vers" = "" ]; then 151 | echo "ERROR: Failed to determine the latest release tag!" 152 | return 1 153 | fi 154 | 155 | echo "-> Discovered the latest tag ${vers}." 156 | 157 | cd .. || ret=$? 158 | if [ $ret -ne 0 ]; then 159 | echo "ERROR: Failed to cd back from temporary directory with code ${ret}!" 160 | return 1 161 | fi 162 | 163 | "${RM}" -rf tmp || ret=$? 164 | if [ $ret -ne 0 ] || [ -d tmp ]; then 165 | echo "ERROR: Failed to remove temporary directory tmp with code ${ret}!" 166 | return 1 167 | fi 168 | 169 | "${MKDIR}" "${PROJECT_PATH}/VoodooInput" || ret=$? 170 | if [ $ret -ne 0 ]; then 171 | echo "ERROR: Failed to create SDK directory ${PROJECT_PATH}/VoodooInput with code ${ret}!" 172 | return 1 173 | fi 174 | 175 | for ((i=0; $i < $CONFNUM; i++)); do 176 | local file="VoodooInput-${vers}-${CONFS[$i]}.zip" 177 | 178 | echo "-> Downloading prebuilt debug version ${file}..." 179 | 180 | local url="https://github.com/${REPO_PATH}/releases/download/${vers}/${file}" 181 | "${CURL}" -LfsO "${url}" || ret=$? 182 | if [ $ret -ne 0 ]; then 183 | echo "ERROR: Failed to download ${file} with code ${ret}!" 184 | return 1 185 | fi 186 | 187 | echo "-> Extracting SDK from prebuilt debug version..." 188 | 189 | if [ ! -f "${file}" ]; then 190 | echo "ERROR: Failed to download ${file} to a non-existent location!" 191 | return 1 192 | fi 193 | 194 | "${MKDIR}" "tmp" || ret=$? 195 | if [ $ret -ne 0 ]; then 196 | echo "ERROR: Failed to create temporary directory at ${TMP_PATH} with code ${ret}!" 197 | return 1 198 | fi 199 | 200 | cd "tmp" || ret=$? 201 | if [ $ret -ne 0 ]; then 202 | echo "ERROR: Failed to cd to temporary directory tmp with code ${ret}!" 203 | return 1 204 | fi 205 | 206 | "${UNZIP}" -q ../"${file}" || ret=$? 207 | if [ $ret -ne 0 ]; then 208 | echo "ERROR: Failed to unzip ${file} with code ${ret}!" 209 | return 1 210 | fi 211 | 212 | echo "-> Installing SDK from the prebuilt debug version..." 213 | 214 | if [ ! -d "${SDK_PATH}" ]; then 215 | echo "ERROR: Failed to find SDK in the downloaded archive!" 216 | return 1 217 | fi 218 | 219 | if [ "${CONFS[$i]}" = "DEBUG" ] && [ ! -f "${SDK_CHECK_PATH}" ]; then 220 | echo "ERROR: Failed to find compilation support SDK in the downloaded archive!" 221 | return 1 222 | fi 223 | 224 | "${MKDIR}" "${PROJECT_PATH}/VoodooInput/${CONFDESTS[$i]}" || ret=$? 225 | if [ $ret -ne 0 ]; then 226 | echo "ERROR: Failed to create SDK directory ${PROJECT_PATH}/VoodooInput/${CONFDESTS[$i]} with code ${ret}!" 227 | return 1 228 | fi 229 | 230 | "${MV}" "${SDK_PATH}" "${PROJECT_PATH}/VoodooInput/${CONFDESTS[$i]}/${SDK_PATH}" || ret=$? 231 | if [ $ret -ne 0 ]; then 232 | echo "ERROR: Failed to install SDK with code ${ret}!" 233 | return 1 234 | fi 235 | 236 | if [ -d "${DSYM_PATH}" ]; then 237 | "${MV}" "${DSYM_PATH}" "${PROJECT_PATH}/VoodooInput/${CONFDESTS[$i]}/${DSYM_PATH}" || ret=$? 238 | if [ $ret -ne 0 ]; then 239 | echo "ERROR: Failed to install SDK dSYM with code ${ret}!" 240 | return 1 241 | fi 242 | fi 243 | 244 | echo "Installed prebuilt SDK ${CONFS[$i]}/${vers}!" 245 | done 246 | 247 | return 0 248 | } 249 | 250 | # Install manually compiled SDK for development builds. 251 | install_compiled_sdk() { 252 | local ret=0 253 | 254 | echo "Installing compiled SDK..." 255 | 256 | echo "-> Cloning the latest version from master..." 257 | 258 | local url="https://github.com/${REPO_PATH}" 259 | "${GIT}" clone "${url}" -b "master" --depth=1 "tmp" || ret=$? 260 | if [ $ret -ne 0 ]; then 261 | echo "ERROR: Failed to clone repository with code ${ret}!" 262 | return 1 263 | fi 264 | 265 | echo "-> Building the latest SDK..." 266 | 267 | cd "tmp" || ret=$? 268 | if [ $ret -ne 0 ]; then 269 | echo "ERROR: Failed to cd to temporary directory tmp with code ${ret}!" 270 | return 1 271 | fi 272 | 273 | "${GIT}" clone "https://github.com/acidanthera/MacKernelSDK" -b "master" --depth=1 || ret=$? 274 | if [ $ret -ne 0 ]; then 275 | echo "ERROR: Failed to clone MacKernelSDK with code ${ret}!" 276 | return 1 277 | fi 278 | 279 | "${MKDIR}" "${PROJECT_PATH}/VoodooInput" || ret=$? 280 | if [ $ret -ne 0 ]; then 281 | echo "ERROR: Failed to create SDK directory ${PROJECT_PATH}/VoodooInput with code ${ret}!" 282 | return 1 283 | fi 284 | 285 | for ((i=0; $i < $CONFNUM; i++)); do 286 | "${XCODEBUILD}" -configuration "${CONFDESTS[$i]}" || ret=$? 287 | if [ $ret -ne 0 ]; then 288 | echo "ERROR: Failed to compile the latest version with code ${ret}!" 289 | return 1 290 | fi 291 | 292 | echo "-> Installing compiled SDK..." 293 | 294 | if [ ! -d "build/${CONFDESTS[$i]}/${SDK_PATH}" ]; then 295 | echo "ERROR: Failed to find the built SDK!" 296 | return 1 297 | fi 298 | 299 | if [ "${CONFS[$i]}" = "DEBUG" ] && [ ! -f "build/${CONFDESTS[$i]}/${SDK_CHECK_PATH}" ]; then 300 | echo "ERROR: Failed to find the built compilation support SDK!" 301 | return 1 302 | fi 303 | 304 | "${MKDIR}" "${PROJECT_PATH}/VoodooInput/${CONFDESTS[$i]}" || ret=$? 305 | if [ $ret -ne 0 ]; then 306 | echo "ERROR: Failed to create SDK directory ${PROJECT_PATH}/VoodooInput/${CONFDESTS[$i]} with code ${ret}!" 307 | return 1 308 | fi 309 | 310 | "${MV}" "build/${CONFDESTS[$i]}/${SDK_PATH}" "${PROJECT_PATH}/VoodooInput/${CONFDESTS[$i]}/${SDK_PATH}" || ret=$? 311 | if [ $ret -ne 0 ]; then 312 | echo "ERROR: Failed to install SDK with code ${ret}!" 313 | return 1 314 | fi 315 | 316 | if [ -d "build/${CONFDESTS[$i]}/${DSYM_PATH}" ]; then 317 | "${MV}" "build/${CONFDESTS[$i]}/${DSYM_PATH}" "${PROJECT_PATH}/VoodooInput/${CONFDESTS[$i]}/${DSYM_PATH}" || ret=$? 318 | if [ $ret -ne 0 ]; then 319 | echo "ERROR: Failed to install SDK dSYM with code ${ret}!" 320 | return 1 321 | fi 322 | fi 323 | 324 | echo "Installed compiled SDK from master!" 325 | done 326 | } 327 | 328 | prepare_environment || exit 1 329 | 330 | ret=0 331 | if [ "${TRAVIS_TAG}" != "" ] || [ "${CIRCLE_TAG}" != "" ] || [ "${BUILD_MODE}" = "prebuilt" ]; then 332 | install_prebuilt_sdk || ret=$? 333 | else 334 | install_compiled_sdk || ret=$? 335 | fi 336 | 337 | cd "${PROJECT_PATH}" || ret=$? 338 | 339 | "${RM}" -rf "${TMP_PATH}" 340 | 341 | if [ $ret -ne 0 ]; then 342 | echo "ERROR: Failed to bootstrap SDK with code ${ret}!" 343 | exit 1 344 | fi 345 | -------------------------------------------------------------------------------- /VoodooInput/Trackpoint/TrackpointDevice.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * TrackpointDevice.cpp 3 | * VoodooTrackpoint 4 | * 5 | * Copyright (c) 2019 Leonard Kleinhans 6 | * 7 | */ 8 | 9 | #include "TrackpointDevice.hpp" 10 | 11 | OSDefineMetaClassAndStructors(TrackpointDevice, IOHIPointing); 12 | 13 | #define abs(x) ((x < 0) ? -(x) : (x)) 14 | #define MIDDLE_MOUSE_MASK 0x4 15 | 16 | UInt32 TrackpointDevice::deviceType() { 17 | return NX_EVS_DEVICE_TYPE_MOUSE; 18 | } 19 | 20 | UInt32 TrackpointDevice::interfaceID() { 21 | return NX_EVS_DEVICE_INTERFACE_BUS_ACE; 22 | } 23 | 24 | IOItemCount TrackpointDevice::buttonCount() { 25 | return btnCount; 26 | }; 27 | 28 | IOFixed TrackpointDevice::resolution() { 29 | return 400 << 16; 30 | }; 31 | 32 | bool TrackpointDevice::start(IOService* provider) { 33 | if (!super::start(provider)) { 34 | return false; 35 | } 36 | 37 | updateTrackpointProperties(); 38 | 39 | setProperty(kIOHIDScrollAccelerationTypeKey, kIOHIDTrackpadScrollAccelerationKey); 40 | setProperty(kIOHIDScrollResolutionKey, 400 << 16, 32); 41 | setProperty("HIDScrollResolutionX", 400 << 16, 32); 42 | setProperty("HIDScrollResolutionY", 400 << 16, 32); 43 | 44 | registerService(); 45 | return true; 46 | } 47 | 48 | void TrackpointDevice::getOSIntValue(OSDictionary *dict, int *val, const char *key) { 49 | OSNumber *osnum = OSDynamicCast(OSNumber, dict->getObject(key)); 50 | if (osnum != nullptr) *val = osnum->unsigned32BitValue(); 51 | } 52 | 53 | void TrackpointDevice::getOSShortValue(OSDictionary *dict, short *val, const char *key) { 54 | OSNumber *osnum = OSDynamicCast(OSNumber, dict->getObject(key)); 55 | if (osnum != nullptr) *val = osnum->unsigned16BitValue(); 56 | } 57 | 58 | void TrackpointDevice::updateTrackpointProperties() { 59 | OSObject *obj = getProperty(VOODOO_TRACKPOINT_KEY, gIOServicePlane); 60 | OSDictionary *dict = OSDynamicCast(OSDictionary, obj); 61 | 62 | if (dict == nullptr) return; 63 | 64 | getOSIntValue(dict, &btnCount, VOODOO_TRACKPOINT_BTN_CNT); 65 | getOSIntValue(dict, &trackpointDeadzone, VOODOO_TRACKPOINT_DEADZONE); 66 | getOSIntValue(dict, &trackpointMultX, VOODOO_TRACKPOINT_MOUSE_MULT_X); 67 | getOSIntValue(dict, &trackpointMultY, VOODOO_TRACKPOINT_MOUSE_MULT_Y); 68 | getOSIntValue(dict, &trackpointDivX, VOODOO_TRACKPOINT_MOUSE_DIV_X); 69 | getOSIntValue(dict, &trackpointDivY, VOODOO_TRACKPOINT_MOUSE_DIV_Y); 70 | 71 | getOSShortValue(dict, &trackpointScrollMultX, VOODOO_TRACKPOINT_SCROLL_MULT_X); 72 | getOSShortValue(dict, &trackpointScrollMultY, VOODOO_TRACKPOINT_SCROLL_MULT_Y); 73 | getOSShortValue(dict, &trackpointScrollDivX, VOODOO_TRACKPOINT_SCROLL_DIV_X); 74 | getOSShortValue(dict, &trackpointScrollDivY, VOODOO_TRACKPOINT_SCROLL_DIV_Y); 75 | 76 | if (trackpointDivX == 0) trackpointDivX = 1; 77 | if (trackpointDivY == 0) trackpointDivY = 1; 78 | if (trackpointScrollDivX == 0) trackpointScrollDivX = 1; 79 | if (trackpointScrollDivY == 0) trackpointScrollDivY = 1; 80 | } 81 | 82 | void TrackpointDevice::stop(IOService* provider) { 83 | super::stop(provider); 84 | } 85 | 86 | int TrackpointDevice::signum(int value) 87 | { 88 | if (value > 0) return 1; 89 | if (value < 0) return -1; 90 | return 0; 91 | } 92 | 93 | void TrackpointDevice::reportPacket(TrackpointReport &report) { 94 | SInt32 dx = report.dx; 95 | SInt32 dy = report.dy; 96 | UInt32 buttons = report.buttons; 97 | AbsoluteTime timestamp = report.timestamp; 98 | 99 | dx -= signum(dx) * min(abs(dx), trackpointDeadzone); 100 | dy -= signum(dy) * min(abs(dy), trackpointDeadzone); 101 | 102 | bool middleBtnNotPressed = (buttons & MIDDLE_MOUSE_MASK) == 0; 103 | 104 | // Do not tell macOS about the middle button until it's been released 105 | // Scrolling with the button down can result in the button being spammed 106 | switch (middleBtnState) { 107 | case NOT_PRESSED: 108 | if (middleBtnNotPressed) { 109 | break; 110 | } 111 | 112 | middleBtnState = PRESSED; 113 | /* fallthrough */ 114 | case PRESSED: 115 | if (dx || dy) { 116 | middleBtnState = SCROLLED; 117 | } 118 | 119 | if (middleBtnNotPressed) { 120 | // Two reports are needed to send the middle button - this is the first 121 | // The second one below is sent with the button released 122 | dispatchRelativePointerEvent(dx, dy, MIDDLE_MOUSE_MASK, timestamp); 123 | middleBtnState = NOT_PRESSED; 124 | } 125 | break; 126 | case SCROLLED: 127 | if (middleBtnNotPressed) { 128 | middleBtnState = NOT_PRESSED; 129 | } 130 | break; 131 | } 132 | 133 | buttons &= ~MIDDLE_MOUSE_MASK; 134 | 135 | if (middleBtnState == SCROLLED) { 136 | short scrollY = dy * trackpointScrollMultX / trackpointScrollDivX; 137 | short scrollX = dx * trackpointScrollMultY / trackpointScrollDivY; 138 | 139 | dispatchScrollWheelEvent(scrollY, scrollX, 0, timestamp); 140 | } else { 141 | int mulDx = dx * trackpointMultX / trackpointDivX; 142 | int mulDy = dy * trackpointMultY / trackpointDivY; 143 | 144 | dispatchRelativePointerEvent(mulDx, mulDy, buttons, timestamp); 145 | } 146 | } 147 | 148 | void TrackpointDevice::updateRelativePointer(int dx, int dy, int buttons, uint64_t timestamp) { 149 | dispatchRelativePointerEvent(dx, dy, buttons, timestamp); 150 | }; 151 | 152 | void TrackpointDevice::updateScrollwheel(short deltaAxis1, short deltaAxis2, short deltaAxis3, uint64_t timestamp) { 153 | dispatchScrollWheelEvent(deltaAxis1, deltaAxis2, deltaAxis3, timestamp); 154 | } 155 | 156 | bool TrackpointDevice::willTerminate(IOService* provider, IOOptionBits options) { 157 | return super::willTerminate(provider, options); 158 | } 159 | -------------------------------------------------------------------------------- /VoodooInput/Trackpoint/TrackpointDevice.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * TrackpointDevice.hpp 3 | * VoodooTrackpoint 4 | * 5 | * Copyright (c) 2019 Leonard Kleinhans 6 | * 7 | */ 8 | 9 | #ifndef TrackpointDevice_hpp 10 | #define TrackpointDevice_hpp 11 | 12 | #include 13 | #include 14 | #include "VoodooInputMessages.h" 15 | #include "VoodooInputEvent.h" 16 | 17 | enum MiddlePressedState { 18 | NOT_PRESSED, 19 | PRESSED, 20 | SCROLLED 21 | }; 22 | 23 | class TrackpointDevice : public IOHIPointing { 24 | typedef IOHIPointing super; 25 | OSDeclareDefaultStructors(TrackpointDevice); 26 | private: 27 | int trackpointMultX {1}; 28 | int trackpointMultY {1}; 29 | int trackpointDivX {1}; 30 | int trackpointDivY {1}; 31 | short trackpointScrollMultX {1}; 32 | short trackpointScrollMultY {1}; 33 | short trackpointScrollDivX {1}; 34 | short trackpointScrollDivY {1}; 35 | int trackpointDeadzone {1}; 36 | int btnCount {3}; 37 | 38 | MiddlePressedState middleBtnState {NOT_PRESSED}; 39 | int signum(int value); 40 | void getOSIntValue(OSDictionary *dict, int *val, const char *key); 41 | void getOSShortValue(OSDictionary *dict, short *val, const char *key); 42 | protected: 43 | virtual IOItemCount buttonCount() override; 44 | virtual IOFixed resolution() override; 45 | 46 | public: 47 | bool start(IOService* provider) override; 48 | void stop(IOService* provider) override; 49 | bool willTerminate(IOService* provider, IOOptionBits options) override; 50 | 51 | virtual UInt32 deviceType() override; 52 | virtual UInt32 interfaceID() override; 53 | 54 | void updateRelativePointer(int dx, int dy, int buttons, uint64_t timestamp); 55 | void updateScrollwheel(short deltaAxis1, short deltaAxis2, short deltaAxis3, uint64_t timestamp); 56 | void updateTrackpointProperties(); 57 | void reportPacket(TrackpointReport &report); 58 | }; 59 | 60 | #endif /* TrackpointDevice_hpp */ 61 | -------------------------------------------------------------------------------- /VoodooInput/VoodooInput.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // VoodooInput.сpp 3 | // VoodooInput 4 | // 5 | // Copyright © 2019 Kishor Prins. All rights reserved. 6 | // Copyright (c) 2020 Leonard Kleinhans 7 | // 8 | 9 | #include "VoodooInput.hpp" 10 | #include "VoodooInputIDs.hpp" 11 | #include "VoodooInputMultitouch/VoodooInputMessages.h" 12 | #include "VoodooInputSimulator/VoodooInputActuatorDevice.hpp" 13 | #include "VoodooInputSimulator/VoodooInputSimulatorDevice.hpp" 14 | #include "Trackpoint/TrackpointDevice.hpp" 15 | 16 | #include "libkern/version.h" 17 | 18 | #define super IOService 19 | OSDefineMetaClassAndStructors(VoodooInput, IOService); 20 | 21 | bool VoodooInput::start(IOService *provider) { 22 | if (!super::start(provider)) { 23 | IOLog("Kishor VoodooInput could not super::start!\n"); 24 | return false; 25 | } 26 | 27 | parentProvider = provider; 28 | 29 | if (!updateProperties()) { 30 | IOLog("VoodooInput could not get provider properties!\n"); 31 | return false; 32 | } 33 | 34 | // Allocate the simulator and actuator devices 35 | simulator = OSTypeAlloc(VoodooInputSimulatorDevice); 36 | actuator = OSTypeAlloc(VoodooInputActuatorDevice); 37 | trackpoint = OSTypeAlloc(TrackpointDevice); 38 | 39 | if (!simulator || !actuator || !trackpoint) { 40 | IOLog("VoodooInput could not alloc simulator, actuator or trackpoint!\n"); 41 | OSSafeReleaseNULL(simulator); 42 | OSSafeReleaseNULL(actuator); 43 | OSSafeReleaseNULL(trackpoint); 44 | return false; 45 | } 46 | 47 | // Initialize simulator device 48 | if (!simulator->init(NULL) || !simulator->attach(this)) { 49 | IOLog("VoodooInput could not attach simulator!\n"); 50 | goto exit; 51 | } 52 | else if (!simulator->start(this)) { 53 | IOLog("VoodooInput could not start simulator!\n"); 54 | simulator->detach(this); 55 | goto exit; 56 | } 57 | 58 | // Initialize actuator device 59 | if (!actuator->init(NULL) || !actuator->attach(this)) { 60 | IOLog("VoodooInput could not init or attach actuator!\n"); 61 | goto exit; 62 | } 63 | else if (!actuator->start(this)) { 64 | IOLog("VoodooInput could not start actuator!\n"); 65 | actuator->detach(this); 66 | goto exit; 67 | } 68 | 69 | // Initialize trackpoint device 70 | if (!trackpoint->init(NULL) || !trackpoint->attach(this)) { 71 | IOLog("VoodooInput could not init or attach trackpoint!\n"); 72 | goto exit; 73 | } 74 | else if (!trackpoint->start(this)) { 75 | IOLog("VoodooInput could not start trackpoint!\n"); 76 | trackpoint->detach(this); 77 | goto exit; 78 | } 79 | 80 | setProperty(VOODOO_INPUT_IDENTIFIER, kOSBooleanTrue); 81 | 82 | if (!parentProvider->open(this)) { 83 | IOLog("VoodooInput could not open!\n"); 84 | return false; 85 | }; 86 | 87 | return true; 88 | 89 | exit: 90 | return false; 91 | } 92 | 93 | bool VoodooInput::willTerminate(IOService* provider, IOOptionBits options) { 94 | if (parentProvider->isOpen(this)) { 95 | parentProvider->close(this); 96 | } 97 | 98 | return super::willTerminate(provider, options); 99 | } 100 | 101 | void VoodooInput::stop(IOService *provider) { 102 | if (simulator) { 103 | simulator->stop(this); 104 | simulator->detach(this); 105 | OSSafeReleaseNULL(simulator); 106 | } 107 | 108 | if (actuator) { 109 | actuator->stop(this); 110 | actuator->detach(this); 111 | OSSafeReleaseNULL(actuator); 112 | } 113 | 114 | if (trackpoint) { 115 | trackpoint->stop(this); 116 | trackpoint->detach(this); 117 | OSSafeReleaseNULL(trackpoint); 118 | } 119 | 120 | super::stop(provider); 121 | } 122 | 123 | bool VoodooInput::updateProperties() { 124 | OSNumber* transformNumber = OSDynamicCast(OSNumber, getProperty(VOODOO_INPUT_TRANSFORM_KEY, gIOServicePlane)); 125 | OSNumber* logicalMaxXNumber = OSDynamicCast(OSNumber, getProperty(VOODOO_INPUT_LOGICAL_MAX_X_KEY, gIOServicePlane)); 126 | OSNumber* logicalMaxYNumber = OSDynamicCast(OSNumber, getProperty(VOODOO_INPUT_LOGICAL_MAX_Y_KEY, gIOServicePlane)); 127 | OSNumber* physicalMaxXNumber = OSDynamicCast(OSNumber, getProperty(VOODOO_INPUT_PHYSICAL_MAX_X_KEY, gIOServicePlane)); 128 | OSNumber* physicalMaxYNumber = OSDynamicCast(OSNumber, getProperty(VOODOO_INPUT_PHYSICAL_MAX_Y_KEY, gIOServicePlane)); 129 | 130 | if (transformNumber == nullptr || logicalMaxXNumber == nullptr || logicalMaxYNumber == nullptr || 131 | physicalMaxXNumber == nullptr || physicalMaxYNumber == nullptr) { 132 | return false; 133 | } 134 | 135 | transformKey = transformNumber->unsigned8BitValue(); 136 | logicalMaxX = logicalMaxXNumber->unsigned32BitValue(); 137 | logicalMaxY = logicalMaxYNumber->unsigned32BitValue(); 138 | physicalMaxX = physicalMaxXNumber->unsigned32BitValue(); 139 | physicalMaxY = physicalMaxYNumber->unsigned32BitValue(); 140 | 141 | return true; 142 | } 143 | 144 | UInt8 VoodooInput::getTransformKey() { 145 | return transformKey; 146 | } 147 | 148 | UInt32 VoodooInput::getPhysicalMaxX() { 149 | return physicalMaxX; 150 | } 151 | 152 | UInt32 VoodooInput::getPhysicalMaxY() { 153 | return physicalMaxY; 154 | } 155 | 156 | UInt32 VoodooInput::getLogicalMaxX() { 157 | return logicalMaxX; 158 | } 159 | 160 | UInt32 VoodooInput::getLogicalMaxY() { 161 | return logicalMaxY; 162 | } 163 | 164 | IOReturn VoodooInput::message(UInt32 type, IOService *provider, void *argument) { 165 | switch (type) { 166 | case kIOMessageVoodooInputMessage: 167 | if (provider == parentProvider && argument && simulator) 168 | simulator->constructReport(*(VoodooInputEvent*)argument); 169 | break; 170 | 171 | case kIOMessageVoodooInputUpdateDimensionsMessage: 172 | if (provider == parentProvider && argument) { 173 | const VoodooInputDimensions& dimensions = *(VoodooInputDimensions*)argument; 174 | logicalMaxX = dimensions.max_x - dimensions.min_x; 175 | logicalMaxY = dimensions.max_y - dimensions.min_y; 176 | } 177 | break; 178 | 179 | case kIOMessageVoodooInputUpdatePropertiesNotification: 180 | updateProperties(); 181 | break; 182 | 183 | case kIOMessageVoodooTrackpointRelativePointer: { 184 | if (trackpoint) { 185 | const RelativePointerEvent& event = *(RelativePointerEvent*)argument; 186 | trackpoint->updateRelativePointer(event.dx, event.dy, event.buttons, event.timestamp); 187 | } 188 | break; 189 | } 190 | case kIOMessageVoodooTrackpointScrollWheel: { 191 | if (trackpoint) { 192 | const ScrollWheelEvent& event = *(ScrollWheelEvent*)argument; 193 | trackpoint->updateScrollwheel(event.deltaAxis1, event.deltaAxis2, event.deltaAxis3, event.timestamp); 194 | } 195 | break; 196 | } 197 | case kIOMessageVoodooTrackpointMessage: 198 | if (trackpoint) { 199 | trackpoint->reportPacket(*(TrackpointReport *)argument); 200 | } 201 | break; 202 | case kIOMessageVoodooTrackpointUpdatePropertiesNotification: 203 | if (trackpoint) { 204 | trackpoint->updateTrackpointProperties(); 205 | } 206 | break; 207 | } 208 | 209 | return super::message(type, provider, argument); 210 | } 211 | 212 | int VoodooInputGetProductId() { 213 | if (version_major >= kVoodooInputVersionMonterey) { 214 | return kVoodooInputProductMacbookAir10_1; 215 | } 216 | 217 | return kVoodooInputProductMacbook8_1; 218 | } 219 | -------------------------------------------------------------------------------- /VoodooInput/VoodooInput.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // VoodooInput.hpp 3 | // VoodooInput 4 | // 5 | // Copyright © 2019 Kishor Prins. All rights reserved. 6 | // 7 | 8 | #ifndef VOODOO_INPUT_HPP 9 | #define VOODOO_INPUT_HPP 10 | 11 | #include 12 | 13 | class VoodooInputSimulatorDevice; 14 | class VoodooInputActuatorDevice; 15 | class TrackpointDevice; 16 | 17 | #ifndef EXPORT 18 | #define EXPORT __attribute__((visibility("default"))) 19 | #endif 20 | 21 | class EXPORT VoodooInput : public IOService { 22 | OSDeclareDefaultStructors(VoodooInput); 23 | 24 | IOService* parentProvider; 25 | 26 | VoodooInputSimulatorDevice* simulator; 27 | VoodooInputActuatorDevice* actuator; 28 | TrackpointDevice* trackpoint; 29 | 30 | UInt8 transformKey; 31 | 32 | UInt32 logicalMaxX = 0; 33 | UInt32 logicalMaxY = 0; 34 | UInt32 physicalMaxX = 0; 35 | UInt32 physicalMaxY = 0; 36 | public: 37 | bool start(IOService* provider) override; 38 | void stop(IOService* provider) override; 39 | bool willTerminate(IOService* provider, IOOptionBits options) override; 40 | 41 | UInt8 getTransformKey(); 42 | 43 | UInt32 getPhysicalMaxX(); 44 | UInt32 getPhysicalMaxY(); 45 | 46 | UInt32 getLogicalMaxX(); 47 | UInt32 getLogicalMaxY(); 48 | 49 | bool updateProperties(); 50 | 51 | IOReturn message(UInt32 type, IOService *provider, void *argument) override; 52 | }; 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /VoodooInput/VoodooInputIDs.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // VoodooInputIDs.hpp 3 | // 4 | // Created by Avery Black on 6/15/24. 5 | // Copyright © 2024 Kishor Prins. All rights reserved. 6 | // 7 | 8 | #ifndef VOODOO_INPUT_IDS_HPP 9 | #define VOODOO_INPUT_IDS_HPP 10 | 11 | // macOS 10.10-14 12 | constexpr int kVoodooInputProductMacbook8_1 = 0x272; 13 | // macOS 12+ 14 | constexpr int kVoodooInputProductMacbookAir10_1 = 0x281; 15 | constexpr int kVoodooInputVendorApple = 0x5ac; 16 | 17 | constexpr int kVoodooInputVersionMonterey = 21; 18 | 19 | int VoodooInputGetProductId(); 20 | 21 | #endif // VOODOO_INPUT_IDS_HPP 22 | -------------------------------------------------------------------------------- /VoodooInput/VoodooInputMultitouch/MultitouchHelpers.h: -------------------------------------------------------------------------------- 1 | // 2 | // MultitouchHelpers.hpp 3 | // VooodooInput 4 | // 5 | // Copyright © 2019 Alexandre Daoud. All rights reserved. 6 | // 7 | 8 | #ifndef MULTITOUCH_HELPERS_H 9 | #define MULTITOUCH_HELPERS_H 10 | 11 | #define AbsoluteTime_to_scalar(x) (*(uint64_t *)(x)) 12 | 13 | #define SUB_ABSOLUTETIME(t1, t2) \ 14 | (AbsoluteTime_to_scalar(t1) -= \ 15 | AbsoluteTime_to_scalar(t2)) 16 | 17 | #define kIOPMPowerOff 0 18 | #define kIOPMNumberPowerStates 2 19 | 20 | enum { 21 | // transforms 22 | kIOFBRotateFlags = 0x0000000f, 23 | 24 | kIOFBSwapAxes = 0x00000001, 25 | kIOFBInvertX = 0x00000002, 26 | kIOFBInvertY = 0x00000004, 27 | 28 | kIOFBRotate0 = 0x00000000, 29 | kIOFBRotate90 = kIOFBSwapAxes | kIOFBInvertX, 30 | kIOFBRotate180 = kIOFBInvertX | kIOFBInvertY, 31 | kIOFBRotate270 = kIOFBSwapAxes | kIOFBInvertY 32 | }; 33 | 34 | static IOPMPowerState PMPowerStates[kIOPMNumberPowerStates] = { 35 | {1, kIOPMPowerOff, kIOPMPowerOff, kIOPMPowerOff, 0, 0, 0, 0, 0, 0, 0, 0}, 36 | {1, kIOPMPowerOn, kIOPMPowerOn, kIOPMPowerOn, 0, 0, 0, 0, 0, 0, 0, 0} 37 | }; 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /VoodooInput/VoodooInputMultitouch/VoodooInputEvent.h: -------------------------------------------------------------------------------- 1 | // 2 | // VoodooInputEvent.h 3 | // VooodooInput 4 | // 5 | // Copyright © 2019 Kishor Prins. All rights reserved. 6 | // Copyright (c) 2020 Leonard Kleinhans 7 | // 8 | 9 | #ifndef VOODOO_INPUT_EVENT_H 10 | #define VOODOO_INPUT_EVENT_H 11 | 12 | #include "VoodooInputTransducer.h" 13 | 14 | struct VoodooInputEvent { 15 | UInt8 contact_count; 16 | AbsoluteTime timestamp; 17 | VoodooInputTransducer transducers[VOODOO_INPUT_MAX_TRANSDUCERS]; 18 | }; 19 | 20 | struct VoodooInputDimensions { 21 | SInt32 min_x; 22 | SInt32 max_x; 23 | SInt32 min_y; 24 | SInt32 max_y; 25 | }; 26 | 27 | struct RelativePointerEvent { 28 | uint64_t timestamp; 29 | int dx; 30 | int dy; 31 | int buttons; 32 | }; 33 | 34 | struct ScrollWheelEvent { 35 | uint64_t timestamp; 36 | short deltaAxis1; 37 | short deltaAxis2; 38 | short deltaAxis3; 39 | }; 40 | 41 | struct TrackpointReport { 42 | AbsoluteTime timestamp; 43 | SInt32 dx; 44 | SInt32 dy; 45 | UInt32 buttons; 46 | }; 47 | 48 | #endif /* VoodooInputEvent_h */ 49 | -------------------------------------------------------------------------------- /VoodooInput/VoodooInputMultitouch/VoodooInputMessages.h: -------------------------------------------------------------------------------- 1 | // 2 | // VoodooInputMessages.h 3 | // VooodooInput 4 | // 5 | // Copyright © 2019 Kishor Prins. All rights reserved. 6 | // 7 | 8 | #ifndef VOODOO_INPUT_MESSAGES_H 9 | #define VOODOO_INPUT_MESSAGES_H 10 | 11 | #define VOODOO_INPUT_IDENTIFIER "VoodooInput Instance" 12 | 13 | #define VOODOO_INPUT_TRANSFORM_KEY "IOFBTransform" 14 | #define VOODOO_INPUT_LOGICAL_MAX_X_KEY "Logical Max X" 15 | #define VOODOO_INPUT_LOGICAL_MAX_Y_KEY "Logical Max Y" 16 | #define VOODOO_INPUT_PHYSICAL_MAX_X_KEY "Physical Max X" 17 | #define VOODOO_INPUT_PHYSICAL_MAX_Y_KEY "Physical Max Y" 18 | 19 | #define VOODOO_INPUT_MAX_TRANSDUCERS 10 20 | #define kIOMessageVoodooInputMessage 12345 21 | #define kIOMessageVoodooInputUpdateDimensionsMessage 12346 22 | #define kIOMessageVoodooInputUpdatePropertiesNotification 12347 23 | #define kIOMessageVoodooTrackpointRelativePointer iokit_vendor_specific_msg(430) 24 | #define kIOMessageVoodooTrackpointScrollWheel iokit_vendor_specific_msg(431) 25 | #define kIOMessageVoodooTrackpointMessage iokit_vendor_specific_msg(432) 26 | #define kIOMessageVoodooTrackpointUpdatePropertiesNotification iokit_vendor_specific_msg(433) 27 | 28 | #define kVoodooInputTransducerFingerType 1 29 | #define kVoodooInputTransducerStylusType 2 30 | 31 | #define VOODOO_TRACKPOINT_KEY "VoodooInput Trackpoint" 32 | #define VOODOO_TRACKPOINT_BTN_CNT "Button Count" 33 | #define VOODOO_TRACKPOINT_DEADZONE "Deadzone" 34 | 35 | #define VOODOO_TRACKPOINT_MOUSE_MULT_X "Mouse Multiplier X" 36 | #define VOODOO_TRACKPOINT_MOUSE_MULT_Y "Mouse Multiplier Y" 37 | #define VOODOO_TRACKPOINT_SCROLL_MULT_X "Scroll Multiplier X" 38 | #define VOODOO_TRACKPOINT_SCROLL_MULT_Y "Scroll Multiplier Y" 39 | 40 | #define VOODOO_TRACKPOINT_MOUSE_DIV_X "Mouse Divisor X" 41 | #define VOODOO_TRACKPOINT_MOUSE_DIV_Y "Mouse Divisor Y" 42 | #define VOODOO_TRACKPOINT_SCROLL_DIV_X "Scroll Divisor X" 43 | #define VOODOO_TRACKPOINT_SCROLL_DIV_Y "Scroll Divisor Y" 44 | 45 | #include "VoodooInputTransducer.h" 46 | #include "VoodooInputEvent.h" 47 | 48 | #endif /* VoodooInputMessages_h */ 49 | -------------------------------------------------------------------------------- /VoodooInput/VoodooInputMultitouch/VoodooInputTransducer.h: -------------------------------------------------------------------------------- 1 | // 2 | // VoodooInputTransducer.hpp 3 | // VooodooInput 4 | // 5 | // Copyright © 2019 Kishor Prins. All rights reserved. 6 | // 7 | 8 | #ifndef VOODOO_INPUT_TRANSDUCER_H 9 | #define VOODOO_INPUT_TRANSDUCER_H 10 | 11 | #define VOODOO_INPUT_MAX_TRANSDUCERS 10 12 | 13 | enum MT2FingerType { 14 | kMT2FingerTypeUndefined = 0, 15 | kMT2FingerTypeThumb, 16 | kMT2FingerTypeIndexFinger, 17 | kMT2FingerTypeMiddleFinger, 18 | kMT2FingerTypeRingFinger, 19 | kMT2FingerTypeLittleFinger, 20 | kMT2FingerTypePalm, 21 | kMT2FingerTypeCount = kMT2FingerTypePalm 22 | }; 23 | 24 | enum VoodooInputTransducerType { 25 | STYLUS, 26 | FINGER 27 | }; 28 | 29 | struct TouchCoordinates { 30 | UInt32 x; 31 | UInt32 y; 32 | UInt8 pressure; 33 | UInt8 width; 34 | }; 35 | 36 | struct VoodooInputTransducer { 37 | AbsoluteTime timestamp; 38 | 39 | MT2FingerType fingerType; 40 | UInt32 secondaryId; 41 | VoodooInputTransducerType type; 42 | 43 | bool isValid; 44 | bool isPhysicalButtonDown; 45 | bool isTransducerActive; 46 | bool supportsPressure; 47 | 48 | TouchCoordinates currentCoordinates; 49 | TouchCoordinates previousCoordinates; 50 | 51 | UInt32 maxPressure; 52 | }; 53 | 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /VoodooInput/VoodooInputSimulator/VoodooInputActuatorDevice.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // VoodooInputActuatorDevice.cpp 3 | // VoodooInput 4 | // 5 | // Copyright © 2018 Alexandre Daoud. All rights reserved. 6 | // 7 | 8 | #include "VoodooInputActuatorDevice.hpp" 9 | #include "VoodooInputIDs.hpp" 10 | 11 | #define super IOHIDDevice 12 | OSDefineMetaClassAndStructors(VoodooInputActuatorDevice, IOHIDDevice); 13 | 14 | const unsigned char actuator_report_descriptor[] = {0x06, 0x00, 0xff, 0x09, 0x0d, 0xa1, 0x01, 0x06, 0x00, 0xff, 0x09, 0x0d, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x85, 0x3f, 0x96, 0x0f, 0x00, 0x81, 0x02, 0x09, 0x0d, 0x85, 0x53, 0x96, 0x3f, 0x00, 0x91, 0x02, 0xc0}; 15 | 16 | IOReturn VoodooInputActuatorDevice::setReport(IOMemoryDescriptor* report, IOHIDReportType reportType, IOOptionBits options) { 17 | return kIOReturnSuccess; 18 | } 19 | 20 | IOReturn VoodooInputActuatorDevice::newReportDescriptor(IOMemoryDescriptor** descriptor) const { 21 | IOBufferMemoryDescriptor* report_descriptor_buffer = IOBufferMemoryDescriptor::inTaskWithOptions(kernel_task, 0, sizeof(actuator_report_descriptor)); 22 | 23 | if (!report_descriptor_buffer) { 24 | IOLog("%s Could not allocate buffer for report descriptor\n", getName()); 25 | return kIOReturnNoResources; 26 | } 27 | 28 | report_descriptor_buffer->writeBytes(0, actuator_report_descriptor, sizeof(actuator_report_descriptor)); 29 | *descriptor = report_descriptor_buffer; 30 | 31 | return kIOReturnSuccess; 32 | } 33 | 34 | OSString* VoodooInputActuatorDevice::newManufacturerString() const { 35 | return OSString::withCString("Apple Inc."); 36 | } 37 | 38 | OSNumber* VoodooInputActuatorDevice::newPrimaryUsageNumber() const { 39 | return OSNumber::withNumber(0xd, 32); 40 | } 41 | 42 | OSNumber* VoodooInputActuatorDevice::newPrimaryUsagePageNumber() const { 43 | return OSNumber::withNumber(0xff00, 32); 44 | } 45 | 46 | OSNumber* VoodooInputActuatorDevice::newProductIDNumber() const { 47 | return OSNumber::withNumber(VoodooInputGetProductId(), 32); 48 | } 49 | 50 | OSString* VoodooInputActuatorDevice::newProductString() const { 51 | return OSString::withCString("Magic Trackpad 2"); 52 | } 53 | 54 | OSString* VoodooInputActuatorDevice::newSerialNumberString() const { 55 | return OSString::withCString("VoodooI2C Magic Trackpad 2 Actuator"); 56 | } 57 | 58 | OSString* VoodooInputActuatorDevice::newTransportString() const { 59 | return OSString::withCString("I2C"); 60 | } 61 | 62 | OSNumber* VoodooInputActuatorDevice::newVendorIDNumber() const { 63 | return OSNumber::withNumber(kVoodooInputVendorApple, 16); 64 | } 65 | 66 | OSNumber* VoodooInputActuatorDevice::newLocationIDNumber() const { 67 | return OSNumber::withNumber(0x14400000, 32); 68 | } 69 | 70 | OSNumber* VoodooInputActuatorDevice::newVersionNumber() const { 71 | return OSNumber::withNumber(0x804, 32); 72 | } 73 | -------------------------------------------------------------------------------- /VoodooInput/VoodooInputSimulator/VoodooInputActuatorDevice.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // VoodooInputActuatorDevice.hpp 3 | // VoodooInput 4 | // 5 | // Copyright © 2019 Alexandre Daoud and Kishor Prins. All rights reserved. 6 | // 7 | 8 | #ifndef VOODOO_ACTUATOR_DEVICE_HPP 9 | #define VOODOO_ACTUATOR_DEVICE_HPP 10 | 11 | #include 12 | #include 13 | 14 | #ifndef EXPORT 15 | #define EXPORT __attribute__((visibility("default"))) 16 | #endif 17 | 18 | class EXPORT VoodooInputActuatorDevice : public IOHIDDevice { 19 | OSDeclareDefaultStructors(VoodooInputActuatorDevice); 20 | 21 | public: 22 | IOReturn setReport(IOMemoryDescriptor* report, IOHIDReportType reportType, IOOptionBits options) override; 23 | 24 | IOReturn newReportDescriptor(IOMemoryDescriptor** descriptor) const override; 25 | OSNumber* newVendorIDNumber() const override; 26 | 27 | 28 | OSNumber* newProductIDNumber() const override; 29 | 30 | 31 | OSNumber* newVersionNumber() const override; 32 | 33 | 34 | OSString* newTransportString() const override; 35 | 36 | 37 | OSString* newManufacturerString() const override; 38 | 39 | OSNumber* newPrimaryUsageNumber() const override; 40 | 41 | OSNumber* newPrimaryUsagePageNumber() const override; 42 | 43 | OSString* newProductString() const override; 44 | 45 | OSString* newSerialNumberString() const override; 46 | 47 | OSNumber* newLocationIDNumber() const override; 48 | }; 49 | 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /VoodooInput/VoodooInputSimulator/VoodooInputSimulatorDevice.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // VoodooInputSimulatorDevice.cpp 3 | // VoodooInput 4 | // 5 | // Copyright © 2018 Alexandre Daoud and Kishor Prins. All rights reserved. 6 | // 7 | 8 | #include "VoodooInput.hpp" 9 | #include "VoodooInputSimulatorDevice.hpp" 10 | #include "../VoodooInputMultitouch/MultitouchHelpers.h" 11 | #include "VoodooInputIDs.hpp" 12 | 13 | #include 14 | #include 15 | 16 | #define super IOHIDDevice 17 | OSDefineMetaClassAndStructors(VoodooInputSimulatorDevice, IOHIDDevice); 18 | 19 | 20 | const unsigned char report_descriptor[] = {0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01, 0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03, 0x15, 0x00, 0x25, 0x01, 0x85, 0x02, 0x95, 0x03, 0x75, 0x01, 0x81, 0x02, 0x95, 0x01, 0x75, 0x05, 0x81, 0x01, 0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x15, 0x81, 0x25, 0x7f, 0x75, 0x08, 0x95, 0x02, 0x81, 0x06, 0x95, 0x04, 0x75, 0x08, 0x81, 0x01, 0xc0, 0xc0, 0x05, 0x0d, 0x09, 0x05, 0xa1, 0x01, 0x06, 0x00, 0xff, 0x09, 0x0c, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x10, 0x85, 0x3f, 0x81, 0x22, 0xc0, 0x06, 0x00, 0xff, 0x09, 0x0c, 0xa1, 0x01, 0x06, 0x00, 0xff, 0x09, 0x0c, 0x15, 0x00, 0x26, 0xff, 0x00, 0x85, 0x44, 0x75, 0x08, 0x96, 0x6b, 0x05, 0x81, 0x00, 0xc0}; 21 | 22 | void VoodooInputSimulatorDevice::constructReport(const VoodooInputEvent& multitouch_event) { 23 | if (!ready_for_reports) 24 | return; 25 | 26 | command_gate->runAction(OSMemberFunctionCast(IOCommandGate::Action, this, &VoodooInputSimulatorDevice::constructReportGated), (void*)&multitouch_event); 27 | } 28 | 29 | void VoodooInputSimulatorDevice::sendReport() { 30 | #if DEBUG 31 | size_t fingerCount = (input_report_buffer->getLength() - sizeof(MAGIC_TRACKPAD_INPUT_REPORT)) / sizeof(MAGIC_TRACKPAD_INPUT_REPORT_FINGER); 32 | IOLog("Sending report with touch active %d, button %d, finger count %zu\n", input_report->TouchActive, input_report->Button, fingerCount); 33 | for (size_t i = 0; i < fingerCount; i++) { 34 | MAGIC_TRACKPAD_INPUT_REPORT_FINGER &f = input_report->FINGERS[i]; 35 | IOLog("[%zu] (%d, %d) F%d St%d Maj%d Min%d Sz%d P%d ID%d A%d\n", i, f.X, f.Y, f.Finger, f.State, f.Touch_Major, f.Touch_Minor, f.Size, f.Pressure, f.Identifier, f.Angle); 36 | } 37 | #endif 38 | handleReport(input_report_buffer, kIOHIDReportTypeInput); 39 | } 40 | 41 | void VoodooInputSimulatorDevice::constructReportGated(const VoodooInputEvent& multitouch_event) { 42 | AbsoluteTime timestamp = multitouch_event.timestamp; 43 | 44 | input_report->ReportID = 0x02; 45 | input_report->Unused[0] = 0; 46 | input_report->Unused[1] = 0; 47 | input_report->Unused[2] = 0; 48 | input_report->Unused[3] = 0; 49 | input_report->Unused[4] = 0; 50 | 51 | const VoodooInputTransducer* transducer = &multitouch_event.transducers[0]; 52 | 53 | // physical button 54 | input_report->Button = transducer->isPhysicalButtonDown; 55 | 56 | // touch active 57 | 58 | // rotation check 59 | 60 | UInt8 transform = engine->getTransformKey(); 61 | 62 | // multitouch report id 63 | input_report->multitouch_report_id = 0x31; // Magic 64 | 65 | // timestamp 66 | AbsoluteTime relative_timestamp = timestamp; 67 | 68 | SUB_ABSOLUTETIME(&relative_timestamp, &start_timestamp); 69 | 70 | UInt64 milli_timestamp; 71 | 72 | absolutetime_to_nanoseconds(relative_timestamp, &milli_timestamp); 73 | 74 | milli_timestamp /= 1000000; 75 | 76 | input_report->timestamp_buffer[0] = (milli_timestamp << 0x3) | 0x4; 77 | input_report->timestamp_buffer[1] = (milli_timestamp >> 0x5) & 0xFF; 78 | input_report->timestamp_buffer[2] = (milli_timestamp >> 0xd) & 0xFF; 79 | 80 | // finger data 81 | bool input_active = input_report->Button; 82 | bool is_error_input_active = false; 83 | 84 | for (int i = 0; i < multitouch_event.contact_count; i++) { 85 | const VoodooInputTransducer* transducer = &multitouch_event.transducers[i]; 86 | 87 | if (!transducer || !transducer->isValid) 88 | continue; 89 | 90 | if (transducer->type == VoodooInputTransducerType::STYLUS) { 91 | continue; 92 | } 93 | 94 | // in case the obtained id is greater than 14, usually 0~4 for common devices. 95 | UInt16 touch_id = transducer->secondaryId % 15; 96 | input_active |= transducer->isTransducerActive; 97 | 98 | MAGIC_TRACKPAD_INPUT_REPORT_FINGER& finger_data = input_report->FINGERS[i]; 99 | 100 | IOFixed scaled_x = ((transducer->currentCoordinates.x * 1.0f) / engine->getLogicalMaxX()) * MT2_MAX_X; 101 | IOFixed scaled_y = ((transducer->currentCoordinates.y * 1.0f) / engine->getLogicalMaxY()) * MT2_MAX_Y; 102 | 103 | if (scaled_x < 1 && scaled_y >= MT2_MAX_Y) { 104 | is_error_input_active = true; 105 | } 106 | 107 | if (transform) { 108 | if (transform & kIOFBSwapAxes) { 109 | scaled_x = ((transducer->currentCoordinates.y * 1.0f) / engine->getLogicalMaxY()) * MT2_MAX_X; 110 | scaled_y = ((transducer->currentCoordinates.x * 1.0f) / engine->getLogicalMaxX()) * MT2_MAX_Y; 111 | } 112 | 113 | if (transform & kIOFBInvertX) { 114 | scaled_x = MT2_MAX_X - scaled_x; 115 | } 116 | if (transform & kIOFBInvertY) { 117 | scaled_y = MT2_MAX_Y - scaled_y; 118 | } 119 | } 120 | 121 | finger_data.State = touch_active[touch_id] ? kTouchStateActive : kTouchStateStart; 122 | touch_active[touch_id] = transducer->isTransducerActive || transducer->isPhysicalButtonDown; 123 | 124 | finger_data.Finger = transducer->fingerType; 125 | 126 | if (transducer->supportsPressure) { 127 | finger_data.Pressure = transducer->currentCoordinates.pressure; 128 | finger_data.Size = transducer->currentCoordinates.width; 129 | finger_data.Touch_Major = transducer->currentCoordinates.width; 130 | finger_data.Touch_Minor = transducer->currentCoordinates.width; 131 | } else { 132 | finger_data.Pressure = 5; 133 | finger_data.Size = 10; 134 | finger_data.Touch_Major = 20; 135 | finger_data.Touch_Minor = 20; 136 | } 137 | 138 | if (input_report->Button) { 139 | finger_data.Pressure = 120; 140 | } 141 | 142 | if (!transducer->isTransducerActive && !transducer->isPhysicalButtonDown) { 143 | finger_data.State = kTouchStateStop; 144 | finger_data.Size = 0x0; 145 | finger_data.Pressure = 0x0; 146 | finger_data.Touch_Minor = 0; 147 | finger_data.Touch_Major = 0; 148 | } 149 | 150 | finger_data.X = (SInt16)(scaled_x - (MT2_MAX_X / 2)); 151 | finger_data.Y = (SInt16)(scaled_y - (MT2_MAX_Y / 2)) * -1; 152 | 153 | finger_data.Angle = 0x4; // pi/2 154 | finger_data.Identifier = touch_id + 1; 155 | } 156 | 157 | if (input_active) 158 | input_report->TouchActive = 0x3; 159 | else 160 | input_report->TouchActive = 0x2; 161 | 162 | vm_size_t total_report_len = sizeof(MAGIC_TRACKPAD_INPUT_REPORT) + 163 | sizeof(MAGIC_TRACKPAD_INPUT_REPORT_FINGER) * multitouch_event.contact_count; 164 | 165 | if (!is_error_input_active) { 166 | input_report_buffer->setLength(total_report_len); 167 | sendReport(); 168 | } 169 | 170 | if (!input_active) { 171 | memset(touch_active, false, sizeof(touch_active)); 172 | 173 | input_report->FINGERS[0].Size = 0x0; 174 | input_report->FINGERS[0].Pressure = 0x0; 175 | input_report->FINGERS[0].Touch_Major = 0x0; 176 | input_report->FINGERS[0].Touch_Minor = 0x0; 177 | 178 | milli_timestamp += 10; 179 | 180 | input_report->timestamp_buffer[0] = (milli_timestamp << 0x3) | 0x4; 181 | input_report->timestamp_buffer[1] = (milli_timestamp >> 0x5) & 0xFF; 182 | input_report->timestamp_buffer[2] = (milli_timestamp >> 0xd) & 0xFF; 183 | input_report_buffer->setLength(total_report_len); 184 | sendReport(); 185 | 186 | input_report->FINGERS[0].Finger = kMT2FingerTypeUndefined; 187 | input_report->FINGERS[0].State = kTouchStateInactive; 188 | input_report_buffer->setLength(total_report_len); 189 | sendReport(); 190 | 191 | milli_timestamp += 10; 192 | 193 | input_report->timestamp_buffer[0] = (milli_timestamp << 0x3) | 0x4; 194 | input_report->timestamp_buffer[1] = (milli_timestamp >> 0x5) & 0xFF; 195 | input_report->timestamp_buffer[2] = (milli_timestamp >> 0xd) & 0xFF; 196 | input_report_buffer->setLength(sizeof(MAGIC_TRACKPAD_INPUT_REPORT)); 197 | sendReport(); 198 | } 199 | 200 | memset(input_report, 0, total_report_len); 201 | } 202 | 203 | bool VoodooInputSimulatorDevice::start(IOService* provider) { 204 | if (!super::start(provider)) 205 | return false; 206 | ready_for_reports = false; 207 | 208 | input_report_buffer = IOBufferMemoryDescriptor::inTaskWithOptions(kernel_task, 0, 209 | sizeof(MAGIC_TRACKPAD_INPUT_REPORT) + sizeof(MAGIC_TRACKPAD_INPUT_REPORT_FINGER) * VOODOO_INPUT_MAX_TRANSDUCERS); 210 | if (!input_report_buffer) { 211 | IOLog("%s Could not allocate IOBufferMemoryDescriptor\n", getName()); 212 | return false; 213 | } 214 | input_report = (MAGIC_TRACKPAD_INPUT_REPORT *) input_report_buffer->getBytesNoCopy(); 215 | 216 | clock_get_uptime(&start_timestamp); 217 | 218 | engine = OSDynamicCast(VoodooInput, provider); 219 | 220 | if (!engine) { 221 | releaseResources(); 222 | return false; 223 | } 224 | 225 | work_loop = this->getWorkLoop(); 226 | if (!work_loop) { 227 | IOLog("%s Could not get a IOWorkLoop instance\n", getName()); 228 | releaseResources(); 229 | return false; 230 | } 231 | 232 | work_loop->retain(); 233 | 234 | command_gate = IOCommandGate::commandGate(this); 235 | if (!command_gate || (work_loop->addEventSource(command_gate) != kIOReturnSuccess)) { 236 | IOLog("%s Could not open command gate\n", getName()); 237 | releaseResources(); 238 | return false; 239 | } 240 | 241 | PMinit(); 242 | provider->joinPMtree(this); 243 | registerPowerDriver(this, PMPowerStates, kIOPMNumberPowerStates); 244 | 245 | new_get_report_buffer = nullptr; 246 | 247 | ready_for_reports = true; 248 | 249 | return true; 250 | } 251 | 252 | void VoodooInputSimulatorDevice::stop(IOService* provider) { 253 | releaseResources(); 254 | 255 | PMstop(); 256 | 257 | super::stop(provider); 258 | } 259 | 260 | IOReturn VoodooInputSimulatorDevice::setPowerState(unsigned long whichState, IOService* whatDevice) { 261 | if (whatDevice != this) 262 | return kIOReturnInvalid; 263 | if (whichState == 0) { 264 | // ready_for_reports = false; 265 | } else { 266 | // ready_for_reports = true; 267 | } 268 | return kIOPMAckImplied; 269 | } 270 | 271 | void VoodooInputSimulatorDevice::releaseResources() { 272 | if (command_gate) { 273 | work_loop->removeEventSource(command_gate); 274 | OSSafeReleaseNULL(command_gate); 275 | } 276 | input_report = nullptr; 277 | 278 | OSSafeReleaseNULL(work_loop); 279 | 280 | OSSafeReleaseNULL(new_get_report_buffer); 281 | 282 | OSSafeReleaseNULL(input_report_buffer); 283 | } 284 | 285 | IOReturn VoodooInputSimulatorDevice::setReport(IOMemoryDescriptor* report, IOHIDReportType reportType, IOOptionBits options) { 286 | UInt32 report_id = options & 0xFF; 287 | 288 | if (report_id == 0x1) { 289 | char* raw_buffer = (char*)IOMalloc(report->getLength()); 290 | 291 | report->prepare(); 292 | 293 | report->readBytes(0, raw_buffer, report->getLength()); 294 | 295 | report->complete(); 296 | 297 | if (new_get_report_buffer) { 298 | new_get_report_buffer->release(); 299 | } 300 | 301 | new_get_report_buffer = OSData::withCapacity(1); 302 | 303 | if (!new_get_report_buffer) { 304 | return kIOReturnNoResources; 305 | } 306 | 307 | UInt8 value = raw_buffer[1]; 308 | 309 | if (value == 0xDB) { 310 | unsigned char buffer[] = {0x1, 0xDB, 0x00, 0x49, 0x00}; 311 | new_get_report_buffer->appendBytes(buffer, sizeof(buffer)); 312 | } 313 | 314 | if (value == 0xD1) { 315 | unsigned char buffer[] = {0x1, 0xD1, 0x00, 0x01, 0x00}; 316 | new_get_report_buffer->appendBytes(buffer, sizeof(buffer)); 317 | } 318 | 319 | if (value == 0xD3) { 320 | unsigned char buffer[] = {0x1, 0xD3, 0x00, 0x0C, 0x00}; 321 | new_get_report_buffer->appendBytes(buffer, sizeof(buffer)); 322 | } 323 | 324 | if (value == 0xD0) { 325 | unsigned char buffer[] = {0x1, 0xD0, 0x00, 0x0F, 0x00}; 326 | new_get_report_buffer->appendBytes(buffer, sizeof(buffer)); 327 | } 328 | 329 | if (value == 0xA1) { 330 | unsigned char buffer[] = {0x1, 0xA1, 0x00, 0x06, 0x00}; 331 | new_get_report_buffer->appendBytes(buffer, sizeof(buffer)); 332 | } 333 | 334 | if (value == 0xD9) { 335 | //Sensor Surface Width = 0x3cf0 (0xf0, 0x3c) = 15.600 cm 336 | //Sensor Surface Height = 0x2b20 (0x20, 0x2b) = 11.040 cm 337 | 338 | // It's already in 0.01 mm units 339 | uint32_t rawWidth = engine->getPhysicalMaxX(); 340 | uint32_t rawHeight = engine->getPhysicalMaxY(); 341 | 342 | uint8_t rawWidthLower = rawWidth & 0xff; 343 | uint8_t rawWidthHigher = (rawWidth >> 8) & 0xff; 344 | 345 | uint8_t rawHeightLower = rawHeight & 0xff; 346 | uint8_t rawHeightHigher = (rawHeight >> 8) & 0xff; 347 | 348 | unsigned char buffer[] = {0xD9, rawWidthLower, rawWidthHigher, 0x00, 0x00, rawHeightLower, rawHeightHigher, 0x00, 0x00, 0x44, 0xE3, 0x52, 0xFF, 0xBD, 0x1E, 0xE4, 0x26}; //Sensor Surface Description 349 | new_get_report_buffer->appendBytes(buffer, sizeof(buffer)); 350 | } 351 | 352 | if (value == 0x7F) { 353 | unsigned char buffer[] = {0x1, 0x7F, 0x00, 0x04, 0x00}; 354 | new_get_report_buffer->appendBytes(buffer, sizeof(buffer)); 355 | } 356 | 357 | if (value == 0xC8) { 358 | unsigned char buffer[] = {0x1, 0xC8, 0x00, 0x01, 0x00}; 359 | new_get_report_buffer->appendBytes(buffer, sizeof(buffer)); 360 | } 361 | 362 | IOFree(raw_buffer, report->getLength()); 363 | } 364 | 365 | return kIOReturnSuccess; 366 | } 367 | 368 | IOReturn VoodooInputSimulatorDevice::getReport(IOMemoryDescriptor* report, IOHIDReportType reportType, IOOptionBits options) { 369 | UInt32 report_id = options & 0xFF; 370 | Boolean getReportedAllocated = true; 371 | 372 | OSData* get_buffer = OSData::withCapacity(1); 373 | 374 | if (!get_buffer || (report_id == 0x1 && !new_get_report_buffer)) { 375 | OSSafeReleaseNULL(get_buffer); 376 | return kIOReturnNoResources; 377 | } 378 | 379 | if (report_id == 0x0) { 380 | unsigned char buffer[] = {0x0, 0x01}; 381 | get_buffer->appendBytes(buffer, sizeof(buffer)); 382 | } 383 | 384 | if (report_id == 0x1) { 385 | get_buffer->release(); 386 | get_buffer = new_get_report_buffer; 387 | getReportedAllocated = false; 388 | } 389 | 390 | if (report_id == 0xD1) { 391 | unsigned char buffer[] = {0xD1, 0x81}; 392 | // Family ID = 0x81 393 | get_buffer->appendBytes(buffer, sizeof(buffer)); 394 | } 395 | 396 | if (report_id == 0xD3) { 397 | unsigned char buffer[] = {0xD3, 0x01, 0x16, 0x1E, 0x03, 0x95, 0x00, 0x14, 0x1E, 0x62, 0x05, 0x00, 0x00}; 398 | // Sensor Rows = 0x16 399 | // Sensor Columns = 0x1e 400 | get_buffer->appendBytes(buffer, sizeof(buffer)); 401 | } 402 | 403 | if (report_id == 0xD0) { 404 | unsigned char buffer[] = {0xD0, 0x02, 0x01, 0x00, 0x14, 0x01, 0x00, 0x1E, 0x00, 0x02, 0x14, 0x02, 0x01, 0x0E, 0x02, 0x00}; // Sensor Region Description 405 | get_buffer->appendBytes(buffer, sizeof(buffer)); 406 | } 407 | 408 | if (report_id == 0xA1) { 409 | unsigned char buffer[] = {0xA1, 0x00, 0x00, 0x05, 0x00, 0xFC, 0x01}; // Sensor Region Param 410 | get_buffer->appendBytes(buffer, sizeof(buffer)); 411 | } 412 | 413 | if (report_id == 0xD9) { 414 | // Sensor Surface Width = 0x3cf0 (0xf0, 0x3c) = 15.600 cm 415 | // Sensor Surface Height = 0x2b20 (0x20, 0x2b) = 11.040 cm 416 | 417 | uint32_t rawWidth = engine->getPhysicalMaxX(); 418 | uint32_t rawHeight = engine->getPhysicalMaxY(); 419 | 420 | uint8_t rawWidthLower = rawWidth & 0xff; 421 | uint8_t rawWidthHigher = (rawWidth >> 8) & 0xff; 422 | 423 | uint8_t rawHeightLower = rawHeight & 0xff; 424 | uint8_t rawHeightHigher = (rawHeight >> 8) & 0xff; 425 | 426 | unsigned char buffer[] = {0xD9, rawWidthLower, rawWidthHigher, 0x00, 0x00, rawHeightLower, rawHeightHigher, 0x00, 0x00, 0x44, 0xE3, 0x52, 0xFF, 0xBD, 0x1E, 0xE4, 0x26}; // Sensor Surface Description 427 | get_buffer->appendBytes(buffer, sizeof(buffer)); 428 | } 429 | 430 | if (report_id == 0x7F) { 431 | unsigned char buffer[] = {0x7F, 0x00, 0x00, 0x00, 0x00}; 432 | get_buffer->appendBytes(buffer, sizeof(buffer)); 433 | } 434 | 435 | if (report_id == 0xC8) { 436 | unsigned char buffer[] = {0xC8, 0x08}; 437 | get_buffer->appendBytes(buffer, sizeof(buffer)); 438 | } 439 | 440 | if (report_id == 0x2) { 441 | unsigned char buffer[] = {0x02, 0x01}; 442 | get_buffer->appendBytes(buffer, sizeof(buffer)); 443 | } 444 | 445 | if (report_id == 0xDB) { 446 | uint32_t rawWidth = engine->getPhysicalMaxX(); 447 | uint32_t rawHeight = engine->getPhysicalMaxY(); 448 | 449 | uint8_t rawWidthLower = rawWidth & 0xff; 450 | uint8_t rawWidthHigher = (rawWidth >> 8) & 0xff; 451 | 452 | uint8_t rawHeightLower = rawHeight & 0xff; 453 | uint8_t rawHeightHigher = (rawHeight >> 8) & 0xff; 454 | 455 | unsigned char buffer[] = {0xDB, 0x01, 0x02, 0x00, 456 | /* Start 0xD1 */ 0xD1, 0x81, /* End 0xD1 */ 457 | 0x0D, 0x00, 458 | /* Start 0xD3 */ 0xD3, 0x01, 0x16, 0x1E, 0x03, 0x95, 0x00, 0x14, 0x1E, 0x62, 0x05, 0x00, 0x00, /* End 0xD3 */ 459 | 0x10, 0x00, 460 | /* Start 0xD0 */ 0xD0, 0x02, 0x01, 0x00, 0x14, 0x01, 0x00, 0x1E, 0x00, 0x02, 0x14, 0x02, 0x01, 0x0E, 0x02, 0x00, /* End 0xD0 */ 461 | 0x07, 0x00, 462 | /* Start 0xA1 */ 0xA1, 0x00, 0x00, 0x05, 0x00, 0xFC, 0x01, /* End 0xA1 */ 463 | 0x11, 0x00, 464 | /* Start 0xD9 */ 0xD9, rawWidthLower, rawWidthHigher, 0x00, 0x00, rawHeightLower, rawHeightHigher, 0x00, 0x00, 0x44, 0xE3, 0x52, 0xFF, 0xBD, 0x1E, 0xE4, 0x26, 465 | /* Start 0x7F */ 0x7F, 0x00, 0x00, 0x00, 0x00 /*End 0x7F */}; 466 | get_buffer->appendBytes(buffer, sizeof(buffer)); 467 | } 468 | 469 | report->writeBytes(0, get_buffer->getBytesNoCopy(), get_buffer->getLength()); 470 | 471 | if (getReportedAllocated) { 472 | get_buffer->release(); 473 | } 474 | 475 | return kIOReturnSuccess; 476 | } 477 | 478 | IOReturn VoodooInputSimulatorDevice::newReportDescriptor(IOMemoryDescriptor** descriptor) const { 479 | IOBufferMemoryDescriptor* report_descriptor_buffer = IOBufferMemoryDescriptor::inTaskWithOptions(kernel_task, 0, sizeof(report_descriptor)); 480 | 481 | if (!report_descriptor_buffer) { 482 | IOLog("%s Could not allocate buffer for report descriptor\n", getName()); 483 | return kIOReturnNoResources; 484 | } 485 | 486 | report_descriptor_buffer->writeBytes(0, report_descriptor, sizeof(report_descriptor)); 487 | *descriptor = report_descriptor_buffer; 488 | 489 | return kIOReturnSuccess; 490 | } 491 | 492 | OSString* VoodooInputSimulatorDevice::newManufacturerString() const { 493 | return OSString::withCString("Apple Inc."); 494 | } 495 | 496 | OSNumber* VoodooInputSimulatorDevice::newPrimaryUsageNumber() const { 497 | return OSNumber::withNumber(kHIDUsage_GD_Mouse, 32); 498 | } 499 | 500 | OSNumber* VoodooInputSimulatorDevice::newPrimaryUsagePageNumber() const { 501 | return OSNumber::withNumber(kHIDPage_GenericDesktop, 32); 502 | } 503 | 504 | OSNumber* VoodooInputSimulatorDevice::newProductIDNumber() const { 505 | return OSNumber::withNumber(VoodooInputGetProductId(), 32); 506 | } 507 | 508 | OSString* VoodooInputSimulatorDevice::newProductString() const { 509 | return OSString::withCString("Magic Trackpad 2"); 510 | } 511 | 512 | OSString* VoodooInputSimulatorDevice::newSerialNumberString() const { 513 | return OSString::withCString("Voodoo Magic Trackpad 2 Simulator"); 514 | } 515 | 516 | OSString* VoodooInputSimulatorDevice::newTransportString() const { 517 | return OSString::withCString("I2C"); 518 | } 519 | 520 | OSNumber* VoodooInputSimulatorDevice::newVendorIDNumber() const { 521 | return OSNumber::withNumber(kVoodooInputVendorApple, 16); 522 | } 523 | 524 | OSNumber* VoodooInputSimulatorDevice::newLocationIDNumber() const { 525 | return OSNumber::withNumber(0x14400000, 32); 526 | } 527 | 528 | OSNumber* VoodooInputSimulatorDevice::newVersionNumber() const { 529 | return OSNumber::withNumber(0x804, 32); 530 | } 531 | -------------------------------------------------------------------------------- /VoodooInput/VoodooInputSimulator/VoodooInputSimulatorDevice.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // VoodooInputSimulatorDevice.hpp 3 | // VoodooInput 4 | // 5 | // Copyright © 2019 Alexandre Daoud and Kishor Prins. All rights reserved. 6 | // 7 | 8 | #ifndef VOODOO_SIMULATOR_DEVICE_HPP 9 | #define VOODOO_SIMULATOR_DEVICE_HPP 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include "../VoodooInput.hpp" 17 | #include "../VoodooInputMultitouch/VoodooInputTransducer.h" 18 | #include "../VoodooInputMultitouch/VoodooInputEvent.h" 19 | #include "../VoodooInputMultitouch/MultitouchHelpers.h" 20 | 21 | #ifndef EXPORT 22 | #define EXPORT __attribute__((visibility("default"))) 23 | #endif 24 | 25 | #define MT2_MAX_X 8134 26 | #define MT2_MAX_Y 5206 27 | 28 | /* State bits reference: linux/drivers/hid/hid-magicmouse.c#L58-L63 */ 29 | #define MT2_TOUCH_STATE_BIT_TRANSITION (0x1) 30 | #define MT2_TOUCH_STATE_BIT_NEAR (0x1 << 1) 31 | #define MT2_TOUCH_STATE_BIT_CONTACT (0x1 << 2) 32 | 33 | enum TouchStates { 34 | kTouchStateInactive = 0x0, 35 | kTouchStateStart = MT2_TOUCH_STATE_BIT_NEAR | MT2_TOUCH_STATE_BIT_TRANSITION, 36 | kTouchStateActive = MT2_TOUCH_STATE_BIT_CONTACT, 37 | kTouchStateStop = MT2_TOUCH_STATE_BIT_CONTACT | MT2_TOUCH_STATE_BIT_NEAR | MT2_TOUCH_STATE_BIT_TRANSITION 38 | }; 39 | 40 | /* Finger Packet 41 | +---+---+---+---+---+---+---+---+---+ 42 | | | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 43 | +---+---+---+---+---+---+---+---+---+ 44 | | 0 | x: SInt13 | 45 | +---+-----------+ + 46 | | 1 | | | 47 | +---+ +-------------------+ 48 | | 2 | y: SInt13 | 49 | +---+-----------+-----------+ + 50 | | 3 | state | finger | | 51 | | | UInt3 | UInt3 | | 52 | +---+-----------+-----------+-------+ 53 | | 4 | touchMajor: UInt8 | 54 | +---+-------------------------------+ 55 | | 5 | touchMinor: UInt8 | 56 | +---+-------------------------------+ 57 | | 6 | size: UInt8 | 58 | +---+-------------------------------+ 59 | | 7 | pressure: UInt8 | 60 | +---+-----------+---+---------------+ 61 | | 8 | angle | 0 | touchID | 62 | | | UInt3 | | UInt4 | 63 | +---+-----------+---+---------------+ 64 | */ 65 | struct __attribute__((__packed__)) MAGIC_TRACKPAD_INPUT_REPORT_FINGER { 66 | SInt16 X: 13; 67 | SInt16 Y: 13; 68 | UInt8 Finger: 3; 69 | UInt8 State: 3; 70 | UInt8 Touch_Major; 71 | UInt8 Touch_Minor; 72 | UInt8 Size; 73 | UInt8 Pressure; 74 | UInt8 Identifier: 4; 75 | UInt8 : 1; 76 | UInt8 Angle: 3; 77 | }; 78 | 79 | struct __attribute__((__packed__)) MAGIC_TRACKPAD_INPUT_REPORT { 80 | UInt8 ReportID; 81 | UInt8 Button; 82 | UInt8 Unused[5]; 83 | 84 | UInt8 TouchActive; 85 | 86 | UInt8 multitouch_report_id; 87 | UInt8 timestamp_buffer[3]; 88 | 89 | MAGIC_TRACKPAD_INPUT_REPORT_FINGER FINGERS[]; // May support more fingers 90 | }; 91 | 92 | static_assert(sizeof(MAGIC_TRACKPAD_INPUT_REPORT) == 12, "Unexpected MAGIC_TRACKPAD_INPUT_REPORT size"); 93 | static_assert(sizeof(MAGIC_TRACKPAD_INPUT_REPORT_FINGER) == 9, "Unexpected MAGIC_TRACKPAD_INPUT_REPORT_FINGER size"); 94 | 95 | class EXPORT VoodooInputSimulatorDevice : public IOHIDDevice { 96 | OSDeclareDefaultStructors(VoodooInputSimulatorDevice); 97 | 98 | public: 99 | void constructReport(const VoodooInputEvent& multitouch_event); 100 | 101 | IOReturn setReport(IOMemoryDescriptor* report, IOHIDReportType reportType, IOOptionBits options) override; 102 | 103 | IOReturn getReport(IOMemoryDescriptor* report, IOHIDReportType reportType, IOOptionBits options) override; 104 | IOReturn newReportDescriptor(IOMemoryDescriptor** descriptor) const override; 105 | 106 | OSNumber* newVendorIDNumber() const override; 107 | OSNumber* newProductIDNumber() const override; 108 | OSNumber* newVersionNumber() const override; 109 | OSString* newTransportString() const override; 110 | OSString* newManufacturerString() const override; 111 | OSNumber* newPrimaryUsageNumber() const override; 112 | OSNumber* newPrimaryUsagePageNumber() const override; 113 | OSString* newProductString() const override; 114 | OSString* newSerialNumberString() const override; 115 | OSNumber* newLocationIDNumber() const override; 116 | 117 | IOReturn setPowerState(unsigned long whichState, IOService* whatDevice) override; 118 | 119 | bool start(IOService* provider) override; 120 | void stop(IOService* provider) override; 121 | void releaseResources(); 122 | 123 | private: 124 | bool ready_for_reports {false}; 125 | VoodooInput* engine {nullptr}; 126 | AbsoluteTime start_timestamp {}; 127 | OSData* new_get_report_buffer {nullptr}; 128 | bool touch_active[15] {false}; 129 | IOWorkLoop* work_loop {nullptr}; 130 | IOCommandGate* command_gate {nullptr}; 131 | IOBufferMemoryDescriptor* input_report_buffer {nullptr}; 132 | MAGIC_TRACKPAD_INPUT_REPORT* input_report {nullptr}; 133 | 134 | void sendReport(); 135 | void constructReportGated(const VoodooInputEvent& multitouch_event); 136 | }; 137 | 138 | 139 | #endif 140 | --------------------------------------------------------------------------------