├── .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 |
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 |
--------------------------------------------------------------------------------