├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── logo.svg ├── photo.gif ├── photo.jpg ├── remarkable_mouse ├── __init__.py ├── __main__.py ├── codes.py ├── common.py ├── evdev.py ├── generate_codes.py ├── notes.md ├── pynput.py ├── remarkable_mouse.py └── version.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.pyc 2 | dist/ 3 | remarkable_mouse.egg-info 4 | **/__pycache__ 5 | build/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 7.1.0 - 2022-09-25 2 | - fix stretching/fitting/filling/rotation in evdev mode 3 | - fix evdev tilt 4 | - try to read Host config from ~/.ssh/config 5 | - report tablet size correct in 'libinput list-devices' 6 | 7 | # 7.0.2 - 2022-04-26 8 | - fix import errors on OSX/Windows 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | passhole - CLI interface with dmenu support for KeePass databases 635 | Copyright (C) 2018 Evan Widloski 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Passhole Copyright (C) 2018 Evan Widloski 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Evan Widloski - 2019-03-04 2 | # makefile for building 3 | 4 | .PHONY: dist 5 | dist: 6 | python setup.py sdist bdist_wheel 7 | 8 | .PHONY: pypi 9 | pypi: dist 10 | twine upload dist/* 11 | 12 | .PHONY: clean 13 | clean: 14 | rm dist/* 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # remarkable_mouse 2 | 3 | Use your reMarkable as a graphics tablet. 4 | 5 | Special thanks to [canselcik](https://github.com/canselcik/libremarkable) and [LinusCDE](https://github.com/LinusCDE/rmWacomToMouse) for inspiration. 6 | 7 | 8 | 9 | # Quick Start 10 | 11 | On the host machine with the tablet plugged in via USB: 12 | 13 | ``` bash 14 | pip install remarkable-mouse 15 | remouse 16 | ``` 17 | 18 | By default, `10.11.99.1` is used as the address. Find your password in the reMarkable's [settings menu](https://remarkablewiki.com/tech/ssh). If you are on Linux using X11, you can use the `--evdev` option for pressure support. 19 | 20 | To use the `--region` flag, you may need to install the `python3-tk` or `python3-tkinter` package with your package manager. 21 | 22 | # Examples 23 | 24 | specify monitor, orientation, password 25 | 26 | ``` bash 27 | remouse --orientation right --mode fit --monitor 1 --password foobar 28 | ``` 29 | 30 | passwordless login 31 | 32 | ``` bash 33 | ssh-keygen -m PEM -t rsa -f ~/.ssh/remarkable -N '' 34 | ssh-copy-id -i ~/.ssh/remarkable.pub root@10.11.99.1 35 | remouse 36 | ``` 37 | 38 | running with pressure sensitivity (Linux only) 39 | 40 | ``` bash 41 | sudo --preserve-env=USER,PATH env remouse --evdev 42 | ``` 43 | 44 | # Usage 45 | 46 | ``` 47 | usage: remouse [-h] [--debug] [--key PATH] [--password PASSWORD] [--address ADDRESS] [--mode {fit,fill,stretch}] [--orientation {top,left,right,bottom}] [--monitor NUM] [--region] [--threshold THRESH] 48 | [--evdev] 49 | 50 | use reMarkable tablet as a mouse input 51 | 52 | optional arguments: 53 | -h, --help show this help message and exit 54 | --debug enable debug messages 55 | --key PATH ssh private key 56 | --password PASSWORD ssh password 57 | --address ADDRESS device address 58 | --mode {fit,fill,stretch} 59 | Scale setting. Fit (default): take up the entire tablet, but not necessarily the entire monitor. Fill: take up the entire monitor, but not necessarily the entire tablet. Stretch: 60 | take up both the entire tablet and monitor, but don't maintain aspect ratio. 61 | --orientation {top,left,right,bottom} 62 | position of tablet buttons 63 | --monitor NUM monitor to output to 64 | --region Use a GUI to position the output area. Overrides --monitor 65 | --threshold THRESH stylus pressure threshold (default 600) 66 | --evdev use evdev to support pen pressure (requires root, Linux only) 67 | ``` 68 | 69 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 49 | 51 | 52 | 54 | image/svg+xml 55 | 57 | 58 | 59 | 60 | 61 | 66 | 69 | 76 | 83 | 90 | 91 | 98 | 101 | 104 | 118 | 131 | 135 | 139 | 143 | 144 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /photo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evidlo/remarkable_mouse/05142ef37a8b3f9e350156a14c2dec6844ed0ea8/photo.gif -------------------------------------------------------------------------------- /photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evidlo/remarkable_mouse/05142ef37a8b3f9e350156a14c2dec6844ed0ea8/photo.jpg -------------------------------------------------------------------------------- /remarkable_mouse/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Evidlo/remarkable_mouse/05142ef37a8b3f9e350156a14c2dec6844ed0ea8/remarkable_mouse/__init__.py -------------------------------------------------------------------------------- /remarkable_mouse/__main__.py: -------------------------------------------------------------------------------- 1 | from remarkable_mouse.remarkable_mouse import main 2 | 3 | if __name__ == '__main__': 4 | main() 5 | -------------------------------------------------------------------------------- /remarkable_mouse/codes.py: -------------------------------------------------------------------------------- 1 | # generated by generate_codes.py 2 | 3 | types= {0: 'EV_SYN', 4 | 1: 'EV_KEY', 5 | 2: 'EV_REL', 6 | 3: 'EV_ABS', 7 | 4: 'EV_MSC', 8 | 5: 'EV_SW', 9 | 17: 'EV_LED', 10 | 18: 'EV_SND', 11 | 20: 'EV_REP', 12 | 21: 'EV_FF', 13 | 22: 'EV_PWR', 14 | 23: 'EV_FF_STATUS', 15 | 31: 'EV_MAX'} 16 | codes = {0: {0: 'SYN_REPORT', 17 | 1: 'SYN_CONFIG', 18 | 2: 'SYN_MT_REPORT', 19 | 3: 'SYN_DROPPED', 20 | 4: 'SYN_04', 21 | 5: 'SYN_05', 22 | 6: 'SYN_06', 23 | 7: 'SYN_07', 24 | 8: 'SYN_08', 25 | 9: 'SYN_09', 26 | 10: 'SYN_0A', 27 | 11: 'SYN_0B', 28 | 12: 'SYN_0C', 29 | 13: 'SYN_0D', 30 | 14: 'SYN_0E', 31 | 15: 'SYN_MAX'}, 32 | 1: {0: 'KEY_RESERVED', 33 | 1: 'KEY_ESC', 34 | 2: 'KEY_1', 35 | 3: 'KEY_2', 36 | 4: 'KEY_3', 37 | 5: 'KEY_4', 38 | 6: 'KEY_5', 39 | 7: 'KEY_6', 40 | 8: 'KEY_7', 41 | 9: 'KEY_8', 42 | 10: 'KEY_9', 43 | 11: 'KEY_0', 44 | 12: 'KEY_MINUS', 45 | 13: 'KEY_EQUAL', 46 | 14: 'KEY_BACKSPACE', 47 | 15: 'KEY_TAB', 48 | 16: 'KEY_Q', 49 | 17: 'KEY_W', 50 | 18: 'KEY_E', 51 | 19: 'KEY_R', 52 | 20: 'KEY_T', 53 | 21: 'KEY_Y', 54 | 22: 'KEY_U', 55 | 23: 'KEY_I', 56 | 24: 'KEY_O', 57 | 25: 'KEY_P', 58 | 26: 'KEY_LEFTBRACE', 59 | 27: 'KEY_RIGHTBRACE', 60 | 28: 'KEY_ENTER', 61 | 29: 'KEY_LEFTCTRL', 62 | 30: 'KEY_A', 63 | 31: 'KEY_S', 64 | 32: 'KEY_D', 65 | 33: 'KEY_F', 66 | 34: 'KEY_G', 67 | 35: 'KEY_H', 68 | 36: 'KEY_J', 69 | 37: 'KEY_K', 70 | 38: 'KEY_L', 71 | 39: 'KEY_SEMICOLON', 72 | 40: 'KEY_APOSTROPHE', 73 | 41: 'KEY_GRAVE', 74 | 42: 'KEY_LEFTSHIFT', 75 | 43: 'KEY_BACKSLASH', 76 | 44: 'KEY_Z', 77 | 45: 'KEY_X', 78 | 46: 'KEY_C', 79 | 47: 'KEY_V', 80 | 48: 'KEY_B', 81 | 49: 'KEY_N', 82 | 50: 'KEY_M', 83 | 51: 'KEY_COMMA', 84 | 52: 'KEY_DOT', 85 | 53: 'KEY_SLASH', 86 | 54: 'KEY_RIGHTSHIFT', 87 | 55: 'KEY_KPASTERISK', 88 | 56: 'KEY_LEFTALT', 89 | 57: 'KEY_SPACE', 90 | 58: 'KEY_CAPSLOCK', 91 | 59: 'KEY_F1', 92 | 60: 'KEY_F2', 93 | 61: 'KEY_F3', 94 | 62: 'KEY_F4', 95 | 63: 'KEY_F5', 96 | 64: 'KEY_F6', 97 | 65: 'KEY_F7', 98 | 66: 'KEY_F8', 99 | 67: 'KEY_F9', 100 | 68: 'KEY_F10', 101 | 69: 'KEY_NUMLOCK', 102 | 70: 'KEY_SCROLLLOCK', 103 | 71: 'KEY_KP7', 104 | 72: 'KEY_KP8', 105 | 73: 'KEY_KP9', 106 | 74: 'KEY_KPMINUS', 107 | 75: 'KEY_KP4', 108 | 76: 'KEY_KP5', 109 | 77: 'KEY_KP6', 110 | 78: 'KEY_KPPLUS', 111 | 79: 'KEY_KP1', 112 | 80: 'KEY_KP2', 113 | 81: 'KEY_KP3', 114 | 82: 'KEY_KP0', 115 | 83: 'KEY_KPDOT', 116 | 84: 'KEY_54', 117 | 85: 'KEY_ZENKAKUHANKAKU', 118 | 86: 'KEY_102ND', 119 | 87: 'KEY_F11', 120 | 88: 'KEY_F12', 121 | 89: 'KEY_RO', 122 | 90: 'KEY_KATAKANA', 123 | 91: 'KEY_HIRAGANA', 124 | 92: 'KEY_HENKAN', 125 | 93: 'KEY_KATAKANAHIRAGANA', 126 | 94: 'KEY_MUHENKAN', 127 | 95: 'KEY_KPJPCOMMA', 128 | 96: 'KEY_KPENTER', 129 | 97: 'KEY_RIGHTCTRL', 130 | 98: 'KEY_KPSLASH', 131 | 99: 'KEY_SYSRQ', 132 | 100: 'KEY_RIGHTALT', 133 | 101: 'KEY_LINEFEED', 134 | 102: 'KEY_HOME', 135 | 103: 'KEY_UP', 136 | 104: 'KEY_PAGEUP', 137 | 105: 'KEY_LEFT', 138 | 106: 'KEY_RIGHT', 139 | 107: 'KEY_END', 140 | 108: 'KEY_DOWN', 141 | 109: 'KEY_PAGEDOWN', 142 | 110: 'KEY_INSERT', 143 | 111: 'KEY_DELETE', 144 | 112: 'KEY_MACRO', 145 | 113: 'KEY_MUTE', 146 | 114: 'KEY_VOLUMEDOWN', 147 | 115: 'KEY_VOLUMEUP', 148 | 116: 'KEY_POWER', 149 | 117: 'KEY_KPEQUAL', 150 | 118: 'KEY_KPPLUSMINUS', 151 | 119: 'KEY_PAUSE', 152 | 120: 'KEY_SCALE', 153 | 121: 'KEY_KPCOMMA', 154 | 122: 'KEY_HANGEUL', 155 | 123: 'KEY_HANJA', 156 | 124: 'KEY_YEN', 157 | 125: 'KEY_LEFTMETA', 158 | 126: 'KEY_RIGHTMETA', 159 | 127: 'KEY_COMPOSE', 160 | 128: 'KEY_STOP', 161 | 129: 'KEY_AGAIN', 162 | 130: 'KEY_PROPS', 163 | 131: 'KEY_UNDO', 164 | 132: 'KEY_FRONT', 165 | 133: 'KEY_COPY', 166 | 134: 'KEY_OPEN', 167 | 135: 'KEY_PASTE', 168 | 136: 'KEY_FIND', 169 | 137: 'KEY_CUT', 170 | 138: 'KEY_HELP', 171 | 139: 'KEY_MENU', 172 | 140: 'KEY_CALC', 173 | 141: 'KEY_SETUP', 174 | 142: 'KEY_SLEEP', 175 | 143: 'KEY_WAKEUP', 176 | 144: 'KEY_FILE', 177 | 145: 'KEY_SENDFILE', 178 | 146: 'KEY_DELETEFILE', 179 | 147: 'KEY_XFER', 180 | 148: 'KEY_PROG1', 181 | 149: 'KEY_PROG2', 182 | 150: 'KEY_WWW', 183 | 151: 'KEY_MSDOS', 184 | 152: 'KEY_COFFEE', 185 | 153: 'KEY_ROTATE_DISPLAY', 186 | 154: 'KEY_CYCLEWINDOWS', 187 | 155: 'KEY_MAIL', 188 | 156: 'KEY_BOOKMARKS', 189 | 157: 'KEY_COMPUTER', 190 | 158: 'KEY_BACK', 191 | 159: 'KEY_FORWARD', 192 | 160: 'KEY_CLOSECD', 193 | 161: 'KEY_EJECTCD', 194 | 162: 'KEY_EJECTCLOSECD', 195 | 163: 'KEY_NEXTSONG', 196 | 164: 'KEY_PLAYPAUSE', 197 | 165: 'KEY_PREVIOUSSONG', 198 | 166: 'KEY_STOPCD', 199 | 167: 'KEY_RECORD', 200 | 168: 'KEY_REWIND', 201 | 169: 'KEY_PHONE', 202 | 170: 'KEY_ISO', 203 | 171: 'KEY_CONFIG', 204 | 172: 'KEY_HOMEPAGE', 205 | 173: 'KEY_REFRESH', 206 | 174: 'KEY_EXIT', 207 | 175: 'KEY_MOVE', 208 | 176: 'KEY_EDIT', 209 | 177: 'KEY_SCROLLUP', 210 | 178: 'KEY_SCROLLDOWN', 211 | 179: 'KEY_KPLEFTPAREN', 212 | 180: 'KEY_KPRIGHTPAREN', 213 | 181: 'KEY_NEW', 214 | 182: 'KEY_REDO', 215 | 183: 'KEY_F13', 216 | 184: 'KEY_F14', 217 | 185: 'KEY_F15', 218 | 186: 'KEY_F16', 219 | 187: 'KEY_F17', 220 | 188: 'KEY_F18', 221 | 189: 'KEY_F19', 222 | 190: 'KEY_F20', 223 | 191: 'KEY_F21', 224 | 192: 'KEY_F22', 225 | 193: 'KEY_F23', 226 | 194: 'KEY_F24', 227 | 195: 'KEY_C3', 228 | 196: 'KEY_C4', 229 | 197: 'KEY_C5', 230 | 198: 'KEY_C6', 231 | 199: 'KEY_C7', 232 | 200: 'KEY_PLAYCD', 233 | 201: 'KEY_PAUSECD', 234 | 202: 'KEY_PROG3', 235 | 203: 'KEY_PROG4', 236 | 204: 'KEY_DASHBOARD', 237 | 205: 'KEY_SUSPEND', 238 | 206: 'KEY_CLOSE', 239 | 207: 'KEY_PLAY', 240 | 208: 'KEY_FASTFORWARD', 241 | 209: 'KEY_BASSBOOST', 242 | 210: 'KEY_PRINT', 243 | 211: 'KEY_HP', 244 | 212: 'KEY_CAMERA', 245 | 213: 'KEY_SOUND', 246 | 214: 'KEY_QUESTION', 247 | 215: 'KEY_EMAIL', 248 | 216: 'KEY_CHAT', 249 | 217: 'KEY_SEARCH', 250 | 218: 'KEY_CONNECT', 251 | 219: 'KEY_FINANCE', 252 | 220: 'KEY_SPORT', 253 | 221: 'KEY_SHOP', 254 | 222: 'KEY_ALTERASE', 255 | 223: 'KEY_CANCEL', 256 | 224: 'KEY_BRIGHTNESSDOWN', 257 | 225: 'KEY_BRIGHTNESSUP', 258 | 226: 'KEY_MEDIA', 259 | 227: 'KEY_SWITCHVIDEOMODE', 260 | 228: 'KEY_KBDILLUMTOGGLE', 261 | 229: 'KEY_KBDILLUMDOWN', 262 | 230: 'KEY_KBDILLUMUP', 263 | 231: 'KEY_SEND', 264 | 232: 'KEY_REPLY', 265 | 233: 'KEY_FORWARDMAIL', 266 | 234: 'KEY_SAVE', 267 | 235: 'KEY_DOCUMENTS', 268 | 236: 'KEY_BATTERY', 269 | 237: 'KEY_BLUETOOTH', 270 | 238: 'KEY_WLAN', 271 | 239: 'KEY_UWB', 272 | 240: 'KEY_UNKNOWN', 273 | 241: 'KEY_VIDEO_NEXT', 274 | 242: 'KEY_VIDEO_PREV', 275 | 243: 'KEY_BRIGHTNESS_CYCLE', 276 | 244: 'KEY_BRIGHTNESS_AUTO', 277 | 245: 'KEY_DISPLAY_OFF', 278 | 246: 'KEY_WWAN', 279 | 247: 'KEY_RFKILL', 280 | 248: 'KEY_MICMUTE', 281 | 249: 'KEY_F9', 282 | 250: 'KEY_FA', 283 | 251: 'KEY_FB', 284 | 252: 'KEY_FC', 285 | 253: 'KEY_FD', 286 | 254: 'KEY_FE', 287 | 255: 'KEY_FF', 288 | 256: 'BTN_0', 289 | 257: 'BTN_1', 290 | 258: 'BTN_2', 291 | 259: 'BTN_3', 292 | 260: 'BTN_4', 293 | 261: 'BTN_5', 294 | 262: 'BTN_6', 295 | 263: 'BTN_7', 296 | 264: 'BTN_8', 297 | 265: 'BTN_9', 298 | 266: 'KEY_10A', 299 | 267: 'KEY_10B', 300 | 268: 'KEY_10C', 301 | 269: 'KEY_10D', 302 | 270: 'KEY_10E', 303 | 271: 'KEY_10F', 304 | 272: 'BTN_LEFT', 305 | 273: 'BTN_RIGHT', 306 | 274: 'BTN_MIDDLE', 307 | 275: 'BTN_SIDE', 308 | 276: 'BTN_EXTRA', 309 | 277: 'BTN_FORWARD', 310 | 278: 'BTN_BACK', 311 | 279: 'BTN_TASK', 312 | 280: 'KEY_118', 313 | 281: 'KEY_119', 314 | 282: 'KEY_11A', 315 | 283: 'KEY_11B', 316 | 284: 'KEY_11C', 317 | 285: 'KEY_11D', 318 | 286: 'KEY_11E', 319 | 287: 'KEY_11F', 320 | 288: 'BTN_TRIGGER', 321 | 289: 'BTN_THUMB', 322 | 290: 'BTN_THUMB2', 323 | 291: 'BTN_TOP', 324 | 292: 'BTN_TOP2', 325 | 293: 'BTN_PINKIE', 326 | 294: 'BTN_BASE', 327 | 295: 'BTN_BASE2', 328 | 296: 'BTN_BASE3', 329 | 297: 'BTN_BASE4', 330 | 298: 'BTN_BASE5', 331 | 299: 'BTN_BASE6', 332 | 300: 'KEY_12C', 333 | 301: 'KEY_12D', 334 | 302: 'KEY_12E', 335 | 303: 'BTN_DEAD', 336 | 304: 'BTN_SOUTH', 337 | 305: 'BTN_EAST', 338 | 306: 'BTN_C', 339 | 307: 'BTN_NORTH', 340 | 308: 'BTN_WEST', 341 | 309: 'BTN_Z', 342 | 310: 'BTN_TL', 343 | 311: 'BTN_TR', 344 | 312: 'BTN_TL2', 345 | 313: 'BTN_TR2', 346 | 314: 'BTN_SELECT', 347 | 315: 'BTN_START', 348 | 316: 'BTN_MODE', 349 | 317: 'BTN_THUMBL', 350 | 318: 'BTN_THUMBR', 351 | 319: 'KEY_13F', 352 | 320: 'BTN_TOOL_PEN', 353 | 321: 'BTN_TOOL_RUBBER', 354 | 322: 'BTN_TOOL_BRUSH', 355 | 323: 'BTN_TOOL_PENCIL', 356 | 324: 'BTN_TOOL_AIRBRUSH', 357 | 325: 'BTN_TOOL_FINGER', 358 | 326: 'BTN_TOOL_MOUSE', 359 | 327: 'BTN_TOOL_LENS', 360 | 328: 'BTN_TOOL_QUINTTAP', 361 | 329: 'BTN_STYLUS3', 362 | 330: 'BTN_TOUCH', 363 | 331: 'BTN_STYLUS', 364 | 332: 'BTN_STYLUS2', 365 | 333: 'BTN_TOOL_DOUBLETAP', 366 | 334: 'BTN_TOOL_TRIPLETAP', 367 | 335: 'BTN_TOOL_QUADTAP', 368 | 336: 'BTN_GEAR_DOWN', 369 | 337: 'BTN_GEAR_UP', 370 | 338: 'KEY_152', 371 | 339: 'KEY_153', 372 | 340: 'KEY_154', 373 | 341: 'KEY_155', 374 | 342: 'KEY_156', 375 | 343: 'KEY_157', 376 | 344: 'KEY_158', 377 | 345: 'KEY_159', 378 | 346: 'KEY_15A', 379 | 347: 'KEY_15B', 380 | 348: 'KEY_15C', 381 | 349: 'KEY_15D', 382 | 350: 'KEY_15E', 383 | 351: 'KEY_15F', 384 | 352: 'KEY_OK', 385 | 353: 'KEY_SELECT', 386 | 354: 'KEY_GOTO', 387 | 355: 'KEY_CLEAR', 388 | 356: 'KEY_POWER2', 389 | 357: 'KEY_OPTION', 390 | 358: 'KEY_INFO', 391 | 359: 'KEY_TIME', 392 | 360: 'KEY_VENDOR', 393 | 361: 'KEY_ARCHIVE', 394 | 362: 'KEY_PROGRAM', 395 | 363: 'KEY_CHANNEL', 396 | 364: 'KEY_FAVORITES', 397 | 365: 'KEY_EPG', 398 | 366: 'KEY_PVR', 399 | 367: 'KEY_MHP', 400 | 368: 'KEY_LANGUAGE', 401 | 369: 'KEY_TITLE', 402 | 370: 'KEY_SUBTITLE', 403 | 371: 'KEY_ANGLE', 404 | 372: 'KEY_FULL_SCREEN', 405 | 373: 'KEY_MODE', 406 | 374: 'KEY_KEYBOARD', 407 | 375: 'KEY_ASPECT_RATIO', 408 | 376: 'KEY_PC', 409 | 377: 'KEY_TV', 410 | 378: 'KEY_TV2', 411 | 379: 'KEY_VCR', 412 | 380: 'KEY_VCR2', 413 | 381: 'KEY_SAT', 414 | 382: 'KEY_SAT2', 415 | 383: 'KEY_CD', 416 | 384: 'KEY_TAPE', 417 | 385: 'KEY_RADIO', 418 | 386: 'KEY_TUNER', 419 | 387: 'KEY_PLAYER', 420 | 388: 'KEY_TEXT', 421 | 389: 'KEY_DVD', 422 | 390: 'KEY_AUX', 423 | 391: 'KEY_MP3', 424 | 392: 'KEY_AUDIO', 425 | 393: 'KEY_VIDEO', 426 | 394: 'KEY_DIRECTORY', 427 | 395: 'KEY_LIST', 428 | 396: 'KEY_MEMO', 429 | 397: 'KEY_CALENDAR', 430 | 398: 'KEY_RED', 431 | 399: 'KEY_GREEN', 432 | 400: 'KEY_YELLOW', 433 | 401: 'KEY_BLUE', 434 | 402: 'KEY_CHANNELUP', 435 | 403: 'KEY_CHANNELDOWN', 436 | 404: 'KEY_FIRST', 437 | 405: 'KEY_LAST', 438 | 406: 'KEY_AB', 439 | 407: 'KEY_NEXT', 440 | 408: 'KEY_RESTART', 441 | 409: 'KEY_SLOW', 442 | 410: 'KEY_SHUFFLE', 443 | 411: 'KEY_BREAK', 444 | 412: 'KEY_PREVIOUS', 445 | 413: 'KEY_DIGITS', 446 | 414: 'KEY_TEEN', 447 | 415: 'KEY_TWEN', 448 | 416: 'KEY_VIDEOPHONE', 449 | 417: 'KEY_GAMES', 450 | 418: 'KEY_ZOOMIN', 451 | 419: 'KEY_ZOOMOUT', 452 | 420: 'KEY_ZOOMRESET', 453 | 421: 'KEY_WORDPROCESSOR', 454 | 422: 'KEY_EDITOR', 455 | 423: 'KEY_SPREADSHEET', 456 | 424: 'KEY_GRAPHICSEDITOR', 457 | 425: 'KEY_PRESENTATION', 458 | 426: 'KEY_DATABASE', 459 | 427: 'KEY_NEWS', 460 | 428: 'KEY_VOICEMAIL', 461 | 429: 'KEY_ADDRESSBOOK', 462 | 430: 'KEY_MESSENGER', 463 | 431: 'KEY_DISPLAYTOGGLE', 464 | 432: 'KEY_SPELLCHECK', 465 | 433: 'KEY_LOGOFF', 466 | 434: 'KEY_DOLLAR', 467 | 435: 'KEY_EURO', 468 | 436: 'KEY_FRAMEBACK', 469 | 437: 'KEY_FRAMEFORWARD', 470 | 438: 'KEY_CONTEXT_MENU', 471 | 439: 'KEY_MEDIA_REPEAT', 472 | 440: 'KEY_10CHANNELSUP', 473 | 441: 'KEY_10CHANNELSDOWN', 474 | 442: 'KEY_IMAGES', 475 | 443: 'KEY_1BB', 476 | 444: 'KEY_NOTIFICATION_CENTER', 477 | 445: 'KEY_PICKUP_PHONE', 478 | 446: 'KEY_HANGUP_PHONE', 479 | 447: 'KEY_1BF', 480 | 448: 'KEY_DEL_EOL', 481 | 449: 'KEY_DEL_EOS', 482 | 450: 'KEY_INS_LINE', 483 | 451: 'KEY_DEL_LINE', 484 | 452: 'KEY_1C4', 485 | 453: 'KEY_1C5', 486 | 454: 'KEY_1C6', 487 | 455: 'KEY_1C7', 488 | 456: 'KEY_1C8', 489 | 457: 'KEY_1C9', 490 | 458: 'KEY_1CA', 491 | 459: 'KEY_1CB', 492 | 460: 'KEY_1CC', 493 | 461: 'KEY_1CD', 494 | 462: 'KEY_1CE', 495 | 463: 'KEY_1CF', 496 | 464: 'KEY_FN', 497 | 465: 'KEY_FN_ESC', 498 | 466: 'KEY_FN_F1', 499 | 467: 'KEY_FN_F2', 500 | 468: 'KEY_FN_F3', 501 | 469: 'KEY_FN_F4', 502 | 470: 'KEY_FN_F5', 503 | 471: 'KEY_FN_F6', 504 | 472: 'KEY_FN_F7', 505 | 473: 'KEY_FN_F8', 506 | 474: 'KEY_FN_F9', 507 | 475: 'KEY_FN_F10', 508 | 476: 'KEY_FN_F11', 509 | 477: 'KEY_FN_F12', 510 | 478: 'KEY_FN_1', 511 | 479: 'KEY_FN_2', 512 | 480: 'KEY_FN_D', 513 | 481: 'KEY_FN_E', 514 | 482: 'KEY_FN_F', 515 | 483: 'KEY_FN_S', 516 | 484: 'KEY_FN_B', 517 | 485: 'KEY_FN_RIGHT_SHIFT', 518 | 486: 'KEY_1E6', 519 | 487: 'KEY_1E7', 520 | 488: 'KEY_1E8', 521 | 489: 'KEY_1E9', 522 | 490: 'KEY_1EA', 523 | 491: 'KEY_1EB', 524 | 492: 'KEY_1EC', 525 | 493: 'KEY_1ED', 526 | 494: 'KEY_1EE', 527 | 495: 'KEY_1EF', 528 | 496: 'KEY_1F0', 529 | 497: 'KEY_BRL_DOT1', 530 | 498: 'KEY_BRL_DOT2', 531 | 499: 'KEY_BRL_DOT3', 532 | 500: 'KEY_BRL_DOT4', 533 | 501: 'KEY_BRL_DOT5', 534 | 502: 'KEY_BRL_DOT6', 535 | 503: 'KEY_BRL_DOT7', 536 | 504: 'KEY_BRL_DOT8', 537 | 505: 'KEY_BRL_DOT9', 538 | 506: 'KEY_BRL_DOT10', 539 | 507: 'KEY_1FB', 540 | 508: 'KEY_1FC', 541 | 509: 'KEY_1FD', 542 | 510: 'KEY_1FE', 543 | 511: 'KEY_1FF', 544 | 512: 'KEY_NUMERIC_0', 545 | 513: 'KEY_NUMERIC_1', 546 | 514: 'KEY_NUMERIC_2', 547 | 515: 'KEY_NUMERIC_3', 548 | 516: 'KEY_NUMERIC_4', 549 | 517: 'KEY_NUMERIC_5', 550 | 518: 'KEY_NUMERIC_6', 551 | 519: 'KEY_NUMERIC_7', 552 | 520: 'KEY_NUMERIC_8', 553 | 521: 'KEY_NUMERIC_9', 554 | 522: 'KEY_NUMERIC_STAR', 555 | 523: 'KEY_NUMERIC_POUND', 556 | 524: 'KEY_NUMERIC_A', 557 | 525: 'KEY_NUMERIC_B', 558 | 526: 'KEY_NUMERIC_C', 559 | 527: 'KEY_NUMERIC_D', 560 | 528: 'KEY_CAMERA_FOCUS', 561 | 529: 'KEY_WPS_BUTTON', 562 | 530: 'KEY_TOUCHPAD_TOGGLE', 563 | 531: 'KEY_TOUCHPAD_ON', 564 | 532: 'KEY_TOUCHPAD_OFF', 565 | 533: 'KEY_CAMERA_ZOOMIN', 566 | 534: 'KEY_CAMERA_ZOOMOUT', 567 | 535: 'KEY_CAMERA_UP', 568 | 536: 'KEY_CAMERA_DOWN', 569 | 537: 'KEY_CAMERA_LEFT', 570 | 538: 'KEY_CAMERA_RIGHT', 571 | 539: 'KEY_ATTENDANT_ON', 572 | 540: 'KEY_ATTENDANT_OFF', 573 | 541: 'KEY_ATTENDANT_TOGGLE', 574 | 542: 'KEY_LIGHTS_TOGGLE', 575 | 543: 'KEY_21F', 576 | 544: 'BTN_DPAD_UP', 577 | 545: 'BTN_DPAD_DOWN', 578 | 546: 'BTN_DPAD_LEFT', 579 | 547: 'BTN_DPAD_RIGHT', 580 | 548: 'KEY_224', 581 | 549: 'KEY_225', 582 | 550: 'KEY_226', 583 | 551: 'KEY_227', 584 | 552: 'KEY_228', 585 | 553: 'KEY_229', 586 | 554: 'KEY_22A', 587 | 555: 'KEY_22B', 588 | 556: 'KEY_22C', 589 | 557: 'KEY_22D', 590 | 558: 'KEY_22E', 591 | 559: 'KEY_22F', 592 | 560: 'KEY_ALS_TOGGLE', 593 | 561: 'KEY_ROTATE_LOCK_TOGGLE', 594 | 562: 'KEY_232', 595 | 563: 'KEY_233', 596 | 564: 'KEY_234', 597 | 565: 'KEY_235', 598 | 566: 'KEY_236', 599 | 567: 'KEY_237', 600 | 568: 'KEY_238', 601 | 569: 'KEY_239', 602 | 570: 'KEY_23A', 603 | 571: 'KEY_23B', 604 | 572: 'KEY_23C', 605 | 573: 'KEY_23D', 606 | 574: 'KEY_23E', 607 | 575: 'KEY_23F', 608 | 576: 'KEY_BUTTONCONFIG', 609 | 577: 'KEY_TASKMANAGER', 610 | 578: 'KEY_JOURNAL', 611 | 579: 'KEY_CONTROLPANEL', 612 | 580: 'KEY_APPSELECT', 613 | 581: 'KEY_SCREENSAVER', 614 | 582: 'KEY_VOICECOMMAND', 615 | 583: 'KEY_ASSISTANT', 616 | 584: 'KEY_KBD_LAYOUT_NEXT', 617 | 585: 'KEY_EMOJI_PICKER', 618 | 586: 'KEY_24A', 619 | 587: 'KEY_24B', 620 | 588: 'KEY_24C', 621 | 589: 'KEY_24D', 622 | 590: 'KEY_24E', 623 | 591: 'KEY_24F', 624 | 592: 'KEY_BRIGHTNESS_MIN', 625 | 593: 'KEY_BRIGHTNESS_MAX', 626 | 594: 'KEY_252', 627 | 595: 'KEY_253', 628 | 596: 'KEY_254', 629 | 597: 'KEY_255', 630 | 598: 'KEY_256', 631 | 599: 'KEY_257', 632 | 600: 'KEY_258', 633 | 601: 'KEY_259', 634 | 602: 'KEY_25A', 635 | 603: 'KEY_25B', 636 | 604: 'KEY_25C', 637 | 605: 'KEY_25D', 638 | 606: 'KEY_25E', 639 | 607: 'KEY_25F', 640 | 608: 'KEY_KBDINPUTASSIST_PREV', 641 | 609: 'KEY_KBDINPUTASSIST_NEXT', 642 | 610: 'KEY_KBDINPUTASSIST_PREVGROUP', 643 | 611: 'KEY_KBDINPUTASSIST_NEXTGROUP', 644 | 612: 'KEY_KBDINPUTASSIST_ACCEPT', 645 | 613: 'KEY_KBDINPUTASSIST_CANCEL', 646 | 614: 'KEY_RIGHT_UP', 647 | 615: 'KEY_RIGHT_DOWN', 648 | 616: 'KEY_LEFT_UP', 649 | 617: 'KEY_LEFT_DOWN', 650 | 618: 'KEY_ROOT_MENU', 651 | 619: 'KEY_MEDIA_TOP_MENU', 652 | 620: 'KEY_NUMERIC_11', 653 | 621: 'KEY_NUMERIC_12', 654 | 622: 'KEY_AUDIO_DESC', 655 | 623: 'KEY_3D_MODE', 656 | 624: 'KEY_NEXT_FAVORITE', 657 | 625: 'KEY_STOP_RECORD', 658 | 626: 'KEY_PAUSE_RECORD', 659 | 627: 'KEY_VOD', 660 | 628: 'KEY_UNMUTE', 661 | 629: 'KEY_FASTREVERSE', 662 | 630: 'KEY_SLOWREVERSE', 663 | 631: 'KEY_DATA', 664 | 632: 'KEY_ONSCREEN_KEYBOARD', 665 | 633: 'KEY_PRIVACY_SCREEN_TOGGLE', 666 | 634: 'KEY_SELECTIVE_SCREENSHOT', 667 | 635: 'KEY_27B', 668 | 636: 'KEY_27C', 669 | 637: 'KEY_27D', 670 | 638: 'KEY_27E', 671 | 639: 'KEY_27F', 672 | 640: 'KEY_280', 673 | 641: 'KEY_281', 674 | 642: 'KEY_282', 675 | 643: 'KEY_283', 676 | 644: 'KEY_284', 677 | 645: 'KEY_285', 678 | 646: 'KEY_286', 679 | 647: 'KEY_287', 680 | 648: 'KEY_288', 681 | 649: 'KEY_289', 682 | 650: 'KEY_28A', 683 | 651: 'KEY_28B', 684 | 652: 'KEY_28C', 685 | 653: 'KEY_28D', 686 | 654: 'KEY_28E', 687 | 655: 'KEY_28F', 688 | 656: 'KEY_MACRO1', 689 | 657: 'KEY_MACRO2', 690 | 658: 'KEY_MACRO3', 691 | 659: 'KEY_MACRO4', 692 | 660: 'KEY_MACRO5', 693 | 661: 'KEY_MACRO6', 694 | 662: 'KEY_MACRO7', 695 | 663: 'KEY_MACRO8', 696 | 664: 'KEY_MACRO9', 697 | 665: 'KEY_MACRO10', 698 | 666: 'KEY_MACRO11', 699 | 667: 'KEY_MACRO12', 700 | 668: 'KEY_MACRO13', 701 | 669: 'KEY_MACRO14', 702 | 670: 'KEY_MACRO15', 703 | 671: 'KEY_MACRO16', 704 | 672: 'KEY_MACRO17', 705 | 673: 'KEY_MACRO18', 706 | 674: 'KEY_MACRO19', 707 | 675: 'KEY_MACRO20', 708 | 676: 'KEY_MACRO21', 709 | 677: 'KEY_MACRO22', 710 | 678: 'KEY_MACRO23', 711 | 679: 'KEY_MACRO24', 712 | 680: 'KEY_MACRO25', 713 | 681: 'KEY_MACRO26', 714 | 682: 'KEY_MACRO27', 715 | 683: 'KEY_MACRO28', 716 | 684: 'KEY_MACRO29', 717 | 685: 'KEY_MACRO30', 718 | 686: 'KEY_2AE', 719 | 687: 'KEY_2AF', 720 | 688: 'KEY_MACRO_RECORD_START', 721 | 689: 'KEY_MACRO_RECORD_STOP', 722 | 690: 'KEY_MACRO_PRESET_CYCLE', 723 | 691: 'KEY_MACRO_PRESET1', 724 | 692: 'KEY_MACRO_PRESET2', 725 | 693: 'KEY_MACRO_PRESET3', 726 | 694: 'KEY_2B6', 727 | 695: 'KEY_2B7', 728 | 696: 'KEY_KBD_LCD_MENU1', 729 | 697: 'KEY_KBD_LCD_MENU2', 730 | 698: 'KEY_KBD_LCD_MENU3', 731 | 699: 'KEY_KBD_LCD_MENU4', 732 | 700: 'KEY_KBD_LCD_MENU5', 733 | 701: 'KEY_2BD', 734 | 702: 'KEY_2BE', 735 | 703: 'KEY_2BF', 736 | 704: 'BTN_TRIGGER_HAPPY1', 737 | 705: 'BTN_TRIGGER_HAPPY2', 738 | 706: 'BTN_TRIGGER_HAPPY3', 739 | 707: 'BTN_TRIGGER_HAPPY4', 740 | 708: 'BTN_TRIGGER_HAPPY5', 741 | 709: 'BTN_TRIGGER_HAPPY6', 742 | 710: 'BTN_TRIGGER_HAPPY7', 743 | 711: 'BTN_TRIGGER_HAPPY8', 744 | 712: 'BTN_TRIGGER_HAPPY9', 745 | 713: 'BTN_TRIGGER_HAPPY10', 746 | 714: 'BTN_TRIGGER_HAPPY11', 747 | 715: 'BTN_TRIGGER_HAPPY12', 748 | 716: 'BTN_TRIGGER_HAPPY13', 749 | 717: 'BTN_TRIGGER_HAPPY14', 750 | 718: 'BTN_TRIGGER_HAPPY15', 751 | 719: 'BTN_TRIGGER_HAPPY16', 752 | 720: 'BTN_TRIGGER_HAPPY17', 753 | 721: 'BTN_TRIGGER_HAPPY18', 754 | 722: 'BTN_TRIGGER_HAPPY19', 755 | 723: 'BTN_TRIGGER_HAPPY20', 756 | 724: 'BTN_TRIGGER_HAPPY21', 757 | 725: 'BTN_TRIGGER_HAPPY22', 758 | 726: 'BTN_TRIGGER_HAPPY23', 759 | 727: 'BTN_TRIGGER_HAPPY24', 760 | 728: 'BTN_TRIGGER_HAPPY25', 761 | 729: 'BTN_TRIGGER_HAPPY26', 762 | 730: 'BTN_TRIGGER_HAPPY27', 763 | 731: 'BTN_TRIGGER_HAPPY28', 764 | 732: 'BTN_TRIGGER_HAPPY29', 765 | 733: 'BTN_TRIGGER_HAPPY30', 766 | 734: 'BTN_TRIGGER_HAPPY31', 767 | 735: 'BTN_TRIGGER_HAPPY32', 768 | 736: 'BTN_TRIGGER_HAPPY33', 769 | 737: 'BTN_TRIGGER_HAPPY34', 770 | 738: 'BTN_TRIGGER_HAPPY35', 771 | 739: 'BTN_TRIGGER_HAPPY36', 772 | 740: 'BTN_TRIGGER_HAPPY37', 773 | 741: 'BTN_TRIGGER_HAPPY38', 774 | 742: 'BTN_TRIGGER_HAPPY39', 775 | 743: 'BTN_TRIGGER_HAPPY40', 776 | 744: 'KEY_2E8', 777 | 745: 'KEY_2E9', 778 | 746: 'KEY_2EA', 779 | 747: 'KEY_2EB', 780 | 748: 'KEY_2EC', 781 | 749: 'KEY_2ED', 782 | 750: 'KEY_2EE', 783 | 751: 'KEY_2EF', 784 | 752: 'KEY_2F0', 785 | 753: 'KEY_2F1', 786 | 754: 'KEY_2F2', 787 | 755: 'KEY_2F3', 788 | 756: 'KEY_2F4', 789 | 757: 'KEY_2F5', 790 | 758: 'KEY_2F6', 791 | 759: 'KEY_2F7', 792 | 760: 'KEY_2F8', 793 | 761: 'KEY_2F9', 794 | 762: 'KEY_2FA', 795 | 763: 'KEY_2FB', 796 | 764: 'KEY_2FC', 797 | 765: 'KEY_2FD', 798 | 766: 'KEY_2FE', 799 | 767: 'KEY_MAX'}, 800 | 2: {0: 'REL_X', 801 | 1: 'REL_Y', 802 | 2: 'REL_Z', 803 | 3: 'REL_RX', 804 | 4: 'REL_RY', 805 | 5: 'REL_RZ', 806 | 6: 'REL_HWHEEL', 807 | 7: 'REL_DIAL', 808 | 8: 'REL_WHEEL', 809 | 9: 'REL_MISC', 810 | 10: 'REL_RESERVED', 811 | 11: 'REL_WHEEL_HI_RES', 812 | 12: 'REL_HWHEEL_HI_RES', 813 | 13: 'REL_0D', 814 | 14: 'REL_0E', 815 | 15: 'REL_MAX'}, 816 | 3: {0: 'ABS_X', 817 | 1: 'ABS_Y', 818 | 2: 'ABS_Z', 819 | 3: 'ABS_RX', 820 | 4: 'ABS_RY', 821 | 5: 'ABS_RZ', 822 | 6: 'ABS_THROTTLE', 823 | 7: 'ABS_RUDDER', 824 | 8: 'ABS_WHEEL', 825 | 9: 'ABS_GAS', 826 | 10: 'ABS_BRAKE', 827 | 11: 'ABS_0B', 828 | 12: 'ABS_0C', 829 | 13: 'ABS_0D', 830 | 14: 'ABS_0E', 831 | 15: 'ABS_0F', 832 | 16: 'ABS_HAT0X', 833 | 17: 'ABS_HAT0Y', 834 | 18: 'ABS_HAT1X', 835 | 19: 'ABS_HAT1Y', 836 | 20: 'ABS_HAT2X', 837 | 21: 'ABS_HAT2Y', 838 | 22: 'ABS_HAT3X', 839 | 23: 'ABS_HAT3Y', 840 | 24: 'ABS_PRESSURE', 841 | 25: 'ABS_DISTANCE', 842 | 26: 'ABS_TILT_X', 843 | 27: 'ABS_TILT_Y', 844 | 28: 'ABS_TOOL_WIDTH', 845 | 29: 'ABS_1D', 846 | 30: 'ABS_1E', 847 | 31: 'ABS_1F', 848 | 32: 'ABS_VOLUME', 849 | 33: 'ABS_21', 850 | 34: 'ABS_22', 851 | 35: 'ABS_23', 852 | 36: 'ABS_24', 853 | 37: 'ABS_25', 854 | 38: 'ABS_26', 855 | 39: 'ABS_27', 856 | 40: 'ABS_MISC', 857 | 41: 'ABS_29', 858 | 42: 'ABS_2A', 859 | 43: 'ABS_2B', 860 | 44: 'ABS_2C', 861 | 45: 'ABS_2D', 862 | 46: 'ABS_RESERVED', 863 | 47: 'ABS_MT_SLOT', 864 | 48: 'ABS_MT_TOUCH_MAJOR', 865 | 49: 'ABS_MT_TOUCH_MINOR', 866 | 50: 'ABS_MT_WIDTH_MAJOR', 867 | 51: 'ABS_MT_WIDTH_MINOR', 868 | 52: 'ABS_MT_ORIENTATION', 869 | 53: 'ABS_MT_POSITION_X', 870 | 54: 'ABS_MT_POSITION_Y', 871 | 55: 'ABS_MT_TOOL_TYPE', 872 | 56: 'ABS_MT_BLOB_ID', 873 | 57: 'ABS_MT_TRACKING_ID', 874 | 58: 'ABS_MT_PRESSURE', 875 | 59: 'ABS_MT_DISTANCE', 876 | 60: 'ABS_MT_TOOL_X', 877 | 61: 'ABS_MT_TOOL_Y', 878 | 62: 'ABS_3E', 879 | 63: 'ABS_MAX'}, 880 | 4: {0: 'MSC_SERIAL', 881 | 1: 'MSC_PULSELED', 882 | 2: 'MSC_GESTURE', 883 | 3: 'MSC_RAW', 884 | 4: 'MSC_SCAN', 885 | 5: 'MSC_TIMESTAMP', 886 | 6: 'MSC_06', 887 | 7: 'MSC_MAX'}, 888 | 5: {0: 'SW_LID', 889 | 1: 'SW_TABLET_MODE', 890 | 2: 'SW_HEADPHONE_INSERT', 891 | 3: 'SW_RFKILL_ALL', 892 | 4: 'SW_MICROPHONE_INSERT', 893 | 5: 'SW_DOCK', 894 | 6: 'SW_LINEOUT_INSERT', 895 | 7: 'SW_JACK_PHYSICAL_INSERT', 896 | 8: 'SW_VIDEOOUT_INSERT', 897 | 9: 'SW_CAMERA_LENS_COVER', 898 | 10: 'SW_KEYPAD_SLIDE', 899 | 11: 'SW_FRONT_PROXIMITY', 900 | 12: 'SW_ROTATE_LOCK', 901 | 13: 'SW_LINEIN_INSERT', 902 | 14: 'SW_MUTE_DEVICE', 903 | 15: 'SW_PEN_INSERTED', 904 | 16: 'SW_MACHINE_COVER'}, 905 | 17: {0: 'LED_NUML', 906 | 1: 'LED_CAPSL', 907 | 2: 'LED_SCROLLL', 908 | 3: 'LED_COMPOSE', 909 | 4: 'LED_KANA', 910 | 5: 'LED_SLEEP', 911 | 6: 'LED_SUSPEND', 912 | 7: 'LED_MUTE', 913 | 8: 'LED_MISC', 914 | 9: 'LED_MAIL', 915 | 10: 'LED_CHARGING', 916 | 11: 'LED_0B', 917 | 12: 'LED_0C', 918 | 13: 'LED_0D', 919 | 14: 'LED_0E', 920 | 15: 'LED_MAX'}, 921 | 18: {0: 'SND_CLICK', 922 | 1: 'SND_BELL', 923 | 2: 'SND_TONE', 924 | 3: 'SND_03', 925 | 4: 'SND_04', 926 | 5: 'SND_05', 927 | 6: 'SND_06', 928 | 7: 'SND_MAX'}, 929 | 20: {0: 'REP_DELAY', 1: 'REP_PERIOD'}, 930 | 21: {0: 'FF_STATUS_STOPPED', 931 | 1: 'FF_STATUS_MAX', 932 | 2: 'FF_02', 933 | 3: 'FF_03', 934 | 4: 'FF_04', 935 | 5: 'FF_05', 936 | 6: 'FF_06', 937 | 7: 'FF_07', 938 | 8: 'FF_08', 939 | 9: 'FF_09', 940 | 10: 'FF_0A', 941 | 11: 'FF_0B', 942 | 12: 'FF_0C', 943 | 13: 'FF_0D', 944 | 14: 'FF_0E', 945 | 15: 'FF_0F', 946 | 16: 'FF_10', 947 | 17: 'FF_11', 948 | 18: 'FF_12', 949 | 19: 'FF_13', 950 | 20: 'FF_14', 951 | 21: 'FF_15', 952 | 22: 'FF_16', 953 | 23: 'FF_17', 954 | 24: 'FF_18', 955 | 25: 'FF_19', 956 | 26: 'FF_1A', 957 | 27: 'FF_1B', 958 | 28: 'FF_1C', 959 | 29: 'FF_1D', 960 | 30: 'FF_1E', 961 | 31: 'FF_1F', 962 | 32: 'FF_20', 963 | 33: 'FF_21', 964 | 34: 'FF_22', 965 | 35: 'FF_23', 966 | 36: 'FF_24', 967 | 37: 'FF_25', 968 | 38: 'FF_26', 969 | 39: 'FF_27', 970 | 40: 'FF_28', 971 | 41: 'FF_29', 972 | 42: 'FF_2A', 973 | 43: 'FF_2B', 974 | 44: 'FF_2C', 975 | 45: 'FF_2D', 976 | 46: 'FF_2E', 977 | 47: 'FF_2F', 978 | 48: 'FF_30', 979 | 49: 'FF_31', 980 | 50: 'FF_32', 981 | 51: 'FF_33', 982 | 52: 'FF_34', 983 | 53: 'FF_35', 984 | 54: 'FF_36', 985 | 55: 'FF_37', 986 | 56: 'FF_38', 987 | 57: 'FF_39', 988 | 58: 'FF_3A', 989 | 59: 'FF_3B', 990 | 60: 'FF_3C', 991 | 61: 'FF_3D', 992 | 62: 'FF_3E', 993 | 63: 'FF_3F', 994 | 64: 'FF_40', 995 | 65: 'FF_41', 996 | 66: 'FF_42', 997 | 67: 'FF_43', 998 | 68: 'FF_44', 999 | 69: 'FF_45', 1000 | 70: 'FF_46', 1001 | 71: 'FF_47', 1002 | 72: 'FF_48', 1003 | 73: 'FF_49', 1004 | 74: 'FF_4A', 1005 | 75: 'FF_4B', 1006 | 76: 'FF_4C', 1007 | 77: 'FF_4D', 1008 | 78: 'FF_4E', 1009 | 79: 'FF_4F', 1010 | 80: 'FF_RUMBLE', 1011 | 81: 'FF_PERIODIC', 1012 | 82: 'FF_CONSTANT', 1013 | 83: 'FF_SPRING', 1014 | 84: 'FF_FRICTION', 1015 | 85: 'FF_DAMPER', 1016 | 86: 'FF_INERTIA', 1017 | 87: 'FF_RAMP', 1018 | 88: 'FF_SQUARE', 1019 | 89: 'FF_TRIANGLE', 1020 | 90: 'FF_SINE', 1021 | 91: 'FF_SAW_UP', 1022 | 92: 'FF_SAW_DOWN', 1023 | 93: 'FF_CUSTOM', 1024 | 94: 'FF_5E', 1025 | 95: 'FF_5F', 1026 | 96: 'FF_GAIN', 1027 | 97: 'FF_AUTOCENTER', 1028 | 98: 'FF_62', 1029 | 99: 'FF_63', 1030 | 100: 'FF_64', 1031 | 101: 'FF_65', 1032 | 102: 'FF_66', 1033 | 103: 'FF_67', 1034 | 104: 'FF_68', 1035 | 105: 'FF_69', 1036 | 106: 'FF_6A', 1037 | 107: 'FF_6B', 1038 | 108: 'FF_6C', 1039 | 109: 'FF_6D', 1040 | 110: 'FF_6E', 1041 | 111: 'FF_6F', 1042 | 112: 'FF_70', 1043 | 113: 'FF_71', 1044 | 114: 'FF_72', 1045 | 115: 'FF_73', 1046 | 116: 'FF_74', 1047 | 117: 'FF_75', 1048 | 118: 'FF_76', 1049 | 119: 'FF_77', 1050 | 120: 'FF_78', 1051 | 121: 'FF_79', 1052 | 122: 'FF_7A', 1053 | 123: 'FF_7B', 1054 | 124: 'FF_7C', 1055 | 125: 'FF_7D', 1056 | 126: 'FF_7E', 1057 | 127: 'FF_MAX'}, 1058 | 22: {}, 1059 | 23: {}, 1060 | 31: {}} -------------------------------------------------------------------------------- /remarkable_mouse/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from collections import namedtuple 4 | import logging 5 | import struct 6 | import sys 7 | from screeninfo import get_monitors, Monitor 8 | 9 | from .codes import codes, types 10 | 11 | logging.basicConfig(format='%(message)s') 12 | log = logging.getLogger('remouse') 13 | 14 | # ev settings 15 | ev = namedtuple('ev_setting', ['min', 'max', 'res']) 16 | 17 | class reMarkable1: 18 | """Class holding some input settings for a reMarkable tablet 19 | 20 | Args: 21 | client (Paramiko SSH client, optional): an active SSH connection to the 22 | device for reading pen/touch/button inputs 23 | """ 24 | 25 | r"""Coordinate systems 26 | 27 | PEN TOUCH 28 | +---------+ +---------+ 29 | | X | | Y | 30 | | | | | | | 31 | | | | | | | 32 | | +--- Y | | X ---+ | 33 | | | | | 34 | |---------| |---------| 35 | | USB PORT| | USB PORT| 36 | +---------+ +---------+ 37 | """ 38 | 39 | # evdev input file 40 | pen_file = '/dev/input/event0' 41 | touch_file = '/dev/input/event2' 42 | button_file = '/dev/input/event1' 43 | # struct parsing format for evdev events 44 | e_format = '2IHHi' 45 | e_sz = struct.calcsize(e_format) 46 | 47 | # stylus evdev settings (min, max, resolution) 48 | touch_x = ev(0, 20967, 100) # touchscreen X coordinate (ABS_MT_POSITION_X) 49 | touch_y = ev(0, 15725, 100) # touchscreen Y coordinate (ABS_MT_POSITION_Y) 50 | touch_pressure = ev(0, 4095, None) # touchscreen pressure (ABS_MT_PRESSURE) 51 | touch_major = ev(0, 255, None) # touch area major axis (ABS_MT_TOUCH_MAJOR) 52 | touch_minor = ev(0, 255, None) # touch area minor axis (ABS_MT_TOUCH_MINOR) 53 | touch_orient = ev(-127, 127, None) # touch orientation (ABS_MT_ORIENTATION) 54 | touch_slot = ev(0, 31, None) # tool slot ID (ABS_MT_SLOT) 55 | touch_tool = ev(0, 1, None) # tool type (ABS_MT_TOOL_TYPE) 56 | touch_trackid = ev(0, 65535, None) # tool tracking id (ABS_MT_TRACKING_ID) 57 | 58 | # pen evdev settings (min, max, resolution) 59 | pen_x = ev(0, 20967, 100) # pen X coordinate (ABS_X) 60 | pen_y = ev(0, 15725, 100) # pen Y coordinate (ABS_Y) 61 | pen_pressure = ev(0, 4095, None) # pen pressure (ABS_PRESSURE) 62 | pen_distance = ev(0, 255, None) # pen distance from screen (ABS_DISTANCE) 63 | pen_tilt_x = ev(-6400, 6400, 6400) # pen tilt angle (ABS_TILT_X) 64 | pen_tilt_y = ev(-6400, 6400, 6400) # pen tilt angle (ABS_TILT_Y) 65 | 66 | def __init__(self, client=None): 67 | self.client = client 68 | 69 | @property 70 | def pen(self): 71 | """(paramiko.ChannelFile) pen stream""" 72 | cmd = f'dd bs={self.e_sz} if={self.pen_file}' 73 | return self.client.exec_command(cmd, bufsize=self.e_sz, timeout=0)[1] 74 | 75 | @property 76 | def touch(self): 77 | """(paramiko.ChannelFile) touch stream""" 78 | cmd = f'dd bs={self.e_sz} if={self.touch_file}' 79 | return self.client.exec_command(cmd, bufsize=self.e_sz, timeout=0)[1] 80 | 81 | @property 82 | def button(self): 83 | """(paramiko.ChannelFile) button stream""" 84 | cmd = f'dd bs={self.e_sz} if={self.button_file}' 85 | return self.client.exec_command(cmd, bufsize=self.e_sz, timeout=0)[1] 86 | 87 | def remap(self, x, y, max_x, max_y, monitor_width, 88 | monitor_height, mode, orientation): 89 | """remap pen coordinates to screen coordinates 90 | 91 | TODO: consider rewriting this whole function as matrix transform 92 | """ 93 | 94 | if orientation == 'right': 95 | x, y = max_x - x, max_y - y 96 | if orientation == 'left': 97 | pass 98 | if orientation == 'top': 99 | x, y = max_y - y, x 100 | max_x, max_y = max_y, max_x 101 | if orientation == 'bottom': 102 | x, y = y, max_x - x 103 | max_x, max_y = max_y, max_x 104 | 105 | ratio_width, ratio_height = monitor_width / max_x, monitor_height / max_y 106 | 107 | if mode == 'fill': 108 | scaling_x = max(ratio_width, ratio_height) 109 | scaling_y = scaling_x 110 | elif mode == 'fit': 111 | scaling_x = min(ratio_width, ratio_height) 112 | scaling_y = scaling_x 113 | elif mode == 'stretch': 114 | scaling_x = ratio_width 115 | scaling_y = ratio_height 116 | else: 117 | raise NotImplementedError 118 | 119 | return ( 120 | scaling_x * (x - (max_x - monitor_width / scaling_x) / 2), 121 | scaling_y * (y - (max_y - monitor_height / scaling_y) / 2) 122 | ) 123 | 124 | class reMarkable2(reMarkable1): 125 | pen_file = '/dev/input/event1' 126 | touch_file = '/dev/input/event2' 127 | button_file = '/dev/input/event0' 128 | 129 | class reMarkablePro(reMarkable1): 130 | r""" 131 | rMPro COORDINATES 132 | 133 | PEN TOUCH 134 | +--------+ +--------+ 135 | | +---X | | +---X | 136 | | | | | | | 137 | | | | | | | 138 | | Y | | Y | 139 | | | | | 140 | |--------| |--------| 141 | |USB PORT| |USB PORT| 142 | +--------+ +--------+ 143 | """ 144 | 145 | pen_file = '/dev/input/event2' 146 | touch_file = '/dev/input/event3' 147 | button_file = '/dev/input/event0' 148 | e_format = 'I4xI4xHHi' 149 | e_sz = struct.calcsize(e_format) 150 | # stylus evdev settings (min, max, resolution) 151 | touch_x = ev(0, 2064, 2064) # touchscreen X coordinate (ABS_MT_POSITION_X) 152 | touch_y = ev(0, 2832, 2832) # touchscreen Y coordinate (ABS_MT_POSITION_Y) 153 | touch_pressure = ev(0, 255, None) # touchscreen pressure (ABS_MT_PRESSURE) 154 | touch_orient = ev(-127, 127, None) # touch orientation (ABS_MT_ORIENTATION) 155 | touch_slot = ev(0, 9, None) # tool slot ID (ABS_MT_SLOT) 156 | touch_tool = ev(0, 2, None) # tool type (ABS_MT_TOOL_TYPE) 157 | 158 | # pen evdev settings (min, max, resolution) 159 | pen_x = ev(0, 11180, 2832) # pen X coordinate (ABS_X) 160 | pen_y = ev(0, 15340, 2064) # pen Y coordinate (ABS_Y) 161 | pen_pressure = ev(0, 4096, None) # pen pressure (ABS_PRESSURE) 162 | pen_distance = ev(0, 65535, None) # pen distance from screen (ABS_DISTANCE) 163 | pen_tilt_x = ev(-9000, 9000, None) # pen tilt angle (ABS_TILT_X) 164 | pen_tilt_y = ev(-9000, 9000, None) # pen tilt angle (ABS_TILT_Y) 165 | 166 | def remap(self, x, y, max_x, max_y, monitor_width, 167 | monitor_height, mode, orientation): 168 | """remap pen coordinates to screen coordinates 169 | 170 | TODO: consider rewriting this whole function as matrix transform 171 | """ 172 | if orientation == 'bottom': 173 | pass 174 | if orientation == 'right': 175 | x, y = y, max_x - x 176 | max_x, max_y = max_y, max_x 177 | if orientation == 'left': 178 | x, y = max_y - y, x 179 | max_x, max_y = max_y, max_x 180 | if orientation == 'top': 181 | x, y = max_x - x, max_y - y 182 | 183 | ratio_width, ratio_height = monitor_width / max_x, monitor_height / max_y 184 | 185 | if mode == 'fill': 186 | scaling_x = max(ratio_width, ratio_height) 187 | scaling_y = scaling_x 188 | elif mode == 'fit': 189 | scaling_x = min(ratio_width, ratio_height) 190 | scaling_y = scaling_x 191 | elif mode == 'stretch': 192 | scaling_x = ratio_width 193 | scaling_y = ratio_height 194 | else: 195 | raise NotImplementedError 196 | 197 | return ( 198 | scaling_x * (x - (max_x - monitor_width / scaling_x) / 2), 199 | scaling_y * (y - (max_y - monitor_height / scaling_y) / 2) 200 | ) 201 | 202 | 203 | def get_monitor(region, monitor_num, orientation): 204 | """ Get info of where we want to map the tablet to 205 | 206 | Args: 207 | region (boolean): whether to prompt the user to select a region 208 | monitor_num (int): index of monitor to use. Implies region=False 209 | orientation (str): Location of tablet charging port. 210 | ('top', 'bottom', 'left', 'right') 211 | 212 | Returns: 213 | screeninfo.Monitor 214 | (width, height): total size of all screens put together 215 | """ 216 | 217 | # compute size of box encompassing all screens 218 | max_x, max_y = 0, 0 219 | for m in get_monitors(): 220 | x = m.x + m.width 221 | y = m.y + m.height 222 | max_x = max(x, max_x) 223 | max_y = max(y, max_y) 224 | 225 | if region: 226 | x, y, width, height = get_region(orientation) 227 | monitor = Monitor( 228 | x, y, width, height, 229 | name="Fake monitor from region selection" 230 | ) 231 | else: 232 | try: 233 | monitor = get_monitors()[monitor_num] 234 | except IndexError: 235 | log.error(f"Monitor {monitor_num} not found. Only {len(get_monitors())} detected.") 236 | 237 | log.debug(f"Chose monitor: {monitor}") 238 | log.debug(f"Screen size: ({max_x}, {max_y})") 239 | return monitor, (max_x, max_y) 240 | 241 | def get_region(orientation): 242 | """ Show tkwindow to user to select mouse bounds 243 | 244 | Args: 245 | orientation (str): Location of tablet charging port. 246 | ('top', 'bottom', 'left', 'right') 247 | 248 | Returns: 249 | x (int), y (int), width (int), height (int) 250 | """ 251 | 252 | try: 253 | import tkinter as tk 254 | from tkinter import ttk 255 | except ImportError: 256 | print( 257 | "Unable to import tkinter; please follow the instructions at https://tkdocs.com/tutorial/install.html to install it") 258 | sys.exit(1) 259 | 260 | window = tk.Tk() 261 | 262 | # A bit of an ugly hack to get this function to run synchronously 263 | # Ideally would use full async support, but this solution required minimal changes to rest of code 264 | window_bounds = None 265 | 266 | def on_click(): 267 | nonlocal window_bounds 268 | window_bounds = ( 269 | window.winfo_x(), window.winfo_y(), 270 | window.winfo_width(), window.winfo_height() 271 | ) 272 | window.destroy() 273 | 274 | confirm = ttk.Button( 275 | window, 276 | text="Resize and move this window, then click or press Enter", 277 | command=on_click 278 | ) 279 | confirm.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.E, tk.W)) 280 | 281 | window.bind('', lambda _: on_click()) 282 | 283 | window.columnconfigure(0, weight=1) 284 | window.rowconfigure(0, weight=1) 285 | 286 | window.attributes('-alpha', 0.5) 287 | window.title("Remarkable Mouse") 288 | 289 | if orientation == 'bottom' or orientation == 'top': 290 | window.geometry("702x936") 291 | else: 292 | window.geometry("936x702") 293 | 294 | # block here 295 | window.mainloop() 296 | 297 | if window_bounds is None: 298 | log.debug("Window closed without giving mouse range") 299 | sys.exit(1) 300 | 301 | return window_bounds 302 | 303 | 304 | 305 | # log evdev event to console 306 | def log_event(e_time, e_millis, e_type, e_code, e_value): 307 | log.debug('{}.{:0>6} - {: <9} {: <15} {: >6}'.format( 308 | e_time, 309 | e_millis, 310 | types[e_type], 311 | codes[e_type][e_code], 312 | e_value 313 | )) 314 | -------------------------------------------------------------------------------- /remarkable_mouse/evdev.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import struct 3 | import subprocess 4 | from screeninfo import get_monitors 5 | import time 6 | from itertools import cycle 7 | from socket import timeout as TimeoutError 8 | import libevdev 9 | 10 | from .codes import codes, types 11 | from .common import get_monitor, log_event 12 | 13 | logging.basicConfig(format='%(message)s') 14 | log = logging.getLogger('remouse') 15 | 16 | def create_local_device(rm): 17 | """ 18 | Create a virtual input device on this host that has the same 19 | characteristics as a Wacom tablet. 20 | 21 | Returns: 22 | virtual input device 23 | """ 24 | import libevdev 25 | device = libevdev.Device() 26 | 27 | # Set device properties to emulate those of Wacom tablets 28 | device.name = 'reMarkable pen' 29 | 30 | device.id = { 31 | 'bustype': 0x03, # usb 32 | 'vendor': 0x056a, # wacom 33 | 'product': 0, 34 | 'version': 54 35 | } 36 | 37 | # Enable buttons supported by the digitizer 38 | device.enable(libevdev.EV_KEY.BTN_TOOL_PEN) 39 | device.enable(libevdev.EV_KEY.BTN_TOOL_RUBBER) 40 | device.enable(libevdev.EV_KEY.BTN_TOUCH) 41 | device.enable(libevdev.EV_KEY.BTN_STYLUS) 42 | device.enable(libevdev.EV_KEY.BTN_STYLUS2) 43 | device.enable(libevdev.EV_KEY.BTN_0) 44 | device.enable(libevdev.EV_KEY.BTN_1) 45 | device.enable(libevdev.EV_KEY.BTN_2) 46 | 47 | inputs = ( 48 | # touch inputs 49 | (libevdev.EV_ABS.ABS_MT_POSITION_X, *rm.touch_x), 50 | (libevdev.EV_ABS.ABS_MT_POSITION_Y, *rm.touch_y), 51 | (libevdev.EV_ABS.ABS_MT_PRESSURE, *rm.touch_pressure), 52 | (libevdev.EV_ABS.ABS_MT_TOUCH_MAJOR, *rm.touch_major), 53 | (libevdev.EV_ABS.ABS_MT_TOUCH_MINOR, *rm.touch_minor), 54 | (libevdev.EV_ABS.ABS_MT_ORIENTATION, *rm.touch_orient), 55 | (libevdev.EV_ABS.ABS_MT_SLOT, *rm.touch_slot), 56 | (libevdev.EV_ABS.ABS_MT_TOOL_TYPE, *rm.touch_tool), 57 | (libevdev.EV_ABS.ABS_MT_TRACKING_ID, *rm.touch_trackid), 58 | 59 | # pen inputs 60 | (libevdev.EV_ABS.ABS_X, *rm.pen_x), # cyttps5_mt driver 61 | (libevdev.EV_ABS.ABS_Y, *rm.pen_y), # cyttsp5_mt 62 | (libevdev.EV_ABS.ABS_PRESSURE, *rm.pen_pressure), 63 | (libevdev.EV_ABS.ABS_DISTANCE, *rm.pen_distance), 64 | (libevdev.EV_ABS.ABS_TILT_X, *rm.pen_tilt_x), 65 | (libevdev.EV_ABS.ABS_TILT_Y, *rm.pen_tilt_y) 66 | ) 67 | 68 | for code, minimum, maximum, resolution in inputs: 69 | device.enable( 70 | code, 71 | libevdev.InputAbsInfo( 72 | minimum=minimum, maximum=maximum, resolution=resolution 73 | ) 74 | ) 75 | 76 | return device.create_uinput_device() 77 | 78 | 79 | def read_tablet(rm, *, orientation, monitor_num, region, threshold, mode): 80 | """Pipe rM evdev events to local device 81 | 82 | Args: 83 | rm (reMarkable): tablet settings and input streams 84 | orientation (str): tablet orientation 85 | monitor_num (int): monitor number to map to 86 | threshold (int): pressure threshold 87 | mode (str): mapping mode 88 | """ 89 | 90 | local_device = create_local_device(rm) 91 | log.debug("Created virtual input device '{}'".format(local_device.devnode)) 92 | 93 | monitor, (tot_width, tot_height) = get_monitor(region, monitor_num, orientation) 94 | 95 | pending_events = [] 96 | 97 | x = y = 0 98 | 99 | stream = rm.pen 100 | while True: 101 | try: 102 | # read evdev events from file stream 103 | data = stream.read(struct.calcsize(rm.e_format)) 104 | except TimeoutError: 105 | continue 106 | 107 | # parse evdev events 108 | e_time, e_millis, e_type, e_code, e_value = struct.unpack(rm.e_format, data) 109 | 110 | if log.level == logging.DEBUG: 111 | log_event(e_time, e_millis, e_type, e_code, e_value) 112 | 113 | try: 114 | # intercept EV_ABS events and modify coordinates 115 | if types[e_type] == 'EV_ABS': 116 | # handle x direction 117 | if codes[e_type][e_code] == 'ABS_X': 118 | x = e_value 119 | 120 | # handle y direction 121 | if codes[e_type][e_code] == 'ABS_Y': 122 | y = e_value 123 | 124 | # map to screen coordinates so that region/monitor/orientation options are applied 125 | mapped_x, mapped_y = rm.remap( 126 | x, y, 127 | rm.pen_x.max, rm.pen_y.max, 128 | monitor.width, monitor.height, 129 | mode, orientation 130 | ) 131 | 132 | mapped_x += monitor.x 133 | mapped_y += monitor.y 134 | 135 | # map back to wacom coordinates to reinsert into event 136 | mapped_x = mapped_x * rm.pen_x.max / tot_width 137 | mapped_y = mapped_y * rm.pen_y.max / tot_height 138 | 139 | # reinsert modified values into evdev event 140 | if codes[e_type][e_code] == 'ABS_X': 141 | e_value = int(mapped_x) 142 | if codes[e_type][e_code] == 'ABS_Y': 143 | e_value = int(mapped_y) 144 | 145 | except KeyError as e: 146 | log.debug(f"Invalid evdev event: type:{e_type} code:{e_code}") 147 | 148 | # pass events directly to libevdev 149 | e_bit = libevdev.evbit(e_type, e_code) 150 | e = libevdev.InputEvent(e_bit, value=e_value) 151 | local_device.send_events([e]) 152 | 153 | -------------------------------------------------------------------------------- /remarkable_mouse/generate_codes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Generate file of evdev codes from libevdev that is importable on OSX/Windows. 4 | # Only runs on Linux. 5 | 6 | import libevdev 7 | import pprint 8 | pp = pprint.PrettyPrinter() 9 | 10 | types = {} 11 | codes = {} 12 | 13 | for t in libevdev.types: 14 | types[t.value] = t.name 15 | codes[t.value] = {} 16 | for c in t.codes: 17 | codes[t.value][c.value] = c.name 18 | 19 | with open('codes.py', 'w') as f: 20 | f.write('# generated by generate_codes.py\n') 21 | f.write('\ntypes= ') 22 | f.write(pp.pformat(types)) 23 | f.write('\ncodes = ') 24 | f.write(pp.pformat(codes)) 25 | -------------------------------------------------------------------------------- /remarkable_mouse/notes.md: -------------------------------------------------------------------------------- 1 | # Coordinate Notes 2 | 3 | 4 | ``` 5 | Coordinate Systems 6 | ------------------ 7 | 8 | reMarkable (pen) 9 | 10 | +---------+ 11 | | X | 12 | | | | 13 | | | | 14 | | +--- Y | 15 | | | 16 | |---------| 17 | |o o o| 18 | +---------+ 19 | 20 | 21 | pynput output 22 | 23 | +--------------+ 24 | | +----- X | 25 | | | | 26 | | | | 27 | | Y | 28 | +--------------+ 29 | | 30 | ------- 31 | 32 | 33 | evdev output 34 | 35 | +--------------+ 36 | | +----- X | 37 | | | | 38 | | | | 39 | | Y | 40 | +--------------+ 41 | | 42 | ------- 43 | ``` 44 | 45 | -------------------------------------------------------------------------------- /remarkable_mouse/pynput.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import struct 3 | from screeninfo import get_monitors 4 | 5 | # from .codes import EV_SYN, EV_ABS, ABS_X, ABS_Y, BTN_TOUCH 6 | from .codes import codes 7 | from .common import get_monitor, log_event 8 | 9 | logging.basicConfig(format='%(message)s') 10 | log = logging.getLogger('remouse') 11 | 12 | # wacom digitizer dimensions 13 | # touchscreen dimensions 14 | # finger_width = 767 15 | # finger_height = 1023 16 | 17 | def read_tablet(rm, *, orientation, monitor_num, region, threshold, mode): 18 | """Loop forever and map evdev events to mouse 19 | 20 | Args: 21 | rm (reMarkable): tablet settings and input streams 22 | orientation (str): tablet orientation 23 | monitor_num (int): monitor number to map to 24 | region (boolean): whether to selection mapping region with region tool 25 | threshold (int): pressure threshold 26 | mode (str): mapping mode 27 | """ 28 | 29 | from pynput.mouse import Button, Controller 30 | 31 | mouse = Controller() 32 | 33 | monitor, _ = get_monitor(region, monitor_num, orientation) 34 | log.debug('Chose monitor: {}'.format(monitor)) 35 | 36 | x = y = 0 37 | 38 | stream = rm.pen 39 | while True: 40 | try: 41 | # read evdev events from file stream 42 | data = stream.read(struct.calcsize(rm.e_format)) 43 | except TimeoutError: 44 | continue 45 | 46 | # parse evdev events 47 | e_time, e_millis, e_type, e_code, e_value = struct.unpack(rm.e_format, data) 48 | 49 | if log.level == logging.DEBUG: 50 | log_event(e_time, e_millis, e_type, e_code, e_value) 51 | 52 | try: 53 | # handle x direction 54 | if codes[e_type][e_code] == 'ABS_X': 55 | x = e_value 56 | 57 | # handle y direction 58 | if codes[e_type][e_code] == 'ABS_Y': 59 | y = e_value 60 | 61 | # handle draw 62 | if codes[e_type][e_code] == 'BTN_TOUCH': 63 | if e_value == 1: 64 | mouse.press(Button.left) 65 | else: 66 | mouse.release(Button.left) 67 | 68 | if codes[e_type][e_code] == 'SYN_REPORT': 69 | mapped_x, mapped_y = rm.remap( 70 | x, y, 71 | rm.pen_x.max, rm.pen_y.max, 72 | monitor.width, monitor.height, 73 | mode, orientation, 74 | ) 75 | mouse.move( 76 | monitor.x + mapped_x - mouse.position[0], 77 | monitor.y + mapped_y - mouse.position[1] 78 | ) 79 | except KeyError as e: 80 | log.debug(f"Invalid evdev event: type:{e_type} code:{e_code}") 81 | -------------------------------------------------------------------------------- /remarkable_mouse/remarkable_mouse.py: -------------------------------------------------------------------------------- 1 | # Evan Widloski - 2019-02-23 2 | # Use reMarkable as mouse input 3 | 4 | import argparse 5 | import logging 6 | import os 7 | import sys 8 | import struct 9 | from getpass import getpass 10 | from itertools import cycle 11 | 12 | import paramiko 13 | import paramiko.agent 14 | import paramiko.config 15 | 16 | from .common import reMarkable1, reMarkable2, reMarkablePro 17 | 18 | logging.basicConfig(format='%(message)s') 19 | log = logging.getLogger('remouse') 20 | 21 | default_key = os.path.expanduser('~/.ssh/remarkable') 22 | config_path = os.path.expanduser('~/.ssh/config') 23 | 24 | 25 | def connect_rm(*, address, key, password): 26 | """ 27 | Open a remote input device via SSH. 28 | 29 | Args: 30 | address (str): address to reMarkable 31 | key (str, optional): path to reMarkable ssh key 32 | password (str, optional): reMarkable ssh password 33 | Returns: 34 | (paramiko.ChannelFile): read-only stream of pen events 35 | (paramiko.ChannelFile): read-only stream of touch events 36 | (paramiko.ChannelFile): read-only stream of button events 37 | """ 38 | log.debug("Connecting to input '{}'".format(address)) 39 | 40 | client = paramiko.SSHClient() 41 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 42 | 43 | pkey = None 44 | 45 | agent = paramiko.agent.Agent() 46 | 47 | # lookup "remarkable" in ssh config 48 | config_entry = {} 49 | if os.path.exists(config_path): 50 | config = paramiko.config.SSHConfig.from_path(config_path) 51 | config_entry = config.lookup('remarkable') 52 | 53 | # open key at provided path 54 | def use_key(key): 55 | for key_type in [paramiko.RSAKey, paramiko.Ed25519Key, paramiko.ECDSAKey]: 56 | try: 57 | pkey = key_type.from_private_key_file(os.path.expanduser(key)) 58 | break 59 | except paramiko.ssh_exception.PasswordRequiredException: 60 | passphrase = getpass( 61 | "Enter passphrase for key '{}': ".format(os.path.expanduser(key)) 62 | ) 63 | # try to read the file again, this time with the password 64 | for key_type in [paramiko.RSAKey, paramiko.Ed25519Key, paramiko.ECDSAKey]: 65 | try: 66 | pkey = key_type.from_private_key_file(os.path.expanduser(key), password=passphrase) 67 | break 68 | except paramiko.ssh_exception.SSHException: 69 | continue 70 | break 71 | except paramiko.ssh_exception.SSHException: 72 | continue 73 | return pkey 74 | 75 | # use provided key 76 | if key is not None: 77 | password = None 78 | pkey = use_key(key) 79 | # fallback to "remarkable" entry in ssh config 80 | elif 'identityfile' in config_entry and len(config_entry['identityfile']) > 0: 81 | password = None 82 | pkey = use_key(config_entry['identityfile'][0]) 83 | # fallback to user-provided password 84 | elif password is not None: 85 | pkey = None 86 | # fallback to default pubkey location 87 | elif os.path.exists(default_key): 88 | password = None 89 | pkey = use_key(default_key) 90 | # finally prompt user for password 91 | elif not agent.get_keys(): 92 | password = getpass( 93 | "Password for '{}': ".format(address) 94 | ) 95 | pkey = None 96 | 97 | client.connect( 98 | address, 99 | username='root', 100 | password=password, 101 | pkey=pkey, 102 | look_for_keys=False, 103 | disabled_algorithms=dict(pubkeys=["rsa-sha2-512", "rsa-sha2-256"]) 104 | ) 105 | 106 | session = client.get_transport().open_session() 107 | 108 | paramiko.agent.AgentRequestHandler(session) 109 | 110 | pen_file = client.exec_command( 111 | 'readlink -f /dev/input/touchscreen0' 112 | )[1].read().decode('utf8').rstrip('\n') 113 | 114 | # detect reMarkable version 115 | # https://github.com/Eeems/oxide/issues/48#issuecomment-690830572 116 | if pen_file == '/dev/input/event0': 117 | # rM 1 118 | rm = reMarkable1(client) 119 | elif pen_file == '/dev/input/event1': 120 | # rM 2 121 | rm = reMarkable2(client) 122 | elif pen_file == '/dev/input/event2': 123 | # rM Pro 124 | rm = reMarkablePro(client) 125 | else: 126 | raise ValueError(f"Could not detect reMarkable version. {pen_file}") 127 | 128 | log.debug("Detected {type(rm).__name__}") 129 | log.debug(f'Pen:{rm.pen_file}\nTouch:{rm.touch_file}\nButton:{rm.button_file}') 130 | 131 | return rm 132 | 133 | def main(): 134 | try: 135 | parser = argparse.ArgumentParser(description="use reMarkable tablet as a mouse input") 136 | parser.add_argument('--debug', action='store_true', default=False, help="enable debug messages") 137 | parser.add_argument('--key', type=str, metavar='PATH', help="ssh private key") 138 | parser.add_argument('--password', default=None, type=str, help="ssh password") 139 | parser.add_argument('--address', default='10.11.99.1', type=str, help="device address") 140 | parser.add_argument('--mode', default='fill', choices=['fit', 'fill', 'stretch'], help="""Scale setting. 141 | Fit (default): take up the entire tablet, but not necessarily the entire monitor. 142 | Fill: take up the entire monitor, but not necessarily the entire tablet. 143 | Stretch: take up both the entire tablet and monitor, but don't maintain aspect ratio.""") 144 | parser.add_argument('--orientation', default='right', choices=['top', 'left', 'right', 'bottom'], help="position of tablet buttons") 145 | parser.add_argument('--monitor', default=0, type=int, metavar='NUM', help="monitor to output to") 146 | parser.add_argument('--region', action='store_true', default=False, help="Use a GUI to position the output area. Overrides --monitor") 147 | parser.add_argument('--threshold', metavar='THRESH', default=600, type=int, help="stylus pressure threshold (default 600)") 148 | parser.add_argument('--evdev', action='store_true', default=False, help="use evdev to support pen pressure (requires root, Linux only)") 149 | 150 | args = parser.parse_args() 151 | 152 | if args.debug: 153 | log.setLevel(logging.DEBUG) 154 | print('Debugging enabled...') 155 | else: 156 | log.setLevel(logging.INFO) 157 | 158 | # ----- Connect to device ----- 159 | 160 | rm = connect_rm( 161 | address=args.address, 162 | key=args.key, 163 | password=args.password, 164 | ) 165 | print("Connected to", args.address) 166 | 167 | # ----- Handle events ----- 168 | 169 | if args.evdev: 170 | from remarkable_mouse.evdev import read_tablet 171 | 172 | else: 173 | from remarkable_mouse.pynput import read_tablet 174 | 175 | read_tablet( 176 | rm, 177 | orientation=args.orientation, 178 | monitor_num=args.monitor, 179 | region=args.region, 180 | threshold=args.threshold, 181 | mode=args.mode, 182 | ) 183 | 184 | except PermissionError: 185 | log.error('Insufficient permissions for creating a virtual input device') 186 | log.error('Make sure you run this program as root') 187 | sys.exit(1) 188 | except KeyboardInterrupt: 189 | pass 190 | except EOFError: 191 | pass 192 | 193 | if __name__ == '__main__': 194 | main() 195 | -------------------------------------------------------------------------------- /remarkable_mouse/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '7.1.1' 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from remarkable_mouse import version 3 | 4 | setup( 5 | name='remarkable-mouse', 6 | version=version.__version__, 7 | packages=['remarkable_mouse'], 8 | author="Evan Widloski", 9 | author_email="evan@evanw.org", 10 | description="use reMarkable as a graphics tablet", 11 | long_description=open('README.md').read(), 12 | long_description_content_type='text/markdown', 13 | license="GPLv3", 14 | keywords="remarkable tablet evdev", 15 | url="https://github.com/evidlo/remarkable_mouse", 16 | entry_points={ 17 | 'console_scripts': [ 18 | 'remarkable-mouse = remarkable_mouse.remarkable_mouse:main', 19 | 'remouse = remarkable_mouse.remarkable_mouse:main' 20 | ] 21 | }, 22 | install_requires=[ 23 | 'paramiko', 24 | 'libevdev', 25 | 'pynput', 26 | 'screeninfo' 27 | ], 28 | classifiers=[ 29 | "Programming Language :: Python :: 3", 30 | ] 31 | ) 32 | --------------------------------------------------------------------------------